Frontend Architecture
Overview
The frontend is a Next.js 15 application with React 19, TypeScript, Redux Toolkit, and Tailwind CSS. It uses the Pages Router with client-side rendering (CSR), optimized for complex admin/builder interactions and PWA offline capabilities.
Location: frontend/src/
Total Files: 408 TypeScript/TSX files across 14 directories
Tech Stack
| Technology |
Version |
Purpose |
| Next.js |
15.3.1 |
React framework with Pages Router |
| React |
19.0.0 |
UI library |
| TypeScript |
5.x |
Type safety |
| Redux Toolkit |
2.1.0 |
State management |
| MUI X DataGrid |
7.0.0 |
Data tables |
| Tailwind CSS |
3.4.1 |
Utility-first styling |
| Formik |
2.4.x |
Form management |
| Axios |
1.x |
HTTP client |
| Serwist |
9.x |
PWA service worker |
| Dexie.js |
4.x |
IndexedDB wrapper |
Architecture Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ ENTRY LAYER │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ pages/_app.tsx │ │
│ │ • Redux Provider (store.ts) │ │
│ │ • DownloadContext (PWA progress) │ │
│ │ • Axios interceptors (JWT refresh, error handling) │ │
│ │ • PWA service worker registration (Serwist) │ │
│ │ • ErrorBoundary wrapper │ │
│ │ • i18n initialization │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAYOUT LAYER │
│ ┌───────────────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ Authenticated.tsx │ │ Guest.tsx │ │
│ │ • JWT validation (client-side) │ │ • Public routes only │ │
│ │ • Permission checking per route │ │ • Login, Register, Forgot │ │
│ │ • Project existence guard │ │ • Minimal layout │ │
│ │ • NavBar, AsideMenu, FooterBar │ │ │ │
│ │ • Tour guide (IntroGuide) │ │ │ │
│ └───────────────────────────────────┘ └─────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PAGE LAYER (99 pages) │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Entity CRUD Pages (78 pages = 13 entities × 6 pages each) │ │
│ │ • [entity]-list.tsx → createListPage factory │ │
│ │ • [entity]-new.tsx → Formik forms with validation │ │
│ │ • [entity]-edit.tsx → useEditPageSync hook │ │
│ │ • [entity]-view.tsx → Read-only display │ │
│ │ • [entity]-table.tsx → Embedded table view │ │
│ │ • [entityId].tsx → Dynamic route handler │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Special Pages (21 pages) │ │
│ │ • constructor.tsx → Visual tour builder (47KB) │ │
│ │ • dashboard.tsx → Admin home with entity counts │ │
│ │ • p/[slug]/index.tsx → Public production presentation │ │
│ │ • p/[slug]/stage.tsx → Stage preview presentation │ │
│ │ • login.tsx, forgot.tsx, password-reset.tsx → Auth flows │ │
│ │ • profile.tsx → User profile management │ │
│ │ • element-type-defaults.tsx → Global element defaults │ │
│ │ • project-element-defaults.tsx → Project element overrides │ │
│ │ • search.tsx → Global search │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ COMPONENT LAYER (193 components) │
│ ┌────────────────────────┐ ┌────────────────────────┐ ┌────────────────┐ │
│ │ Entity Components (78) │ │ Constructor (15) │ │ Runtime (5) │ │
│ │ • Table[Entity] │ │ • ElementEditorPanel │ │ • Runtime- │ │
│ │ • configure*Cols │ │ • CanvasElement │ │ Presentation│ │
│ │ • Card[Entity] │ │ • CanvasBackground │ │ • Runtime- │ │
│ │ • List[Entity] │ │ • ConstructorMenu │ │ Element │ │
│ │ • Form fields │ │ • PageSelector │ │ • UiElement- │ │
│ └────────────────────────┘ │ • TransitionPreview │ │ Renderer │ │
│ ┌────────────────────────┐ └────────────────────────┘ │ │ │
│ │ ElementSettings (23) │ ┌────────────────────────┐ └────────────────┘ │
│ │ • StyleSettings │ │ Offline/PWA (5) │ ┌────────────────┐ │
│ │ • NavigationSettings │ │ • DownloadProgress │ │ Generic (50+) │ │
│ │ • EffectsSettings │ │ • OfflineToggle │ │ • CardBox* │ │
│ │ • CarouselSettings │ │ • StorageUsage │ │ • FormField* │ │
│ │ • MediaSettings │ │ • OfflineStatus │ │ • NavBar* │ │
│ │ • TooltipSettings │ └────────────────────────┘ │ • AsideMenu* │ │
│ └────────────────────────┘ │ • BaseButton │ │
│ ┌────────────────────────┐ │ • Pagination │ │
│ │ Factories (2) │ │ • Search │ │
│ │ • createTableComp │ └────────────────┘ │
│ │ • configBuilderFact │ │
│ └────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ HOOKS LAYER (32 hooks) │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Runtime/Preloading (8 hooks) │ │
│ │ usePreloadOrchestrator ──→ usePageSwitch ──→ useBackgroundTransition │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ useNeighborGraph useTransitionPlayback ──→ useReversePlayback│ │
│ │ │ │ │
│ │ ▼ │ │
│ │ usePageDataLoader ──→ usePreloadProgress │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Constructor (7 hooks) │ │
│ │ useConstructorElements ──→ useConstructorPageActions │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ useCanvasElementDrag ──→ useCanvasElapsedTime │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ useTransitionPreview ──→ useIconPreload ──→ useMediaDurationProbe │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ ┌────────────────────────┐ ┌────────────────────────┐ ┌────────────────┐ │
│ │ Offline/PWA (4) │ │ Forms/Tables (4) │ │ Utility (8) │ │
│ │ • useOfflineMode │ │ • useFormSync │ │ • useCSV- │ │
│ │ • usePWAPreload │ │ • useEntityTable │ │ Handling │ │
│ │ • useStorageQuota │ │ • useFilterItems │ │ • useElement- │ │
│ │ • useNetworkAware │ │ • useEditPageSync │ │ Effects │ │
│ └────────────────────────┘ └────────────────────────┘ │ • useOutside- │ │
│ │ Click │ │
│ │ • useDraggable│ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ STATE LAYER │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ Redux Store (stores/store.ts) │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Entity Slices (13) - via createEntitySlice factory │ │ │
│ │ │ users, roles, permissions, projects, project_memberships, │ │ │
│ │ │ assets, asset_variants, presigned_url_requests, tour_pages, │ │ │
│ │ │ project_audio_tracks, publish_events, pwa_caches, access_logs │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Core Slices (3) │ │ │
│ │ │ • authSlice - JWT token, current user, permissions │ │ │
│ │ │ • mainSlice - UI state (sidebar, dark mode) │ │ │
│ │ │ • styleSlice - Theme configuration │ │ │
│ │ └─────────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ React Context │ │
│ │ • DownloadContext - PWA download progress tracking │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ LIBRARY LAYER (20 files) │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ lib/offline/ - PWA Storage │ │
│ │ • StorageManager.ts - Cache API + IndexedDB dual storage │ │
│ │ • DownloadManager.ts - Asset download orchestration │ │
│ │ • DownloadEventBus.ts - Progress event system (Observer pattern) │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ lib/offlineDb/ - IndexedDB Persistence │ │
│ │ • OfflineDbManager.ts - Dexie.js wrapper for large assets │ │
│ │ • schema.ts - Database schema definition │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ lib/ - Utilities │ │
│ │ • assetUrl.ts - Presigned URL management, expiry handling │ │
│ │ • elementDefaults.ts - Default element configurations (17KB) │ │
│ │ • elementStyles.ts - Element CSS generation │ │
│ │ • elementEffects.ts - Element animation effects │ │
│ │ • constructorHelpers.ts - Canvas manipulation utilities │ │
│ │ • navigationHelpers.ts - Link resolution, transition detection │ │
│ │ • extractPageLinks.ts - Parse navigation links from ui_schema │ │
│ │ • imagePreDecode.ts - Image pre-decoding for smooth transitions │ │
│ │ • mediaDuration.ts - Audio/video duration extraction │ │
│ │ • mediaHelpers.ts - Media URL handling │ │
│ │ • parseJson.ts - Safe JSON parsing with fallbacks │ │
│ │ • logger.ts - Structured logging (dev/prod modes) │ │
│ │ • slugHelpers.ts - URL slug generation │ │
│ │ • tourFlowHelpers.ts - Page ordering utilities │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ SUPPORT LAYER │
│ ┌────────────────────┐ ┌────────────────────┐ ┌────────────────────────┐ │
│ │ factories/ (3) │ │ schemas/ (6) │ │ types/ (12) │ │
│ │ • createListPage │ │ • userSchema │ │ • constructor.ts │ │
│ │ • createFormPage │ │ • assetSchema │ │ • entities.ts │ │
│ │ • index │ │ • projectSchema │ │ • runtime.ts │ │
│ └────────────────────┘ │ • tourPageSchema │ │ • preload.ts │ │
│ ┌────────────────────┐ │ • roleSchema │ │ • presentation.ts │ │
│ │ helpers/ (6) │ └────────────────────┘ │ • offline.ts │ │
│ │ • dataFormatter │ ┌────────────────────┐ │ • permissions.ts │ │
│ │ • textFormatters │ │ config/ (2) │ │ • forms.ts │ │
│ │ • userPermissions │ │ • preload.config │ │ • filters.ts │ │
│ │ • notifyState │ │ • offline.config │ │ • api.ts │ │
│ │ • humanize │ └────────────────────┘ │ • redux.ts │ │
│ │ • fileSaver │ └────────────────────────┘ │
│ └────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
Directory Structure
frontend/src/
├── pages/ # 99 files - Next.js pages
│ ├── _app.tsx # App entry point
│ ├── api/ # API routes (logError.ts, hello.js)
│ ├── p/[projectSlug]/ # Public presentation routes
│ │ ├── index.tsx # Production presentation
│ │ └── stage.tsx # Stage preview
│ ├── constructor.tsx # Visual tour builder
│ ├── dashboard.tsx # Admin home
│ ├── [entity]/ # 13 entity directories
│ │ ├── [entityId].tsx # Dynamic route
│ │ ├── [entity]-list.tsx # List page
│ │ ├── [entity]-new.tsx # Create page
│ │ ├── [entity]-edit.tsx # Edit page
│ │ ├── [entity]-view.tsx # View page
│ │ └── [entity]-table.tsx# Embedded table
│ └── ... # Auth, profile, search, etc.
│
├── components/ # 193 files - React components
│ ├── [Entity]/ # 13 entity component directories
│ │ ├── Table[Entity].tsx # DataGrid wrapper
│ │ ├── configure[Entity]Cols.tsx # Column config
│ │ ├── Card[Entity].tsx # Card view
│ │ └── List[Entity].tsx # List view
│ ├── Constructor/ # 15 files - Tour builder components
│ ├── ElementSettings/ # 23 files - Element configuration
│ ├── UiElements/ # 16 files - Unified element rendering (shared by Constructor + Runtime)
│ ├── Offline/ # 5 files - PWA components
│ ├── Generic/ # 3 files - Reusable table/form
│ ├── DataGrid/ # 1 file - Column config factory
│ ├── Factory/ # 1 file - Table component factory
│ ├── Uploaders/ # 1 file - Upload service
│ └── *.tsx # ~50 standalone components
│
├── hooks/ # 32 files - Custom React hooks
│ ├── index.ts # Re-exports
│ ├── usePreloadOrchestrator.ts # Asset preloading (26KB)
│ ├── useTransitionPlayback.ts # Video transitions (23KB)
│ ├── usePageSwitch.ts # Page navigation (14KB)
│ ├── useConstructorElements.ts # Element CRUD (12KB)
│ └── ... # 26 more hooks
│
├── stores/ # Redux state
│ ├── store.ts # Store configuration
│ ├── hooks.ts # useAppSelector, useAppDispatch
│ ├── createEntitySlice.ts # Entity slice factory (10KB)
│ ├── authSlice.ts # Authentication state
│ ├── mainSlice.ts # UI state
│ ├── styleSlice.ts # Theme state
│ └── [entity]/ # 13 entity slice directories
│ └── [entity]Slice.ts # Generated via factory
│
├── lib/ # 20 files - Utility libraries
│ ├── offline/ # PWA storage layer
│ │ ├── StorageManager.ts # Cache API + IndexedDB
│ │ ├── DownloadManager.ts# Download orchestration
│ │ └── DownloadEventBus.ts # Progress events
│ ├── offlineDb/ # IndexedDB layer
│ │ ├── OfflineDbManager.ts # Dexie wrapper
│ │ └── schema.ts # DB schema
│ ├── assetUrl.ts # Presigned URL management
│ ├── elementDefaults.ts # Default element configs
│ ├── elementStyles.ts # CSS generation
│ ├── elementEffects.ts # Animation effects
│ ├── constructorHelpers.ts # Canvas utilities
│ ├── navigationHelpers.ts # Link resolution
│ ├── extractPageLinks.ts # Parse links from schema
│ ├── imagePreDecode.ts # Image pre-decoding
│ ├── mediaDuration.ts # Media duration extraction
│ ├── mediaHelpers.ts # Media URL handling
│ ├── parseJson.ts # Safe JSON parsing
│ ├── logger.ts # Structured logging
│ ├── slugHelpers.ts # Slug generation
│ └── tourFlowHelpers.ts # Page ordering
│
├── types/ # 12 files - TypeScript definitions
│ ├── constructor.ts # Canvas/element types
│ ├── entities.ts # Entity interfaces
│ ├── runtime.ts # Presentation runtime
│ ├── preload.ts # Preloading state
│ ├── presentation.ts # Navigation/transitions
│ ├── offline.ts # PWA/cache types
│ ├── permissions.ts # RBAC types
│ ├── forms.ts # Form field types
│ ├── filters.ts # Data filtering
│ ├── api.ts # API response types
│ ├── redux.ts # Store types
│ └── index.ts # Re-exports
│
├── factories/ # 3 files - Page generators
│ ├── createListPage.tsx # List page factory
│ ├── createFormPage.tsx # Form page factory
│ └── index.ts # Re-exports
│
├── schemas/ # 6 files - Validation schemas
│ ├── userSchema.ts # User validation
│ ├── assetSchema.ts # Asset validation
│ ├── projectSchema.ts # Project validation
│ ├── tourPageSchema.ts # Tour page validation
│ ├── roleSchema.ts # Role validation
│ └── index.ts # Re-exports
│
├── helpers/ # 6 files - Utility functions
│ ├── dataFormatter.js # Data formatting
│ ├── textFormatters.ts # Text formatting
│ ├── userPermissions.ts # Permission checking
│ ├── notifyStateHandler.ts # Toast notifications
│ ├── humanize.ts # Human-readable strings
│ └── fileSaver.ts # File download
│
├── layouts/ # 2 files - Page layouts
│ ├── Authenticated.tsx # Auth layout with nav
│ └── Guest.tsx # Public layout
│
├── config/ # 2 files - Configuration
│ ├── preload.config.ts # Preload settings
│ └── offline.config.ts # PWA settings
│
├── context/ # 1 file - React Context
│ └── DownloadContext.tsx # PWA download progress
│
├── interfaces/ # 1 file - Shared interfaces
│ └── index.ts # Common UI interfaces
│
└── css/ # Stylesheets
└── _theme.css # Tailwind theme customization
Module Details
1. Pages Module (99 files)
Entity CRUD Pages (78 files)
Each of the 13 entities has 6 standardized pages:
| Page Pattern |
Purpose |
Factory/Hook |
[entity]-list.tsx |
List with filters, pagination, CSV export |
createListPage factory |
[entity]-new.tsx |
Create form with validation |
Formik + entity schema |
[entity]-edit.tsx |
Edit form with data sync |
useEditPageSync hook |
[entity]-view.tsx |
Read-only display |
Direct entity fetch |
[entity]-table.tsx |
Embedded table view |
createTableComponent |
[entityId].tsx |
Dynamic route redirect |
Redirects to -view |
Entities: users, roles, permissions, projects, project_memberships, assets, asset_variants, presigned_url_requests, tour_pages, project_audio_tracks, publish_events, pwa_caches, access_logs
Special Pages (21 files)
| Page |
Size |
Description |
index.tsx |
1KB |
Root redirect to login or projects list |
constructor.tsx |
47KB |
Visual tour builder with canvas, drag-drop, element editing |
dashboard.tsx |
6KB |
Admin home with entity counts, quick actions |
p/[projectSlug]/index.tsx |
2KB |
Public production presentation (wraps RuntimePresentation) |
p/[projectSlug]/stage.tsx |
2KB |
Stage preview presentation |
_app.tsx |
11KB |
App entry, providers, interceptors |
login.tsx |
8KB |
Authentication with OAuth options |
forgot.tsx |
3KB |
Password reset request |
profile.tsx |
6KB |
User profile management |
element-type-defaults.tsx |
4KB |
Global element defaults editor |
project-element-defaults.tsx |
6KB |
Project-level element overrides |
search.tsx |
3KB |
Global search results |
2. Components Module (193 files)
Entity Components (78 files, 13 directories)
Each entity directory contains:
| File Pattern |
Purpose |
Table[Entity].tsx |
MUI DataGrid wrapper with actions |
configure[Entity]Cols.tsx |
Column definitions using configBuilderFactory |
Card[Entity].tsx |
Card view for single entity |
List[Entity].tsx |
List view variant |
useAssetUploader.ts |
(Assets only) Upload hook |
ProjectSelector.tsx |
(Assets only) Project filter |
Constructor Components (15 files)
| Component |
Size |
Description |
ElementEditorPanel.tsx |
22KB |
Right panel for element editing |
ConstructorToolbar.tsx |
~19KB |
Floating main toolbar with mode, Page actions, Elements actions, save/stage/exit controls |
ConstructorMenu.tsx |
5KB |
Legacy/collapsible constructor menu component |
ConstructorControlsPanel.tsx |
2KB |
Legacy/secondary constructor controls |
CanvasElement.tsx |
3KB |
Draggable element on canvas |
CanvasBackground.tsx |
3KB |
Background image/video layer |
CreateTransitionForm.tsx |
3KB |
Transition configuration modal |
TransitionPreviewOverlay.tsx |
1KB |
Transition video preview |
PageSelector.tsx |
1KB |
Page dropdown selector |
InteractionModeToggle.tsx |
2KB |
Edit/interact mode toggle |
AssetSelectCompact.tsx |
2KB |
Compact asset picker |
BackgroundSettingsEditor.tsx |
2KB |
Background configuration |
ElementEditorHeader.tsx |
1KB |
Editor panel header |
MenuActionButton.tsx |
1KB |
Menu button component |
types.ts |
7KB |
Constructor type definitions |
index.ts |
0.3KB |
Re-exports |
ElementSettings Components (23 files)
Settings panels for different element aspects:
| Component |
Description |
StyleSettingsSection.tsx |
Position, size, colors, borders |
StyleSettingsSectionCompact.tsx |
Compact version for constructor |
NavigationSettingsSection.tsx |
Link targets, transitions |
NavigationSettingsSectionCompact.tsx |
Compact version |
EffectsSettingsSection.tsx |
Animations, hover effects |
EffectsSettingsSectionCompact.tsx |
Compact version |
CarouselSettingsSection.tsx |
Gallery/carousel configuration |
GallerySettingsSection.tsx |
Image gallery settings |
MediaSettingsSection.tsx |
Video/audio player settings |
MediaSettingsSectionCompact.tsx |
Compact version |
TooltipSettingsSection.tsx |
Tooltip configuration |
TooltipSettingsSectionCompact.tsx |
Compact version |
DescriptionSettingsSection.tsx |
Text content settings |
DescriptionSettingsSectionCompact.tsx |
Compact version |
CommonSettingsSection.tsx |
Shared element settings |
CommonSettingsSectionCompact.tsx |
Compact version |
GallerySettingsSection.tsx |
Gallery settings |
GallerySettingsSectionCompact.tsx |
Compact version |
GalleryCarouselSettingsSectionCompact.tsx |
Gallery carousel settings |
ElementSettingsTabs.tsx |
Tab navigation |
useElementSettingsForm.ts |
Form state management hook |
types.ts |
Type definitions |
index.ts |
Re-exports |
UiElements Components (16 files)
Unified element rendering shared by both Constructor (CanvasElement) and Runtime (RuntimeElement):
| Component |
Description |
UiElementRenderer.tsx |
Main entry point - delegates to per-type components |
shared/useElementWrapperStyle.ts |
Shared hook for consistent wrapper styling |
elements/NavigationElement.tsx |
Navigation button (next/prev) |
elements/GalleryElement.tsx |
Image gallery grid |
elements/TooltipElement.tsx |
Tooltip with icon |
elements/DescriptionElement.tsx |
Title + text block |
elements/CarouselElement.tsx |
Image carousel with navigation |
elements/LogoElement.tsx |
Logo display |
elements/SpotElement.tsx |
Hotspot/clickable area |
elements/VideoPlayerElement.tsx |
Embedded video player |
elements/AudioPlayerElement.tsx |
Embedded audio player |
elements/PopupElement.tsx |
Popup/modal dialog |
GalleryCarouselOverlay.tsx |
Gallery/carousel fullscreen overlay |
ElementPreview.tsx |
Preview element in constructor |
defaults.ts |
Default element configurations |
types.ts |
Element type definitions |
Offline Components (5 files)
| Component |
Description |
DownloadProgressPanel.tsx |
Shows download progress for PWA |
OfflineToggle.tsx |
Enable/disable offline mode |
StorageUsageDisplay.tsx |
Shows storage quota usage |
OfflineStatusIndicator.tsx |
Online/offline status badge |
index.ts |
Re-exports |
Factory Components (2 files)
| Component |
Description |
createTableComponent.tsx |
Factory that generates table wrapper components |
configBuilderFactory.tsx |
Factory that generates DataGrid column configurations |
Generic Components (3 files)
| Component |
Size |
Description |
GenericTable.tsx |
14KB |
Reusable DataGrid with filters, pagination |
GenericFormField.tsx |
6KB |
Reusable form field component |
index.ts |
0.2KB |
Re-exports |
Standalone Components (~50 files)
| Category |
Components |
| Layout |
NavBar, NavBarItem, FooterBar, AsideMenu, AsideMenuItem, AsideMenuLayer, AsideMenuList |
| Cards |
CardBox, CardBoxModal, CardBoxComponentBody, CardBoxComponentTitle, CardBoxComponentFooter, CardBoxComponentEmpty |
| Forms |
FormField, FormFieldCompact, FormFilePicker, FormImagePicker, FormCheckRadio, FormCheckRadioGroup |
| Buttons |
BaseButton, BaseButtons, BaseIcon, BaseDivider |
| Data |
DataGridMultiSelect, SelectField, SelectFieldMany, SwitchField, RichTextField, ImageField |
| Sections |
SectionMain, SectionFullScreen, SectionTitle, SectionTitleLineWithButton |
| User |
UserAvatar, UserAvatarCurrentUser, UserCard, IconRounded |
| Utils |
Pagination, Search, SearchResults, LoadingSpinner, NotificationBar, OverlayLayer, ClickOutside |
| Auth |
PasswordSetOrReset |
| Features |
TourFlowManager, RuntimePresentation, RuntimeElement, DevModeBadge, IntroGuide, LanguageSwitcher, ErrorBoundary, DragDropFilePicker, ListActionsPopover |
3. Hooks Module (32 files)
Runtime/Preloading Hooks (8 hooks)
| Hook |
Size |
Description |
Used By |
usePreloadOrchestrator |
26KB |
Coordinates asset preloading with priority queue, blob URL caching |
RuntimePresentation |
useTransitionPlayback |
23KB |
Manages video transition playback (forward/reverse) |
RuntimePresentation |
usePageSwitch |
14KB |
Handles page navigation with preloaded assets |
RuntimePresentation |
useReversePlayback |
11KB |
Reverse video playback for back navigation |
useTransitionPlayback |
useNeighborGraph |
8KB |
Builds navigation graph for preload prioritization |
usePreloadOrchestrator |
usePageDataLoader |
7KB |
Loads project and pages data with caching |
RuntimePresentation |
usePreloadProgress |
6KB |
Tracks preload progress percentages |
RuntimePresentation |
useBackgroundTransition |
5KB |
Fade-out animation during page transitions |
RuntimePresentation |
Constructor Hooks (7 hooks)
| Hook |
Size |
Description |
Used By |
useConstructorElements |
12KB |
Element CRUD operations on canvas |
constructor.tsx |
useConstructorPageActions |
10KB |
Page management (create, delete, reorder) |
constructor.tsx |
useMediaDurationProbe |
6KB |
Extracts duration from video/audio files |
constructor.tsx |
useTransitionPreview |
6KB |
Previews transition videos |
constructor.tsx |
useDraggable |
6KB |
Generic drag functionality |
CanvasElement |
useIconPreload |
5KB |
Preloads icon assets |
constructor.tsx |
useCanvasElementDrag |
4KB |
Canvas-specific element dragging |
CanvasElement |
useCanvasElapsedTime |
3KB |
Animation timing for canvas |
constructor.tsx |
Offline/PWA Hooks (4 hooks)
| Hook |
Size |
Description |
Used By |
useOfflineMode |
11KB |
Detects and manages offline state |
_app.tsx, Offline components |
usePWAPreload |
5KB |
Preloads assets for PWA mode |
RuntimePresentation |
useNetworkAware |
5KB |
Network quality detection |
usePreloadOrchestrator |
useStorageQuota |
3KB |
Monitors storage usage |
StorageUsageDisplay |
Form/Table Hooks (4 hooks)
| Hook |
Size |
Description |
Used By |
useEntityTable |
8KB |
Table state management (pagination, sorting, filters) |
All entity tables |
useEditPageSync |
5KB |
Syncs form data with Redux on edit pages |
All -edit pages |
useFilterItems |
5KB |
Filter state management |
createListPage |
useFormSync |
3KB |
Form state synchronization |
Form components |
Utility Hooks (8 hooks)
| Hook |
Size |
Description |
Used By |
useDashboardCounts |
8KB |
Fetches entity counts for dashboard |
dashboard.tsx |
useElementEffects |
3KB |
Applies element animation effects |
RuntimeElement |
useCSVHandling |
4KB |
CSV import/export |
List pages |
useOutsideClick |
3KB |
Detects clicks outside element |
Modals, dropdowns |
usePageNavigation |
5KB |
Page navigation state |
RuntimePresentation |
useProjectAssets |
3KB |
Fetches and manages project assets |
Projects pages |
useDevCompilationStatus |
1KB |
Hot reload status indicator |
DevModeBadge |
4. Stores Module (22 files)
Store Configuration
| File |
Description |
store.ts |
Redux store configuration with all slices |
hooks.ts |
Typed hooks: useAppSelector, useAppDispatch |
createEntitySlice.ts |
Factory for generating entity slices |
introSteps.ts |
Tour guide step definitions |
Core Slices
| Slice |
Size |
State |
authSlice.ts |
4KB |
{ token, currentUser, permissions, loading, error } |
mainSlice.ts |
1KB |
{ isAsideMobileExpanded, isAsideLgActive, darkMode } |
styleSlice.ts |
3KB |
{ asideStyle, navBarStyle, footerStyle, ... } |
usersSlice.ts |
3KB |
Extended user slice with password reset actions |
Entity Slices (13)
Generated via createEntitySlice factory, each provides:
interface EntityState<T> {
rows: T[];
row: T | null;
loading: boolean;
error: string | null;
count: number;
filters: FilterState;
}
// Async thunks
fetch({ query: string }) // GET /api/[entity]?...
findById(id: string) // GET /api/[entity]/:id
create(data: T) // POST /api/[entity]
update({ id, data }) // PUT /api/[entity]/:id
deleteItem(id: string) // DELETE /api/[entity]/:id
5. Library Module (20 files)
Offline Storage (lib/offline/)
| File |
Size |
Description |
StorageManager.ts |
7KB |
Dual storage abstraction (Cache API < 5MB, IndexedDB >= 5MB) |
DownloadManager.ts |
12KB |
Download orchestration with retry, progress |
DownloadEventBus.ts |
5KB |
Event system for download progress (Observer pattern) |
IndexedDB (lib/offlineDb/)
| File |
Size |
Description |
OfflineDbManager.ts |
8KB |
Dexie.js wrapper for large asset storage |
schema.ts |
1KB |
IndexedDB schema: assets { url, blob, size, timestamp } |
Element Utilities
| File |
Size |
Description |
elementDefaults.ts |
18KB |
Default configurations for all 11 element types |
elementStyles.ts |
3KB |
Generates inline CSS from element config |
elementEffects.ts |
6KB |
Animation effect definitions and application |
fonts.ts |
2KB |
Font family definitions and mappings |
Asset Utilities
| File |
Size |
Description |
assetUrl.ts |
10KB |
Presigned URL management, expiry tracking, refresh |
imagePreDecode.ts |
6KB |
Pre-decodes images for smooth transitions |
mediaDuration.ts |
3KB |
Extracts duration from video/audio elements |
mediaHelpers.ts |
4KB |
Media URL normalization, type detection |
Navigation Utilities
| File |
Size |
Description |
navigationHelpers.ts |
4KB |
resolveNavigationTarget(), isTransitionBlocking() |
extractPageLinks.ts |
6KB |
Parses navigation links from ui_schema_json |
Constructor Utilities
| File |
Size |
Description |
constructorHelpers.ts |
8KB |
Canvas manipulation, element positioning |
slugHelpers.ts |
2KB |
URL slug generation for pages |
tourFlowHelpers.ts |
3KB |
Page ordering, drag-drop reordering |
General Utilities
| File |
Size |
Description |
parseJson.ts |
3KB |
Safe JSON parsing with fallbacks |
logger.ts |
4KB |
Structured logging (console in dev, silent in prod) |
6. Types Module (12 files)
| File |
Size |
Description |
constructor.ts |
10KB |
Canvas element, page, selection types |
entities.ts |
7KB |
All 13 entity interfaces |
runtime.ts |
2KB |
Runtime presentation state types |
preload.ts |
1KB |
Preload queue, progress types |
presentation.ts |
2KB |
Navigation, transition types |
offline.ts |
4KB |
PWA cache, download types |
permissions.ts |
4KB |
RBAC permission types |
forms.ts |
2KB |
Form field, validation types |
filters.ts |
2KB |
Data filter types |
api.ts |
1KB |
API response types |
redux.ts |
1KB |
Store, dispatch types |
index.ts |
0.3KB |
Re-exports |
7. Factories Module (3 files)
| Factory |
Size |
Description |
createListPage.tsx |
6KB |
Generates list pages with filters, CSV, pagination |
createFormPage.tsx |
9KB |
Generates form pages with validation |
index.ts |
0.1KB |
Re-exports |
createListPage Usage:
export default createListPage({
entityName: 'assets',
entityNamePlural: 'Assets',
permissionPrefix: 'ASSETS',
columns: configureAssetsCols,
filterFields: ['name', 'type', 'status'],
});
createFormPage Usage:
export default createFormPage({
entityName: 'assets',
schema: assetSchema,
fields: ['name', 'file', 'project'],
});
8. Schemas Module (6 files)
Formik/Yup validation schemas:
| Schema |
Fields Validated |
userSchema.ts |
email, firstName, lastName, password, role |
assetSchema.ts |
name, file, type, project |
projectSchema.ts |
name, slug, description |
tourPageSchema.ts |
name, slug, order, background |
roleSchema.ts |
name, permissions |
index.ts |
Re-exports |
9. Helpers Module (6 files)
| Helper |
Description |
dataFormatter.js |
Format dates, numbers, booleans for display |
textFormatters.ts |
Truncate, capitalize, slugify text |
userPermissions.ts |
hasPermission(), canAccess() utilities |
notifyStateHandler.ts |
Toast notification triggers |
humanize.ts |
Convert snake_case to readable text |
fileSaver.ts |
Trigger file downloads |
10. Layouts Module (2 files)
| Layout |
Description |
Authenticated.tsx |
Full layout with JWT validation, permission checks, NavBar, AsideMenu, FooterBar, IntroGuide |
Guest.tsx |
Minimal layout for public pages (login, password reset, public presentations) |
Per-Page Layout Pattern:
UsersListPage.getLayout = (page: ReactElement) => (
<LayoutAuthenticated permission="READ_USERS">
{page}
</LayoutAuthenticated>
);
11. Config Module (2 files)
| Config |
Settings |
preload.config.ts |
Priority weights, batch sizes, concurrent limits |
offline.config.ts |
Cache names, storage thresholds, expiry times |
Preload Priority Weights:
export const PRELOAD_PRIORITY = {
TRANSITION_VIDEO: 150, // Highest - needed for navigation
IMAGE: 100,
AUDIO: 50,
VIDEO: 30, // Lowest - large files
};
12. Context Module (1 file)
| Context |
Description |
DownloadContext.tsx |
PWA download progress tracking, shared across components |
Usage:
const { progress, isDownloading, startDownload, cancelDownload } = useDownloadContext();
Design Patterns
1. Factory Pattern
Eliminates ~80% boilerplate code:
| Factory |
Generates |
Files Eliminated |
createEntitySlice |
Redux slices with CRUD thunks |
13 × 200 LOC = 2600 LOC |
createListPage |
List pages with filters, pagination |
13 × 150 LOC = 1950 LOC |
createFormPage |
Form pages with validation |
13 × 180 LOC = 2340 LOC |
createTableComponent |
Table wrapper components |
13 × 50 LOC = 650 LOC |
configBuilderFactory |
Column configurations |
13 × 80 LOC = 1040 LOC |
Total: ~8500 LOC eliminated through factories
2. Custom Hook Pattern
Hooks are composed hierarchically:
usePreloadOrchestrator
├── useNeighborGraph (build priority queue)
├── useNetworkAware (adjust batch size)
└── Internal: presigned URL fetching, blob caching
usePageSwitch
├── usePreloadOrchestrator.getReadyBlobUrl()
└── useBackgroundTransition (fade animation)
useTransitionPlayback
├── useReversePlayback (back navigation)
└── Internal: video element management
3. HOC via getLayout
Per-page layout injection without wrapper components:
// Page defines its layout
MyPage.getLayout = (page) => (
<LayoutAuthenticated permission="READ_ASSETS">
{page}
</LayoutAuthenticated>
);
// _app.tsx applies it
const getLayout = Component.getLayout ?? ((page) => page);
return getLayout(<Component {...pageProps} />);
4. Repository Pattern
Entity slices abstract all API calls:
// Component only knows about Redux actions
dispatch(fetch({ query: '?limit=100' }));
dispatch(create(newAsset));
dispatch(update({ id, data: changes }));
dispatch(deleteItem(id));
// Slice handles API communication
const fetch = createAsyncThunk('assets/fetch', async ({ query }) => {
const response = await axios.get(`/api/assets${query}`);
return response.data;
});
5. Observer Pattern
Download progress events:
// DownloadEventBus.ts
class DownloadEventBus {
private listeners: Map<string, Set<Callback>>;
emit(event: 'progress' | 'complete' | 'error', data: any) {
this.listeners.get(event)?.forEach(cb => cb(data));
}
on(event: string, callback: Callback) {
this.listeners.get(event)?.add(callback);
}
}
// Consumer
downloadEventBus.on('progress', ({ url, percent }) => {
setProgress(prev => ({ ...prev, [url]: percent }));
});
6. Strategy Pattern
Storage selection based on file size:
// StorageManager.ts
class StorageManager {
async store(url: string, blob: Blob) {
if (blob.size < 5 * 1024 * 1024) { // < 5MB
return this.cacheApiStore(url, blob); // Fast, limited
} else {
return this.indexedDbStore(url, blob); // Slow, unlimited
}
}
}
Key Flows
1. Runtime Presentation Flow
User navigates to /p/[slug]
│
▼
┌─────────────────────────────────────┐
│ usePageDataLoader │
│ • Fetch project by slug │
│ • Fetch pages for environment │
│ • Determine initial page │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ usePreloadOrchestrator │
│ • Build neighbor graph │
│ • Calculate priorities │
│ • Request presigned URLs │
│ • Download assets to cache │
│ • Pre-decode images │
│ • Store blob URLs │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ RuntimePresentation renders │
│ • Background (image/video) │
│ • RuntimeElement for each element │
│ └── UiElementRenderer │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ User clicks navigation element │
│ • resolveNavigationTarget() │
│ • isTransitionBlocking() │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ usePageSwitch │
│ • Get blob URL from cache (O(1)) │
│ • useBackgroundTransition() │
│ • If transition video: │
│ useTransitionPlayback() │
│ • Switch to new page │
└─────────────────────────────────────┘
2. Constructor Flow
User opens /constructor?projectId=X
│
▼
┌─────────────────────────────────────┐
│ Fetch project and pages │
│ • Redux: projects.findById() │
│ • Redux: tour_pages.fetch() │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ useConstructorElements │
│ • Parse ui_schema_json │
│ • Initialize element state │
│ • Provide CRUD methods │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Canvas renders │
│ • CanvasBackground │
│ • CanvasElement for each element │
│ • useCanvasElementDrag │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ User edits element │
│ • ElementEditorPanel opens │
│ • useElementSettingsForm │
│ • Settings sections render │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Save changes │
│ • Update ui_schema_json │
│ • Redux: tour_pages.update() │
│ • API: PUT /api/tour_pages/:id │
└─────────────────────────────────────┘
3. Offline/PWA Flow
User enables offline mode
│
▼
┌─────────────────────────────────────┐
│ DownloadManager.startDownload() │
│ • Get asset manifest │
│ • Request presigned URLs │
│ • Queue downloads │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ For each asset: │
│ • Fetch with progress tracking │
│ • DownloadEventBus.emit() │
│ • StorageManager.store() │
│ - Cache API (< 5MB) │
│ - IndexedDB (>= 5MB) │
└─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Service worker intercepts │
│ • Check Cache API │
│ • Check IndexedDB │
│ • Return cached response │
└─────────────────────────────────────┘
Entity Architecture
Each entity follows a consistent structure:
[Entity] (e.g., Assets)
│
├── pages/assets/
│ ├── [assetsId].tsx # Dynamic route → view
│ ├── assets-list.tsx # List with filters
│ ├── assets-new.tsx # Create form
│ ├── assets-edit.tsx # Edit form
│ ├── assets-view.tsx # Read-only view
│ └── assets-table.tsx # Embedded table
│
├── components/Assets/
│ ├── TableAssets.tsx # DataGrid wrapper
│ ├── configureAssetsCols.tsx # Column definitions
│ ├── CardAssets.tsx # Card view
│ ├── ListAssets.tsx # List view
│ ├── useAssetUploader.ts # Upload hook (assets only)
│ └── ProjectSelector.tsx # Filter component
│
├── stores/assets/
│ └── assetsSlice.ts # Redux slice via factory
│
└── schemas/
└── assetSchema.ts # Validation schema
13 Entities:
- users, roles, permissions
- projects, project_memberships
- assets, asset_variants, presigned_url_requests
- tour_pages, project_audio_tracks
- publish_events, pwa_caches, access_logs
Configuration
Next.js Config (next.config.mjs)
const nextConfig = {
reactStrictMode: true,
typescript: {
ignoreBuildErrors: false, // Enforce type checking
},
eslint: {
ignoreDuringBuilds: false, // Enforce linting
},
images: {
domains: ['cdn.platform.com', 's3.amazonaws.com'],
},
// PWA via Serwist
// Turbopack enabled for dev
};
Tailwind Config
Theme customization in css/_theme.css:
/* Sidebar styling */
#asideMenu {
@apply bg-gray-900 text-white;
}
/* Theme variants */
.theme-pink .card {
@apply border-pink-500;
}
Performance Optimizations
1. Asset Preloading
- Priority Queue: Transition videos > Images > Audio > Video
- Batch Fetching: Max 50 presigned URLs per request
- Network Awareness: Reduce batch size on slow connections
- Blob URL Caching: O(1) lookup during navigation
2. Image Pre-Decoding
// imagePreDecode.ts
export async function preDecodeImage(blobUrl: string): Promise<void> {
const img = new Image();
img.src = blobUrl;
await img.decode(); // Decode before display
}
3. Lazy Loading
- Route-based code splitting (Next.js automatic)
- Dynamic imports for large components
4. Redux Optimization
- Normalized entity state
- Selective re-renders via
useAppSelector
- Memoized selectors where needed
Related Documentation
File Reference by Size
Largest Files (> 10KB)
| File |
Size |
Description |
pages/constructor.tsx |
47KB |
Visual tour builder |
hooks/usePreloadOrchestrator.ts |
26KB |
Asset preloading |
hooks/useTransitionPlayback.ts |
23KB |
Video transitions |
components/Constructor/ElementEditorPanel.tsx |
22KB |
Element editor |
components/RuntimePresentation.tsx |
19KB |
Tour playback |
lib/elementDefaults.ts |
18KB |
Element defaults |
components/TourFlowManager.tsx |
17KB |
Page management |
hooks/usePageSwitch.ts |
14KB |
Page navigation |
components/Generic/GenericTable.tsx |
14KB |
Reusable table |
hooks/useConstructorElements.ts |
12KB |
Element CRUD |
lib/offline/DownloadManager.ts |
12KB |
Download management |
hooks/useReversePlayback.ts |
11KB |
Reverse video |
hooks/useOfflineMode.ts |
11KB |
Offline detection |
pages/_app.tsx |
11KB |
App entry |
lib/assetUrl.ts |
10KB |
Presigned URLs |
stores/createEntitySlice.ts |
10KB |
Slice factory |
types/constructor.ts |
10KB |
Constructor types |
Summary
The frontend architecture demonstrates several strong patterns:
Strengths:
- Factory patterns eliminate ~8500 LOC of boilerplate
- 32 custom hooks provide clean separation of concerns
- Dual storage strategy (Cache API + IndexedDB) enables robust offline
- Per-page layout pattern with permission checking
- Strong TypeScript typing across 12 type definition files
Architecture Decisions:
- CSR-only approach (appropriate for admin/builder app)
- Pages Router (stable, full-featured)
- Redux Toolkit for predictable state management
- MUI X DataGrid for complex data tables
- Tailwind CSS for utility-first styling
Total Codebase:
- 408 TypeScript/TSX files
- ~80,000 LOC estimated
- 14 distinct modules
- 13 entity CRUD implementations