From e9f51d6ea036aa73f62caac86d4b67deba97373c Mon Sep 17 00:00:00 2001 From: Ayobami Date: Sun, 6 Jul 2025 23:10:29 +0100 Subject: [PATCH] Refactor; add, edit and delete contract forms logic and api calls --- package.json | 3 - .../ContractFormsModal/ContractFormsModal.tsx | 10 ++- .../ContractFormsSuccessToast.tsx | 35 ----------- .../Forms/ContractFormsSuccessToast/index.ts | 1 - .../ContractFormsToast/ContractFormsToast.tsx | 44 +++++++++++++ .../contractFormsToast.module.css} | 6 +- .../Forms/ContractFormsToast/index.ts | 1 + .../FormTabs/DeleteToast/DeleteToast.tsx | 63 +++++++++++-------- src/shared/components/Forms/index.ts | 2 +- .../AddContractForms/AddContractForms.tsx | 21 ++++++- .../EditContractForms/EditContractForms.tsx | 22 ++++++- .../FormTabs/ContractForms/ContractForms.tsx | 45 ++++++++++--- src/shared/containers/Forms/actions.ts | 46 ++++++++------ 13 files changed, 197 insertions(+), 102 deletions(-) delete mode 100644 src/shared/components/Forms/ContractFormsSuccessToast/ContractFormsSuccessToast.tsx delete mode 100644 src/shared/components/Forms/ContractFormsSuccessToast/index.ts create mode 100644 src/shared/components/Forms/ContractFormsToast/ContractFormsToast.tsx rename src/shared/components/Forms/{ContractFormsSuccessToast/contractFormsSuccessToast.module.css => ContractFormsToast/contractFormsToast.module.css} (94%) create mode 100644 src/shared/components/Forms/ContractFormsToast/index.ts diff --git a/package.json b/package.json index 7fe2ea3..43a88b7 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,6 @@ "version": "1.0.0", "main": "index.js", "license": "MIT", - "engines": { - "node": "18.x" - }, "scripts": { "start": "cross-env NODE_ENV=development webpack serve --config config/webpack.dev.js", "prep": "yarn && yarn prepare-husky && yarn start", diff --git a/src/shared/components/Forms/ContractFormsModal/ContractFormsModal.tsx b/src/shared/components/Forms/ContractFormsModal/ContractFormsModal.tsx index 0bba33c..60538d9 100644 --- a/src/shared/components/Forms/ContractFormsModal/ContractFormsModal.tsx +++ b/src/shared/components/Forms/ContractFormsModal/ContractFormsModal.tsx @@ -7,7 +7,7 @@ import { Label } from 'Components/Label'; import { TextBox } from 'Components/TextBox'; import { CheckBox } from 'Components/CheckBox'; import { TextArea } from 'Components/TextArea'; -import { ContractFormsSuccessToast } from '../ContractFormsSuccessToast'; +import { ContractFormsToast } from '../ContractFormsToast'; import classes from './contractFormsModal.module.css'; @@ -35,6 +35,7 @@ interface Props { submitText: string; showToast: boolean; toastMessage: string; + toastType?: string; onFormSubmit: (e: any) => void; onClickClose: (e: any) => void; handleChange: (e: any) => void; @@ -51,6 +52,7 @@ const ContractFormsModal = ({ formErrors, showToast, toastMessage, + toastType = 'success', onFormSubmit, onClickClose, handleChange, @@ -66,7 +68,7 @@ const ContractFormsModal = ({ leftHeaderIcon="projects" modalHeader modalCloseClick={onClickClose} - toast={} + toast={} >
@@ -139,6 +141,10 @@ const ContractFormsModal = ({ ); +ContractFormsModal.defaultProps = { + toastType: 'success', +}; + const ContractFormsModalMemo = memo(ContractFormsModal, areEqual); export { ContractFormsModalMemo as ContractFormsModal }; diff --git a/src/shared/components/Forms/ContractFormsSuccessToast/ContractFormsSuccessToast.tsx b/src/shared/components/Forms/ContractFormsSuccessToast/ContractFormsSuccessToast.tsx deleted file mode 100644 index 7dd9c31..0000000 --- a/src/shared/components/Forms/ContractFormsSuccessToast/ContractFormsSuccessToast.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { memo } from 'react'; - -import { areEqual } from 'Utils/equalityChecks'; -import { CheckedMarkSvg } from 'Components/Icons/CheckedMark'; - -import classes from './contractFormsSuccessToast.module.css'; - -export interface Props { - showToast: boolean; - message: string; -} - -const ContractFormsSuccessToast = ({ showToast = false, message }: Props) => ( -
-
- {message} - - - -
-
-); - -ContractFormsSuccessToast.defaultProps = {}; - -const ContractFormsSuccessToastMemo = memo(ContractFormsSuccessToast, areEqual); - -export { ContractFormsSuccessToastMemo as ContractFormsSuccessToast }; diff --git a/src/shared/components/Forms/ContractFormsSuccessToast/index.ts b/src/shared/components/Forms/ContractFormsSuccessToast/index.ts deleted file mode 100644 index 0a1dd81..0000000 --- a/src/shared/components/Forms/ContractFormsSuccessToast/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ContractFormsSuccessToast } from './ContractFormsSuccessToast'; diff --git a/src/shared/components/Forms/ContractFormsToast/ContractFormsToast.tsx b/src/shared/components/Forms/ContractFormsToast/ContractFormsToast.tsx new file mode 100644 index 0000000..1f2701e --- /dev/null +++ b/src/shared/components/Forms/ContractFormsToast/ContractFormsToast.tsx @@ -0,0 +1,44 @@ +import React, { memo } from 'react'; + +import { areEqual } from 'Utils/equalityChecks'; +import { CheckedMarkSvg } from 'Components/Icons/CheckedMark'; + +import classes from './contractFormsToast.module.css'; + +export interface Props { + showToast: boolean; + message: string; + type?: 'success' | 'error'; +} + +const ContractFormsToast = ({ showToast = false, message, type = 'success' }: Props) => { + const getToastClass = () => (type === 'success' ? classes.toastSuccess : classes.toastWarning); + + return ( +
+
+ {message} + {type === 'success' && ( + + + + )} +
+
+ ); +}; + +ContractFormsToast.defaultProps = { + type: 'success', +}; + +const ContractFormsToastMemo = memo(ContractFormsToast, areEqual); + +export { ContractFormsToastMemo as ContractFormsToast }; diff --git a/src/shared/components/Forms/ContractFormsSuccessToast/contractFormsSuccessToast.module.css b/src/shared/components/Forms/ContractFormsToast/contractFormsToast.module.css similarity index 94% rename from src/shared/components/Forms/ContractFormsSuccessToast/contractFormsSuccessToast.module.css rename to src/shared/components/Forms/ContractFormsToast/contractFormsToast.module.css index 0df7c9f..9170dd1 100644 --- a/src/shared/components/Forms/ContractFormsSuccessToast/contractFormsSuccessToast.module.css +++ b/src/shared/components/Forms/ContractFormsToast/contractFormsToast.module.css @@ -57,7 +57,11 @@ .toastWarning { background-color: #fff0f0; - border-bottom: 1px solid #e82828; + border-bottom: 1px solid #e82828 !important; +} + +.toastWarning .toastText { + color: #d32f2f !important; } .toastCloseIcon { diff --git a/src/shared/components/Forms/ContractFormsToast/index.ts b/src/shared/components/Forms/ContractFormsToast/index.ts new file mode 100644 index 0000000..fe724d7 --- /dev/null +++ b/src/shared/components/Forms/ContractFormsToast/index.ts @@ -0,0 +1 @@ +export { ContractFormsToast } from './ContractFormsToast'; diff --git a/src/shared/components/Forms/FormTabs/DeleteToast/DeleteToast.tsx b/src/shared/components/Forms/FormTabs/DeleteToast/DeleteToast.tsx index e507b9c..6dda044 100644 --- a/src/shared/components/Forms/FormTabs/DeleteToast/DeleteToast.tsx +++ b/src/shared/components/Forms/FormTabs/DeleteToast/DeleteToast.tsx @@ -10,36 +10,45 @@ export interface Props { isDisplayed: boolean; message: string; closeToast: (e: any) => void; + type?: 'success' | 'error'; } -const DeleteToast = ({ isDisplayed = false, message, closeToast }: Props) => ( -
-
- {message} - - - -
-
-
-
-); +const DeleteToast = ({ isDisplayed = false, message, closeToast, type = 'success' }: Props) => { + const getToastClass = () => (type === 'success' ? classes.toastSuccess : classes.toastWarning); -DeleteToast.defaultProps = {}; + return ( +
+
+ {message} + {type === 'success' && ( + + + + )} +
+
+
+
+ ); +}; + +DeleteToast.defaultProps = { + type: 'success', +}; const DeleteToastMemo = memo(DeleteToast, areEqual); diff --git a/src/shared/components/Forms/index.ts b/src/shared/components/Forms/index.ts index d1e7ef2..75a3f22 100644 --- a/src/shared/components/Forms/index.ts +++ b/src/shared/components/Forms/index.ts @@ -1,3 +1,3 @@ export { FormsList, ContractForms } from './FormTabs'; -export { ContractFormsSuccessToast } from './ContractFormsSuccessToast'; +export { ContractFormsToast } from './ContractFormsToast'; export { ContractFormsModal } from './ContractFormsModal'; diff --git a/src/shared/containers/Forms/AddContractForms/AddContractForms.tsx b/src/shared/containers/Forms/AddContractForms/AddContractForms.tsx index 65c4e25..95faa28 100644 --- a/src/shared/containers/Forms/AddContractForms/AddContractForms.tsx +++ b/src/shared/containers/Forms/AddContractForms/AddContractForms.tsx @@ -33,6 +33,8 @@ const AddContractForms = ({ isOpen, onClose }: Props) => { const [formData, setFormData] = useState({ ...initData }); const [toastMessage, setToastMessage] = useState(''); const [showToast, setShowToast] = useState(false); + const [toastType, setToastType] = useState<'success' | 'error'>('success'); + const [wasAdding, setWasAdding] = useState(false); const textAreaRef = useRef(null); const handleChange = useCallback((e: React.ChangeEvent) => { @@ -89,6 +91,7 @@ const AddContractForms = ({ isOpen, onClose }: Props) => { useEffect(() => { if (isAdded) { + setToastType('success'); setToastMessage('Contract Form Added'); setShowToast(true); setFormData({ ...initData }); @@ -102,7 +105,22 @@ const AddContractForms = ({ isOpen, onClose }: Props) => { dispatch(setContractFormAdded(false)); }, 1500); } - }, [isAdded]); + }, [isAdded, companyId, dispatch, onClose]); + + useEffect(() => { + if (isAdding) { + setWasAdding(true); + } else if (wasAdding && !isAdded) { + // API call finished but form wasn't added (error occurred) + setToastType('error'); + setToastMessage('Failed to add contract form. Please try again.'); + setShowToast(true); + setWasAdding(false); + setTimeout(() => { + setShowToast(false); + }, 3000); + } + }, [isAdding, isAdded, wasAdding]); useEffect(() => { if (!isOpen) { @@ -123,6 +141,7 @@ const AddContractForms = ({ isOpen, onClose }: Props) => { }} showToast={showToast} toastMessage={toastMessage} + toastType={toastType} onFormSubmit={onFormSubmit} onClickClose={(e) => { e.preventDefault(); diff --git a/src/shared/containers/Forms/EditContractForms/EditContractForms.tsx b/src/shared/containers/Forms/EditContractForms/EditContractForms.tsx index f535b47..5bc50a9 100644 --- a/src/shared/containers/Forms/EditContractForms/EditContractForms.tsx +++ b/src/shared/containers/Forms/EditContractForms/EditContractForms.tsx @@ -33,6 +33,8 @@ const EditContractForms = ({ isOpen, onClose, initData }: Props) => { }); const [toastMessage, setToastMessage] = useState(''); const [showToast, setShowToast] = useState(false); + const [toastType, setToastType] = useState<'success' | 'error'>('success'); + const [wasEditing, setWasEditing] = useState(false); const textAreaRef = useRef(null); useEffect(() => { @@ -47,6 +49,7 @@ const EditContractForms = ({ isOpen, onClose, initData }: Props) => { useEffect(() => { if (isEdited) { + setToastType('success'); setToastMessage('Contract Form Updated'); setShowToast(true); if (companyId) { @@ -59,8 +62,22 @@ const EditContractForms = ({ isOpen, onClose, initData }: Props) => { dispatch(setContractFormEdited(false)); }, 1500); } - // eslint-disable-next-line - }, [isEdited]); + }, [isEdited, companyId, dispatch, onClose]); + + useEffect(() => { + if (isEditing) { + setWasEditing(true); + } else if (wasEditing && !isEdited) { + // API call finished but form wasn't edited (error occurred) + setToastType('error'); + setToastMessage('Failed to update contract form. Please try again.'); + setShowToast(true); + setWasEditing(false); + setTimeout(() => { + setShowToast(false); + }, 3000); + } + }, [isEditing, isEdited, wasEditing]); useEffect(() => { if (!isOpen) { @@ -139,6 +156,7 @@ const EditContractForms = ({ isOpen, onClose, initData }: Props) => { }} showToast={showToast} toastMessage={toastMessage} + toastType={toastType} onFormSubmit={onFormSubmit} onClickClose={(e) => { e.preventDefault(); diff --git a/src/shared/containers/Forms/FormTabs/ContractForms/ContractForms.tsx b/src/shared/containers/Forms/FormTabs/ContractForms/ContractForms.tsx index 0be73db..5002b53 100644 --- a/src/shared/containers/Forms/FormTabs/ContractForms/ContractForms.tsx +++ b/src/shared/containers/Forms/FormTabs/ContractForms/ContractForms.tsx @@ -5,7 +5,7 @@ import { contractFormsSelector, fetchingContractFormsSelector, companyIdSelector, - // deletingContractFormSelector, + deletingContractFormSelector, contractFormDeletedSelector, } from 'Containers/Forms/selectors'; import { ContractForms } from 'Components/Forms'; @@ -20,13 +20,16 @@ const ContractFormsContainer = () => { const contractForms = useSelector(contractFormsSelector, areEqual); const fetching = useSelector(fetchingContractFormsSelector, areEqual); const companyId = useSelector(companyIdSelector, areEqual); - // const deleting = useSelector(deletingContractFormSelector, areEqual); + const deleting = useSelector(deletingContractFormSelector, areEqual); const deletedId = useSelector(contractFormDeletedSelector, areEqual); const [deleteModal, setDeleteModal] = useState({ isOpen: false, id: null, }); const [showDeletedToast, setShowDeletedToast] = useState(false); + const [deleteToastMessage, setDeleteToastMessage] = useState('Form Deleted'); + const [deleteToastType, setDeleteToastType] = useState<'success' | 'error'>('success'); + const [wasDeleting, setWasDeleting] = useState(false); const [showAddFormsModal, setShowAddFormsModal] = useState(false); const [editModal, setEditModal] = useState<{ isOpen: boolean; @@ -89,15 +92,34 @@ const ContractFormsContainer = () => { useEffect(() => { if (deletedId) { + // Success case + setDeleteToastType('success'); + setDeleteToastMessage('Form Deleted'); + setShowDeletedToast(true); dispatch(listCompanyContractForms(companyId)); - setShowDeletedToast(true); - - setTimeout(() => setShowDeletedToast(false), 1500); - - dispatch(setDeletedFormId(null)); + setTimeout(() => { + setShowDeletedToast(false); + dispatch(setDeletedFormId(null)); + }, 1500); } - }, [deletedId]); + }, [deletedId, companyId, dispatch]); + + useEffect(() => { + if (deleting) { + setWasDeleting(true); + } else if (wasDeleting && !deletedId) { + // API call finished but form wasn't deleted (error occurred) + setDeleteToastType('error'); + setDeleteToastMessage('Failed to delete form. Please try again.'); + setShowDeletedToast(true); + setWasDeleting(false); + + setTimeout(() => { + setShowDeletedToast(false); + }, 3000); + } + }, [deleting, deletedId, wasDeleting]); useEffect(() => { getContractForms(); @@ -128,7 +150,12 @@ const ContractFormsContainer = () => { } onDelete={handleDelete} /> - {deletedId && } + ); }; diff --git a/src/shared/containers/Forms/actions.ts b/src/shared/containers/Forms/actions.ts index d35fb7d..7343ae2 100644 --- a/src/shared/containers/Forms/actions.ts +++ b/src/shared/containers/Forms/actions.ts @@ -53,12 +53,12 @@ export const setDeletedFormId = (value: number | null) => async (dispatch: any) // Thunk to delete a contract form by id export const deleteContractForm = (contractFormId: number) => async (dispatch: any) => { dispatch({ type: DELETING_CONTRACT_FORM, payload: true }); + try { await handleApiRequest(dispatch, Api.delete(`/contract-forms/${contractFormId}`), '', DELETING_CONTRACT_FORM); + dispatch(setDeletedFormId(contractFormId)); - // dispatch({ type: CONTRACT_FORM_DELETED, payload: contractFormId }); } catch (error) { - // handle error if needed dispatch({ type: DELETING_CONTRACT_FORM, payload: false }); } }; @@ -73,16 +73,19 @@ export const setContractFormAdded = (value: boolean) => (dispatch: any) => { // Thunk to add a contract form export const addContractForm = (formData: FormRequestBody) => async (dispatch: any) => { dispatch({ type: ADDING_CONTRACT_FORM, payload: true }); - try { - await handleApiRequest( - dispatch, - Api.post('/contract-forms', formData), - 'CONTRACT_FORM_ERRORS', - ADDING_CONTRACT_FORM - ); + + const response = await handleApiRequest( + dispatch, + Api.post('/contract-forms', formData), + 'CONTRACT_FORM_ERRORS', + ADDING_CONTRACT_FORM + ); + + if (response) { + // API call succeeded dispatch(setContractFormAdded(true)); - } catch (error) { - dispatch({ type: ADDING_CONTRACT_FORM, payload: false }); + } else { + // API call failed dispatch(setContractFormAdded(false)); } }; @@ -97,16 +100,19 @@ export const setContractFormEdited = (value: boolean) => (dispatch: any) => { // Thunk to edit a contract form export const editContractForm = (formData: FormRequestBody, contractId: string) => async (dispatch: any) => { dispatch({ type: EDITING_CONTRACT_FORM, payload: true }); - try { - await handleApiRequest( - dispatch, - Api.put('/contract-forms', formData), - 'CONTRACT_FORM_ERRORS', - EDITING_CONTRACT_FORM - ); + + const response = await handleApiRequest( + dispatch, + Api.put('/contract-forms', formData), + 'CONTRACT_FORM_ERRORS', + EDITING_CONTRACT_FORM + ); + + if (response) { + // API call succeeded dispatch(setContractFormEdited(true)); - } catch (error) { - dispatch({ type: EDITING_CONTRACT_FORM, payload: false }); + } else { + // API call failed dispatch(setContractFormEdited(false)); } };