UNPKG

@citiwave/im-disclaimer-modal

Version:

A customizable React disclaimer modal component with theme support

92 lines (81 loc) 2.45 kB
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 }; };