Skip to content

Routes & Guards

DoCurious uses React Router v7 with createBrowserRouter. The application defines 195 routes, organized into public and protected sections. All page components are lazy-loaded for code splitting, and route access is controlled by composable guard components.

For the complete auto-generated route listing, see the Route Map (195 routes with component and guard info).

Route Organization

Routes are defined in src/routes/index.tsx and organized into two top-level groups:

Public Routes (no auth required)

These routes are accessible without authentication:

PathPageNotes
/loginLoginEager-loaded for fast initial paint
/registerRegisterEager-loaded
/forgot-passwordForgotPasswordEager-loaded
/reset-password/:tokenResetPasswordEager-loaded
/verify-emailVerifyEmailEager-loaded
/demoDemoLandingDemo persona selector
/welcomeLandingPublic landing page
/termsTermsOfService
/privacyPrivacyPolicy
/accessibilityAccessibilityStatement
/acceptable-useAcceptableUsePolicy
/community-guidelinesCommunityGuidelines
/vendor-termsVendorTerms
/onboardingOnboarding
/onboarding/:roleRoleOnboardingRole-specific onboarding
/age-verificationAgeVerificationCOPPA age check
/consent/:tokenParentalConsentParent consent flow
/vendor/applyVendorApplicationPublic vendor application

Protected Routes (auth required)

All protected routes are children of the root / layout route, which is wrapped in AuthGuard and renders AppLayout (sidebar + header + content area).

Core: See Accounts

PathPageGuard
/ (index)DashboardAuthGuard
/accountAccountAuthGuard
/account/deleteAccountDeletionAuthGuard
/account/data-exportDataExportRequestAuthGuard
/account/appealAppealFormAuthGuard
/account/linked-accountsLinkedAccountsAuthGuard
/account/data-access-requestDataAccessRequestAuthGuard
/notificationsNotificationsAuthGuard

Explore: See Explore

PathPageGuard
/exploreExploreAuthGuard
/explore/searchExplore (with query params)AuthGuard
/explore/best-fitExploreCategoryViewAuthGuard
/explore/featuredExploreCategoryViewAuthGuard
/explore/freeExploreCategoryViewAuthGuard
/explore/popularExploreCategoryViewAuthGuard
/explore/newExploreCategoryViewAuthGuard
/explore/category/:slugExploreCategoryViewAuthGuard
/explore/mapMapViewAuthGuard
/dealers-choiceDealersChoicePageAuthGuard

Challenges: See Challenges and Track Records

PathPageGuard
/challenges/:idChallengeDetailAuthGuard
/my-challengesMyChallengesAuthGuard
/my-challenges/:id/trackTrackRecordAuthGuard
/challenges/gift/:codeGiftRedemptionAuthGuard
/challenges/group/:idGroupChallengeAuthGuard
/challenges/submitSubmitChallengeAuthGuard

Social: See Communities and Gifting

PathPageGuard
/savedSavedListAuthGuard + TierGuard
/communitiesCommunitiesAuthGuard
/communities/:idCommunityDetailAuthGuard
/communities/schoolSchoolCommunityAuthGuard
/communities/gradeSchoolCommunityAuthGuard
/communities/classSchoolCommunityAuthGuard
/communities/privatePrivateCommunitiesAuthGuard + TierGuard
/communities/:id/assignments/:idAssignmentProgressAuthGuard
/communities/:id/goalsCommunityGoalsAuthGuard
/giftsGiftsAuthGuard + TierGuard

Progress & Gamification: See Gamification and Reflection

PathPageGuard
/learning-pathsLearningPathsAuthGuard
/learning-paths/:idLearningPathDetailAuthGuard
/portfoliosPortfoliosAuthGuard
/portfolios/:idPortfolioViewAuthGuard
/journey-mapJourneyMapAuthGuard
/badgesBadgesAuthGuard
/xp-historyXPHistoryAuthGuard
/reflectionsMyReflectionsAuthGuard

Parent: See Parent role guide

PathPageGuard
/parentParentDashboardRoleGuard (parent)

Vendor: See Vendor guide and Vendor role guide

PathPageGuard
/vendorVendorDashboardRoleGuard (vendor)
/vendor/challengesVendorChallengesRoleGuard (vendor)
/vendor/challenges/createVendorChallengeCreateRoleGuard (vendor)
/vendor/ordersVendorOrdersRoleGuard (vendor)
/vendor/analyticsVendorAnalyticsRoleGuard (vendor)
/vendor/eventsVendorEventsRoleGuard (vendor)
/vendor/settingsVendorSettingsRoleGuard (vendor)

School Administration: See School guide, Teacher role guide, and School Admin role guide

