Meeting Changes | Hot Keys Shortcut Update

This commit is contained in:
=bhagwanSingh 2025-09-01 14:15:24 +05:30
parent f3b1fc021f
commit e23b132d53
18 changed files with 1342 additions and 1142 deletions

6
.env
View File

@ -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"

View File

@ -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 */}

BIN
src/assets/SMELogo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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" />

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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

View File

@ -0,0 +1,12 @@
import React from 'react'
import NavTwo from '../processStepTwo/NavTwo'
const FinalPreview = () => {
return (
<>
</>
)
}
export default FinalPreview

View File

@ -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}

View File

@ -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

View File

@ -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>}
</>}

View File

@ -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} />
{

View File

@ -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>

View File

@ -115,7 +115,7 @@ canvas {
}
50% {
color: #41644A;
color: var(--primary);
/* Tailwind blue-500 */
}
}