[TSM.ID].[11031972] PXE : Platform X Ecosystem I [118 Module -LIVE-]
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
"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<OmniContextProps | null>(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<Theme>('dark');
|
||||
const [currency, setCurrencyState] = useState<Currency>('Rp');
|
||||
const [locale, setLocaleState] = useState<Locale>(initialLocale);
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
// Use a ref for the broadcast channel to avoid mutating variables in useEffect
|
||||
const channelRef = React.useRef<BroadcastChannel | null>(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 (
|
||||
<OmniContext.Provider value={{ theme, setTheme, currency, setCurrency, locale, setLocale, formatCurrency }}>
|
||||
{children}
|
||||
</OmniContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user