# Forms and Fields

All forms use React Hook Form, Zod, and the project wrappers in `src/components/hook-form`.

```tsx
const methods = useForm<FormValues>({ resolver: zodResolver(schema), defaultValues });

<Form methods={methods} onSubmit={methods.handleSubmit(onSubmit)}>
  <Field.Text name="name" label={t('feature.name')} />
</Form>
```

Use `src/lib/schema-helpers.ts` for shared validation and `BilingualField` for paired Arabic/English values. Do not create controlled form fields with manual `useState`, and do not render raw MUI `TextField` in a domain form when a `Field.*` wrapper exists.

Inspect `src/components/hook-form/fields` for the available typed field variants before creating a new one.

## Images

`Field.ImageUpload` handles every admin picture (advertisements, campaigns, contestants, contests). It is a two-step field: the picked file goes to the resource's `upload` endpoint, and the form keeps only the returned **filename**, which is what the API validates against its stored files.

```tsx
<Field.ImageUpload
  name="image"
  label={t('voting.campaigns.image')}
  uploadUrl={endpoints.voting.campaigns.upload}
  previewUrl={campaign?.image} // full URL of the record being edited
  onUploadingChange={setUploading} // disable submit while the file is in flight
/>
```

Use `variant="avatar"` for portraits. Every image is optional: the field shows a remove button once one is set, and clearing it must submit `null` so the API drops the column and deletes the file. Reset the field with `storedFileName(record?.image)`, and on edit omit the key from the payload when it still equals that value — records created before the upload flow would otherwise fail validation.
