sync: phase 1 backend + frontend from server
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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 = activeServerId ? membersByServer[activeServerId] || [] : [];
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user