import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; import { dismissProNudge, getProPitch } from "~/fns/pro"; import { useAction } from "~/hooks/use-action"; import { MarketingHero } from "~/components/marketing/MarketingHero"; import { DisplayTitle } from "~/components/marketing/DisplayTitle"; import { ProBadge } from "~/components/billing/ProBadge"; import { PageContainer } from "~/ui/PageContainer"; import { buttonClass } from "~/ui/Button"; import { formatQuantity, meterDef } from "~/lib/meters"; import type { MeterId } from "~/lib/meters"; import { seo } from "~/utils/seo"; export const Route = createFileRoute("/pricing")({ loader: () => getProPitch(), head: ({ match }) => ({ meta: seo({ origin: match.context.publicUrl, path: "/pricing", title: "gitfor Pro", description: "More CI, more storage, hourly mirroring and AI merge fixes, and a forge that stays independent.", }), }), component: PricingPage, }); type Perk = { title: string; body: string; meters: MeterId[] }; const PERKS: Perk[] = [ { title: "CI that keeps up", body: "More cloud runner hours, plus bigger standard-2 runners for the builds that need them, and a roomier Actions cache so warm builds stay warm.", meters: ["compute.runner", "compute.runner_large", "storage.ci_cache"], }, { title: "Room for the big repos", body: "Generous repository and LFS storage, so the monorepo, the design assets and the test fixtures can all move in.", meters: ["storage.artifacts", "storage.lfs", "network.lfs"], }, { title: "A browser editor you can live in", body: "Ten times the editor container hours: open a repo, get a full dev environment, push without ever cloning.", meters: ["compute.editor"], }, { title: "Sites that can take a hug", body: "Millions of requests and a terabyte of egress for your sites. Launch day stops being scary.", meters: ["compute.site_requests", "network.sites", "storage.sites"], }, { title: "AI merge fixes and PR write-ups", body: "Twenty times the AI calls for resolving merge conflicts and drafting pull request descriptions.", meters: ["ai.calls"], }, ]; const EXTRAS = [ { title: "Hourly mirroring", body: "Repository mirrors sync every hour instead of daily, with ten times the push- and webhook-triggered syncs.", }, { title: "Grow past the limits, on your terms", body: "Free stops at its allowances. Pro keeps going at the same per-unit rates, capped by a spending limit you choose.", }, { title: "A little badge", body: "A modest Pro badge on your profile. Tasteful. Understated. Entirely deserved.", }, ]; function allowance(meter: MeterId, value: number | undefined): string { const def = meterDef(meter); if (!def || !value) return "—"; return formatQuantity(def.unit, value); } function PricingPage() { const data = Route.useLoaderData(); const navigate = useNavigate(); const dismiss = useAction(dismissProNudge, { invalidate: false }); const price = `$${(data.priceCents / 100).toFixed(0)}`; const cta = data.supporter ? ( Manage billing ) : data.signedIn ? ( Upgrade to Pro · {price}/month ) : ( Create an account ); return ( <> {cta} {data.nudge ? ( ) : null} } />
{PERKS.map((perk) => (

{perk.title}

{perk.body}

{perk.meters.map((meter) => (
{meterDef(meter)?.label ?? meter}
{allowance(meter, data.free[meter])} →{" "} {allowance(meter, data.pro[meter])}
))}
))}
{EXTRAS.map((extra) => (

{extra.title} {extra.title === "A little badge" ? : null}

{extra.body}

))}

gitfor is open source and has no investors to answer to. Pro subscriptions pay for the servers, the runners and the people who keep it all going, so the free tier can stay generous for everyone else.

{cta}
{!data.billingEnabled ? (

This instance doesn't sell subscriptions; everything is on the house.

) : null}

And from us over at gitfor, our undying gratitude.

); }