Files

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;
}