39948-vm/frontend/README.md
2026-03-31 08:56:49 +04:00

528 lines
18 KiB
Markdown

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