Appearance
System Architecture
DoCurious is a React 19 single-page application built with TypeScript, Vite 7, Zustand for state management, and Tailwind CSS 4 for styling. It follows a mock-first architecture -- every feature works with client-side mock APIs, and each module can be individually swapped to a real backend.
Tech Stack
Frontend
| Layer | Technology | Version |
|---|---|---|
| Framework | React | 19.2.0 |
| Language | TypeScript | 5.9.3 |
| Build Tool | Vite | 7.2.4 |
| Styling | Tailwind CSS | 4.1.18 |
| State Management | Zustand | 5.0.11 |
| Routing | React Router | 7.13.0 |
| Forms | React Hook Form | 7.71.1 |
| i18n | react-i18next | 14.1.2 |
| Charts | Recharts | 3.7.0 |
| Testing | Vitest + Testing Library | 4.0.18 |
| Linting | ESLint + Prettier | 9.39.1 / 3.8.1 |
| Documentation | VitePress + TypeDoc | 1.6.4 / 0.28.17 |
| Storybook | Storybook | 10.2.8 |
Backend (server/)
| Layer | Technology | Version |
|---|---|---|
| Framework | Express | 4.21 |
| Language | TypeScript | 5.7 |
| ORM | Prisma | 6.3 |
| Database | PostgreSQL | 17 |
| Validation | Zod | 3.24 |
| Auth | jsonwebtoken + bcryptjs | 9.0 / 2.4 |
| Security | helmet | 8.0 |
| Testing | Vitest + Supertest | 4.0 / 7.2 |
See Backend Architecture for the full server documentation.
Architecture Diagram
Layer-by-Layer
Types (src/types/ -- 32 files)
Every domain entity has a dedicated type file. All types are re-exported through src/types/index.ts as a barrel.
| File | Domain |
|---|---|
common.types.ts | BaseEntity, UUID, ApiResponse<T>, PaginatedResponse<T>, PaginationParams |
auth.types.ts | LoginCredentials, AuthTokens, AuthState |
user.types.ts | User, UserRole (9 roles), AccountTier, ConsentStatus |
challenge.types.ts | Challenge, ChallengeInstance, TrackRecord, TrackRecordEntry |
community.types.ts | Community (6 types), CommunityPost, FeedType |
gamification.types.ts | Badge, XPEvent, LevelDefinition, UserStreak |
school.types.ts | School, Class, Assignment, StudentProgress |
vendor.types.ts | Vendor, VendorAccountStatus, VendorApprovalStatus |
featureFlag.types.ts | FeatureFlag, TargetingRule, FlagEvaluationContext |
wallet.types.ts | WalletTransaction, Payout, WalletSummary |
challengeSeries.types.ts | Series, SeriesStatus, SeriesOrder |
notification.types.ts | Notification, NotificationPreferences |
gift.types.ts | Gift sending/receiving types |
explore.types.ts | CuratedView, SavedListItem, search types |
portfolio.types.ts | Portfolio/scrapbook types |
event.types.ts | Challenge events and registrations |
learningPath.types.ts | Learning path progression |
dealersChoice.types.ts | Dealer's Choice game mechanics |
reflection.types.ts | SEL reflection prompts and responses |
admin.types.ts | Admin roles, permissions, audit log |
invitation.types.ts | Challenge invitations |
payment.types.ts | Stripe payment integration types |
sharing.types.ts | Cross-community sharing |
onboarding.types.ts | Onboarding flow state |
legal.types.ts | Consent, cookie preferences |
location.types.ts | Geocoding and map types |
report.types.ts | Content reporting/flagging |
review.types.ts | Challenge reviews |
order.types.ts | Order management |
coupon.types.ts | Coupon/discount types |
communityGoal.types.ts | Cooperative community goals |
API Layer (src/api/ -- ~50 files)
The API layer uses a dual-implementation pattern. Each domain has a mock implementation (*.api.ts) and a real implementation (*.real.api.ts). The barrel export (src/api/index.ts) selects between them based on VITE_USE_MOCK_API.
typescript
// src/api/index.ts — switching pattern
import { apiConfig } from './config'
import { challengeApi as mockChallengeApi } from './challenge.api'
import { realChallengeApi } from './challenge.real.api'
export const challengeApi = apiConfig.useMockApi
? mockChallengeApi
: realChallengeApi25 API modules: auth, user, challenge, trackRecord, explore, community, gift, school, notification, gamification, learningPath, portfolio, event, communityGoal, payment, emailVerification, invitation, reflection, onboarding, sharing, admin, dealersChoice, wallet, series (real only).
See API Layer for full documentation.
Adapters (src/adapters/)
The adapter layer maps between SQL backend responses (snake_case, bigint IDs, UPPER_CASE enums) and FE types (camelCase, UUID strings, lowercase enums). Entity adapters: challenge, vendor, series, portfolio, wallet.
See Adapter Guide for full documentation.
Stores (src/store/ -- 30 stores)
Zustand stores with selectors for optimized re-renders. All stores and selectors are re-exported through src/store/index.ts.
Production stores (26): useAuthStore, useUserStore, useChallengeStore, useTrackRecordStore, useExploreStore, useCommunityStore, useGiftStore, useSchoolStore, useNotificationStore, useGamificationStore, useLearningPathStore, usePortfolioStore, useCommunityGoalStore, useVendorStore, useInvitationStore, useOnboardingStore, useShareStore, useAdminStore, useDealersChoiceStore, useLegalStore, useWalletStore, useCheckoutStore, useReflectionStore, useVendorTeamStore, useImpersonationStore, useToastStore.
Infrastructure stores (4): useFeatureFlagStore, useDemoModeStore, useDebugPanelStore (dev-only), useNetworkLogStore (dev-only).
See Store Patterns for full documentation.
Custom Hooks (src/hooks/)
| Hook | Purpose |
|---|---|
useFeatureFlag(flagKey) | Evaluates a feature flag for the current user by bridging auth store (user context) with the feature flag store (evaluation). Returns { enabled, value, isLoading }. |
useLocation() | Manages user location state with 4 strategies: saved location (localStorage), user profile location, browser geolocation API, manual address input. Returns location, radius, permission status, and control functions. |
useAddressSearch() | Address autocomplete hook that provides suggestions and place selection via the location API. |
Utilities (src/lib/)
| File | Exports | Purpose |
|---|---|---|
utils.ts | cn(...inputs) | Tailwind CSS class merge utility. Combines clsx (conditional classes) with tailwind-merge (deduplication). Used throughout all components. |
contextHelpers.ts | 18 functions | Pure functions for the multi-context user model. Includes buildContextsFromLegacyRole (bridge for old single-role data), hasContext, getRoleInContext, getAvailableContexts, role checks (isSchoolAdmin, isPlatformAdmin, isVendor, hasLinkedChildren, isTier1), and getContextHomeRoute. Used by auth store selectors, ContextGuard, and ParentGuard. |
Components (src/components/ -- 26 directories, ~148 components)
Component directories organized by domain:
| Directory | Purpose |
|---|---|
account/ | Account management (notification preferences) |
admin/ | Admin-only components (ImpersonationBar) |
challenge/ | ChallengeCard, ChallengeGrid, finalization (7-step wizard) |
common/ | ErrorBoundary, Skeleton, FeatureGate, CookieConsentBanner, GuidedTour |
community/ | CommunityCard, FeedPost, MemberList, GoalCard, AssignChallengeModal |
debug/ | Debug Panel (dev-only, 6 tabs) |
demo/ | DemoModeBar (sales presentation mode) |
explore/ | DealersChoice, FilterPanel, CalendarView, EventCard |
filters/ | FilterSidebar, FilterModal, AppliedFilters |
gamification/ | BadgeCard, LevelProgress, StreakDisplay, JourneyMap, LeaderboardTable |
gift/ | GiftCard, GiftModal, GiftPermissionSettings |
invitation/ | InvitationCard, InviteModal |
layout/ | AppLayout, Header, Sidebar, TopNav, CartDrawer, ContextSwitcher |
learningPaths/ | PathCard, PathProgress |
notifications/ | NotificationCenter |
payment/ | PaymentForm, StripeCheckout, CheckoutModal |
portfolio/ | PortfolioCard, PortfolioBuilder |
review/ | TrackRecordReviewModal |
school/ | CSVRosterImport, TeacherReflectionConfig, ReflectionDefaults |
search/ | SearchTrigger, SearchFilterBar, LocationFilter |
social/ | ShareButton, BatchShareModal, BlockUserButton, CommunityModTools, JoinRequestFlow |
themeEditor/ | EditorShell, 6 editor panels, 5 control primitives, PreviewPanel |
trackRecord/ | EntryCard, MediaUpload, TrackRecordView, DocumentationGuideModal |
ui/ | 22 themed UI primitives (Button, Card, Dialog, Table, etc.) |
Pages (src/pages/ -- 189 pages)
Route-level components organized by domain: auth/, challenges/, communities/, explore/, gifts/, school/, admin/ (85+ toolkit pages), vendor/, parent/, learningPaths/, portfolio/, profile/, account/, and public/ (compliance pages).
Routes (src/routes/)
React Router v7 with createBrowserRouter. 195 routes with lazy loading via React.lazy(). Seven guard types: AuthGuard, RoleGuard, TierGuard, AgeGuard, ConsentGuard, ContextGuard, ParentGuard (plus AdminRoleGuard for granular admin permissions).
See Routes & Guards for patterns and guard documentation, or the Route Map for the complete auto-generated listing.
Key Architectural Patterns
Mock-First API
Every API module has both a mock (*.api.ts) and real (*.real.api.ts) implementation. The app defaults to mocks (VITE_USE_MOCK_API defaults to true unless explicitly set to 'false'). This enables:
- Full frontend development without a backend
- Demo mode with pre-seeded data
- Gradual backend integration per module
Selector-Based Store Access
Stores export memoized selector functions to prevent unnecessary re-renders:
typescript
// Define selectors alongside the store
export const selectChallenges = (state: ChallengeStore) => state.challenges
export const selectMyChallenges = (state: ChallengeStore) => state.myChallenges
// Use in components
const challenges = useChallengeStore(selectChallenges)Lazy Loading
All page components are lazy-loaded with React.lazy() and wrapped in Suspense with a PageSkeleton fallback and ErrorBoundary:
tsx
const Dashboard = lazy(() =>
import('../pages/Dashboard').then(m => ({ default: m.Dashboard }))
)
function LazyPage({ children }: { children: React.ReactNode }) {
return (
<ErrorBoundary>
<Suspense fallback={<PageSkeleton />}>
{children}
</Suspense>
</ErrorBoundary>
)
}Guard Composition
Route guards compose naturally by nesting:
tsx
<AuthGuard>
<RoleGuard roles={['teacher', 'school_admin']}>
<TierGuard>
<LazyPage><SomePage /></LazyPage>
</TierGuard>
</RoleGuard>
</AuthGuard>Theme via CSS Variables
The theme system applies configuration as CSS custom properties on :root, allowing any component to reference theme tokens without importing the theme:
css
.card {
background: var(--background);
border: var(--theme-card-border-width) solid var(--border);
border-radius: var(--theme-card-radius);
box-shadow: var(--theme-card-shadow);
}Backend Layer
The backend is a full Express server in server/ with 130+ REST endpoints, JWT authentication, role-based access control, and Prisma ORM. It follows a Routes → Controllers → Services → Prisma pattern across 18 domain services.
See the Backend section for complete documentation:
- Backend Quick Start -- get the server running
- Backend Architecture -- server layers, middleware, auth flow
- Database & Prisma -- schema with legacy + modern model layers
- API Endpoints -- complete endpoint reference (130+ routes)
- Backend Testing -- test patterns and helpers
See Also
Feature Guides
- Accounts | Challenges | Track Records | Explore | Communities | Gifting | School | Gamification | Vendor | Admin | Notifications | Reflection
Operations
- Onboarding | Performance | Launch Plan | Safety | Privacy | i18n
Role Guides
- General User | Student | Parent | Teacher | School Admin | Vendor | Platform Admin