101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { useConversationStore } from "../stores/conversation.ts";
|
|
import { useAuthStore } from "../stores/auth.ts";
|
|
import { NewConversationModal } from "./NewConversationModal.tsx";
|
|
|
|
function otherMemberName(conv: { members: { id: string; username: string }[] }, currentUserId: string) {
|
|
const other = conv.members.find((m) => m.id !== currentUserId);
|
|
return other?.username || "unknown";
|
|
}
|
|
|
|
function isSelfDM(conv: { members: { id: string }[] }, currentUserId: string) {
|
|
return conv.members.length === 1 && conv.members[0].id === currentUserId;
|
|
}
|
|
|
|
export function ConversationList() {
|
|
const conversations = useConversationStore((s) => s.conversations);
|
|
const activeId = useConversationStore((s) => s.activeConversationId);
|
|
const fetchConversations = useConversationStore((s) => s.fetchConversations);
|
|
const setActive = useConversationStore((s) => s.setActiveConversation);
|
|
const createConversation = useConversationStore((s) => s.createConversation);
|
|
const currentUser = useAuthStore((s) => s.user);
|
|
const [showNew, setShowNew] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetchConversations();
|
|
}, [fetchConversations]);
|
|
|
|
const [notesError, setNotesError] = useState<string | null>(null);
|
|
|
|
const openNotes = async () => {
|
|
setNotesError(null);
|
|
// Find existing self-DM or create one
|
|
const existing = conversations.find((c) => isSelfDM(c, currentUser?.id || ""));
|
|
if (existing) {
|
|
setActive(existing.id);
|
|
return;
|
|
}
|
|
try {
|
|
await createConversation([]);
|
|
} catch (err) {
|
|
console.error('Failed to create notes:', err);
|
|
setNotesError(err instanceof Error ? err.message : 'Failed to create notes');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="h-full w-56 bg-gb-bg-s border-r border-gb-bg-t flex flex-col">
|
|
<div className="terminal-border border-t-0 border-x-0 px-3 py-2 text-gb-fg truncate flex items-center justify-between">
|
|
<span>[DIRECT MESSAGES]</span>
|
|
<div className="flex gap-1">
|
|
<button
|
|
onClick={openNotes}
|
|
className="terminal-button text-xs"
|
|
title="Notes to self"
|
|
>
|
|
[📝]
|
|
</button>
|
|
<button
|
|
onClick={() => setShowNew(true)}
|
|
className="terminal-button text-xs"
|
|
title="New conversation"
|
|
>
|
|
[+]
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 overflow-y-auto p-2 font-mono text-sm">
|
|
{conversations.length === 0 && (
|
|
<p className="text-gb-fg-f">[no conversations]</p>
|
|
)}
|
|
{conversations.map((conv) => {
|
|
const self = isSelfDM(conv, currentUser?.id || "");
|
|
const name = self
|
|
? "Notes"
|
|
: conv.type === "group_dm"
|
|
? conv.name || conv.members.map((m) => m.username).join(", ")
|
|
: otherMemberName(conv, currentUser?.id || "");
|
|
return (
|
|
<button
|
|
key={conv.id}
|
|
onClick={() => setActive(conv.id)}
|
|
className={`w-full text-left px-2 py-1 rounded-sm flex items-center gap-2 ${
|
|
conv.id === activeId
|
|
? "terminal-active"
|
|
: "hover:bg-gb-bg-t text-gb-fg-s"
|
|
}`}
|
|
>
|
|
<span className="text-gb-fg-f">{self ? "📝" : "@"}</span>
|
|
<span className="truncate">{name}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
{notesError && (
|
|
<div className="px-3 py-1 text-gb-red text-xs font-mono">ERR: {notesError}</div>
|
|
)}
|
|
{showNew && <NewConversationModal onClose={() => setShowNew(false)} />}
|
|
</div>
|
|
);
|
|
}
|