g1t/apps/sudo/app/routes/overages.tsx
Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look | 1 | import { AlertTriangle, Gift } from "lucide-react"; |
| 2 | import { Link, data, redirect, useLocation } from "react-router"; | |
| 3 | ||
| 4 | import type { Overage } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import type { Route } from "./+types/overages"; | |
| 7 | import { Hidden } from "~/components/billing"; | |
| 8 | import { Badge, Button, EmptyState, Field, Input, Notice, PageHeader, Stat, Textarea, When } from "~/components/ui"; | |
| 9 | import { fields, parseGoodwill } from "~/lib/forms"; | |
| 10 | import { dollarsField, parseDollars, usd } from "~/lib/money"; | |
| 11 | import { FORGIVE_COST_MICROS, goodwillChoices, goodwillLine, goodwillWarning, spikeLabel } from "~/lib/pricing"; | |
| 12 | import { DONE, doneKey } from "~/lib/review"; | |
| 13 | import { admin, priceBook } from "~/lib/services.server"; | |
| 14 | import { settle } from "~/lib/settle"; | |
| 15 | import { requireStaff } from "~/lib/staff"; | |
| 16 | ||
| 17 | export const meta: Route.MetaFunction = () => [{ title: "Overages · sudo" }, { name: "robots", content: "noindex, nofollow" }]; | |
| 18 | ||
| 19 | async function capMicros(): Promise<number> { | |
| 20 | const book = await settle(priceBook()); | |
| 21 | return (book.ok && book.value.free?.overageForgiveCostMicros) || FORGIVE_COST_MICROS; | |
| 22 | } | |
| 23 | ||
| 24 | export async function loader({ request, context }: Route.LoaderArgs) { | |
| 25 | requireStaff(context); | |
| 26 | const [queue, cap] = await Promise.all([settle(admin.overages()), capMicros()]); | |
| 27 | const done = doneKey(request.url); | |
| 28 | return { | |
| 29 | queue: queue.ok ? queue.value : [], | |
| 30 | error: queue.ok ? null : queue.error, | |
| 31 | cap, | |
| 32 | done: done ? DONE[done] : null, | |
| 33 | }; | |
| 34 | } | |
| 35 | ||
| 36 | type Review = { | |
| 37 | workspace: string; | |
| 38 | amount: string; | |
| 39 | day: string; | |
| 40 | reason: string; | |
| 41 | absorbedMicros: number; | |
| 42 | overCap: boolean; | |
| 43 | needsReason: boolean; | |
| 44 | amountMicros: number; | |
| 45 | }; | |
| 46 | ||
| 47 | type ActionResult = { error: string; workspace: string; values: Record<string, string> } | { review: Review }; | |
| 48 | ||
| 49 | export async function action({ request, context }: Route.ActionArgs) { | |
| 50 | const staff = requireStaff(context); | |
| 51 | const form = await request.formData(); | |
| 52 | const values = fields(form, "workspace", "amount", "reason", "day"); | |
| 53 | const fail = (error: string) => data<ActionResult>({ error, workspace: values.workspace, values }, { status: 422 }); | |
| 54 | const [queue, cap] = await Promise.all([admin.overages(), capMicros()]); | |
| 55 | // What is credited comes from billing's queue, not the form. | |
| 56 | const overage = queue.find((row) => row.workspace === values.workspace); | |
| 57 | if (!overage) return fail("That workspace is no longer in the Overages queue. Give credit from its page instead."); | |
| 58 | const parsed = parseGoodwill(form, (amount) => goodwillWarning(amount, overage.goodwill, overage.lastGoodwillAt, cap).needsReason); | |
| 59 | // The amount as typed, even when the form is not complete yet: it decides whether the red review shows. | |
| 60 | const typed = values.amount ? parseDollars(values.amount) : null; | |
| 61 | const warning = goodwillWarning(typed, overage.goodwill, overage.lastGoodwillAt, cap); | |
| 62 | // Past the cap, staff see what g1t absorbs in red, with the reason, before it goes. | |
| 63 | if (warning.overCap && form.get("confirm") !== "yes") { | |
| 64 | return { | |
| 65 | review: { | |
| 66 | workspace: overage.workspace, | |
| 67 | amount: values.amount ?? "", | |
| 68 | day: values.day ?? "", | |
| 69 | reason: values.reason ?? "", | |
| 70 | ...warning, | |
| 71 | amountMicros: typed ?? overage.goodwill.creditMicros, | |
| 72 | }, | |
| 73 | } satisfies ActionResult; | |
| 74 | } | |
| 75 | if (!parsed.ok) return fail(parsed.error); | |
| 76 | const result = await admin.goodwill(overage.workspace, parsed.value.amountMicros, parsed.value.reason, staff.email, parsed.value.day); | |
| 77 | if (!result.ok) return fail(result.error.message); | |
| 78 | throw redirect("/overages?done=goodwill"); | |
| 79 | } | |
| 80 | ||
| 81 | export default function Overages({ loaderData, actionData }: Route.ComponentProps) { | |
| 82 | const { queue, error, cap, done } = loaderData; | |
| 83 | const result = actionData as ActionResult | undefined; | |
| 84 | const review = result && "review" in result ? result.review : null; | |
| 85 | const totals = queue.reduce( | |
| 86 | (sum, row) => ({ | |
| 87 | overage: sum.overage + row.goodwill.overageMicros, | |
| 88 | margin: sum.margin + row.goodwill.marginMicros, | |
| 89 | absorbed: sum.absorbed + (row.goodwillAvailable ? row.goodwill.absorbedMicros : 0), | |
| 90 | }), | |
| 91 | { overage: 0, margin: 0, absorbed: 0 }, | |
| 92 | ); | |
| 93 | return ( | |
| 94 | <main className="mx-auto max-w-6xl px-4 py-8 sm:py-10"> | |
| 95 | <PageHeader | |
| 96 | title="Overages" | |
| 97 | description={ | |
| 98 | <> | |
| 99 | Workspaces whose month went well past their usual (twice it, and $10 over), hit a spend spike, or asked for help. A goodwill credit | |
| 100 | always returns g1t's margin on the overage and absorbs at most {usd(cap)} of real cost in one click, once per workspace in 12 months. | |
| 101 | </> | |
| 102 | } | |
| 103 | /> | |
| 104 | <div className="mt-5 space-y-3 empty:hidden"> | |
| 105 | {done && <Notice tone="ok">{done}</Notice>} | |
| 106 | {error && <Notice tone="warn">Billing did not answer for overages: {error}</Notice>} | |
| 107 | </div> | |
| 108 | {queue.length > 0 && ( | |
| 109 | <div className="mt-5 grid grid-cols-2 gap-3 lg:grid-cols-4"> | |
| 110 | <Stat label="In the queue" value={String(queue.length)} hint={`${queue.filter((row) => row.request).length} asked for help`} /> | |
| 111 | <Stat label="Over their usual" value={usd(totals.overage)} hint="This month, above each typical month" /> | |
| 112 | <Stat label="g1t's margin in it" value={usd(totals.margin)} hint="Returned by any goodwill credit" /> | |
| 113 | <Stat label="Real cost, if all forgiven" value={usd(totals.absorbed)} hint="One-click credits still available" tone={totals.absorbed > 0 ? "warn" : undefined} /> | |
| 114 | </div> | |
| 115 | )} | |
| 116 | {review && <ReviewPanel review={review} cap={cap} />} | |
| 117 | <div className="mt-6 space-y-4"> | |
| 118 | {queue.length === 0 && !error ? ( | |
| 119 | <EmptyState title="No overages this month">Workspaces show up here when a month runs well past their usual, or a spike pauses them.</EmptyState> | |
| 120 | ) : ( | |
| 121 | queue.map((row) => ( | |
| 122 | <OverageCard | |
| 123 | key={row.workspace} | |
| 124 | row={row} | |
| 125 | cap={cap} | |
| 126 | error={result && "error" in result && result.workspace === row.workspace ? result : null} | |
| 127 | /> | |
| 128 | )) | |
| 129 | )} | |
| 130 | </div> | |
| 131 | </main> | |
| 132 | ); | |
| 133 | } | |
| 134 | ||
| 135 | /** Past the cap: what g1t absorbs in red, and the reason, before it is given. */ | |
| 136 | function ReviewPanel({ review, cap }: { review: Review; cap: number }) { | |
| 137 | const { pathname } = useLocation(); | |
| 138 | return ( | |
| 139 | <section id="review" className="mt-6 scroll-mt-20 rounded-lg border border-danger/50 bg-danger/8 p-4 sm:p-5"> | |
| 140 | <h2 className="flex items-center gap-2 font-semibold tracking-tight text-danger"> | |
| 141 | <AlertTriangle size={16} /> | |
| 142 | g1t absorbs {usd(review.absorbedMicros)} of real cost | |
| 143 | </h2> | |
| 144 | <p className="mt-2 text-sm text-fg-soft"> | |
| 145 | A {usd(review.amountMicros)} credit to <span className="font-mono">{review.workspace}</span> is past the {usd(cap)} a goodwill credit | |
| 146 | absorbs in one click. That is money g1t already paid Cloudflare and model providers. Say why, for whoever looks next; it goes in the | |
| 147 | audit log with your email. | |
| 148 | </p> | |
| 149 | <form method="post" action={`${pathname}#review`} className="mt-4 space-y-3"> | |
| 150 | <Hidden values={{ workspace: review.workspace, amount: review.amount, day: review.day, confirm: "yes" }} /> | |
| 151 | <Field label="Reason" hint="Required: a sentence at least."> | |
| 152 | <Textarea name="reason" rows={2} required minLength={10} maxLength={500} defaultValue={review.reason} /> | |
| 153 | </Field> | |
| 154 | <div className="flex flex-wrap items-center gap-2"> | |
| 155 | <Button type="submit" variant="danger"> | |
| 156 | Give {usd(review.amountMicros)} anyway | |
| 157 | </Button> | |
| 158 | <Link to="/overages" className="px-2 text-sm text-muted hover:text-fg"> | |
| 159 | Cancel | |
| 160 | </Link> | |
| 161 | </div> | |
| 162 | </form> | |
| 163 | </section> | |
| 164 | ); | |
| 165 | } | |
| 166 | ||
| 167 | function OverageCard({ row, cap, error }: { row: Overage; cap: number; error: { error: string; values: Record<string, string> } | null }) { | |
| 168 | const { pathname } = useLocation(); | |
| 169 | const { goodwill } = row; | |
| 170 | const spike = row.spike ? spikeLabel(row.spike.status) : null; | |
| 171 | const choices = goodwillChoices(goodwill).map((choice) => ({ | |
| 172 | ...choice, | |
| 173 | ...goodwillWarning(choice.micros, goodwill, row.lastGoodwillAt, cap), | |
| 174 | amount: choice.micros ?? goodwill.creditMicros, | |
| 175 | })); | |
| 176 | return ( | |
| 177 | <section id={row.workspace} className="scroll-mt-20 rounded-lg border border-line bg-surface"> | |
| 178 | <header className="flex flex-wrap items-start justify-between gap-3 border-b border-line px-4 py-3 sm:px-5"> | |
| 179 | <div className="min-w-0"> | |
| 180 | <p className="flex flex-wrap items-center gap-2"> | |
| 181 | <Link to={`/workspaces/${encodeURIComponent(row.workspace)}#billing`} className="font-mono font-medium text-fg hover:underline"> | |
| 182 | {row.workspace} | |
| 183 | </Link> | |
| 184 | <Badge>{row.plan}</Badge> | |
| 185 | {spike && <Badge tone={spike.tone === "plain" ? "plain" : spike.tone}>{spike.label}</Badge>} | |
| 186 | {row.request && <Badge tone="warn">Asked for help</Badge>} | |
| 187 | {!row.goodwillAvailable && <Badge tone="danger">Goodwill given{" "}<When at={row.lastGoodwillAt} /></Badge>} | |
| 188 | </p> | |
| 189 | <p className="mt-0.5 text-xs text-faint"> | |
| 190 | Typical month {usd(row.typicalMonthMicros)} · this month {usd(row.thisMonthMicros)} · cost to g1t {usd(row.costMicros)} | |
| 191 | </p> | |
| 192 | </div> | |
| 193 | </header> | |
| 194 | <div className="grid gap-5 p-4 sm:p-5 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)]"> | |
| 195 | <div className="min-w-0 space-y-4"> | |
| 196 | {row.request && ( | |
| 197 | <blockquote className="border-l-2 border-warn/50 pl-3 text-sm break-words whitespace-pre-line text-fg-soft"> | |
| 198 | “{row.request.reason}” <span className="text-xs text-faint">— {row.request.createdBy}</span> | |
| 199 | </blockquote> | |
| 200 | )} | |
| 201 | <div> | |
| 202 | <p className="text-xs font-medium text-muted">The goodwill math</p> | |
| 203 | <dl className="mt-2 space-y-1 text-sm"> | |
| 204 | <Line label="Over the typical month" value={usd(goodwill.overageMicros)} /> | |
| 205 | <Line label="g1t's margin on it, always returned" value={usd(goodwill.marginMicros)} tone="mint" /> | |
| 206 | <Line label="What it cost g1t" value={usd(goodwill.costMicros)} /> | |
| 207 | <Line label={`Real cost absorbed in one click (at most ${usd(cap)})`} value={usd(goodwill.absorbedMicros)} tone="warn" /> | |
| 208 | <Line label="One-click credit" value={usd(goodwill.creditMicros)} strong /> | |
| 209 | </dl> | |
| 210 | </div> | |
| 211 | {row.topEntries.length > 0 && ( | |
| 212 | <div> | |
| 213 | <p className="text-xs font-medium text-muted">What caused it</p> | |
| 214 | <ul className="mt-2 divide-y divide-line rounded-md border border-line text-xs"> | |
| 215 | {row.topEntries.slice(0, 5).map((entry) => ( | |
| 216 | <li key={entry.id} className="flex items-start justify-between gap-3 px-3 py-2"> | |
| 217 | <span className="min-w-0"> | |
| 218 | <span className="block break-words text-fg-soft">{entry.description}</span> | |
| 219 | <span className="font-mono text-faint"> | |
| 220 | {[entry.repo && (entry.number != null ? `${entry.repo}#${entry.number}` : entry.repo), entry.task, entry.model].filter(Boolean).join(" · ")} | |
| 221 | </span> | |
| 222 | </span> | |
| 223 | <span className="tabular shrink-0">{usd(-entry.amountMicros)}</span> | |
| 224 | </li> | |
| 225 | ))} | |
| 226 | </ul> | |
| 227 | </div> | |
| 228 | )} | |
| 229 | </div> | |
| 230 | <div className="min-w-0 space-y-3"> | |
| 231 | <ul className="space-y-2"> | |
| 232 | {choices.map((choice) => ( | |
| 233 | <li | |
| 234 | key={choice.label} | |
| 235 | className={`rounded-md border px-3 py-2 text-sm ${choice.overCap ? "border-danger/50 bg-danger/8" : "border-line bg-bg"}`} | |
| 236 | > | |
| 237 | <p className="flex justify-between gap-3"> | |
| 238 | <span className="font-medium">{choice.label}</span> | |
| 239 | <span className="tabular">{usd(choice.amount)}</span> | |
| 240 | </p> | |
| 241 | <p className={`mt-0.5 text-xs ${choice.overCap ? "text-danger" : "text-muted"}`}> | |
| 242 | Returns {usd(Math.min(choice.amount, goodwill.marginMicros))} of margin; g1t absorbs {usd(choice.absorbedMicros)} of real cost | |
| 243 | {choice.overCap ? `, past the ${usd(cap)} cap` : ""}.{choice.needsReason ? " Needs a reason." : ""} | |
| 244 | </p> | |
| 245 | </li> | |
| 246 | ))} | |
| 247 | </ul> | |
| 248 | <form method="post" action={`${pathname}#${row.workspace}`} className="space-y-3"> | |
| 249 | <input type="hidden" name="workspace" value={row.workspace} /> | |
| 250 | {error && <Notice tone="error">{error.error}</Notice>} | |
| 251 | <div className="grid gap-3 sm:grid-cols-2"> | |
| 252 | <Field label="Amount $" hint="Blank: the one-click credit."> | |
| 253 | <Input name="amount" inputMode="decimal" placeholder={dollarsField(goodwill.creditMicros)} defaultValue={error?.values.amount ?? ""} /> | |
| 254 | </Field> | |
| 255 | <Field label="Day of the usage" hint="Blank: the spike's day, or today."> | |
| 256 | <Input type="date" name="day" defaultValue={error?.values.day ?? row.spike?.detectedAt.slice(0, 10) ?? ""} /> | |
| 257 | </Field> | |
| 258 | </div> | |
| 259 | <Field label="Reason" hint={row.goodwillAvailable ? "Needed past the one-click credit." : "Needed: a credit was given in the last 12 months."}> | |
| 260 | <Textarea name="reason" rows={2} maxLength={500} defaultValue={error?.values.reason ?? ""} placeholder="e.g. A review loop ran overnight on a misconfigured workflow." /> | |
| 261 | </Field> | |
| 262 | <div className="flex flex-wrap items-center justify-between gap-2"> | |
| 263 | <p className="text-xs text-faint">On their statement as “{goodwillLine("YYYY-MM-DD")}”.</p> | |
| 264 | <Button type="submit" variant="lavender"> | |
| 265 | <Gift size={14} /> | |
| 266 | Give goodwill credit | |
| 267 | </Button> | |
| 268 | </div> | |
| 269 | </form> | |
| 270 | </div> | |
| 271 | </div> | |
| 272 | </section> | |
| 273 | ); | |
| 274 | } | |
| 275 | ||
| 276 | function Line({ label, value, tone, strong = false }: { label: string; value: string; tone?: "mint" | "warn"; strong?: boolean }) { | |
| 277 | return ( | |
| 278 | <div className={`flex justify-between gap-4 ${strong ? "border-t border-line pt-1 font-medium" : ""}`}> | |
| 279 | <dt className="text-muted">{label}</dt> | |
| 280 | <dd className={`tabular ${tone === "mint" ? "text-accent" : tone === "warn" ? "text-warn" : "text-fg"}`}>{value}</dd> | |
| 281 | </div> | |
| 282 | ); | |
| 283 | } |