diff --git a/.claude/prompt/login-redirect-back.md b/.claude/prompt/login-redirect-back.md new file mode 100644 index 0000000..201fd03 --- /dev/null +++ b/.claude/prompt/login-redirect-back.md @@ -0,0 +1,109 @@ +# بازگشت به صفحهٔ مبدأ بعد از لاگین (redirect-back) + +## پروژه + +`nobat724_front` (سایت عمومی) + +## زمینه + +مکانیزم بازگشت بعد از لاگین **از قبل وجود دارد**: مرحلهٔ تأیید کد (`SendReq.js`) بعد از ورود موفق، پارامتر `?redirect=` را از URL می‌خواند و کاربر را به همان مسیر برمی‌گرداند (فقط مسیر داخلی امن). اما **لینک‌ها/ناوبری‌های لاگین در سایت این پارامتر را نمی‌سازند** — به‌جز یک مورد (`components/doctor/claim/index.js`). نتیجه: کاربر از هر صفحه‌ای (مثلاً صفحهٔ پزشک برای گرفتن نوبت) روی «ورود | ثبت نام» بزند، بعد از لاگین به `/` (خانه) می‌رود، نه صفحهٔ مبدأ. + +## مشکل / هدف + +هر جای سایت که به `/login` می‌رویم، باید مسیر فعلی را به‌صورت `?redirect=` به لینک لاگین اضافه کنیم تا بعد از ورود موفق کاربر به همان صفحه برگردد. الگوی درست از قبل در پروژه هست و فقط باید به بقیهٔ نقاط تعمیم داده شود. + +## فایل‌های مرتبط + +| فایل | نقش | +|------|-----| +| `components/register/verificationPage/SendReq.js` | (بدون تغییر) منطق بازگشت با `?redirect=` — L89–95 | +| `components/doctor/claim/index.js` | (مرجع الگوی درست) L191 — `/login?redirect=${encodeURIComponent(pathname)}` | +| `components/layout/header/Content.js` | لینک اصلی «ورود \| ثبت نام» هدر — L43 | +| `app/payment/[uuid]/page.js` | `router.push("/login")` هنگام نبود auth — L35 | +| `app/dashboard/page.js` | `redirect("/login")` سمت سرور برای صفحهٔ محافظت‌شده — L29 | +| `app/component/ModalLogout.js` | `router.replace("/login")` بعد از خروج — L19 (به «نکات» رجوع کن) | + +## وضعیت فعلی + +منطق بازگشت که **باید حفظ شود** (`SendReq.js` L89–95): + +```jsx +// بازگشت به صفحهٔ مبدأ اگر ?redirect= داده شده (فقط مسیر داخلی امن) +const redirect = new URLSearchParams(window.location.search).get("redirect"); +window.location.href = + redirect && redirect.startsWith("/") && !redirect.startsWith("//") + ? redirect + : "/"; +``` + +الگوی درستِ موجود (`components/doctor/claim/index.js` L191): + +```jsx + +``` + +لینک هدر که پارامتر ندارد (`components/layout/header/Content.js` L40–47) — این کامپوننت `"use client"` است و همین حالا `const pathname = usePathname();` را دارد (L17): + +```jsx +{isLogged ? ( + +) : ( + + + +)} +``` + +## وظایف + +### ۱. لینک لاگین هدر (اصلی‌ترین) + +در `components/layout/header/Content.js` L43، `pathname` که همین‌جا موجود است را به لینک اضافه کن. از رفتن به `/login?redirect=/login` جلوگیری کن: + +```jsx + + + +``` + +### ۲. ناوبری لاگین صفحهٔ پرداخت + +در `app/payment/[uuid]/page.js` L35 (`router.push("/login")`)، مسیر فعلی را ضمیمه کن. این کامپوننت client است؛ اگر `usePathname` وارد نشده، آن را از `next/navigation` وارد کن و استفاده کن: + +```jsx +import { usePathname } from "next/navigation"; +// ... +const pathname = usePathname(); +// ... +router.push(`/login?redirect=${encodeURIComponent(pathname)}`); +``` + +### ۳. redirect سمت سرور داشبورد + +در `app/dashboard/page.js` L29، صفحهٔ محافظت‌شده هنگام نبود auth به لاگین می‌رود؛ مقصد بازگشت را ثابت `/dashboard` بگذار: + +```jsx +return redirect("/login?redirect=/dashboard"); +``` + +(چون این redirect سمت سرور است و به مسیر ثابت داشبورد مربوط است، نیازی به `usePathname` نیست.) + +## نکات مهم + +- **پارامتر در طول جریان لاگین حفظ می‌شود:** صفحهٔ `/login` → `ContentLogin` → `RegisterPage` مرحلهٔ ارسال کد و تأیید را با state داخلی (`isSendMsg`) عوض می‌کند و **ناوبری جدید انجام نمی‌دهد**، پس URL روی `/login?redirect=...` می‌ماند و `SendReq.js` با `window.location.search` آن را می‌خواند. نیازی به پاس‌دادن prop اضافه نیست. +- **امنیت redirect از قبل هندل شده:** فقط مسیرِ داخلی که با `/` شروع شود و با `//` شروع نشود پذیرفته می‌شود (`SendReq.js` L92). مقدار را همیشه با `encodeURIComponent(pathname)` بساز. +- **`app/login/page.js`**: کاربرِ از قبل لاگین‌شده به `/` هدایت می‌شود (`ability.can("access","Login")`). این رفتار درست است و نباید تغییر کند؛ فقط برای کاربرِ مهمان `?redirect=` معنا دارد. +- **`ModalLogout.js` (L19) را تغییر نده:** بعد از *خروج* بردن کاربر به `/login` با `?redirect=` به صفحهٔ محافظت‌شده باعث حلقه/بازگشت ناخواسته می‌شود. لاگ‌اوت باید به `/login` سادهٔ فعلی برود. +- **صفحهٔ appointment inline login دارد:** جریان گرفتن نوبت با `setStep(3)` بعد از لاگین در همان صفحه می‌ماند (`SendReq.js` L86–87) و ناوبری نمی‌کند؛ نیازی به `?redirect=` ندارد و نباید دست بخورد. +- `usePathname` فقط در Client Component کار می‌کند؛ برای redirectهای سمت سرور (وظیفهٔ ۳) از مسیر ثابت استفاده کن. +- بعد از تغییر: `npm run build` بدون خطا. diff --git a/app/dashboard/page.js b/app/dashboard/page.js index b082727..de3be2b 100644 --- a/app/dashboard/page.js +++ b/app/dashboard/page.js @@ -26,7 +26,7 @@ export default async function Dashboard({ searchParams }) { if (!ability.can("access", "Dashboard") || !token) { removeToken(); - return redirect("/login"); + return redirect("/login?redirect=/dashboard"); } // The userInfo cookie holds the real user uuid; the standalone `uuid` cookie diff --git a/app/payment/[uuid]/page.js b/app/payment/[uuid]/page.js index 0a6f462..cf1c035 100644 --- a/app/payment/[uuid]/page.js +++ b/app/payment/[uuid]/page.js @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import { useParams, useRouter, useSearchParams } from "next/navigation"; +import { useParams, usePathname, useRouter, useSearchParams } from "next/navigation"; import { request } from "@/services/response"; import { formatToman } from "@/lib/money"; import Cookies from "js-cookie"; @@ -10,6 +10,7 @@ import Layout from "@/components/layout"; export default function PaymentDetailsPage() { const params = useParams(); const router = useRouter(); + const pathname = usePathname(); const searchParams = useSearchParams(); const [payment, setPayment] = useState(null); const [loading, setLoading] = useState(true); @@ -32,7 +33,7 @@ export default function PaymentDetailsPage() { // بررسی لاگین بودن کاربر const userInfo = Cookies.get("userInfo"); if (!userInfo) { - router.push("/login"); + router.push(`/login?redirect=${encodeURIComponent(pathname)}`); return; } @@ -53,7 +54,7 @@ export default function PaymentDetailsPage() { if (params.uuid) { fetchPaymentDetails(); } - }, [params.uuid, router]); + }, [params.uuid, router, pathname]); // پس از دیدن جزئیاتِ پرداختِ موفق، خودکار به تاریخچهٔ نوبت‌ها (تب تأییدشده) برو. useEffect(() => { diff --git a/components/layout/header/Content.js b/components/layout/header/Content.js index 33bdc9f..95939c0 100644 --- a/components/layout/header/Content.js +++ b/components/layout/header/Content.js @@ -40,7 +40,13 @@ function Content({ matchedCity, name, logged }) { {isLogged ? ( ) : ( - +