88 lines
2.2 KiB
TypeScript
88 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useMemo,
|
|
useSyncExternalStore,
|
|
type ReactNode,
|
|
} from "react";
|
|
import {
|
|
type CurrencyCode,
|
|
convertFromUsd,
|
|
formatMoneyFromUsd,
|
|
isCurrencyCode,
|
|
} from "@/lib/currency";
|
|
|
|
const STORAGE_KEY = "shitaye-currency";
|
|
const CURRENCY_EVENT = "shitaye-currency-change";
|
|
|
|
type CurrencyContextValue = {
|
|
currency: CurrencyCode;
|
|
setCurrency: (c: CurrencyCode) => void;
|
|
formatUsd: (amountUsd: number, maximumFractionDigits?: 0 | 1 | 2) => string;
|
|
convertUsd: (amountUsd: number) => number;
|
|
};
|
|
|
|
const CurrencyContext = createContext<CurrencyContextValue | null>(null);
|
|
|
|
function readCurrency(): CurrencyCode {
|
|
if (typeof window === "undefined") return "USD";
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (raw && isCurrencyCode(raw)) return raw;
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
return "USD";
|
|
}
|
|
|
|
function subscribe(onChange: () => void) {
|
|
if (typeof window === "undefined") return () => {};
|
|
const handler = () => onChange();
|
|
window.addEventListener("storage", handler);
|
|
window.addEventListener(CURRENCY_EVENT, handler);
|
|
return () => {
|
|
window.removeEventListener("storage", handler);
|
|
window.removeEventListener(CURRENCY_EVENT, handler);
|
|
};
|
|
}
|
|
|
|
export function CurrencyProvider({ children }: { children: ReactNode }) {
|
|
const currency = useSyncExternalStore(
|
|
subscribe,
|
|
readCurrency,
|
|
() => "USD" as CurrencyCode,
|
|
) as CurrencyCode;
|
|
|
|
const setCurrency = useCallback((c: CurrencyCode) => {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, c);
|
|
window.dispatchEvent(new Event(CURRENCY_EVENT));
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}, []);
|
|
|
|
const value = useMemo((): CurrencyContextValue => {
|
|
return {
|
|
currency,
|
|
setCurrency,
|
|
formatUsd: (amountUsd, maximumFractionDigits = 2) =>
|
|
formatMoneyFromUsd(amountUsd, currency, maximumFractionDigits),
|
|
convertUsd: (amountUsd) => convertFromUsd(amountUsd, currency),
|
|
};
|
|
}, [currency, setCurrency]);
|
|
|
|
return (
|
|
<CurrencyContext.Provider value={value}>{children}</CurrencyContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useCurrency() {
|
|
const ctx = useContext(CurrencyContext);
|
|
if (!ctx) throw new Error("useCurrency must be used within CurrencyProvider");
|
|
return ctx;
|
|
}
|