Added base error handling

This commit is contained in:
Sianida26 2024-01-28 05:00:03 +07:00
parent 77bb120a00
commit 2e04483343
6 changed files with 183 additions and 50 deletions

View File

@ -12,55 +12,71 @@ import {
Stack, Stack,
TextInput, TextInput,
Title, Title,
Alert,
} from "@mantine/core"; } from "@mantine/core";
import { showNotification } from "@/utils/notifications"; import { showNotification } from "@/utils/notifications";
import deleteRole from "@/features/dashboard/roles/actions/deleteRole"; import deleteRole from "@/features/dashboard/roles/actions/deleteRole";
import withErrorHandling from "@/features/dashboard/utils/withServerAction";
import { error } from "console";
import DashboardError from "@/features/dashboard/errors/DashboardError";
import { revalidatePath } from "next/cache";
export interface DeleteModalProps { export interface DeleteModalProps {
data?: { data?: {
id: string, id: string;
name: string, name: string;
}; };
onClose: () => void onClose: () => void;
} }
export default function DeleteModal(props: DeleteModalProps) { export default function DeleteModal(props: DeleteModalProps) {
const router = useRouter(); const router = useRouter();
const [isSubmitting, setSubmitting] = useState(false); const [isSubmitting, setSubmitting] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
/** /**
* Closes the modal. It won't close if a submission is in progress. * Closes the modal. It won't close if a submission is in progress.
*/ */
const closeModal = () => { const closeModal = () => {
if (isSubmitting) return; if (isSubmitting) return;
props.onClose() setErrorMessage("")
props.onClose();
}; };
const confirmAction = () => { const confirmAction = () => {
if (!props.data?.id) return; if (!props.data?.id) return;
setSubmitting(true) setSubmitting(true);
deleteRole(props.data.id)
.then((response) => { withErrorHandling(() => deleteRole(props.data!.id))
if (response.success){ .then((response) => {
showNotification(response.message); showNotification(
setSubmitting(false) response.message ?? "Role deleted successfully"
props.onClose() );
return; setSubmitting(false);
} else { props.onClose()
showNotification(response.message, "error") })
.catch((e) => {
if (e instanceof DashboardError){
setErrorMessage(`ERROR: ${e.message} (${e.errorCode})`)
} }
}) else if (e instanceof Error) {
.catch(() => { setErrorMessage(`ERROR: ${e.message}`)
//TODO: Handle Error } else {
}) setErrorMessage(`Unkown error is occured. Please contact administrator`)
}
})
.finally(() => { .finally(() => {
setSubmitting(false) setSubmitting(false)
}) });
} };
return ( return (
<Modal opened={!!props.data} onClose={closeModal} title={`Delete confirmation`}> <Modal
opened={!!props.data}
onClose={closeModal}
title={`Delete confirmation`}
>
<Text size="sm"> <Text size="sm">
Are you sure you want to delete role{" "} Are you sure you want to delete role{" "}
<Text span fw={700}> <Text span fw={700}>
@ -68,6 +84,8 @@ export default function DeleteModal(props: DeleteModalProps) {
</Text> </Text>
? This action is irreversible. ? This action is irreversible.
</Text> </Text>
{errorMessage && <Alert color="red">{errorMessage}</Alert>}
{/* Buttons */} {/* Buttons */}
<Flex justify="flex-end" align="center" gap="lg" mt="lg"> <Flex justify="flex-end" align="center" gap="lg" mt="lg">
<Button <Button
@ -83,7 +101,7 @@ export default function DeleteModal(props: DeleteModalProps) {
type="submit" type="submit"
color="red" color="red"
loading={isSubmitting} loading={isSubmitting}
onClick={confirmAction} onClick={confirmAction}
> >
Delete Role Delete Role
</Button> </Button>

View File

@ -1,5 +1,15 @@
import { PrismaClient } from "@prisma/client"; import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient(); const prismaClientSingleton = () => {
return new PrismaClient()
}
export default prisma; declare global {
var prisma: undefined | ReturnType<typeof prismaClientSingleton>
}
const prisma = globalThis.prisma ?? prismaClientSingleton()
export default prisma
if (process.env.NODE_ENV !== 'production') globalThis.prisma = prisma

View File

@ -0,0 +1,73 @@
import { Prisma } from "@prisma/client";
export const DashboardErrorCodes = [
"UNAUTHORIZED",
"NOT_FOUND",
"UNKNOWN_ERROR",
"INVALID_FORM_DATA"
] as const;
interface ErrorOptions {
message?: string,
errorCode?: typeof DashboardErrorCodes[number] | string & {},
formErrors?: {[k: string]: string}
}
export default class DashboardError extends Error {
public readonly errorCode: string;
public readonly formErrors?: {[k: string]: string}
// public readonly data: object;
constructor(options: ErrorOptions) {
super(options.message ?? "Undetermined Error"); // Pass message to the Error parent class
this.errorCode = options.errorCode ?? "UNKNOWN_ERROR";
this.formErrors = options.formErrors;
// this.data = data;
Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
}
getErrorReponseObject(){
return {
success: false,
dashboardError: true,
error: {
message: `${this.message}`,
errorCode: this.errorCode,
errors: this.formErrors ?? undefined
}
} as const
}
}
export const handleCatch = (e: unknown) => {
if (e instanceof DashboardError){
return e.getErrorReponseObject()
}
if (e instanceof Prisma.PrismaClientKnownRequestError){
//Not found
if (e.code === "P2025"){
const error = new DashboardError({errorCode: "NOT_FOUND", message: "The requested data could not be located. It may have been deleted or relocated. Please verify the information or try a different request."})
return error.getErrorReponseObject()
}
}
if (e instanceof Error) {
return {
success: false,
dashboardError: false,
message: e.message
} as const;
} else {
return {
success: false,
dashboardError: false,
message: "Unkown error"
} as const
}
}
export const unauthorized = () => {
throw new DashboardError({
errorCode: "UNAUTHORIZED",
message: "You are unauthorized to do this action"
})
}

View File

@ -1,26 +1,26 @@
"use server"; "use server";
import { unauthorized } from "@/BaseError";
import prisma from "@/db"; import prisma from "@/db";
import checkPermission from "@/features/auth/tools/checkPermission"; import checkPermission from "@/features/auth/tools/checkPermission";
import { handleCatch, unauthorized } from "../../errors/DashboardError";
import ServerResponse from "@/types/Action";
import { revalidatePath } from "next/cache";
export default async function deleteRole(id: string) { export default async function deleteRole(id: string): Promise<ServerResponse> {
if (!(await checkPermission("role.delete"))) return unauthorized(); try {
if (!(await checkPermission("role.delete"))) return unauthorized();
try {
const role = await prisma.role.delete({ const role = await prisma.role.delete({
where: { id }, where: { id },
}); });
revalidatePath(".")
return { return {
success: true, success: true,
message: "The role has been deleted successfully", message: "The role has been deleted successfully",
} as const; };
} catch (e) { } catch (e: unknown) {
//TODO: Handle error return handleCatch(e)
return {
success: false,
message: "Unable to delete the role",
} as const;
} }
} }

View File

@ -0,0 +1,25 @@
import ServerResponse from "@/types/Action";
import DashboardError from "../errors/DashboardError";
async function withErrorHandling<T extends ServerResponse>(
asyncFunction: () => Promise<T>
): Promise<T> {
const result = await asyncFunction();
if (result.success) {
return result;
} else {
if (result.dashboardError && result.error) {
const errorDetails = result.error;
throw new DashboardError({
message: errorDetails.message,
errorCode: errorDetails.errorCode,
formErrors: errorDetails.errors,
});
} else {
// Handle non-dashboard errors
throw new Error(result.message ?? "Unknown error occurred.");
}
}
}
export default withErrorHandling;

27
src/types/Action.d.ts vendored
View File

@ -1,11 +1,18 @@
type SuccessResponse<T> = { type ServerResponse<T = undefined> =
success: true, | {
data: T success: false;
} dashboardError?: boolean;
error?: {
message?: string;
errorCode?: string;
errors?: { [k: string]: string };
};
message?: string;
}
| {
success: true;
message?: string;
data?: T;
};
type ErrorResponse<E> = { export default ServerResponse;
success: false,
error: E & {message: string}
}
type Action = SuccessResponse<> | ErrorResponse<>