pdated settings/git-projects/

This commit is contained in:
null 2026-05-25 17:07:09 -05:00
parent 59e739768f
commit 809975cb76
2 changed files with 164 additions and 35 deletions

View File

@ -28,6 +28,7 @@ import { useAuth } from "@/auth/clerk";
import { ForgejoConnectionsTable } from "@/components/git/ForgejoConnectionsTable";
import { ForgejoRepositoriesTable } from "@/components/git/ForgejoRepositoriesTable";
import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ConfirmActionDialog } from "@/components/ui/confirm-action-dialog";
import {
@ -37,6 +38,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { getApiBaseUrl } from "@/lib/api-base";
import { cn } from "@/lib/utils";
import {
deleteForgejoConnection,
deleteForgejoRepository,
@ -71,6 +73,72 @@ type AttentionItem = {
href?: string;
};
type SettingsTone = "accent" | "success" | "warning" | "danger" | "neutral";
const toneStyles: Record<
SettingsTone,
{
panel: string;
icon: string;
rail: string;
text: string;
}
> = {
accent: {
panel:
"border-[color:rgba(96,165,250,0.24)] bg-[linear-gradient(145deg,rgba(96,165,250,0.1),var(--surface)_58%)]",
icon: "border-[color:rgba(96,165,250,0.3)] bg-[color:var(--accent-soft)] text-[color:var(--accent-strong)]",
rail: "bg-[linear-gradient(90deg,rgba(96,165,250,0),rgba(96,165,250,0.82),rgba(96,165,250,0))]",
text: "text-[color:var(--accent-strong)]",
},
success: {
panel:
"border-[color:rgba(52,211,153,0.22)] bg-[linear-gradient(145deg,rgba(52,211,153,0.09),var(--surface)_58%)]",
icon: "border-[color:rgba(52,211,153,0.3)] bg-[color:rgba(52,211,153,0.14)] text-[color:var(--success)]",
rail: "bg-[linear-gradient(90deg,rgba(52,211,153,0),rgba(52,211,153,0.78),rgba(52,211,153,0))]",
text: "text-[color:var(--success)]",
},
warning: {
panel:
"border-[color:rgba(251,191,36,0.24)] bg-[linear-gradient(145deg,rgba(251,191,36,0.09),var(--surface)_58%)]",
icon: "border-[color:rgba(251,191,36,0.32)] bg-[color:rgba(251,191,36,0.14)] text-[color:var(--warning)]",
rail: "bg-[linear-gradient(90deg,rgba(251,191,36,0),rgba(251,191,36,0.82),rgba(251,191,36,0))]",
text: "text-[color:var(--warning)]",
},
danger: {
panel:
"border-[color:rgba(248,113,113,0.24)] bg-[linear-gradient(145deg,rgba(248,113,113,0.09),var(--surface)_58%)]",
icon: "border-[color:rgba(248,113,113,0.3)] bg-[color:rgba(248,113,113,0.12)] text-[color:var(--danger)]",
rail: "bg-[linear-gradient(90deg,rgba(248,113,113,0),rgba(248,113,113,0.78),rgba(248,113,113,0))]",
text: "text-[color:var(--danger)]",
},
neutral: {
panel:
"border-[color:var(--border)] bg-[linear-gradient(180deg,rgba(255,255,255,0.035),rgba(255,255,255,0)_72%),var(--surface)]",
icon: "border-[color:var(--border-strong)] bg-[color:var(--surface-muted)] text-muted",
rail: "bg-[linear-gradient(90deg,rgba(96,165,250,0),rgba(96,165,250,0.28),rgba(52,211,153,0.2),rgba(96,165,250,0))]",
text: "text-muted",
},
};
function ToneRail({ tone }: { tone: SettingsTone }) {
return (
<span
className={cn(
"pointer-events-none absolute inset-x-4 top-0 h-px",
toneStyles[tone].rail,
)}
/>
);
}
function panelClass(tone: SettingsTone = "neutral") {
return cn(
"relative overflow-hidden rounded-xl border p-4 shadow-lush md:p-5",
toneStyles[tone].panel,
);
}
const repositoryName = (repository: ForgejoRepository) =>
repository.display_name || `${repository.owner}/${repository.repo}`;
@ -114,16 +182,23 @@ function SectionHeader({
title,
description,
action,
tone = "accent",
}: {
icon: ReactNode;
title: string;
description: string;
action?: ReactNode;
tone?: SettingsTone;
}) {
return (
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="flex min-w-0 items-start gap-3">
<div className="shrink-0 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-2 text-[color:var(--accent)]">
<div
className={cn(
"shrink-0 rounded-lg border p-2",
toneStyles[tone].icon,
)}
>
{icon}
</div>
<div className="min-w-0">
@ -141,14 +216,22 @@ function StatCard({
label,
value,
caption,
tone = "accent",
}: {
icon: ReactNode;
label: string;
value: string;
caption: string;
tone?: SettingsTone;
}) {
return (
<div className="rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-lush">
<div
className={cn(
"relative overflow-hidden rounded-xl border p-4 shadow-lush",
toneStyles[tone].panel,
)}
>
<ToneRail tone={tone} />
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-xs font-semibold uppercase tracking-[0.14em] text-muted">
@ -158,7 +241,7 @@ function StatCard({
{value}
</p>
</div>
<div className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-2 text-[color:var(--accent)]">
<div className={cn("rounded-lg border p-2", toneStyles[tone].icon)}>
{icon}
</div>
</div>
@ -190,11 +273,32 @@ function HealthSummaryPanel({
: status === "warning"
? "border-[color:rgba(245,158,11,0.35)] bg-[color:rgba(245,158,11,0.08)] text-[color:var(--warning)]"
: "border-[color:rgba(248,113,113,0.35)] bg-[color:rgba(248,113,113,0.08)] text-[color:var(--danger)]";
const metricTones: SettingsTone[] = [
"accent",
"success",
"warning",
"danger",
];
return (
<section className="rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-lush md:p-5">
<section
className={cn(
"relative overflow-hidden rounded-xl border border-[color:rgba(96,165,250,0.28)] bg-[linear-gradient(135deg,rgba(96,165,250,0.16),rgba(52,211,153,0.09)_34%,rgba(251,191,36,0.08)_68%,var(--surface)_100%)] p-4 shadow-lush md:p-6",
status === "danger" &&
"border-[color:rgba(248,113,113,0.28)] bg-[linear-gradient(135deg,rgba(248,113,113,0.12),rgba(96,165,250,0.1)_42%,var(--surface)_100%)]",
status === "warning" &&
"border-[color:rgba(251,191,36,0.28)] bg-[linear-gradient(135deg,rgba(251,191,36,0.12),rgba(96,165,250,0.1)_42%,var(--surface)_100%)]",
)}
>
<div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,rgba(96,165,250,0),rgba(96,165,250,0.85),rgba(52,211,153,0.85),rgba(251,191,36,0))]" />
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div className="min-w-0">
<Badge
variant="accent"
className="mb-3 w-fit shadow-[0_0_24px_rgba(96,165,250,0.16)]"
>
Git operations
</Badge>
<div
className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-semibold uppercase tracking-wide ${statusStyles}`}
>
@ -229,11 +333,15 @@ function HealthSummaryPanel({
</Button>
</div>
<div className="mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
{metrics.map((metric) => (
{metrics.map((metric, index) => (
<div
key={metric.label}
className="rounded-lg border border-[color:var(--border)] bg-[color:var(--surface-muted)] p-3"
className={cn(
"relative overflow-hidden rounded-lg border p-3",
toneStyles[metricTones[index % metricTones.length]].panel,
)}
>
<ToneRail tone={metricTones[index % metricTones.length]} />
<p className="text-xs font-semibold uppercase tracking-wide text-muted">
{metric.label}
</p>
@ -281,11 +389,35 @@ function AttentionPanel({
const hasWebhookItems = items.some((item) => item.kind === "webhook");
return (
<section className="rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-lush md:p-5">
<section
className={panelClass(
items.some((item) => item.tone === "danger")
? "danger"
: items.length
? "warning"
: "success",
)}
>
<ToneRail
tone={
items.some((item) => item.tone === "danger")
? "danger"
: items.length
? "warning"
: "success"
}
/>
<SectionHeader
icon={<ListChecks className="h-4 w-4" />}
title="Needs Attention"
description="Connection and repository signals that can block fresh issue data."
tone={
items.some((item) => item.tone === "danger")
? "danger"
: items.length
? "warning"
: "success"
}
action={
<div className="flex flex-wrap gap-2">
{hasSyncItems ? (
@ -340,7 +472,7 @@ function AttentionPanel({
return (
<div
key={group.key}
className="rounded-lg border border-[color:var(--border)]"
className="rounded-lg border border-[color:var(--border)] bg-[color:rgba(15,23,36,0.22)]"
>
<div className="flex items-center justify-between border-b border-[color:var(--border)] bg-[color:var(--surface-muted)] px-3 py-2">
<p className="text-xs font-semibold uppercase tracking-wide text-muted">
@ -393,11 +525,13 @@ function LastImportPanel({
}) {
const lastImport = importRuns[0] ?? null;
return (
<section className="rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-lush md:p-5">
<section className={panelClass("accent")}>
<ToneRail tone="accent" />
<SectionHeader
icon={<Download className="h-4 w-4" />}
title="Full Import"
description="Run a complete issue pull when webhooks or scheduled sync need a reset."
tone="accent"
action={
<Button type="button" variant="outline" size="sm" onClick={onOpen}>
<Download className="h-4 w-4" />
@ -1141,6 +1275,7 @@ export default function GitProjectSettingsPage() {
title="Git Project Settings"
description="Manage Forgejo connections, tracked repositories, and issue sync."
stickyHeader
mainClassName="relative bg-app"
headerActions={
<div className="flex flex-wrap gap-2">
<Link href="/git-projects/issues">
@ -1192,7 +1327,9 @@ export default function GitProjectSettingsPage() {
</div>
}
>
<div className="space-y-6">
<div className="pointer-events-none absolute inset-x-0 top-0 h-72 bg-[linear-gradient(135deg,rgba(96,165,250,0.16),rgba(52,211,153,0.1)_38%,rgba(251,191,36,0.08)_64%,rgba(96,165,250,0)_100%)] blur-2xl" />
<div className="relative space-y-6">
{notice ? <NoticeBanner notice={notice} /> : null}
{error ? (
<div className="flex items-start gap-3 rounded-xl border border-[color:rgba(248,113,113,0.35)] bg-[color:rgba(248,113,113,0.08)] p-3 text-sm text-[color:var(--danger)]">
@ -1247,11 +1384,13 @@ export default function GitProjectSettingsPage() {
</div>
{/* Connections */}
<section className="rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-lush md:p-5">
<section className={panelClass("accent")}>
<ToneRail tone="accent" />
<SectionHeader
icon={<Link2 className="h-4 w-4" />}
title="Forgejo Connections"
description="URL and token records used by tracked repositories."
tone="accent"
action={
<Link href="/git-projects/connections">
<Button size="sm" variant="outline">
@ -1274,11 +1413,13 @@ export default function GitProjectSettingsPage() {
</section>
{/* Repositories */}
<section className="rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-lush md:p-5">
<section className={panelClass("success")}>
<ToneRail tone="success" />
<SectionHeader
icon={<GitBranch className="h-4 w-4" />}
title="Tracked Repositories"
description="Repositories whose issues are cached and shown in Pipeline."
tone="success"
action={
<div className="flex flex-wrap gap-2">
<Link href="/git-projects/repositories/new">
@ -1313,12 +1454,14 @@ export default function GitProjectSettingsPage() {
{/* Webhook Setup */}
<section
id="git-project-webhooks"
className="scroll-mt-24 rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-lush md:p-5"
className={cn("scroll-mt-24", panelClass("warning"))}
>
<ToneRail tone="warning" />
<SectionHeader
icon={<Webhook className="h-4 w-4" />}
title="Webhook Setup"
description="Configure Forgejo webhooks to push issue updates to Pipeline in real time."
tone="warning"
/>
{repositories.length === 0 ? (
@ -1415,11 +1558,13 @@ export default function GitProjectSettingsPage() {
</section>
{/* Scheduled Sync */}
<section className="rounded-xl border border-[color:var(--border)] bg-[color:var(--surface)] p-4 shadow-lush md:p-5">
<section className={panelClass("neutral")}>
<ToneRail tone="neutral" />
<SectionHeader
icon={<Clock className="h-4 w-4" />}
title="Scheduled Sync"
description="Pipeline runs a background sync for all active repositories every 60 minutes."
tone="neutral"
/>
<div className="mt-4 pl-0 sm:pl-11">
<p className="text-sm text-muted">

View File

@ -594,26 +594,10 @@ export default function SettingsPage() {
forceRedirectUrl: "/settings",
signUpForceRedirectUrl: "/settings",
}}
title="Settings"
description="Manage your profile, workspace configuration, and integrations."
title={null}
stickyHeader
headerActions={
<div className="flex flex-wrap gap-2">
<Link href="/settings/ai-providers">
<Button variant="outline" size="sm">
<KeyRound className="h-4 w-4" />
AI Providers
</Button>
</Link>
<Link href="/settings/git-projects">
<Button variant="outline" size="sm">
<GitBranch className="h-4 w-4" />
Git Settings
</Button>
</Link>
</div>
}
mainClassName="relative bg-app"
headerClassName="hidden"
>
<div className="pointer-events-none absolute inset-x-0 top-0 h-72 bg-[linear-gradient(135deg,rgba(96,165,250,0.16),rgba(52,211,153,0.1)_38%,rgba(251,191,36,0.08)_64%,rgba(96,165,250,0)_100%)] blur-2xl" />
@ -666,8 +650,8 @@ export default function SettingsPage() {
</div>
</section>
<div className="grid gap-6 lg:grid-cols-[280px_minmax(0,1fr)]">
<aside className="lg:sticky lg:top-28 lg:self-start">
<div className="grid gap-6 lg:grid-cols-[minmax(0,1fr)_280px]">
<aside className="order-2 lg:sticky lg:top-8 lg:self-start">
<nav className="relative overflow-hidden rounded-xl border border-[color:var(--border)] bg-[linear-gradient(180deg,rgba(255,255,255,0.035),rgba(255,255,255,0)_72%),var(--surface)] p-3 shadow-lush">
<span className="pointer-events-none absolute inset-x-4 top-0 h-px bg-[linear-gradient(90deg,rgba(96,165,250,0),rgba(96,165,250,0.46),rgba(52,211,153,0.32),rgba(96,165,250,0))]" />
<p className="px-2 pb-2 pt-1 text-xs font-semibold uppercase tracking-wider text-muted">
@ -712,7 +696,7 @@ export default function SettingsPage() {
</nav>
</aside>
<div className="space-y-6">
<div className="order-1 space-y-6">
<div className="grid gap-6 xl:grid-cols-[minmax(0,1.3fr)_minmax(320px,0.7fr)]">
<SettingsPanel
id="profile"