Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
513 changes: 2 additions & 511 deletions src/app/dashboard/page.tsx

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions src/app/demo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { Metadata } from "next";
import { DashboardScreen } from "@/features/dashboard/components/dashboard-screen";
import { buildDemoDashboardOverview, demoSyncStatus } from "@/features/demo/demo-dashboard";

export const metadata: Metadata = {
title: "Live demo · GitFusion",
description: "Explore the GitFusion dashboard with sample GitHub and GitLab activity.",
};

export default function DemoPage() {
return (
<DashboardScreen
demo
initialOverview={buildDemoDashboardOverview()}
initialSyncStatus={demoSyncStatus}
initialViewerName="Alex"
/>
);
}
12 changes: 11 additions & 1 deletion src/features/auth/components/auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import { ChangeEvent, FormEvent, useState } from "react";
import { createSupabaseBrowserClient, isSupabaseConfigured } from "@/lib/supabase/client";
import { uploadUserAvatar } from "@/lib/supabase/avatar";
import { Alert, AlertDialog } from "@heroui/react";

Check warning on line 8 in src/features/auth/components/auth-form.tsx

View workflow job for this annotation

GitHub Actions / verify

'AlertDialog' is defined but never used

Check warning on line 8 in src/features/auth/components/auth-form.tsx

View workflow job for this annotation

GitHub Actions / verify

'Alert' is defined but never used

type AuthMode = "sign-in" | "sign-up";

Expand Down Expand Up @@ -154,7 +155,7 @@
)}

{message && (
<div className="rounded-md border border-gray-200 bg-background px-3 py-2 text-sm font-semibold text-muted-foreground dark:border-gray-800">
<div className="rounded-md border border-red-400 bg-red-600/20 px-3 py-2 text-sm font-semibold text-gray-300">
{message}
</div>
)}
Expand All @@ -167,6 +168,15 @@
{isSubmitting ? "Processing..." : isSignUp ? "Create account" : "Sign in"}
</button>

{!isSignUp && (
<Link
href="/demo"
className="flex h-11 w-full items-center justify-center rounded-md border border-gray-200 bg-background px-4 text-sm font-extrabold text-foreground transition hover:border-primary hover:text-primary focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary dark:border-gray-800"
>
Explore the live demo
</Link>
)}

<p className="text-center text-sm text-muted-foreground">
{isSignUp ? "Already have an account?" : "Don't have an account?"}{" "}
<Link className="font-bold text-primary hover:text-primary-dark" href={isSignUp ? "/sign-in" : "/sign-up"}>
Expand Down
532 changes: 532 additions & 0 deletions src/features/dashboard/components/dashboard-screen.tsx

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions src/features/demo/demo-dashboard.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { describe, expect, it } from "vitest";
import { buildDemoDashboardOverview } from "./demo-dashboard";

describe("buildDemoDashboardOverview", () => {
it("builds a complete, internally consistent year of sample data", () => {
const overview = buildDemoDashboardOverview(new Date(2026, 8, 20));

expect(overview.hasConnections).toBe(true);
expect(overview.connectedProviders).toEqual(["github", "gitlab"]);
expect(overview.dailyContributions).toHaveLength(365);
expect(overview.dailyContributions.at(-1)?.date).toBe("2026-09-20");
expect(overview.dailyContributions.slice(-9).every((day) => day.total > 0)).toBe(true);
expect(overview.dailyContributions.at(-10)?.total).toBe(0);
expect(overview.dailyContributions.every((day) => day.total === day.platforms.github + day.platforms.gitlab)).toBe(true);
expect(overview.topRepositories.length).toBeGreaterThan(0);
expect(overview.recentActivity.length).toBeGreaterThan(0);
});
});
72 changes: 72 additions & 0 deletions src/features/demo/demo-dashboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { DashboardOverview, DashboardSyncStatus } from "@/types/dashboard";
import type { DailyContribution } from "@/types/mock-app";

const DAY_IN_MS = 86_400_000;
const DEMO_DAYS = 365;

export const demoSyncStatus: DashboardSyncStatus = {
status: "synced",
progressPercent: 100,
startedAt: "2026-09-20T14:28:00.000Z",
finishedAt: "2026-09-20T14:28:08.000Z",
errorMessage: null,
};

export function buildDemoDashboardOverview(now = new Date()): DashboardOverview {
const dailyContributions = buildDailyContributions(now);
const activeDays = dailyContributions.filter((day) => day.total > 0).length;

return {
hasConnections: true,
connectedProviders: ["github", "gitlab"],
metrics: [
{ label: "Pull / merge requests", value: "86", helper: "Across GitHub and GitLab", trend: "+14% from last period" },
],
activeDays,
currentStreak: 9,
dailyContributions,
recentActivity: [
{ id: "demo-1", title: "Merged pull request", description: "feat: add team activity insights · gitfusion/web", platform: "github", time: "2 hours ago" },
{ id: "demo-2", title: "Pushed 4 commits", description: "Improve dashboard accessibility · atlas-ui", platform: "gitlab", time: "Yesterday" },
{ id: "demo-3", title: "Opened pull request", description: "refactor: simplify provider adapters · gitfusion/api", platform: "github", time: "2 days ago" },
{ id: "demo-4", title: "Created release v2.4.0", description: "Performance and chart improvements · pulse", platform: "gitlab", time: "4 days ago" },
{ id: "demo-5", title: "Reviewed pull request", description: "Add repository filters · open-work", platform: "github", time: "5 days ago" },
],
topRepositories: [
{ name: "gitfusion", platform: "github", language: "TypeScript", contributions: 284, visibility: "Public" },
{ name: "atlas-ui", platform: "gitlab", language: "TypeScript", contributions: 193, visibility: "Private" },
{ name: "pulse-api", platform: "github", language: "Python", contributions: 146, visibility: "Public" },
{ name: "developer-portal", platform: "gitlab", language: "Vue", contributions: 121, visibility: "Private" },
{ name: "dotfiles", platform: "github", language: "Shell", contributions: 74, visibility: "Public" },
{ name: "event-worker", platform: "gitlab", language: "Go", contributions: 61, visibility: "Private" },
],
generatedAt: now.toISOString(),
};
}

