"use client"; import type { JSX } from "react"; import { useEffect, useState } from "react"; import { useParams } from "next/navigation"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Code2, FolderOpen, File, ChevronRight, ChevronDown, Search, Loader2, Github, RefreshCw, FileCode } from "lucide-react"; import { auth } from "@/lib/firebase/config"; import { db } from "@/lib/firebase/config"; import { doc, getDoc } from "firebase/firestore"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; interface Project { githubRepo?: string; githubRepoUrl?: string; githubDefaultBranch?: string; } interface FileNode { path: string; name: string; type: 'file' | 'folder'; children?: FileNode[]; size?: number; sha?: string; } interface GitHubFile { path: string; sha: string; size: number; url: string; } export default function CodePage() { const params = useParams(); const projectId = params.projectId as string; const [project, setProject] = useState(null); const [loading, setLoading] = useState(true); const [loadingFiles, setLoadingFiles] = useState(false); const [fileTree, setFileTree] = useState([]); const [expandedFolders, setExpandedFolders] = useState>(new Set(['/'])); const [selectedFile, setSelectedFile] = useState(null); const [fileContent, setFileContent] = useState(null); const [loadingContent, setLoadingContent] = useState(false); const [searchQuery, setSearchQuery] = useState(""); useEffect(() => { fetchProject(); }, [projectId]); const fetchProject = async () => { try { const projectRef = doc(db, "projects", projectId); const projectSnap = await getDoc(projectRef); if (projectSnap.exists()) { const projectData = projectSnap.data() as Project; setProject(projectData); // Auto-load files if GitHub is connected if (projectData.githubRepo) { await fetchFileTree(projectData.githubRepo, projectData.githubDefaultBranch); } } } catch (error) { console.error("Error fetching project:", error); toast.error("Failed to load project"); } finally { setLoading(false); } }; const fetchFileTree = async (repoFullName: string, branch = 'main') => { setLoadingFiles(true); try { const user = auth.currentUser; if (!user) { toast.error("Please sign in"); return; } const token = await user.getIdToken(); const [owner, repo] = repoFullName.split('/'); const response = await fetch( `/api/github/repo-tree?owner=${owner}&repo=${repo}&branch=${branch}`, { headers: { Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { throw new Error("Failed to fetch repository files"); } const data = await response.json(); const tree = buildFileTree(data.files); setFileTree(tree); toast.success(`Loaded ${data.totalFiles} files from ${repoFullName}`); } catch (error) { console.error("Error fetching file tree:", error); toast.error("Failed to load repository files"); } finally { setLoadingFiles(false); } }; const buildFileTree = (files: GitHubFile[]): FileNode[] => { const root: FileNode = { path: '/', name: '/', type: 'folder', children: [], }; files.forEach((file) => { const parts = file.path.split('/'); let currentNode = root; parts.forEach((part, index) => { const isFile = index === parts.length - 1; const fullPath = parts.slice(0, index + 1).join('/'); if (!currentNode.children) { currentNode.children = []; } let childNode = currentNode.children.find(child => child.name === part); if (!childNode) { childNode = { path: fullPath, name: part, type: isFile ? 'file' : 'folder', ...(isFile && { size: file.size, sha: file.sha }), ...(!isFile && { children: [] }), }; currentNode.children.push(childNode); } if (!isFile) { currentNode = childNode; } }); }); // Sort children recursively const sortNodes = (nodes: FileNode[]) => { nodes.sort((a, b) => { if (a.type === b.type) return a.name.localeCompare(b.name); return a.type === 'folder' ? -1 : 1; }); nodes.forEach(node => { if (node.children) { sortNodes(node.children); } }); }; if (root.children) { sortNodes(root.children); } return root.children || []; }; const fetchFileContent = async (filePath: string) => { if (!project?.githubRepo) return; setLoadingContent(true); setSelectedFile(filePath); setFileContent(null); try { const user = auth.currentUser; if (!user) { toast.error("Please sign in"); return; } const token = await user.getIdToken(); const [owner, repo] = project.githubRepo.split('/'); const branch = project.githubDefaultBranch || 'main'; console.log('[Code Page] Fetching file:', filePath); const response = await fetch( `/api/github/file-content?owner=${owner}&repo=${repo}&path=${encodeURIComponent(filePath)}&branch=${branch}`, { headers: { Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { const errorData = await response.json().catch(() => ({})); console.error('[Code Page] Failed to fetch file:', errorData); throw new Error(errorData.error || "Failed to fetch file content"); } const data = await response.json(); console.log('[Code Page] File loaded:', data.name, `(${data.size} bytes)`); setFileContent(data.content); } catch (error) { console.error("Error fetching file content:", error); toast.error(error instanceof Error ? error.message : "Failed to load file content"); setFileContent(`// Error loading file: ${error instanceof Error ? error.message : 'Unknown error'}`); } finally { setLoadingContent(false); } }; const toggleFolder = (path: string) => { const newExpanded = new Set(expandedFolders); if (newExpanded.has(path)) { newExpanded.delete(path); } else { newExpanded.add(path); } setExpandedFolders(newExpanded); }; const renderFileTree = (nodes: FileNode[], level = 0): JSX.Element[] => { return nodes .filter(node => { if (!searchQuery) return true; return node.name.toLowerCase().includes(searchQuery.toLowerCase()); }) .map((node) => (
{node.type === 'folder' && expandedFolders.has(node.path) && node.children && ( renderFileTree(node.children, level + 1) )}
)); }; const formatFileSize = (bytes: number): string => { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; }; if (loading) { return (
); } if (!project?.githubRepo) { return (

Code

No Repository Connected

Connect a GitHub repository in the Context section to view your code here

); } return (
{/* Header */}

Code

{project.githubRepo}
{/* Content */}
{/* File Tree Sidebar */}
setSearchQuery(e.target.value)} className="pl-9" />
{loadingFiles ? (
) : fileTree.length === 0 ? (
No files found
) : ( renderFileTree(fileTree) )}
{/* Code Viewer */}
{selectedFile ? ( <>
{selectedFile}
{loadingContent ? (
) : fileContent ? (
{/* Line Numbers */}
{fileContent.split('\n').map((_, i) => (
{i + 1}
))}
{/* Code Content */}
                      {fileContent}
                    
) : (

Failed to load file content

)}
) : (

Select a file to view its contents

)}
); }