feat: update workspace repo pages (branches, code, commits, layout, settings, tags)

This commit is contained in:
zhenyi 2026-05-30 15:08:10 +08:00
parent a771dcf5be
commit c7d67960b7
6 changed files with 79 additions and 29 deletions

View File

@ -94,9 +94,19 @@ export default function BranchesTab() {
{otherBranches.map((b) => <BranchRow key={b.name} branch={b} onDelete={deleteBranch} onRename={renameBranch} renaming={renaming} setRenaming={setRenaming} renameValue={renameValue} setRenameValue={setRenameValue} />)} {otherBranches.map((b) => <BranchRow key={b.name} branch={b} onDelete={deleteBranch} onRename={renameBranch} renaming={renaming} setRenaming={setRenaming} renameValue={renameValue} setRenameValue={setRenameValue} />)}
</div> </div>
) : ( ) : (
<div className="py-12 text-center"> <div className="flex flex-col items-center justify-center py-16 text-center">
<GitBranch className="mx-auto size-5 text-muted-foreground/20" /> <div className="grid size-14 place-items-center rounded-2xl bg-muted/40">
<p className="mt-3 text-[13px] text-muted-foreground">No branches yet</p> <GitBranch className="size-6 text-muted-foreground/40" />
</div>
<h3 className="mt-4 font-heading text-sm font-semibold text-foreground">No branches yet</h3>
<p className="mt-1.5 max-w-[280px] text-[12px] leading-relaxed text-muted-foreground">
{search ? `No branches matching "${search}"` : "Create a branch to start working on your project."}
</p>
{!search && (
<Button className="mt-4 h-8 px-3 text-[12px]" onClick={() => setShowCreate(true)}>
<Plus className="size-3 mr-1" /> Create first branch
</Button>
)}
</div> </div>
)} )}
</div> </div>

View File

@ -445,7 +445,7 @@ export default function CodeTab() {
<span className="font-mono text-[11px] text-muted-foreground shrink-0 ml-auto"> <span className="font-mono text-[11px] text-muted-foreground shrink-0 ml-auto">
{latestCommit.author?.time_secs ? formatTimeAgo(latestCommit.author.time_secs) : ""} {latestCommit.author?.time_secs ? formatTimeAgo(latestCommit.author.time_secs) : ""}
</span> </span>
<code className="font-mono text-[11px] text-muted-foreground/60 shrink-0">{latestCommit.oid.slice(0, 7)}</code> <code className="rounded bg-muted/60 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">{latestCommit.oid.slice(0, 7)}</code>
</div> </div>
)} )}
@ -571,17 +571,36 @@ export default function CodeTab() {
))} ))}
</div> </div>
) : currentTreeOid ? ( ) : currentTreeOid ? (
<div className="py-8 text-center"> <div className="flex flex-col items-center justify-center py-12 text-center">
<Folder className="mx-auto size-5 text-muted-foreground/20" /> <div className="grid size-12 place-items-center rounded-xl bg-muted/40">
<p className="mt-2 text-[13px] text-muted-foreground">Empty directory</p> <Folder className="size-5 text-muted-foreground/40" />
</div>
<p className="mt-3 text-[13px] font-medium text-muted-foreground">Empty directory</p>
<p className="mt-1 text-[11px] text-muted-foreground/60">This directory contains no files.</p>
</div> </div>
) : ( ) : (
<div className="py-12 text-center"> <div className="flex flex-col items-center justify-center py-16 text-center">
<GitCommitHorizontal className="mx-auto size-5 text-muted-foreground/20" /> <div className="grid size-14 place-items-center rounded-2xl bg-muted/40">
<p className="mt-2 text-[13px] text-muted-foreground">No content</p> <GitCommitHorizontal className="size-6 text-muted-foreground/40" />
<p className="text-[11px] text-muted-foreground/60"> </div>
Push a commit to get started <h3 className="mt-4 font-heading text-sm font-semibold text-foreground">No content yet</h3>
<p className="mt-1.5 max-w-[280px] text-[12px] leading-relaxed text-muted-foreground">
This repository is empty. Push your first commit to get started.
</p> </p>
<div className="mt-5 flex items-center gap-2 rounded-md border border-border bg-muted/30 px-3 py-2 font-mono text-[11px] text-muted-foreground">
<code className="select-all">git push -u origin {selectedBranch}</code>
<button
className="ml-1 text-muted-foreground/60 hover:text-foreground transition-colors"
onClick={async () => {
try {
await navigator.clipboard.writeText(`git push -u origin ${selectedBranch}`);
} catch { /* noop */ }
}}
title="Copy"
>
<Copy className="size-3" />
</button>
</div>
</div> </div>
)} )}
</div> </div>

View File

