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.
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 1 | import { Suspense } from "react"; |
| 2 | import { Await, data } from "react-router"; | |
| 3 | ||
| 4 | import { PRODUCTS } from "@g1t/contracts"; | |
| 5 | ||
| 6 | import type { Route } from "./+types/usage"; | |
| 7 | import { UsageFilterBar, UsageSkeleton } from "../../components/usage"; | |
| 8 | import { page } from "../../lib/meta"; | |
| 9 | import { billing } from "../../lib/services.server"; | |
| 10 | import { getViewer, roleIn } from "../../lib/session.server"; | |
| 11 | import { USAGE_API, UsageView, readFilters } from "../workspace/usage"; | |
| 12 | ||
| 13 | // A project's usage: the workspace's Usage page, held to this one project. | |
| 14 | // Its members only, as the workspace's page is. | |
| 15 | ||
| 16 | export function meta({ params, ...args }: Route.MetaArgs) { | |
| 17 | return page(args, { title: `Usage · ${params.owner}/${params.repo} · g1t` }); | |
| 18 | } | |
| 19 | ||
| 20 | export async function loader({ params, context, request }: Route.LoaderArgs) { | |
| 21 | const viewer = getViewer(context); | |
| 22 | const role = roleIn(viewer, params.owner); | |
| 23 | if (!role) throw data(null, { status: 404 }); | |
| 24 | const slug = params.owner.toLowerCase(); | |
| 25 | const project = `${slug}/${params.repo.toLowerCase()}`; | |
| 26 | const url = new URL(request.url); | |
| 27 | const filters = readFilters(url, project); | |
| 28 | const report = billing | |
| 29 | .usageReport(slug, viewer, { from: filters.from, until: filters.until, products: filters.products, projects: [project] }) | |
| 30 | .then((result) => (result.ok ? { report: result.value, error: null } : { report: null, error: result.error.message })); | |
| 31 | return { slug, project, owner: role === "owner", filters, url: url.toString(), report }; | |
| 32 | } | |
| 33 | ||
| 34 | export default function ProjectUsage({ loaderData }: Route.ComponentProps) { | |
| 35 | const { slug, project, owner, filters, url, report } = loaderData; | |
| 36 | return ( | |
| 37 | <div className="mx-auto max-w-6xl px-4 py-6 sm:px-6"> | |
| 38 | <h1 className="mb-4 text-lg font-semibold tracking-tight">Usage</h1> | |
| 39 | <Suspense | |
| 40 | fallback={ | |
| 41 | <UsageSkeleton> | |
| 42 | <UsageFilterBar filters={filters} products={[...PRODUCTS]} projects={[]} report={null} apiHref={USAGE_API} fixedProject={project} /> | |
| 43 | </UsageSkeleton> | |
| 44 | } | |
| 45 | > | |
| 46 | <Await resolve={report}> | |
| 47 | {(loaded) => ( | |
| 48 | <UsageView slug={slug} owner={owner} filters={filters} url={url} report={loaded.report} error={loaded.error} fixedProject={project} /> | |
| 49 | )} | |
| 50 | </Await> | |
| 51 | </Suspense> | |
| 52 | </div> | |
| 53 | ); | |
| 54 | } |