// src/hooks/usePermissionGuide.js /** * 通知权限引导管理 Hook * * 功能: * - 管理多个引导场景的显示状态 * - 使用 localStorage 持久化记录 * - 支持定期提醒策略 */ import { useState, useCallback, useEffect } from 'react'; import { logger } from '../utils/logger'; // 引导场景类型 export const GUIDE_TYPES = { WELCOME: 'welcome', // 首次登录欢迎引导 COMMUNITY: 'community', // 社区功能引导 FIRST_FOLLOW: 'first_follow', // 首次关注事件引导 PERIODIC: 'periodic', // 定期提醒 }; // localStorage 键名 const STORAGE_KEYS = { SHOWN_GUIDES: 'notification_guides_shown', LAST_PERIODIC: 'notification_last_periodic_prompt', TOTAL_PROMPTS: 'notification_total_prompts', }; // 定期提醒间隔(毫秒) const PERIODIC_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 3 天 const MAX_PERIODIC_PROMPTS = 3; // 最多提醒 3 次 /** * 权限引导管理 Hook */ export function usePermissionGuide() { const [shownGuides, setShownGuides] = useState(() => { try { const stored = localStorage.getItem(STORAGE_KEYS.SHOWN_GUIDES); return stored ? JSON.parse(stored) : []; } catch (error) { logger.error('usePermissionGuide', 'Failed to load shown guides', error); return []; } }); /** * 检查是否应该显示某个引导 * @param {string} guideType - 引导类型 * @returns {boolean} */ const shouldShowGuide = useCallback((guideType) => { // 已经显示过的引导不再显示 if (shownGuides.includes(guideType)) { return false; } // 特殊逻辑:定期提醒 if (guideType === GUIDE_TYPES.PERIODIC) { try { const lastPrompt = localStorage.getItem(STORAGE_KEYS.LAST_PERIODIC); const totalPrompts = parseInt(localStorage.getItem(STORAGE_KEYS.TOTAL_PROMPTS) || '0', 10); // 超过最大提醒次数 if (totalPrompts >= MAX_PERIODIC_PROMPTS) { logger.debug('usePermissionGuide', 'Periodic prompts limit reached', { totalPrompts }); return false; } // 未到提醒间隔 if (lastPrompt) { const elapsed = Date.now() - parseInt(lastPrompt, 10); if (elapsed < PERIODIC_INTERVAL) { logger.debug('usePermissionGuide', 'Periodic interval not reached', { elapsed: Math.round(elapsed / 1000 / 60 / 60), // 小时 required: Math.round(PERIODIC_INTERVAL / 1000 / 60 / 60) }); return false; } } return true; } catch (error) { logger.error('usePermissionGuide', 'Failed to check periodic guide', error); return false; } } return true; }, [shownGuides]); /** * 标记引导已显示 * @param {string} guideType - 引导类型 */ const markGuideAsShown = useCallback((guideType) => { try { // 更新状态 setShownGuides(prev => { if (prev.includes(guideType)) { return prev; } const updated = [...prev, guideType]; // 持久化 localStorage.setItem(STORAGE_KEYS.SHOWN_GUIDES, JSON.stringify(updated)); logger.info('usePermissionGuide', 'Guide marked as shown', { guideType }); return updated; }); // 特殊处理:定期提醒 if (guideType === GUIDE_TYPES.PERIODIC) { localStorage.setItem(STORAGE_KEYS.LAST_PERIODIC, String(Date.now())); const totalPrompts = parseInt(localStorage.getItem(STORAGE_KEYS.TOTAL_PROMPTS) || '0', 10); localStorage.setItem(STORAGE_KEYS.TOTAL_PROMPTS, String(totalPrompts + 1)); logger.info('usePermissionGuide', 'Periodic prompt recorded', { totalPrompts: totalPrompts + 1 }); } } catch (error) { logger.error('usePermissionGuide', 'Failed to mark guide as shown', error); } }, []); /** * 重置所有引导(用于测试或用户主动重置) */ const resetAllGuides = useCallback(() => { try { localStorage.removeItem(STORAGE_KEYS.SHOWN_GUIDES); localStorage.removeItem(STORAGE_KEYS.LAST_PERIODIC); localStorage.removeItem(STORAGE_KEYS.TOTAL_PROMPTS); setShownGuides([]); logger.info('usePermissionGuide', 'All guides reset'); } catch (error) { logger.error('usePermissionGuide', 'Failed to reset guides', error); } }, []); /** * 获取定期提醒的统计信息(用于调试) */ const getPeriodicStats = useCallback(() => { try { const lastPrompt = localStorage.getItem(STORAGE_KEYS.LAST_PERIODIC); const totalPrompts = parseInt(localStorage.getItem(STORAGE_KEYS.TOTAL_PROMPTS) || '0', 10); return { lastPromptTime: lastPrompt ? new Date(parseInt(lastPrompt, 10)) : null, totalPrompts, remainingPrompts: MAX_PERIODIC_PROMPTS - totalPrompts, nextPromptTime: lastPrompt ? new Date(parseInt(lastPrompt, 10) + PERIODIC_INTERVAL) : new Date(), }; } catch (error) { logger.error('usePermissionGuide', 'Failed to get periodic stats', error); return null; } }, []); return { shouldShowGuide, markGuideAsShown, resetAllGuides, getPeriodicStats, shownGuides, }; }