Files
s.esmaeiliandClaude Opus 5 89e2d7937e feat(apps): tell a guest why they were bounced to the login page (D3)
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>
2026-10-12 10:40:22 +03:30

158 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# طرح فاز ۲ — قلم ۲ (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 با یک ردیف «عمداً تکرار
نشد» برای آمار بسته می‌شود و می‌رویم سراغ قلم بعدی فاز ۲.