fix: review findings from full-diff audit (7 fixed)
Some checks failed
Architecture Governance / architecture (push) Has been cancelled

- popularSearches sent translated display text as the actual search
  query instead of the canonical term - useSuggestion() now prefers
  target.query.q when present.
- CartService.addItem() dedup guard resolved immediately instead of
  awaiting the real in-flight add; now tracks the pending Promise per
  itemID so concurrent callers await the actual result.
- addItem()'s Promise never rejected on failure (resolve() in both
  next/error branches) - now rejects on error; buyNow() catches and
  shows an error toast instead of navigating on a failed add.
- Quick View had no stale-response guard - a slower earlier request
  could overwrite a faster later one. Added a request-generation
  counter.
- cart autoSubmitPurchase() set paymentStatus to null synchronously
  right after firing the async submit call, blanking the success
  screen while the request was still in flight. Removed the
  redundant/harmful line.
- Order terminal-status guard (cancelled/refunded can't be reopened)
  lived only in the page component. Moved enforcement into the
  gateway (single write path) via a shared TERMINAL_ORDER_STATUSES
  const, so no future caller can bypass it.
- TranslatePipe's per-instance memoization cache had no eviction,
  so bindings with volatile params (pagination counts) grew it
  unbounded for the component's lifetime. Capped at 50 entries.

Not changed: the dark-mode color override was flagged as clobbering
admin branding, but it's the exact palette explicitly requested this
session for the global dark default - not a bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-13 17:20:00 +04:00
parent 357d346787
commit d8c078ad5a
10 changed files with 73 additions and 26 deletions

View File

@@ -14,7 +14,7 @@ export class CartService {
private readonly STORAGE_KEY = 'marketplace_cart';
private cartItems = signal<CartItem[]>([]);
private isTelegram = typeof window !== 'undefined' && !!window.Telegram?.WebApp;
private addingItems = new Set<number>();
private addingItems = new Map<number, Promise<void>>();
private initialized = false;
items = this.cartItems.asReadonly();
@@ -211,8 +211,11 @@ export class CartService {
* synchronously.
*/
addItem(itemID: number, quantity: number = 1, variant?: CartVariant): Promise<void> {
// Prevent duplicate API calls for same item
if (this.addingItems.has(itemID)) return Promise.resolve();
// A concurrent call for the same not-yet-in-cart item awaits the same
// in-flight promise instead of resolving immediately - otherwise a
// rapid double-click could see the item "added" before it actually is.
const pending = this.addingItems.get(itemID);
if (pending) return pending;
const currentItems = this.cartItems();
const existingItem = currentItems.find(i => this.isSameCartLine(i, itemID, variant));
@@ -224,9 +227,8 @@ export class CartService {
}
// Get item details from API and add to cart
this.addingItems.add(itemID);
return import('./api.service').then(({ ApiService }) =>
new Promise<void>((resolve) => {
const promise = import('./api.service').then(({ ApiService }) =>
new Promise<void>((resolve, reject) => {
this.injector.get(ApiService).getItem(itemID).subscribe({
next: (item) => {
const cartItem = this.normalizeCartItem({
@@ -244,14 +246,18 @@ export class CartService {
error: (err) => {
console.error('Error adding to cart:', err);
this.addingItems.delete(itemID);
resolve();
reject(err);
}
});
})
).catch((err) => {
console.error('Error loading API service:', err);
this.addingItems.delete(itemID);
throw err;
});
this.addingItems.set(itemID, promise);
return promise;
}
updateQuantity(itemID: number, quantity: number, variant?: CartVariant): void {