Skip to content

Frontend Architecture

This page maps the Vite/React frontend so a new developer can find entrypoints, routes, and cross‑cutting concerns quickly.

Directory layout (src/)

High‑level structure under src/:

  • App.tsx – top‑level React tree: providers, router, and all route definitions
  • main.tsx – React entrypoint, wraps <App /> with ErrorBoundary and HelmetProvider
  • components/ – reusable UI and feature components
    • components/ui/ – shadcn‑style primitives (buttons, cards, inputs, dialogs, etc.)
    • components/billing/ – billing/upgrade UI (e.g. UpgradePrompt used by ProtectedRoute)
    • components/owner/, components/orders/, components/inventory/, components/repairs/, etc. – feature‑oriented subtrees
    • components/seo/ – SEO helpers
    • components/public/ – marketing/landing components
    • components/certificates/, components/invoice/, components/clients/, etc. – domain‑specific building blocks
  • contexts/ – global React contexts and providers
    • AuthContext.tsx – authentication + tenant/role context (Supabase‑backed)
    • ThemeContext.tsx – light/dark theme state and toggling
    • any other context modules as the app grows
  • hooks/ – custom hooks
    • use-mobile-optimizations.ts – viewport height and mobile tweaks applied at app root
    • useFeatureAccess, useSubscription, etc. – feature flag + subscription helpers
  • integrations/ – external service clients
    • integrations/supabase/ – Supabase client setup and typed helpers
  • lib/ – shared domain logic
    • lib/constants/, lib/validations/, lib/utils/ – constants, zod schemas, and general utilities
    • lib/auth-utils.ts, lib/subscription-utils.ts – auth/role and subscription helpers used by routing and guards
  • pages/ – route‑level screens (each file or folder corresponds to one or more routes)
    • pages/public/ – marketing pages
    • pages/auth/ – login/signup/auth callback flows
    • pages/checkout/ – Stripe/checkout result pages
    • pages/owner/ – owner‑only management screens
    • pages/tools/ – AI and pricing tools
    • pages/repairs/ – repairs calculators
    • pages/orders/, pages/clients/, pages/design/, pages/payments/, pages/settings/ – main dashboard feature areas
    • top‑level pages/Dashboard.tsx, pages/OwnerDashboard.tsx, pages/Inventory.tsx, etc.
  • types/ – shared TypeScript types (including Supabase types)
  • utils/ – additional small helpers (formatting, date helpers, etc.)

The frontend is a single‑page app (SPA) built with React Router and React Query. All route mapping happens inside App.tsx.


Entry points and providers

main.tsx

src/main.tsx is the React entrypoint:

  • Creates the React root on #root
  • Wraps <App /> with:
    • ErrorBoundary – catches render errors and shows a generic fallback screen with a "Try again" button
    • HelmetProvider – enables per‑page <head> configuration via react-helmet-async

App.tsx

App.tsx is responsible for:

  • Creating a QueryClient and providing it via QueryClientProvider
  • Wrapping the app with AuthProvider and ThemeProvider
  • Installing global UI primitives (TooltipProvider, Toaster)
  • Setting up the React Router tree (BrowserRouter, Routes, Route)
  • Applying mobile viewport optimizations via useMobileOptimizations and useViewportHeight
  • Wiring Vercel Analytics

The tree looks like this (simplified):

const App = () => (
  <QueryClientProvider client={queryClient}>
    <AuthProvider>
      <ThemeProvider>
        <TooltipProvider>
          <Toaster />
          <AppContent />
          <VercelAnalytics />
        </TooltipProvider>
      </ThemeProvider>
    </AuthProvider>
  </QueryClientProvider>
);

AppContent then mounts the router and all routes.

Providers in detail

AuthProvider (contexts/AuthContext.tsx)

  • Backed by Supabase auth.
  • Tracks:
    • user and session from Supabase
    • tenantId and companyName (resolved via an RPC get_tenant_info_for_user)
    • role (string union: 'owner' | 'tenant' | 'admin')
  • Stores a subset of this profile in localStorage under sb-user-profile to speed up reloads.
  • On login/refresh:
    • Uses the current Supabase session to find a matching row in user_profiles
    • Reads tenant_id and role, then joins to companies to get company name
    • Falls back to stored profile if RPC calls fail
  • Exposes a React context consumed via useAuth(); ProtectedRoute and other components rely on this.