@ -121,7 +121,7 @@ export default function CommitsTab() {
<AuthorAvatar author={{ name: commit.author?.name ?? "unknown" }} /> <AuthorAvatar author={{ name: commit.author?.name ?? "unknown" }} />
<span className="font-mono text-[11px] text-muted-foreground">{commit.author?.name}</span> <span className="font-mono text-[11px] text-muted-foreground">{commit.author?.name}</span>
<span className="font-mono text-[11px] text-muted-foreground">{commit.author?.time_secs ? formatTimeAgo(commit.author.time_secs) : ""}</span> <span className="font-mono text-[11px] text-muted-foreground">{commit.author?.time_secs ? formatTimeAgo(commit.author.time_secs) : ""}</span>
<code className="font-mono text-[11px] text-muted-foreground/60">{commit.oid.slice(0, 7)}</code> <code className="rounded bg-muted/60 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">{commit.oid.slice(0, 7)}</code>
</div> </div>
</Link> </Link>
))} ))}
@ -129,9 +129,16 @@ export default function CommitsTab() {
<PaginationBar offset={offset} limit={LIMIT} hasMore={commits.length >= LIMIT} onPrev={() => setOffset(Math.max(0, offset - LIMIT))} onNext={() => setOffset(offset + LIMIT)} /> <PaginationBar offset={offset} limit={LIMIT} hasMore={commits.length >= LIMIT} onPrev={() => setOffset(Math.max(0, offset - LIMIT))} onNext={() => setOffset(offset + LIMIT)} />
</> </>
) : ( ) : (
<div className="py-12 text-center"> <div className="flex flex-col items-center justify-center py-16 text-center">
<GitCommitHorizontal className="mx-auto size-5 text-muted-foreground/20" /> <div className="grid size-14 place-items-center rounded-2xl bg-muted/40">
<p className="mt-3 text-[13px] text-muted-foreground">No commits found</p> <GitCommitHorizontal className="size-6 text-muted-foreground/40" />
</div>
<h3 className="mt-4 font-heading text-sm font-semibold text-foreground">
{search ? "No matching commits" : "No commits yet"}
</h3>
<p className="mt-1.5 max-w-[280px] text-[12px] leading-relaxed text-muted-foreground">
{search ? `No commits match "${search}"` : "Push your first commit to this branch to get started."}
</p>
</div> </div>
)} )}
</div> </div>

View File

