fix(dashboard): guard null userInfo in Head to stop 500 crash
getParsedUserInfo() can return null (cookie missing/unparseable), so reading userInfo.realName crashed the dashboard with a 500. Default to an empty object and fall back to the user prop's name or 'کاربر'. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
# رفع باگهای داشبورد کاربر: کوکی uuid اشتباه، URL غلط نوبتها/پرداختها، و crash هدر
|
||||
|
||||
## پروژه
|
||||
|
||||
`nobat724_front` — سایت عمومی. **بعد از پرامپت backend اجرا شود.**
|
||||
|
||||
> **Cross-repo:** تب پرداختها به endpoint جدید backend وابسته است:
|
||||
> `GET /api/v1/my/payments` (در `clinicpro/.claude/prompt/my-payments-list-endpoint.md`).
|
||||
|
||||
## زمینه
|
||||
|
||||
داشبورد کاربر چند خطای ۴۰۴ و یک crash میدهد:
|
||||
|
||||
1. **پروفایل ۴۰۴ (server-side):** `app/dashboard/page.js` پروفایل را با `cookieStore.get("uuid")` میگیرد، اما کوکی `uuid` هنگام لاگین با **uuid اوتیپی (send-code)** ست شده، نه uuid کاربر. پس `GET /user-profile/{otp-uuid}` ۴۰۴ میدهد (نه با پروفایل match میشود نه با کاربر).
|
||||
2. **crash هدر:** `components/dashboard/userAccount/Head.js` مقدار `userInfo.realName` را روی نتیجهی `getParsedUserInfo()` میخواند که ممکن است `null` باشد → «Cannot read properties of null (reading 'realName')».
|
||||
3. **نوبتهای من ۴۰۴:** `getMyAppointments(userId)` به `appointment/my-appointments/{userId}` میزند که **route ناموجود** است. route درست: `GET /api/v1/my/appointments` (کاربر از توکن).
|
||||
4. **تراکنشها ۴۰۴:** `getMyPayments(userId)` به `payment/my-payments/{userId}` میزند که **وجود ندارد** → با endpoint جدید `GET /api/v1/my/payments` جایگزین میشود.
|
||||
|
||||
## ریشهی اصلی کوکی `uuid`
|
||||
|
||||
در `components/register/verificationPage/SendReq.js`، مقدار `uuid` که ست میشود **uuid اوتیپی** (از `send-code`) است، نه uuid کاربر. uuid واقعی کاربر در کوکی `userInfo` (`res.data.uuid` از userinfo) موجود است.
|
||||
|
||||
```js
|
||||
// SendReq.js — handleSetCookie
|
||||
Cookies.set("uuid", uuid, { ... }); // ❌ uuid اوتیپی، نه uuid کاربر
|
||||
```
|
||||
|
||||
## فایلهای مرتبط
|
||||
|
||||
| فایل | نقش |
|
||||
|------|-----|
|
||||
| `components/register/verificationPage/SendReq.js` | ستکردن کوکی `uuid` هنگام لاگین — باید uuid کاربر شود |
|
||||
| `app/dashboard/page.js` | fetch پروفایل server-side با کوکی `uuid` |
|
||||
| `components/dashboard/userAccount/Head.js` | `userInfo.realName` بدون گارد null |
|
||||
| `services/response.js` | `getMyAppointments`, `getMyPayments` — URLهای غلط |
|
||||
| `components/dashboard/userAccount/sidebars/turns/index.js` | مصرف `getMyAppointments` |
|
||||
| `components/dashboard/userAccount/sidebars/transactions/index.js` | مصرف `getMyPayments` |
|
||||
|
||||
## وضعیت فعلی (کد واقعی)
|
||||
|
||||
### `services/response.js` — URLهای ناموجود
|
||||
|
||||
```js
|
||||
getMyAppointments: (userId, params) => api.get(`api/v1/appointment/my-appointments/${userId}`, { params, requireAuth: true }),
|
||||
getMyPayments: (userId, params) => api.get(`api/v1/payment/my-payments/${userId}`, { params, requireAuth: true }),
|
||||
```
|
||||
|
||||
### `app/dashboard/page.js`
|
||||
|
||||
```js
|
||||
const uuid = cookieStore.get("uuid")?.value; // ❌ uuid اوتیپی
|
||||
profile = await fetchReq(`${API_URL}/api/v1/user-profile/${uuid}`, { headers: { Authorization: `Bearer ${token.value}` } });
|
||||
```
|
||||
|
||||
### `Head.js`
|
||||
|
||||
```js
|
||||
const userInfo = getParsedUserInfo();
|
||||
// ...
|
||||
{userInfo.realName} // ❌ اگر null → crash
|
||||
```
|
||||
|
||||
## وظایف
|
||||
|
||||
### ۱. کوکی `uuid` = uuid کاربر (`SendReq.js`)
|
||||
|
||||
- در `handleSetCookie`، بهجای ستکردن `uuid` اوتیپی، uuid واقعی کاربر را ست کن. چون `getInfo` بعداً پروفایل/userinfo را میگیرد، بهترین جا برای ستکردن `uuid` کاربر **داخل `getInfo`** است بعد از دریافت `res.data.uuid`:
|
||||
|
||||
```js
|
||||
const getInfo = (token, cookieOptions) => {
|
||||
request.getUserInfo({ headers: { Authorization: `Bearer ${token}` } })
|
||||
.then((res) => {
|
||||
const profile = res?.data;
|
||||
if (profile?.uuid) {
|
||||
Cookies.set("uuid", profile.uuid, cookieOptions); // ✅ uuid کاربر (overwrite uuid اوتیپی)
|
||||
Cookies.set("userInfo", JSON.stringify({ ...profile, username: profile.mobile_number }), cookieOptions);
|
||||
// ... redirect / setStep ...
|
||||
}
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
> کوکی `uuid` در `handleSetCookie` با uuid اوتیپی ست میشود؛ این خط را یا حذف کن یا بگذار و در `getInfo` با uuid کاربر overwrite کن (overwrite سادهتر است). نتیجه: کوکی نهایی `uuid` = uuid کاربر.
|
||||
|
||||
### ۲. گارد null در `Head.js`
|
||||
|
||||
```js
|
||||
const userInfo = getParsedUserInfo() || {};
|
||||
// ...
|
||||
{userInfo?.realName || "کاربر"}
|
||||
```
|
||||
|
||||
- اگر `user` (prop از `buildPatientUser`) هم اطلاعات دارد، میتوانی بهجای کوکی از `user.name` استفاده کنی؛ ولی حداقل گارد null اجباری است تا crash نشود.
|
||||
|
||||
### ۳. اصلاح URLها در `services/response.js`
|
||||
|
||||
```js
|
||||
getMyAppointments: (params) => api.get(`api/v1/my/appointments`, { params, requireAuth: true }),
|
||||
getMyPayments: (params) => api.get(`api/v1/my/payments`, { params, requireAuth: true }),
|
||||
```
|
||||
|
||||
- امضای تابع `userId` را حذف کن (کاربر از توکن میآید). همهی callerها را بهروز کن.
|
||||
|
||||
### ۴. بهروزرسانی callerها (turns + transactions)
|
||||
|
||||
- در `sidebars/turns/index.js` و `sidebars/transactions/index.js`: `userId` را حذف کن، فقط `params` بفرست:
|
||||
|
||||
```js
|
||||
const response = await request.getMyAppointments(params); // turns
|
||||
const response = await request.getMyPayments(params); // transactions
|
||||
```
|
||||
|
||||
- شکل پاسخ paginated است (`paginated()` در backend): items از `response?.data` و total از `response?.meta?.totalRecords`/`totalPages`. کد فعلی `response.page.totalPages` میخواند — با شکل واقعی (`meta`) همخوان کن:
|
||||
|
||||
```js
|
||||
if (Array.isArray(response?.data)) {
|
||||
setAppointments(response.data); // یا setPayments
|
||||
setTotalPages(response?.meta?.totalPages || 1);
|
||||
}
|
||||
```
|
||||
|
||||
> **قرارداد دقیق پاسخ را از پرامپت/داک backend بگیر** (`docs/api/payment.md` و `docs/api/appointment.md` بخش my/*). اگر `my/appointments` برای نقش کاربرِ ساده خالی برمیگرداند، با backend چک کن کدام endpoint نوبتهای خود بیمار را میدهد (شاید `GET /api/v1/appointments/user`). **اگر مبهم بود متوقف شو و بپرس.**
|
||||
|
||||
### ۵. (در صورت لزوم) `app/dashboard/page.js`
|
||||
|
||||
- حالا که کوکی `uuid` = uuid کاربر است، `fetchReq(/user-profile/{uuid})` با endpoint اصلاحشدهی backend (resolve با user-uuid + lazy-create) ۲۰۰ میدهد. تأیید کن `buildPatientUser(profile)` با شکل پاسخ (`{success, data:{data:{...}}}` → `profile.data.data`) همخوان است؛ اگر `buildPatientUser` از `profile?.data` میخواند ولی پاسخ دوبار تودرتو است، عمق استخراج را اصلاح کن.
|
||||
|
||||
## نکات مهم
|
||||
|
||||
- **وابستگی cross-repo:** تب تراکنشها به `GET /api/v1/my/payments` جدید نیاز دارد؛ اگر نبود اول backend را اجرا کن.
|
||||
- **امنیت/درستی:** endpointهای `my/*` کاربر را از توکن میگیرند؛ دیگر `userId` در URL نفرست.
|
||||
- **شکل پاسخ:** `paginated()` → `data` آرایه، `meta.totalRecords`/`meta.totalPages`. این با `success(['data'=>...])` (دوبار تودرتو) فرق دارد — هرکدام را درست مصرف کن.
|
||||
- **کوکی uuid:** بعد از این تغییر، همهی مصرفکنندههای کوکی `uuid` (dashboard/page.js, SubmitData.js, lib/auth.js) uuid کاربر میگیرند — درست.
|
||||
- RTL/Jalali/multi-domain حفظ شوند؛ توهمسازی داده نکن (لیست خالی = پیام «موردی نیست»).
|
||||
- **تست:** `npm run build`؛ سپس دستی با کاربر `09210651788`: داشبورد بدون ۴۰۴/crash لود شود؛ هدر نام کاربر یا «کاربر» را نشان دهد؛ تب نوبتها و تراکنشها بدون خطا (لیست خالی یا واقعی). سپس commit.
|
||||
@@ -4,14 +4,15 @@ import { getParsedUserInfo } from "@/helper";
|
||||
import Image from "next/image";
|
||||
|
||||
function Head({ user }) {
|
||||
const userInfo = getParsedUserInfo();
|
||||
const userInfo = getParsedUserInfo() || {};
|
||||
const displayName = userInfo.realName || user?.name || "کاربر";
|
||||
|
||||
return (
|
||||
<div className="hidden md:flex flex-col lg:flex-row w-full items-stretch justify-center gap-[12px] lg:gap-[24px]">
|
||||
<div className="bg-head-dashboard rounded-[8px] relative w-full !bg-no-repeat !bg-cover py-[20px] px-[24px] !bg-center flex items-center">
|
||||
<div className="flex w-full pl-[calc(5%+140px)] lg:pl-[calc(9%+171px)] flex-col justify-start items-start gap-[8px]">
|
||||
<p className="text-[#3B3B3B] text-[16px] font-bold">
|
||||
{userInfo.realName}{" "}
|
||||
{displayName}{" "}
|
||||
<span className="text-[#3B3B3B] text-[16px] font-normal">
|
||||
عزیز!
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user