ThemeProvider (contexts/ThemeContext.tsx)

  • Holds the current theme ('light' | 'dark'), defaulting to dark.
  • Reads from localStorage key theme on startup.
  • Applies theme by toggling document.documentElement.classList (light/dark).
  • Persists changes back to localStorage.
  • Exposes toggleTheme() for UI components to switch modes.

QueryClientProvider (@tanstack/react-query)

  • Provides a single QueryClient instance at the app root.
  • All data‑fetching hooks (Supabase queries, etc.) use React Query for caching, retries, and background refetch.

TooltipProvider and Toaster (components/ui)

  • TooltipProvider – wraps the app so Radix tooltips (via shadcn/ui) work consistently.
  • Toaster – global notification system for success/error toasts.

ErrorBoundary (components/ErrorBoundary.tsx)

  • Catches uncaught render errors in any child component.
  • Logs the error and React component stack to the console.
  • Shows a simple full‑screen fallback with error message and "Try again" button.

Best Practice

When adding new global concerns (e.g. feature flag provider, analytics), prefer wiring them into App.tsx near the existing providers so there is a single, predictable place to find them.


Route map (from App.tsx)

All routing is defined in App.tsx using React Router v6. Pages are loaded via React.lazy and grouped by feature area.

Below is a conceptual map; for exact file names, see src/App.tsx.

Public routes (no auth required)

  • /pages/public/Landing
  • /featurespages/public/Features
  • /pricingpages/public/Pricing
  • /termspages/public/Terms
  • /privacypages/public/Privacy
  • /refundpages/public/Refund
  • /contactpages/public/Contact
  • /affiliatepages/public/Affiliate
  • * (catch‑all) → pages/public/NotFound

These routes are rendered directly, without ProtectedRoute.

Auth routes

  • /loginpages/auth/Login
  • /signuppages/auth/Signup
  • /auth/callbackpages/auth/AuthCallback
  • /clear-sessionpages/auth/ClearSession

These interact with Supabase auth via hooks/context, but the routes themselves are public (they manage login state rather than being behind auth).

Dashboard routes

  • /dashboardpages/Dashboard
    • Wrapped with ThemeProvider (inner) and ProtectedRoute
    • Requires an authenticated, non‑owner user with an active subscription
  • /owner-dashboardpages/OwnerDashboard
    • Wrapped with ThemeProvider (inner) and ProtectedRoute with requiredRole="owner"
    • Owner routes bypass subscription checks

Owner management routes

All wrapped in ThemeProvider + ProtectedRoute requiredRole="owner":

  • /owner/companiespages/owner/CompaniesManagement
  • /owner/userspages/owner/UsersManagement
  • /owner/affiliatespages/owner/AffiliateManagement
  • /owner/analyticspages/owner/AnalyticsReports

Tools routes

Wrapped in ThemeProvider + ProtectedRoute (subscription/feature‑gated):

  • /blog-writerpages/tools/BlogPostWriter
  • /scrap-calculatorpages/tools/ScrapValueCalculator
  • /pricing-updaterpages/tools/PricingUpdater
  • /tools/certificatespages/tools/CertificateGenerator (feature flag jewelry_authenticity_card)
  • /tools/appraisal-certificatespages/tools/AppraisalCertificateGenerator
  • /tools/photo-enhancerpages/tools/ProductPhotoEnhancer
  • /tools/quote-builderpages/tools/CustomQuoteBuilder
  • /tools/metal-rate-trackerpages/tools/LiveMetalRateTracker

Repairs routes

All wrapped in ThemeProvider + ProtectedRoute:

  • /repairs/ring-sizing-uppages/repairs/RingSizingUp
  • /repairs/ring-sizing-downpages/repairs/RingSizingDown
  • /repairs/solder-chainpages/repairs/SolderChain
  • /repairs/solder-braceletpages/repairs/SolderBracelet
  • /repairs/replace-clasp-chainpages/repairs/ReplaceClaspChain
  • /repairs/tighten-stonespages/repairs/TightenStones
  • /repairs/prong-rebuildpages/repairs/ProngRebuild
  • /repairs/rhodium-platingpages/repairs/RhodiumPlating
  • /repairs/polish-cleanpages/repairs/PolishClean

