Skip to content

g1t/apps/web/app/routes/repo/usage.tsx

54 lines2,310 bytesCodeBlame
1import { Suspense } from "react";
2import { Await, data } from "react-router";
3
4import { PRODUCTS } from "@g1t/contracts";
5
6import type { Route } from "./+types/usage";
7import { UsageFilterBar, UsageSkeleton } from "../../components/usage";
8import { page } from "../../lib/meta";
9import { billing } from "../../lib/services.server";
10import { getViewer, roleIn } from "../../lib/session.server";
11import { 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
16export function meta({ params, ...args }: Route.MetaArgs) {
17 return page(args, { title: `Usage · ${params.owner}/${params.repo} · g1t` });
18}
19
20export 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
34export 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}