feat: Track A frontend - analytics event pipeline core + real call sites

core/analytics (AnalyticsEvent model, gateway/token/mock, AnalyticsService
wrapper) against docs/backend/TRACK-A-ANALYTICS-CONTRACT.md §1. isSynthetic
is derived from the build environment at the service layer, never
client-settable at a call site - matches the contract's §6 requirement
that synthetic traffic be inseparable-by-accident from production data
once a real backend exists.

Wired into real, live interaction points (additive only, no existing
logic touched): product_view + add_to_cart in
product-details-container.component.ts, checkout_started + payment_started
in pages/cart/cart.component.ts. This is the actual event-firing
infrastructure the plan calls "the single largest remaining backend
effort" (§3.1) - the frontend side (call sites) is real now; the mock
gateway just doesn't persist anywhere yet.

Not wired: search/category_view/seller_view/cart_view/payment_success/
payment_failed/order_created - follow-up call sites once this pattern is
reviewed, to avoid a much larger unreviewed diff in one push.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-18 00:05:46 +04:00
parent 34f79b0303
commit be167d110e
7 changed files with 77 additions and 0 deletions

View File

@@ -17,6 +17,7 @@ import { DEFAULT_PRODUCT_PAGE_CONFIG, DEFAULT_USER_EXPERIENCE_CONFIG, ProductPag
import { getStockStatus, getTranslatedField } from '../../../../utils/item.utils';
import { ProductShareService } from '../../user-experience/services/product-share.service';
import { SeoService } from '../../../../services/seo.service';
import { AnalyticsService } from '../../../../core/analytics/services/analytics.service';
import { ApiService } from '../../../../services/api.service';
import { LocalStorageService } from '../../../../core/storage/local-storage.service';
import { UserNotificationService } from '../../user-experience/services/user-notification.service';
@@ -78,6 +79,7 @@ export class ProductDetailsContainerComponent {
private readonly translate = inject(TranslateService);
private readonly shareService = inject(ProductShareService);
private readonly seoService = inject(SeoService);
private readonly analytics = inject(AnalyticsService);
private readonly apiService = inject(ApiService);
private readonly storage = inject(LocalStorageService);
private readonly notifications = inject(UserNotificationService);
@@ -276,6 +278,7 @@ export class ProductDetailsContainerComponent {
this.product.set(product);
this.seoService.setItemMeta(product);
this.analytics.track('product_view', { itemID: product.itemID });
if (this.userExperienceConfig().recentlyViewed.enabled) {
this.uxFacade.trackRecentlyViewed(product, this.userExperienceConfig().recentlyViewed.maxItems);
}
@@ -321,6 +324,7 @@ export class ProductDetailsContainerComponent {
addToCart(): Promise<void> {
const current = this.product();
if (!current) return Promise.resolve();
this.analytics.track('add_to_cart', { itemID: current.itemID, price: this.effectivePrice(), currency: this.effectiveCurrency() });
return this.cartService.addItem(current.itemID, 1, {
colour: this.selectedColour() ?? undefined,
size: this.selectedSize() ?? undefined,