A warmer, friendlier admin: first pass

- The INBUXA palette on warm surfaces, softer cards, buttons and inputs, and
  self-hosted Inter and Space Grotesk.
- Each section's icon sits on a colored tile, colored by what the section is
  about.
- The sidebar gets a guide line for sub-pages and a labeled Management /
  Settings / Account switch, and folds to a rail of tiles (remembered).
- Every page has a header with its section's tile.
- Fields and pages with no label of their own are spelled out in words
  (defaultCertificateId becomes Default certificate ID).
- The dashboard greets you, and its stat cards wear colored tiles.
- Unavailable live numbers are a calm note, not an error.
- The cat appears in empty lists and while loading.
- Chart colors work again: they were hex values wrapped in hsl().
This commit is contained in:
2026-09-19 00:38:53 -07:00
parent 92bcb58f76
commit e4ad5e2c1e
26 changed files with 789 additions and 161 deletions
+6 -23
View File
@@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
import { IconTile } from '@/components/common/IconTile';
import { useMemo } from 'react';
import * as LucideIcons from 'lucide-react';
import { Info } from 'lucide-react';
import { LineChart, Line } from 'recharts';
import { Card, CardContent } from '@/components/ui/card';
@@ -17,24 +18,6 @@ import { cardValue, formatValue, sparklineData, computeDelta } from '../helpers'
import { useLiveMetricsStore } from '../stores/liveMetricsStore';
import { getChartColor } from '@/components/ui/chart';
const warnedIcons = new Set<string>();
function LucideIcon({ name, className }: { name: string; className?: string }) {
const formatted = name
.split('-')
.map((s) => s[0].toUpperCase() + s.slice(1))
.join('');
const IconComp = (LucideIcons as Record<string, unknown>)[formatted] as LucideIcons.LucideIcon | undefined;
if (!IconComp) {
if (import.meta.env.DEV && !warnedIcons.has(name)) {
warnedIcons.add(name);
console.warn(`Unknown icon name: "${name}"`);
}
return <LucideIcons.HelpCircle className={className} />;
}
return <IconComp className={className} />;
}
interface StatCardProps {
card: CardSchema;
historySamples: Metric[];
@@ -70,10 +53,10 @@ export function StatCard({ card, historySamples, historyWindow }: StatCardProps)
}, [card, historySamples, from, to]);
return (
<Card>
<CardContent className="p-4">
<Card className="transition-shadow hover:shadow-md">
<CardContent className="p-5">
<div className="flex items-center gap-2">
<LucideIcon name={card.icon} className="h-4 w-4 text-muted-foreground" />
<IconTile name={card.icon} size="sm" />
<span className="text-sm font-medium text-muted-foreground">{card.title}</span>
{card.description && (
<TooltipProvider>
@@ -89,7 +72,7 @@ export function StatCard({ card, historySamples, historyWindow }: StatCardProps)
)}
</div>
<div className="mt-2 text-2xl font-bold">{formattedValue}</div>
<div className="mt-3 font-display text-3xl font-semibold tracking-tight">{formattedValue}</div>
{(delta || sparkline) && (
<div className="mt-1 flex items-center gap-2">