# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash npm run dev # Start dev server with HMR (Vite) npm run build # Production build to dist/ npm run preview # Preview production build locally npm run lint # ESLint check ``` Tests exist in `src/**/__tests__/` and `src/test/` but are mostly placeholders — no test runner is configured. ## Project Overview 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 ### Routing `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. ### Public Landing Page `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`.