diff --git a/apps/hq-web/src/pages/Dashboard.tsx b/apps/hq-web/src/pages/Dashboard.tsx index e39fbf9..d741859 100644 --- a/apps/hq-web/src/pages/Dashboard.tsx +++ b/apps/hq-web/src/pages/Dashboard.tsx @@ -70,13 +70,27 @@ export function Dashboard() { - {/* SMS low-balance alert (D28) — surfaced here so it's seen without opening the SMS screen. */} - {smsLow.data !== undefined && smsLow.data.lowCount > 0 && ( + {/* SMS low-balance alert (D28) — surfaced here so it's seen without opening the SMS + screen. Three states, never collapsed into one: while the check is in flight a + skeleton stands in; a FAILED check says so with a retry (rendering nothing there + would read as "no client is low" — the dangerous direction); and only a loaded, + genuinely-empty result gets the positive line. `unknownCount` is spelled out in + that line because balances the gateway couldn't return aren't "fine" either. */} + {smsLow.error !== undefined ? ( + + ) : smsLow.data === undefined ? ( + + ) : smsLow.data.lowCount > 0 ? ( {smsLow.data.lowCount} SMS client{smsLow.data.lowCount === 1 ? '' : 's'} low on balance — review & top up → + ) : ( +

+ SMS balances checked — none low + {smsLow.data.unknownCount > 0 ? ` (${smsLow.data.unknownCount} could not be read)` : ''}. +

)} {/* KPIs — the app's existing stat cards (kept minimal; red only when a send failed). */} @@ -146,11 +160,14 @@ export function Dashboard() { {stalled.error !== undefined ? @@ -182,10 +199,12 @@ export function Dashboard() { } /** One bento card: an uppercase-labelled header (+ count and optional link) over a - * scrollable list, or an empty-state line when there's nothing to show. */ + * scrollable list, or an empty-state line when there's nothing to show. Cards fed by + * their own request pass `loading` so a fetch still in flight shows a skeleton instead + * of the empty line — "nothing to show" must mean the data loaded and was empty. */ function Card(props: { title: string; count?: number; link?: { to: string; label?: string } - empty: string; isEmpty: boolean; children: ReactNode + empty: string; isEmpty: boolean; loading?: boolean; children: ReactNode }) { return (
@@ -194,7 +213,9 @@ function Card(props: { {props.count !== undefined && {props.count}} {props.link !== undefined && {props.link.label ?? 'All'} →} - {props.isEmpty ?
{props.empty}
:
{props.children}
} + {props.loading === true ?
+ : props.isEmpty ?
{props.empty}
+ :
{props.children}
}
) }