# Forms

A Slate plugin for public-facing forms — contact, quote requests,
intake, anything where a visitor fills in fields and you want the
result in an admin inbox.

## What it does

- **Admin builder** with a textarea-DSL for the field list (no
  drag-drop yet — that's a follow-up)
- **Public form** at `/forms/<slug>` and an iframe embed via
  `/forms/<slug>?embed=1`
- **Submissions inbox** in the admin — filter by form + read state,
  open into a right-rail detail view (kv-list + audit-trail
  components)
- **Email notification** to a configured address per form
- **Optional submitter confirmation** email
- **Webhooks** — POST JSON on submit; failures logged into
  `forms_webhook_log`
- **CSV export** per form

## Field types

This first cut supports: `text`, `email`, `tel`, `url`, `number`,
`date`, `textarea`, `select`, `radio`, `checkbox`.

Deferred to follow-up sessions: `file`, `signature`, multi-step,
conditional logic, payment field.

## Field DSL

One field per line, pipe-separated:

```
type|name|label|required|placeholder|options
```

Lines starting with `#` are comments. Examples:

```
# Contact form
text|full_name|Your name|required
email|email|Email|required
tel|phone|Phone
select|reason|Reason for contact||Choose…|sales,support,other
textarea|message|Tell us more|required
checkbox|consent|I agree to be contacted|required
```

- `name` must start with a letter and only contain letters, digits,
  underscores. Used as the form input name and the CSV column.
- `required` is the literal word `required`. Anything else means
  optional.
- `placeholder` shows as the input's grey placeholder text (and as
  the empty `<option>` label on selects).
- `options` is comma-separated. Required for `select` and `radio`,
  ignored elsewhere.

The DSL round-trips: editing a saved form re-renders the same DSL
text in the editor.

## Hooks

| Hook | Type | Args | When |
|---|---|---|---|
| `forms_submitted` | action | `(int $submissionId, int $formId, array $data)` | After a submission is saved and webhooks fire |

```php
Hook::addAction('forms_submitted', function (int $submissionId, int $formId, array $data) {
    // e.g. push to your CRM
});
```

## Webhooks

Configure one URL per line in the form's "Webhooks" textarea. Each
submission POSTs JSON:

```json
{
  "event": "forms.submitted",
  "submission_id": 42,
  "ref": "SUB-ABCD1234",
  "form": {"id": 1, "slug": "contact-us", "title": "Contact Us"},
  "submitted_at": "2026-05-28T14:22:00+00:00",
  "data": { "full_name": "Mariko", "email": "m@example.com", "message": "Hi!" }
}
```

Headers include `X-Slate-Form: <id>` and `X-Slate-Submission: <id>`
for routing on the receiver side. Timeout is 10s, no follow on
redirects. Each delivery is logged into `forms_webhook_log` with
status code + first 4KB of the response body.

## Embedding

```html
<iframe src="https://yoursite.com/forms/contact-us?embed=1"
        style="width:100%;border:0;min-height:520px"></iframe>
```

`?embed=1` strips the outer canvas and footer so the form sits
cleanly inside the host page.

## Permissions

| Key | Allows |
|---|---|
| `forms.view`   | See forms list, submissions inbox, submission detail |
| `forms.manage` | Create / edit / publish / delete forms; delete submissions |
| `forms.export` | Download a form's submissions as CSV |

Super-admins (role_id=1) get everything via short-circuit.

## Tables

- `forms_definitions` — title, slug, fields_json, settings, status
- `forms_submissions` — ref, data_json, submitter_email, ip, ua, read_at
- `forms_webhooks` — url, is_active, form_id
- `forms_webhook_log` — delivery attempts (status, response, error)

All tenant-scoped via `tenant_id`.

## Honeypot

The public form includes a hidden `website_url` field. Bots fill it;
humans don't. Submissions with that field set are silently dropped
(success page is still shown, but nothing is saved).

## What's NOT in v0.1

- Drag-drop field builder UI
- Multi-step forms / conditional logic
- File upload field
- Signature field (SignaturePad)
- PDF generation per submission
- Stripe payment field
- Anti-spam beyond honeypot + CSRF + auth-rate-limit (reCAPTCHA, etc.)

These are tracked under Phase 3 of `SLATE_ROADMAP.md` for follow-up
sessions.
