Files

103 lines
5.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 5070 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`.