Meeting Changes | Hot Keys Shortcut Update
This commit is contained in:
parent
f3b1fc021f
commit
e23b132d53
6
.env
6
.env
|
|
@ -1,3 +1,7 @@
|
|||
VITE_BASE_URI="https://officially-powerful-goat.ngrok-free.app"
|
||||
VITE_BASE_URI="https://wired-terminally-muskrat.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://officially-powerful-goat.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://python-amused-adder.ngrok-free.app"
|
||||
# VITE_BASE_URI="https://concrete-gladly-mullet.ngrok-free.app"
|
||||
|
||||
|
||||
# VITE_BASE_URI="http://192.168.2.176:8083"
|
||||
|
|
@ -22,6 +22,7 @@ import Templates from "./components/templates/Templates";
|
|||
import DocumentCompletionScreen from "./components/process/CompletionScreen/DocumentCompletionScreen";
|
||||
import JoyrideController from "./utlity/JoyRideTour";
|
||||
import Contacts from "./components/contacts/Contacts";
|
||||
import TransactionSentSuccess from "./components/process/CompletionScreen/TransactionSentSuccess";
|
||||
const App = () => {
|
||||
|
||||
return (
|
||||
|
|
@ -84,10 +85,16 @@ const App = () => {
|
|||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
<Route path="/document-completion-screen/:accountId/:transactionId" element={
|
||||
{/* <Route path="/document-completion-screen/:accountId/:transactionId" element={
|
||||
<ProtectedRoute>
|
||||
<DocumentCompletionScreen />
|
||||
</ProtectedRoute>
|
||||
} /> */}
|
||||
|
||||
<Route path="/transaction-sent/:accountId/:transactionId" element={
|
||||
<ProtectedRoute>
|
||||
<TransactionSentSuccess />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
|
||||
{/* Links work wothout Login */}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
|
|
@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react';
|
|||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { FaSignOutAlt, FaUser, FaBuilding, FaChevronDown, FaCheck, FaBars } from "react-icons/fa";
|
||||
import api from '../config/api';
|
||||
import { IoSettings, IoSettingsOutline } from 'react-icons/io5';
|
||||
|
||||
interface AccountNavProps {
|
||||
accountName?: string | null;
|
||||
|
|
@ -365,6 +366,18 @@ const CommonNav: React.FC<AccountNavProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-b border-gray-100">
|
||||
<button
|
||||
onClick={() => navigate("/s/settings#profile-info")}
|
||||
className="w-full flex items-center justify-between px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 transition-colors cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<IoSettings className="mr-2.5 text-lg text-gray-500" />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
{/* <FaChevronDown className={`text-gray-400 transition-transform ${accountsOpen ? 'rotate-180' : ''}`} /> */}
|
||||
</button>
|
||||
</div>
|
||||
<div className="border-b border-gray-100">
|
||||
<button
|
||||
onClick={handleAccountsClick}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,806 @@
|
|||
import React, { useState } from 'react'
|
||||
import { DroppedItem, TagsTypes } from '../../utlity/types';
|
||||
import { RiDragMoveFill } from 'react-icons/ri';
|
||||
import { IoMdClose } from 'react-icons/io';
|
||||
import maskUtils from '../../utlity/maskUtils';
|
||||
import { FaFileAlt, FaSignature, FaStamp } from 'react-icons/fa';
|
||||
import { TbSignature } from 'react-icons/tb';
|
||||
import { BsFileArrowDownFill } from 'react-icons/bs';
|
||||
import { handleGroupBorderPosition } from '../../utlity/tagGroupConUtils';
|
||||
import api from '../../config/api';
|
||||
import { SaveType, SignConfTypeObj, StampTypeObj } from './SignDocument';
|
||||
import dayjs from 'dayjs';
|
||||
import typeToIconMap from '../../helper/iconMap';
|
||||
|
||||
|
||||
interface TagsConProps {
|
||||
currDocTags: DroppedItem[]
|
||||
pageIdkey: string;
|
||||
provideTags: boolean
|
||||
documentId: string;
|
||||
pageIndex: number;
|
||||
hideTags: boolean,
|
||||
transparentTags: boolean;
|
||||
focusElement: DroppedItem | null;
|
||||
selectedTag: { key: string; index: number } | null;
|
||||
droppedItems: DroppedItem[]
|
||||
saved: SaveType;
|
||||
signConfObj: SignConfTypeObj | {};
|
||||
setStamp: React.Dispatch<React.SetStateAction<StampTypeObj>>;
|
||||
setSignConfObj: React.Dispatch<React.SetStateAction<SignConfTypeObj | {}>>;
|
||||
setOpenSign: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
setSignTagId: React.Dispatch<React.SetStateAction<number>>;
|
||||
setDroppedItems: React.Dispatch<React.SetStateAction<DroppedItem[]>>;
|
||||
setSelectedTag: React.Dispatch<React.SetStateAction<{ key: string; index: number } | null>>
|
||||
setFocusElement: React.Dispatch<React.SetStateAction<DroppedItem | null>>
|
||||
}
|
||||
|
||||
const TagsCon: React.FC<TagsConProps> = ({
|
||||
currDocTags,
|
||||
pageIdkey,
|
||||
provideTags,
|
||||
documentId,
|
||||
pageIndex,
|
||||
hideTags,
|
||||
transparentTags,
|
||||
focusElement,
|
||||
selectedTag,
|
||||
droppedItems,
|
||||
saved,
|
||||
signConfObj,
|
||||
setStamp,
|
||||
setSignConfObj,
|
||||
setOpenSign,
|
||||
setSignTagId,
|
||||
setDroppedItems,
|
||||
setSelectedTag,
|
||||
setFocusElement
|
||||
}) => {
|
||||
|
||||
const [dateValues, setDateValues] = useState<{ [key: string]: string }>({});
|
||||
const [isDateOpen, setIsDateOpen] = useState<string | null>(null);
|
||||
|
||||
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
|
||||
setSelectedTag({ key, index })
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
let targetContainer = event?.currentTarget?.parentElement?.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 conScrollWidth = targetContainer?.scrollWidth;
|
||||
const conScrollHeight = targetContainer?.scrollHeight;
|
||||
|
||||
let isGroupAttr = event.currentTarget.hasAttribute("group");
|
||||
let groupId = null;
|
||||
if (isGroupAttr) {
|
||||
groupId = event.currentTarget.getAttribute("group");
|
||||
}
|
||||
|
||||
let groupElements: NodeListOf<HTMLElement> | null = null
|
||||
if (isGroupAttr && groupId) {
|
||||
groupElements = document.querySelectorAll(`div[group="${groupId}"]`);
|
||||
}
|
||||
|
||||
const handleMouseMove = (event: MouseEvent) => {
|
||||
// Get new X and Y positions relative to the container
|
||||
let left = event.clientX - containerRect.left - offsetX;
|
||||
let top = event.clientY - containerRect.top - offsetY;
|
||||
|
||||
|
||||
// **Restrict movement within container bounds**
|
||||
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
|
||||
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height));
|
||||
|
||||
|
||||
if (groupElements != null && groupElements.length) handleGroupBorderPosition(groupElements, containerRect, offsetX, offsetY);
|
||||
|
||||
// Apply the movement to the element
|
||||
setDroppedItems(
|
||||
droppedItems.map((item, i) =>
|
||||
i === index ? { ...item, dimensions: { ...item.dimensions, left, top }, update: true } : item
|
||||
)
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
document.removeEventListener("pointermove", handleMouseMove);
|
||||
document.removeEventListener("pointerup", handleMouseUp);
|
||||
};
|
||||
|
||||
// Add event listeners
|
||||
document.addEventListener("pointermove", handleMouseMove);
|
||||
document.addEventListener("pointerup", handleMouseUp);
|
||||
};
|
||||
|
||||
const handleRemoveTag = (key: string, targetIndex: number) => {
|
||||
|
||||
const tag: DroppedItem = droppedItems[targetIndex];
|
||||
const recipientId = tag.recipientId;
|
||||
|
||||
(async () => {
|
||||
if ("id" in tag) {
|
||||
try {
|
||||
const response = await api.delete(`/api/sign/tags?recipientId=${recipientId}`, { data: [{ tagId: tag.id }] })
|
||||
if (response.status !== 200) throw "Tag Deletion error"
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedTag(null);
|
||||
|
||||
// Check if this item has a group for special group handling
|
||||
if ('group' in tag && tag.group) {
|
||||
const groupId = tag.group;
|
||||
|
||||
// Find all items in the same group
|
||||
const groupItems = droppedItems[key]
|
||||
.map((item, idx) => ({ item, index: idx }))
|
||||
.filter(({ item }) => 'group' in item && item.group === groupId && !item.isDeleted);
|
||||
|
||||
// Sort group items by their top position to identify the parent (top-most)
|
||||
groupItems.sort((a, b) => a.item.dimensions.top - b.item.dimensions.top);
|
||||
|
||||
// Check if the item being removed is the parent (top-most item)
|
||||
const isParentItem = groupItems.length > 0 && groupItems[0].index === targetIndex;
|
||||
|
||||
if (isParentItem && groupItems.length > 1) {
|
||||
// If removing the parent and there are child items, mark all items in the group as deleted
|
||||
|
||||
// Get indices of all group items to mark as deleted
|
||||
const indicesToDelete = groupItems.map(({ index }) => index);
|
||||
|
||||
setDroppedItems(prevState => ({
|
||||
...prevState,
|
||||
[key]: prevState[key].map((item, index) =>
|
||||
indicesToDelete.includes(index) ? { ...item, isDeleted: true } : item
|
||||
)
|
||||
}));
|
||||
|
||||
// Remove group border elements after state update
|
||||
// setTimeout(() => {
|
||||
// removeGroupBorders(groupId);
|
||||
// }, 100);
|
||||
|
||||
} else {
|
||||
// If removing a child item or the only item in group, just mark this item as deleted
|
||||
|
||||
setDroppedItems(prevState => ({
|
||||
...prevState,
|
||||
[key]: prevState[key].map((item, index) =>
|
||||
index === targetIndex ? { ...item, isDeleted: true } : item
|
||||
)
|
||||
}));
|
||||
|
||||
// Check if this was the last item in the group after state update
|
||||
// setTimeout(() => {
|
||||
// const remainingGroupItems = droppedItems[key].filter(item =>
|
||||
// 'group' in item && item.group === groupId && !item.isDeleted
|
||||
// );
|
||||
|
||||
// if (remainingGroupItems.length === 0) {
|
||||
// // Remove group borders if no items left
|
||||
// removeGroupBorders(groupId);
|
||||
// } else {
|
||||
// // Update group borders for remaining items
|
||||
// handleIGroupBorderPosition(groupId);
|
||||
// }
|
||||
// }, 100);
|
||||
}
|
||||
} else {
|
||||
// Handle non-group item removal (your existing logic)
|
||||
setDroppedItems(prevState => prevState.filter((item, idx) => {
|
||||
return idx !== targetIndex;
|
||||
}));
|
||||
}
|
||||
})()
|
||||
};
|
||||
|
||||
const handleTagUpdate = (updateIndx: number, updateValue: string) => {
|
||||
if (droppedItems && droppedItems.length) {
|
||||
const updatedTag = droppedItems.map((val, indx) => {
|
||||
if (indx === updateIndx) {
|
||||
return {
|
||||
...val,
|
||||
value: updateValue
|
||||
}
|
||||
}
|
||||
return val;
|
||||
})
|
||||
setDroppedItems(updatedTag);
|
||||
}
|
||||
|
||||
// setTagJson((prev) => {
|
||||
// const newTagJson = [...prev];
|
||||
// if (newTagJson[updateIndx]) {
|
||||
// newTagJson[updateIndx].label = updateValue;
|
||||
// }
|
||||
// return newTagJson;
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
const handleSignClick = (index: number, id: number) => {
|
||||
setSignTagId(id)
|
||||
if (saved.save) {
|
||||
if (index in signConfObj) {
|
||||
setOpenSign(true);
|
||||
return
|
||||
};
|
||||
setSignConfObj({
|
||||
...signConfObj,
|
||||
[index]: {
|
||||
accepted: true,
|
||||
index: index
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
setOpenSign(true);
|
||||
|
||||
const newObj = {
|
||||
[index]: {
|
||||
"accepted": false,
|
||||
index: index
|
||||
}
|
||||
}
|
||||
setSignConfObj({ ...newObj })
|
||||
|
||||
}
|
||||
|
||||
const handleStampClick = (index: number) => {
|
||||
setStamp({
|
||||
open: true,
|
||||
blob: ""
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const handleSignAcknowledgeClick = (index: number) => {
|
||||
if (saved.save && signConfObj === null) {
|
||||
setSignConfObj([...signConfObj, index])
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{currDocTags.map((item, index) => {
|
||||
if (item.pageIndex !== pageIndex || item.documentId != documentId) return <></>;
|
||||
return (
|
||||
<div
|
||||
tabIndex={index+1}
|
||||
onFocus={() => {
|
||||
setSelectedTag({ key: pageIdkey, index })
|
||||
// setFocusElement({ ...item, focusElementIndx: index })
|
||||
}
|
||||
}
|
||||
key={index}
|
||||
// onPointerDown={(e) => handleTagMouseDown(e, key, index)}
|
||||
className="absolute rounded dropped-item text-sm"
|
||||
// onClick={() => handleTagClick(key, index)}
|
||||
style={{
|
||||
left: `${item.dimensions.left}px`,
|
||||
top: `${item.dimensions.top}px`,
|
||||
width: `${item.dimensions.width}px`,
|
||||
height: `${item.dimensions.height}px`,
|
||||
border: `${((!hideTags && focusElement) && !transparentTags && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) ? '2px dashed #1c398e' : 'none'}`,
|
||||
// display: `${hideTags ? 'none' : 'block'}`
|
||||
}}
|
||||
>
|
||||
{provideTags && !transparentTags && !hideTags && <div
|
||||
onPointerDown={(e) => handleTagMouseDown(e, pageIdkey, index)}
|
||||
// className="absolute top-[-15px] left-[-15px] w-4 h-4 bg-blue-600 rounded-tr cursor-move z-30"
|
||||
title="Drag"
|
||||
>
|
||||
<RiDragMoveFill
|
||||
className="absolute top-[-15px] left-[-15px] w-4 h-4 bg-slate-100 text-[var(--primary)] cursor-move z-30"
|
||||
/>
|
||||
</div>}
|
||||
|
||||
<>
|
||||
{selectedTag?.key && selectedTag.key === pageIdkey && selectedTag.index === index &&
|
||||
<div className='absolute flex justify-between items-center w-full max-w-max bg-white top-[-22px] shadow z-20 text-sm rounded-t-sm' >
|
||||
<div className='flex justify-center items-center gap-1 bg-white px-2 shadow'>
|
||||
<p className='w-2.5 h-2.5 rounded'
|
||||
style={{ "background": "#2d2d2d" }}
|
||||
>
|
||||
</p>
|
||||
{/* <p
|
||||
className=''>{IconComponent}
|
||||
</p> */}
|
||||
<p className='capitalize text-ellipsis whitespace-nowrap'>{item.label}</p>
|
||||
<IoMdClose onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleRemoveTag(pageIdkey, index)
|
||||
}} className='cursor-pointer' />
|
||||
</div>
|
||||
</div>}
|
||||
</>
|
||||
|
||||
{/* TEXT FIELD - START */}
|
||||
{
|
||||
item.type === "TEXT" &&
|
||||
<div
|
||||
id={item.id}
|
||||
tabIndex={1}
|
||||
className={`max-h-max text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
|
||||
// style={{ height: item.dimensions.height + "px" }}
|
||||
>
|
||||
|
||||
{/* <input
|
||||
name={item.id}
|
||||
className="w-full px-2 outline-none"
|
||||
>
|
||||
</input> */}
|
||||
|
||||
{
|
||||
!item.multiLine && !item.maskType && <p
|
||||
// name={item.id}
|
||||
// style={{ height: item.dimensions.height + "px" }}
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.textContent;
|
||||
handleTagUpdate(index, val as string);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
contentEditable={true}
|
||||
className="outline-none whitespace-nowrap w-full max-h-max"
|
||||
>{item.value ?? ""}</p>
|
||||
}
|
||||
{
|
||||
item.multiLine && !item.maskType && <p
|
||||
suppressContentEditableWarning
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.textContent;
|
||||
handleTagUpdate(index, val as string);
|
||||
}}
|
||||
contentEditable={true}
|
||||
className="w-full outline-none max-h-max"
|
||||
>{item.value ?? ""}</p>
|
||||
}
|
||||
{
|
||||
"maskType" in item && "maskCharacter" in item && item.maskType && <p
|
||||
// onBlur={(e) => {
|
||||
// const val = e.currentTarget.textContent;
|
||||
// handleTagUpdate(index, val as string);
|
||||
// }}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") e.preventDefault();
|
||||
}}
|
||||
// onFocus={() => {
|
||||
// }}
|
||||
onBlur={(e) => {
|
||||
let val = e.target.textContent;
|
||||
handleTagUpdate(index, val as string);
|
||||
if (typeof item.maskType === "string" && typeof val === "string" && item.maskCharacter) {
|
||||
val = val.replace(/ /g, "");
|
||||
e.target.textContent = maskUtils(item.maskType, val, item.maskCharacter)
|
||||
}
|
||||
}}
|
||||
contentEditable={true}
|
||||
className="outline-none whitespace-nowrap w-full max-h-max"
|
||||
></p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* TEXT FIELD - END */}
|
||||
|
||||
{/* EMAIL FIELD - START */}
|
||||
{
|
||||
item.type === "EMAIL" &&
|
||||
<div
|
||||
id={item.id}
|
||||
tabIndex={1}
|
||||
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
|
||||
>
|
||||
{
|
||||
<p
|
||||
// name={item.id}
|
||||
// style={{ height: item.dimensions.height + "px" }}
|
||||
onInput={(e) => {
|
||||
const val = e.currentTarget.textContent;
|
||||
handleTagUpdate(index, val as string);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
const val = e.currentTarget.textContent?.trim() || "";
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
if (val && !emailRegex.test(val)) {
|
||||
alert("Please enter a valid email address");
|
||||
handleTagUpdate(index, "" as string)
|
||||
e.currentTarget.textContent = "";
|
||||
e.currentTarget.focus(); // Optional: return focus if invalid
|
||||
}
|
||||
e.preventDefault()
|
||||
};
|
||||
}}
|
||||
|
||||
onBlur={(e) => {
|
||||
const val = e.currentTarget.textContent?.trim() || "";
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
if (val && !emailRegex.test(val)) {
|
||||
alert("Please enter a valid email address");
|
||||
handleTagUpdate(index, "" as string)
|
||||
e.currentTarget.textContent = "";
|
||||
e.currentTarget.focus(); // Optional: return focus if invalid
|
||||
}
|
||||
}}
|
||||
// contentEditable={true}
|
||||
className="outline-none whitespace-nowrap w-full max-h-max"
|
||||
>
|
||||
{item.value ?? ""}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* EMAIL FIELD - END */}
|
||||
|
||||
|
||||
{/* NAME FIELD - START */}
|
||||
{
|
||||
item.type === "NAME" &&
|
||||
<div
|
||||
id={item.id}
|
||||
tabIndex={1}
|
||||
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
|
||||
>
|
||||
{
|
||||
<p
|
||||
// name={item.id}
|
||||
// style={{ height: item.dimensions.height + "px" }}
|
||||
onInput={(e) => {
|
||||
const val = e.currentTarget.textContent;
|
||||
handleTagUpdate(index, val as string);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
};
|
||||
}}
|
||||
|
||||
onBlur={(e) => {
|
||||
|
||||
}}
|
||||
// contentEditable={true}
|
||||
className="outline-none whitespace-nowrap w-full max-h-max"
|
||||
>
|
||||
{item.value ?? ""}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* NAME FIELD - END */}
|
||||
|
||||
|
||||
{/* NUMBER FIELD - START */}
|
||||
{
|
||||
item.type === "NUMBER" &&
|
||||
<div
|
||||
id={item.id}
|
||||
tabIndex={1}
|
||||
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
|
||||
>
|
||||
{
|
||||
<input
|
||||
onInput={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
handleTagUpdate(index, val as string);
|
||||
}}
|
||||
type="number"
|
||||
className="outline-none h-full max-w-max"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* NUMBER FIELD - END */}
|
||||
|
||||
|
||||
|
||||
{/* COMPANY FIELD - START */}
|
||||
{
|
||||
item.type === "COMPANY" &&
|
||||
<div
|
||||
id={item.id}
|
||||
tabIndex={1}
|
||||
className={`h-full text-[14px] font-medium w-full border-slate-100 rounded ${(transparentTags || hideTags) ? "pointer-events-nones" : "bg-slate-200 shadow-sm opacity-80"} flex justify-start items-end`}
|
||||
>
|
||||
{
|
||||
<p
|
||||
// name={item.id}
|
||||
// style={{ height: item.dimensions.height + "px" }}
|
||||
onInput={(e) => {
|
||||
const val = e.currentTarget.textContent;
|
||||
handleTagUpdate(index, val as string);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault()
|
||||
};
|
||||
}}
|
||||
|
||||
onBlur={(e) => {
|
||||
|
||||
}}
|
||||
contentEditable={true}
|
||||
className="outline-none whitespace-nowrap w-full max-h-max"
|
||||
></p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* COMPANY FIELD - END */}
|
||||
|
||||
|
||||
{/* CHECKBOX FIELD - START */}
|
||||
{
|
||||
item.type === "CHECKBOX" &&
|
||||
<div
|
||||
id={item.id}
|
||||
className={`h-full w-full rounded flex justify-center items-center ${transparentTags || hideTags ? "pointer-events-none" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
|
||||
// style={{ height: item.dimensions.height + "px" }}
|
||||
>
|
||||
<input
|
||||
name={item.id}
|
||||
// style={{ height: item.dimensions.height + "px" }}
|
||||
className="w-full px-2 outline-none"
|
||||
type="checkbox" />
|
||||
</div>
|
||||
}
|
||||
{/* CHECKBOX FIELD - END */}
|
||||
|
||||
|
||||
{/* SIGNATURE FIELD - START */}
|
||||
{
|
||||
item.type === "SIGNATURE" &&
|
||||
<div
|
||||
tabIndex={1}
|
||||
id={item.id}
|
||||
onClick={() => handleSignClick(index, item.id)}
|
||||
className={`flex justify-center items-center h-full w-full rounded cursor-pointer ${transparentTags || hideTags ? "pointer-events-none bg-transparent" : "bg-slate-200 shadow-sm border-slate-100 opacity-80"}`}
|
||||
// style={{ height: item.dimensions.height + "px" }}
|
||||
>
|
||||
{/* <input type="checkbox" className="" /> */}
|
||||
{
|
||||
(!transparentTags && !hideTags && !(saved.save && (index in signConfObj && signConfObj[index].accepted))) ?
|
||||
<>
|
||||
<FaSignature className="text-lg text-slate-600" />
|
||||
</>
|
||||
:
|
||||
<img
|
||||
style={{
|
||||
width: `${item.dimensions.width}px`,
|
||||
height: `${item.dimensions.height}px`,
|
||||
}}
|
||||
// src={saved.signature as string} />
|
||||
src={`data:image/png;base64,${saved.signature}`}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* SIGNATURE FIELD - END */}
|
||||
|
||||
|
||||
{/* INITIALS FIELD - START */}
|
||||
{
|
||||
item.type === "INITIALS" &&
|
||||
<div
|
||||
tabIndex={1}
|
||||
id={item.id}
|
||||
onClick={() => handleSignClick(index, item.id)}
|
||||
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) && !(saved.save && (index in signConfObj) && signConfObj[index].accepted)) ?
|
||||
<TbSignature className="text-lg text-slate-600" />
|
||||
:
|
||||
<img
|
||||
style={{
|
||||
width: `${item.dimensions.width}px`,
|
||||
height: `${item.dimensions.height}px`,
|
||||
}}
|
||||
className="w-[100%] h-full"
|
||||
// src={saved.initial as string}
|
||||
src={`data:image/png;base64,${saved.initial}`}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* INITIALS FIELD - END */}
|
||||
|
||||
|
||||
{/* STAMP FIELD - START */}
|
||||
{
|
||||
item.type === "STAMP" &&
|
||||
<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 ?
|
||||
<FaStamp className="text-lg text-slate-600" />
|
||||
:
|
||||
<img className="w-[100%] h-full" src={saved.initial as string} />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* STAMP FIELD - END */}
|
||||
|
||||
{/* IMAGE FIELD - START */}
|
||||
{
|
||||
item.type === "FILE" &&
|
||||
<div
|
||||
tabIndex={1}
|
||||
id={item.id}
|
||||
// onClick={() => handleSignClick(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 ?
|
||||
<FaFileAlt className="text-lg text-slate-600" />
|
||||
:
|
||||
<img className="w-[100%] h-full" src={saved.initial as string} />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{/* IMAGE FIELD - END */}
|
||||
|
||||
|
||||
{/* SELECT FIELD - START */}
|
||||
{
|
||||
item.type === "SELECT" &&
|
||||
<div
|
||||
id={item.id}
|
||||
tabIndex={1}
|
||||
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"}`}
|
||||
>
|
||||
<select
|
||||
name={item.id}
|
||||
className="outline-none"
|
||||
onInput={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
handleTagUpdate(index, val as string);
|
||||
}}
|
||||
>
|
||||
<option value="">Select</option>
|
||||
{item.options && item.options.map((obj, indx) => {
|
||||
return (
|
||||
<option
|
||||
key={indx}
|
||||
value={obj.value}
|
||||
>
|
||||
{obj.label}
|
||||
</option>
|
||||
)
|
||||
})
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
{/* SELECT FIELD - END */}
|
||||
|
||||
|
||||
|
||||
|
||||
{/* TEMPORAL FIELD - START */}
|
||||
|
||||
{
|
||||
item.type === "TEMPORAL" &&
|
||||
<div
|
||||
id={item.id}
|
||||
tabIndex={1}
|
||||
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"}`}
|
||||
>
|
||||
<p
|
||||
style={{ opacity: `${item.id === isDateOpen ? "0" : "100"}` }}
|
||||
className="w-full h-full rounded pl-1 pt-1 whitespace-nowrap">
|
||||
{(item.id && item.id in dateValues) ? dateValues[item.id] : item.value ? item.value : item.dateFormat?.toLowerCase()}
|
||||
</p>
|
||||
<input
|
||||
name={item.id}
|
||||
onFocus={(e) => {
|
||||
e.target.showPicker?.()
|
||||
if (item.id) setIsDateOpen(item.id)
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
let val = e.target.value;
|
||||
|
||||
// Adding dummy time in case of time as it giving Invalid date without that
|
||||
if (item.dateFormat?.toLowerCase() === "time") val = "09/02/2025 " + val
|
||||
|
||||
if (!item.id) return;
|
||||
setIsDateOpen(null);
|
||||
setDateValues({
|
||||
...dateValues,
|
||||
[item.id as string]: dayjs(val).format(item.format)
|
||||
})
|
||||
}}
|
||||
onInput={(e) => {
|
||||
const val = e.currentTarget.value;
|
||||
handleTagUpdate(index, val as string);
|
||||
}}
|
||||
style={{ opacity: `${item.id === isDateOpen ? "100" : "0"}` }}
|
||||
className="w-full px-2 outline-none absolute"
|
||||
onClick={() => {
|
||||
|
||||
}}
|
||||
type={`${item.dateFormat?.toLowerCase() === "date" ? "date" : (item.dateFormat?.toLowerCase() === "time" ? "time" : "datetime-local")}`}
|
||||
/>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
{/* 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 */}
|
||||
{
|
||||
(!transparentTags && !hideTags) && (focusElement && focusElement.documentId === item.documentId && focusElement.focusElementIndx === index) &&
|
||||
<>
|
||||
{/* <div
|
||||
className="absolute left-0 z-50 bg-blue-900 text-xs text-slate-100 px-2 py-[2px] rounded-sm whitespace-nowrap"
|
||||
style={{ top: -23 + "px" }}
|
||||
>
|
||||
<p>{focusElement.label}</p>
|
||||
</div> */}
|
||||
<div
|
||||
className="absolute left-[-10px] z-50 flex max-w-max"
|
||||
style={{ left: -(item.dimensions.left + 20) + "px", top: ((item.dimensions.height / 2) - 18) + "px" }}
|
||||
>
|
||||
{/* <FaArrowRight className="text-[var(--primary)] text-xl" />
|
||||
*/}
|
||||
<BsFileArrowDownFill className="text-blue-900 text-4xl rotate-270" />
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsCon
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
import { LuSignature } from "react-icons/lu";
|
||||
import { useSelector } from 'react-redux';
|
||||
import { RootState } from '../../store/store';
|
||||
import SMELogo from "../../assets/SMELogo2.png";
|
||||
import { FaFileSignature } from "react-icons/fa";
|
||||
|
||||
|
||||
const Loader = () => {
|
||||
|
|
@ -16,7 +18,7 @@ const Loader = () => {
|
|||
{/* Loader */}
|
||||
<div className="absolute z-50 w-screen h-screen flex items-center justify-center backdrop-blur-sm bg-opacity-50 bg-black/30">
|
||||
<div className='bg-[var(--loaderBg)] flex flex-col justify-center items-center gap-2 px-16 py-8 rounded-md shadow-lg'>
|
||||
<LuSignature className="text-4xl font-bold animate-text" />
|
||||
<FaFileSignature className="text-4xl font-bold animate-text" />
|
||||
<p>
|
||||
{loaderData.message != undefined ? `${loaderData.message}` : 'Loading ...'}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -33,9 +33,10 @@ import confetti from 'canvas-confetti'
|
|||
import { IoClose } from "react-icons/io5";
|
||||
import { IoMdClose, IoMdHome } from "react-icons/io";
|
||||
import { Bounce, toast } from "react-toastify";
|
||||
import NavTwo from '../processStepTwo/NavTwo';
|
||||
|
||||
|
||||
type Signer = {
|
||||
export type Signer = {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
|
|
@ -44,7 +45,7 @@ type Signer = {
|
|||
};
|
||||
|
||||
// Signers List Component
|
||||
const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
|
||||
export const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
|
||||
return (
|
||||
// <div className="bg-white/60 rounded-md shadow-sm border border-gray-100 ">
|
||||
<div className="p-2 ">
|
||||
|
|
@ -198,6 +199,7 @@ const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
|
|||
// </div>
|
||||
);
|
||||
};
|
||||
|
||||
const DocumentSubmissionOptions = () => {
|
||||
const location = useLocation();
|
||||
const [status, setStatus] = useState(null);
|
||||
|
|
@ -224,18 +226,6 @@ const DocumentSubmissionOptions = () => {
|
|||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
// toast.dismiss();
|
||||
// toast.success("Your document has been successfully sent for signature.", {
|
||||
// position: "top-center",
|
||||
// autoClose: 1500,
|
||||
// hideProgressBar: false,
|
||||
// closeOnClick: false,
|
||||
// pauseOnHover: true,
|
||||
// draggable: true,
|
||||
// progress: undefined,
|
||||
// theme: "light",
|
||||
// transition: Bounce,
|
||||
// });
|
||||
|
||||
if (showConfetti) {
|
||||
const duration = 3000;
|
||||
|
|
@ -314,25 +304,6 @@ const DocumentSubmissionOptions = () => {
|
|||
}
|
||||
}, [status]);
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// if (status === 'SENT') {
|
||||
// // Step 1: Push a dummy state to trap the user
|
||||
// window.history.pushState(null, '', window.location.href);
|
||||
|
||||
// // Step 2: Intercept back button
|
||||
// const handlePopState = (event) => {
|
||||
// // Push the same dummy state again to block back
|
||||
// window.history.pushState(null, '', window.location.href);
|
||||
// };
|
||||
|
||||
// window.addEventListener('popstate', handlePopState);
|
||||
|
||||
// return () => {
|
||||
// window.removeEventListener('popstate', handlePopState);
|
||||
// };
|
||||
// }
|
||||
// }, [status]);
|
||||
const [selectedOption, setSelectedOption] = useState('success');
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [previewData, setPreviewData] = useState({
|
||||
|
|
@ -440,95 +411,6 @@ const DocumentSubmissionOptions = () => {
|
|||
</div>
|
||||
<div className={`max-w-4xl mx-auto transition-all bg-white rounded-lg duration-700 ${isVisible ? 'translate-y-0 opacity-100' : 'translate-y-8 opacity-0'}`}>
|
||||
|
||||
{/* Header */}
|
||||
|
||||
|
||||
{/* <div className="grid grid-cols-1 lg:grid-cols-2 gap-3 max-w-5xl mx-auto">
|
||||
<div className="space-y-6 ">
|
||||
|
||||
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 min-h-[81vh] ">
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-4">
|
||||
Choose an action after a signer completes the document.
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{options.map((option) => {
|
||||
const Icon = option.icon;
|
||||
const isSelected = selectedOption === option.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={option.id}
|
||||
onClick={() => setSelectedOption(option.id)}
|
||||
className={`
|
||||
relative cursor-pointer p-4 rounded-xl border-2 transition-all duration-200 group
|
||||
${isSelected
|
||||
? 'border-[var(--primary)] bg-blue-50 shadow-md scale-[1.02]'
|
||||
: option.color + ' border-2'
|
||||
}
|
||||
hover:shadow-md hover:scale-[1.01] transform
|
||||
`}
|
||||
>
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className={`
|
||||
w-12 h-12 rounded-full flex items-center justify-center transition-all duration-200
|
||||
${isSelected
|
||||
? 'bg-[var(--primary)] text-white shadow-lg'
|
||||
: 'bg-white ' + option.iconColor + ' group-hover:scale-110'
|
||||
}
|
||||
`}>
|
||||
<Icon className="text-lg" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h4 className={`font-semibold transition-colors duration-200 ${isSelected ? 'text-[var(--primary)]' : 'text-gray-800'
|
||||
}`}>
|
||||
{option.title}
|
||||
</h4>
|
||||
{option.isPremium && (
|
||||
<span className="text-purple-500 text-sm">★</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
{option.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{isSelected && (
|
||||
<div className="absolute top-4 right-4">
|
||||
<div className="w-6 h-6 bg-[var(--primary)] rounded-full flex items-center justify-center">
|
||||
<FaCheck className="text-white text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden sticky top-6">
|
||||
<div className="bg-gradient-to-r from-gray-50 to-blue-50 px-6 py-2 border-b border-gray-200">
|
||||
<h3 className="text-lg font-semibold text-gray-800">Live Preview</h3>
|
||||
<p className="text-sm text-gray-500">See how your users will experience the completion</p>
|
||||
</div>
|
||||
|
||||
<div className="p-3 min-h-[70vh] flex justify-center">
|
||||
<div className="w-full transition-all duration-500 transform">
|
||||
{getPreviewContent()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="text-center pt-1 max-h-[25vh]">
|
||||
<div className="w-20 h-20 bg-gradient-to-br from-teal-500 to-green-400 rounded-full flex items-center justify-center mt-3 mx-auto shadow-lg ">
|
||||
<FaCheck className="text-white text-4xl" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import { FaCheck, FaClock, FaUsers } from "react-icons/fa";
|
||||
import { Signer } from "./DocumentCompletionScreen";
|
||||
|
||||
export const SignersList: React.FC<{ signers: Signer[] }> = ({ signers }) => {
|
||||
return (
|
||||
// <div className="bg-white/60 rounded-md shadow-sm border border-gray-100 ">
|
||||
<div className="p-4 bg-slate-100 rounded-sm shadow-sm">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-lg font-semibold text-[var(--primary)] flex items-center gap-2 ">
|
||||
{/* <FaUsers className="text-blue-600" /> */}
|
||||
Recipients
|
||||
</h3>
|
||||
<span className="text-sm text-gray-600">
|
||||
{signers.filter(s => s.completed).length} of {signers.length} signed
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="max-h-[24vh] overflow-y-auto no-scrollbar">
|
||||
{signers.map((signer) => (
|
||||
<div key={signer.id} className="text-sm flex items-center gap-4 rounded-lg">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center text-blue-600 font-medium">
|
||||
{signer.name.length > 2 ? signer.name.split(" ").map(n => n[0].toUpperCase()).join("") : signer.email.split("").map(n => n[0].toUpperCase()).join("")}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-gray-800">{signer.name}</div>
|
||||
<div className="text-xs text-gray-600">{signer.email}</div>
|
||||
<div className="text-xs text-gray-500">Order: {signer.routingOrder}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{signer.completed ? (
|
||||
<div className="flex items-center gap-1 px-3 py-1 bg-green-100 text-green-700 rounded-full text-xs">
|
||||
<FaCheck className="w-3 h-3" />
|
||||
Signed
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 px-3 py-1 bg-slate-100 text-slate-700 rounded-full text-xs">
|
||||
<FaClock className="w-3 h-3" />
|
||||
Pending
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
// </div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
import React from "react";
|
||||
import { FaCheck, FaClock, FaEnvelope, FaLock } from "react-icons/fa";
|
||||
|
||||
type Signer = {
|
||||
id: number;
|
||||
name: string;
|
||||
firstName: string;
|
||||
email: string;
|
||||
routingOrder: number;
|
||||
completed: boolean;
|
||||
};
|
||||
|
||||
export const SigningOrderDiagram: React.FC<{ signers: Signer[] }> = ({ signers }) => {
|
||||
const groupByRoutingOrder = (data: Signer[]) => {
|
||||
const grouped: Record<number, Signer[]> = {};
|
||||
data.forEach((signer) => {
|
||||
if (!grouped[signer.routingOrder]) {
|
||||
grouped[signer.routingOrder] = [];
|
||||
}
|
||||
grouped[signer.routingOrder].push(signer);
|
||||
});
|
||||
return grouped;
|
||||
};
|
||||
|
||||
const getInitials = (name: string) => {
|
||||
return name
|
||||
.split(" ")
|
||||
.map((n) => n[0].toUpperCase())
|
||||
.join("");
|
||||
};
|
||||
|
||||
const grouped = groupByRoutingOrder(signers);
|
||||
const sortedOrders = Object.keys(grouped)
|
||||
.map(Number)
|
||||
.sort((a, b) => a - b);
|
||||
|
||||
const currentProcessingOrder =
|
||||
sortedOrders.find((order) =>
|
||||
grouped[order].some((signer) => !signer.completed)
|
||||
) || -1;
|
||||
|
||||
return (
|
||||
// <div className="bg-white/60 rounded-md shadow-sm border border-gray-100">
|
||||
<div className="py-3">
|
||||
<h3 className="text-lg font-semibold text-gray-800 mb-1 text-center ">
|
||||
Signing Flow</h3>
|
||||
<div className="overflow-x-auto overflow-y-auto h-[18vh] no-scrollbar flex justify-center items-center">
|
||||
<div className="flex justify-center items-center gap-2 min-w-max">
|
||||
{/* Mail icon */}
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="bg-blue-100 p-4 rounded-full">
|
||||
<FaEnvelope className="text-blue-600 text-2xl" />
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mt-2">Sent</p>
|
||||
</div>
|
||||
|
||||
{/* Dotted line */}
|
||||
<div className="flex items-center mb-3">
|
||||
<div className="w-16 border-t-2 border-dotted border-gray-300"></div>
|
||||
</div>
|
||||
|
||||
{/* Signers by routing order */}
|
||||
<div className="flex items-center justify-center space-x-6">
|
||||
{sortedOrders.map((order, index) => (
|
||||
<React.Fragment key={order}>
|
||||
<div className="flex flex-col items-center space-y-4 mb-4">
|
||||
<div className="text-sm font-medium text-gray-600 mb-2">
|
||||
Step {order}
|
||||
</div>
|
||||
<div className="flex flex-col items-center space-y-3">
|
||||
{grouped[order].map((signer) => {
|
||||
let bgColor = "";
|
||||
let icon = null;
|
||||
|
||||
if (signer.completed) {
|
||||
bgColor = "bg-[var(--primary)]";
|
||||
icon = <FaCheck className="text-green-600" />;
|
||||
} else if (order === currentProcessingOrder) {
|
||||
bgColor = "bg-yellow-500";
|
||||
icon = <FaClock className="text-yellow-800" />;
|
||||
} else {
|
||||
bgColor = "bg-gray-400";
|
||||
icon = <FaLock className="text-gray-500" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={signer.id} className="relative">
|
||||
<div
|
||||
className={`w-10 h-10 rounded-full flex items-center justify-center text-white text-sm font-semibold ${bgColor}`}
|
||||
title={signer.name}
|
||||
>
|
||||
{getInitials(signer.name)}
|
||||
<span className="absolute bottom-[-4px] right-[-8px] p-1 rounded-full bg-white shadow">
|
||||
{icon}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-600 text-center max-w-16 truncate">
|
||||
{signer.name.split(" ")[0] || signer.firstName.split(0, 1)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{index < sortedOrders.length - 1 && (
|
||||
<div className="flex items-center">
|
||||
<div className="w-16 border-t-2 border-dotted border-gray-300"></div>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
// </div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import DocumentContainer from '../processStepTwo/DocumentContainer'
|
||||
import { Link, useParams } from 'react-router-dom'
|
||||
import { IoMdHome } from 'react-icons/io'
|
||||
import { FaCheck } from 'react-icons/fa'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { AppDispatch, RootState } from '../../../store/store'
|
||||
import { fetchRecipients, recipientType } from '../../../store/slices/selectedRecipientsSlice'
|
||||
import { fetchDocuments } from '../../../store/slices/documentSlice'
|
||||
import { SigningOrderDiagram } from './SigningOrderDiagram'
|
||||
import { SignersList } from './SignerList'
|
||||
import { IoCheckmarkDoneCircle } from 'react-icons/io5'
|
||||
|
||||
const TransactionSentSuccess = () => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [isSignNow, setIsSignNow] = useState(false);
|
||||
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
const { accountId, transactionId } = useParams<{ accountId: string, transactionId: string }>();
|
||||
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
|
||||
const userDetails = useSelector((state: RootState) => state.user);
|
||||
const [selfUserSign, setSelfUserSign] = useState({ sign: false, recipientId: 0 });
|
||||
|
||||
if (!accountId || !transactionId) {
|
||||
window.location.href = "/";
|
||||
return null;
|
||||
}
|
||||
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
|
||||
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
|
||||
// const [transactionData, setTransactionData] = useState({})
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
selectedRecipients.length === 0 && dispatch(fetchRecipients({ accountId, transactionId }));
|
||||
selectedDocuments.length === 0 && dispatch(fetchDocuments({ accountId, transactionId }));
|
||||
setIsVisible(true)
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRecipients.length === 0) return;
|
||||
|
||||
let minRoutingOrder: number = Infinity;
|
||||
let userEmailPresent = {
|
||||
present: false,
|
||||
routingOrder: 0,
|
||||
recipientId: 0,
|
||||
type: recipientType.CC
|
||||
};
|
||||
// Getting Minimum RoutingOrder
|
||||
for (let i = 0; i < selectedRecipients.length; i++) {
|
||||
const recipient = selectedRecipients[i]
|
||||
if (recipient.routingOrder < minRoutingOrder) {
|
||||
minRoutingOrder = recipient.routingOrder;
|
||||
}
|
||||
|
||||
if (recipient.email === userDetails.email) {
|
||||
userEmailPresent.present = true;
|
||||
userEmailPresent.routingOrder = recipient.routingOrder;
|
||||
userEmailPresent.recipientId = Number(recipient.id);
|
||||
userEmailPresent.type = recipient.type;
|
||||
}
|
||||
}
|
||||
|
||||
if (userEmailPresent.present && userEmailPresent.routingOrder === minRoutingOrder && userEmailPresent.type !== recipientType.CC && userEmailPresent.type !== recipientType.INPERSONSIGNER) {
|
||||
setSelfUserSign({ sign: true, recipientId: userEmailPresent.recipientId })
|
||||
}
|
||||
|
||||
}, [selectedRecipients])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='w-full'>
|
||||
|
||||
<div className="flex items-center space-x-4 bg-white w-full py-2 px-5 shadow">
|
||||
<Link to={'/'} className="flex items-center space-x-2 text-gray-600 hover:text-[var(--primary)] transition-colors duration-200 group">
|
||||
<IoMdHome
|
||||
className='text-2xl text-slate-500 cursor-pointer hover:text-[var(--primary)] transition'
|
||||
/>
|
||||
</Link>
|
||||
<div className="h-8 w-px bg-gray-300"></div>
|
||||
<h1 className="text-md text-gray-800">🎉 Awesome! The document’s been sent for signature. 🎉</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div className='flex flex-col p-4 justify-center items-center w-full'>
|
||||
|
||||
<div className='mx-auto max-w-max'>
|
||||
<div className='flex items-center gap-4 mb-3 ml-0 bg-slate-100 p-4 rounded-md'>
|
||||
<div className="w-12 h-12 bg-gradient-to-br from-teal-500 to-green-400 rounded-full flex items-center justify-center shadow-sm">
|
||||
<FaCheck className="text-white text-2xl" />
|
||||
</div>
|
||||
<div>
|
||||
<p className='text-2xl font-medium text-[var(--primary)]'>Your Document is send for signature</p>
|
||||
<p className='text-sm text-slate-600'>All signers will be notified via email</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-4'>
|
||||
<div className='pb-4 bg-slate-100'>
|
||||
<div className='w-full overflow-auto h-[calc(100vh-200px)]'>
|
||||
<div className='py-2 bg-white pl-4 pr-4 shadow-sm rounded-sm border-1 border-slate-200 sticky top-0 w-full z-1'>
|
||||
<p className='text-sm font-medium'>QuickPreview</p>
|
||||
</div>
|
||||
{/* <NavTwo isPreview={true} aId={accountId} tId={transactionId} /> */}
|
||||
<div className='pl-4 pr-4'>
|
||||
<DocumentContainer isPreview={true} aId={accountId} tId={transactionId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Right Side */}
|
||||
<div className={`transition-all rounded-lg duration-700 ${isVisible ? 'translate-y-0 opacity-100' : 'translate-y-8 opacity-0'}`}>
|
||||
|
||||
{/* <div className="text-center pt-1 max-h-[25vh]">
|
||||
<div className="w-20 h-20 bg-gradient-to-br from-teal-500 to-green-400 rounded-full flex items-center justify-center mt-3 mx-auto shadow-lg ">
|
||||
<FaCheck className="text-white text-4xl" />
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* <div className='text-center py-1'>
|
||||
<h3 className="text-2xl font-bold text-gray-800 mb-2">Document Send Successfully!</h3>
|
||||
</div> */}
|
||||
<div className='max-h-[30vh]'>
|
||||
<SignersList signers={selectedRecipients} />
|
||||
</div>
|
||||
|
||||
<div className='mt-4 flex flex-col gap-2'>
|
||||
{selfUserSign.sign && <Link to={`/sign/${selfUserSign.recipientId}`} className={`p-2 text-center rounded-md bg-[var(--primary)] text-white hover:shadow-sm transition-all duration-150`}>Sign Now</Link>}
|
||||
<Link to={"/"} className={`p-2 text-center rounded-md bg-slate-200 hover:shadow-sm transition-all duration-150`}>Home</Link>
|
||||
</div>
|
||||
|
||||
{/* <div className='border border-b border-gray-50'></div> */}
|
||||
{/* <div className='min-h-[30vh]'>
|
||||
<SigningOrderDiagram signers={selectedRecipients} />
|
||||
</div> */}
|
||||
{/* <div className='border border-b border-gray-50'></div> */}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TransactionSentSuccess
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react'
|
||||
import NavTwo from '../processStepTwo/NavTwo'
|
||||
|
||||
const FinalPreview = () => {
|
||||
return (
|
||||
<>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FinalPreview
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useLayoutEffect, useState } from 'react'
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from '../../../store/store'
|
||||
|
||||
|
|
@ -35,17 +35,23 @@ export const droppedTagsDefaultField: DroppedTagsDefaultFieldType = {
|
|||
}
|
||||
|
||||
interface DocumentContainerProps {
|
||||
transactionSaving: boolean;
|
||||
setTransactionSaving: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isSend: boolean | null;
|
||||
setIsSend: React.Dispatch<React.SetStateAction<boolean | null>>;
|
||||
transactionSaving?: boolean;
|
||||
setTransactionSaving?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
isSend?: boolean | null;
|
||||
setIsSend?: React.Dispatch<React.SetStateAction<boolean | null>>;
|
||||
isPreview?: boolean;
|
||||
aId?: string;
|
||||
tId?: string;
|
||||
}
|
||||
|
||||
const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving, setTransactionSaving, isSend }) => {
|
||||
let copiedTags: DroppedItem[] = []
|
||||
let selectedDoc: { [key: string]: string | null } = { key: null }
|
||||
|
||||
const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving, setTransactionSaving, isSend, isPreview = false, aId = null, tId = null }) => {
|
||||
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||
const dispatch = useDispatch();
|
||||
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
|
||||
const accountId = params.accountId;
|
||||
let accountId = params.accountId;
|
||||
let transactionId = params.transactionId
|
||||
if (!transactionId) {
|
||||
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
|
||||
|
|
@ -57,8 +63,15 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
return null;
|
||||
}
|
||||
|
||||
// If is preview is enable
|
||||
if (isPreview && aId !== null && tId !== null) {
|
||||
accountId = aId;
|
||||
transactionId = tId;
|
||||
}
|
||||
|
||||
// const transactionId = useSelector((state: RootState) => state.transaction.transactionId)
|
||||
const navigate = useNavigate();
|
||||
const childRef = useRef<any>(null);
|
||||
|
||||
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
|
||||
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
|
||||
|
|
@ -66,7 +79,6 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
|
||||
const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({});
|
||||
|
||||
|
||||
interface HotKeysObj {
|
||||
isActive: boolean;
|
||||
key: string | null;
|
||||
|
|
@ -213,9 +225,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
})
|
||||
)
|
||||
|
||||
console.log(droppedItemsUpdateFalse, "Line 182")
|
||||
|
||||
// setDroppedItems(droppedItemsUpdateFalse);
|
||||
setDroppedItems(droppedItemsUpdateFalse);
|
||||
} catch (error) {
|
||||
console.error("Error submitting recipient tags:", error);
|
||||
}
|
||||
|
|
@ -356,9 +366,24 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
|
||||
switch (hotKeysObj.hotKey) {
|
||||
case "Ctrl+c":
|
||||
// Handle Ctrl+C
|
||||
const tempCopy = { ...droppedItems[selectedTag?.key][selectedTag?.index] }
|
||||
delete tempCopy.id;
|
||||
copiedTags = [{ ...tempCopy, dimensions: { ...tempCopy.dimensions, top: tempCopy.dimensions.top + 10, left: tempCopy.dimensions.left + 10 } }]
|
||||
console.log("from Copy copiedTags", copiedTags)
|
||||
break;
|
||||
case "Ctrl+v":
|
||||
console.log(selectedDoc.key, "keyss")
|
||||
let updatedKey = droppedItems[selectedDoc.key]
|
||||
updatedKey.push(...copiedTags)
|
||||
|
||||
console.log("copiedTags", copiedTags)
|
||||
console.log("updatedKey", updatedKey)
|
||||
|
||||
setDroppedItems(prevState => ({
|
||||
...prevState,
|
||||
[selectedDoc.key]: updatedKey
|
||||
}));
|
||||
|
||||
// Handle Ctrl+V
|
||||
break;
|
||||
case "Ctrl+x":
|
||||
|
|
@ -372,12 +397,11 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
break;
|
||||
case "Ctrl+a":
|
||||
docCon?.querySelectorAll("[data-tag-id]").forEach(tag => {
|
||||
tag.classList.add("border-2", "border-slate-800")
|
||||
tag.classList.add("border-2", "border-sky-700")
|
||||
tag.setAttribute("all-select-group", "true")
|
||||
|
||||
docCon.addEventListener("click", () => {
|
||||
console.log("focus in", tag)
|
||||
tag.classList.remove("border-2", "border-slate-800")
|
||||
tag.classList.remove("border-2", "border-sky-800")
|
||||
tag.removeAttribute("all-select-group")
|
||||
})
|
||||
// docCon.addEventListener("focusout", () => {
|
||||
|
|
@ -388,9 +412,15 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
});
|
||||
break;
|
||||
case "delete":
|
||||
docCon?.querySelectorAll("[all-select-group]").forEach((tag) => {
|
||||
console.log(tag, "TAGID")
|
||||
const allselectedTags = docCon?.querySelectorAll("[all-select-group]") ?? []
|
||||
allselectedTags.forEach((tag) => {
|
||||
const tagIndex = tag.getAttribute("data-tag-id") ?? "";
|
||||
const key = tag.getAttribute("data-drop-key") ?? "";
|
||||
|
||||
childRef.current.handleRemoveTag(key, Number(tagIndex))
|
||||
})
|
||||
|
||||
if (allselectedTags.length === 0) childRef.current.handleRemoveTag(selectedTag?.key, selectedTag?.index)
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -399,10 +429,11 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
}, [hotKeysObj])
|
||||
|
||||
const handleDocumentFocus = (key: string) => {
|
||||
const id = key.replace("_", "");
|
||||
const targetCon = document.getElementById(id);
|
||||
// const id = key.replace("_", "");
|
||||
// const targetCon = document.getElementById(id);
|
||||
selectedDoc.key = key;
|
||||
|
||||
targetCon?.addEventListener("keydown", (event) => {
|
||||
document?.addEventListener("keydown", (event) => {
|
||||
event.preventDefault();
|
||||
const ctrl = event.ctrlKey;
|
||||
const alt = event.altKey;
|
||||
|
|
@ -425,7 +456,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
|
||||
setHotKeysObj({
|
||||
isActive: true,
|
||||
key: pressedKey,
|
||||
key: key,
|
||||
tagId: null,
|
||||
hotKey: hotKey
|
||||
});
|
||||
|
|
@ -439,18 +470,18 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
return (
|
||||
<>
|
||||
{/* Center Doc Container - START */}
|
||||
<div id="doc-con" className='zoom-con col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth'>
|
||||
<div id="doc-con" className={`zoom-con col-span-8 bg-slate-100 h-full w-full overflow-auto scroll-smooth`}>
|
||||
|
||||
{
|
||||
selectedDocuments.length > 0 ? selectedDocuments.map((doc, indxDoc) => {
|
||||
return (
|
||||
<div key={indxDoc} className='flex flex-col'>
|
||||
<div key={indxDoc} className='flex flex-col overflow-x-auto'>
|
||||
<div
|
||||
className='py-2 text-sm'>
|
||||
<p className='text-center'>{doc.name}</p>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%]'>
|
||||
<div className={`flex flex-col gap-3 max-w-max relative left-[50%] translate-x-[-50%] ${isPreview ? "pointer-events-none" : ""}`}>
|
||||
{
|
||||
doc.pages && doc.pages.length > 0 && doc.pages.map((page, indx) => {
|
||||
const key = `${doc.id}_${page.index}`;
|
||||
|
|
@ -466,6 +497,7 @@ const DocumentContainer: React.FC<DocumentContainerProps> = ({ transactionSaving
|
|||
>
|
||||
|
||||
<DroppedItemsTagsCon
|
||||
ref={childRef}
|
||||
droppedItems={droppedItems}
|
||||
pageIdkey={key}
|
||||
selectedTag={selectedTag}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
// -- handleGroupBorderPosition
|
||||
// -- handleAddGroupItemClicked
|
||||
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
||||
import { DroppedItemsObj } from './DocumentContainer';
|
||||
import typeToIconMap from '../../../helper/iconMap';
|
||||
import { IoMdClose } from 'react-icons/io';
|
||||
|
|
@ -29,7 +29,8 @@ interface DroppedItemsTagsConProps {
|
|||
transactionTemplateToggle: string;
|
||||
}
|
||||
|
||||
const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({
|
||||
// 👶 Child component with forwardRef
|
||||
const DroppedItemsTagsCon = forwardRef(({
|
||||
droppedItems,
|
||||
pageIdkey,
|
||||
selectedTag,
|
||||
|
|
@ -39,7 +40,7 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({
|
|||
accountId,
|
||||
transactionId,
|
||||
transactionTemplateToggle
|
||||
}) => {
|
||||
}: DroppedItemsTagsConProps, ref) => {
|
||||
|
||||
const positionRef = useRef<{ left: number; top: number }>({ left: 0, top: 0 });
|
||||
|
||||
|
|
@ -48,6 +49,8 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({
|
|||
}
|
||||
|
||||
const handleRemoveTag = (key: string, targetIndex: number) => {
|
||||
if(key === undefined || targetIndex === undefined) return
|
||||
|
||||
const tag = droppedItems[key][targetIndex];
|
||||
const recipientId = tag.recipientId;
|
||||
|
||||
|
|
@ -136,6 +139,10 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({
|
|||
})()
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleRemoveTag
|
||||
}));
|
||||
|
||||
// HANDLE TAG MOUSE DOWN - START
|
||||
// Updated handleTagMouseDown function with proper group border positioning
|
||||
const handleTagMouseDown = (event: React.MouseEvent<HTMLDivElement>, key: string, index: number) => {
|
||||
|
|
@ -536,6 +543,7 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({
|
|||
// key={index}
|
||||
draggable="false"
|
||||
data-tag-id={index}
|
||||
data-drop-key={pageIdkey}
|
||||
data-tag-selected={index === selectedTag?.index}
|
||||
className="absolute rounded dropped-item"
|
||||
onClick={() => handleTagClick(pageIdkey, index)}
|
||||
|
|
@ -621,6 +629,6 @@ const DroppedItemsTagsCon: React.FC<DroppedItemsTagsConProps> = ({
|
|||
{/* Render only the dropped items for this specific docId and page - ENDS */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default DroppedItemsTagsCon
|
||||
|
|
@ -12,14 +12,18 @@ import { fetchRecipients, recipientType, toggleToTemplates, TRANSACTION_TEMPLATE
|
|||
import { fetchDocuments } from '../../../store/slices/documentSlice'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
import { Bounce, toast } from 'react-toastify'
|
||||
import { isPending } from '@reduxjs/toolkit'
|
||||
|
||||
interface NavTwoProps {
|
||||
transactionSaving: boolean;
|
||||
transactionSaving?: boolean;
|
||||
// setTransactionSaving: React.Dispatch<React.SetStateAction<boolean>>
|
||||
isFields: boolean;
|
||||
setIsFields: React.Dispatch<React.SetStateAction<boolean>>
|
||||
isSend: boolean | null;
|
||||
setIsSend: React.Dispatch<React.SetStateAction<boolean | null>>
|
||||
isFields?: boolean;
|
||||
setIsFields?: React.Dispatch<React.SetStateAction<boolean>>
|
||||
isSend?: boolean | null;
|
||||
setIsSend?: React.Dispatch<React.SetStateAction<boolean | null>>
|
||||
isPreview?: boolean
|
||||
aId?: string | null
|
||||
tId?: string | null
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -47,11 +51,11 @@ const fitObj = [
|
|||
},
|
||||
]
|
||||
|
||||
const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsFields, isSend, setIsSend }) => {
|
||||
const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving = false, isFields, setIsFields, isSend, setIsSend, isPreview = false, aId = null, tId = null }) => {
|
||||
let transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS;
|
||||
const dispatch = useDispatch();
|
||||
const params = useParams<{ accountId?: string; transactionId?: string; templateId?: string }>();
|
||||
const accountId = params.accountId;
|
||||
let accountId = params.accountId;
|
||||
let transactionId = params.transactionId
|
||||
if (!transactionId) {
|
||||
transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES;
|
||||
|
|
@ -63,6 +67,11 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
|
|||
return null;
|
||||
}
|
||||
|
||||
// If is preview is enable
|
||||
if (isPreview && aId !== null && tId !== null) {
|
||||
accountId = aId;
|
||||
transactionId = tId;
|
||||
}
|
||||
|
||||
// const [transactionSaving, setTransactionSaving] = useState<boolean>(false);
|
||||
const [dotCount, setDotCount] = useState(0);
|
||||
|
|
@ -131,7 +140,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
|
|||
window.open(`./../../../sign/${selfUserSign.recipientId}`, "_self");
|
||||
}
|
||||
else {
|
||||
navigate(`/document-completion-screen/${accountId}/${transactionId}`, { replace: true })
|
||||
navigate(`/transaction-sent/${accountId}/${transactionId}`, { replace: true })
|
||||
}
|
||||
} else {
|
||||
navigate(`/templates`);
|
||||
|
|
@ -190,12 +199,12 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
|
|||
{/* Left Side */}
|
||||
<div className='flex justify-start items-center gap-2'>
|
||||
|
||||
<div
|
||||
{!isPending && <div
|
||||
onClick={() => handleSetIsFields(true)}
|
||||
className={`flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm ${isFields ? 'bg-slate-100' : ''}`}>
|
||||
<LuTableOfContents />
|
||||
<p className='tracking-wide'>Fields</p>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<div
|
||||
onClick={() => handleSetIsFields(false)}
|
||||
|
|
@ -234,12 +243,12 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
|
|||
</div>
|
||||
|
||||
|
||||
<div className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
{!isPreview && <div className='flex items-center gap-1 cursor-pointer text-xs px-2 py-1 rounded-sm'>
|
||||
<RiResetRightLine
|
||||
className={`${transactionSaving ? "animate-spin" : ""} transition duration-300`}
|
||||
/>
|
||||
<p className="tracking-wide transition-all">{transactionSaving ? `Saving ${dots}` : "Save"}</p>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
|
||||
|
|
@ -293,6 +302,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
|
|||
</div>
|
||||
|
||||
{/* Send */}
|
||||
{!isPreview && <>
|
||||
<div
|
||||
id='send-sign-dash-btn'
|
||||
onClick={() => handleSend()}
|
||||
|
|
@ -311,6 +321,7 @@ const NavTwo: React.FC<NavTwoProps> = ({ transactionSaving, isFields, setIsField
|
|||
</p>
|
||||
<FaSignature className='text-white text-md' />
|
||||
</div>}
|
||||
</>}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -302,9 +302,7 @@ const RightSideBar: React.FC<DroppedItemProps> = ({ droppedItems, selectedTag, s
|
|||
<CurrTimeStampAttr type={selectedTag.type} handlePropertyChange={handlePropertyChange} />
|
||||
}
|
||||
|
||||
{selectedTag.label &&
|
||||
<LabelAttr label={selectedTag.label} handlePropertyChange={handlePropertyChange} />}
|
||||
|
||||
<LabelAttr label={selectedTag.label ?? ""} handlePropertyChange={handlePropertyChange} />
|
||||
<ToolTipAttr tooltip={selectedTag.tooltip ?? ""} handlePropertyChange={handlePropertyChange} />
|
||||
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import { LuSignature } from "react-icons/lu";
|
||||
import SMELogo from "../assets/SMELogo2.png";
|
||||
import { FaFileSignature } from "react-icons/fa";
|
||||
|
||||
|
||||
interface StyleProps {
|
||||
classStyle?: string;
|
||||
|
|
@ -11,8 +14,8 @@ const LoaderUi = ({ classStyle = '' }: StyleProps) => {
|
|||
<div className="absolute inset-0 bg-black/30 opacity-60"></div>
|
||||
|
||||
{/* Loader box */}
|
||||
<div className="relative z-10 bg-[var(--loaderBg)] flex flex-col justify-center items-center gap-2 px-16 py-8 rounded-md shadow-lg">
|
||||
<LuSignature className="text-4xl font-bold animate-text" />
|
||||
<div className="w-60 relative z-10 bg-[var(--loaderBg)] flex flex-col justify-center items-center gap-2 px-16 py-8 rounded-md shadow-lg">
|
||||
<FaFileSignature className="text-4xl font-bold animate-text" />
|
||||
<p>Loading...!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ canvas {
|
|||
}
|
||||
|
||||
50% {
|
||||
color: #41644A;
|
||||
color: var(--primary);
|
||||
/* Tailwind blue-500 */
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue