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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fd9191eca8
commit
89e2d7937e
@@ -4,6 +4,7 @@ import { useNavStore } from '../stores/navStore';
|
|||||||
import { useTransitionStore } from '../stores/transitionStore';
|
import { useTransitionStore } from '../stores/transitionStore';
|
||||||
import { api } from '../api/client';
|
import { api } from '../api/client';
|
||||||
import { useCachedReads } from '../api/cachedReads';
|
import { useCachedReads } from '../api/cachedReads';
|
||||||
|
import { toast } from '../composables/useToast';
|
||||||
|
|
||||||
// Legacy lazy-loaded every route component (`component: () => import(...)`) so
|
// Legacy lazy-loaded every route component (`component: () => import(...)`) so
|
||||||
// the initial bundle only carries the entry route. Matched here.
|
// the initial bundle only carries the entry route. Matched here.
|
||||||
@@ -131,7 +132,13 @@ router.beforeEach(async (to) => {
|
|||||||
if (to.name === 'login' && session.isLoggedIn) {
|
if (to.name === 'login' && session.isLoggedIn) {
|
||||||
return { name: 'home' };
|
return { name: 'home' };
|
||||||
}
|
}
|
||||||
|
// D3 — see the same guard in user-app's router for the full reasoning.
|
||||||
|
// Legacy showed the guest «ابتدا وارد شوید» (henza_pwa/src/router/index.ts:259)
|
||||||
|
// and the target redirected silently. The owner kept the target's `redirect`
|
||||||
|
// so signing in returns the guest to the page they wanted, which legacy did
|
||||||
|
// not do — «عمداً تکرار نشد — D3/الف».
|
||||||
if (to.meta.requiresAuth && !session.isLoggedIn) {
|
if (to.meta.requiresAuth && !session.isLoggedIn) {
|
||||||
|
toast.info('ابتدا وارد شوید');
|
||||||
return { name: 'login', query: { redirect: to.fullPath } };
|
return { name: 'login', query: { redirect: to.fullPath } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@
|
|||||||
// HomeSlider's popup/link/discount click handling works unchanged.
|
// HomeSlider's popup/link/discount click handling works unchanged.
|
||||||
//
|
//
|
||||||
// Not ported (no backend): promotionList, ignoreList, priceList, isLoaded.
|
// Not ported (no backend): promotionList, ignoreList, priceList, isLoaded.
|
||||||
|
//
|
||||||
|
// `isLoaded` — «عمداً تکرار نشد — D3/ب» (owner, 2026-10-12). Legacy used it as a
|
||||||
|
// router guard against a bootstrap request this app does not have; see the same
|
||||||
|
// note in user-app's configStore for the full reasoning (D4).
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { useCatalogStore } from './catalogStore'
|
import { useCatalogStore } from './catalogStore'
|
||||||
import shippingArt from '../assets/img/shipping-and-delivery.png'
|
import shippingArt from '../assets/img/shipping-and-delivery.png'
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useTransitionStore } from '../stores/transitionStore';
|
|||||||
import { api } from '../api/client';
|
import { api } from '../api/client';
|
||||||
import { useCachedReads } from '../api/cachedReads';
|
import { useCachedReads } from '../api/cachedReads';
|
||||||
import { afterPageLeave } from '../composables/usePageTransition';
|
import { afterPageLeave } from '../composables/usePageTransition';
|
||||||
|
import { toast } from '../composables/useToast';
|
||||||
|
|
||||||
// Legacy lazy-loaded every route component (`component: () => import(...)`) so
|
// Legacy lazy-loaded every route component (`component: () => import(...)`) so
|
||||||
// the initial bundle only carries the entry route. Matched here.
|
// the initial bundle only carries the entry route. Matched here.
|
||||||
@@ -136,7 +137,25 @@ router.beforeEach(async (to) => {
|
|||||||
if (to.name === 'login' && session.isLoggedIn) {
|
if (to.name === 'login' && session.isLoggedIn) {
|
||||||
return { name: 'home' };
|
return { name: 'home' };
|
||||||
}
|
}
|
||||||
|
// D3 — legacy told the guest why they were bounced
|
||||||
|
// (henza_pwa/src/router/index.ts:253-261: `showNotify('ابتدا وارد شوید')`),
|
||||||
|
// and the target redirected in silence, so tapping «سفارشهای من» landed on
|
||||||
|
// the login box with nothing explaining it.
|
||||||
|
//
|
||||||
|
// «عمداً تکرار نشد — D3/الف»: legacy then sent the guest to `home` and lost
|
||||||
|
// where they were going. The owner chose to keep the target's `redirect`, so
|
||||||
|
// signing in returns them to the page they asked for. The message is the
|
||||||
|
// parity row; the destination is deliberately better.
|
||||||
|
//
|
||||||
|
// Legacy listed five route names; the target gates on `meta.requiresAuth`,
|
||||||
|
// which also covers the two sales-expert pages — the same rule, applied
|
||||||
|
// wherever it belongs instead of a hand-kept list.
|
||||||
|
//
|
||||||
|
// Only reached when the IAM is off (the default) or unreachable: with silent
|
||||||
|
// SSO enabled the block above has already left the page, and a toast on a
|
||||||
|
// document that is being replaced would never be read. Not a gap.
|
||||||
if (to.meta.requiresAuth && !session.isLoggedIn) {
|
if (to.meta.requiresAuth && !session.isLoggedIn) {
|
||||||
|
toast.info('ابتدا وارد شوید');
|
||||||
return { name: 'login', query: { redirect: to.fullPath } };
|
return { name: 'login', query: { redirect: to.fullPath } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,14 @@
|
|||||||
// HomeSlider's popup/link/discount click handling works unchanged.
|
// HomeSlider's popup/link/discount click handling works unchanged.
|
||||||
//
|
//
|
||||||
// Not ported (no backend): promotionList, ignoreList, priceList, isLoaded.
|
// Not ported (no backend): promotionList, ignoreList, priceList, isLoaded.
|
||||||
|
//
|
||||||
|
// `isLoaded` — «عمداً تکرار نشد — D3/ب» (owner, 2026-10-12). Legacy used it as a
|
||||||
|
// router guard: a signed-in user whose one big bootstrap request had not landed
|
||||||
|
// was sent to `home` from any route lacking `meta.skipLoadedCheck`
|
||||||
|
// (henza_pwa/src/router/index.ts:263-266). The target has no such bootstrap —
|
||||||
|
// every page fetches its own data — so the flag would guard nothing and only
|
||||||
|
// add a bounce: opening a shared product link would land on home instead of the
|
||||||
|
// product. Replicating it would port the breakage, not the behaviour (D4).
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { useCatalogStore } from './catalogStore'
|
import { useCatalogStore } from './catalogStore'
|
||||||
import shippingArt from '../assets/img/shipping-and-delivery.png'
|
import shippingArt from '../assets/img/shipping-and-delivery.png'
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
# طرح فاز ۲ — قلم ۲ (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 با یک ردیف «عمداً تکرار
|
||||||
|
نشد» برای آمار بسته میشود و میرویم سراغ قلم بعدی فاز ۲.
|
||||||
@@ -876,3 +876,56 @@ earlier ruling this deviation stays, and it is re-recorded in `ISSUES.md` so it
|
|||||||
is not "fixed" later by someone restoring parity for its own sake.
|
is not "fixed" later by someone restoring parity for its own sake.
|
||||||
|
|
||||||
_No data was deleted in this entry._
|
_No data was deleted in this entry._
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-10-12 — Phase 2 — item #2: D3's three outstanding behaviours
|
||||||
|
|
||||||
|
- **What:** (1) A guest bounced off a personal page now sees «ابتدا وارد شوید»
|
||||||
|
in both apps. (2) Legacy's `isLoaded` router guard is recorded as deliberately
|
||||||
|
not ported. (3) Route analytics parked as ANLY-01.
|
||||||
|
- **Why:** D3's remaining items. Seven of its ten were already built — all five
|
||||||
|
screens in **both** apps, the `sts_verify_step_two` guard and v2's cart stock
|
||||||
|
ceiling — confirmed by reading the code rather than trusting the checkmarks.
|
||||||
|
- **Reference:** `henza_pwa/src/router/index.ts:253-261` (the notify + home),
|
||||||
|
`:263-266` (the `isLoaded` guard), `router/customAnalytics.ts` +
|
||||||
|
`henza_server/app/Http/Controllers/Api/LogController.php` (analytics).
|
||||||
|
- **Target:** both apps' `src/router/index.ts` and `src/stores/configStore.ts`.
|
||||||
|
- **Matrix row:** APP-03, APP-04, ANLY-01 in `ISSUES.md` §"Phase 2 — item #2".
|
||||||
|
- **Commit:** see below.
|
||||||
|
|
||||||
|
**The one judgement call.** Legacy showed the toast and then sent the guest to
|
||||||
|
`home`, discarding where they were going. The target redirected to `/login` with
|
||||||
|
`query.redirect`, so signing in returns the guest to the page they wanted — that
|
||||||
|
part is better than the reference, and the gap was only the missing message.
|
||||||
|
Rather than choose silently, the trade-off went to the owner, who kept the
|
||||||
|
target's destination. So the message is the parity row and the destination is
|
||||||
|
**«عمداً تکرار نشد — D3/الف»**. The guard also keys off `meta.requiresAuth`
|
||||||
|
instead of legacy's hand-kept list of five route names, which means the two
|
||||||
|
sales-expert pages are covered too.
|
||||||
|
|
||||||
|
**`isLoaded` — argued against, and the owner agreed.** It guarded legacy's one
|
||||||
|
big bootstrap request, which the target does not have (each page fetches its own
|
||||||
|
data). Added, it would guard nothing and introduce a bounce: a shared product
|
||||||
|
link would open `home` instead of the product. That is porting the breakage, not
|
||||||
|
the behaviour (D4). The reasoning is written into both apps' `configStore.ts`,
|
||||||
|
beside the line that says the flag was not ported, so the next reader finds it
|
||||||
|
where they would actually look.
|
||||||
|
|
||||||
|
**Analytics deferred by the owner** («ج اصلا اولیوت نداره»). The design is kept
|
||||||
|
in `docs/PHASE2-D3-PLAN.md` so it does not have to be re-derived, including the
|
||||||
|
two reference defects not to copy — `log/create` was unauthenticated and took any
|
||||||
|
title, so anyone could fill the table from outside, and `log/update` was a `GET`
|
||||||
|
that wrote — plus the two questions only the owner can answer: how long to keep
|
||||||
|
per-customer browsing history (legacy never deleted it) and whether to store the
|
||||||
|
IP at all.
|
||||||
|
|
||||||
|
**Verification.** Neither app has a test runner, so the check is `vue-tsc -b` on
|
||||||
|
both (clean) plus reading the three things that make the toast actually appear
|
||||||
|
rather than merely compile: `toast.info` exists (`composables/useToast.ts:66`),
|
||||||
|
`<ToastHost />` is mounted in each `App.vue:25`, and the call sits before the
|
||||||
|
guard's `return`. A compiling toast with no host would have been invisible.
|
||||||
|
Noted in the code: with the IAM enabled, silent SSO leaves the page before this
|
||||||
|
guard runs, so the toast is a no-op there by design, not a defect.
|
||||||
|
|
||||||
|
_No data was deleted in this entry._
|
||||||
|
|||||||
@@ -211,3 +211,15 @@ Severity: `Critical` (money loss / security / data loss) · `High` · `Medium`
|
|||||||
ruling the target's behaviour stays — identical whenever the client sends the
|
ruling the target's behaviour stays — identical whenever the client sends the
|
||||||
true weight (which the reference app always does, `cart.vue:178`), different
|
true weight (which the reference app always does, `cart.vue:178`), different
|
||||||
only for a forged request, which is the point.
|
only for a forged request, which is the point.
|
||||||
|
|
||||||
|
## Phase 2 — item #2, D3 close-out (2026-10-12)
|
||||||
|
|
||||||
|
Seven of D3's ten items were already built — all five screens exist in **both**
|
||||||
|
apps, as do the `sts_verify_step_two` guard and v2's cart stock ceiling
|
||||||
|
(verified in the code, not taken from a checkmark). Three were outstanding:
|
||||||
|
|
||||||
|
| ID | Severity | Where | Description | Evidence | Status |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| **APP-03** | Medium (UX) | REF `henza_pwa/src/router/index.ts:253-261` vs TGT `apps/user-app/src/router/index.ts`, `user-app-v2` same | 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 saying why. Legacy showed «ابتدا وارد شوید». | Read both guards; legacy calls `showNotify('ابتدا وارد شوید')` then `next({name:'home'})`. | ✅ **FIXED 2026-10-12** — `toast.info('ابتدا وارد شوید')` in both apps' `beforeEach`. **«عمداً تکرار نشد — D3/الف»** for the destination: legacy sent the guest to `home` and lost where they were going; the owner chose to keep the target's `query.redirect`, so signing in returns them to the page they asked for. Also applied via `meta.requiresAuth` rather than legacy's hand-kept list of five names, which additionally covers the two sales-expert pages. Only reached when the IAM is off (the default) or unreachable — silent SSO leaves the page before this runs, and a toast on a document being replaced could not be read. |
|
||||||
|
| **APP-04** | Low | REF `henza_pwa/src/router/index.ts:263-266` vs TGT both apps `stores/configStore.ts` | Legacy's `isLoaded` router guard sent a signed-in user to `home` from any route without `meta.skipLoadedCheck` until its one big bootstrap request had landed. | Read both; the target's store header already said `isLoaded` was not ported. | ⏹️ **«عمداً تکرار نشد — D3/ب»** (owner, 2026-10-12). The target has no bootstrap request — every page fetches its own data — so the flag would guard nothing and only add a bounce: opening a shared product link would land on home instead of the product. Porting it would port the breakage, not the behaviour (D4). Recorded in both apps' `configStore.ts` so a later reader finds the reasoning where they would look for the flag. |
|
||||||
|
| **ANLY-01** | Low | REF `henza_server/app/Http/Controllers/Api/LogController.php`, `henza_pwa/src/router/customAnalytics.ts`; routes `api.php:57-58` | Per-page dwell-time analytics: `POST /log/create/{title}` opened a row (`start_at`, `ip`, `title`, `customer_id`, optional `product_id`), `GET /log/update/{id}` closed it; four admin report endpoints aggregated it. Entirely absent from the target — no table, no routes, no calls. | Read the controller, the composable and the routes. | ⏸️ **DEFERRED — lowest priority, may never be built** (owner, 2026-10-12: «ج اصلا اولیوت نداره جزو اخرین چیز هایی هست که شاید درستش کنیم»). The full design is parked in `docs/PHASE2-D3-PLAN.md` §"شکاف ۳", including two reference defects **not** to copy if it is ever built: `log/create` required no auth and accepted any title (anyone could fill the table from outside), and `log/update` was a `GET` that wrote. Open owner questions there too: retention (suggested 90 days — legacy never deleted) and whether to store the IP at all (suggested no). |
|
||||||
|
|||||||
+22
-4
@@ -19,11 +19,11 @@ The "Current state" table below is the parity project's and was last updated 202
|
|||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| **Phase** | **Phase 2 — apps parity — item #1 done** (Phase 1 closed and committed, `83e5d28`) |
|
| **Phase** | **Phase 2 — apps parity — items #1 and #2 done** (Phase 1 closed, `83e5d28`) |
|
||||||
| **Branch** | `main` (the parity branches were merged; see git log 2026-10-08..10-12) |
|
| **Branch** | `main` (the parity branches were merged; see git log 2026-10-08..10-12) |
|
||||||
| **Status** | Phase 1: all in-scope sections closed (§2 §4 §5 §6 §7 §8 §9 ✅ · §3 ⏸️ by D16). Phase 2: **item #1 MON-08 ✅**. **286/286** jest tests, typecheck clean. |
|
| **Status** | Phase 1: all in-scope sections closed (§2 §4 §5 §6 §7 §8 §9 ✅ · §3 ⏸️ by D16). Phase 2: **#1 MON-08 ✅**, **#2 D3 ✅**. **286/286** jest tests; both apps `vue-tsc` clean. |
|
||||||
| **Next step** | Phase 2 item #2 — **the 5 screens missing from both apps (D3)**. `02-APPS-MATRIX.md` still to be created. |
|
| **Next step** | Build **`02-APPS-MATRIX.md`** — the field-by-field apps matrix (D12), which Phase 2 has been running without. Then the remaining app rows it turns up. |
|
||||||
| **Blocked on** | nothing. Two additive items await an owner ruling when convenient: **PERM-02** and the open rows below. |
|
| **Blocked on** | nothing. Awaiting an owner ruling when convenient: **PERM-02**. Deferred by the owner: **ANLY-01** (route analytics, lowest priority). |
|
||||||
| **Last updated** | 2026-10-12 |
|
| **Last updated** | 2026-10-12 |
|
||||||
|
|
||||||
### Phase 1 close-out (2026-10-12)
|
### Phase 1 close-out (2026-10-12)
|
||||||
@@ -207,6 +207,24 @@ actually had: the suite only covered a tampered request, not an honest one.
|
|||||||
An operator ordering from the panel still keeps the numbers they typed — only an
|
An operator ordering from the panel still keeps the numbers they typed — only an
|
||||||
app order is re-priced by the server.
|
app order is re-priced by the server.
|
||||||
|
|
||||||
|
## Phase 2 — item #2 (D3) — ✅ DONE 2026-10-12
|
||||||
|
|
||||||
|
D3 was ten items, not one. **Seven were already built** — all five screens
|
||||||
|
(`/report`, `/report/detail`, `/ChildOrder`, `/customerChoice`,
|
||||||
|
`/complete_profile`) exist in **both** apps, as do the `sts_verify_step_two`
|
||||||
|
guard and v2's cart stock ceiling. Checked in the code, not read off a
|
||||||
|
checkmark.
|
||||||
|
|
||||||
|
| Outstanding item | Result |
|
||||||
|
|---|---|
|
||||||
|
| guest toast «ابتدا وارد شوید» | ✅ both apps (**APP-03**). The destination stays the target's `/login?redirect=…`, better than legacy's `home` — owner's call, logged as «عمداً تکرار نشد — D3/الف». |
|
||||||
|
| `isLoaded` router guard | ⏹️ **not ported** (**APP-04**) — guards a bootstrap request the target does not have; would only bounce a shared product link to `home` (D4). Owner agreed. |
|
||||||
|
| route analytics | ⏸️ **deferred, lowest priority** (**ANLY-01**) — design parked in `docs/PHASE2-D3-PLAN.md`, with the two reference defects not to copy and the two retention/privacy questions still open. |
|
||||||
|
|
||||||
|
⚠️ **Phase 2 is running without its matrix.** `02-APPS-MATRIX.md` still does not
|
||||||
|
exist, so "what else do the apps miss" rests on the earlier survey rather than a
|
||||||
|
field-by-field comparison (D12). That is the next item.
|
||||||
|
|
||||||
The original requirement, for the record:
|
The original requirement, for the record:
|
||||||
|
|
||||||
Before anything else in Phase 2: the server must derive `wagePercent`,
|
Before anything else in Phase 2: the server must derive `wagePercent`,
|
||||||
|
|||||||
Reference in New Issue
Block a user