"use client"; /** * Create a group: name it, pick people, done. Bottom drawer on mobile, modal on * desktop — see ResponsiveDialog. * * Members are named by handle, matching the member-management API — search * results already identify people that way, so nothing needs id plumbing. */ import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { Check, Search, X } from "lucide-react"; import { toast } from "sonner"; import { useAuth } from "@/components/auth/auth-provider"; import { useMediaQuery } from "@/components/selection-toolbar/shared"; import { UserAvatar } from "@/components/ui/user-avatar"; import { Button } from "@/components/ui/button"; import { chat } from "@/lib/chat"; import { ResponsiveDialog } from "@/components/chat/responsive-dialog"; type Candidate = { handle: string; name: string; avatar_url: string | null }; export function NewGroupDialog({ onClose }: { onClose: () => void }) { const router = useRouter(); const { user } = useAuth(); const isMobile = useMediaQuery("(max-width: 767px)"); const [title, setTitle] = useState(""); const [query, setQuery] = useState(""); // Results are stored with the query they belong to, so what's displayed can // be derived: a stale page from a previous query simply stops matching, and // nothing has to be cleared from an effect. const [results, setResults] = useState<{ q: string; rows: Candidate[] }>({ q: "", rows: [], }); const [picked, setPicked] = useState>(new Map()); const [busy, setBusy] = useState(false); const q = query.trim(); const searchable = q.length >= 2; // Never offer yourself: you're the group's creator, and the BFF drops the // caller from the member list anyway — so picking yourself would look like it // worked and then silently do nothing. const visible = results.q === q ? results.rows.filter((u) => u.handle !== user?.handle) : []; useEffect(() => { if (!searchable) return; const timer = setTimeout(() => { fetch(`/api/search/users?q=${encodeURIComponent(q)}&limit=15`) .then((r) => r.json()) .then((d: { results?: Candidate[] }) => setResults({ q, rows: d.results ?? [] }), ) .catch(() => setResults({ q, rows: [] })); }, 300); return () => clearTimeout(timer); }, [q, searchable]); const toggle = (u: Candidate) => setPicked((prev) => { const next = new Map(prev); if (next.has(u.handle)) next.delete(u.handle); else next.set(u.handle, u); return next; }); const create = async () => { const name = title.trim(); if (!name) return; setBusy(true); try { const conv = await chat.createGroup(name, [...picked.keys()]); toast.success("Group created"); router.push(`/chats/${conv.id}`); } catch { toast.error("Could not create the group"); setBusy(false); } }; return ( setTitle(e.target.value)} maxLength={120} placeholder="Group name" className="w-full rounded-lg border border-border bg-muted/40 px-3 py-2 text-sm outline-none pointer-coarse:text-base focus:ring-1 focus:ring-primary/40" />
setQuery(e.target.value)} placeholder="Add people" className="flex-1 bg-transparent text-sm outline-none pointer-coarse:text-base placeholder:text-muted-foreground" />
{picked.size > 0 && (
{[...picked.values()].map((u) => ( ))}
)} } footer={ } > {!searchable ? (

Search to add people. You can also add them later.

) : visible.length === 0 ? (

No one found.

) : ( )}
); }