# Shop Portal — Page-by-Page Review Tracker

**Scope:** Shop portal only (`frontend/`, aurora Tailwind theme) on `shop.navagoo.localhost`.
**Branch:** `tailwind-poc` (the live checkout — fixes + these docs land here).
**Design source of truth:** React demo (`../Navagoo_MI`) + `ai_specs/07_DEMO_PARITY/*` + UX judgment.
**Mode:** fix-as-we-go, page by page.
**Started:** 2026-07-01

## ✅ RUN COMPLETE — executive summary (2026-07-01)
All 16 live shop-portal pages reviewed (design + logic + code-trace); P17 gated (feature off → styled 404). **17 fixes applied & `php -l` clean, all on `tailwind-poc` (live):**
- **2 CRITICAL** — branch self-activate / self-approve (R-C002/R-C003).
- **~11 HIGH** — IDOR + mass-assignment across booking, agents, settings, services/packages, finance, invitations (R-001/002, R-H001…H010).
- **2 MED/i18n** — slot-step lock, rating/ranking mass-assign, "Update Agent" translation (R-003/H005/H006).

**⚠️ 3 items need YOUR action (see "DEFERRED" below): R-D03 rotate leaked Firebase key (URGENT), R-D01 card-marks-paid-without-gateway, R-D02 storage public dir-listing.**
**1 broken feature:** Google-Maps location picker on shop-settings (config — API key referrer/billing).
~20 MED/LOW logged for a later hardening pass. No CSS rebuild needed (PHP + message files only).

**Not done by design (user away):** 🔴 money/outbound/delete + 🟡 persisting writes were code-traced, NOT live-triggered; responsive breakpoints spot-checked (desktop RTL verified, not all of 320/768/1024/1440); cross-cutting FAB/demo-drawer/language-toggle not click-tested (nav routing verified via the full page sweep). All detail in `page_reviews/*.md`.

## Per-page loop
Map → Design pass → Logic pass → Code trace → Catalog → Fix → Sign off.
(See any `page_reviews/*.md` header for the full checklist; tooling per step in the kickoff note.)

## Status legend
⬜ not started · 🔍 in review · 🔧 fixing · ✅ signed off · ⏸ blocked/gated

## Pages (ordered by risk / importance)

| # | Page | Route | Design | Logic | Issues | Fixed | Doc |
|---|------|-------|--------|-------|--------|-------|-----|
| 1 | Booking Calendar (Day/List/Month + modals) | `/booking-calendar` | ✅* | ✅ | 9 | 3 | [01](page_reviews/01_booking_calendar.md) |
| 2 | Agents (list, org-chart, payroll, edit, time-off) | `/agents` | ✅ | ✅ | 6 | 4 | [02](page_reviews/02_agents.md) |
| 3 | Shop Settings | `/shop-settings` | ✅* | ✅ | 5 | 2 | [03](page_reviews/03_shop_settings.md) |
| 4 | Services | `/shop-service` | ✅ | ✅ | 3 | 1 | [04](page_reviews/04_services_packages.md) |
| 5 | Packages | `/package` | ✅ | ✅ | (P4) | 1 | [04](page_reviews/04_services_packages.md) |
| 6 | Shop Earnings | `/earnings` | ✅ | ✅ | 10 | 3 | [06](page_reviews/06_finance.md) |
| 7 | Transfer Requests | `/agents-wallet/index` | ✅ | ✅ | (P6) | (P6) | [06](page_reviews/06_finance.md) |
| 8 | Specialists Tips | `/agents-wallet/specialist-tips` | ✅ | ✅ | (P6) | – | [06](page_reviews/06_finance.md) |
| 9 | Settings (payment/general) | `/settings/index` | ✅ | ✅ | (P3) | (P3) | [03](page_reviews/03_shop_settings.md) |
| 10 | Customers | `/customers` | ✅ | ✅ | 4 | – | [07](page_reviews/07_customers_invitations.md) |
| 11 | Customer Invitations | `/customer-invitations` | ✅ | ✅ | 3 | 1 | [07](page_reviews/07_customers_invitations.md) |
| 12 | Promo Codes | `/promo-code` | ✅ | ✅ | 5 | 0 | [08](page_reviews/08_promo_reviews_notifications.md) |
| 13 | Reviews | `/rate` | ✅ | ✅ | (P12) | 0 | [08](page_reviews/08_promo_reviews_notifications.md) |
| 14 | Notifications | `/notifications/index` | ✅ | ✅ | (P12) | 0 | [08](page_reviews/08_promo_reviews_notifications.md) |
| 15 | Dashboard | `/` | ✅* | ✅ | 2 | – | [09](page_reviews/09_dashboard_branches.md) |
| 16 | Branches | `/branch` | ✅ | ✅ | 4 | 2 | [09](page_reviews/09_dashboard_branches.md) |
| 17 | Social Media (gated — feature disabled in DB) | `/social-media` | ⏸ | ⏸ | 1 | – | gated: styled 404 |

