From 08ec9e5a284c9b25e7ea7beda06a9c3b83685e9c Mon Sep 17 00:00:00 2001 From: houseassassin Date: Tue, 12 May 2026 15:24:40 +0300 Subject: [PATCH] docs: update CLAUDE.md to reflect admin panel, PWA, and Docker setup --- CLAUDE.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ecda6eb..7da7772 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 ``. 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`.