"use client"; import { useState, useEffect, forwardRef, useImperativeHandle, useCallback } from "react"; import { getConversations, type Idea } from "@/lib/api/ideas"; import { useAuth } from "@/lib/auth/auth-context"; type ConversationSummary = { id: string; title: string | null; projectId: string | null; updatedAt: string; messageCount: number; }; export interface ConversationSidebarRef { refresh: () => Promise; addConversation: (conversation: ConversationSummary) => void; } interface ConversationSidebarProps { isOpen: boolean; onClose: () => void; currentConversationId: string | null; onSelectConversation: (conversationId: string | null) => Promise; onNewConversation: (projectId?: string | null) => void; } export const ConversationSidebar = forwardRef(function ConversationSidebar({ isOpen, onClose, currentConversationId, onSelectConversation, onNewConversation, }, ref) { const [searchQuery, setSearchQuery] = useState(""); const [conversations, setConversations] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const { user } = useAuth(); /** * Convert Idea to ConversationSummary */ const ideaToConversation = useCallback((idea: Idea): ConversationSummary => { // Count messages from the stored JSON content let messageCount = 0; try { const messages = JSON.parse(idea.content); messageCount = Array.isArray(messages) ? messages.length : 0; } catch { // If parsing fails, assume 0 messages messageCount = 0; } return { id: idea.id, title: idea.title, projectId: idea.projectId, updatedAt: idea.updatedAt, messageCount, }; }, []); /** * Fetch conversations from backend */ const fetchConversations = useCallback(async (): Promise => { if (!user) { setConversations([]); return; } try { setIsLoading(true); setError(null); const response = await getConversations({ limit: 50, page: 1, }); const summaries = response.data.map(ideaToConversation); setConversations(summaries); } catch (err) { const errorMsg = err instanceof Error ? err.message : "Failed to load conversations"; setError(errorMsg); console.error("Error fetching conversations:", err); } finally { setIsLoading(false); } }, [user, ideaToConversation]); // Load conversations on mount and when user changes useEffect(() => { void fetchConversations(); }, [fetchConversations]); // Expose methods to parent via ref useImperativeHandle(ref, () => ({ refresh: async () => { await fetchConversations(); }, addConversation: (conversation: ConversationSummary) => { setConversations((prev) => [conversation, ...prev]); }, })); const filteredConversations = conversations.filter((conv) => { if (!searchQuery.trim()) return true; const title = conv.title || "Untitled conversation"; return title.toLowerCase().includes(searchQuery.toLowerCase()); }); const formatRelativeTime = (dateString: string): string => { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return "Just now"; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString(undefined, { month: "short", day: "numeric" }); }; const truncateTitle = (title: string | null, maxLength = 32): string => { const displayTitle = title || "Untitled conversation"; if (displayTitle.length <= maxLength) return displayTitle; return displayTitle.substring(0, maxLength - 1) + "…"; }; return ( <> {/* Mobile overlay */} {isOpen && (