Commit Graph
5 Commits
Author SHA1 Message Date
s.esmaeili f441ca8289 feat(user-apps): appearance settings (dark mode + reduced motion), product morph, filter sheet
Applies to both user-app and user-app-v2. Logic and structure are shared;
each app keeps its own look.

Appearance settings
- New "تنظیمات ظاهری" page (/settings/appearance), opened from the
  dashboard and available without login. It offers a light/dark/system
  theme and a "fast & light" motion switch.
- appearanceStore saves the choice to localStorage. A small inline boot
  script in index.html applies it before first paint so there is no
  white flash. The "system" option follows OS changes live.
- New ToggleSwitch component.

Dark mode
- Theme LESS tokens now compile to CSS custom properties. Light/dark
  values come from a generated virtual:palette.css (themeConfig/
  appearance.ts, with colors.json + new colors.dark.json).
- New LESS alpha() plugin replaces fade() on theme tokens.
- New semantic tokens: page, onInk, heroSurface, hairlineStrong,
  photoMat, photoStage.
- The dark palette is layered (page < card < panel < border) so
  surfaces separate without shadows.
- Hard-coded colors were replaced by tokens according to their role.
- antd inputs/text get dark overrides; vant uses its own
  .van-theme-dark.
- Light mode looks the same as before.

Reduced motion
- With html.motion-reduced, transitions are capped at 120ms, keyframe
  loops stop, and backdrop blurs and permanent will-change are dropped.
- Spinners and the toast progress bar (which closes the toast) are
  exempt via .keep-motion.
- Carousel autoplay, the v1 Lottie and the product morph honor the
  setting.

Product detail
- Opening a product is now a container-transform morph from the tapped
  card (ProductMorph). It replaces the bottom Drawer and uses composited
  transform/opacity only.
- The detail view is seeded with the card's data, so it no longer
  opens blank and jumps.

Filters and sheets
- Filters are now a full-width bottom sheet. Selections stay a draft
  until applied, so the result list no longer re-renders while
  dragging the slider.
- DropDown collapse animates grid-template-rows instead of a capped
  max-height.
- v2 IslandSheet is restyled to the v2 design language (API unchanged).

Fixes
- filtersStore.reset() shallow-copied shared arrays, so clearing the
  filters never cleared the selected brands.
2026-10-04 16:18:56 +03:30
s.esmaeiliandClaude Opus 5 f71fd5bfe1 feat(apps): sales-expert screens in both apps + v2 admin home slider (D21, APP-02)
Both expert screens are built in user-app and user-app-v2, with byte-identical
template and script and a per-app style block only — verified by diffing
everything above <style>:

  ChildOrdersView  "سفارش مشتریان"  — legacy ChildOrders.vue. Keeps the
    reference's title, search, infinite paging, and per-card item count, total
    weight, customer name and the fixed «تحویل مسئول فروش» chip (the endpoint
    only returns that status, ApiMobileController:766).
  CustomerChoiceView "لیست مشتریان" — legacy customerChoice.vue. Keeps its
    title, per-search re-query and the reference's choice to LIST inactive
    customers (its active() scope is commented out at :187).

Reachable from inside the app, not just by URL: routes carry the reference's own
paths and names, the dashboard grows the two entries only for an expert (v1 as
tiles, v2 as its own rows), and a router guard sends an ordinary customer back
to their dashboard. None of that is relied on for safety — the server scopes
both endpoints on the AdminUser id in the token and answers 403 otherwise.

Not ported, both recorded in D21: the act-as api_token minting (:197-201) and
the child-order edit drawer, which recomputed an order's money from client-sent
values. The card opens a read-only summary instead.

APP-02 fixed: user-app-v2 gained getHomeSlider and now prefers admin-configured
slides, keeping its category-derived hero as the fallback and its own design.

Session stores stay byte-identical across the apps.
build 5/5, jest 45/45, expert login 36/36, apps smoke 43/43, typecheck clean.

[ref: henza_pwa/src/views/user/src/components/ChildOrders.vue, customerChoice.vue]
[ref: henza_pwa/src/router/index.ts:171-188]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 01:12:53 +03:30
s.esmaeiliandClaude Opus 5 460cf276b6 parity(api,apps): delivery-mode gaps — master OTP, non-blocking SMS, faToEn, v2 cart cap
D20: OTP_MASTER_CODE login bypass read only from the environment, disabled when
unset, audited on every use, with a boot banner while armed. SMS no longer
aborts the boot in production — it warns and declines silently, so no message
can stop an operation.

