# 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.9
- **State Management**: Redux Toolkit
- **Styling**: Tailwind CSS 3.4 + MUI 6
- **Forms**: Formik
- **Data Grid**: MUI X Data Grid v7
- **PWA**: Serwist (Service Worker)
- **i18n**: i18next
- **Offline Storage**: Dexie (IndexedDB)

## Prerequisites

- Node.js 24.x
- npm

## Quick Start

```bash
# Install dependencies
npm install

# Start development server (port 3001)
npm run dev

# Production build
npm run build
npm run start
```

The app runs on **port 3001** 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:3000)
NEXT_PUBLIC_BACK_API=http://localhost:3000

# Frontend port (optional, default 3001)
FRONT_PORT=3001
```

## 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 <span>{t('common.save')}</span>;
```

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;
  // ...
}
```
