Full Stack Engineering

Building Today's Stash: A Full Stack Local Deals Platform

Powered by Next.js and Supabase

01

The Vision

Today's Stash is a full stack web application I designed and engineered from the ground up to connect local businesses with consumers through exclusive, time sensitive deals. Think of it as a hyper local "Too Good To Go" but instead of surplus food, it powers promotions for cafes, restaurants, pubs, retail stores, fitness studios, and more across participating towns and regions in Australia.

What started as a concept for helping small business owners drive foot traffic evolved into a production grade SaaS platform with multi role authentication, real time inventory management, AI assisted deal creation, atomic reservation systems, multi channel notifications, and a comprehensive admin panel, all deployed on a modern, scalable infrastructure.

This post walks through the technical architecture, the engineering decisions I made at every layer, and why this platform is built to scale.

02

The Tech Stack

LayerTechnology
FrontendNext.js (App Router), React 18, TypeScript
StylingTailwind CSS v4 with custom CSS variables
Backend / DatabaseSupabase (PostgreSQL 15, Auth, Storage, Edge Functions)
AuthenticationSupabase Auth (Email OTP, Phone OTP via Twilio, Google OAuth, Apple OAuth)
SMS NotificationsTwilio Programmable SMS + Twilio Verify
Email NotificationsResend (transactional emails and deal alerts)
AI IntegrationOpenAI GPT 4o for AI powered deal suggestions
DeploymentVercel (automatic CI/CD from GitHub)
SchedulingPostgreSQL pg_cron for automated background jobs

Every technology choice was deliberate. Next.js App Router gives me server side rendering, API routes, and file based routing in a single framework. Supabase provides a complete backend as a service with PostgreSQL, real time subscriptions, row level security, and managed auth, all without maintaining my own server infrastructure. Twilio and Resend handle the notification delivery layer, and Vercel provides zero config deployments with edge network performance.

03

Multi Role Architecture

The platform supports three distinct user roles, each with their own permissions, dashboards, and data boundaries:

1. Consumer

Consumers are the end users who browse deals, subscribe to towns, reserve offers, and redeem promotions at local businesses. They have access to a personalised discovery feed, deal detail pages, a reservation management hub, and a full profile and notification settings page.

2. Merchant

Merchants are the business owners who list their venues, set business hours, create and manage deals, and track redemption analytics. They get a dedicated full width dashboard with performance statistics (Total Redemptions, Unique Customers, Estimated Revenue), active and expired deal management, inventory restocking, venue branding (logo and banner upload), QR poster generation, and AI assisted deal creation.

3. Admin

Admins act as platform moderators and regional managers. They can create and manage towns, review merchant applications, monitor the global deals table, manage user reservations and suspensions, and process support requests. Critically, admins operate under a "Sovereign Merchant" model; they cannot create, edit, or delete merchant deals. This ensures data integrity and preserves merchant autonomy over their own pricing and operational data.

Role assignment is enforced at both the database level (via RLS policies) and the application level (via middleware route guards and conditional rendering). Admin roles can only be assigned through the Supabase Dashboard directly, preventing any in app privilege escalation attacks.

04

Authentication and Security

Security is not an afterthought in this application, it is the foundation every feature is built on. Here is how the authentication and authorization system works:

Supabase Auth with Multi Provider Support

The app supports four authentication methods:

Identity Linking and Unlinking

Users who sign up with OAuth can later link a traditional email/password or phone number to their account. Conversely, they can unlink a social account as long as they have an alternative verified login method. This satisfies Supabase's identity integrity constraints while giving users full control over their credentials.

Middleware Level Route Protection

The application uses Next.js middleware (middleware.ts) with @supabase/ssr to intercept every request. The middleware:

Row Level Security (RLS)

Every single table in the database has Row Level Security enabled. This means that even if someone managed to bypass the frontend entirely and query the database directly with the anon key, they would only see data they are authorised to access.

Key RLS policies include:

Security Definer Functions

All SECURITY DEFINER PostgreSQL functions, including the redemption RPCs, reservation logic, and directory sync triggers, have an explicit search_path set to public, pg_temp. This prevents search path shadowing attacks where a malicious user could create a function in a different schema to intercept queries.

Additional Security Measures

05

The Database Schema

The database is designed around a relational model with carefully normalised tables, JSONB for flexible scheduling data, and integer based pricing for accuracy.

Core Tables

Pricing in Cents