function buildDailyContributions(now: Date): DailyContribution[] {
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());

return Array.from({ length: DEMO_DAYS }, (_, index) => {
const daysAgo = DEMO_DAYS - index - 1;
const date = new Date(today.getTime() - daysAgo * DAY_IN_MS);
const seed = index + date.getMonth() * 11 + date.getDate() * 3;
const isRecentStreak = daysAgo < 9;
const isActive = isRecentStreak || (daysAgo !== 9 && seed % 7 !== 0);
const github = isActive ? (seed * 5) % 8 + (isRecentStreak ? 2 : 0) : 0;
const gitlab = isActive && seed % 3 !== 0 ? (seed * 3) % 5 : 0;

return {
date: formatDate(date),
total: github + gitlab,
platforms: { github, gitlab },
};
});
}

function formatDate(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ type AccountConnectionCardProps = {
onConnect: (provider: ConnectedAccount["provider"]) => void;
onDisconnect?: (provider: ConnectedAccount["provider"]) => void;
compact?: boolean;
readOnly?: boolean;
};

export function AccountConnectionCard({ account, onConnect, onDisconnect, compact = false }: AccountConnectionCardProps) {
export function AccountConnectionCard({ account, onConnect, onDisconnect, compact = false, readOnly = false }: AccountConnectionCardProps) {
const isConnected = account.status === "connected";
const isConnecting = account.status === "connecting";
const Icon = account.provider === "github" ? FaGithub : FaGitlab;
Expand All @@ -35,15 +36,15 @@ export function AccountConnectionCard({ account, onConnect, onDisconnect, compac
{isConnected ? (
<>
<span className={`rounded-full bg-emerald-500/12 px-3 py-1 text-xs font-extrabold text-emerald-400`}>Connected</span>
<button
{!readOnly && <button
type="button"
onClick={() => onDisconnect?.(account.provider)}
className={`grid size-8 place-items-center rounded-full text-muted-foreground transition hover:bg-rose-500/10 hover:text-rose-300`}
aria-label={`Disconnect ${account.name}`}
title={`Disconnect ${account.name}`}
>
<FiLogOut className="size-4" aria-hidden />
</button>
</button>}
<FiChevronRight className={`size-4 text-muted-foreground ${compact ? "hidden sm:block" : ""}`} aria-hidden />
</>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type IntegrationAccountsPanelProps = {
showContinue?: boolean;
redirectTo?: string;
className?: string;
demo?: boolean;
};

const providerAccounts: ConnectedAccount[] = [
Expand All @@ -47,8 +48,13 @@ const providerAccounts: ConnectedAccount[] = [
},
];

export function IntegrationAccountsPanel({ compact = false, showContinue = false, redirectTo = "/connect-accounts", className = "" }: IntegrationAccountsPanelProps) {
const [accounts, setAccounts] = useState<ConnectedAccount[]>(() => buildAccounts([]));
const demoAccounts: ConnectedAccount[] = [
{ provider: "github", name: "GitHub", username: "alexdev", email: "", status: "connected", repositories: 18, contributions: 842, lastSync: "Today" },
{ provider: "gitlab", name: "GitLab", username: "alex.morgan", email: "", status: "connected", repositories: 7, contributions: 316, lastSync: "Today" },
];

export function IntegrationAccountsPanel({ compact = false, showContinue = false, redirectTo = "/connect-accounts", className = "", demo = false }: IntegrationAccountsPanelProps) {
const [accounts, setAccounts] = useState<ConnectedAccount[]>(() => demo ? demoAccounts : buildAccounts([]));

const loadAccounts = useCallback(async () => {
if (!isSupabaseConfigured()) {
Expand Down Expand Up @@ -81,6 +87,8 @@ export function IntegrationAccountsPanel({ compact = false, showContinue = false
}, []);

useEffect(() => {
if (demo) return;

const searchParams = new URLSearchParams(window.location.search);
const connected = searchParams.get("connected");
const integrationError = searchParams.get("integration_error");
Expand All @@ -95,7 +103,7 @@ export function IntegrationAccountsPanel({ compact = false, showContinue = false
}

loadAccounts();
}, [loadAccounts]);
}, [demo, loadAccounts]);

const connectAccount = async (provider: AccountProvider) => {
setAccounts((current) => current.map((account) => account.provider === provider ? { ...account, status: "connecting" } : account));
Expand Down Expand Up @@ -181,7 +189,7 @@ export function IntegrationAccountsPanel({ compact = false, showContinue = false
</div>
<div className={compact ? "grid grid-cols-1 sm:grid-cols-2 gap-3 xl:grid-cols-1" : "space-y-3"}>
{accounts.map((account) => (
<AccountConnectionCard key={account.provider} account={account} onConnect={connectAccount} onDisconnect={disconnectAccount} compact={compact} />
<AccountConnectionCard key={account.provider} account={account} onConnect={connectAccount} onDisconnect={disconnectAccount} compact={compact} readOnly={demo} />
))}
</div>
</div>
Expand Down
Loading
Loading