# 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 `<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`.
