import { createSlice, PayloadAction, createAsyncThunk } from '@reduxjs/toolkit'; import { toast } from 'react-toastify'; import colors from '../../utlity/colorsArr'; import api from '../../config/api'; export enum recipientType { SIGNER = "Signer", INPERSONSIGNER = "InPersonSigner", CC = "CC" } export interface Recipient { id?: string | null; firstName: string; lastName: string; routingOrder: number; email: string; hostName: string; hostEmail: string; type: recipientType; color?: string; allowSenderToEdit: boolean; allowSenderToDelete: boolean; roleName: string provideOwnTags?: boolean; uuid?: string; newOne?: boolean; completed?: boolean; } export interface SelectedRecipientInt { recipients: Recipient[], signingOrder: boolean, transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE } interface AddRecipientArgs { recipientsData: Recipient[]; accountId: string; transactionId: string; transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE; } interface RemoveRecipientAsyncInterface { recipientId: number | null; accountId: string; transactionId: string; email: string; transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE; } export enum TRANSACTION_TEMPLATE_TOGGLE { TRANSACTIONS = "transactions", TEMPLATES = "templates" } const initialState: SelectedRecipientInt = { recipients: [], signingOrder: false, transactionTemplateToggle: TRANSACTION_TEMPLATE_TOGGLE.TRANSACTIONS }; // 1. Add Recipient Async export const addRecipientAsync = createAsyncThunk< Recipient[], { recipientsData: Recipient[]; accountId: string; transactionId: string } >( 'selectedRecipients/addRecipientAsync', async ({ recipientsData, accountId, transactionId }, { rejectWithValue, getState }) => { try { const state = getState() as { selectedRecipients: SelectedRecipientInt }; const toggle = state.selectedRecipients.transactionTemplateToggle; const updateRecipientsData = recipientsData.filter(recipient => recipient.id != null); const createRecipientsData = recipientsData.filter(recipient => recipient.id == null); const basePath = `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/recipients`; const resUpdate = updateRecipientsData.length ? await api.put(basePath, updateRecipientsData) : { data: [] }; const resCreate = createRecipientsData.length ? await api.post(basePath, createRecipientsData) : { data: [] }; return [...resCreate.data, ...resUpdate.data] as Recipient[]; } catch (err: any) { return rejectWithValue(err.response?.data || "Failed to add recipient"); } } ); // 2. Remove Recipient Async export const removeRecipientAsync = createAsyncThunk< string | number, { recipientId: number | null; accountId: string; transactionId: string; email: string } >( 'selectedRecipients/removeRecipientAsync', async ({ recipientId, accountId, transactionId, email }, { rejectWithValue, getState }) => { try { const state = getState() as { selectedRecipients: SelectedRecipientInt }; const toggle = state.selectedRecipients.transactionTemplateToggle; const basePath = `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/recipients`; // return if (recipientId) { await api.delete(`${basePath}/${recipientId}`); return recipientId; } return email; } catch (err: any) { return rejectWithValue("Failed to remove recipient"); } } ); // 3. Fetch Recipients Async export const fetchRecipients = createAsyncThunk< Recipient[], { accountId: string; transactionId: string } >( 'selectedRecipients/fetchRecipients', async ({ accountId, transactionId }, { rejectWithValue, getState }) => { try { const state = getState() as { selectedRecipients: SelectedRecipientInt }; const toggle = state.selectedRecipients.transactionTemplateToggle; const basePath = `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/recipients`; const response = await api.get(basePath); let data = response.data as Recipient[]; // data = data.filter((recipient, indx) => { // return recipient.type != "CC"; // }) // if (data.length == 0 && window.location.pathname.includes('/review-and-tag')) { // window.location.href = window.location.href.replace("review-and-tag", "setup-transaction") // ✅ navigate using window // } return data as Recipient[]; } catch (err: any) { return rejectWithValue(err.response?.data || "Failed to fetch recipients"); } } ); const selectedRecipientsSlice = createSlice({ name: 'selectedRecipients', initialState, reducers: { addRecipient: (state, action: PayloadAction) => { if (action.payload.type == recipientType.INPERSONSIGNER) { state.signingOrder = true; state.recipients = [action.payload, ...state.recipients]; } else { state.recipients.push(action.payload); } }, resetRecipients: (state) => { state.recipients = []; }, toggleToTemplates: (state) => { state.transactionTemplateToggle = TRANSACTION_TEMPLATE_TOGGLE.TEMPLATES; }, setSelectedRecipients: (state, action: PayloadAction) => { for (const [index, recipient] of action.payload.entries()) { if ((recipient.type == recipientType.INPERSONSIGNER && index > 0) && action.payload[index - 1].type != recipientType.INPERSONSIGNER) { return state; } } state.recipients = action.payload; }, setSigningOrder: (state, action: PayloadAction) => { for (const recipient of state.recipients) { if (recipient.type == recipientType.INPERSONSIGNER) { if (state.signingOrder) return state; state.signingOrder = true; return state; } } state.signingOrder = action.payload; }, removeSelectedRecipient: (state, action: PayloadAction) => { state.recipients = state.recipients.filter((recipient) => recipient.uuid != action.payload.uuid); }, removeAllRecipients: (state) => { state.recipients = []; }, addColorsToRecipient: (state) => { for (const [index, recipient] of state.recipients.entries()) { recipient.color = colors[index]; } return state; }, updateRecipientOrder: (state, action: PayloadAction<{ id: string; routingOrder: number }>) => { const { id, routingOrder } = action.payload; const recipient = state.recipients.find((r) => r.id == id || r.uuid == id); if (recipient) { recipient.routingOrder = routingOrder; } }, reorderAllRecipientAscOrder: (state) => { state.recipients = state.recipients.sort((a, b) => a.routingOrder - b.routingOrder); }, updateRecipientByIndxAndRo: (state, action: PayloadAction<{ indx1: number; ro1: number, indx2: number; ro2: number }>) => { const indx1 = action.payload.indx1 const indx2 = action.payload.indx2 const ro1 = action.payload.ro1 const ro2 = action.payload.ro2 const temp = { ...state.recipients[indx1], routingOrder: ro1 }; state.recipients[indx1] = { ...state.recipients[indx2], routingOrder: ro2 }; state.recipients[indx2] = temp; }, updateRecipientIds: (state, action: PayloadAction) => { for (const recObj of state.recipients) { for (const recObj2 of action.payload) { recObj.id = recObj2.id; } } }, updateRecipient(state, action: PayloadAction) { const payloadUuid = String(action.payload.uuid); // First try matching by id let index = state.recipients.findIndex( r => String(r.id) == payloadUuid ); // If not found, try matching by uuid if (index == -1) { index = state.recipients.findIndex( r => String(r.uuid) == payloadUuid ); } if (index != -1) { // Merge existing recipient with new values state.recipients[index] = { ...state.recipients[index], ...action.payload }; } else { console.warn('Recipient not found with uuid:', payloadUuid); } } , resetSelRecipient: () => initialState }, extraReducers: (builder) => { builder .addCase(fetchRecipients.pending, () => { }) .addCase(fetchRecipients.fulfilled, (state, action: PayloadAction) => { const assignedRecipients = action.payload.map((recipient, index) => ({ ...recipient, color: colors[index], })); state.recipients = assignedRecipients; }) .addCase(fetchRecipients.rejected, (state, action) => { console.error("Failed to fetch recipients:", action.payload); // toast.error("Failed to fetch recipients", { // position: "bottom-right", // autoClose: 5000, // theme: "light", // }); }) .addCase(addRecipientAsync.pending, () => { }) .addCase(addRecipientAsync.fulfilled, (state, action: PayloadAction) => { state.recipients = action.payload.map((recipient, index) => ({ ...recipient, color: colors[index % colors.length], })); }) .addCase(addRecipientAsync.rejected, (state, action) => { console.error("Failed to add recipients:", action.payload); // toast.error("Failed to add recipients", { // position: "bottom-right", // autoClose: 5000, // theme: "light", // }); }) .addCase(removeRecipientAsync.pending, () => { }) .addCase(removeRecipientAsync.fulfilled, (state, action: PayloadAction) => { state.recipients = state.recipients.filter( (recipient) => recipient.id != action.payload && recipient.email != action.payload ); }) .addCase(removeRecipientAsync.rejected, (state, action) => { console.error("Failed to delete recipient:", action.payload); // toast.error("Failed to delete recipient", { // position: "bottom-right", // autoClose: 5000, // theme: "light", // }); }); } }); export const { addRecipient, setSelectedRecipients, toggleToTemplates, removeSelectedRecipient, removeAllRecipients, setSigningOrder, addColorsToRecipient, updateRecipientIds, resetSelRecipient, updateRecipientOrder, updateRecipient, resetRecipients, reorderAllRecipientAscOrder, updateRecipientByIndxAndRo } = selectedRecipientsSlice.actions; export default selectedRecipientsSlice.reducer;