import { useState, useEffect, useRef } from "react"; import { Dialog, DialogPanel, DialogBackdrop } from "@headlessui/react"; import { UserIcon, XMarkIcon } from "@heroicons/react/20/solid"; import { useSettingsStore , useRTCStore } from "@/hooks/stores"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import { generateNickname } from "@/utils/nicknameGenerator"; import { Button } from "./Button"; type SessionRole = "primary" | "observer" | "queued" | "pending"; interface NicknameModalProps { isOpen: boolean; onSubmit: (nickname: string) => void | Promise; onSkip?: () => void; title?: string; description?: string; isRequired?: boolean; expectedRole?: SessionRole; } export default function NicknameModal({ isOpen, onSubmit, onSkip, title = "Set Your Session Nickname", description = "Add a nickname to help identify your session to other users", isRequired, expectedRole = "observer" }: NicknameModalProps) { const [nickname, setNickname] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); const [generatedNickname, setGeneratedNickname] = useState(""); const inputRef = useRef(null); const { requireSessionNickname } = useSettingsStore(); const { send } = useJsonRpc(); const { rpcDataChannel } = useRTCStore(); const isNicknameRequired = isRequired ?? requireSessionNickname; // Role-based color coding const getRoleColors = (role: SessionRole) => { switch (role) { case "primary": return { bg: "bg-green-100 dark:bg-green-900/30", icon: "text-green-600 dark:text-green-400" }; case "observer": return { bg: "bg-blue-100 dark:bg-blue-900/30", icon: "text-blue-600 dark:text-blue-400" }; case "queued": return { bg: "bg-yellow-100 dark:bg-yellow-900/30", icon: "text-yellow-600 dark:text-yellow-400" }; case "pending": return { bg: "bg-orange-100 dark:bg-orange-900/30", icon: "text-orange-600 dark:text-orange-400" }; default: return { bg: "bg-slate-100 dark:bg-slate-900/30", icon: "text-slate-600 dark:text-slate-400" }; } }; const roleColors = getRoleColors(expectedRole); // Generate nickname when modal opens and RPC is ready useEffect(() => { if (!isOpen || generatedNickname) return; if (rpcDataChannel?.readyState !== "open") return; generateNickname(send).then(nickname => { setGeneratedNickname(nickname); }).catch((error) => { console.error('Backend nickname generation failed:', error); }); }, [isOpen, generatedNickname, rpcDataChannel?.readyState, send]); // Focus input when modal opens useEffect(() => { if (isOpen) { setTimeout(() => { if (inputRef.current) { inputRef.current.focus(); } }, 100); } }, [isOpen]); const validateNickname = (value: string): string | null => { if (value.length < 2) { return "Nickname must be at least 2 characters"; } if (value.length > 30) { return "Nickname must be 30 characters or less"; } if (!/^[a-zA-Z0-9\s\-_.@]+$/.test(value)) { return "Nickname can only contain letters, numbers, spaces, and - _ . @"; } return null; }; const handleSubmit = async (e?: React.FormEvent) => { e?.preventDefault(); // Use generated nickname if input is empty const trimmedNickname = nickname.trim() || generatedNickname; // Validate const validationError = validateNickname(trimmedNickname); if (validationError) { setError(validationError); return; } setIsSubmitting(true); setError(null); try { await onSubmit(trimmedNickname); setNickname(""); setGeneratedNickname(""); // Reset generated nickname after successful submit } catch (error) { setError(error instanceof Error ? error.message : "Failed to set nickname"); setIsSubmitting(false); } }; const handleSkip = () => { if (!isNicknameRequired && onSkip) { onSkip(); setNickname(""); setError(null); setGeneratedNickname(""); // Reset generated nickname when skipping } }; return ( { if (!isNicknameRequired && onSkip) { onSkip(); setNickname(""); setError(null); setGeneratedNickname(""); } }} className="relative z-50" >

{title}

{description}

{!isNicknameRequired && ( )}
{ setNickname(e.target.value); setError(null); }} placeholder={generatedNickname || "e.g., John's Laptop, Office PC, etc."} className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-md bg-white dark:bg-slate-700 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" maxLength={30} />
{error ? (

{error}

) : (

{nickname.trim() === "" && generatedNickname ? `Leave empty to use: ${generatedNickname}` : "2-30 characters, letters, numbers, spaces, and - _ . @ allowed"}

)} {nickname.length}/30
{isNicknameRequired && (

Required: A nickname is required by the administrator to help identify sessions.

)}
); }