studio-website (sitecrate.ca)
studio-website is the public face of SiteCrate at sitecrate.ca. It is a
single-page React 18 app that does three jobs: market the agency, capture
prospect intake, and let clients track their project.
Routing
Section titled “Routing”Hash-based routing — no React Router. All navigation goes through go(route)
in App.jsx, which sets window.location.hash and calls trackPageView. The
route is read from the hash on load and on every hashchange event.
Routes:
home | portfolio | how | pricing | start | contact | status/:tokenstatus/:token is the client-facing project tracker. The token is a UUID stored
in Supabase — not guessable. It reads via the get_project_status RPC, not
direct table access.
Data flow
Section titled “Data flow”The public site never touches the projects table directly. All anon access
goes through SECURITY DEFINER RPCs keyed on the status_token capability, so
the anon key can only ever see or affect the single project whose token is held.
| Surface | File | Calls |
|---|---|---|
| Intake form | IntakePage.jsx | supabase.rpc('create_intake', { payload }) → returns the new status_token → fires send-email with { type: 'intake', token } (non-blocking) |
| Status page (read) | StatusPage.jsx | rpc('get_project_status', { p_token }) — client-safe columns only |
| Status page (write) | StatusPage.jsx | rpc('submit_project_feedback') / rpc('approve_project'), then fires send-email with { type: 'feedback' | 'approved', token } |
See Capability-token RPCs & RLS for the full security model.
The send-email function
Section titled “The send-email function”netlify/functions/send-email.js is the only email sender in the entire
system — the admin app calls it too.
- It resolves the project from the DB by token using the service-role key.
- It builds all recipients and content server-side — never from the request body — so a forged request body can’t redirect or inject email.
- CORS is locked to SiteCrate origins.
- It sends via Resend from
hello@sitecrate.ca. - Requires
SUPABASE_SERVICE_ROLE_KEY+ the Supabase URL in the function env.
Email types handled: intake, review, launch, feedback, approved.
Analytics
Section titled “Analytics”GA4 property G-ZQDTTDS80V. All tracking flows through src/analytics.js —
import track for preset events or trackEvent for custom ones. Page views
fire on every route change via trackPageView.
Status routes are normalized: status/abc-123 is tracked as Project Status
— the raw UUID is never sent to GA4 as a page title.
Tracked events: cta_click, generate_lead, pricing_click, portfolio_filter.
See Analytics.
Styling
Section titled “Styling”All styles live in src/styles.css. CSS custom properties in :root; the
accent colour uses oklch(). No CSS modules, no Tailwind.
Breakpoints: 820px (nav), 720px (layout), 640px (status grid),
620px (form grid), 520px (footer/portfolio).
Commands
Section titled “Commands”npm run dev # local dev server (Vite, port 5173)npm run build # production build → dist/npm run preview # serve dist/ locallynpm run lint # ESLint — must pass before merging any PRnetlify dev # local dev with Netlify functions (required to test send-email)