diff --git a/src/modules/client/layout/ClientLayout.jsx b/src/modules/client/layout/ClientLayout.jsx index ed3fe40..c971c8f 100644 --- a/src/modules/client/layout/ClientLayout.jsx +++ b/src/modules/client/layout/ClientLayout.jsx @@ -152,12 +152,20 @@ function ClientNav() { // Caches the resolved stream URL per asset_id — avoids re-hitting /media/token // for the same asset on re-renders. Token TTL is 4h so safe within a session. const badgeTokenCache = useRef({}) + // Starts false on every mount (incl. full page reloads) so the tier badge + // shows a gray skeleton instead of flashing "Free" before /tiers/me resolves — + // myTier stays null for genuinely-free users too, so it can't be used as the signal. + const [tierReady, setTierReady] = useState(false) useEffect(() => { if (!user) return; if (achievements.length === 0) getAchievements(); - if (!myTier) getMyTier(); if (tierCategories.length === 0) getTierCategories(); + if (!myTier) { + getMyTier().finally(() => setTierReady(true)); + } else { + setTierReady(true); + } }, [user]); // ── Derive directly from auth user — same pattern as admin UserMenu ────── @@ -247,7 +255,12 @@ function ClientNav() {
- {tierBadge && ( + {!tierReady ? ( +
+
+ Free +
+ ) : tierBadge && (
{badgeImgUrl ?