Files
buzz-sheet/components/admin/guide-overview-form.tsx
T
gunshiz 7708a2df31
CI / Verify (push) Successful in 59s
CI / Build immutable images and deploy (push) Successful in 1m58s
feat(admin) : persist drafts and add constellation conditions
2026-09-02 12:40:13 +07:00

45 lines
3.1 KiB
TypeScript

"use client";
import Image from "next/image";
import { useState } from "react";
import { saveGuideOverview } from "@/app/admin/actions";
import { AutosaveStatus } from "@/components/admin/autosave-status";
import { MediaUploader } from "@/components/admin/media-uploader";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Field, FieldDescription, FieldGroup, FieldLabel } from "@/components/ui/field";
import { Textarea } from "@/components/ui/textarea";
import { useAutosave } from "@/hooks/use-autosave";
import { adminDraftKey } from "@/lib/editor/local-draft";
export function GuideOverviewForm({ guide }: { guide: { id: string; version: number; overview: string; coverMediaId: string; coverUrl: string; name: string } }) {
const [overview, setOverview] = useState(guide.overview);
const [cover, setCover] = useState({ id: guide.coverMediaId, url: guide.coverUrl });
const value = { coverMediaId: cover.id, overview };
const incompleteIssues = [
!cover.id ? "Guide Image is required" : null,
!overview.trim() ? "Overview is required" : null,
].filter((issue): issue is string => Boolean(issue));
const localDraft = { overview, cover };
const autosave = useAutosave({
value,
valid: Boolean(cover.id && overview.trim()),
initialVersion: guide.version,
persist: (next, expectedVersion) => saveGuideOverview(guide.id, { ...next, expectedVersion }),
storageKey: adminDraftKey(guide.id, "overview"),
localDraft,
parseLocalDraft: (candidate) => {
if (!candidate || typeof candidate !== "object") return null;
const draft = candidate as Record<string, unknown>;
if (typeof draft.overview !== "string" || !draft.cover || typeof draft.cover !== "object") return null;
const draftCover = draft.cover as Record<string, unknown>;
return typeof draftCover.id === "string" && typeof draftCover.url === "string" ? candidate as typeof localDraft : null;
},
restoreLocalDraft: (draft) => {
setOverview(draft.overview);
setCover(draft.cover);
},
});
return <Card><CardHeader><CardTitle>Overview</CardTitle><CardDescription>ภาพและข้อความนี้แสดงบนหน้าแรกและส่วนแนะนำของ Guide</CardDescription></CardHeader><CardContent><FieldGroup><Field><FieldLabel>Guide Image</FieldLabel><div className="relative mx-auto aspect-square w-full max-w-md overflow-hidden rounded-lg border"><Image src={cover.url} alt={`Guide ${guide.name}`} fill className="object-cover" /></div><MediaUploader onReady={(asset) => setCover(asset)} /></Field><Field><FieldLabel htmlFor="guide-overview">Overview</FieldLabel><Textarea id="guide-overview" value={overview} maxLength={500} onChange={(event) => setOverview(event.target.value)} /><FieldDescription>{overview.length}/500 ตัวอักษร</FieldDescription></Field></FieldGroup></CardContent><CardFooter className="justify-end"><AutosaveStatus state={autosave.state} issues={incompleteIssues} onSave={autosave.saveNow} /></CardFooter></Card>;
}