384 lines
16 KiB
Markdown
384 lines
16 KiB
Markdown
# CLAUDE.md — BellSystems Control Panel v2
|
|
# Instructions for Claude Code
|
|
|
|
> Read this file at the start of every session.
|
|
> This is the v2 project — a clean rebuild. The old v1 code lives in `frontend/src/_archive/` for reference only.
|
|
> Also read `DESIGN.md` before writing any UI code.
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
C:\development\bellsystems-cp-v2\
|
|
│ CLAUDE.md ← you are here
|
|
│ DESIGN.md ← design rules, component contracts, page layout spec
|
|
│ docker-compose.yml
|
|
│
|
|
├── backend/ ← FastAPI backend — DO NOT MODIFY
|
|
│
|
|
└── frontend/
|
|
├── src/
|
|
│ ├── _archive/ ← v1 reference code — READ ONLY, never import from here except auth
|
|
│ ├── assets/
|
|
│ │ ├── global-icons/ ← action SVGs (edit, delete, download, etc.)
|
|
│ │ ├── side-menu-icons/ ← sidebar navigation SVGs
|
|
│ │ ├── comms/ ← communication type SVGs
|
|
│ │ ├── other-icons/ ← misc SVGs
|
|
│ │ └── customer-status/ ← CRM status SVGs
|
|
│ ├── components/
|
|
│ │ ├── ui/ ← design system components (the ONLY place to source UI)
|
|
│ │ ├── layout/ ← Sidebar, Header, MainLayout
|
|
│ │ └── shared/
|
|
│ ├── hooks/
|
|
│ ├── lib/
|
|
│ ├── modals/ ← all modal components live here, grouped by domain
|
|
│ ├── pages/ ← one file per page, grouped by domain
|
|
│ ├── providers/
|
|
│ ├── router/
|
|
│ │ └── index.jsx ← all routes defined here
|
|
│ ├── styles/
|
|
│ │ ├── tokens.css ← ALL design tokens (colors, fonts, spacing, shadows)
|
|
│ │ ├── components.css ← ALL component-level styles
|
|
│ │ └── global.css ← base resets, typography, scrollbar, .page-wrapper
|
|
│ └── main.jsx ← app entry point
|
|
└── vite.config.js
|
|
```
|
|
|
|
---
|
|
|
|
## Project Overview
|
|
|
|
Bespoke SaaS Admin Console for BellSystems. Manages Devices, Customers (CRM),
|
|
Manufacturing, Firmware, MQTT, Melodies, Staff, and more.
|
|
|
|
- **Backend:** FastAPI at `backend/` — never modify
|
|
- **Archive:** `frontend/src/_archive/` — v1 reference, read-only
|
|
- **Active code:** `frontend/src/` (everything except `_archive/`)
|
|
- **Design rules:** `DESIGN.md` at project root — read before writing any UI
|
|
- **Style Guide:** live at `/dev/styleguide` — shows every component with every variant
|
|
- **API client:** `frontend/src/lib/api.js` wraps `_archive/api/client.js`
|
|
|
|
---
|
|
|
|
## Import Alias
|
|
|
|
`@/` maps to `frontend/src/`:
|
|
|
|
```js
|
|
import Button from '@/components/ui/Button' ✅
|
|
import Select from '@/components/ui/Select' ✅
|
|
import { useAuth } from '@/hooks/useAuth' ✅
|
|
import MainLayout from '@/components/layout/MainLayout' ✅
|
|
```
|
|
|
|
Never use relative `../` paths except inside `providers/` and `hooks/` when referencing `_archive/`.
|
|
|
|
---
|
|
|
|
## The Golden Rules
|
|
|
|
- **Never modify `_archive/`** — it is read-only reference material
|
|
- **All new code goes in `frontend/src/`** — no exceptions
|
|
- **No `/v2/` prefix anywhere** — routes start from `/`, imports start from `@/`
|
|
- **Read `DESIGN.md` before writing any UI code**
|
|
- **Source every UI element from `@/components/ui/`** — no raw HTML elements for styled things
|
|
- **Use only CSS tokens** — never raw hex, rgb, or pixel values in component or page files
|
|
- **Use `.masonry-grid` for all content pages with multiple variable-height sections** — never `display: grid` with fixed columns for card layouts. See DESIGN.md §11.
|
|
|
|
---
|
|
|
|
## Available UI Components
|
|
|
|
Every component lives in `frontend/src/components/ui/`. These are the ONLY components to use.
|
|
Check the live Style Guide at `/dev/styleguide` to see all variants and states.
|
|
|
|
| Component | Import path | Purpose |
|
|
|-----------------|--------------------------------------|----------------------------------------------|
|
|
| `Button` | `@/components/ui/Button` | All interactive actions |
|
|
| `StatusBadge` | `@/components/ui/StatusBadge` | Coloured status pills |
|
|
| `FormField` | `@/components/ui/FormField` | Every text/email/password/textarea input |
|
|
| `Select` | `@/components/ui/Select` | Custom dropdown (used inside FormField type="select") |
|
|
| `Modal` | `@/components/ui/Modal` | All overlay dialogs |
|
|
| `ConfirmDialog` | `@/components/ui/ConfirmDialog` | Destructive / confirmation prompts |
|
|
| `DataTable` | `@/components/ui/DataTable` | All tabular data with sorting/selection |
|
|
| `Pagination` | `@/components/ui/Pagination` | Page controls beneath DataTable |
|
|
| `Spinner` | `@/components/ui/Spinner` | Loading indicators |
|
|
| `PageHeader` | `@/components/ui/PageHeader` | Page title block — every page starts with this |
|
|
| `Card` | `@/components/ui/Card` | Contained content sections |
|
|
| `Tabs` | `@/components/ui/Tabs` | Tabbed navigation within a page |
|
|
| `Toast` | `@/components/ui/Toast` | Transient notifications (via `useToast`) |
|
|
| `SearchBar` | `@/components/ui/SearchBar` | Search inputs with debounce |
|
|
| `Breadcrumbs` | `@/components/ui/Breadcrumbs` | Navigation trail on detail pages |
|
|
| `Icon` | `@/components/ui/Icon` | Inline SVG icons by name |
|
|
|
|
---
|
|
|
|
## Folder Structure — Pages & Modals
|
|
|
|
Folders mirror the sidebar section hierarchy exactly.
|
|
|
|
```
|
|
frontend/src/pages/
|
|
├── auth/ ← unauthenticated routes (login)
|
|
├── dashboard/ ← General section
|
|
├── bellcloud/ ← Bell Cloud section
|
|
│ ├── devices/
|
|
│ │ └── notes/
|
|
│ ├── users/
|
|
│ ├── melodies/
|
|
│ │ └── archetypes/
|
|
│ └── mqtt/
|
|
├── crm/ ← Headquarters section
|
|
│ ├── comms/
|
|
│ │ └── mail/
|
|
│ ├── customers/
|
|
│ │ └── tabs/
|
|
│ ├── orders/
|
|
│ ├── quotations/
|
|
│ └── products/
|
|
├── engineering/ ← Engineering section
|
|
│ ├── manufacturing/
|
|
│ ├── firmware/
|
|
│ └── developer/
|
|
├── public/ ← Public / unauthenticated pages
|
|
│ ├── cloudflash/
|
|
│ └── serial/
|
|
├── settings/ ← Console Settings section
|
|
│ └── staff/
|
|
└── dev/ ← Internal dev tools (StyleGuide)
|
|
```
|
|
|
|
```
|
|
frontend/src/modals/
|
|
├── bellcloud/
|
|
│ ├── devices/
|
|
│ ├── melodies/
|
|
│ └── users/
|
|
├── crm/
|
|
│ └── products/
|
|
├── engineering/
|
|
│ └── manufacturing/
|
|
└── shared/
|
|
```
|
|
|
|
---
|
|
|
|
## Page Layout — How Every Page Is Structured
|
|
|
|
Every authenticated page is rendered inside `MainLayout`, which provides:
|
|
- **Sidebar** — fixed left, `224px` wide (`--sidebar-width`)
|
|
- **Header** — fixed top, `56px` tall (`--header-height`)
|
|
- **Content area** — the remaining viewport space
|
|
|
|
Inside the content area, every page uses the `.page-wrapper` class (defined in `global.css`):
|
|
|
|
```css
|
|
.page-wrapper {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: var(--space-12); /* 48px on all sides — desktop */
|
|
gap: var(--space-6); /* 24px between top-level sections */
|
|
min-width: 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.page-wrapper {
|
|
padding: var(--space-8); /* 32px on mobile */
|
|
gap: var(--space-4);
|
|
}
|
|
}
|
|
```
|
|
|
|
**This is the consistency guarantee**: because every page uses `.page-wrapper`, the `<PageHeader>` title on every page starts at exactly the same position — 48px from the top and 48px from the left edge of the content area. Never override these paddings. Never add extra wrappers around `page-wrapper` that introduce additional offset.
|
|
|
|
### Content width modes
|
|
|
|
Pages fall into two modes — choose based on how much content the page has:
|
|
|
|
**Full-width** (default — lists, tables, dashboards):
|
|
```jsx
|
|
<div className="page-wrapper">
|
|
```
|
|
|
|
**Centered** (forms, settings, pages with very few items):
|
|
```jsx
|
|
<div className="page-wrapper page-wrapper--centered">
|
|
```
|
|
Centered mode caps each direct child at `--content-max-width-sm` (640px) by default and centers it horizontally. Override when needed:
|
|
```jsx
|
|
<div className="page-wrapper page-wrapper--centered"
|
|
style={{ '--page-content-max-width': 'var(--content-max-width-md)' }}>
|
|
```
|
|
Available tokens: `--content-max-width-xs` (480px), `--content-max-width-sm` (640px), `--content-max-width-md` (800px), `--content-max-width-lg` (1024px).
|
|
|
|
---
|
|
|
|
## Rules for Every Page
|
|
|
|
### Before writing any code
|
|
1. Read `DESIGN.md` — confirm tokens and components to use
|
|
2. Check `frontend/src/components/ui/` — use existing components only
|
|
3. Check the Style Guide at `/dev/styleguide` for the correct variant/props
|
|
4. Check `frontend/src/_archive/` for the equivalent v1 page — copy API calls and data shape only, never styling
|
|
|
|
### Page template
|
|
|
|
```jsx
|
|
// frontend/src/pages/[domain]/PageName.jsx
|
|
|
|
import PageHeader from '@/components/ui/PageHeader'
|
|
import { useAuth } from '@/hooks/useAuth'
|
|
// Import ONLY from @/components/ui/ — never raw HTML elements for styled things
|
|
|
|
export default function PageName() {
|
|
// 1. Auth
|
|
const { user } = useAuth()
|
|
|
|
// 2. State & data fetching
|
|
|
|
// 3. Event handlers
|
|
|
|
// 4. Render — always handle: loading, error, empty, data states
|
|
return (
|
|
<div className="page-wrapper">
|
|
<PageHeader title="Page Title" subtitle="Optional description">
|
|
{/* Action buttons — use <Button variant="primary"> etc. */}
|
|
</PageHeader>
|
|
|
|
{/* Page content — use Card, DataTable, Tabs, etc. */}
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Styling rules
|
|
- **Tailwind** for layout only: `flex`, `grid`, `items-center`, `min-w-0`, etc.
|
|
- **CSS token variables** for ALL colors, spacing, typography — `var(--token-name)`
|
|
- **No** `.module.css` or per-page scoped CSS files
|
|
- **No** `style={{ }}` inline styles except for genuinely dynamic values (e.g. calculated widths)
|
|
- **No** raw hex, rgb, or pixel values anywhere
|
|
|
|
### Toolbar buttons — matching SearchBar height
|
|
|
|
`.btn` has `line-height: 1` while `.searchbar-input` has `line-height: var(--line-height-base)` (1.5). This makes buttons shorter than the search bar by default. Whenever a `<Button>`, `<SegmentedControl>`, or `<IconButtonGroup>` sits in the same toolbar row as a `<SearchBar>`, all buttons must use `padding-top/bottom: var(--space-3)` and `line-height: var(--line-height-base)`.
|
|
|
|
**Already handled automatically (no extra props needed):**
|
|
- `SegmentedControl` — `.seg-ctrl__btn` in `components.css` enforces `--space-3` padding and `var(--line-height-base)` globally.
|
|
- `IconButtonGroup` — `.icon-btn-group__btn` in `components.css` enforces `--space-3` padding globally.
|
|
|
|
**Must be overridden manually — standalone `<Button>` in the same row as a `<SearchBar>`:**
|
|
|
|
```jsx
|
|
// Option A — inline style prop on the button:
|
|
<Button
|
|
size="md"
|
|
style={{ paddingTop: 'var(--space-3)', paddingBottom: 'var(--space-3)', lineHeight: 'var(--line-height-base)' }}
|
|
>
|
|
Label
|
|
</Button>
|
|
|
|
// Option B (preferred when multiple buttons share a toolbar) — scoped CSS block:
|
|
<>
|
|
<style>{`
|
|
.my-toolbar .btn {
|
|
padding-top: var(--space-3) !important;
|
|
padding-bottom: var(--space-3) !important;
|
|
line-height: var(--line-height-base) !important;
|
|
}
|
|
`}</style>
|
|
<div className="my-toolbar" style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-2)' }}>
|
|
<IconButtonGroup … />
|
|
<Button variant="primary" size="md">Compose</Button>
|
|
</div>
|
|
</>
|
|
```
|
|
|
|
### Date & time formatting
|
|
- **Greek/European date style everywhere** — DD/MM/YYYY, never US-style MM/DD/YYYY
|
|
- **All date/time formatting must use `@/lib/formatters`** — never use raw `toLocaleDateString()`, `Intl.DateTimeFormat`, `toLocaleString()`, or `toISOString().slice()` in pages or modals
|
|
- **For `datetime-local` input values** — use `toDatetimeLocal(iso)` and `nowLocal()` from formatters. Never use `new Date(x).toISOString().slice(0, 16)` — it converts to UTC and shifts the time (timezone bug)
|
|
- **Currency** — use `fmtEuro(n)` from formatters (Greek locale: `1.250,00 €`)
|
|
|
|
Available formatters (`import { ... } from '@/lib/formatters'`):
|
|
|
|
| Function | Output example | Use for |
|
|
|---------------------|------------------------------------|--------------------------------------|
|
|
| `fmtDate` | `05/03/2026` | Short numeric dates (tables, lists) |
|
|
| `fmtDateMedium` | `5 Mar 2026` | Medium dates (cards, details) |
|
|
| `fmtDateLong` | `5 March 2026` | Long dates (headings, summaries) |
|
|
| `fmtDateFull` | `Wednesday, 5 March 2026` | Dashboard, full context |
|
|
| `fmtDateTime` | `5 March 2026, 2:30 pm` | Date + 12h time |
|
|
| `fmtDateTimeMedium` | `5 Mar 2026, 14:30` | Date + 24h time (compact) |
|
|
| `fmtDateTimeFull` | `Wed, 5 Mar 2026, 2:30 pm` | Emails, comms |
|
|
| `fmtTime24` | `14:30:05` | Time with seconds |
|
|
| `fmtRelative` | `5 minutes ago` | Relative timestamps |
|
|
| `toDatetimeLocal` | `2026-03-05T14:30` | `datetime-local` input values |
|
|
| `nowLocal` | `2026-03-05T14:30` | Current time for form defaults |
|
|
| `toDateInput` | `2026-03-05` | `date` input values |
|
|
| `fmtEuro` | `1.250,00 €` | Euro currency |
|
|
|
|
### Data fetching
|
|
- Use `frontend/src/lib/api.js` (wraps `_archive/api/client.js`)
|
|
- Every data-fetching component must handle **loading**, **error**, and **empty** states
|
|
|
|
### Modals
|
|
- Never defined inside page files
|
|
- Live in `frontend/src/modals/[sidebar-section]/[domain]/ModalName.jsx` — mirror the pages folder structure
|
|
- Pass data via props, actions via callbacks
|
|
|
|
---
|
|
|
|
## Missing Components — Stop and Ask
|
|
|
|
If building a page requires a UI component that does not exist in
|
|
frontend/src/components/ui/, Claude Code must STOP and say:
|
|
|
|
"I need a [ComponentName] component which doesn't exist yet.
|
|
Please build it and add it to the StyleGuide before I continue."
|
|
|
|
Do NOT:
|
|
- Invent an inline one-off component inside a page file
|
|
- Use raw HTML elements styled with inline CSS as a substitute
|
|
- Proceed and leave a placeholder
|
|
|
|
The StyleGuide at frontend/src/pages/dev/StyleGuide.jsx is the source
|
|
of truth for what components exist and how they look. Every component
|
|
used in a page must have a visible example there first.
|
|
|
|
## Building a New Page — Checklist
|
|
|
|
- [ ] File in correct `frontend/src/pages/[domain]/` folder
|
|
- [ ] Root element is `<div className="page-wrapper">` — nothing else, nothing wrapping it
|
|
- [ ] First child inside `page-wrapper` is `<PageHeader title="...">`
|
|
- [ ] Only components from `frontend/src/components/ui/` used
|
|
- [ ] No raw hex colors or pixel spacing values anywhere
|
|
- [ ] Loading state implemented
|
|
- [ ] Error state implemented
|
|
- [ ] Empty state implemented
|
|
- [ ] Mobile responsive (375px minimum)
|
|
- [ ] Modals in `frontend/src/modals/`
|
|
- [ ] Route added to `frontend/src/router/index.jsx`
|
|
|
|
---
|
|
|
|
## API Client
|
|
|
|
```js
|
|
// frontend/src/lib/api.js
|
|
export { default } from '../_archive/api/client'
|
|
```
|
|
|
|
All pages import from `@/lib/api`, never directly from `_archive`.
|
|
|
|
---
|
|
|
|
## Auth
|
|
|
|
`frontend/src/hooks/useAuth.js` re-exports from the archive AuthContext.
|
|
`frontend/src/providers/AuthProvider.jsx` re-exports the AuthProvider.
|
|
|
|
These are the ONLY two files permitted to import from `_archive/auth/`.
|
|
All other files use `@/hooks/useAuth`.
|