diff --git a/src/hooks/useFeatureEnabled.ts b/src/hooks/useFeatureEnabled.ts new file mode 100644 index 00000000..2cd0acea --- /dev/null +++ b/src/hooks/useFeatureEnabled.ts @@ -0,0 +1,27 @@ +// hooks/useFeatureRollout.js +import { useEffect, useState } from "react"; + +import { getCookie, setCookie } from "@/utils/cookies"; + +export function useFeatureEnabled(featureName: string): boolean { + const [isFeatureEnabled, setIsFeatureEnabled] = useState(false); + const rolloutPercentage = parseInt(process.env.NEXT_PUBLIC_FEATURE_ROLLOUT_PERCENTAGE || "0", 10); + + useEffect(() => { + if (typeof window !== "undefined") { + let featureEnabled = getCookie(featureName); + + if (featureEnabled === undefined) { + const randomNumber = Math.random() * 100; + featureEnabled = randomNumber < rolloutPercentage ? "true" : "false"; + + // Set the cookie for 1 day + setCookie(featureName, featureEnabled, 1); + } + + setIsFeatureEnabled(featureEnabled === "true"); + } + }, [featureName, rolloutPercentage]); + + return isFeatureEnabled; +} diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts new file mode 100644 index 00000000..ce160a1e --- /dev/null +++ b/src/hooks/useTheme.ts @@ -0,0 +1,22 @@ +import { useEffect, useState } from "react"; + +export function useTheme() { + const [theme, setTheme] = useState<"light" | "dark">(); + + useEffect(() => { + if (typeof window !== "undefined") { + if (window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches) { + setTheme("light"); + } else { + setTheme("dark"); + } + + window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", (event) => { + const newColorScheme = event.matches ? "light" : "dark"; + setTheme(newColorScheme); + }); + } + }, []); + + return theme; +} diff --git a/src/middleware.ts b/src/middleware.ts index 2796f044..8e7b33f4 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,5 +1,4 @@ import { createClient } from "@vercel/edge-config"; -import { RequestCookie } from "next/dist/compiled/@edge-runtime/cookies"; import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; @@ -55,36 +54,6 @@ const isPreview = (str: string) => { // Donetsk and Luhansk Regions of Ukraine, Russia, Crimea, Cuba, Iran, North Korea or Syria const BLOCKED_COUNTRY = ["RU", "CU", "IR", "KP", "SY"]; -const TREATMENT_BUCKET_PERCENTAGE = 0.5; -const COOKIE_NAME = "ab-test"; // name of the cookie to store the variant - -const abTestMiddleware = (request: NextRequest, response: NextResponse) => { - const randomlySetBucket: RequestCookie = - Math.random() < TREATMENT_BUCKET_PERCENTAGE - ? { - name: COOKIE_NAME, - value: "new", - } - : { - name: COOKIE_NAME, - value: "old", - }; - - const url = request.nextUrl.clone(); - - const RequestCookie = request.cookies.get(COOKIE_NAME) || randomlySetBucket; - - if (RequestCookie.value === "new") { - url.pathname = "/widgetv2"; - response = NextResponse.rewrite(url); - } - - if (!request.cookies.get(COOKIE_NAME)) { - response.cookies.set(RequestCookie); - } - return response; -}; - const geoBlockMiddleware = (request: NextRequest) => { if (request.nextUrl.pathname === "/") { const country = request.geo?.country || "US"; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index e7afbc54..10250a11 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,9 +1,11 @@ -import { useEffect, useMemo, useState } from "react"; +import { useMemo } from "react"; import { defaultTheme, lightTheme, Widget } from "widgetv2"; import DiscordButton from "@/components/DiscordButton"; import { LogoGo } from "@/components/LogoGo"; import WidgetButton from "@/components/WidgetButton"; +import { useFeatureEnabled } from "@/hooks/useFeatureEnabled"; +import { useTheme } from "@/hooks/useTheme"; import { useURLQueryParams } from "@/hooks/useURLQueryParams"; import { apiURL, endpointOptions } from "@/lib/skip-go-widget"; import { isMobile } from "@/utils/os"; @@ -11,22 +13,8 @@ import { cn } from "@/utils/ui"; export default function Home() { const defaultRoute = useURLQueryParams(); - const [theme, setTheme] = useState<"light" | "dark">(); - useEffect(() => { - if (typeof window !== "undefined") { - if (window.matchMedia && window.matchMedia("(prefers-color-scheme: light)").matches) { - setTheme("light"); - } else { - setTheme("dark"); - } - - window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", (event) => { - const newColorScheme = event.matches ? "light" : "dark"; - setTheme(newColorScheme); - }); - } - }, []); - + const goFast = useFeatureEnabled("goFastEnabled"); + const theme = useTheme(); const mobile = useMemo(() => isMobile(), []); if (!theme) return null; @@ -67,7 +55,7 @@ export default function Home() { apiUrl={apiURL} defaultRoute={defaultRoute} routeConfig={{ - goFast: true, + goFast, }} /> diff --git a/src/utils/cookies.ts b/src/utils/cookies.ts new file mode 100644 index 00000000..8e1f1e12 --- /dev/null +++ b/src/utils/cookies.ts @@ -0,0 +1,20 @@ +// utils/cookies.ts + +export const getCookie = (name: string): string | undefined => { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) { + return parts.pop()?.split(";").shift(); + } + return undefined; +}; + +export const setCookie = (name: string, value: string, days?: number): void => { + let expires = ""; + if (days) { + const date = new Date(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + expires = `; expires=${date.toUTCString()}`; + } + document.cookie = `${name}=${encodeURIComponent(value || "")}${expires}; path=/`; +};