| 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 | } |