commit 0c51b3005961409a3e57e663d7dd09f14fab17ba Author: Amirmahdi Nourkazemi Date: Fri Aug 7 09:40:16 2026 +0330 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfc3442 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/.next +/out +next-env.d.ts +*.tsbuildinfo +.env*.local diff --git a/app/avatars/page.tsx b/app/avatars/page.tsx new file mode 100644 index 0000000..11cb111 --- /dev/null +++ b/app/avatars/page.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { Upload, Loader2 } from "lucide-react"; +import Shell from "@/components/Shell"; +import EditableTable, { Col } from "@/components/EditableTable"; +import { TableSkeleton } from "@/components/Skeleton"; +import Thumb from "@/components/Thumb"; +import { api, getAuth, ASSET_BASE } from "@/lib/api"; +import { toastError, toastSuccess } from "@/lib/toast"; + +type Row = Record; + +const COLS: Col[] = [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "price_coins", label: "قیمت (سکه)", type: "number" }, + { key: "vip", label: "VIP رایگان", type: "bool" }, + { key: "sort", label: "ترتیب", type: "number" }, +]; + +const API = + process.env.NEXT_PUBLIC_API_URL || "https://hakem.approagency.ir/admin/api"; + +export default function AvatarsPage() { + const [rows, setRows] = useState([]); + const [ver, setVer] = useState(1); // تازه‌سازیِ کشِ تصویر پس از آپلود + const [loading, setLoading] = useState(true); + + async function load() { + setLoading(true); + try { + const r = await api.get<{ avatars: Row[] }>("/avatars"); + setRows(r.avatars || []); + setVer((v) => v + 1); + } finally { + setLoading(false); + } + } + useEffect(() => { + load(); + }, []); + + return ( + +

+ شخصیتِ آواتار که کاربر می‌خرد و در کلِ بازی (سرِ میز، پروفایل و لابی) دیده + می‌شود. ابتدا ردیف را با شناسه و قیمت بسازید، سپس یک تصویرِ{" "} + png یا svg (ترجیحاً مربع و با پس‌زمینه‌ی شفاف) + آپلود کنید. +

+ {loading ? ( + + ) : ( + { + await api.post("/avatars", row); + await load(); + }} + onDelete={async (id) => { + if (!confirm("حذف شخصیت؟")) return; + await api.del(`/avatars?id=${encodeURIComponent(id)}`); + await load(); + }} + preview={(row) => ( + + )} + extra={(row) => } + /> + )} +
+ ); +} + +function UploadBtn({ id, onDone }: { id: string; onDone: () => void }) { + const ref = useRef(null); + const [busy, setBusy] = useState(false); + + async function upload(file: File) { + setBusy(true); + try { + const fd = new FormData(); + fd.append("image", file); + const auth = getAuth(); + const res = await fetch(`${API}/avatars/${encodeURIComponent(id)}/upload`, { + method: "POST", + headers: auth ? { Authorization: `Basic ${auth}` } : {}, + body: fd, + }); + if (!res.ok) throw new Error("آپلود ناموفق بود"); + toastSuccess("تصویر آپلود شد"); + onDone(); + } catch (e) { + toastError((e as Error).message); + } finally { + setBusy(false); + } + } + + return ( + <> + + { + const f = e.target.files?.[0]; + if (f) upload(f); + e.target.value = ""; + }} + /> + + ); +} diff --git a/app/carpets/page.tsx b/app/carpets/page.tsx new file mode 100644 index 0000000..cd2b9dd --- /dev/null +++ b/app/carpets/page.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { Upload, Loader2 } from "lucide-react"; +import Shell from "@/components/Shell"; +import EditableTable, { Col } from "@/components/EditableTable"; +import Thumb from "@/components/Thumb"; +import { api, getAuth, ASSET_BASE } from "@/lib/api"; +import { toastError, toastSuccess } from "@/lib/toast"; + +type Row = Record; + +const COLS: Col[] = [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "price_coins", label: "قیمت (سکه)", type: "number" }, + { key: "vip", label: "VIP رایگان", type: "bool" }, + { key: "sort", label: "ترتیب", type: "number" }, +]; + +const API = + process.env.NEXT_PUBLIC_API_URL || "https://hakem.approagency.ir/admin/api"; + +export default function CarpetsPage() { + const [rows, setRows] = useState([]); + const [ver, setVer] = useState(1); // برای تازه‌سازیِ کشِ تصویر پس از آپلود + + async function load() { + const r = await api.get<{ carpets: Row[] }>("/carpets"); + setRows(r.carpets || []); + setVer((v) => v + 1); + } + useEffect(() => { + load(); + }, []); + + return ( + +

+ هر فرش به‌جای میزِ بازی استفاده می‌شود. پس از ساختِ ردیف، تصویرِ jpg آپلود کنید. + شناسهٔ classic یعنی میزِ پیش‌فرضِ بدونِ تصویر. +

+ { + await api.post("/carpets", row); + await load(); + }} + onDelete={async (id) => { + if (!confirm("حذف فرش؟")) return; + await api.del(`/carpets?id=${encodeURIComponent(id)}`); + await load(); + }} + preview={(row) => + String(row.id) === "classic" ? ( + پیش‌فرض + ) : ( + + ) + } + extra={(row) => } + /> +
+ ); +} + +function UploadBtn({ id, onDone }: { id: string; onDone: () => void }) { + const ref = useRef(null); + const [busy, setBusy] = useState(false); + + async function upload(file: File) { + setBusy(true); + try { + const fd = new FormData(); + fd.append("image", file); + const auth = getAuth(); + const res = await fetch(`${API}/carpets/${encodeURIComponent(id)}/upload`, { + method: "POST", + headers: auth ? { Authorization: `Basic ${auth}` } : {}, + body: fd, + }); + if (!res.ok) throw new Error("آپلود ناموفق بود"); + toastSuccess("تصویر آپلود شد"); + onDone(); + } catch (e) { + toastError((e as Error).message); + } finally { + setBusy(false); + } + } + + return ( + <> + + { + const f = e.target.files?.[0]; + if (f) upload(f); + e.target.value = ""; + }} + /> + + ); +} diff --git a/app/chat/page.tsx b/app/chat/page.tsx new file mode 100644 index 0000000..1159aaa --- /dev/null +++ b/app/chat/page.tsx @@ -0,0 +1,116 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Trash2, Plus } from "lucide-react"; +import Shell from "@/components/Shell"; +import EditableTable, { Col } from "@/components/EditableTable"; +import { api } from "@/lib/api"; + +type Row = Record; + +const PACK_COLS: Col[] = [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "kind", label: "نوع (text/emoji)" }, + { key: "price_coins", label: "قیمت (سکه)", type: "number" }, + { key: "vip", label: "VIP رایگان", type: "bool" }, + { key: "sort", label: "ترتیب", type: "number" }, +]; + +export default function ChatPage() { + const [packs, setPacks] = useState([]); + const [messages, setMessages] = useState([]); + const [sel, setSel] = useState(""); + const [newMsg, setNewMsg] = useState(""); + + async function load() { + const r = await api.get<{ packs: Row[]; messages: Row[] }>("/chat"); + setPacks(r.packs || []); + setMessages(r.messages || []); + if (!sel && r.packs?.length) setSel(String(r.packs[0].id)); + } + useEffect(() => { + load(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + const packMsgs = messages.filter((m) => String(m.pack_id) === sel); + + async function addMsg() { + if (!sel || !newMsg.trim()) return; + await api.post("/chat/message", { pack_id: sel, body: newMsg.trim(), sort: packMsgs.length }); + setNewMsg(""); + await load(); + } + + return ( + +

بسته‌ها

+ { + await api.post("/chat/pack", row); + await load(); + }} + onDelete={async (id) => { + if (!confirm("حذف بسته و پیام‌هایش؟")) return; + await api.del(`/chat/pack?id=${encodeURIComponent(id)}`); + await load(); + }} + /> + +
+

پیام‌ها

+ +
+ +
+
+ setNewMsg(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && addMsg()} + /> + +
+
+ {packMsgs.map((m) => ( + + {String(m.body)} + + + ))} + {packMsgs.length === 0 && ( + پیامی نیست + )} +
+
+
+ ); +} diff --git a/app/frames/page.tsx b/app/frames/page.tsx new file mode 100644 index 0000000..ee544ae --- /dev/null +++ b/app/frames/page.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Shell from "@/components/Shell"; +import EditableTable, { Col } from "@/components/EditableTable"; +import { api } from "@/lib/api"; + +type Row = Record; + +const COLS: Col[] = [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "price_coins", label: "قیمت (سکه)", type: "number" }, + { key: "vip", label: "VIP رایگان", type: "bool" }, + { key: "c1", label: "رنگ ۱ (hex)" }, + { key: "c2", label: "رنگ ۲ (hex)" }, + { key: "sort", label: "ترتیب", type: "number" }, + { key: "enabled", label: "فعال", type: "bool" }, +]; + +const hex = (s: unknown) => { + const v = String(s || "").replace("#", "").trim(); + return /^[0-9a-fA-F]{6}$/.test(v) ? `#${v}` : null; +}; + +function FramePreview({ row }: { row: Record }) { + const a = hex(row.c1); + const b = hex(row.c2); + if (!a || !b) { + return رتبه; + } + return ( +
+ ); +} + +export default function FramesPage() { + const [rows, setRows] = useState([]); + + async function load() { + const r = await api.get<{ frames: Row[] }>("/frames"); + setRows(r.frames || []); + } + useEffect(() => { + load(); + }, []); + + return ( + +

+ قابِ آواتار (کازمتیک). رنگِ گرادیان را با دو کدِ hex (مثلِ F3D27A) تعیین + کنید؛ قابِ جدید بدونِ به‌روزرسانیِ اپ در بازی دیده می‌شود. شناسهٔ none = قابِ رتبه. +

+ { + await api.post("/frames", row); + await load(); + }} + onDelete={async (id) => { + if (!confirm("حذف قاب؟")) return; + await api.del(`/frames?id=${encodeURIComponent(id)}`); + await load(); + }} + preview={(row) => } + /> +
+ ); +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..0cd2893 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,188 @@ +@import "tailwindcss"; + +/* فونتِ دانا — همان فونتِ اپلیکیشن. basePath=/panel پس مسیرِ public با /panel آغاز می‌شود. */ +@font-face { + font-family: "Dana"; + src: url("/panel/fonts/dana_regular.ttf") format("truetype"); + font-weight: 400; + font-display: swap; +} +@font-face { + font-family: "Dana"; + src: url("/panel/fonts/dana_medium.ttf") format("truetype"); + font-weight: 500; + font-display: swap; +} +@font-face { + font-family: "Dana"; + src: url("/panel/fonts/dana_bold.ttf") format("truetype"); + font-weight: 700; + font-display: swap; +} + +:root { + --bg: #0e2347; + --bg-dark: #081428; + --panel: #17345c; + --panel-2: #0c2848; + --gold: #e9b949; + --gold-dark: #b8860b; + --line: #1e5fa8; +} + +html, +body { + background: var(--bg-dark); + color: #e8eef7; + font-family: "Dana", -apple-system, "Segoe UI", Tahoma, sans-serif; +} + +/* اسکرول‌بار تیره */ +::-webkit-scrollbar { + width: 9px; + height: 9px; +} +::-webkit-scrollbar-thumb { + background: #1e3a63; + border-radius: 6px; +} + +.card { + background: linear-gradient(160deg, var(--panel), var(--panel-2)); + border: 1px solid #1e3a63; + border-radius: 16px; +} +.btn { + border-radius: 10px; + padding: 8px 14px; + font-weight: 700; + font-size: 13px; + cursor: pointer; + transition: filter 0.15s; +} +.btn:hover { + filter: brightness(1.1); +} +.btn:disabled { + opacity: 0.5; + cursor: default; +} +.btn-gold { + background: linear-gradient(180deg, var(--gold), var(--gold-dark)); + color: #04101f; +} +.btn-ghost { + background: transparent; + border: 1px solid var(--line); + color: #cfe0f5; +} +.btn-danger { + background: #7f1d1d; + color: #fecaca; +} +.inp { + background: #0b1c39; + border: 1px solid #1e3a63; + border-radius: 8px; + padding: 7px 10px; + font-size: 13px; + color: #e8eef7; + width: 100%; +} +.inp:focus { + outline: none; + border-color: var(--gold); +} +table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} +th, +td { + padding: 8px 10px; + text-align: right; + border-bottom: 1px solid #16305a; + white-space: nowrap; +} +th { + color: #8ab4e8; + font-weight: 700; +} + +/* نشانک‌ها — تن‌های وضعیت/نوع */ +.badge { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + line-height: 1.6; + white-space: nowrap; +} +.badge::before { + content: ""; + width: 6px; + height: 6px; + border-radius: 999px; + background: currentColor; + flex: none; +} +.badge-gold { background: rgba(233, 185, 73, 0.14); color: #e9b949; } +.badge-blue { background: rgba(97, 165, 255, 0.14); color: #7bb3ff; } +.badge-purple{ background: rgba(168, 130, 255, 0.14); color: #bda6ff; } +.badge-green { background: rgba(74, 222, 128, 0.12); color: #6ee7a0; } +.badge-red { background: rgba(248, 113, 113, 0.12); color: #fca5a5; } +.badge-gray { background: rgba(148, 163, 184, 0.14); color: #cbd5e1; } + +/* اسکلتِ لودینگ */ +.skeleton { + position: relative; + overflow: hidden; + background: #163a63; + border-radius: 6px; +} +.skeleton::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.06), transparent); + animation: shimmer 1.4s infinite; +} +@keyframes shimmer { + 100% { transform: translateX(100%); } +} + +/* حلقه‌ی فوکوسِ طلایی برای دسترسی‌پذیری */ +:focus-visible { + outline: 2px solid var(--gold); + outline-offset: 2px; + border-radius: 4px; +} + +/* نوار ابزارِ فیلترها — در موبایل جستجو تمام‌عرض می‌شود */ +.toolbar { + display: flex; + flex-wrap: wrap; + gap: 10px; + align-items: center; +} +.toolbar .inp, +.toolbar select { + width: auto; + min-width: 130px; +} +@media (max-width: 640px) { + .toolbar { + flex-direction: column; + align-items: stretch; + } + .toolbar .inp, + .toolbar select, + .toolbar button { + width: 100%; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..8901737 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next"; +import "./globals.css"; +import Toaster from "@/components/Toaster"; + +export const metadata: Metadata = { + title: "پنل مدیریت حکم‌شو", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + + ); +} diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..d1273d7 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { Trophy } from "lucide-react"; +import { api } from "@/lib/api"; + +export default function LoginPage() { + const router = useRouter(); + const [user, setUser] = useState(""); + const [pass, setPass] = useState(""); + const [busy, setBusy] = useState(false); + const [err, setErr] = useState(""); + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setBusy(true); + setErr(""); + const ok = await api.login(user.trim(), pass); + setBusy(false); + if (ok) router.replace("/"); + else setErr("نام کاربری یا رمز اشتباه است"); + } + + return ( +
+
+
+ +

+ پنل مدیریت حکم‌شو +

+
+ + + {err &&

{err}

} + +
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..29d931d --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,180 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Link from "next/link"; +import { + Users, + ShoppingBag, + Gamepad2, + Coins, + Trophy, + RotateCcw, + Wallet, +} from "lucide-react"; +import Shell from "@/components/Shell"; +import StatCard from "@/components/StatCard"; +import Badge from "@/components/Badge"; +import RevenueChart from "@/components/RevenueChart"; +import { fmtNum } from "@/components/NumberInput"; +import { api } from "@/lib/api"; +import { KIND_LABEL, KIND_TONE } from "@/lib/labels"; + +type Recent = { + id: number; + mobile: string; + name: string; + kind: string; + product_id: string; + product_title: string; + price_toman: number; + created_local: string; +}; + +type Dash = { + users: number; + purchases: number; + games: number; + coins: number; + tournaments: number; + season: number; + revenue: number; + revenue_today: { count: number; revenue: number }; + revenue_14d: { date: string; revenue: number; count: number }[]; + recent: Recent[]; +}; + +export default function DashboardPage() { + const [d, setD] = useState(null); + const [err, setErr] = useState(""); + + useEffect(() => { + api + .get("/dashboard") + .then(setD) + .catch((e) => setErr(e.message)); + }, []); + + async function resetSeason() { + if (!confirm("فصلِ رتبه‌بندی صفر شود؟ این کار برگشت‌ناپذیر است.")) return; + await api.post("/season/reset"); + const nd = await api.get("/dashboard"); + setD(nd); + } + + const cards = [ + { label: "درآمد کل", value: d ? fmtNum(d.revenue) : undefined, suffix: "تومان", icon: Wallet, c: "var(--gold)" }, + { label: "کاربران", value: d ? fmtNum(d.users) : undefined, icon: Users, c: "#60a5fa" }, + { label: "خریدها", value: d ? fmtNum(d.purchases) : undefined, icon: ShoppingBag, c: "#c084fc" }, + { label: "بازی‌ها", value: d ? fmtNum(d.games) : undefined, icon: Gamepad2, c: "#4ade80" }, + { label: "کل سکه‌ها", value: d ? fmtNum(d.coins) : undefined, icon: Coins, c: "var(--gold)" }, + { label: "تورنومنت‌ها", value: d ? fmtNum(d.tournaments) : undefined, icon: Trophy, c: "#fbbf24" }, + ]; + + return ( + + {err &&

{err}

} + +
+ {cards.map((c) => ( + } + color={c.c} + loading={!d && !err} + /> + ))} +
+ +
+
+
+
درآمدِ ۱۴ روزِ گذشته
+
بر اساسِ پرداخت‌های تأییدشده
+
+ {d && ( +
+ + {fmtNum(d.revenue_today.revenue)} + + تومان امروز +
+ )} +
+ {d ? ( + + ) : ( +
+ )} +
+ +
+
+
آخرین خریدها
+ + همه‌ی تراکنش‌ها + +
+ + + + + + + + + + + + {d?.recent.map((r) => ( + + + + + + + + ))} + {d && d.recent.length === 0 && ( + + + + )} + +
کاربرمحصولنوعمبلغزمان (تهران)
+
{r.name || "—"}
+
+ {r.mobile} +
+
{r.product_title || r.product_id} + + {KIND_LABEL[r.kind] ?? (r.kind || "نامشخص")} + + + + {fmtNum(r.price_toman)} + {" "} + تومان + + {r.created_local} +
+ هنوز خریدی ثبت نشده +
+
+ +
+
+
فصل رتبه‌بندی: {d?.season ?? "—"}
+
+ با ریست، امتیازِ رتبه‌ی همه صفر و فصلِ جدید آغاز می‌شود. +
+
+ +
+ + ); +} diff --git a/app/rank-tiers/page.tsx b/app/rank-tiers/page.tsx new file mode 100644 index 0000000..c90da4b --- /dev/null +++ b/app/rank-tiers/page.tsx @@ -0,0 +1,151 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Shell from "@/components/Shell"; +import { TableSkeleton } from "@/components/Skeleton"; +import { api } from "@/lib/api"; +import { toastError } from "@/lib/toast"; + +type Tier = { + id: string; + label: string; + c1: string; + c2: string; + sort: number; +}; + +const hex = (s: string) => { + const v = String(s || "").replace("#", "").trim(); + return /^[0-9a-fA-F]{6}$/.test(v) ? `#${v}` : null; +}; + +function Swatch({ c1, c2 }: { c1: string; c2: string }) { + const a = hex(c1); + const b = hex(c2); + return ( +
+ ); +} + +export default function RankTiersPage() { + const [tiers, setTiers] = useState([]); + const [busy, setBusy] = useState(null); + const [loading, setLoading] = useState(true); + + async function load() { + setLoading(true); + try { + const r = await api.get<{ rank_tiers: Tier[] }>("/rank-tiers"); + setTiers(r.rank_tiers || []); + } finally { + setLoading(false); + } + } + useEffect(() => { + load(); + }, []); + + const patch = (id: string, key: keyof Tier, val: string | number) => + setTiers((ts) => ts.map((t) => (t.id === id ? { ...t, [key]: val } : t))); + + async function save(t: Tier) { + setBusy(t.id); + try { + await api.post("/rank-tiers", t); + } catch (e) { + toastError((e as Error).message); + } finally { + setBusy(null); + } + } + + return ( + +

+ رنگ و برچسبِ رتبه‌ها (برنز تا پادشاه). این‌ها در حلقه‌ی آواتار و نشانِ + رتبه‌ی داخلِ بازی دیده می‌شوند و بدونِ به‌روزرسانیِ اپ اعمال می‌شوند. + رنگ را با کدِ hex (مثلِ FFD54F) وارد کنید. +

+ {loading ? ( + + ) : ( +
+ + + + + + + + + + + + + + {tiers.map((t) => ( + + + + + + + + + + ))} + +
پیش‌نمایششناسهبرچسبرنگ ۱ (hex)رنگ ۲ (hex)ترتیب
+ + {t.id} + patch(t.id, "label", e.target.value)} + /> + + patch(t.id, "c1", e.target.value)} + /> + + patch(t.id, "c2", e.target.value)} + /> + + patch(t.id, "sort", +e.target.value)} + /> + + +
+
+ )} +
+ ); +} diff --git a/app/shop/page.tsx b/app/shop/page.tsx new file mode 100644 index 0000000..bf20e3a --- /dev/null +++ b/app/shop/page.tsx @@ -0,0 +1,227 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { Upload } from "lucide-react"; +import Shell from "@/components/Shell"; +import EditableTable, { Col } from "@/components/EditableTable"; +import { TableSkeleton } from "@/components/Skeleton"; +import Thumb from "@/components/Thumb"; +import { api, getAuth, ASSET_BASE } from "@/lib/api"; +import { toastError } from "@/lib/toast"; + +type Catalog = Record[]>; + +const TABS: { key: string; label: string; cols: Col[]; tpl: Record }[] = [ + { + key: "coin", + label: "سکه", + cols: [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "coins", label: "سکه", type: "number" }, + { key: "vip_days", label: "VIP روز", type: "number" }, + { key: "price_toman", label: "قیمت", type: "number" }, + { key: "bonus_pct", label: "٪هدیه", type: "number" }, + { key: "sku", label: "SKU", readonly: true }, + { key: "sort", label: "ترتیب", type: "number" }, + { key: "enabled", label: "فعال", type: "bool" }, + ], + tpl: { id: "", title: "", coins: 0, vip_days: 0, price_toman: 0, bonus_pct: 0, sort: 0, enabled: true }, + }, + { + key: "ticket", + label: "بلیط", + cols: [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "tickets", label: "بلیط", type: "number" }, + { key: "price_toman", label: "قیمت", type: "number" }, + { key: "sku", label: "SKU", readonly: true }, + { key: "sort", label: "ترتیب", type: "number" }, + { key: "enabled", label: "فعال", type: "bool" }, + ], + tpl: { id: "", title: "", tickets: 0, price_toman: 0, sort: 0, enabled: true }, + }, + { + key: "card", + label: "کارت", + cols: [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "price_coins", label: "قیمت (سکه)", type: "number" }, + { key: "sort", label: "ترتیب", type: "number" }, + { key: "enabled", label: "فعال", type: "bool" }, + ], + tpl: { id: "", title: "", price_coins: 0, sort: 0, enabled: true }, + }, + { + key: "booster", + label: "تجهیزات", + cols: [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "multiplier", label: "ضریب", type: "number" }, + { key: "hours", label: "ساعت", type: "number" }, + { key: "price_toman", label: "قیمت", type: "number" }, + { key: "sku", label: "SKU", readonly: true }, + { key: "sort", label: "ترتیب", type: "number" }, + { key: "enabled", label: "فعال", type: "bool" }, + ], + tpl: { id: "", title: "", multiplier: 2, hours: 24, price_toman: 0, sort: 0, enabled: true }, + }, + { + key: "vip", + label: "VIP", + cols: [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "months", label: "ماه", type: "number" }, + { key: "price_toman", label: "قیمت", type: "number" }, + { key: "sku", label: "SKU", readonly: true }, + { key: "sort", label: "ترتیب", type: "number" }, + { key: "enabled", label: "فعال", type: "bool" }, + ], + tpl: { id: "", title: "", months: 1, price_toman: 0, sort: 0, enabled: true }, + }, + { + key: "tier", + label: "میزها", + cols: [ + { key: "id", label: "شناسه" }, + { key: "title", label: "عنوان" }, + { key: "hands", label: "دست", type: "number" }, + { key: "entry", label: "ورودی", type: "number" }, + { key: "prize", label: "جایزه", type: "number" }, + { key: "xp", label: "XP", type: "number" }, + { key: "trophy", label: "جام", type: "number" }, + { key: "rank_reward", label: "رتبه", type: "number" }, + { key: "sort", label: "ترتیب", type: "number" }, + { key: "enabled", label: "فعال", type: "bool" }, + ], + tpl: { id: "", title: "", hands: 7, entry: 0, prize: 0, xp: 0, trophy: 0, rank_reward: 0, sort: 0, enabled: true }, + }, +]; + +export default function ShopPage() { + const [cat, setCat] = useState({}); + const [tab, setTab] = useState("coin"); + const [ver, setVer] = useState(1); + const [loading, setLoading] = useState(true); + + async function load() { + setLoading(true); + try { + setCat(await api.get("/catalog")); + setVer((v) => v + 1); + } finally { + setLoading(false); + } + } + useEffect(() => { + load(); + }, []); + + const active = TABS.find((t) => t.key === tab)!; + const isCard = tab === "card"; + + return ( + +
+ {TABS.map((t) => ( + + ))} +
+ {loading ? ( + + ) : ( + { + await api.post(`/catalog/${tab}`, row); + await load(); + }} + onDelete={async (id) => { + if (!confirm("حذف آیتم؟")) return; + await api.del(`/catalog/${tab}?id=${encodeURIComponent(id)}`); + await load(); + }} + preview={ + isCard + ? (row) => + String(row.id) === "simple" ? ( + پیش‌فرض + ) : ( + + ) + : undefined + } + extra={isCard ? (row) => : undefined} + /> + )} +

+ {isCard + ? "برای هر اسکینِ کارت، یک فایلِ zip از تصاویرِ کارت‌ها (مثلِ AS.png، KH.png، back.jpg) آپلود کنید." + : "SKU برای سکه/بلیط/تجهیزات/VIP هنگام ساخت خودکار تولید می‌شود؛ همان را در پنل مایکت/کافه‌بازار ثبت کنید."} +

+
+ ); +} + +// آپلودِ zipِ اسکینِ کارت (به /catalog/card/{id}/upload، فیلد "deck"). +function DeckUpload({ id, onDone }: { id: string; onDone: () => void }) { + const ref = useRef(null); + const [busy, setBusy] = useState(false); + + async function upload(file: File) { + setBusy(true); + try { + const fd = new FormData(); + fd.append("deck", file); + const auth = getAuth(); + const res = await fetch(`${ASSET_BASE}/admin/api/catalog/card/${encodeURIComponent(id)}/upload`, { + method: "POST", + headers: auth ? { Authorization: `Basic ${auth}` } : {}, + body: fd, + }); + if (!res.ok) throw new Error("آپلودِ zip ناموفق بود"); + onDone(); + } catch (e) { + toastError((e as Error).message); + } finally { + setBusy(false); + } + } + + return ( + <> + + { + const f = e.target.files?.[0]; + if (f) upload(f); + e.target.value = ""; + }} + /> + + ); +} diff --git a/app/tournaments/page.tsx b/app/tournaments/page.tsx new file mode 100644 index 0000000..46ea369 --- /dev/null +++ b/app/tournaments/page.tsx @@ -0,0 +1,258 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Trash2, Plus, Pencil, X } from "lucide-react"; +import Shell from "@/components/Shell"; +import Badge, { type BadgeTone } from "@/components/Badge"; +import Empty from "@/components/Empty"; +import { TableSkeleton } from "@/components/Skeleton"; +import { api } from "@/lib/api"; +import { toastError } from "@/lib/toast"; + +type Tournament = { + id: number; + title: string; + description: string; + entry_fee: number; + prizes: number[]; + prize_pool: number; + status: string; + players: number; + starts_local: string; + ends_local: string; +}; + +const STATUS: Record = { + active: { t: "در حال برگزاری", tone: "green" }, + upcoming: { t: "به‌زودی", tone: "gold" }, + ended: { t: "پایان‌یافته", tone: "gray" }, +}; + +const EMPTY = { + title: "", + description: "", + entry_fee: 0, + prizes: "", + starts_at: "", + ends_at: "", +}; + +// "2026-07-07 13:00" → "2026-07-07T13:00" (ورودیِ datetime-local) +const toLocalInput = (s: string) => (s || "").replace(" ", "T"); + +export default function TournamentsPage() { + const [list, setList] = useState([]); + const [form, setForm] = useState({ ...EMPTY }); + const [editingId, setEditingId] = useState(null); + const [busy, setBusy] = useState(false); + const [loading, setLoading] = useState(true); + + async function load() { + setLoading(true); + try { + const r = await api.get<{ tournaments: Tournament[] }>("/tournaments"); + setList(r.tournaments || []); + } finally { + setLoading(false); + } + } + useEffect(() => { + load(); + }, []); + + function startEdit(t: Tournament) { + setEditingId(t.id); + setForm({ + title: t.title, + description: t.description || "", + entry_fee: t.entry_fee, + prizes: (t.prizes || []).join(","), + starts_at: toLocalInput(t.starts_local), + ends_at: toLocalInput(t.ends_local), + }); + window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" }); + } + + function cancelEdit() { + setEditingId(null); + setForm({ ...EMPTY }); + } + + async function submit(e: React.FormEvent) { + e.preventDefault(); + setBusy(true); + try { + const payload = { + title: form.title, + description: form.description, + entry_fee: Number(form.entry_fee), + prizes: form.prizes + .split(",") + .map((s) => parseInt(s.trim(), 10)) + .filter((n) => !isNaN(n)), + starts_at: form.starts_at, + ends_at: form.ends_at, + }; + if (editingId) { + await api.put("/tournaments", { id: editingId, ...payload }); + } else { + await api.post("/tournaments", payload); + } + cancelEdit(); + await load(); + } catch (e) { + toastError((e as Error).message); + } finally { + setBusy(false); + } + } + + async function remove(id: number) { + if (!confirm("حذف تورنومنت؟")) return; + await api.del(`/tournaments?id=${id}`); + if (editingId === id) cancelEdit(); + await load(); + } + + return ( + + {loading ? ( + + ) : list.length === 0 ? ( + + ) : ( +
+ + + + + + + + + + + + + + + + {list.map((t) => { + const s = STATUS[t.status] || STATUS.ended; + return ( + + + + + + + + + + + + ); + })} + +
#عنوانورودیجوایزوضعیتبازیکنانشروع (تهران)پایان (تهران)
{t.id}{t.title}{t.entry_fee}{(t.prizes || []).join("، ")} + {s.t} + {t.players}{t.starts_local}{t.ends_local} + + +
+
+ )} + +
+

