This commit is contained in:
sdarbinyan
2026-03-24 02:25:50 +04:00
parent 97214c3a90
commit 650bf137f2
18 changed files with 1036 additions and 164 deletions

View File

@@ -182,37 +182,45 @@ export class CartComponent implements OnDestroy {
}
createPayment(): void {
const telegramUsername = this.getTelegramUsername();
const userId = this.getUserId();
const orderId = this.generateOrderId();
const sessionId = this.authService.session()?.sessionId || '';
if (!sessionId) {
this.paymentStatus.set('timeout');
return;
}
const paymentData = {
amount: this.totalPrice(),
currency: this.langService.currentCurrency(),
siteuserID: userId,
siteorderID: orderId,
redirectUrl: '',
telegramUsername: telegramUsername,
items: this.items().map((item: CartItem) => ({
itemID: item.itemID,
price: item.discount > 0
? item.price * (1 - item.discount / 100)
: item.price,
name: item.name,
quantity: item.quantity
}))
};
// First sync cart items to server via websession, then create QR
const cartItems = this.items().map((item: CartItem) => ({
itemID: item.itemID,
quantity: item.quantity,
colour: item.colour || '',
size: item.size || '',
price: item.discount > 0
? item.price * (1 - item.discount / 100)
: item.price,
}));
this.apiService.createPayment(paymentData).subscribe({
next: (response) => {
this.paymentId.set(response.qrId);
this.qrCodeUrl.set(response.qrUrl);
this.paymentUrl.set(response.payload);
this.paymentStatus.set('waiting');
this.startPolling();
this.apiService.addToCart(sessionId, cartItems).subscribe({
next: () => {
this.apiService.createPayment(sessionId).subscribe({
next: (response) => {
this.paymentId.set(response.qrId);
this.qrCodeUrl.set(response.qrUrl);
this.paymentUrl.set(response.Payload);
this.paymentStatus.set('waiting');
this.startPolling();
},
error: (err) => {
console.error('Error creating payment:', err);
this.paymentStatus.set('timeout');
if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => {
this.closePaymentPopup();
}, PAYMENT_ERROR_CLOSE_MS);
}
});
},
error: (err) => {
console.error('Error creating payment:', err);
console.error('Error syncing cart:', err);
this.paymentStatus.set('timeout');
if (this.closeTimeout) clearTimeout(this.closeTimeout);
this.closeTimeout = setTimeout(() => {
@@ -228,7 +236,8 @@ export class CartComponent implements OnDestroy {
.pipe(
take(this.maxChecks), // maximum 36 checks (3 minutes)
switchMap(() => {
return this.apiService.checkPaymentStatus(this.paymentId());
const sessionId = this.authService.session()?.sessionId || '';
return this.apiService.checkPaymentStatus(sessionId, this.paymentId());
})
)
.subscribe({
@@ -283,27 +292,6 @@ export class CartComponent implements OnDestroy {
}
}
private getTelegramUsername(): string {
if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user) {
const user = window.Telegram.WebApp.initDataUnsafe.user;
return user.username || 'nontelegram';
}
return 'nontelegram';
}
private getUserId(): string {
if (typeof window !== 'undefined' && window.Telegram?.WebApp?.initDataUnsafe?.user) {
return window.Telegram.WebApp.initDataUnsafe.user.id.toString();
}
return `web_${Date.now()}`;
}
private generateOrderId(): string {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8);
return `order_${timestamp}_${random}`;
}
submitEmail(): void {
// Mark both fields as touched
this.emailTouched.set(true);

View File

@@ -48,13 +48,13 @@
<a [routerLink]="['/category', cat.categoryID] | langRoute" class="category-card">
<div class="category-image">
@if (cat.icon) {
<img [src]="cat.icon" [alt]="cat.name" loading="lazy" decoding="async" />
<img [src]="cat.icon" [alt]="categoryName(cat)" loading="lazy" decoding="async" />
} @else {
<div class="category-fallback">{{ cat.name.charAt(0) }}</div>
<div class="category-fallback">{{ categoryName(cat).charAt(0) }}</div>
}
</div>
<div class="category-info">
<h3 class="category-name">{{ cat.name }}</h3>
<h3 class="category-name">{{ categoryName(cat) }}</h3>
</div>
</a>
}

View File

@@ -7,7 +7,7 @@ import { Subscription } from 'rxjs';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
import { TranslateService } from '../../i18n/translate.service';
import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField } from '../../utils/item.utils';
import { getDiscountedPrice, getMainImage, trackByItemId, getBadgeClass, getTranslatedField, getTranslatedCategoryName } from '../../utils/item.utils';
@Component({
selector: 'app-subcategories',
@@ -59,7 +59,7 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
next: (cats) => {
this.categories.set(cats);
const parent = cats.find(c => c.categoryID === parentID);
this.parentName.set(parent ? parent.name : this.i18n.t('home.categoriesTitle'));
this.parentName.set(parent ? getTranslatedCategoryName(parent, this.langService.currentLanguage()) : this.i18n.t('home.categoriesTitle'));
// Check for nested subcategories from API response (backOffice format)
const nested = parent?.subcategories || [];
@@ -135,4 +135,6 @@ export class SubcategoriesComponent implements OnInit, OnDestroy {
readonly getBadgeClass = getBadgeClass;
itemName(item: Item): string { return getTranslatedField(item, 'name', this.langService.currentLanguage()); }
categoryName(cat: Category): string { return getTranslatedCategoryName(cat, this.langService.currentLanguage()); }
}

View File

@@ -66,15 +66,15 @@
<a [routerLink]="['/category', category.categoryID] | langRoute" class="novo-category-card">
<div class="novo-category-image">
@if (category.icon) {
<img [src]="category.icon" [alt]="category.name" loading="lazy" />
<img [src]="category.icon" [alt]="categoryName(category)" loading="lazy" />
} @else {
<div class="novo-category-placeholder">
<span>{{ category.name.charAt(0) }}</span>
<span>{{ categoryName(category).charAt(0) }}</span>
</div>
}
</div>
<div class="novo-category-info">
<h3>{{ category.name }}</h3>
<h3>{{ categoryName(category) }}</h3>
<span class="novo-category-arrow"></span>
</div>
</a>
@@ -154,15 +154,15 @@
[class.dexar-category-card--wide]="isWideCategory(category.categoryID)">
<div class="dexar-category-image">
@if (isWideCategory(category.categoryID) && category.wideBanner) {
<img [src]="category.wideBanner" [alt]="category.name" loading="lazy" decoding="async" />
<img [src]="category.wideBanner" [alt]="categoryName(category)" loading="lazy" decoding="async" />
} @else if (category.icon) {
<img [src]="category.icon" [alt]="category.name" loading="lazy" decoding="async" />
<img [src]="category.icon" [alt]="categoryName(category)" loading="lazy" decoding="async" />
} @else {
<div class="dexar-category-fallback">{{ category.name.charAt(0) }}</div>
<div class="dexar-category-fallback">{{ categoryName(category).charAt(0) }}</div>
}
</div>
<div class="dexar-category-info">
<h3 class="dexar-category-name">{{ category.name }}</h3>
<h3 class="dexar-category-name">{{ categoryName(category) }}</h3>
<p class="dexar-category-count">{{ 'home.itemsCount' | translate:{ count: getItemCount(category.categoryID) } }}</p>
</div>
</a>

View File

@@ -3,6 +3,7 @@ import { Router, RouterLink } from '@angular/router';
import { ApiService, LanguageService } from '../../services';
import { Category } from '../../models';
import { environment } from '../../../environments/environment';
import { getTranslatedCategoryName } from '../../utils/item.utils';
import { ItemsCarouselComponent } from '../../components/items-carousel/items-carousel.component';
import { LangRoutePipe } from '../../pipes/lang-route.pipe';
import { TranslatePipe } from '../../i18n/translate.pipe';
@@ -123,6 +124,10 @@ export class HomeComponent implements OnInit, OnDestroy {
this.router.navigate([`/${lang}/search`]);
}
categoryName(cat: Category): string {
return getTranslatedCategoryName(cat, this.langService.currentLanguage());
}
scrollToCatalog(): void {
const target = document.getElementById('catalog');
if (!target) return;

View File

@@ -3,6 +3,7 @@ import { DecimalPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { ApiService, CartService, TelegramService, LanguageService, SeoService } from '../../services';
import { AuthService } from '../../services/auth.service';
import { Item, DescriptionField } from '../../models';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
@@ -42,6 +43,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
private seoService = inject(SeoService);
private i18n = inject(TranslateService);
private authService = inject(AuthService);
constructor(
private route: ActivatedRoute,
@@ -207,8 +209,7 @@ export class ItemDetailComponent implements OnInit, OnDestroy {
itemID: currentItem.itemID,
rating: this.newReview.rating,
comment: this.newReview.comment.trim(),
username: this.newReview.anonymous ? null : this.getUserDisplayName(),
userId: this.telegramService.getUserId(),
sessionID: this.authService.session()?.sessionId || '',
timestamp: new Date().toISOString()
};