# Tour Builder Platform - Frontend Next.js 15 application with React 19, TypeScript, Redux Toolkit, and Tailwind CSS for the Tour Builder Platform. ## Tech Stack - **Framework**: Next.js 15 with Turbopack - **UI Library**: React 19 - **Language**: TypeScript 5.4 - **State Management**: Redux Toolkit - **Styling**: Tailwind CSS 3.4 + MUI 6 - **Forms**: Formik - **Data Grid**: MUI X Data Grid v7 - **Charts**: ApexCharts, Chart.js - **Drag & Drop**: react-dnd - **PWA**: Serwist (Service Worker) - **i18n**: i18next - **Offline Storage**: Dexie (IndexedDB) ## Prerequisites - Node.js 18+ - npm or yarn ## Quick Start ```bash # Install dependencies npm install # Start development server (port 3000) npm run dev # Production build npm run build npm run start ``` The app runs on **port 3000** by default (configurable via `FRONT_PORT` env var). ## Available Commands ```bash npm run dev # Start dev server with Turbopack npm run build # Production build npm run start # Start production server npm run lint # ESLint check (.ts, .tsx files) npm run format # Format code with Prettier ``` ## Project Structure ``` frontend/src/ ├── pages/ # Next.js pages (file-based routing) │ ├── _app.tsx # App wrapper (Redux, i18n, toast providers) │ ├── index.tsx # Landing/redirect page │ ├── login.tsx # Login page │ ├── register.tsx # Registration page │ ├── dashboard.tsx # Main dashboard │ ├── constructor.tsx # Tour builder/editor (drag-drop, elements) │ ├── search.tsx # Global search results │ ├── p/[projectSlug]/ # Public tour pages (PWA-enabled) │ │ ├── index.tsx # Production presentation │ │ └── stage.tsx # Stage preview │ ├── projects/ # Project CRUD pages │ ├── tour_pages/ # Tour page management │ ├── assets/ # Asset library │ └── ... # Other entity pages │ ├── components/ # React components (PascalCase) │ ├── Assets/ # Asset management components │ ├── Constructor/ # Tour builder components (CanvasElement, etc.) │ ├── ElementSettings/ # Shared element settings form components │ ├── UiElements/ # Unified element rendering (WYSIWYG consistency) │ │ ├── UiElementRenderer.tsx # Main entry point (used by both constructor & runtime) │ │ ├── shared/ # Shared hooks (useElementWrapperStyle) │ │ └── elements/ # Per-type components (NavigationElement, GalleryElement, etc.) │ ├── RuntimeElement.tsx # Runtime element wrapper (position, effects) │ ├── Generic/ # Generic CRUD components │ ├── CardBox.tsx # Card container │ ├── NavBar.tsx # Top navigation │ ├── AsideMenu*.tsx # Sidebar menu components │ ├── FormField.tsx # Form input wrapper │ ├── DataGridMultiSelect.tsx # Enhanced data grid │ └── ... # UI components │ ├── stores/ # Redux Toolkit │ ├── store.ts # Store configuration │ ├── hooks.ts # useAppSelector, useAppDispatch │ ├── authSlice.ts # Authentication state │ ├── mainSlice.ts # UI state (sidebar, modals) │ ├── styleSlice.ts # Theme/styling state │ ├── createEntitySlice.ts # Factory for entity slices │ └── {entity}/ # Entity-specific slices │ └── {entity}Slice.ts │ ├── hooks/ # Custom React hooks (32 hooks) │ ├── usePreloadOrchestrator.ts # Asset preloading with S3 presigned URLs │ ├── usePageSwitch.ts # Page navigation with preloaded blob URLs │ ├── useNeighborGraph.ts # Page navigation graph (maxDepth: 1) │ ├── useTransitionPlayback.ts # Video transitions │ ├── usePageNavigation.ts # Runtime page history │ ├── useReversePlayback.ts # Reverse video playback │ ├── useConstructorElements.ts # Manage canvas elements │ ├── useConstructorPageActions.ts # Save, publish, create pages │ ├── useOfflineMode.ts # PWA offline detection │ ├── useFormSync.ts # Form state synchronization │ ├── useEntityTable.ts # Data grid with CRUD │ └── ... # Other hooks (see Custom Hooks section) │ ├── types/ # TypeScript definitions │ ├── constructor.ts # Tour builder types │ ├── runtime.ts # Runtime playback types │ ├── preload.ts # Asset preloading types │ ├── entities.ts # Entity interfaces │ ├── permissions.ts # RBAC types │ └── ... # Other types │ ├── lib/ # Utility libraries │ ├── assetUrl.ts # CDN URL resolution │ ├── constructorHelpers.ts # Constructor page helpers │ ├── elementDefaults.ts # Element default values │ ├── elementEffects.ts # Element effect utilities │ ├── elementStyles.ts # Element styling utilities │ ├── extractPageLinks.ts # Extract navigation links from pages │ ├── fonts.ts # Font configuration │ ├── imagePreDecode.ts # Image pre-decoding │ ├── logger.ts # Client-side logging │ ├── mediaDuration.ts # Video/audio duration │ ├── mediaHelpers.ts # Media utilities │ ├── navigationHelpers.ts # Navigation utilities │ ├── parseJson.ts # Safe JSON parsing │ ├── slugHelpers.ts # Slug generation utilities │ ├── tourFlowHelpers.ts # Tour flow utilities │ ├── offline/ # Offline utilities │ │ ├── DownloadEventBus.ts # Download event handling │ │ ├── DownloadManager.ts # Download queue management │ │ └── StorageManager.ts # Cache API storage for assets │ └── offlineDb/ # IndexedDB (Dexie) │ ├── schema.ts # Dexie database schema │ └── OfflineDbManager.ts # Offline data management │ ├── layouts/ # Page layouts │ ├── Authenticated.tsx # Logged-in users layout │ └── Guest.tsx # Public pages layout │ ├── css/ # Stylesheets │ └── _theme.css # Tailwind theme overrides │ ├── config/ # Configuration files │ ├── offline.config.ts # Offline/PWA settings │ └── preload.config.ts # Preload priorities and settings ├── schemas/ # Validation schemas (Zod) ├── factories/ # Component/hook factories ├── context/ # React contexts ├── sw.ts # Service Worker (Serwist) ├── menuAside.ts # Sidebar menu definition ├── menuNavBar.ts # Navbar menu definition ├── colors.ts # Color palette └── styles.ts # Style constants ``` ## Key Pages ### Constructor (`/constructor?projectId=`) Visual tour builder with: - Drag-and-drop element positioning - Canvas-based page editing - Element property panels - Page thumbnail navigation - Live preview - **Always shows `dev` environment content** - "Save to Stage" button copies dev → stage ### Runtime Presentation (`RuntimePresentation.tsx`) The tour playback is handled by the `RuntimePresentation` component (used in public tour pages), featuring: - Full-screen presentation mode - Video transitions between pages - Forward/reverse playback - Background audio - Keyboard/touch navigation - Asset preloading with S3 presigned URLs ### Public Tours (`/p/[projectSlug]`) PWA-enabled public tour access: - **`/p/[projectSlug]`** - Shows `production` environment (published content) - **`/p/[projectSlug]/stage`** - Shows `stage` environment (preview) - Offline support via Service Worker - Asset caching in Cache API and IndexedDB - Direct S3 downloads via presigned URLs ## State Management ### Redux Slices | Slice | Purpose | |-------|---------| | `authSlice` | User authentication, JWT tokens | | `mainSlice` | UI state (sidebar, dark mode) | | `styleSlice` | Theme configuration | | `{entity}Slice` | Entity CRUD state | ### Usage Pattern ```typescript import { useAppSelector, useAppDispatch } from '../stores/hooks'; import { fetch, create, update, deleteItem } from '../stores/projects/projectsSlice'; const dispatch = useAppDispatch(); const { rows, loading } = useAppSelector((state) => state.projects); // Fetch with pagination dispatch(fetch({ query: '?limit=100&offset=0' })); // Create dispatch(create({ data: newProject })); ``` ## Custom Hooks ### Runtime & Preloading Hooks | Hook | Purpose | |------|---------| | `usePreloadOrchestrator` | Asset preloading with S3 presigned URLs and ready blob URLs | | `usePageSwitch` | Page navigation using preloaded blob URLs (O(1) instant lookup) | | `useNeighborGraph` | Build page navigation graph (maxDepth: 1) | | `useTransitionPlayback` | Video transition playback coordination | | `usePageNavigation` | Runtime page history management | | `useReversePlayback` | Reverse video playback | | `usePageDataLoader` | Load pages with environment filtering | | `usePreloadProgress` | Track preload progress | | `useBackgroundTransition` | Background transition effects | ### PWA & Offline Hooks | Hook | Purpose | |------|---------| | `useOfflineMode` | Detect offline/online status | | `usePWAPreload` | Preload assets for offline | | `useStorageQuota` | Monitor IndexedDB usage | | `useNetworkAware` | Network-aware operations | ### Constructor Hooks | Hook | Purpose | |------|---------| | `useConstructorElements` | Manage canvas elements | | `useConstructorPageActions` | Save, publish, create pages | | `useCanvasElementDrag` | Element drag-and-drop | | `useCanvasElapsedTime` | Track canvas elapsed time | | `useTransitionPreview` | Preview transitions | | `useElementEffects` | Element visual effects | | `useIconPreload` | Preload element icons | | `useMediaDurationProbe` | Probe media duration | ### Form & Table Hooks | Hook | Purpose | |------|---------| | `useFormSync` | Sync form state with Redux | | `useEntityTable` | Data grid with sorting, filtering, pagination | | `useEditPageSync` | Sync edit page state | | `useFilterItems` | Filter items in lists | | `useCSVHandling` | CSV import/export | ### Utility Hooks | Hook | Purpose | |------|---------| | `useDraggable` | Generic draggable behavior | | `useOutsideClick` | Detect clicks outside element | | `useDashboardCounts` | Dashboard statistics | | `useProjectAssets` | Project asset management | | `useDevCompilationStatus` | Dev server compilation status | **Component-specific hooks:** - `useElementSettingsForm` (`components/ElementSettings/`) - Element settings form state ## Element Types The tour builder supports these UI elements: | Type | Description | |------|-------------| | `navigation_next` | Forward navigation button | | `navigation_prev` | Back navigation button | | `spot` | Hotspot/clickable area | | `description` | Text description | | `tooltip` | Hover tooltip | | `gallery` | Image gallery | | `carousel` | Image carousel | | `logo` | Logo element | | `video_player` | Video player | | `audio_player` | Audio player | | `popup` | Popup/modal | ### Element Defaults Hierarchy Element types follow a three-tier defaults system: 1. **Global** (`element_type_defaults`) - Platform-wide default settings (11 predefined types) 2. **Project** (`project_element_defaults`) - Project-specific overrides (auto-snapshotted on project creation) 3. **Instance** (`tour_pages.ui_schema_json`) - Page-specific element instances with custom settings The constructor fetches project defaults via `/api/project-element-defaults?projectId=xxx` and merges them when creating new elements. Existing element values in `ui_schema_json` take precedence over defaults. **Admin Pages:** - `/element-type-defaults` - Edit global defaults (platform-wide) - `/project-element-defaults/[id]` - Edit project defaults (with Reset to Global, Diff indicator) **Key files:** - `components/ElementSettings/` - Shared form components (tabs, CSS styling, type-specific sections) - `components/ElementSettings/useElementSettingsForm.ts` - Form state hook (~60 fields) - `types/constructor.ts` - `normalizeElementDefault()`, `buildElementDefaultsMap()` utilities - `pages/constructor.tsx` - Loads project defaults, creates elements with merged settings ## Content Environments Tour pages have a three-tier environment model: | Environment | Route | Description | |-------------|-------|-------------| | `dev` | `/constructor?projectId=` | Editing/draft content | | `stage` | `/p/[projectSlug]/stage` | Pre-production review | | `production` | `/p/[projectSlug]` | Published public content | **Publishing flow:** `dev` → `stage` → `production` Pages are filtered by environment before display: ```typescript const filteredPages = pages.filter(p => p.environment === environment); const { pageLinks, preloadElements } = extractPageLinksAndElements(filteredPages); ``` The `X-Runtime-Environment` header tells the backend which environment to query. ## Environment Variables ```env # Backend API URL (defaults to localhost:8080) NEXT_PUBLIC_BACK_API=http://localhost:8080 # Frontend port (optional, default 3000) FRONT_PORT=3000 ``` ## API Integration API calls are made via Axios to the backend: ```typescript // src/pages/api/ contains API route handlers // Most data fetching goes through Redux thunks import axios from 'axios'; const response = await axios.get('/api/projects', { headers: { Authorization: `Bearer ${token}` } }); ``` ## Styling ### Tailwind CSS Primary styling via Tailwind with custom theme in `css/_theme.css`: ```css /* Theme overrides use @apply */ .btn-primary { @apply bg-blue-500 hover:bg-blue-600 text-white; } ``` ### MUI Components MUI is used for complex components (Data Grid, dialogs): ```typescript import { DataGrid } from '@mui/x-data-grid'; ``` ### Sidebar Styling Target `#asideMenu` for sidebar customization: ```css #asideMenu { /* Sidebar styles */ } ``` ## PWA & Offline Support ### Service Worker Generated by Serwist from `src/sw.ts`: - Precaches static assets - Runtime caching for API responses - Background sync for offline changes ### Asset Preloading (S3 Direct Download) Assets are preloaded directly from S3 for better performance: ```typescript // 1. Request presigned URLs (max 50 per batch, 1-hour expiry) POST /api/file/presign { urls: ["assets/img.jpg", ...] } // 2. Download directly from S3 → Store in Cache API (dual key) // 3. Create blob URL → Decode image → Store in readyBlobUrlsRef (dual key) // 4. Instant lookup during navigation (O(1)) - use storage key for reliability const blobUrl = preloadOrchestrator.getReadyBlobUrl(storageKey); ``` **Storage Key Mapping:** Assets are cached under both download URL and canonical storage key (e.g., `assets/project/video.mp4`). Lookups prioritize storage key because presigned URL signatures change on each resolution. This ensures cache hits even after URL regeneration or page refresh. **Preload Priority:** | Type | Priority | Notes | |------|----------|-------| | Transition video | +150 | Needed immediately on click | | Image | +100 | Backgrounds | | Audio | +50 | Background audio | | Video | +30 | Can stream progressively | ### Storage Layers | Layer | Size Limit | Purpose | |-------|------------|---------| | Cache API | < 5MB | Fast asset storage | | IndexedDB (Dexie) | ≥ 5MB | Large assets, offline data | ```typescript import { offlineDb } from '../lib/offlineDb'; // Store assets for offline access await offlineDb.assets.put({ id, data }); ``` ## Internationalization Uses i18next with browser language detection: ```typescript import { useTranslation } from 'react-i18next'; const { t } = useTranslation(); return {t('common.save')}; ``` Translation files in `public/locales/{lang}/`. ## MUI X Data Grid v7 Use the new `valueGetter` signature: ```typescript // Value transformation valueGetter: (value) => value?.id ?? value // Row access (for computed values) valueGetter: (_value, row) => new Date(row.created_at) ``` ## Docker The project includes Docker support: ```bash cd docker chmod +x start-backend.sh wait-for-it.sh docker-compose up ``` Access at `http://localhost:3000` ### Docker Files - `Dockerfile` - Production build for cloud deployment - `docker/docker-compose.yml` - Full stack (frontend, backend, db) - `docker/start-backend.sh` - Backend startup script - `docker/wait-for-it.sh` - Service dependency waiter ## Development Tips ### Adding a New Entity Page 1. Create Redux slice in `stores/{entity}/{entity}Slice.ts` 2. Create pages: `{entity}.tsx`, `{entity}/[id].tsx` 3. Add to sidebar menu in `menuAside.ts` 4. Add permissions check in page wrapper ### Creating Custom Hooks Place in `src/hooks/` with `use` prefix: ```typescript // src/hooks/useMyHook.ts export function useMyHook() { // Hook logic } // Export from index // src/hooks/index.ts export * from './useMyHook'; ``` ### Type Safety All entities should have TypeScript interfaces in `src/types/`: ```typescript // src/types/entities.ts export interface Project { id: string; name: string; slug: string; // ... } ```