| 1 | import { ArrowUpRight } from "lucide-react"; |
| 2 | |
| 3 | import { type FeaturePlan, type FreeTier, MICROS_PER_DOLLAR, type Price } from "@g1t/contracts"; |
| 4 | |
| 5 | import type { Route } from "./+types/pricing"; |
| 6 | import { wholeDollars } from "../lib/billing"; |
| 7 | import { page } from "../lib/meta"; |
| 8 | import { TimeAgo } from "../components/ui"; |
| 9 | import { billing } from "../lib/services.server"; |
| 10 | |
| 11 | export function meta(args: Route.MetaArgs) { |
| 12 | return page(args, { |
| 13 | title: "Pricing · g1t", |
| 14 | description: |
| 15 | "The forge is free. One plan, $20 a month per workspace with $10 of usage included; usage at what it costs g1t plus 20%. No seats, ever.", |
| 16 | }); |
| 17 | } |
| 18 | |
| 19 | export async function loader() { |
| 20 | const [book, status] = await Promise.all([billing.prices().catch(() => null), billing.status().catch(() => null)]); |
| 21 | return { book, free: status?.free ?? false }; |
| 22 | } |
| 23 | |
| 24 | /** A price in dollars, with as many digits as it needs to say anything. */ |
| 25 | function money(micros: number): string { |
| 26 | const dollars = micros / MICROS_PER_DOLLAR; |
| 27 | if (dollars >= 1) return `$${dollars.toFixed(2)}`; |
| 28 | if (dollars >= 0.01) return `$${dollars.toFixed(3)}`; |
| 29 | return `$${dollars.toPrecision(2)}`; |
| 30 | } |
| 31 | |
| 32 | /** Per second is easier to read per minute. */ |
| 33 | function perUnit(price: Price, micros: number): string { |
| 34 | return price.unit === "second" ? `${money(micros * 60)} a minute` : `${money(micros)} per ${price.unit}`; |
| 35 | } |
| 36 | |
| 37 | function gigabytes(bytes: number): string { |
| 38 | return `${Math.round((bytes / 1e9) * 10) / 10} GB`; |
| 39 | } |
| 40 | |
| 41 | function count(n: number): string { |
| 42 | return n.toLocaleString("en-US"); |
| 43 | } |
| 44 | |
| 45 | /** What the page says when billing cannot be reached: the published defaults. */ |
| 46 | const DEFAULT_FREE: Required<FreeTier> = { |
| 47 | trialWorkspaceMicros: 5_000_000, |
| 48 | trialMonthlyPoolMicros: 100_000_000, |
| 49 | ossPoolMicros: 25_000_000, |
| 50 | ossRepoMicros: 2_000_000, |
| 51 | freePrivateStorageBytes: 1_000_000_000, |
| 52 | auditRetentionDays: 90, |
| 53 | minChargeMicros: 5_000_000, |
| 54 | gitOperationsIncluded: 50_000, |
| 55 | paidStartCeilingMicros: 100_000_000, |
| 56 | overageForgiveCostMicros: 50_000_000, |
| 57 | }; |
| 58 | |
| 59 | const DEFAULT_PLAN: FeaturePlan = { |
| 60 | feature: "plan", |
| 61 | title: "g1t", |
| 62 | monthlyCents: 2000, |
| 63 | includes: [ |
| 64 | "$10 of usage each month at cost plus 20%, used first", |
| 65 | "Everyone in the workspace at one price, never per person", |
| 66 | "Unlimited projects, previews and repositories", |
| 67 | "Agents, checks, workflows, the merge queue, deployments and semantic search", |
| 68 | "Usage past $10 is charged at cost plus 20%, up to your spend limit", |
| 69 | ], |
| 70 | overage: "Everything is metered from the first unit at what it costs g1t plus 20%. Unused included usage does not roll over.", |
| 71 | }; |
| 72 | |
| 73 | /** The ways to reach g1t about an enterprise account. */ |
| 74 | const ENTERPRISE_MAIL = "mailto:hey@flagon.io?subject=Enterprise%20billing%20for%20g1t"; |
| 75 | |
| 76 | const HOW = [ |
| 77 | { |
| 78 | title: "Our cost, passed through", |
| 79 | body: "Every sandbox second, build, app request and model token costs g1t money at Cloudflare or a model provider. Each is metered and charged at that cost plus 20%, from the first second and the first request. Use a little, pay a little.", |
| 80 | }, |
| 81 | { |
| 82 | title: "Prices follow costs, by themselves", |
| 83 | body: "Model runs are charged at what Cloudflare's AI Gateway priced each request at, so a provider's price change reaches you the same day. Every day, each Cloudflare cost is checked against what Cloudflare billed g1t; when one moves, its price moves with it, and the change is listed below.", |
| 84 | }, |
| 85 | { |
| 86 | title: "The 20% is the overhead", |
| 87 | body: "It pays for running g1t and for building and keeping up the features you use. The same 20% on everything, and nothing bundled in.", |
| 88 | }, |
| 89 | { |
| 90 | title: "No seats, ever", |
| 91 | body: "Add as many people and agents as you like. A workspace pays one flat price for the plan, and for what it uses past what the plan includes.", |
| 92 | }, |
| 93 | ]; |
| 94 | |
| 95 | /** One row of the free-and-plan table. */ |
| 96 | type Row = { what: string; free: string; plan: string; note?: string }; |
| 97 | |
| 98 | function rows(tier: Required<FreeTier>): Row[] { |
| 99 | return [ |
| 100 | { |
| 101 | what: "The forge", |
| 102 | free: "Free", |
| 103 | plan: "Included", |
| 104 | note: "Public and private repositories, issues, pull requests, reviews, protected branches, code owners, search and Explore.", |
| 105 | }, |
| 106 | { |
| 107 | what: "Agents, checks, workflows, merge queue", |
| 108 | free: "After a card check: the trial, and the open-source pool for checks, workflows and the merge queue on public repositories", |
| 109 | plan: "From the plan's included usage, then at cost plus 20%", |
| 110 | }, |
| 111 | { |
| 112 | what: "Projects and previews", |
| 113 | free: "Not available: deployments run on g1t's machines", |
| 114 | plan: "Unlimited, and never charged for. An app no one visits costs nothing.", |
| 115 | }, |
| 116 | { |
| 117 | what: "Deployments", |
| 118 | free: "Not available", |
| 119 | plan: "Builds by the second, app requests, CPU time and custom domains, each from the first at cost plus 20%", |
| 120 | }, |
| 121 | { |
| 122 | what: "Semantic search", |
| 123 | free: "After a card check, from the trial", |
| 124 | plan: "From the plan's included usage, then at cost plus 20%", |
| 125 | }, |
| 126 | { |
| 127 | what: "Private storage", |
| 128 | free: `${gigabytes(tier.freePrivateStorageBytes)}, never charged. Past it, pushes to private repositories stop until you make room or start the plan.`, |
| 129 | plan: `${gigabytes(tier.freePrivateStorageBytes)} free, then at cost plus 20%. Pushes never stop.`, |
| 130 | note: "Public repositories are never charged for storage.", |
| 131 | }, |
| 132 | { |
| 133 | what: "Git operations", |
| 134 | free: `${count(tier.gitOperationsIncluded)} a month, never charged. Past it, slowed to 60 an hour until the month turns.`, |
| 135 | plan: `${count(tier.gitOperationsIncluded)} a month free, then at cost plus 20%. Never slowed.`, |
| 136 | note: "Clones, fetches and pushes through g1t.", |
| 137 | }, |
| 138 | { |
| 139 | what: "Audit log", |
| 140 | free: `${tier.auditRetentionDays} days, with export`, |
| 141 | plan: `${tier.auditRetentionDays} days, with export`, |
| 142 | }, |
| 143 | { what: "Secret push protection", free: "Included", plan: "Included" }, |
| 144 | { what: "Single sign-on", free: "On every plan, once it is built", plan: "On every plan, once it is built" }, |
| 145 | { what: "Members", free: "Unlimited", plan: "Unlimited" }, |
| 146 | { |
| 147 | what: "Usage past what is included", |
| 148 | free: "Not possible: a free workspace never runs up a bill", |
| 149 | plan: "Charged at cost plus 20%, up to a spend limit you set. No quotas: only your limit stops anything.", |
| 150 | }, |
| 151 | ]; |
| 152 | } |
| 153 | |
| 154 | export default function Pricing({ loaderData }: Route.ComponentProps) { |
| 155 | const { book, free } = loaderData; |
| 156 | const checked = book?.prices.map((p) => p.checkedAt).filter((at): at is string => !!at).sort().at(-1); |
| 157 | const given = Object.fromEntries(Object.entries(book?.free ?? {}).filter(([, value]) => value != null)); |
| 158 | const tier: Required<FreeTier> = { ...DEFAULT_FREE, ...given }; |
| 159 | const plan = book?.plans?.find((p) => p.feature === "plan") ?? DEFAULT_PLAN; |
| 160 | const price = plan.monthlyCents / 100; |
| 161 | const dollars = wholeDollars; |
| 162 | return ( |
| 163 | <main className="mx-auto max-w-4xl px-4 py-12"> |
| 164 | <p className="text-sm font-medium text-accent">Pricing</p> |
| 165 | <h1 className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">One plan, and usage at cost plus 20%</h1> |
| 166 | <p className="mt-3 max-w-2xl text-muted"> |
| 167 | The forge is free for everyone. Work that runs on g1t's machines is metered at what it costs g1t, plus 20%. The |
| 168 | numbers on this page are the live price book g1t charges from. |
| 169 | </p> |
| 170 | {free && ( |
| 171 | <div className="mt-5 rounded-xl border border-accent/30 bg-accent/5 px-4 py-3 text-sm"> |
| 172 | <span className="font-medium">Free while g1t is being built out.</span>{" "} |
| 173 | <span className="text-muted">Usage is recorded at these prices but not charged for now. The plan is charged.</span> |
| 174 | </div> |
| 175 | )} |
| 176 | |
| 177 | <section className="mt-10 rounded-xl border border-accent/40 bg-surface p-6"> |
| 178 | <div className="flex flex-wrap items-baseline justify-between gap-3"> |
| 179 | <h2 className="text-xl font-semibold tracking-tight">{plan.title}</h2> |
| 180 | <p> |
| 181 | <span className="text-3xl font-semibold tabular-nums">${price}</span>{" "} |
| 182 | <span className="text-sm text-muted">a month per workspace</span> |
| 183 | </p> |
| 184 | </div> |
| 185 | <ul className="mt-4 space-y-1.5 text-sm text-muted"> |
| 186 | {plan.includes.map((line) => ( |
| 187 | <li key={line} className="flex gap-2"> |
| 188 | <span aria-hidden className="text-accent"> |
| 189 | ✓ |
| 190 | </span> |
| 191 | {line} |
| 192 | </li> |
| 193 | ))} |
| 194 | </ul> |
| 195 | <p className="mt-3 text-xs text-faint">{plan.overage}</p> |
| 196 | <div className="mt-5 grid gap-5 border-t border-line pt-5 text-sm sm:grid-cols-2"> |
| 197 | <div> |
| 198 | <p className="font-medium">Where the ${price} goes</p> |
| 199 | <p className="mt-1 text-muted"> |
| 200 | $10 comes back to you as usage: agents, sandboxes, builds and deployments at cost plus 20%, used before |
| 201 | anything else. The other $10 pays for running g1t, the free forge for everyone, and the people building it. |
| 202 | </p> |
| 203 | </div> |
| 204 | <div> |
| 205 | <p className="font-medium">No quotas</p> |
| 206 | <p className="mt-1 text-muted"> |
| 207 | No count of projects, previews, builds, requests or git operations ever stops a workspace on the plan. Past |
| 208 | the $10, usage is charged at cost plus 20%, and only your spend limit stops it. Unused included usage does |
| 209 | not roll over. |
| 210 | </p> |
| 211 | </div> |
| 212 | </div> |
| 213 | <p className="mt-5 text-sm text-muted">An owner starts the plan from the workspace's Billing page.</p> |
| 214 | </section> |
| 215 | |
| 216 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Free and the plan, side by side</h2> |
| 217 | <p className="mt-1 text-sm text-muted">No security feature is held back for a plan.</p> |
| 218 | {/* On a phone, one row at a time with both answers under it; a table that wide would scroll. */} |
| 219 | <ul className="mt-4 divide-y divide-line rounded-xl border border-line text-sm sm:hidden"> |
| 220 | {rows(tier).map((row) => ( |
| 221 | <li key={row.what} className="px-4 py-3"> |
| 222 | <p className="font-medium">{row.what}</p> |
| 223 | {row.note && <p className="text-xs text-faint">{row.note}</p>} |
| 224 | <dl className="mt-2 grid grid-cols-[4.5rem_1fr] gap-x-3 gap-y-1.5"> |
| 225 | <dt className="text-xs leading-5 text-faint">Free</dt> |
| 226 | <dd className="text-muted">{row.free}</dd> |
| 227 | <dt className="text-xs leading-5 text-faint">{plan.title}</dt> |
| 228 | <dd className="text-muted">{row.plan}</dd> |
| 229 | </dl> |
| 230 | </li> |
| 231 | ))} |
| 232 | </ul> |
| 233 | <div className="mt-4 hidden overflow-x-auto rounded-xl border border-line sm:block"> |
| 234 | <table className="w-full min-w-[36rem] text-left text-sm"> |
| 235 | <thead className="border-b border-line text-xs text-muted"> |
| 236 | <tr> |
| 237 | <th className="px-4 py-2.5 font-medium"> |
| 238 | <span className="sr-only">What</span> |
| 239 | </th> |
| 240 | <th className="px-4 py-2.5 font-medium">Free</th> |
| 241 | <th className="px-4 py-2.5 font-medium"> |
| 242 | {plan.title}, ${price} a month |
| 243 | </th> |
| 244 | </tr> |
| 245 | </thead> |
| 246 | <tbody className="divide-y divide-line"> |
| 247 | {rows(tier).map((row) => ( |
| 248 | <tr key={row.what} className="align-top"> |
| 249 | <td className="px-4 py-3"> |
| 250 | <p className="font-medium">{row.what}</p> |
| 251 | {row.note && <p className="text-xs text-faint">{row.note}</p>} |
| 252 | </td> |
| 253 | <td className="px-4 py-3 text-muted">{row.free}</td> |
| 254 | <td className="px-4 py-3 text-muted">{row.plan}</td> |
| 255 | </tr> |
| 256 | ))} |
| 257 | </tbody> |
| 258 | </table> |
| 259 | </div> |
| 260 | <p className="mt-3 text-sm text-muted"> |
| 261 | Git operations cost g1t money too: Cloudflare charges g1t $0.15 per 1,000. {count(tier.gitOperationsIncluded)} a |
| 262 | month is far more than an active workspace uses; an agent run takes two to four. |
| 263 | </p> |
| 264 | |
| 265 | <h2 className="mt-14 text-xl font-semibold tracking-tight">No card, no compute</h2> |
| 266 | <div className="mt-3 max-w-3xl space-y-3 text-sm text-muted"> |
| 267 | <p> |
| 268 | Agents, checks, workflows, the merge queue, deployments and semantic search run on g1t's machines. They need |
| 269 | the plan, or a card check. |
| 270 | </p> |
| 271 | <p> |
| 272 | The reason: compute costs g1t real money from the first second, and free compute draws people who use it to |
| 273 | mine cryptocurrency. A card check is the smallest gate that stops that, with one trial per card. |
| 274 | </p> |
| 275 | <p> |
| 276 | A card check is never charged. Stripe saves and verifies the card, with 3-D Secure where your bank asks for it. |
| 277 | Nothing is charged until an owner starts the plan, and then the same card is used, without asking for it again. |
| 278 | The trial needs a credit or debit card; prepaid cards can still pay for the plan. |
| 279 | </p> |
| 280 | </div> |
| 281 | |
| 282 | <div className="mt-5 grid gap-4 sm:grid-cols-2"> |
| 283 | <section className="rounded-xl border border-line bg-surface p-5"> |
| 284 | <h3 className="font-medium">The trial</h3> |
| 285 | <p className="mt-1.5 text-sm text-muted"> |
| 286 | {dollars(tier.trialWorkspaceMicros)} of usage, once per workspace and once per card, after a card check with a credit or |
| 287 | debit card. It comes from a{" "} |
| 288 | {dollars(tier.trialMonthlyPoolMicros)} pool each month for everyone; when a month's pool is given out, new |
| 289 | trials wait until the 1st. The trial does not cover deployments and never turns into a charge. If the last |
| 290 | run on a trial goes past it, g1t covers the difference. |
| 291 | </p> |
| 292 | </section> |
| 293 | <section className="rounded-xl border border-line bg-surface p-5"> |
| 294 | <h3 className="font-medium">The open-source pool</h3> |
| 295 | <p className="mt-1.5 text-sm text-muted"> |
| 296 | Checks, workflows and the merge queue on public repositories, after a card check:{" "} |
| 297 | {dollars(tier.ossPoolMicros)} a month in all, up to {dollars(tier.ossRepoMicros)} a month per repository. |
| 298 | Agents do not run from the pool. When the pool or a repository's share is used up, the work waits for the |
| 299 | month to turn, or the workspace pays as usual. |
| 300 | </p> |
| 301 | </section> |
| 302 | </div> |
| 303 | |
| 304 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Limits, and how to raise them</h2> |
| 305 | <p className="mt-1 max-w-3xl text-sm text-muted"> |
| 306 | g1t lets usage run ahead of payment only so far. Limits keep a mistake small, for you and for g1t. |
| 307 | </p> |
| 308 | <ul className="mt-4 grid gap-3 text-sm sm:grid-cols-2"> |
| 309 | <li className="rounded-xl border border-line bg-surface p-4"> |
| 310 | <p className="font-medium">{dollars(tier.paidStartCeilingMicros)} in the first month</p> |
| 311 | <p className="mt-1 text-muted"> |
| 312 | A workspace on the plan can use up to {dollars(tier.paidStartCeilingMicros)} in its first month. After that, |
| 313 | the limit grows with payments that clear. |
| 314 | </p> |
| 315 | </li> |
| 316 | <li className="rounded-xl border border-line bg-surface p-4"> |
| 317 | <p className="font-medium">Your own spend limit</p> |
| 318 | <p className="mt-1 text-muted"> |
| 319 | Owners set a monthly spend limit anywhere up to the highest limit the workspace has had, with no approval. |
| 320 | Once, you can raise it yourself to twice that. |
| 321 | </p> |
| 322 | </li> |
| 323 | <li className="rounded-xl border border-line bg-surface p-4"> |
| 324 | <p className="font-medium">Prepay to raise it now</p> |
| 325 | <p className="mt-1 text-muted"> |
| 326 | Prepaying from $25 raises what you can use by the same amount, at once. From $1,000 you can also pay by bank |
| 327 | transfer. |
| 328 | </p> |
| 329 | </li> |
| 330 | <li className="rounded-xl border border-line bg-surface p-4"> |
| 331 | <p className="font-medium">Raise my limit</p> |
| 332 | <p className="mt-1 text-muted"> |
| 333 | Need more? Ask from Billing with the amount, why, and what you expect to spend. You get an answer within one |
| 334 | business day, in the app and by email. |
| 335 | </p> |
| 336 | </li> |
| 337 | </ul> |
| 338 | |
| 339 | <h2 className="mt-14 text-xl font-semibold tracking-tight">When usage runs high</h2> |
| 340 | <ul className="mt-4 grid gap-3 text-sm sm:grid-cols-2"> |
| 341 | <li className="rounded-xl border border-line bg-surface p-4"> |
| 342 | <p className="font-medium">A spike pauses new work</p> |
| 343 | <p className="mt-1 text-muted"> |
| 344 | When an hour's spend is five times your usual hour, and at least $5, new compute waits for an owner to choose |
| 345 | Keep going or Stop. Runs already going finish. |
| 346 | </p> |
| 347 | </li> |
| 348 | <li className="rounded-xl border border-line bg-surface p-4"> |
| 349 | <p className="font-medium">Caps per run and per issue</p> |
| 350 | <p className="mt-1 text-muted"> |
| 351 | One agent run stops at $2 and the agents on one issue at $10 in all, so a run stuck in a loop stops on its own. |
| 352 | Owners change both on Billing. |
| 353 | </p> |
| 354 | </li> |
| 355 | <li className="rounded-xl border border-line bg-surface p-4"> |
| 356 | <p className="font-medium">Alerts at 50, 75, 90 and 100%</p> |
| 357 | <p className="mt-1 text-muted"> |
| 358 | Of the plan's included usage, your spend limit and g1t's limit: in the app and by email. |
| 359 | </p> |
| 360 | </li> |
| 361 | <li className="rounded-xl border border-line bg-surface p-4"> |
| 362 | <p className="font-medium">Spent more than you meant to? Tell us</p> |
| 363 | <p className="mt-1 text-muted"> |
| 364 | Tell us from Billing what happened. Once in 12 months, g1t can credit back usage you did not mean, always |
| 365 | including its 20% on it. Larger credits, or a second one within 12 months, are reviewed by a person. |
| 366 | </p> |
| 367 | </li> |
| 368 | </ul> |
| 369 | <p className="mt-3 text-sm text-muted"> |
| 370 | No card is charged less than {dollars(tier.minChargeMicros)} when a month closes, so a payment's fee is never most |
| 371 | of it. Smaller amounts carry over to the next invoice. Charges at a limit always go through. |
| 372 | </p> |
| 373 | |
| 374 | <div className="mt-14 grid gap-4 sm:grid-cols-2"> |
| 375 | {HOW.map((item) => ( |
| 376 | <section key={item.title} className="rounded-xl border border-line bg-surface p-5"> |
| 377 | <h2 className="font-medium">{item.title}</h2> |
| 378 | <p className="mt-1.5 text-sm text-muted">{item.body}</p> |
| 379 | </section> |
| 380 | ))} |
| 381 | </div> |
| 382 | |
| 383 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Usage</h2> |
| 384 | <p className="mt-1 text-sm text-muted"> |
| 385 | {checked ? ( |
| 386 | <> |
| 387 | Last checked against Cloudflare's bill <TimeAgo at={checked} />. |
| 388 | </> |
| 389 | ) : ( |
| 390 | "Starting from Cloudflare's published prices; checked against its bill daily." |
| 391 | )} |
| 392 | </p> |
| 393 | <div className="mt-4 overflow-x-auto rounded-xl border border-line"> |
| 394 | <table className="w-full text-left text-sm sm:min-w-[36rem]"> |
| 395 | <thead className="border-b border-line text-xs text-muted"> |
| 396 | <tr> |
| 397 | <th className="px-4 py-2.5 font-medium">What</th> |
| 398 | <th className="px-4 py-2.5 font-medium">Costs g1t</th> |
| 399 | {/* The markup is in what you pay; on a phone the column gives way. */} |
| 400 | <th className="hidden px-4 py-2.5 font-medium sm:table-cell">Markup</th> |
| 401 | <th className="px-4 py-2.5 font-medium">You pay</th> |
| 402 | </tr> |
| 403 | </thead> |
| 404 | <tbody className="divide-y divide-line"> |
| 405 | <tr> |
| 406 | <td className="px-4 py-3"> |
| 407 | <p className="font-medium">Models</p> |
| 408 | <p className="text-xs text-faint">g1t's hosted models, through AI Gateway</p> |
| 409 | </td> |
| 410 | <td className="px-4 py-3 text-muted">What the provider charges, per request</td> |
| 411 | <td className="hidden px-4 py-3 tabular-nums sm:table-cell">{book?.modelMarginPercent ?? 20}%</td> |
| 412 | <td className="px-4 py-3 text-muted">Cost + {book?.modelMarginPercent ?? 20}%</td> |
| 413 | </tr> |
| 414 | <tr> |
| 415 | <td className="px-4 py-3"> |
| 416 | <p className="font-medium">Projects, previews and apps</p> |
| 417 | <p className="text-xs text-faint">An app costs only the requests and CPU it answers with</p> |
| 418 | </td> |
| 419 | <td className="px-4 py-3 text-muted">Next to nothing</td> |
| 420 | <td className="hidden px-4 py-3 tabular-nums sm:table-cell">—</td> |
| 421 | <td className="px-4 py-3 text-muted">Not charged</td> |
| 422 | </tr> |
| 423 | {(book?.prices ?? []).filter((price) => price.meter !== "app_month").map((price) => ( |
| 424 | <tr key={price.meter}> |
| 425 | <td className="px-4 py-3"> |
| 426 | <p className="font-medium">{price.title}</p> |
| 427 | <p className="text-xs text-faint"> |
| 428 | {price.source === "cloudflare" ? "Measured from Cloudflare's bill" : "Cloudflare's published price"} |
| 429 | </p> |
| 430 | </td> |
| 431 | <td className="px-4 py-3 font-mono text-xs tabular-nums text-muted">{perUnit(price, price.costMicros)}</td> |
| 432 | <td className="hidden px-4 py-3 tabular-nums sm:table-cell">{price.markupPercent}%</td> |
| 433 | <td className="px-4 py-3 font-mono text-xs tabular-nums">{perUnit(price, price.priceMicros)}</td> |
| 434 | </tr> |
| 435 | ))} |
| 436 | </tbody> |
| 437 | </table> |
| 438 | </div> |
| 439 | |
| 440 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Enterprise</h2> |
| 441 | <p className="mt-1 text-sm text-muted"> |
| 442 | For organizations with several workspaces: one invoice and one limit. The prices are the ones on this page. |
| 443 | </p> |
| 444 | <section className="mt-4 rounded-xl border border-line bg-surface p-5"> |
| 445 | <ul className="grid gap-3 text-sm sm:grid-cols-2"> |
| 446 | <li> |
| 447 | <p className="font-medium">Consolidated invoicing</p> |
| 448 | <p className="text-muted">One monthly invoice for every workspace, paid by card or bank transfer.</p> |
| 449 | </li> |
| 450 | <li> |
| 451 | <p className="font-medium">Custom terms</p> |
| 452 | <p className="text-muted">One limit across every workspace, invoice terms and a billing contact, set with you.</p> |
| 453 | </li> |
| 454 | <li> |
| 455 | <p className="font-medium">Security on every plan</p> |
| 456 | <p className="text-muted"> |
| 457 | The audit log for {tier.auditRetentionDays} days with export, and secret push protection, for every workspace. |
| 458 | </p> |
| 459 | </li> |
| 460 | <li> |
| 461 | <p className="font-medium"> |
| 462 | Single sign-on <span className="ml-1 rounded bg-raised px-1.5 py-0.5 text-xs text-muted">Coming</span> |
| 463 | </p> |
| 464 | <p className="text-muted">Through your identity provider, on every plan once it is built. Not available yet.</p> |
| 465 | </li> |
| 466 | </ul> |
| 467 | <a |
| 468 | href={ENTERPRISE_MAIL} |
| 469 | className="mt-5 inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-sm hover:border-line-strong" |
| 470 | > |
| 471 | Write to us about enterprise |
| 472 | <ArrowUpRight size={14} /> |
| 473 | </a> |
| 474 | </section> |
| 475 | |
| 476 | <h2 className="mt-14 text-xl font-semibold tracking-tight">Price changes</h2> |
| 477 | {book && book.changes.length > 0 ? ( |
| 478 | <ul className="mt-4 divide-y divide-line rounded-xl border border-line"> |
| 479 | {book.changes.map((change) => { |
| 480 | const title = book.prices.find((p) => p.meter === change.meter)?.title ?? change.meter; |
| 481 | // What you pay moved: the cost, the markup, or both. |
| 482 | const before = change.oldCostMicros * (100 + (change.oldMarkupPercent ?? change.markupPercent)); |
| 483 | const after = change.newCostMicros * (100 + change.markupPercent); |
| 484 | const up = after > before; |
| 485 | const fresh = after === before || before === 0; |
| 486 | return ( |
| 487 | <li key={`${change.meter}-${change.createdAt}`} className="px-4 py-3 text-sm"> |
| 488 | <p> |
| 489 | <span className="font-medium">{title}</span>{" "} |
| 490 | {fresh ? ( |
| 491 | <span className="text-muted">new</span> |
| 492 | ) : ( |
| 493 | <span className={up ? "text-warn" : "text-accent"}> |
| 494 | {up ? "up" : "down"} {Math.abs((after / before - 1) * 100).toFixed(1)}% |
| 495 | </span> |
| 496 | )} |
| 497 | </p> |
| 498 | <p className="mt-0.5 text-xs text-faint"> |
| 499 | {change.reason} · <TimeAgo at={change.createdAt} /> |
| 500 | </p> |
| 501 | </li> |
| 502 | ); |
| 503 | })} |
| 504 | </ul> |
| 505 | ) : ( |
| 506 | <p className="mt-2 text-sm text-muted">None yet. When a price moves, it is listed here with why.</p> |
| 507 | )} |
| 508 | |
| 509 | <a |
| 510 | href="https://docs.g1t.sh/guides/usage-and-billing/" |
| 511 | className="mt-12 inline-flex items-center gap-1.5 text-sm text-accent hover:underline" |
| 512 | > |
| 513 | How usage and billing work |
| 514 | <ArrowUpRight size={14} /> |
| 515 | </a> |
| 516 | </main> |
| 517 | ); |
| 518 | } |