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 definitionsmain.tsx– React entrypoint, wraps<App />withErrorBoundaryandHelmetProvidercomponents/– reusable UI and feature componentscomponents/ui/– shadcn‑style primitives (buttons, cards, inputs, dialogs, etc.)components/billing/– billing/upgrade UI (e.g.UpgradePromptused byProtectedRoute)components/owner/,components/orders/,components/inventory/,components/repairs/, etc. – feature‑oriented subtreescomponents/seo/– SEO helperscomponents/public/– marketing/landing componentscomponents/certificates/,components/invoice/,components/clients/, etc. – domain‑specific building blocks
contexts/– global React contexts and providersAuthContext.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 hooksuse-mobile-optimizations.ts– viewport height and mobile tweaks applied at app rootuseFeatureAccess,useSubscription, etc. – feature flag + subscription helpers
integrations/– external service clientsintegrations/supabase/– Supabase client setup and typed helpers
lib/– shared domain logiclib/constants/,lib/validations/,lib/utils/– constants, zod schemas, and general utilitieslib/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 pagespages/auth/– login/signup/auth callback flowspages/checkout/– Stripe/checkout result pagespages/owner/– owner‑only management screenspages/tools/– AI and pricing toolspages/repairs/– repairs calculatorspages/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" buttonHelmetProvider– enables per‑page<head>configuration viareact-helmet-async
App.tsx¶
App.tsx is responsible for:
- Creating a
QueryClientand providing it viaQueryClientProvider - Wrapping the app with
AuthProviderandThemeProvider - Installing global UI primitives (
TooltipProvider,Toaster) - Setting up the React Router tree (
BrowserRouter,Routes,Route) - Applying mobile viewport optimizations via
useMobileOptimizationsanduseViewportHeight - 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:
userandsessionfrom SupabasetenantIdandcompanyName(resolved via an RPCget_tenant_info_for_user)role(string union:'owner' | 'tenant' | 'admin')
- Stores a subset of this profile in
localStorageundersb-user-profileto speed up reloads. - On login/refresh:
- Uses the current Supabase session to find a matching row in
user_profiles - Reads
tenant_idand role, then joins tocompaniesto get company name - Falls back to stored profile if RPC calls fail
- Uses the current Supabase session to find a matching row in
- Exposes a React context consumed via
useAuth();ProtectedRouteand other components rely on this.
ThemeProvider (contexts/ThemeContext.tsx)
- Holds the current theme (
'light' | 'dark'), defaulting todark. - Reads from
localStoragekeythemeon 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
QueryClientinstance 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/features→pages/public/Features/pricing→pages/public/Pricing/terms→pages/public/Terms/privacy→pages/public/Privacy/refund→pages/public/Refund/contact→pages/public/Contact/affiliate→pages/public/Affiliate*(catch‑all) →pages/public/NotFound
These routes are rendered directly, without ProtectedRoute.
Auth routes¶
/login→pages/auth/Login/signup→pages/auth/Signup/auth/callback→pages/auth/AuthCallback/clear-session→pages/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¶
/dashboard→pages/Dashboard- Wrapped with
ThemeProvider(inner) andProtectedRoute - Requires an authenticated, non‑owner user with an active subscription
- Wrapped with
/owner-dashboard→pages/OwnerDashboard- Wrapped with
ThemeProvider(inner) andProtectedRoutewithrequiredRole="owner" - Owner routes bypass subscription checks
- Wrapped with
Owner management routes¶
All wrapped in ThemeProvider + ProtectedRoute requiredRole="owner":
/owner/companies→pages/owner/CompaniesManagement/owner/users→pages/owner/UsersManagement/owner/affiliates→pages/owner/AffiliateManagement/owner/analytics→pages/owner/AnalyticsReports
Tools routes¶
Wrapped in ThemeProvider + ProtectedRoute (subscription/feature‑gated):
/blog-writer→pages/tools/BlogPostWriter/scrap-calculator→pages/tools/ScrapValueCalculator/pricing-updater→pages/tools/PricingUpdater/tools/certificates→pages/tools/CertificateGenerator(feature flagjewelry_authenticity_card)/tools/appraisal-certificates→pages/tools/AppraisalCertificateGenerator/tools/photo-enhancer→pages/tools/ProductPhotoEnhancer/tools/quote-builder→pages/tools/CustomQuoteBuilder/tools/metal-rate-tracker→pages/tools/LiveMetalRateTracker
Repairs routes¶
All wrapped in ThemeProvider + ProtectedRoute:
/repairs/ring-sizing-up→pages/repairs/RingSizingUp/repairs/ring-sizing-down→pages/repairs/RingSizingDown/repairs/solder-chain→pages/repairs/SolderChain/repairs/solder-bracelet→pages/repairs/SolderBracelet/repairs/replace-clasp-chain→pages/repairs/ReplaceClaspChain/repairs/tighten-stones→pages/repairs/TightenStones/repairs/prong-rebuild→pages/repairs/ProngRebuild/repairs/rhodium-plating→pages/repairs/RhodiumPlating/repairs/polish-clean→pages/repairs/PolishClean
Inventory routes¶
/inventory→pages/Inventory/inventory/new→pages/Inventory/inventory/edit/:id→pages/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":
/orders→pages/Orders/orders/active→pages/orders/ActiveOrders/orders/completed→pages/orders/CompletedOrders/orders/work-orders→pages/orders/WorkOrders
Clients / CRM routes¶
Wrapped in ThemeProvider + ProtectedRoute:
/clients→pages/Clients/clients/crm→pages/clients/CRM/clients/appointments→pages/clients/AppointmentsCalendar
Design & library routes¶
Wrapped in ThemeProvider + ProtectedRoute:
/design/nameplate→pages/design/NameplateGenerator/library→pages/Library/design/cad-library→pages/design/CADFileLibrary
Payments routes¶
Wrapped in ThemeProvider + ProtectedRoute (and plan checks where applicable):
/payments→pages/Payments/payments/invoice→pages/payments/InvoiceCreation/payments/pay-link→pages/payments/PayLinkCreation
Billing / subscription routes¶
/billing→pages/settings/Billing(public entry used for upgrade/lockout handling)/settings/billing→pages/settings/Billing(protected, inside settings)
Settings routes¶
Wrapped in ThemeProvider + ProtectedRoute:
/settings/profile→pages/settings/Profile
Checkout result routes¶
Wrapped in inner ThemeProvider (but not behind ProtectedRoute):
/checkout/success→pages/checkout/CheckoutSuccess/checkout/cancel→pages/checkout/CheckoutCancel/upgrade→pages/checkout/Upgrade
These are used for Stripe checkout success/cancel flows and upgrades.
Route guard behavior (ProtectedRoute)¶
The ProtectedRoute component centralizes access control logic:
-
Loading state
- While auth or subscription information is loading, it renders a full‑screen
Loading...state.
- While auth or subscription information is loading, it renders a full‑screen
-
Auth check
- If there is no
userinuseAuth(), redirects to/login.
- If there is no
-
Role check
- If a
requiredRoleis provided andhasRequiredRole(role, requiredRole)fails, redirects to/dashboard.
- If a
-
Owner bypass
- Owners (
isOwnerorrole === 'owner') bypass subscription checks entirely.
- Owners (
-
Subscription checks (non‑owner)
- If no subscription exists, redirects to
/billingwithstate.reason = 'no_subscription'. - If a subscription exists but
isSubscriptionActive(subscription)is false, redirects to/billingwith a lockout reason fromgetLockoutReason(subscription).
- If no subscription exists, redirects to
-
Feature flags
- If a
featureis specified andcanAccess(feature)is false, renders a locked feature card andUpgradePrompt, encouraging upgrade to the required plan.
- If a
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
requiredRolefor owner/admin‑only screens - Pass
featureandrequiredPlanfor 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 consultsAuthProvider, subscription state, and feature flags. - Auth and subscription hooks ultimately rely on the Supabase client in
integrations/supabaseto talk to the backend.