Commit Graph

124 Commits

Author SHA1 Message Date
sdarbinyan
14467cc6fb feat: FX-quote-backed currency conversion, delete admin rate editor
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
F10-F12 of the frontend backlog. Contract: PHASE-1-MONEY-FX-PAYMENTS-CONTRACT.md §3.

Removed the failure mode §5 of that contract exists to close: rates were
typed once by an admin into Settings, persisted to localStorage, seeded from
a hardcoded DEFAULT_RATES table (USD: 0.011, AMD: 4.3) that never updated and
drifted from market. Nothing recorded which rate produced a displayed price
or when.

- currency-rates.service.ts   now fetches through FX_QUOTE_GATEWAY instead of
                              reading admin-typed/localStorage numbers. Stays
                              synchronous at the call site (getRate/convert) -
                              rewriting every consuming template to `| async`
                              is a separate, larger change (F13, not this
                              commit). Before a quote has loaded for a pair,
                              getRate returns 1 rather than a fabricated
                              market rate; isRateReady() lets a caller that
                              cares distinguish the two. ensureFreshQuote()
                              added for checkout to await before charging,
                              per contract §3.2's stale-quote policy.
- language.service.ts        setCurrency() now triggers a quote fetch instead
                              of just flipping the display signal.
- cart.component.ts           openPaymentPopup() awaits ensureFreshQuote()
                              before computing the charged amount.
- admin-settings-page.*        currency-rate editor deleted (F11) - card,
                              component state, and the three orphaned i18n
                              keys it was the only consumer of.

Two real bugs surfaced fixing this, neither cosmetic:

1. fx-quote-local.gateway.ts had CurrencyRatesService.convert() as its rate
   source. That is now circular - CurrencyRatesService depends on
   FX_QUOTE_GATEWAY, and under useMockData:true this gateway IS
   FX_QUOTE_GATEWAY. Would have recursed the moment mock FX data was
   exercised. Fixed by giving the local gateway its own static mock table -
   the correct home for those numbers now: explicitly labelled dev/mock data,
   only wired in behind useMockData, never presented as a live rate.

2. currency-convert.pipe.ts memoized its result on (amount, from, to) alone.
   That was already latently wrong - rates could change via the old
   setRate() without the pipe re-evaluating for an already-rendered price -
   but never surfaced because rates never changed mid-session in practice.
   Async quote loading made it concrete and reproducible: a price rendered
   before its quote arrived stayed wrong forever, because none of the three
   cached inputs ever changed again on their own. Fixed with a ratesVersion
   counter on the service, bumped on every quote arrival, included in the
   pipe's cache key.

Both found and fixed via the E2E suite (docs from the prior commit) actually
exercising the real code path: GET /api/v2/pricing/fx-quote intercepted with
a contract-shaped response rather than flipping the whole app into mock mode,
so the test runs the real FxQuoteApiGateway, not a stand-in for it.

Verified: 3/3 E2E green, 115/115 unit tests green, arch:check clean,
production build succeeds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:03:02 +04:00
sdarbinyan
23060261c7 feat: Track S frontend - permission core + Audit & Security section
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
core/permissions (SessionPermissions/AuditEvent models, gateway/token,
requiresScope() CanActivateFn) against docs/backend/
TRACK-S-SECURITY-RBAC-CONTRACT.md §1-3. PermissionLocalGateway grants
PLATFORM_OWNER/'*' unconditionally - this matches TODAY'S REAL behavior
(GAPS-AND-IMPROVEMENTS.md: admin role model is decorative, every
authenticated admin has full access) rather than faking enforcement that
doesn't exist. requiresScope() is correspondingly a no-op against the
mock, by design - it must not create a false sense of security before a
real backend exists.

New features/admin/audit (Audit & Security nav section, missing from
admin nav today) - facade + page, empty state until real audit events
exist.

Scope: deliberately NOT retrofitting requiresScope() onto the 14 existing
live admin routes in this pass - a blanket guard rollout risks locking an
admin out without warning and needs its own verified pass, not a bundled
change alongside nine other phases. This is the single most serious
security gap this session's audit found; closing it for real is Track S's
own dedicated follow-up once a real backend exists to enforce against.

