"use client"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { api } from "@/lib/api/client"; import { useToast } from "@/components/Toast"; import { GENRES, SELLING_POINT_PRESETS, STRUCTURES, WIZARD_STEPS, canAdvance, canSubmit, clampStep, emptyWizardForm, toCreateRequest, type WizardForm, } from "@/lib/wizard/wizard"; const STEP_TITLES = [ "书名 + 题材", "一句话故事 + 卖点 + 结构", "立意 / 总纲", "主角 / 金手指", "基础世界观", ]; // 立项向导(UX §6.2)。Client Component:收集字段 → POST /projects → 进工作台。 export function ProjectWizard() { const router = useRouter(); const toast = useToast(); const [step, setStep] = useState(1); const [form, setForm] = useState(emptyWizardForm); const [submitting, setSubmitting] = useState(false); const update = (patch: Partial): void => setForm((prev) => ({ ...prev, ...patch })); const toggleSellingPoint = (point: string): void => setForm((prev) => ({ ...prev, sellingPoints: prev.sellingPoints.includes(point) ? prev.sellingPoints.filter((p) => p !== point) : [...prev.sellingPoints, point], })); const goBack = (): void => setStep((s) => clampStep(s - 1)); const goNext = (): void => setStep((s) => clampStep(s + 1)); const submit = async (): Promise => { if (!canSubmit(form)) { toast("请先填写书名", "error"); setStep(1); return; } setSubmitting(true); const { data, error } = await api.POST("/projects", { body: toCreateRequest(form), }); if (error || !data) { setSubmitting(false); toast("创建作品失败,请重试", "error"); return; } router.push(`/projects/${data.id}/write`); }; const isLast = step === WIZARD_STEPS; return (

新建作品

步骤 {step}/{WIZARD_STEPS} · {STEP_TITLES[step - 1]}

{step === 1 && } {step === 2 && ( )} {step === 3 && } {step === 4 && } {step === 5 && }
{isLast ? ( ) : ( )}
); } function StepDots({ current }: { current: number }) { return ( ); } interface StepProps { form: WizardForm; update: (patch: Partial) => void; } function Field({ label, children, }: { label: string; children: React.ReactNode; }) { return ( ); } const inputCls = "w-full rounded border border-line bg-bg px-3 py-2 text-ink focus:border-cinnabar focus:outline-none"; function StepBasics({ form, update }: StepProps) { return (
update({ title: e.target.value })} placeholder="例:逐光而行" autoFocus />
); } function StepStory({ form, update, toggleSellingPoint, }: StepProps & { toggleSellingPoint: (p: string) => void }) { return (