"use client"; import React, { createContext, useContext, useEffect, useState } from 'react'; import { useRouter, usePathname } from 'next/navigation'; type Theme = 'dark' | 'light'; type Currency = 'Rp' | 'USD' | 'Crypto'; type Locale = 'id' | 'en'; interface OmniContextProps { theme: Theme; setTheme: (t: Theme) => void; currency: Currency; setCurrency: (c: Currency) => void; locale: Locale; setLocale: (l: Locale) => void; formatCurrency: (amountInIDR: number) => string; } const OmniContext = createContext(null); export const useOmni = () => { const ctx = useContext(OmniContext); if (!ctx) throw new Error("useOmni must be used within OmniSyncProvider"); return ctx; }; export function OmniSyncProvider({ children, initialLocale }: { children: React.ReactNode, initialLocale: Locale }) { const [theme, setThemeState] = useState('dark'); const [currency, setCurrencyState] = useState('Rp'); const [locale, setLocaleState] = useState(initialLocale); const router = useRouter(); const pathname = usePathname(); // Use a ref for the broadcast channel to avoid mutating variables in useEffect const channelRef = React.useRef(null); const setCookie = (name: string, value: string) => { const host = window.location.hostname; const cookieDomain = process.env.NEXT_PUBLIC_COOKIE_DOMAIN || (host === 'localhost' || host === '127.0.0.1' ? host : `.${host}`); document.cookie = `${name}=${value}; path=/; domain=${cookieDomain}; max-age=31536000`; if (window.location.hostname === 'localhost') { document.cookie = `${name}=${value}; path=/; max-age=31536000`; } }; const getCookie = (name: string) => { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop()?.split(';').shift(); return null; }; const setTheme = (t: Theme) => { setThemeState(t); setCookie('omni_theme', t); document.documentElement.setAttribute('data-theme', t); if (channelRef.current) channelRef.current.postMessage({ type: 'SYNC_THEME', payload: t }); }; const setCurrency = (c: Currency) => { setCurrencyState(c); setCookie('omni_currency', c); if (channelRef.current) channelRef.current.postMessage({ type: 'SYNC_CURRENCY', payload: c }); }; const setLocale = (l: Locale) => { setLocaleState(l); setCookie('NEXT_LOCALE', l); if (channelRef.current) channelRef.current.postMessage({ type: 'SYNC_LOCALE', payload: l }); router.refresh(); }; useEffect(() => { // Client-side initialization const savedTheme = getCookie('omni_theme') as Theme; if (savedTheme) { document.documentElement.setAttribute('data-theme', savedTheme); if (theme !== savedTheme) { queueMicrotask(() => setThemeState(savedTheme)); } } else { document.documentElement.setAttribute('data-theme', 'dark'); } const savedCurrency = getCookie('omni_currency') as Currency; if (savedCurrency && currency !== savedCurrency) { queueMicrotask(() => setCurrencyState(savedCurrency)); } if (typeof window !== 'undefined' && !channelRef.current) { channelRef.current = new BroadcastChannel('omni_sync_channel'); } const channel = channelRef.current; if (channel) { channel.onmessage = (event: MessageEvent) => { const { type, payload } = event.data; if (type === 'SYNC_THEME') { setThemeState(payload); document.documentElement.setAttribute('data-theme', payload); } if (type === 'SYNC_CURRENCY') { setCurrencyState(payload); } if (type === 'SYNC_LOCALE') { setLocaleState(payload); if (pathname) { router.refresh(); } } }; } return () => { if (channel) channel.close(); channelRef.current = null; }; }, [pathname, router, currency, theme]); const formatCurrency = (amountInIDR: number): string => { // Quantum Conversion Matrix const rates = { Rp: 1, USD: 1 / 16000, Crypto: 1 / 1000000000 // 1 BTC = 1B IDR approx }; const value = amountInIDR * rates[currency]; if (currency === 'Crypto') { return `₿ ${value.toFixed(8)}`; } return new Intl.NumberFormat(locale === 'id' ? 'id-ID' : 'en-US', { style: 'currency', currency: currency === 'Rp' ? 'IDR' : 'USD', minimumFractionDigits: currency === 'Rp' ? 0 : 2 }).format(value); }; return ( {children} ); }