Skip to content

update navigation#39

Closed
0x3EF8 wants to merge 5 commits intohallofcodes:masterfrom
0x3EF8:update-navigation
Closed

update navigation#39
0x3EF8 wants to merge 5 commits intohallofcodes:masterfrom
0x3EF8:update-navigation

Conversation

@0x3EF8
Copy link
Member

@0x3EF8 0x3EF8 commented Mar 22, 2026

update navigation

Copilot AI review requested due to automatic review settings March 22, 2026 07:03
@0x3EF8 0x3EF8 closed this Mar 22, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the leaderboard experience with a new header layout (banner/back/invite), a new stats sidebar, and a redesigned ranking/podium presentation, along with some related client-component and dependency updates.

Changes:

  • Add new leaderboard UI components (Banner, Invite Friends button, Stats sidebar) and integrate them into the leaderboard header/table.
  • Redesign leaderboard rankings to include a podium for the top 3 plus a badge/legend system.
  • Update dependencies/lockfile (adds shx) and adjust a few client components / typings.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
package.json Adds shx to devDependencies.
package-lock.json Lockfile updated to include shx and many additional metadata changes.
app/components/leaderboard/LeaderboardStats.tsx New stats sidebar (languages/editors/aggregates) with AOS animation hooks.
app/components/leaderboard/InviteFriendsButton.tsx New “Invite Friends” button that copies an invite message to clipboard.
app/components/leaderboard/Header.tsx Replaces simple header with banner-based layout, back button overlay, and invite action.
app/components/leaderboard/Banner.tsx New banner component (image or gradient fallback).
app/components/leaderboard/BackButton.tsx Adds href prop and changes default destination.
app/components/LeaderboardTable.tsx Adds podium UI, badge system + legend, and moves stats into a sidebar layout.
app/components/chat/Messages.tsx Updates SyntaxHighlighter typing/casts in markdown rendering.
app/components/chat/Conversations.tsx Marks component as client-side.
app/components/dashboard/widgets/StatsCard.tsx Marks component as client-side.
app/components/dashboard/Navbar.tsx Removes unused faGear import.
app/components/Chat.tsx Removes unused toast import.
app/components/landing-page/TopLeaderbord.tsx Simplifies categories typing.
app/(public)/leaderboard/page.tsx Uses new BackButton API (href).
app/(public)/leaderboard/[slug]/page.tsx Layout tweaks; back navigation now handled via header.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +6
import React, { useMemo } from "react";
import Image from "next/image";

