186 lines
6.3 KiB
TypeScript
186 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, createContext, useContext } from "react";
|
|
|
|
// ── TYPES & INTERFACES ────────────────────────────────────────────────────────
|
|
|
|
export interface UTMParams {
|
|
utm_source: string | null;
|
|
utm_medium: string | null;
|
|
utm_campaign: string | null;
|
|
utm_content: string | null;
|
|
utm_term: string | null;
|
|
referrer: string | null;
|
|
}
|
|
|
|
export interface IdentifyTraits {
|
|
userId: string | number;
|
|
email: string;
|
|
name?: string;
|
|
plan?: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
export interface VibnTrackerContextType {
|
|
getStoredAttribution: () => UTMParams;
|
|
clearAttribution: () => void;
|
|
identify: (traits: IdentifyTraits) => void;
|
|
track: (eventName: string, properties?: Record<string, any>) => void;
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
umami?: {
|
|
identify: (traits: Record<string, any>) => void;
|
|
track: (eventName: string, properties?: Record<string, any>) => void;
|
|
};
|
|
}
|
|
}
|
|
|
|
// ── UTILITY FUNCTIONS ─────────────────────────────────────────────────────────
|
|
|
|
const COOKIE_EXPIRY_DAYS = 30;
|
|
|
|
function setCookie(name: string, value: string, days: number) {
|
|
if (typeof document === "undefined") return;
|
|
const date = new Date();
|
|
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
|
const expires = "; expires=" + date.toUTCString();
|
|
document.cookie = name + "=" + (value || "") + expires + "; path=/; SameSite=Lax";
|
|
}
|
|
|
|
function getCookie(name: string): string | null {
|
|
if (typeof document === "undefined") return null;
|
|
const nameEQ = name + "=";
|
|
const ca = document.cookie.split(";");
|
|
for (let i = 0; i < ca.length; i++) {
|
|
let c = ca[i];
|
|
while (c.charAt(0) === " ") c = c.substring(1, c.length);
|
|
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function deleteCookie(name: string) {
|
|
if (typeof document === "undefined") return;
|
|
document.cookie = name + "=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;";
|
|
}
|
|
|
|
// ── CONTEXT PROVIDER ──────────────────────────────────────────────────────────
|
|
|
|
const VibnTrackerContext = createContext<VibnTrackerContextType | undefined>(undefined);
|
|
|
|
export function VibnTrackerProvider({
|
|
children,
|
|
umamiWebsiteId,
|
|
umamiScriptUrl = "https://analytics.vibnai.com/script.js",
|
|
}: {
|
|
children: React.ReactNode;
|
|
umamiWebsiteId?: string;
|
|
umamiScriptUrl?: string;
|
|
}) {
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return;
|
|
|
|
// 1. Extract UTMs from active search string
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const keys: Array<keyof UTMParams> = [
|
|
"utm_source",
|
|
"utm_medium",
|
|
"utm_campaign",
|
|
"utm_content",
|
|
"utm_term",
|
|
];
|
|
|
|
keys.forEach((key) => {
|
|
const val = urlParams.get(key);
|
|
if (val) {
|
|
// Persist to sessionStorage (for current window lifespans)
|
|
sessionStorage.setItem(key, val);
|
|
// Persist to cookie (cross-session backup, used if user closes window and signs up later)
|
|
setCookie(`vibn_${key}`, val, COOKIE_EXPIRY_DAYS);
|
|
}
|
|
});
|
|
|
|
// 2. Persist browser referrer
|
|
if (!sessionStorage.getItem("referrer") && !getCookie("vibn_referrer")) {
|
|
const ref = document.referrer || "direct";
|
|
sessionStorage.setItem("referrer", ref);
|
|
setCookie("vibn_referrer", ref, COOKIE_EXPIRY_DAYS);
|
|
}
|
|
|
|
// 3. Inject Umami analytics script automatically if website ID provided
|
|
if (umamiWebsiteId && !document.querySelector(`script[data-website-id="${umamiWebsiteId}"]`)) {
|
|
const script = document.createElement("script");
|
|
script.src = umamiScriptUrl;
|
|
script.setAttribute("data-website-id", umamiWebsiteId);
|
|
script.async = true;
|
|
script.defer = true;
|
|
document.head.appendChild(script);
|
|
}
|
|
}, [umamiWebsiteId, umamiScriptUrl]);
|
|
|
|
const getStoredAttribution = (): UTMParams => {
|
|
if (typeof window === "undefined") {
|
|
return {
|
|
utm_source: null,
|
|
utm_medium: null,
|
|
utm_campaign: null,
|
|
utm_content: null,
|
|
utm_term: null,
|
|
referrer: null,
|
|
};
|
|
}
|
|
|
|
return {
|
|
utm_source: sessionStorage.getItem("utm_source") || getCookie("vibn_utm_source"),
|
|
utm_medium: sessionStorage.getItem("utm_medium") || getCookie("vibn_utm_medium"),
|
|
utm_campaign: sessionStorage.getItem("utm_campaign") || getCookie("vibn_utm_campaign"),
|
|
utm_content: sessionStorage.getItem("utm_content") || getCookie("vibn_utm_content"),
|
|
utm_term: sessionStorage.getItem("utm_term") || getCookie("vibn_utm_term"),
|
|
referrer: sessionStorage.getItem("referrer") || getCookie("vibn_referrer"),
|
|
};
|
|
};
|
|
|
|
const clearAttribution = () => {
|
|
if (typeof window === "undefined") return;
|
|
const keys = ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term", "referrer"];
|
|
keys.forEach((key) => {
|
|
sessionStorage.removeItem(key);
|
|
deleteCookie(`vibn_${key}`);
|
|
});
|
|
};
|
|
|
|
const identify = (traits: IdentifyTraits) => {
|
|
if (typeof window !== "undefined" && window.umami) {
|
|
window.umami.identify(traits);
|
|
console.log(`[VibnTracker] User identified in Umami: ${traits.email}`);
|
|
}
|
|
};
|
|
|
|
const track = (eventName: string, properties?: Record<string, any>) => {
|
|
if (typeof window !== "undefined" && window.umami) {
|
|
window.umami.track(eventName, properties);
|
|
console.log(`[VibnTracker] Event tracked: "${eventName}"`, properties || "");
|
|
}
|
|
};
|
|
|
|
return (
|
|
<VibnTrackerContext.Provider
|
|
value={{ getStoredAttribution, clearAttribution, identify, track }}
|
|
>
|
|
{children}
|
|
</VibnTrackerContext.Provider>
|
|
);
|
|
}
|
|
|
|
// ── CUSTOM HOOK ───────────────────────────────────────────────────────────────
|
|
|
|
export function useVibnTracker() {
|
|
const context = useContext(VibnTrackerContext);
|
|
if (!context) {
|
|
throw new Error("useVibnTracker must be used within a VibnTrackerProvider");
|
|
}
|
|
return context;
|
|
}
|