From 943d2e866447e159f8acd83409ba4bbca18d6b78 Mon Sep 17 00:00:00 2001 From: Abdulkhalek Muhammad Date: Tue, 7 Jul 2026 14:08:13 +0300 Subject: [PATCH 01/21] refactor(dashboard): unify icons on Lucide, drop Material Symbols font Rewrite shared Icon.tsx as a Material-Symbol-name to Lucide component map (same public API, strokeWidth 1.5, size default 20, filled fill). Remove the Material Symbols font link from index.html (eliminates ligature-word FOUT) and add font preconnects; delete the now-dead .material-symbols-outlined CSS. Co-Authored-By: Claude Opus 4.8 --- apps/dashboard/src/client/index.html | 3 +- .../src/client/shared/components/Icon.tsx | 210 +++++++++++++++++- apps/dashboard/src/client/styles.css | 8 - 3 files changed, 202 insertions(+), 19 deletions(-) diff --git a/apps/dashboard/src/client/index.html b/apps/dashboard/src/client/index.html index 3cb41eb..8271b6d 100644 --- a/apps/dashboard/src/client/index.html +++ b/apps/dashboard/src/client/index.html @@ -8,8 +8,9 @@ + + - FluxCore Dashboard diff --git a/apps/dashboard/src/client/shared/components/Icon.tsx b/apps/dashboard/src/client/shared/components/Icon.tsx index 2c5e8f8..ea1a7e1 100644 --- a/apps/dashboard/src/client/shared/components/Icon.tsx +++ b/apps/dashboard/src/client/shared/components/Icon.tsx @@ -1,21 +1,211 @@ +import { + Activity, + AlertCircle, + AlertTriangle, + ArrowDown, + ArrowLeft, + ArrowUp, + Badge, + Bell, + BookOpen, + Braces, + Check, + CheckCircle, + CheckCircle2, + CheckSquare, + ChevronDown, + ChevronRight, + ChevronUp, + Circle, + Clock, + Compass, + Copy, + FileText, + Filter, + Gavel, + Hand, + Hash, + HelpCircle, + History, + Info, + LayoutDashboard, + LayoutGrid, + Lightbulb, + List, + Lock, + Mail, + Maximize, + Menu, + MessageCircle, + MessageSquare, + MessagesSquare, + Mic, + MicOff, + MicVocal, + Minus, + MinusCircle, + Monitor, + MoreVertical, + Music, + PartyPopper, + Pause, + PauseCircle, + Pencil, + Play, + PlayCircle, + Plus, + PlusCircle, + Receipt, + RefreshCw, + Rocket, + Save, + Search, + SearchX, + Send, + Server, + Settings, + Shield, + ShieldCheck, + ShieldUser, + SlidersHorizontal, + SmilePlus, + Split, + Square, + Star, + Terminal, + Ticket, + Timer, + Trash2, + TrendingUp, + Unlock, + UserCog, + UserMinus, + UserPlus, + Users, + Webhook, + X, + XCircle, + Zap, + type LucideIcon, +} from "lucide-react"; + +/** + * Maps the legacy Material-Symbols string names (still used throughout the app + * as data-driven icon keys) to their Lucide equivalents. Lucide is the project's + * canonical icon set — one visual language, SVG, themeable, no icon font. + */ +const iconMap: Record = { + account_tree: Split, + add: Plus, + add_circle: PlusCircle, + add_reaction: SmilePlus, + admin_panel_settings: UserCog, + arrow_back: ArrowLeft, + arrow_downward: ArrowDown, + arrow_upward: ArrowUp, + badge: Badge, + bolt: Zap, + call_split: Split, + cancel: XCircle, + celebration: PartyPopper, + chat: MessageSquare, + check: Check, + check_circle: CheckCircle, + chevron_right: ChevronRight, + circle: Circle, + close: X, + confirmation_number: Ticket, + content_copy: Copy, + dashboard: LayoutDashboard, + dashboard_customize: LayoutGrid, + data_object: Braces, + delete: Trash2, + delete_sweep: Trash2, + description: FileText, + deselect: Square, + dns: Server, + edit: Pencil, + error: AlertCircle, + expand_less: ChevronUp, + expand_more: ChevronDown, + explore: Compass, + filter_alt: Filter, + fit_screen: Maximize, + forum: MessagesSquare, + gavel: Gavel, + hash: Hash, + help: HelpCircle, + history: History, + info: Info, + library_music: Music, + lightbulb: Lightbulb, + list: List, + lock: Lock, + lock_open: Unlock, + mail: Mail, + menu: Menu, + menu_book: BookOpen, + "message-circle": MessageCircle, + mic: Mic, + mic_off: MicOff, + more_vert: MoreVertical, + notifications: Bell, + pause_circle: PauseCircle, + person_add: UserPlus, + person_remove: UserMinus, + play_arrow: Play, + play_circle: PlayCircle, + receipt_long: Receipt, + remove: Minus, + remove_circle: MinusCircle, + rocket: Rocket, + rocket_launch: Rocket, + save: Save, + schedule: Clock, + search: Search, + search_off: SearchX, + security: ShieldCheck, + select_check_box: CheckSquare, + send: Send, + settings: Settings, + settings_voice: MicVocal, + shield: Shield, + shield_person: ShieldUser, + star: Star, + sync: RefreshCw, + task_alt: CheckCircle2, + terminal: Terminal, + timer: Timer, + trending_up: TrendingUp, + tune: SlidersHorizontal, + users: Users, + viewport: Monitor, + warning: AlertTriangle, + waving_hand: Hand, + webhook: Webhook, + webhooks: Webhook, + pause: Pause, + activity: Activity, +}; + interface IconProps { name: string; className?: string; + /** Renders a solid (filled) glyph — used for active/selected states. */ filled?: boolean; + /** Pixel size. Defaults to 20 to match the previous icon baseline. */ size?: number; } -export function Icon({ name, className = "", filled = false, size }: IconProps) { +export function Icon({ name, className = "", filled = false, size = 20 }: IconProps) { + const LucideGlyph = iconMap[name] ?? Circle; return ( - + /> ); } diff --git a/apps/dashboard/src/client/styles.css b/apps/dashboard/src/client/styles.css index de46ed8..b633166 100644 --- a/apps/dashboard/src/client/styles.css +++ b/apps/dashboard/src/client/styles.css @@ -151,14 +151,6 @@ input[type="color"] { background: #2c2c2f; } -/* Material Symbols */ -.material-symbols-outlined { - font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 24; - font-size: 20px; - line-height: 1; - vertical-align: middle; -} - /* ── Focus visible — enhanced for WCAG AA ── */ :focus-visible { outline: 2px solid var(--color-ring); From 6a8495e87418a765a0875f5875ef9318d6b0c4cf Mon Sep 17 00:00:00 2001 From: Abdulkhalek Muhammad Date: Tue, 7 Jul 2026 14:08:13 +0300 Subject: [PATCH 02/21] fix(dashboard): resolve UI/UX review findings (a11y, dead controls, landing) Heading hierarchy: PageHeader title is now h1, sidebar brand demoted to a non-heading. Remove dead controls: topbar notifications button, sidebar docs/support and footer placeholder links (no real URLs); wire the topbar settings gear to the settings route. Demote the hero's tertiary CTA to a text link. Give the mobile sidebar close button a 44x44 target. Hero min-height vh -> dvh. Add an accessible refetch spinner to the overview day toggle. Add a persistent landing header (logo + login). Co-Authored-By: Claude Opus 4.8 --- .../src/client/features/landing/Footer.tsx | 35 ++++------------ .../client/features/landing/HeroSection.tsx | 18 +++++---- .../client/features/landing/LandingHeader.tsx | 40 +++++++++++++++++++ .../client/features/landing/LandingPage.tsx | 2 + apps/dashboard/src/client/routes/__root.tsx | 34 ++++++++-------- .../client/routes/guild/$guildId/overview.tsx | 14 +++++-- .../client/shared/components/PageHeader.tsx | 2 +- .../src/client/shared/components/Sidebar.tsx | 24 ++--------- 8 files changed, 92 insertions(+), 77 deletions(-) create mode 100644 apps/dashboard/src/client/features/landing/LandingHeader.tsx diff --git a/apps/dashboard/src/client/features/landing/Footer.tsx b/apps/dashboard/src/client/features/landing/Footer.tsx index b191c58..928bf92 100644 --- a/apps/dashboard/src/client/features/landing/Footer.tsx +++ b/apps/dashboard/src/client/features/landing/Footer.tsx @@ -4,37 +4,16 @@ import { Icon } from "../../shared/components/Icon"; export function Footer() { const { t } = useTranslation("landing"); - const footerLinks = [ - { label: t("footer.links.documentation"), href: "#" }, - { label: t("footer.links.support"), href: "#" }, - { label: t("footer.links.privacy"), href: "#" }, - { label: t("footer.links.api"), href: "#" }, - { label: t("footer.links.github"), href: "#" }, - ]; - return (
-
-
-
- - {t("footer.brand")} -
-

- {t("footer.copyright", { year: new Date().getFullYear() })} -

+
+
+ + {t("footer.brand")}
- +

+ {t("footer.copyright", { year: new Date().getFullYear() })} +

); diff --git a/apps/dashboard/src/client/features/landing/HeroSection.tsx b/apps/dashboard/src/client/features/landing/HeroSection.tsx index 3fc2a41..d791b60 100644 --- a/apps/dashboard/src/client/features/landing/HeroSection.tsx +++ b/apps/dashboard/src/client/features/landing/HeroSection.tsx @@ -6,7 +6,7 @@ export function HeroSection() { const { t } = useTranslation("landing"); const { data: botInfo } = useBotInfo(); return ( -
+
{/* Background glow */}
@@ -59,15 +59,17 @@ export function HeroSection() { {t("cta.openDashboard")} - - - {t("cta.exploreFeatures")} -
+ {/* Tertiary action — visually subordinate text link */} + + + {t("cta.exploreFeatures")} + + {/* Stats strip */}
{[ diff --git a/apps/dashboard/src/client/features/landing/LandingHeader.tsx b/apps/dashboard/src/client/features/landing/LandingHeader.tsx new file mode 100644 index 0000000..fd286e4 --- /dev/null +++ b/apps/dashboard/src/client/features/landing/LandingHeader.tsx @@ -0,0 +1,40 @@ +import { useTranslation } from "react-i18next"; +import { Icon } from "../../shared/components/Icon"; + +export function LandingHeader() { + const { t } = useTranslation("landing"); + + return ( +
+ +
+ ); +} diff --git a/apps/dashboard/src/client/features/landing/LandingPage.tsx b/apps/dashboard/src/client/features/landing/LandingPage.tsx index 24942b1..feab24a 100644 --- a/apps/dashboard/src/client/features/landing/LandingPage.tsx +++ b/apps/dashboard/src/client/features/landing/LandingPage.tsx @@ -1,3 +1,4 @@ +import { LandingHeader } from "./LandingHeader"; import { HeroSection } from "./HeroSection"; import { FeaturesSection } from "./FeaturesSection"; import { CTASection } from "./CTASection"; @@ -6,6 +7,7 @@ import { Footer } from "./Footer"; export function LandingPage() { return (
+ diff --git a/apps/dashboard/src/client/routes/__root.tsx b/apps/dashboard/src/client/routes/__root.tsx index 33a8692..2a923b2 100644 --- a/apps/dashboard/src/client/routes/__root.tsx +++ b/apps/dashboard/src/client/routes/__root.tsx @@ -64,22 +64,24 @@ export function RootLayout() {
- - - - - {t("header.notifications")} - - - - - - {t("header.settings")} - + {params.guildId && ( + + + + + {t("header.settings")} + + )} {params.guildId && } diff --git a/apps/dashboard/src/client/routes/guild/$guildId/overview.tsx b/apps/dashboard/src/client/routes/guild/$guildId/overview.tsx index 05d770c..db82a33 100644 --- a/apps/dashboard/src/client/routes/guild/$guildId/overview.tsx +++ b/apps/dashboard/src/client/routes/guild/$guildId/overview.tsx @@ -10,13 +10,13 @@ import { ExecutionChart } from "../../../features/overview/components/ExecutionC import { EventDistributionChart } from "../../../features/overview/components/EventDistributionChart"; import { RecentActivityFeed } from "../../../features/overview/components/RecentActivityFeed"; import { Button } from "../../../shared/ui/button"; -import { Zap, CheckCircle, BarChart3, Target } from "lucide-react"; +import { Zap, CheckCircle, BarChart3, Target, RefreshCw } from "lucide-react"; export function OverviewPage() { - const { t } = useTranslation("overview"); + const { t } = useTranslation(["overview", "common"]); const { guildId } = useParams({ from: "/guild/$guildId" }); const [days, setDays] = useState(7); - const { data: analytics, isLoading } = useAnalytics(guildId, days); + const { data: analytics, isLoading, isFetching } = useAnalytics(guildId, days); const { data: constants } = useConstants(); if (isLoading || !analytics) return ; @@ -57,7 +57,7 @@ export function OverviewPage() { />
-
+
{[7, 30].map((d) => (
{actions &&
{actions}
} diff --git a/apps/dashboard/src/client/shared/components/Sidebar.tsx b/apps/dashboard/src/client/shared/components/Sidebar.tsx index f61d9ed..99069fd 100644 --- a/apps/dashboard/src/client/shared/components/Sidebar.tsx +++ b/apps/dashboard/src/client/shared/components/Sidebar.tsx @@ -67,10 +67,10 @@ export function Sidebar({ guildId, isOpen, onClose }: SidebarProps) { {t("sidebar.closeSidebar")} @@ -82,7 +82,7 @@ export function Sidebar({ guildId, isOpen, onClose }: SidebarProps) {
-

{t("brand.engine")}

+

{t("brand.engine")}