export default function Banner({ name, imageUrl }: { name: string, imageUrl?: string }) {
const gradient = useMemo(() => {
let hash = 0;
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component uses useMemo, but it’s missing a "use client" directive. In the Next.js App Router this will fail to compile as a Server Component. Add "use client" at the top (or remove the hook and compute the gradient without React hooks).

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +87
const daily_average = totalSeconds / Math.max(totalDevs, 1);

const totalHoursFormatted = formatHours(totalSeconds);
const avgHoursFormatted = formatHours(daily_average || 0);
const topLang = languageList[0]?.name || "N/A";
const topEditor = editorList[0]?.name || "N/A";
const topEditorCount = editorList[0]?.count || 0;
const topLangProgress = languageList[0]?.percent || 0;

const statCards = [
{
label: "Total Coding",
value: totalHoursFormatted,
sub: "Leaderboard Total",
trend: `${totalDevs} Dev${totalDevs !== 1 ? 's' : ''}`,
trendUp: true,
},
{
label: "Avg Daily Time",
value: avgHoursFormatted,
sub: "Per Developer",
trend: "+12%",
trendUp: true,
},
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

daily_average here is computed as total seconds per developer, but it’s displayed as “Avg Daily Time”. Either rename/relabel this stat (e.g., “Avg per developer”) or compute a true daily average from the underlying stats; otherwise the UI is misleading.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +18
setTimeout(() => {
AOS.refresh();
}, 200);
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setTimeout in this effect isn’t cleared on unmount. Store the timeout id and clear it in the cleanup function to avoid calling AOS.refresh() after unmount.

Suggested change
setTimeout(() => {
AOS.refresh();
}, 200);
const timeoutId = setTimeout(() => {
AOS.refresh();
}, 200);
return () => {
clearTimeout(timeoutId);
};

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +20
export default function InviteFriendsButton({ joinCode, leaderboardName }: { joinCode?: string; leaderboardName?: string }) {
const handleInvite = () => {
if (typeof window !== "undefined") {
const inviteUrl = joinCode
? `${window.location.origin}/join/${joinCode}`
: window.location.href; // fallback

const message = leaderboardName
? `Join my coding leaderboard "${leaderboardName}" on DevPulse!\n\nTrack metrics, compete with fellow developers, and showcase your engineering skills.\n\nJoin here: ${inviteUrl}`
: `Join my coding leaderboard on DevPulse!\n\nJoin here: ${inviteUrl}`;

navigator.clipboard.writeText(message);
toast.success("Invite message copied to clipboard!");
}
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

navigator.clipboard.writeText(...) returns a promise and can fail (non-secure context, permission denied, unsupported). Await it and handle errors (show a toast error), and guard for navigator.clipboard being unavailable. Also consider setting type="button" to avoid accidental form submission if this button is ever placed inside a form.

Copilot uses AI. Check for mistakes.
Comment on lines 270 to 292
@@ -263,15 +280,15 @@ export default function LeaderboardTable({
{/* Header Row (Desktop) */}
<div className="hidden md:flex items-center px-4 sm:px-6 py-4 border-b border-white/5 bg-white/[0.01] text-[10px] font-bold text-gray-500 uppercase tracking-widest">
<div className="w-12 shrink-0 text-center">Rank</div>
<div className="flex-1 ml-4">Developer</div>
<div className="w-48 lg:w-72 xl:w-80">Tech Stack</div>
<div className="w-32 lg:w-48">Environment</div>
<div className="w-24 text-right">Score</div>
<div className="flex-1 ml-4 min-w-[150px]">Developer</div>
<div className="w-40 md:w-48 lg:w-48 xl:w-64">Language</div>
<div className="w-24 md:w-32 lg:w-32 xl:w-48">Editor</div>
<div className="w-24 text-right">Hours</div>
</div>

{/* List Body */}
<div className="flex flex-col divide-y divide-white/5">
{ranked.map((user) => {
{ranked.slice(3).map((user) => {
const isCurrentUser = user.user_id === ownerId;
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When ranked.length is 1–3, the podium renders but the table renders with headers and an empty body because of ranked.slice(3). Consider conditionally hiding the table section when there are no rows beyond the podium, or include the top 3 in the list as well.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +38
const secs = typeof l === "string" ? 3600 : l.total_seconds || 3600;
languageTime[name] = (languageTime[name] || 0) + secs;
});
(m.editors as { name?: string; total_seconds?: number }[] || []).forEach((e: { name?: string; total_seconds?: number } | string) => {
const name = typeof e === "string" ? e : e.name || "Unknown";
const secs = typeof e === "string" ? 3600 : e.total_seconds || 3600;
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NonNullableMember.languages/editors are typed as { name: string }[] (no total_seconds). With the current fallback of || 3600, any missing total_seconds will add 1 hour per entry, inflating totals and percentages (often > 100%). Default missing total_seconds to 0 (and consider aligning the member types to the actual Supabase JSON shape).

Suggested change
const secs = typeof l === "string" ? 3600 : l.total_seconds || 3600;
languageTime[name] = (languageTime[name] || 0) + secs;
});
(m.editors as { name?: string; total_seconds?: number }[] || []).forEach((e: { name?: string; total_seconds?: number } | string) => {
const name = typeof e === "string" ? e : e.name || "Unknown";
const secs = typeof e === "string" ? 3600 : e.total_seconds || 3600;
const secs = typeof l === "string" ? 3600 : (l.total_seconds ?? 0);
languageTime[name] = (languageTime[name] || 0) + secs;
});
(m.editors as { name?: string; total_seconds?: number }[] || []).forEach((e: { name?: string; total_seconds?: number } | string) => {
const name = typeof e === "string" ? e : e.name || "Unknown";
const secs = typeof e === "string" ? 3600 : (e.total_seconds ?? 0);

Copilot uses AI. Check for mistakes.

import { useEffect } from "react";
import AOS from "aos";
import "aos/dist/aos.css";
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global AOS CSS is already imported in app/components/AOSWrapper.tsx. Importing it again here is redundant and increases CSS duplication/bundle size; prefer a single global import location.

Suggested change
import "aos/dist/aos.css";

Copilot uses AI. Check for mistakes.
Comment on lines +134 to 136
import LeaderboardStats from "./leaderboard/LeaderboardStats";

export default function LeaderboardTable({
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import appears mid-file. While syntactically valid, it breaks the project’s typical import organization and can confuse tooling. Move import LeaderboardStats ... to the top with the other imports.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +77
{/* Using a temporary placeholder banner image */}
<Banner name={leaderboard.name} imageUrl="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?q=80&w=2070&auto=format&fit=crop" />
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hard-coded Unsplash URL is labeled as a “temporary placeholder”. If this isn’t meant to ship, prefer omitting imageUrl so the gradient fallback is used, or source the banner image from persisted leaderboard data/config instead of a baked-in external URL.

Suggested change
{/* Using a temporary placeholder banner image */}
<Banner name={leaderboard.name} imageUrl="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?q=80&w=2070&auto=format&fit=crop" />
{/* Using the default banner background (gradient fallback) */}
<Banner name={leaderboard.name} />

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +78
<div className="group relative mb-20 sm:mb-24">
{/* Using a temporary placeholder banner image */}
<Banner name={leaderboard.name} imageUrl="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?q=80&w=2070&auto=format&fit=crop" />

Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR title/description say “update navigation”, but this change set also adds new leaderboard UI components, animations, and dependencies. Please update the PR description to reflect the full scope (or split into smaller PRs) so reviewers can assess impact accurately.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants