-
Notifications
You must be signed in to change notification settings - Fork 4
update leaderboard #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| 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; | ||
| for (let i = 0; i < name.length; i++) { | ||
| hash = name.charCodeAt(i) + ((hash << 5) - hash); | ||
| } | ||
| const c1 = `hsl(${Math.abs(hash) % 360}, 70%, 40%)`; | ||
| const c2 = `hsl(${(Math.abs(hash) + 60) % 360}, 80%, 30%)`; | ||
| const c3 = `hsl(${(Math.abs(hash) + 120) % 360}, 60%, 20%)`; | ||
|
|
||
| return `linear-gradient(135deg, ${c1}, ${c2}, ${c3})`; | ||
| }, [name]); | ||
|
|
||
| return ( | ||
| <div className="w-full relative sm:rounded-2xl md:rounded-3xl overflow-hidden h-40 sm:h-56 md:h-72 shadow-2xl border-t border-b sm:border border-white/5 bg-[#121226]"> | ||
| {imageUrl ? ( | ||
| <Image | ||
| src={imageUrl} | ||
| alt={`${name} banner`} | ||
| fill | ||
| className="object-cover opacity-80" | ||
| priority | ||
| /> | ||
| ) : ( | ||
| <div | ||
| className="absolute inset-0 opacity-80" | ||
| style={{ background: gradient }} | ||
| /> | ||
| )} | ||
| {/* Decorative patterns */} | ||
| <div className="absolute inset-0 mix-blend-overlay opacity-30" style={{ | ||
| backgroundImage: "radial-gradient(circle at 2px 2px, rgba(255,255,255,0.2) 1px, transparent 0)", | ||
| backgroundSize: "32px 32px" | ||
| }} /> | ||
| <div className="absolute inset-x-0 bottom-0 h-1/2 bg-gradient-to-t from-[#0a0a1a] to-transparent opacity-80" /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,9 @@ import { toast } from "react-toastify"; | |||||||
| import { useRouter } from "next/navigation"; | ||||||||
| import Image from "next/image"; | ||||||||
| import { Database } from "@/app/supabase-types"; | ||||||||
| import Banner from "./Banner"; | ||||||||
| import BackButton from "./BackButton"; | ||||||||
| import InviteFriendsButton from "./InviteFriendsButton"; | ||||||||
|
|
||||||||
| type LeaderboardRow = Database["public"]["Tables"]["leaderboards"]["Row"]; | ||||||||
|
|
||||||||
|
|
@@ -69,26 +72,49 @@ export default function LeaderboardHeader({ | |||||||
|
|
||||||||
| return ( | ||||||||
| <> | ||||||||
| <div className="group relative mb-8 text-center"> | ||||||||
| <div className="flex justify-center items-center gap-3"> | ||||||||
| <Image src="/logo.svg" alt="DevPulse Logo" width={36} height={36} /> | ||||||||
| <h1 className="text-3xl font-bold text-white">{leaderboard.name}</h1> | ||||||||
|
|
||||||||
| {isOwner && ( | ||||||||
| <button | ||||||||
| onClick={() => setOpen(true)} | ||||||||
| className="opacity-0 group-hover:opacity-100 transition text-gray-600 hover:text-indigo-400 p-1.5" | ||||||||
| > | ||||||||
| <FontAwesomeIcon icon={faPencil} className="text-sm" /> | ||||||||
| </button> | ||||||||
| )} | ||||||||
| <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" /> | ||||||||
|
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" /> | |
| <Banner name={leaderboard.name} /> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { faShareNodes } from "@fortawesome/free-solid-svg-icons"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from "react-toastify"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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!"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+8
to
+19
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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!"); | |
| const handleInvite = async () => { | |
| if (typeof window === "undefined") { | |
| return; | |
| } | |
| 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}`; | |
| if (!navigator.clipboard || typeof navigator.clipboard.writeText !== "function") { | |
| toast.error("Clipboard is not available in this browser."); | |
| return; | |
| } | |
| try { | |
| await navigator.clipboard.writeText(message); | |
| toast.success("Invite message copied to clipboard!"); | |
| } catch (_error) { | |
| toast.error("Failed to copy invite message to clipboard."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The standalone
{ }inside the fragment evaluates to an empty object and will be rendered as a React child, which triggers a runtime error (“Objects are not valid as a React child”). Remove this expression entirely.