From 6fa30eb29a0fc7582fc6214832d17aa4a8b98b13 Mon Sep 17 00:00:00 2001 From: AmirmahdiNourkazemi Date: Wed, 3 Jun 2026 03:08:57 +0330 Subject: [PATCH] feat: initial --- app/dashboard/breathing/page.tsx | 303 +++++++++++++++ app/dashboard/comments/page.tsx | 174 +++++++++ app/dashboard/images/page.tsx | 247 ++++++++++++ app/dashboard/layout.tsx | 76 ++++ app/dashboard/leaderboard/page.tsx | 65 ++++ app/dashboard/media/categories/page.tsx | 173 +++++++++ app/dashboard/media/page.tsx | 355 ++++++++++++++++++ app/dashboard/media/sub-categories/page.tsx | 217 +++++++++++ app/dashboard/moods/page.tsx | 54 +++ app/dashboard/music/categories/page.tsx | 249 ++++++++++++ app/dashboard/music/playlists/page.tsx | 263 +++++++++++++ app/dashboard/music/sub-categories/page.tsx | 287 ++++++++++++++ app/dashboard/music/tracks/page.tsx | 308 +++++++++++++++ app/dashboard/page.tsx | 44 +++ app/dashboard/questions/page.tsx | 259 +++++++++++++ app/dashboard/scenes/page.tsx | 285 ++++++++++++++ app/dashboard/scenes/settings/page.tsx | 143 +++++++ app/dashboard/sliders/page.tsx | 277 ++++++++++++++ app/dashboard/surveys/page.tsx | 317 ++++++++++++++++ .../timer/background-sounds/page.tsx | 265 +++++++++++++ app/dashboard/timer/bell-sounds/page.tsx | 260 +++++++++++++ app/dashboard/timer/options/page.tsx | 127 +++++++ app/dashboard/timer/presets/page.tsx | 354 +++++++++++++++++ app/dashboard/worries/page.tsx | 234 ++++++++++++ app/globals.css | 60 ++- app/layout.tsx | 28 +- app/login/page.tsx | 85 +++++ app/page.tsx | 76 +--- components/DataTable.tsx | 108 ++++++ components/MediaPreview.tsx | 103 +++++ components/Providers.tsx | 12 + components/Sidebar.tsx | 56 +++ components/icons.tsx | 178 +++++++++ components/toast.tsx | 71 ++++ components/ui.tsx | 308 +++++++++++++++ docs/API.md | 82 ++++ docs/UI_GUIDE.md | 65 ++++ lib/api.ts | 133 +++++++ lib/auth.tsx | 107 ++++++ lib/config.ts | 23 ++ lib/media.ts | 76 ++++ lib/nav.ts | 117 ++++++ lib/useResource.ts | 87 +++++ lib/utils.ts | 19 + next.config.ts | 9 +- 45 files changed, 7053 insertions(+), 86 deletions(-) create mode 100644 app/dashboard/breathing/page.tsx create mode 100644 app/dashboard/comments/page.tsx create mode 100644 app/dashboard/images/page.tsx create mode 100644 app/dashboard/layout.tsx create mode 100644 app/dashboard/leaderboard/page.tsx create mode 100644 app/dashboard/media/categories/page.tsx create mode 100644 app/dashboard/media/page.tsx create mode 100644 app/dashboard/media/sub-categories/page.tsx create mode 100644 app/dashboard/moods/page.tsx create mode 100644 app/dashboard/music/categories/page.tsx create mode 100644 app/dashboard/music/playlists/page.tsx create mode 100644 app/dashboard/music/sub-categories/page.tsx create mode 100644 app/dashboard/music/tracks/page.tsx create mode 100644 app/dashboard/page.tsx create mode 100644 app/dashboard/questions/page.tsx create mode 100644 app/dashboard/scenes/page.tsx create mode 100644 app/dashboard/scenes/settings/page.tsx create mode 100644 app/dashboard/sliders/page.tsx create mode 100644 app/dashboard/surveys/page.tsx create mode 100644 app/dashboard/timer/background-sounds/page.tsx create mode 100644 app/dashboard/timer/bell-sounds/page.tsx create mode 100644 app/dashboard/timer/options/page.tsx create mode 100644 app/dashboard/timer/presets/page.tsx create mode 100644 app/dashboard/worries/page.tsx create mode 100644 app/login/page.tsx create mode 100644 components/DataTable.tsx create mode 100644 components/MediaPreview.tsx create mode 100644 components/Providers.tsx create mode 100644 components/Sidebar.tsx create mode 100644 components/icons.tsx create mode 100644 components/toast.tsx create mode 100644 components/ui.tsx create mode 100644 docs/API.md create mode 100644 docs/UI_GUIDE.md create mode 100644 lib/api.ts create mode 100644 lib/auth.tsx create mode 100644 lib/config.ts create mode 100644 lib/media.ts create mode 100644 lib/nav.ts create mode 100644 lib/useResource.ts create mode 100644 lib/utils.ts diff --git a/app/dashboard/breathing/page.tsx b/app/dashboard/breathing/page.tsx new file mode 100644 index 0000000..37f124c --- /dev/null +++ b/app/dashboard/breathing/page.tsx @@ -0,0 +1,303 @@ +"use client"; + +import { useState } from "react"; +import { apiFetch, ApiError, toFormData } from "@/lib/api"; +import { useList } from "@/lib/useResource"; +import { useToast } from "@/components/toast"; +import { DataTable, type Column } from "@/components/DataTable"; +import { + Button, + ConfirmDialog, + Field, + Input, + Textarea, + Modal, + PageHeader, +} from "@/components/ui"; +import { EditIcon, PlusIcon, TrashIcon } from "@/components/icons"; +import { toFa, formatDuration } from "@/lib/utils"; + +interface BreathingTemplate { + id: number; + name?: string; + inhale?: number; + exhale?: number; + breath_hold?: number; + duration?: number; + description?: string; + image_id?: number; +} + +interface BreathingForm { + name: string; + inhale: string; + exhale: string; + breath_hold: string; + duration: string; + description: string; + image_id: string; +} + +const emptyForm: BreathingForm = { + name: "", + inhale: "", + exhale: "", + breath_hold: "", + duration: "", + description: "", + image_id: "", +}; + +function numOrUndefined(v: string): number | undefined { + if (v === "") return undefined; + const n = Number(v); + return Number.isNaN(n) ? undefined : n; +} + +export default function BreathingPage() { + const { data, loading, error, reload } = + useList("/breathing-templates"); + const toast = useToast(); + + const [editing, setEditing] = useState(null); + const [open, setOpen] = useState(false); + const [form, setForm] = useState(emptyForm); + const [saving, setSaving] = useState(false); + + const [deleting, setDeleting] = useState(null); + const [removing, setRemoving] = useState(false); + + function set(key: K, value: string) { + setForm((f) => ({ ...f, [key]: value })); + } + + function openCreate() { + setEditing(null); + setForm(emptyForm); + setOpen(true); + } + function openEdit(row: BreathingTemplate) { + setEditing(row); + setForm({ + name: row.name ?? "", + inhale: row.inhale != null ? String(row.inhale) : "", + exhale: row.exhale != null ? String(row.exhale) : "", + breath_hold: row.breath_hold != null ? String(row.breath_hold) : "", + duration: row.duration != null ? String(row.duration) : "", + description: row.description ?? "", + image_id: row.image_id != null ? String(row.image_id) : "", + }); + setOpen(true); + } + + async function save(e: React.FormEvent) { + e.preventDefault(); + setSaving(true); + try { + const payload = { + name: form.name, + inhale: numOrUndefined(form.inhale), + exhale: numOrUndefined(form.exhale), + breath_hold: numOrUndefined(form.breath_hold), + duration: numOrUndefined(form.duration), + description: form.description, + image_id: numOrUndefined(form.image_id), + }; + if (editing) { + // Update is JSON on /breathing-templates/:id + await apiFetch(`/breathing-templates/${editing.id}`, { + method: "PUT", + body: payload, + }); + toast.success("قالب تنفس ویرایش شد."); + } else { + // Create is multipart on /breathing-templates + await apiFetch("/breathing-templates", { + method: "POST", + body: toFormData(payload), + }); + toast.success("قالب تنفس افزوده شد."); + } + setOpen(false); + reload(); + } catch (err) { + toast.error(err instanceof ApiError ? err.message : "خطا در ذخیره‌سازی"); + } finally { + setSaving(false); + } + } + + async function confirmDelete() { + if (!deleting) return; + setRemoving(true); + try { + await apiFetch(`/breathing-templates/${deleting.id}`, { + method: "DELETE", + }); + toast.success("قالب تنفس حذف شد."); + setDeleting(null); + reload(); + } catch (err) { + toast.error(err instanceof ApiError ? err.message : "خطا در حذف"); + } finally { + setRemoving(false); + } + } + + const columns: Column[] = [ + { key: "id", header: "شناسه", render: (r) => toFa(r.id), className: "w-20" }, + { key: "name", header: "نام", render: (r) => r.name ?? "—" }, + { + key: "inhale", + header: "دم", + render: (r) => (r.inhale != null ? toFa(r.inhale) : "—"), + }, + { + key: "exhale", + header: "بازدم", + render: (r) => (r.exhale != null ? toFa(r.exhale) : "—"), + }, + { + key: "breath_hold", + header: "حبس نفس", + render: (r) => (r.breath_hold != null ? toFa(r.breath_hold) : "—"), + }, + { + key: "duration", + header: "مدت زمان", + render: (r) => (r.duration != null ? formatDuration(r.duration) : "—"), + }, + ]; + + return ( +
+ } onClick={openCreate}> + قالب جدید + + } + /> + + } onClick={openCreate}> + قالب جدید + + } + actions={(row) => ( + <> + + + + )} + /> + + setOpen(false)} + title={editing ? "ویرایش قالب تنفس" : "قالب تنفس جدید"} + footer={ + <> + + + + } + > +
+ + set("name", e.target.value)} + placeholder="مثلاً تنفس آرام‌بخش" + required + autoFocus + /> + +
+ + set("inhale", e.target.value)} + dir="ltr" + /> + + + set("exhale", e.target.value)} + dir="ltr" + /> + + + set("breath_hold", e.target.value)} + dir="ltr" + /> + + + set("duration", e.target.value)} + dir="ltr" + /> + +
+ + set("image_id", e.target.value)} + dir="ltr" + /> + + +