# رفع ماندن صفحه‌ی لاگین بعد از وارد کردن کد OTP (نشست ساخته نمی‌شود)

## پروژه

`nobat724_front` — سایت عمومی. **این کار صرفاً frontend است؛ backend درست کار می‌کند** (با curl تأیید شد: `send-code` → uuid، `verify-code` با `12345` → ۲۰۰، `oauth/token` → توکن، `oauth/userinfo` → `{ success, data: {...} }`).

## زمینه

در صفحه‌ی `/login`، کاربر شماره موبایل را وارد می‌کند، کد `12345` (کد ثابت محیط dev) را می‌زند، ولی **صفحه روی `/login` می‌ماند** و وارد نمی‌شود. توکن گرفته می‌شود ولی نشست کامل نمی‌شود.

علت دقیق: بعد از گرفتن توکن، `components/register/verificationPage/SendReq.js` تابع `getInfo` را صدا می‌زند که `request.getUserInfo()` (یعنی `GET /oauth/userinfo`) را فراخوانی می‌کند و فقط در صورت `res.uuid` کوکی `userInfo` را ست و ریدایرکت می‌کند. اما پاسخ `oauth/userinfo` به شکل `{ success: true, data: { uuid, mobile_number, roles, ... } }` است و interceptor در `services/api.js` **یک‌بار** پاسخ را باز می‌کند، پس داده‌ی واقعی در `res.data` است، نه `res` مستقیم. در نتیجه `res.uuid === undefined`، شرط `if (res && res.uuid)` رد می‌شود، **نه کوکی `userInfo` ست می‌شود و نه ریدایرکت اتفاق می‌افتد** → صفحه می‌ماند.

> توجه: این همان pitfall «double-nesting» است که قبلاً در پروفایل و لیست بیمه هم دیده شد. اینجا پاسخ یک‌لایه envelope دارد (`{success, data}`)؛ بعد از interceptor، داده در `res.data`.

## مشکل / هدف

تابع `getInfo` در `SendReq.js` باید کاربر را از `res.data` بخواند (نه `res`)، کوکی `userInfo` را با **شیء کاربر** (نه envelope) ست کند، و سپس ریدایرکت/پیشروی مرحله را انجام دهد. همچنین در صورت پاسخ نامعتبر، خطای واضح نمایش داده شود (نه ماندن بی‌صدا).

## فایل‌های مرتبط

| فایل | نقش |
|------|-----|
| `components/register/verificationPage/SendReq.js` | بعد از گرفتن توکن، userinfo می‌گیرد و کوکی `userInfo` را ست می‌کند — محل باگ |
| `services/response.js` | `getUserInfo` → `GET /oauth/userinfo` با `requireAuth` |
| `services/api.js` | interceptor که یک‌بار `response.data` را برمی‌گرداند |
| `lib/auth.js` | `getUser()` — وجود کوکی `userInfo` را چک می‌کند (server-side) |
| مصرف‌کننده‌های `userInfo` | `components/appointment/index.js`، `components/layout/sidebar/*`، `components/dashboard/*` — همگی `userInfo` را parse می‌کنند و انتظار شیء کاربر (`.uuid`، `.mobile_number`، `.representation_uuid`) دارند |

## وضعیت فعلی (کد واقعی)

### `SendReq.js` → `getInfo` (باگ‌دار)

```js
const getInfo = (token, cookieOptions) => {
  request
    .getUserInfo({ headers: { Authorization: `Bearer ${token}` } })
    .then((res) => {
      setLoading(false);
      if (res && res.uuid) {                                   // ❌ uuid در res.data است نه res
        Cookies.set("userInfo", JSON.stringify(res), cookieOptions);  // ❌ کل envelope ذخیره می‌شود
        if (setStep) {
          setStep(3);
        } else {
          window.location.pathname = "/";
        }
      }
      // ❌ اگر uuid نبود، هیچ اتفاقی نمی‌افتد — صفحه می‌ماند، خطا هم نشان داده نمی‌شود
    })
    .catch(() => {
      setLoading(false);
      setIsError(true);
    });
};
```

### قرارداد واقعی `GET /oauth/userinfo` (از `clinicpro/docs/api/auth.md`)

```json
{
  "success": true,
  "data": {
    "id": 4766,
    "uuid": "550e8400-...",
    "mobile_number": "09123456789",
    "realName": "...",
    "roles": ["ROLE_USER"],
    "primary_role": "...",
    "doctor_uuid": "...",
    "representation_uuid": null
  }
}
```

