Skip to content

polish(contact): zod validation, persistent success/error, fallback email#22

Open
f1shyfang wants to merge 1 commit into
mainfrom
polish/contact
Open

polish(contact): zod validation, persistent success/error, fallback email#22
f1shyfang wants to merge 1 commit into
mainfrom
polish/contact

Conversation

@f1shyfang
Copy link
Copy Markdown
Owner

Summary

Polish pass on /contact. Critique score moved from 17/40 → 34/40. This is one of two surfaces (with /sponsors) where prospective industry contacts decide whether to reach out — form UX matters disproportionately here.

Validation & errors

  • Rebuilt with react-hook-form + zod (existing deps; no new dependencies).
  • Validation runs onBlur with onChange re-validation.
  • Plain-language error messages with concrete fix guidance ("That email looks incomplete. Check for an @ and a domain." not "Invalid input").

Inputs

  • Each field: top label with htmlFor/id, autoComplete (name, email), inputMode="email", aria-required, aria-invalid, aria-describedby linking to either the error or a calm helper line.
  • Helper text sets expectations ("We will only use this to reply..." / "A short line like 'Sponsorship enquiry'...").
  • DESIGN.md input token: rounded-md, border-gray-200, px-4 py-3, focus border blue-600 with ring-3 ring-blue-600/25.
  • Required-field legend with screen-reader fallback.

Submit & state

  • Submit button now bg-[#1B397E] (was bg-blue-600), full-width on mobile, aria-busy when sending, label flips to "Sending..." with disabled.
  • Success now replaces the form with a persistent confirmation, restates reply-time SLA, surfaces fallback email, offers "Send another message" + "Email us instead" CTAs. Was auto-dismissing after 5s.
  • Error uses role="alert", does not auto-dismiss, points to fallback email with plain-language fix guidance.

Layout & tokens

  • lg:grid-cols-5 with form lg:col-span-3 / info lg:col-span-2 — gives the form proper weight.
  • Headings text-gray-900 + tracking-tight; body text-gray-700; helpers text-gray-600; hairline border-gray-200.
  • Buzzword copy ("We'd love to hear from you", "ready to answer all your questions") replaced.
  • Icons get aria-hidden, recoloured to Brand Navy.

Test plan

  • npm run dev/contact — verify form layout works at mobile and desktop
  • Submit empty form → confirm inline errors appear under each required field with concrete fix wording
  • Type invalid email → confirm helper text shifts to error
  • Submit valid form → confirm success state persists (no auto-dismiss), shows reply-time and fallback email
  • Simulate server error → confirm error state persists with fallback email
  • Tab through the form → confirm tab order is label → input → label → input
  • On mobile, verify autoComplete fills name/email from saved data

Open questions for the team

  • Is ceus@unsw.edu.au actually monitored, or is there a sponsorship-specific alias?
  • Is the "3 to 4 days during term" reply-time SLA accurate? Sponsor enquiries probably warrant sub-48h.
  • Anti-spam (Turnstile/honeypot)? Without one this form will get scraped.
  • Should sponsor enquiries route to a separate form (with org/role/budget fields) rather than this general route?

Verification

  • tsc --noEmit: clean
  • eslint: clean

Merge note

Independently mergeable. Touches only CEUS/src/app/contact/page.tsx and ContactClient.tsx.

🤖 Generated with Claude Code

…or, fallback email

This is one of two surfaces (with /sponsors) where industry contacts
decide whether to reach out. The form was missing real validation
(browser-default messages on submit only), missing autoComplete (bad
mobile UX), and auto-dismissed success/error after 5s so a sponsor
who looked away missed confirmation.

Form rebuilt with react-hook-form + zod (existing deps; no new
dependencies). Validation runs onBlur with onChange re-validation,
producing plain-language messages with concrete fix guidance
("That email looks incomplete. Check for an @ and a domain.").
Each field gets: top label with htmlFor/id, autoComplete (name,
email), inputMode="email" on email, aria-required, aria-invalid,
aria-describedby pointing at error OR a calm helper line setting
expectations.

Submit button: bg-[#1B397E] (was bg-blue-600), full-width on mobile,
aria-busy when sending, label flips to "Sending..." with disabled
state. Focus-visible ring with token-aligned blue-600/25 tint.

Success state now replaces the form with a friendly confirmation,
restates reply-time SLA ("3 to 4 days during term"), surfaces a
fallback email address, and offers "Send another message" + ghost
"Email us instead" CTAs. Server-error path uses role="alert", does
not auto-dismiss, points to fallback email with plain-language fix
guidance.

Layout switched from 50/50 columns to lg:grid-cols-5 with form
lg:col-span-3 / info lg:col-span-2, giving the form proper weight.
Tokens corrected throughout: text-gray-900 with tracking-tight for
headings; text-gray-700 body; text-gray-600 helpers; hairline
border-gray-200; rounded-md inputs with px-4 py-3.

Required-field legend with hidden screen-reader text. Icons get
aria-hidden and recolour from text-blue-500 to text-[#1B397E]. Map
iframe title made descriptive. Buzzword copy ("We'd love to hear
from you", "ready to answer all your questions") replaced.

tsc --noEmit: clean. eslint: clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 29, 2026 11:30
@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

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

Project Deployment Actions Updated (UTC)
ceus-website Ready Ready Preview, Comment May 29, 2026 11:31am

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Polish pass on the /contact surface by improving form UX/accessibility and making submit feedback (success/error) persistent, while aligning layout and styling with current design tokens.

Changes:

  • Updated /contact metadata title/description to be more specific and include response-time expectations.
  • Rebuilt the contact form UI with react-hook-form + zod validation and improved a11y attributes/helper text.
  • Reworked submit states so success and error messaging persist and include a fallback email CTA.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
CEUS/src/app/contact/page.tsx Updates contact page metadata (title/description).
CEUS/src/app/contact/ContactClient.tsx Replaces the contact form implementation with RHF+Zod validation, persistent success/error states, and refreshed layout/styling/a11y.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +74 to +76
setServerErrorMessage(
'Something went wrong on our end. Please try again in a moment, or email us directly.'
);
Comment on lines +8 to +9
description:
'Get in touch with the Chemical Engineering Undergraduate Society at UNSW. Send us a message about events, merch, or sponsorship and we will reply within 3 to 4 days during term.',
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants