import { BrowserRouter, Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider, useAuth } from './contexts/AuthContext';
import { ErrorBoundary } from './components/ErrorBoundary';
import { LandingPage } from './pages/LandingPage';
import { IndustryPage } from './pages/IndustryPage';
import { LoginPage } from './pages/auth/LoginPage';
import { SignupPage } from './pages/auth/SignupPage';
import { ForgotPasswordPage } from './pages/auth/ForgotPasswordPage';
import { ResetPasswordPage } from './pages/auth/ResetPasswordPage';
import { PartnerDashboard } from './pages/partner/PartnerDashboard';
import { OrganizationsPage as PartnerOrganizations } from './pages/partner/OrganizationsPage';
import { CommissionsPage } from './pages/partner/CommissionsPage';
import { AdminDashboard } from './pages/admin/AdminDashboard';
import { AdminOrganizations } from './pages/admin/AdminOrganizations';
import { AdminOrganizationDetail } from './pages/admin/AdminOrganizationDetail';
import { AnalyticsPage } from './pages/admin/AnalyticsPage';
import { UsersPage } from './pages/admin/UsersPage';
import { AuditLogPage } from './pages/admin/AuditLogPage';
import { CrmAnalyticsPage } from './pages/admin/CrmAnalyticsPage';
import { ForecastingPage } from './pages/admin/ForecastingPage';
import { GoalsPage } from './pages/admin/GoalsPage';
import StrategicIntelligencePage from './pages/admin/StrategicIntelligencePage';
import ScaleOptimizationPage from './pages/admin/ScaleOptimizationPage';
import { LeadsPage } from './pages/admin/LeadsPage';
import { IntegrationsPage } from './pages/partner/IntegrationsPage';
import EnterpriseSettingsPage from './pages/admin/EnterpriseSettingsPage';
import { AdminIntegrationsPage } from './pages/admin/AdminIntegrationsPage';
import MultiMarketPage from './pages/admin/MultiMarketPage';
import CoachingPage from './pages/admin/CoachingPage';
import { SettingsPage } from './pages/settings/SettingsPage';
import { PortfolioListPage } from './pages/admin/PortfolioListPage';
import { PortfolioDetailPage } from './pages/admin/PortfolioDetailPage';
import { EmailCampaignsPage } from './pages/admin/EmailCampaignsPage';
import { SuperAdminsPage } from './pages/admin/SuperAdminsPage';
import { PartnersPage } from './pages/admin/PartnersPage';
import { CommissionsAdminPage } from './pages/admin/CommissionsAdminPage';
import { AccessLevelsPage } from './pages/admin/AccessLevelsPage';
import { BillingPage } from './pages/admin/BillingPage';
import { RevenueLeaksPage } from './pages/partner/RevenueLeaksPage';
import { OptimizationMovesPage } from './pages/partner/OptimizationMovesPage';
import { ForecastsPage } from './pages/partner/ForecastsPage';
import { CoachingAssignmentsPage } from './pages/partner/CoachingAssignmentsPage';
import { CoachingScorecardsPage } from './pages/partner/CoachingScorecardsPage';
import { TeamPage } from './pages/TeamPage';
import { AngiLeadsPage } from './pages/partner/AngiLeadsPage';
import { QuickBooksPage } from './pages/partner/QuickBooksPage';
import { GoogleAdsPage } from './pages/partner/GoogleAdsPage';
import { FacebookAdsPage } from './pages/partner/FacebookAdsPage';
import { SlackPage } from './pages/partner/SlackPage';
import { SmsCallPage } from './pages/SmsCallPage';
import { LeadGenDashboardPage } from './pages/partner/LeadGenDashboard';
import { CampaignBuilderPage } from './pages/partner/CampaignBuilderPage';
import { CampaignDetailPage } from './pages/partner/CampaignDetailPage';
import { CreativesLibraryPage } from './pages/partner/CreativesLibraryPage';
import { OptimizationCenterPage } from './pages/partner/OptimizationCenterPage';
import { AttributionReportPage } from './pages/partner/AttributionReportPage';
import { LoadingSpinner } from './components/ui/LoadingSpinner';
import { FeatureGate } from './components/ui/FeatureGate';
import { EstimatesFunnelPage } from './pages/partner/EstimatesFunnelPage';
import { EstimatesListPage } from './pages/partner/EstimatesListPage';
import { EstimatesDetailPage } from './pages/partner/EstimatesDetailPage';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: 1,
},
},
});
// Protected route wrapper — requires any authenticated user
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
<LoadingSpinner size="lg" />
</div>
);
}
if (!isAuthenticated) {
return <Navigate to="/auth/login" replace />;
}
return <>{children}</>;
}
// RoleGuard — redirects non-matching roles to /app
function RoleGuard({
children,
requireRole,
requireAnyRole,
}: {
children: React.ReactNode;
requireRole?: string;
requireAnyRole?: string[];
}) {
const { isAuthenticated, isLoading, isRole, hasRole } = useAuth();
if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
<LoadingSpinner size="lg" />
</div>
);
}
if (!isAuthenticated) {
return <Navigate to="/auth/login" replace />;
}
// If requireRole is set, check exact match
if (requireRole && !isRole(requireRole)) {
return <Navigate to="/app" replace />;
}
// If requireAnyRole is set, check any match
if (requireAnyRole && !hasRole(...requireAnyRole)) {
return <Navigate to="/app" replace />;
}
return <>{children}</>;
}
// RequirePermission — requires a specific company permission
function RequirePermission({
children,
permission,
}: {
children: React.ReactNode;
permission: string;
}) {
const { isAuthenticated, isLoading, hasCompanyPermission } = useAuth();
if (isLoading) {
return (
<div className="flex min-h-screen items-center justify-center">
<LoadingSpinner size="lg" />
</div>
);
}
if (!isAuthenticated) {
return <Navigate to="/auth/login" replace />;
}
if (!hasCompanyPermission(permission as Parameters<typeof hasCompanyPermission>[0])) {
return <Navigate to="/app" replace />;
}
return <>{children}</>;
}
function AppRoutes() {
return (
<Routes>
{/* Landing page */}
<Route path="/" element={<LandingPage />} />
{/* Industry landing pages */}
<Route path="/industries/:slug" element={<IndustryPage />} />
{/* Public routes */}
<Route path="/auth/login" element={<LoginPage />} />
<Route path="/auth/signup" element={<SignupPage />} />
<Route path="/auth/forgot-password" element={<ForgotPasswordPage />} />
<Route path="/reset-password" element={<ResetPasswordPage />} />
{/* Partner routes — any authenticated user */}
<Route
path="/app"
element={
<ProtectedRoute>
<PartnerDashboard />
</ProtectedRoute>
}
/>
<Route
path="/app/organizations"
element={
<ProtectedRoute>
<PartnerOrganizations />
</ProtectedRoute>
}
/>
<Route
path="/app/commissions"
element={
<ProtectedRoute>
<CommissionsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/coaching"
element={
<ProtectedRoute>
<CoachingPage />
</ProtectedRoute>
}
/>
<Route
path="/app/integrations"
element={
<ProtectedRoute>
<IntegrationsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/settings"
element={
<ProtectedRoute>
<SettingsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/billing"
element={
<ProtectedRoute>
<BillingPage />
</ProtectedRoute>
}
/>
<Route
path="/app/team"
element={
<ProtectedRoute>
<RequirePermission permission="manage_team">
<TeamPage />
</RequirePermission>
</ProtectedRoute>
}
/>
<Route
path="/app/revenue-leaks"
element={
<ProtectedRoute>
<RevenueLeaksPage />
</ProtectedRoute>
}
/>
<Route
path="/app/optimization-moves"
element={
<ProtectedRoute>
<FeatureGate feature="scale_optimization">
<OptimizationMovesPage />
</FeatureGate>
</ProtectedRoute>
}
/>
<Route
path="/app/forecasts"
element={
<ProtectedRoute>
<ForecastsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/coaching-assignments"
element={
<ProtectedRoute>
<CoachingAssignmentsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/coaching-scorecards"
element={
<ProtectedRoute>
<CoachingScorecardsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/angi-leads"
element={
<ProtectedRoute>
<AngiLeadsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/quickbooks"
element={
<ProtectedRoute>
<QuickBooksPage />
</ProtectedRoute>
}
/>
<Route
path="/app/google-ads"
element={
<ProtectedRoute>
<GoogleAdsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/facebook-ads"
element={
<ProtectedRoute>
<FacebookAdsPage />
</ProtectedRoute>
}
/>
<Route
path="/app/slack"
element={
<ProtectedRoute>
<SlackPage />
</ProtectedRoute>
}
/>
<Route
path="/app/estimates"
element={
<ProtectedRoute>
<EstimatesFunnelPage />
</ProtectedRoute>
}
/>
<Route
path="/app/estimates/list"
element={
<ProtectedRoute>
<EstimatesListPage />
</ProtectedRoute>
}
/>
<Route
path="/app/estimates/:estimateId"
element={
<ProtectedRoute>
<EstimatesDetailPage />
</ProtectedRoute>
}
/>
<Route
path="/app/sms-call"
element={
<ProtectedRoute>
<SmsCallPage />
</ProtectedRoute>
}
/>
<Route
path="/app/sms-call/:leadId"
element={
<ProtectedRoute>
<SmsCallPage />
</ProtectedRoute>
}
/>
<Route
path="/app/lead-gen"
element={
<ProtectedRoute>
<LeadGenDashboardPage />
</ProtectedRoute>
}
/>
<Route
path="/app/lead-gen/campaigns/new"
element={
<ProtectedRoute>
<CampaignBuilderPage />
</ProtectedRoute>
}
/>
<Route
path="/app/lead-gen/campaigns/:campaignId"
element={
<ProtectedRoute>
<CampaignDetailPage />
</ProtectedRoute>
}
/>
<Route
path="/app/lead-gen/creatives"
element={
<ProtectedRoute>
<CreativesLibraryPage />
</ProtectedRoute>
}
/>
<Route
path="/app/lead-gen/optimization"
element={
<ProtectedRoute>
<OptimizationCenterPage />
</ProtectedRoute>
}
/>
<Route
path="/app/lead-gen/attribution"
element={
<ProtectedRoute>
<AttributionReportPage />
</ProtectedRoute>
}
/>
{/* Admin routes — super_admin only */}
<Route
path="/admin"
element={
<RoleGuard requireRole="super_admin">
<AdminDashboard />
</RoleGuard>
}
/>
<Route
path="/admin/organizations"
element={
<RoleGuard requireRole="super_admin">
<AdminOrganizations />
</RoleGuard>
}
/>
<Route
path="/admin/organizations/:orgId"
element={
<RoleGuard requireRole="super_admin">
<AdminOrganizationDetail />
</RoleGuard>
}
/>
<Route
path="/admin/users"
element={
<RoleGuard requireRole="super_admin">
<UsersPage />
</RoleGuard>
}
/>
<Route
path="/admin/analytics"
element={
<RoleGuard requireRole="super_admin">
<AnalyticsPage />
</RoleGuard>
}
/>
<Route
path="/admin/audit"
element={
<RoleGuard requireRole="super_admin">
<AuditLogPage />
</RoleGuard>
}
/>
<Route
path="/admin/crm-analytics"
element={
<RoleGuard requireRole="super_admin">
<CrmAnalyticsPage />
</RoleGuard>
}
/>
<Route
path="/admin/forecasting"
element={
<RoleGuard requireRole="super_admin">
<ForecastingPage />
</RoleGuard>
}
/>
<Route
path="/admin/goals"
element={
<RoleGuard requireRole="super_admin">
<FeatureGate feature="cascading_goals">
<GoalsPage />
</FeatureGate>
</RoleGuard>
}
/>
<Route
path="/admin/strategic-intelligence"
element={
<RoleGuard requireRole="super_admin">
<FeatureGate feature="strategic_intelligence">
<StrategicIntelligencePage />
</FeatureGate>
</RoleGuard>
}
/>
<Route
path="/admin/scale-optimization"
element={
<RoleGuard requireRole="super_admin">
<FeatureGate feature="scale_optimization">
<ScaleOptimizationPage />
</FeatureGate>
</RoleGuard>
}
/>
<Route
path="/admin/leads"
element={
<RoleGuard requireRole="super_admin">
<LeadsPage />
</RoleGuard>
}
/>
<Route
path="/admin/integrations"
element={
<RoleGuard requireRole="super_admin">
<AdminIntegrationsPage />
</RoleGuard>
}
/>
<Route
path="/admin/enterprise"
element={
<RoleGuard requireRole="super_admin">
<FeatureGate feature="enterprise">
<EnterpriseSettingsPage />
</FeatureGate>
</RoleGuard>
}
/>
<Route
path="/admin/multi-market"
element={
<RoleGuard requireRole="super_admin">
<FeatureGate feature="multi_market">
<MultiMarketPage />
</FeatureGate>
</RoleGuard>
}
/>
<Route
path="/admin/coaching"
element={
<RoleGuard requireRole="super_admin">
<CoachingPage />
</RoleGuard>
}
/>
<Route
path="/admin/portfolios"
element={
<RoleGuard requireRole="super_admin">
<PortfolioListPage />
</RoleGuard>
}
/>
<Route
path="/admin/portfolios/:portfolioId"
element={
<RoleGuard requireRole="super_admin">
<PortfolioDetailPage />
</RoleGuard>
}
/>
<Route
path="/admin/email-campaigns"
element={
<RoleGuard requireRole="super_admin">
<EmailCampaignsPage />
</RoleGuard>
}
/>
<Route
path="/admin/super-admins"
element={
<RoleGuard requireRole="super_admin">
<SuperAdminsPage />
</RoleGuard>
}
/>
<Route
path="/admin/partners"
element={
<RoleGuard requireRole="super_admin">
<PartnersPage />
</RoleGuard>
}
/>
<Route
path="/admin/commissions"
element={
<RoleGuard requireRole="super_admin">
<CommissionsAdminPage />
</RoleGuard>
}
/>
<Route
path="/admin/access-levels"
element={
<RoleGuard requireRole="super_admin">
<AccessLevelsPage />
</RoleGuard>
}
/>
<Route
path="/admin/settings"
element={
<RoleGuard requireRole="super_admin">
<SettingsPage />
</RoleGuard>
}
/>
{/* Catch-all */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
);
}
/** Redirect to integrations page when OAuth callback params detected in URL. */
function OAuthRedirectHandler() {
const location = useLocation();
const navigate = useNavigate();
const { user } = useAuth();
useEffect(() => {
const params = new URLSearchParams(location.search);
const oauthSuccess = params.get('oauth_success');
const oauthError = params.get('oauth_error');
const picker = params.get('picker');
if ((oauthSuccess || oauthError) && user) {
// Preserve all query params for the target page to read
const search = location.search;
const isAdmin = user.role === 'super_admin';
const target = isAdmin ? '/admin/integrations' : '/app/integrations';
navigate(`${target}${search}`, { replace: true });
}
}, [location.search, navigate, user]);
return null;
}
import { useEffect } from 'react';
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<BrowserRouter>
<ErrorBoundary>
<OAuthRedirectHandler />
<AppRoutes />
</ErrorBoundary>
</BrowserRouter>
</AuthProvider>
</QueryClientProvider>
);
}