110 lines
2.7 KiB
TypeScript
110 lines
2.7 KiB
TypeScript
import React from 'react';
|
|
|
|
type GuestIdentityContextValue = {
|
|
slug: string;
|
|
name: string;
|
|
hydrated: boolean;
|
|
setName: (nextName: string) => void;
|
|
clearName: () => void;
|
|
reload: () => void;
|
|
};
|
|
|
|
const GuestIdentityContext = React.createContext<GuestIdentityContextValue | undefined>(undefined);
|
|
|
|
function storageKey(slug: string) {
|
|
return `guestName_${slug}`;
|
|
}
|
|
|
|
export function readGuestName(slug: string) {
|
|
if (!slug || typeof window === 'undefined') {
|
|
return '';
|
|
}
|
|
|
|
try {
|
|
return window.localStorage.getItem(storageKey(slug)) ?? '';
|
|
} catch (error) {
|
|
console.warn('Failed to read guest name', error);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
export function GuestIdentityProvider({ slug, children }: { slug: string; children: React.ReactNode }) {
|
|
const [name, setNameState] = React.useState('');
|
|
const [hydrated, setHydrated] = React.useState(false);
|
|
|
|
const loadFromStorage = React.useCallback(() => {
|
|
if (!slug) {
|
|
setHydrated(true);
|
|
setNameState('');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const stored = window.localStorage.getItem(storageKey(slug));
|
|
setNameState(stored ?? '');
|
|
} catch (error) {
|
|
console.warn('Failed to read guest name from storage', error);
|
|
setNameState('');
|
|
} finally {
|
|
setHydrated(true);
|
|
}
|
|
}, [slug]);
|
|
|
|
React.useEffect(() => {
|
|
setHydrated(false);
|
|
loadFromStorage();
|
|
}, [loadFromStorage]);
|
|
|
|
const persistName = React.useCallback(
|
|
(nextName: string) => {
|
|
const trimmed = nextName.trim();
|
|
setNameState(trimmed);
|
|
try {
|
|
if (trimmed) {
|
|
window.localStorage.setItem(storageKey(slug), trimmed);
|
|
} else {
|
|
window.localStorage.removeItem(storageKey(slug));
|
|
}
|
|
} catch (error) {
|
|
console.warn('Failed to persist guest name', error);
|
|
}
|
|
},
|
|
[slug]
|
|
);
|
|
|
|
const clearName = React.useCallback(() => {
|
|
setNameState('');
|
|
try {
|
|
window.localStorage.removeItem(storageKey(slug));
|
|
} catch (error) {
|
|
console.warn('Failed to clear guest name', error);
|
|
}
|
|
}, [slug]);
|
|
|
|
const value = React.useMemo<GuestIdentityContextValue>(
|
|
() => ({
|
|
slug,
|
|
name,
|
|
hydrated,
|
|
setName: persistName,
|
|
clearName,
|
|
reload: loadFromStorage,
|
|
}),
|
|
[slug, name, hydrated, persistName, clearName, loadFromStorage]
|
|
);
|
|
|
|
return <GuestIdentityContext.Provider value={value}>{children}</GuestIdentityContext.Provider>;
|
|
}
|
|
|
|
export function useGuestIdentity() {
|
|
const ctx = React.useContext(GuestIdentityContext);
|
|
if (!ctx) {
|
|
throw new Error('useGuestIdentity must be used within a GuestIdentityProvider');
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
export function useOptionalGuestIdentity() {
|
|
return React.useContext(GuestIdentityContext);
|
|
}
|