feat(login): add redirect parameter to login links for better user experience

This commit is contained in:
hamed
2026-07-12 10:06:15 +03:30
parent 42ac6645d5
commit 58f05f8f4f
4 changed files with 121 additions and 5 deletions
+109
View File
@@ -0,0 +1,109 @@
# بازگشت به صفحهٔ مبدأ بعد از لاگین (redirect-back)
## پروژه
`nobat724_front` (سایت عمومی)
## زمینه
مکانیزم بازگشت بعد از لاگین **از قبل وجود دارد**: مرحلهٔ تأیید کد (`SendReq.js`) بعد از ورود موفق، پارامتر `?redirect=` را از URL می‌خواند و کاربر را به همان مسیر برمی‌گرداند (فقط مسیر داخلی امن). اما **لینک‌ها/ناوبری‌های لاگین در سایت این پارامتر را نمی‌سازند** — به‌جز یک مورد (`components/doctor/claim/index.js`). نتیجه: کاربر از هر صفحه‌ای (مثلاً صفحهٔ پزشک برای گرفتن نوبت) روی «ورود | ثبت نام» بزند، بعد از لاگین به `/` (خانه) می‌رود، نه صفحهٔ مبدأ.
## مشکل / هدف
هر جای سایت که به `/login` می‌رویم، باید مسیر فعلی را به‌صورت `?redirect=<current-path>` به لینک لاگین اضافه کنیم تا بعد از ورود موفق کاربر به همان صفحه برگردد. الگوی درست از قبل در پروژه هست و فقط باید به بقیهٔ نقاط تعمیم داده شود.
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `components/register/verificationPage/SendReq.js` | (بدون تغییر) منطق بازگشت با `?redirect=` — L8995 |
| `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` L8995):
```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
<Link href={`/login?redirect=${encodeURIComponent(pathname)}`} className="w-full">
```
لینک هدر که پارامتر ندارد (`components/layout/header/Content.js` L40–47) — این کامپوننت `"use client"` است و همین حالا `const pathname = usePathname();` را دارد (L17):
```jsx
{isLogged ? (
<ProfileUser />
) : (
<Link href="/login">
<Button variant="outlined" color="primary">
ورود | ثبت نام
</Button>
</Link>
)}
```
## وظایف
### ۱. لینک لاگین هدر (اصلی‌ترین)
در `components/layout/header/Content.js` L43، `pathname` که همین‌جا موجود است را به لینک اضافه کن. از رفتن به `/login?redirect=/login` جلوگیری کن:
```jsx
<Link
href={
pathname && pathname !== "/login"
? `/login?redirect=${encodeURIComponent(pathname)}`
: "/login"
}
>
<Button variant="outlined" color="primary">
ورود | ثبت نام
</Button>
</Link>
```
### ۲. ناوبری لاگین صفحهٔ پرداخت
در `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` بدون خطا.
+1 -1
View File
@@ -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
+4 -3
View File
@@ -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(() => {
+7 -1
View File
@@ -40,7 +40,13 @@ function Content({ matchedCity, name, logged }) {
{isLogged ? (
<ProfileUser />
) : (
<Link href="/login">
<Link
href={
pathname && pathname !== "/login"
? `/login?redirect=${encodeURIComponent(pathname)}`
: "/login"
}
>
<Button variant="outlined" color="primary">
ورود | ثبت نام
</Button>