From 64c9551a4d89a60d6fbefafc074dda62b44fc452 Mon Sep 17 00:00:00 2001 From: Eduard Gert Date: Tue, 16 Jun 2026 12:09:49 +0200 Subject: [PATCH] remove "Loading..." from profile dropdown and show skeleton --- .../frontend/src/contexts/ProfileContext.tsx | 17 ++++++++++++++--- .../src/modules/profiles/ProfileDropdown.tsx | 19 +++++++++++++++---- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/client/ui/frontend/src/contexts/ProfileContext.tsx b/client/ui/frontend/src/contexts/ProfileContext.tsx index 616655176..22e6efc44 100644 --- a/client/ui/frontend/src/contexts/ProfileContext.tsx +++ b/client/ui/frontend/src/contexts/ProfileContext.tsx @@ -4,6 +4,7 @@ import { useContext, useEffect, useMemo, + useRef, useState, type ReactNode, } from "react"; @@ -42,8 +43,13 @@ export const ProfileProvider = ({ children }: { children: ReactNode }) => { const [activeProfile, setActiveProfile] = useState(""); const [profiles, setProfiles] = useState([]); const [loaded, setLoaded] = useState(false); + const retryRef = useRef | null>(null); const refresh = useCallback(async () => { + if (retryRef.current) { + clearTimeout(retryRef.current); + retryRef.current = null; + } try { const u = await ProfilesSvc.Username(); const [active, list] = await Promise.all([ @@ -53,23 +59,28 @@ export const ProfileProvider = ({ children }: { children: ReactNode }) => { setUsername(u); setActiveProfile(active.profileName || "default"); setProfiles(list); + setLoaded(true); } catch (e) { - // Daemon-down is already surfaced by DaemonUnavailableOverlay; swallow it here. const msg = e instanceof Error ? e.message : String(e); if (msg.includes("code = Unavailable")) { + retryRef.current = setTimeout(() => { + void refresh(); + }, 1000); return; } + setLoaded(true); await errorDialog({ Title: i18next.t("profile.error.loadTitle"), Message: formatErrorMessage(e), }); - } finally { - setLoaded(true); } }, []); useEffect(() => { refresh().catch((err: unknown) => console.error("[ProfileContext] refresh failed", err)); + return () => { + if (retryRef.current) clearTimeout(retryRef.current); + }; }, [refresh]); useEffect(() => { diff --git a/client/ui/frontend/src/modules/profiles/ProfileDropdown.tsx b/client/ui/frontend/src/modules/profiles/ProfileDropdown.tsx index 75d88440d..a370ce32a 100644 --- a/client/ui/frontend/src/modules/profiles/ProfileDropdown.tsx +++ b/client/ui/frontend/src/modules/profiles/ProfileDropdown.tsx @@ -19,7 +19,7 @@ const MANAGE_VALUE = "__manage_profiles__"; export const ProfileDropdown = ({ onManageProfiles }: ProfileDropdownProps) => { const { t } = useTranslation(); - const { activeProfile, profiles, switchProfile } = useProfile(); + const { activeProfile, profiles, switchProfile, loaded } = useProfile(); const [open, setOpen] = useState(false); const [busy, setBusy] = useState(false); @@ -55,12 +55,15 @@ export const ProfileDropdown = ({ onManageProfiles }: ProfileDropdownProps) => { onManageProfiles?.(); }; - const displayName = activeProfile || t("profile.selector.loading"); + if (!loaded) return ; + + const hasProfile = !!activeProfile; + const displayName = hasProfile ? activeProfile : t("profile.selector.noProfile"); return ( - - + + { ); }; +const ProfileTriggerSkeleton = () => ( +
+
+
+
+); + type ProfileTriggerButtonProps = React.ButtonHTMLAttributes & { name: string; }; @@ -149,6 +159,7 @@ const ProfileTriggerButton = forwardRef