Commit Graph

441 Commits

Author SHA1 Message Date
sdarbinyan
a4f44dbb58 feat: Phase 7 frontend - Refund/Reconciliation core + Payments & Finance section
New core/finance (Refund/ReconciliationRecord/Settlement models + gateway/
token, mock backed, seeded empty) and features/admin/finance (facade +
page: reconciliation queue with resolve action, settlements placeholder).
New /backoffice/finance route + nav entry, nav i18n key in all 3 languages.
Against docs/backend/PHASE-7-PAYMENTS-RECONCILIATION-CONTRACT.md.

Note: does not wire AdminOrdersLocalGateway's existing mock
requestRefund(id) method into this new Refund flow yet - that's a small
follow-up once Phase 2's real Order backend exists to refund against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:56:59 +04:00
sdarbinyan
e3a70f5e65 feat: Phase 6 frontend - server-cart core, mock-gateway backed (scoped)
core/cart against docs/backend/PHASE-6-CART-CHECKOUT-CONTRACT.md §2-5:
ServerCart/ServerCartLine/CheckoutSession/DeliveryOption models + gateway/
token, in-memory mock implementation.

Deliberately does not touch pages/cart/cart.component.ts or
services/cart.service.ts (the live localStorage/Telegram-CloudStorage
cart) or features/website/checkout/ (still an empty directory) - same
judgment as Phases 1/3/5: this is real money/payment-adjacent flow and
deserves a dedicated, verified rewiring pass once a real backend exists,
not a bundled swap alongside nine other phases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:54:51 +04:00
sdarbinyan
475d75781f feat: Phase 5 frontend - Seller gateway core, mock-backed (scoped)
core/sellers/services against docs/backend/PHASE-5-SELLER-PORTAL-
CONTRACT.md §3: SellerGateway operating on the EXISTING Seller domain
type (core/sellers/models/seller.model.ts - deliberately not a new
competing shape; GAPS-AND-IMPROVEMENTS.md already flags two rival seller
shapes and a third would make that worse). Adds SellerUser/SellerRole.
Seeded empty - Seller Management has zero real sellers today (flag off
by default).

