fix(icons): replace hand-rolled inline SVGs with Lucide (cart, selectors, carousel)

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>
This commit is contained in:
sdarbinyan
2026-07-20 02:45:37 +04:00
parent 6b0ad6455b
commit 3837ddfb2d
12 changed files with 28 additions and 78 deletions

View File

@@ -1,5 +0,0 @@
<svg width="80" height="80" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4h2l2.5 10.5a2 2 0 0 0 2 1.5H17a2 2 0 0 0 2-1.5L21 7H6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="10" cy="19" r="1.5" stroke="currentColor" stroke-width="2"/>
<circle cx="17" cy="19" r="1.5" stroke="currentColor" stroke-width="2"/>
</svg>

Before

Width:  |  Height:  |  Size: 411 B

View File

@@ -1,7 +0,0 @@
:host {
display: block;
svg {
color: var(--primary-color);
}
}

View File

@@ -1,9 +0,0 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-empty-cart-icon',
templateUrl: './empty-cart-icon.component.html',
styleUrls: ['./empty-cart-icon.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EmptyCartIconComponent {}

View File

@@ -36,9 +36,7 @@
@if (product.rating) { @if (product.rating) {
<div class="item-rating"> <div class="item-rating">
<svg width="14" height="14" viewBox="0 0 24 24" fill="#497671" xmlns="http://www.w3.org/2000/svg"> <app-icon name="star" [size]="14" />
<path d="M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2Z"/>
</svg>
<span class="rating-value">{{ product.rating }}</span> <span class="rating-value">{{ product.rating }}</span>
<span class="rating-count">({{ product.callbacks?.length || 0 }})</span> <span class="rating-count">({{ product.callbacks?.length || 0 }})</span>
</div> </div>
@@ -54,11 +52,7 @@
} }
</div> </div>
<button class="cart-icon-btn" (click)="addToCart($event, product)" [title]="'carousel.addToCart' | translate"> <button class="cart-icon-btn" (click)="addToCart($event, product)" [title]="'carousel.addToCart' | translate">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <app-icon name="cart" [size]="16" />
<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path>
<line x1="3" y1="6" x2="21" y2="6"></line>
<path d="M16 10a4 4 0 0 1-8 0"></path>
</svg>
</button> </button>
</div> </div>
</div> </div>

View File

@@ -11,11 +11,12 @@ import { LanguageService } from '../../services/language.service';
import { LangRoutePipe } from '../../pipes/lang-route.pipe'; import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe'; import { TranslatePipe } from '../../i18n/translate.pipe';
import { ProductFacade } from '../../facades/platform/product.facade'; import { ProductFacade } from '../../facades/platform/product.facade';
import { IconComponent } from '../../shared/ui/icon/icon.component';
@Component({ @Component({
selector: 'app-items-carousel', selector: 'app-items-carousel',
templateUrl: './items-carousel.component.html', templateUrl: './items-carousel.component.html',
imports: [DecimalPipe, RouterLink, CarouselModule, ButtonModule, TagModule, LangRoutePipe, TranslatePipe], imports: [DecimalPipe, RouterLink, CarouselModule, ButtonModule, TagModule, LangRoutePipe, TranslatePipe, IconComponent],
styleUrls: ['./items-carousel.component.scss'], styleUrls: ['./items-carousel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })

View File

@@ -4,9 +4,7 @@
[alt]="languageService.getCurrentLanguage()?.name" [alt]="languageService.getCurrentLanguage()?.name"
class="language-flag"> class="language-flag">
<span class="language-code">{{ languageService.getCurrentLanguage()?.code?.toUpperCase() }}</span> <span class="language-code">{{ languageService.getCurrentLanguage()?.code?.toUpperCase() }}</span>
<svg class="dropdown-arrow" [class.rotated]="dropdownOpen" width="12" height="12" viewBox="0 0 12 12" fill="none"> <app-icon name="chevronDown" [size]="14" [class.rotated]="dropdownOpen" class="dropdown-arrow" />
<path d="M2.5 4.5L6 8L9.5 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button> </button>
<div class="language-dropdown" [class.open]="dropdownOpen"> <div class="language-dropdown" [class.open]="dropdownOpen">

View File

@@ -1,9 +1,10 @@
import { Component, HostListener, ElementRef, ChangeDetectionStrategy } from '@angular/core'; import { Component, HostListener, ElementRef, ChangeDetectionStrategy } from '@angular/core';
import { LanguageService, Language, Currency } from '../../services/language.service'; import { LanguageService, Language, Currency } from '../../services/language.service';
import { IconComponent } from '../../shared/ui/icon/icon.component';
@Component({ @Component({
selector: 'app-language-selector', selector: 'app-language-selector',
imports: [], imports: [IconComponent],
templateUrl: './language-selector.component.html', templateUrl: './language-selector.component.html',
styleUrls: ['./language-selector.component.scss'], styleUrls: ['./language-selector.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -1,9 +1,6 @@
<div class="region-selector"> <div class="region-selector">
<button class="region-trigger" (click)="toggleDropdown()" [class.active]="dropdownOpen()"> <button class="region-trigger" (click)="toggleDropdown()" [class.active]="dropdownOpen()">
<svg class="pin-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <app-icon name="mapPin" [size]="16" class="pin-icon" />
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
<circle cx="12" cy="10" r="3"></circle>
</svg>
<span class="region-name"> <span class="region-name">
@if (detecting()) { @if (detecting()) {
<span class="detecting">...</span> <span class="detecting">...</span>
@@ -13,10 +10,7 @@
{{ 'location.allRegions' | translate }} {{ 'location.allRegions' | translate }}
} }
</span> </span>
<svg class="chevron" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" <app-icon name="chevronDown" [size]="14" [class.rotated]="dropdownOpen()" class="chevron" />
[class.rotated]="dropdownOpen()">
<path d="M6 9l6 6 6-6"></path>
</svg>
</button> </button>
@if (dropdownOpen()) { @if (dropdownOpen()) {
@@ -25,20 +19,14 @@
<span>{{ 'location.chooseRegion' | translate }}</span> <span>{{ 'location.chooseRegion' | translate }}</span>
@if (!detecting()) { @if (!detecting()) {
<button class="detect-btn" (click)="detectLocation()" title="{{ 'location.detectAuto' | translate }}"> <button class="detect-btn" (click)="detectLocation()" title="{{ 'location.detectAuto' | translate }}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <app-icon name="locate" [size]="14" />
<circle cx="12" cy="12" r="3"></circle>
<path d="M12 2v4M12 18v4M2 12h4M18 12h4"></path>
</svg>
</button> </button>
} }
</div> </div>
<div class="region-list"> <div class="region-list">
<button class="region-option" [class.selected]="!region()" (click)="selectGlobal()"> <button class="region-option" [class.selected]="!region()" (click)="selectGlobal()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <app-icon name="globe" [size]="14" />
<circle cx="12" cy="12" r="10"></circle>
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>
<span>{{ 'location.allRegions' | translate }}</span> <span>{{ 'location.allRegions' | translate }}</span>
</button> </button>

View File

@@ -2,10 +2,11 @@ import { Component, ChangeDetectionStrategy, inject, signal, HostListener } from
import { LocationService } from '../../services/location.service'; import { LocationService } from '../../services/location.service';
import { Region } from '../../models/location.model'; import { Region } from '../../models/location.model';
import { TranslatePipe } from '../../i18n/translate.pipe'; import { TranslatePipe } from '../../i18n/translate.pipe';
import { IconComponent } from '../../shared/ui/icon/icon.component';
@Component({ @Component({
selector: 'app-region-selector', selector: 'app-region-selector',
imports: [TranslatePipe], imports: [TranslatePipe, IconComponent],
templateUrl: './region-selector.component.html', templateUrl: './region-selector.component.html',
styleUrls: ['./region-selector.component.scss'], styleUrls: ['./region-selector.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -3,9 +3,7 @@
<h1>{{ 'cart.title' | translate }}</h1> <h1>{{ 'cart.title' | translate }}</h1>
@if (itemCount() > 0) { @if (itemCount() > 0) {
<button class="clear-cart-btn" (click)="clearCart()"> <button class="clear-cart-btn" (click)="clearCart()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <app-icon name="trash" [size]="16" />
<path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/>
</svg>
{{ 'cart.clear' | translate }} {{ 'cart.clear' | translate }}
</button> </button>
} }
@@ -14,7 +12,7 @@
@if (itemCount() === 0) { @if (itemCount() === 0) {
<div class="empty-cart"> <div class="empty-cart">
<div class="empty-icon"> <div class="empty-icon">
<app-empty-cart-icon /> <app-icon name="cart" [size]="64" />
</div> </div>
<h2>{{ 'cart.empty' | translate }}</h2> <h2>{{ 'cart.empty' | translate }}</h2>
<p>{{ 'cart.emptyDesc' | translate }}</p> <p>{{ 'cart.emptyDesc' | translate }}</p>
@@ -38,9 +36,7 @@
<div class="item-header"> <div class="item-header">
<a [routerLink]="['/product', item.itemID] | langRoute" class="item-name">{{ itemName(item) }}</a> <a [routerLink]="['/product', item.itemID] | langRoute" class="item-name">{{ itemName(item) }}</a>
<button class="remove-btn" (click)="removeItem(item)" [attr.title]="'cart.removeItem' | translate" [attr.aria-label]="'cart.removeItem' | translate"> <button class="remove-btn" (click)="removeItem(item)" [attr.title]="'cart.removeItem' | translate" [attr.aria-label]="'cart.removeItem' | translate">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <app-icon name="x" [size]="18" />
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button> </button>
</div> </div>
@@ -82,15 +78,11 @@
<div class="quantity-controls"> <div class="quantity-controls">
<button class="qty-btn" (click)="decreaseQuantity(item)" [disabled]="item.quantity <= 1" [attr.aria-label]="'cart.decreaseQuantity' | translate"> <button class="qty-btn" (click)="decreaseQuantity(item)" [disabled]="item.quantity <= 1" [attr.aria-label]="'cart.decreaseQuantity' | translate">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> <app-icon name="minus" [size]="14" />
<path d="M5 12h14"/>
</svg>
</button> </button>
<span class="qty-value">{{ item.quantity }}</span> <span class="qty-value">{{ item.quantity }}</span>
<button class="qty-btn" (click)="increaseQuantity(item)" [attr.aria-label]="'cart.increaseQuantity' | translate"> <button class="qty-btn" (click)="increaseQuantity(item)" [attr.aria-label]="'cart.increaseQuantity' | translate">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> <app-icon name="plus" [size]="14" />
<path d="M12 5v14M5 12h14"/>
</svg>
</button> </button>
</div> </div>
</div> </div>
@@ -105,9 +97,7 @@
</div> </div>
<button class="delete-btn-mobile" (click)="removeItem(item)" [attr.aria-label]="'cart.removeItem' | translate"> <button class="delete-btn-mobile" (click)="removeItem(item)" [attr.aria-label]="'cart.removeItem' | translate">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <app-icon name="trash" [size]="20" />
<path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>
</button> </button>
</div> </div>
} }
@@ -181,9 +171,7 @@
@if (!isAuthenticated()) { @if (!isAuthenticated()) {
<div class="cart-login-gate"> <div class="cart-login-gate">
<div class="login-gate-icon"> <div class="login-gate-icon">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"> <app-icon name="lock" [size]="32" />
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
</svg>
</div> </div>
<p class="login-gate-title">{{ 'cart.loginRequired' | translate }}</p> <p class="login-gate-title">{{ 'cart.loginRequired' | translate }}</p>
<p class="login-gate-desc">{{ 'cart.loginRequiredDesc' | translate }}</p> <p class="login-gate-desc">{{ 'cart.loginRequiredDesc' | translate }}</p>
@@ -206,9 +194,7 @@
<div class="payment-modal-overlay"> <div class="payment-modal-overlay">
<div class="payment-modal"> <div class="payment-modal">
<button class="close-modal-btn" (click)="closePaymentPopup()" [attr.aria-label]="'cart.close' | translate"> <button class="close-modal-btn" (click)="closePaymentPopup()" [attr.aria-label]="'cart.close' | translate">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <app-icon name="x" [size]="20" />
<path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button> </button>
@if (paymentStatus() === 'creating') { @if (paymentStatus() === 'creating') {
<div class="payment-status-screen"> <div class="payment-status-screen">
@@ -343,9 +329,7 @@
<div class="bank-payment-modal-overlay"> <div class="bank-payment-modal-overlay">
<div class="bank-payment-modal"> <div class="bank-payment-modal">
<button class="close-modal-btn" (click)="closeBankPaymentPopup()" [attr.aria-label]="'cart.close' | translate"> <button class="close-modal-btn" (click)="closeBankPaymentPopup()" [attr.aria-label]="'cart.close' | translate">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"> <app-icon name="x" [size]="20" />
<path d="M6 6L18 18M6 18L18 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</button> </button>
<iframe [src]="bankPaymentFrameUrl()" class="bank-payment-frame" title="Bank card payment"></iframe> <iframe [src]="bankPaymentFrameUrl()" class="bank-payment-frame" title="Bank card payment"></iframe>
</div> </div>

View File

@@ -8,19 +8,19 @@ import { Item, CartItem, DeliveryOption } from '../../models';
import { EMPTY, interval, of, Subscription } from 'rxjs'; import { EMPTY, interval, of, Subscription } from 'rxjs';
import { catchError, exhaustMap, take, timeout } from 'rxjs/operators'; import { catchError, exhaustMap, take, timeout } from 'rxjs/operators';
import { DeliverySelectorComponent } from '../../components/delivery-selector/delivery-selector.component'; import { DeliverySelectorComponent } from '../../components/delivery-selector/delivery-selector.component';
import { EmptyCartIconComponent } from '../../components/empty-cart-icon/empty-cart-icon.component';
import { TelegramLoginComponent } from '../../components/telegram-login/telegram-login.component'; import { TelegramLoginComponent } from '../../components/telegram-login/telegram-login.component';
import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils'; import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils';
import { LangRoutePipe } from '../../pipes/lang-route.pipe'; import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe'; import { TranslatePipe } from '../../i18n/translate.pipe';
import { TranslateService } from '../../i18n/translate.service'; import { TranslateService } from '../../i18n/translate.service';
import { PAYMENT_POLL_INTERVAL_MS, PAYMENT_MAX_CHECKS, PAYMENT_TIMEOUT_CLOSE_MS, LINK_COPIED_DURATION_MS } from '../../config/constants'; import { PAYMENT_POLL_INTERVAL_MS, PAYMENT_MAX_CHECKS, PAYMENT_TIMEOUT_CLOSE_MS, LINK_COPIED_DURATION_MS } from '../../config/constants';
import { IconComponent } from '../../shared/ui/icon/icon.component';
type PaymentMethod = 'qr' | 'card'; type PaymentMethod = 'qr' | 'card';
@Component({ @Component({
selector: 'app-cart', selector: 'app-cart',
imports: [DecimalPipe, RouterLink, FormsModule, EmptyCartIconComponent, DeliverySelectorComponent, TelegramLoginComponent, LangRoutePipe, TranslatePipe], imports: [DecimalPipe, RouterLink, FormsModule, DeliverySelectorComponent, TelegramLoginComponent, LangRoutePipe, TranslatePipe, IconComponent],
templateUrl: './cart.component.html', templateUrl: './cart.component.html',
styleUrls: ['./cart.component.scss'], styleUrls: ['./cart.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush

View File

@@ -42,8 +42,10 @@ import {
LucideLink as Link, LucideLink as Link,
LucideList as List, LucideList as List,
LucideLoader2 as Loader2, LucideLoader2 as Loader2,
LucideLocate as Locate,
LucideLock as Lock, LucideLock as Lock,
LucideLogOut as LogOut, LucideLogOut as LogOut,
LucideMapPin as MapPin,
LucideMegaphone as Megaphone, LucideMegaphone as Megaphone,
LucideMenu as Menu, LucideMenu as Menu,
LucideMinus as Minus, LucideMinus as Minus,
@@ -149,6 +151,8 @@ export const APP_ICONS = {
history: History, history: History,
link: Link, link: Link,
list: List, list: List,
locate: Locate,
mapPin: MapPin,
megaphone: Megaphone, megaphone: Megaphone,
logOut: LogOut, logOut: LogOut,
network: Network, network: Network,