> بعد از interceptor: داده در `res.data`. هیچ فیلد `username` در پاسخ نیست؛ شماره موبایل `mobile_number` است.

## وظایف

### ۱. خواندن کاربر از `res.data` و ست‌کردن کوکی با شیء کاربر

```js
const getInfo = (token, cookieOptions) => {
  request
    .getUserInfo({ headers: { Authorization: `Bearer ${token}` } })
    .then((res) => {
      setLoading(false);
      const userInfo = res?.data;
      if (userInfo?.uuid) {
        Cookies.set("userInfo", JSON.stringify(userInfo), cookieOptions);
        if (setStep) {
          setStep(3);
        } else {
          window.location.href = "/";
        }
      } else {
        setIsError(true);
        toast.error("دریافت اطلاعات کاربر ناموفق بود. دوباره تلاش کنید.");
      }
    })
    .catch(() => {
      setLoading(false);
      setIsError(true);
    });
};
```

- کوکی `userInfo` باید **شیء کاربر** (`res.data`) باشد تا مصرف‌کننده‌ها (`parsedData.uuid`, `.mobile_number`, `.representation_uuid`) درست کار کنند.
- `window.location.href = "/"` به‌جای `window.location.pathname = "/"` (مطمئن‌تر برای reload کامل و خواندن کوکی‌های تازه server-side).
- `toast` از قبل در این فلو استفاده می‌شود (در همان فایل بعد از رفع قبلی `import { toast } from "react-toastify"` اضافه شده) — اگر نبود اضافه کن؛ `ToastContainer` در `app/layout.js` mount است.

### ۲. سازگاری مصرف‌کننده‌های `username`

- بعضی جاها (مثل `components/appointment/index.js`) `parsedData.username` خوانده می‌شود، ولی پاسخ `oauth/userinfo` فیلد `username` ندارد (فقط `mobile_number`). بررسی کن:
  - یا در `getInfo` هنگام ذخیره، یک فیلد `username` معادل `mobile_number` هم اضافه کن (سازگاری عقب‌رو، کم‌ریسک‌تر):
    ```js
    const userInfo = { ...res.data, username: res.data.mobile_number };
    ```
  - یا همه‌ی خواننده‌های `username` را به `mobile_number` مهاجرت بده.
  - **یکی را انتخاب کن**؛ گزینه‌ی اول (افزودن `username`) دیف کوچک‌تری دارد و فلوی appointment را نمی‌شکند. در گزارش ذکر کن.

### ۳. تأیید عدم ماندن بی‌صدا

- اگر `verify-code` یا `oauth/token` خطا داد (۴۰۰)، از قبل (رفع قبلی) پیام toast نمایش داده می‌شود. مطمئن شو مسیر userinfo هم در خطا، `setIsError(true)` و پیام می‌دهد، نه سکوت.

## نکات مهم

- **هیچ تغییری در backend لازم نیست.** پاسخ `oauth/userinfo` درست است؛ فقط استخراج سمت کلاینت غلط بود.
- **interceptor:** همه‌ی `request.*` بدنه‌ی HTTP را یک‌بار باز می‌کنند → برای `{success, data}` داده در `res.data`. این الگو را با pitfallهای قبلی (پروفایل، بیمه) یکدست نگه‌دار.
- **شکل کوکی `userInfo`:** باید شیء کاربر باشد (نه `{success, data}`)؛ مصرف‌کننده‌های متعدد (`appointment`, `sidebar`, `dashboard`, `lib/auth.getUser`) به این متکی‌اند. تغییر این شکل را در همه‌ی مصرف‌کننده‌ها بررسی کن.
- **دو مسیر استفاده از `SendReq`:** صفحه‌ی مستقل `/login` (`RegisterPage` با `setStep={false}` → ریدایرکت به `/`) و فلوی نوبت (`setStep` تابع است → `setStep(3)`). هر دو باید بعد از رفع کار کنند. هر دو را تست کن.
- **dev OTP:** کد ثابت `12345` است؛ هنگام تست با همین وارد شو.
- **rate-limit:** backend روی `send-code` rate-limit دارد؛ اگر حین تست `ERR_RATE_LIMIT_001` گرفتی، چند دقیقه صبر کن یا شماره‌ی متفاوت بزن.
- **تست:** `npm run build`؛ سپس دستی روی `/login`: موبایل + `12345` → باید کوکی‌های `access_token`/`refresh_token`/`uuid`/`userInfo` ست شوند و به `/` ریدایرکت شود. سپس commit با پیام توصیفی.
