import Link from "next/link";
import { Card, Surface } from "@unlocalhosted/metalui";
import { BRAND } from "@/components/brand";
import { HomeTerminal } from "@/components/home-terminal";
import { OrgInvitations } from "@/components/org-invitations";
import {
ArrowRightIcon,
BranchIcon,
CodeIcon,
CommitIcon,
CompassIcon,
IssueOpenedIcon,
KeyIcon,
OrganizationIcon,
PencilIcon,
PeopleIcon,
PlusIcon,
ShieldLockIcon,
SparkleIcon,
} from "@/components/icons";
import { CardDescription, CardTitle } from "@/components/metal";
import { RepoFilterList } from "@/components/repo-filter-list";
import { Avatar, avatarUrl, Blankslate, Box, ButtonLink, Counter, Label, RefChip, RepoAvatar } from "@/components/ui";
import { getViewer } from "@/lib/auth";
import { shortSha, timeAgo } from "@/lib/format";
import { recentActivity, withActivity } from "@/lib/listing";
import { listInvitationsFor, listOrgsOf } from "@/lib/orgs";
import { listReposForUser, searchRepos } from "@/lib/repos";
import { serverHost } from "@/lib/request-origin";
import { SIGNUPS_ENABLED } from "@/lib/signups";
import { sshCloneUrl } from "@/lib/ssh-config";
import type { User } from "@/lib/users";
export default async function Home() {
const viewer = await getViewer();
if (viewer) return ;
return ;
}
const features = [
{ icon: CodeIcon, title: "Beautiful code browsing", text: "Syntax highlighting, rendered READMEs, full history and crisp diffs.", wide: true },
{ icon: PencilIcon, title: "Edit in the browser", text: "A real editor. Create, rename, upload and delete files in one commit." },
{ icon: KeyIcon, title: "SSH keys", text: "Push with git@ URLs, exactly the way you're used to." },
{ icon: ShieldLockIcon, title: "Scoped tokens", text: "Read-only or read & write, per repository, with expiry." },
{ icon: PeopleIcon, title: "Built for teams", text: "Accounts, collaborators, public and private repositories." },
{ icon: IssueOpenedIcon, title: "Issues", text: "Track bugs and ideas with markdown discussions.", wide: true },
{ icon: SparkleIcon, title: "Light & dark", text: "A calm interface that follows your system theme." },
];
/** A section heading on the landing page: tight display type in ink. */
const displayHeading = "font-semibold tracking-[-0.04em] text-ink";
function Landing({ sshUrl }: { sshUrl: string }) {
return (
Self-hosted
Your code stays yours
A calm, beautiful home
for your code.
{BRAND.name} hosts git repositories for your whole team. Push over SSH or HTTPS, edit in the browser, and keep private work private.
{SIGNUPS_ENABLED ? (
}>
Create Your Account
) : (
}>
Sign In
)}
}>
Explore Repositories
Everything you need
Ship without the noise
The workflow you use every day, running on your own machine.
{features.map((f) => (
{f.title}
{f.text}
))}
Get started
Plant your first repository
It takes less than a minute.
} className="mt-7">
{SIGNUPS_ENABLED ? "Get Started for Free" : "Sign In"}
);
}
function greeting() {
const h = new Date().getHours();
return h < 5 ? "Good evening" : h < 12 ? "Good morning" : h < 18 ? "Good afternoon" : "Good evening";
}
/** A row in a list that lifts out of its plate on hover (MetalUI's list row). */
const liftRow = "rounded-row transition-row outline-none hover:recipe-row-list-hover focus-visible:recipe-row-list-hover focus-visible:focus-ring-flush";
async function Dashboard({ viewer }: { viewer: User }) {
const [mine, recent, orgs, invitations] = await Promise.all([
listReposForUser(viewer.id),
searchRepos("", viewer.id, 30),
listOrgsOf(viewer.id),
listInvitationsFor(viewer.id),
]);
const [activity, explore] = await Promise.all([
recentActivity(mine),
withActivity(recent.filter((r) => r.ownerId !== viewer.id && r.visibility === "public").slice(0, 5)),
]);
return (
Your repositories {mine.length}
}>
New
({ owner: r.owner, name: r.name, visibility: r.visibility, imageVersion: r.imageVersion, ownerAvatarVersion: r.ownerAvatarVersion }))} />
Your organizations {orgs.length}
}>
New
{orgs.length === 0 && Share repositories with a team, and choose what each person may do.
}
{orgs.map(({ org }) => (
{org.avatarVersion ? : }
{org.username}
))}
{greeting()}, {viewer.name?.split(" ")[0] || viewer.username}
Here’s what’s been happening across your repositories.
{invitations.length > 0 && (
)}
{activity.length === 0 ? (
}
title={mine.length ? "No commits yet" : "Create your first repository"}
action={
: }
>
{mine.length ? "Open Repository" : "Create Repository"}
}
>
{mine.length ? "Push some code or add a file in the browser to see activity here." : "Repositories hold your project’s files and history."}
) : (
Recent activity {activity.length}
{activity.map(({ repo, commit }) => (
{commit.authorName} pushed to
{repo.owner}/{repo.name}
{timeAgo(commit.date)}
{shortSha(commit.sha)}
{commit.subject}
))}
)}
);
}