Introduction
Project Overview
Purpose of the Project
- Primary goal: A petition validation and campaign management platform that helps teams organize campaigns, manage teams and roles, collect and validate petition signatures, and search voter records.
- Problems it solves:
- Centralizes petition workflows (campaign setup, formats, circulators, signature entry and validation).
- Enforces fine-grained permissions and team-based access.
- Integrates voter search via an external service (Directus-backed Elasticsearch) for validation.
- Prevents duplicate signers per campaign using Redis.
- Manages invitations and onboarding via Supabase.
High-level Architecture
-
Front-end: Next.js App Router (React 18, TypeScript, TailwindCSS, Radix UI).
- Route structure under
src/appwith parallel layouts:src/app/(base-layout)for core unauthenticated/outer pages.src/app/[primaryTeam]/(team-layout)for team-scoped features (campaigns, permissions, petitions, dashboards).- Auth flows in
src/app/auth(sign-in, sign-up, callback, confirmations).
- UI components under
src/componentsgrouped by domain (campaign, petitions, tables, dashboard, permissions, teams, forms, ui). - Client-side helpers and hooks in
src/hooksandsrc/lib.
- Route structure under
-
Backend (within Next.js):
- Middleware:
src/middleware.tsdelegates tosrc/lib/supabase/updateSession.tsto:- Hydrate Supabase session via cookies.
- Redirect unauthenticated users to
/auth/sign-in, preserving intended path viaredirect_urlcookie. - Enforce route-level permission pipelines defined in
src/middleware/routePipelines.tswith handlers (feature gates, team access, API guards).
- API routes:
src/app/api/...:essearchandessearch-common: Proxy to a Directus instance which authenticates and calls a custom endpoint (/essearch/validation) for voter/person search. Uses short-lived HTTP-only cookies for Directus tokens.duplicate: Checks if a voter has already signed a campaign using Redis (Upstash).invite-team-member: Uses Supabase service role to invite users, upsert membership, and call a Supabase Edge Function (send-team-invitation).
- Server Actions / Services:
- Domain “services” under
src/services/**use Supabase (server client) to query/update Postgres (e.g., petitions, teams, permissions, campaigns). - Server actions under
src/actions/**for mutations across auth, campaigns, petitions, teams, transactions, Responsibilities, credentials. - Permission and route control logic under
src/middleware/**(validators, pipelines, types).
- Domain “services” under
- Middleware:
-
Data Stores
- Supabase Postgres: Primary system of record for users, teams, campaigns, petitions, permissions, transactions, etc.
- Redis (Upstash): Membership checks for duplicates per campaign (e.g., set
campaign:{campaignId}:signers). - Cookies: Session cookies for Supabase SSR and short-lived Directus authentication tokens.
-
External Services
- Supabase:
- Auth (email invitation, session cookies via SSR client).
- Postgres database access via
@supabase/supabase-js. - Edge Functions (e.g.,
send-team-invitation).
- Directus:
- Authentication and REST client via
@directus/sdk. - Proxies an Elasticsearch-backed endpoint (
/essearch/validation) for voter/person search.
- Authentication and REST client via
- Upstash Redis:
- TLS-enabled Redis used for signature duplication checks.
- Deployment:
- Vercel (
vercel.jsonpresent). Git-based deployments enabled formain,dev,staging.
- Vercel (
- Supabase:
-
Security and Access Control
- Middleware enforces:
- Authenticated access outside
/auth/*. - Dynamic, team/campaign-aware permissions via Supabase RPC
get_user_combined_permissionsand route pipelines insrc/middleware/routePipelines.ts.
- Authenticated access outside
- API routes guard external calls and validate credentials loaded from environment variables.
- Service Role usage restricted to server-only contexts (e.g., invitations).
- Middleware enforces:
Tech Stack
-
Framework
- Next.js 14.2.25 (App Router)
- React 18
- TypeScript 5
- TailwindCSS 3.4,
tailwind-merge,tailwindcss-animate - Radix UI primitives (avatar, checkbox, dialog, dropdown, popover, select, tabs, toast, tooltip, etc.)
@tanstack/react-tablefor complex table UIs
-
Backend/runtime
- Next.js API routes and middleware for server logic
- Supabase:
@supabase/ssrfor server/client instantiation and cookie management@supabase/supabase-jsfor DB/auth/edge functions
- Directus SDK (
@directus/sdk) for REST and token-based auth to proxy Elasticsearch queries - Redis (
redisnpm package) for duplication checks (configured for Upstash TLS) - Deployed on Vercel
-
Node / Runtime
- No explicit
enginesspecified. Recommended Node 18+ (Next.js 14 compatible). Type definitions target@types/node^20. - Vercel default Node runtime is supported.
- No explicit
-
Configuration Highlights
- Typescript config includes path alias
@/* -> ./src/*. - Next image config allows Supabase storage host.
- Middleware matcher excludes static assets and images.
- Environment variables expected:
- Supabase:
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY, andSERVICE_ROLE_KEY(server only). - Directus: Either a generic credential triplet provided dynamically via request (
credentials) or fixed envs for common search:DIRECTUS_API_EMAIL_SEARCH,DIRECTUS_API_PASSWORD_SEARCH,DIRECTUS_URL_SEARCH
- Redis:
REDIS_URL
- Supabase:
- Typescript config includes path alias
-
Notable App Conventions
src/services/**: server-side data access with Supabase server client.src/actions/**: server actions handling create/update flows for key entities.src/middleware/**: permission pipelines and validators; integrates with Supabase RPC for combined permissions.src/components/tables/**: rich data tables (sorting, filtering, pagination) powered by TanStack Table.src/lib/supabase/**: SSR-safe client setup for both client and server, including a special client for service role operations.