From 7b3552d21db9deb40fc425e8e6ea59beafb72731 Mon Sep 17 00:00:00 2001 From: aaniya22 Date: Thu, 13 Aug 2026 14:42:55 +0530 Subject: [PATCH] feat: add manual Sync Data refresh button to dashboard header (#2406) Signed-off-by: aaniya22 --- src/components/DashboardHeader.tsx | 64 ++++++++++++++++++++++-------- src/components/SyncDataButton.tsx | 38 ++++++++++++++++++ 2 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 src/components/SyncDataButton.tsx diff --git a/src/components/DashboardHeader.tsx b/src/components/DashboardHeader.tsx index 2d222a106..4965b737c 100644 --- a/src/components/DashboardHeader.tsx +++ b/src/components/DashboardHeader.tsx @@ -18,6 +18,7 @@ import SignOutButton from "@/components/SignOutButton"; import ThemeToggle from "@/components/ThemeToggle"; import UserAvatar from "@/components/UserAvatar"; import KeyboardShortcuts from "@/components/KeyboardShortcuts"; +import SyncDataButton from "@/components/SyncDataButton"; import OnboardingTour from "@/components/OnboardingTour"; import { Moon, Sun } from "lucide-react"; import { toast } from "sonner"; @@ -51,7 +52,7 @@ const STALE_TIMES: Record = { "/api/metrics/repos": 5 * 60 * 1000, "/api/metrics/languages": 5 * 60 * 1000, "/api/notifications": 1 * 60 * 1000, - "default": 2 * 60 * 1000, + default: 2 * 60 * 1000, }; function getStaleTime(url: string): number { @@ -101,7 +102,9 @@ export function DashboardSyncProvider({ children }: { children: ReactNode }) { useEffect(() => { if (process.env.NODE_ENV === "test") return; const handleSync = () => { - console.log("[Client Cache] Invalidating dashboard metrics cache due to sync event."); + console.log( + "[Client Cache] Invalidating dashboard metrics cache due to sync event." + ); clientCache.clear(); }; @@ -116,13 +119,22 @@ export function DashboardSyncProvider({ children }: { children: ReactNode }) { const originalFetch = window.fetch; window.fetch = async (input, init) => { - const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; - const isGet = !init || !init.method || init.method.toUpperCase() === "GET"; + const url = + typeof input === "string" + ? input + : input instanceof URL + ? input.toString() + : input.url; + const isGet = + !init || !init.method || init.method.toUpperCase() === "GET"; if (isDashboardDataRequest(input)) { if (!isGet) { // Clear cache on write requests (POST, PUT, DELETE, PATCH) - console.log("[Client Cache] Invalidate cache due to mutation request:", url); + console.log( + "[Client Cache] Invalidate cache due to mutation request:", + url + ); clientCache.clear(); return originalFetch(input, init); } @@ -150,7 +162,10 @@ export function DashboardSyncProvider({ children }: { children: ReactNode }) { const nowTime = new Date(); setLastSynced(nowTime); - localStorage.setItem("devtrack-last-synced", nowTime.toISOString()); + localStorage.setItem( + "devtrack-last-synced", + nowTime.toISOString() + ); } return response; } catch (error) { @@ -258,7 +273,7 @@ export default function DashboardHeader() { const { isLive: isHeaderLive } = useRealtimeSync( "users", ["UPDATE"], - loadSettings, + loadSettings ); useEffect(() => { if (!session?.githubLogin) return; @@ -285,7 +300,10 @@ export default function DashboardHeader() { if (nightOwlCommitsCount >= 1) setIsNightOwl(true); if (earlyBirdCommitsCount >= 1) setIsEarlyBird(true); } catch (err) { - console.error("Failed to compile milestone hour distribution profiles:", err); + console.error( + "Failed to compile milestone hour distribution profiles:", + err + ); } } @@ -298,7 +316,8 @@ export default function DashboardHeader() { const [now, setNow] = useState(() => Date.now()); // Extract a fallback username parameter from active session data strings - const displayName = session?.user?.name || session?.githubLogin || "Developer"; + const displayName = + session?.user?.name || session?.githubLogin || "Developer"; useEffect(() => { if (!lastSynced) return; @@ -318,7 +337,6 @@ export default function DashboardHeader() {
- {/* Left Section */}
@@ -327,7 +345,9 @@ export default function DashboardHeader() { - {greeting}, {displayName}! + + {greeting}, {displayName}! +
{isNightOwl && (

Dashboard overview

@@ -360,7 +382,10 @@ export default function DashboardHeader() {

coding activity at a glance

@@ -370,7 +395,9 @@ export default function DashboardHeader() { aria-atomic="true" className="mt-1 flex items-center gap-1.5 text-xs text-[var(--muted-foreground)]" > - {minutesAgo <= 0 ? "Synced just now" : `Synced ${minutesAgo} min ago`} + {minutesAgo <= 0 + ? "Synced just now" + : `Synced ${minutesAgo} min ago`} {isHeaderLive && ( )} -
+
+ +
@@ -468,6 +497,9 @@ export default function DashboardHeader() { {menuOpen && (
+
+ +
@@ -503,4 +535,4 @@ export default function DashboardHeader() { {!seenOnboarding && } ); -} \ No newline at end of file +} diff --git a/src/components/SyncDataButton.tsx b/src/components/SyncDataButton.tsx new file mode 100644 index 000000000..806c8b7b1 --- /dev/null +++ b/src/components/SyncDataButton.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useState, useCallback } from "react"; +import { useRouter } from "next/navigation"; +import { RefreshCw } from "lucide-react"; +import { toast } from "sonner"; + +export default function SyncDataButton() { + const [isSyncing, setIsSyncing] = useState(false); + const router = useRouter(); + + const handleSync = useCallback(async () => { + if (isSyncing) return; + setIsSyncing(true); + try { + window.dispatchEvent(new Event("devtrack:sync")); + router.refresh(); + toast.success("Dashboard data synced"); + } catch (error) { + console.error("Failed to sync dashboard data:", error); + toast.error("Failed to sync data"); + } finally { + setTimeout(() => setIsSyncing(false), 600); + } + }, [isSyncing, router]); + + return ( + + ); +}