feat: update workspace repo pages (branches, code, commits, layout, settings, tags)
This commit is contained in:
parent
a771dcf5be
commit
c7d67960b7
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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")}
|
||||||
|
|||||||
@ -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" },
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user