A guest tapping a personal page — orders, transactions, profile, favourites, complete-profile — was redirected in silence, so they landed on the login box with nothing explaining what happened. Legacy showed «ابتدا وارد شوید» (henza_pwa/src/router/index.ts:259). The message is now shown in both apps. The destination deliberately stays the target's /login?redirect=…, which legacy did not have: legacy sent the guest to home and lost where they were going, while here signing in returns them to the page they asked for. The owner chose that trade-off, so it is logged as «عمداً تکرار نشد — D3/الف» rather than quietly left as a divergence. The guard also keys off meta.requiresAuth instead of legacy's hand-kept list of five route names, so the two sales-expert pages are covered too. Legacy's isLoaded router guard is NOT ported, and the reasoning is written into both apps' configStore beside the line that says so: it guarded legacy's one big bootstrap request, which this target does not have, so it would guard nothing and only add a bounce — a shared product link would open home instead of the product. That is porting the breakage, not the behaviour (D4). Route analytics is deferred at the owner's request; its design is parked in docs/PHASE2-D3-PLAN.md so it need not be re-derived, along with the two reference defects not to copy (log/create was unauthenticated and accepted any title; log/update was a GET that wrote) and the open retention and IP questions. Seven of D3's ten items turned out to be built already — all five screens in both apps, the sts_verify_step_two guard, v2's cart ceiling — verified in the code rather than from the checkmarks. Neither app has a test runner: verified with vue-tsc -b on both (clean) plus confirming the toast can actually appear, not merely compile — toast.info exists and <ToastHost /> is mounted in each App.vue. اپ: وقتی بدون ورود صفحه شخصی را باز کنید، پیام «ابتدا وارد شوید» نشان داده میشود. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
158 lines
9.8 KiB
Markdown
158 lines
9.8 KiB
Markdown
# طرح فاز ۲ — قلم ۲ (D3): سه رفتار جامانده در اپها
|
||
|
||
تاریخ: ۱۴۰۵/۰۷/۲۰ (2026-10-12)
|
||
|
||
## وضعیت — تصمیم مالک گرفته شد (۲۰۲۶/۱۰/۱۲)
|
||
|
||
> «الف و ب مهم پس به ترتیب برو جلو — ج اصلا اولیوت نداره جزو اخرین چیز هایی هست
|
||
> که شاید درستش کنیم»
|
||
|
||
| قلم | تصمیم | وضعیت |
|
||
|---|---|---|
|
||
| **الف** — پیام مهمان | انجام شود | ✅ **انجام شد** — گزینه ب (پیام + حفظ بازگشت به مقصد) در هر دو اپ |
|
||
| **ب** — گارد `isLoaded` | انجام شود، طبق پیشنهاد پورت **نشود** | ✅ **ثبت شد** بهعنوان «عمداً تکرار نشد — D3/ب» در `configStore.ts` هر دو اپ |
|
||
| **ج** — آمار بازدید | **آخرین اولویت، شاید هرگز** | ⏸️ معوق — بخش «شکاف ۳» پایین بهعنوان طرحِ آماده نگه داشته شد |
|
||
|
||
---
|
||
|
||
---
|
||
|
||
## چه چیزی قبلاً انجام شده (بررسی شد، ادعا نیست)
|
||
|
||
D3 ده کار بود. هفتتایش انجام شده است:
|
||
|
||
| کار | وضعیت | مدرک |
|
||
|---|---|---|
|
||
| ۵ صفحه `/report`، `/report/detail`، `/ChildOrder`، `/customerChoice`، `/complete_profile` | ✅ در **هر دو** اپ | `apps/user-app/src/router/index.ts` و همان فایل در `user-app-v2` — هر پنج مسیر ثبت شده با `meta.layout='FullPage'` |
|
||
| گارد `sts_verify_step_two` روی تکمیل پروفایل | ✅ | `router/index.ts` گارد `requiresIncompleteProfile`؛ از سرور میپرسد نه از حافظه، پس پروفایلی که روی دستگاه دیگر کامل شده هم محترم است |
|
||
| سقف موجودی سبد v2 (`capQty`/`maxQty`) | ✅ یکسان در هر دو اپ | `stores/cartStore.ts:80,88` در هر دو |
|
||
|
||
**سه کار باقی است** و هر سه را بررسی و در مرجع تأیید کردم.
|
||
|
||
---
|
||
|
||
## شکاف ۱ — رفتار مهمان: الان به صفحه ورود پرتاب میشود
|
||
|
||
**مرجع** (`henza_pwa/src/router/index.ts:253-261`): کاربر واردنشده که یکی از پنج
|
||
صفحه شخصی را باز کند، یک پیام «ابتدا وارد شوید» میبیند و **به خانه** میرود.
|
||
|
||
```ts
|
||
if (to.name === 'ordersList' || ... || to.name === 'completeProfile') {
|
||
showNotify('ابتدا وارد شوید');
|
||
return next({name: 'home'})
|
||
} else return next()
|
||
```
|
||
|
||
**تارگت** (`apps/user-app/src/router/index.ts:139-141`): به `/login` میرود با
|
||
`query.redirect`، و **هیچ پیامی نشان نمیدهد**.
|
||
|
||
```ts
|
||
if (to.meta.requiresAuth && !session.isLoggedIn) {
|
||
return { name: 'login', query: { redirect: to.fullPath } };
|
||
}
|
||
```
|
||
|
||
**چرا مهم است:** کاربر مهمان که روی «سفارشهای من» میزند، بیهیچ توضیحی در صفحه
|
||
ورود میافتد و نمیداند چه شد. رفتار مرجع صریحتر است و D3 هم همان را خواسته.
|
||
|
||
### ⚠️ یک تضاد که باید خودت تصمیم بگیری
|
||
|
||
رفتار فعلی تارگت از یک جهت **بهتر** است: `query.redirect` دارد، یعنی بعد از
|
||
ورود، کاربر به همان صفحهای که میخواست برمیگردد. رفتار مرجع این را ندارد —
|
||
کاربر به خانه میرود و باید دوباره دستی به آن صفحه برود.
|
||
|
||
سه گزینه:
|
||
|
||
| گزینه | نتیجه |
|
||
|---|---|
|
||
| **الف) عیناً مرجع** | toast + خانه. `query.redirect` از بین میرود. تطبیق کامل با D3. |
|
||
| **ب) بهترین هر دو** (پیشنهاد من) | toast «ابتدا وارد شوید» نشان داده شود، **ولی** مقصد همان صفحه ورود با `redirect` بماند. کاربر هم میفهمد چه شد، هم بعد از ورود سرِ جایش برمیگردد. در ماتریس بهعنوان «عمداً تکرار نشد» ثبت میشود. |
|
||
| **ج) دست نزن** | شکاف باز میماند و در ماتریس ثبت میشود. |
|
||
|
||
هزینه هر سه یکی است (چند خط). تفاوت فقط در تجربه کاربر است.
|
||
|
||
---
|
||
|
||
## شکاف ۲ — گارد `isLoaded`
|
||
|
||
**مرجع** (`router/index.ts:263-266`): کاربر واردشدهای که دادههای اولیهاش
|
||
(`memory.state.isLoaded`) هنوز نیامده، به هر صفحهای جز `home` برود به خانه
|
||
برگردانده میشود — مگر آن مسیر `meta.skipLoadedCheck` داشته باشد.
|
||
|
||
**تارگت**: پورت نشده، و در کد **صریحاً نوشته شده چرا**:
|
||
|
||
```ts
|
||
// apps/user-app/src/stores/configStore.ts:19
|
||
// Not ported (no backend): promotionList, ignoreList, priceList, isLoaded.
|
||
```
|
||
|
||
**نظر من: این یکی را نباید پورت کنیم، و پیشنهادم «نه» است.**
|
||
|
||
دلیل: `isLoaded` در مرجع پرچمِ «یک درخواست بزرگ اولیه نشست» بود که تارگت ندارد —
|
||
تارگت هر صفحه دادهی خودش را میگیرد. اگر این گارد را اضافه کنیم، چیزی به کاربر
|
||
اضافه نمیشود و فقط یک پرش ناخواسته به خانه ساخته میشود: مثلاً کاربری که لینک
|
||
محصول را از تلگرام باز میکند، بهجای محصول در خانه میافتد. این دقیقاً مصداق
|
||
«باگ مرجع را پورت نکن» (D4) است.
|
||
|
||
اگر موافقی، بهعنوان **«عمداً تکرار نشد — D4»** ثبت میشود و D3 بسته میشود.
|
||
اگر مخالفی، پورت میکنم.
|
||
|
||
---
|
||
|
||
## شکاف ۳ — آمار بازدید صفحهها (`log/create` و `log/update`)
|
||
|
||
**مرجع** دو مسیر دارد و زمان ماندن کاربر در هر صفحه را اندازه میگیرد:
|
||
|
||
| | مرجع |
|
||
|---|---|
|
||
| ورود به صفحه | `POST /log/create/{title}` → یک ردیف با `start_at`، `ip`، `title`، `customer_id` (اگر وارد شده باشد) میسازد و `id` را برمیگرداند |
|
||
| خروج از صفحه | `GET /log/update/{id}` → `end_at` و `time_present` را پر میکند |
|
||
| صفحه محصول | `data: {product_id}` هم میفرستد تا بازدید هر محصول جدا شمرده شود |
|
||
| مستثنا | `login` و `userPage::hasSubRoute` |
|
||
|
||
مرجع: `henza_server/app/Http/Controllers/Api/LogController.php`،
|
||
`henza_pwa/src/router/customAnalytics.ts`.
|
||
|
||
**تارگت**: نه مسیر، نه جدول، نه فراخوانی. کامل غایب.
|
||
|
||
این بزرگترین قلم از سهتاست — جدول، مدل Prisma، مهاجرت، دو مسیر API، یک
|
||
composable در هر اپ، و صفحههای گزارش ادمین (`queryTitle`، `queryAllCustomer`،
|
||
`queryCustomer`، `ago`) که آمار را نشان میدهند.
|
||
|
||
### ⚠️ دو ایراد مرجع که عیناً پورت نمیکنم
|
||
|
||
۱. **مسیر بیمحافظ است.** در مرجع `log/create` احراز هویت ندارد و هر عنوانی را
|
||
میپذیرد، پس هر کسی از بیرون میتواند جدول را با ردیفهای جعلی پر کند و آمار
|
||
را بیمعنا یا دیسک را پر کند. پیشنهاد: عنوان فقط از یک فهرست بسته (نام
|
||
مسیرهای واقعی) پذیرفته شود و نرخ درخواست محدود شود.
|
||
۲. **`GET` که داده را عوض میکند.** `log/update` یک `GET` است که ردیف را
|
||
مینویسد. پیشنهاد: `POST` باشد. (اپهای تارگت تازه نوشته میشوند، پس سازگاری
|
||
عقبرو لازم نیست.)
|
||
|
||
### ⚠️ و یک پرسش که فقط تو میتوانی جواب بدهی
|
||
|
||
این قابلیت **رفتار هر مشتری را با IP ثبت میکند** — کدام صفحهها را دید و چند
|
||
ثانیه ماند. فنیاش ساده است، ولی این دادهی شخصی است و مرجع هیچوقت آن را پاک
|
||
نمیکرد (جدول بینهایت رشد میکند).
|
||
|
||
سه پرسش:
|
||
|
||
| پرسش | پیشنهاد من |
|
||
|---|---|
|
||
| این آمار را واقعاً لازم داری، یا در سیستم قدیم هم عملاً نگاهش نمیکردی؟ | اگر نگاهش نمیکردی، نسازیم. سادهترین قابلیت، قابلیتی است که وجود ندارد. |
|
||
| اگر لازم است، چند وقت نگه داشته شود؟ | ۹۰ روز، و قدیمیترها خودکار پاک شوند. |
|
||
| IP هم ذخیره شود؟ | نه. برای «کدام صفحه چقدر دیده شد» لازم نیست و حساسترین بخش داده است. |
|
||
|
||
---
|
||
|
||
## جمعبندی و پیشنهاد ترتیب کار
|
||
|
||
| # | قلم | پیشنهاد من | هزینه |
|
||
|---|---|---|---|
|
||
| ۱ | toast مهمان | **گزینه ب** — هم پیام، هم بازگشت به صفحه مقصد | کم (نیم ساعت، هر دو اپ) |
|
||
| ۲ | گارد `isLoaded` | **پورت نشود** — «عمداً تکرار نشد — D4» | صفر |
|
||
| ۳ | آمار بازدید | **اول جواب بده لازمش داری یا نه.** اگر بله: با عنوان بسته، `POST`، بدون IP، نگهداری ۹۰ روزه | متوسط تا زیاد (جدول + مهاجرت + ۲ مسیر + ۲ اپ + صفحه گزارش ادمین) |
|
||
|
||
اگر بگویی «قلم ۱ و ۲ را انجام بده، ۳ را فعلاً نه»، D3 با یک ردیف «عمداً تکرار
|
||
نشد» برای آمار بسته میشود و میرویم سراغ قلم بعدی فاز ۲.
|