5.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
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:
- Public landing page — marketing site at
/ - 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 insrc/index.css. - Animations use
framer-motion(motion.*withwhileInView,whileHover,whileTap). - Icons from
lucide-react. - CSS custom properties in
:rootinindex.cssare the design tokens — always usevar(--...)for colors and spacing. Estimator.jsxreads prices from Supabase (usePricinghook) rather than hardcoding them. Fallback is 200µm=2000₽/m², 300µm=3000₽/m².LeadForm.jsxPOSTs to Telegram Bot API usingVITE_TG_BOT_TOKEN/VITE_TG_CHAT_IDfrom.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 becomenullfor numeric fields (area,final_cost).scheduled_atis stored as rawYYYY-MM-DDTHH:mmin form state and converted to ISO only intoPayload(). Never pass it throughnew Date()inonChange— this breaks datetime-local inputs.- Drag-and-drop in
KanbanBoarduses@dnd-kit/core.
Overdue orders: isOverdue = scheduled_at < now && status !== 'закрыт' — highlighted in both OrderCard (red border) and CalendarPage chips.
Shared Hooks
usePricing— readsprice_200/price_300fromsettingstable. Used byEstimatorandOrderModal.useIsMobile—window.innerWidth < 768with resize listener.usePushNotifications— wraps Web Push API, stores subscription inpush_subscriptionstable viapush-notifyEdge Function.useToast— fromToastContext, calladdToast(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 withscheduled_atin the next 50–70 min. Usesreminder_sent_atguard 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.
# 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.