From 0958bd365b453fd554fda7daf564b9de6978b3a7 Mon Sep 17 00:00:00 2001 From: Amir Husayn Panahifar Date: Wed, 7 Oct 2026 19:40:26 +0330 Subject: [PATCH] feat(frontend): wire App Router pages for the LMS flows Connect login, learning path, quiz, profile, community, and admin routes to the shared feature components. --- frontend/src/app/admin/page.tsx | 12 + frontend/src/app/apple-icon.png | Bin 0 -> 19434 bytes frontend/src/app/c/[id]/page.tsx | 219 ++++++ frontend/src/app/c/layout.tsx | 21 + frontend/src/app/cap/page.tsx | 459 +++++++++++++ frontend/src/app/community/page.tsx | 13 + frontend/src/app/favicon.ico | Bin 0 -> 32038 bytes .../src/app/fonts/Vazirmatn-FD-Black.woff2 | Bin 0 -> 49100 bytes .../src/app/fonts/Vazirmatn-FD-Bold.woff2 | Bin 0 -> 49868 bytes .../app/fonts/Vazirmatn-FD-ExtraBold.woff2 | Bin 0 -> 49628 bytes .../app/fonts/Vazirmatn-FD-ExtraLight.woff2 | Bin 0 -> 49816 bytes .../src/app/fonts/Vazirmatn-FD-Light.woff2 | Bin 0 -> 49712 bytes .../src/app/fonts/Vazirmatn-FD-Medium.woff2 | Bin 0 -> 49784 bytes .../src/app/fonts/Vazirmatn-FD-Regular.woff2 | Bin 0 -> 49044 bytes .../src/app/fonts/Vazirmatn-FD-SemiBold.woff2 | Bin 0 -> 49608 bytes .../src/app/fonts/Vazirmatn-FD-Thin.woff2 | Bin 0 -> 49556 bytes .../src/app/fonts/Vazirmatn-Variable.woff2 | Bin 0 -> 111152 bytes frontend/src/app/globals.css | 647 ++++++++++++++++++ frontend/src/app/icon.png | Bin 0 -> 50390 bytes frontend/src/app/layout.tsx | 59 ++ frontend/src/app/leaderboard/page.tsx | 31 + frontend/src/app/lockout/page.tsx | 152 ++++ frontend/src/app/login/page.tsx | 240 +++++++ frontend/src/app/not-found.tsx | 58 ++ frontend/src/app/notifications/page.tsx | 135 ++++ frontend/src/app/page.tsx | 305 +++++++++ frontend/src/app/player/[lessonId]/page.tsx | 200 ++++++ frontend/src/app/profile/page.tsx | 171 +++++ frontend/src/app/quiz/[lessonId]/page.tsx | 30 + frontend/src/app/recovery/page.tsx | 6 + .../src/app/register/invite/[token]/page.tsx | 111 +++ frontend/src/app/register/page.tsx | 87 +++ frontend/src/app/robots.ts | 11 + frontend/src/app/unwrap/page.tsx | 274 ++++++++ 34 files changed, 3241 insertions(+) create mode 100644 frontend/src/app/admin/page.tsx create mode 100644 frontend/src/app/apple-icon.png create mode 100644 frontend/src/app/c/[id]/page.tsx create mode 100644 frontend/src/app/c/layout.tsx create mode 100644 frontend/src/app/cap/page.tsx create mode 100644 frontend/src/app/community/page.tsx create mode 100644 frontend/src/app/favicon.ico create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-Black.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-Bold.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-ExtraBold.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-ExtraLight.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-Light.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-Medium.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-Regular.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-SemiBold.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-FD-Thin.woff2 create mode 100644 frontend/src/app/fonts/Vazirmatn-Variable.woff2 create mode 100644 frontend/src/app/globals.css create mode 100644 frontend/src/app/icon.png create mode 100644 frontend/src/app/layout.tsx create mode 100644 frontend/src/app/leaderboard/page.tsx create mode 100644 frontend/src/app/lockout/page.tsx create mode 100644 frontend/src/app/login/page.tsx create mode 100644 frontend/src/app/not-found.tsx create mode 100644 frontend/src/app/notifications/page.tsx create mode 100644 frontend/src/app/page.tsx create mode 100644 frontend/src/app/player/[lessonId]/page.tsx create mode 100644 frontend/src/app/profile/page.tsx create mode 100644 frontend/src/app/quiz/[lessonId]/page.tsx create mode 100644 frontend/src/app/recovery/page.tsx create mode 100644 frontend/src/app/register/invite/[token]/page.tsx create mode 100644 frontend/src/app/register/page.tsx create mode 100644 frontend/src/app/robots.ts create mode 100644 frontend/src/app/unwrap/page.tsx diff --git a/frontend/src/app/admin/page.tsx b/frontend/src/app/admin/page.tsx new file mode 100644 index 0000000..8feccff --- /dev/null +++ b/frontend/src/app/admin/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { Suspense } from "react"; +import { AdminStudio } from "@/components/admin-studio"; + +export default function AdminPage() { + return ( + + + + ); +} \ No newline at end of file diff --git a/frontend/src/app/apple-icon.png b/frontend/src/app/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dec1f08af408318f77ab8b9730803a1218eb063b GIT binary patch literal 19434 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 = { + requested: "درخواست‌شده — منتظر تأیید پرداخت", + paid: "پرداخت تأیید شد — در صف ارسال", + shipped: "ارسال شد", + cancelled: "لغو شده", +}; + +export default function CertificatePage() { + const params = useParams<{ id: string }>(); + const publicId = params?.id ?? ""; + const authUser = useAuth((s) => s.user); + const qc = useQueryClient(); + const [requesting, setRequesting] = useState(false); + const [form, setForm] = useState({ + recipientName: "", + phone: "", + address: "", + city: "", + postalCode: "", + }); + + const { data, isLoading, error } = useQuery({ + queryKey: ["certificate", publicId], + queryFn: () => http.get(`/api/certificates/${publicId}`), + enabled: !!publicId, + meta: { silentError: true }, + }); + + const { data: mine } = useQuery({ + queryKey: ["me", "certificate"], + queryFn: () => + http.get<{ + certificate: Certificate | null; + physicalOrder: CertificatePhysicalOrder | null; + physical: PhysicalCertSettings; + }>("/api/me/certificate"), + enabled: !!authUser, + }); + + const isOwner = !!authUser && mine?.certificate?.publicId === publicId; + + useEffect(() => { + if (!isOwner || !authUser) return; + setForm((f) => ({ + ...f, + recipientName: f.recipientName || authUser.name || "", + phone: f.phone || authUser.phone || "", + })); + }, [isOwner, authUser]); + + const requestPhysical = async () => { + setRequesting(true); + try { + await http.post("/api/me/certificate/physical", { + ...form, + phone: normalizePhone(form.phone), + }); + toast.success("درخواست ثبت شد — پس از واریز با ادمین هماهنگ کنید"); + qc.invalidateQueries({ queryKey: ["me", "certificate"] }); + } catch (e) { + toast.error(toUserError(e)); + } finally { + setRequesting(false); + } + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error || !data?.certificate) { + return ( +
+

گواهینامه پیدا نشد

+ +
+ ); + } + + const cert = data.certificate; + const order = isOwner ? mine?.physicalOrder : null; + const physical = isOwner ? mine?.physical : null; + const windowEndsAt = mine?.certificate?.issuedAt + ? new Date(mine.certificate.issuedAt).getTime() + (physical?.windowDays ?? 30) * 86400000 + : 0; + const windowStillOpen = !windowEndsAt || Date.now() < windowEndsAt; + const windowOpen = + physical?.enabled && + windowStillOpen && + (!order || order.status === "cancelled") && + !!mine?.certificate; + + return ( + + {isOwner && physical?.enabled && ( +
+

+ نسخه فیزیکی +

+

+ قیمت حدودی {formatFaNumber(physical.priceIrr)} ریال · مهلت درخواست{" "} + {formatFaNumber(physical.windowDays)} روز از صدور + {windowEndsAt ? ` (تا ${new Date(windowEndsAt).toLocaleDateString("fa-IR")})` : ""}. + پرداخت آنلاین در سامانه نداریم. پس از واریز بیرونی با ادمین هماهنگ کنید تا وضعیت سفارش به‌روز شود. +

+ {order && order.status !== "cancelled" ? ( +
+

+ وضعیت: {STATUS_LABEL[order.status] ?? order.status} +

+ {order.trackingCode && ( +

+ کد پیگیری: {order.trackingCode} +

+ )} + {order.city && ( +

+ ارسال به {order.recipientName || "—"} · {order.city} +

+ )} + {order.note &&

یادداشت ادمین: {order.note}

} + +
+ ) : windowOpen ? ( +
+
+ setForm((f) => ({ ...f, recipientName: e.target.value }))} + /> + setForm((f) => ({ ...f, phone: clampPhoneInput(e.target.value) }))} + dir="ltr" + inputMode="tel" + maxLength={PHONE_INPUT_MAX_LEN} + className="text-left" + /> + setForm((f) => ({ ...f, city: e.target.value }))} + /> + setForm((f) => ({ ...f, postalCode: e.target.value }))} + dir="ltr" + /> +
+