"use client"; import { useState, useEffect, useCallback } from "react"; import { createClient } from "@/lib/supabase/client"; import { useAuth } from "@/hooks/use-auth"; import { cn } from "@/lib/utils"; import type { Contact, Deal, ContactNote, Tag } from "@/types"; import { Phone, Mail, Copy, Check, User, Tag as TagIcon, DollarSign, StickyNote, Plus, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { format } from "date-fns"; interface ContactSidebarProps { contact: Contact | null; } export function ContactSidebar({ contact }: ContactSidebarProps) { const { accountId } = useAuth(); const [copied, setCopied] = useState(false); const [deals, setDeals] = useState([]); const [notes, setNotes] = useState([]); const [tags, setTags] = useState<(Tag & { contact_tag_id: string })[]>([]); const [newNote, setNewNote] = useState(""); const [addingNote, setAddingNote] = useState(false); const fetchContactData = useCallback(async () => { if (!contact) return; const supabase = createClient(); // Fetch deals, notes, and tags in parallel const [dealsRes, notesRes, tagsRes] = await Promise.all([ supabase .from("deals") .select("*, stage:pipeline_stages(*)") .eq("contact_id", contact.id) .order("created_at", { ascending: false }), supabase .from("contact_notes") .select("*") .eq("contact_id", contact.id) .order("created_at", { ascending: false }), supabase .from("contact_tags") .select("id, tag_id, tags(*)") .eq("contact_id", contact.id), ]); if (dealsRes.data) setDeals(dealsRes.data); if (notesRes.data) setNotes(notesRes.data); if (tagsRes.data) { const mapped = tagsRes.data .filter((ct: Record) => ct.tags) .map((ct: Record) => ({ ...(ct.tags as Tag), contact_tag_id: ct.id as string, })); setTags(mapped); } }, [contact]); // Load on contact change. setContactData/setTags run inside async // Supabase callbacks, not synchronously in the effect body. useEffect(() => { // eslint-disable-next-line react-hooks/set-state-in-effect fetchContactData(); }, [fetchContactData]); const handleCopyPhone = useCallback(async () => { if (!contact?.phone) return; await navigator.clipboard.writeText(contact.phone); setCopied(true); setTimeout(() => setCopied(false), 2000); // Dep is the whole `contact` object (not `contact?.phone`) so the // React Compiler's inference agrees with the manual dep list — // fixes the `preserve-manual-memoization` lint error. }, [contact]); const handleAddNote = useCallback(async () => { if (!contact || !newNote.trim()) return; if (!accountId) return; setAddingNote(true); const supabase = createClient(); const { data: { session }, } = await supabase.auth.getSession(); const user = session?.user; const { data, error } = await supabase .from("contact_notes") .insert({ contact_id: contact.id, account_id: accountId, user_id: user?.id, note_text: newNote.trim(), }) .select() .single(); if (!error && data) { setNotes((prev) => [data, ...prev]); setNewNote(""); } setAddingNote(false); }, [contact, newNote, accountId]); if (!contact) { return (

Select a conversation

); } const displayName = contact.name || contact.phone; const initials = displayName.charAt(0).toUpperCase(); return (
{/* Contact Info */}
{contact.avatar_url ? ( {displayName} ) : ( initials )}

{displayName}

{contact.company && (

{contact.company}

)}
{/* Phone */}
{contact.email && (
{contact.email}
)}
{/* Divider */}
{/* Tags */}
Tags
{tags.length === 0 ? (

No tags

) : ( tags.map((tag) => ( {tag.name} )) )}
{/* Divider */}
{/* Active Deals */}
Active Deals
{deals.length === 0 ? (

No deals

) : ( deals.map((deal) => (

{deal.title}

{deal.currency ?? "$"} {deal.value.toLocaleString()} {deal.stage && ( {deal.stage.name} )}
)) )}
{/* Divider */}
{/* Notes */}
Notes