From dac78163c31f3c0bf29ada6417ecf8f74788a25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Ign=C3=A1cio=20Fontoura=20Solla?= Date: Sun, 22 Feb 2026 23:08:00 -0300 Subject: [PATCH] feat: add petition form config and server-action submission flow - add group to collection with title, subtitle, and required consent relationship - refactor to: - use a dedicated shared zod schema - submit via server action (create contact + signature) - register RHF fields and handle pending/error states - load city options from selected state - add server action - add shared schema - replace state source in collection: derive select options from - make immutable with and remove obsolete - update generated payload types for petition and consent references --- .../actions/submitPetitionSignature.ts | 32 +++++ src/collections/Contact.ts | 4 +- src/collections/Petition.ts | 25 ++++ src/components/PetitionForm.tsx | 136 +++++++++--------- src/lib/cities.ts | 2 +- src/lib/schemas/petition-form.ts | 40 ++++++ src/lib/states.ts | 29 ---- src/payload-types.ts | 32 +++-- 8 files changed, 192 insertions(+), 108 deletions(-) create mode 100644 src/app/(frontend)/actions/submitPetitionSignature.ts create mode 100644 src/lib/schemas/petition-form.ts delete mode 100644 src/lib/states.ts diff --git a/src/app/(frontend)/actions/submitPetitionSignature.ts b/src/app/(frontend)/actions/submitPetitionSignature.ts new file mode 100644 index 00000000..57ac6bba --- /dev/null +++ b/src/app/(frontend)/actions/submitPetitionSignature.ts @@ -0,0 +1,32 @@ +'use server' + +import config from '@payload-config' +import { petitionFormSchema, type PetitionFormInput } from '@/lib/schemas/petition-form' +import { getPayload } from 'payload' + +type SubmitPetitionSignatureInput = PetitionFormInput & { + petitionId: string + consentId: number +} + +export const submitPetitionSignature = async (input: SubmitPetitionSignatureInput) => { + const { petitionId, consentId, ...contactInput } = input + const data = petitionFormSchema.parse(contactInput) + const payload = await getPayload({ config }) + + const contact = await payload.create({ + collection: 'contact', + data, + }) + + await payload.create({ + collection: 'signature', + data: { + contact: contact.id, + petition: petitionId, + consent: consentId, + }, + }) + + return { ok: true as const } +} diff --git a/src/collections/Contact.ts b/src/collections/Contact.ts index 6ccdb84a..86b0df43 100644 --- a/src/collections/Contact.ts +++ b/src/collections/Contact.ts @@ -1,4 +1,4 @@ -import { STATES } from '@/lib/states' +import { CitiesByState } from '@/lib/cities' import { CollectionConfig } from 'payload' export const Contact: CollectionConfig = { @@ -38,7 +38,7 @@ export const Contact: CollectionConfig = { name: 'state', type: 'select', label: 'Estado', - options: STATES, + options: Object.keys(CitiesByState) as (keyof typeof CitiesByState)[], required: true, }, { diff --git a/src/collections/Petition.ts b/src/collections/Petition.ts index 3034088b..b1aecea4 100644 --- a/src/collections/Petition.ts +++ b/src/collections/Petition.ts @@ -62,5 +62,30 @@ export const Petition: CollectionConfig = { label: 'Corpo', required: true, }, + { + name: 'form', + type: 'group', + label: 'Formulário', + required: true, + fields: [ + { + name: 'title', + type: 'text', + label: 'Título', + }, + { + name: 'subtitle', + type: 'text', + label: 'Subtítulo', + }, + { + name: 'consent', + type: 'relationship', + label: 'Consentimento', + relationTo: 'consent', + required: true, + }, + ], + }, ], } diff --git a/src/components/PetitionForm.tsx b/src/components/PetitionForm.tsx index 850de7e6..dbbb4c63 100644 --- a/src/components/PetitionForm.tsx +++ b/src/components/PetitionForm.tsx @@ -11,22 +11,14 @@ import { import { Input } from '@/components/ui/input' import { Petition } from '@/payload-types' import { zodResolver } from '@hookform/resolvers/zod' -import { useForm } from 'react-hook-form' -import z from 'zod' -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from './ui/select' -import { STATES } from '@/lib/states' +import { type SubmitHandler, useForm } from 'react-hook-form' import { NativeSelect, NativeSelectOption } from './ui/native-select' -import { ReactEventHandler, useEffect, useState } from 'react' +import { useState, useTransition } from 'react' import { CitiesByState } from '@/lib/cities' import { Textarea } from './ui/textarea' import { Button } from './ui/button' +import { petitionFormSchema, type PetitionFormInput } from '@/lib/schemas/petition-form' +import { submitPetitionSignature } from '@/app/(frontend)/actions/submitPetitionSignature' interface PetitionFormProps { id: string @@ -34,47 +26,64 @@ interface PetitionFormProps { } export const PetitionForm = ({ id, petition }: PetitionFormProps) => { - const methods = useForm>({ - resolver: zodResolver(formSchema), + const methods = useForm({ + resolver: zodResolver(petitionFormSchema), defaultValues: { name: '', email: '', phone: '', - state: '', + state: undefined, city: '', postalCode: '', comment: '', }, }) - const [cityOptions, setCityOptions] = useState([]) + const [isSubmitting, startTransition] = useTransition() - const handleStateUpdate: ReactEventHandler = (e) => { - if (!e.currentTarget.value) { + const [cityOptions, setCityOptions] = useState([]) + + const handleStateUpdate = (stateValue?: keyof typeof CitiesByState) => { + if (!stateValue) { setCityOptions([]) + methods.setValue('city', '') return } - const state = e.currentTarget.value as keyof typeof CitiesByState - - setCityOptions(CitiesByState[state]) - - console.log('ran') + setCityOptions(CitiesByState[stateValue]) + methods.setValue('city', '') } - console.log(cityOptions) + const onSubmit: SubmitHandler = (input) => { + const consentId = + typeof petition.form?.consent === 'number' ? petition.form.consent : petition.form.consent.id - const onSubmit = (data: z.infer) => { - console.log(data) + startTransition(async () => { + try { + await submitPetitionSignature({ + ...input, + petitionId: petition.id, + consentId, + }) + methods.reset() + setCityOptions([]) + } catch { + methods.setError('root', { + message: 'Falha ao enviar assinatura. Tente novamente.', + }) + } + }) } return (
- Revogar concessão da orla de Salvador - - Assine esta petição e ajude a manter a orla de Salvador para todos nós! - + {petition.form.title ? {petition.form.title} : null} + {petition.form.subtitle ? ( + + Assine esta petição e ajude a manter a orla de Salvador para todos nós! + + ) : null} Nome Completo @@ -84,6 +93,7 @@ export const PetitionForm = ({ id, petition }: PetitionFormProps) => { placeholder="Digite seu nome completo" autoComplete="name" maxLength={120} + {...methods.register('name')} required /> @@ -96,6 +106,7 @@ export const PetitionForm = ({ id, petition }: PetitionFormProps) => { inputMode="email" type="text" maxLength={254} + {...methods.register('email')} required /> @@ -108,12 +119,16 @@ export const PetitionForm = ({ id, petition }: PetitionFormProps) => { inputMode="tel" type="tel" maxLength={15} + {...methods.register('phone')} required /> Estado - + handleStateUpdate(e.target.value) })} + > Selecione um estado {Object.keys(CitiesByState).map((state) => ( @@ -124,7 +139,12 @@ export const PetitionForm = ({ id, petition }: PetitionFormProps) => { Cidade - + Selecione uma cidade {cityOptions.map((city) => ( @@ -135,48 +155,32 @@ export const PetitionForm = ({ id, petition }: PetitionFormProps) => { CEP - + Comentário -