- @if (isWideCategory(category.categoryID) && category.wideBanner) {
+ @if (isWideCategory(category.categoryID) && category.wideBanner && category.wideBanner !== true) {
![]()
} @else if (category.icon) {
![]()
diff --git a/src/app/pages/home/home.component.scss b/src/app/pages/home/home.component.scss
index 2c84d3b..30e555d 100644
--- a/src/app/pages/home/home.component.scss
+++ b/src/app/pages/home/home.component.scss
@@ -1,329 +1,25 @@
-.home-container {
- max-width: 1200px;
- margin: 0 auto;
- padding: 20px;
- animation: fadeIn 0.5s ease-in;
-}
-
+// ========== SHARED ANIMATIONS ==========
@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
}
-.hero {
- text-align: center;
- padding: 80px 20px;
- background: var(--gradient-hero);
- color: white;
- border-radius: var(--radius-xl);
- margin-bottom: 50px;
- position: relative;
- overflow: hidden;
- box-shadow: var(--shadow-lg);
-
- &.hero-compact {
- padding: 35px 20px;
- margin-bottom: 25px;
-
- h1 {
- font-size: 2.2rem;
- margin-bottom: 8px;
- }
-
- p {
- font-size: 1.1rem;
- }
- }
-
- &::before {
- content: '';
- position: absolute;
- top: -50%;
- left: -50%;
- width: 200%;
- height: 200%;
- background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
- animation: pulse 4s ease-in-out infinite;
- }
-
- @keyframes pulse {
- 0%, 100% {
- transform: scale(1);
- opacity: 0.5;
- }
- 50% {
- transform: scale(1.1);
- opacity: 0.8;
- }
- }
-
- h1 {
- font-size: 3.5rem;
- margin: 0 0 15px 0;
- font-weight: 700;
- position: relative;
- z-index: 1;
- text-shadow: 0 2px 10px rgba(0,0,0,0.2);
- animation: slideDown 0.8s ease-out;
- }
-
- @keyframes slideDown {
- from {
- opacity: 0;
- transform: translateY(-30px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
- }
-
- p {
- font-size: 1.4rem;
- margin: 0;
- opacity: 0.95;
- position: relative;
- z-index: 1;
- animation: slideUp 0.8s ease-out 0.2s both;
- }
-
- @keyframes slideUp {
- from {
- opacity: 0;
- transform: translateY(20px);
- }
- to {
- opacity: 0.95;
- transform: translateY(0);
- }
- }
+@keyframes fadeInUp {
+ from { opacity: 0; transform: translateY(30px); }
+ to { opacity: 1; transform: translateY(0); }
}
-.loading,
-.error {
- text-align: center;
- padding: 60px 20px;
-}
-
-.spinner {
- width: 50px;
- height: 50px;
- border: 4px solid #f3f3f3;
- border-top: 4px solid var(--primary-color);
- border-radius: 50%;
- animation: spin 1s linear infinite;
- margin: 0 auto 20px;
+@keyframes slideDown {
+ from { opacity: 0; transform: translateY(-30px); }
+ to { opacity: 1; transform: translateY(0); }
}
@keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
}
-.error {
- button {
- margin-top: 20px;
- padding: 10px 24px;
- background: var(--primary-color);
- color: white;
- border: none;
- border-radius: 6px;
- cursor: pointer;
- font-size: 1rem;
-
- &:hover {
- background: var(--primary-hover);
- }
- }
-}
-
-.categories {
- h2 {
- font-size: 2rem;
- margin-bottom: 30px;
- color: #333;
- }
-}
-
-.empty-categories {
- text-align: center;
- padding: 60px 20px;
- background: white;
- border-radius: 12px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-
- .empty-icon {
- font-size: 4rem;
- margin-bottom: 20px;
- opacity: 0.5;
- }
-
- h3 {
- font-size: 1.5rem;
- color: #333;
- margin: 0 0 10px 0;
- }
-
- p {
- color: #666;
- font-size: 1rem;
- margin: 0;
- }
-}
-
-.categories-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
- gap: 30px;
- animation: fadeIn 0.6s ease-in 0.3s both;
-}
-
-.category-card {
- background: white;
- border-radius: var(--radius-lg);
- box-shadow: var(--shadow-md);
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- height: 100%;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- position: relative;
-
- &::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: var(--gradient-primary);
- opacity: 0;
- transition: opacity 0.3s ease;
- z-index: 1;
- }
-
- &:hover {
- transform: translateY(-8px) scale(1.02);
- box-shadow: var(--shadow-lg);
-
- &::before {
- opacity: 0.1;
- }
-
- .category-media img {
- transform: scale(1.1);
- }
-
- h3 {
- color: var(--primary-color);
- }
- }
-}
-
-.category-link {
- display: flex;
- flex-direction: column;
- flex: 1;
- text-decoration: none;
- color: inherit;
- position: relative;
- min-height: 220px;
- z-index: 2;
-
- .category-media {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- background: linear-gradient(135deg, #f6f7fb 0%, #e9ecf5 100%);
- }
-
- .category-media img {
- width: 100%;
- height: 100%;
- object-fit: contain;
- background: white;
- padding: 15px;
- transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), filter 0.5s ease;
- filter: drop-shadow(0 4px 8px rgba(0,0,0,0.08));
- border-radius: 8px;
- }
-
- &:hover .category-media img {
- transform: scale(1.05);
- filter: drop-shadow(0 16px 32px rgba(0,0,0,0.18)) saturate(1.1);
- }
-
- .category-fallback {
- font-size: 1.5rem;
- font-weight: 700;
- color: var(--primary-color);
- background: var(--gradient-primary);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- text-align: center;
- padding: 20px;
- }
-
- h3 {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- margin: 0;
- padding: 20px;
- font-size: 1.3rem;
- font-weight: 600;
- color: #333;
- background: linear-gradient(to top, rgba(255,255,255,0.98) 0%, rgba(255,255,255,0.95) 70%, transparent 100%);
- z-index: 3;
- transition: color 0.3s ease;
- }
-}
-
-@media (max-width: 768px) {
- .home-container {
- padding: 15px;
- }
-
- .hero {
- padding: 50px 20px;
- border-radius: 15px;
-
- h1 {
- font-size: 2.5rem;
- }
-
- p {
- font-size: 1.1rem;
- }
- }
-
- .categories-grid {
- grid-template-columns: 1fr;
- gap: 20px;
- }
-
- .category-card {
- &:hover {
- transform: translateY(-4px) scale(1);
- }
- }
-}
-
-// ========== novo HOME PAGE STYLES ==========
+// ========== NOVO HOME PAGE STYLES ==========
.novo-home {
min-height: calc(100vh - 200px);
animation: fadeIn 0.6s ease;
@@ -619,26 +315,6 @@
}
}
-@keyframes slideDown {
- from {
- opacity: 0;
- transform: translateY(-30px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-@keyframes spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
@media (max-width: 968px) {
.novo-hero {
padding: 4rem 2rem;
@@ -730,11 +406,10 @@
width: 100%;
height: 100%;
background: linear-gradient(
- to right,
- rgba(255, 255, 255, 0.9) 0%,
- rgba(255, 255, 255, 0.7) 50%,
- rgba(255, 255, 255, 0.3) 100%
- );
+ to right,
+ rgba(255, 255, 255, 0.1) 10%,
+ rgba(255, 255, 255, 0.1) 10%,
+ rgba(255, 255, 255, 0.1) 15%);
display: flex;
align-items: center;
padding: 0 56px;
@@ -748,42 +423,28 @@
animation: fadeInUp 0.8s ease-out;
}
-@keyframes fadeInUp {
- from {
- opacity: 0;
- transform: translateY(30px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
.dexar-hero-title {
- font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 500;
font-size: 42px;
- color: #1e3c38;
+ color: var(--text-primary);
line-height: 1.2;
margin: 0;
animation: fadeInUp 0.8s ease-out 0.1s both;
}
.dexar-hero-subtitle {
- font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 500;
font-size: 24px;
- color: #1e3c38;
+ color: var(--text-primary);
line-height: 1.3;
margin: 0;
animation: fadeInUp 0.8s ease-out 0.2s both;
}
.dexar-hero-tagline {
- font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 500;
font-size: 24px;
- color: #1e3c38;
+ color: var(--text-primary);
line-height: 1.3;
margin: 0;
animation: fadeInUp 0.8s ease-out 0.3s both;
@@ -802,10 +463,9 @@
justify-content: center;
width: 280px;
height: 48px;
- background: linear-gradient(360deg, #497671 0%, #a7ceca 100%);
- border: 1px solid #d3dad9;
- border-radius: 13px;
- font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ background: var(--gradient-primary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
font-weight: 500;
font-size: 20px;
color: #ffffff;
@@ -833,13 +493,12 @@
gap: 9px;
width: 220px;
height: 48px;
- background: #f5f5f5;
- border: 1px solid #d3dad9;
- border-radius: 13px;
- font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg);
font-weight: 500;
font-size: 20px;
- color: #1e3c38;
+ color: var(--text-primary);
letter-spacing: 1.08px;
cursor: pointer;
transition: all 0.3s ease;
@@ -880,7 +539,7 @@
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
- border-top: 4px solid #497671;
+ border-top: 4px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
@@ -890,17 +549,17 @@
button {
margin-top: 20px;
padding: 12px 28px;
- background: #497671;
+ background: var(--primary-color);
color: white;
border: none;
- border-radius: 13px;
+ border-radius: var(--radius-lg);
cursor: pointer;
font-size: 1.1rem;
font-weight: 500;
transition: all 0.3s ease;
&:hover {
- background: #3d635f;
+ background: var(--primary-hover);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(73, 118, 113, 0.3);
}
@@ -915,11 +574,10 @@
}
.dexar-categories-title {
- font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 2.5rem;
font-weight: 600;
margin-bottom: 40px;
- color: #1e3c38;
+ color: var(--text-primary);
}
.dexar-empty-categories {
@@ -937,13 +595,13 @@
h3 {
font-size: 1.8rem;
- color: #1e3c38;
+ color: var(--text-primary);
margin: 0 0 12px 0;
font-weight: 600;
}
p {
- color: #667a77;
+ color: var(--text-secondary);
font-size: 1.1rem;
margin: 0;
}
@@ -980,14 +638,14 @@
.dexar-category-image {
width: 100%;
aspect-ratio: 4 / 3;
- border: 1px solid #d3dad9;
- border-radius: 13px 13px 0 0;
+ border: 1px solid var(--border-color);
+ border-radius: var(--radius-lg) var(--radius-lg) 0 0;
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
- background: #f5f5f5;
+ background: var(--bg-secondary);
position: relative;
img {
@@ -1010,15 +668,15 @@
justify-content: center;
font-size: 5rem;
font-weight: 700;
- color: #497671;
- background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
+ color: var(--primary-color);
+ background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
}
.dexar-category-info {
width: 100%;
- border: 1px solid #d3dad9;
+ border: 1px solid var(--border-color);
border-top: none;
- border-radius: 0 0 13px 13px;
+ border-radius: 0 0 var(--radius-lg) var(--radius-lg);
padding: 12px 16px;
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15);
background: #f5f3f9;
@@ -1030,10 +688,9 @@
}
.dexar-category-name {
- font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 600;
font-size: clamp(14px, 1.4vw, 18px);
- color: #1e3c38;
+ color: var(--text-primary);
margin: 0;
line-height: 1.3;
display: -webkit-box;
@@ -1046,10 +703,9 @@
}
.dexar-category-count {
- font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 600;
font-size: clamp(11px, 1vw, 13px);
- color: #697777;
+ color: var(--text-secondary);
margin: 0;
line-height: 1.2;
}
@@ -1114,16 +770,18 @@
font-size: 20px;
}
- .dexar-btn-primary {
- width: 240px;
+ .dexar-btn-primary,
+ .dexar-btn-secondary {
height: 44px;
font-size: 18px;
}
+
+ .dexar-btn-primary {
+ width: 240px;
+ }
.dexar-btn-secondary {
width: 200px;
- height: 44px;
- font-size: 18px;
}
.dexar-categories {
@@ -1146,8 +804,8 @@
padding: 0 20px;
background: linear-gradient(
to bottom,
- rgba(255, 255, 255, 0.95) 0%,
- rgba(255, 255, 255, 0.85) 100%
+ rgba(255, 255, 255, 0.45) 0%,
+ rgba(255, 255, 255, 0.15) 100%
);
}
diff --git a/src/app/pages/home/home.component.ts b/src/app/pages/home/home.component.ts
index d16165b..61b83a6 100644
--- a/src/app/pages/home/home.component.ts
+++ b/src/app/pages/home/home.component.ts
@@ -1,5 +1,4 @@
import { Component, OnInit, signal, computed, ChangeDetectionStrategy } from '@angular/core';
-import { CommonModule } from '@angular/common';
import { Router, RouterLink } from '@angular/router';
import { ApiService } from '../../services';
import { Category } from '../../models';
@@ -8,8 +7,7 @@ import { ItemsCarouselComponent } from '../../components/items-carousel/items-ca
@Component({
selector: 'app-home',
- standalone: true,
- imports: [CommonModule, RouterLink, ItemsCarouselComponent],
+ imports: [RouterLink, ItemsCarouselComponent],
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -24,7 +22,16 @@ export class HomeComponent implements OnInit {
// Memoized computed values for performance
topLevelCategories = computed(() => {
- return this.categories().filter(cat => cat.parentID === 0);
+ return this.categories()
+ .filter(cat => cat.parentID === 0)
+ .sort((a, b) => (a.priority ?? Infinity) - (b.priority ?? Infinity));
+ });
+
+ // Memoized item count lookup
+ private itemCountMap = computed(() => {
+ const map = new Map
();
+ this.categories().forEach(cat => map.set(cat.categoryID, cat.itemCount || 0));
+ return map;
});
// Cache subcategories by parent ID
@@ -64,12 +71,7 @@ export class HomeComponent implements OnInit {
}
getItemCount(categoryID: number): number {
- const cat = this.categories().find(c => c.categoryID === categoryID);
- return cat?.itemCount || 0;
- }
-
- getTopLevelCategories(): Category[] {
- return this.topLevelCategories();
+ return this.itemCountMap().get(categoryID) || 0;
}
getSubCategories(parentID: number): Category[] {
@@ -83,8 +85,14 @@ export class HomeComponent implements OnInit {
private detectWideImages(categories: Category[]): void {
const topLevel = categories.filter(c => c.parentID === 0);
topLevel.forEach(cat => {
- const src = cat.wideBanner || null;
- if (!src) return;
+ if (!cat.wideBanner) return;
+
+ // API may send wideBanner as a boolean flag instead of a URL
+ if (cat.wideBanner === true) {
+ this.wideCategories.update(set => { const next = new Set(set); next.add(cat.categoryID); return next; });
+ return;
+ }
+
const img = new Image();
img.onload = () => {
const ratio = img.naturalWidth / img.naturalHeight;
@@ -96,7 +104,7 @@ export class HomeComponent implements OnInit {
});
}
};
- img.src = src;
+ img.src = cat.wideBanner as string;
});
}
diff --git a/src/app/pages/info/about/about.component.ts b/src/app/pages/info/about/about.component.ts
index ec3e778..b0af9aa 100644
--- a/src/app/pages/info/about/about.component.ts
+++ b/src/app/pages/info/about/about.component.ts
@@ -1,11 +1,9 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-about',
- standalone: true,
- imports: [CommonModule],
+ imports: [],
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss']
})
diff --git a/src/app/pages/info/contacts/contacts.component.ts b/src/app/pages/info/contacts/contacts.component.ts
index 0029236..54013e7 100644
--- a/src/app/pages/info/contacts/contacts.component.ts
+++ b/src/app/pages/info/contacts/contacts.component.ts
@@ -1,11 +1,9 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-contacts',
- standalone: true,
- imports: [CommonModule],
+ imports: [],
templateUrl: './contacts.component.html',
styleUrls: ['./contacts.component.scss']
})
diff --git a/src/app/pages/info/delivery/delivery.component.ts b/src/app/pages/info/delivery/delivery.component.ts
index 2397bb3..d2e3bf3 100644
--- a/src/app/pages/info/delivery/delivery.component.ts
+++ b/src/app/pages/info/delivery/delivery.component.ts
@@ -1,11 +1,9 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-delivery',
- standalone: true,
- imports: [CommonModule],
+ imports: [],
templateUrl: './delivery.component.html',
styleUrls: ['./delivery.component.scss']
})
diff --git a/src/app/pages/info/faq/faq.component.ts b/src/app/pages/info/faq/faq.component.ts
index 5d19238..7a05eb5 100644
--- a/src/app/pages/info/faq/faq.component.ts
+++ b/src/app/pages/info/faq/faq.component.ts
@@ -1,11 +1,9 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-faq',
- standalone: true,
- imports: [CommonModule],
+ imports: [],
templateUrl: './faq.component.html',
styleUrls: ['./faq.component.scss']
})
diff --git a/src/app/pages/info/guarantee/guarantee.component.ts b/src/app/pages/info/guarantee/guarantee.component.ts
index 49ef85e..3ae2e97 100644
--- a/src/app/pages/info/guarantee/guarantee.component.ts
+++ b/src/app/pages/info/guarantee/guarantee.component.ts
@@ -1,11 +1,9 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-guarantee',
- standalone: true,
- imports: [CommonModule],
+ imports: [],
templateUrl: './guarantee.component.html',
styleUrls: ['./guarantee.component.scss']
})
diff --git a/src/app/pages/item-detail/item-detail.component.ts b/src/app/pages/item-detail/item-detail.component.ts
index 0b63cec..3ed8b25 100644
--- a/src/app/pages/item-detail/item-detail.component.ts
+++ b/src/app/pages/item-detail/item-detail.component.ts
@@ -1,5 +1,5 @@
-import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component, OnInit, OnDestroy, signal, ChangeDetectionStrategy } from '@angular/core';
+import { DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { ApiService, CartService, TelegramService } from '../../services';
@@ -10,8 +10,7 @@ import { environment } from '../../../environments/environment';
@Component({
selector: 'app-item-detail',
- standalone: true,
- imports: [CommonModule, RouterLink, FormsModule],
+ imports: [DecimalPipe, RouterLink, FormsModule],
templateUrl: './item-detail.component.html',
styleUrls: ['./item-detail.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -92,8 +91,8 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
getRatingStars(rating: number): string {
const fullStars = Math.floor(rating);
const hasHalfStar = rating % 1 >= 0.5;
- let stars = '⭐'.repeat(fullStars);
- if (hasHalfStar) stars += '⭐';
+ let stars = '?'.repeat(fullStars);
+ if (hasHalfStar) stars += '?';
return stars;
}
@@ -103,10 +102,10 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
const diffMs = now.getTime() - date.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
- if (diffDays === 0) return 'Сегодня';
- if (diffDays === 1) return 'Вчера';
- if (diffDays < 7) return `${diffDays} дн. назад`;
- if (diffDays < 30) return `${Math.floor(diffDays / 7)} нед. назад`;
+ if (diffDays === 0) return '�������';
+ if (diffDays === 1) return '�����';
+ if (diffDays < 7) return `${diffDays} ��. �����`;
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)} ���. �����`;
return date.toLocaleDateString('ru-RU', {
day: 'numeric',
@@ -121,7 +120,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
getUserDisplayName(): string | null {
if (!this.telegramService.isTelegramApp()) {
- return 'Пользователь';
+ return '������������';
}
return this.telegramService.getDisplayName();
}
@@ -150,12 +149,12 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
this.reviewSubmitStatus.set('success');
this.newReview = { rating: 0, comment: '', anonymous: false };
- // Скрыть сообщение через 3 секунды
+ // ������ ��������� ����� 3 �������
setTimeout(() => {
this.reviewSubmitStatus.set('idle');
}, 3000);
- // Перезагрузить данные товара через небольшую задержку
+ // ������������� ������ ������ ����� ��������� ��������
setTimeout(() => {
this.loadItem(currentItem.itemID);
}, 500);
@@ -164,7 +163,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
console.error('Error submitting review:', err);
this.reviewSubmitStatus.set('error');
- // Скрыть сообщение об ошибке через 5 секунд
+ // ������ ��������� �� ������ ����� 5 ������
setTimeout(() => {
this.reviewSubmitStatus.set('idle');
}, 5000);
diff --git a/src/app/pages/legal/company-details/company-details.component.ts b/src/app/pages/legal/company-details/company-details.component.ts
index 9d49859..ce23538 100644
--- a/src/app/pages/legal/company-details/company-details.component.ts
+++ b/src/app/pages/legal/company-details/company-details.component.ts
@@ -1,11 +1,9 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-company-details',
- standalone: true,
- imports: [CommonModule],
+ imports: [],
templateUrl: './company-details.component.html',
styleUrls: ['./company-details.component.scss']
})
diff --git a/src/app/pages/legal/payment-terms/payment-terms.component.ts b/src/app/pages/legal/payment-terms/payment-terms.component.ts
index fd12759..aa841e5 100644
--- a/src/app/pages/legal/payment-terms/payment-terms.component.ts
+++ b/src/app/pages/legal/payment-terms/payment-terms.component.ts
@@ -1,12 +1,10 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-payment-terms',
- standalone: true,
- imports: [CommonModule, RouterLink],
+ imports: [RouterLink],
templateUrl: './payment-terms.component.html',
styleUrls: ['./payment-terms.component.scss']
})
diff --git a/src/app/pages/legal/privacy-policy/privacy-policy.component.ts b/src/app/pages/legal/privacy-policy/privacy-policy.component.ts
index 3b421e0..f0841ac 100644
--- a/src/app/pages/legal/privacy-policy/privacy-policy.component.ts
+++ b/src/app/pages/legal/privacy-policy/privacy-policy.component.ts
@@ -1,11 +1,9 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-privacy-policy',
- standalone: true,
- imports: [CommonModule],
+ imports: [],
templateUrl: './privacy-policy.component.html',
styleUrls: ['./privacy-policy.component.scss']
})
diff --git a/src/app/pages/legal/public-offer/public-offer.component.ts b/src/app/pages/legal/public-offer/public-offer.component.ts
index 2fb838c..71cb01c 100644
--- a/src/app/pages/legal/public-offer/public-offer.component.ts
+++ b/src/app/pages/legal/public-offer/public-offer.component.ts
@@ -1,12 +1,10 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-public-offer',
- standalone: true,
- imports: [CommonModule, RouterLink],
+ imports: [RouterLink],
templateUrl: './public-offer.component.html',
styleUrls: ['./public-offer.component.scss']
})
diff --git a/src/app/pages/legal/return-policy/return-policy.component.ts b/src/app/pages/legal/return-policy/return-policy.component.ts
index 2be30c5..a8faa7e 100644
--- a/src/app/pages/legal/return-policy/return-policy.component.ts
+++ b/src/app/pages/legal/return-policy/return-policy.component.ts
@@ -1,12 +1,10 @@
-import { Component } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { environment } from '../../../../environments/environment';
@Component({
selector: 'app-return-policy',
- standalone: true,
- imports: [CommonModule],
+ imports: [],
templateUrl: './return-policy.component.html',
styleUrls: ['./return-policy.component.scss']
})
diff --git a/src/app/pages/search/search.component.ts b/src/app/pages/search/search.component.ts
index e32178d..570b61d 100644
--- a/src/app/pages/search/search.component.ts
+++ b/src/app/pages/search/search.component.ts
@@ -1,16 +1,16 @@
import { Component, signal, HostListener, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
-import { CommonModule } from '@angular/common';
+import { DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';
import { ApiService, CartService } from '../../services';
import { Item } from '../../models';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
+import { getDiscountedPrice, getMainImage, trackByItemId } from '../../utils/item.utils';
@Component({
selector: 'app-search',
- standalone: true,
- imports: [CommonModule, FormsModule, RouterLink],
+ imports: [DecimalPipe, FormsModule, RouterLink],
templateUrl: './search.component.html',
styleUrls: ['./search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
@@ -39,7 +39,9 @@ export class SearchComponent implements OnDestroy {
distinctUntilChanged()
)
.subscribe(query => {
- this.performSearch(query);
+ if (query.trim().length >= 3 || query.trim().length === 0) {
+ this.performSearch(query);
+ }
});
}
@@ -126,16 +128,7 @@ export class SearchComponent implements OnDestroy {
this.cartService.addItem(itemID);
}
- getDiscountedPrice(item: Item): number {
- return item.price * (1 - item.discount / 100);
- }
-
- getMainImage(item: Item): string {
- return item.photos?.[0]?.url || '';
- }
-
- // TrackBy function for performance optimization
- trackByItemId(index: number, item: Item): number {
- return item.itemID;
- }
+ readonly getDiscountedPrice = getDiscountedPrice;
+ readonly getMainImage = getMainImage;
+ readonly trackByItemId = trackByItemId;
}
diff --git a/src/app/services/cart.service.ts b/src/app/services/cart.service.ts
index 4915441..f430b64 100644
--- a/src/app/services/cart.service.ts
+++ b/src/app/services/cart.service.ts
@@ -1,13 +1,14 @@
import { Injectable, signal, computed, effect } from '@angular/core';
import { ApiService } from './api.service';
import { Item, CartItem } from '../models';
+import { environment } from '../../environments/environment';
import type { } from '../types/telegram.types';
@Injectable({
providedIn: 'root'
})
export class CartService {
- private readonly STORAGE_KEY = 'dexarmarket_cart';
+ private readonly STORAGE_KEY = `${environment.brandName.toLowerCase().replace(/\s+/g, '_')}_cart`;
private cartItems = signal([]);
private isTelegram = typeof window !== 'undefined' && !!window.Telegram?.WebApp;
@@ -60,29 +61,13 @@ export class CartService {
console.error('Error loading from Telegram CloudStorage:', err);
this.loadFromSessionStorage();
} else if (value) {
- try {
- const items = JSON.parse(value);
- if (Array.isArray(items)) {
- // Migrate old items without quantity field
- const migratedItems: CartItem[] = items.map(item => ({
- ...item,
- quantity: item.quantity || 1
- }));
- this.cartItems.set(migratedItems);
- } else {
- this.cartItems.set([]);
- }
- } catch (parseErr) {
- console.error('Error parsing cart data:', parseErr);
- this.loadFromSessionStorage();
- }
+ this.parseAndSetCart(value) || this.loadFromSessionStorage();
} else {
// No data in CloudStorage, try sessionStorage
this.loadFromSessionStorage();
}
});
} else {
- // Load from sessionStorage
this.loadFromSessionStorage();
}
}
@@ -90,27 +75,28 @@ export class CartService {
private loadFromSessionStorage(): void {
const stored = sessionStorage.getItem(this.STORAGE_KEY);
if (stored) {
- try {
- const items = JSON.parse(stored);
- if (Array.isArray(items)) {
- // Migrate old items without quantity field
- const migratedItems: CartItem[] = items.map(item => ({
- ...item,
- quantity: item.quantity || 1
- }));
- this.cartItems.set(migratedItems);
- } else {
- this.cartItems.set([]);
- }
- } catch (err) {
- console.error('Error parsing cart from sessionStorage:', err);
- this.cartItems.set([]);
- }
- } else {
- this.cartItems.set([]);
+ this.parseAndSetCart(stored);
}
}
+ /** Parse JSON cart data, migrate legacy items, and set the signal. Returns true on success. */
+ private parseAndSetCart(json: string): boolean {
+ try {
+ const items = JSON.parse(json);
+ if (Array.isArray(items)) {
+ this.cartItems.set(items.map(item => ({
+ ...item,
+ quantity: item.quantity || 1
+ })));
+ return true;
+ }
+ } catch (err) {
+ console.error('Error parsing cart data:', err);
+ }
+ this.cartItems.set([]);
+ return false;
+ }
+
addItem(itemID: number, quantity: number = 1): void {
const currentItems = this.cartItems();
const existingItem = currentItems.find(i => i.itemID === itemID);
diff --git a/src/app/utils/item.utils.ts b/src/app/utils/item.utils.ts
new file mode 100644
index 0000000..dcd7809
--- /dev/null
+++ b/src/app/utils/item.utils.ts
@@ -0,0 +1,13 @@
+import { Item } from '../models';
+
+export function getDiscountedPrice(item: Item): number {
+ return item.price * (1 - item.discount / 100);
+}
+
+export function getMainImage(item: Item): string {
+ return item.photos?.[0]?.url || '';
+}
+
+export function trackByItemId(index: number, item: Item): number {
+ return item.itemID;
+}