From 493746b14c1251a45b061d2e3edd9160c929d2b9 Mon Sep 17 00:00:00 2001
From: benj <benj@rse8.com>
Date: Fri, 10 Apr 2026 11:13:34 +0800
Subject: a basic ui and landing web interface for tidyindex.com

---
 web/ui/src/routes/dashboard/usage/+page.svelte | 152 +++++++++++++++++++++++++
 1 file changed, 152 insertions(+)
 create mode 100644 web/ui/src/routes/dashboard/usage/+page.svelte

(limited to 'web/ui/src/routes/dashboard/usage/+page.svelte')

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>
-- 
cgit v1.2.3

