Skip to content

Commit 4b3af20

Browse files
committed
chore: refactor admin forms error display
1 parent 2397265 commit 4b3af20

8 files changed

Lines changed: 284 additions & 141 deletions

File tree

bciers/apps/administration/app/components/buttons/Review.tsx

Lines changed: 58 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import Modal from "@bciers/components/modal/Modal";
99
import IconButton from "@mui/material/IconButton";
1010
import CloseIcon from "@mui/icons-material/Close";
1111
import { Role, Status } from "@bciers/utils/src/enums";
12+
import {
13+
useValidationErrors,
14+
handleApiResponse,
15+
} from "@bciers/components/validationErrors";
1216

1317
interface Props {
1418
confirmApproveMessage: string;
@@ -36,9 +40,7 @@ const CloseButton = ({ onClose }: CloseProps) => {
3640
aria-label="close"
3741
color="inherit"
3842
size="small"
39-
onClick={() => {
40-
onClose();
41-
}}
43+
onClick={onClose}
4244
>
4345
<CloseIcon fontSize="inherit" />
4446
</IconButton>
@@ -56,11 +58,14 @@ const Review = ({
5658
onApprove,
5759
onReject,
5860
}: Readonly<Props>) => {
59-
const [errorList, setErrorList] = useState([] as any[]);
6061
const [successMessageList, setSuccessMessageList] = useState([] as any[]);
6162
const [modalState, setModalState] = useState("" as string);
6263
const [dismissAlert, setDismissAlert] = useState(false);
6364

65+
const { setErrors, renderedErrors } = useValidationErrors({
66+
config: {},
67+
});
68+
6469
const handleApprove = () => {
6570
setModalState("approve");
6671
};
@@ -74,25 +79,29 @@ const Review = ({
7479
};
7580

7681
const handleConfirmApprove = async () => {
82+
setErrors(undefined);
83+
setModalState("");
7784
const response = await onApprove();
78-
if (response.error) {
79-
setModalState("");
80-
return setErrorList([{ message: response.error }]);
85+
const isSuccess = handleApiResponse(response, setErrors);
86+
87+
if (!isSuccess) {
88+
return;
8189
}
8290

83-
setModalState("");
84-
return setSuccessMessageList([{ message: approvedMessage }]);
91+
setSuccessMessageList([{ message: approvedMessage }]);
8592
};
8693

8794
const handleConfirmReject = async () => {
95+
setErrors(undefined);
96+
setModalState("");
8897
const response = await onReject();
89-
if (response.error) {
90-
setModalState("");
91-
return setErrorList([{ message: response.error }]);
98+
const isSuccess = handleApiResponse(response, setErrors);
99+
100+
if (!isSuccess) {
101+
return;
92102
}
93103

94-
setModalState("");
95-
return setSuccessMessageList([{ message: declinedMessage }]);
104+
setSuccessMessageList([{ message: declinedMessage }]);
96105
};
97106

98107
const handleCloseAlert = () => {
@@ -102,7 +111,7 @@ const Review = ({
102111
const isReviewButtons =
103112
status !== Status.DECLINED &&
104113
role !== Role.ADMIN &&
105-
errorList.length === 0 &&
114+
!renderedErrors &&
106115
successMessageList.length === 0;
107116

108117
const isApprove = modalState === "approve";
@@ -113,7 +122,6 @@ const Review = ({
113122
return (
114123
<Box
115124
sx={{
116-
// 🛠️ to prevent leaving extra space when there is no content
117125
minHeight: "auto",
118126
width: "100%",
119127
marginBottom: isReviewButtons ? "16px" : "0",
@@ -191,56 +199,45 @@ const Review = ({
191199
<Note message={note} />
192200
</span>
193201
)}
194-
{
195-
<Box
202+
<Box
203+
sx={{
204+
width: "fit-content",
205+
minWidth: "fit-content",
206+
height: "fit-content",
207+
}}
208+
>
209+
<Button
210+
onClick={handleApprove}
211+
className="mr-2"
212+
color="success"
213+
variant="outlined"
214+
aria-label="Approve application"
215+
sx={{
216+
marginRight: "12px",
217+
border: "1px solid",
218+
fontWeight: "bold",
219+
}}
220+
>
221+
Approve as Administrator <RecommendIcon />
222+
</Button>
223+
<Button
224+
onClick={handleReject}
225+
color="error"
226+
variant="outlined"
227+
aria-label="Reject application"
196228
sx={{
197-
width: "fit-content",
198-
minWidth: "fit-content",
199-
height: "fit-content",
229+
border: "1px solid",
230+
fontWeight: "bold",
200231
}}
201232
>
202-
<Button
203-
onClick={handleApprove}
204-
className="mr-2"
205-
color="success"
206-
variant="outlined"
207-
aria-label="Approve application"
208-
sx={{
209-
marginRight: "12px",
210-
border: "1px solid",
211-
fontWeight: "bold",
212-
}}
213-
>
214-
Approve as Administrator <RecommendIcon />
215-
</Button>
216-
<Button
217-
onClick={handleReject}
218-
color="error"
219-
variant="outlined"
220-
aria-label="Reject application"
221-
sx={{
222-
border: "1px solid",
223-
fontWeight: "bold",
224-
}}
225-
>
226-
Decline Access <DoNotDisturbIcon />
227-
</Button>
228-
</Box>
229-
}
233+
Decline Access <DoNotDisturbIcon />
234+
</Button>
235+
</Box>
230236
</Box>
231237
)}
232-
{errorList.length > 0 &&
233-
!dismissAlert &&
234-
errorList.map((e: any) => (
235-
<Alert
236-
key={e.message}
237-
action={<CloseButton onClose={handleCloseAlert} />}
238-
severity="error"
239-
className="mb-4"
240-
>
241-
{e?.stack ?? e.message}
242-
</Alert>
243-
))}
238+
239+
{renderedErrors && <div className="mb-4">{renderedErrors}</div>}
240+
244241
{successMessageList.length > 0 &&
245242
!dismissAlert &&
246243
successMessageList.map((e: any) => (

bciers/apps/administration/app/components/profile/ProfileForm.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"use client";
22
import { useState } from "react";
33
import type { ReactNode } from "react";
4-
import { Alert } from "@mui/material";
54
import { actionHandler } from "@bciers/actions";
65
import FormBase from "@bciers/components/form/FormBase";
76
import { Button } from "@mui/material";
@@ -13,6 +12,11 @@ import {
1312
UserProfilePartialFormData,
1413
} from "@bciers/types/form/formData";
1514
import { IDP } from "@bciers/utils/src/enums";
15+
import {
16+
useValidationErrors,
17+
handleApiResponse,
18+
createGenericValidationError,
19+
} from "@bciers/components/validationErrors";
1620

1721
export const userSchema: RJSFSchema = {
1822
type: "object",
@@ -50,7 +54,9 @@ export default function ProfileForm({
5054
contactId,
5155
}: Props) {
5256
// 🐜 To display errors
53-
const [errorList, setErrorList] = useState([] as any[]);
57+
const { setErrors, renderedErrors } = useValidationErrors({
58+
config: {},
59+
});
5460

5561
// 🌀 Loading state for the Submit button
5662
const [isLoading, setIsLoading] = useState(false);
@@ -120,8 +126,7 @@ export default function ProfileForm({
120126

121127
// 🛠️ Function to submit user form data to API
122128
const submitHandler = async (data: { formData?: UserProfileFormData }) => {
123-
//Set states
124-
setErrorList([]);
129+
setErrors(undefined);
125130
setIsLoading(true);
126131
setIsSuccess(false);
127132

@@ -142,11 +147,10 @@ export default function ProfileForm({
142147
},
143148
);
144149

145-
// 🛑 Set loading to false after the API call is completed
146150
setIsLoading(false);
147151

148-
if (response.error) {
149-
setErrorList([{ message: response.error }]);
152+
const isSuccessResponse = handleApiResponse(response, setErrors);
153+
if (!isSuccessResponse) {
150154
return;
151155
}
152156

@@ -162,12 +166,7 @@ export default function ProfileForm({
162166
uiSchema={userUiSchema}
163167
onSubmit={submitHandler}
164168
>
165-
{errorList.length > 0 &&
166-
errorList.map((e: any) => (
167-
<Alert key={e.message} severity="error">
168-
{e?.stack ?? e.message}
169-
</Alert>
170-
))}
169+
{renderedErrors}
171170
<div className="flex justify-end gap-3">
172171
{/* Disable the button when loading or when success state is true */}
173172
<Button

bciers/apps/administration/app/components/userOperators/SelectOperatorForm.tsx

Lines changed: 36 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,64 @@
11
"use client";
2+
23
import { BC_GOV_LINKS_COLOR } from "@bciers/styles/colors";
34
import Link from "next/link";
45
import Form from "@bciers/components/form/FormBase";
5-
import { useState } from "react";
6-
import { Alert } from "@mui/material";
76
import { useRouter } from "next/navigation";
87
import { actionHandler } from "@bciers/actions";
8+
import {
9+
useValidationErrors,
10+
handleApiResponse,
11+
} from "@bciers/components/validationErrors";
912
import { SelectOperatorFormData } from "../userOperators/types";
1013
import { selectOperatorUiSchema } from "../../data/jsonSchema/selectOperator";
1114
import { selectOperatorSchema } from "../../data/jsonSchema/selectOperator";
15+
import { validationUIConfig } from "@/administration/app/components/validationErrors/config";
16+
import type { ValidationKey } from "@/administration/app/components/validationErrors/types";
1217

1318
export default function SelectOperatorForm() {
14-
const [errorList, setErrorList] = useState<{ message: string }[]>([]);
1519
const router = useRouter();
20+
const { setErrors, renderedErrors } = useValidationErrors<ValidationKey>({
21+
config: validationUIConfig,
22+
});
1623

1724
const handleSubmit = async (data: { formData?: SelectOperatorFormData }) => {
18-
// Reset previous errors on new submission
19-
setErrorList([]);
25+
setErrors(undefined);
2026

2127
const queryParam = `?${data.formData?.search_type}=${
2228
data.formData?.[
2329
data.formData?.search_type as keyof SelectOperatorFormData
2430
]
2531
}`;
2632

27-
try {
28-
const response = await actionHandler(
29-
`registration/operators/search${queryParam}`,
30-
"GET",
31-
"/select-operator",
32-
);
33-
34-
// Updated check: handles response.error, response.message, or response.detail
35-
const errorMessage =
36-
response?.error || response?.message || response?.detail;
33+
const response = await actionHandler(
34+
`registration/operators/search${queryParam}`,
35+
"GET",
36+
"/select-operator",
37+
);
3738

38-
if (errorMessage) {
39-
console.log("[ERROR DETECTED] Setting error message:", errorMessage);
40-
setErrorList([
41-
{
42-
message:
43-
typeof errorMessage === "string"
44-
? errorMessage
45-
: JSON.stringify(errorMessage),
46-
},
47-
]);
48-
return;
49-
}
39+
const isSuccess = handleApiResponse<ValidationKey>(
40+
response,
41+
setErrors,
42+
"operator_not_found",
43+
);
44+
if (!isSuccess) {
45+
return;
46+
}
5047

51-
// If the response is an array, we want the first element
52-
let operatorId;
53-
let operatorLegalName;
54-
if (Array.isArray(response) && response.length > 0) {
55-
operatorId = response[0].id;
56-
operatorLegalName = response[0].legal_name;
57-
} else if (response && response.id) {
58-
operatorId = response.id;
59-
operatorLegalName = response.legal_name;
60-
} else {
61-
setErrorList([{ message: "Unexpected response format from server." }]);
62-
return;
63-
}
48+
const operator = Array.isArray(response) ? response[0] : response;
6449

65-
router.push(
66-
`/select-operator/confirm/${operatorId}?title=${operatorLegalName}`,
50+
if (!operator?.id) {
51+
handleApiResponse<ValidationKey>(
52+
{ error: "No operator found matching the provided criteria." },
53+
setErrors,
54+
"operator_not_found",
6755
);
68-
} catch (err: any) {
69-
setErrorList([
70-
{ message: err?.message || "An unexpected error occurred." },
71-
]);
56+
return;
7257
}
58+
59+
router.push(
60+
`/select-operator/confirm/${operator.id}?title=${operator.legal_name}`,
61+
);
7362
};
7463

7564
return (
@@ -82,16 +71,7 @@ export default function SelectOperatorForm() {
8271
uiSchema={selectOperatorUiSchema}
8372
className="mx-auto"
8473
>
85-
{/* Needed to display errors from cra number */}
86-
{errorList.length > 0 &&
87-
errorList.map((e: any, index: number) => {
88-
return (
89-
<Alert key={index} severity="error" className="mt-2">
90-
{e.message}
91-
</Alert>
92-
);
93-
})}
94-
{/* Needed to prevent rendering of standard submit buttons by RJSF */}
74+
{renderedErrors}
9575
<></>
9676
</Form>
9777
<p>

bciers/apps/administration/app/components/validationErrors/config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,15 @@ export const validationUIConfig: Partial<
2727
resolveFormattedMessage: (error) =>
2828
error.message ?? "Please return to Contacts to assign a representative.",
2929
},
30+
operator_not_found: {
31+
priority: 10,
32+
renderMode: "inline_link",
33+
resolveLabel: () => "Add Operator",
34+
resolveHref: () => "/select-operator/add-operator",
35+
resolveMessage: (error) =>
36+
error.message ?? "No operator found matching the provided criteria.",
37+
resolveFormattedMessage: (error) =>
38+
error.message ??
39+
"No operator found matching the provided criteria. You can Add Operator instead.",
40+
},
3041
};

0 commit comments

Comments
 (0)