A seemingly small but critical design decision: all monetary values are stored as integers in cents. This eliminates the floating point precision issues that plague applications storing prices as decimals. The frontend converts price_cents / 100 for display while never performing arithmetic on floating point dollar values.

06

The Atomic Reservation System

One of the most technically sophisticated features is the reservation system, which I built to prevent race conditions and ensure fair stock allocation.

The Problem

When multiple users try to reserve the last remaining stock of a deal simultaneously, naive implementations can oversell. User A and User B both see "1 left", both click "Reserve", and both get confirmed, but there was only one unit available.

The Solution: reserve_offer RPC with Row Locking

I built a PostgreSQL RPC function (reserve_offer) that uses SELECT ... FOR UPDATE to acquire an exclusive row lock on the specific deal before checking availability:

  1. 1Authentication Check. Verifies the calling user via auth.uid().
  2. 2Suspension Check. Queries the profiles table to ensure the user is not suspended.
  3. 3Daily Limit Enforcement. Checks for existing reservations on the same Sydney timezone calendar day, ignoring cancelled ones.
  4. 4Row Lock. Locks the offers row with FOR UPDATE to prevent concurrent modifications.
  5. 5Stock Validation. Verifies redeemed_count < total_limit.
  6. 6Reservation Creation. Inserts a pending reservation.
  7. 7Immediate Stock Decrement. Increments redeemed_count at reservation time (not redemption time) to hold the spot.

This entire operation runs inside a single transaction. If any step fails, the entire operation rolls back. No double booking, no overselling.

The Strike System

Frontend Implementation

The ReservationModal component uses framework agnostic, React state driven UI instead of native OS selectors. This ensures a consistent experience across iOS, Android, macOS, and Windows:

07

AI Powered Deal Creation

The merchant dashboard integrates OpenAI's GPT 4o to assist merchants in creating high converting deal listings.

How It Works

  1. 1The merchant describes their deal in natural language (e.g., "Tuesday morning coffee special" or "Happy hour 4 to 6pm craft beers").
  2. 2The system sends this to the OpenAI API, which generates three creative deal concepts, each with a marketing optimised title, compelling description, and inferred scheduling data.
  3. 3The AI processes time related language intelligently: "Mornings" maps to open to 11:00 AM, "Afternoon" maps to 1:00 PM to 5:00 PM, "Evening" maps to 5:00 PM to close. Abstract tokens like open and close are resolved against the merchant’s actual operating hours.
  4. 4The merchant reviews three consumer aligned previews rendered in a high fidelity CouponPreview component that mirrors the exact styling of the live deal cards.
  5. 5The merchant selects their preferred concept, adjusts pricing (which the AI never touches; pricing authority stays with the merchant), and publishes.

This "AI as marketing partner, merchant as pricing authority" pattern prevents AI hallucinations in business critical financial data while leveraging the model's strength in creative copywriting and schedule inference.

A manual deal creation flow is always available as a fallback at every stage, ensuring merchants are never locked into the AI workflow.

08

Multi Channel Notification System

The notification system is one of the most complex subsystems in the application, handling SMS delivery via Twilio, email delivery via Resend, carrier level opt out compliance, and granular per merchant subscription management.

Architecture

Users choose exactly one active notification method: Phone (SMS) or Email. Both cannot be active simultaneously. Switching methods requires the new method to be verified first.

Verification Flow

Deal Alert Broadcasting

When a merchant publishes a new deal or restocks an expired one, the system automatically broadcasts alerts. The API uses a Hybrid Authentication pattern (Bearer token + Cookie fallback) to identify the merchant. An ownership check verifies that the requesting user actually owns the merchant profile. Subscribers are partitioned into SMS and email groups, and messages are dispatched in parallel using Promise.allSettled(), ensuring a failure to deliver to one recipient does not block delivery to the remaining subscribers.

Carrier Compliance (Hard Opt Out)

A production Twilio webhook at /api/webhooks/twilio listens for "STOP" keyword replies. When triggered, it looks up the user's profile by matching the incoming phone number and sets notifications_enabled = false and notification_method = 'none'. This ensures the application's internal state stays synchronised with carrier level blocks, satisfying Australian telecommunications compliance requirements.

Performance Optimisation (N+1 Prevention)

To prevent the N+1 query problem where each deal card on the consumer feed would independently query its notification status, the system uses batch fetching. The parent page fetches all enabled merchant IDs in a single query, stores them in a Set<string> for O(1) lookup, and individual CouponTicket components receive a simple boolean isNotificationEnabled prop, making them stateless for notification logic.

09

