100 lines
3.4 KiB
TypeScript
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>
|
|
);
|
|
}
|