Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | "use client";
/**
* A/B Test Hook
*
* Hook for fetching A/B test variant assignments and tracking conversions.
*/
import { useState, useEffect, useCallback } from "react";
import { useAnalytics } from "@/components/providers/AnalyticsProvider";
import { clientLogger } from '@/lib/logging/clientLogger';
/**
* Result from the A/B test hook
*/
interface ABTestResult<T = Record<string, unknown>> {
/** The assigned variant name (e.g., "control", "variant_a") */
variant: string | null;
/** Variant configuration data */
config: T | null;
/** Whether the variant is still loading */
isLoading: boolean;
/** Track a conversion for this experiment */
trackConversion: (value?: number) => void;
}
/**
* Hook for A/B testing
*
* @param experimentName - The name of the experiment
* @returns Object containing variant info and conversion tracking
*
* @example
* ```tsx
* const { variant, isLoading, trackConversion } = useABTest("checkout-button-color");
*
* if (isLoading) return <Skeleton />;
*
* return (
* <Button
* variant={variant === "variant_a" ? "success" : "primary"}
* onClick={() => {
* trackConversion();
* handleCheckout();
* }}
* >
* Checkout
* </Button>
* );
* ```
*/
export function useABTest<T = Record<string, unknown>>(experimentName: string): ABTestResult<T> {
const { visitorId, isReady, trackEvent } = useAnalytics();
const [variant, setVariant] = useState<string | null>(null);
const [config, setConfig] = useState<T | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (!isReady || !visitorId) return;
const fetchVariant = async () => {
try {
const response = await fetch(
`/api/analytics/ab-test?experiment=${encodeURIComponent(experimentName)}&visitorId=${encodeURIComponent(visitorId)}`
);
if (response.ok) {
const result = await response.json();
// Handle both new wrapped format and legacy format
const data = result.data ?? result;
setVariant(data.variant);
setConfig(data.config || null);
// Track experiment exposure
if (data.variant) {
trackEvent("ab_test", "exposure", {
experiment: experimentName,
variant: data.variant});
}
}
} catch (error) {
clientLogger.error('A/B test fetch error', error instanceof Error ? error : new Error(String(error)), { experimentName });
} finally {
setIsLoading(false);
}
};
fetchVariant();
}, [experimentName, visitorId, isReady, trackEvent]);
const trackConversion = useCallback(
(value?: number) => {
if (!variant || !visitorId) return;
// Send conversion to API
fetch("/api/analytics/ab-test", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
experiment: experimentName,
visitorId,
variant,
value})}).catch((error) => {
clientLogger.error('A/B test conversion error', error instanceof Error ? error : new Error(String(error)), { experimentName, variant });
});
// Also track as analytics event
trackEvent("ab_test", "conversion", {
experiment: experimentName,
variant,
value});
},
[experimentName, visitorId, variant, trackEvent]
);
return { variant, config, isLoading, trackConversion };
}
|