mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
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:
@@ -1,6 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { brandingApi } from '../api/branding';
|
||||
import { setYandexCid } from '../utils/yandexCid';
|
||||
|
||||
const YM_SCRIPT_ID = 'ym-counter-script';
|
||||
const GTAG_LOADER_ID = 'gtag-loader-script';
|
||||
@@ -11,6 +12,11 @@ function removeElement(id: string) {
|
||||
}
|
||||
|
||||
function injectYandexMetrika(counterId: string) {
|
||||
try {
|
||||
localStorage.setItem('ym_counter_id', counterId);
|
||||
} catch {
|
||||
/* sandboxed / private */
|
||||
}
|
||||
if (document.getElementById(YM_SCRIPT_ID)) return;
|
||||
|
||||
const script = document.createElement('script');
|
||||
@@ -70,6 +76,8 @@ export function useAnalyticsCounters() {
|
||||
// Yandex Metrika
|
||||
if (data.yandex_metrika_id) {
|
||||
injectYandexMetrika(data.yandex_metrika_id);
|
||||
cacheYandexCid(data.yandex_metrika_id);
|
||||
syncYandexCid(data.yandex_metrika_id);
|
||||
} else {
|
||||
removeElement(YM_SCRIPT_ID);
|
||||
}
|
||||
@@ -83,3 +91,87 @@ export function useAnalyticsCounters() {
|
||||
}
|
||||
}, [data]);
|
||||
}
|
||||
|
||||
function cacheYandexCid(counterId: string) {
|
||||
const w = window as unknown as Record<string, unknown>;
|
||||
const ym = w.ym as ((...args: unknown[]) => void) | undefined;
|
||||
if (typeof ym !== 'function') return;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
(w.ym as (...args: unknown[]) => void)(Number(counterId), 'getClientID', (cid: string) => {
|
||||
if (cid) setYandexCid(cid);
|
||||
});
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function syncYandexCid(counterId: string) {
|
||||
const SENT_KEY = 'ym_cid_sent';
|
||||
try {
|
||||
if (localStorage.getItem(SENT_KEY)) return;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
const w = window as unknown as Record<string, unknown>;
|
||||
const ym = w.ym as ((...args: unknown[]) => void) | undefined;
|
||||
if (typeof ym !== 'function') return;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
(w.ym as (...args: unknown[]) => void)(Number(counterId), 'getClientID', (cid: string) => {
|
||||
if (!cid) return;
|
||||
setYandexCid(cid);
|
||||
// Only POST when the user is authenticated. Guest sessions cache the
|
||||
// CID locally; it gets synced after login by the next mount of this
|
||||
// hook on the cabinet shell.
|
||||
let token: string | null = null;
|
||||
try {
|
||||
token = localStorage.getItem('access_token');
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
if (!token) return;
|
||||
// Route through brandingApi (apiClient) so baseURL, auth refresh, and
|
||||
// error handling all flow through the same interceptors as every other
|
||||
// cabinet API call.
|
||||
import('../api/branding').then(({ brandingApi: api }) => {
|
||||
api
|
||||
.storeYandexCid(cid)
|
||||
.then(() => {
|
||||
try {
|
||||
localStorage.setItem(SENT_KEY, '1');
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
/* swallow -- non-critical, will retry on next login */
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
export function fireAnalyticsEvent(goalName: string, params?: Record<string, unknown>) {
|
||||
const w = window as unknown as Record<string, unknown>;
|
||||
const ym = w.ym as ((...args: unknown[]) => void) | undefined;
|
||||
if (typeof ym === 'function') {
|
||||
try {
|
||||
const counterId = localStorage.getItem('ym_counter_id');
|
||||
if (counterId && /^\d{1,15}$/.test(counterId)) {
|
||||
ym(Number(counterId), 'reachGoal', goalName, params);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export the canonical CID accessor from utils for back-compat with
|
||||
// existing imports inside this hook file. New code should import from
|
||||
// '../utils/yandexCid' directly to avoid pulling React into api/.
|
||||
export { getYandexCid } from '../utils/yandexCid';
|
||||
|
||||
Reference in New Issue
Block a user