@citiwave/im-disclaimer-modal
Version:
A customizable React disclaimer modal component with theme support
92 lines (81 loc) • 2.45 kB
text/typescript
import { useState, useEffect, useCallback } from 'react';
export interface DisclaimerHookOptions {
storagePrefix?: string;
defaultOpen?: boolean;
}
export interface DisclaimerHookResult {
isOpen: boolean;
onClose: () => void;
reset: () => void;
setAllSectionsAgreed: (agreed: boolean) => void;
}
// Safe localStorage access
const storage = {
get: (key: string): string | null => {
if (typeof window === 'undefined') return null;
try {
return localStorage.getItem(key);
} catch (e) {
console.warn('Failed to access localStorage:', e);
return null;
}
},
set: (key: string, value: string): void => {
if (typeof window === 'undefined') return;
try {
localStorage.setItem(key, value);
} catch (e) {
console.warn('Failed to write to localStorage:', e);
}
},
remove: (key: string): void => {
if (typeof window === 'undefined') return;
try {
localStorage.removeItem(key);
} catch (e) {
console.warn('Failed to remove from localStorage:', e);
}
}
};
export const useDisclaimer = (
disclaimerId: string,
options: DisclaimerHookOptions = {}
): DisclaimerHookResult => {
const {
storagePrefix = 'disclaimer',
defaultOpen = true
} = options;
// Start with modal closed during SSR to prevent hydration mismatch
const [isOpen, setIsOpen] = useState(false);
const [mounted, setMounted] = useState(false);
const [allSectionsAgreed, setAllSectionsAgreed] = useState(false);
useEffect(() => {
setMounted(true);
const key = `${storagePrefix}_${disclaimerId}`;
const accepted = storage.get(key) === 'true';
// Only open if not previously accepted
setIsOpen(!accepted && defaultOpen);
}, [disclaimerId, storagePrefix, defaultOpen]);
const onClose = useCallback(() => {
if (allSectionsAgreed) {
const key = `${storagePrefix}_${disclaimerId}`;
storage.set(key, 'true');
setIsOpen(false);
}
}, [allSectionsAgreed, disclaimerId, storagePrefix]);
const reset = useCallback(() => {
const key = `${storagePrefix}_${disclaimerId}`;
storage.remove(key);
setIsOpen(true);
}, [disclaimerId, storagePrefix]);
// Don't render anything until after hydration
if (!mounted) {
return {
isOpen: false,
onClose: () => {},
reset: () => {},
setAllSectionsAgreed: () => {}
};
}
return { isOpen, onClose, reset, setAllSectionsAgreed };
};