The header decided between profile and login button only from the server-passed logged prop (read once via cookies() in StLayout), so a user who logged in client-side still saw ورود | ثبت نام until a hard reload. Seed isLogged from the server prop (correct first paint, no hydration mismatch) then sync it from the access_token cookie on mount and on every route change, so login/logout reflect immediately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
114 lines
7.1 KiB
Markdown
114 lines
7.1 KiB
Markdown
# رفع نمایش «ورود | ثبتنام» در هدر با وجود لاگینبودن کاربر
|
|
|
|
## پروژه
|
|
|
|
`nobat724_front` — سایت عمومی. این کار **صرفاً frontend** است؛ backend تغییر نمیکند.
|
|
|
|
## زمینه
|
|
|
|
هدر سایت عمومی بسته به وضعیت لاگین، یا دکمهی «ورود | ثبت نام» را نشان میدهد یا آیکن پروفایل کاربر (`ProfileUser`). تصمیم بر اساس prop `logged` گرفته میشود که در `components/layout/StLayout.js` (Server Component) از روی کوکی `access_token` با `cookies()` خوانده و به هدر پاس داده میشود.
|
|
|
|
مشکل: کاربر بعد از لاگین موفق (کوکیهای `access_token`/`refresh_token`/`uuid`/`userInfo` با `js-cookie` ست میشوند)، **هنوز در هدر «ورود | ثبت نام» میبیند**. علت: `logged` فقط یکبار سمت سرور هنگام render محاسبه میشود و به ناوبریهای client-side (soft navigation) و کوکیهایی که سمت کلاینت ست شدهاند واکنش نشان نمیدهد؛ هدر تا یک hard-reload کامل وضعیت تازه را منعکس نمیکند و در بسیاری از مسیرها همان حالت اولیهی «لاگیننشده» باقی میماند.
|
|
|
|
## مشکل / هدف
|
|
|
|
هدر باید وضعیت لاگین را **بهصورت زنده سمت کلاینت** هم تشخیص دهد: اگر کوکی `access_token` وجود دارد → پروفایل، وگرنه → دکمهی ورود. prop سرور (`logged`) بهعنوان مقدار اولیهی paint اول حفظ شود تا flash/hydration mismatch ندهد، ولی منبع نهایی تصمیم، کوکی سمت کلاینت باشد.
|
|
|
|
## فایلهای مرتبط
|
|
|
|
| فایل | نقش |
|
|
|------|-----|
|
|
| `components/layout/header/Content.js` | `"use client"`؛ بر اساس `logged` بین `ProfileUser` و دکمهی ورود سوییچ میکند — محل اصلی رفع |
|
|
| `components/layout/StLayout.js` | Server Component؛ `logged={token}` را از `cookies().get("access_token")` میدهد (مقدار اولیه) |
|
|
| `components/layout/index.js` | layout کلاینتیِ پنل/داشبورد؛ `logged` را بهصورت prop میگیرد |
|
|
| `components/layout/header/index.js` | فقط prop را پاس میدهد |
|
|
| `components/layout/header/profile/index.js` | `ProfileUser` — حالت لاگینشده |
|
|
| `utils/index.js` | `removeToken()` — خروج |
|
|
|
|
## وضعیت فعلی (کد واقعی)
|
|
|
|
### `header/Content.js` — تصمیم فقط بر اساس prop سرور
|
|
|
|
```jsx
|
|
"use client";
|
|
// ...
|
|
function Content({ matchedCity, name, logged }) {
|
|
// ...
|
|
{logged ? (
|
|
<ProfileUser />
|
|
) : (
|
|
<Link href="/login">
|
|
<Button variant="outlined" color="primary">ورود | ثبت نام</Button>
|
|
</Link>
|
|
)}
|
|
}
|
|
```
|
|
|
|
### `StLayout.js` — مقدار اولیه از کوکی سرور
|
|
|
|
```jsx
|
|
const cookieStore = await cookies();
|
|
const token = cookieStore.get("access_token");
|
|
// ...
|
|
<Header matchedCity={matchedCity} name={name} logged={token} />
|
|
```
|
|
|
|
> `token` یک شیء `{ name, value }` یا `undefined` است؛ پس `logged` truthy/undefined میشود. این فقط در render سرور محاسبه میشود.
|
|
|
|
### کوکیها سمت کلاینت ست میشوند (`SendReq.js`)
|
|
|
|
```js
|
|
Cookies.set("access_token", response.access_token, cookieOptions); // js-cookie, غیر httpOnly
|
|
```
|
|
|
|
## وظایف
|
|
|
|
### ۱. تشخیص زندهی لاگین در `header/Content.js`
|
|
|
|
- یک state کلاینتی برای لاگینبودن اضافه کن که مقدار اولیهاش از prop `logged` (سرور) میآید تا paint اول درست باشد، سپس در `useEffect` با کوکی `access_token` همگام شود:
|
|
|
|
```jsx
|
|
"use client";
|
|
import { useEffect, useState } from "react";
|
|
import Cookies from "js-cookie";
|
|
// ...
|
|
function Content({ matchedCity, name, logged }) {
|
|
const [isLogged, setIsLogged] = useState(Boolean(logged));
|
|
|
|
useEffect(() => {
|
|
setIsLogged(Boolean(Cookies.get("access_token")));
|
|
}, []);
|
|
// ...
|
|
{isLogged ? <ProfileUser /> : <Link href="/login">...دکمه ورود...</Link>}
|
|
}
|
|
```
|
|
|
|
- `Boolean(logged)` مهم است: `logged` ممکن است شیء `{name,value}` یا `undefined` باشد؛ تبدیل به boolean کن.
|
|
- چون مقدار اولیه از سرور میآید، hydration mismatch رخ نمیدهد؛ بعد از mount با کوکی واقعی کلاینت تصحیح میشود.
|
|
|
|
### ۲. واکنش به تغییر مسیر (اختیاری ولی توصیهشده)
|
|
|
|
- اگر لازم بود وضعیت بعد از login/logout بدون hard-reload بهروز شود، میتوانی `usePathname()` را به deps افزودن useEffect اضافه کنی تا با هر تغییر مسیر کوکی دوباره چک شود:
|
|
|
|
```jsx
|
|
import { usePathname } from "next/navigation";
|
|
const pathname = usePathname();
|
|
useEffect(() => {
|
|
setIsLogged(Boolean(Cookies.get("access_token")));
|
|
}, [pathname]);
|
|
```
|
|
|
|
> اگر فلوی فعلی login با `window.location.href` ریدایرکت میکند (hard reload)، paint اول سرور هم درست خواهد بود؛ این مرحله بیشتر برای ناوبریهای soft و logout است.
|
|
|
|
### ۳. سازگاری logout
|
|
|
|
- `ProfileUser`/`DetailProfile` هنگام logout `removeToken()` را صدا میزنند که کوکیها را پاک میکند. مطمئن شو بعد از logout، هدر دوباره دکمهی ورود را نشان میدهد (با همان مکانیزم کوکیخوانی + ناوبری/ریلود فعلی). اگر logout فقط state را عوض میکند و ریدایرکت ندارد، بررسی کن هدر بهروز شود.
|
|
|
|
## نکات مهم
|
|
|
|
- **هیچ تغییری در backend لازم نیست.** کوکی `access_token` غیر httpOnly است و سمت کلاینت با `js-cookie` قابلخواندن است.
|
|
- **Hydration:** مقدار اولیهی state حتماً از prop `logged` سرور باشد، نه مستقیماً از `Cookies.get` در زمان render؛ خواندن کوکی فقط داخل `useEffect` (بعد از mount) تا SSR و کلاینت در paint اول یکی باشند.
|
|
- **multi-domain / RTL** را خراب نکن؛ منطق `matchedCity`/`name` دستنخورده بماند.
|
|
- این هدر هم در `StLayout` (صفحات عمومی، logged از سرور) و هم در `components/layout/index.js` (پنل/داشبورد، logged از prop) استفاده میشود؛ هر دو مسیر باید بعد از تغییر کار کنند (در هر دو، fallback به prop + همگامسازی با کوکی درست عمل میکند).
|
|
- **تست:** `npm run build`؛ سپس دستی: قبل از لاگین → دکمهی «ورود | ثبت نام»؛ بعد از لاگین با `12345` و بازگشت به صفحه → آیکن پروفایل؛ بعد از logout → دوباره دکمهی ورود. سپس commit با پیام توصیفی.
|