feat: landing analytics goals, daily bar chart, referrer tracking, contact persistence

- Add per-landing analytics goals (view/click) with admin editor toggle
- Add sticky pay button option for mobile landing pages
- Add daily purchases bar chart (created vs paid) to landing stats
- Replace single purchase count with created/paid split in stats summary
- Add referrer tracking to purchases with hostname display in stats
- Add time display to purchase cards alongside date
- Pass user timezone to stats API for correct daily grouping
- Clamp referrer (500 chars) and subid (255 chars) to backend limits
- Persist contact value per-landing-slug in localStorage
- Fire buy_success analytics goal on successful delivery
- Export USER_TIMEZONE from format utils
- Add analytics/stats translations for fa.json and zh.json locales

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fringg
2026-04-29 09:17:52 +03:00
parent a50bd39df2
commit 020f4c95e2
11 changed files with 482 additions and 79 deletions

View File

@@ -89,12 +89,11 @@ 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;
analytics_view_enabled: boolean;
analytics_view_goal: string;
analytics_click_enabled: boolean;
analytics_click_goal: string;
sticky_pay_button: boolean;
}
export interface PurchaseRequest {
@@ -167,6 +166,8 @@ export interface LandingListItem {
gift_enabled: boolean;
tariff_count: number;
method_count: number;
analytics_view_enabled: boolean;
analytics_click_enabled: boolean;
purchase_stats: {
total: number;
pending: number;
@@ -205,6 +206,11 @@ export interface LandingDetail {
discount_ends_at: string | null;
discount_badge_text: LocaleDict | null;
background_config: AnimationConfig | null;
analytics_view_enabled: boolean;
analytics_view_goal: string;
analytics_click_enabled: boolean;
analytics_click_goal: string;
sticky_pay_button: boolean;
}
export interface LandingCreateRequest {
@@ -227,6 +233,11 @@ export interface LandingCreateRequest {
discount_ends_at?: string | null;
discount_badge_text?: LocaleDict | null;
background_config?: AnimationConfig | null;
analytics_view_enabled?: boolean;
analytics_view_goal?: string;
analytics_click_enabled?: boolean;
analytics_click_goal?: string;
sticky_pay_button?: boolean;
}
export type LandingUpdateRequest = Partial<LandingCreateRequest>;
@@ -272,6 +283,7 @@ export const landingApi = {
export interface LandingDailyStat {
date: string;
created: number;
purchases: number;
revenue_kopeks: number;
gifts: number;
@@ -321,6 +333,7 @@ export interface LandingPurchaseItem {
status: PurchaseItemStatus;
created_at: string;
paid_at: string | null;
referrer: string | null;
}
export interface LandingPurchaseListResponse {
@@ -364,7 +377,10 @@ export const adminLandingsApi = {
},
getStats: async (id: number): Promise<LandingStatsResponse> => {
const response = await apiClient.get(`/cabinet/admin/landings/${id}/stats`);
const { USER_TIMEZONE } = await import('../utils/format');
const response = await apiClient.get(`/cabinet/admin/landings/${id}/stats`, {
params: { tz: USER_TIMEZONE },
});
return response.data;
},