feat: Yandex Metrika CID tracking, offline conversions UI, sticky pay button

- Pass yandex_cid to all auth endpoints (telegram, email, OIDC, OAuth)
- Add OfflineConvGoal interface and offline_conv_* fields to AnalyticsCounters
- Add storeYandexCid API method for server-side CID persistence
- Add analytics fields to LandingConfig (view/click goals, sticky_pay_button)
- Add yandex_cid/referrer/subid to PurchaseRequest
- Add offline conversions UI block in admin AnalyticsTab
- Add cacheYandexCid, syncYandexCid, fireAnalyticsEvent to analytics hook
- Create yandexCid.ts utility (localStorage get/set helpers)
- Add sticky pay button with portal on mobile in QuickPurchase
- Fire view/click analytics goals on landing pages
- Persist contact value and referrer/subid in session/localStorage
- Add i18n keys for offlineConv and apiKey in all 4 locales
This commit is contained in:
Fringg
2026-04-29 08:59:19 +03:00
parent 6d3010b621
commit 80059681da
11 changed files with 404 additions and 34 deletions

View File

@@ -1,4 +1,5 @@
import apiClient from './client';
import { getYandexCid } from '../utils/yandexCid';
import type {
AuthResponse,
LinkCallbackResponse,
@@ -22,6 +23,7 @@ export const authApi = {
init_data: initData,
campaign_slug: campaignSlug || undefined,
referral_code: referralCode || undefined,
yandex_cid: getYandexCid() || undefined,
});
return response.data;
},
@@ -43,6 +45,7 @@ export const authApi = {
...data,
campaign_slug: campaignSlug || undefined,
referral_code: referralCode || undefined,
yandex_cid: getYandexCid() || undefined,
});
return response.data;
},
@@ -56,6 +59,7 @@ export const authApi = {
id_token: idToken,
campaign_slug: campaignSlug || undefined,
referral_code: referralCode || undefined,
yandex_cid: getYandexCid() || undefined,
});
return response.data;
},
@@ -71,6 +75,7 @@ export const authApi = {
password,
campaign_slug: campaignSlug || undefined,
referral_code: referralCode || undefined,
yandex_cid: getYandexCid() || undefined,
});
return response.data;
},
@@ -87,6 +92,7 @@ export const authApi = {
const response = await apiClient.post('/cabinet/auth/email/register', {
email,
password,
yandex_cid: getYandexCid() || undefined,
});
return response.data;
},
@@ -101,7 +107,7 @@ export const authApi = {
}): Promise<RegisterResponse> => {
const response = await apiClient.post<RegisterResponse>(
'/cabinet/auth/email/register/standalone',
data,
{ ...data, yandex_cid: getYandexCid() || undefined },
);
return response.data;
},
@@ -198,6 +204,7 @@ export const authApi = {
device_id: deviceId || undefined,
campaign_slug: campaignSlug || undefined,
referral_code: referralCode || undefined,
yandex_cid: getYandexCid() || undefined,
},
);
return response.data;

View File

@@ -38,10 +38,20 @@ export interface TelegramWidgetConfig {
oidc_client_id: string;
}
export interface OfflineConvGoal {
name: string;
event_id: string;
dedup: string;
}
export interface AnalyticsCounters {
yandex_metrika_id: string;
google_ads_id: string;
google_ads_label: string;
offline_conv_enabled?: boolean;
offline_conv_counter_id?: string;
offline_conv_measurement_secret_masked?: string;
offline_conv_goals?: OfflineConvGoal[];
}
const BRANDING_CACHE_KEY = 'cabinet_branding';
@@ -274,7 +284,14 @@ export const brandingApi = {
const response = await apiClient.get<AnalyticsCounters>('/cabinet/branding/analytics');
return response.data;
} catch {
return { yandex_metrika_id: '', google_ads_id: '', google_ads_label: '' };
return {
yandex_metrika_id: '',
google_ads_id: '',
google_ads_label: '',
offline_conv_enabled: false,
offline_conv_counter_id: '',
offline_conv_goals: [],
};
}
},
@@ -303,4 +320,9 @@ export const brandingApi = {
const response = await apiClient.patch<AnalyticsCounters>('/cabinet/branding/analytics', data);
return response.data;
},
// Store Yandex Metrika ClientID for the authenticated cabinet user
storeYandexCid: async (cid: string): Promise<void> => {
await apiClient.post('/cabinet/branding/analytics/yandex-cid', { cid });
},
};

View File

@@ -89,6 +89,12 @@ export interface LandingConfig {
meta_description: string | null;
discount: LandingDiscountInfo | null;
background_config: AnimationConfig | null;
// Per-landing analytics goals + sticky pay button (bot PR #2852 backend fields)
analytics_view_enabled?: boolean;
analytics_view_goal?: string | null;
analytics_click_enabled?: boolean;
analytics_click_goal?: string | null;
sticky_pay_button?: boolean;
}
export interface PurchaseRequest {
@@ -102,6 +108,10 @@ export interface PurchaseRequest {
gift_recipient_value?: string;
gift_message?: string;
language?: string;
// Yandex offline-conversions linkage (bot PR #2851 backend fields)
yandex_cid?: string;
referrer?: string;
subid?: string;
}
export interface PurchaseResponse {