Files
dumpsterChat/web/src/components/NewConversationModal.tsx
T

100 lines
3.4 KiB
TypeScript

import { useState, useMemo } from "react";
import { useConversationStore } from "../stores/conversation.ts";
import { useMemberStore } from "../stores/member.ts";
import { useServerStore } from "../stores/server.ts";
import { useAuthStore } from "../stores/auth.ts";
interface NewConversationModalProps {
onClose: () => void;
}
export function NewConversationModal({ onClose }: NewConversationModalProps) {
const [query, setQuery] = useState("");
const [selected, setSelected] = useState<string[]>([]);
const createConversation = useConversationStore((s) => s.createConversation);
const activeServerId = useServerStore((s) => s.activeServerId);
const membersByServer = useMemberStore((s) => s.membersByServer);
const currentUserId = useAuthStore((s) => s.user?.id);
const members = useMemo(() => {
const map = new Map<string, any>();
if (activeServerId && membersByServer[activeServerId]) {
membersByServer[activeServerId].forEach((m) => map.set(m.id, m));
} else {
// Global search if no active server
Object.values(membersByServer).forEach((list) => {
list.forEach((m) => map.set(m.id, m));
});
}
return Array.from(map.values());
}, [activeServerId, membersByServer]);
const filtered = useMemo(() => {
const q = query.toLowerCase();
return members.filter(
(m) =>
m.id !== currentUserId &&
(m.username.toLowerCase().includes(q) ||
(m.display_name || "").toLowerCase().includes(q)),
);
}, [members, query, currentUserId]);
const toggle = (id: string) => {
setSelected((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id],
);
};
const handleCreate = async () => {
if (selected.length === 0) return;
await createConversation(selected);
onClose();
};
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" onClick={onClose}>
<div
className="bg-gb-bg border border-gb-bg-t p-4 min-w-[300px] font-mono"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-3">
<span className="text-sm text-gb-orange">NEW DIRECT MESSAGE</span>
<button onClick={onClose} className="text-xs text-gb-red">[x]</button>
</div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="search members..."
className="terminal-input w-full mb-3 text-sm"
/>
<div className="max-h-48 overflow-y-auto space-y-1 mb-3">
{filtered.map((m) => (
<button
key={m.id}
onClick={() => toggle(m.id)}
className={`w-full text-left px-2 py-1 text-sm ${
selected.includes(m.id)
? "bg-gb-aqua text-gb-bg"
: "hover:bg-gb-bg-t text-gb-fg-s"
}`}
>
{m.username}
</button>
))}
{filtered.length === 0 && (
<p className="text-gb-fg-f text-xs">[no members found]</p>
)}
</div>
<button
onClick={handleCreate}
disabled={selected.length === 0}
className="w-full px-3 py-1.5 bg-gb-orange text-gb-bg text-xs font-mono hover:bg-gb-yellow transition-colors disabled:opacity-50"
>
[START CONVERSATION]
</button>
</div>
</div>
);
}