Files
meditation-admin/docs/UI_GUIDE.md
T
2026-06-03 03:08:57 +03:30

3.2 KiB

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

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.