From 1fbc6cab5b47507d096f097a4a669b8cb6a01126 Mon Sep 17 00:00:00 2001 From: kongkx Date: Thu, 10 Sep 2026 14:10:03 +0800 Subject: [PATCH] refactor(studio): validate editors with zod --- .../studio/category-brief-edit-sheet.tsx | 94 +++++-------------- src/features/studio/subtopic-edit-sheet.tsx | 67 +++++-------- 2 files changed, 45 insertions(+), 116 deletions(-) diff --git a/src/features/studio/category-brief-edit-sheet.tsx b/src/features/studio/category-brief-edit-sheet.tsx index afe2ac5..046c0cc 100644 --- a/src/features/studio/category-brief-edit-sheet.tsx +++ b/src/features/studio/category-brief-edit-sheet.tsx @@ -1,83 +1,31 @@ -import { useEffect, useState } from 'react' +import { useEffect } from 'react' +import { zodResolver } from '@hookform/resolvers/zod' +import { Controller, useForm } from 'react-hook-form' import { toast } from 'sonner' +import { z } from 'zod' +import { createCategoryBrief, updateCategoryBrief, type CategoryBriefPayload } from '@/api/modules/studio' import type { StudioCategoryBrief } from '@/api/types' -import { - createCategoryBrief, - updateCategoryBrief, - type CategoryBriefPayload, -} from '@/api/modules/studio' -import { handleApiError } from '@/lib/errors' -import { CategoryPicker } from '@/features/categories/category-picker' import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet' import { Textarea } from '@/components/ui/textarea' +import { CategoryPicker } from '@/features/categories/category-picker' +import { handleApiError } from '@/lib/errors' -interface Props { - brief: StudioCategoryBrief | null - creating: boolean - onOpenChange: (open: boolean) => void - onSaved: () => void -} - +const schema = z.object({ category_id: z.string().min(1, '请选择分类'), description: z.string(), target_audience: z.string(), content_angles: z.string(), risk_notes: z.string() }) +type FormValues = z.infer +interface Props { brief: StudioCategoryBrief | null; creating: boolean; onOpenChange: (open: boolean) => void; onSaved: () => void } export function CategoryBriefEditSheet({ brief, creating, onOpenChange, onSaved }: Props) { - const open = creating || brief !== null - const [categoryId, setCategoryId] = useState(null) - const [description, setDescription] = useState('') - const [targetAudience, setTargetAudience] = useState('') - const [contentAngles, setContentAngles] = useState('') - const [riskNotes, setRiskNotes] = useState('') - const [saving, setSaving] = useState(false) - - useEffect(() => { - setCategoryId(brief?.category_id ?? null) - setDescription(brief?.description ?? '') - setTargetAudience(brief?.target_audience ?? '') - setContentAngles(brief?.content_angles?.join('\n') ?? '') - setRiskNotes(brief?.risk_notes ?? '') - }, [brief, creating]) - - const save = async () => { - if (!categoryId) { - toast.error('请选择分类') - return - } - - const payload: CategoryBriefPayload = { - category_id: categoryId, - description: description.trim() || null, - target_audience: targetAudience.trim() || null, - content_angles: contentAngles.split('\n').map((value) => value.trim()).filter(Boolean), - risk_notes: riskNotes.trim() || null, - } - - setSaving(true) - try { - if (brief) await updateCategoryBrief(brief.id, payload) - else await createCategoryBrief(payload) - toast.success(brief ? '分类简报已更新' : '分类简报已创建') - onSaved() - onOpenChange(false) - } catch (error) { - handleApiError(error) - } finally { - setSaving(false) - } + const form = useForm({ resolver: zodResolver(schema), defaultValues: { category_id: '', description: '', target_audience: '', content_angles: '', risk_notes: '' } }) + useEffect(() => { form.reset({ category_id: brief?.category_id ?? '', description: brief?.description ?? '', target_audience: brief?.target_audience ?? '', content_angles: brief?.content_angles?.join('\n') ?? '', risk_notes: brief?.risk_notes ?? '' }) }, [brief, creating, form]) + const submit = async (values: FormValues) => { + const payload: CategoryBriefPayload = { category_id: values.category_id, description: values.description.trim() || null, target_audience: values.target_audience.trim() || null, content_angles: values.content_angles.split('\n').map((value) => value.trim()).filter(Boolean), risk_notes: values.risk_notes.trim() || null } + try { if (brief) await updateCategoryBrief(brief.id, payload); else await createCategoryBrief(payload); toast.success(brief ? '分类简报已更新' : '分类简报已创建'); onSaved(); onOpenChange(false) } catch (error) { handleApiError(error, form.setError) } } - - return ( - - - {brief ? '编辑分类简报' : '新建分类简报'} -
-
-