"use client"; // Force rebuild - v3 import { useEffect, useState } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Plus, Sparkles, Loader2, MoreVertical, Trash2 } from "lucide-react"; import Link from "next/link"; import { useParams } from "next/navigation"; import { db, auth } from "@/lib/firebase/config"; import { collection, query, where, orderBy, getDocs } from "firebase/firestore"; import { ProjectCreationModal } from "@/components/project-creation-modal"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { toast } from "sonner"; interface ProjectWithStats { id: string; name: string; slug: string; productName: string; productVision?: string; workspacePath?: string; status?: string; createdAt: { toDate?: () => Date } | Date | string | number | null; updatedAt: { toDate?: () => Date } | Date | string | number | null; stats: { sessions: number; costs: number; }; } export default function ProjectsPage() { const params = useParams(); const workspace = params.workspace as string; const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [showCreationModal, setShowCreationModal] = useState(false); const [projectToDelete, setProjectToDelete] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const fetchProjects = async (user: { uid: string }) => { try { // Fetch projects for this user const projectsRef = collection(db, 'projects'); const projectsQuery = query( projectsRef, where('userId', '==', user.uid), orderBy('createdAt', 'desc') ); const projectsSnapshot = await getDocs(projectsQuery); // Fetch sessions to calculate stats const sessionsRef = collection(db, 'sessions'); const sessionsQuery = query( sessionsRef, where('userId', '==', user.uid) ); const sessionsSnapshot = await getDocs(sessionsQuery); // Group sessions by project const sessionsByProject = new Map(); sessionsSnapshot.docs.forEach(doc => { const session = doc.data(); const projectId = session.projectId || 'unassigned'; const existing = sessionsByProject.get(projectId) || { count: 0, totalCost: 0 }; sessionsByProject.set(projectId, { count: existing.count + 1, totalCost: existing.totalCost + (session.cost || 0), }); }); // Map projects with stats const projectsWithStats: ProjectWithStats[] = projectsSnapshot.docs.map(doc => { const project = doc.data(); const stats = sessionsByProject.get(doc.id) || { count: 0, totalCost: 0 }; return { id: doc.id, name: project.name, slug: project.slug, productName: project.productName, productVision: project.productVision, workspacePath: project.workspacePath, status: project.status || 'active', createdAt: project.createdAt, updatedAt: project.updatedAt, stats: { sessions: stats.count, costs: stats.totalCost, }, }; }); setProjects(projectsWithStats); } catch (err: unknown) { console.error('Error fetching projects:', err); setError(err instanceof Error ? err.message : 'Unknown error'); } finally { setLoading(false); } }; useEffect(() => { const unsubscribe = auth.onAuthStateChanged(async (user) => { if (!user) { setError('Not authenticated'); setLoading(false); return; } fetchProjects(user); }); return () => unsubscribe(); }, []); const handleDeleteProject = async () => { if (!projectToDelete) return; setIsDeleting(true); try { const user = auth.currentUser; if (!user) { toast.error('You must be signed in'); return; } const token = await user.getIdToken(); const response = await fetch('/api/projects/delete', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ projectId: projectToDelete.id, }), }); if (response.ok) { const data = await response.json(); toast.success('Project deleted successfully', { description: data.sessionsPreserved > 0 ? `${data.sessionsPreserved} sessions preserved and can be reassigned` : undefined }); // Remove from local state setProjects(projects.filter(p => p.id !== projectToDelete.id)); setProjectToDelete(null); } else { const error = await response.json(); toast.error(error.error || 'Failed to delete project'); } } catch (error) { console.error('Error deleting project:', error); toast.error('An error occurred while deleting'); } finally { setIsDeleting(false); } }; const getTimeAgo = (timestamp: { toDate?: () => Date } | Date | string | number | null | undefined) => { if (!timestamp) return 'Recently'; let date: Date; if (typeof timestamp === 'object' && timestamp !== null && 'toDate' in timestamp && timestamp.toDate) { date = timestamp.toDate(); } else { date = new Date(timestamp as string | number | Date); } const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000); if (seconds < 60) return 'Just now'; if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`; if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`; if (seconds < 2592000) return `${Math.floor(seconds / 86400)} days ago`; return date.toLocaleDateString(); }; return ( <> {/* Header */}

Projects

Manage your product development projects

{/* Content */}
{/* Loading State */} {loading && (
)} {/* Error State */} {error && (

Error: {error}

)} {/* Projects Grid */} {!loading && !error && projects.length > 0 && (

Your Projects

{projects.map((project) => { const projectHref = `/${workspace}/project/${project.id}/overview`; return (
📦
{project.productName} {getTimeAgo(project.updatedAt)}
{project.status}
e.preventDefault()}> { e.preventDefault(); setProjectToDelete(project); }} > Delete Project

{project.productVision || 'No description'}

{project.workspacePath && (

📁 {project.workspacePath.split('/').pop()}

)}
Sessions:{" "} {project.stats.sessions}
Costs:{" "} ${project.stats.costs.toFixed(2)}
); })} {/* New Project Card */} setShowCreationModal(true)} >

Create New Project

Start tracking a new product

)} {/* Empty State (show when no projects) */} {!loading && !error && projects.length === 0 && (

No projects yet

Create your first project to start tracking your AI-powered development workflow, manage costs, and maintain living documentation.

)}
{/* Project Creation Modal */} { setShowCreationModal(open); if (!open) { // Refresh projects list when modal closes const user = auth.currentUser; if (user) fetchProjects(user); } }} workspace={workspace} /> {/* Delete Project Confirmation Dialog */} !open && setProjectToDelete(null)}> Are you absolutely sure? This action will delete the project "{projectToDelete?.productName}". All associated sessions and data will be preserved but unlinked from this project. You can reassign them to another project later. Cancel {isDeleting ? ( ) : ( )} Delete Project ); }