feat: initial

This commit is contained in:
2026-06-03 03:08:57 +03:30
parent 3d9585ac5c
commit 6fa30eb29a
45 changed files with 7053 additions and 86 deletions
+82
View File
@@ -0,0 +1,82 @@
# Meditation Admin — API reference
Two base URLs (see `lib/config.ts`):
- **approagency** (account login): `https://api.approagency.ir/api`
- **meditation** (everything else): `https://meditation.approagency.ir/api`
- `package_name`: `com.approagency.meditation`
## Auth flow (two steps)
1. `POST {approagency}/auth/login` — multipart: `auth` (email or mobile), `password`, `package_name`
`{ token }` (the "approo" token).
2. `POST {meditation}/auth/login-with-approo-v2?token=<approoToken>&package_name=...` — multipart: `token`, `package_name`
`{ token }` (the meditation bearer token used for all calls below).
All meditation calls send `Authorization: Bearer <meditationToken>` and `Accept: application/json`.
## Endpoints (path is after the meditation base `/api`)
### Media
- GET `/media` — list. query: `categories`, `tags`, `search`
- GET `/media/filters`
- GET `/media/:id`
- POST `/media` — multipart: title, caption, category_id, subcategory_id, file, duration, visibility, type
- POST `/media/:id` — update (multipart: is_premium; json: subcategory_ids[])
- GET `/media/popular`, `/media/recently-played`, `/media/saved`
- POST `/media/:id/play`, `/media/:id/save`, `/media/:id/note`, `/media/:id/feedback` (stars, content)
- Categories: GET/POST/PUT/DELETE `/categories` (multipart name)
- Sub-categories: GET/POST `/sub-categories`, PUT/DELETE `/sub-categories/:id` (category_id, name)
### Track (music)
- Categories: GET `/music-categories/:id`, POST `/music-categories`, PUT/DELETE `/music-categories/:id` (name, description, order, image_id, is_active)
- Sub-categories: GET `/music-subcategories`, POST `/music-subcategories`, PUT/DELETE `/music-subcategories/:id`
- Music: GET `/music/:id`, POST `/music` (title, artist, file, playlist_id, type, duration, image_id), PUT/DELETE `/music/:id`
- POST `/music/update-order`, GET `/music/playlist/:id`, POST `/music/:musicId/add-to-playlist`, DELETE `/music/:musicId/remove-from-playlist`
- Playlists: GET `/music-playlists/:id?`, POST `/music-playlists`, PUT/DELETE `/music-playlists/:id` (name, description, category_ids[], subcategory_ids[])
### Insight timer
- Timer options: GET `/timer/options`
- Presets: GET/POST `/timer-presets`, GET/PUT/DELETE `/timer-presets/:id` (name, duration_seconds, start/end/interval_bell_id, interval_seconds, interval_repeat, background_sound_id, background_image_id, volume)
- Bell sounds: GET/POST `/bell-sounds`, GET `/bell-sounds/:id`, POST `/bell-sounds/:id` (update), DELETE `/bell-sounds/:id` (multipart: name, order, is_active, sound, image)
- Background sounds: GET/POST `/background-sounds`, GET `/background-sounds/:id`, POST `/background-sounds/:id`, DELETE `/background-sounds/:id`
### Settings / scenes
- GET/POST `/scenes`, POST `/scenes/:id` (update), DELETE `/scenes/:id` (multipart: name, order, is_active, image, video, sound)
- GET `/scene-settings`, PUT `/scene-settings` (active_scene_id, scene_volume, background_play_seconds, video_enabled)
### Slider
- GET `/slider`, POST `/slider` (multipart: title, description, action[path], action[type], url), PUT/DELETE `/slider/:id`
### Images
- GET `/images/all`, GET `/images/public`, POST `/images` (multipart: image, title, type, description), PUT/DELETE `/images/:id`
### Questions
- GET `/questions?tag=&category=`, GET `/questions/:id`, POST `/questions`, PUT/DELETE `/questions/:id` (title, category, tags[])
### Survey questions
- GET `/survey-questions/:id`, POST `/survey-questions`, PUT/DELETE `/survey-questions/:id` (question, description, type, order, is_active, options[].label)
- POST `/survey-questions/:id/answer` (option_ids[])
- GET `/admin/survey-questions/:id`, GET `/admin/survey-questions/:id/analytics`
- GET `/survey-questions/suggested-media`
### Breathing exercise
- GET `/breathing-templates`, POST `/breathing-templates`, PUT/DELETE `/breathing-templates/:id` (name, inhale, exhale, breath_hold, duration, description, image_id)
- GET `/user-templates`, GET `/breathing-sessions`, POST `/breathing-complete?template_id=&duration=`
### Mood
- GET `/moods`, GET `/moods/history`, POST `/moods/today` (mood_id)
### Worry box
- GET `/worries`, POST `/worries` (title, note), PUT/DELETE `/worries/:id`, PATCH `/worries/:id/toggle`
### Comments / Likes / Saves / Ratings (by type+id, e.g. type=media|music|playlist)
- Comments: GET/POST/DELETE `/comments/:type/:id` (content)
- Likes: POST `/likes/toggle`, `/likes/like` (type, id), GET `/likes/my-liked?type=`
- Saves: POST `/saves/toggle`, `/saves/save`, `/saves/unsave`, `/saves/check` (type, id), GET `/saves/my-saved?type=`
- Ratings: GET/POST/DELETE `/ratings/:type/:id` (stars), GET `/ratings/:type/:id/user`
### Misc
- GET `/leader-board`
- GET `/profile`
- POST `/zarinpal/gateway` (description, product_id, platform)
+65
View File
@@ -0,0 +1,65 @@
# 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`.