322 lines
9.9 KiB
TypeScript
322 lines
9.9 KiB
TypeScript
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<Document[]>) => {
|
|
state.documents = action.payload;
|
|
},
|
|
clearDocumentUploadErrors: (state) => {
|
|
state.error = initialState.error;
|
|
},
|
|
setDocProgComplete: (state, action: PayloadAction<string>) => {
|
|
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<string>) => {
|
|
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<Document[]>) => {
|
|
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<Document[]>) => {
|
|
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;
|