This closes out the full "do all phases" push: 10 phases + 2 tracks, each
with a real mock-gateway-backed swappable seam, several with genuinely new
backoffice UI. Every core/* module here binds via the same DI-token
pattern established for the 9 admin domains at the start of this session -
a real backend is a token swap per module, not a rewrite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:08:28 +04:00
sdarbinyan
5d585ff8ff feat: Phase 9 frontend - Marketplaces registry + Domains & Releases (combined page)
core/marketplace-registry (Marketplace/MarketplaceDomain/
LifecycleAdvanceResult models + gateway/token) derives a single-row
registry from the current tenant's own live bootstrap config and current
hostname, since the platform runs one tenant per deployment today with no
registry anywhere. New features/admin/marketplaces page + nav entry.
Against docs/backend/PHASE-9-TENANT-REGISTRY-DOMAINS-CONTRACT.md.

Scope simplification: combined "Marketplaces" and "Domains & Releases"
(two separate backoffice sections in the plan) into one page with two
sections, to keep pace through this push - splitting them into dedicated
routes is a small follow-up once there's real multi-marketplace data to
justify two separate list views. Onboarding wizard (8 steps), Hostinger
DNS automation, and the full lifecycle-advance state machine are not
built - registry/domain/lifecycle read-only display only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 00:01:09 +04:00
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
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
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
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
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
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
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
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
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
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
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
sdarbinyan
4c4417dc1d fix: cart email/phone capture form was never rendered
recordOrder() and autoSubmitPurchase() read userEmail()/userPhone()
signals and submitEmail() was fully implemented (validation, error
handling), but the success screen's template never rendered the
inputs - so the form was unreachable and the fallback auto-submit
always sent blank email/phone.

- Added the email/phone form to the payment-success screen, wired to
  the existing signals/handlers.
- autoSubmitPurchase() (the 5s fallback if the user doesn't submit
  manually) no longer navigates home via an unconditional setTimeout(0)
  fired before the submission result is known - it now waits for
  submitPurchaseEmail() to settle, same as the manual path, and skips
  entirely if the user already submitted (new purchaseSubmitted flag).
- It also now sends whatever the user has typed instead of
  hardcoded-blank fields, and shows a toast instead of only logging to
  console when no Telegram user id is available.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 07:37:36 +04:00
sdarbinyan
c3b5820ac9 fix: category slug-uniqueness check fails closed on API error
isSlugTaken previously caught network/API errors and returned false,
letting the save proceed as if the slug were free. Now the error
propagates and blocks save via a distinct slugCheckError state,
surfaced in the category form with its own hint.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 07:15:50 +04:00
sdarbinyan
ce63931bc2 feat: dead-config sweep, test suite foundation, widget settingsSchema validation
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Sprint G: audited every BootstrapConfig field for a real runtime consumer
(docs/DEAD-CONFIG-AUDIT.md). Wired 3 previously-dead editable fields:
footer.logoUrl, company.address.street/contacts.phone, catalog.suggestionsEnabled.
Remaining dead fields needing a business/design decision tracked in
PRODUCT_BACKLOG.md/KNOWN-ISSUES.md, not silently left.

Sprint H: 6 new spec files (test count 57 -> 83), covering ProjectEditorFacade
(undo/redo, draft persistence, publish gating), AdminAnalyticsFacade
(never-fabricate-a-number contract), and regression coverage for this
session's carousel/hero/profile-toggle fixes.

Sprint I: widget settingsSchema (declared in widget-manifest.json, never
validated) now enforced via a new lightweight schema check in
ProjectValidator, surfaced through the existing issuesByField pipeline.
Same check reused in diagnostics so editor and diagnostics can't disagree.

Verification: tsc clean, ng build clean, 83/83 tests pass, barry-cache
validate clean (2 pre-existing unrelated warnings only).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 20:47:13 +04:00
sdarbinyan
55b379bd6d fix: manifest-aware layout picker, real carousel items-per-page, hero arrows/swipe/2-panel
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Sprint E: homepage section editor now filters the layout-strategy picker
to each widget's widget-manifest.json supportedLayouts instead of always
showing all 5 strategies. columns field gated to widgets that read it
(hero, product-collection carousel).

Sprint F: closes client bug report (no items-per-page control, hero
carousel not manually/automatically scrollable, no 1-2 slide big-carousel
option). Product carousel item width now driven by layout.columns
(reused, was already editable but dead). Hero widget gains prev/next
arrows, touch swipe, and 1-2 panel mode via the same field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 19:25:34 +04:00
sdarbinyan
48bcffa22c feat: close stub-page gaps - profile login/logout, admin Reports/Settings, Help/Docs links
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Sprint A: storefront header profile control (login/logout only, no menu),
wired to existing customer Telegram auth (AuthService).

Sprint B: backoffice/reports page, reuses AdminAnalyticsFacade (Sales,
Top Products, Marketplace Health cards + CSV export).

Sprint C: backoffice/settings page, admin UI density preference
(comfortable/compact), localStorage-persisted, applied to app-table
across all admin list pages.

Sprint D: admin bottom-nav Help -> mailto using existing supportEmail,
Documentation -> external link via new TenantConfig.documentationUrl.
AdminNavLink gains externalHref for non-routerLink nav entries.

Docs: docs/GLOBAL-SPRINT-PLAN.md tracks the full sprint breakdown.
docs/COMING-SOON-AUDIT.md removed, folded into docs/KNOWN-ISSUES.md.
docs/BACKEND.md updated with the new documentationUrl bootstrap field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 17:48:50 +04:00
sdarbinyan
65c6d6f5d1 feat: Page Editor UX Phase 2 - unsaved changes panel, property search, reset property, empty states
Some checks failed
Architecture Governance / architecture (push) Has been cancelled
- ProjectEditorFacade.resetField(key): field-level revert-to-original, reusing getByPath + a new immutable setByPath (write-side counterpart, scalar/object dot-paths only).
- app-form-field gains showReset/resetLabel/(resetClicked) - a reusable per-field reset affordance, wired on branding logo/title and theme primary/background color.
- Save bar: unsaved-changes count is now clickable, expanding a field-level diff list (reuses facade.changeSummary(), already built for the Preview tab) with jump-to-section links.
- BuilderPropertySearchComponent: filters EditorSchemaService.all() by translated label/hint, jumps to the owning section - no new registry, reuses the existing schema.
- navigation-section: app-empty-state (existing component) added for empty header/footer link lists.
- Reset Section and Draft/Published badge were already implemented; not touched.
- New copy added as translation keys (en/ru/hy).
2026-07-27 11:12:35 +04:00
sdarbinyan
42f11dd8c0 feat: Page Editor UX Phase 1 - live preview, hover mapping, visual layout picker
- PreviewHighlightService + appHighlightSource directive: shared hover/focus bridge between editor fields and the schematic live preview.
- BuilderLivePreviewComponent: in-page schematic homepage render (header/hero/blocks/footer) reading the same bootstrap the sections mutate, highlighting the area matching the active field.
- VisualLayoutPickerComponent: card-based layout picker (ControlValueAccessor, same shape as app-select) replacing the raw layout <select> in homepage-section.
- app-form-field gains optional usedBy/usedByLabel inputs for the "Where is this used?" helper text, wired into aria-describedby.
- Wired homepage/branding/theme sections with highlight sources + usedBy hints; live preview panel shown in project-editor-page for those three sections.
- All new copy added as translation keys (en/ru/hy).
2026-07-27 10:00:42 +04:00
sdarbinyan
20442eb93c feat(admin): Seller Management Phase 1 UI - Partners section, empty state, request/learn-more dialogs
No backend, no CRUD, no API, no business logic - production-quality
UI only, built entirely from existing shared components (app-dialog,
app-empty-state, app-button, app-form-field, app-input, app-icon,
app-badge). Gated per ADR-011: reads
modules.sellerManagement.enabled from bootstrap (always false today,
no backend sets it) rather than hardcoding disabled state.

New:
- AdminSellerManagementPageComponent (features/admin/seller-management/
  pages/) - renders the specified empty state (title/description/
  Request Access + Learn More buttons) using existing shared/ui
  primitives only, no new UI infrastructure.
- Request Access dialog: Company/Email/Message form via
  app-form-field + app-input + a plain textarea (no dedicated
  textarea component exists yet, styled to match app-input's own
  tokens exactly). Submission is mocked (setTimeout), no API call.
  On submit: closes and opens a success dialog ("Thank you...").
- Learn More dialog: 6 capability bullets (seller dashboards,
  storefronts, permissions, analytics, product ownership, marketplace
  administration) under a "Coming Soon" badge.
- New admin nav group "Partners" > "Seller Management" link
  (admin-nav.model.ts), new route /backoffice/partners/seller-management
  (app.routes.ts), using the same loadComponent/breadcrumb pattern as
  every other admin route.

Translations: full en/ru/hy coverage, zero hardcoded strings - new
adminShell.nav.{partnersGroup,sellerManagement},
adminShell.pages.sellerManagement, and a new adminSellerManagement.*
namespace (emptyState/requestDialog/requestSuccessDialog/
learnMoreDialog) added to translations.ts (types) and all three
locale files.

Accessibility: inherited from app-dialog (role="dialog",
aria-modal, focus trap on Tab/Shift+Tab, Escape to close, focus
restored to trigger on close) - no new a11y code needed, reused as-is.

Responsive: existing --space-*/--font-size-* tokens throughout,
flex-wrap on button row, mobile breakpoint stacks actions full-width.