@ -6,6 +6,7 @@ import { Lock, Globe, Archive, GitFork, Star, Eye, EyeOff } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
function formatSize(bytes: number) { function formatSize(bytes: number) {
if (bytes === 0) return "Empty";
if (bytes < 1024) return `${bytes} B`; if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
@ -112,9 +113,14 @@ export default function RepoLayout() {
if (!repo) { if (!repo) {
return ( return (
<div className="px-8 py-10 text-center"> <div className="flex flex-col items-center justify-center px-8 py-20 text-center">
<GitFork className="mx-auto size-5 text-muted-foreground/20" /> <div className="grid size-16 place-items-center rounded-2xl bg-muted/40">
<p className="mt-3 text-[13px] text-muted-foreground">Repository not found</p> <GitFork className="size-7 text-muted-foreground/40" />
</div>
<h2 className="mt-5 font-heading text-lg font-semibold text-foreground">Repository not found</h2>
<p className="mt-2 max-w-sm text-[13px] text-muted-foreground">
This repository doesn't exist or you don't have permission to view it.
</p>
</div> </div>
); );
} }
@ -155,7 +161,7 @@ export default function RepoLayout() {
<span className="text-muted-foreground/30">|</span> <span className="text-muted-foreground/30">|</span>
<span>{formatSize(repo.size_bytes)}</span> <span>{formatSize(repo.size_bytes)}</span>
<span className="text-muted-foreground/30">|</span> <span className="text-muted-foreground/30">|</span>
<span>created {formatDate(repo.created_at)} by {repo.created_by}</span> <span>created {formatDate(repo.created_at)}</span>
<div className="flex items-center gap-1.5 ml-2"> <div className="flex items-center gap-1.5 ml-2">
<button <button
className={cn("inline-flex items-center gap-1 rounded-sm border px-2 py-1 text-[11px] transition-colors", starData?.starred ? "border-primary/40 bg-primary/5 text-primary" : "border-border text-muted-foreground hover:text-foreground")} className={cn("inline-flex items-center gap-1 rounded-sm border px-2 py-1 text-[11px] transition-colors", starData?.starred ? "border-primary/40 bg-primary/5 text-primary" : "border-border text-muted-foreground hover:text-foreground")}

View File

@ -142,27 +142,27 @@ export default function RepoSettingsTab() {
<h2 className="font-heading text-[15px] font-bold text-foreground">General</h2> <h2 className="font-heading text-[15px] font-bold text-foreground">General</h2>
<div> <div>
<label className="text-[13px] font-medium text-foreground">Repository name</label> <label className="text-[13px] font-medium text-foreground">Repository name</label>
<Input className="mt-1.5 h-9 text-[13px]" name="name" defaultValue={repo.name} /> <Input className="mt-1 h-9 text-[13px]" name="name" defaultValue={repo.name} />
<p className="mt-1 text-[11px] text-muted-foreground">Changing the name will also change the repository URL.</p> <p className="mt-0.5 text-[11px] text-muted-foreground">Changing the name will also change the repository URL.</p>
</div> </div>
<div> <div>
<label className="text-[13px] font-medium text-foreground">Description <span className="text-muted-foreground">(optional)</span></label> <label className="text-[13px] font-medium text-foreground">Description <span className="text-muted-foreground">(optional)</span></label>
<Textarea className="mt-1.5 min-h-[72px] text-[13px]" name="description" defaultValue={repo.description ?? ""} /> <Textarea className="mt-1 min-h-[72px] text-[13px]" name="description" defaultValue={repo.description ?? ""} />
</div> </div>
<div> <div>
<label className="text-[13px] font-medium text-foreground">Default branch</label> <label className="text-[13px] font-medium text-foreground">Default branch</label>
{hasBranches ? ( {hasBranches ? (
<Select value={defaultBranch} onValueChange={(v) => setDefaultBranch(v ?? "")}> <Select value={defaultBranch} onValueChange={(v) => setDefaultBranch(v ?? "")}>
<SelectTrigger className="mt-1.5 h-9 w-48 text-[13px]"><SelectValue /></SelectTrigger> <SelectTrigger className="mt-1 h-9 w-48 text-[13px]"><SelectValue /></SelectTrigger>
<SelectContent>{branches!.map((b) => <SelectItem key={b.name} value={b.name}>{b.name}</SelectItem>)}</SelectContent> <SelectContent>{branches!.map((b) => <SelectItem key={b.name} value={b.name}>{b.name}</SelectItem>)}</SelectContent>
</Select> </Select>
) : ( ) : (
<Input className="mt-1.5 h-9 w-48 text-[13px]" name="default_branch" defaultValue={repo.default_branch} onChange={(e) => setDefaultBranch(e.target.value)} /> <Input className="mt-1 h-9 w-48 text-[13px]" name="default_branch" defaultValue={repo.default_branch} onChange={(e) => setDefaultBranch(e.target.value)} />
)} )}
</div> </div>
<div> <div>
<label className="text-[13px] font-medium text-foreground">Visibility</label> <label className="text-[13px] font-medium text-foreground">Visibility</label>
<div className="mt-1.5 flex gap-2"> <div className="mt-1 flex gap-2">
{[ {[
{ value: "private", icon: <Lock className="size-3.5" />, label: "Private", desc: "Only members can see this repository" }, { value: "private", icon: <Lock className="size-3.5" />, label: "Private", desc: "Only members can see this repository" },
{ value: "public", icon: <Globe className="size-3.5" />, label: "Public", desc: "Anyone can see this repository" }, { value: "public", icon: <Globe className="size-3.5" />, label: "Public", desc: "Anyone can see this repository" },

View File

@ -95,9 +95,17 @@ export default function TagsTab() {
<PaginationBar offset={offset} limit={LIMIT} hasMore={tags.length >= LIMIT} onPrev={() => setOffset(Math.max(0, offset - LIMIT))} onNext={() => setOffset(offset + LIMIT)} /> <PaginationBar offset={offset} limit={LIMIT} hasMore={tags.length >= LIMIT} onPrev={() => setOffset(Math.max(0, offset - LIMIT))} onNext={() => setOffset(offset + LIMIT)} />
</> </>
) : ( ) : (
<div className="py-12 text-center"> <div className="flex flex-col items-center justify-center py-16 text-center">
<Tag className="mx-auto size-5 text-muted-foreground/20" /> <div className="grid size-14 place-items-center rounded-2xl bg-muted/40">
<p className="mt-2 text-[13px] text-muted-foreground">No tags yet</p> <Tag className="size-6 text-muted-foreground/40" />
</div>
<h3 className="mt-4 font-heading text-sm font-semibold text-foreground">No tags yet</h3>
<p className="mt-1.5 max-w-[280px] text-[12px] leading-relaxed text-muted-foreground">
Tags mark specific points in repository history, typically used for releases.
</p>
<Button className="mt-4 h-8 px-3 text-[12px]" onClick={() => setShowCreate(true)}>
<Plus className="size-3.5 mr-1" /> Create first tag
</Button>
</div> </div>
)} )}
</div> </div>