Skip to content

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Mal — Waitlist Landing Page

A fast, statically-generated waitlist landing page for Mal, an AI-native digital bank testing appetite for a lending product in Egypt.

Live URL: https://<your-deployment>.vercel.app (replace after deploy)


Tech stack

  • Next.js (App Router, TypeScript) — page is statically generated (SSG) for fastest load and full crawlability.
  • Tailwind CSS v4 — utility styling, no CSS-in-JS, no component kit.
  • Supabase (Postgres) — signups written server-side with the service-role key.
  • Zod — server-side (authoritative) validation, mirrored client-side for UX.
  • Deployed on Vercel.

The page itself is a Server Component; only the signup form is a Client Component. No analytics SDKs, no heavy images (all UI is CSS/SVG), one self-hosted font family via next/font.


Setup

# 1. Clone
git clone <your-repo-url>
cd mal-waitlist

# 2. Install dependencies
npm install

# 3. Configure environment
cp .env.example .env.local
# then edit .env.local with your real values (see below)

# 4. Run locally
npm run dev
# open http://localhost:3000

The page renders and builds without Supabase configured. Env vars are only required for the form write to succeed at runtime.


Environment variables

Copy .env.example → .env.local and fill in:

Variable Description
NEXT_PUBLIC_SITE_URL Public base URL of the deployed site, no trailing slash (e.g. https://mal-waitlist.vercel.app). Used for canonical URL, Open Graph, sitemap.xml, and robots.txt. Defaults to http://localhost:3000 in dev. The only public var — safe to expose.
SUPABASE_URL Supabase project URL, e.g. https://xxxx.supabase.co. Found in Project Settings → API → Project URL. Server-side only.
SUPABASE_SERVICE_ROLE_KEY Supabase service-role secret. Bypasses Row Level Security, so it is server-side only — never prefix with NEXT_PUBLIC_. Found in Project Settings → API → service_role.

Database

Schema

The migration lives at supabase/migrations/0001_init.sql. It creates public.waitlist_signups:

Column Type Notes
id uuid PK, gen_random_uuid()
full_name text required
email text nullable
phone text nullable
referral_source text nullable
market text required, default 'egypt'
created_at timestamptz default now()

Constraints & indexes:

  • contact_required check: email IS NOT NULL OR phone IS NOT NULL — at least one contact method.
  • Unique index on (lower(email), market) where email is not null — case-insensitive, one email per market, still allows phone-only signups.
  • Index on (market, created_at desc) — fast "newest signups per market" admin query.

Security model — RLS enabled, no public policies

Row Level Security is enabled on the table and no policies are added. This means the anon and authenticated roles have zero access. All writes go through the service-role key on the server (which bypasses RLS), so the table stays fully locked to the public. There is no client-side database access anywhere in this app.

Applying the migration

Option A — Supabase SQL Editor (fastest):

  1. Open your project → SQL Editor → New query.
  2. Paste the contents of supabase/migrations/0001_init.sql.
  3. Click Run.

Option B — Supabase CLI:

# one-time
supabase link --project-ref <your-project-ref>

# apply migrations in supabase/migrations/
supabase db push

Deployment (Vercel)

  1. Push this repo to GitHub/GitLab/Bitbucket.
  2. In Vercel: New Project → import the repo. Framework preset auto-detects Next.js.
  3. Add the environment variables (Project Settings → Environment Variables) for the Production (and Preview) environments:
    • NEXT_PUBLIC_SITE_URL → your production URL
    • SUPABASE_URL
    • SUPABASE_SERVICE_ROLE_KEY
  4. Deploy. Vercel runs npm run build and serves the static page.
  5. After the first deploy, set NEXT_PUBLIC_SITE_URL to the real domain and redeploy so canonical/OG/sitemap URLs are correct.

What I'd do next with 2 more hours

  • Anti-spam: the form already ships a honeypot field; next add Cloudflare Turnstile (invisible) and rate limiting on the Server Action (e.g. Upstash Redis, per-IP) to stop scripted floods.
  • Double opt-in: send a confirmation email/SMS with a verification link and a confirmed_at column, so the list is deliverable and consented.
  • Event tracking: add a privacy-light analytics beacon (Vercel Analytics or a single Plausible call) to measure form-view → submit conversion and referral-source performance.
  • A/B headline test: serve two hero headlines via a cookie split and record the variant on the signup row to see which drives more completions.
  • Admin view: a tiny authenticated /admin reading waitlist_signups (newest-first, per market) instead of eyeballing rows in Supabase.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages