diff --git a/src/app/github/analyze/page.tsx b/src/app/github/analyze/page.tsx index cee33db..2bca88a 100644 --- a/src/app/github/analyze/page.tsx +++ b/src/app/github/analyze/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useEffect, useState } from "react" +import { useEffect, useState, Suspense } from "react" import { useRouter, useSearchParams } from "next/navigation" import Link from "next/link" import { Button } from "@/components/ui/button" @@ -44,7 +44,8 @@ interface FileAnalysis { details?: string } -export default function AIAnalysisPage() { +// Component that uses useSearchParams - needs to be wrapped in Suspense +function AIAnalysisContent() { const router = useRouter() const searchParams = useSearchParams() const repoId = searchParams.get('repoId') @@ -318,3 +319,11 @@ export default function AIAnalysisPage() { ) } + +export default function AIAnalysisPage() { + return ( +
Loading analysis...
}> + +
+ ) +} diff --git a/src/app/github/repo/[id]/page.tsx b/src/app/github/repo/[id]/page.tsx deleted file mode 100644 index 4529eef..0000000 --- a/src/app/github/repo/[id]/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import RepoByIdClient from "./repo-client" - -export const dynamic = 'force-dynamic' -export const dynamicParams = true -export function generateStaticParams() { return [] } - -export default function Page({ params, searchParams }: { params: { id: string }, searchParams?: { path?: string } }) { - const id = params.id - const path = (searchParams?.path as string) || "" - return -} diff --git a/src/app/github/repo/[id]/repo-client.tsx b/src/app/github/repo/[id]/repo-client.tsx deleted file mode 100644 index 366b885..0000000 --- a/src/app/github/repo/[id]/repo-client.tsx +++ /dev/null @@ -1,125 +0,0 @@ -"use client" - -import { useEffect, useMemo, useState } from "react" -import Link from "next/link" -import { Button } from "@/components/ui/button" -import { Card, CardContent } from "@/components/ui/card" -import { Input } from "@/components/ui/input" -import { getRepositoryStructure, getRepositoryFileContent, type RepoStructureEntry } from "@/lib/api/github" -import { ArrowLeft, BookText, Clock, Code, FileText, Folder, GitBranch, Search } from "lucide-react" - -export default function RepoByIdClient({ repositoryId, initialPath = "" }: { repositoryId: string, initialPath?: string }) { - const [path, setPath] = useState(initialPath) - const [loading, setLoading] = useState(true) - const [entries, setEntries] = useState([]) - const [fileQuery, setFileQuery] = useState("") - const [readme, setReadme] = useState(null) - - useEffect(() => { - let mounted = true - ;(async () => { - try { - setLoading(true) - const struct = await getRepositoryStructure(repositoryId, path) - if (!mounted) return - setEntries(struct?.structure || []) - // try README at this path - const candidates = ["README.md", "readme.md", "README.MD"] - for (const name of candidates) { - try { - const fp = path ? `${path}/${name}` : name - const content = await getRepositoryFileContent(repositoryId, fp) - if (content?.content) { setReadme(content.content); break } - } catch (_) { /* ignore */ } - } - } finally { - setLoading(false) - } - })() - return () => { mounted = false } - }, [repositoryId, path]) - - const visible = useMemo(() => { - const q = fileQuery.toLowerCase() - if (!q) return entries - return entries.filter(e => e.name.toLowerCase().includes(q)) - }, [entries, fileQuery]) - - const navigateFolder = (name: string) => { - const next = path ? `${path}/${name}` : name - setPath(next) - } - - const goUp = () => { - if (!path) return - const parts = path.split("/") - parts.pop() - setPath(parts.join("/")) - } - - return ( -
-
- - - -

Repository #{repositoryId}

- Attached -
- -
- -
- - setFileQuery(e.target.value)} placeholder="Go to file" className="pl-9 h-8 text-sm bg-white/5 border-white/10 text-white placeholder:text-white/40"/> -
- - -
- - - - {loading && ( -
Loading...
- )} - {!loading && visible.length === 0 && ( -
No entries found.
- )} - {visible.map((e, i) => ( -
e.type === 'directory' ? navigateFolder(e.name) : void 0}> -
- {e.type === 'directory' ? : } -
-
{e.name}
-
- -
-
- ))} -
-
- - - -
README
- {!readme ? ( -
- -

No README found

-

Add a README.md to the repository to show it here.

-
- ) : ( -
{readme}
- )} -
-
-
- ) -} diff --git a/src/app/github/repo/page.tsx b/src/app/github/repo/page.tsx index d589961..131f0bc 100644 --- a/src/app/github/repo/page.tsx +++ b/src/app/github/repo/page.tsx @@ -1,10 +1,33 @@ +"use client" + +import { useEffect, useState, Suspense } from "react" +import { useRouter, useSearchParams } from "next/navigation" import RepoByIdClient from "./repo-client" -export default function Page({ searchParams }: { searchParams?: { id?: string; path?: string } }) { - const id = (searchParams?.id as string) || "" - const path = (searchParams?.path as string) || "" +// Component that uses useSearchParams - needs to be wrapped in Suspense +function RepoPageContent() { + const router = useRouter() + const searchParams = useSearchParams() + const [repositoryId, setRepositoryId] = useState("") + const [initialPath, setInitialPath] = useState("") + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + const id = searchParams.get('id') + const path = searchParams.get('path') || "" + + if (id) { + setRepositoryId(id) + setInitialPath(path) + } + setIsLoading(false) + }, [searchParams]) - if (!id) { + if (isLoading) { + return
Loading...
+ } + + if (!repositoryId) { return (

Repository

@@ -13,5 +36,13 @@ export default function Page({ searchParams }: { searchParams?: { id?: string; p ) } - return + return +} + +export default function Page() { + return ( + Loading...
}> + + + ) } diff --git a/src/app/github/repo/repo-client.tsx b/src/app/github/repo/repo-client.tsx index cfed557..366b885 100644 --- a/src/app/github/repo/repo-client.tsx +++ b/src/app/github/repo/repo-client.tsx @@ -5,9 +5,8 @@ import Link from "next/link" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { Input } from "@/components/ui/input" -import { getRepositoryStructure, getRepositoryFileContent, getRepositoryCommitSummary, type RepoStructureEntry, type CommitSummaryResponse } from "@/lib/api/github" -import { ArrowLeft, BookText, Clock, Code, FileText, Folder, GitBranch, Search, GitCommit } from "lucide-react" -import RepoTree from "./RepoTree" +import { getRepositoryStructure, getRepositoryFileContent, type RepoStructureEntry } from "@/lib/api/github" +import { ArrowLeft, BookText, Clock, Code, FileText, Folder, GitBranch, Search } from "lucide-react" export default function RepoByIdClient({ repositoryId, initialPath = "" }: { repositoryId: string, initialPath?: string }) { const [path, setPath] = useState(initialPath) @@ -15,30 +14,6 @@ export default function RepoByIdClient({ repositoryId, initialPath = "" }: { rep const [entries, setEntries] = useState([]) const [fileQuery, setFileQuery] = useState("") const [readme, setReadme] = useState(null) - const [commitSummary, setCommitSummary] = useState(null) - const [selectedFile, setSelectedFile] = useState(null) - const [previewLoading, setPreviewLoading] = useState(false) - const [previewContent, setPreviewContent] = useState(null) - const [selectedDir, setSelectedDir] = useState("") - const [dirEntries, setDirEntries] = useState([]) - - const timeAgo = (iso?: string) => { - if (!iso) return '' - const d = new Date(iso).getTime() - const diff = Math.max(0, Date.now() - d) - const s = Math.floor(diff/1000) - if (s < 60) return `${s}s ago` - const m = Math.floor(s/60) - if (m < 60) return `${m}m ago` - const h = Math.floor(m/60) - if (h < 24) return `${h}h ago` - const days = Math.floor(h/24) - if (days < 30) return `${days}d ago` - const mo = Math.floor(days/30) - if (mo < 12) return `${mo}mo ago` - const y = Math.floor(mo/12) - return `${y}y ago` - } useEffect(() => { let mounted = true @@ -48,23 +23,14 @@ export default function RepoByIdClient({ repositoryId, initialPath = "" }: { rep const struct = await getRepositoryStructure(repositoryId, path) if (!mounted) return setEntries(struct?.structure || []) - // Only fetch README if it exists in the current directory listing - const readmeEntry = (struct?.structure || []).find(e => e.type === 'file' && /^readme\.(md|markdown)$/i.test(e.name)) - if (readmeEntry) { + // try README at this path + const candidates = ["README.md", "readme.md", "README.MD"] + for (const name of candidates) { try { - const fp = path ? `${path}/${readmeEntry.name}` : readmeEntry.name + const fp = path ? `${path}/${name}` : name const content = await getRepositoryFileContent(repositoryId, fp) - if (content?.content) { setReadme(content.content) } else { setReadme(null) } - } catch { setReadme(null) } - } else { - setReadme(null) - } - // fetch commit summary (only on root path) - if (!path) { - try { - const summary = await getRepositoryCommitSummary(repositoryId) - if (mounted) setCommitSummary(summary) - } catch { /* ignore */ } + if (content?.content) { setReadme(content.content); break } + } catch (_) { /* ignore */ } } } finally { setLoading(false) @@ -79,9 +45,9 @@ export default function RepoByIdClient({ repositoryId, initialPath = "" }: { rep return entries.filter(e => e.name.toLowerCase().includes(q)) }, [entries, fileQuery]) - const navigateFolder = (rel: string) => { - // rel is expected to be a relative path from repo root - setPath(rel) + const navigateFolder = (name: string) => { + const next = path ? `${path}/${name}` : name + setPath(next) } const goUp = () => { @@ -117,135 +83,28 @@ export default function RepoByIdClient({ repositoryId, initialPath = "" }: { rep - {/* Commit summary row */} - {commitSummary?.last_commit && ( -
-
-
-
- {commitSummary.last_commit.author_name} - {commitSummary.last_commit.message} -
-
- {commitSummary.last_commit.short_hash} • {timeAgo(commitSummary.last_commit.committed_at)} -
-
-
- {commitSummary.total_commits} commits -
-
- )} - - {/* Three-column layout: left tree, middle directory listing, right preview */} -
-
- { - // Update center panel with this directory contents - setSelectedDir(dirPath) - try { - const res = await getRepositoryStructure(repositoryId, dirPath) - setDirEntries(res?.structure || []) - } catch { - setDirEntries([]) - } - }} - onSelectFile={async (filePath) => { - setSelectedFile(filePath) - try { - setPreviewLoading(true) - const file = await getRepositoryFileContent(repositoryId, filePath) - setPreviewContent(file?.content || null) - } finally { - setPreviewLoading(false) - } - }} - /> -
- {/* Middle: Directory listing */} -
-
- Folder - {selectedDir || "/"} -
-
- {dirEntries.length === 0 && ( -
Select a folder from the tree.
- )} - {dirEntries.map((e, i) => { - const rawPath = (e as any).path || (e as any).relative_path || '' - const displayName = (e as any).name || (e as any).filename || (rawPath ? rawPath.split('/').slice(-1)[0] : '(unknown)') - const typeVal = (e as any).type || (e as any).entry_type - const isDirHint = typeVal === 'directory' || typeVal === 'dir' || (e as any).is_directory === true || (e as any).isDir === true - // heuristic fallback if type missing: no dot in last segment often means folder - const isDir = isDirHint || (!!displayName && !displayName.includes('.')) - return ( -
{ - const relPath = (e as any).path || (e as any).relative_path || displayName - if (isDir) { - // drill down in tree and center - setSelectedDir(relPath) - try { - const res = await getRepositoryStructure(repositoryId, relPath) - setDirEntries(res?.structure || []) - } catch { - setDirEntries([]) - } - return - } - // file - setSelectedFile(relPath) - try { - setPreviewLoading(true) - let file = await getRepositoryFileContent(repositoryId, relPath) - // if backend still reports directory, probe and navigate instead of showing error - if (!file?.content) { - try { - const probe = await getRepositoryStructure(repositoryId, relPath) - if (Array.isArray(probe?.structure) && probe.structure.length > 0) { - setPreviewContent(null) - setSelectedFile(null) - setSelectedDir(relPath) - setDirEntries(probe.structure) - return - } - } catch {} - } - setPreviewContent(file?.content || null) - } finally { - setPreviewLoading(false) - } - }}> -
- {isDir ? : } -
-
{displayName}
-
{isDir ? 'folder' : 'file'}
-
- ) - })} -
-
- {/* Right: Preview */} -
-
- Preview - {selectedFile && {selectedFile}} -
- {previewLoading ? ( -
Loading file...
- ) : !selectedFile ? ( -
Select a file to preview its contents.
- ) : previewContent ? ( -
{previewContent}
- ) : ( -
No preview available (binary or empty file).
+ + + {loading && ( +
Loading...
)} -
-
+ {!loading && visible.length === 0 && ( +
No entries found.
+ )} + {visible.map((e, i) => ( +
e.type === 'directory' ? navigateFolder(e.name) : void 0}> +
+ {e.type === 'directory' ? : } +
+
{e.name}
+
+ +
+
+ ))} + + diff --git a/src/app/project-builder/page.tsx b/src/app/project-builder/page.tsx index fe2d33b..8644437 100644 --- a/src/app/project-builder/page.tsx +++ b/src/app/project-builder/page.tsx @@ -5,7 +5,8 @@ import { useRouter, useSearchParams } from "next/navigation" import { MainDashboard } from "@/components/main-dashboard" import { useAuth } from "@/contexts/auth-context" -export default function ProjectBuilderPage() { +// Component that uses useSearchParams - needs to be wrapped in Suspense +function ProjectBuilderContent() { const router = useRouter() const searchParams = useSearchParams() const { user, isLoading } = useAuth() @@ -52,9 +53,13 @@ export default function ProjectBuilderPage() { return
Loading...
} + return +} + +export default function ProjectBuilderPage() { return ( - Loading...}> - + Loading...}> + ) }