"use client"; import { useState, useRef, useCallback, useEffect, forwardRef, useImperativeHandle } from "react"; import { useTranslations } from "next-intl"; import { X } from "lucide-react"; import { Input } from "@/components/ui/input"; import { useContactStore } from "@/stores/contact-store"; interface Participant { name: string; email: string; } interface ParticipantInputProps { participants: Participant[]; onAdd: (participant: Participant) => void; onRemove: (email: string) => void; disabled?: boolean; } export interface ParticipantInputHandle { flush: () => Participant | null; } const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; export const ParticipantInput = forwardRef(function ParticipantInput({ participants, onAdd, onRemove, disabled }, ref) { const t = useTranslations("calendar.participants"); const [query, setQuery] = useState(""); const [suggestions, setSuggestions] = useState([]); const [activeIndex, setActiveIndex] = useState(-1); const [showSuggestions, setShowSuggestions] = useState(false); const inputRef = useRef(null); const listRef = useRef(null); const debounceRef = useRef>(undefined); const updateSuggestions = useCallback((q: string) => { if (q.length < 2) { setSuggestions([]); setShowSuggestions(false); return; } const results = useContactStore.getState().getAutocomplete(q); const existing = new Set(participants.map(p => p.email.toLowerCase())); const filtered = results.filter(r => !existing.has(r.email.toLowerCase())); setSuggestions(filtered.slice(0, 8)); setShowSuggestions(filtered.length > 0); setActiveIndex(-1); }, [participants]); const handleInputChange = useCallback((value: string) => { setQuery(value); if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => updateSuggestions(value), 200); }, [updateSuggestions]); useEffect(() => { return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, []); const addParticipant = useCallback((p: Participant) => { if (!p.email || !EMAIL_REGEX.test(p.email)) return; if (participants.some(e => e.email.toLowerCase() === p.email.toLowerCase())) return; onAdd(p); setQuery(""); setSuggestions([]); setShowSuggestions(false); inputRef.current?.focus(); }, [participants, onAdd]); const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === "ArrowDown" && showSuggestions) { e.preventDefault(); setActiveIndex(i => Math.min(i + 1, suggestions.length - 1)); } else if (e.key === "ArrowUp" && showSuggestions) { e.preventDefault(); setActiveIndex(i => Math.max(i - 1, 0)); } else if (e.key === "Enter") { e.preventDefault(); if (activeIndex >= 0 && activeIndex < suggestions.length) { addParticipant(suggestions[activeIndex]); } else if (query.trim() && EMAIL_REGEX.test(query.trim())) { addParticipant({ name: "", email: query.trim() }); } } else if (e.key === "Escape") { setShowSuggestions(false); } }, [showSuggestions, activeIndex, suggestions, query, addParticipant]); const handleBlur = useCallback(() => { const trimmed = query.trim(); if (trimmed && EMAIL_REGEX.test(trimmed)) { addParticipant({ name: "", email: trimmed }); } setTimeout(() => setShowSuggestions(false), 200); }, [query, addParticipant]); useImperativeHandle(ref, () => ({ flush: () => { const trimmed = query.trim(); if (!trimmed || !EMAIL_REGEX.test(trimmed)) return null; if (participants.some(e => e.email.toLowerCase() === trimmed.toLowerCase())) return null; const p = { name: "", email: trimmed }; onAdd(p); setQuery(""); setSuggestions([]); setShowSuggestions(false); return p; }, }), [query, participants, onAdd]); return (
handleInputChange(e.target.value)} onKeyDown={handleKeyDown} onFocus={() => { if (suggestions.length > 0) setShowSuggestions(true); }} onBlur={handleBlur} placeholder={t("email_placeholder")} disabled={disabled} role="combobox" aria-expanded={showSuggestions} aria-controls="participant-suggestions" aria-activedescendant={activeIndex >= 0 ? `suggestion-${activeIndex}` : undefined} aria-autocomplete="list" /> {showSuggestions && (
    {suggestions.map((s, i) => (
  • { e.preventDefault(); addParticipant(s); }} className={`px-3 py-2 text-sm cursor-pointer transition-colors ${ i === activeIndex ? "bg-accent text-accent-foreground" : "hover:bg-muted" }`} >
    {s.name || s.email}
    {s.name && (
    {s.email}
    )}
  • ))}
)}
{participants.length > 0 && (
{participants.map((p, i) => ( {!disabled ? ( ) : ( {p.name || p.email} )} {!disabled && ( )} ))}
)}
); });