A local recommendations directory for Bolton, UK: local people, vouched for by Bolton. Built with Next.js (App Router), Tailwind and Supabase.
npm install
cp .env.example .env.local
npm run dev
.env.example)| Key | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase API URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anon (public) key |
SUPABASE_SERVICE_ROLE_KEY |
Server-only service role key |
DEVICE_HASH_SECRET |
Server-only secret for hashing device identifiers |
NEXT_PUBLIC_SITE_URL |
Public site URL |
OWNER_LEGAL_NAME |
Legal name of the site owner (data controller); shown on the consent page and privacy notice |
CONTACT_EMAIL |
Public contact address for privacy requests and content reports |
SUPABASE_DB_URL |
Postgres URL used by npm run test:db |
npx supabase start
Requires Docker. Local development also needs psql and pg_prove on your PATH (used by the DB scripts and npm run test:db).
In sandboxes without Docker, use npm run stack:start (and npm run stack:stop), which starts an equivalent local stack with the same defaults as .env.example.
npm run db:reset (also run by the E2E setup) drops and rebuilds schema public with psql, which needs a superuser and is meant for that sandbox stack. With the real Supabase CLI stack set SUPABASE_CLI_STACK=1 (CI does) so it runs npx supabase db reset --local instead. Both refuse non-local databases. Run npm run test:db right after a reset: the scoring tests expect an empty register, without the E2E fixtures.
The admin area (/admin) uses Supabase email + password. Public sign-ups are disabled. To create the real admin:
In the SQL editor, give that user the admin role:
insert into public.profiles (user_id, role)
select id, 'admin' from auth.users where email = 'you@example.com'
on conflict (user_id) do update set role = 'admin';
The E2E suite creates admin@example.test / e2e-admin-password itself (scripts/e2e-prepare.sh); local databases only.
npm run dev / build / startnpm run lint, npm run typechecknpm test - Vitest unit testsnpm run test:db - pgTAP tests via pg_prove (needs a running local Supabase)npm run test:e2e - Playwright (builds and serves the app; set PW_CHROMIUM_PATH to use a custom Chromium)npx supabase link --project-ref <ref> then npx supabase db push.NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY from the Supabase project.DEVICE_HASH_SECRET: a strong random secret (for example openssl rand -hex 32). Do not reuse change-me.NEXT_PUBLIC_SITE_URL: the public URL of the site (for example https://boltonvouched.co.uk). Must be set: without it, provider consent links copied from the admin area are relative paths that do not work when sent to a provider.OWNER_LEGAL_NAME and CONTACT_EMAIL: required. In production the provider consent page fails closed (it will not render) without them, and the privacy notice and terms use them.SUPABASE_SERVICE_ROLE_KEY, DEVICE_HASH_SECRET) a NEXT_PUBLIC_ prefix: NEXT_PUBLIC_ variables are inlined into the browser bundle and are public./privacy, /terms and /how-ratings-work carry a “Draft — pending legal review” banner. Have them reviewed, then remove the banner (DRAFT_BANNER in components/PolicyPage.tsx) before launch. They are prerendered at build time, so redeploy after changing OWNER_LEGAL_NAME or CONTACT_EMAIL; in production the build fails if either is unset.OWNER_LEGAL_NAME and CONTACT_EMAIL, deploy, and verify /privacy shows them. Production values:
OWNER_LEGAL_NAME=YSB Ventures Ltd (check the exact registered name on Companies House and match it character for character)CONTACT_EMAIL=yameen@ysbdesigns.uk/privacy, /terms, /how-ratings-work), then remove the draft banner.