85 lines
2.2 KiB
TypeScript
85 lines
2.2 KiB
TypeScript
"use client";
|
|
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import { usePathname } from "next/navigation";
|
|
import { api } from "@/lib/client-api";
|
|
import {
|
|
defaultSiteSettings,
|
|
siteSettingsSchema,
|
|
type SiteSettings,
|
|
} from "@/shared/site-settings";
|
|
|
|
const SettingsContext = createContext({
|
|
settings: defaultSiteSettings,
|
|
updateSettings: (_settings: SiteSettings) => {},
|
|
});
|
|
|
|
export function SiteSettingsProvider({
|
|
initialSettings,
|
|
children,
|
|
}: {
|
|
initialSettings: SiteSettings;
|
|
children: React.ReactNode;
|
|
}) {
|
|
const [settings, setSettings] = useState(initialSettings);
|
|
const version = useRef(0);
|
|
const pending = useRef(false);
|
|
const pathname = usePathname();
|
|
const updateSettings = useCallback((next: SiteSettings) => {
|
|
version.current++;
|
|
setSettings(next);
|
|
}, []);
|
|
const refresh = useCallback(async () => {
|
|
if (pending.current || document.visibilityState === "hidden") return;
|
|
pending.current = true;
|
|
const requestVersion = version.current;
|
|
try {
|
|
const next = siteSettingsSchema.parse(await api("site-settings"));
|
|
// An older poll must not overwrite a moderator's just-saved setting.
|
|
if (requestVersion === version.current) setSettings(next);
|
|
} catch {
|
|
// Keep the last known setting while disconnected.
|
|
} finally {
|
|
pending.current = false;
|
|
}
|
|
}, []);
|
|
useEffect(() => {
|
|
void refresh();
|
|
}, [pathname, refresh]);
|
|
useEffect(() => {
|
|
const timer = window.setInterval(() => {
|
|
void refresh();
|
|
}, 30000);
|
|
const resume = () => {
|
|
void refresh();
|
|
};
|
|
document.addEventListener("visibilitychange", resume);
|
|
window.addEventListener("focus", resume);
|
|
return () => {
|
|
window.clearInterval(timer);
|
|
document.removeEventListener("visibilitychange", resume);
|
|
window.removeEventListener("focus", resume);
|
|
};
|
|
}, [refresh]);
|
|
const value = useMemo(
|
|
() => ({ settings, updateSettings }),
|
|
[settings, updateSettings],
|
|
);
|
|
return (
|
|
<SettingsContext.Provider value={value}>
|
|
{children}
|
|
</SettingsContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useSiteSettings() {
|
|
return useContext(SettingsContext);
|
|
}
|