Files
sim/apps
Waleed af53eda5ac feat(landing): reintroduce /contact page styled like /demo (#5315)
* feat(landing): reintroduce /contact page styled like /demo

- Restore the /contact page (removed in #5181) with a two-column layout
  mirroring /demo: value prop + trusted-by logos on the left, a message
  form card on the right, on the platform light tokens and chip components
- Restore the contact contract, /api/contact route (rate-limit, honeypot,
  Turnstile, help-inbox notification + visitor confirmation), now fully
  contract-bound via parseRequest
- Add a useSubmitContact React Query mutation hook
- Link Contact from the footer Resources column and add it to the sitemap

* fix(contact): server-authoritative captcha + review fixes

- Make captcha server-authoritative: drop the client-trusted captchaUnavailable
  flag; a valid Turnstile token is the only way past the stricter fallback
  bucket, so callers can't opt out of the challenge
- Re-execute the Turnstile widget on every submit (incl. after expiry) instead
  of falling into the no-captcha path once the token expires
- Reset the pre-submit gate on mutation settle so rapid double-clicks can't fire
  a duplicate /api/contact request
- Map only feature_request to its email type; every other topic resolves to a
  General Inquiry confirmation so support requests aren't labeled bug reports
- Drop the confirmation-email promise from the success copy (it's best-effort)
- Collapse the duplicated no-captcha rate-limit branch; hoist shared response
  constants; read the Turnstile site key as a module constant

* fix(contact): drop redundant Turnstile hostname pin

The Turnstile site key is already domain-bound in Cloudflare, so pinning
expectedHostname to the marketing SITE_URL (www.sim.ai) only rejected valid
tokens issued on self-hosted, preview, and apex-vs-www hosts. Remove the pin
and rely on Cloudflare's own domain binding.

* fix(contact): fail closed on the no-captcha rate-limit backstop

checkRateLimitDirect fails open on limiter-storage errors so a limiter outage
never takes down normal traffic. But the contact route's no-captcha bucket is
the only throttle on token-less submits, so a fail-open there let uncaptcha'd
requests reach the email path unthrottled during an outage.

- Add an opt-in { failClosed } option to checkRateLimitDirect; default behavior
  (fail open) is unchanged
- Use failClosed on the contact no-captcha backstop so an unenforceable limit
  rejects instead of admitting
- Cover both fail-open and fail-closed paths with tests

* refactor(contact): TSDoc over inline comments

Move the captcha-design rationale into the route handler's TSDoc and drop the
inline body/JSX comments, per the project's TSDoc-only comment convention.
2026-07-01 01:17:11 -07:00
..
…
…