import { useQuery } from "@tanstack/react-query"; import { Plus, Search } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { Tldraw } from "tldraw"; import { NewSpaceDialog } from "@/components/new-space-dialog"; import { SpaceActions } from "@/components/space-actions"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter, CardHeader, } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { listUserSpaceSnapshots } from "@/lib/appwrite-db"; import { authClient } from "@/lib/auth-client"; type SpaceCard = { id: string; name: string; lastEdited: string; snapshotText: string; itemCount: number; color: string; }; export function SpacesGrid() { const { data: session } = authClient.useSession(); const spacesQuery = useQuery({ queryKey: ["spaces", session?.$id], queryFn: async () => { if (!session?.$id) { return [] as SpaceCard[]; } const rows = await listUserSpaceSnapshots(session.$id); return rows.map(({ row, snapshot }) => { const snapshotText = snapshot ? JSON.stringify(snapshot) : (row.snapshot ?? ""); const itemCount = (() => { try { const doc = snapshot?.document as unknown as | { store?: { records?: Record }; records?: Record; } | undefined; const recs = doc?.store?.records ?? doc?.records; return recs ? Object.keys(recs).length : 0; } catch { return 0; } })(); return { id: row.spaceId, name: row.title || row.spaceId, lastEdited: row.$updatedAt ? new Date(row.$updatedAt).toLocaleString() : "", snapshotText, itemCount, // Use actual color from DB or fallback to color variety color: row.color || "#3b82f6", // Default to blue if no color } satisfies SpaceCard; }); }, staleTime: 10_000, }); const spaces = spacesQuery.data ?? []; // Use an uncontrolled input and a ref-based debounce to avoid rerendering // on every keystroke. We only update `debouncedSearchTerm` after the // debounce delay. const inputRef = useRef(null); const timerRef = useRef(null); const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(""); const DEBOUNCE_MS = 200; useEffect(() => { return () => { if (timerRef.current) { clearTimeout(timerRef.current); } }; }, []); const filteredSpaces = useMemo(() => { const q = debouncedSearchTerm.trim().toLowerCase(); if (!q) { return spaces; } return spaces.filter((s) => { return s.name.toLowerCase().includes(q); }); }, [spaces, debouncedSearchTerm]); let badgeText = ""; if (spacesQuery.isLoading) { badgeText = "Loading…"; } else if (debouncedSearchTerm) { badgeText = `${filteredSpaces.length} of ${spaces.length} spaces`; } else { badgeText = `${spaces.length} spaces`; } return (
{/* Header */}

Your Spaces

Organize your thoughts and ideas in visual workspaces

spacesQuery.refetch()} />
{/* Search and Filters */}
{ if (timerRef.current) { clearTimeout(timerRef.current); } // Use window.setTimeout so the returned id is a number timerRef.current = window.setTimeout(() => { const value = inputRef.current?.value ?? ""; setDebouncedSearchTerm(value); timerRef.current = null; }, DEBOUNCE_MS); }} placeholder="Search spaces..." ref={inputRef} />
{badgeText}
{/* Spaces Grid */}
{filteredSpaces.map((space) => { const snapshot = space.snapshotText ? JSON.parse(space.snapshotText).document : undefined; return ( { window.location.href = `/space?id=${space.id}`; }} >

{space.name}

spacesQuery.refetch()} onUpdated={() => spacesQuery.refetch()} space={space} userId={session?.$id} />
{ editor.updateInstanceState({ isReadonly: true }); }} snapshot={snapshot} />
{ Object.keys(snapshot?.store || {}).filter((key) => key.startsWith("shape:") ).length }{" "} items Edited {space.lastEdited} ); })}
{/* Empty State (when no spaces exist) */} {!spacesQuery.isLoading && filteredSpaces.length === 0 && (

No spaces yet

Create your first space to start organizing your thoughts and ideas visually.

spacesQuery.refetch()} triggerButton={ } />
)}
); } // Inlined action/dialog components moved to dedicated files for reuse