2025-01-01 00:00:00 +00:00
|
|
|
import { Toaster } from "@/components/ui/toaster";
|
|
|
|
|
import { Toaster as Sonner } from "@/components/ui/sonner";
|
|
|
|
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
|
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
2026-04-06 20:57:30 +09:30
|
|
|
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
|
|
|
|
|
import { useEffect } from "react";
|
2026-01-06 14:46:23 +00:00
|
|
|
|
|
|
|
|
// Layouts
|
|
|
|
|
import PublicLayout from "@/components/layouts/PublicLayout";
|
2026-01-16 17:31:25 +10:30
|
|
|
import ProtectedLayout from "@/components/layouts/ProtectedLayout";
|
2026-03-20 21:52:52 +10:30
|
|
|
import MarketingLayout from "@/components/layouts/MarketingLayout";
|
|
|
|
|
|
|
|
|
|
// Marketing pages
|
|
|
|
|
import HomePage from "@/pages/marketing/HomePage";
|
|
|
|
|
import FeaturesPage from "@/pages/marketing/FeaturesPage";
|
|
|
|
|
import PricingPage from "@/pages/marketing/PricingPage";
|
|
|
|
|
import SecurityPage from "@/pages/marketing/SecurityPage";
|
|
|
|
|
import SSHCertificatesPage from "@/pages/marketing/SSHCertificatesPage";
|
2026-04-06 20:57:30 +09:30
|
|
|
import ZeroTierPage from "@/pages/marketing/ZeroTierPage";
|
2026-03-20 21:52:52 +10:30
|
|
|
import DemoPage from "@/pages/marketing/DemoPage";
|
2026-01-06 14:46:23 +00:00
|
|
|
|
|
|
|
|
// Public pages
|
|
|
|
|
import LoginPage from "@/pages/auth/LoginPage";
|
|
|
|
|
import RegisterPage from "@/pages/auth/RegisterPage";
|
|
|
|
|
import VerifyEmailPage from "@/pages/auth/VerifyEmailPage";
|
|
|
|
|
import ForgotPasswordPage from "@/pages/auth/ForgotPasswordPage";
|
|
|
|
|
import ResetPasswordPage from "@/pages/auth/ResetPasswordPage";
|
|
|
|
|
import InviteAcceptPage from "@/pages/auth/InviteAcceptPage";
|
|
|
|
|
import OIDCConsentPage from "@/pages/auth/OIDCConsentPage";
|
|
|
|
|
import OIDCErrorPage from "@/pages/auth/OIDCErrorPage";
|
2026-03-02 23:55:47 +05:45
|
|
|
import OIDCLoginPage from "@/pages/auth/OIDCLoginPage";
|
2026-01-20 15:54:11 +10:30
|
|
|
import OAuthCallbackPage from "@/pages/auth/OAuthCallbackPage";
|
2026-02-28 23:35:32 +05:45
|
|
|
import ActivatePage from "@/pages/auth/ActivatePage";
|
2026-01-06 14:46:23 +00:00
|
|
|
|
|
|
|
|
// User pages
|
|
|
|
|
import ProfilePage from "@/pages/user/ProfilePage";
|
2026-03-20 21:52:52 +10:30
|
|
|
import UserSecurityPage from "@/pages/user/SecurityPage";
|
2026-01-06 14:46:23 +00:00
|
|
|
import LinkedAccountsPage from "@/pages/user/LinkedAccountsPage";
|
|
|
|
|
import ActivityPage from "@/pages/user/ActivityPage";
|
2026-02-28 23:35:32 +05:45
|
|
|
import SSHKeysPage from "@/pages/user/SSHKeysPage";
|
2026-03-22 15:36:17 +05:45
|
|
|
import CLIGuidePage from "@/pages/user/CLIGuidePage";
|
2026-01-06 14:46:23 +00:00
|
|
|
|
|
|
|
|
// Organization pages
|
|
|
|
|
import OrgOverviewPage from "@/pages/org/OrgOverviewPage";
|
|
|
|
|
import MembersPage from "@/pages/org/MembersPage";
|
2026-04-08 16:45:57 +09:30
|
|
|
import UserManagementPage from "@/pages/admin/UserManagementPage";
|
2026-01-06 14:46:23 +00:00
|
|
|
import PoliciesPage from "@/pages/org/PoliciesPage";
|
2026-01-16 17:31:25 +10:30
|
|
|
import CompliancePage from "@/pages/org/CompliancePage";
|
2026-01-06 14:46:23 +00:00
|
|
|
import OrgAuditPage from "@/pages/org/OrgAuditPage";
|
|
|
|
|
import OIDCClientsPage from "@/pages/org/OIDCClientsPage";
|
2026-02-28 23:35:32 +05:45
|
|
|
import CAsPage from "@/pages/org/CAsPage";
|
2026-02-27 21:08:16 +05:45
|
|
|
import DepartmentsPage from "@/pages/org/DepartmentsPage";
|
|
|
|
|
import PrincipalsPage from "@/pages/org/PrincipalsPage";
|
2026-03-08 18:08:42 +05:45
|
|
|
import ApiKeysPage from "@/pages/org/ApiKeysPage";
|
2026-03-01 16:50:19 +05:45
|
|
|
import MyMembershipsPage from "@/pages/org/MyMembershipsPage";
|
2026-03-20 21:52:52 +10:30
|
|
|
import NetworksPage from "@/pages/org/NetworksPage";
|
|
|
|
|
import DevicesPage from "@/pages/org/DevicesPage";
|
|
|
|
|
import AccessPage from "@/pages/org/AccessPage";
|
2026-03-29 21:33:37 +05:45
|
|
|
import ZeroTierConfigPage from "@/pages/org/ZeroTierConfigPage";
|
2026-02-27 21:08:16 +05:45
|
|
|
import SystemAuditPage from "@/pages/admin/SystemAuditPage";
|
2026-03-01 16:50:19 +05:45
|
|
|
import OAuthProvidersPage from "@/pages/admin/OAuthProvidersPage";
|
2026-03-01 20:11:22 +05:45
|
|
|
import OrgSetupPage from "@/pages/auth/OrgSetupPage";
|
2026-01-06 14:46:23 +00:00
|
|
|
|
|
|
|
|
import NotFound from "@/pages/NotFound";
|
2025-01-01 00:00:00 +00:00
|
|
|
|
2026-01-16 17:31:25 +10:30
|
|
|
const queryClient = new QueryClient({
|
|
|
|
|
defaultOptions: {
|
|
|
|
|
queries: {
|
|
|
|
|
retry: (failureCount, error) => {
|
|
|
|
|
// Don't retry on 403 authorization errors
|
|
|
|
|
if (error && typeof error === 'object' && 'code' in error && error.code === 403) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Default retry behavior for other errors (max 3 retries)
|
|
|
|
|
return failureCount < 3;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
2025-01-01 00:00:00 +00:00
|
|
|
|
|
|
|
|
const App = () => (
|
|
|
|
|
<QueryClientProvider client={queryClient}>
|
|
|
|
|
<TooltipProvider>
|
|
|
|
|
<Toaster />
|
|
|
|
|
<Sonner />
|
|
|
|
|
<BrowserRouter>
|
2026-04-06 20:57:30 +09:30
|
|
|
<ScrollToTop />
|
2026-01-06 15:33:03 +00:00
|
|
|
<AppRoutes />
|
2025-01-01 00:00:00 +00:00
|
|
|
</BrowserRouter>
|
|
|
|
|
</TooltipProvider>
|
|
|
|
|
</QueryClientProvider>
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-06 20:57:30 +09:30
|
|
|
// Scroll to top on navigation
|
|
|
|
|
function ScrollToTop() {
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
window.scrollTo({ top: 0, left: 0, behavior: 'instant' });
|
|
|
|
|
}, [location.pathname]);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-06 15:33:03 +00:00
|
|
|
// Separate component so AuthProvider can use useNavigate
|
2026-02-28 23:35:32 +05:45
|
|
|
import { AuthProvider, useAuth } from "@/contexts/AuthContext";
|
2026-03-02 23:55:47 +05:45
|
|
|
import { OrgProvider } from "@/contexts/OrgContext";
|
2026-02-28 23:35:32 +05:45
|
|
|
import { Navigate } from "react-router-dom";
|
|
|
|
|
|
|
|
|
|
/** Redirects already-authenticated users away from guest-only pages (e.g. /login). */
|
|
|
|
|
function GuestRoute({ children }: { children: React.ReactNode }) {
|
2026-03-01 20:11:22 +05:45
|
|
|
const { isAuthenticated, isOrgMember, isLoading } = useAuth();
|
2026-03-02 23:55:47 +05:45
|
|
|
// Allow authenticated users through to /login when it's a CLI auth request or
|
|
|
|
|
// an OIDC session — LoginPage will immediately forward the existing token.
|
2026-03-01 16:50:19 +05:45
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
const isCli = params.has('cli_token') || params.has('cli_redirect');
|
2026-03-02 23:55:47 +05:45
|
|
|
const isOidcBridge = params.has('oidc_session_id');
|
2026-02-28 23:35:32 +05:45
|
|
|
if (isLoading) return null; // wait for auth state to resolve
|
2026-03-02 23:55:47 +05:45
|
|
|
if (isAuthenticated && !isCli && !isOidcBridge) {
|
2026-03-01 20:11:22 +05:45
|
|
|
// If the user hasn't set up an org yet, send them there first
|
|
|
|
|
return <Navigate to={isOrgMember ? "/profile" : "/org-setup"} replace />;
|
|
|
|
|
}
|
2026-03-01 16:50:19 +05:45
|
|
|
return <>{children}</>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Blocks access to /admin/* for non-admin users. */
|
|
|
|
|
function RequireAdmin({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const { isOrgAdmin, isLoading, isAuthenticated } = useAuth();
|
|
|
|
|
if (isLoading) return null;
|
|
|
|
|
if (!isAuthenticated) return <Navigate to="/login" replace />;
|
|
|
|
|
if (!isOrgAdmin) return <Navigate to="/profile" replace />;
|
|
|
|
|
return <>{children}</>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Blocks access to /org/* for users who don't belong to any organisation. */
|
|
|
|
|
function RequireOrgMember({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const { isOrgMember, isLoading, isAuthenticated } = useAuth();
|
|
|
|
|
if (isLoading) return null;
|
|
|
|
|
if (!isAuthenticated) return <Navigate to="/login" replace />;
|
|
|
|
|
if (!isOrgMember) return <Navigate to="/profile" replace />;
|
2026-02-28 23:35:32 +05:45
|
|
|
return <>{children}</>;
|
|
|
|
|
}
|
2026-01-06 15:33:03 +00:00
|
|
|
|
2026-03-01 20:11:22 +05:45
|
|
|
/**
|
|
|
|
|
* Used for /org-setup which lives inside PublicLayout */
|
|
|
|
|
function RequireAuth({ children }: { children: React.ReactNode }) {
|
|
|
|
|
const { isAuthenticated, isLoading } = useAuth();
|
|
|
|
|
if (isLoading) return null;
|
|
|
|
|
if (!isAuthenticated) return <Navigate to="/login" replace />;
|
|
|
|
|
return <>{children}</>;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-06 15:33:03 +00:00
|
|
|
function AppRoutes() {
|
|
|
|
|
return (
|
|
|
|
|
<AuthProvider>
|
2026-03-02 23:55:47 +05:45
|
|
|
<OrgProvider>
|
2026-01-06 15:33:03 +00:00
|
|
|
<Routes>
|
2026-03-20 21:52:52 +10:30
|
|
|
{/* Marketing pages */}
|
|
|
|
|
<Route element={<MarketingLayout />}>
|
|
|
|
|
<Route path="/" element={<HomePage />} />
|
|
|
|
|
<Route path="/features" element={<FeaturesPage />} />
|
|
|
|
|
<Route path="/pricing" element={<PricingPage />} />
|
|
|
|
|
<Route path="/security" element={<SecurityPage />} />
|
|
|
|
|
<Route path="/ssh-certificates" element={<SSHCertificatesPage />} />
|
2026-04-06 20:57:30 +09:30
|
|
|
<Route path="/zerotier" element={<ZeroTierPage />} />
|
2026-03-20 21:52:52 +10:30
|
|
|
<Route path="/demo" element={<DemoPage />} />
|
|
|
|
|
</Route>
|
2026-01-06 15:33:03 +00:00
|
|
|
|
|
|
|
|
{/* Public routes */}
|
|
|
|
|
<Route element={<PublicLayout />}>
|
2026-02-28 23:35:32 +05:45
|
|
|
<Route path="/login" element={<GuestRoute><LoginPage /></GuestRoute>} />
|
2026-01-06 15:33:03 +00:00
|
|
|
<Route path="/register" element={<RegisterPage />} />
|
|
|
|
|
<Route path="/verify-email" element={<VerifyEmailPage />} />
|
|
|
|
|
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
|
|
|
|
|
<Route path="/reset-password" element={<ResetPasswordPage />} />
|
|
|
|
|
<Route path="/invite" element={<InviteAcceptPage />} />
|
|
|
|
|
<Route path="/consent" element={<OIDCConsentPage />} />
|
2026-03-02 23:55:47 +05:45
|
|
|
<Route path="/oidc-login" element={<OIDCLoginPage />} />
|
2026-01-06 15:33:03 +00:00
|
|
|
<Route path="/error" element={<OIDCErrorPage />} />
|
2026-01-20 15:54:11 +10:30
|
|
|
<Route path="/oauth/callback" element={<OAuthCallbackPage />} />
|
2026-02-28 23:35:32 +05:45
|
|
|
<Route path="/activate" element={<ActivatePage />} />
|
2026-03-01 20:11:22 +05:45
|
|
|
{/* Org-setup uses the same full-screen centred layout as auth pages,
|
|
|
|
|
but requires a valid session token (RequireAuth guard below). */}
|
|
|
|
|
<Route path="/org-setup" element={<RequireAuth><OrgSetupPage /></RequireAuth>} />
|
2026-01-06 15:33:03 +00:00
|
|
|
</Route>
|
|
|
|
|
|
2026-01-16 17:31:25 +10:30
|
|
|
{/* Protected routes - handles auth and MFA enforcement */}
|
|
|
|
|
<Route element={<ProtectedLayout />}>
|
2026-01-06 15:33:03 +00:00
|
|
|
{/* User routes */}
|
|
|
|
|
<Route path="/profile" element={<ProfilePage />} />
|
2026-03-29 10:49:27 +05:45
|
|
|
<Route path="/account/security" element={<UserSecurityPage />} />
|
2026-01-06 15:33:03 +00:00
|
|
|
<Route path="/linked-accounts" element={<LinkedAccountsPage />} />
|
|
|
|
|
<Route path="/activity" element={<ActivityPage />} />
|
2026-02-28 23:35:32 +05:45
|
|
|
<Route path="/ssh-keys" element={<SSHKeysPage />} />
|
2026-03-22 15:36:17 +05:45
|
|
|
<Route path="/cli-guide" element={<CLIGuidePage />} />
|
2026-01-06 15:33:03 +00:00
|
|
|
|
2026-03-01 16:50:19 +05:45
|
|
|
{/* Organization routes — org members: overview + own memberships only */}
|
|
|
|
|
<Route path="/org" element={<RequireOrgMember><OrgOverviewPage /></RequireOrgMember>} />
|
|
|
|
|
<Route path="/org/my-memberships" element={<RequireOrgMember><MyMembershipsPage /></RequireOrgMember>} />
|
2026-03-20 21:52:52 +10:30
|
|
|
<Route path="/org/zerotier/devices" element={<RequireOrgMember><DevicesPage /></RequireOrgMember>} />
|
2026-03-01 16:50:19 +05:45
|
|
|
|
|
|
|
|
{/* Organization management routes — org admins/owners only */}
|
|
|
|
|
<Route path="/org/members" element={<RequireAdmin><MembersPage /></RequireAdmin>} />
|
2026-04-08 16:45:57 +09:30
|
|
|
<Route path="/org/members/:userId" element={<RequireAdmin><UserManagementPage /></RequireAdmin>} />
|
2026-03-01 16:50:19 +05:45
|
|
|
<Route path="/org/departments" element={<RequireAdmin><DepartmentsPage /></RequireAdmin>} />
|
|
|
|
|
<Route path="/org/principals" element={<RequireAdmin><PrincipalsPage /></RequireAdmin>} />
|
2026-03-08 18:08:42 +05:45
|
|
|
<Route path="/org/api-keys" element={<RequireAdmin><ApiKeysPage /></RequireAdmin>} />
|
2026-03-01 16:50:19 +05:45
|
|
|
<Route path="/org/policies" element={<RequireAdmin><PoliciesPage /></RequireAdmin>} />
|
|
|
|
|
<Route path="/org/policies/compliance" element={<RequireAdmin><CompliancePage /></RequireAdmin>} />
|
|
|
|
|
<Route path="/org/audit" element={<RequireAdmin><OrgAuditPage /></RequireAdmin>} />
|
|
|
|
|
<Route path="/org/clients" element={<RequireAdmin><OIDCClientsPage /></RequireAdmin>} />
|
|
|
|
|
<Route path="/org/cas" element={<RequireAdmin><CAsPage /></RequireAdmin>} />
|
2026-03-20 21:52:52 +10:30
|
|
|
<Route path="/org/zerotier/networks" element={<RequireAdmin><NetworksPage /></RequireAdmin>} />
|
|
|
|
|
<Route path="/org/zerotier/access" element={<RequireAdmin><AccessPage /></RequireAdmin>} />
|
2026-03-29 21:33:37 +05:45
|
|
|
<Route path="/org/zerotier/config" element={<RequireAdmin><ZeroTierConfigPage /></RequireAdmin>} />
|
2026-03-01 16:50:19 +05:45
|
|
|
|
|
|
|
|
{/* Admin routes — org admin/owner only */}
|
|
|
|
|
<Route path="/admin/audit" element={<RequireAdmin><SystemAuditPage /></RequireAdmin>} />
|
2026-03-02 23:55:47 +05:45
|
|
|
<Route path="/admin/users" element={<Navigate to="/org/members" replace />} />
|
2026-03-01 16:50:19 +05:45
|
|
|
<Route path="/admin/oauth" element={<RequireAdmin><OAuthProvidersPage /></RequireAdmin>} />
|
2026-01-06 15:33:03 +00:00
|
|
|
</Route>
|
|
|
|
|
|
|
|
|
|
{/* Catch-all */}
|
|
|
|
|
<Route path="*" element={<NotFound />} />
|
|
|
|
|
</Routes>
|
2026-01-06 15:39:14 +00:00
|
|
|
|
2026-03-02 23:55:47 +05:45
|
|
|
</OrgProvider>
|
2026-01-06 15:33:03 +00:00
|
|
|
</AuthProvider>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-01 00:00:00 +00:00
|
|
|
export default App;
|