feat(ui): equal-width Yes/Close buttons in confirm dialogs; trim dashboard activity to 3

DialogFooter with showCloseButton now lays out the auto-rendered Close
and the caller's primary action (typically a <form>-wrapped Submit) in
a 2-column grid that's identical at every viewport. Both buttons are
sized "sm" with w-full so they fill their column and match in height.

The trick to making this transparent for callers: \`[&>form]:contents\`
collapses the form box so its <Button> child becomes a real grid item
sibling of the Close button, not a single grid cell containing the
button. \`[&>form>button]:w-full\` then sizes the submit button to
match the Close button's column width.

Five existing call sites pick this up automatically — no changes
needed at the call site:
- reminder pause/restart/delete (actions-bar)
- account unpair / delete
- dashboard "Clear history"
- activity tab "Clear history"

Also: dashboard "Recent activity" now shows the 3 most recent runs
instead of 10. The "Recent runs" stat card description updates to
match ("3 most recent runs"), points to /activity, and a "View all"
ghost link sits beside the section heading so you can jump to the
full history without hunting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
yiekheng 2026-05-10 11:35:38 +08:00
parent 48cae84919
commit 7b991a565d
3 changed files with 26 additions and 7 deletions

View File

@ -177,8 +177,8 @@ export default async function DashboardPage() {
title="Recent runs"
value={stats.recentRuns.length}
icon={ActivityIcon}
description="Last 10 reminder runs"
href="/reminders"
description="3 most recent runs"
href="/activity"
/>
</div>
@ -186,6 +186,13 @@ export default async function DashboardPage() {
<section className="space-y-4">
<div className="flex items-center justify-between gap-2">
<h2 className="text-lg font-medium tracking-tight">Recent activity</h2>
<div className="flex items-center gap-1">
{hasRuns && (
<Button asChild variant="ghost" size="sm" className="text-muted-foreground">
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
<Link href={"/activity" as any}>View all</Link>
</Button>
)}
{hasRuns && (
<Dialog>
<DialogTrigger asChild>
@ -215,6 +222,7 @@ export default async function DashboardPage() {
</Dialog>
)}
</div>
</div>
{hasRuns ? (
<>

View File

@ -107,7 +107,16 @@ function DialogFooter({
<div
data-slot="dialog-footer"
className={cn(
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 sm:flex-row sm:justify-end",
"-mx-4 -mb-4 rounded-b-xl border-t bg-muted/50 p-4",
showCloseButton
// 2-col grid so the auto-rendered Close and the caller's primary
// button (typically wrapped in a <form>) get equal width on the
// same row at every viewport. `[&>form]:contents` makes the form
// a transparent grid parent so its <Button> child becomes a real
// grid item; `[&>form>button]:w-full` matches the Close button
// width so both fill their column.
? "grid grid-cols-2 gap-2 [&>form]:contents [&>form>button]:w-full"
: "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
className
)}
{...props}
@ -115,7 +124,9 @@ function DialogFooter({
{children}
{showCloseButton && (
<DialogPrimitive.Close asChild>
<Button variant="outline">Close</Button>
<Button variant="outline" size="sm" className="w-full">
Close
</Button>
</DialogPrimitive.Close>
)}
</div>

View File

@ -26,7 +26,7 @@ export async function getDashboardStats(operatorId: string) {
LEFT JOIN whatsapp_accounts wa ON wa.id = r.account_id
WHERE wa.operator_id = ${operatorId} OR r.id IS NULL
ORDER BY rr.fired_at DESC
LIMIT 10
LIMIT 3
`);
return {
connectedAccounts: accounts.filter((a) => a.status === "connected").length,