The Consumer Experience

TGTG Inspired Design

The consumer UI draws heavy inspiration from the "Too Good To Go" aesthetic: card based discovery with aggressive scarcity indicators, high contrast neon on dark theming, and physical coupon textures.

Sophisticated Availability Logic

The getMerchantStatus helper centralises all business hours calculations including overnight shift support (correctly identifies merchants as "Open" when the shift started on the previous calendar day), case insensitive day matching, next open prediction, and Sydney timezone anchoring for consistent timezone handling across the entire deal lifecycle.

Feed Filtering and Sorting

10

Merchant Dashboard

The merchant dashboard is a full width, 12 column grid management interface:

11

The Admin Panel

The admin panel operates under a "Moderator First" model with strict boundaries:

12

Supabase Integration

Supabase serves as the entire backend infrastructure. Here is how each service is utilised:

PostgreSQL Database

  • 20+ tables with full RLS coverage
  • Complex JSONB columns for operating hours
  • Integer based pricing for financial accuracy
  • Custom ENUM types for roles and status
  • Comprehensive migration history

Supabase Auth

  • Multi provider authentication
  • Identity linking and unlinking
  • Cookie based session management
  • Production OAuth with domain whitelisting

RPC Functions

  • reserve_offer() with row locking
  • redeem_offer_with_pin() with auto linking
  • check_missed_reservations() via pg_cron
  • admin_lift_suspension() role gated
  • getUserRole() from profiles and JWT

Storage and Background Jobs

  • offer media bucket for deal images
  • Randomised filenames to prevent collisions
  • Hourly pg_cron execution for strike enforcement
  • Automated schema cache management

13

API Architecture

The application uses Next.js API Route Handlers for server side logic that cannot run in the browser:

RoutePurpose
/api/auth/*Custom Twilio phone authentication flow
/api/notifications/send phone verificationTwilio SMS OTP dispatch
/api/notifications/send email verificationResend email OTP dispatch
/api/notifications/verify phone / verify emailOTP validation endpoints
/api/notifications/update methodNotification method switching
/api/notifications/preferencesCRUD for per merchant settings
/api/notifications/notify subscribersBroadcast deal alerts via SMS and email
/api/webhooks/twilioCarrier level opt out compliance webhook
/api/auth/reset password/verifyPassword reset flow

All API routes implement the Hybrid Authentication pattern: checking for a Bearer token in the Authorization header first, falling back to cookie based session resolution. This ensures reliable authentication across different client contexts, network latencies, and development environments.

14

The Merchant Onboarding Pipeline

The merchant registration process was intentionally designed as a high touch, manually vetted pipeline:

  1. 1Public Lead Form (/venue register). Anyone can submit a business application without creating an account. No OTP verification required. RLS allows anonymous inserts.
  2. 2Admin Review. Applications appear in the admin dashboard as "unread" leads.
  3. 3Manual Outreach. The sales team contacts applicants offline to verify legitimacy and discuss terms.
  4. 4Developer Activation. Only after manual approval is the merchant record created and linked to a user account.
  5. 5Dashboard Access. The business owner logs in to access the full merchant dashboard.

This deliberate friction ensures platform quality and prevents fraudulent listings, a critical differentiator for a consumer facing deals platform.

15

Profile Management and Accessibility

The profile page is designed with a specific focus on accessibility for older users:

16

Deployment and DevOps

The application follows a streamlined deployment workflow:

17

What Makes This App Stand Out

True Full Stack Engineering

From PostgreSQL row level security policies and atomic database functions to responsive frontend components with CSS mask gradients and custom state driven selectors. Every layer is hand crafted.

Production Grade Security

RLS on every table, security definer functions with hardened search paths, leaked password protection, carrier level notification compliance, and middleware enforced route protection.

Real World Concurrency Handling

The SELECT ... FOR UPDATE row locking pattern in the reservation system is the same technique used in banking and e commerce systems to prevent race conditions.

AI Integration Done Responsibly

The AI generates creative marketing copy while pricing authority remains firmly with the merchant, preventing hallucinated financial data.

Multi Channel Communication

A unified notification pipeline that handles SMS via Twilio, email via Resend, with automatic carrier opt out compliance and batch subscriber fetching to prevent N+1 queries.

Accessibility First Design

The platform serves a broad demographic including seniors, with large text, generous touch targets, and plain English error messages.

Scalable Architecture

Supabase’s managed PostgreSQL, Vercel’s edge network, and the stateless API design mean this application can scale without re architecture.