import React, { useState, useCallback } from 'react'; import { tutorialSteps, TutorialHint, TUTORIAL_PAGE_GROUPS } from './tutorialSteps'; // 현재 키가 속한 그룹의 전체 힌트 수와 현재까지의 offset 반환 function getGroupProgress(key: string, currentIndex: number): { groupTotal: number; groupOffset: number; isLastKeyInGroup: boolean } | null { const group = TUTORIAL_PAGE_GROUPS.find(g => g.includes(key)); if (!group) return null; let offset = 0; let total = 0; for (const k of group) { const step = tutorialSteps.find(s => s.key === k); const count = step?.hints.length ?? 0; if (k === key) offset = total; total += count; } const isLastKeyInGroup = group[group.length - 1] === key; return { groupTotal: total, groupOffset: offset + currentIndex, isLastKeyInGroup }; } const SEEN_KEY = 'ado2_tutorial_seen'; const PROGRESS_KEY = 'ado2_tutorial_progress'; // 전역 단일 활성 튜토리얼 관리 — 새 튜토리얼 시작 시 이전 것을 skip 처리 let globalSkip: (() => void) | null = null; function getSeenKeys(): string[] { try { return JSON.parse(localStorage.getItem(SEEN_KEY) || '[]'); } catch { return []; } } function markSeen(key: string) { const seen = getSeenKeys(); if (!seen.includes(key)) { localStorage.setItem(SEEN_KEY, JSON.stringify([...seen, key])); } clearProgress(key); } function saveProgress(key: string, index: number) { try { const progress = JSON.parse(localStorage.getItem(PROGRESS_KEY) || '{}'); progress[key] = index; localStorage.setItem(PROGRESS_KEY, JSON.stringify(progress)); } catch {} } function loadProgress(key: string): number { try { const progress = JSON.parse(localStorage.getItem(PROGRESS_KEY) || '{}'); return progress[key] ?? 0; } catch { return 0; } } function clearProgress(key: string) { try { const progress = JSON.parse(localStorage.getItem(PROGRESS_KEY) || '{}'); delete progress[key]; localStorage.setItem(PROGRESS_KEY, JSON.stringify(progress)); } catch {} } interface UseTutorialReturn { isActive: boolean; isRestartPopupVisible: boolean; currentHintIndex: number; hints: TutorialHint[]; tutorialKey: string | null; groupProgress: { groupTotal: number; groupOffset: number } | null; startTutorial: (key: string, onComplete?: () => void, forceFromStart?: boolean) => void; nextHint: () => void; prevHint: () => void; skipTutorial: () => void; showRestartPopup: (key: string) => void; confirmRestart: () => void; cancelRestart: () => void; hasSeen: (key: string) => boolean; } export function useTutorial(): UseTutorialReturn { const [isActive, setIsActive] = useState(false); const [isRestartPopupVisible, setIsRestartPopupVisible] = useState(false); const [pendingRestartKey, setPendingRestartKey] = useState(null); const [currentHintIndex, setCurrentHintIndex] = useState(0); const [hints, setHints] = useState([]); const [tutorialKey, setTutorialKey] = useState(null); const onCompleteRef = React.useRef<(() => void) | undefined>(undefined); const startTutorial = useCallback((key: string, onComplete?: () => void, forceFromStart?: boolean) => { const step = tutorialSteps.find(s => s.key === key); if (!step || step.hints.length === 0) return; // 다른 인스턴스에서 활성화된 튜토리얼이 있으면 skip 처리 globalSkip?.(); const savedIndex = forceFromStart ? 0 : loadProgress(key); const resumeIndex = savedIndex < step.hints.length ? savedIndex : 0; onCompleteRef.current = onComplete; setHints(step.hints); setTutorialKey(key); setCurrentHintIndex(resumeIndex); setIsActive(true); // 이 인스턴스의 skip을 전역에 등록 globalSkip = () => { if (key) saveProgress(key, resumeIndex); setIsActive(false); setCurrentHintIndex(0); globalSkip = null; }; }, []); const nextHint = useCallback(() => { setCurrentHintIndex(prev => { if (prev < hints.length - 1) { const next = prev + 1; if (tutorialKey) saveProgress(tutorialKey, next); return next; } // 마지막 힌트 완료 → seen 기록 + 진행 상태 삭제 setIsActive(false); if (tutorialKey) markSeen(tutorialKey); globalSkip = null; // 완료된 튜토리얼은 globalSkip 해제 onCompleteRef.current?.(); onCompleteRef.current = undefined; return 0; }); }, [hints.length, tutorialKey]); const prevHint = useCallback(() => { setCurrentHintIndex(prev => Math.max(0, prev - 1)); }, []); // 건너뛰기: 현재 진행 인덱스 저장 후 오버레이 닫기 → 다음 방문 시 이어서 표시 const skipTutorial = useCallback(() => { if (tutorialKey) saveProgress(tutorialKey, currentHintIndex); setIsActive(false); setCurrentHintIndex(0); }, [tutorialKey, currentHintIndex]); // 튜토리얼 다시 보기: 팝업 표시만 const showRestartPopup = useCallback((key: string) => { setPendingRestartKey(key); setIsRestartPopupVisible(true); }, []); // 팝업에서 확인 → seen/progress 초기화 후 튜토리얼 시작 const confirmRestart = useCallback(() => { setIsRestartPopupVisible(false); if (pendingRestartKey) { localStorage.removeItem(SEEN_KEY); localStorage.removeItem(PROGRESS_KEY); startTutorial(pendingRestartKey, undefined, true); } setPendingRestartKey(null); }, [pendingRestartKey, startTutorial]); // 팝업에서 취소 const cancelRestart = useCallback(() => { setIsRestartPopupVisible(false); setPendingRestartKey(null); }, []); const hasSeen = useCallback((key: string) => { return getSeenKeys().includes(key); }, []); return { isActive, isRestartPopupVisible, currentHintIndex, hints, tutorialKey, groupProgress: tutorialKey ? getGroupProgress(tutorialKey, currentHintIndex) : null, startTutorial, nextHint, prevHint, skipTutorial, showRestartPopup, confirmRestart, cancelRestart, hasSeen, }; }