aboutsummaryrefslogtreecommitdiff
path: root/web/ui/src/routes/dashboard/usage
diff options
context:
space:
mode:
authorbenj <benj@rse8.com>2026-04-10 11:13:34 +0800
committerbenj <benj@rse8.com>2026-04-10 11:13:34 +0800
commit493746b14c1251a45b061d2e3edd9160c929d2b9 (patch)
tree1607cceb94c1aac1a17a01bb5c0d71b97342e892 /web/ui/src/routes/dashboard/usage
parentc041641634650c31e03c70dcad132fd94cb08e63 (diff)
downloadtidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.gz
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.bz2
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.lz
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.xz
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.tar.zst
tidyindex-493746b14c1251a45b061d2e3edd9160c929d2b9.zip
a basic ui and landing web interface for tidyindex.com
Diffstat (limited to 'web/ui/src/routes/dashboard/usage')
-rw-r--r--web/ui/src/routes/dashboard/usage/+page.server.ts13
-rw-r--r--web/ui/src/routes/dashboard/usage/+page.svelte152
2 files changed, 165 insertions, 0 deletions
diff --git a/web/ui/src/routes/dashboard/usage/+page.server.ts b/web/ui/src/routes/dashboard/usage/+page.server.ts
new file mode 100644
index 0000000..34ff002
--- /dev/null
+++ b/web/ui/src/routes/dashboard/usage/+page.server.ts
@@ -0,0 +1,13 @@
+import { PLANS } from '$lib/plans';
+import { usageByDataset, usageByKey, usageCountThisMonth } from '$lib/server/usage';
+import type { PageServerLoad } from './$types';
+
+export const load: PageServerLoad = async ({ locals }) => {
+ const account = locals.account!;
+ return {
+ plan: PLANS[account.plan],
+ total: usageCountThisMonth(account.id),
+ byDataset: usageByDataset(account.id),
+ byKey: usageByKey(account.id)
+ };
+};
diff --git a/web/ui/src/routes/dashboard/usage/+page.svelte b/web/ui/src/routes/dashboard/usage/+page.svelte
new file mode 100644
index 0000000..883460c
--- /dev/null
+++ b/web/ui/src/routes/dashboard/usage/+page.svelte
@@ -0,0 +1,152 @@
+<script lang="ts">
+ import { pushToast } from '$lib/stores/toasts';
+ import type { PageData } from './$types';
+
+ let { data }: { data: PageData } = $props();
+
+ const monthName = new Date().toLocaleDateString('en-US', {
+ month: 'long',
+ year: 'numeric'
+ });
+
+ function fmt(n: number): string {
+ return n.toLocaleString('en-US');
+ }
+
+ const curlExample = `curl https://api.tidyindex.com/v1/datasets/irs-990/records/20-0049703 \\
+ -H "Authorization: Bearer YOUR_API_KEY"`;
+
+ async function copyCurl() {
+ try {
+ await navigator.clipboard.writeText(curlExample);
+ pushToast('Copied', 'success');
+ } catch {
+ pushToast("Couldn't copy", 'error');
+ }
+ }
+
+ let pct = $derived.by(() => {
+ if (!Number.isFinite(data.plan.requestsPerMonth)) return 0;
+ const p = (data.total / data.plan.requestsPerMonth) * 100;
+ return Math.min(100, Math.max(0, p));
+ });
+
+ let limitLabel = $derived(
+ Number.isFinite(data.plan.requestsPerMonth)
+ ? fmt(data.plan.requestsPerMonth)
+ : 'unlimited'
+ );
+</script>
+
+<p class="section-marker">ยง 02 &nbsp;&middot;&nbsp; usage</p>
+<h1 class="page-title">This month.</h1>
+<p class="page-subtitle">
+ Requests counted against your plan limit for {monthName}.
+</p>
+
+<div class="card">
+ <div class="usage-summary">
+ <div>
+ <p class="text-mono text-mute" style="margin:0 0 6px;">
+ requests / {limitLabel}
+ </p>
+ <p class="usage-count">
+ {fmt(data.total)}
+ {#if Number.isFinite(data.plan.requestsPerMonth)}
+ <small>&nbsp;of {limitLabel}</small>
+ {/if}
+ </p>
+ </div>
+ <div>
+ <span class="badge">{data.plan.name.toLowerCase()} plan</span>
+ </div>
+ </div>
+ {#if Number.isFinite(data.plan.requestsPerMonth)}
+ <div class="usage-bar" aria-label="Usage bar">
+ <div class="usage-bar-fill" style="width: {pct}%"></div>
+ </div>
+ {/if}
+</div>
+
+<div class="card mt-24">
+ <div class="card-head">
+ <h2 class="card-title">By dataset</h2>
+ <p class="card-sub">Which datasets drew the most traffic.</p>
+ </div>
+ {#if data.byDataset.length === 0}
+ <div class="empty-state">
+ <p class="empty-title">No requests yet this month.</p>
+ <p>Usage data will show up here once your keys start making calls.</p>
+ </div>
+ {:else}
+ <table class="usage-table">
+ <thead>
+ <tr>
+ <th>dataset</th>
+ <th style="text-align:right;">requests</th>
+ <th style="text-align:right; width: 140px;">share</th>
+ </tr>
+ </thead>
+ <tbody>
+ {#each data.byDataset as row}
+ <tr>
+ <td>{row.dataset}</td>
+ <td class="num">{fmt(row.count)}</td>
+ <td class="num">
+ {data.total > 0
+ ? ((row.count / data.total) * 100).toFixed(1)
+ : '0.0'}%
+ </td>
+ </tr>
+ {/each}
+ </tbody>
+ </table>
+ {/if}
+</div>
+
+<div class="card mt-24">
+ <div class="card-head">
+ <h2 class="card-title">By key</h2>
+ <p class="card-sub">Traffic attributed to each of your keys.</p>
+ </div>
+ {#if data.byKey.length === 0}
+ <div class="empty-state">
+ <p class="empty-title">No keys yet.</p>
+ <p>Create one on the Keys tab.</p>
+ </div>
+ {:else}
+ <table class="usage-table">
+ <thead>
+ <tr>
+ <th>key</th>
+ <th style="text-align:right;">requests</th>
+ </tr>
+ </thead>
+ <tbody>
+ {#each data.byKey as row}
+ <tr>
+ <td>{row.name}</td>
+ <td class="num">{fmt(row.count)}</td>
+ </tr>
+ {/each}
+ </tbody>
+ </table>
+ {/if}
+</div>
+
+<div class="card mt-24">
+ <div class="card-head">
+ <h2 class="card-title">API base URL</h2>
+ </div>
+ <p class="card-sub">All endpoints live under this host.</p>
+ <pre class="code-block mt-16">https://api.tidyindex.com/v1</pre>
+</div>
+
+<div class="card mt-24">
+ <div class="card-head">
+ <h2 class="card-title">Quick start</h2>
+ <button class="btn btn-sm btn-ghost" onclick={copyCurl}>Copy curl</button>
+ </div>
+ <p class="card-sub">Fetch one record from the IRS 990 dataset.</p>
+ <pre class="code-block mt-16">{curlExample}</pre>
+</div>