docs: update CLAUDE.md to reflect admin panel, PWA, and Docker setup
This commit is contained in:
@@ -11,22 +11,92 @@ npm run preview # Preview production build locally
|
||||
npm run lint # ESLint check
|
||||
```
|
||||
|
||||
No test suite is configured.
|
||||
Tests exist in `src/**/__tests__/` and `src/test/` but are mostly placeholders — no test runner is configured.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Russian-language landing page for a ballistic window film (бронирование стекол) company. Single-page marketing site with a lead capture form that posts to a Telegram bot.
|
||||
Russian-language site for a ballistic window film company (Осколкам.Нет). Two completely separate apps live in the same repo:
|
||||
|
||||
1. **Public landing page** — marketing site at `/`
|
||||
2. **Admin panel** — CRM at `/admin/*` (Supabase auth required)
|
||||
|
||||
There's also a public **order tracking portal** at `/track/:token`.
|
||||
|
||||
## Architecture
|
||||
|
||||
Single-page React 19 app with Vite, no routing. `App.jsx` composes all sections in vertical order: `Navbar → Hero → PhysicsOfSafety → UseCases → TrustBar → Comparison → Estimator → LeadForm → Footer`.
|
||||
### Routing
|
||||
|
||||
**Key patterns:**
|
||||
- All styling is inline `style={{}}` props — there are no CSS modules or Tailwind classes. The only utility classes are defined in `src/index.css`: `.glass-panel`, `.btn-primary`, `.btn-outline`, `.container`, `.text-accent-blue/yellow/orange`.
|
||||
- Animations use `framer-motion` (`motion.*` components with `whileInView`, `whileHover`, `whileTap`).
|
||||
- Icons come from `lucide-react`.
|
||||
- CSS custom properties (set in `:root` in `index.css`) define the design tokens — use these for any new color or spacing values.
|
||||
`App.jsx` uses React Router with lazy-loaded routes. All admin routes are nested under `ProtectedRoute` → `AdminLayout`. The public site has no routing — it's one scrollable page.
|
||||
|
||||
**Lead form integration:** `LeadForm.jsx` reads `VITE_TG_BOT_TOKEN` and `VITE_TG_CHAT_ID` from `.env` and POSTs to the Telegram Bot API. Copy `.env.example` to `.env` to configure locally.
|
||||
### Public Landing Page
|
||||
|
||||
**Estimator:** `Estimator.jsx` calculates cost client-side based on film thickness (200µm = 2000 ₽/m², 300µm = 3000 ₽/m²) and glazing area. The `windows` slider state exists but is not used in the cost formula.
|
||||
`src/components/` — sections composed in `App.jsx` in this order:
|
||||
`Navbar → Hero → PhysicsOfSafety → UseCases → TrustBar → Comparison → Estimator → LeadForm → Footer`
|
||||
|
||||
- All styling is **inline `style={{}}`** — no CSS modules, no Tailwind. Utility classes (`.glass-panel`, `.btn-primary`, `.btn-outline`, `.container`, `.text-accent-blue/yellow/orange`) are defined in `src/index.css`.
|
||||
- Animations use `framer-motion` (`motion.*` with `whileInView`, `whileHover`, `whileTap`).
|
||||
- Icons from `lucide-react`.
|
||||
- CSS custom properties in `:root` in `index.css` are the design tokens — always use `var(--...)` for colors and spacing.
|
||||
- `Estimator.jsx` reads prices from Supabase (`usePricing` hook) rather than hardcoding them. Fallback is 200µm=2000₽/m², 300µm=3000₽/m².
|
||||
- `LeadForm.jsx` POSTs to Telegram Bot API using `VITE_TG_BOT_TOKEN` / `VITE_TG_CHAT_ID` from `.env`.
|
||||
|
||||
### Admin Panel
|
||||
|
||||
`src/admin/` — scoped under `.admin-panel` CSS class (light theme tokens, see `index.css`). All admin styling is also inline `style={{}}` using `var(--bg-*)`, `var(--border-*)`, `var(--text-*)` tokens.
|
||||
|
||||
**Layout:** `AdminLayout.jsx` wraps everything in `ToastProvider`, renders the `Sidebar`, header (with push-bell, search, new-order button), and `<Outlet>`. On mobile, sidebar is a drawer toggled by hamburger.
|
||||
|
||||
**Pages and their hooks:**
|
||||
| Route | Component | Hook |
|
||||
|---|---|---|
|
||||
| `/admin/kanban` | `KanbanBoard` | `useOrders` |
|
||||
| `/admin/analytics` | `AnalyticsPage` | `useAnalyticsPage` |
|
||||
| `/admin/calendar` | `CalendarPage` | `useCalendar` |
|
||||
| `/admin/clients` | `ClientsPage` | `useClients` |
|
||||
| `/admin/archive` | `ArchivePage` | `useArchive` |
|
||||
| `/admin/settings` | `SettingsPage` | `useSettings` |
|
||||
| `/admin/users` | `UserManagementPage` | `useAdminUsers` |
|
||||
|
||||
**`useOrders`** is the core hook — fetches all orders, subscribes to realtime changes via `supabase.channel`, exposes `updateStatus`, `createOrder`, `updateOrder`, `refetch`. Every mutation also writes to `order_events` table for the status timeline.
|
||||
|
||||
**`OrderModal`** handles both create and edit. Key patterns:
|
||||
- `toPayload()` converts form state to DB payload — empty strings become `null` for numeric fields (`area`, `final_cost`).
|
||||
- `scheduled_at` is stored as raw `YYYY-MM-DDTHH:mm` in form state and converted to ISO only in `toPayload()`. Never pass it through `new Date()` in `onChange` — this breaks datetime-local inputs.
|
||||
- Drag-and-drop in `KanbanBoard` uses `@dnd-kit/core`.
|
||||
|
||||
**Overdue orders:** `isOverdue = scheduled_at < now && status !== 'закрыт'` — highlighted in both `OrderCard` (red border) and `CalendarPage` chips.
|
||||
|
||||
### Shared Hooks
|
||||
|
||||
- `usePricing` — reads `price_200`/`price_300` from `settings` table. Used by `Estimator` and `OrderModal`.
|
||||
- `useIsMobile` — `window.innerWidth < 768` with resize listener.
|
||||
- `usePushNotifications` — wraps Web Push API, stores subscription in `push_subscriptions` table via `push-notify` Edge Function.
|
||||
- `useToast` — from `ToastContext`, call `addToast(message, type)` where type is `'success' | 'error' | 'info'`.
|
||||
|
||||
### Supabase
|
||||
|
||||
`src/lib/supabase.js` — single client instance, reads `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY`.
|
||||
|
||||
**Database tables:** `orders`, `order_events`, `order_comments`, `order_photos` (Storage bucket), `push_subscriptions`, `settings`, `clients` (view).
|
||||
|
||||
**Edge Functions** (`supabase/functions/`):
|
||||
- `push-notify` — sends Web Push to all subscribers. Called on status change.
|
||||
- `send-reminders` — scheduled every 15 min; pushes reminders for orders with `scheduled_at` in the next 50–70 min. Uses `reminder_sent_at` guard to prevent duplicates.
|
||||
- `invite-admin-user`, `list-admin-users`, `delete-admin-user` — user management via Supabase Admin API (service role).
|
||||
|
||||
All hooks use an `isMounted` / `active` guard pattern to prevent state updates after unmount.
|
||||
|
||||
### PWA
|
||||
|
||||
`vite-plugin-pwa` with `injectManifest` strategy (not `generateSW`) — required because `src/sw.js` has a custom push event handler alongside Workbox precaching. The built service worker is output as `dist/sw.js`.
|
||||
|
||||
### Deployment
|
||||
|
||||
Docker multi-stage build: `node:20-alpine` builds the Vite app (VITE_ vars injected as `ARG`), `caddy:2-alpine` serves `dist/`. The `Caddyfile` uses `{$DOMAIN:localhost}` — setting `DOMAIN` to a real hostname enables automatic HTTPS via Let's Encrypt.
|
||||
|
||||
```bash
|
||||
# On server
|
||||
cd /opt/glass && git pull && docker compose up -d --build
|
||||
```
|
||||
|
||||
Production server: `2.26.96.212`, repo at `/opt/glass`. Gitea: `gitea.houseassassin.keenetic.pro/houseassassin/glass`.
|
||||
|
||||
Reference in New Issue
Block a user