2026-07-09 02:29:12 +04:00
|
|
|
<div class="cart-container platform">
|
2026-01-18 18:57:06 +04:00
|
|
|
<div class="cart-header">
|
2026-02-26 23:09:20 +04:00
|
|
|
<h1>{{ 'cart.title' | translate }}</h1>
|
2026-01-18 18:57:06 +04:00
|
|
|
@if (itemCount() > 0) {
|
|
|
|
|
<button class="clear-cart-btn" (click)="clearCart()">
|
2026-07-20 02:45:37 +04:00
|
|
|
<app-icon name="trash" [size]="16" />
|
2026-02-26 23:09:20 +04:00
|
|
|
{{ 'cart.clear' | translate }}
|
2026-01-18 18:57:06 +04:00
|
|
|
</button>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@if (itemCount() === 0) {
|
|
|
|
|
<div class="empty-cart">
|
fix(storefront): composition audit fixes for cart, catalog, product, compare, wishlist, static pages
- 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
2026-07-23 10:37:06 +04:00
|
|
|
<app-empty-state [title]="'cart.empty' | translate" [description]="'cart.emptyDesc' | translate">
|
|
|
|
|
<app-icon slot="icon" name="cart" [size]="64" />
|
|
|
|
|
<div slot="actions">
|
|
|
|
|
<app-button variant="primary" [routerLink]="'/' | langRoute">{{ 'cart.goShopping' | translate }}</app-button>
|
|
|
|
|
</div>
|
|
|
|
|
</app-empty-state>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (itemCount() > 0) {
|
|
|
|
|
<div class="cart-content">
|
|
|
|
|
<div class="cart-items">
|
|
|
|
|
@for (item of items(); track trackByItemId($index, item)) {
|
|
|
|
|
<div class="cart-item-wrapper"
|
|
|
|
|
[class.swiped]="swipedItemId() === item.itemID"
|
|
|
|
|
(touchstart)="onSwipeStart(item.itemID, $event)">
|
|
|
|
|
<div class="cart-item">
|
2026-07-05 01:43:56 +04:00
|
|
|
<a [routerLink]="['/product', item.itemID] | langRoute" class="item-image">
|
2026-07-26 00:12:43 +04:00
|
|
|
<img [src]="getMainImage(item)" [alt]="itemName(item)" loading="lazy" (error)="onImageError($event)" />
|
2026-01-18 18:57:06 +04:00
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
<div class="item-info">
|
|
|
|
|
<div class="item-header">
|
2026-07-05 01:43:56 +04:00
|
|
|
<a [routerLink]="['/product', item.itemID] | langRoute" class="item-name">{{ itemName(item) }}</a>
|
2026-07-19 23:39:14 +04:00
|
|
|
<button class="remove-btn" (click)="removeItem(item)" [attr.title]="'cart.removeItem' | translate" [attr.aria-label]="'cart.removeItem' | translate">
|
2026-07-20 02:45:37 +04:00
|
|
|
<app-icon name="x" [size]="18" />
|
2026-01-18 18:57:06 +04:00
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-07-24 09:46:51 +04:00
|
|
|
@if (itemDesc(item)) {
|
|
|
|
|
<p class="item-description">{{ itemDesc(item) }}...</p>
|
|
|
|
|
}
|
2026-03-24 00:09:11 +04:00
|
|
|
|
2026-03-24 03:24:34 +04:00
|
|
|
@if (item.colour || (item.size && item.size.toLowerCase() !== 'default')) {
|
2026-03-24 00:09:11 +04:00
|
|
|
<div class="cart-item-variants">
|
|
|
|
|
@if (item.colour) {
|
2026-03-24 03:24:34 +04:00
|
|
|
<span class="cart-variant cart-variant-colour">
|
|
|
|
|
{{ 'itemDetail.colour' | translate }}:
|
|
|
|
|
<span class="cart-colour-swatch" [style.background-color]="item.colour" [title]="item.colour"></span>
|
|
|
|
|
</span>
|
2026-03-24 00:09:11 +04:00
|
|
|
}
|
2026-03-24 03:24:34 +04:00
|
|
|
@if (item.size && item.size.toLowerCase() !== 'default') {
|
2026-03-24 00:09:11 +04:00
|
|
|
<span class="cart-variant">{{ 'itemDetail.size' | translate }}: {{ item.size }}</span>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2026-02-20 10:44:03 +04:00
|
|
|
|
|
|
|
|
@if (item.badges && item.badges.length > 0) {
|
|
|
|
|
<div class="cart-item-badges">
|
|
|
|
|
@for (badge of item.badges; track badge) {
|
|
|
|
|
<span class="item-badge" [class]="getBadgeClass(badge)">{{ badge }}</span>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2026-01-18 18:57:06 +04:00
|
|
|
|
|
|
|
|
<div class="item-footer">
|
|
|
|
|
<div class="item-pricing">
|
|
|
|
|
@if (item.discount > 0) {
|
|
|
|
|
<div class="price-with-discount">
|
2026-03-24 00:09:11 +04:00
|
|
|
<span class="original-price">{{ item.price }} {{ item.currency }}</span>
|
|
|
|
|
<span class="current-price">{{ getDiscountedPrice(item) | number:'1.2-2' }} {{ item.currency }}</span>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
} @else {
|
2026-03-24 00:09:11 +04:00
|
|
|
<span class="current-price">{{ item.price }} {{ item.currency }}</span>
|
2026-01-18 18:57:06 +04:00
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="quantity-controls">
|
2026-07-19 23:39:14 +04:00
|
|
|
<button class="qty-btn" (click)="decreaseQuantity(item)" [disabled]="item.quantity <= 1" [attr.aria-label]="'cart.decreaseQuantity' | translate">
|
2026-07-20 02:45:37 +04:00
|
|
|
<app-icon name="minus" [size]="14" />
|
2026-01-18 18:57:06 +04:00
|
|
|
</button>
|
fix(storefront): premium UX polish for cart, checkout
- cart.component.scss: normalize hardcoded hex colors to design tokens
(--text-primary, --text-secondary, --bg-primary/--bg-secondary/
--bg-tertiary, --border-color, --error-color, --success-color,
--warning-color, --shadow-*, --transition-*, --radius-* fallbacks)
across cart items, quantity controls, summary, login gate, terms
checkbox, payment modal, payment-active QR screen, and bank-payment
iframe modal
- cart.component.scss: deduplicate an accidental duplicate
.close-modal-btn rule block (identical CSS repeated twice)
- cart.component.scss: add focus-visible rings to clear-cart, remove,
quantity, checkout, close-modal, retry-payment, copy/open-link,
telegram-login, and card-payment buttons
- cart.component.scss: delivery-required warning now pairs an icon
with the text instead of relying on color/background alone
- cart.component.html: add warning icon + role="alert" to the
delivery-required notice; add aria-live/aria-label to the quantity
value so screen readers announce quantity changes
- delivery-selector.component.scss: normalize hardcoded hex colors to
design tokens; remove dead :host-context(.cart-container.alt) rules
left over after the .alt theme was removed from cart.component in
RC-Visual-02 (cart-container never carries an .alt class anymore);
add hover/focus-visible states to the delivery <select>
Build verified green via `npm run build`.
Out of scope / skipped:
- Did not restructure the payment modal or bank-payment iframe overlay
into shared app-dialog - it has custom multi-step state (creating/
waiting/success/error/timeout) and an already-implemented manual
focus-trap; restructuring it is a composition change, not visual
polish
- Did not convert clearCart()'s native confirm() to a custom
confirm-remove dialog - no existing storefront confirm-dialog
pattern to follow, and adding one is a composition/architecture
change
- spinner-large/spinner-small left untouched per RC-Visual-02 guidance
(in-progress action state, not content loading)
- .email-form block (email/phone capture after payment success) is
dead CSS behind commented-out markup; left in place rather than
deleting, since removing it is a code-cleanup call, not visual
polish
- region-selector/language-selector are header-only, not part of the
cart/checkout flow - left untouched
- no dedicated checkout page exists; checkout is the payment section
of the cart page, covered above
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:57:23 +04:00
|
|
|
<span class="qty-value" aria-live="polite" [attr.aria-label]="item.quantity + ' ' + ('cart.items' | translate)">{{ item.quantity }}</span>
|
2026-07-19 23:39:14 +04:00
|
|
|
<button class="qty-btn" (click)="increaseQuantity(item)" [attr.aria-label]="'cart.increaseQuantity' | translate">
|
2026-07-20 02:45:37 +04:00
|
|
|
<app-icon name="plus" [size]="14" />
|
2026-01-18 18:57:06 +04:00
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-06-21 23:13:01 +04:00
|
|
|
|
|
|
|
|
@if (item.deliveryMode === 'digital' || item.deliveryOptions?.length) {
|
|
|
|
|
<app-delivery-selector
|
|
|
|
|
[item]="item"
|
|
|
|
|
(selectedDeliveryChange)="selectDelivery(item.itemID, $event)"
|
|
|
|
|
/>
|
|
|
|
|
}
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-08-13 10:12:41 +04:00
|
|
|
<button
|
|
|
|
|
class="delete-btn-mobile"
|
|
|
|
|
(click)="removeItem(item)"
|
|
|
|
|
[attr.aria-label]="'cart.removeItem' | translate"
|
|
|
|
|
[attr.tabindex]="swipedItemId() === item.itemID ? null : -1"
|
|
|
|
|
[attr.aria-hidden]="swipedItemId() === item.itemID ? null : 'true'"
|
|
|
|
|
>
|
2026-07-20 02:45:37 +04:00
|
|
|
<app-icon name="trash" [size]="20" />
|
2026-01-18 18:57:06 +04:00
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="cart-summary">
|
|
|
|
|
<div class="summary-header">
|
2026-02-26 23:09:20 +04:00
|
|
|
<h3>{{ 'cart.total' | translate }}</h3>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="summary-row">
|
2026-02-26 23:09:20 +04:00
|
|
|
<span>{{ 'cart.items' | translate }} ({{ itemCount() }})</span>
|
2026-03-24 00:09:11 +04:00
|
|
|
<span class="value">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
|
2026-06-20 15:16:25 +04:00
|
|
|
@if (hasDeliveryPrice()) {
|
|
|
|
|
<div class="summary-row delivery">
|
|
|
|
|
<span>{{ 'cart.deliveryLabel' | translate }}</span>
|
|
|
|
|
<span class="value">{{ totalDeliveryPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2026-01-18 18:57:06 +04:00
|
|
|
|
2026-06-21 23:13:01 +04:00
|
|
|
@if (!allRequiredDeliveriesSelected()) {
|
fix(storefront): premium UX polish for cart, checkout
- cart.component.scss: normalize hardcoded hex colors to design tokens
(--text-primary, --text-secondary, --bg-primary/--bg-secondary/
--bg-tertiary, --border-color, --error-color, --success-color,
--warning-color, --shadow-*, --transition-*, --radius-* fallbacks)
across cart items, quantity controls, summary, login gate, terms
checkbox, payment modal, payment-active QR screen, and bank-payment
iframe modal
- cart.component.scss: deduplicate an accidental duplicate
.close-modal-btn rule block (identical CSS repeated twice)
- cart.component.scss: add focus-visible rings to clear-cart, remove,
quantity, checkout, close-modal, retry-payment, copy/open-link,
telegram-login, and card-payment buttons
- cart.component.scss: delivery-required warning now pairs an icon
with the text instead of relying on color/background alone
- cart.component.html: add warning icon + role="alert" to the
delivery-required notice; add aria-live/aria-label to the quantity
value so screen readers announce quantity changes
- delivery-selector.component.scss: normalize hardcoded hex colors to
design tokens; remove dead :host-context(.cart-container.alt) rules
left over after the .alt theme was removed from cart.component in
RC-Visual-02 (cart-container never carries an .alt class anymore);
add hover/focus-visible states to the delivery <select>
Build verified green via `npm run build`.
Out of scope / skipped:
- Did not restructure the payment modal or bank-payment iframe overlay
into shared app-dialog - it has custom multi-step state (creating/
waiting/success/error/timeout) and an already-implemented manual
focus-trap; restructuring it is a composition change, not visual
polish
- Did not convert clearCart()'s native confirm() to a custom
confirm-remove dialog - no existing storefront confirm-dialog
pattern to follow, and adding one is a composition/architecture
change
- spinner-large/spinner-small left untouched per RC-Visual-02 guidance
(in-progress action state, not content loading)
- .email-form block (email/phone capture after payment success) is
dead CSS behind commented-out markup; left in place rather than
deleting, since removing it is a code-cleanup call, not visual
polish
- region-selector/language-selector are header-only, not part of the
cart/checkout flow - left untouched
- no dedicated checkout page exists; checkout is the payment section
of the cart page, covered above
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:57:23 +04:00
|
|
|
<p class="delivery-warning" role="alert">
|
|
|
|
|
<app-icon name="warning" [size]="16" />
|
|
|
|
|
{{ 'cart.deliveryRequired' | translate }}
|
|
|
|
|
</p>
|
2026-06-21 23:13:01 +04:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 18:57:06 +04:00
|
|
|
<div class="summary-row total">
|
2026-02-26 23:09:20 +04:00
|
|
|
<span>{{ 'cart.toPay' | translate }}</span>
|
2026-06-20 15:16:25 +04:00
|
|
|
<span class="total-price">{{ totalWithDelivery() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="terms-agreement">
|
|
|
|
|
<label class="checkbox-container">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
[(ngModel)]="termsAccepted"
|
|
|
|
|
id="terms-checkbox"
|
|
|
|
|
/>
|
|
|
|
|
<span class="checkmark"></span>
|
|
|
|
|
<span class="terms-text">
|
2026-02-26 23:09:20 +04:00
|
|
|
{{ 'cart.agreeWith' | translate }}
|
2026-07-05 00:51:29 +04:00
|
|
|
<!-- TODO(CMS): Replace these labels with backend-configured legal document links. -->
|
|
|
|
|
<span>{{ 'cart.publicOffer' | translate }}</span>,
|
|
|
|
|
<span>{{ 'cart.returnPolicy' | translate }}</span>,
|
|
|
|
|
<span>{{ 'cart.guaranteeTerms' | translate }}</span> {{ 'cart.and' | translate }}
|
|
|
|
|
<span>{{ 'cart.privacyPolicy' | translate }}</span>
|
2026-01-18 18:57:06 +04:00
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-06-29 00:06:18 +04:00
|
|
|
<div class="payment-method-actions">
|
|
|
|
|
<button
|
|
|
|
|
class="checkout-btn qr-payment-btn"
|
|
|
|
|
(click)="checkout('qr')"
|
|
|
|
|
[class.disabled]="isCheckoutDisabled"
|
|
|
|
|
[disabled]="isCheckoutDisabled"
|
|
|
|
|
>
|
|
|
|
|
{{ 'cart.payViaQr' | translate }}
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
class="checkout-btn card-payment-btn"
|
|
|
|
|
(click)="checkout('card')"
|
|
|
|
|
[class.disabled]="isCheckoutDisabled"
|
|
|
|
|
[disabled]="isCheckoutDisabled"
|
|
|
|
|
>
|
|
|
|
|
{{ 'cart.payViaCard' | translate }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2026-03-24 00:09:11 +04:00
|
|
|
|
|
|
|
|
@if (!isAuthenticated()) {
|
|
|
|
|
<div class="cart-login-gate">
|
|
|
|
|
<div class="login-gate-icon">
|
2026-07-20 02:45:37 +04:00
|
|
|
<app-icon name="lock" [size]="32" />
|
2026-03-24 00:09:11 +04:00
|
|
|
</div>
|
|
|
|
|
<p class="login-gate-title">{{ 'cart.loginRequired' | translate }}</p>
|
|
|
|
|
<p class="login-gate-desc">{{ 'cart.loginRequiredDesc' | translate }}</p>
|
|
|
|
|
|
|
|
|
|
<button class="telegram-login-btn" (click)="requestLogin()">
|
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
|
|
|
<path d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/>
|
|
|
|
|
</svg>
|
|
|
|
|
{{ 'cart.loginWithTelegram' | translate }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Payment Popup Modal -->
|
refactor: migrate cart payment modals to shared app-dialog primitive
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>
2026-08-06 11:13:33 +04:00
|
|
|
<app-dialog
|
|
|
|
|
class="payment-dialog"
|
|
|
|
|
[open]="showPaymentPopup()"
|
|
|
|
|
size="md"
|
|
|
|
|
[ariaLabel]="'cart.checkout' | translate"
|
|
|
|
|
[closeOnBackdropClick]="false"
|
|
|
|
|
[closeOnEscape]="!showBankPaymentPopup()"
|
|
|
|
|
(closed)="closePaymentPopup()"
|
|
|
|
|
>
|
2026-02-26 23:09:20 +04:00
|
|
|
<button class="close-modal-btn" (click)="closePaymentPopup()" [attr.aria-label]="'cart.close' | translate">
|
2026-07-20 02:45:37 +04:00
|
|
|
<app-icon name="x" [size]="20" />
|
2026-01-18 18:57:06 +04:00
|
|
|
</button>
|
|
|
|
|
@if (paymentStatus() === 'creating') {
|
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
|
|
|
<div class="payment-status-screen" role="status" aria-live="polite">
|
2026-01-18 18:57:06 +04:00
|
|
|
<div class="spinner-large"></div>
|
2026-02-26 23:09:20 +04:00
|
|
|
<h2>{{ 'cart.creatingPayment' | translate }}</h2>
|
|
|
|
|
<p>{{ 'cart.waitFewSeconds' | translate }}</p>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (paymentStatus() === 'waiting') {
|
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
|
|
|
<div class="payment-active" role="status" aria-live="polite">
|
2026-06-29 00:06:18 +04:00
|
|
|
<h2>{{ selectedPaymentMethod() === 'card' ? ('cart.waitingPayment' | translate) : ('cart.scanQr' | translate) }}</h2>
|
2026-01-18 18:57:06 +04:00
|
|
|
|
2026-06-28 22:18:35 +04:00
|
|
|
@if (qrCodeUrl()) {
|
|
|
|
|
<div class="qr-section">
|
|
|
|
|
<div class="qr-wrapper">
|
2026-07-26 00:12:43 +04:00
|
|
|
<img [src]="qrCodeUrl()" [attr.alt]="'cart.paymentQrAlt' | translate" class="qr-code" (error)="onImageError($event)" />
|
2026-06-28 22:18:35 +04:00
|
|
|
<div class="scan-line"></div>
|
|
|
|
|
</div>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
2026-06-28 22:18:35 +04:00
|
|
|
}
|
2026-01-18 18:57:06 +04:00
|
|
|
|
|
|
|
|
<div class="payment-info">
|
|
|
|
|
<div class="payment-amount">
|
2026-02-26 23:09:20 +04:00
|
|
|
<span class="label">{{ 'cart.amountToPay' | translate }}</span>
|
2026-06-21 23:13:01 +04:00
|
|
|
<span class="amount">{{ totalWithDelivery() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="waiting-indicator">
|
|
|
|
|
<div class="pulse-dot"></div>
|
2026-02-26 23:09:20 +04:00
|
|
|
<span>{{ 'cart.waitingPayment' | translate }}</span>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-06-28 22:18:35 +04:00
|
|
|
<div class="payment-actions">
|
|
|
|
|
@if (paymentUrl()) {
|
|
|
|
|
<button class="copy-btn" (click)="copyPaymentLink()">
|
|
|
|
|
{{ linkCopied() ? ('cart.copied' | translate) : ('cart.copyLink' | translate) }}
|
|
|
|
|
</button>
|
|
|
|
|
<a [href]="paymentUrl()" target="_blank" rel="noopener noreferrer" class="open-btn">
|
|
|
|
|
{{ 'cart.openNewTab' | translate }}
|
|
|
|
|
</a>
|
|
|
|
|
}
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 00:57:36 +04:00
|
|
|
@if (paymentStatus() === 'error') {
|
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
|
|
|
<div class="payment-status-screen error" role="alert" aria-live="assertive">
|
2026-07-19 23:39:14 +04:00
|
|
|
<div class="error-icon" aria-hidden="true">!</div>
|
2026-06-02 00:57:36 +04:00
|
|
|
<h2>{{ 'cart.paymentError' | translate }}</h2>
|
|
|
|
|
<p>{{ 'cart.paymentErrorDesc' | translate }}</p>
|
|
|
|
|
|
|
|
|
|
<div class="payment-error-actions">
|
|
|
|
|
<button class="retry-payment-btn" (click)="retryPayment()">
|
|
|
|
|
{{ 'cart.retryPayment' | translate }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 18:57:06 +04:00
|
|
|
@if (paymentStatus() === 'success') {
|
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
|
|
|
<div class="payment-status-screen success" role="status" aria-live="polite">
|
2026-07-19 23:39:14 +04:00
|
|
|
<div class="success-icon" aria-hidden="true">✓</div>
|
2026-02-26 23:09:20 +04:00
|
|
|
<h2>{{ 'cart.paymentSuccess' | translate }}</h2>
|
2026-08-13 07:37:36 +04:00
|
|
|
@if (!purchaseSubmitted()) {
|
|
|
|
|
<p>{{ 'cart.paymentSuccessDesc' | translate }}</p>
|
|
|
|
|
<form class="contact-capture-form" (ngSubmit)="submitEmail()">
|
|
|
|
|
<label>
|
|
|
|
|
<input
|
|
|
|
|
type="email"
|
|
|
|
|
name="email"
|
|
|
|
|
[value]="userEmail()"
|
|
|
|
|
[placeholder]="'cart.emailPlaceholder' | translate"
|
|
|
|
|
[attr.aria-invalid]="!!emailError()"
|
|
|
|
|
(input)="onEmailInput($event)"
|
|
|
|
|
(blur)="onEmailBlur()"
|
|
|
|
|
[disabled]="emailSubmitting()"
|
|
|
|
|
/>
|
|
|
|
|
@if (emailError()) {
|
|
|
|
|
<span class="field-error">{{ emailError() }}</span>
|
|
|
|
|
}
|
|
|
|
|
</label>
|
|
|
|
|
<label>
|
|
|
|
|
<input
|
|
|
|
|
type="tel"
|
|
|
|
|
name="phone"
|
|
|
|
|
[value]="userPhone()"
|
|
|
|
|
[placeholder]="'cart.phonePlaceholder' | translate"
|
|
|
|
|
[attr.aria-invalid]="!!phoneError()"
|
|
|
|
|
(input)="onPhoneInput($event)"
|
|
|
|
|
(blur)="onPhoneBlur()"
|
|
|
|
|
[disabled]="emailSubmitting()"
|
|
|
|
|
/>
|
|
|
|
|
@if (phoneError()) {
|
|
|
|
|
<span class="field-error">{{ phoneError() }}</span>
|
|
|
|
|
}
|
|
|
|
|
</label>
|
|
|
|
|
<button type="submit" [disabled]="emailSubmitting()">
|
|
|
|
|
{{ emailSubmitting() ? ('cart.sending' | translate) : ('cart.send' | translate) }}
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
}
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@if (paymentStatus() === 'timeout') {
|
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
|
|
|
<div class="payment-status-screen timeout" role="status" aria-live="polite">
|
2026-07-19 23:39:14 +04:00
|
|
|
<div class="timeout-icon" aria-hidden="true">⏱</div>
|
2026-02-26 23:09:20 +04:00
|
|
|
<h2>{{ 'cart.paymentTimeout' | translate }}</h2>
|
|
|
|
|
<p>{{ 'cart.paymentTimeoutDesc' | translate }}</p>
|
|
|
|
|
<p class="auto-close">{{ 'cart.autoClose' | translate }}</p>
|
2026-01-18 18:57:06 +04:00
|
|
|
</div>
|
|
|
|
|
}
|
refactor: migrate cart payment modals to shared app-dialog primitive
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>
2026-08-06 11:13:33 +04:00
|
|
|
</app-dialog>
|
2026-06-28 22:18:35 +04:00
|
|
|
|
refactor: migrate cart payment modals to shared app-dialog primitive
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>
2026-08-06 11:13:33 +04:00
|
|
|
@if (showBankPaymentPopup() && bankPaymentFrameUrl()) {
|
|
|
|
|
<app-dialog
|
|
|
|
|
class="bank-payment-dialog"
|
|
|
|
|
[open]="showBankPaymentPopup()"
|
|
|
|
|
size="lg"
|
|
|
|
|
[ariaLabel]="'cart.payViaCard' | translate"
|
|
|
|
|
[closeOnBackdropClick]="false"
|
|
|
|
|
(closed)="closeBankPaymentPopup()"
|
|
|
|
|
>
|
|
|
|
|
<button class="close-modal-btn" (click)="closeBankPaymentPopup()" [attr.aria-label]="'cart.close' | translate">
|
|
|
|
|
<app-icon name="x" [size]="20" />
|
|
|
|
|
</button>
|
|
|
|
|
<iframe [src]="bankPaymentFrameUrl()" class="bank-payment-frame" [attr.title]="'common.bankCardPayment' | translate"></iframe>
|
|
|
|
|
</app-dialog>
|
2026-01-18 18:57:06 +04:00
|
|
|
}
|
2026-03-24 00:09:11 +04:00
|
|
|
|
|
|
|
|
<app-telegram-login />
|
2026-07-09 02:29:12 +04:00
|
|
|
|
2026-07-26 00:08:00 +04:00
|
|
|
<app-confirm-dialog
|
|
|
|
|
[open]="clearCartConfirmOpen()"
|
|
|
|
|
[titleText]="'cart.confirmClear' | translate"
|
|
|
|
|
[message]="'cart.confirmClear' | translate"
|
|
|
|
|
[destructive]="true"
|
|
|
|
|
(confirmed)="confirmClearCart()"
|
|
|
|
|
(cancelled)="clearCartConfirmOpen.set(false)"
|
|
|
|
|
/>
|
|
|
|
|
|
2026-07-09 02:29:12 +04:00
|
|
|
|