Inventory routes

  • /inventorypages/Inventory
  • /inventory/newpages/Inventory
  • /inventory/edit/:idpages/Inventory

All are wrapped in ThemeProvider + ProtectedRoute and represent different modes of the same inventory screen.

Orders routes

Wrapped in ThemeProvider + ProtectedRoute with feature="order_management":

  • /orderspages/Orders
  • /orders/activepages/orders/ActiveOrders
  • /orders/completedpages/orders/CompletedOrders
  • /orders/work-orderspages/orders/WorkOrders

Clients / CRM routes

Wrapped in ThemeProvider + ProtectedRoute:

  • /clientspages/Clients
  • /clients/crmpages/clients/CRM
  • /clients/appointmentspages/clients/AppointmentsCalendar

Design & library routes

Wrapped in ThemeProvider + ProtectedRoute:

  • /design/nameplatepages/design/NameplateGenerator
  • /librarypages/Library
  • /design/cad-librarypages/design/CADFileLibrary

Payments routes

Wrapped in ThemeProvider + ProtectedRoute (and plan checks where applicable):

  • /paymentspages/Payments
  • /payments/invoicepages/payments/InvoiceCreation
  • /payments/pay-linkpages/payments/PayLinkCreation

Billing / subscription routes

  • /billingpages/settings/Billing (public entry used for upgrade/lockout handling)
  • /settings/billingpages/settings/Billing (protected, inside settings)

Settings routes

Wrapped in ThemeProvider + ProtectedRoute:

  • /settings/profilepages/settings/Profile

Checkout result routes

Wrapped in inner ThemeProvider (but not behind ProtectedRoute):

  • /checkout/successpages/checkout/CheckoutSuccess
  • /checkout/cancelpages/checkout/CheckoutCancel
  • /upgradepages/checkout/Upgrade

These are used for Stripe checkout success/cancel flows and upgrades.


Route guard behavior (ProtectedRoute)

The ProtectedRoute component centralizes access control logic:

  1. Loading state

    • While auth or subscription information is loading, it renders a full‑screen Loading... state.
  2. Auth check

    • If there is no user in useAuth(), redirects to /login.
  3. Role check

    • If a requiredRole is provided and hasRequiredRole(role, requiredRole) fails, redirects to /dashboard.
  4. Owner bypass

    • Owners (isOwner or role === 'owner') bypass subscription checks entirely.
  5. Subscription checks (non‑owner)

    • If no subscription exists, redirects to /billing with state.reason = 'no_subscription'.
    • If a subscription exists but isSubscriptionActive(subscription) is false, redirects to /billing with a lockout reason from getLockoutReason(subscription).
  6. Feature flags

    • If a feature is specified and canAccess(feature) is false, renders a locked feature card and UpgradePrompt, encouraging upgrade to the required plan.

Where to add new protected routes

When adding a new route that should be gated by auth, role, or subscription:

  • Wrap the element with <ProtectedRoute>
  • Pass requiredRole for owner/admin‑only screens
  • Pass feature and requiredPlan for feature‑gated functionality

High‑level flow: user → router → Supabase

flowchart LR
  U[User Browser] --> R["React Router (App.tsx)"]
  R -->|Public routes| PUB["Public pages (Landing, Pricing, etc.)"]
  R -->|Auth routes| AUTH["Auth pages (Login, Signup, Callback)"]
  R -->|Protected routes| PROT[ProtectedRoute]

  PROT -->|uses| AUTHCTX[AuthProvider / useAuth]
  PROT -->|uses| SUBS[useSubscription]
  PROT -->|checks| FEAT[useFeatureAccess]

  AUTHCTX -->|Supabase client| SB[(Supabase Auth + DB)]
  SUBS --> SB

  click R "../architecture/frontend/" "Route definitions in App.tsx"
  click AUTHCTX "../architecture/supabase/" "Supabase auth & tenant info"
  click SB "../architecture/supabase/" "Supabase architecture overview"

This diagram shows:

  • All navigation enters through React Router in App.tsx.
  • Public and auth routes bypass ProtectedRoute.
  • Protected routes go through ProtectedRoute, which consults AuthProvider, subscription state, and feature flags.
  • Auth and subscription hooks ultimately rely on the Supabase client in integrations/supabase to talk to the backend.