'use client'; import React, { createContext, useContext, useState, useEffect, useRef } from 'react'; import { RecordEvent, RecordingSession } from '@/types/record-mode'; interface RecordModeContextType { isActive: boolean; setIsActive: (active: boolean) => void; events: RecordEvent[]; addEvent: (event: Omit) => void; updateEvent: (id: string, event: Partial) => void; removeEvent: (id: string) => void; clearEvents: () => void; setEvents: (events: RecordEvent[]) => void; isPlaying: boolean; playEvents: () => void; stopPlayback: () => void; cursorPosition: { x: number; y: number }; zoomLevel: number; isBlurry: boolean; currentSession: RecordingSession | null; saveSession: (name: string) => void; isFeedbackActive: boolean; setIsFeedbackActive: (active: boolean) => void; reorderEvents: (startIndex: number, endIndex: number) => void; hoveredEventId: string | null; setHoveredEventId: (id: string | null) => void; isClicking: boolean; isEnabled: boolean; } const RecordModeContext = createContext(null); export function useRecordMode(): RecordModeContextType { const context = useContext(RecordModeContext); if (!context) { return { isActive: false, setIsActive: () => {}, events: [], addEvent: () => {}, updateEvent: () => {}, removeEvent: () => {}, clearEvents: () => {}, isPlaying: false, playEvents: () => {}, stopPlayback: () => {}, cursorPosition: { x: 0, y: 0 }, zoomLevel: 1, isBlurry: false, currentSession: null, isFeedbackActive: false, setIsFeedbackActive: () => {}, saveSession: () => {}, reorderEvents: () => {}, hoveredEventId: null, setHoveredEventId: () => {}, setEvents: () => {}, isClicking: false, isEnabled: false, }; } return context; } export function RecordModeProvider({ children, isEnabled = false, }: { children: React.ReactNode; isEnabled?: boolean; }) { const [isActive, setIsActiveState] = useState(false); const [events, setEvents] = useState([]); const [isPlaying, setIsPlaying] = useState(false); const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 }); const [zoomLevel, setZoomLevel] = useState(1); const [isBlurry, setIsBlurry] = useState(false); const [isFeedbackActive, setIsFeedbackActiveState] = useState(false); const [hoveredEventId, setHoveredEventId] = useState(null); const [isClicking, setIsClicking] = useState(false); const [isEmbedded, setIsEmbedded] = useState(false); useEffect(() => { console.log('[RecordModeProvider] Mounted with isEnabled:', isEnabled); }, [isEnabled]); useEffect(() => { if (!isEnabled) return; const embedded = typeof window !== 'undefined' && (window.location.search.includes('embedded=true') || window.name === 'record-mode-iframe' || window.self !== window.top); setIsEmbedded(embedded); }, [isEnabled]); const setIsActive = (active: boolean) => { if (!isEnabled) return; setIsActiveState(active); if (active) setIsFeedbackActiveState(false); }; const setIsFeedbackActive = (active: boolean) => { setIsFeedbackActiveState(active); if (active && isEnabled) setIsActiveState(false); }; const isPlayingRef = useRef(false); const isLoadedRef = useRef(false); useEffect(() => { if (!isEnabled) return; const savedEvents = localStorage.getItem('klz-record-events'); const savedActive = localStorage.getItem('klz-record-active'); if (savedEvents) setEvents(JSON.parse(savedEvents)); if (savedActive) setIsActive(JSON.parse(savedActive)); isLoadedRef.current = true; }, [isEnabled]); useEffect(() => { if (!isEnabled || !isLoadedRef.current) return; localStorage.setItem('klz-record-events', JSON.stringify(events)); }, [events, isEnabled]); useEffect(() => { if (!isEnabled) return; localStorage.setItem('klz-record-active', JSON.stringify(isActive)); }, [isActive, isEnabled]); useEffect(() => { if (!isEnabled) return; if (isEmbedded) { const handlePlaybackMessage = (e: MessageEvent) => { if (e.data.type === 'PLAY_EVENT') { const { event } = e.data; const el = event.selector ? (document.querySelector(event.selector) as HTMLElement) : null; if (el) { if (event.type === 'scroll') { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } else if (event.type === 'mouse') { const currentRect = el.getBoundingClientRect(); let targetX = currentRect.left + currentRect.width / 2; let targetY = currentRect.top + currentRect.height / 2; if (event.clickOrigin === 'top-left') { targetX = currentRect.left + 5; targetY = currentRect.top + 5; } else if (event.clickOrigin === 'top-right') { targetX = currentRect.right - 5; targetY = currentRect.top + 5; } else if (event.clickOrigin === 'bottom-left') { targetX = currentRect.left + 5; targetY = currentRect.bottom - 5; } else if (event.clickOrigin === 'bottom-right') { targetX = currentRect.right - 5; targetY = currentRect.bottom - 5; } const eventCoords = { clientX: targetX, clientY: targetY }; const dispatchMouse = (type: string) => { el.dispatchEvent( new MouseEvent(type, { view: window, bubbles: true, cancelable: true, ...eventCoords, }), ); }; if (event.interactionType === 'click') { setIsClicking(true); dispatchMouse('mousedown'); setTimeout(() => { dispatchMouse('mouseup'); if (event.realClick) { dispatchMouse('click'); el.click(); } setIsClicking(false); }, 150); } else { dispatchMouse('mousemove'); dispatchMouse('mouseover'); dispatchMouse('mouseenter'); } } } } }; window.addEventListener('message', handlePlaybackMessage); return () => window.removeEventListener('message', handlePlaybackMessage); } }, [isEmbedded, isEnabled]); useEffect(() => { if (!isEnabled || isEmbedded || !isActive) return; const event = events.find((e) => e.id === hoveredEventId); const iframe = document.querySelector('iframe[name="record-mode-iframe"]') as HTMLIFrameElement; if (iframe?.contentWindow) { iframe.contentWindow.postMessage( { type: 'SET_HOVER_SELECTOR', selector: event?.selector || null }, '*', ); } }, [hoveredEventId, events, isActive, isEmbedded, isEnabled]); const addEvent = (event: Omit) => { if (!isEnabled) return; const newEvent: RecordEvent = { realClick: false, ...event, id: Math.random().toString(36).substr(2, 9), timestamp: Date.now(), }; setEvents((prev) => [...prev, newEvent]); }; const updateEvent = (id: string, updatedFields: Partial) => { if (!isEnabled) return; setEvents((prev) => prev.map((event) => (event.id === id ? { ...event, ...updatedFields } : event)), ); }; const reorderEvents = (startIndex: number, endIndex: number) => { if (!isEnabled) return; const result = Array.from(events); const [removed] = result.splice(startIndex, 1); result.splice(endIndex, 0, removed); setEvents(result); }; const removeEvent = (id: string) => { if (!isEnabled) return; setEvents((prev) => prev.filter((event) => event.id !== id)); }; const clearEvents = () => { if (!isEnabled) return; if (confirm('Clear all recorded events?')) setEvents([]); }; const currentSession: RecordingSession | null = events.length > 0 ? { id: 'draft', name: 'Draft Session', events, createdAt: new Date().toISOString(), } : null; const saveSession = (name: string) => { if (!isEnabled) return; console.log('Saving session:', name, events); }; const playEvents = async () => { if (!isEnabled || events.length === 0 || isPlayingRef.current) return; setIsPlaying(true); isPlayingRef.current = true; const sortedEvents = [...events].sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0)); for (const event of sortedEvents) { if (!isPlayingRef.current) break; if (event.rect && !isEmbedded) { const iframe = document.querySelector( 'iframe[name="record-mode-iframe"]', ) as HTMLIFrameElement; const iframeRect = iframe?.getBoundingClientRect(); setCursorPosition({ x: (iframeRect?.left || 0) + event.rect.x + event.rect.width / 2, y: (iframeRect?.top || 0) + event.rect.y + event.rect.height / 2, }); } if (event.selector) { if (!isEmbedded) { const iframe = document.querySelector( 'iframe[name="record-mode-iframe"]', ) as HTMLIFrameElement; if (iframe?.contentWindow) iframe.contentWindow.postMessage({ type: 'PLAY_EVENT', event }, '*'); } else { const el = document.querySelector(event.selector) as HTMLElement; if (el) { if (event.type === 'scroll') el.scrollIntoView({ behavior: 'smooth', block: 'center' }); else if (event.type === 'mouse') { const currentRect = el.getBoundingClientRect(); let targetX = currentRect.left + currentRect.width / 2; let targetY = currentRect.top + currentRect.height / 2; if (event.clickOrigin === 'top-left') { targetX = currentRect.left + 5; targetY = currentRect.top + 5; } else if (event.clickOrigin === 'top-right') { targetX = currentRect.right - 5; targetY = currentRect.top + 5; } else if (event.clickOrigin === 'bottom-left') { targetX = currentRect.left + 5; targetY = currentRect.bottom - 5; } else if (event.clickOrigin === 'bottom-right') { targetX = currentRect.right - 5; targetY = currentRect.bottom - 5; } const eventCoords = { clientX: targetX, clientY: targetY }; const dispatchMouse = (type: string) => { el.dispatchEvent( new MouseEvent(type, { view: window, bubbles: true, cancelable: true, ...eventCoords, }), ); }; if (event.interactionType === 'click') { setIsClicking(true); dispatchMouse('mousedown'); setTimeout(() => { dispatchMouse('mouseup'); if (event.realClick) { dispatchMouse('click'); el.click(); } setIsClicking(false); }, 150); } else { dispatchMouse('mousemove'); dispatchMouse('mouseover'); dispatchMouse('mouseenter'); } } } } } if (event.zoom) setZoomLevel(event.zoom); if (event.motionBlur) setIsBlurry(true); await new Promise((resolve) => setTimeout(resolve, event.duration || 1000)); setIsBlurry(false); } setIsPlaying(false); isPlayingRef.current = false; setZoomLevel(1); }; const stopPlayback = () => { setIsPlaying(false); isPlayingRef.current = false; setZoomLevel(1); setIsBlurry(false); }; return ( {children} ); }