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

@@ -665,6 +665,33 @@ export default function PurchaseSuccess() {
}, [token, queryClient]);
const isSuccess = purchaseStatus?.status === 'delivered';
// Fire analytics goal on successful delivery (once per purchase).
// Idempotency keyed by token so a page refresh doesn't double-count.
useEffect(() => {
if (!isSuccess || !token) return;
const FIRED_KEY = `ym_buy_success_${token}`;
try {
if (localStorage.getItem(FIRED_KEY)) return;
} catch {
/* ignore */
}
try {
const counterId = localStorage.getItem('ym_counter_id');
const w = window as unknown as Record<string, unknown>;
if (counterId && typeof w.ym === 'function') {
(w.ym as (...args: unknown[]) => void)(Number(counterId), 'reachGoal', 'buy_success');
try {
localStorage.setItem(FIRED_KEY, '1');
} catch {
/* ignore */
}
}
} catch {
/* analytics error */
}
}, [isSuccess, token]);
const isPendingActivation = purchaseStatus?.status === 'pending_activation';
const isFailed = purchaseStatus?.status === 'failed' || purchaseStatus?.status === 'expired';