Add error handling to upsert role

This commit is contained in:
Sianida26 2024-01-28 05:14:42 +07:00
parent 2e04483343
commit bae8a2aa3e
4 changed files with 108 additions and 84 deletions

View File

@ -1,9 +1,11 @@
/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable react-hooks/exhaustive-deps */
import DashboardError from "@/features/dashboard/errors/DashboardError";
import getRoleById from "@/features/dashboard/roles/actions/getRoleById"; import getRoleById from "@/features/dashboard/roles/actions/getRoleById";
import upsertRole from "@/features/dashboard/roles/actions/upsertRole"; import upsertRole from "@/features/dashboard/roles/actions/upsertRole";
import roleFormDataSchema, { import roleFormDataSchema, {
RoleFormData, RoleFormData,
} from "@/features/dashboard/roles/formSchemas/RoleFormData"; } from "@/features/dashboard/roles/formSchemas/RoleFormData";
import withErrorHandling from "@/features/dashboard/utils/withServerAction";
import { showNotification } from "@/utils/notifications"; import { showNotification } from "@/utils/notifications";
import { import {
Flex, Flex,
@ -17,6 +19,7 @@ import {
Checkbox, Checkbox,
Skeleton, Skeleton,
Fieldset, Fieldset,
Alert,
} from "@mantine/core"; } from "@mantine/core";
import { useForm, zodResolver } from "@mantine/form"; import { useForm, zodResolver } from "@mantine/form";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
@ -39,27 +42,28 @@ export interface ModalProps {
*/ */
export default function FormModal(props: ModalProps) { export default function FormModal(props: ModalProps) {
const router = useRouter(); const router = useRouter();
const [isSubmitting, setSubmitting] = useState(false); const [isSubmitting, setSubmitting] = useState(false);
const [isFetching, setFetching] = useState(false); const [isFetching, setFetching] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const form = useForm<RoleFormData>({ const form = useForm<RoleFormData>({
initialValues: { initialValues: {
code: "", code: "",
description: "", description: "",
id: "", id: "",
isActive: false, isActive: false,
name: "", name: "",
}, },
validate: zodResolver(roleFormDataSchema), validate: zodResolver(roleFormDataSchema),
validateInputOnChange: false, validateInputOnChange: false,
onValuesChange: (values) => { onValuesChange: (values) => {
console.log(values); console.log(values);
}, },
}); });
/** /**
* Fetches role data by ID and populates the form if the modal is opened and an ID is provided. * Fetches role data by ID and populates the form if the modal is opened and an ID is provided.
*/ */
useEffect(() => { useEffect(() => {
if (!props.opened || !props.id) { if (!props.opened || !props.id) {
return; return;
@ -93,21 +97,29 @@ export default function FormModal(props: ModalProps) {
}; };
const handleSubmit = (values: RoleFormData) => { const handleSubmit = (values: RoleFormData) => {
upsertRole(values) setSubmitting(true);
withErrorHandling(() => upsertRole(values))
.then((response) => { .then((response) => {
if (response.success) { showNotification(response.message!, "success");
showNotification(response.message, "success"); closeModal();
return closeModal();
} else {
form.setErrors(response.errors ?? {});
if (!response.errors) {
showNotification(response.message, "error");
}
}
}) })
.catch((e) => { .catch((e) => {
//TODO: Handle Error if (e instanceof DashboardError) {
console.log(e); if (e.errorCode === "INVALID_FORM_DATA") {
form.setErrors(e.formErrors ?? {});
} else {
setErrorMessage(`ERROR: ${e.message} (${e.errorCode})`);
}
} else if (e instanceof Error) {
setErrorMessage(`ERROR: ${e.message}`);
} else {
setErrorMessage(
`Unkown error is occured. Please contact administrator`
);
}
})
.finally(() => {
setSubmitting(false);
}); });
}; };
@ -121,6 +133,7 @@ export default function FormModal(props: ModalProps) {
> >
<form onSubmit={form.onSubmit(handleSubmit)}> <form onSubmit={form.onSubmit(handleSubmit)}>
<Stack mt="sm" gap="lg" px="lg"> <Stack mt="sm" gap="lg" px="lg">
{errorMessage && <Alert color="red">{errorMessage}</Alert>}
{/* ID */} {/* ID */}
{form.values.id ? ( {form.values.id ? (
<TextInput <TextInput

View File

@ -4,7 +4,7 @@ export const DashboardErrorCodes = [
"UNAUTHORIZED", "UNAUTHORIZED",
"NOT_FOUND", "NOT_FOUND",
"UNKNOWN_ERROR", "UNKNOWN_ERROR",
"INVALID_FORM_DATA" "INVALID_FORM_DATA",
] as const; ] as const;
interface ErrorOptions { interface ErrorOptions {
@ -14,7 +14,7 @@ interface ErrorOptions {
} }
export default class DashboardError extends Error { export default class DashboardError extends Error {
public readonly errorCode: string; public readonly errorCode: typeof DashboardErrorCodes[number] | string & {};
public readonly formErrors?: {[k: string]: string} public readonly formErrors?: {[k: string]: string}
// public readonly data: object; // public readonly data: object;

View File

@ -2,69 +2,80 @@
import checkPermission from "@/features/auth/tools/checkPermission"; import checkPermission from "@/features/auth/tools/checkPermission";
import roleFormDataSchema, { RoleFormData } from "../formSchemas/RoleFormData"; import roleFormDataSchema, { RoleFormData } from "../formSchemas/RoleFormData";
import { unauthorized } from "@/BaseError";
import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue"; import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
import prisma from "@/db"; import prisma from "@/db";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import ServerResponse from "@/types/Action";
import DashboardError, { handleCatch, unauthorized } from "../../errors/DashboardError";
/** /**
* Upserts a role based on the provided RoleFormData. * Upserts a role based on the provided RoleFormData.
* If the role already exists (determined by `id`), it updates the role; otherwise, it creates a new role. * If the role already exists (determined by `id`), it updates the role; otherwise, it creates a new role.
* Authorization checks are performed based on whether it's a create or update operation. * Authorization checks are performed based on whether it's a create or update operation.
* *
* @param {RoleFormData} data - The data for creating or updating the role. * @param data - The data for creating or updating the role.
* @returns {Promise<object>} An object containing the success status, message, and any errors. * @returns An object containing the success status, message, and any errors.
*/ */
export default async function upsertRole(data: RoleFormData) { export default async function upsertRole(
const isInsert = !data.id; data: RoleFormData
): Promise<ServerResponse> {
try {
const isInsert = !data.id;
// Authorization check // Authorization check
const permissionType = isInsert ? "role.create" : "role.update"; const permissionType = isInsert ? "role.create" : "role.update";
if (!await checkPermission(permissionType)) { if (!(await checkPermission(permissionType))) {
return unauthorized(); return unauthorized();
} }
// Validate form data // Validate form data
const validatedFields = roleFormDataSchema.safeParse(data); const validatedFields = roleFormDataSchema.safeParse(data);
if (!validatedFields.success) { if (!validatedFields.success) {
return { throw new DashboardError({
success: false, errorCode: "INVALID_FORM_DATA",
message: "Invalid Form Data", formErrors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors)
errors: mapObjectToFirstValue(validatedFields.error.flatten().fieldErrors), })
}; }
} const roleData = {
code: validatedFields.data.code,
description: validatedFields.data.description,
name: validatedFields.data.name,
isActive: validatedFields.data.isActive,
};
try { // Database operation
const roleData = { if (isInsert) {
code: validatedFields.data.code, if (await prisma.role.findFirst({
description: validatedFields.data.description, where: {
name: validatedFields.data.name, code: roleData.code
isActive: validatedFields.data.isActive, }
}; })){
throw new DashboardError({
errorCode: "INVALID_FORM_DATA",
formErrors: {
code: "The code is already exists"
}
})
}
await prisma.role.create({ data: roleData });
} else {
await prisma.role.update({
where: { id: validatedFields.data.id! },
data: roleData,
});
}
// Database operation // Revalidate the cache
if (isInsert) { revalidatePath(".");
await prisma.role.create({ data: roleData });
} else {
await prisma.role.update({
where: { id: validatedFields.data.id! },
data: roleData,
});
}
// Revalidate the cache // Return success message
revalidatePath("."); return {
success: true,
// Return success message message: `Role ${validatedFields.data.name} has been successfully ${
return { isInsert ? "created" : "updated"
success: true, }.`,
message: `Role ${validatedFields.data.name} has been successfully ${isInsert ? "created" : "updated"}.`, };
}; } catch (error) {
} catch (error) { return handleCatch(error)
console.error('Error updating role data', error); }
return {
success: false,
message: "Error updating role data.",
};
}
} }

View File

@ -10,8 +10,8 @@ export interface RoleFormData {
const roleFormDataSchema = z.object({ const roleFormDataSchema = z.object({
id: z.string().nullable(), id: z.string().nullable(),
name: z.string(), name: z.string().min(1),
code: z.string(), code: z.string().min(1),
description: z.string(), description: z.string(),
isActive: z.boolean(), isActive: z.boolean(),
}) })