[verified] refactor: remove dashboard payload panels

This commit is contained in:
2026-05-09 16:32:30 +09:00
parent 89983fd84b
commit c763926b32
5 changed files with 107 additions and 133 deletions

View File

@@ -53,7 +53,6 @@ import {
import { Progress } from '@/components/ui/progress'; import { Progress } from '@/components/ui/progress';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { JsonViewer } from '@/components/json-viewer';
const registerSchema = z.object({ const registerSchema = z.object({
name: z.string().min(2), name: z.string().min(2),
@@ -706,42 +705,34 @@ function Dashboard() {
</div> </div>
</div> </div>
<div className="mt-6 grid gap-6 xl:grid-cols-[1.15fr_0.85fr]"> <Card className="mt-6 min-w-0">
<Card className="min-w-0"> <CardHeader>
<CardHeader> <CardTitle>Usage metrics</CardTitle>
<CardTitle>Usage metrics</CardTitle> </CardHeader>
</CardHeader> <CardContent className="min-w-0">
<CardContent className="min-w-0"> {metricCards.length === 0 ? (
{metricCards.length === 0 ? ( <div className="rounded-2xl border border-dashed border-white/10 bg-white/3 p-6 text-sm text-slate-400">
<div className="rounded-2xl border border-dashed border-white/10 bg-white/3 p-6 text-sm text-slate-400"> No usage data yet. Connect an OpenAI account and complete the sign-in flow to start refreshing.
No usage data yet. Connect an OpenAI account and complete the sign-in flow to start refreshing. </div>
</div> ) : (
) : ( <div className="grid gap-3 sm:grid-cols-2">
<div className="grid gap-3 sm:grid-cols-2"> {metricCards.map((metric) => (
{metricCards.map((metric) => ( <div
<div key={metric.label}
key={metric.label} className="min-w-0 rounded-2xl border border-white/10 bg-white/4 p-4"
className="min-w-0 rounded-2xl border border-white/10 bg-white/4 p-4" >
> <div className="text-sm text-slate-400 break-words">
<div className="text-sm text-slate-400 break-words"> {titleizeMetric(metric.label)}
{titleizeMetric(metric.label)}
</div>
<div className="mt-3 text-2xl font-semibold text-white">
{metric.value.toLocaleString()}
</div>
</div> </div>
))} <div className="mt-3 text-2xl font-semibold text-white">
</div> {metric.value.toLocaleString()}
)} </div>
</CardContent> </div>
</Card> ))}
</div>
<JsonViewer )}
title="Merged payload" </CardContent>
description="Combined raw JSON." </Card>
value={summary.aggregatedUsage ?? { message: 'No data yet' }}
/>
</div>
<Card className="mt-6"> <Card className="mt-6">
<CardHeader> <CardHeader>
@@ -768,79 +759,68 @@ function Dashboard() {
</TabsList> </TabsList>
{summary.accounts.map((account) => ( {summary.accounts.map((account) => (
<TabsContent key={account.id} value={account.id}> <TabsContent key={account.id} value={account.id}>
<div className="grid gap-4 lg:grid-cols-[0.9fr_1.1fr]"> <div className="space-y-4 rounded-3xl border border-white/10 bg-white/4 p-5">
<div className="space-y-4 rounded-3xl border border-white/10 bg-white/4 p-5"> <div className="flex items-start justify-between gap-4">
<div className="flex items-start justify-between gap-4"> <div>
<div> <div className="text-lg font-semibold text-white">
<div className="text-lg font-semibold text-white"> {account.label}
{account.label}
</div>
<div className="mt-1 text-sm text-slate-400">
{account.providerEmail ||
account.emailHint ||
'No email available yet'}
</div>
</div> </div>
<Badge <div className="mt-1 text-sm text-slate-400">
className={ {account.providerEmail ||
account.status === 'active' account.emailHint ||
? 'border-emerald-400/20 bg-emerald-400/10 text-emerald-200' 'No email available yet'}
: 'border-rose-400/20 bg-rose-400/10 text-rose-200'
}
>
{account.status}
</Badge>
</div>
<Separator />
<div className="space-y-2 text-sm text-slate-300">
<div className="flex items-center gap-2">
<Waypoints className="size-4 text-sky-300" />
Auth: {account.authType}
</div>
<div>Plan: {account.planType || 'Unknown'}</div>
<div>
Provider account:{' '}
<span className="text-slate-400">
{account.providerAccountId || 'Unknown'}
</span>
</div>
<div>Session expires: {formatDate(account.sessionExpiresAt)}</div>
<div>Last synced: {formatDate(account.lastSyncedAt)}</div>
<div>Connected: {formatDate(account.createdAt)}</div>
<div>
Error:{' '}
<span className="text-slate-400">
{account.lastError || 'None'}
</span>
</div> </div>
</div> </div>
<div className="flex gap-3"> <Badge
<Button className={
variant="outline" account.status === 'active'
onClick={() => summaryQuery.refetch()} ? 'border-emerald-400/20 bg-emerald-400/10 text-emerald-200'
> : 'border-rose-400/20 bg-rose-400/10 text-rose-200'
<RefreshCw className="size-4" /> }
Refresh >
</Button> {account.status}
<Button </Badge>
variant="destructive" </div>
disabled={deleteMutation.isPending} <Separator />
onClick={() => deleteMutation.mutate(account.id)} <div className="space-y-2 text-sm text-slate-300">
> <div className="flex items-center gap-2">
<Trash2 className="size-4" /> <Waypoints className="size-4 text-sky-300" />
Remove Auth: {account.authType}
</Button> </div>
<div>Plan: {account.planType || 'Unknown'}</div>
<div>
Provider account:{' '}
<span className="text-slate-400">
{account.providerAccountId || 'Unknown'}
</span>
</div>
<div>Session expires: {formatDate(account.sessionExpiresAt)}</div>
<div>Last synced: {formatDate(account.lastSyncedAt)}</div>
<div>Connected: {formatDate(account.createdAt)}</div>
<div>
Error:{' '}
<span className="text-slate-400">
{account.lastError || 'None'}
</span>
</div> </div>
</div> </div>
<JsonViewer <div className="flex gap-3">
title="Account payload" <Button
description="Raw JSON for this account." variant="outline"
value={ onClick={() => summaryQuery.refetch()}
account.usage ?? { >
message: account.lastError || 'No usage fetched yet', <RefreshCw className="size-4" />
} Refresh
} </Button>
/> <Button
variant="destructive"
disabled={deleteMutation.isPending}
onClick={() => deleteMutation.mutate(account.id)}
>
<Trash2 className="size-4" />
Remove
</Button>
</div>
</div> </div>
</TabsContent> </TabsContent>
))} ))}

View File

@@ -1,19 +0,0 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
export function JsonViewer({ title, description, value }: { title: string; description: string; value: unknown }) {
return (
<Card className="min-w-0 border-white/8 bg-slate-900/80">
<CardHeader>
<CardTitle>{title}</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent className="min-w-0">
<div className="min-w-0 max-h-80 overflow-x-auto overflow-y-auto rounded-2xl bg-black/30">
<pre className="min-w-0 whitespace-pre-wrap break-all p-4 text-xs leading-6 text-slate-300">
{JSON.stringify(value, null, 2)}
</pre>
</div>
</CardContent>
</Card>
);
}

View File

@@ -9,7 +9,6 @@ describe('dashboard card copy', () => {
expect(appSource).toContain('<CardTitle>Unified capacity</CardTitle>'); expect(appSource).toContain('<CardTitle>Unified capacity</CardTitle>');
expect(appSource).toContain('<CardTitle>Usage metrics</CardTitle>'); expect(appSource).toContain('<CardTitle>Usage metrics</CardTitle>');
expect(appSource).toContain('<CardTitle>Connected OpenAI accounts</CardTitle>'); expect(appSource).toContain('<CardTitle>Connected OpenAI accounts</CardTitle>');
expect(appSource).toContain('description="Combined raw JSON."');
expect(appSource).toContain(">Merged by default. Inspect each account below.<"); expect(appSource).toContain(">Merged by default. Inspect each account below.<");
expect(appSource).not.toContain( expect(appSource).not.toContain(
'Fast glance card for the first two numeric metrics extracted from the merged usage payload.', 'Fast glance card for the first two numeric metrics extracted from the merged usage payload.',
@@ -20,8 +19,10 @@ describe('dashboard card copy', () => {
expect(appSource).not.toContain( expect(appSource).not.toContain(
'Raw aggregated JSON merged from every attached OpenAI Codex account.', 'Raw aggregated JSON merged from every attached OpenAI Codex account.',
); );
expect(appSource).not.toContain('Combined raw JSON.');
expect(appSource).not.toContain( expect(appSource).not.toContain(
'By default, these accounts are merged into one Codex usage view. Switch tabs to inspect individual account payloads and timestamps.', 'By default, these accounts are merged into one Codex usage view. Switch tabs to inspect individual account payloads and timestamps.',
); );
expect(appSource).not.toContain('Raw JSON for this account.');
}); });
}); });

View File

@@ -0,0 +1,17 @@
import { describe, expect, test } from 'bun:test';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8');
describe('dashboard payload panels', () => {
test('removes merged payload and account payload panels from the dashboard', () => {
expect(appSource).not.toContain('title="Merged payload"');
expect(appSource).not.toContain('title="Account payload"');
expect(appSource).not.toContain('description="Combined raw JSON."');
expect(appSource).not.toContain('description="Raw JSON for this account."');
expect(appSource).not.toContain("summary.aggregatedUsage ?? { message: 'No data yet' }");
expect(appSource).not.toContain("account.usage ?? {");
expect(appSource).not.toContain("import { JsonViewer } from '@/components/json-viewer';");
});
});

View File

@@ -3,22 +3,17 @@ import { readFileSync } from 'node:fs';
import { join } from 'node:path'; import { join } from 'node:path';
const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8'); const appSource = readFileSync(join(import.meta.dir, '../src/App.tsx'), 'utf8');
const jsonViewerSource = readFileSync(
join(import.meta.dir, '../src/components/json-viewer.tsx'),
'utf8',
);
describe('mobile overflow guards', () => { describe('mobile overflow guards', () => {
test('usage metrics cards allow long metric labels to wrap on mobile', () => { test('usage metrics cards allow long metric labels to wrap on mobile', () => {
expect(appSource).toContain('className="mt-6 min-w-0"');
expect(appSource).toContain('className="grid gap-3 sm:grid-cols-2"'); expect(appSource).toContain('className="grid gap-3 sm:grid-cols-2"');
expect(appSource).toContain('className="min-w-0 rounded-2xl border border-white/10 bg-white/4 p-4"'); expect(appSource).toContain('className="min-w-0 rounded-2xl border border-white/10 bg-white/4 p-4"');
expect(appSource).toContain('className="text-sm text-slate-400 break-words"'); expect(appSource).toContain('className="text-sm text-slate-400 break-words"');
}); });
test('merged payload viewer constrains JSON horizontally inside the card', () => { test('connected account tabs no longer render a side-by-side payload column', () => {
expect(jsonViewerSource).toContain('className="min-w-0"'); expect(appSource).not.toContain('lg:grid-cols-[0.9fr_1.1fr]');
expect(jsonViewerSource).toContain('overflow-x-auto'); expect(appSource).not.toContain('Account payload');
expect(jsonViewerSource).toContain('whitespace-pre-wrap');
expect(jsonViewerSource).toContain('break-all');
}); });
}); });