Files
nobat724_front/.claude/prompt/fix-header-logged-state.md
hamedandClaude Opus 4.8 83b2ec21b7 fix(header): detect login state live from client cookie
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>
2026-06-15 18:41:58 +03:30

7.1 KiB

رفع نمایش «ورود | ثبت‌نام» در هدر با وجود لاگین‌بودن کاربر

پروژه

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 سرور

"use client";
// ...
function Content({ matchedCity, name, logged }) {
  // ...
  {logged ? (
    <ProfileUser />
  ) : (
    <Link href="/login">
      <Button variant="outlined" color="primary">ورود | ثبت نام</Button>
    </Link>
  )}
}

StLayout.js — مقدار اولیه از کوکی سرور

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)

Cookies.set("access_token", response.access_token, cookieOptions); // js-cookie, غیر httpOnly

وظایف

۱. تشخیص زنده‌ی لاگین در header/Content.js

  • یک state کلاینتی برای لاگین‌بودن اضافه کن که مقدار اولیه‌اش از prop logged (سرور) می‌آید تا paint اول درست باشد، سپس در useEffect با کوکی access_token همگام شود:
"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 اضافه کنی تا با هر تغییر مسیر کوکی دوباره چک شود:
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 با پیام توصیفی.