From 44b431687007c8c42756e250c67e6be479c9412d Mon Sep 17 00:00:00 2001 From: Prakshitha Malla Date: Sat, 30 May 2026 18:02:08 +0530 Subject: [PATCH] feat: display Night Owl and Early Bird badges across dashboard header and public profile page --- src/app/u/[username]/page.tsx | 72 +++++++++++++++-------- src/components/DashboardHeader.tsx | 91 ++++++++++++++++++++++++++++-- 2 files changed, 136 insertions(+), 27 deletions(-) diff --git a/src/app/u/[username]/page.tsx b/src/app/u/[username]/page.tsx index 3ac6d8e11..763c4fab9 100644 --- a/src/app/u/[username]/page.tsx +++ b/src/app/u/[username]/page.tsx @@ -12,9 +12,7 @@ import { getUserByUsername } from "@/lib/supabase"; import { syncGitHubAchievementsForUser } from "@/lib/github-achievements"; import PinnedReposWidget from "@/components/PinnedReposWidget"; import { fetchPinnedRepoDetails } from "@/lib/pinned-repos"; - - - +import { Moon, Sun } from "lucide-react"; // 🎯 UI vectors for server visibility import { fetchPublicTopRepos, @@ -23,10 +21,16 @@ import { type PublicProfileData, } from "@/lib/public-profile-data"; +// Extend tracking structures to forward gamification flags seamlessly downstream +interface ExtendedPublicProfileData extends PublicProfileData { + isNightOwl: boolean; + isEarlyBird: boolean; +} + async function fetchPublicProfile( username: string, options: { includeAchievements?: boolean } = {} -): Promise { +): Promise { const user = await getUserByUsername(username); if (!user) return null; @@ -48,11 +52,26 @@ async function fetchPublicProfile( userId: user.id, githubLogin: user.github_login, token: githubToken, - }) + }) : Promise.resolve({ achievements: [], syncedAt: null, error: null }), fetchPinnedRepoDetails(user.github_login, user.pinned_repos || [], githubToken), ]); + // Server-side parsing layout to compute hourly metrics cleanly from available repo data arrays + let nightOwlCount = 0; + let earlyBirdCount = 0; + + const combinedRepos = repos || []; + combinedRepos.forEach((repo: any) => { + if (repo.last_commit_date || repo.updatedAt) { + const targetDate = repo.last_commit_date || repo.updatedAt; + const commitHour = new Date(targetDate).getHours(); + + if (commitHour >= 0 && commitHour <= 4) nightOwlCount++; + if (commitHour >= 5 && commitHour <= 8) earlyBirdCount++; + } + }); + return { username: user.github_login, userId: user.id, @@ -63,6 +82,8 @@ async function fetchPublicProfile( achievements: achievementsCache.achievements, achievementsError: achievementsCache.error, spotlightRepos: spotlight, + isNightOwl: nightOwlCount >= 1, + isEarlyBird: earlyBirdCount >= 1, }; } @@ -81,7 +102,6 @@ export async function generateMetadata({ params: { username: string }; }): Promise { const { username } = params; - // Minimal lookup — avoids duplicating 3 GitHub API calls that the page already makes const user = await getUserByUsername(username); const profileUrl = getProfileUrl(username); @@ -158,16 +178,35 @@ export default async function PublicProfilePage({
-

- @{profile.username}'s Profile +

+ @{profile.username}'s Profile {profile.isSponsor && } + + {/* 🎯 Render Server-Calculated Time Distribution Badges Safely on Public Profile View */} + {profile.isNightOwl && ( + + + Night Owl + + )} + {profile.isEarlyBird && ( + + + Early Bird + + )}

GitHub activity and coding stats

- {/* Download stats card button — client component */}
) : (
- {/* Simple text-based activity display for public profiles */}
{data.length} active days
@@ -290,10 +324,6 @@ function PublicContributionGraph({ ); } -/** - * Public variant of StreakTracker component. - * Displays data passed as props. - */ function PublicStreakTracker({ streak }: { streak: any }) { const stats = [ { @@ -364,10 +394,6 @@ function PublicStreakTracker({ streak }: { streak: any }) { ); } -/** - * Public variant of TopRepos component. - * Displays data passed as props. - */ function PublicTopRepos({ repos, }: { @@ -425,4 +451,4 @@ function PublicTopRepos({ )}
); -} +} \ No newline at end of file diff --git a/src/components/DashboardHeader.tsx b/src/components/DashboardHeader.tsx index 3e66be423..d8f06102a 100644 --- a/src/components/DashboardHeader.tsx +++ b/src/components/DashboardHeader.tsx @@ -8,10 +8,58 @@ import SignOutButton from "@/components/SignOutButton"; import ThemeToggle from "@/components/ThemeToggle"; import UserAvatar from "@/components/UserAvatar"; import KeyboardShortcuts from "@/components/KeyboardShortcuts"; +import { Moon, Sun } from "lucide-react"; export default function DashboardHeader() { const { data: session } = useSession(); const [isPublic, setIsPublic] = useState(null); + const [greeting, setGreeting] = useState("Welcome back"); + + const [isNightOwl, setIsNightOwl] = useState(false); + const [isEarlyBird, setIsEarlyBird] = useState(false); + + useEffect(() => { + const computeCurrentGreeting = () => { + const currentHour = new Date().getHours(); + if (currentHour >= 5 && currentHour < 12) return "Good morning ☀️"; + if (currentHour >= 12 && currentHour < 17) return "Good afternoon 🌤️"; + if (currentHour >= 17 && currentHour < 22) return "Good evening 🌙"; + return "Burning the midnight oil 🦉"; + }; + setGreeting(computeCurrentGreeting()); + }, []); + + useEffect(() => { + if (!session?.githubLogin) return; + + async function evaluateCodingDistributionMilestones() { + try { + const res = await fetch("/api/metrics/repos?days=90"); + if (!res.ok) return; + + const data = await res.json(); + const commitsArray = data.repos || []; + + let nightOwlCommitsCount = 0; + let earlyBirdCommitsCount = 0; + + commitsArray.forEach((repo: any) => { + if (repo.last_commit_date) { + const commitHour = new Date(repo.last_commit_date).getHours(); + if (commitHour >= 0 && commitHour <= 4) nightOwlCommitsCount++; + if (commitHour >= 5 && commitHour <= 8) earlyBirdCommitsCount++; + } + }); + + if (nightOwlCommitsCount >= 1) setIsNightOwl(true); + if (earlyBirdCommitsCount >= 1) setIsEarlyBird(true); + } catch (err) { + console.error("Failed to compile milestone hour distribution profiles:", err); + } + } + + evaluateCodingDistributionMilestones(); + }, [session]); useEffect(() => { if (!session) { @@ -22,7 +70,6 @@ export default function DashboardHeader() { async function loadSettings() { try { const res = await fetch("/api/user/settings"); - if (res.ok) { const data = await res.json(); setIsPublic(data.is_public === true); @@ -38,15 +85,51 @@ export default function DashboardHeader() { loadSettings(); }, [session]); + const displayName = session?.user?.name || session?.githubLogin || "Developer"; + return (
{/* Left Section */}
-

- Dashboard -

+
+
+
+ + + + + + {greeting}, {displayName}! + +
+ + {isNightOwl && ( +
+ + Night Owl +
+ )} + + {isEarlyBird && ( +
+ + Early Bird +
+ )} +
+ +

+ Dashboard +

+

Your coding activity at a glance 🚀