D19: port Utility::faTOen onto admin-entered mobiles (before the uniqueness
lookup, which could previously be bypassed with Persian digits), and the
create-only welcome SMS, byte-identical to the reference and fire-and-forget.

user-app-v2 was missing the cart stock ceiling entirely: capQty/maxQty, the
un-orderable guard and the qty clamp are ported, leaving v2's own design intact.
The two cart stores are now byte-identical. isVariantOrderable and the
orderable-first default selection ported too.

jest 45/45; typecheck clean on api, admin-panel, user-app, user-app-v2.

[ref: henza_server/app/Http/Controllers/Adm/AdminController.php:333,348,364,378-387]
[ref: henza_server/app/Models/Utility.php:26]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 18:35:33 +03:30
s.esmaeiliandClaude Opus 5 4efe543d62 parity(api,apps): security hotfix stage 2 - real customer sessions, close IDOR
Stage 1 narrowed GET /customers but did not close the leak: the response still
carried every customer id, and /customers/:id, /:id/orders and
/:id/ledger-entries were still public, so the full customer book was still two
requests away. The user caught this. Stage 2 deletes the id-bearing surface.

Customer JWT, separate from the admin one (T1):
  - new src/modules/customer-auth/ with its own passport strategy,
    CustomerJwtGuard, @CustomerAuth() and @CurrentCustomer()
  - signed with JWT_CUSTOMER_SECRET, and every token carries typ:'customer'
    which the strategy verifies; both checks are present deliberately
  - customer state re-read per request, so deactivating takes effect at once
  - mirrors the reference's own split: auth:api vs auth('api-customer')

Real OTP (D6), porting ApiMobileController::userAuth/userVerfiy (:39-64):
  - new CustomerOtp table, additive migration, nothing existing touched
  - parity behaviour: rand(1000,9999), active-customer filter, faTOen digit
    normalisation on both number and code, rejection on mismatch
  - approved improvements over the reference: bcrypt-hashed code, 2-minute
    expiry, 5 attempts then lockout, single use, send throttle (60s per
    number, 5/number and 20/IP per 10 min), and no number enumeration

Routes removed, replaced by token-derived equivalents:
  GET customers, customers/:id, /:id/orders, /:id/ledger-entries
    -> GET customer/me, customer/me/orders, customer/me/ledger-entries
No route accepts a customer id any more, so the IDOR is closed structurally
rather than by a check. The admin panel was unaffected (admin/customers-omde/*).

Both apps migrated in the same commit per the v1/v2 parity rule: token-based
session store, Bearer header, 401 -> clear session and route to login, real
two-step OTP login. Each app keeps its own markup and styling; only logic moved.

Tests: auth-smoke.sh extended with the stage-2 assertions, 46 passed 0 failed
against a live server. jest installed dev-only (user-approved) so pnpm test
works for the first time: 7 passed covering faToEn, which moved to its own
digits.ts to be testable without booting Nest. Typecheck clean on api,
user-app and user-app-v2; both apps build.

[ref: henza_server ApiMobileController.php:39-78]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 01:20:00 +03:30
s.esmaeili 53a304b5d5 SearchView: Removed the dynamic collection-tile grid (e.g. "پرفروش‌های این هفته") and the "نتایج / N محصول" section header from the UI. Underlying data/filtering logic untouched.
DashboardView ("میز کار"): Rebuilt from scratch with a new structure, distinct from the old card+grid layout but visually aligned with HomeView (same blurred bar, @ink/@wine palette, staged reveal animation on mount):

Single hero card (ink→wine gradient) showing balance, gold/fiat debt, and identity — no quick-action icons inside it anymore.

Live gold rate folded into a slim inline row at the bottom instead of a titled section.
Iterated shadow/spacing on the hero card per feedback (softer, less clipped).
DialogVant / DialogVantOneButton (shared modal components, used in 7+ call sites: Dashboard, Cart, SingleProduct, Register, App): Replaced Vant's default dialog chrome with the app's own rounded-card language — circular icon badge for the illustration, pill-shaped primary/secondary buttons. No call-site markup changed.

LoginView: Rebuilt from scratch — full-bleed dark hero (brand + step copy) overlapped by a floating white form sheet, animated crossfade transition between the mobile-number and OTP steps, same staged entrance animation as Home/Dashboard. All auth logic (API check, OTP stub, session login) unchanged.
2026-09-18 12:54:45 +03:30