import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import { toast, Bounce } from 'react-toastify'; import api from '../../config/api'; import { hideLoader, showLoader } from './loaderSlice'; import { AxiosError } from 'axios'; // import { showLoader, hideLoader } from '../loader/loaderSlice'; // ----------------------- // Document Type // ----------------------- export interface Document { id?: string; name: string; url: string; type: string; pageCount?: number; [key: string]: any; } // ----------------------- // Initial State // ----------------------- export enum DocumentErrorEnum { UNSUPPORTED_DOC_TYPE = "UNSUPPORTED_DOC_TYPE", FILE_SIZE_EXCEEDED = "FILE_SIZE_EXCEEDED" } export interface DocumentErrorInt { [DocumentErrorEnum.UNSUPPORTED_DOC_TYPE]: string[]; [DocumentErrorEnum.FILE_SIZE_EXCEEDED]: string[]; } interface DocumentState { documents: Document[]; loading: boolean; error: DocumentErrorInt; } const initialState: DocumentState = { documents: [], loading: false, error: { [DocumentErrorEnum.UNSUPPORTED_DOC_TYPE]: [], [DocumentErrorEnum.FILE_SIZE_EXCEEDED]: [] }, }; // ----------------------- // Thunk: Fetch Documents // ----------------------- export const fetchDocuments = createAsyncThunk< Document[], { accountId: string; transactionId: string } >( 'documents/fetchDocuments', async ({ accountId, transactionId }, { rejectWithValue, getState }) => { try { const state = getState() as { selectedRecipients: { transactionTemplateToggle: string }; }; const toggle = state.selectedRecipients.transactionTemplateToggle; const response = await api.get( `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/documents?include_pages=true` ); const data = response.data as Document[]; 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; } catch (error: any) { return rejectWithValue(error.response?.data || 'Failed to fetch documents'); } } ); // ----------------------- // Thunk: Upload Documents // ----------------------- const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export const uploadDocuments = createAsyncThunk< Document[], { formData: FormData; accountId: string; transactionId: string } >( 'documents/uploadDocuments', async ({ formData, accountId, transactionId }, { dispatch, rejectWithValue, getState }) => { try { dispatch(showLoader('Uploading documents...')); const state = getState() as { selectedRecipients: { transactionTemplateToggle: string }; }; const toggle = state.selectedRecipients.transactionTemplateToggle; let response = []; for (let i = 0; i < formData.getAll("file").length; i++) { const currentFile = formData.getAll("file")[i] as File; // ---- Client-side validation ---- if (currentFile.type != "application/pdf") { response.push({ error: true, docName: currentFile.name, reason: DocumentErrorEnum.UNSUPPORTED_DOC_TYPE, }); continue; // skip upload } if (currentFile.size > 10 * 1024 * 1024) { response.push({ error: true, docName: currentFile.name, reason: DocumentErrorEnum.FILE_SIZE_EXCEEDED, }); continue; // skip upload } const file = new FormData(); file.append("file", currentFile); try { const res = await api.post( `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/documents`, file, { headers: { 'Content-Type': 'multipart/form-data', }, } ); response.push(res.data); } catch (e: unknown) { if (e instanceof AxiosError && (e.response?.status == 415)) { response.push({ error: true, docName: (formData.getAll("file")[i] as File).name, reason: DocumentErrorEnum.UNSUPPORTED_DOC_TYPE }); } else if (e instanceof AxiosError && (e.response?.status == 413)) { response.push({ error: true, docName: (formData.getAll("file")[i] as File).name, reason: DocumentErrorEnum.FILE_SIZE_EXCEEDED }); } } } // await sleep(5000); dispatch(hideLoader()); return [...response]; } catch (error: any) { dispatch(hideLoader()); // toast.error('Error Uploading Document', { // position: 'bottom-right', // autoClose: 3000, // theme: 'light', // transition: Bounce, // }); return rejectWithValue('Failed Uploading Documents'); } } ); export const handleMultipleTransactionThunk = createAsyncThunk< Document[], { formData: FormData; accountId: string; transactionId: string } >( 'documents/handleMultipleTransactionThunk', async ({ formData, accountId }, { dispatch, rejectWithValue, getState }) => { try { const state = getState() as { selectedRecipients: { transactionTemplateToggle: string }; }; const toggle = state.selectedRecipients.transactionTemplateToggle; let response = []; for (let i = 0; i < formData.getAll("file").length; i++) { const currentFile = formData.getAll("file")[i] as File; dispatch(showLoader(`Creating Transaction ${i + 1} ...`)); const transactionRes = await api.post(`/api/v1/accounts/${accountId}/transactions`, { "subject": currentFile.name }); const transactionId = transactionRes.data.id dispatch(showLoader('Uploading documents...')); // ---- Client-side validation ---- if (currentFile.type != "application/pdf") { response.push({ error: true, docName: currentFile.name, reason: DocumentErrorEnum.UNSUPPORTED_DOC_TYPE, }); continue; // skip upload } if (currentFile.size > 10 * 1024 * 1024) { response.push({ error: true, docName: currentFile.name, reason: DocumentErrorEnum.FILE_SIZE_EXCEEDED, }); continue; // skip upload } const file = new FormData(); file.append("file", currentFile); try { const res = await api.post( `/api/v1/accounts/${accountId}/${toggle}/${transactionId}/documents`, file, { headers: { 'Content-Type': 'multipart/form-data', }, } ); response.push(res.data); } catch (e: unknown) { if (e instanceof AxiosError && (e.response?.status == 415)) { response.push({ error: true, docName: (formData.getAll("file")[i] as File).name, reason: DocumentErrorEnum.UNSUPPORTED_DOC_TYPE }); } else if (e instanceof AxiosError && (e.response?.status == 413)) { response.push({ error: true, docName: (formData.getAll("file")[i] as File).name, reason: DocumentErrorEnum.FILE_SIZE_EXCEEDED }); } } } // await sleep(5000); dispatch(hideLoader()); return [...response]; } catch (error: any) { dispatch(hideLoader()); return rejectWithValue('Failed Uploading Documents'); } } ); // ----------------------- // Slice // ----------------------- const documentsSlice = createSlice({ name: 'documents', initialState, reducers: { setDocuments: (state, action: PayloadAction) => { state.documents = action.payload; }, clearDocumentUploadErrors: (state) => { state.error = initialState.error; }, setDocProgComplete: (state, action: PayloadAction) => { const docIndex = state.documents.findIndex(doc => doc.id == action.payload); if (docIndex != -1) { state.documents[docIndex].isCompleted = true; } }, resetDocuments: (state) => { state.documents = []; }, deleteDocument: (state, action: PayloadAction) => { state.documents = state.documents.filter(doc => doc.id != action.payload); }, }, extraReducers: (builder) => { builder // fetchDocuments .addCase(fetchDocuments.pending, (state) => { state.loading = true; state.error = initialState.error; }) .addCase(fetchDocuments.fulfilled, (state, action: PayloadAction) => { state.loading = false; state.documents = action.payload; }) .addCase(fetchDocuments.rejected, (state, action) => { state.loading = false; state.error = initialState.error; // toast.error('Failed to fetch documents', { // position: 'bottom-right', // autoClose: 3000, // theme: 'light', // transition: Bounce, // }); }) // uploadDocuments .addCase(uploadDocuments.pending, (state) => { state.loading = true; state.error = initialState.error; }) .addCase(uploadDocuments.fulfilled, (state, action: PayloadAction) => { state.loading = false; action.payload.forEach((doc, indx) => { if ("error" in doc && doc.error) { if (doc.reason in state.error) { (state.error[doc.reason as DocumentErrorEnum.UNSUPPORTED_DOC_TYPE | DocumentErrorEnum.FILE_SIZE_EXCEEDED]).push(doc.docName) } } else { state.documents.push(doc); } }); }) .addCase(uploadDocuments.rejected, (state, action) => { state.loading = false; state.error = initialState.error; }) }, }); // ----------------------- // Exports // ----------------------- export const { deleteDocument, resetDocuments, setDocuments, setDocProgComplete, clearDocumentUploadErrors } = documentsSlice.actions; export default documentsSlice.reducer;