2026-03-31 08:56:49 +04:00
..
2026-03-18 11:53:34 +04:00
2026-03-31 08:56:49 +04:00
2026-03-31 08:56:49 +04:00
2026-03-17 12:06:17 +00:00
2026-03-24 15:40:35 +04:00
2026-03-17 12:06:17 +00:00
2026-03-17 12:06:17 +00:00
2026-03-17 12:06:17 +00:00
2026-03-24 17:39:15 +04:00
2026-03-17 12:06:17 +00:00
2026-03-17 12:06:17 +00:00
2026-03-31 08:56:49 +04:00
2026-03-17 12:06:17 +00:00

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

# 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

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

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: devstageproduction

Pages are filtered by environment before display:

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

# 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:

// 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:

/* 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):

import { DataGrid } from '@mui/x-data-grid';

Sidebar Styling

Target #asideMenu for sidebar customization:

#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:

// 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
import { offlineDb } from '../lib/offlineDb';

// Store assets for offline access
await offlineDb.assets.put({ id, data });

Internationalization

Uses i18next with browser language detection:

import { useTranslation } from 'react-i18next';

const { t } = useTranslation();
return <span>{t('common.save')}</span>;

Translation files in public/locales/{lang}/.

MUI X Data Grid v7

Use the new valueGetter signature:

// 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:

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:

// 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/:

// src/types/entities.ts
export interface Project {
  id: string;
  name: string;
  slug: string;
  // ...
}