diff --git a/README.md b/README.md index a65d9fa..f77756f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ```env NEXT_PUBLIC_BACKEND_BASE_URL= -APP_REDIRECT_URL= +AUTH_SECRET= ``` ## Development diff --git a/app/(admin)/(question-management)/database/_components/create.tsx b/app/(admin)/(question-management)/database/_components/create.tsx index e885245..637e519 100644 --- a/app/(admin)/(question-management)/database/_components/create.tsx +++ b/app/(admin)/(question-management)/database/_components/create.tsx @@ -1,6 +1,7 @@ "use client"; import { buttonVariants } from "@/components/ui/button"; +import { ConfirmationDialog } from "@/components/ui/confirmation-dialog"; import { Dialog, DialogContent, @@ -9,35 +10,66 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { useDialogCloseConfirmation } from "@/hooks/use-dialog-close-confirmation"; import { useMutation } from "@apollo/client/react"; -import { useRouter } from "next/navigation"; import { useState } from "react"; import { toast } from "sonner"; import { DATABASE_CREATE_MUTATION } from "./mutation"; import { DATABASES_TABLE_QUERY } from "./query"; -import { UpdateDatabaseForm, type UpdateDatabaseFormData } from "./update-form"; +import { UpdateDatabaseForm } from "./update-form"; export function CreateDatabaseTrigger() { - const router = useRouter(); const [open, setOpen] = useState(false); + const [isFormDirty, setIsFormDirty] = useState(false); + + const { + showConfirmation, + handleDialogOpenChange, + handleConfirmClose, + handleCancelClose, + } = useDialogCloseConfirmation({ + isDirty: isFormDirty, + setOpen, + onConfirmedClose: () => { + setIsFormDirty(false); + }, + }); + + const handleFormStateChange = (isDirty: boolean) => { + setIsFormDirty(isDirty); + }; + + const handleCompleted = () => { + setIsFormDirty(false); + setOpen(false); + }; return ( - - 新增資料庫 - { - setOpen(false); - router.refresh(); - }} + <> + + 新增資料庫 + + + + {}} + onConfirm={handleConfirmClose} + onCancel={handleCancelClose} /> - + ); } function CreateDatabaseDialogContent({ onCompleted, + onFormStateChange, }: { onCompleted: () => void; + onFormStateChange: (isDirty: boolean) => void; }) { const [createDatabase] = useMutation(DATABASE_CREATE_MUTATION, { refetchQueries: [DATABASES_TABLE_QUERY], @@ -54,25 +86,6 @@ function CreateDatabaseDialogContent({ }, }); - const onSubmit = (data: UpdateDatabaseFormData) => { - try { - createDatabase({ - variables: { - input: { - slug: data.slug!, - description: data.description, - schema: data.schema!, - relationFigure: data.relationFigure!, - }, - }, - }); - } catch (error) { - toast.error("資料庫建立失敗", { - description: error instanceof Error ? error.message : "未知錯誤", - }); - } - }; - return ( @@ -88,8 +101,20 @@ function CreateDatabaseDialogContent({ schema: "", relationFigure: "", }} - onSubmit={onSubmit} + onSubmit={(data) => { + createDatabase({ + variables: { + input: { + slug: data.slug, + description: data.description, + schema: data.schema, + relationFigure: data.relationFigure, + }, + }, + }); + }} action="create" + onFormStateChange={onFormStateChange} /> ); diff --git a/app/(admin)/(question-management)/database/_components/delete.tsx b/app/(admin)/(question-management)/database/_components/delete.tsx index d85eb71..5804db5 100644 --- a/app/(admin)/(question-management)/database/_components/delete.tsx +++ b/app/(admin)/(question-management)/database/_components/delete.tsx @@ -13,7 +13,7 @@ import { } from "@/components/ui/alert-dialog"; import { buttonVariants } from "@/components/ui/button"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { useMutation, useSuspenseQuery } from "@apollo/client/react"; +import { skipToken, useMutation, useSuspenseQuery } from "@apollo/client/react"; import { Trash } from "lucide-react"; import { useRouter } from "next/navigation"; import { Suspense, useState } from "react"; @@ -42,6 +42,7 @@ export function DeleteDatabaseDropdownTrigger({ id }: { id: string }) { { setOpen(false); router.refresh(); @@ -66,6 +67,7 @@ export function DeleteDatabaseButtonTrigger({ id }: { id: string }) { { setOpen(false); router.push("/database"); @@ -78,14 +80,21 @@ export function DeleteDatabaseButtonTrigger({ id }: { id: string }) { function DeleteDatabaseAlertDialogContent({ id, + open, onCompleted, }: { id: string; + open: boolean; onCompleted: () => void; }) { - const { data } = useSuspenseQuery(DATABASE_BY_ID_QUERY, { - variables: { id }, - }); + const { data } = useSuspenseQuery( + DATABASE_BY_ID_QUERY, + open + ? { + variables: { id }, + } + : skipToken, + ); const [deleteDatabase] = useMutation(DATABASE_DELETE_MUTATION, { refetchQueries: [DATABASES_TABLE_QUERY], @@ -106,7 +115,7 @@ function DeleteDatabaseAlertDialogContent({ - 確定要刪除「{data.database.slug}」資料庫嗎? + 確定要刪除「{data?.database.slug}」資料庫嗎? 刪除後將無法復原此資料庫,且相關的題目可能會受到影響。請謹慎操作。 diff --git a/app/(admin)/(question-management)/database/_components/update-form.tsx b/app/(admin)/(question-management)/database/_components/update-form.tsx index 04d1822..2445c47 100644 --- a/app/(admin)/(question-management)/database/_components/update-form.tsx +++ b/app/(admin)/(question-management)/database/_components/update-form.tsx @@ -1,142 +1,117 @@ -import { Button } from "@/components/ui/button"; -import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; +import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; +import { UpdateFormBody } from "@/components/update-modal/form-body"; +import type { UpdateFormBaseProps } from "@/components/update-modal/types"; import { zodResolver } from "@hookform/resolvers/zod"; +import React from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; export const formSchema = z.object({ - slug: z.string().min(1, "標識不能為空"), + slug: z.string().min(1, "slug 不能為空"), description: z.string().optional(), schema: z.string().min(1, "資料結構不能為空"), relationFigure: z.string().min(1, "關係圖不能為空"), }); -export interface UpdateDatabaseFormData { - slug?: string; - description?: string; - schema?: string; - relationFigure?: string; - clearDescription?: boolean; -} - -export interface UpdateDatabaseFormProps { - defaultValues?: z.infer; - onSubmit: (newValues: UpdateDatabaseFormData) => void; - action: "update" | "create"; -} +export type UpdateDatabaseFormProps = UpdateFormBaseProps>; export function UpdateDatabaseForm({ defaultValues, onSubmit, action, + onFormStateChange, }: UpdateDatabaseFormProps) { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues, }); - const handleSubmit = (data: z.infer) => { - form.reset(); - - if (action === "create") { - onSubmit({ - slug: data.slug, - description: data.description, - schema: data.schema, - relationFigure: data.relationFigure, - }); - } else { - onSubmit({ - slug: data.slug || undefined, - description: data.description || undefined, - schema: data.schema || undefined, - relationFigure: data.relationFigure || undefined, - clearDescription: data.description === "", - }); - } - }; - return ( -
- - ( - - 標識 - - - - 資料庫的唯一識別符,通常為小寫英文。 - - - )} - /> - - ( - - 描述(可選) - -