2026-07-12 21:08:29 +03:30
|
|
|
---
|
|
|
|
|
description: Frontend folder structure — ui vs non-ui, thin pages, feature layout
|
|
|
|
|
globs: frontend/src/**
|
|
|
|
|
alwaysApply: false
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# Frontend component structure
|
|
|
|
|
|
|
|
|
|
## Rules
|
|
|
|
|
|
|
|
|
|
| Kind | Location |
|
|
|
|
|
|------|----------|
|
|
|
|
|
| Cross-feature UI | `components/ui/shared/` |
|
|
|
|
|
| Feature UI | `components/ui/{feature}/` |
|
|
|
|
|
| Cross-feature non-UI | `components/shared/` |
|
|
|
|
|
| Feature non-UI | `components/{feature}/` |
|
|
|
|
|
| Route logic | `components/ui/{feature}/{Feature}Page.tsx` |
|
|
|
|
|
| App routes | `app/**/page.tsx` — **thin wrapper only** |
|
|
|
|
|
|
|
|
|
|
## Thin page pattern
|
|
|
|
|
|
|
|
|
|
```tsx
|
|
|
|
|
'use client';
|
|
|
|
|
import { PatientsPage } from '@/components/ui/patient/PatientsPage';
|
|
|
|
|
export default function Page() {
|
|
|
|
|
return <PatientsPage />;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Reference: `app/.../treatment/page.tsx` + `components/ui/treatment/TreatmentWorkspace.tsx`.
|
|
|
|
|
|
|
|
|
|
## Do not
|
|
|
|
|
|
|
|
|
|
- Put React components (`.tsx` with JSX) in `components/` outside `ui/`.
|
|
|
|
|
- Put pure helpers (`.ts`, no JSX) inside `components/ui/`.
|
|
|
|
|
- Put business logic, API calls, or large forms directly in `app/**/page.tsx`.
|
|
|
|
|
|
|
|
|
|
## API & errors
|
|
|
|
|
|
|
|
|
|
- API clients: `lib/api/`.
|
|
|
|
|
- Catch blocks: `getUserFacingError(err, tErrors, fallback)` from `components/shared/formatApiError.ts`.
|
|
|
|
|
|
2026-07-12 21:30:28 +03:30
|
|
|
## Toasts
|
|
|
|
|
|
|
|
|
|
- Use `useToast()` for transient page feedback — rendered globally by `ToastProvider` in dashboard layout (fixed bottom, above dialogs).
|
|
|
|
|
- Do **not** add `<ToastStack />` inside pages or dialogs.
|
|
|
|
|
- Form/dialog validation: inline error text near the field or submit button, not a toast.
|
|
|
|
|
|
2026-07-12 21:08:29 +03:30
|
|
|
## When adding UI
|
|
|
|
|
|
|
|
|
|
1. Check `components/ui/shared/` for an existing primitive.
|
|
|
|
|
2. Check the feature's `ui/{feature}/` folder for an existing pattern.
|
|
|
|
|
3. Add i18n keys to en, fa, and nl.
|
2026-07-14 01:27:11 +03:30
|
|
|
|
|
|
|
|
## Shared form & table primitives
|
|
|
|
|
|
|
|
|
|
| Need | Use |
|
|
|
|
|
|------|-----|
|
|
|
|
|
| Date filter / due date field | `AppDateInput` (`ui/shared/`) — not `<input type="date">` |
|
|
|
|
|
| Filter or inline `<select>` | `FORM_SELECT_CLASS` from `components/shared/formSelectStyles.ts` |
|
|
|
|
|
| Tiny select (sort dir, etc.) | `FORM_SELECT_COMPACT_CLASS` |
|
|
|
|
|
| Desktop data table | `Table` (`ui/shared/Table.tsx`) — logical alignment only |
|
2026-07-17 10:58:04 +03:30
|
|
|
| API action button | `Button` — return the Promise from `onClick` (`() => doThing()` not `() => void doThing()`); auto-disables until settle. List/icon mutations: `useAsyncAction` / `useAsyncActionById` |
|
2026-07-14 01:27:11 +03:30
|
|
|
|
|
|
|
|
Styles for `.form-select` chevrons live in `styles/globals.css`. Do not duplicate chevron icons on raw selects.
|