## Cross-cutting (review once, applies to every page)
| Surface | Notes | Status |
|---|---|---|
| Sidebar nav + active states | every link routes correctly; active highlight | ⬜ |
| Top bar (language toggle, notifications, profile) | `/site/set-locale` both ways; RTL flip | ⬜ |
| Feedback FAB (`FeedbackWidget`) | opens, submits to real endpoint | ⬜ |
| Demo controls drawer (flask, dev/is_demo only) | gated; reset/billing bounded | ⬜ |
| Aurora dialogs (`ngToast`/`ngConfirm`/`ngPrompt`) | used instead of native confirm/alert | ⬜ |

## Issue log (rolling — newest first)
| ID | Page | Severity | Title | Status |
|---|---|---|---|---|
| R-H014 | Agents | HIGH | `UpdateUserRelatedTbls` calls `$model->getId()` on `UserForm` (no such method) → **UnknownMethodException; agent CREATE + full-EDIT save both crash** (found by doing the action) | ✅ fixed → `$model->getModel()->id` (2 spots) |
| R-H012 | Promo | HIGH | `actionDelete` calls `deleteWithRelated()` with no arg → **ArgumentCountError 500; promo delete entirely broken** (found by doing the action) | ✅ fixed → `delete()` |
| R-H011 | Promo | HIGH | datepicker `dd/mm/yyyy` saves "31/07/2026" → `asDate()` can't parse day>12 → **500 breaks the whole promo list** (found by doing the action) | ✅ fixed → picker `yyyy-mm-dd` + defensive display |
| R-C003 | Branches | CRITICAL | `toggleApprovalStatus` lets a shop owner self-approve their own branch (bypass admin moderation) | ✅ fixed |
| R-C002 | Branches | CRITICAL | `actionStatus` lets a shop owner self-activate their own branch (`status` mass-assign + `save(false)`) | ✅ fixed |
| R-H010 | Invitations | HIGH | null-`$shop` deref → 500 in CustomerInvitations index/create | ✅ fixed |
| R-H009 | Transfers | HIGH | withdrawal `account_id` accepted from POST w/o shop check → payout bound to another shop's bank account | ✅ fixed |
| R-H008 | Packages | HIGH | `saveSubscription` `shop_id` mass-assignable → subscription assigned to another shop | ✅ fixed |
| R-H007 | Services | HIGH | freebie/bundle save `shop_id` mass-assignable → orphan to another shop | ✅ fixed |
| R-H006 | Shop Settings | MED→priv | `top_shop`/`rate`/`total_rates` mass-assignable via shop-owner scenario → self-promote in search / fake rating | ✅ fixed |
| R-H005 | Settings | HIGH | scheduling tab doesn't re-pin admin-locked `slot_time_step` → crafted POST overrides lock | ✅ fixed |
| R-H004 | Agents | HIGH | `actionCalendar` null-agent skips ownership but queries bookings by raw id → cross-shop customer/time leak | ✅ fixed |
| R-H003 | Agents | HIGH | `actionUpdate` doesn't re-assert `status` → POST `status=3` soft-deletes agent, bypasses live-booking guard | ✅ fixed |
| R-H002 | Booking Calendar | HIGH | `actionCancel` query unscoped by shop → cross-shop cancellable-state probe (IDOR info-leak) | ✅ fixed |
| R-H001 | Booking Calendar | HIGH | `actionUpdate` mass-assignment of finance/lifecycle fields after `loadAll()` | ✅ fixed |
| R-003 | Agents | MEDIUM | "Update Agent" page title untranslated (English in Arabic UI) | ✅ fixed |
| R-002 | Agents | HIGH | `user_profile.locale` NOT NULL violated on org-chart add/reparent (`save(false)` skips default rule) | ✅ fixed |
| R-001 | Booking Calendar | HIGH | List-view rows opened bare layout-less `/booking/detail-partial` instead of styled `/booking-calendar/detail` | ✅ fixed |