Scope note: this session's largest deferral. A real Seller Portal is a
separate self-service app surface (/api/seller/v1/*, its own auth, its
own layout) per the contract - building that alongside 9 other phases in
one push risks a shallow, unreviewed seller-facing app. The gateway core
here is the swappable seam; the actual portal deserves its own focused
pass once a real backend and the unified-orders Fulfillment model
(Phase 2) are further along.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:53:24 +04:00
sdarbinyan
6ac52b1c50 feat: Phase 4 frontend - Integrations backoffice section (mock-gateway backed)
New core/integrations (Connector/DeadLetterEntry models + gateway/token)
and features/admin/integrations (facade + page: connector table with
status/lag/errors/backlog/unmatched, pause/resume). Seeded empty per
Sprint 0.1's "no fixed partner list" decision - the section is ready to
populate the moment the first real connector is onboarded against
docs/backend/PHASE-4-CONNECTOR-FRAMEWORK-CONTRACT.md §7. New /backoffice/
integrations route + nav entry, nav i18n key in all 3 languages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:51:38 +04:00
sdarbinyan
5f23c6e5aa feat: Phase 3 frontend - Offer/InventoryRecord core, mock-gateway backed
core/offers module against docs/backend/PHASE-3-CATALOG-OFFER-FULFILLMENT-
CONTRACT.md §2-3, §7. OfferLocalGateway derives one Offer per existing
AdminProduct (sellerId defaults to 'marketplace-owned' when absent - same
convention AdminProduct.sellerId already documents) so the shape is real
without touching the live admin Products domain.

Scope note: this is the core swappable seam only (model + gateway + token),
same judgment as Phase 1's pricing core - deferred is the actual Product/
Offer split UI (multi-seller product page, admin lookup-by-SKU screen),
which is the single largest structural change in the whole programme per
the delivery plan and needs its own dedicated pass against a real backend,
not a bundled mock-data rewire of the working Products admin surface.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:49:24 +04:00
sdarbinyan
580d228484 feat: Phase 2 frontend - Notification Center (mock-gateway backed)
New features/admin/notifications module against
docs/backend/PHASE-2-ORDERS-NOTIFICATIONS-CONTRACT.md §6: model, gateway
interface + local mock (derives notifications from the existing real
ADMIN_ORDERS_GATEWAY so the shape is genuine), facade, page (unread
filter, event-type filter, mark-read/mark-all-read). New /backoffice/
notifications route + nav entry (nav i18n key added in all 3 languages;
page body copy is plain English - see scope note below).

Existing AdminOrder model/facade/mock-gateway were already solid and
real (just gained a DI token this session) - Order/OrderLine/Fulfillment
canonical-model rework from the Phase 2 contract is deferred; today's
AdminOrder shape is close enough to build the Notification Center against
without a disruptive rewrite of an already-working admin surface.

Scope note (applies going forward for this "do all phases" push): new
page body text uses plain English instead of the full TranslatePipe/
i18n-key system. Multiplying every new string across en/ru/hy + the
Translations type for every phase isn't sustainable at this pace: nav
labels (few, highly visible) still get real i18n keys; page content
does not. Flagged for a follow-up i18n pass before any of this ships.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:47:13 +04:00
sdarbinyan
b19fd77a60 feat: Phase 1 pricing core - Money/FxQuote/PriceSnapshot, mock-gateway backed
First frontend build against docs/backend/PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md:
- Money type (amountMinor + currency, no float math) with add/subtract/
  multiply helpers respecting per-currency minor-unit decimals.
- FxQuote model + FX_QUOTE_GATEWAY token, mirroring the DI-seam pattern
  already used for the 9 admin domains. FxQuoteLocalGateway derives a
  quote from the existing CurrencyRatesService so the shape is real even
  before a backend rate source exists (Sprint 0.1: FX is ours in-house).
  source: 'local-mock' is explicit and distinct from the eventual real
  backend's 'internal' - swapping the token when the real endpoint ships
  requires zero caller changes.
- PriceSnapshot/CheckoutLine/CheckoutResult models per contract §4-5.

Scope note: this does NOT yet rewire the live cart/checkout payment flow
(pages/cart/cart.component.ts) onto this module - that flow handles real
money against a live payment provider, and rewiring it deserves its own
focused pass with explicit verification, not a bundled mega-change. The
core module is ready for that pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:34:00 +04:00
sdarbinyan
54cd089e80 fix: WCAG AA contrast remediation on border/status colors (Z8, user-authorized)
Darkened --border-color and --success/--warning/--error/--info-color in
all three theme files, hue-preserving, computed against WCAG 2.1 formulas:
- border-color: 1.24-1.42:1 -> >=3.0:1 (non-text/UI-component minimum)
- status colors: 2.15-3.76:1 -> >=4.5:1 (plain-text minimum)
--primary-color/--secondary-color/--accent-color/gradients untouched -
only semantic feedback tokens changed. lavero's success-color now diverges
from primary-color (they only happened to share a hex before; semantic
status vs. brand identity are different concerns).

Also verified/corrected during this pass, doc was stale not code:
- Footer "Contacts" is not a code gap - Footer Builder + static-page CMS
  already resolve any authored page generically via pageKey
- Checkout payment-description fallback already tries brandName -> hostname
  -> i18n-translated fallback (en/ru/hy), not a hardcoded RU string

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 22:04:14 +04:00
sdarbinyan
6ca672987e refactor: extract shared app-breadcrumb component (Z14)
Only breadcrumb logic anywhere in the storefront was a local signal +
inline markup inside catalog-container. Extracted a generic
shared/ui/breadcrumb component (rootLabel/items/ariaLabel inputs,
rootClick/itemClick outputs) and repointed catalog-container onto it,
removing the now-dead inline SCSS block. Future breadcrumb usages
(product detail, admin) have something to reuse instead of duplicating.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:49:54 +04:00
sdarbinyan
288c7ac33f fix: wire dark-mode theme tokens for all three tenant themes (Z1)
theme-engine.service.ts already sets [data-theme-mode] on the root, but
no CSS anywhere consumed it - picking Dark/System never changed anything
visually. Adds a structural dark override block per theme (dexar/lavero/
novo): bg/text/border/shadow tokens only. Brand colors (primary/secondary/
accent/gradients) are left untouched - a distinct dark-mode brand palette
is a design decision for the theme owner, not made here.

Also verified during this pass, no change needed (GAPS-AND-IMPROVEMENTS.md
was stale on these):
- og:locale already reads languageService.currentLanguage() dynamically
- SeoService.setItemMeta() is already called from product-details-container
- stars.component.scss already uses var(--border-color), no literal hex
- sellerId is already typed UUID, no bare-string field remains

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:35:13 +04:00
sdarbinyan
ffaa6d2a1c refactor: rename storefront CategoryApiModel; correct stale auth-error doc; add Phase 1 backend contract
- models/category.model.ts: Category -> CategoryApiModel, disambiguated
  from core/categories/models/category-domain.model.ts's Category (admin
  domain shape). Removes a dead unused import in item.utils.ts along the
  way. Only live consumer was services/api.service.ts, updated in place.
- BACKEND-API-REFERENCE.md §5: corrected two rows documenting the
  TOKEN_EXPIRED/INVALID_SIGNATURE auth-error bug as still open - the fix
  (reading error.error.code before falling back to HTTP status) is
  already in auth.service.ts. Doc was stale, not the code.
- Sprint 0.2 audit: AdminRole duplication and the
  PRODUCT_DATA_PROVIDER/CATEGORY_REPOSITORY dead mock branches were
  already resolved in a prior pass - verified, no code change needed.
- docs/backend/PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md: new wire contract
  for Money/FxQuote/PriceSnapshot/payment state machine, so backend can
  start Phase 1 the moment the frozen payment chain is unblocked.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:31:11 +04:00
sdarbinyan
3c72c37e31 docs: v3.1 gap analysis + delivery plan; add DI seams to 9 admin gateways
Adds InjectionToken + factory for Orders, Products, Users, Transactions,
Monitoring, Moderation (mirrors existing Categories/Dashboard pattern) and
repoints their facades plus the derived Analytics/Customers facades and
admin-order-watcher off the mock LocalGateway class directly. No behavior
change today - still resolves to the mock - but a real backend can now be
bound per domain with zero facade edits.

Docs: full gap analysis of Product Plan v3.1 against current repo state,
and a phased delivery plan (10 phases, 34 sprints, 5 tracks) breaking every
identified gap into scoped, sequenced work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 21:24:57 +04:00
sdarbinyan
0d1d468307 feat: admin product views column (always 0 until backend tracks it)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 16:18:20 +04:00
sdarbinyan
1ab689e056 fix: unreadCount badge falls back to timestamp when ack pointer scrolls off page
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 04:33:25 +04:00
sdarbinyan
f1ee199d92 fix: AdminOrderWatcherService reacts to auth state, not component lifecycle
Previous fix (1032891) stopped polling via AdminLayoutComponent's
DestroyRef, but logout() never navigates or destroys the component -
the watcher kept polling and toasting indefinitely after logout.
Now polling starts/stops off AdminAuthService.isAuthenticated() directly,
following the same effect() pattern already used by AdminDashboardFacade.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 04:25:51 +04:00
sdarbinyan
1032891d26 fix: address final review findings (order-notification watcher robustness)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 04:18:20 +04:00
sdarbinyan
9ccd807a55 feat: editable new-order poll interval in admin settings
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 04:04:32 +04:00
sdarbinyan
35b1c7ed27 feat: wire order watcher into admin topbar bell (badge + panel)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 03:59:23 +04:00
sdarbinyan
28f39a31f6 feat: AdminOrderWatcherService polls for new orders and toasts
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 03:52:55 +04:00
sdarbinyan
5ed4936898 feat: UserNotificationService supports click-to-navigate toasts
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
2026-08-15 03:47:46 +04:00
sdarbinyan
0cadc1a642 fix: delivery-selector price/currency not converting on currency switch
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Component read item.currency (source) as both the display label and the
conversion target, so amounts never actually converted - only the label
technically matched. Now converts deliveryPrice/selectedDeliveryTotal via
CurrencyRatesService and labels with the shopper's selected currency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 02:01:08 +04:00
sdarbinyan
4510eb769a feat: client-side currency conversion with admin-configurable rates
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- CurrencyRatesService: RUB-based rates, persisted via localStorage
- CurrencyConvertPipe: impure pipe converting item price to selected currency
- Admin settings page: editable currency rates form
- Applied conversion to product-card, product-information, quick-view-dialog,
  delivery-information, compare-table, cart totals + payment payload

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 01:45:12 +04:00
sdarbinyan
d8c078ad5a fix: review findings from full-diff audit (7 fixed)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- popularSearches sent translated display text as the actual search
  query instead of the canonical term - useSuggestion() now prefers
  target.query.q when present.
- CartService.addItem() dedup guard resolved immediately instead of
  awaiting the real in-flight add; now tracks the pending Promise per
  itemID so concurrent callers await the actual result.
- addItem()'s Promise never rejected on failure (resolve() in both
  next/error branches) - now rejects on error; buyNow() catches and
  shows an error toast instead of navigating on a failed add.
- Quick View had no stale-response guard - a slower earlier request
  could overwrite a faster later one. Added a request-generation
  counter.
- cart autoSubmitPurchase() set paymentStatus to null synchronously
  right after firing the async submit call, blanking the success
  screen while the request was still in flight. Removed the
  redundant/harmful line.
- Order terminal-status guard (cancelled/refunded can't be reopened)
  lived only in the page component. Moved enforcement into the
  gateway (single write path) via a shared TERMINAL_ORDER_STATUSES
  const, so no future caller can bypass it.
- TranslatePipe's per-instance memoization cache had no eviction,
  so bindings with volatile params (pagination counts) grew it
  unbounded for the component's lifetime. Capped at 50 entries.

Not changed: the dark-mode color override was flagged as clobbering
admin branding, but it's the exact palette explicitly requested this
session for the global dark default - not a bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 17:20:00 +04:00
sdarbinyan
357d346787 refactor: remove dead mock-mode branches in PRODUCT_DATA_PROVIDER/CATEGORY_REPOSITORY
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Both tokens switched on getProductProviderMode()/getCategoryProviderMode()
but every case (including 'mock') fell through to the same real API
provider - no mock implementation of either interface exists. Removed
the dead switch instead of leaving code that implies a mock mode which
was never built.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 11:18:14 +04:00
sdarbinyan
23d9f2f66f refactor: rename duplicate AdminRole interface, type sellerId as UUID
AdminRole was defined twice with unrelated shapes (core/auth's real
JWT role union vs. the Users admin page's display interface), flagged
in BACKEND-API-REFERENCE.md \u00a72b as needing a rename. Renamed the
Users-page one to AdminUserRoleRecord.

sellerId was bare string in admin-order/admin-product/item models
while core/sellers/models/seller-scope.model.ts already used the
shared UUID alias. Aligned all three to UUID for consistency (UUID is
currently just = string, so this is a documentation-level type change,
not a behavior change).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 11:16:16 +04:00
sdarbinyan
00e5ce6b20 perf: AdminAnalyticsFacade.load() - forkJoin instead of 4 nested subscriptions
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
orders/products/categories/reviews don't depend on each other but were
fetched serially, 4 levels deep. forkJoin runs them in parallel.

Also fixes a real race: a rapid setDateRange() double-call previously
had no cancellation, so a stale in-flight chain could resolve after
and overwrite a newer one. Added a cancelPreviousLoad$ subject with
takeUntil.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 11:08:47 +04:00
sdarbinyan
a339a1c64e perf: debounce price-range/slider filter inputs
updateRange()/updateSlider() emitted stateChange synchronously on
every keystroke/drag event, triggering a full catalog filter
recompute each time. Debounced both (350ms, per filterId+key timer,
cleared on destroy).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 11:07:03 +04:00
sdarbinyan
c9a80da7c3 perf: memoize TranslatePipe instead of re-walking translations every CD cycle
pure:false stays (needed so language switches propagate without
touching every | translate template call site), but repeat calls with
unchanged key/params/language now hit a Map lookup instead of
re-splitting the key and re-walking the translation object tree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 11:03:30 +04:00
sdarbinyan
bad3002006 fix: WCAG 2.2.2 hero autoplay pause control, invisible keyboard-focusable cart button, literal hex token
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- Hero widget autoplay had no pause control and ignored
  prefers-reduced-motion (WCAG 2.2.2 requires a way to pause
  auto-updating content lasting >5s). Added a pause/resume toggle
  button and skip autoplay entirely when the OS prefers reduced motion.
- Cart's swipe-reveal delete-btn-mobile was reachable by Tab even
  while invisible (opacity: 0, only the touch-swipe gesture could
  reveal it) - a confusing, unusable focus stop for keyboard users.
  Now tabindex=-1 + aria-hidden until swiped. Keyboard users already
  had a full removal path via the always-visible header remove button;
  this just stops the redundant hidden button from being a dead tab
  stop.
- stars.component.scss hardcoded #cdd6d5 for the unfilled-star color
  instead of the --border-color design token.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 10:12:41 +04:00
sdarbinyan
178b5f0dc7 fix: i18n gaps in popular searches and compare table
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- SearchFacade.popularSearches hardcoded English titles regardless of
  active locale. Converted to a getter using translate.t() for the
  displayed title/text; the underlying search query stays the stable
  English canonical term the backend index matches against.
- Compare table and compare page rendered product.name raw instead of
  through getTranslatedField(), same pattern used everywhere else
  product titles are shown (catalog, product detail).
- SearchTrendingService.loadTrending() is a genuine backend gap (no
  trending-search endpoint exists) - already degrades gracefully,
  documented as a gap in BACKEND-API-REFERENCE.md \u00a712.6 rather than
  faked client-side.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 10:04:29 +04:00
sdarbinyan
6cc5d43a10 feat: build and wire the Quick View modal
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
The Quick View button (search results page only) emitted
quickViewPlaceholder with zero listeners anywhere up the chain - the
button did nothing. Built a minimal QuickViewDialogComponent (image,
name, price incl. discount, short description, Add to Cart, link to
full product page) and wired the event through product-grid ->
search-results -> catalog-container, which fetches the product via
ProductFacade and opens the dialog.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:45:03 +04:00
sdarbinyan
b04e3a67f5 feat: wire up dark mode selector with a real dark palette
theme.mode wrote data-theme-mode to the DOM but no CSS ever reacted to
it, and mapThemeConfigToCssVariables() never looked at mode at all -
selecting Dark had zero visible effect.

Added dark-mode neutral overrides (background/text/border axis only -
brand colors stay as configured) using the palette provided by the
user (colorhunt.co/palette/091413285a48408a71b0e4cc):
  --bg-primary: #091413   --bg-secondary: #285a48
  --text-primary: #b0e4cc --text-secondary: #408a71
  --border-color: #285a48

ThemeEngineService now resolves 'system' mode via
prefers-color-scheme and re-renders live on OS theme changes, and
sets data-theme-mode to the *effective* resolved mode instead of the
raw setting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:35:09 +04:00
sdarbinyan
0b08802996 feat: real back-in-stock subscription for Notify Me (API + localStorage fallback)
notifyMe() just called toggleWishlist() - no actual subscription
mechanism existed. Now calls a new subscribeToRestock() API method
(POST /items/{id}/notify-me, not yet built server-side - see
BACKEND-API-REFERENCE.md §12.5) and falls back to a local-only record
in localStorage on failure, so the request isn't silently dropped
while the backend catches up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:30:53 +04:00
sdarbinyan
1af337f005 fix: footer Contacts link resolved to nothing, add placeholder static page
The /contacts route had no bootstrap.staticPages entry at all (the old
hardcoded contacts page was intentionally removed per app.routes.ts's
comment, in favor of the generic staticPath -> CMS resolver). Added a
placeholder entry (route/title/html per locale) so the link resolves
instead of 404ing. Placeholder text explicitly says the real contact
details go through the admin panel - per user decision, real content
belongs to whoever runs a given marketplace, not something to fabricate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:26:32 +04:00
sdarbinyan
3feb806caa feat: add JSON-LD structured data for product and site pages
JSON-LD was absent entirely (sitemap generation is separate backend
work, out of scope here). Added Product schema (name/description/
image/offers with price+availability) on item pages via setItemMeta(),
and a site-wide Organization schema via resetToDefaults(), both
injected as a single #seo-json-ld <script type=application/ld+json>
tag that gets replaced on navigation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:16:37 +04:00
sdarbinyan
ec8ed8f6a8 fix: Site Layout builder setting saved but never read by page rendering
bootstrap.layout.type (the 'Site Layout' selector in the theme editor)
was only ever consulted by a validator checking it against the known
list - nothing used it to actually pick a layout. SectionEngineService
already resolves a per-page layout.type with a hardcoded 'default'
fallback; that fallback now reads the site-wide setting first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:14:56 +04:00
sdarbinyan
8937aea57c fix: checkout payment-description fallback hardcoded Russian regardless of locale
getPaymentDescription()'s final fallback ('Покупка на Маркетплейсе')
ignored the active language. Moved to a translated key.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:09:12 +04:00
sdarbinyan
c0bce7feac fix: Buy Now raced ahead of addToCart, per-product SEO tags never applied
CartService.addItem() fired an async dynamic import + API call for
new items but returned void immediately - buyNow() navigated to /cart
before the item was actually added, landing the user on an empty or
stale cart. addItem() now returns a Promise that resolves once the
cart signal actually contains the item; buyNow() awaits it before
navigating.

Also wired SeoService.setItemMeta()/resetToDefaults() into the product
detail page - built and working, but never called anywhere, so every
product page rendered the site-wide default OG/Twitter tags instead of
per-product ones.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:06:46 +04:00
sdarbinyan
9f784406d7 fix: og:locale hardcoded ru_RU regardless of active locale
Both setItemMeta() and resetToDefaults() hardcoded 'ru_RU'. Added a
LanguageService-driven mapping (ru/en/hy -> ru_RU/en_US/hy_AM).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:02:18 +04:00
sdarbinyan
461cd8421c fix: toAuthErrorShape() ignored backend error.code, session-expired screen unreachable
Known bug per BACKEND-API-REFERENCE.md §5: the client has dedicated
session-expired/invalid-signature recovery screens fully built, but
the mapper only ever derived the error code from HTTP status, never
the response body - so a real 401 with error.code: 'TOKEN_EXPIRED'
rendered the generic 'Unauthorized' screen instead.

Frontend half of the fix: prefer error.code from the body when present
(mapped via authErrorCodeFromBackendCode), fall back to status-derived
code otherwise. Stays dormant until the backend actually sends the
code, per the doc.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:00:39 +04:00
sdarbinyan
6231128288 fix: static-page loadByKey/loadByPath had no error handler, infinite spinner on failure
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
resolveByKey/resolveByRoute subscribed with only a next callback -
a resolver failure left loading=true forever with no error branch to
recover from. Added an error signal, error subscribe handler, and a
distinct error state UI (separate from the existing 404 not-found
state) with a way back home.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:56:25 +04:00
sdarbinyan
fd8e7e1b28 fix: DataSourceResolverService.resolve() had no catchError
A category/product facade error propagated through switchMap
uncaught, erroring the shared widget stream (shareReplay) in
WidgetHostService with no fallback - the widget just silently failed
to render, and the error stayed cached for every later subscriber.

Added catchError falling back to an empty { section, settings } shape,
same pattern as the widget-manifest fetch (falls back to { widgets: [] }
on any error, never throws to the UI).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:54:39 +04:00
sdarbinyan
000bb78112 fix: no error feedback on failed save/delete/role-change, editors navigated away before save result was known
Products/Categories saveDraft() and deleteOne(), and Users
setRole()/setStatus()/invite(), had no error handler at all - a
failed mutation was completely silent.

Also found and fixed the same premature-navigation bug as the Phase 1
cart fix: both product and category editor pages called
router.navigate() immediately after facade.saveDraft(), before the
save had resolved - so even after adding error feedback, the user
would already be gone from the page before it could show. saveDraft()
now takes an onSuccess callback and only the page navigates on actual
success; on failure it stays put and shows a themed error dialog.

Added a mutationError signal to all three facades and a themed
app-dialog error alert on the products/categories editor + list pages
and the users page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:53:12 +04:00
sdarbinyan
d3d6632375 fix: reports page never read facade.error(), silently showed 0/0
AdminAnalyticsFacade.error() already existed but the reports page
template never checked it - a load failure just rendered the summary
cards with default/zero values, looking like a legitimate empty
report instead of a failure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:46:17 +04:00
sdarbinyan
1e84d67e24 fix: load errors swallowed to empty array across 5 admin facades
Orders/Products/Categories/Customers/Transactions loadList() caught
errors by silently clearing the list to [] with no error state - an
API failure looked identical to a genuine 'no results' empty state.

Added an error signal to each facade (set on failure, cleared on
retry) and an error branch in each list page/component, distinct from
both loading and the real empty state.

Order and Customer detail (loadDetail) had it worse: no error handler
at all, so a failure just left the page on 'Loading...' forever with
nothing to retry or navigate away with. Added selectedLoading/
selectedError to both facades and an error screen with a back button
to both detail pages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:44:30 +04:00
sdarbinyan
c461d9bd5f fix: kill remaining native confirm() in admin, standardize on themed dialog
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Order cancel/refund and user suspend used window.confirm(). Migrated
all three to app-confirm-dialog, matching products/categories/orders
delete gates from earlier in this phase.

Remaining window.confirm() usages are the three canDeactivate dirty
guards (categories/products/project-editor) - left as-is, since
CanDeactivate needs a synchronous or Observable/Promise return and
browser navigation guards conventionally use the native dialog there;
converting those is a separate, larger refactor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:34:18 +04:00
sdarbinyan
63039b8707 fix: review 'Archive' button was mislabeled delete, zero confirmation
The bulk button read 'Archive' but called applyBulkDelete() ->
gateway.deleteReview() - a real hard delete, and reviews have no
archived status in the model at all, so 'archive' was never a real
concept here. Relabeled to 'Delete' and added an app-confirm-dialog
gate, same pattern as products/categories/orders.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:28:56 +04:00
sdarbinyan
908f10e022 fix: order bulk-delete had zero confirmation
Bulk-delete button called facade.applyBulkDelete() with no gate.
Added app-confirm-dialog, same pattern as products/categories.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:25:09 +04:00
sdarbinyan
49aab63124 fix: product delete (single+bulk) had zero confirmation
(delete) and (bulkDelete) called facade.deleteOne()/applyBulkDelete()
straight from the click event. Added app-confirm-dialog gates for
both, same pattern as categories.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:22:01 +04:00
sdarbinyan
320f1f44b7 fix: category bulk-delete had no confirmation, single-delete used native dialogs
Bulk-delete called facade.applyBulkDelete() straight from the button
with zero confirmation. Single-delete had a confirm gate but via raw
window.confirm/alert instead of the themed dialog used elsewhere.

Migrated both to app-confirm-dialog (single + new bulk), and the
delete-blocked message to a themed app-dialog instead of window.alert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:19:22 +04:00