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

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