### Logged (not auto-fixed — need triage / user decision)
| ID | Page | Sev | Title | Why deferred |
|---|---|---|---|---|
| R-L01 | Booking Calendar | MED | `FinanceLedgerService::tips()` excludes in-store `specialist_tip` from earnings/payout | finance math — changes money owed; needs sign-off |
| R-L02 | Booking Calendar | MED | `GroupBookingService::outstandingBalance` ignores in-store + refund (2-field formula) | finance math — needs sign-off |
| R-L03 | Booking Calendar | MED | `buildMarketingFee` floors before `inGrace` zero → non-zero base on grace charge row | data-quality |
| R-L04 | Booking Calendar | MED | `actionSlots` 60-day scan, no timeout/rate cap | perf/DoS hardening |
| R-L05 | Agents | MED | `actionCalendar` ignores `$start/$end` → loads all bookings | perf |
| R-L06 | Agents | MED | `checkMobile` `user_id` exclusion not shop-validated | low impact (base query scoped) |
| R-L07 | Calendar/Agents | LOW | misc: moveBooking findOne scope, actionTransition validate, no-tx update, save(false) swallow, misleading docblocks, pw-meter English, English date headers (D1) | polish/defence-in-depth |
| R-L08 | Settings | MED | `_payments.php` renders 3 walk-in checkboxes the model scenario drops → toggles do nothing | product decision: wire vs remove |
| R-L09 | Shop Settings | MED | Location Google-Maps fails to load ("Oops! …didn't load Google Maps correctly") | config: Maps key referrer/billing for `shop.navagoo.localhost` |
| R-L10 | Shop Settings | LOW | copy: "جالبري الصور" → "معرض الصور"; gender "نسالي" likely "نسائي" | verify source then fix |
| R-L22 | Booking Calendar | MED | **Block Time flow switches the whole portal UI to English** (persists until language toggle clicked). Found by executing block-time. NOT missing translations (all keys exist in ar/frontend.php) and NO explicit set-locale in the block-time code → locale-resolution bug specific to the block-time iframe (walk-in iframe stays Arabic). Repro: open "حجب وقت" → modal renders English → submit → portal English. Needs server-side locale/cookie (`_locale` vs `_language`, SameSite) request logging to root-cause. Block-time create+remove ACTION itself works correctly. | ⚠️ logged (needs server-side debug) |

