In App Navigation

This commit is contained in:
Bhagwan Singh 2026-05-22 18:22:41 +05:30
parent dcb1934cfa
commit f6e0b3b8af
7 changed files with 346 additions and 51 deletions

View File

@ -225,12 +225,11 @@ const StartNow = () => {
</button>
<button
onClick={() => navigate("/templates")}
className="w-full text-left px-4 py-2.5 text-xs hover:bg-slate-50 border-t border-slate-100 cursor-pointer flex gap-2 items-center"
>
<HiTemplate className='text-[var(--primary)]' />
<span
onClick={() => navigate("/templates")}
>Use a Template</span>
<span>Use a Template</span>
</button>
</div>
</div>

View File

@ -17,6 +17,12 @@ import Loader from '../../dashboard/Loader';
import { hideLoader, showLoader } from '../../../store/slices/loaderSlice';
import DroppedItemsTagsCon from './DroppedItemsTagsCon';
import DocumentPages from './documentPages/DocumentPages';
import {
initializeDroppedItems,
setDroppedItems,
clearTransactionTags,
incrementCounter,
} from '../../../store/slices/documentTagsSlice';
export interface DroppedItemsObj {
[key: string]: DroppedItem[]
@ -87,12 +93,16 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
const tagUndoRef = useRef<DroppedItem[] | []>([]);
const imageRefs = useRef<{ [key: string]: HTMLImageElement | null }>({});
const [imageSizes, setImageSizes] = useState<Record<string, { width: number; height: number }>>({});
const selectedRecipients = useSelector((state: RootState) => state.selectedRecipients.recipients);
// const selectedDocuments = useSelector((state: RootState) => state.selectedDocuments.selectedDocuments)
const selectedDocuments = useSelector((state: RootState) => state.documents.documents)
const [droppedItems, setDroppedItems] = useState<DroppedItemsObj>({});
// Redux state for dropped items
const transactionKey = `${accountId}_${transactionId}`;
const droppedItems = useSelector((state: RootState) => state.documentTags.items[transactionKey] || {});
const droppedItemsCounter = useSelector((state: RootState) => state.documentTags.counter[transactionKey] || 0);
interface HotKeysObj {
isActive: boolean;
@ -143,7 +153,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
}
}
setDroppedItems(recTags)
// Dispatch to Redux instead of local state
dispatch(initializeDroppedItems({
transactionKey,
items: recTags
}));
// Handle your response here
} catch (err) {
console.error(err);
@ -157,7 +171,6 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
}, [selectedRecipients, isPreview]);
// FETCHING TAG - END
const [droppedItemsCounter, setDroppedItemsCounter] = useState<number>(1);
const [selectedTag, setSelectedTag] = useState<{ key: string, index: number } | null>(null);
// NAVIGATING to Dashboard if Transaction is not created - START
@ -197,13 +210,17 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
if ("update" in tag && tag.update) {
updateTags.push(tag)
}
if (updateTags.length && "update" in updateTags[updateTags.length - 1]) {
delete updateTags[updateTags.length - 1].update
delete updateTags[updateTags.length - 1].icon
if (updateTags.length) {
const lastTag = updateTags[updateTags.length - 1];
if ("update" in lastTag || "icon" in lastTag) {
// Create new object without update and icon properties
const { update, icon, ...cleanTag } = lastTag as any;
updateTags[updateTags.length - 1] = cleanTag;
}
}
}
updatedData[recipientId] = updateTags.filter((tag, indx) => !tag.isDeleted);
updatedData[recipientId] = updateTags.filter((tag) => !tag.isDeleted);
}
const requests = Object.entries(updatedData)
@ -224,13 +241,18 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
try {
const responses = await Promise.all(requests);
// Dispatch Redux action instead of local state
const droppedItemsUpdateFalse = Object.fromEntries(
Object.entries(droppedItems).map(([recipientId, tags]) => {
return [recipientId, tags.map(tag => ({ ...tag, update: false }))];
})
)
setDroppedItems(droppedItemsUpdateFalse);
dispatch(setDroppedItems({
transactionKey,
items: droppedItemsUpdateFalse
}));
return responses;
} catch (error) {
console.error("Error submitting recipient tags:", error);
@ -302,10 +324,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
// HANDLE DROP - START
const handleDrop = (event: React.DragEvent<HTMLDivElement>, docId: string, documentId: number, pageIndex: number, originalWidth: number, originalHeight: number) => {
event.preventDefault();
const imageElement = document.getElementById(`${docId}-${pageIndex}`) as HTMLImageElement;
const pageRefKey = `${docId}_${pageIndex}`;
const imageElement = imageRefs.current[pageRefKey] || document.getElementById(`${docId}-${pageIndex}`) as HTMLImageElement;
const currentWidth = imageElement.offsetWidth;
const currentHeight = imageElement.offsetHeight;
const currentWidth = imageElement?.offsetWidth || originalWidth;
const currentHeight = imageElement?.offsetHeight || originalHeight;
const scaleX = currentWidth / originalWidth; // 350 / 700 = 0.5
const scaleY = currentHeight / originalHeight; // 225 / 450 = 0.5
@ -371,10 +394,10 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
label: dragElemData.label,
// subType: (dragElemData.label in droppedTagsDefaultField) ? droppedTagsDefaultField[dragElemData.label] : '',
dimensions: {
left: x,
top: y,
width: dragElemData.width,
height: dragElemData.height,
left: x / scaleX,
top: y / scaleY,
width: dragElemData.width / scaleX,
height: dragElemData.height / scaleY,
},
styles: null,
documentId: docId,
@ -409,8 +432,11 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
prepData],
}
setDroppedItems(dataToSet);
setDroppedItemsCounter((prev) => prev + 1);
dispatch(setDroppedItems({
transactionKey,
items: dataToSet
}));
dispatch(incrementCounter(transactionKey));
// setTimeout(() => {
// const tagIndex = key in droppedItems ? droppedItems[key].length : 0;
@ -440,9 +466,12 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
console.log("copiedTags", copiedTags)
console.log("updatedKey", updatedKey)
setDroppedItems(prevState => ({
...prevState,
[selectedDoc.key]: updatedKey
dispatch(setDroppedItems({
transactionKey,
items: {
...droppedItems,
[selectedDoc.key]: updatedKey
}
}));
// Handle Ctrl+V
@ -566,7 +595,21 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
// className={`w-[${(page.width / 2)}px] h-[${(page.height / 2)}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
id={`${doc.id}-${page.index}`}
className={`w-[${page.width / 2}px] h-[${page.height / 2}px] sm:w-[${page.width}px] sm:h-[${page.height}px]`}
ref={(el) => (imageRefs.current[page.index] = el)}
ref={(el) => {
const pageRefKey = `${doc.id}_${page.index}`;
imageRefs.current[pageRefKey] = el;
}}
onLoad={(event) => {
const pageRefKey = `${doc.id}_${page.index}`;
const el = event.currentTarget;
setImageSizes((prev) => ({
...prev,
[pageRefKey]: {
width: el.offsetWidth,
height: el.offsetHeight,
},
}));
}}
draggable="false"
width={page.width}
height={page.height}
@ -579,19 +622,28 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
<DroppedItemsTagsCon
pageWidth={page.width}
pageHeight={page.height}
renderedWidth={imageRefs.current[page.index]?.offsetWidth || 0}
renderedHeight={imageRefs.current[page.index]?.offsetHeight || 0}
renderedWidth={imageSizes[key]?.width || imageRefs.current[key]?.offsetWidth || 0}
renderedHeight={imageSizes[key]?.height || imageRefs.current[key]?.offsetHeight || 0}
ref={childRef}
droppedItems={droppedItems}
pageIdkey={key}
selectedTag={selectedTag}
selectedRecipients={selectedRecipients}
setSelectedTag={setSelectedTag}
setDroppedItems={setDroppedItems}
setDroppedItems={(updateFn: any) => {
const newItems = typeof updateFn === 'function'
? updateFn(droppedItems)
: updateFn;
dispatch(setDroppedItems({
transactionKey,
items: newItems
}));
}}
accountId={accountId}
transactionId={transactionId}
transactionTemplateToggle={transactionTemplateToggle}
tagUndoRef={tagUndoRef}
transactionKey={transactionKey}
/>
{/* Side details of documents Pages and document Name - START */}
@ -625,7 +677,15 @@ const DocumentContainer = forwardRef<DocumentContainerRef, DocumentContainerProp
{selectedTag && selectedTag != null && "key" in selectedTag && "index" in selectedTag && droppedItems[selectedTag.key] && droppedItems[selectedTag.key][selectedTag.index] && <TagProperties
droppedItems={droppedItems}
selectedTag={droppedItems[selectedTag.key][selectedTag.index]}
setDroppedItems={setDroppedItems}
setDroppedItems={(updateFn: any) => {
const newItems = typeof updateFn === 'function'
? updateFn(droppedItems)
: updateFn;
dispatch(setDroppedItems({
transactionKey,
items: newItems
}));
}}
selTagKey={selectedTag.key}
selTagIndx={selectedTag.index}
signClient={false}

View File

@ -9,6 +9,8 @@
// -- handleAddGroupItemClicked
import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
import { useDispatch } from 'react-redux'
import type { AppDispatch } from '../../../store/store'
import { DroppedItemsObj } from './DocumentContainer';
import typeToIconMap from '../../../helper/iconMap';
import { IoMdClose } from 'react-icons/io';
@ -17,6 +19,7 @@ import { FaQuestionCircle } from 'react-icons/fa';
import api from '../../../config/api';
import { Recipient } from '../../../store/slices/selectedRecipientsSlice';
import { DroppedItem } from '../../../utlity/types';
import { setDroppedItems as setDroppedItemsAction } from '../../../store/slices/documentTagsSlice';
interface DroppedItemsTagsConProps {
pageWidth: number,
@ -28,11 +31,12 @@ interface DroppedItemsTagsConProps {
selectedTag: { key: string; index: number } | null;
selectedRecipients: Recipient[];
setSelectedTag: React.Dispatch<React.SetStateAction<{ key: string; index: number } | null>>;
setDroppedItems: React.Dispatch<React.SetStateAction<DroppedItemsObj>>;
setDroppedItems: (updateFn: ((prevState: DroppedItemsObj) => DroppedItemsObj) | DroppedItemsObj) => void;
accountId: string;
transactionId: string;
transactionTemplateToggle: string;
tagUndoRef: React.MutableRefObject<DroppedItem[]>;
transactionKey: string;
}
// 👶 Child component with forwardRef
@ -50,9 +54,12 @@ const DroppedItemsTagsCon = forwardRef(({
accountId,
transactionId,
transactionTemplateToggle,
tagUndoRef
tagUndoRef,
transactionKey
}: DroppedItemsTagsConProps, ref) => {
const dispatch = useDispatch();
const positionRef = useRef<{ left: number; top: number }>({ left: 0, top: 0 });
const handleTagClick = (key: string, index: number) => {
@ -388,11 +395,17 @@ const DroppedItemsTagsCon = forwardRef(({
// check pageIndex match
const isSamePage = currentPageIndex == tagDocumentUpdate.pageIndex;
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
// same document + same page
if (isSameDocument && isSamePage) {
left = Math.max(0, Math.min(left, conScrollWidth - elementRect.width));
top = Math.max(0, Math.min(top, conScrollHeight - elementRect.height));
const normalizedLeft = left / scaleX;
const normalizedTop = top / scaleY;
return {
...prevState,
[key]: Array.isArray(prevState[key])
@ -406,8 +419,8 @@ const DroppedItemsTagsCon = forwardRef(({
tagDocumentUpdate.pageIndex,
dimensions: {
...item.dimensions,
left,
top,
left: normalizedLeft,
top: normalizedTop,
},
update: true,
}
@ -561,8 +574,11 @@ const DroppedItemsTagsCon = forwardRef(({
borderPositions.forEach(position => {
const borderElement = document.querySelector(`[border-group='${groupId}'][border-pos='${position}']`);
if (borderElement && borderElement.parentNode) {
borderElement.parentNode.removeChild(borderElement);
if (borderElement) {
borderElement.remove?.();
if (!borderElement.remove && borderElement.parentNode && borderElement.parentNode.contains(borderElement)) {
borderElement.parentNode.removeChild(borderElement);
}
}
});
};
@ -790,8 +806,8 @@ const DroppedItemsTagsCon = forwardRef(({
const IconComponent = typeToIconMap[type] || <FaQuestionCircle />;
const selectedRecipient = selectedRecipients.filter((obj, indx) => obj.id == item.recipientId);
const scaleX = renderedWidth / Math.round(pageWidth);
const scaleY = renderedHeight / Math.round(pageHeight);
const scaleX = renderedWidth && pageWidth ? renderedWidth / pageWidth : 1;
const scaleY = renderedHeight && pageHeight ? renderedHeight / pageHeight : 1;
return (
<React.Fragment key={index}>

View File

@ -254,11 +254,18 @@ const NavTwo: React.FC<NavTwoProps> = ({
{/* Left Side */}
<div className='flex justify-start items-center gap-2'>
<div>
{/* <div>
<Link to={"/"}>
<img width={120} src="/sme-colored.png" alt="Sign Made Easy" />
</Link>
</div>
</div> */}
{/* <div>
<Link to={"/"}>
<img width={120} src="/sme-colored.png" alt="Sign Made Easy" />
</Link>
</div> */}
<div
@ -467,8 +474,9 @@ const NavTwo: React.FC<NavTwoProps> = ({
{editRecipientsModal && accountId && transactionId && <>
<div className="w-full h-full absolute top-0 left-0 z-[1000] flex justify-center items-center">
<div className="w-full h-full bg-slate-100 opacity-85 blur-2xl absolute top-0 left-0"></div>
<div className="w-full h-[100vh] absolute top-0 left-0 z-[1000] flex justify-center items-center">
{/* <div className="w-full h-full bg-slate-100 opacity-40 blur-2xl absolute top-0 left-0"></div> */}
<div className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50" />
<div className="z-50 bg-white p-4 rounded-sm shadow-md">
<RecipientsCon
transactionTemplateToggle={transactionTemplateToggle}
@ -482,8 +490,9 @@ const NavTwo: React.FC<NavTwoProps> = ({
</>}
{messageModal && accountId && transactionId && <>
<div className="w-full h-full absolute top-0 left-0 z-[1000] flex justify-center items-center">
<div className="w-full h-full bg-slate-100 opacity-85 blur-2xl absolute top-0 left-0"></div>
<div className="w-full h-[100vh] absolute top-0 left-0 z-[1000] flex justify-center items-center">
{/* <div className="w-full h-full bg-slate-100 opacity-40 blur-2xl absolute top-0 left-0"></div> */}
<div className="fixed inset-0 bg-black/40 backdrop-blur-sm z-50" />
<div className="z-50 bg-white p-4 rounded-sm shadow-md">
<MessageCon
transactionTemplateToggle={transactionTemplateToggle}

View File

@ -0,0 +1,218 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// Define the interface matching DocumentContainer.tsx usage
export interface DroppedItem {
id?: string;
icon?: any;
label?: string;
type: string;
subType?: string;
dimensions: {
left: number;
top: number;
width: number;
height: number;
};
styles?: any;
documentId: string;
pageIndex: number;
recipientId: string | number;
recipientIndx?: number;
required?: boolean;
update?: boolean;
isDeleted?: boolean;
group?: string;
isGroupExist?: boolean;
currentTimeStamp?: boolean;
decimalPlace?: number;
isCurrency?: boolean;
maskType?: string;
maskCharacter?: string;
format?: string;
regex?: string[];
[key: string]: any; // Allow other properties
}
export interface DroppedItemsObj {
[key: string]: DroppedItem[];
}
export interface DocumentTagsState {
items: {
[transactionKey: string]: DroppedItemsObj; // Key: `${accountId}_${transactionId}`
};
counter: {
[transactionKey: string]: number;
};
isLoading: {
[transactionKey: string]: boolean;
};
}
const initialState: DocumentTagsState = {
items: {},
counter: {},
isLoading: {},
};
const documentTagsSlice = createSlice({
name: 'documentTags',
initialState,
reducers: {
/**
* Initialize or replace all dropped items for a transaction
* Used when fetching tags from API
*/
initializeDroppedItems: (
state,
action: PayloadAction<{
transactionKey: string;
items: DroppedItemsObj;
}>
) => {
const { transactionKey, items } = action.payload;
state.items[transactionKey] = items;
state.isLoading[transactionKey] = false;
},
/**
* Set all dropped items (replaces existing)
* Used when user adds/modifies tags in current session
*/
setDroppedItems: (
state,
action: PayloadAction<{
transactionKey: string;
items: DroppedItemsObj;
}>
) => {
const { transactionKey, items } = action.payload;
state.items[transactionKey] = items;
},
/**
* Add a dropped item to specific page
*/
addDroppedItem: (
state,
action: PayloadAction<{
transactionKey: string;
pageKey: string;
item: DroppedItem;
}>
) => {
const { transactionKey, pageKey, item } = action.payload;
if (!state.items[transactionKey]) {
state.items[transactionKey] = {};
}
if (!state.items[transactionKey][pageKey]) {
state.items[transactionKey][pageKey] = [];
}
state.items[transactionKey][pageKey].push(item);
state.counter[transactionKey] = (state.counter[transactionKey] || 0) + 1;
},
/**
* Update dropped items for a specific page
*/
updatePageItems: (
state,
action: PayloadAction<{
transactionKey: string;
pageKey: string;
items: DroppedItem[];
}>
) => {
const { transactionKey, pageKey, items } = action.payload;
if (!state.items[transactionKey]) {
state.items[transactionKey] = {};
}
state.items[transactionKey][pageKey] = items;
},
/**
* Remove an item by index from a page
*/
removeDroppedItem: (
state,
action: PayloadAction<{
transactionKey: string;
pageKey: string;
index: number;
}>
) => {
const { transactionKey, pageKey, index } = action.payload;
if (state.items[transactionKey]?.[pageKey]) {
state.items[transactionKey][pageKey].splice(index, 1);
}
},
/**
* Update a specific item by index
*/
updateDroppedItem: (
state,
action: PayloadAction<{
transactionKey: string;
pageKey: string;
index: number;
updates: Partial<DroppedItem>;
}>
) => {
const { transactionKey, pageKey, index, updates } = action.payload;
if (state.items[transactionKey]?.[pageKey]?.[index]) {
state.items[transactionKey][pageKey][index] = {
...state.items[transactionKey][pageKey][index],
...updates,
};
}
},
/**
* Clear all tags for a specific transaction
*/
clearTransactionTags: (state, action: PayloadAction<string>) => {
const transactionKey = action.payload;
delete state.items[transactionKey];
delete state.counter[transactionKey];
delete state.isLoading[transactionKey];
},
/**
* Increment counter for a transaction
*/
incrementCounter: (state, action: PayloadAction<string>) => {
const transactionKey = action.payload;
state.counter[transactionKey] = (state.counter[transactionKey] || 0) + 1;
},
/**
* Set loading state
*/
setLoading: (
state,
action: PayloadAction<{
transactionKey: string;
isLoading: boolean;
}>
) => {
const { transactionKey, isLoading } = action.payload;
state.isLoading[transactionKey] = isLoading;
},
},
});
export const {
initializeDroppedItems,
setDroppedItems,
addDroppedItem,
updatePageItems,
removeDroppedItem,
updateDroppedItem,
clearTransactionTags,
incrementCounter,
setLoading,
} = documentTagsSlice.actions;
export default documentTagsSlice.reducer;

View File

@ -2,7 +2,6 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
/* TYPES */
export interface Position {
top: number;
left: number;
@ -43,8 +42,6 @@ export interface Tag {
/* PAYLOAD TYPES */
interface AddTagPayload {
recipientId: string;
tag: Tag;
@ -99,7 +96,6 @@ const localTagsSlice = createSlice({
reducers: {
/* ------------------------------ ADD TAG ----------------------------- */
addTag: (state, action: PayloadAction<AddTagPayload>) => {
const { recipientId, tag } = action.payload;
@ -111,7 +107,6 @@ const localTagsSlice = createSlice({
},
/* ---------------------------- UPDATE TAG ---------------------------- */
updateTag: (state, action: PayloadAction<UpdateTagPayload>) => {
const { recipientId, tagId, updatedData } = action.payload;
@ -125,7 +120,6 @@ const localTagsSlice = createSlice({
},
/* ---------------------------- REMOVE TAG ---------------------------- */
removeTag: (state, action: PayloadAction<RemoveTagPayload>) => {
const { recipientId, tagId } = action.payload;
@ -140,7 +134,6 @@ const localTagsSlice = createSlice({
},
/* ------------------------- REPLACE ALL TAGS ------------------------- */
replaceRecipientTags: (
state,
action: PayloadAction<ReplaceRecipientTagsPayload>
@ -155,7 +148,6 @@ const localTagsSlice = createSlice({
},
/* -------------------------- CLEAR RECIPIENT ------------------------- */
clearRecipientTags: (
state,
action: PayloadAction<string>
@ -164,7 +156,6 @@ const localTagsSlice = createSlice({
},
/* ----------------------------- CLEAR ALL ---------------------------- */
clearTags: () => {
return initialState;
},

View File

@ -72,6 +72,7 @@ import documentReducer from "./slices/documentSlice";
import tagsReducer from "./slices/tagsSlice";
import transactionReducer from "./slices/transactionSlice";
import signatureReducer from "./slices/signatureSlice";
import documentTagsReducer from "./slices/documentTagsSlice";
const rootReducer = combineReducers({
user: userReducer,
@ -83,6 +84,7 @@ const rootReducer = combineReducers({
tags: tagsReducer,
transaction: transactionReducer,
signature: signatureReducer,
documentTags: documentTagsReducer,
});
export const store = configureStore({