Skip to content

feat(contact): wire up Send us a message form → Airtable (#41)#50

Merged
koechkevin merged 6 commits into
mainfrom
fix/contact-form-41
Jul 10, 2026
Merged

feat(contact): wire up Send us a message form → Airtable (#41)#50
koechkevin merged 6 commits into
mainfrom
fix/contact-form-41

Conversation

@koechkevin

@koechkevin koechkevin commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

The Contact Us "Send us a message" form was fully static — the Submit button was type="button" with no handler and no backend. This wires it end-to-end: validation → reCAPTCHA v3 → Airtable.

Decisions & rationale

  • Store submissions in Airtable (not Slack). The first cut posted to a Slack webhook. Per feedback, Slack alone gives PesaCheck no durable, trackable record — no way to see contact details or which requests have been responded to over time. The stakeholder's priority order was CMS → existing platform → Airtable:
    • CMS (Superdesk/Hasura): not feasible. The data layer (lib/data/) is read-only editorial content (swp_*); there's no table or write access for inbound contact requests, and it's the wrong system for it.
    • Airtable: chosen. Each submission becomes a row with a Status column (New → In Progress → Responded → Closed), so PC gets a proper request log. Airtable has native Slack notifications, so instant pings are still available — configured in Airtable, not in our code.
  • reCAPTCHA v3 (score ≥ 0.7), env-gated. Invisible, no user friction. Enforced only when keys are set, so local dev works without them. Google badge hidden with the required notice by the submit button.
  • Route Handler + sonner toast. Consistent with the newsletter form (PesaCheck UI | Fix | Home Page | Subscribe to receive update #43). Manual validation (no new deps).

How it works

POST /api/contact validates input → verifies reCAPTCHA (when configured) → creates a record via POST https://api.airtable.com/v0/{base}/{table} (typecast: true, Status: "New").

Configuration (set in Vercel)

Env var Purpose
AIRTABLE_API_TOKEN Personal Access Token with data.records:write on the base
AIRTABLE_BASE_ID Target base id (app…)
AIRTABLE_TABLE_NAME Table name (default Contact Us)
NEXT_PUBLIC_RECAPTCHA_SITE_KEY / RECAPTCHA_SECRET_KEY reCAPTCHA v3 keys

Airtable table columns (exact names): Name, Email, Subject, Message (text), Status (single-select).

UI walkthrough

Verified locally against a live Airtable base — a real row was created and the success toast shown.

1. Empty form
01-empty-form

2. Filled in
02-filled-form

3. Success toast after submit
03-success-toast

Testing

Case Result
Missing fields 400 "Please fill in all fields."
Invalid email 400 "Please enter a valid email address."
Not configured 500 "Messaging is not configured…"
Airtable error (bad base) 502, error logged
Happy path (live Airtable) 200 {ok:true} + row created + success toast ✅

tsc --noEmit and biome lint pass.

Closes #41

The contact form's Submit button was inert. Wire it to a new
POST /api/contact route handler that validates input, verifies
reCAPTCHA v3 (score >= 0.7, env-gated), and forwards submissions
to a Slack channel via an Incoming Webhook.

- app/api/contact/route.ts: validation + captcha + Slack Block Kit post
- ContactMessageForm: client form with reCAPTCHA v3, sonner toasts,
  pending state; extracted from the static ContactForm
- Hide reCAPTCHA badge, show required Google notice by the submit button
- Env: SLACK_CONTACT_WEBHOOK_URL, NEXT_PUBLIC_RECAPTCHA_SITE_KEY,
  RECAPTCHA_SECRET_KEY (documented in .env.example)
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pesacheck-ui Ready Ready Preview, Comment Jul 10, 2026 12:50pm

Request Review

@koechkevin
koechkevin marked this pull request as ready for review July 9, 2026 14:29
@koechkevin koechkevin self-assigned this Jul 10, 2026
Per stakeholder feedback on PR #50: Slack alone gives PesaCheck no
durable, trackable record of requests (status, contact details,
follow-up). Store each submission as a row in an Airtable base (with a
Status column) so requests can be tracked over time. Slack pings, if
wanted, are configured natively in Airtable rather than in code.

- app/api/contact/route.ts: POST to Airtable /v0/{base}/{table} (typecast)
  in place of the Slack webhook; validation + reCAPTCHA unchanged
- Env: swap SLACK_CONTACT_WEBHOOK_URL for AIRTABLE_API_TOKEN,
  AIRTABLE_BASE_ID, AIRTABLE_TABLE_NAME (default "Contact Us")
@koechkevin koechkevin changed the title feat(contact): wire up Send us a message form (#41) feat(contact): wire up Send us a message form → Airtable (#41) Jul 10, 2026
Comment thread app/api/contact/route.ts Outdated
RECAPTCHA_SCORE_THRESHOLD overrides the default 0.7 cutoff; invalid or
unset values fall back to 0.7.
@koechkevin
koechkevin merged commit ef6703f into main Jul 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PesaCheck UI | Fix | Contact Us | Send Us a Message form is not working

2 participants