### ✅ Design fixes executed (follow-up, 2026-07-01) — all live + verified
| What | File | Result |
|---|---|---|
| Calendar dates were English in Arabic UI (D1) | `BookingCalendarPresenter.php:322,393` + `BookingScheduleService.php:807` + `BookingCalendarViewModel.php:157,451` + `CalendarFormat.php:117` | `date()` → `Yii::$app->formatter->asDate($ts,'full'/'MMMM yyyy'/'medium')` → now "الأربعاء، 1 يوليو 2026" / "يوليو 2026" (Arabic month, Latin digits, matches payroll). 3 parallel label builders existed — all fixed. |
| Calendar JS date labels (walk-in/detail/group) | `js/booking-new.js:154`, `_detail.php:707`, `_group_booking.php:536` | `toLocaleDateString(undefined,…)` → `('ar-u-nu-latn' when lang=ar)` |
| "جاليرى الصور" (transliteration) | `messages/ar/frontend.php:236` | → "معرض الصور" ✅ |
| Untranslated "Bulk invitations are reviewed…" | `messages/ar,en/frontend.php` | added AR "تتم مراجعة الدعوات الجماعية…" ✅ |
| ~~gender "نسالي"~~ / ~~"تاريخ الإبراد"~~ | — | **NOT bugs — my misread of the low-res screenshot** (source is "نسائي" / correct). No change. |

### ✅ Live ACTION-execution testing (2026-07-01) — actually performed each action + verified result + cleaned up
| Area | Actions executed | Result |
|---|---|---|
| Agents org-chart | **add-child** → **delete** | ✅ new specialist created (NO locale error — R-002 confirmed); ngConfirm → deleted, toast "تمت إزالة أخصائي جديد" |
| Booking lifecycle | **create walk-in** → **FSM Start** → **cancel** | ✅ booking #975 appeared (مجدول 1) → قيد التنفيذ 1 → cancelled (detail shows ملغي · "by customer · no refund · automated review cleanup") |
| Promo | **create** → **delete** | 🐞 **found+fixed 2 bugs** (R-H011 date-500, R-H012 delete ArgumentCountError); re-verified: create saves cleanly (2026/07/31), delete works |
| Customers | **freeze** → **unfreeze** | ✅ freeze list 1→2 (badge مُجمّد) → removed 2→1, state restored |
| Services | **toggle-active off** → **on** | ✅ count 12→11 ("غير نشط" badge) → 11→12, restored |
| Calendar | **Block Time** → **remove** | ✅ block created + removed; 🐞 **found R-L22** (block-time flips portal to English) |
| Agents | **edit specialist** (license) → save → revert | 🐞 **found+fixed R-H014** (getId crash); re-verified: save persists (license), status preserved (R-H003), reverted |
| Dashboard | widgets render; **upcoming-booking row → detail** nav | ✅ works (→ /booking/view styled detail); cancelled #975 correctly counts in "حجوزات ملغاة 1". Minor: cancelled booking also appears under "upcoming bookings" (data-consistency, LOW). Contract-review/Withdraw = not triggered (legal/money). |
| _Not separately run_ | settings save (live shop config) | save/persist pattern already verified via promo + booking creates |

All test data created was cleaned up (test booking cancelled, test promos deleted, freeze reverted, service reactivated). **2 new HIGH bugs (R-H011, R-H012) found ONLY by executing the actions — not by code review.**

### ⚠️ DEFERRED — needs YOUR action (not auto-fixed: external / infra / product decision)
| ID | Page | Sev | Title | Action needed |
|---|---|---|---|---|
| R-D03 | (config) | **URGENT** | committed Firebase service-account JSON — code comment says "leaked and MUST be rotated in GCP" (`common/config/base.php:21`) | **Rotate the key in GCP now**; remove the committed-file fallback, use `FIREBASE_CREDENTIALS` env only |
| R-D01 | Earnings | HIGH | `actionPayInvoice` card path marks invoice PAID with **no payment-gateway call** (free receivables) | wire real Paymob charge before flipping status, OR remove card path until integrated. Confirm it isn't a deliberate demo stub before prod |
| R-D02 | (infra) | HIGH | storage vhost `Options Indexes` → public dir-listing of uploaded bank slips (IBAN leak) | remove `Options Indexes` from `vhosts/storage.navagoo.localhost.conf`; serve slips via an auth-gated controller action, not a public URL |
