Added base error handling
This commit is contained in:
parent
77bb120a00
commit
2e04483343
|
|
@ -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)
|
|
||||||
|
withErrorHandling(() => deleteRole(props.data!.id))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.success){
|
showNotification(
|
||||||
showNotification(response.message);
|
response.message ?? "Role deleted successfully"
|
||||||
setSubmitting(false)
|
);
|
||||||
|
setSubmitting(false);
|
||||||
props.onClose()
|
props.onClose()
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
showNotification(response.message, "error")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch((e) => {
|
||||||
//TODO: Handle Error
|
if (e instanceof DashboardError){
|
||||||
|
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(() => {
|
.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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
73
src/features/dashboard/errors/DashboardError.ts
Normal file
73
src/features/dashboard/errors/DashboardError.ts
Normal 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"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
try {
|
||||||
|
if (!(await checkPermission("role.delete"))) return unauthorized();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
25
src/features/dashboard/utils/withServerAction.ts
Normal file
25
src/features/dashboard/utils/withServerAction.ts
Normal 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;
|
||||||
25
src/types/Action.d.ts
vendored
25
src/types/Action.d.ts
vendored
|
|
@ -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<>
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user