user-app: redesigned home screen and product detail

Design language introduced here and reused across both screens:
  - LESS colour tokens (@ink/@inkSoft/@inkMuted/@inkFaint, @wine/@wineSoft,
    @canvas/@canvasSoft, @hairline/@hairlineSoft) added to
    themeConfig/colors.json and injected globally via vite's modifyVars, so
    scoped <style lang="less"> blocks resolve them with no import
  - BEM-ish scoped class names, hairline-bordered cards at 14-18px radii,
    ink surfaces with a wine accent, dashed-border empty states
  - components/home/: SectionHead, ShowcaseCard, ShowcaseSlider, CategoryRail,
    RateStrip, AccountStrip

Data honesty is deliberate throughout: no rial price is printed for a piece,
because a finished item's price is the live gold rate x weight plus wage,
computed at order time and never stored on the product. Only genuinely
present fields are shown (weight, making-charge percent, stock, category,
brand). Categories and the hero are derived from the real catalogue, so an
empty catalogue renders an empty state rather than placeholder tiles.

Also fixes a real bug in the product detail view: it ran, at module scope,
history.pushState + window.onpopstate = () => history.go(1). Because the
drawer mounts its slot eagerly, that fired for every product card on the
home screen and permanently hijacked the back button for the session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-09-14 23:05:29 +03:30
co-authored by Claude Opus 5
parent a12fb0450e
commit f2d83c53df
41 changed files with 2041 additions and 241 deletions
+10 -1
View File
@@ -36,7 +36,13 @@ export function useProduct(productItem: ProductSummary, initialVariant?: Product
const min_gram = computed(() => selectedVariant.value ? parseFloat(selectedVariant.value.rashWeightGrams) : 0)
const wage = computed(() => (selectedVariant.value ? parseFloat(selectedVariant.value.defaultWagePercent) : null))
const hasDiscountWage = ref<number | null>(null) // no promotions backend yet — see file header
const is_exist = ref(true) // no stock field on ProductVariant yet
// REAL stock: ProductVariant.qty / isPreOrder exist and are returned by
// GET /catalog/products. A variant is orderable when it has stock, or when
// it is flagged pre-order — the same rule the admin cart uses.
const qty = computed(() => selectedVariant.value?.qty ?? 0)
const isPreOrder = computed(() => selectedVariant.value?.isPreOrder ?? false)
const is_exist = computed(() => qty.value > 0 || isPreOrder.value)
const productCode = computed(() => product.value.productCode ?? null)
const has_warranty = ref(false) // no warranty field on ProductVariant yet
return {
@@ -44,11 +50,14 @@ export function useProduct(productItem: ProductSummary, initialVariant?: Product
image,
id,
title,
productCode,
variants,
selectedVariant,
min_gram,
wage,
hasDiscountWage,
qty,
isPreOrder,
is_exist,
has_warranty,
}