'use client' import { useState } from 'react' import { Search, UserPlus, Mail } from 'lucide-react' import { toast } from 'sonner' import { trpc } from '@/lib/trpc/client' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Slider } from '@/components/ui/slider' import { Textarea } from '@/components/ui/textarea' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' interface AddMemberDialogProps { juryGroupId: string open: boolean onOpenChange: (open: boolean) => void } export function AddMemberDialog({ juryGroupId, open, onOpenChange }: AddMemberDialogProps) { const [tab, setTab] = useState<'search' | 'invite'>('search') // Search existing user state const [searchQuery, setSearchQuery] = useState('') const [selectedUserId, setSelectedUserId] = useState('') const [maxAssignments, setMaxAssignments] = useState('') const [capMode, setCapMode] = useState('') const [role, setRole] = useState('MEMBER') const [startupRatio, setStartupRatio] = useState(null) const [availabilityNotes, setAvailabilityNotes] = useState('') // Invite new user state const [inviteName, setInviteName] = useState('') const [inviteEmail, setInviteEmail] = useState('') const [inviteMaxAssignments, setInviteMaxAssignments] = useState('') const [inviteCapMode, setInviteCapMode] = useState('') const [inviteRole, setInviteRole] = useState('MEMBER') const [inviteStartupRatio, setInviteStartupRatio] = useState(null) const [inviteAvailabilityNotes, setInviteAvailabilityNotes] = useState('') const [inviteExpertise, setInviteExpertise] = useState('') const utils = trpc.useUtils() const { data: userResponse, isLoading: isSearching } = trpc.user.list.useQuery( { search: searchQuery, perPage: 20 }, { enabled: searchQuery.length > 0 } ) const users = userResponse?.users || [] const { mutate: addMember, isPending: isAdding } = trpc.juryGroup.addMember.useMutation({ onSuccess: () => { utils.juryGroup.getById.invalidate({ id: juryGroupId }) toast.success('Member added successfully') onOpenChange(false) resetForm() }, onError: (err) => { toast.error(err.message) }, }) const { mutate: createUser, isPending: isCreating } = trpc.user.create.useMutation({ onSuccess: (newUser) => { // Immediately add the newly created user to the jury group addMember({ juryGroupId, userId: newUser.id, role: inviteRole as 'CHAIR' | 'MEMBER' | 'OBSERVER', maxAssignmentsOverride: inviteMaxAssignments ? parseInt(inviteMaxAssignments, 10) : null, capModeOverride: inviteCapMode && inviteCapMode !== 'DEFAULT' ? (inviteCapMode as 'HARD' | 'SOFT' | 'NONE') : null, preferredStartupRatio: inviteStartupRatio, availabilityNotes: inviteAvailabilityNotes.trim() || null, }) // Send invitation email sendInvitation({ userId: newUser.id, juryGroupId }) }, onError: (err) => { toast.error(err.message) }, }) const { mutate: sendInvitation } = trpc.user.sendInvitation.useMutation({ onSuccess: (result) => { toast.success(`Invitation sent to ${result.email}`) utils.user.list.invalidate() }, onError: (err) => { // Don't block — user was created and added, just invitation failed toast.error(`Member added but invitation email failed: ${err.message}`) }, }) const resetForm = () => { setSearchQuery('') setSelectedUserId('') setMaxAssignments('') setCapMode('') setRole('MEMBER') setStartupRatio(null) setAvailabilityNotes('') setInviteName('') setInviteEmail('') setInviteMaxAssignments('') setInviteCapMode('') setInviteRole('MEMBER') setInviteStartupRatio(null) setInviteAvailabilityNotes('') setInviteExpertise('') } const handleSearchSubmit = (e: React.FormEvent) => { e.preventDefault() if (!selectedUserId) { toast.error('Please select a user') return } addMember({ juryGroupId, userId: selectedUserId, role: role as 'CHAIR' | 'MEMBER' | 'OBSERVER', maxAssignmentsOverride: maxAssignments ? parseInt(maxAssignments, 10) : null, capModeOverride: capMode && capMode !== 'DEFAULT' ? (capMode as 'HARD' | 'SOFT' | 'NONE') : null, preferredStartupRatio: startupRatio, availabilityNotes: availabilityNotes.trim() || null, }) } const handleInviteSubmit = (e: React.FormEvent) => { e.preventDefault() if (!inviteEmail.trim()) { toast.error('Please enter an email address') return } const expertiseTags = inviteExpertise .split(',') .map((t) => t.trim()) .filter(Boolean) createUser({ email: inviteEmail.trim(), name: inviteName.trim() || undefined, role: 'JURY_MEMBER', expertiseTags: expertiseTags.length > 0 ? expertiseTags : undefined, maxAssignments: inviteMaxAssignments ? parseInt(inviteMaxAssignments, 10) : undefined, }) } const isPending = isAdding || isCreating return ( Add Member to Jury Group Search for an existing user or invite a new juror to the platform setTab(v as 'search' | 'invite')}> Search Existing Invite New {/* Search existing user tab */}
setSearchQuery(e.target.value)} />
{isSearching && (

Searching...

)} {users && users.length > 0 && (
{users.map((user) => ( ))}
)}
setMaxAssignments(e.target.value)} />
Startup setStartupRatio(v / 100)} min={0} max={100} step={10} className="flex-1" /> Concept

{startupRatio !== null ? `~${Math.round(startupRatio * 100)}% startups / ~${Math.round((1 - startupRatio) * 100)}% concepts` : 'No preference set (balanced distribution)'}