Skip to content

Commit 8cec4ca

Browse files
committed
feat(dashboard): simplify Own account view
Hide request logs from the private dashboard and let account cards expand without an internal scroll cap.
1 parent cbe730f commit 8cec4ca

5 files changed

Lines changed: 23 additions & 216 deletions

File tree

.github/workflows/docker.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
branches:
77
- main
88
- feat/opencode-auth-export
9+
- Own
910
tags:
1011
- "v*"
1112

@@ -42,6 +43,7 @@ jobs:
4243
images: ${{ env.IMAGE_NAME }}
4344
tags: |
4445
type=raw,value=opencode-auth-export,enable=${{ github.ref == 'refs/heads/feat/opencode-auth-export' }}
46+
type=raw,value=own,enable=${{ github.ref == 'refs/heads/Own' }}
4547
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
4648
type=ref,event=tag
4749
type=sha,prefix=sha-

frontend/src/features/dashboard/components/account-cards.test.tsx

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AccountCards } from "@/features/dashboard/components/account-cards";
55
import { createAccountSummary } from "@/test/mocks/factories";
66

77
describe("AccountCards", () => {
8-
it("caps the dashboard account grid at two visible rows without clipping taller cards", () => {
8+
it("shows all dashboard account cards without an internal scroll cap", () => {
99
render(
1010
<AccountCards
1111
accounts={Array.from({ length: 7 }, (_, index) =>
@@ -19,23 +19,9 @@ describe("AccountCards", () => {
1919
/>,
2020
);
2121

22-
expect(screen.getByTestId("dashboard-account-cards")).toHaveStyle({
23-
maxHeight: "calc(2 * 12.5rem + 1rem)",
24-
});
25-
});
26-
27-
it("keeps the scrollbar hidden on the dashboard account grid", () => {
28-
render(
29-
<AccountCards
30-
accounts={[createAccountSummary(), createAccountSummary({ accountId: "acc-2", email: "two@example.com" })]}
31-
onAction={vi.fn()}
32-
/>,
33-
);
34-
35-
expect(screen.getByTestId("dashboard-account-cards")).toHaveClass(
36-
"overflow-y-auto",
37-
"[scrollbar-width:none]",
38-
"[&::-webkit-scrollbar]:hidden",
39-
);
22+
const grid = screen.getByTestId("dashboard-account-cards");
23+
expect(grid).not.toHaveClass("overflow-y-auto");
24+
expect(grid).not.toHaveAttribute("style");
25+
expect(screen.getAllByText(/Account \d/)).toHaveLength(7);
4026
});
4127
});

frontend/src/features/dashboard/components/account-cards.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,6 @@ import { AccountCard, type AccountCardProps } from "@/features/dashboard/compone
66
import type { AccountSummary } from "@/features/dashboard/schemas";
77
import { buildDuplicateAccountIdSet } from "@/utils/account-identifiers";
88

9-
const ACCOUNT_CARD_VISIBLE_ROWS = 2;
10-
// Account cards can grow when the optional email row is rendered.
11-
const ACCOUNT_CARD_ROW_HEIGHT_REM = 12.5;
12-
const ACCOUNT_CARD_ROW_GAP_REM = 1;
13-
149
export type AccountCardsProps = {
1510
accounts: AccountSummary[];
1611
onAction?: AccountCardProps["onAction"];
@@ -32,10 +27,7 @@ export function AccountCards({ accounts, onAction }: AccountCardsProps) {
3227
return (
3328
<div
3429
data-testid="dashboard-account-cards"
35-
className="grid gap-4 overflow-y-auto pr-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden sm:grid-cols-2 lg:grid-cols-3"
36-
style={{
37-
maxHeight: `calc(${ACCOUNT_CARD_VISIBLE_ROWS} * ${ACCOUNT_CARD_ROW_HEIGHT_REM}rem + ${(ACCOUNT_CARD_VISIBLE_ROWS - 1) * ACCOUNT_CARD_ROW_GAP_REM}rem)`,
38-
}}
30+
className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"
3931
>
4032
{accounts.map((account, index) => (
4133
<div key={account.accountId} className="animate-fade-in-up" style={{ animationDelay: `${index * 75}ms` }}>

frontend/src/features/dashboard/components/dashboard-page.tsx

Lines changed: 15 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,9 @@ import { useAccountMutations } from "@/features/accounts/hooks/use-accounts";
88
import { AccountCards } from "@/features/dashboard/components/account-cards";
99
import { DashboardSkeleton } from "@/features/dashboard/components/dashboard-skeleton";
1010
import { OverviewTimeframeSelect } from "@/features/dashboard/components/filters/overview-timeframe-select";
11-
import { RequestFilters } from "@/features/dashboard/components/filters/request-filters";
12-
import { RecentRequestsTable } from "@/features/dashboard/components/recent-requests-table";
1311
import { StatsGrid } from "@/features/dashboard/components/stats-grid";
1412
import { UsageDonuts } from "@/features/dashboard/components/usage-donuts";
1513
import { useDashboard } from "@/features/dashboard/hooks/use-dashboard";
16-
import { useRequestLogs } from "@/features/dashboard/hooks/use-request-logs";
1714
import { buildDashboardView } from "@/features/dashboard/utils";
1815
import {
1916
DEFAULT_OVERVIEW_TIMEFRAME,
@@ -22,10 +19,6 @@ import {
2219
type OverviewTimeframe,
2320
} from "@/features/dashboard/schemas";
2421
import { useThemeStore } from "@/hooks/use-theme";
25-
import { REQUEST_STATUS_LABELS } from "@/utils/constants";
26-
import { formatModelLabel, formatSlug } from "@/utils/formatters";
27-
28-
const MODEL_OPTION_DELIMITER = ":::";
2922

3023
export function DashboardPage() {
3124
const navigate = useNavigate();
@@ -37,10 +30,9 @@ export function DashboardPage() {
3730
[searchParams],
3831
);
3932
const dashboardQuery = useDashboard(overviewTimeframe);
40-
const { filters, logsQuery, optionsQuery, updateFilters } = useRequestLogs();
4133
const { resumeMutation } = useAccountMutations();
4234

43-
const isRefreshing = dashboardQuery.isFetching || logsQuery.isFetching;
35+
const isRefreshing = dashboardQuery.isFetching;
4436

4537
const handleRefresh = useCallback(() => {
4638
void queryClient.invalidateQueries({ queryKey: ["dashboard"] });
@@ -77,63 +69,16 @@ export function DashboardPage() {
7769
);
7870

7971
const overview = dashboardQuery.data;
80-
const logPage = logsQuery.data;
8172

8273
const view = useMemo(() => {
83-
if (!overview || !logPage) {
74+
if (!overview) {
8475
return null;
8576
}
86-
return buildDashboardView(overview, logPage.requests, isDark);
87-
}, [overview, logPage, isDark]);
88-
89-
const accountOptions = useMemo(() => {
90-
const entries = new Map<string, { label: string; isEmail: boolean }>();
91-
for (const account of overview?.accounts ?? []) {
92-
const raw = account.displayName || account.email || account.accountId;
93-
const isEmail = !!account.email && raw === account.email;
94-
entries.set(account.accountId, { label: raw, isEmail });
95-
}
96-
return (optionsQuery.data?.accountIds ?? []).map((accountId) => {
97-
const entry = entries.get(accountId);
98-
return {
99-
value: accountId,
100-
label: entry?.label ?? accountId,
101-
isEmail: entry?.isEmail ?? false,
102-
};
103-
});
104-
}, [optionsQuery.data?.accountIds, overview?.accounts]);
105-
106-
const apiKeyOptions = useMemo(
107-
() =>
108-
(optionsQuery.data?.apiKeys ?? []).map((option) => ({
109-
value: option.id,
110-
label: option.keyPrefix ? `${option.name} · ${option.keyPrefix}` : option.name,
111-
})),
112-
[optionsQuery.data?.apiKeys],
113-
);
114-
115-
const modelOptions = useMemo(
116-
() =>
117-
(optionsQuery.data?.modelOptions ?? []).map((option) => ({
118-
value: `${option.model}${MODEL_OPTION_DELIMITER}${option.reasoningEffort ?? ""}`,
119-
label: formatModelLabel(option.model, option.reasoningEffort),
120-
})),
121-
[optionsQuery.data?.modelOptions],
122-
);
123-
124-
const statusOptions = useMemo(
125-
() =>
126-
(optionsQuery.data?.statuses ?? []).map((status) => ({
127-
value: status,
128-
label: REQUEST_STATUS_LABELS[status] ?? formatSlug(status),
129-
})),
130-
[optionsQuery.data?.statuses],
131-
);
77+
return buildDashboardView(overview, [], isDark);
78+
}, [overview, isDark]);
13279

13380
const errorMessage =
13481
(dashboardQuery.error instanceof Error && dashboardQuery.error.message) ||
135-
(logsQuery.error instanceof Error && logsQuery.error.message) ||
136-
(optionsQuery.error instanceof Error && optionsQuery.error.message) ||
13782
null;
13883

13984
return (
@@ -143,7 +88,7 @@ export function DashboardPage() {
14388
<div>
14489
<h1 className="text-2xl font-semibold tracking-tight">Dashboard</h1>
14590
<p className="mt-1 text-sm text-muted-foreground">
146-
Overview, account health, and recent request logs.
91+
Overview and account health for your active pool.
14792
</p>
14893
</div>
14994
<div className="flex items-center gap-2">
@@ -171,16 +116,16 @@ export function DashboardPage() {
171116
<>
172117
<StatsGrid stats={view.stats} />
173118

174-
<UsageDonuts
175-
primaryItems={view.primaryUsageItems}
176-
secondaryItems={view.secondaryUsageItems}
177-
primaryTotal={overview?.summary.primaryWindow.capacityCredits ?? 0}
178-
secondaryTotal={overview?.summary.secondaryWindow?.capacityCredits ?? 0}
179-
primaryCenterValue={view.primaryTotal}
180-
secondaryCenterValue={view.secondaryTotal}
181-
safeLinePrimary={view.safeLinePrimary}
182-
safeLineSecondary={view.safeLineSecondary}
183-
/>
119+
<UsageDonuts
120+
primaryItems={view.primaryUsageItems}
121+
secondaryItems={view.secondaryUsageItems}
122+
primaryTotal={overview?.summary.primaryWindow.capacityCredits ?? 0}
123+
secondaryTotal={overview?.summary.secondaryWindow?.capacityCredits ?? 0}
124+
primaryCenterValue={view.primaryTotal}
125+
secondaryCenterValue={view.secondaryTotal}
126+
safeLinePrimary={view.safeLinePrimary}
127+
safeLineSecondary={view.safeLineSecondary}
128+
/>
184129

185130
<section className="space-y-4">
186131
<div className="flex items-center gap-3">
@@ -189,51 +134,6 @@ export function DashboardPage() {
189134
</div>
190135
<AccountCards accounts={overview?.accounts ?? []} onAction={handleAccountAction} />
191136
</section>
192-
193-
<section className="space-y-4">
194-
<div className="flex items-center gap-3">
195-
<h2 className="text-[13px] font-medium uppercase tracking-wider text-muted-foreground">Request Logs</h2>
196-
<div className="h-px flex-1 bg-border" />
197-
</div>
198-
<RequestFilters
199-
filters={filters}
200-
accountOptions={accountOptions}
201-
apiKeyOptions={apiKeyOptions}
202-
modelOptions={modelOptions}
203-
statusOptions={statusOptions}
204-
onSearchChange={(search) => updateFilters({ search, offset: 0 })}
205-
onTimeframeChange={(timeframe) => updateFilters({ timeframe, offset: 0 })}
206-
onAccountChange={(accountIds) => updateFilters({ accountIds, offset: 0 })}
207-
onApiKeyChange={(apiKeyIds) => updateFilters({ apiKeyIds, offset: 0 })}
208-
onModelChange={(modelOptionsSelected) =>
209-
updateFilters({ modelOptions: modelOptionsSelected, offset: 0 })
210-
}
211-
onStatusChange={(statuses) => updateFilters({ statuses, offset: 0 })}
212-
onReset={() =>
213-
updateFilters({
214-
search: "",
215-
timeframe: "all",
216-
accountIds: [],
217-
apiKeyIds: [],
218-
modelOptions: [],
219-
statuses: [],
220-
offset: 0,
221-
})
222-
}
223-
/>
224-
<div className="transition-opacity duration-200">
225-
<RecentRequestsTable
226-
requests={view.requestLogs}
227-
accounts={overview?.accounts ?? []}
228-
total={logPage?.total ?? 0}
229-
limit={filters.limit}
230-
offset={filters.offset}
231-
hasMore={logPage?.hasMore ?? false}
232-
onLimitChange={(limit) => updateFilters({ limit, offset: 0 })}
233-
onOffsetChange={(offset) => updateFilters({ offset })}
234-
/>
235-
</div>
236-
</section>
237137
</>
238138
)}
239139

frontend/src/features/dashboard/components/dashboard-skeleton.tsx

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -86,79 +86,6 @@ export function DashboardSkeleton() {
8686
))}
8787
</div>
8888
</div>
89-
90-
{/* Request logs section */}
91-
<div className="space-y-4">
92-
<div className="flex items-center gap-3">
93-
<Skeleton className="h-5 w-28" />
94-
<div className="h-px flex-1 bg-border" />
95-
</div>
96-
97-
{/* Filters */}
98-
<div className="space-y-2 rounded-xl border bg-card p-4">
99-
<div className="flex items-center gap-2">
100-
<Skeleton className="h-8 flex-1 rounded-md" />
101-
<Skeleton className="h-8 w-32 rounded-md" />
102-
</div>
103-
<div className="flex items-center gap-2">
104-
<Skeleton className="h-8 w-24 rounded-md" />
105-
<Skeleton className="h-8 w-20 rounded-md" />
106-
<Skeleton className="h-8 w-22 rounded-md" />
107-
<Skeleton className="h-8 w-16 rounded-md" />
108-
</div>
109-
</div>
110-
111-
{/* Table */}
112-
<div className="rounded-xl border bg-card">
113-
<div className="overflow-x-auto">
114-
<div className="min-w-[960px]">
115-
{/* Table header */}
116-
<div className="flex items-center gap-4 border-b px-4 py-2.5">
117-
<Skeleton className="h-3 w-16" />
118-
<Skeleton className="h-3 w-20 flex-1" />
119-
<Skeleton className="h-3 w-16 flex-1" />
120-
<Skeleton className="h-3 w-16 flex-1" />
121-
<Skeleton className="h-3 w-14" />
122-
<Skeleton className="h-3 w-14" />
123-
<Skeleton className="h-3 w-10" />
124-
<Skeleton className="h-3 w-14" />
125-
</div>
126-
{/* Table rows */}
127-
{Array.from({ length: 5 }).map((_, i) => (
128-
<div key={i} className="flex items-start gap-4 border-b px-4 py-3 last:border-b-0">
129-
<div className="w-16 space-y-1">
130-
<Skeleton className="h-3.5 w-14" />
131-
<Skeleton className="h-3 w-16" />
132-
</div>
133-
<Skeleton className="h-3.5 w-24 flex-1" />
134-
<Skeleton className="h-3.5 w-20 flex-1" />
135-
<Skeleton className="h-3.5 w-28 flex-1 font-mono" />
136-
<Skeleton className="h-5 w-14 rounded-full" />
137-
<div className="w-14 space-y-1">
138-
<Skeleton className="h-3.5 w-12" />
139-
<Skeleton className="h-3 w-14" />
140-
</div>
141-
<Skeleton className="h-3.5 w-10" />
142-
<Skeleton className="h-3.5 w-14" />
143-
</div>
144-
))}
145-
</div>
146-
</div>
147-
</div>
148-
149-
{/* Pagination */}
150-
<div className="flex justify-end">
151-
<div className="flex items-center gap-2">
152-
<Skeleton className="h-3 w-8" />
153-
<Skeleton className="h-8 w-20 rounded-md" />
154-
<Skeleton className="h-3 w-20" />
155-
<Skeleton className="h-8 w-8 rounded-md" />
156-
<Skeleton className="h-8 w-8 rounded-md" />
157-
<Skeleton className="h-8 w-8 rounded-md" />
158-
<Skeleton className="h-8 w-8 rounded-md" />
159-
</div>
160-
</div>
161-
</div>
16289
</div>
16390
);
16491
}

0 commit comments

Comments
 (0)