import React, { useCallback, useEffect, useRef, useState } from 'react' import axios from 'axios' import ReactMarkdown from 'react-markdown' import EmojiPickerButton from './EmojiPickerButton' /* ── Toolbar icon components ──────────────────────────────────────────────── */ function BoldIcon() { return ( ) } function ItalicIcon() { return ( ) } function CodeIcon() { return ( ) } function LinkIcon() { return ( ) } function ListIcon() { return ( ) } function QuoteIcon() { return ( ) } /* ── Toolbar button wrapper ───────────────────────────────────────────────── */ function ToolbarBtn({ title, onClick, children }) { return ( ) } /* ── Main component ───────────────────────────────────────────────────────── */ export default function CommentForm({ artworkId, onPosted, isLoggedIn = false, loginUrl = '/login', parentId = null, replyTo = null, onCancelReply = null, compact = false, }) { const [content, setContent] = useState('') const [tab, setTab] = useState('write') // 'write' | 'preview' const [submitting, setSubmitting] = useState(false) const [errors, setErrors] = useState([]) const textareaRef = useRef(null) const formRef = useRef(null) // Auto-focus when entering reply mode useEffect(() => { if (replyTo && textareaRef.current) { textareaRef.current.focus() formRef.current?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) } }, [replyTo]) /* ── Helpers to wrap selected text ────────────────────────────────────── */ const wrapSelection = useCallback((before, after) => { const el = textareaRef.current if (!el) return const start = el.selectionStart const end = el.selectionEnd const selected = content.slice(start, end) const replacement = before + (selected || 'text') + after const next = content.slice(0, start) + replacement + content.slice(end) setContent(next) requestAnimationFrame(() => { const cursorPos = selected ? start + replacement.length : start + before.length const cursorEnd = selected ? start + replacement.length : start + before.length + 4 el.selectionStart = cursorPos el.selectionEnd = cursorEnd el.focus() }) }, [content]) const prefixLines = useCallback((prefix) => { const el = textareaRef.current if (!el) return const start = el.selectionStart const end = el.selectionEnd const selected = content.slice(start, end) const lines = selected ? selected.split('\n') : [''] const prefixed = lines.map(l => prefix + l).join('\n') const next = content.slice(0, start) + prefixed + content.slice(end) setContent(next) requestAnimationFrame(() => { el.selectionStart = start el.selectionEnd = start + prefixed.length el.focus() }) }, [content]) const insertLink = useCallback(() => { const el = textareaRef.current if (!el) return const start = el.selectionStart const end = el.selectionEnd const selected = content.slice(start, end) const isUrl = /^https?:\/\//.test(selected) const replacement = isUrl ? `[link](${selected})` : `[${selected || 'link'}](https://)` const next = content.slice(0, start) + replacement + content.slice(end) setContent(next) requestAnimationFrame(() => { if (isUrl) { el.selectionStart = start + 1 el.selectionEnd = start + 5 } else { const urlStart = start + replacement.length - 1 el.selectionStart = urlStart - 8 el.selectionEnd = urlStart - 1 } el.focus() }) }, [content]) // Insert text at cursor (for emoji picker) const insertAtCursor = useCallback((text) => { const el = textareaRef.current if (!el) { setContent((v) => v + text) return } const start = el.selectionStart ?? content.length const end = el.selectionEnd ?? content.length const next = content.slice(0, start) + text + content.slice(end) setContent(next) requestAnimationFrame(() => { el.selectionStart = start + text.length el.selectionEnd = start + text.length el.focus() }) }, [content]) const handleEmojiSelect = useCallback((emoji) => { insertAtCursor(emoji) }, [insertAtCursor]) /* ── Keyboard shortcuts ───────────────────────────────────────────────── */ const handleKeyDown = useCallback((e) => { const mod = e.ctrlKey || e.metaKey if (!mod) return switch (e.key.toLowerCase()) { case 'b': e.preventDefault() wrapSelection('**', '**') break case 'i': e.preventDefault() wrapSelection('*', '*') break case 'k': e.preventDefault() insertLink() break case 'e': e.preventDefault() wrapSelection('`', '`') break default: break } }, [wrapSelection, insertLink]) /* ── Submit ───────────────────────────────────────────────────────────── */ const handleSubmit = useCallback( async (e) => { e.preventDefault() if (!isLoggedIn) { window.location.href = loginUrl return } const trimmed = content.trim() if (!trimmed) return setSubmitting(true) setErrors([]) try { const { data } = await axios.post(`/api/artworks/${artworkId}/comments`, { content: trimmed, parent_id: parentId || null, }) setContent('') setTab('write') onPosted?.(data.data) onCancelReply?.() } catch (err) { if (err.response?.status === 422) { const fieldErrors = err.response.data?.errors ?? {} const allErrors = Object.values(fieldErrors).flat() setErrors(allErrors.length ? allErrors : ['Invalid content.']) } else { setErrors(['Something went wrong. Please try again.']) } } finally { setSubmitting(false) } }, [artworkId, content, isLoggedIn, loginUrl, onPosted, parentId, onCancelReply], ) /* ── Logged-out state ─────────────────────────────────────────────────── */ if (!isLoggedIn) { return (

Sign in {' '} to join the conversation.

) } /* ── Editor ───────────────────────────────────────────────────────────── */ return (
{/* Reply indicator */} {replyTo && (
Replying to {replyTo}
)}
{/* ── Top bar: tabs + emoji ─────────────────────────────────────── */}
{/* Tabs */}
9000 ? 'text-amber-400/80' : 'text-white/20', ].join(' ')} > {content.length > 0 && `${content.length.toLocaleString()}/10,000`}
{/* ── Formatting toolbar (write mode only) ──────────────────────── */} {tab === 'write' && (
wrapSelection('**', '**')}> wrapSelection('*', '*')}> wrapSelection('`', '`')}>
prefixLines('- ')}> prefixLines('> ')}>
)} {/* ── Write tab ─────────────────────────────────────────────────── */} {tab === 'write' && (