import { useCallback, useEffect, useRef } from 'react'; import { driver } from 'driver.js'; import 'driver.js/dist/driver.css'; import { getTour } from '../lib/tour/registry'; import { anchorSelector, resolveSteps } from '../lib/tour/resolveSteps'; import { formatNumber } from '../lib/utils'; import { useTourProgress } from './useTourProgress'; interface Options { /** وقتی true شد یعنی دادهٔ صفحه آمده و المان‌های هدف رندر شده‌اند */ ready?: boolean; } /** * راهنمای قدم‌به‌قدم یک صفحه. بار اول خودکار اجرا می‌شود و بعد از آن فقط با * صدا زدن start — یعنی دکمهٔ «؟» صفحه. */ export function useTour(tourId?: string, { ready = false }: Options = {}) { const tour = getTour(tourId); const { isReady, isSeen, markSeen } = useTourProgress(); const autoStarted = useRef(false); const start = useCallback(() => { if (!tour) return; const steps = resolveSteps(tour.steps); if (steps.length === 0) return; driver({ showProgress: true, allowClose: true, overlayOpacity: 0.55, popoverClass: 'cp-tour', nextBtnText: 'بعدی', prevBtnText: 'قبلی', doneBtnText: 'باشه، فهمیدم', steps: steps.map((s, i) => ({ element: anchorSelector(s.anchor), popover: { title: s.title, description: s.body, side: s.side ?? 'bottom', align: 'start', // قالبِ سراسری driver فقط {{current}} می‌دهد و آن ارقام لاتین است؛ // شمارنده باید مثل بقیهٔ پنل فارسی باشد. progressText: `${formatNumber(i + 1)} از ${formatNumber(steps.length)}`, }, })), // بستن وسط تور هم «دیده شده» است؛ تکرارش برای کسی که ردش کرده آزار است. onDestroyed: () => markSeen(tour.id, tour.version), }).drive(); }, [tour, markSeen]); // `start` با هر رندر بازساخته می‌شود؛ اگر وابستگیِ effect باشد، cleanup تایمرِ // سیصد میلی‌ثانیه‌ای را قبل از شلیک پاک می‌کند و تور هرگز اجرا نمی‌شود. const startRef = useRef(start); startRef.current = start; // مقدار boolean وابستگیِ پایداری است، برخلاف خودِ تابع isSeen. const alreadySeen = tour ? isSeen(tour.id, tour.version) : true; useEffect(() => { if (!tour || !ready || autoStarted.current) return; // تا پاسخ سرور نیامده هیچ چیز اجرا نمی‌شود؛ وگرنه در خطای شبکه کاربر قدیمی // هر بار رفرش یک تور می‌بیند. if (!isReady || alreadySeen) return; autoStarted.current = true; // یک لحظه صبر تا چیدمان نهایی بنشیند و highlight سرِ جای درست بیفتد. const timer = window.setTimeout(() => startRef.current(), 300); return () => window.clearTimeout(timer); }, [tour, ready, isReady, alreadySeen]); return { available: tour !== null, start }; }