diff --git a/apps/desktop/src/shell/AppLayout.tsx b/apps/desktop/src/shell/AppLayout.tsx index 75f78ff..bba2826 100644 --- a/apps/desktop/src/shell/AppLayout.tsx +++ b/apps/desktop/src/shell/AppLayout.tsx @@ -6,6 +6,7 @@ import cursorIconUrl from "../shared/assets/icons/cursor.svg"; import { api } from "../shared/api"; import { AdMenu } from "./ads/AdMenu"; import { FloatingAd } from "./ads/FloatingAd"; +import { loadCachedAds, saveCachedAds } from "./ads/cache"; import { AdActionType, type AdAction, type AdSlot } from "./ads/types"; import { PageLayout } from "./layout/PageLayout"; import { Card } from "../shared/ui/Card"; @@ -50,7 +51,7 @@ export function AppLayout() { const location = useLocation(); const [leftActionTarget, setLeftActionTarget] = useState(null); const [rightActionTarget, setRightActionTarget] = useState(null); - const [ads, setAds] = useState([]); + const [ads, setAds] = useState(() => loadCachedAds()); const [activeAd, setActiveAd] = useState(null); const [dismissCandidate, setDismissCandidate] = useState(null); const [dismissReason, setDismissReason] = useState(""); @@ -98,6 +99,8 @@ export function AppLayout() { let disposed = false; let pending = false; let lastRequestedAt = 0; + setAds(loadCachedAds()); + setActiveAd(null); const refreshAds = () => { const now = Date.now(); if (pending || now - lastRequestedAt < 10_000) return; @@ -105,6 +108,7 @@ export function AppLayout() { lastRequestedAt = now; void api.ads(dismissedAdIdsRef.current, locale) .then((runtime) => { + saveCachedAds(runtime.slots); if (disposed) return; setAds(runtime.slots); setActiveAd((current) => current @@ -112,9 +116,7 @@ export function AppLayout() { : null); }) .catch(() => { - if (disposed) return; - setAds([]); - setActiveAd(null); + // Keep the current ads and local cache available when refreshing fails. }) .finally(() => { pending = false; }); }; diff --git a/apps/desktop/src/shell/ads/cache.ts b/apps/desktop/src/shell/ads/cache.ts new file mode 100644 index 0000000..94828db --- /dev/null +++ b/apps/desktop/src/shell/ads/cache.ts @@ -0,0 +1,21 @@ +import type { AdSlot } from "./types"; + +const adCacheStorageKey = "cursor-byok:ad-runtime"; + +export function loadCachedAds(): AdSlot[] { + try { + const value: unknown = JSON.parse(localStorage.getItem(adCacheStorageKey) ?? "null"); + if (!value || typeof value !== "object" || !Array.isArray((value as { slots?: unknown }).slots)) return []; + return (value as { slots: AdSlot[] }).slots; + } catch { + return []; + } +} + +export function saveCachedAds(slots: AdSlot[]): void { + try { + localStorage.setItem(adCacheStorageKey, JSON.stringify({ slots })); + } catch { + // The current session still uses the fetched ads when storage is unavailable. + } +}