66 lines
3.2 KiB
Markdown
66 lines
3.2 KiB
Markdown
# Feature page conventions (read before writing any page)
|
|
|
|
This is a **static-export** Next.js 16 SPA. Every page is a Client Component
|
|
(`"use client"` at the top). All data fetching happens in the browser via the
|
|
helpers below. Persian (Farsi), RTL. Look at the reference page
|
|
`app/dashboard/media/categories/page.tsx` and copy its structure.
|
|
|
|
## Imports & contracts
|
|
|
|
```ts
|
|
import { apiFetch, ApiError, toFormData, unwrap } from "@/lib/api";
|
|
import { useList, useItem } from "@/lib/useResource";
|
|
import { useToast } from "@/components/toast";
|
|
import { DataTable, type Column } from "@/components/DataTable";
|
|
import {
|
|
Button, ConfirmDialog, Field, Input, Textarea, Select, Switch,
|
|
Modal, PageHeader, Card, Badge, EmptyState,
|
|
} from "@/components/ui";
|
|
import { EditIcon, PlusIcon, TrashIcon } from "@/components/icons";
|
|
import { toFa, formatDuration } from "@/lib/utils";
|
|
```
|
|
|
|
### `apiFetch<T>(path, { method, body, query, baseUrl, auth })`
|
|
- `path` is appended to the meditation base (`/api` already implied — pass e.g. `"/media"`).
|
|
- `body`: pass a **plain object** for JSON, or a **FormData** for multipart. Build multipart with `toFormData({...})` (handles File, arrays as `key[]`, booleans as 1/0, skips empty).
|
|
- Bearer token is attached automatically. Throws `ApiError` (has `.message`, `.status`).
|
|
|
|
### `useList<T>(path, query?)` → `{ data: T[], loading, error, reload }`
|
|
Use for GET list endpoints. Already unwraps `{ data: [...] }`.
|
|
|
|
### `useItem<T>(path)` → `{ data, loading, error, reload }`
|
|
Use for a single GET record (e.g. settings).
|
|
|
|
### `DataTable<T>` props: `columns`, `rows`, `loading`, `error`, `emptyMessage?`, `actions?(row)`
|
|
`Column<T> = { key, header, render?(row), className? }`.
|
|
|
|
### Form components
|
|
- `<Field label required hint>{children}</Field>` wraps an input with a label.
|
|
- `<Input/>`, `<Textarea/>`, `<Select/>` are styled native elements (pass value/onChange).
|
|
- `<Switch checked onChange={(v)=>...} label/>` for booleans.
|
|
- `<Modal open onClose title footer>` — put the form inside; trigger submit via a
|
|
`<Button form="my-form" type="submit" loading={saving}>` in the footer and give the
|
|
`<form id="my-form" onSubmit={save}>`.
|
|
- `<ConfirmDialog open message loading onConfirm onClose/>` for deletes.
|
|
- `<Button variant="primary|secondary|danger|ghost" loading icon>`.
|
|
|
|
### Helpers
|
|
- `toFa(value)` → Persian digits for display (use for ids/numbers/durations in tables).
|
|
- `formatDuration(seconds)` → `م:ث`.
|
|
|
|
## Page skeleton
|
|
|
|
Every CRUD page: `PageHeader` (title + subtitle + "new" Button) → `DataTable` with
|
|
edit/delete `actions` → a `Modal` create/edit form → a `ConfirmDialog` for delete.
|
|
On success call `reload()` and `toast.success(...)`; on error `toast.error(err instanceof ApiError ? err.message : "...")`.
|
|
|
|
Use real Persian labels everywhere. Keep numeric inputs `type="number"`, file inputs
|
|
`type="file"` (read `e.target.files?.[0]`). For ltr-ish fields (urls, ids) add `dir="ltr"`.
|
|
|
|
## REST conventions in this API (important quirks)
|
|
- **Create**: usually `POST /resource` (multipart unless noted JSON).
|
|
- **Update**: RESTful resources use `PUT /resource/:id` (JSON). BUT file-bearing
|
|
resources (**scenes, bell-sounds, background-sounds**) update via `POST /resource/:id` (multipart).
|
|
- **Delete**: `DELETE /resource/:id`.
|
|
- Lists: `GET /resource`.
|