+ {editingId ? ( + <> + ویرایش تورنومنت #{editingId} + + + ) : ( + <> + افزودن تورنومنت + + )} +

+
+ + + + + + +
+

+ برای فعال‌شدنِ فوری، زمانِ شروع را کمی قبل از الان بگذارید. برد ۱۰۰ و شرکت ۲۵ امتیاز. +

+ +
+
+ ); +} diff --git a/app/transactions/page.tsx b/app/transactions/page.tsx new file mode 100644 index 0000000..221fa05 --- /dev/null +++ b/app/transactions/page.tsx @@ -0,0 +1,301 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + Search, + Wallet, + Sun, + CalendarDays, + ShoppingBag, + Users, + ChevronRight, + ChevronLeft, +} from "lucide-react"; +import Shell from "@/components/Shell"; +import StatCard from "@/components/StatCard"; +import Badge from "@/components/Badge"; +import { TableSkeleton } from "@/components/Skeleton"; +import Empty from "@/components/Empty"; +import { fmtNum } from "@/components/NumberInput"; +import { api } from "@/lib/api"; +import { KIND_LABEL, KIND_TONE, STATUS_LABEL, STATUS_TONE, STORE_LABEL } from "@/lib/labels"; + +type Purchase = { + id: number; + user_id: number; + mobile: string; + name: string; + store: string; + kind: string; + product_id: string; + coins: number; + tickets: number; + vip_days: number; + price_toman: number; + status: string; + product_title: string; + created_local: string; +}; + +type Stats = { + total_revenue: number; + verified_count: number; + unique_buyers: number; + avg_revenue: number; + today: { count: number; revenue: number }; + month: { count: number; revenue: number }; +}; + +type Resp = { + purchases: Purchase[]; + stats: Stats; + total: number; + page: number; + page_size: number; + pages: number; +}; + +function received(p: Purchase): string { + const parts: string[] = []; + if (p.coins) parts.push(`${fmtNum(p.coins)} سکه`); + if (p.tickets) parts.push(`${fmtNum(p.tickets)} بلیط`); + if (p.vip_days) parts.push(`${fmtNum(p.vip_days)} روز VIP`); + return parts.join("، ") || "—"; +} + +export default function TransactionsPage() { + const [q, setQ] = useState(""); + const [qApplied, setQApplied] = useState(""); + const [store, setStore] = useState(""); + const [kind, setKind] = useState(""); + const [status, setStatus] = useState("verified"); + const [page, setPage] = useState(1); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [err, setErr] = useState(""); + + useEffect(() => { + let alive = true; + setLoading(true); + setErr(""); + const params = new URLSearchParams(); + if (qApplied) params.set("q", qApplied); + if (store) params.set("store", store); + if (kind) params.set("kind", kind); + if (status) params.set("status", status); + params.set("page", String(page)); + api + .get(`/purchases?${params.toString()}`) + .then((r) => { + if (alive) setData(r); + }) + .catch((e) => { + if (alive) setErr(e.message); + }) + .finally(() => { + if (alive) setLoading(false); + }); + return () => { + alive = false; + }; + }, [qApplied, store, kind, status, page]); + + const s = data?.stats; + + return ( + + {err &&

{err}

} + +
+ } + color="var(--gold)" + loading={loading} + /> + } + color="#4ade80" + sub={s ? `${fmtNum(s.today.count)} خرید` : undefined} + loading={loading} + /> + } + color="#60a5fa" + loading={loading} + /> + } + color="#c084fc" + sub={s ? `میانگین ${fmtNum(s.avg_revenue)} تومان` : undefined} + loading={loading} + /> + } + color="#fbbf24" + loading={loading} + /> +
+ +
+
{ + e.preventDefault(); + setQApplied(q.trim()); + setPage(1); + }} + > + setQ(e.target.value)} + /> + +
+ + + +
+ + {loading ? ( + + ) : !data || data.purchases.length === 0 ? ( + + ) : ( + <> +
+ + + + + + + + + + + + + + + {data.purchases.map((p) => ( + + + + + + + + + + + ))} + +
کاربرمحصولنوعمبلغدریافتفروشگاهزمان (تهران)وضعیت
+
{p.name || "—"}
+
+ {p.mobile} · {p.user_id} +
+
+
{p.product_title || p.product_id}
+
+ {p.product_id} +
+
+ + {KIND_LABEL[p.kind] ?? (p.kind || "نامشخص")} + + + + {fmtNum(p.price_toman)} + {" "} + تومان + {received(p)}{STORE_LABEL[p.store] ?? p.store} + {p.created_local} + + + {STATUS_LABEL[p.status] ?? p.status} + +
+
+ +
+ + + صفحه {fmtNum(data.page)} از {fmtNum(data.pages)} — {fmtNum(data.total)} تراکنش + + +
+ + )} +
+ ); +} diff --git a/app/users/page.tsx b/app/users/page.tsx new file mode 100644 index 0000000..2180132 --- /dev/null +++ b/app/users/page.tsx @@ -0,0 +1,212 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Search, Coins, Gift, Loader2 } from "lucide-react"; +import Shell from "@/components/Shell"; +import NumberInput, { fmtNum } from "@/components/NumberInput"; +import Badge from "@/components/Badge"; +import Empty from "@/components/Empty"; +import { TableSkeleton } from "@/components/Skeleton"; +import { api } from "@/lib/api"; +import { toastError } from "@/lib/toast"; + +type User = { + id: number; + mobile: string; + first_name: string | null; + coins: number; + rank_points: number; + is_admin: number; +}; + +export default function UsersPage() { + const [q, setQ] = useState(""); + const [users, setUsers] = useState([]); + const [sel, setSel] = useState(null); + const [loading, setLoading] = useState(true); + + async function load(query = "") { + setLoading(true); + try { + const r = await api.get<{ users: User[] }>( + `/users${query ? `?q=${encodeURIComponent(query)}` : ""}`, + ); + setUsers(r.users || []); + } finally { + setLoading(false); + } + } + useEffect(() => { + load(); + }, []); + + return ( + +
{ + e.preventDefault(); + load(q); + }} + className="flex gap-2 mb-4" + > + setQ(e.target.value)} + /> + +
+ + {loading ? ( + + ) : users.length === 0 ? ( + + ) : ( +
+ + + + + + + + + + + + + + {users.map((u) => ( + + + + + + + + + + ))} + +
#موبایلنامسکهامتیاز رتبهادمین
{u.id}{u.mobile}{u.first_name || "—"} + {fmtNum(u.coins)} + {fmtNum(u.rank_points)}{u.is_admin ? مدیر : ""} + +
+
+ )} + + {sel && setSel(null)} onDone={() => load(q)} />} +
+ ); +} + +function ManageUser({ + user, + onClose, + onDone, +}: { + user: User; + onClose: () => void; + onDone: () => void; +}) { + const [coins, setCoins] = useState(0); + const [tickets, setTickets] = useState(0); + const [vipDays, setVipDays] = useState(30); + const [busy, setBusy] = useState(false); + + async function run(fn: () => Promise) { + setBusy(true); + try { + await fn(); + onDone(); + } catch (e) { + toastError((e as Error).message); + } finally { + setBusy(false); + } + } + + return ( +
+
e.stopPropagation()}> +

{user.first_name || user.mobile}

+

+ {user.mobile} · سکه: {fmtNum(user.coins)} +

+ +
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + +
+
+ ); +} diff --git a/components/Badge.tsx b/components/Badge.tsx new file mode 100644 index 0000000..9ed8d8d --- /dev/null +++ b/components/Badge.tsx @@ -0,0 +1,22 @@ +"use client"; + +const TONES = { + gold: "badge-gold", + blue: "badge-blue", + purple: "badge-purple", + green: "badge-green", + red: "badge-red", + gray: "badge-gray", +} as const; + +export type BadgeTone = keyof typeof TONES; + +export default function Badge({ + tone = "gray", + children, +}: { + tone?: BadgeTone; + children: React.ReactNode; +}) { + return {children}; +} diff --git a/components/EditableTable.tsx b/components/EditableTable.tsx new file mode 100644 index 0000000..61847bc --- /dev/null +++ b/components/EditableTable.tsx @@ -0,0 +1,197 @@ +"use client"; + +import { useState } from "react"; +import { Save, Trash2, Plus, Loader2 } from "lucide-react"; +import NumberInput from "./NumberInput"; +import { toastError, toastSuccess } from "@/lib/toast"; + +export type Col = { + key: string; + label: string; + type?: "text" | "number" | "bool"; + readonly?: boolean; +}; + +type Row = Record; + +// سلولِ ورودی — در سطحِ ماژول تعریف شده تا با هر کلید دوباره mount نشود (وگرنه +// اینپوت پس از یک حرف فوکوس را از دست می‌دهد). +function Cell({ + row, + col, + onChange, +}: { + row: Row; + col: Col; + onChange: (v: unknown) => void; +}) { + const v = row[col.key]; + if (col.type === "bool") { + return ( + onChange(e.target.checked)} + /> + ); + } + if (col.type === "number") { + // ورودیِ عددیِ گروه‌بندی‌شده (جداکننده‌ی هزارگان) و قابلِ تایپ با کیبورد. + return ( + + ); + } + return ( + onChange(e.target.value)} + /> + ); +} + +export default function EditableTable({ + cols, + rows, + onSave, + onDelete, + newTemplate, + extra, + preview, +}: { + cols: Col[]; + rows: Row[]; + onSave: (row: Row) => Promise; + onDelete: (id: string) => Promise; + newTemplate: Row; + /** ستون/دکمه‌ی اضافی برای هر ردیف (مثلاً آپلود تصویر). */ + extra?: (row: Row) => React.ReactNode; + /** پیش‌نمایشِ تصویرِ ابتدای ردیف (مثلاً فرش یا کارت). */ + preview?: (row: Row) => React.ReactNode; +}) { + const [draft, setDraft] = useState({ ...newTemplate }); + const [busy, setBusy] = useState(null); + + // نسخه‌ی محلیِ قابلِ‌ویرایش هر ردیف. + const [edit, setEdit] = useState>({}); + const rowState = (r: Row) => edit[String(r.id)] ?? r; + const patch = (id: string, key: string, val: unknown) => + setEdit((e) => ({ ...e, [id]: { ...(e[id] ?? rows.find((r) => String(r.id) === id)!), [key]: val } })); + + // پیش از ذخیره، هر ستون را به نوعِ اعلام‌شده تبدیل کن تا سرور عددِ رشته‌ای نگیرد + // (وگرنه 400: bad request). اعداد → number، بولین‌ها → boolean. + const coerce = (row: Row): Row => { + const out: Row = { ...row }; + for (const c of cols) { + if (c.type === "number") out[c.key] = Number(out[c.key] ?? 0) || 0; + else if (c.type === "bool") out[c.key] = !!out[c.key] && out[c.key] !== 0; + } + return out; + }; + + return ( +
+ + + + {preview && } + {cols.map((c) => ( + + ))} + + + + + {rows.map((r) => { + const id = String(r.id); + const rs = rowState(r); + return ( + + {preview && } + {cols.map((c) => ( + + ))} + + + ); + })} + {/* ردیفِ افزودن */} + + {preview && + ))} + + + +
پیش‌نمایش{c.label}
{preview(r)} + patch(id, c.key, v)} /> + + + + {extra?.(r)} +
} + {cols.map((c) => ( + + setDraft((d) => ({ ...d, [c.key]: v }))} + /> + + +
+
+ ); +} diff --git a/components/Empty.tsx b/components/Empty.tsx new file mode 100644 index 0000000..a2a968d --- /dev/null +++ b/components/Empty.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { Inbox } from "lucide-react"; + +export default function Empty({ + title = "چیزی پیدا نشد", + hint, +}: { + title?: string; + hint?: string; +}) { + return ( +
+
+ +
+
{title}
+ {hint &&
{hint}
} +
+ ); +} diff --git a/components/NumberInput.tsx b/components/NumberInput.tsx new file mode 100644 index 0000000..c0cb8a3 --- /dev/null +++ b/components/NumberInput.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useEffect, useState } from "react"; + +/** عددِ گروه‌بندی‌شده با جداکننده‌ی هزارگان (مثلِ 12,500). */ +export function fmtNum(n: number | null | undefined): string { + const v = Number(n ?? 0); + return Number.isFinite(v) ? v.toLocaleString("en-US") : "0"; +} + +/** + * ورودیِ عددی با جداکننده‌ی هزارگان که با کیبورد هم قابلِ تایپ است. + * برخلافِ که جداکننده را نمی‌پذیرد، این یک ورودیِ متنی است + * که هنگامِ تایپ زنده گروه‌بندی می‌کند و مقدارِ عددی را به onChange می‌دهد. + */ +export default function NumberInput({ + value, + onChange, + allowNegative = false, + className = "inp", + style, + disabled, +}: { + value: number; + onChange: (n: number) => void; + allowNegative?: boolean; + className?: string; + style?: React.CSSProperties; + disabled?: boolean; +}) { + const show = (n: number) => n.toLocaleString("en-US"); + const [text, setText] = useState(show(value)); + + // اگر مقدار از بیرون عوض شد (مثلاً ریست فرم)، متن را هم‌گام کن. + useEffect(() => { + const cur = parseInt(text.replace(/[^\d-]/g, "") || "0", 10) || 0; + if (cur !== value) setText(show(value)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]); + + return ( + { + let raw = e.target.value.replace(/[^\d-]/g, ""); + if (!allowNegative) raw = raw.replace(/-/g, ""); + raw = raw.replace(/(?!^)-/g, ""); // فقط منفیِ ابتدایی + const n = raw === "" || raw === "-" ? 0 : parseInt(raw, 10); + const grouped = + raw === "" ? "" : raw === "-" ? "-" : (n || 0).toLocaleString("en-US"); + setText(grouped); + onChange(Number.isNaN(n) ? 0 : n); + }} + /> + ); +} diff --git a/components/RevenueChart.tsx b/components/RevenueChart.tsx new file mode 100644 index 0000000..507b701 --- /dev/null +++ b/components/RevenueChart.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { fmtNum } from "@/components/NumberInput"; + +// نمودارِ درآمدِ ۱۴ روزِ گذشته — فقط CSS (بدون کتابخانه). میله‌های طلایی با +// نسبتِ به بیشترین مقدار؛ برچسبِ روز زیرِ هر میله و tooltipِ بومی روی هرکدام. +export default function RevenueChart({ + days, +}: { + days: { date: string; revenue: number; count: number }[]; +}) { + if (!days.length) return null; + const max = Math.max(...days.map((d) => d.revenue), 1); + return ( +
+
+ {days.map((d) => { + const pct = Math.round((d.revenue / max) * 100); + return ( +
0 ? 3 : 0)}%` }} + /> + ); + })} +
+
+ {days.map((d) => ( + + {d.date.slice(5)} + + ))} +
+
+ ); +} diff --git a/components/Shell.tsx b/components/Shell.tsx new file mode 100644 index 0000000..a1dd8f0 --- /dev/null +++ b/components/Shell.tsx @@ -0,0 +1,168 @@ +"use client"; + +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { + LayoutDashboard, + Trophy, + ShoppingBag, + Users, + ReceiptText, + Image as ImageIcon, + Frame as FrameIcon, + Smile, + Medal, + MessageSquare, + LogOut, + Menu, + X, + Loader2, +} from "lucide-react"; +import { clearAuth, isAuthed } from "@/lib/api"; + +const NAV = [ + { href: "/", label: "داشبورد", icon: LayoutDashboard }, + { href: "/tournaments", label: "تورنومنت‌ها", icon: Trophy }, + { href: "/shop", label: "فروشگاه", icon: ShoppingBag }, + { href: "/users", label: "کاربران", icon: Users }, + { href: "/transactions", label: "تراکنش‌ها", icon: ReceiptText }, + { href: "/carpets", label: "فرش‌ها", icon: ImageIcon }, + { href: "/frames", label: "قاب‌ها", icon: FrameIcon }, + { href: "/avatars", label: "شخصیت‌ها", icon: Smile }, + { href: "/rank-tiers", label: "رتبه‌ها", icon: Medal }, + { href: "/chat", label: "چت", icon: MessageSquare }, +]; + +export default function Shell({ + title, + subtitle, + children, +}: { + title: string; + subtitle?: string; + children: React.ReactNode; +}) { + const pathname = usePathname(); + const router = useRouter(); + const [ready, setReady] = useState(false); + const [open, setOpen] = useState(false); + + useEffect(() => { + if (!isAuthed()) { + router.replace("/login"); + } else { + setReady(true); + } + }, [router]); + + // با تغییرِ مسیر، منوی موبایل را ببند. + useEffect(() => { + setOpen(false); + }, [pathname]); + + if (!ready) { + return ( +
+
+ + در حال بارگذاری… +
+
+ ); + } + + // basePath را حذف و اسلشِ انتهایی را نرمال می‌کنیم تا مسیرِ فعال (مثلِ + // /transactions و /transactions/ برابر) درست تشخیص داده شود. + const cur = (pathname.replace(/^\/panel/, "") || "/").replace(/\/+$/, "") || "/"; + + const aside = ( + + ); + + return ( +
+ {/* دسکتاپ: سایدبارِ ثابت کنارِ محتوا */} +
{aside}
+ + {/* موبایل: منوی کشویی با پس‌زمینه‌ی تیره */} + {open && ( +
setOpen(false)} + aria-hidden + /> + )} +
+ {aside} +
+ +
+
+ +
+

{title}

+ {subtitle &&

{subtitle}

} +
+
+
{children}
+
+
+ ); +} diff --git a/components/Skeleton.tsx b/components/Skeleton.tsx new file mode 100644 index 0000000..8f274cd --- /dev/null +++ b/components/Skeleton.tsx @@ -0,0 +1,41 @@ +"use client"; + +export function Skeleton({ className = "" }: { className?: string }) { + return
; +} + +// اسکلتِ جدولِ در حالِ لود — با سطر/ستونِ دلخواه. +export function TableSkeleton({ + rows = 5, + cols = 5, +}: { + rows?: number; + cols?: number; +}) { + return ( +
+ + + + {Array.from({ length: cols }).map((_, i) => ( + + ))} + + + + {Array.from({ length: rows }).map((_, r) => ( + + {Array.from({ length: cols }).map((_, c) => ( + + ))} + + ))} + +
+ +
+ +
+
+ ); +} diff --git a/components/StatCard.tsx b/components/StatCard.tsx new file mode 100644 index 0000000..14c2902 --- /dev/null +++ b/components/StatCard.tsx @@ -0,0 +1,40 @@ +"use client"; + +// کارتِ آماریِ سربرگ — مقدارِ عددی LTR، واحد (مثلاً «تومان») جدا و RTL. +export default function StatCard({ + label, + value, + suffix, + icon, + color = "var(--gold)", + sub, + loading, +}: { + label: string; + value?: React.ReactNode; + suffix?: string; + icon?: React.ReactNode; + color?: string; + sub?: React.ReactNode; + loading?: boolean; +}) { + return ( +
+
+ {label} + {icon && {icon}} +
+ {loading ? ( +
+ ) : ( +
+ + {value ?? "—"} + + {suffix && {suffix}} +
+ )} + {sub &&
{sub}
} +
+ ); +} diff --git a/components/Thumb.tsx b/components/Thumb.tsx new file mode 100644 index 0000000..e26c9b8 --- /dev/null +++ b/components/Thumb.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { useState } from "react"; +import { ImageOff } from "lucide-react"; + +/** پیش‌نمایشِ تصویر با جایگزینِ خطا. اگر src بارگذاری نشود، آیکنِ «بدون تصویر». */ +export default function Thumb({ + src, + alt = "", + size = 44, + rounded = 8, +}: { + src: string; + alt?: string; + size?: number; + rounded?: number; +}) { + const [failed, setFailed] = useState(false); + if (failed) { + return ( +
+ +
+ ); + } + return ( + // eslint-disable-next-line @next/next/no-img-element + {alt} setFailed(true)} + style={{ + width: size, + height: size, + borderRadius: rounded, + objectFit: "cover", + border: "1px solid #1e3a63", + }} + /> + ); +} diff --git a/components/Toaster.tsx b/components/Toaster.tsx new file mode 100644 index 0000000..901472b --- /dev/null +++ b/components/Toaster.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { AlertCircle, CheckCircle2, Info } from "lucide-react"; +import { subscribe, ToastItem } from "@/lib/toast"; + +const STYLE: Record< + ToastItem["type"], + { border: string; icon: React.ReactNode } +> = { + error: { border: "#e5484d", icon: }, + success: { border: "#3fa34d", icon: }, + info: { border: "#e9b949", icon: }, +}; + +export default function Toaster() { + const [items, setItems] = useState([]); + useEffect(() => subscribe(setItems), []); + + return ( +
+ {items.map((t) => ( +
+ {STYLE[t.type].icon} + {t.msg} +
+ ))} + +
+ ); +} diff --git a/lib/api.ts b/lib/api.ts new file mode 100644 index 0000000..6c32c5c --- /dev/null +++ b/lib/api.ts @@ -0,0 +1,86 @@ +// کلاینتِ APIِ پنل. احرازِ هویت با Basic Auth (همان کاربر/پسوردِ پنل ادمین) +// که پس از ورود در localStorage نگه‌داری می‌شود و در هر درخواست فرستاده می‌شود. + +// همان مبدأ (nginx، /admin/api → بک‌اند). برای توسعه‌ی محلی با +// NEXT_PUBLIC_API_URL قابلِ override است. +const BASE = + process.env.NEXT_PUBLIC_API_URL || "https://hakem.approagency.ir/admin/api"; + +// مبدأِ فایل‌های استاتیک (کارت/فرش): همان دامنه بدونِ /admin/api. +export const ASSET_BASE = BASE.replace(/\/admin\/api\/?$/, ""); + +const AUTH_KEY = "hakem_admin_auth"; + +export function saveAuth(user: string, pass: string) { + if (typeof window !== "undefined") { + localStorage.setItem(AUTH_KEY, btoa(`${user}:${pass}`)); + } +} +export function clearAuth() { + if (typeof window !== "undefined") localStorage.removeItem(AUTH_KEY); +} +export function getAuth(): string | null { + if (typeof window === "undefined") return null; + return localStorage.getItem(AUTH_KEY); +} +export function isAuthed(): boolean { + return !!getAuth(); +} + +class ApiError extends Error { + status: number; + constructor(status: number, msg: string) { + super(msg); + this.status = status; + } +} + +async function request( + method: string, + path: string, + body?: unknown, +): Promise { + const auth = getAuth(); + const headers: Record = { Accept: "application/json" }; + if (auth) headers["Authorization"] = `Basic ${auth}`; + const opts: RequestInit = { method, headers }; + if (body !== undefined) { + if (body instanceof FormData) { + opts.body = body; + } else { + headers["Content-Type"] = "application/json"; + opts.body = JSON.stringify(body); + } + } + const res = await fetch(`${BASE}${path}`, opts); + if (res.status === 401) { + clearAuth(); + if (typeof window !== "undefined") window.location.href = "/panel/login/"; + throw new ApiError(401, "unauthorized"); + } + if (!res.ok) { + const e = await res.json().catch(() => ({})); + throw new ApiError(res.status, (e as { error?: string }).error || "خطا"); + } + return res.json() as Promise; +} + +export const api = { + get: (p: string) => request("GET", p), + post: (p: string, body?: unknown) => request("POST", p, body), + put: (p: string, body?: unknown) => request("PUT", p, body), + del: (p: string) => request("DELETE", p), + // ورود: اعتبارسنجی با فراخوانی /me. + async login(user: string, pass: string) { + const prev = getAuth(); + saveAuth(user, pass); + try { + await request("GET", "/me"); + return true; + } catch { + if (prev) localStorage.setItem(AUTH_KEY, prev); + else clearAuth(); + return false; + } + }, +}; diff --git a/lib/labels.ts b/lib/labels.ts new file mode 100644 index 0000000..e260bfe --- /dev/null +++ b/lib/labels.ts @@ -0,0 +1,27 @@ +import type { BadgeTone } from "@/components/Badge"; + +// برچسب‌ها و تن‌هایِ نشانکیِ مشترک بین صفحه‌ی تراکنش‌ها و داشبورد. +export const KIND_LABEL: Record = { + coin: "سکه", + ticket: "بلیط", + booster: "بوستر", + vip: "VIP", +}; +export const KIND_TONE: Record = { + coin: "gold", + ticket: "blue", + booster: "purple", + vip: "green", +}; +export const STATUS_LABEL: Record = { + verified: "پرداخت‌شده", + pending: "در انتظار", +}; +export const STATUS_TONE: Record = { + verified: "green", + pending: "gray", +}; +export const STORE_LABEL: Record = { + bazaar: "بازار", + myket: "مایکت", +}; diff --git a/lib/toast.ts b/lib/toast.ts new file mode 100644 index 0000000..effeb32 --- /dev/null +++ b/lib/toast.ts @@ -0,0 +1,37 @@ +"use client"; + +// سیستمِ توستِ سبک (بدونِ وابستگی): یک pub/sub ساده که پیام‌ها را به Toaster می‌دهد. +export type ToastType = "error" | "success" | "info"; +export type ToastItem = { id: number; msg: string; type: ToastType }; + +type Listener = (items: ToastItem[]) => void; + +let items: ToastItem[] = []; +let seq = 1; +const listeners = new Set(); + +function emit() { + for (const l of listeners) l(items); +} + +/** یک توست نشان می‌دهد (پیش‌فرض: خطا). پس از چند ثانیه خودش محو می‌شود. */ +export function toast(msg: string, type: ToastType = "error") { + const id = seq++; + items = [...items, { id, msg, type }]; + emit(); + setTimeout(() => { + items = items.filter((t) => t.id !== id); + emit(); + }, type === "error" ? 5000 : 3000); +} + +export const toastError = (msg: string) => toast(msg, "error"); +export const toastSuccess = (msg: string) => toast(msg, "success"); + +export function subscribe(l: Listener) { + listeners.add(l); + l(items); + return () => { + listeners.delete(l); + }; +} diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..206242d --- /dev/null +++ b/next.config.ts @@ -0,0 +1,11 @@ +import type { NextConfig } from "next"; + +// خروجیِ ایستا (static export) که nginx زیرِ مسیرِ /panel/ سرو می‌کند. +const nextConfig: NextConfig = { + output: "export", + trailingSlash: true, + basePath: "/panel", + images: { unoptimized: true }, +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9c9c183 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1911 @@ +{ + "name": "hakemsho-admin", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hakemsho-admin", + "version": "0.1.0", + "dependencies": { + "lucide-react": "^0.552.0", + "next": "16.0.1", + "react": "19.2.0", + "react-dom": "19.2.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "autoprefixer": "^10.4.24", + "postcss": "^8.5.6", + "tailwindcss": "^4.2.0", + "typescript": "^5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.1.tgz", + "integrity": "sha512-LFvlK0TG2L3fEOX77OC35KowL8D7DlFF45C0OvKMC4hy8c/md1RC4UMNDlUGJqfCoCS2VWrZ4dSE6OjaX5+8mw==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.1.tgz", + "integrity": "sha512-R0YxRp6/4W7yG1nKbfu41bp3d96a0EalonQXiMe+1H9GTHfKxGNCGFNWUho18avRBPsO8T3RmdWuzmfurlQPbg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.1.tgz", + "integrity": "sha512-kETZBocRux3xITiZtOtVoVvXyQLB7VBxN7L6EPqgI5paZiUlnsgYv4q8diTNYeHmF9EiehydOBo20lTttCbHAg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.1.tgz", + "integrity": "sha512-hWg3BtsxQuSKhfe0LunJoqxjO4NEpBmKkE+P2Sroos7yB//OOX3jD5ISP2wv8QdUwtRehMdwYz6VB50mY6hqAg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.1.tgz", + "integrity": "sha512-UPnOvYg+fjAhP3b1iQStcYPWeBFRLrugEyK/lDKGk7kLNua8t5/DvDbAEFotfV1YfcOY6bru76qN9qnjLoyHCQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.1.tgz", + "integrity": "sha512-Et81SdWkcRqAJziIgFtsFyJizHoWne4fzJkvjd6V4wEkWTB4MX6J0uByUb0peiJQ4WeAt6GGmMszE5KrXK6WKg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.1.tgz", + "integrity": "sha512-qBbgYEBRrC1egcG03FZaVfVxrJm8wBl7vr8UFKplnxNRprctdP26xEv9nJ07Ggq4y1adwa0nz2mz83CELY7N6Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.1.tgz", + "integrity": "sha512-cPuBjYP6I699/RdbHJonb3BiRNEDm5CKEBuJ6SD8k3oLam2fDRMKAvmrli4QMDgT2ixyRJ0+DTkiODbIQhRkeQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.1.tgz", + "integrity": "sha512-XeEUJsE4JYtfrXe/LaJn3z1pD19fK0Q6Er8Qoufi+HqvdO4LEPyCxLUt4rxA+4RfYo6S9gMlmzCMU2F+AatFqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", + "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", + "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-x64": "4.3.2", + "@tailwindcss/oxide-freebsd-x64": "4.3.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-x64-musl": "4.3.2", + "@tailwindcss/oxide-wasm32-wasi": "4.3.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", + "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", + "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", + "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", + "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", + "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", + "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", + "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", + "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", + "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", + "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", + "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", + "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.2.tgz", + "integrity": "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.2", + "@tailwindcss/oxide": "4.3.2", + "postcss": "^8.5.15", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", + "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.4", + "caniuse-lite": "^1.0.30001799", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.42", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", + "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz", + "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001800", + "electron-to-chromium": "^1.5.387", + "node-releases": "^2.0.50", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001803", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz", + "integrity": "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.388", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.388.tgz", + "integrity": "sha512-Pl/aJaqOOxYxda3vcx1IKSJimwYXHDkEnGn0F+kG2EE68dDtx2uCinaS+Vih8Z91B9t8CSAbiF/HKyWcnXjhzw==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lucide-react": { + "version": "0.552.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.552.0.tgz", + "integrity": "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/next/-/next-16.0.1.tgz", + "integrity": "sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==", + "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/CVE-2025-66478 for more details.", + "license": "MIT", + "dependencies": { + "@next/env": "16.0.1", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.0.1", + "@next/swc-darwin-x64": "16.0.1", + "@next/swc-linux-arm64-gnu": "16.0.1", + "@next/swc-linux-arm64-musl": "16.0.1", + "@next/swc-linux-x64-gnu": "16.0.1", + "@next/swc-linux-x64-musl": "16.0.1", + "@next/swc-win32-arm64-msvc": "16.0.1", + "@next/swc-win32-x64-msvc": "16.0.1", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tailwindcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", + "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..633ea96 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "hakemsho-admin", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "lucide-react": "^0.552.0", + "next": "16.0.1", + "react": "19.2.0", + "react-dom": "19.2.0" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "autoprefixer": "^10.4.24", + "postcss": "^8.5.6", + "tailwindcss": "^4.2.0", + "typescript": "^5" + } +} diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..8405e5a --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + autoprefixer: {}, + }, +}; +export default config; diff --git a/public/fonts/dana_bold.ttf b/public/fonts/dana_bold.ttf new file mode 100644 index 0000000..a0bf09c Binary files /dev/null and b/public/fonts/dana_bold.ttf differ diff --git a/public/fonts/dana_medium.ttf b/public/fonts/dana_medium.ttf new file mode 100644 index 0000000..fa4c39c Binary files /dev/null and b/public/fonts/dana_medium.ttf differ diff --git a/public/fonts/dana_regular.ttf b/public/fonts/dana_regular.ttf new file mode 100644 index 0000000..dd3d861 Binary files /dev/null and b/public/fonts/dana_regular.ttf differ diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..5bbeecc --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# استقرارِ پنلِ ادمینِ حکم‌شو: build ایستا + آپلود به سرور (همان سرورِ بک‌اند). +# nginx فایل‌ها را زیرِ https://hakem.approagency.ir/panel/ سرو می‌کند. +set -euo pipefail + +HOST="${HAKEM_HOST:-ubuntu@37.32.20.103}" +KEY="${HAKEM_KEY:-$HOME/.ssh/hakem_deploy}" +REMOTE_DIR="/var/www/hakem-panel/panel" # با root در nginx: /panel → این پوشه + +cd "$(dirname "$0")/.." + +echo "==> build (static export → ./out)" +npm run build + +if [ ! -d out ]; then + echo "build produced no ./out" >&2 + exit 1 +fi + +echo "==> upload to $HOST:$REMOTE_DIR" +# محتوای پوشه را جایگزین می‌کنیم (tar pipe روی یک اتصالِ SSH). +tar -C out -czf - . | ssh -i "$KEY" -o StrictHostKeyChecking=no "$HOST" " + set -e + sudo mkdir -p '$REMOTE_DIR' + sudo find '$REMOTE_DIR' -mindepth 1 -delete + sudo tar -C '$REMOTE_DIR' -xzf - + sudo chown -R www-data:www-data /var/www/hakem-panel +" +echo "==> done: https://hakem.approagency.ir/panel/" diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1f51897 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./*" + ] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/typescript b/typescript new file mode 100644 index 0000000..c8f2a4e --- /dev/null +++ b/typescript @@ -0,0 +1,204 @@ +Script started on Fri Aug 7 09:22:04 2026 +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004hlls[?2004l +app next-env.d.ts out postcss.config.mjs tsconfig.json +components next.config.ts package-lock.json public typescript +lib node_modules package.json scripts +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004hccd c  s ccd scs cript      cripts/ [?2004l +% amirmahdi@Amirmahdis-MacBook-Pro scripts % [?2004hlls[?2004l +deploy.sh +% amirmahdi@Amirmahdis-MacBook-Pro scripts % [?2004h../deploy.sh  [?2004l +==> build (static export → ./out) + +> hakemsho-admin@0.1.0 build +> next build + +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` + ▲ Next.js 16.0.1 (Turbopack) + +   Creating an optimized production build ... +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` + ✓ Compiled successfully in 7.4s +[?25l   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...[?25h[?25l   Finished TypeScript in 7.2s .[?25h   Finished TypeScript in 7.2s ✓ Finished TypeScript in 7.2s +[?25l   Collecting page data .   Collecting page data ..   Collecting page data ...   Collecting page data .   Collecting page data ..[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +   Collecting page data ...[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +   Collecting page data .   Collecting page data ..   Collecting page data ...   Collecting page data .   Collecting page data ..   Collecting page data ...[?25h   Collecting page data in 2.2s ✓ Collecting page data in 2.2s +[?25l   Generating static pages (0/13) [ ]   Generating static pages (0/13) [= ]   Generating static pages (0/13) [== ]   Generating static pages (0/13) [=== ][?25h[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[?25h ✓ Generating static pages (13/13) in 1835.8ms +[?25l   Finalizing page optimization .   Finalizing page optimization ..   Finalizing page optimization ...   Finalizing page optimization .[?25h   Finalizing page optimization in 681.7ms ✓ Finalizing page optimization in 681.7ms + +Route (app) +┌ ○ / +├ ○ /_not-found +├ ○ /avatars +├ ○ /carpets +├ ○ /chat +├ ○ /frames +├ ○ /login +├ ○ /rank-tiers +├ ○ /shop +├ ○ /tournaments +├ ○ /transactions +└ ○ /users + + +○ (Static) prerendered as static content + +[?25h⠙==> upload to ubuntu@37.32.20.103:/var/www/hakem-panel/panel +==> done: https://hakem.approagency.ir/panel/ +% amirmahdi@Amirmahdis-MacBook-Pro scripts % [?2004h./deploy.sh[?2004l +==> build (static export → ./out) + +> hakemsho-admin@0.1.0 build +> next build + +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` + ▲ Next.js 16.0.1 (Turbopack) + +   Creating an optimized production build ... +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` + ✓ Compiled successfully in 6.0s +[?25l   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .[?25h[?25l   Finished TypeScript in 7.9s .[?25h   Finished TypeScript in 7.9s ✓ Finished TypeScript in 7.9s +[?25l   Collecting page data .   Collecting page data ..   Collecting page data ...   Collecting page data .   Collecting page data ..   Collecting page data ...[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +   Collecting page data .   Collecting page data ..[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +   Collecting page data ...   Collecting page data .   Collecting page data ..   Collecting page data ...   Collecting page data .[?25h   Collecting page data in 2.6s ✓ Collecting page data in 2.6s +[?25l   Generating static pages (0/13) [ ]   Generating static pages (0/13) [= ]   Generating static pages (0/13) [== ]   Generating static pages (0/13) [=== ]   Generating static pages (0/13) [ ===][?25h[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[?25h ✓ Generating static pages (13/13) in 2.1s +[?25l   Finalizing page optimization .   Finalizing page optimization ..   Finalizing page optimization ...   Finalizing page optimization .[?25h   Finalizing page optimization in 728.6ms ✓ Finalizing page optimization in 728.6ms + +Route (app) +┌ ○ / +├ ○ /_not-found +├ ○ /avatars +├ ○ /carpets +├ ○ /chat +├ ○ /frames +├ ○ /login +├ ○ /rank-tiers +├ ○ /shop +├ ○ /tournaments +├ ○ /transactions +└ ○ /users + + +○ (Static) prerendered as static content + +[?25h⠙==> upload to ubuntu@37.32.20.103:/var/www/hakem-panel/panel +==> done: https://hakem.approagency.ir/panel/ +% amirmahdi@Amirmahdis-MacBook-Pro scripts % [?2004hccd ..[?2004l +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004hgit initgit init[?2004l +Initialized empty Git repository in /Users/amirmahdi/StudioProjects/hakem sho/admin/.git/ +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004hgit checkout -b maingit checkout -b main[?2004l +Switched to a new branch 'main' +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004hgit commit -m "first commit"git commit -m "first commit"s +zsh: do you wish to see all 184 possibilities (31 lines)? s  sscr +screen screencapture script script  [?2004l +Script started, output file is typescript +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004hlls[?2004l +app next-env.d.ts out postcss.config.mjs tsconfig.json +components next.config.ts package-lock.json public typescript +lib node_modules package.json scripts +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004hccd c  s ccd scs cript      cripts/ [?2004l +% amirmahdi@Amirmahdis-MacBook-Pro scripts % [?2004hlls[?2004l +deploy.sh +% amirmahdi@Amirmahdis-MacBook-Pro scripts % [?2004h../deploy.sh  [?2004l +==> build (static export → ./out) + +> hakemsho-admin@0.1.0 build +> next build + +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` + ▲ Next.js 16.0.1 (Turbopack) + +   Creating an optimized production build ... +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` + ✓ Compiled successfully in 7.4s +[?25l   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...[?25h[?25l   Finished TypeScript in 7.2s .[?25h   Finished TypeScript in 7.2s ✓ Finished TypeScript in 7.2s +[?25l   Collecting page data .   Collecting page data ..   Collecting page data ...   Collecting page data .   Collecting page data ..[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +   Collecting page data ...[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +   Collecting page data .   Collecting page data ..   Collecting page data ...   Collecting page data .   Collecting page data ..   Collecting page data ...[?25h   Collecting page data in 2.2s ✓ Collecting page data in 2.2s +[?25l   Generating static pages (0/13) [ ]   Generating static pages (0/13) [= ]   Generating static pages (0/13) [== ]   Generating static pages (0/13) [=== ][?25h[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[?25h ✓ Generating static pages (13/13) in 1835.8ms +[?25l   Finalizing page optimization .   Finalizing page optimization ..   Finalizing page optimization ...   Finalizing page optimization .[?25h   Finalizing page optimization in 681.7ms ✓ Finalizing page optimization in 681.7ms + +Route (app) +┌ ○ / +├ ○ /_not-found +├ ○ /avatars +├ ○ /carpets +├ ○ /chat +├ ○ /frames +├ ○ /login +├ ○ /rank-tiers +├ ○ /shop +├ ○ /tournaments +├ ○ /transactions +└ ○ /users + + +○ (Static) prerendered as static content + +[?25h⠙==> upload to ubuntu@37.32.20.103:/var/www/hakem-panel/panel +==> done: https://hakem.approagency.ir/panel/ +% amirmahdi@Amirmahdis-MacBook-Pro scripts % [?2004h./deploy.sh[?2004l +==> build (static export → ./out) + +> hakemsho-admin@0.1.0 build +> next build + +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` + ▲ Next.js 16.0.1 (Turbopack) + +   Creating an optimized production build ... +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` + ✓ Compiled successfully in 6.0s +[?25l   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .   Running TypeScript ..   Running TypeScript ...   Running TypeScript .[?25h[?25l   Finished TypeScript in 7.9s .[?25h   Finished TypeScript in 7.9s ✓ Finished TypeScript in 7.9s +[?25l   Collecting page data .   Collecting page data ..   Collecting page data ...   Collecting page data .   Collecting page data ..   Collecting page data ...[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +   Collecting page data .   Collecting page data ..[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +   Collecting page data ...   Collecting page data .   Collecting page data ..   Collecting page data ...   Collecting page data .[?25h   Collecting page data in 2.6s ✓ Collecting page data in 2.6s +[?25l   Generating static pages (0/13) [ ]   Generating static pages (0/13) [= ]   Generating static pages (0/13) [== ]   Generating static pages (0/13) [=== ]   Generating static pages (0/13) [ ===][?25h[baseline-browser-mapping] The data in this module is over two months old. To ensure accurate Baseline data, please update: `npm i baseline-browser-mapping@latest -D` +[?25h ✓ Generating static pages (13/13) in 2.1s +[?25l   Finalizing page optimization .   Finalizing page optimization ..   Finalizing page optimization ...   Finalizing page optimization .[?25h   Finalizing page optimization in 728.6ms ✓ Finalizing page optimization in 728.6ms + +Route (app) +┌ ○ / +├ ○ /_not-found +├ ○ /avatars +├ ○ /carpets +├ ○ /chat +├ ○ /frames +├ ○ /login +├ ○ /rank-tiers +├ ○ /shop +├ ○ /tournaments +├ ○ /transactions +└ ○ /users + + +○ (Static) prerendered as static content + +[?25h⠙==> upload to ubuntu@37.32.20.103:/var/www/hakem-panel/panel +==> done: https://hakem.approagency.ir/panel/ +% amirmahdi@Amirmahdis-MacBook-Pro scripts % [?2004hccd ..[?2004l +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004hgit initgit init[?2004l +Initialized empty Git repository in /Users/amirmahdi/StudioProjects/hakem sho/admin/.git/ +% amirmahdi@Amirmahdis-MacBook-Pro admin % [?2004h \ No newline at end of file