shadimage
atroui
libanalyticssource: ok · hit

Analytics

trackEvent and experiment helpers.

npx shadcn add "https://www.atroui.com/r/analytics.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Files1 · source cached 10/14/2026, 3:33:53 PM

registry/default/lib/analytics.ts
/** Lightweight analytics + A/B testing - no external SDK required. */

export type AnalyticsEvent = {
  name: string;
  properties?: Record<string, string | number | boolean>;
};

export type Experiment = {
  id: string;
  variants: string[];
  weights?: number[];
};

const STORAGE_PREFIX = "ms_";
const EXPERIMENT_PREFIX = `${STORAGE_PREFIX}exp_`;

function isBrowser(): boolean {
  return typeof window !== "undefined";
}

/** Assign and persist a variant for an experiment (sticky per user). */
export function getExperimentVariant(experiment: Experiment): string {
  if (!isBrowser()) return experiment.variants[0] ?? "";

  const key = `${EXPERIMENT_PREFIX}${experiment.id}`;
  const stored = localStorage.getItem(key);
  if (stored && experiment.variants.includes(stored)) return stored;

  const weights = experiment.weights ?? experiment.variants.map(() => 1);
  const total = weights.reduce((a, b) => a + b, 0);
  let roll = Math.random() * total;
  let variant = experiment.variants[0] ?? "";
  for (let i = 0; i < experiment.variants.length; i++) {
    roll -= weights[i] ?? 0;
    if (roll <= 0) {
      variant = experiment.variants[i] ?? variant;
      break;
    }
  }

  localStorage.setItem(key, variant);
  trackEvent("experiment_assigned", {
    experiment_id: experiment.id,
    variant,
  });
  return variant;
}

/** Track an event - forwards to Plausible/GA if configured, always logs in dev. */
export function trackEvent(
  name: string,
  properties?: Record<string, string | number | boolean>
): void {
  if (!isBrowser()) return;

  const payload: AnalyticsEvent = { name, properties };

  if (process.env.NODE_ENV === "development") {
    console.debug("[analytics]", payload);
  }

  // Plausible custom events
  const plausible = (
    window as Window & { plausible?: (e: string, o?: { props: Record<string, string | number | boolean> }) => void }
  ).plausible;
  if (plausible) {
    plausible(name, properties ? { props: properties } : undefined);
  }

  // Google Analytics 4
  const gtag = (
    window as Window & { gtag?: (...args: unknown[]) => void }
  ).gtag;
  if (gtag) {
    gtag("event", name, properties);
  }

  // Local event buffer for debugging
  try {
    const key = `${STORAGE_PREFIX}events`;
    const existing = JSON.parse(localStorage.getItem(key) ?? "[]") as AnalyticsEvent[];
    existing.push({ ...payload, properties: { ...properties, ts: Date.now() } as Record<string, string | number | boolean> });
    localStorage.setItem(key, JSON.stringify(existing.slice(-50)));
  } catch {
    // ignore quota errors
  }
}

export const CTA_EXPERIMENT: Experiment = {
  id: "homepage_cta",
  variants: ["book_call", "try_planner"],
  weights: [50, 50],
};

export const CONTACT_FORM_EXPERIMENT: Experiment = {
  id: "contact_form_layout",
  variants: ["multi_step", "single_step"],
  weights: [70, 30],
};

More from atroui

Browse all components in this library →