Compare commits

...

8 Commits

26 changed files with 991 additions and 523 deletions

145
package-lock.json generated
View File

@ -26,6 +26,7 @@
"react-draggable": "^4.4.6", "react-draggable": "^4.4.6",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-id-generator": "^3.0.2", "react-id-generator": "^3.0.2",
"react-joyride": "^2.9.3",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"react-router-dom": "^7.2.0", "react-router-dom": "^7.2.0",
"react-sortablejs": "^6.1.4", "react-sortablejs": "^6.1.4",
@ -921,6 +922,12 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@gilbarbara/deep-equal": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.3.1.tgz",
"integrity": "sha512-I7xWjLs2YSVMc5gGx1Z3ZG1lgFpITPndpi8Ku55GeEIKpACCPQNS/OTqQbxgTCfq0Ncvcc+CrFov96itVh6Qvw==",
"license": "MIT"
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@ -2658,6 +2665,12 @@
} }
} }
}, },
"node_modules/deep-diff": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz",
"integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==",
"license": "MIT"
},
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -2665,6 +2678,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -3438,6 +3460,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-lite": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-lite/-/is-lite-1.2.1.tgz",
"integrity": "sha512-pgF+L5bxC+10hLBgf6R2P4ZZUBOQIIacbdo8YvuCP8/JvsWxG7aZ9p10DYuLtifFci4l3VITphhMlMV4Y+urPw==",
"license": "MIT"
},
"node_modules/is-number": { "node_modules/is-number": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@ -4163,6 +4191,17 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.6", "version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
@ -4323,12 +4362,84 @@
"react": ">= 16.8.0" "react": ">= 16.8.0"
} }
}, },
"node_modules/react-innertext": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/react-innertext/-/react-innertext-1.1.5.tgz",
"integrity": "sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==",
"license": "MIT",
"peerDependencies": {
"@types/react": ">=0.0.0 <=99",
"react": ">=0.0.0 <=99"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-joyride": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.9.3.tgz",
"integrity": "sha512-1+Mg34XK5zaqJ63eeBhqdbk7dlGCFp36FXwsEvgpjqrtyywX2C6h9vr3jgxP0bGHCw8Ilsp/nRDzNVq6HJ3rNw==",
"license": "MIT",
"dependencies": {
"@gilbarbara/deep-equal": "^0.3.1",
"deep-diff": "^1.0.2",
"deepmerge": "^4.3.1",
"is-lite": "^1.2.1",
"react-floater": "^0.7.9",
"react-innertext": "^1.1.5",
"react-is": "^16.13.1",
"scroll": "^3.0.1",
"scrollparent": "^2.1.0",
"tree-changes": "^0.11.2",
"type-fest": "^4.27.0"
},
"peerDependencies": {
"react": "15 - 18",
"react-dom": "15 - 18"
}
},
"node_modules/react-joyride/node_modules/react-floater": {
"version": "0.7.9",
"resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.9.tgz",
"integrity": "sha512-NXqyp9o8FAXOATOEo0ZpyaQ2KPb4cmPMXGWkx377QtJkIXHlHRAGer7ai0r0C1kG5gf+KJ6Gy+gdNIiosvSicg==",
"license": "MIT",
"dependencies": {
"deepmerge": "^4.3.1",
"is-lite": "^0.8.2",
"popper.js": "^1.16.0",
"prop-types": "^15.8.1",
"tree-changes": "^0.9.1"
},
"peerDependencies": {
"react": "15 - 18",
"react-dom": "15 - 18"
}
},
"node_modules/react-joyride/node_modules/react-floater/node_modules/@gilbarbara/deep-equal": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/@gilbarbara/deep-equal/-/deep-equal-0.1.2.tgz",
"integrity": "sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==",
"license": "MIT"
},
"node_modules/react-joyride/node_modules/react-floater/node_modules/is-lite": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.2.tgz",
"integrity": "sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==",
"license": "MIT"
},
"node_modules/react-joyride/node_modules/react-floater/node_modules/tree-changes": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.9.3.tgz",
"integrity": "sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==",
"license": "MIT",
"dependencies": {
"@gilbarbara/deep-equal": "^0.1.1",
"is-lite": "^0.8.2"
}
},
"node_modules/react-redux": { "node_modules/react-redux": {
"version": "9.2.0", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
@ -4623,6 +4734,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/scroll": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz",
"integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==",
"license": "MIT"
},
"node_modules/scrollparent": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.1.0.tgz",
"integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==",
"license": "ISC"
},
"node_modules/semver": { "node_modules/semver": {
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@ -4890,6 +5013,16 @@
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/tree-changes": {
"version": "0.11.3",
"resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.11.3.tgz",
"integrity": "sha512-r14mvDZ6tqz8PRQmlFKjhUVngu4VZ9d92ON3tp0EGpFBE6PAHOq8Bx8m8ahbNoGE3uI/npjYcJiqVydyOiYXag==",
"license": "MIT",
"dependencies": {
"@gilbarbara/deep-equal": "^0.3.1",
"is-lite": "^1.2.1"
}
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@ -4934,6 +5067,18 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.7.3", "version": "5.7.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",

View File

@ -29,6 +29,7 @@
"react-draggable": "^4.4.6", "react-draggable": "^4.4.6",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-id-generator": "^3.0.2", "react-id-generator": "^3.0.2",
"react-joyride": "^2.9.3",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"react-router-dom": "^7.2.0", "react-router-dom": "^7.2.0",
"react-sortablejs": "^6.1.4", "react-sortablejs": "^6.1.4",

View File

@ -1,4 +1,4 @@
import { BrowserRouter, Route, Routes } from "react-router-dom" import { BrowserRouter, Route, Routes, useLocation } from "react-router-dom"
import Dashboard from "./components/dashboard/Dashboard" import Dashboard from "./components/dashboard/Dashboard"
import Home from "./components/home/Home" import Home from "./components/home/Home"
import Login from "./components/login/Login"; import Login from "./components/login/Login";
@ -20,83 +20,93 @@ import Account from "./components/accounts/Account";
import TransactionView from "./components/transactions/TransactionView"; import TransactionView from "./components/transactions/TransactionView";
import Templates from "./components/templates/Templates"; import Templates from "./components/templates/Templates";
import DocumentCompletionScreen from "./components/process/CompletionScreen/DocumentCompletionScreen"; import DocumentCompletionScreen from "./components/process/CompletionScreen/DocumentCompletionScreen";
import JoyrideController from "./utlity/JoyRideTour";
import Contacts from "./components/contacts/Contacts"; import Contacts from "./components/contacts/Contacts";
const App = () => { const App = () => {
return ( return (
<BrowserRouter> <>
{/* <HashRouter> */} <BrowserRouter>
<ToastContainer />
<Routes> {/* <HashRouter> */}
<ToastContainer />
<Route element={ <Routes>
<ProtectedRoute>
<Dashboard /> <Route element={
</ProtectedRoute> <ProtectedRoute>
}> <Dashboard />
<Route path="/" element={<Home />} /> </ProtectedRoute>
{/* <Route path="/s/settings" element={<Setting />} /> */} }>
{/* <Route element={<AccountTransactionWrapper />}> */} <Route path="/" element={<Home />} />
{/* <Route path="/s/settings" element={<Setting />} /> */}
{/* <Route element={<AccountTransactionWrapper />}> */}
<Route path="/accounts" element={<Accounts />} /> <Route path="/accounts" element={<Accounts />} />
<Route path="/accounts/:accountId/transactions" element={<Account />} /> <Route path="/accounts/:accountId/transactions" element={<Account />} />
<Route path="/transactions" element={<Account />} /> <Route path="/transactions" element={<Account />} />
<Route path="/accounts/:accountId/transactions/:transactionId" element={<TransactionView />} /> <Route path="/accounts/:accountId/transactions/:transactionId" element={<TransactionView />} />
{/* <Route path="/transactions/:filterParam" element={<Transactions />} /> */} {/* <Route path="/transactions/:filterParam" element={<Transactions />} /> */}
<Route path="/templates" element={<Templates />} /> <Route path="/templates" element={<Templates />} />
{/* </Route> */}
<Route path="/contacts" element={<Contacts />} /> <Route path="/contacts" element={<Contacts />} />
{/* </Route> */}
<Route path="/s/:page1" element={<Setting />} /> <Route path="/s/:page1" element={<Setting />} />
</Route> </Route>
<Route path="/process-step-one/:accountId/:transactionId" element={ <Route path="/process-step-one/:accountId/:transactionId" element={
<ProtectedRoute> <ProtectedRoute>
<ProcessStepOne /> <ProcessStepOne />
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route path="/process-step-one/:accountId/:transactionId/:signType" element={ <Route path="/process-step-one/:accountId/:transactionId/:signType" element={
<ProtectedRoute> <ProtectedRoute>
<ProcessStepOne /> <ProcessStepOne />
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route path="/process-step-one/:accountId/template/:templateId" element={ <Route path="/process-step-one/:accountId/template/:templateId" element={
<ProtectedRoute> <ProtectedRoute>
<ProcessStepOne /> <ProcessStepOne />
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route path="/process-step-two/:accountId/template/:templateId" element={ <Route path="/process-step-two/:accountId/template/:templateId" element={
<ProtectedRoute> <ProtectedRoute>
<ProcessStepTwo /> <ProcessStepTwo />
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route path="/process-step-two/:accountId/:transactionId" element={ <Route path="/process-step-two/:accountId/:transactionId" element={
<ProtectedRoute> <ProtectedRoute>
<ProcessStepTwo /> <ProcessStepTwo />
</ProtectedRoute> </ProtectedRoute>
} /> } />
<Route path="/document-completion-screen/:accountId/:transactionId" element={ <Route path="/document-completion-screen/:accountId/:transactionId" element={
<ProtectedRoute> <ProtectedRoute>
<DocumentCompletionScreen /> <DocumentCompletionScreen />
</ProtectedRoute> </ProtectedRoute>
} /> } />
{/* Links work wothout Login */} {/* Links work wothout Login */}
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} /> <Route path="/register" element={<Register />} />
<Route path="/sign/:recipientId" element={<SignDocument />} /> <Route path="/sign/:recipientId" element={<SignDocument />} />
<Route path="/view" element={<DocView />} /> <Route path="/view" element={<DocView />} />
{/* Links work wothout Login */} {/* Links work wothout Login */}
</Routes> </Routes>
<JoyrideController />
{/* </HashRouter> */}
</BrowserRouter>
</>
{/* </HashRouter> */}
</BrowserRouter>
) )
} }

View File

@ -152,7 +152,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
if (item) { if (item) {
const userData = { const userData = {
id: item?.id, id: item?.id,
name : item?.firstName + " " + item?.lastName , name: item?.firstName + " " + item?.lastName,
email: item?.email, email: item?.email,
language: "", language: "",
account: { account: {
@ -162,7 +162,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
}, },
}; };
console.log("userData",userData) console.log("userData", userData)
setUser(userData); setUser(userData);
sessionStorage.setItem('userDetails', JSON.stringify(userData)); sessionStorage.setItem('userDetails', JSON.stringify(userData));
@ -295,6 +295,7 @@ const CommonNav: React.FC<AccountNavProps> = () => {
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="relative" ref={dropdownRef}> <div className="relative" ref={dropdownRef}>
<button <button
id='user-details-avatar'
className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors" className="flex items-center space-x-2 p-1 cursor-pointer rounded-lg transition-colors"
onClick={handleMainDropdownToggle} onClick={handleMainDropdownToggle}
aria-expanded={open} aria-expanded={open}

View File

@ -213,10 +213,6 @@ const Account = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const handleTransactionClick = (id: number) => {
navigate(`/accounts/${activeAccountId}/transactions/${id}`);
};
const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => { const handleInvite = async (data: { i_accountName: number; email: string; role: string }) => {
setInviteLoading(true); setInviteLoading(true);
try { try {

View File

@ -4,6 +4,9 @@ import api from '../../config/api';
import { Transaction } from '../transactions/Transactions'; import { Transaction } from '../transactions/Transactions';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { FaCheck } from 'react-icons/fa';
const formatDate = (timestamp: number) => { const formatDate = (timestamp: number) => {
@ -21,7 +24,7 @@ const TransactionDataTable = () => {
const fetchTransactions = async () => { const fetchTransactions = async () => {
console.log("fetchTransactions") console.log("fetchTransactions")
try { try {
const response = await api(`/api/accounts/${activeAccountId}/transactions`); const response = await api.get(`/api/accounts/${activeAccountId}/transactions?sortBy=lastModifiedAt desc`);
setTransactions(response.data || []); setTransactions(response.data || []);
} catch (error) { } catch (error) {
console.error("Error fetching transactions:", error); console.error("Error fetching transactions:", error);
@ -30,6 +33,10 @@ const TransactionDataTable = () => {
const [transactions, setTransactions] = useState<Transaction[]>([]); const [transactions, setTransactions] = useState<Transaction[]>([]);
const handleTransactionClick = (id: number) => {
navigate(`/accounts/${activeAccountId}/transactions/${id}`);
};
useEffect(() => { useEffect(() => {
if (activeAccountId) { if (activeAccountId) {
fetchTransactions(); fetchTransactions();
@ -42,9 +49,7 @@ const TransactionDataTable = () => {
}, []); }, []);
const handleTransactionClick = (transactionId) => {
navigate(`/accounts/${activeAccountId}/transactions/${transactionId}`)
}
return ( return (
<> <>

View File

@ -9,7 +9,7 @@ interface DocumentPagesInterface {
const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownTogglePage }: DocumentPagesInterface) => { const DocumentPages = ({ selectedDocuments, setDropDownTogglePage, dropDownTogglePage }: DocumentPagesInterface) => {
return ( return (
<div className='col-span-2 bg-white h-full p-4 overflow-y-auto'> <div className='bg-white h-full p-4 overflow-y-auto'>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{selectedDocuments && selectedDocuments.length && selectedDocuments.map((document, indx) => { {selectedDocuments && selectedDocuments.length && selectedDocuments.map((document, indx) => {

View File

@ -3,7 +3,7 @@ import { useEffect, useState } from "react";
import axios from "axios" import axios from "axios"
import { RootState } from "../../store/store"; import { RootState } from "../../store/store";
import { useParams } from "react-router-dom" import { useParams } from "react-router-dom"
import { DroppedItem, DroppedItemsObj } from "../../utlity/types"; import { DroppedItem, DroppedItemsObj, TagsTypes } from "../../utlity/types";
import Loader from "../dashboard/Loader"; import Loader from "../dashboard/Loader";
import SignCon from "../signature/SignCon"; import SignCon from "../signature/SignCon";
@ -172,6 +172,8 @@ const SignDocument = () => {
const [hideTags, setHideTags] = useState<boolean>(false); const [hideTags, setHideTags] = useState<boolean>(false);
const [transparentTags, setTransparentTags] = useState<boolean>(false); const [transparentTags, setTransparentTags] = useState<boolean>(false);
const [provideTags, setProvideTags] = useState(false)
const [saved, setSaved] = useState<SaveType>({ const [saved, setSaved] = useState<SaveType>({
saveType: null, saveType: null,
save: false, save: false,
@ -308,6 +310,10 @@ const SignDocument = () => {
setTransaction(res.data.transaction) setTransaction(res.data.transaction)
setRecSignStatus(res.data.status) setRecSignStatus(res.data.status)
if ("provideOwnTags" in res.data.details && res.data.details.provideOwnTags) {
setProvideTags(true)
}
setTimeout(() => { setTimeout(() => {
dispatch(hideLoader()); dispatch(hideLoader());
}, 600); }, 600);
@ -850,7 +856,9 @@ const SignDocument = () => {
{(!transparentTags && !hideTags) ? <button {(!transparentTags && !hideTags) ? <button
// onClick={() => handleFormSubmitBtnClick()} // onClick={() => handleFormSubmitBtnClick()}
className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)] outline-none'> className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)] outline-none'>
<p className='tracking-wide text-white'> <p
id="sign-client-side-btn"
className='tracking-wide text-white'>
Sign Sign
</p> </p>
<FaSignature className='text-white text-md' /> <FaSignature className='text-white text-md' />
@ -870,9 +878,10 @@ const SignDocument = () => {
{/* Left Side pages start */} {/* Left Side pages start */}
{/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */} {/* <div className="lg:col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"></div> */}
<div
<div className='col-span-2 bg-white h-full p-4 overflow-y-auto'> id="tags-list-client-side"
{recipientId && <FieldList color={"#8f87f1"} recipientId={Number(recipientId)} recipientIndx={0} acntId={"1"} />} className={`col-span-2 h-full p-4 overflow-y-auto ${provideTags ? "bg-white" : "order-last"}`}>
{recipientId && provideTags && <FieldList color={"#8f87f1"} recipientId={Number(recipientId)} recipientIndx={0} acntId={"1"} />}
</div> </div>
@ -1188,6 +1197,31 @@ const SignDocument = () => {
{/* TEMPORAL FIELD - END */} {/* TEMPORAL FIELD - END */}
{/* Attachment Field - START */}
{item.type.toLowerCase() === TagsTypes.ATTACHMENT &&
<div
tabIndex={1}
id={item.id}
// onClick={() => handleStampClick(index)}
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
// style={{ height: item.dimensions.height + "px" }}
>
{!transparentTags && !hideTags ?
(
<p className="text-lg text-slate-600">
{typeToIconMap.ATTACHMENT}
</p>
)
:
(
<p className="text-lg text-slate-600">
{typeToIconMap.ATTACHMENT}
</p>
)
}
</div>}
{/* Attachment Field - END */}
{/* Focus Show Element */} {/* Focus Show Element */}
@ -1257,9 +1291,12 @@ const SignDocument = () => {
</div> </div>
<div className={`col-span-2 ${provideTags ? "" : "order-first"}`}>
<DocumentPages selectedDocuments={selectedDocuments} setDropDownTogglePage={setDropDownTogglePage} dropDownTogglePage={dropDownTogglePage} /> <DocumentPages
selectedDocuments={selectedDocuments}
setDropDownTogglePage={setDropDownTogglePage}
dropDownTogglePage={dropDownTogglePage} />
</div>
{/* Right Side - START */} {/* Right Side - START */}
{/* <div className="col-span-2 bg-slate-100 h-full p-4 overflow-y-auto"> {/* <div className="col-span-2 bg-slate-100 h-full p-4 overflow-y-auto">

View File

@ -76,7 +76,9 @@ const SendForSign: React.FC<Props> = ({ showSideBar }) => {
// } // }
return ( return (
<div className='relative max-h-max pb-3' <div
id='send-for-sign-self-sign-btn'
className='relative max-h-max pb-3'
tabIndex={0} tabIndex={0}
ref={dropDownRef} ref={dropDownRef}
onBlur={() => setSendForSignDropdown(false)} onBlur={() => setSendForSignDropdown(false)}

View File

@ -60,7 +60,9 @@ const SideBar: React.FC<SideBarProps> = ({ showSideBar, setShowSideBar }) => {
{showSideBar ? 'Sign Made Easy' : 'SME'} {showSideBar ? 'Sign Made Easy' : 'SME'}
</h1> </h1>
<div className={`flex flex-col mt-4 ${showSideBar ? '' : 'items-center'}`}> <div
id="nav-link-dash"
className={`flex flex-col mt-4 ${showSideBar ? '' : 'items-center'}`}>
<SendForSign showSideBar={showSideBar} /> <SendForSign showSideBar={showSideBar} />
<NavLink to="/" icon={<FaHome />} label="Home" showSideBar={showSideBar} menuPathMap={menuPathMap} /> <NavLink to="/" icon={<FaHome />} label="Home" showSideBar={showSideBar} menuPathMap={menuPathMap} />
<NavLink to="/templates" icon={<HiTemplate />} label="Templates" showSideBar={showSideBar} menuPathMap={menuPathMap} /> <NavLink to="/templates" icon={<HiTemplate />} label="Templates" showSideBar={showSideBar} menuPathMap={menuPathMap} />

View File

@ -49,6 +49,7 @@ const StartNow = () => {
<> <>
<div <div
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
id='start-now-upload-con'
className="group cursor-pointer border-slate-300 rounded-lg bg-white/60 transition-all duration-200 p-4 flex flex-col items-center justify-center gap-2 text-center shadow-sm hover:shadow-md"> className="group cursor-pointer border-slate-300 rounded-lg bg-white/60 transition-all duration-200 p-4 flex flex-col items-center justify-center gap-2 text-center shadow-sm hover:shadow-md">
{/* Upload icon with subtle animation */} {/* Upload icon with subtle animation */}
<div className="p-2 text-blue-600 group-hover:text-blue-700 transition-colors"> <div className="p-2 text-blue-600 group-hover:text-blue-700 transition-colors">

View File

@ -65,10 +65,29 @@ const ProcessStepOne = () => {
console.log("selectedRecipients", selectedRecipients) console.log("selectedRecipients", selectedRecipients)
useEffect(() => { useEffect(() => {
// <<<<<<< HEAD
// Fetching Transaction Details - START
(async () => { (async () => {
const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`) try {
console.log(res.data); const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`);
setTransactionDetails(res.data)
} catch (e) {
console.log("Error - transactions details")
}
})() })()
// Fetching Transaction Details - END
// =======
// (async () => {
// const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`)
// console.log(res.data);
// })()
// >>>>>>> origin/main
// (async () => {
// const res = await api.get(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`)
// console.log(res.data);
// })()
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId })); selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId })); selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
@ -297,12 +316,14 @@ const ProcessStepOne = () => {
<p className="text-base font-normal text-gray-700 border-l pl-4">Add Recipient and Message</p> <p className="text-base font-normal text-gray-700 border-l pl-4">Add Recipient and Message</p>
</div> </div>
<div className='flex items-center gap-3'> <div
id="add-tags-btn-step-one"
className='flex items-center gap-3'>
<button <button
onClick={() => handleNextClick(false)} onClick={() => handleNextClick(false)}
className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition" className="text-sm px-4 py-2 rounded bg-[var(--primary)] text-white shadow hover:bg-[var(--primaryLight2)] transition"
> >
Add Tags {signType === SignTypeEnum.selfSign ? "Sign" : "Next"}
</button> </button>
</div> </div>
</div> </div>

View File

@ -272,6 +272,7 @@ useEffect(() => {
<form <form
ref={formRef} ref={formRef}
onSubmit={handleRecipientAdd} onSubmit={handleRecipientAdd}
id="add-recipient-step-one"
className="w-full bg-white border border-gray-200 rounded-lg shadow-md px-6 py-4 space-y-3 relative" className="w-full bg-white border border-gray-200 rounded-lg shadow-md px-6 py-4 space-y-3 relative"
> >
{transactionTemplateToggle === 'templates' && ( {transactionTemplateToggle === 'templates' && (

View File

@ -59,6 +59,15 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
const handleRemoveRecipient = (recipient) => { const handleRemoveRecipient = (recipient) => {
if (!accountId || !transactionId) return; if (!accountId || !transactionId) return;
// <<<<<<< HEAD
// dispatch(removeSelectedRecipient(recipient))
// dispatch(removeRecipientAsync([{
// recipientId: recipient.id,
// email: recipient.email,
// accountId: accountId,
// transactionId: transactionId,
// }]))
// =======
if (recipient.uuid) { if (recipient.uuid) {
dispatch(removeSelectedRecipient(recipient)) dispatch(removeSelectedRecipient(recipient))
} else { } else {
@ -69,6 +78,7 @@ const RecipientCards = ({ setEditRecipient, setEditCase, transactionTemplateTogg
transactionId: transactionId, transactionId: transactionId,
})) }))
} }
// >>>>>>> origin/main
if (recipients.length === 2) { if (recipients.length === 2) {
let leftRecipient = recipients.filter((obj) => { let leftRecipient = recipients.filter((obj) => {
if ("id" in obj && "id" in recipient) { if ("id" in obj && "id" in recipient) {

View File

@ -87,7 +87,9 @@ const SelectedDocuments = ({transactionTemplateToggle}) => {
return ( return (
<> <>
{/* Upload Card First */} {/* Upload Card First */}
<div className="flex flex-wrap gap-3"> <div
id='upload-doc-step-one'
className="flex flex-wrap gap-3">
{/* Uploaded documents */} {/* Uploaded documents */}
<ReactSortable <ReactSortable
className="flex flex-wrap gap-3" className="flex flex-wrap gap-3"

View File

@ -5,7 +5,7 @@ import { RootState } from '../../../store/store'
import api from '../../../config/api'; import api from '../../../config/api';
import RightSideBar from './rightSideBar/RightSideBar'; import RightSideBar from './rightSideBar/RightSideBar';
import { DroppedItem } from '../../../utlity/types'; import { DroppedItem, TagsTypes } from '../../../utlity/types';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { IoMdClose } from 'react-icons/io'; import { IoMdClose } from 'react-icons/io';
@ -19,6 +19,7 @@ import { IoAdd } from 'react-icons/io5';
import Loader from '../../dashboard/Loader'; import Loader from '../../dashboard/Loader';
// import { TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice"; // import { TRANSACTION_TEMPLATE_TOGGLE } from "../../../store/slices/selectedRecipientsSlice";
import { handleGroupBorderPosition } from '../../../utlity/tagGroupConUtils'; import { handleGroupBorderPosition } from '../../../utlity/tagGroupConUtils';
import { hideLoader, showLoader } from '../../../store/slices/loaderSlice';
export interface DroppedItemsObj { export interface DroppedItemsObj {
[key: string]: DroppedItem[] [key: string]: DroppedItem[]
@ -65,7 +66,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments) // const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents) const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
console.log("selectedDocuments",selectedDocuments) console.log("selectedDocuments", selectedDocuments)
const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({}); const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({});
console.log("droppedItems", droppedItems) console.log("droppedItems", droppedItems)
@ -190,7 +191,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
useEffect(() => { useEffect(() => {
if (isSend === null) return; if (isSend === null) return;
// dispatch(showLoader("Creating Transaction")) dispatch(showLoader("Loading ..."))
let updatedTags = { ...droppedItems } let updatedTags = { ...droppedItems }
@ -209,7 +210,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
for (const dropTag of updatedTags[dropPageIds]) { for (const dropTag of updatedTags[dropPageIds]) {
let mask = null let mask = null
// console.log(dropTag, "line 132") // console.log(dropTag, "line 132")
// if (dropTag.subType?.toLowerCase() === "password") { // if (dropTag.subType?.toLowerCase() === "password") {
@ -248,8 +248,8 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
"color": "black", "color": "black",
"textSize": 0 "textSize": 0
}, },
// "required": (dropTag.type.toUpperCase() !== "SIGNATURE" && dropTag.type.toUpperCase() !== "INITIALS") ? dropTag.required : true, "required": (dropTag.type.toUpperCase() !== "SIGNATURE" && dropTag.type.toUpperCase() !== "INITIALS") ? dropTag.required : true,
"required": "required" in dropTag ? dropTag.required : true, // "required": "required" in dropTag ? dropTag.required : ,
"update": "update" in dropTag ? dropTag.update : false, "update": "update" in dropTag ? dropTag.update : false,
"readOnly": false, "readOnly": false,
} }
@ -292,13 +292,17 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
// for(const recipient of selectedRecipients) { // for(const recipient of selectedRecipients) {
// const recrecipient // const recrecipient
// } // }
tagsData = groupByRecipientId(tagsData); tagsData = groupByRecipientId(tagsData);
sendTagsToRecipients(tagsData, accountId, transactionId); sendTagsToRecipients(tagsData, accountId, transactionId);
(async () => { (async () => {
const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" }) const res = await api.put(`/api/accounts/${accountId}/${transactionTemplateToggle}/${transactionId}`, { status: "SENT" })
// const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { status: "SENT" }) // const res = await api.put(`/api/accounts/${accountId}/transactions/${transactionId}`, { status: "SENT" })
console.log(res, "Check123") console.log(res, "Check123")
dispatch(hideLoader())
})() })()
}, [isSend]) }, [isSend])
// Save Tags - END // Save Tags - END
@ -331,6 +335,19 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
const key = `${docId}_${pageIndex}`; const key = `${docId}_${pageIndex}`;
// const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />; // const IconComponent = typeToIconMap[field.type] || <FaQuestionCircle />;
// Handling Initia Width and height of Some tags - START
if (dragElemData.label === TagsTypes.STAMP) {
dragElemData.width = 160
dragElemData.height = 160
} else if (dragElemData.label === TagsTypes.SIGNATURE) {
dragElemData.width = 200
dragElemData.height = 75
} else if (dragElemData.label === TagsTypes.INITIALS) {
dragElemData.width = 85
dragElemData.height = 75
}
// Handling Initia Width and height of Some tags - END
let prepData = { let prepData = {
properties: data.properties, properties: data.properties,
icon: typeToIconMap[field.label.toUpperCase()] || <FaQuestionCircle />, icon: typeToIconMap[field.label.toUpperCase()] || <FaQuestionCircle />,
@ -355,6 +372,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
pageIndex: pageIndex, pageIndex: pageIndex,
recipientId: dragElemData.recipientId, recipientId: dragElemData.recipientId,
recipientIndx: dragElemData.recipientIndx, recipientIndx: dragElemData.recipientIndx,
required: (dragElemData.label !== TagsTypes.SIGNATURE && dragElemData.label !== TagsTypes.INITIALS) ? false : true,
update: true, update: true,
isDeleted: false isDeleted: false
} }
@ -726,104 +744,104 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ isSend }) => {
}; };
// Enhanced handleGroupBorderPosition with better positioning logic // Enhanced handleGroupBorderPosition with better positioning logic
const handleGroupBorderPosition = (groupElements: NodeListOf<HTMLElement>, containerRect: DOMRect, offsetX: number, offsetY: number) => { const handleGroupBorderPosition = (groupElements: NodeListOf<HTMLElement>, containerRect: DOMRect, offsetX: number, offsetY: number) => {
if (!groupElements || groupElements.length === 0) return; if (!groupElements || groupElements.length === 0) return;
const groupId = groupElements[0].getAttribute('group'); const groupId = groupElements[0].getAttribute('group');
if (!groupId) return; if (!groupId) return;
// Calculate the bounding box of all group elements // Calculate the bounding box of all group elements
let minLeft = Infinity, minTop = Infinity, maxRight = -Infinity, maxBottom = -Infinity; let minLeft = Infinity, minTop = Infinity, maxRight = -Infinity, maxBottom = -Infinity;
groupElements.forEach(element => { groupElements.forEach(element => {
const parentElement = element.parentElement; const parentElement = element.parentElement;
if (!parentElement) return; if (!parentElement) return;
const rect = parentElement.getBoundingClientRect(); const rect = parentElement.getBoundingClientRect();
const relativeLeft = rect.left - containerRect.left; const relativeLeft = rect.left - containerRect.left;
const relativeTop = rect.top - containerRect.top; const relativeTop = rect.top - containerRect.top;
const relativeRight = relativeLeft + rect.width; const relativeRight = relativeLeft + rect.width;
const relativeBottom = relativeTop + rect.height; const relativeBottom = relativeTop + rect.height;
minLeft = Math.min(minLeft, relativeLeft); minLeft = Math.min(minLeft, relativeLeft);
minTop = Math.min(minTop, relativeTop); minTop = Math.min(minTop, relativeTop);
maxRight = Math.max(maxRight, relativeRight); maxRight = Math.max(maxRight, relativeRight);
maxBottom = Math.max(maxBottom, relativeBottom); maxBottom = Math.max(maxBottom, relativeBottom);
}); });
// Add some padding around the group // Add some padding around the group
const padding = 10; const padding = 10;
minLeft -= padding; minLeft -= padding;
minTop -= padding; minTop -= padding;
maxRight += padding; maxRight += padding;
maxBottom += padding; maxBottom += padding;
const width = maxRight - minLeft; const width = maxRight - minLeft;
const height = maxBottom - minTop; const height = maxBottom - minTop;
// Update or create border elements // Update or create border elements
const borderPositions = ['top', 'right', 'bottom', 'left']; const borderPositions = ['top', 'right', 'bottom', 'left'];
borderPositions.forEach(position => { borderPositions.forEach(position => {
let borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`) as HTMLElement; let borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`) as HTMLElement;
if (!borderElement) { if (!borderElement) {
// Create border element if it doesn't exist // Create border element if it doesn't exist
borderElement = document.createElement('div'); borderElement = document.createElement('div');
borderElement.className = `group-border ${position === 'bottom' ? 'bottom-border' : ''}`; borderElement.className = `group-border ${position === 'bottom' ? 'bottom-border' : ''}`;
borderElement.setAttribute('border-group', groupId); borderElement.setAttribute('border-group', groupId);
borderElement.setAttribute('border-pos', position); borderElement.setAttribute('border-pos', position);
// Add the + icon for bottom border // Add the + icon for bottom border
if (position === 'bottom') { if (position === 'bottom') {
const addIcon = document.createElement('div'); const addIcon = document.createElement('div');
addIcon.innerHTML = '<IoAdd />'; // You might need to adjust this based on your icon system addIcon.innerHTML = '<IoAdd />'; // You might need to adjust this based on your icon system
addIcon.onclick = () => { addIcon.onclick = () => {
// Find the original item to pass to handleAddGroupItemClicked // Find the original item to pass to handleAddGroupItemClicked
const originalItem = droppedItems[key]?.find(item => 'group' in item && item.group === groupId); const originalItem = droppedItems[key]?.find(item => 'group' in item && item.group === groupId);
if (originalItem) { if (originalItem) {
handleAddGroupItemClicked(key, originalItem); handleAddGroupItemClicked(key, originalItem);
} }
}; };
borderElement.appendChild(addIcon); borderElement.appendChild(addIcon);
}
// Append to container
groupElements[0].parentElement?.parentElement?.appendChild(borderElement);
} }
// Append to container // Position the border element
groupElements[0].parentElement?.parentElement?.appendChild(borderElement); borderElement.style.position = 'absolute';
} borderElement.style.pointerEvents = position === 'bottom' ? 'auto' : 'none';
// Position the border element switch (position) {
borderElement.style.position = 'absolute'; case 'top':
borderElement.style.pointerEvents = position === 'bottom' ? 'auto' : 'none'; borderElement.style.left = `${minLeft}px`;
borderElement.style.top = `${minTop}px`;
switch (position) { borderElement.style.width = `${width}px`;
case 'top': borderElement.style.height = '2px';
borderElement.style.left = `${minLeft}px`; break;
borderElement.style.top = `${minTop}px`; case 'right':
borderElement.style.width = `${width}px`; borderElement.style.left = `${maxRight - 2}px`;
borderElement.style.height = '2px'; borderElement.style.top = `${minTop}px`;
break; borderElement.style.width = '2px';
case 'right': borderElement.style.height = `${height}px`;
borderElement.style.left = `${maxRight - 2}px`; break;
borderElement.style.top = `${minTop}px`; case 'bottom':
borderElement.style.width = '2px'; borderElement.style.left = `${minLeft}px`;
borderElement.style.height = `${height}px`; borderElement.style.top = `${maxBottom - 2}px`;
break; borderElement.style.width = `${width}px`;
case 'bottom': borderElement.style.height = '2px';
borderElement.style.left = `${minLeft}px`; break;
borderElement.style.top = `${maxBottom - 2}px`; case 'left':
borderElement.style.width = `${width}px`; borderElement.style.left = `${minLeft}px`;
borderElement.style.height = '2px'; borderElement.style.top = `${minTop}px`;
break; borderElement.style.width = '2px';
case 'left': borderElement.style.height = `${height}px`;
borderElement.style.left = `${minLeft}px`; break;
borderElement.style.top = `${minTop}px`; }
borderElement.style.width = '2px'; });
borderElement.style.height = `${height}px`; };
break;
}
});
};
const handleAddGroupItemClicked = (key: string, item: any) => { const handleAddGroupItemClicked = (key: string, item: any) => {
console.log("handleAddGroupItemClicked", item); console.log("handleAddGroupItemClicked", item);
@ -835,6 +853,12 @@ const handleGroupBorderPosition = (groupElements: NodeListOf<HTMLElement>, conta
'group' in existingItem && existingItem.group === groupId 'group' in existingItem && existingItem.group === groupId
) || []; ) || [];
const existingGroupItem = droppedItems[key]?.filter(existingItem =>
'group' in existingItem && existingItem.group === groupId
) || [];
existingGroupItem[0].options
// Find the bottom-most item in the group // Find the bottom-most item in the group
let bottomMostItem = item; let bottomMostItem = item;
if (existingGroupItems.length > 0) { if (existingGroupItems.length > 0) {
@ -875,131 +899,27 @@ const handleGroupBorderPosition = (groupElements: NodeListOf<HTMLElement>, conta
useEffect(() => { useEffect(() => {
// Initialize group borders for all existing groups after component updates // Initialize group borders for all existing groups after component updates
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
const allGroupElements = document.querySelectorAll('[group]'); const allGroupElements = document.querySelectorAll('[group]');
const groupIds = new Set<string>(); const groupIds = new Set<string>();
allGroupElements.forEach((element) => { allGroupElements.forEach((element) => {
const groupId = element.getAttribute('group'); const groupId = element.getAttribute('group');
if (groupId) { if (groupId) {
groupIds.add(groupId); groupIds.add(groupId);
} }
}); });
groupIds.forEach((groupId) => { groupIds.forEach((groupId) => {
handleIGroupBorderPosition(groupId); handleIGroupBorderPosition(groupId);
}); });
}, 100); }, 100);
return () => clearTimeout(timeoutId); return () => clearTimeout(timeoutId);
}, [droppedItems]); // Re-run when droppedItems changes }, [droppedItems]); // Re-run when droppedItems changes
// const handleAddRadio = (e: React.MouseEvent<HTMLElement>, key: string, index: number) => {
// // const dataToSet: DroppedItemsObj = {
// // ...droppedItems,
// // [key]: [...(droppedItems[key] || []),
// // {
// // ...prepData,
// // radioGroups: dragElemData.label === "radio" ? [{ ...prepData }] : []
// // }],
// // }
// // const lastRadioElemRect = e.currentTarget?.parentElement.previousSibling.getBoundingClientRect();
// // console.log(lastRadioElemRect, "lastRadioElemRect")
// const setNewData = droppedItems;
// if (Array.isArray(setNewData[key][index].radioGroups) && setNewData[key][index].radioGroups?.length > 0) {
// setNewData[key][index].radioGroups?.push(setNewData[key][index].radioGroups[0])
// }
// setDroppedItems(setNewData);
// };
// const handleRadioTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number, radioIndex: number) => {
// event.preventDefault();
// let targetContainer = event?.currentTarget?.parentElement;
// let targetElement = event?.currentTarget;
// if (!targetContainer || !targetElement) return;
// const containerRect = targetContainer.getBoundingClientRect();
// const elementRect = targetElement.getBoundingClientRect();
// // Calculate the initial offset (where the mouse was clicked inside the element)
// const offsetX = event.clientX - elementRect.left;
// const offsetY = event.clientY - elementRect.top;
// const handleMouseMove = (event: PointerEvent) => {
// // Get new X and Y positions relative to the container
// // let x = offsetX;
// // let y = offsetY;
// let x = event.clientX - elementRect.left;
// let y = event.clientY - elementRect.top;
// const newData = droppedItems;
// if (newData[key][index].radioGroups && newData[key][index].radioGroups.length > 0) {
// newData[key][index].radioGroups[radioIndex].dimensions.left = x;
// newData[key][index].radioGroups[radioIndex].dimensions.top = y;
// let topMin = Number.POSITIVE_INFINITY;
// let topMax = Number.NEGATIVE_INFINITY;
// let leftMin = Number.POSITIVE_INFINITY;
// let leftMax = Number.NEGATIVE_INFINITY;
// for (const radio of newData[key][index].radioGroups) {
// if (radio.dimensions.left < leftMin) {
// leftMin = radio.dimensions.left;
// }
// if (radio.dimensions.left > leftMax) {
// leftMax = radio.dimensions.left;
// }
// if (radio.dimensions.top < topMin) {
// topMin = radio.dimensions.top;
// }
// if (radio.dimensions.top > topMax) {
// topMax = radio.dimensions.top;
// }
// }
// newData[key][index].radioPosition = {
// topMin: topMin,
// topMax: topMax,
// leftMin: leftMin,
// leftMax: leftMax,
// width: leftMax - leftMin,
// height: topMax - topMin
// }
// newData[key][index].dimensions.left = x;
// newData[key][index].dimensions.top = y;
// }
// setDroppedItems({ ...newData });
// };
// const handleMouseUp = () => {
// document.removeEventListener("pointermove", handleMouseMove);
// document.removeEventListener("pointerup", handleMouseUp);
// };
// // Add event listeners
// document.addEventListener("pointermove", handleMouseMove);
// document.addEventListener("pointerup", handleMouseUp);
// }
// NEW DIRECTIONAL MOVEMENT HANDLER - START
const handleDirectionalMove = (direction: 'up' | 'down' | 'left' | 'right', stepSize: number = 5) => { const handleDirectionalMove = (direction: 'up' | 'down' | 'left' | 'right', stepSize: number = 5) => {
if (!selectedTag) return; if (!selectedTag) return;
@ -1043,38 +963,38 @@ const handleGroupBorderPosition = (groupElements: NodeListOf<HTMLElement>, conta
}; };
// Add this new handler function to your parent component // Add this new handler function to your parent component
const handleDirectPositionChange = (newLeft: number, newTop: number) => { const handleDirectPositionChange = (newLeft: number, newTop: number) => {
console.log("handleDirectPositionChange",newLeft,newTop) console.log("handleDirectPositionChange", newLeft, newTop)
if (!selectedTag) return; if (!selectedTag) return;
const { key, index } = selectedTag; const { key, index } = selectedTag;
const currentItem = droppedItems[key]?.[index]; const currentItem = droppedItems[key]?.[index];
if (!currentItem) return; if (!currentItem) return;
// Ensure values are not negative // Ensure values are not negative
const clampedLeft = Math.max(0, newLeft); const clampedLeft = Math.max(0, newLeft);
const clampedTop = Math.max(0, newTop); const clampedTop = Math.max(0, newTop);
setDroppedItems({ setDroppedItems({
...droppedItems, ...droppedItems,
[key]: Array.isArray(droppedItems[key]) [key]: Array.isArray(droppedItems[key])
? droppedItems[key].map((item, i) => ? droppedItems[key].map((item, i) =>
i === index i === index
? { ? {
...item, ...item,
dimensions: { dimensions: {
...item.dimensions, ...item.dimensions,
left: clampedLeft, left: clampedLeft,
top: clampedTop top: clampedTop
}, },
update: true update: true
} }
: item : item
) )
: [], : [],
}); });
}; };
return ( return (
<> <>
@ -1149,7 +1069,7 @@ const handleDirectPositionChange = (newLeft: number, newTop: number) => {
{/* Expand Circle - STARTS */} {/* Expand Circle - STARTS */}
{item.type !== "radio" && <div {item.type !== "radio" && item.type !== "checkbox" && <div
onPointerDown={(e) => handleExpandTag(e, key, index)} onPointerDown={(e) => handleExpandTag(e, key, index)}
className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'> className='w-2.5 h-2.5 absolute bg-red-400 rounded-full bottom-[-8px] right-[-8px] cursor-nw-resize hover:bg-blue-400'>
</div>} </div>}
@ -1178,8 +1098,6 @@ const handleDirectPositionChange = (newLeft: number, newTop: number) => {
{ {
"group" in item && item.group && "isGroupExist" in item && !item.isGroupExist && "group" in item && item.group && "isGroupExist" in item && !item.isGroupExist &&
<> <>
<div className="group-border" border-group={item.group} border-pos="top"></div> <div className="group-border" border-group={item.group} border-pos="top"></div>
<div className="group-border" border-group={item.group} border-pos="right"></div> <div className="group-border" border-group={item.group} border-pos="right"></div>
<div className="group-border bottom-border" border-group={item.group} border-pos="bottom"> <div className="group-border bottom-border" border-group={item.group} border-pos="bottom">
@ -1208,11 +1126,11 @@ const handleDirectPositionChange = (newLeft: number, newTop: number) => {
{/* Side details of documents Pages and document Name - START */} {/* Side details of documents Pages and document Name - START */}
<p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'> <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs left-[-60px] text-slate-600'>
page {indx + 1} of {doc.pages && doc.pages.length} page {indx + 1} of {doc.pages && doc.pages.length}
</p> </p>
{/* <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs right-[-140px] text-slate-600'> {/* <p className='absolute top-[50%] translate-y-[-50%] rotate-[-90deg] text-xs right-[-140px] text-slate-600'>
Document {indxDoc + 1} {doc.name} Document {indxDoc + 1} {doc.name}
</p> */} </p> */}
{/* Side details of documents Pages and document Name - END */} {/* Side details of documents Pages and document Name - END */}

View File

@ -65,7 +65,7 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
<> <>
{/* Left Side Bar - START */} {/* Left Side Bar - START */}
{ {
selectedRecipients.length && <div className='col-span-2 bg-white h-full p-4 overflow-y-auto'> selectedRecipients.length > 0 && <div className='col-span-2 bg-white h-full p-4 overflow-y-auto'>
{isFields && <> {isFields && <>
{/* Fields List and Recipeint - Start */} {/* Fields List and Recipeint - Start */}
{/* Side Bar showing Recipients */} {/* Side Bar showing Recipients */}
@ -74,6 +74,7 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
onBlur={() => setDropDownToggle(false)}> onBlur={() => setDropDownToggle(false)}>
{/* Selected Recipient */} {/* Selected Recipient */}
<div <div
id='switch-recipients-left'
onClick={() => setDropDownToggle(!dropDownToggle)} onClick={() => setDropDownToggle(!dropDownToggle)}
className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm border-1 border-slate-200 cursor-pointer'> className='flex items-center justify-between gap-2 w-full p-2 text-xs rounded-sm border-1 border-slate-200 cursor-pointer'>
<div className='flex items-center gap-1.5'> <div className='flex items-center gap-1.5'>
@ -110,7 +111,9 @@ const LeftSideBar: React.FC<LeftSideBarProps> = ({ isFields, selectedRecipientIn
{/* Side Bar showing Recipients - ENDS*/} {/* Side Bar showing Recipients - ENDS*/}
{/* Drag Drop Tags Start */} {/* Drag Drop Tags Start */}
<div className='mt-4'> <div
id='tags-con-dash-left'
className='mt-4'>
<FieldList color={selectedRecipient?.color || '#2d2d2d'} recipientId={selectedRecipient.id} recipientIndx={selectedRecipientIndx} /> <FieldList color={selectedRecipient?.color || '#2d2d2d'} recipientId={selectedRecipient.id} recipientIndx={selectedRecipientIndx} />
</div> </div>

View File

@ -262,6 +262,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ isFields, setIsFields, isSend, setIsSen
{/* Send */} {/* Send */}
<div <div
id='send-sign-dash-btn'
onClick={() => handleSend()} onClick={() => handleSend()}
className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]'> className='flex items-center gap-2 cursor-pointer text-xs px-4 py-2 ml-3 rounded-sm bg-[var(--primary)]'>
<p className='tracking-wide text-white'>Send</p> <p className='tracking-wide text-white'>Send</p>

View File

@ -44,11 +44,8 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
}; };
const handlePositionInputChange = (type: 'left' | 'top', value: string) => { const handlePositionInputChange = (type: 'left' | 'top', value: string) => {
if (type === 'left') { if (type === 'left') setLeftValue(value);
setLeftValue(value); else setTopValue(value);
} else {
setTopValue(value);
}
}; };
const handlePositionInputBlur = (type: 'left' | 'top') => { const handlePositionInputBlur = (type: 'left' | 'top') => {
@ -57,11 +54,8 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
const numericValue = parseFloat(type === 'left' ? leftValue : topValue); const numericValue = parseFloat(type === 'left' ? leftValue : topValue);
if (isNaN(numericValue)) { if (isNaN(numericValue)) {
const { top = 0, left = 0 } = selectedType || {}; const { top = 0, left = 0 } = selectedType || {};
if (type === 'left') { setLeftValue(Math.round(left).toString());
setLeftValue(Math.round(left).toString()); setTopValue(Math.round(top).toString());
} else {
setTopValue(Math.round(top).toString());
}
return; return;
} }
@ -73,9 +67,7 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
}; };
const handleKeyPress = (e: React.KeyboardEvent, type: 'left' | 'top') => { const handleKeyPress = (e: React.KeyboardEvent, type: 'left' | 'top') => {
if (e.key === 'Enter') { if (e.key === 'Enter') handlePositionInputBlur(type);
handlePositionInputBlur(type);
}
}; };
useEffect(() => { useEffect(() => {
@ -84,20 +76,11 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
switch (event.key) { switch (event.key) {
case 'ArrowUp': case 'ArrowUp':
event.preventDefault();
handleMove('up');
break;
case 'ArrowDown': case 'ArrowDown':
event.preventDefault();
handleMove('down');
break;
case 'ArrowLeft': case 'ArrowLeft':
event.preventDefault();
handleMove('left');
break;
case 'ArrowRight': case 'ArrowRight':
event.preventDefault(); event.preventDefault();
handleMove('right'); handleMove(event.key.replace('Arrow', '').toLowerCase() as any);
break; break;
} }
}; };
@ -107,39 +90,65 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
}, [disabled, stepSize]); }, [disabled, stepSize]);
const buttonClass = ` const buttonClass = `
p-1 rounded-sm transition-all duration-200 border shadow-sm text-[10px] w-8 h-8 flex items-center justify-center rounded-full border shadow-sm text-[12px]
${disabled
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'bg-white text-gray-600 hover:bg-blue-100 active:bg-blue-200 cursor-pointer'}
flex items-center justify-center w-6 h-6 `;
const inputClass = `
w-12 h-6 text-[10px] text-center border rounded px-1 bg-white
${disabled ${disabled
? 'bg-gray-100 text-gray-400 cursor-not-allowed border-gray-300' ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'text-gray-600 border-gray-300 focus:border-blue-400 focus:outline-none'} : 'bg-white text-gray-700 hover:bg-blue-100 active:bg-blue-200 cursor-pointer'}
`; `;
const { top = 0, left = 0, width = 0, height = 0 } = selectedType || {}; const inputClass = `
w-14 text-center text-[12px] rounded border px-1 py-0.5 bg-white
${disabled
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
: 'border-gray-300 text-gray-700 focus:outline-none focus:ring focus:border-blue-400'}
`;
return ( return (
<div className={`flex flex-col items-center space-y-4 text-[10px] text-gray-700 ${className}`}> <div className={`inline-flex flex-col items-center space-y-2 text-[12px] ${className}`}>
{/* Top Button */} {/* Top */}
<div className="flex justify-center me-4"> <button onClick={() => handleMove('up')} disabled={disabled} className={buttonClass} title="Move Up">
<div className="flex flex-col items-center"> <FaArrowUp />
<button </button>
onClick={() => handleMove('up')}
{/* Middle: Left / Inputs / Right */}
<div className="flex items-center space-x-3">
<button onClick={() => handleMove('left')} disabled={disabled} className={buttonClass} title="Move Left">
<FaArrowLeft />
</button>
{/* Input fields */}
<div className="flex flex-col items-center space-y-1">
<input
type="number"
value={leftValue}
onChange={(e) => handlePositionInputChange('left', e.target.value)}
onBlur={() => handlePositionInputBlur('left')}
onKeyPress={(e) => handleKeyPress(e, 'left')}
disabled={disabled} disabled={disabled}
className={buttonClass} className={inputClass}
title="Move up" placeholder="Left"
type="button" />
> <input
<FaArrowUp size={12} /> type="number"
</button> value={topValue}
<span className="mt-1">Top</span> onChange={(e) => handlePositionInputChange('top', e.target.value)}
onBlur={() => handlePositionInputBlur('top')}
onKeyPress={(e) => handleKeyPress(e, 'top')}
disabled={disabled}
className={inputClass}
placeholder="Top"
/>
</div> </div>
<button onClick={() => handleMove('right')} disabled={disabled} className={buttonClass} title="Move Right">
<FaArrowRight />
</button>
</div> </div>
{/* Bottom */}
<button onClick={() => handleMove('down')} disabled={disabled} className={buttonClass} title="Move Down">
<FaArrowDown />
</button>
{/* Middle Row: Left - Center (Inputs) - Right */} {/* Middle Row: Left - Center (Inputs) - Right */}
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
{/* Left Button */} {/* Left Button */}
@ -215,7 +224,6 @@ const DirectionalControls: React.FC<DirectionalControlsProps> = ({
</div> </div>
</div> </div>
); );
}; };
export default DirectionalControls; export default DirectionalControls;

View File

@ -122,28 +122,26 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ selectedTag, setDroppedItems
console.log("changeTo", changeTo, "changeWith", changeWith) console.log("changeTo", changeTo, "changeWith", changeWith)
if (changeTo === '' || changeWith === '') return if (changeTo === '' || changeWith === '') return
if (changeWith === "paragraph") { // if (changeTo === "subType" && (changeWith === TEMPORAL.DATE || changeWith === TEMPORAL.DATE_TIME || changeWith === TEMPORAL.TIME)) {
return // // let change: string = changeWith;
} // // if (change === "DATE TIME") {
// // change = "DATE_TIME"
// // }
// setDroppedItems((prev) => {
// console.log("Temporal30", prev);
// return {
// ...prev,
// [selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
// ? prev[selTagKey].map((item, i) =>
// i === selTagIndx ? { ...item, [changeTo]: changeWith || "", format: "", update: true } : item
// )
// : [], // Fallback in case it's undefined or not an array
// };
// });
if (changeTo === "subType" && (changeWith === TEMPORAL.DATE || changeWith === TEMPORAL.DATE_TIME || changeWith === TEMPORAL.TIME)) { // } else
// let change: string = changeWith;
// if (change === "DATE TIME") {
// change = "DATE_TIME"
// }
setDroppedItems((prev) => {
console.log("Temporal30", prev);
return {
...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) // Ensure it's an array
? prev[selTagKey].map((item, i) =>
i === selTagIndx ? { ...item, [changeTo]: changeWith || "", format: "", update: true } : item
)
: [], // Fallback in case it's undefined or not an array
};
});
} else if (changeTo === "subType" && typeof changeWith === "string" && changeWith.toLowerCase() === "password") { if (changeTo === "subType" && typeof changeWith === "string" && changeWith.toLowerCase() === "password") {
console.log("Second check") console.log("Second check")
console.log("Line 161") console.log("Line 161")
setDroppedItems((prev) => ( setDroppedItems((prev) => (
@ -174,13 +172,26 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ selectedTag, setDroppedItems
}// Provide a fallback in case it's undefined or not an array }// Provide a fallback in case it's undefined or not an array
)); ));
} else if (changeTo === "format" && selectedTag.subType === TEMPORAL.DATE_TIME) { }
else if (changeTo === "format" && selectedTag.subType === TEMPORAL.DATE_TIME) {
console.log("Fourth Check") console.log("Fourth Check")
let val = timeFormats[0].format; let val = timeFormats[0].format;
if (!is24Hrs) { console.log(is24Hrs, "is24Hrs Line 176")
if (is24Hrs === null) {
val = timeFormats[0].format
} else if(!is24Hrs) {
val = timeFormats[1].format val = timeFormats[1].format
} else {
val = timeFormats[0].format
} }
console.log("val", val) // console.log("val", val, is24Hrs)
// if(is24Hrs === null) {
// changeWith = changeWith + " " + val;
// }
setDroppedItems((prev) => { setDroppedItems((prev) => {
const updatedArray = Array.isArray(prev[selTagKey]) const updatedArray = Array.isArray(prev[selTagKey])
@ -199,9 +210,9 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ selectedTag, setDroppedItems
[selTagKey]: updatedArray, [selTagKey]: updatedArray,
}; };
}); });
}
else {
} else {
setDroppedItems((prev) => { setDroppedItems((prev) => {
const updatedItems = Array.isArray(prev[selTagKey]) const updatedItems = Array.isArray(prev[selTagKey])
@ -284,6 +295,7 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ selectedTag, setDroppedItems
const tagHeight = selectedTag?.dimensions?.height - 18; const tagHeight = selectedTag?.dimensions?.height - 18;
if ( if (
selectedTag.type === TagsTypes.TEXT &&
selectedTag.subType !== TEXT.PASSWORD && selectedTag.subType !== TEXT.PASSWORD &&
selectedTag.subType !== TEXT.PARAGRAPH && selectedTag.subType !== TEXT.PARAGRAPH &&
tagHeight > fontSize * 2 tagHeight > fontSize * 2

View File

@ -15,31 +15,31 @@ const SelectTagAttr: React.FC<SelectTagAttrInt> = ({ options, selTagKey, selTagI
const [selectedOption, setSelectedOption] = useState<string>() const [selectedOption, setSelectedOption] = useState<string>()
const handleAddSelectOption = () => { const handleAddSelectOption = () => {
if (selectedOption && selectedOption.length) { if (selectedOption && selectedOption.length) {
setDroppedItems((prev) => { setDroppedItems((prev) => {
console.log("Previous droppedItems:", prev); console.log("Previous droppedItems:", prev);
const newState = { const newState = {
...prev, ...prev,
[selTagKey]: Array.isArray(prev[selTagKey]) [selTagKey]: Array.isArray(prev[selTagKey])
? prev[selTagKey].map((item, i) => { ? prev[selTagKey].map((item, i) => {
if (i === selTagIndx) { if (i === selTagIndx) {
return { return {
...item, ...item,
options: item.options ? [...item.options, selectedOption] : [selectedOption], options: item.options ? [...item.options, selectedOption] : [selectedOption],
update: true, update: true,
}; };
} }
return item; return item;
}) })
: [], : [],
}; };
console.log("New droppedItems state:", newState); console.log("New droppedItems state:", newState);
return newState; return newState;
}); });
} }
}; };
const handleSelectOptionDel = (indx: number) => { const handleSelectOptionDel = (indx: number) => {
@ -65,14 +65,11 @@ const SelectTagAttr: React.FC<SelectTagAttrInt> = ({ options, selTagKey, selTagI
<input <input
type="text" type="text"
onChange={(e) => setSelectedOption(e.target.value)} onChange={(e) => setSelectedOption(e.target.value)}
value={selectedOption} // value={selectedOption}
onKeyUp={(e) => { onKeyUp={(e) => {
if (e.code === "Enter") { if (e.code === "Enter") {
// if (selectedOption) { e.currentTarget.value = "";
// setSelectedOptions((prev) => [...prev, selectedOption]); handleAddSelectOption();
// setSelectedOption("");
// }
handleAddSelectOption()
} }
}} }}
className="bg-slate-100 w-full outline-none p-2 rounded" /> className="bg-slate-100 w-full outline-none p-2 rounded" />

View File

@ -27,6 +27,7 @@ const TemporalDateAttr: React.FC<TemporalDateAttrInt> = ({ subType, format, drop
const [is24Hrs, setIs24Hrs] = useState<boolean>(true); const [is24Hrs, setIs24Hrs] = useState<boolean>(true);
const handleIs24HrsClick = (checked: boolean) => { const handleIs24HrsClick = (checked: boolean) => {
console.log(is24Hrs, checked, "line 30")
if (subType === TEMPORAL.TIME) { if (subType === TEMPORAL.TIME) {
if (!checked) { if (!checked) {
handlePropertyChange("format", timeFormats[1].format, is24Hrs) handlePropertyChange("format", timeFormats[1].format, is24Hrs)
@ -34,6 +35,9 @@ const TemporalDateAttr: React.FC<TemporalDateAttrInt> = ({ subType, format, drop
handlePropertyChange("format", timeFormats[0].format, is24Hrs) handlePropertyChange("format", timeFormats[0].format, is24Hrs)
} }
} }
else if (subType === TEMPORAL.DATE_TIME) {
handlePropertyChange("format", (format.split(" ").slice(0, -1).join(" ")), !is24Hrs)
}
setIs24Hrs(checked) setIs24Hrs(checked)
} }
@ -86,11 +90,11 @@ const TemporalDateAttr: React.FC<TemporalDateAttrInt> = ({ subType, format, drop
val = val + " " + timeFormats[1].format; val = val + " " + timeFormats[1].format;
} }
} }
console.log('line 89' , val ) console.log('line 89', val)
return ( return (
<div <div
key={indx} key={indx}
onClick={() => handlePropertyChange("format", valObj["format"])} onClick={() => handlePropertyChange("format", valObj["format"], is24Hrs)}
className="px-2 py-1 hover:bg-slate-200 cursor-pointer" className="px-2 py-1 hover:bg-slate-200 cursor-pointer"
> >
{val} {val}

View File

@ -30,7 +30,7 @@ const Templates = () => {
console.log("handleAddTemplate called"); console.log("handleAddTemplate called");
try { try {
const res = await api.post(`/api/accounts/${activeAccountId}/templates`); const res = await api.post(`/api/accounts/${activeAccountId}/templates`);
navigate(`/process-step-one/${activeAccountId}/template/${res.data}`) navigate(`/process-step-one/${activeAccountId}/template/${res.data.id}`)
} catch (error) { } catch (error) {
console.log(error) console.log(error)
} }

View File

@ -1,5 +1,5 @@
import { AiFillSignature } from 'react-icons/ai'; import { AiFillSignature } from 'react-icons/ai';
import { Link, useParams } from 'react-router-dom'; import { Link, useNavigate, useParams } from 'react-router-dom';
import { LuGitGraph } from 'react-icons/lu'; import { LuGitGraph } from 'react-icons/lu';
import AccountNav from '../accounts/AccountNav'; import AccountNav from '../accounts/AccountNav';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@ -7,6 +7,8 @@ import api from '../../config/api';
import SigningOrderDiagram from '../../SigningOrderDiagram '; import SigningOrderDiagram from '../../SigningOrderDiagram ';
import { IoCloseOutline } from 'react-icons/io5'; import { IoCloseOutline } from 'react-icons/io5';
import { MdEditDocument } from "react-icons/md"; import { MdEditDocument } from "react-icons/md";
import { useSelector } from 'react-redux';
import { RootState } from '../../store/store';
// Sample recipient data — you can replace this with actual API data later // Sample recipient data — you can replace this with actual API data later
const recipients = [ const recipients = [
@ -30,8 +32,8 @@ const recipients = [
const TransactionView = () => { const TransactionView = () => {
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>(); const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
console.log("account" , accountId , transactionId)
const navigate = useNavigate();
if (!accountId || !transactionId) { if (!accountId || !transactionId) {
window.location.href = "/"; window.location.href = "/";
@ -40,6 +42,8 @@ const TransactionView = () => {
const [transactionDetails, setTransactionDetails] = useState(null); const [transactionDetails, setTransactionDetails] = useState(null);
const [signingOrderModal, setSigningOrderModal] = useState({ open: false }); const [signingOrderModal, setSigningOrderModal] = useState({ open: false });
const user = useSelector((state: RootState) => state.user)
console.log(transactionDetails, "line 37"); console.log(transactionDetails, "line 37");
const fetchData = async () => { const fetchData = async () => {
@ -56,6 +60,11 @@ const TransactionView = () => {
fetchData() fetchData()
}, [accountId, transactionId]) }, [accountId, transactionId])
const handleNavigateToSign = (id: number) => {
window.open(`./../../../../sign/${id}`)
}
return ( return (
<> <>
{transactionDetails && {transactionDetails &&
@ -64,17 +73,17 @@ const TransactionView = () => {
<div className="w-full px-6 py-4 rounded-tl-sm text-xs border-r-2 border-gray-200 overflow-y-auto"> <div className="w-full px-6 py-4 rounded-tl-sm text-xs border-r-2 border-gray-200 overflow-y-auto">
{/* Header */} {/* Header */}
<h1 className='text-lg font-semibold text-gray-800'> <h1 className='text-lg font-semibold text-gray-800'>
Complete with Forms Made Easy: <span className='text-slate-600'>{ transactionDetails?.transaction?.subject ?? "Untitled"}</span> Complete with Forms Made Easy: <span className='text-slate-600'>{transactionDetails?.transaction?.subject ?? "Untitled"}</span>
</h1> </h1>
<div className='mt-2'> <div className='mt-2'>
{/* new Date(transactionDetails?.transaction?.lastModifiedAt * 1000) */} {/* new Date(transactionDetails?.transaction?.lastModifiedAt * 1000) */}
<p className='text-slate-600'>From <span className='text-blue-600'>Shanu.R</span></p> <p className='text-slate-600'>From <span className='text-blue-600'>{transactionDetails?.transaction.createdBy.firstName + " " + transactionDetails?.transaction.createdBy.lastName}</span></p>
<p className=''><span className='text-slate-600 font-semibold'>Last Change on</span> <span className='text-slate-400'>{new Date(transactionDetails?.transaction?.lastModifiedAt * 1000).toISOString()}</span></p> <p className=''><span className='text-slate-600 font-semibold'>Last Change on</span> <span className='text-slate-400'>{new Date(transactionDetails?.transaction?.lastModifiedAt * 1000).toISOString()}</span></p>
<p className='text-slate-600'>Created at <span>{new Date(transactionDetails?.transaction?.createdAt * 1000).toISOString()}</span></p> <p className='text-slate-600'>Created at <span>{new Date(transactionDetails?.transaction?.createdAt * 1000).toISOString()}</span></p>
</div> </div>
<p className='text-slate-600 text-[0.98rem] mt-2 font-semibold'>Draft</p> <p className='text-slate-600 text-[0.98rem] mt-2 font-semibold'>{transactionDetails?.transaction.status}</p>
{/* Buttons */} {/* Buttons */}
<div className='my-2 flex gap-2'> <div className='my-2 flex gap-2'>
@ -98,50 +107,65 @@ const TransactionView = () => {
{/* Recipients Section */} {/* Recipients Section */}
{transactionDetails.recipients.length > 0 && {transactionDetails.recipients.length > 0 &&
<div> <div>
<div className='flex justify-between items-center text-md'> <div className='flex justify-between items-center text-md'>
<h2 className='text-lg font-semibold text-gray-800 cursor-pointer'>Signers</h2> <h2 className='text-lg font-semibold text-gray-800 cursor-pointer'>Signers</h2>
<p className='font-bold flex items-center gap-1.5 text-slate-800 cursor-pointer' onClick={() => setSigningOrderModal({ ...signingOrderModal, open: true })}> <p className='font-bold flex items-center gap-1.5 text-slate-800 cursor-pointer' onClick={() => setSigningOrderModal({ ...signingOrderModal, open: true })}>
<LuGitGraph className='text-[0.9rem] text-[var(--primary)]' /> <LuGitGraph className='text-[0.9rem] text-[var(--primary)]' />
Signing Graph Signing Graph
</p> </p>
</div> </div>
{/* Recipients List */} {/* Recipients List */}
{transactionDetails.recipients.map((recipient, index) => ( {transactionDetails.recipients.map((recipient, index) => (
<div key={index}> <div key={index}>
<div className='mt-2 flex justify-between items-center'> <div className='mt-2 flex justify-between items-center'>
<div className='flex gap-8 items-center'> <div className='flex gap-8 items-center'>
<p className='text-slate-600'>{recipient.id}</p> {/* <p className='text-slate-600'>{index+1}</p> */}
<div> <div>
<p className='text-slate-600 font-semibold'>{recipient.name}</p> <p className='text-slate-600 font-semibold'>{recipient.name}</p>
<p className='text-slate-600'>{recipient.email}</p> <p className='text-slate-600'>{recipient.email}</p>
{recipient.hostEmail !== null && <p className='text-xs'><i><b>Host: {recipient.hostEmail}</b></i></p>} {recipient.hostEmail !== null && recipient.hostEmail !== "" && <p className='text-xs'><i><b>Host: {recipient.hostEmail}</b></i></p>}
</div>
</div> </div>
</div>
<div className='flex flex-col leading-4.5 items-start text-md text-slate-600 pr-18'> <div className='flex flex-col leading-4.5 items-start text-md text-slate-600'>
<div className='flex gap-1 items-center relative'> <div className='flex gap-3 items-center relative'>
<AiFillSignature className='absolute left-[-30px] text-[1rem]' /> <div className='flex gap-1 items-center relative'>
<p className='text-black font-semibold'>{recipient.status}</p> <AiFillSignature className='absolute left-[-30px] text-[1rem]' />
</div> <p className='text-black font-semibold'>{recipient.status}</p>
{recipient.status !== "PENDING" && <p>on {recipient.completedAt}</p>} </div>
{/* <a href={recipient.viewLink} className='text-blue-600 hover:underline'> {recipient.email === user.email &&
<p
onClick={() => handleNavigateToSign(recipient.id)}
className='text-white rounded-sm bg-[var(--primary)] px-3 py-1 font-semibold'>
Sign
</p>}
</div>
{recipient.status !== "PENDING" && <p>on {recipient.completedAt}</p>}
{/* <a href={recipient.viewLink} className='text-blue-600 hover:underline'>
View signed location View signed location
</a> */} </a> */}
</div>
</div> </div>
<hr className='my-2 border-slate-200' />
</div> </div>
<hr className='my-2 border-slate-200' /> ))}
</div> </div>
))}
</div>
} }
<div> {transactionDetails?.transaction?.subject !== "" &&
<h2 className='text-lg mt-4 font-semibold text-gray-800 cursor-pointer'>Message</h2> <div>
<p className='mt-2 text-slate-600'>{ transactionDetails?.transaction?.message ?? "No Message Found...!"}</p> <h2 className='text-lg mt-4 font-semibold text-gray-800 cursor-pointer'>Subject</h2>
</div> <p className='mt-2 text-slate-600'>{`${transactionDetails?.transaction?.subject ? transactionDetails?.transaction?.subject : "Not Found"} `}</p>
</div>}
{transactionDetails?.transaction?.message !== "" &&
<div>
<h2 className='text-lg mt-4 font-semibold text-gray-800 cursor-pointer'>Message</h2>
<p className='mt-2 text-slate-600'>{`${transactionDetails?.transaction?.message ? transactionDetails?.transaction?.message : "Not Found"} `}</p>
</div>}
</div> </div>
<div className='w-[210px] text-xs rounded-sm '> <div className='w-[210px] text-xs rounded-sm '>
@ -152,9 +176,9 @@ const TransactionView = () => {
<img style={{ width: "200px", height: "160px" }} src="https://images.unsplash.com/photo-1746469535771-71a672e8719f?q=80&w=870&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" /> <img style={{ width: "200px", height: "160px" }} src="https://images.unsplash.com/photo-1746469535771-71a672e8719f?q=80&w=870&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="" />
<div className='bg-slate-100 p-2 absolute w-full bottom-[0px]'> <div className='bg-slate-100 p-2 absolute w-full bottom-[0px]'>
<p className='text-[0.7rem] text-slate-500 flex justify-between items-center'> <p className='text-[0.7rem] text-slate-500 flex justify-between items-center'>
<span> 1 of 10</span> <span> 1 of 10</span>
<span className='cursor-pointer'><Link to={`/process-step-two/${accountId}/${transactionId}`}><MdEditDocument size={16}/></Link></span> <span className='cursor-pointer'><Link to={`/process-step-two/${accountId}/${transactionId}`}><MdEditDocument size={16} /></Link></span>
</p> </p>
</div> </div>
</div> </div>
@ -165,7 +189,7 @@ const TransactionView = () => {
</div> </div>
{signingOrderModal.open && {signingOrderModal.open &&
<div className="fixed top-0 left-0 inset-0 z-50 bg-black/50 backdrop-blur-sm flex justify-center items-center overflow-hidden"> <div className="fixed top-0 left-0 inset-0 z-50 bg-black/50 backdrop-blur-sm flex justify-center items-center overflow-hidden">
<div className="flex-col relative justify-center items-center max-w-[80%] max-h-[60vh] h-full "> <div className="flex-col relative justify-center items-center max-w-[80%] max-h-[60vh] h-full ">
<div className="pb-2 text-white flex justify-between items-center bg-[var(--primary)] px-2 py-3 rounded-t-lg"> <div className="pb-2 text-white flex justify-between items-center bg-[var(--primary)] px-2 py-3 rounded-t-lg">

View File

@ -120,7 +120,7 @@ canvas {
} }
.bottom-border>div:hover { .bottom-border>div:hover {
background-color: var(--primaryLight2) ; background-color: var(--primaryLight2);
opacity: 0.9; opacity: 0.9;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
cursor: pointer; cursor: pointer;
@ -129,8 +129,8 @@ canvas {
/* Group Border - END */ /* Group Border - END */
/* Loader CSS */ /* Loader CSS */
/* From Uiverse.io by Juanes200122 */ /* From Uiverse.io by Juanes200122 */
.pl { .pl {
width: 6em; width: 6em;
height: 6em; height: 6em;
@ -159,9 +159,11 @@ canvas {
stroke: #41644A; stroke: #41644A;
} }
/* Animations */ /* Animations */
@keyframes ringA { @keyframes ringA {
from, 4% {
from,
4% {
stroke-dasharray: 0 660; stroke-dasharray: 0 660;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -330; stroke-dashoffset: -330;
@ -179,7 +181,8 @@ canvas {
stroke-dashoffset: -595; stroke-dashoffset: -595;
} }
40%, 54% { 40%,
54% {
stroke-dasharray: 0 660; stroke-dasharray: 0 660;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -660; stroke-dashoffset: -660;
@ -197,7 +200,8 @@ canvas {
stroke-dashoffset: -925; stroke-dashoffset: -925;
} }
90%, to { 90%,
to {
stroke-dasharray: 0 660; stroke-dasharray: 0 660;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -990; stroke-dashoffset: -990;
@ -205,7 +209,9 @@ canvas {
} }
@keyframes ringB { @keyframes ringB {
from, 12% {
from,
12% {
stroke-dasharray: 0 220; stroke-dasharray: 0 220;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -110; stroke-dashoffset: -110;
@ -223,7 +229,8 @@ canvas {
stroke-dashoffset: -195; stroke-dashoffset: -195;
} }
48%, 62% { 48%,
62% {
stroke-dasharray: 0 220; stroke-dasharray: 0 220;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -220; stroke-dashoffset: -220;
@ -241,7 +248,8 @@ canvas {
stroke-dashoffset: -305; stroke-dashoffset: -305;
} }
98%, to { 98%,
to {
stroke-dasharray: 0 220; stroke-dasharray: 0 220;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -330; stroke-dashoffset: -330;
@ -267,7 +275,8 @@ canvas {
stroke-dashoffset: -175; stroke-dashoffset: -175;
} }
36%, 58% { 36%,
58% {
stroke-dasharray: 0 440; stroke-dasharray: 0 440;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -220; stroke-dashoffset: -220;
@ -285,7 +294,8 @@ canvas {
stroke-dashoffset: -395; stroke-dashoffset: -395;
} }
94%, to { 94%,
to {
stroke-dasharray: 0 440; stroke-dasharray: 0 440;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -440; stroke-dashoffset: -440;
@ -293,7 +303,9 @@ canvas {
} }
@keyframes ringD { @keyframes ringD {
from, 8% {
from,
8% {
stroke-dasharray: 0 440; stroke-dasharray: 0 440;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: 0; stroke-dashoffset: 0;
@ -311,7 +323,8 @@ canvas {
stroke-dashoffset: -175; stroke-dashoffset: -175;
} }
44%, 50% { 44%,
50% {
stroke-dasharray: 0 440; stroke-dasharray: 0 440;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -220; stroke-dashoffset: -220;
@ -329,14 +342,16 @@ canvas {
stroke-dashoffset: -395; stroke-dashoffset: -395;
} }
86%, to { 86%,
to {
stroke-dasharray: 0 440; stroke-dasharray: 0 440;
stroke-width: 20; stroke-width: 20;
stroke-dashoffset: -440; stroke-dashoffset: -440;
} }
} }
.bottom-border>div:hover { .bottom-border>div:hover {
background-color: var(--primaryLight2) ; background-color: var(--primaryLight2);
opacity: 0.9; opacity: 0.9;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
cursor: pointer; cursor: pointer;
@ -344,3 +359,11 @@ canvas {
/* Group Border - END */ /* Group Border - END */
/* JoyRide - START */
.__floater__body > div > button:last-child {
display: none !important;
}
/* JoyRide - END */

244
src/utlity/JoyRideTour.tsx Normal file
View File

@ -0,0 +1,244 @@
import { useLocation } from "react-router-dom";
import { useEffect, useState } from "react";
import Joyride, { CallBackProps, STATUS, Step } from "react-joyride";
const JoyrideController = () => {
const location = useLocation();
const [runTour, setRunTour] = useState(false);
const [steps, setSteps] = useState<Step[]>([]);
const [stepIndex, setStepIndex] = useState(0);
useEffect(() => {
let timeout: NodeJS.Timeout;
const tryStartTour = () => {
let currentSteps: Step[] = [];
if (location.pathname === "/") {
currentSteps = [
{
target: "body", // This makes the welcome step show in center of screen
content: "👋 Welcome to the SME! Let's take a quick tour.",
placement: "center",
disableBeacon: true,
},
{
target: "#user-details-avatar",
content: "User Details",
placement: "left",
disableBeacon: true,
},
{
target: "#send-for-sign-self-sign-btn",
content: "Send document for signature, or for Self Sign",
placement: "left",
disableBeacon: true,
},
{
target: "#nav-link-dash",
content: (
<div>
<p>Easy Navigation to see transaction and templates.</p>
<p>
<strong>Templates:</strong> you can save all recipient documents and tags and
use them multiple times on one click.
</p>
</div>
),
placement: "left",
disableBeacon: true,
},
{
target: "#start-now-upload-con",
content: (
<div>
<p>Let's Start Some Action</p>
<p>
Click On <strong>Start Now</strong> To upload your first document for signature
</p>
</div>
),
placement: "top",
disableBeacon: true,
},
];
} else if (location.pathname.includes("/process-step-one")) {
currentSteps = [
{
target: "body", // This makes the welcome step show in center of screen
content: (
<div>
<p>👋 Welcome To process step one</p>
<p>
Here we have to add core two ingredients <strong>Document, Recipient</strong> to start cooking
</p>
</div>
),
placement: "center",
disableBeacon: true,
},
{
target: "#upload-doc-step-one",
content: "Add Documents",
placement: "right",
disableBeacon: true,
},
{
target: "#add-recipient-step-one",
content: "Add recipient for sign",
placement: "right",
disableBeacon: true,
},
{
target: "#add-tags-btn-step-one",
content: "Click On Add Tags",
placement: "right",
disableBeacon: true,
},
];
} else if (location.pathname.includes("/process-step-two")) {
currentSteps = [
{
target: "body", // This makes the welcome step show in center of screen
content: (
<div>
<p>👋 This is Final Step</p>
<br />
<p>Here we can add tags in documents and assign them to recipients</p>
</div>
),
placement: "center",
disableBeacon: true,
},
{
target: "#switch-recipients-left",
content: "Switch Recipients",
placement: "right",
disableBeacon: true,
},
{
target: "#tags-con-dash-left",
content: "All Tags, Drag and drop on the document",
placement: "right",
disableBeacon: true,
},
{
target: "#send-sign-dash-btn",
content: <div>
<p>When setisfied with all the change, Click on <strong>Send</strong></p>
</div>,
placement: "right",
disableBeacon: true,
},
];
} else if (location.pathname.includes("/sign/")) {
currentSteps = [
{
target: "body", // This makes the welcome step show in center of screen
content: (
<div>
<p>👋 Hi there! Welcome to SME. Let’s start signing.</p>
</div>
),
placement: "center",
disableBeacon: true,
},
{
target: "#tags-list-client-side",
content: <div>
<p>Drag and drop tags onto the document to fill or sign.</p>
</div>,
placement: "right",
disableBeacon: true,
},
{
target: "#sign-client-side-btn",
content: <div>
<p>Check your document carefully When you're satisfied Click on Sign button</p>
</div>,
placement: "right",
disableBeacon: true,
},
];
}
const allTargetsExist = currentSteps.every((step) =>
step.target === "body" ? true : document.querySelector(step.target)
);
if (allTargetsExist) {
setSteps(currentSteps);
setRunTour(true);
setStepIndex(0);
} else {
// Try again after slight delay (in case DOM not ready yet)
timeout = setTimeout(tryStartTour, 300);
}
};
setRunTour(false);
setSteps([]);
setStepIndex(0);
timeout = setTimeout(tryStartTour, 300);
return () => clearTimeout(timeout);
}, [location]);
// Optional: handle callback (e.g. restart, stop, next)
const handleJoyrideCallback = (data: CallBackProps) => {
const { status, type } = data;
const finished = [STATUS.FINISHED, STATUS.SKIPPED].includes(status);
if (finished) {
setRunTour(false);
}
};
return (
<Joyride
steps={steps}
run={runTour}
callback={handleJoyrideCallback}
continuous
scrollToFirstStep
showProgress
showSkipButton
disableOverlayClose
styles={{
options: {
zIndex: 9999,
primaryColor: "#3D74B6", // Changes "Next"/"Done" button
textColor: "#111827", // Content text color
backgroundColor: "#F9FAFB",// Tooltip background
arrowColor: "#F9FAFB", // Arrow background
},
buttonNext: {
backgroundColor: "#3D74B6", // Indigo
color: "#FFFFFF",
},
buttonBack: {
color: "#4F46E5", // Indigo text
},
buttonSkip: {
color: "#EF4444", // Red skip button
}
}}
locale={{
back: "Back",
close: "Close",
last: "Done",
next: "Next",
skip: "Skip",
}}
/>
);
};
export default JoyrideController;