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 { desc, eq } from "drizzle-orm";
|
||||||
import { ArrowRight, Save } from "lucide-react";
|
import { ArrowRight, Save, Trash2 } from "lucide-react";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import type { TypedFormData } from "@/app/(ui)/rubgram/type";
|
import type { TypedFormData } from "@/app/(ui)/rubgram/type";
|
||||||
@@ -88,8 +88,18 @@ export default async function CharacterEditPage({
|
|||||||
return { toast: "Character saved." };
|
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 (
|
return (
|
||||||
<ModalBase title="Create Character">
|
<ModalBase title="Edit Character">
|
||||||
<FormProvider
|
<FormProvider
|
||||||
id="char-create"
|
id="char-create"
|
||||||
onSubmit={submit}
|
onSubmit={submit}
|
||||||
@@ -198,6 +208,12 @@ export default async function CharacterEditPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
<Button asChild variant="destructive">
|
||||||
|
<FormAction type="action" action={deleteChar}>
|
||||||
|
<Trash2 />
|
||||||
|
Delete
|
||||||
|
</FormAction>
|
||||||
|
</Button>
|
||||||
<DialogClose>
|
<DialogClose>
|
||||||
<Button variant="outline" type="button">
|
<Button variant="outline" type="button">
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
+58
-8
@@ -23,6 +23,7 @@ type FormContextValue = {
|
|||||||
updateValues: (updater: (prev: FormValues) => FormValues) => void;
|
updateValues: (updater: (prev: FormValues) => FormValues) => void;
|
||||||
clear: () => void;
|
clear: () => void;
|
||||||
submit: () => Promise<void>;
|
submit: () => Promise<void>;
|
||||||
|
closeDialog: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormContext = React.createContext<FormContextValue | null>(null);
|
const FormContext = React.createContext<FormContextValue | null>(null);
|
||||||
@@ -188,6 +189,10 @@ export function FormProvider<T extends TypedFormDataShape>({
|
|||||||
formRef.current?.reset();
|
formRef.current?.reset();
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
|
const closeDialog = React.useCallback(() => {
|
||||||
|
closerRef.current?.click();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const submit = React.useCallback(async () => {
|
const submit = React.useCallback(async () => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
for (const [key, value] of Object.entries(values)) {
|
for (const [key, value] of Object.entries(values)) {
|
||||||
@@ -226,8 +231,17 @@ export function FormProvider<T extends TypedFormDataShape>({
|
|||||||
}, [onSubmit, values]);
|
}, [onSubmit, values]);
|
||||||
|
|
||||||
const contextValue = React.useMemo<FormContextValue>(
|
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 (
|
return (
|
||||||
@@ -339,7 +353,9 @@ type FormActionProps = Omit<
|
|||||||
React.ComponentProps<"button">,
|
React.ComponentProps<"button">,
|
||||||
"type" | "children"
|
"type" | "children"
|
||||||
> & {
|
> & {
|
||||||
type: "clear" | "submit";
|
type: "clear" | "submit" | "action";
|
||||||
|
|
||||||
|
action?: () => SubmitFnResult | Promise<SubmitFnResult>;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
loading?: React.ReactNode;
|
loading?: React.ReactNode;
|
||||||
asChild?: boolean;
|
asChild?: boolean;
|
||||||
@@ -349,18 +365,52 @@ export function FormAction({
|
|||||||
type,
|
type,
|
||||||
children,
|
children,
|
||||||
asChild,
|
asChild,
|
||||||
|
action,
|
||||||
loading: loadingComponent,
|
loading: loadingComponent,
|
||||||
...props
|
...props
|
||||||
}: FormActionProps) {
|
}: FormActionProps) {
|
||||||
const { loading, clear } = useFormContext();
|
const { loading: formLoading, clear, closeDialog } = useFormContext();
|
||||||
const isSubmit = type === "submit";
|
const [actionLoading, setActionLoading] = React.useState(false);
|
||||||
|
const loading = formLoading || actionLoading;
|
||||||
|
|
||||||
const buttonProps = {
|
const buttonProps = {
|
||||||
...props,
|
...props,
|
||||||
type: (isSubmit ? "submit" : "button") as "submit" | "button",
|
type: (type === "submit" ? "submit" : "button") as "submit" | "button",
|
||||||
disabled: isSubmit && loading,
|
disabled: type !== "clear" && loading,
|
||||||
onClick: (event: React.MouseEvent<HTMLButtonElement>) => {
|
onClick: async (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
props.onClick?.(event);
|
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();
|
if (!event.defaultPrevented && type === "clear") clear();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user