PathPageGuard
/schoolSchoolDashboardRoleGuard (head_school_admin, school_admin, teacher)
/school/rosterRosterRoleGuard (head_school_admin, school_admin)
/school/classesClassesRoleGuard (head_school_admin, school_admin)
/school/gradesGradesRoleGuard (head_school_admin, school_admin, teacher)
/school/teachersTeachersRoleGuard (head_school_admin, school_admin)
/school/assignmentsAssignmentsRoleGuard (head_school_admin, school_admin, teacher)
/school/classes/:id/progressClassProgressRoleGuard (head_school_admin, school_admin, teacher)
/school/reviewsTeacherReviewsRoleGuard (head_school_admin, school_admin, teacher)
/school/surveysSurveysRoleGuard (head_school_admin, school_admin)
/school/reflection-analyticsReflectionAnalyticsRoleGuard (head_school_admin, school_admin, teacher)
/school/purchase-requestsPurchaseRequestQueueRoleGuard (head_school_admin, school_admin)
/school/bulk-giftBulkGiftChallengesRoleGuard (head_school_admin, school_admin)
/school/alumni-transitionAlumniTransitionRoleGuard (head_school_admin, school_admin)
/school/my-assignmentsAssignmentsRoleGuard (student)
/school/my-gradesGradesRoleGuard (student)
/school/child-gradesGradesRoleGuard (parent)

Platform Admin: See Admin guide and Platform Admin role guide

PathPageGuard
/admin/usersUserManagementRoleGuard (platform_admin)
/admin/users/:userIdUserDetailRoleGuard (platform_admin)
/admin/vendorsVendorManagementRoleGuard (platform_admin, staff)
/admin/challengesChallengeManagementRoleGuard (platform_admin)
/admin/challenge-seriesChallengeSeriesManagementRoleGuard (platform_admin)
/admin/challenge-queueChallengeApprovalQueueRoleGuard (platform_admin, staff)
/admin/verificationVerificationQueueRoleGuard (platform_admin, staff)
/admin/reviewsReviewModerationRoleGuard (platform_admin, staff)
/admin/ordersOrderManagementRoleGuard (platform_admin)
/admin/couponsCouponManagementRoleGuard (platform_admin)
/admin/schoolsSchoolsManagementRoleGuard (platform_admin)
/admin/settingsPlatformSettingsRoleGuard (platform_admin)
/admin/analyticsAnalyticsDashboardRoleGuard (platform_admin)
/admin/audit-logAuditLogRoleGuard (platform_admin)
/admin/flagged-contentFlaggedContentQueueRoleGuard (platform_admin, staff)
/admin/rolesAdminRolesRoleGuard (platform_admin)
/admin/reflection-analyticsReflectionAnalyticsAdminRoleGuard (platform_admin)
/admin/featured-contentFeaturedContentRoleGuard (platform_admin)
/admin/school-healthSchoolHealthDashboardRoleGuard (platform_admin)
/admin/product-analyticsProductAnalyticsRoleGuard (platform_admin)
/admin/content-analyticsContentAnalyticsRoleGuard (platform_admin)
/admin/engineering-analyticsEngineeringAnalyticsRoleGuard (platform_admin)
/admin/report-builderReportBuilderRoleGuard (platform_admin)
/admin/theme-editorDesignTokenEditorRoleGuard (platform_admin)
/admin/component-showcaseComponentShowcaseRoleGuard (platform_admin)
/admin/feature-flagsFeatureFlagManagementRoleGuard (platform_admin)

Catch-all:

PathBehavior
*Redirect to /

Guard Types

Guards are React components that wrap route content. They check conditions and either render children or redirect/show a fallback.

AuthGuard

Ensures the user is authenticated. Wraps the entire protected route tree.

tsx
// src/routes/guards/AuthGuard.tsx
interface AuthGuardProps {
  children: React.ReactNode
}
  • Initializes auth state on mount
  • Shows a spinner while loading
  • Redirects to /login if not authenticated (saves attempted location for post-login redirect)

RoleGuard

Restricts access to specific user roles. Must be used inside AuthGuard.

tsx
// src/routes/guards/RoleGuard.tsx
interface RoleGuardProps {
  children: React.ReactNode
  roles: UserRole[]       // Allowed roles
  redirectTo?: string     // Default: '/'
  fallback?: React.ReactNode
}

Example:

tsx
<RoleGuard roles={['platform_admin', 'staff']}>
  <VerificationQueue />
</RoleGuard>

TierGuard

Restricts Tier 1 (school-only) accounts from accessing personal features like saved lists, gifts, and private communities.

tsx
// src/routes/guards/TierGuard.tsx
interface TierGuardProps {
  children: React.ReactNode
  redirectTo?: string
  fallback?: React.ReactNode  // Default: TierRestrictedFallback
}

Shows a "Feature Not Available" message for school-only accounts.

AgeGuard

