import React, { useState, useEffect, useRef } from 'react'; import { NodeViewWrapper } from '@tiptap/react'; import { Globe, Edit2, Save, X, AlignLeft, AlignCenter, AlignRight, Trash } from 'lucide-react'; import { useEditorProvider } from '@components/Contexts/Editor/EditorContext'; import { getUrlPreview } from '@services/courses/activities'; import Modal from '@components/Objects/StyledElements/Modal/Modal'; import { Input } from '@components/ui/input'; import { Label } from '@components/ui/label'; import { Checkbox } from '@components/ui/checkbox'; import { Button } from '@components/ui/button'; interface EditorContext { isEditable: boolean; [key: string]: any; } interface WebPreviewProps { node: any; updateAttributes: (attrs: any) => void; extension: any; deleteNode?: () => void; } const ALIGNMENTS = [ { value: 'left', label: }, { value: 'center', label: }, { value: 'right', label: }, ]; const WebPreviewComponent: React.FC = ({ node, updateAttributes, deleteNode }) => { const [inputUrl, setInputUrl] = useState(node.attrs.url || ''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [editing, setEditing] = useState(!node.attrs.url); const inputRef = useRef(null); const editorContext = useEditorProvider() as EditorContext; const isEditable = editorContext?.isEditable ?? true; const previewData = { title: node.attrs.title, description: node.attrs.description, og_image: node.attrs.og_image, favicon: node.attrs.favicon, og_type: node.attrs.og_type, og_url: node.attrs.og_url, url: node.attrs.url, }; const alignment = node.attrs.alignment || 'left'; const hasPreview = !!previewData.title; const [buttonLabel, setButtonLabel] = useState(node.attrs.buttonLabel || 'Visit Site'); const [showButton, setShowButton] = useState(node.attrs.showButton !== false); const [openInPopup, setOpenInPopup] = useState(node.attrs.openInPopup || false); const [popupOpen, setPopupOpen] = useState(false); const [modalOpen, setModalOpen] = useState(!node.attrs.url); const fetchPreview = async (url: string) => { setLoading(true); setError(null); try { const res = await getUrlPreview(url); if (!res) throw new Error('Failed to fetch preview'); const data = res; updateAttributes({ ...data, url }); setEditing(false); } catch (err: any) { setError(err.message || 'Error fetching preview'); } finally { setLoading(false); } }; useEffect(() => { if (node.attrs.url && !hasPreview) { fetchPreview(node.attrs.url); } // eslint-disable-next-line }, []); useEffect(() => { if (editing && inputRef.current) { inputRef.current.focus(); } }, [editing]); useEffect(() => { setButtonLabel(node.attrs.buttonLabel || 'Visit Site'); setShowButton(!!node.attrs.showButton); setOpenInPopup(!!node.attrs.openInPopup); }, [node.attrs.buttonLabel, node.attrs.showButton, node.attrs.openInPopup]); useEffect(() => { if (!node.attrs.url) { setEditing(true); setModalOpen(true); } }, [node.attrs.url]); const handleAlignmentChange = (value: string) => { updateAttributes({ alignment: value }); }; const handleEdit = () => { setEditing(true); setInputUrl(node.attrs.url || ''); setModalOpen(true); }; const handleSaveEdit = () => { if (inputUrl && inputUrl !== node.attrs.url) { fetchPreview(inputUrl); } else { setEditing(false); setModalOpen(false); } updateAttributes({ buttonLabel, showButton, openInPopup }); setModalOpen(false); }; const handleCancelEdit = () => { setEditing(false); setInputUrl(node.attrs.url || ''); setError(null); setModalOpen(false); }; const handleDelete = () => { if (typeof deleteNode === 'function') { deleteNode(); } else { updateAttributes({ url: null, title: null, description: null, og_image: null, favicon: null, og_type: null, og_url: null }); } }; // Compute alignment class for CardWrapper let alignClass = 'justify-start'; if (alignment === 'center') alignClass = 'justify-center'; else if (alignment === 'right') alignClass = 'justify-end'; return ( {/* Popup Modal for Embedded Website */} } /> {/* CardWrapper */} {/* PreviewCard */} {/* Floating edit and delete buttons (only if not editing and isEditable) */} {isEditable && !editing && ( )} {/* Modal for editing */} { setModalOpen(open); if (!open) handleCancelEdit(); }} dialogTitle="Edit Web Preview Card" dialogDescription="Update the website preview, button, and display options." minWidth="md" dialogContent={ { e.preventDefault(); handleSaveEdit(); }}> Website URL setInputUrl(e.target.value)} disabled={loading} autoFocus /> Button Options setShowButton(!!checked)} /> Show button {showButton && ( <> setOpenInPopup(!!checked)} /> Open in-app popup (might not work on all websites) Button label setButtonLabel(e.target.value)} placeholder="Button label" className="w-36" /> > )} Alignment {ALIGNMENTS.map(opt => ( handleAlignmentChange(opt.value)} className={`rounded-full px-2 py-1 ${alignment === opt.value ? 'bg-black text-white' : ''}`} > {opt.label} ))} {error && {error}} Cancel Save } /> {/* Only show preview card when not editing */} {hasPreview && !editing && ( <> {previewData.og_image && ( )} {previewData.title} {previewData.description} {previewData.favicon && ( )} {previewData.url} {showButton && previewData.url && ( openInPopup ? ( setPopupOpen(true)} > {buttonLabel || 'Visit Site'} ) : ( {buttonLabel || 'Visit Site'} ) )} {/* Alignment bar in view mode */} {isEditable && ( {/* AlignmentBar */} Align: {ALIGNMENTS.map(opt => ( handleAlignmentChange(opt.value)} title={`Align ${opt.value}`} type="button" className={`flex items-center justify-center border transition-colors duration-150 focus:outline-none focus:ring-2 focus:ring-blue-300 p-1.5 rounded-full text-gray-600 ${alignment === opt.value ? 'bg-gray-600 text-white border-gray-600 hover:bg-gray-700' : 'bg-white border-gray-200 hover:bg-gray-100'} `} > {opt.label} ))} )} > )} ); }; export default WebPreviewComponent;