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>
createOrder() sent a discount-applied price per line item that the
client computed itself, with no server revalidation. Items now only
carry productId/name/quantity - the backend must price from its own
catalog. createPayment()'s amount (required to actually charge the
payment gateway) is unchanged; backend must revalidate it instead,
tracked in BACKEND-API-REFERENCE.md §12.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Third attempt, done properly this time - first two were reverted
(one stopped cleanly on real conflicts, one botched sequencing and
deleted the old focus-trap before finishing the swap).
DialogComponent gains closeOnEscape/closeOnBackdropClick (default true,
backward-compatible with its 13 other call sites) and ariaLabel (for
dialogs with no visible title header). FOCUSABLE_SELECTOR now includes
iframe for the bank-payment panel's focus trap.
Cart wires closeOnBackdropClick=false on both dialogs (in-flight payment
shouldn't cancel on a stray click) and closeOnEscape tied to the bank
popup's open state, so Escape closes the nested bank iframe first and
falls back to the QR view - matches the original priority exactly.
Original geometry (500px QR modal/40px padding, 960x760 bank modal/
56-16-16 padding, both mobile breakpoints) preserved via :host ::ng-deep
overrides scoped per dialog instance - same pattern already used by
product-carousel-widget.component.ts.
cart.component.ts loses ~90 lines of hand-rolled ViewChild/HostListener/
focus-trap code - app-dialog owns all of it now.
Verified live in browser: dialog sizing/padding/aria-label correct at
mobile+desktop, backdrop-click confirmed inert, Escape-priority confirmed
(bank closes first, then QR), initial focus lands on close button.
83/83 tests pass, tsc/build clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
getMainImage() referenced /assets/images/placeholder.svg as the no-image
fallback, but src/assets/images/ never existed - any item with zero
photos rendered a browser broken-image icon instead of a placeholder.
Added the asset.
Also added an (error) handler (onImageError) on every dynamic <img> that
renders a user/admin-supplied URL (product card, cart line item, cart
payment QR code, product gallery main + thumbnails) so a 404'd/broken
image URL swaps to the shared placeholder instead of shipping broken.
- 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.
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>
- Replace hand-rolled loading/error/empty markup with shared app-skeleton,
app-empty-state, and app-button across catalog, product details, cart,
compare, wishlist, and the public static-page renderer
- Fix hardcoded hex colors that bypassed theme CSS variables (catalog,
product details), restoring multi-tenant theme correctness
- Remove ~1100 lines of dead "alt" cart theme CSS (never applied by the
template) from cart.component.scss, bringing it back under the 40kB
build budget (89.49kB -> 59.39kB cart-component chunk)
- Swap legacy global .btn/.btn-ghost/.btn-primary classes for app-button
in compare and wishlist empty/toolbar actions
- CartPaymentRequest gains qrDescription (brandName > hostname > fallback
text) and customerID (telegram id)
- QrCreateResponse gains qrTTL; polling window now derived from it
(min 60s) instead of a fixed 3-minute/36-check cap
- PAYMENT_MAX_CHECKS replaced by PAYMENT_MIN_POLL_SECONDS
Cart: trash/X/plus/minus/lock icons replaced with app-icon. Removed
the standalone EmptyCartIconComponent entirely - it was a duplicate
80px shopping-cart glyph with no unique illustration, only ever used
in one place; now app-icon name="cart" inline.
Language/region selectors: dropdown chevrons (duplicated 3x with
identical path data across two components) unified on
chevronDown; region pin, locate (crosshair), and globe icons replaced.
New mapPin/locate icons added to the registry.
Items carousel: rating star and add-to-cart icons replaced.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>