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}
+ >
);
}