From 963529baaaeafd49854ae1fd925cfd756a58313f Mon Sep 17 00:00:00 2001 From: narang24 Date: Sat, 30 May 2026 20:02:44 +0530 Subject: [PATCH] feat(friend-comparison): add dedicated comparison page and search dropdown ct resolved --- next.config.mjs | 4 + src/app/dashboard/page.tsx | 106 ++++++++++++---- src/app/friend-compare/page.tsx | 127 +++++++++++++++++++ src/components/FriendComparison.tsx | 190 ++++++++++++++++++---------- 4 files changed, 331 insertions(+), 96 deletions(-) create mode 100644 src/app/friend-compare/page.tsx diff --git a/next.config.mjs b/next.config.mjs index 623ae5d8..e64ec997 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -140,6 +140,10 @@ const nextConfig = { protocol: "https", hostname: "github.githubassets.com", }, + { + protocol: "https", + hostname: "via.placeholder.com", + }, ], }, async headers() { diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index aac71d79..69d9c687 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,4 +1,4 @@ -import LazyWidget from "@/components/LazyWidget"; +import LazyWidget from "@/components/LazyWidget"; import DiscussionsWidget from "@/components/DiscussionsWidget"; import CommunityMetrics from "@/components/CommunityMetrics"; import GoalTracker from "@/components/GoalTracker"; @@ -22,6 +22,7 @@ import PersonalRecords from "@/components/PersonalRecords"; import LocalCodingTime from "@/components/LocalCodingTime"; import CodingTimeWidget from "@/components/CodingTimeWidget"; import RecentActivity from "@/components/RecentActivity"; +import FriendComparison from "@/components/FriendComparison"; import { authOptions } from "@/lib/auth"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; @@ -58,11 +59,6 @@ const CodingActivityInsightsCard = dynamic( { ssr: false, loading: () => }, ); -const FriendComparison = dynamic( - () => import("@/components/FriendComparison"), - { ssr: false, loading: () => }, -); - const ActivityRingChart = dynamic( () => import("@/components/ActivityRingChart"), { ssr: false, loading: () => }, @@ -108,24 +104,29 @@ export default async function DashboardPage() { {/* Quick actions */} -
- {/* Left side actions */} -
- - Year in Code - - - Settings - -
- {/* Right side exports */} -
+
+ + ✨ Year in Code + + + + + + Compare Friends + + + Settings + +
@@ -198,8 +199,60 @@ export default async function DashboardPage() {

Analytics & Repositories

- {/* Repo Analytics Explorer spans full width */} -
+ {/* Right: streak + coding time */} +
+ + + +
+ + {/* Repo analytics explorer — full width */} +
+ }> + + +
+ + {/* -- Row 2: PR metrics + Community metrics -- */} +
+ + +
+ + {/* PR breakdown + commit time — 2-col so charts have room */} +
+ }> + + + }> + + +
+ + {/* Activity ring — full width */} +
+ }> + + +
+ + {/* Coding activity insights — full width */} +
+ }> + + +
+ + {/* PR review trend — full width */} +
+ }> + + +
+ + {/* -- Row 3: Issues (2/3) + CI analytics (1/3) -- */} +
+
}> @@ -231,6 +284,7 @@ export default async function DashboardPage() {
+
{/* 4. GOALS & INSIGHTS */} diff --git a/src/app/friend-compare/page.tsx b/src/app/friend-compare/page.tsx new file mode 100644 index 00000000..b44d4c7d --- /dev/null +++ b/src/app/friend-compare/page.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useSession } from "next-auth/react"; +import { redirect } from "next/navigation"; +import FriendComparison from "@/components/FriendComparison"; +import dynamic from "next/dynamic"; +import Link from "next/link"; + +const ContributionGraph = dynamic( + () => import("@/components/ContributionGraph"), + { ssr: false } +); + +export default function FriendComparePage() { + const { data: session, status } = useSession(); + const [showCommitActivity, setShowCommitActivity] = useState(false); + const [compareUsername, setCompareUsername] = useState(null); + + useEffect(() => { + if (status === "unauthenticated") { + redirect("/"); + } + }, [status]); + + useEffect(() => { + const handleShowCommitActivity = (e: Event) => { + const customEvent = e as CustomEvent<{ username?: string }>; + const username = customEvent.detail?.username; + setCompareUsername(username || null); + setShowCommitActivity(true); + }; + + const handleClearCommitActivity = () => { + setShowCommitActivity(false); + setCompareUsername(null); + }; + + window.addEventListener("devtrack:show-commit-activity", handleShowCommitActivity as EventListener); + window.addEventListener("devtrack:clear-compare-user", handleClearCommitActivity); + return () => { + window.removeEventListener("devtrack:show-commit-activity", handleShowCommitActivity as EventListener); + window.removeEventListener("devtrack:clear-compare-user", handleClearCommitActivity); + }; + }, []); + + // When showCommitActivity becomes true, dispatch the compare event after a tick + useEffect(() => { + if (showCommitActivity && compareUsername) { + // Dispatch after the component has fully mounted (1000ms delay ensures dynamic import + listener setup) + const timer = setTimeout(() => { + window.dispatchEvent( + new CustomEvent("devtrack:compare-user", { + detail: { username: compareUsername }, + }) + ); + // Scroll to the element + const element = document.getElementById("contribution-activity"); + if (element) { + const elementPosition = element.getBoundingClientRect().top; + const offsetPosition = elementPosition + window.pageYOffset - 100; + window.scrollTo({ top: offsetPosition, behavior: "smooth" }); + } + }, 1000); + return () => clearTimeout(timer); + } + }, [showCommitActivity, compareUsername]); + + // Auto-show commit activity if a friend was persisted on page refresh + useEffect(() => { + if (typeof window !== "undefined") { + try { + const persistedFriend = localStorage.getItem("devtrack:compare_username"); + if (persistedFriend) { + setCompareUsername(persistedFriend); + setShowCommitActivity(true); + } + } catch { + // Silently fail if localStorage is not available + } + } + }, []); + + if (status === "loading") { + return ( +
+
+
+

Loading...

+
+
+ ); + } + + return ( +
+ {/* Header */} +
+ + + + + Dashboard + +

+ Friend Comparison +

+

+ Compare your GitHub stats with friends and see how you stack up +

+
+ + {/* Main content */} +
+ + + {/* Commit Activity Comparison - Only rendered when button is clicked */} + {showCommitActivity && ( + + )} +
+
+ ); +} diff --git a/src/components/FriendComparison.tsx b/src/components/FriendComparison.tsx index 05cd81c8..516ab8f5 100644 --- a/src/components/FriendComparison.tsx +++ b/src/components/FriendComparison.tsx @@ -29,6 +29,7 @@ export default function FriendComparison() { if (typeof window === "undefined") return ""; return localStorage.getItem(STORAGE_KEY) ?? ""; }); + const [selectedUserAvatar, setSelectedUserAvatar] = useState(""); const [comparingUser, setComparingUser] = useState(""); const [myData, setMyData] = useState(null); const [friendData, setFriendData] = useState(null); @@ -127,6 +128,7 @@ export default function FriendComparison() { const chooseSuggestion = (user: SuggestedUser) => { setFriendUsername(user.username); + setSelectedUserAvatar(user.avatarUrl); setSuppressNextSuggestFetch(true); setSuggestions([]); setSuggestOpen(false); @@ -158,6 +160,11 @@ export default function FriendComparison() { detail: { username: trimmed }, }) ); + window.dispatchEvent( + new CustomEvent("devtrack:show-commit-activity", { + detail: { username: trimmed }, + }) + ); } } catch { setError("An error occurred"); @@ -173,6 +180,7 @@ export default function FriendComparison() { const clearComparison = () => { setFriendUsername(""); + setSelectedUserAvatar(""); setComparingUser(""); setFriendData(null); setError(""); @@ -185,12 +193,6 @@ export default function FriendComparison() { const handleCommitActivityClick = (e: React.MouseEvent) => { e.preventDefault(); - const element = document.getElementById("contribution-activity"); - if (element) { - const elementPosition = element.getBoundingClientRect().top; - const offsetPosition = elementPosition + window.pageYOffset - 100; - window.scrollTo({ top: offsetPosition, behavior: "smooth" }); - } }; return ( @@ -211,39 +213,63 @@ export default function FriendComparison() { className="flex flex-col sm:flex-row gap-2 w-full" >
- setFriendUsername(e.target.value)} - onFocus={() => { - if (suggestions.length > 0) setSuggestOpen(true); - }} - onKeyDown={(e) => { - if (!suggestOpen || suggestions.length === 0) return; - - if (e.key === "ArrowDown") { - e.preventDefault(); - setActiveIndex((prev) => Math.min(prev + 1, suggestions.length - 1)); - } else if (e.key === "ArrowUp") { - e.preventDefault(); - setActiveIndex((prev) => Math.max(prev - 1, 0)); - } else if (e.key === "Enter") { - if (activeIndex >= 0 && activeIndex < suggestions.length) { + {selectedUserAvatar && friendUsername ? ( +
+ {`${friendUsername} + {friendUsername} + +
+ ) : ( + setFriendUsername(e.target.value)} + onFocus={() => { + if (suggestions.length > 0) setSuggestOpen(true); + }} + onKeyDown={(e) => { + if (!suggestOpen || suggestions.length === 0) return; + + if (e.key === "ArrowDown") { e.preventDefault(); - chooseSuggestion(suggestions[activeIndex]); + setActiveIndex((prev) => Math.min(prev + 1, suggestions.length - 1)); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + setActiveIndex((prev) => Math.max(prev - 1, 0)); + } else if (e.key === "Enter") { + if (activeIndex >= 0 && activeIndex < suggestions.length) { + e.preventDefault(); + chooseSuggestion(suggestions[activeIndex]); + } + } else if (e.key === "Escape") { + setSuggestOpen(false); + setActiveIndex(-1); } - } else if (e.key === "Escape") { - setSuggestOpen(false); - setActiveIndex(-1); - } - }} - aria-autocomplete="list" - aria-expanded={suggestOpen} - aria-controls="friend-compare-suggestions" - className="w-full rounded-md border border-[var(--border)] bg-[var(--background)] px-3 py-2 text-sm outline-none focus:border-[var(--accent)]" - /> + }} + aria-autocomplete="list" + aria-expanded={suggestOpen} + aria-controls="friend-compare-suggestions" + className="w-full rounded-md border border-[var(--border)] bg-[var(--background)] px-3 py-2 text-sm outline-none focus:border-[var(--accent)]" + /> + )} {suggestOpen && suggestions.length > 0 && (
{loading ? "Loading..." : "Compare"} + + {friendData && ( + + )}
@@ -332,14 +368,41 @@ export default function FriendComparison() { )}
-
-
-
You ({myData.username})
-
Metric
-
Them ({friendData.username})
+
+ {/* Header with profile info */} +
+ {/* Metric column header */} +
+ Metric +
+ + {/* My profile header */} +
+ {myData.username} + {myData.username} +
+ + {/* Friend profile header */} +
+ {friendData.username} + {friendData.username} +
-
+ {/* Metrics rows */} +
+
+ +
)} -
- - View Commit Activity - - -
)} @@ -425,20 +475,20 @@ function ComparisonRow({ } return ( -
+
+
+ {label} +
{myValue} {suffix}
-
- {label} -