diff --git a/src/components/StreakMilestoneBanner.tsx b/src/components/StreakMilestoneBanner.tsx new file mode 100644 index 0000000..7cde29b --- /dev/null +++ b/src/components/StreakMilestoneBanner.tsx @@ -0,0 +1,33 @@ +"use client"; +type Props = { + streak: number; + onDismiss?: () => void; +}; + +export default function StreakMilestoneBanner({ + streak, + onDismiss, +}: Props) { + return ( +
+
+ 🎉 You reached a {streak}-day streak! Keep it up! +
+ + +
+ ); +} diff --git a/src/components/StreakTracker.tsx b/src/components/StreakTracker.tsx index 477edd5..b27e3e5 100644 --- a/src/components/StreakTracker.tsx +++ b/src/components/StreakTracker.tsx @@ -3,8 +3,11 @@ import { useCallback, useEffect, useState } from "react"; import { useAccount } from "@/components/AccountContext"; import { useCountUp } from "@/hooks/useCountUp"; +import StreakMilestoneBanner from "@/components/StreakMilestoneBanner"; import { useHeatmapTheme } from "@/hooks/useHeatmapTheme"; +const STREAK_MILESTONES = [7, 30, 50, 100, 200, 365]; + interface StreakData { current: number; longest: number; @@ -27,6 +30,7 @@ export default function StreakTracker() { const [data, setData] = useState(null); const [contributionData, setContributionData] = useState(null); const [loading, setLoading] = useState(true); + const [dismissedMilestones, setDismissedMilestones] = useState([]); const [lastUpdated, setLastUpdated] = useState(null); const [minutesAgo, setMinutesAgo] = useState(0); const [copied, setCopied] = useState(false); @@ -91,6 +95,20 @@ export default function StreakTracker() { fetchFreeze(); }, [fetchStreak]); + useEffect(() => { + const stored = localStorage.getItem( + "devtrack:dismissed-milestones" + ); + + if (stored) { + try { + setDismissedMilestones(JSON.parse(stored)); + }catch { + // ignore invalid localStorage data + } + } + }, []); + useEffect(() => { if (!lastUpdated) return; const interval = setInterval(() => { @@ -237,7 +255,37 @@ export default function StreakTracker() { }).catch(() => {}); }; + const currentMilestone = + STREAK_MILESTONES.find( + (m) => m === data?.current + ); + const shouldShowBanner = + currentMilestone && + !dismissedMilestones.includes(currentMilestone); + const handleDismissBanner = () => { + if (!currentMilestone) return; + + const updated = [ + ...dismissedMilestones, + currentMilestone, + ]; + + setDismissedMilestones(updated); + + localStorage.setItem( + "devtrack:dismissed-milestones", + JSON.stringify(updated) + ); + }; + return ( + <> + {shouldShowBanner && currentMilestone && ( + + )}

@@ -397,6 +445,7 @@ export default function StreakTracker() { /> ) : null}

+ ); }