BT-ADV Agency Website
A full-stack agency platform with a secure booking wizard, bilingual RTL/LTR UI, and cinema-themed confirmation tickets.
At a Glance
The Problem
BT-ADV is a premium video production and advertising studio. Before this platform, the agency had no structured digital presence — inquiries came through WhatsApp with no way to filter lead quality, capture project context, or confirm meetings with a paper trail. Brand managers commissioning high-budget advertising (EGP 300K–1M+) had no self-service intake flow. There was no central admin view of booking status and no bilingual support for the agency's Arabic-speaking market.
The Solution
A full-stack agency website with a production-quality multi-step booking wizard, cinema-themed confirmation ticket, complete admin CMS, bilingual RTL/LTR, and a hardened security layer. The 4-step wizard pre-qualifies every lead before submission: contact info → company profile → project goals → meeting preferences and budget. Lead qualification is baked into the UX — each step acts as a filter. The dark Navy + Yellow cinema aesthetic communicates premium positioning.
Architecture
- →Public-facing pages: Hero, Services, Works, Clients, Reviews, Booking wizard, Portfolio gallery, Pricing, About, Careers, Contact.
- →Admin panel (/admin/*): Auth-gated CRUD across 12 content types — bookings, works, clients, pricing, team, careers, BTS media, reviews, messages, site settings.
- →Edge Middleware: CSRF token injection (double-submit cookie pattern), Supabase session refresh, role check via JWT app_metadata (zero DB round-trip).
- →API Routes: /api/booking (POST: validate → rate-limit → CSRF → insert → dual email), /api/booking/slots (GET: slot availability), /api/csrf, /api/upload (Cloudinary), /api/contact.
- →Database: Supabase PostgreSQL with RLS on every table, UNIQUE constraint on (date, time_slot), custom JWT access token hook for role embedding.
- →External: Upstash Redis (rate limiting), Resend (email), Cloudinary (CDN), Sentry (observability), Vercel (hosting + analytics).
Key Features
- ✓4-step booking wizard with per-step validation — each step validates only its own fields, preventing advancement on errors
- ✓Cinema-style booking confirmation ticket generated on screen and sent as branded HTML email (reference code: BT-A3F9C12E)
- ✓Dual-layer rate limiting via Upstash Redis sliding window — 5 req/IP/10 min + 3 bookings/email/24h, running in parallel
- ✓CSRF protection via double-submit cookie pattern with constant-time token comparison to prevent timing attacks
- ✓Slot double-booking prevention: optimistic pre-check + PostgreSQL UNIQUE constraint as atomic last guard (catches 23505)
- ✓Bilingual Arabic/English with automatic RTL layout swap — CSS logical properties, Cairo font for AR, Inter for EN
- ✓JWT-embedded role check in Edge middleware — zero DB round-trip via custom Supabase Postgres access token hook
- ✓Transactional HTML email system with XSS prevention via escapeHtml() before all user-value interpolation
- ✓Admin CMS with full CRUD across 12 content types — bookings, works, clients, pricing, team, careers, BTS, reviews, messages, settings
- ✓Next.js unstable_cache with tag-based revalidation — pages cached 1 hour, revalidated on-demand after admin mutations
Screenshots
Code Highlight
Challenges & Solutions
🎯 Supabase cookie replacement silently discarding the CSRF token on session refresh
The CSRF token is generated before Supabase runs and stored in a local variable (`pendingCsrfToken`). After `supabase.auth.getUser()` resolves (potentially replacing the `response` object), the token is re-applied to whatever the final response is — requiring a deep understanding of Next.js middleware execution order and Supabase's internal cookie mutation pattern.
🎯 Slot double-booking race condition under concurrent load (TOCTOU vulnerability)
The pre-check provides a fast UX error. The real guarantee comes from a PostgreSQL UNIQUE constraint on `(date, time_slot)`. The API catches error code `23505` (unique violation) and returns a typed `SLOT_TAKEN` response — turning a raw DB error into a user-friendly message without exposing internals.
🎯 Admin role check adding DB latency on every Edge middleware request
Implemented a custom Supabase Postgres "Access Token Hook" that embeds the user's role into the JWT's `app_metadata` at issuance time. The Edge middleware reads the role directly from the decoded JWT — zero DB round-trip. A DB fallback is retained for old tokens, making the migration backwards-compatible.
Tech Stack
What I Learned
- 💡Middleware execution order matters as much as the code itself. In Next.js Edge middleware, `response` is not a stable object — any call to `NextResponse.next()` creates a new one. You must design around this, not fight it.
- 💡Database constraints are a safety net, not an afterthought. Catching `23505` at the API boundary and returning a meaningful typed error is the correct pattern — not a hack.
- 💡Bilingual RTL support is an architectural decision, not a feature flag. Building `dir` awareness into the layout from day one (automatic font swap, CSS logical properties, RTL-safe Framer Motion) saved significant rework.
Links
Interested in a similar solution?
Let's discuss how I can build something like this for your business.