Restricts content based on user age for COPPA compliance.

tsx
// src/routes/guards/AgeGuard.tsx
interface AgeGuardProps {
  children: React.ReactNode
  minAge?: number         // Default: 13
  redirectTo?: string
  fallback?: React.ReactNode  // Default: AgeRestrictedFallback
}

ConsentGuard

Ensures under-13 users have parental consent for Tier 2 features.

tsx
// src/routes/guards/ConsentGuard.tsx
interface ConsentGuardProps {
  children: React.ReactNode
  redirectTo?: string
  fallback?: React.ReactNode  // Default: ConsentRequiredFallback
}

Only blocks under-13 users whose parentalConsentStatus is not 'granted'.

AdminRoleGuard

Provides granular admin role checking beyond the basic platform_admin RoleGuard.

tsx
// src/routes/guards/AdminRoleGuard.tsx
interface AdminRoleGuardProps {
  children: React.ReactNode
  roles?: AdminRole[]              // Required admin roles
  requiredFeature?: AdminFeatureArea  // Required feature area access
  redirectTo?: string
  fallback?: React.ReactNode
}

Super admins (adminRole === 'super') always pass. Other admin roles are checked against the ADMIN_PERMISSIONS map.

ContextGuard

Protects routes based on the multi-context user model. Used by all school, admin, and vendor routes. Replaces RoleGuard for context-aware routing.

tsx
// src/routes/guards/ContextGuard.tsx
interface ContextGuardProps {
  children: React.ReactNode
  context: ContextType       // 'personal' | 'school' | 'business' | 'platform'
  roles?: string[]           // Optional: required roles within that context
  redirectTo?: string
  fallback?: React.ReactNode
}

Examples:

tsx
// School routes — user must have school context with admin or teacher role
<ContextGuard context="school" roles={['school_admin', 'head_school_admin', 'teacher']}>
  <SchoolDashboard />
</ContextGuard>

// Platform admin routes — user must have platform context
<ContextGuard context="platform" roles={['admin']}>
  <UserManagement />
</ContextGuard>

// Vendor routes — any business context
<ContextGuard context="business">
  <VendorDashboard />
</ContextGuard>

Uses hasContext and hasRoleInContext from src/lib/contextHelpers.ts for context resolution. Shows an "Access Restricted" fallback with a link to the appropriate context home route.

ParentGuard

Restricts access to routes that require linked child accounts. Parent is a person-level relationship, not a context.

tsx
// src/routes/guards/ParentGuard.tsx
interface ParentGuardProps {
  children: React.ReactNode
  redirectTo?: string
  fallback?: React.ReactNode  // Default: NotAParentFallback
}

Uses hasLinkedChildren() from src/lib/contextHelpers.ts. Shows a "Parent Access Required" message for non-parent users.

Guard Composition Pattern

Guards compose by nesting. The outermost guard checks first:

tsx
// AuthGuard wraps the entire layout
<AuthGuard>
  <AppLayout>
    {/* RoleGuard checks role within authenticated context */}
    <RoleGuard roles={['school_admin', 'teacher']}>
      {/* TierGuard checks account tier */}
      <TierGuard>
        <LazyPage><SomePage /></LazyPage>
      </TierGuard>
    </RoleGuard>
  </AppLayout>
</AuthGuard>

Lazy Loading Pattern

All page components use React.lazy() with named exports:

typescript
const Dashboard = lazy(() =>
  import('../pages/Dashboard').then(m => ({ default: m.Dashboard }))
)

Every lazy route is wrapped in LazyPage, which provides Suspense with a skeleton fallback and an ErrorBoundary:

tsx
function LazyPage({ children }: { children: React.ReactNode }) {
  return (
    <ErrorBoundary>
      <Suspense fallback={<PageSkeleton />}>
        {children}
      </Suspense>
    </ErrorBoundary>
  )
}

Auth pages (Login, Register, ForgotPassword, ResetPassword, VerifyEmail) are eagerly imported for fast initial load since they are the first pages users see.

How to Add a New Route

  1. Create the page component in src/pages/myDomain/MyPage.tsx:
tsx
export function MyPage() {
  return <div>My Page Content</div>
}
  1. Add lazy import at the top of src/routes/index.tsx:
typescript
const MyPage = lazy(() =>
  import('../pages/myDomain/MyPage').then(m => ({ default: m.MyPage }))
)
  1. Add the route in the appropriate section:
tsx
// Inside the protected routes children array:
{
  path: 'my-page',
  element: (
    <RoleGuard roles={['platform_admin']}>
      <LazyPage><MyPage /></LazyPage>
    </RoleGuard>
  ),
},
  1. Add navigation -- update the sidebar navigation in src/components/layout/Sidebar.tsx to include a link to the new route.

DoCurious Platform Documentation