Add delete character functionality and close dialog support in form context
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
import { ArrowRight, Save } from "lucide-react";
|
||||
import { ArrowRight, Save, Trash2 } from "lucide-react";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import type { TypedFormData } from "@/app/(ui)/rubgram/type";
|
||||
@@ -88,8 +88,18 @@ export default async function CharacterEditPage({
|
||||
return { toast: "Character saved." };
|
||||
}
|
||||
|
||||
async function deleteChar() {
|
||||
"use server";
|
||||
if (!(await adminCheck())) redirect("/login");
|
||||
|
||||
await db.delete(characters).where(eq(characters.id, charId));
|
||||
|
||||
revalidatePath("/admin/char");
|
||||
return { toast: "Character deleted.", close: true };
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalBase title="Create Character">
|
||||
<ModalBase title="Edit Character">
|
||||
<FormProvider
|
||||
id="char-create"
|
||||
onSubmit={submit}
|
||||
@@ -198,6 +208,12 @@ export default async function CharacterEditPage({
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button asChild variant="destructive">
|
||||
<FormAction type="action" action={deleteChar}>
|
||||
<Trash2 />
|
||||
Delete
|
||||
</FormAction>
|
||||
</Button>
|
||||
<DialogClose>
|
||||
<Button variant="outline" type="button">
|
||||
Cancel
|
||||
|
||||
+58
-8
@@ -23,6 +23,7 @@ type FormContextValue = {
|
||||
updateValues: (updater: (prev: FormValues) => FormValues) => void;
|
||||
clear: () => void;
|
||||
submit: () => Promise<void>;
|
||||
closeDialog: () => void;
|
||||
};
|
||||
|
||||
const FormContext = React.createContext<FormContextValue | null>(null);
|
||||
@@ -188,6 +189,10 @@ export function FormProvider<T extends TypedFormDataShape>({
|
||||
formRef.current?.reset();
|
||||
}, [id]);
|
||||
|
||||
const closeDialog = React.useCallback(() => {
|
||||
closerRef.current?.click();
|
||||
}, []);
|
||||
|
||||
const submit = React.useCallback(async () => {
|
||||
const formData = new FormData();
|
||||
for (const [key, value] of Object.entries(values)) {
|
||||
@@ -226,8 +231,17 @@ export function FormProvider<T extends TypedFormDataShape>({
|
||||
}, [onSubmit, values]);
|
||||
|
||||
const contextValue = React.useMemo<FormContextValue>(
|
||||
() => ({ id, values, loading, setValue, updateValues, clear, submit }),
|
||||
[id, values, loading, setValue, updateValues, clear, submit],
|
||||
() => ({
|
||||
id,
|
||||
values,
|
||||
loading,
|
||||
setValue,
|
||||
updateValues,
|
||||
clear,
|
||||
submit,
|
||||
closeDialog,
|
||||
}),
|
||||
[id, values, loading, setValue, updateValues, clear, submit, closeDialog],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -339,7 +353,9 @@ type FormActionProps = Omit<
|
||||
React.ComponentProps<"button">,
|
||||
"type" | "children"
|
||||
> & {
|
||||
type: "clear" | "submit";
|
||||
type: "clear" | "submit" | "action";
|
||||
|
||||
action?: () => SubmitFnResult | Promise<SubmitFnResult>;
|
||||
children?: React.ReactNode;
|
||||
loading?: React.ReactNode;
|
||||
asChild?: boolean;
|
||||
@@ -349,18 +365,52 @@ export function FormAction({
|
||||
type,
|
||||
children,
|
||||
asChild,
|
||||
action,
|
||||
loading: loadingComponent,
|
||||
...props
|
||||
}: FormActionProps) {
|
||||
const { loading, clear } = useFormContext();
|
||||
const isSubmit = type === "submit";
|
||||
const { loading: formLoading, clear, closeDialog } = useFormContext();
|
||||
const [actionLoading, setActionLoading] = React.useState(false);
|
||||
const loading = formLoading || actionLoading;
|
||||
|
||||
const buttonProps = {
|
||||
...props,
|
||||
type: (isSubmit ? "submit" : "button") as "submit" | "button",
|
||||
disabled: isSubmit && loading,
|
||||
onClick: (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
type: (type === "submit" ? "submit" : "button") as "submit" | "button",
|
||||
disabled: type !== "clear" && loading,
|
||||
onClick: async (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
props.onClick?.(event);
|
||||
if (!event.defaultPrevented && type === "action") {
|
||||
setActionLoading(true);
|
||||
try {
|
||||
const res = await action?.();
|
||||
if (res && typeof res === "object") {
|
||||
if ("error" in res) {
|
||||
const { error } = res;
|
||||
if (typeof error === "string") throw new Error(error);
|
||||
}
|
||||
if ("toast" in res) {
|
||||
const { toast: toastMsg } = res;
|
||||
if (typeof toastMsg === "string") toast(toastMsg);
|
||||
}
|
||||
if ("close" in res) {
|
||||
const { close } = res;
|
||||
if (typeof close === "boolean") {
|
||||
if (close) closeDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: typeof error === "string"
|
||||
? error
|
||||
: "Something went wrong";
|
||||
toast.error(message);
|
||||
} finally {
|
||||
setActionLoading(false);
|
||||
}
|
||||
}
|
||||
if (!event.defaultPrevented && type === "clear") clear();
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user