Verified live (ru locale, devBypassAdmin): nav group/link render
correctly, breadcrumb shows "Управление продавцами", empty state
copy matches spec exactly, Request Access dialog opens with all 3
fields + Cancel/Send Request, filled + submitted -> success dialog
with exact spec copy, Learn More dialog shows all 6 bullets + Coming
Soon badge, no console errors, verified again at 375px mobile
viewport. tsc --noEmit clean, ng build clean (pre-existing bundle-
budget warning only), arch:check (boundaries + cycles) clean.
2026-07-26 20:47:18 +04:00
sdarbinyan
ca343c493f fix(backoffice): merchant-friendly wording in Monitoring
Analytics, Reports, and Diagnostics were already clean (no raw
HTTP/queue-worker strings found on audit). Monitoring had three spots
speaking developer language by default:

- Background queue names ("order-notifications") -> friendly labels
  ("Order notifications").
- Webhook event keys ("order.created") -> friendly labels ("New order
  placed").
- Activity log's "api" category showed the raw HTTP line
  ("GET /api/products responded 200 in 84ms") as the primary message.
  Now shows a plain-language summary by default ("Product data
  refreshed successfully"), with the raw string moved to a collapsed
  "Technical details" <details> per event (api/error/warning rows).
2026-07-26 00:17:14 +04:00
sdarbinyan
6c6fa00ccf fix(ui): replace native confirm()/alert() with shared dialogs and toasts
New app-confirm-dialog (wraps existing app-dialog + app-button) replaces
every native confirm() across media library bulk-delete, static pages
editor (delete/bulk-delete), builder save-bar (publish/reset-draft),
project-editor-page (reset-section), homepage/languages/widgets sections
(remove block/language/widget), and cart (clear-cart).

Cart's native alert() calls (delivery/terms validation, email send
success/failure) now route through the existing UserNotificationService
toast pipeline instead.

No native confirm()/alert()/prompt() remain in production UI.
2026-07-26 00:08:00 +04:00
sdarbinyan
1163bfd88a fix(storefront): replace hardcoded strings with i18n, neutral empty-state wording
- Route aria-label/alt/title strings (rating, discount, carousel arrows,
  hero slides, dialog close, toast dismiss, QR code, bank payment iframe,
  guest checkout fallback) through the translate pipe/service instead of
  literal English.
- Drop the "Oops!"/"Упс!" apology framing from category/subcategory empty
  states (en/ru/hy) - zero results is not an error.
2026-07-25 23:54:08 +04:00
sdarbinyan
e153a67ec0 fix(backoffice): add error+retry states to Users, Monitoring, Analytics, Reports
Phase 8 (RC-01): these 4 list/dashboard pages had no error-state handling
on their primary data-load subscriptions — on a gateway error, `loading`
was either never reset (Users, Monitoring, Analytics: genuine infinite-
spinner risk, nested subscribe chain in Analytics never resolved on
failure) or there was no loading/empty/error handling at all (Reports
queue: raw table with zero skeleton or fallback).

- admin-users.facade.ts, admin-monitoring.facade.ts: add `error` signal,
  error callback on the primary load subscribe so `loading` always
  resolves.
- admin-analytics.facade.ts: add `error` signal; every level of the
  4-deep nested gateway subscribe chain (orders -> products ->
  categories -> reviews) now has an error handler that resolves loading
  instead of leaving it stuck true.
- admin-moderation.facade.ts: add `reportsLoading`/`reportsError` signals
  (reports list had none previously).
- Templates: reuse existing `app-skeleton`/`app-empty-state`/`app-button`
  primitives for the new error branch, `common.retry` label, two new
  generic `common.errorTitle`/`common.errorDescription` i18n keys added
  to en/ru/hy (reused across all 4 fixes instead of one-off per-page
  copy).

Verified: tsc --noEmit clean, `npm run build` green (pre-existing bundle-
budget warning only, unrelated). Live-checked Home (375px) and Backoffice
Products (1024px) — no console errors, tables/cards render without
overflow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 21:31:30 +04:00
sdarbinyan
b909a195f7 fix(backoffice): wording quality pass across orders, moderation, transactions, users, customers
- Replaced hardcoded English audit/timeline text (order status changes,
  review moderation events, user role/status changes) with proper
  adminXxx.timelineEvent.*/adminUsers.audit.* i18n keys, so Recent
  Activity/Timeline/Audit panels no longer mix English into ru/hy UI.
- Translated raw internal codes rendered directly to users: transaction
  payment method ('card'/'qr'/'cash_on_delivery' -> adminTransactions.methodValue.*)
  and user roles/permissions ('products.manage' etc -> adminUsers.roleValue.*/
  adminUsers.permission.*), replacing developer-facing enum leakage with
  real copy.
- Fixed wrong-noun list-footer counts: Orders/Transactions/Moderation
  list pages all reused adminProducts.items ("N товаров"/"N products")
  regardless of what was actually listed; each now has its own itemsCount
  key ("N заказов", "N транзакций", "N отзывов").
- Fixed customer detail page's "Back" button reusing adminOrders.back
  ("Back to orders") instead of a customers-specific label.
- Added translation keys to en/ru/hy + translations.ts interface for all
  of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 21:20:37 +04:00
sdarbinyan
72846b44b1 fix(builder): un-hide and relabel static-page content editor, warn on publish/remove-language
- KNOWN-ISSUES item 11: the WYSIWYG page content editor was buried inside
  a collapsed Advanced <details>, labeled 'Raw HTML (advanced)'. Moved it
  to the top of the Content tab, unwrapped, relabeled 'Page Content' /
  'Содержимое страницы' / 'Эջի բովանդակություն' with a plain-language
  description. Advanced tab keeps genuinely technical fields (id, slug,
  route, custom template).
- Publish (save-bar) and Remove language (languages-section) had no
  confirmation despite being destructive/high-impact — added
  window.confirm guards using the existing builder.confirm* i18n pattern
  (matches resetDraft/resetSection/dirty-guard precedent), all 3 locales.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 20:43:43 +04:00
sdarbinyan
7b639cabf8 fix(admin): products canDeactivate guard, unblock npm, drop dead deps
RC-01 Phase 1 mechanical fixes (verified against current repo state,
not blindly reapplied from TODO.md):

- admin/products create/edit/duplicate now protected by an unsaved-
  changes guard (adminProductDirtyGuard), mirroring the existing
  categories pattern. AdminProductsFacade had zero dirty-tracking
  before this - added a dirty signal, set true on updateDraft(),
  cleared on load/create/successful save. Added confirmLeaveUnsaved
  to the adminProducts i18n section (en/ru/hy) - categories already
  had its own copy of this key, products didn't.
- barry-cache bumped ^0.1.0 -> ^0.9.3 (the pinned range no longer
  resolved on the registry - ETARGET - which had been silently
  blocking every npm install/uninstall all cycle).
- Removed primeng/primeicons (npm uninstall, now unblocked) - the
  only consumer (items-carousel) was already deleted in RC PERF-01.
- Removed core/search/services/search-history.service.ts, a dead
  1-line re-export with zero importers (verified: the real
  implementation is features/search/services/search-history.service.ts,
  used by search.facade.ts). Left core/search/models/* alone - those
  ARE live, imported by catalog components.

Verified before touching: HeaderConfig.showProfile toggle is already
removed from the header-section editor template (TODO.md was stale on
this one) - no change needed, will correct the tracking doc separately.

tsc --noEmit clean, npm run build green (bundle unchanged, primeng
was already tree-shaken out, this just removes the dead dependency
declaration itself).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 20:04:51 +04:00
sdarbinyan
565fd9b3a8 fix(backoffice): WCAG 2.1 AA accessibility fixes
RC A11Y-01 audit pass, Backoffice (admin/*) area. Builds on 712a7b4/63c9cee's
composition pass (scope="col", skeletons, empty-states) without redoing that
work.

- Sidebar nav landmark: admin-layout's <nav aria-label> reused the
  "Dashboard" nav-item translation key as its landmark label, misleadingly
  announcing the whole sidebar as "Dashboard" - added a dedicated
  adminShell.sidebarLabel key ("Admin sidebar navigation") in en/ru/hy.
  Skip link, #admin-content main landmark, Escape handling, and mobile-drawer
  focus management were already correct - verified, not touched.

- Categories tree drag-and-drop keyboard fallback (WCAG 2.1.1): the category
  tree's native HTML5 DnD (draggable/dragstart/drop) reorders siblings with
  no keyboard equivalent - existing arrow-key tree navigation only expands/
  collapses/selects, never reorders. Added per-row move-up/move-down icon
  buttons (disabled at sibling boundaries), reusing the existing `reorder`
  output so the facade's reorder logic is untouched; new
  adminCategories.moveUp/moveDown keys in en/ru/hy.

- Screen-reader loading announcements: skeleton-row loading states across
  Products, Categories, Customers, Orders, Transactions, Users, Reviews,
  Monitoring (webhooks/events), and Analytics (summary cards + top products)
  were purely visual (app-skeleton is aria-hidden by design) with no
  accessible "loading" text, unlike the storefront/product-details pattern -
  added role="status"/aria-live="polite"/aria-busy + sr-only text using the
  existing common.loading key.

- Table row headers: added scope="row" to the primary identifying cell
  (product/category/customer name, order number, transaction order number,
  user name, review customer, report target, top-products/low-stock product
  name, webhook endpoint) on 9 tables that only had scope="col". Added
  matching `tbody th[scope='row'] { font-weight/color/text-align/
  vertical-align }` + last-row border resets in each component's own scss so
  the semantic change doesn't alter visuals (the shared app-table stylesheet
  styles all <th> as bold/muted by default).

Verified via `git show --stat` of fb1afb7/a03260e and `docs/UI-COMPOSITION-
REVIEW.md`'s Backoffice sections first, per instructions - confirmed
scope="col" coverage already complete, all admin modals already route
through the shared app-dialog (focus-trap/Escape/return-focus already
correct, nothing to fix), and the bare-<select> filters still carry
aria-label per the accepted Sprint 28 decision (not re-migrated to
app-select).

Flagged, not fixed:
- No toast/notification system exists anywhere in this codebase (product/
  category save and delete call the gateway with no success/error UI at
  all, not even a subscribe error handler) - there is nothing to wire
  aria-live onto without adding a new UI mechanism, which is out of scope
  for an a11y-only pass. A prerequisite feature-level fix, not an a11y
  regression.
- Dashboard's per-card metric/status-row/timeline skeletons (dashboard-
  metric, dashboard-status-row, dashboard-timeline) were left without
  aria-live wiring - wrapping each of the ~10 simultaneous mini-widgets in
  its own live region would fire a burst of redundant announcements; needs
  a single page-level "loading dashboard" region instead, a larger change
  than this surgical pass.
- Monitoring's events table and the notifications dropdown (role="menu"
  with a static empty-state message, aria-haspopup="true") were left as-is -
  matches the same partial-widget-pattern precedent already accepted for
  locale-tabs/product-tabs in the storefront and builder passes.
- Analytics `lowStockProducts` table's missing loading-skeleton branch
  (already flagged, not fixed, in the RC-Visual-02 pass) - untouched again
  here for the same reason.

Verified: npx tsc --noEmit clean; npm run build green (only the pre-existing
768.57 kB vs 700 kB initial-bundle budget warning, unrelated to this pass).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 09:18:09 +04:00
sdarbinyan
a03260eccf fix(builder): WCAG 2.1 AA accessibility fixes
RC A11Y-01 audit pass, Builder (project-editor) + content-management static
pages editor. Builds on 4ebc15f's composition pass without redoing it.

- Skip link: Builder's /edit and /edit/:section routes render outside the
  storefront app-shell (isAdminRoute() branch has no skip link/landmark, only
  a bare router-outlet, unlike the storefront's app.html) - added a
  "skip to content" link targeting a new #builder-main-content landmark on
  project-editor-page.component, reusing the existing adminShell.skipToContent
  key and global .skip-link style. Nav landmark already had aria-label
  (builder.appName) from the prior pass, verified correct.
- Save bar: added role="status"/aria-live="polite" to the save/publish status
  block and role="status" to the draft-restored notice so save/publish state
  changes and draft recovery are announced to screen readers (previously
  silent DOM updates).
- Media picker (shared, used by both Builder and static-pages editor): upload
  error message had no aria-live wiring - added role="alert".
- HTML editor (marketplace-html-editor): the contenteditable rich-text surface
  had no accessible role/name - added role="textbox", aria-multiline="true",
  aria-label.
- Drag-and-drop keyboard fallback (WCAG 2.1.1): homepage-section's block list
  and footer-section's column list + per-column link list use Angular CDK
  drag-drop (cdkDrag/cdkDropList), which has no built-in keyboard reordering.
  Added move-up/move-down icon buttons (disabled at the first/last boundary),
  matching the existing pattern already used by widgets-section and
  navigation-section.
- Color picker: the swatch <input type="color"> had no accessible name (only
  the paired text input was labelled via app-form-field) - added explicit
  ariaLabel bindings to all 8 color-picker instances in theme-section.
- Languages: the new-locale code input relied on a placeholder ("de") as its
  only accessible name - added ariaLabel + new builder.newLanguageLabel i18n
  key (en/ru/hy).
- Undefined CSS var --color-primary (never defined anywhere, silently used its
  hardcoded hex fallback and never responded to tenant theming - same
  recurring bug class as 4ebc15f) - remapped to the real --primary-color token
  in marketplace-html-editor, homepage-section, and section.shared (7 usages).
- role="alert" added to all 11 validation-error <p class="editor-error">
  occurrences across footer/homepage/languages/navigation/preview/widgets
  sections and the static-pages editor, so field/section validation messages
  are announced.
- scope="col" added to preview-section's change-summary table headers.

Flagged, not fixed (design-system decisions, matching the storefront pass's
precedent):
- Save bar's --warning-color/--error-color/--info-color text fail WCAG AA
  4.5:1 in some themes - same genuine brand semantic colors flagged (not
  fixed) in fb1afb7's storefront pass; needs a deliberate token decision,
  not a Builder-specific issue.
- locale-tabs (app-locale-tabs, shared) has role="tablist"/"tab" and
  aria-selected but no roving-tabindex/arrow-key navigation - matches the
  same partial-tablist pattern already accepted for product-tabs in the
  storefront pass; all tabs remain natively Tab-focusable, so this meets
  4.1.2/2.1.1 without the full ARIA authoring-practice pattern.

Verified: npx tsc --noEmit clean; npm run build green (only the pre-existing
bundle-budget warning, unrelated to this pass).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 09:01:38 +04:00
sdarbinyan
fb1afb72d4 fix(storefront): WCAG 2.1 AA accessibility fixes
RC A11Y-01 audit pass, storefront + shared app-shell chrome only. Builds
on RC-Visual-02/RC-Premium-01/RC STORE-01 without redoing that work.

- Skip link: added first-focusable "skip to main content" link (app.html,
  styles.scss .skip-link/.sr-only), targeting new #main-content landmark.
  New app.skipToContent i18n key in en/ru/hy.
- Header: mobile menu items stayed keyboard-focusable and screen-reader
  reachable while visually collapsed (max-height:0 with no visibility
  toggle) - fixed with visibility:hidden + matched transition-delay.
  Desktop search input (readonly, click-to-navigate) had no keyboard
  activation - added aria-label + (keydown.enter).
- Cart payment/bank-payment modals: custom (non-app-dialog) UI had no
  focus trap, no Escape handling, and never returned focus to the
  triggering element - ported app-dialog's confirmed-correct
  focus-trap/Escape/return-focus pattern directly onto cart.component.ts.
  Added role="dialog"/aria-modal/aria-label to both panels and
  role="status"|"alert"/aria-live to every payment-status screen so
  screen readers announce state changes (creating/waiting/success/
  error/timeout).
- Search combobox: suggestion listbox had no role="combobox" wiring on
  the input and suggestion buttons weren't role="option" - added
  aria-autocomplete, aria-controls, aria-activedescendant, aria-selected
  so the existing arrow-key navigation is announced to screen readers.
- Product tabs: tablist/tab pattern was incomplete (no role="tablist",
  no tabpanel) - added role="tablist" + ids to product-tabs.component,
  role="tabpanel"/aria-labelledby to the content panel in
  product-details-container.
- Review form: rating/text validation errors weren't associated with
  their controls (no aria-describedby, no role="alert") - fixed; added
  aria-required to the review textarea.
- delivery-selector: added aria-required to the delivery <select> when
  a selection is mandatory.
- Shared app-icon component: doc comment claimed "decorative by default
  (aria-hidden)" but no aria-hidden was ever applied - fixed to actually
  set aria-hidden="true" when undecorated, and role="img"/aria-label
  when ariaLabel is passed. Shared component, affects every icon-only
  usage app-wide, no visual change.
- Color contrast: --text-light fails WCAG AA 4.5:1 for normal text in
  every theme (dexar 3.39:1, lavero/novo 2.54:1 against white). The two
  in-scope usages (company-details org-short/basis, review-form
  upload-placeholder) switched to --text-secondary (4.55:1-7.56:1,
  passes), same visual family, no layout change.

Flagged, not fixed (design-system decisions, not polish):
- --border-color fails WCAG 1.4.11 3:1 for UI-component boundaries in
  every theme (dexar 1.42:1, lavero/novo 1.24:1 vs white) - pervasive
  token used by hundreds of borders app-wide; needs theme-owner sign-off.
- --success-color/--warning-color/--error-color/--info-color used as
  plain text-on-white in several places (product-information,
  question-card, review-form, compare-page) fail 4.5:1 (2.15-3.76:1) -
  genuine brand semantic colors, changing them to pass would visibly
  shift the palette; needs a deliberate token decision.
- Header mobile-menu max-height/padding transition (pre-existing,
  unrelated to this fix) flagged by design lint as layout-thrashing;
  left as-is per the "no layout/business-logic changes" constraint.

Verified: npx tsc --noEmit clean; npm run build green (only the
pre-existing bundle-budget warning, unrelated to this pass).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 08:46:18 +04:00
sdarbinyan
ea1a5d9b8a fix(storefront): premium UX polish for product, compare, wishlist
- delivery-information, product-actions, product-description,
  product-gallery, product-information, related-products,
  variant-selector: normalize hardcoded hex colors to design tokens
  (--text-primary, --text-secondary, --border-color, --bg-primary/
  --bg-secondary, --primary-color/--primary-hover); stock status and
  discount badge now use semantic --success-color/--warning-color/
  --error-color instead of near-duplicate literal hex
- product-actions: add aria-pressed to wishlist/compare toggle
  buttons; add hover/active/disabled states to action buttons
- product-gallery: add aria-current + aria-label to active thumbnail
  button; add focus-visible ring and hover state on thumbnails and
  toolbar buttons
- variant-selector: add aria-pressed to colour/size option buttons;
  add a visible checkmark glyph on the selected colour swatch so
  selection isn't color-only; add hover states
- product-tabs: add role="tab"/aria-selected to tab buttons
- star-selector: add per-star aria-label (new starsLabel i18n key
  added to en/hy/ru + translations.ts interface)
- question-list: add aria-expanded to the ask-question disclosure
  toggle; swap plain empty-state <p> for app-empty-state; add
  hover/disabled states to pager buttons
- review-list: swap plain empty-state <p> for app-empty-state; add
  hover/disabled states to pager and load-more buttons
- question-card, question-form, review-form: normalize accepted/
  success/error colors to semantic tokens; add focus-visible and
  hover/disabled states to inputs and submit buttons
- compare-table: add scope="col"/scope="row" to table headers; make
  header row and attribute column sticky for easier comparison on
  long tables
- compare-page: add hover/focus states to the remove-from-compare
  chip button

Build verified green via `npm run build`.

Out of scope / skipped:
- src/app/pages/item-detail/* is dead code (not referenced by any
  route or component) - left untouched
- wishlist page and product-details-container were already fully
  composed with shared skeleton/empty-state/button components from
  the RC-Visual-02 pass - no changes needed
- stars.component display-only rating glyphs use a light gray not an
  exact token match - left as-is to avoid an unintended visual shift

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:47:57 +04:00
sdarbinyan
4ebc15fff8 fix(builder): composition audit fixes for project editor sections
- Reset-section button: raw <button> with hardcoded colors -> app-button variant="danger"
- section.shared.scss: raw button/input/select colors switched to CSS theme vars (--primary-color, --bg-primary, --bg-secondary, --error-color) instead of bare hex
- save-bar scss referenced nonexistent CSS vars (--surface, --border, --warning, --danger, --muted-foreground, --info-bg, --info) that always fell back to hardcoded hex; renamed to the real theme vars (--bg-primary, --border-color, --warning-color, --error-color, --text-secondary, --info-color) so the save bar is actually theme-aware
- footer/homepage/widgets section scss: nonexistent --danger-color var renamed to --error-color
- static-pages-editor: replaced dead `.editor-section-card` wrapper class (removed from shared stylesheet in the Sprint 30 redesign, never migrated here) with app-section-card, restoring the card chrome every sibling editor section has
- widgets-section: empty state (no widgets) rendered nothing; added app-empty-state
- homepage-section: empty state (no homepage page) rendered nothing; added app-empty-state
- navigation-section: header/footer nav move-up/move-down buttons had no accessible name (bare uarr/darr glyphs); added aria-label
- Added builder.widgetsEmptyTitle/Desc and builder.homepageEmptyTitle/Desc i18n keys (en/ru/hy)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 10:47:37 +04:00
sdarbinyan
63b3cd8744 fix(admin): polish reviews
Same select-all/row-checkbox accessible-name gap in table view; grid
view already had aria-label on its row checkbox (customer name),
table view was the gap. New adminModeration.selectAllRows key added
across en/ru/hy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 00:03:46 +04:00
sdarbinyan
687cc5f5dc fix(admin): polish orders
Same gap as products/categories: table select-all and per-row select
checkboxes had no accessible name. Added aria-label (row label
includes order number for context). New adminOrders.selectAllRows/
selectRow keys across en/ru/hy.

Order detail page and order-timeline component reviewed — no
interactive checkboxes or focus-visible gaps found there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 00:00:29 +04:00
sdarbinyan
a0d3f086c1 fix(admin): polish categories
Add accessible names to select-all, per-row select, and per-row
visibility-toggle checkboxes in the table view — none had them,
including the visibility toggle whose <label> wrapped only the input
with no text content (empty accessible name). Tree/grid views already
had aria-label on their row checkboxes; table view was the gap.

New adminCategories.selectAllRows/selectRow/toggleVisibility keys
added across en/ru/hy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:57:49 +04:00
sdarbinyan
8956cd00d7 fix(admin): polish products
Add accessible names to the table-view select-all and per-row
checkboxes — they had none, while the grid-view equivalent already
did (product name via aria-label). New adminProducts.selectAllRows/
selectRow keys added across en/ru/hy.

Reviewed toolbar, filters, bulk actions, empty/loading states, and
grid view: all buttons already route through app-button (own
:focus-visible), empty state already uses shared EmptyStateComponent.
No orphaned bindings or corrupted glyphs found.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:55:11 +04:00
sdarbinyan
f5225ac326 fix(storefront): polish cart
Fix real regression: the payment success checkmark and timeout clock
icons had been corrupted to literal '?' glyphs (confirmed via git
history — ✓ and ⏱ were replaced), so a customer who just paid saw a
confusing '?' instead of a success indicator. Restored both icons and
marked them aria-hidden since the adjacent heading already conveys
the status.

Add missing accessible names to icon-only buttons that had none:
quantity increase/decrease controls and the mobile delete button
(desktop remove button had a title attribute only, which is not
reliably announced by screen readers — added aria-label alongside it).
New cart.increaseQuantity/decreaseQuantity keys added across all
three locales.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:39:14 +04:00
sdarbinyan
56e311109a fix(storefront): polish compare
Add accessible label to the compare-chip remove button: it rendered
only a bare × glyph with no aria-label, announced as meaningless
symbol text by screen readers. Added ux.removeFromCompare across all
three locales.

Global :focus-visible already covers .btn/.btn-primary/.btn-ghost, so
no separate focus styling was needed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:35:17 +04:00
sdarbinyan
1c3926332f fix(admin): close remaining raw i18n key leaks (Orders status filter, Users suspend/reactivate)
P0-8: re-verified the ~178-key admin i18n gap (docs/KNOWN-ISSUES.md
item 2, docs/ADMIN-UX-AUDIT.md) is largely fixed by prior commits,
but a live pass over Orders/Transactions/Users/Monitoring/Analytics
found 2 remaining leaks:

- adminOrders.status.all rendered literally in the Orders list's
  primary status filter (the bulk-action status select already
  excluded 'all' and was fine; the primary filter loop did not).
- adminUsers.suspend / adminUsers.reactivate rendered literally on
  every row action button on the Users page.

Added the missing keys to all 3 locales. Transactions, Monitoring,
and Analytics show no raw dot-key leaks in this pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:04:51 +04:00
sdarbinyan
1a5d43523a fix(catalog): translate product-card stock badge and action-button labels
P0-1: stock badge rendered the raw item.remainings value ('High',
etc) untranslated on every product card sitewide. Now maps to
catalog.stockHigh/Medium/Low/Out via the translate pipe; the 'out'
class check is now case-insensitive to match.

P0-2: favorite/share/quick-view action-button aria-labels rendered
literal 'catalog.favorite'/'catalog.share'/'catalog.quickView' keys
because they never existed in translations.ts/en.ts/ru.ts/hy.ts
(only catalog.compare existed, and it was likewise unused). Added
all 4 keys to the Translations interface and all 3 locales.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 22:04:13 +04:00
sdarbinyan
71d5f4d320 feat(builder): visual homepage blocks, merchant-language widget settings, real carousel arrows
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

P0 user feedback: homepage builder showed raw section.id like 'section-hero'/'section-categories'; hero widget exposed 'full-bleed'/'boxed' and raw px/vh as free text with no explanation; Overlay/Autoplay toggles made no sense without a slides concept; product carousel widgets rendered arrows nowhere near a working carousel.

Homepage section (sections list -> visual blocks):
- Replaced raw section.id display with a merchant-facing block catalog (icon + name + one-line explanation) for hero/categories/featured-products/product-carousel/recently-viewed/banner/partners/custom-html
- Added block catalog picker to append new blocks (was fixed at whatever the seed data had - task asked 'what if we add manually? not fixed 3')
- Added duplicate and remove per block, alongside the existing drag-to-reorder
- Verified in browser: labels render correctly, add-block and duplicate both confirmed working end-to-end

Widgets section (hero widget):
- 'Layout' free-text replaced with a select (Full width / Boxed) instead of typing 'full-bleed'/'boxed' blind
- 'Height' free-text replaced with a select (Compact/Medium/Tall/Full screen) mapped to real vh values
- New Slides editor: title/subtitle pairs an admin can add/remove: this is the actual multi-slide data the Overlay/Autoplay toggles were referring to with nothing to point at before
- HeroWidgetData contract gains slides[]/autoplay; HeroWidgetComponent now renders a real rotator (dots, click-to-jump, autoplay interval) when more than one slide exists - previously autoplay/overlay props existed but there was no slideshow behavior anywhere to control

Carousel arrows root cause and fix:
- widget-manifest.json offers 'carousel' as a layout option for product-collection/product-carousel widgets, and the admin UI let you select it, but ProductCarouselWidgetComponent always rendered a static CSS grid regardless - there was no carousel implementation to have arrows in the first place
- Now renders a real horizontally-scrollable strip with working prev/next buttons (native scrollBy, disabled at each end) when section.layout.strategy === 'carousel'; falls back to the existing grid otherwise
- Confirmed src/app/components/items-carousel (a PrimeNG p-carousel) is dead code, not wired into any route or widget - not the source of the reported bug

New builder.* i18n keys (en/ru/hy), zero duplicate-key collisions verified via scan
2026-07-19 14:14:29 +04:00
sdarbinyan
726df0cee0 feat(builder): visual footer builder with drag-and-drop columns
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

P0 user feedback: footer static pages were comma-separated text; no way to add extra phones/emails for different countries.

- New FooterColumnConfig/FooterLinkConfig model (footer-config.model.ts): columns of links, each link pointing at an existing static page (resolved by key, so it survives route renames) or a custom URL
- Footer Builder UI: add/remove columns and links, per-link toggle between 'existing page' (dropdown of real static pages) and 'custom URL', drag-and-drop reordering of both columns and links via @angular/cdk/drag-drop (same primitive already used by the homepage section builder)
- Wired FooterResolverService (the service the real storefront footer actually renders through) to read footer.columns as the primary source when present - without this the builder would have saved data nobody ever displayed. Falls back to the existing legacy static-page auto-grouping when no columns are configured, so existing sites are unaffected
- CompanyContactConfig gains additionalPhones/additionalEmails (primary phone/email field unchanged) with add/remove UI for country-specific support lines
- Old comma-separated staticPageKeys input removed from the UI; field kept on the model as deprecated/read-compat only
- New builder.* i18n keys (en/ru/hy); fixed an accidental duplicate-key collision with pre-existing navigation-section addLink/removeLink keys during the rename pass
- Verified in browser: added column, added link, switched link source page->custom, added phone number - all reactive and error-free
2026-07-19 13:58:57 +04:00
sdarbinyan
3b955b116a feat(admin): Shopify-style variant attributes matching production data shape
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

P0 user feedback: variants were just free-text name+price, and the user shared the real production payload - a flat array of {color, size, price, currency, remaining} rows, colors as '0x8B4513' hex.

- New model: AdminProductVariantAttributeDef (key/label/isColor/values) + AdminProductVariant (attributes: Record<string,string>, sku, image, remaining, prices: {currency,price}[]) - this IS the production shape, grouped by combo with one row per currency instead of flattened, so admins edit one variant card instead of 4 duplicate rows
- Attribute manager: add custom attributes (Color, Size, or anything), color attributes get a native color picker + live swatch preview instead of typing hex; other attributes get plain value chips
- 'Generate variants' computes the cartesian product of attribute values (Color x Size = 4 combos for 1 color x 4 sizes) and preserves existing sku/price/stock data for combos that still exist after regeneration
- Multi-currency pricing per variant (matches prod: same combo priced in RUB/USD/EUR/AMD) with per-currency add/remove
- Color hex kept in the exact '0x8B4513' production format (toBackendColor/toCssColor conversion helpers)
- Fixed an Angular v21 control-flow parser bug hit while building this: an @if/@else block whose only content is a bare {{ interpolation }} touching the block's closing brace fails to parse (NG5002 'Unclosed block for' cascading from a completely unrelated line) - worked around by keeping interpolation in its own element
- Verified end-to-end in browser: added Color (color picker) + Size (S/M/L/XL) attributes, generated 4 variant combos, added all 4 currencies to a variant - matches the shared production JSON exactly
2026-07-19 13:40:51 +04:00