From c66bdfb5aede479375ac804466dae6377fba91cc Mon Sep 17 00:00:00 2001 From: BilalG1 Date: Tue, 14 Apr 2026 19:38:52 -0700 Subject: [PATCH] Fix five dashboard UI issues (#1337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes five independent UI bugs in the dashboard. Each is a narrow, localized fix — no changes to shared table / card primitives. ### 1. Auth methods preview didn't update until save Toggling Email/password, Magic link, or Passkey updated the switch UI but the right-hand sign-in preview kept rendering the pre-save config until "Save changes" was clicked. The preview was reading `project.config` instead of the local pending state. **Fix:** pass the computed local state (`passwordEnabled`, `otpEnabled`, `passkeyEnabled`) into `AuthPage`'s `mockProject.config` so the preview reflects toggles immediately. | Before | After | |---|---| | ![before](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/01-auth-methods-before.gif) | ![after](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/01-auth-methods-after.gif) | --- ### 2. Email-drafts "New Draft" dropdown items stacked on two rows Icon rendered above text in the dropdown because the icon was a child of a non-flex inner wrapper inside `DropdownMenuItem` and phosphor icons default to `display: block`. **Fix:** use `DropdownMenuItem`'s built-in `icon` prop (which absolute-positions the icon) instead of passing it as a child. | Before | After | |---|---| | ![before](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/02-email-drafts-before.png) | ![after](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/02-email-drafts-after.png) | --- ### 3. Project-keys status filter: clicking options did nothing visible `DesignDataTable` renders the toolbar outside the card when `glassmorphic && !insideDesignCard`. The table instance was captured once via `onTableReady`; filter clicks updated the table's internal state (rows actually filtered to "No results") but the toolbar's parent never re-rendered, so checkboxes, chip count, and button label stayed frozen. **Fix:** wrap `InternalApiKeyTable` in `DesignCard` so `useInsideDesignCard()` returns true, `needsOwnCard` becomes false, and the toolbar renders inside the `DataTable` where it re-renders normally. No changes to the shared `DesignDataTable` component. | Before | After | |---|---| | ![before](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/03-project-keys-before.gif) | ![after](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/03-project-keys-after.gif) | --- ### 4. Analytics "Tables" page only listed Events `AVAILABLE_TABLES` was hardcoded to a single entry. **Fix:** registered all 12 ClickHouse views that exist in the `default` schema (events, users, contact_channels, teams, team_member_profiles, team_permissions, team_invitations, email_outboxes, project_permissions, notification_preferences, refresh_tokens, connected_accounts) with sensible default sort columns. Widened `TableId` to `string`. | Before | After | |---|---| | ![before](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/04-analytics-tables-before.png) | ![after](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/04-analytics-tables-after.png) | --- ### 5. Price input `$` prefix overlapped the number on prod The Input composed `h-9 px-3 ... pl-7`. In production's CSS bundle order `.px-3` declared after `.pl-7`, so `padding-left` resolved to 12px — same as the prefix's `left-3` position — making `$` overlap the first digit. The emulator's bundle happened to order them the other way, which is why it only reproduced in prod. Verified with a devtools injection that mimics the prod CSS ordering. **Fix:** change `pl-7` → `!pl-7` in `repeating-input.tsx` so the prefix padding wins regardless of CSS order. | Before (prod CSS ordering) | After (same ordering) | |---|---| | ![before](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/05-price-overlap-before.png) | ![after](https://gist.githubusercontent.com/BilalG1/8fb37def33d42481002f02d500eb6742/raw/b6d4f39f6626b7e922c2b9d8360ac4019f379b76/05-price-overlap-after.png) | --- ## Test plan - [x] `pnpm --filter @stackframe/dashboard typecheck` - [x] `pnpm --filter @stackframe/dashboard lint` - [x] Manual verification of each issue against the local dev dashboard at localhost:8101 - [ ] Reviewer: confirm no visual regressions on other `DesignDataTable` usages (api-key-table is the only one wrapped here) - [ ] Reviewer: confirm analytics queries on added tables work with the signed-in user's permissions ## Summary by CodeRabbit ## Release Notes * **New Features** * Added 12 new analytics tables to the dashboard for enhanced data visibility and tracking. * **Bug Fixes** * Fixed input styling issue with prefix alignment. * **Style** * Improved visual presentation of data tables with enhanced card styling. * Refined dropdown menu icon display for better UI consistency. * Enhanced authentication preview settings to reflect current configuration state. --- .../analytics/tables/page-client.tsx | 68 ++++++++++++++++++- .../[projectId]/auth-methods/page-client.tsx | 3 + .../[projectId]/email-drafts/page-client.tsx | 12 ++-- .../components/data-table/api-key-table.tsx | 19 +++--- .../src/components/repeating-input.tsx | 2 +- 5 files changed, 85 insertions(+), 19 deletions(-) diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx index ddca58c8f..a3dca3d18 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/tables/page-client.tsx @@ -49,9 +49,75 @@ const AVAILABLE_TABLES = new Map([ defaultOrderBy: "event_at", defaultOrderDir: "DESC" as const, }], + ["users", { + displayName: "Users", + baseQuery: "SELECT * FROM default.users", + defaultOrderBy: "signed_up_at", + defaultOrderDir: "DESC" as const, + }], + ["contact_channels", { + displayName: "Contact Channels", + baseQuery: "SELECT * FROM default.contact_channels", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], + ["teams", { + displayName: "Teams", + baseQuery: "SELECT * FROM default.teams", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], + ["team_member_profiles", { + displayName: "Team Member Profiles", + baseQuery: "SELECT * FROM default.team_member_profiles", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], + ["team_permissions", { + displayName: "Team Permissions", + baseQuery: "SELECT * FROM default.team_permissions", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], + ["team_invitations", { + displayName: "Team Invitations", + baseQuery: "SELECT * FROM default.team_invitations", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], + ["email_outboxes", { + displayName: "Email Outboxes", + baseQuery: "SELECT * FROM default.email_outboxes", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], + ["project_permissions", { + displayName: "Project Permissions", + baseQuery: "SELECT * FROM default.project_permissions", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], + ["notification_preferences", { + displayName: "Notification Preferences", + baseQuery: "SELECT * FROM default.notification_preferences", + defaultOrderBy: "user_id", + defaultOrderDir: "DESC" as const, + }], + ["refresh_tokens", { + displayName: "Refresh Tokens", + baseQuery: "SELECT * FROM default.refresh_tokens", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], + ["connected_accounts", { + displayName: "Connected Accounts", + baseQuery: "SELECT * FROM default.connected_accounts", + defaultOrderBy: "created_at", + defaultOrderDir: "DESC" as const, + }], ]); -type TableId = "events"; +type TableId = string; type SortDir = "ASC" | "DESC"; const PAGE_SIZE = 50; diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx index 68b0738a2..190cb66cd 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx @@ -482,6 +482,9 @@ export default function PageClient() { mockProject={{ config: { ...project.config, + credentialEnabled: passwordEnabled, + magicLinkEnabled: otpEnabled, + passkeyEnabled: passkeyEnabled, oauthProviders: enabledProviders .map(([, provider]) => provider) .filter((provider): provider is AdminOAuthProviderConfig => !!provider), diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx index 42671d8f7..4aaebe0b2 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx @@ -457,12 +457,10 @@ function NewDraftDropdown({ - - + }> Create from scratch - - + }> Create from template @@ -480,12 +478,10 @@ function NewDraftDropdown({ - - + }> Create from scratch - - + }> Create from template diff --git a/apps/dashboard/src/components/data-table/api-key-table.tsx b/apps/dashboard/src/components/data-table/api-key-table.tsx index 9f7c142eb..c7938d9c9 100644 --- a/apps/dashboard/src/components/data-table/api-key-table.tsx +++ b/apps/dashboard/src/components/data-table/api-key-table.tsx @@ -1,6 +1,6 @@ 'use client'; import { InternalApiKey } from '@stackframe/stack'; -import { DesignDataTable } from "@/components/design-components"; +import { DesignCard, DesignDataTable } from "@/components/design-components"; import { ActionCell, ActionDialog, BadgeCell, DataTableColumnHeader, DataTableFacetedFilter, DateCell, SearchToolbarItem, TextCell, standardFilterFn } from "@/components/ui"; import { ColumnDef, Row, Table } from "@tanstack/react-table"; import { useMemo, useState } from "react"; @@ -144,12 +144,13 @@ export function InternalApiKeyTable(props: { apiKeys: InternalApiKey[], showPubl }); }, [props.apiKeys]); - return ; + return + + ; } diff --git a/apps/dashboard/src/components/repeating-input.tsx b/apps/dashboard/src/components/repeating-input.tsx index 6d09b25fc..d6ced0a44 100644 --- a/apps/dashboard/src/components/repeating-input.tsx +++ b/apps/dashboard/src/components/repeating-input.tsx @@ -166,7 +166,7 @@ export function RepeatingInput({ disabled={disabled || readOnly} className={cn( "rounded-r-none border-0 focus-visible:ring-0 focus-visible:ring-offset-0", - prefix && "pl-7", + prefix && "!pl-7", inputClassName )} />