Skip to content
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

Leaderboards #263

Merged
merged 26 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions web/pages/leaderboards/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import LeaderboardView from "../../src/components/leaderboard/LeaderboardView";
import LeaderboardLayout from "../../src/components/leaderboard/LeaderboardLayout";

const AtBat = () => {
return (
<LeaderboardLayout>
<LeaderboardView />
</LeaderboardLayout>
);
};

export default AtBat;
22 changes: 22 additions & 0 deletions web/src/components/icons/ArrowLeft.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";

const ArrowLeft: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
{...props}
>
<path
d="M6.25 14.1668L2.08333 10.0002L6.25 5.8335"
stroke="#262019"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path d="M17.5 10L2.5 10" stroke="#262019" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);

export default ArrowLeft;
28 changes: 16 additions & 12 deletions web/src/components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import { useMediaQuery, useDisclosure, Image } from "@chakra-ui/react";
import { Image } from "@chakra-ui/react";
import { useEffect, useRef, useState } from "react";
import styles from "./Navbar.module.css";
import useUser from "../../contexts/UserContext";
import FullcountLogoSmall from "../icons/FullcountLogoSmall";
import AccountMobile from "../icons/AccountMobile";
import VolumeOn from "../icons/VolumeOn";
import MoreHorizontal from "../icons/MoreHorizontal";
import useLogout from "../../hooks/useLogout";
import { FEEDBACK_FORM_URL, FULLCOUNT_ASSETS_PATH } from "../../constants";
import { setLocalStorageItem } from "../../utils/localStorage";
import { useGameContext } from "../../contexts/GameContext";
import router from "next/router";

const Navbar = () => {
const [isSmallScreen, isMediumScreen] = useMediaQuery([
"(max-width: 767px)",
"(min-width: 1024px)",
]);
const { user } = useUser();

const [isMenuOpen, setIsMenuOpen] = useState(false);
Expand All @@ -34,6 +29,11 @@ const Navbar = () => {
updateContext({ joinedNotification: !joinedNotification });
};

const handleLeaderboardClick = () => {
// sendReport('Leaderboards', {}, ["type:click","click:leaderboards"])
router.push("/leaderboards");
};

useEffect(() => {
if (isMenuOpen) {
document.addEventListener("click", handleClickOutside, true);
Expand All @@ -54,10 +54,12 @@ const Navbar = () => {
src={`${FULLCOUNT_ASSETS_PATH}/logo/fullcount-mini.png`}
/>
<div className={styles.rightSide}>
<div className={styles.account}>
<AccountMobile />
<div className={styles.username}>{user.username}</div>
</div>
{user && (
<div className={styles.account}>
<AccountMobile />
<div className={styles.username}>{user.username}</div>
</div>
)}
{/*<div className={styles.menuButton}>*/}
{/* <VolumeOn />*/}
{/*</div>*/}
Expand All @@ -70,7 +72,9 @@ const Navbar = () => {
<div ref={menuRef} className={styles.menuList}>
{/*<div className={styles.menuItem}>About</div>*/}
{/*<div className={styles.menuItem}>Achievements</div>*/}
{/*<div className={styles.menuItem}>Leaderboards</div>*/}
<div className={styles.menuItem} onClick={handleLeaderboardClick}>
Leaderboards
</div>
<div
className={styles.menuItem}
onClick={handleNotificationClick}
Expand Down
140 changes: 140 additions & 0 deletions web/src/components/leaderboard/Leaderboard.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
.container {
display: flex;
flex-direction: column;
width: 100%;
gap: 5px;
margin-bottom: 20px;
}

.divider {
height: 0.5px;
align-self: stretch;
background: #8B8B8B;
}


.tableHeader, .tableContent {
display: grid;
grid-template-columns: 60px auto 60px;
gap: 8px;
padding: 5px 0;
width: 100%;
}

.dataContainer {
overflow: auto;
scrollbar-width: none;
-ms-overflow-style: none;
height: 330px;
color: black;
}

.dataContainer::-webkit-scrollbar {
display: none;
}

.header {
color: #262019;
font-family: Pangolin, cursive;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 100%; /* 14px */
}

.pageContainer {
display: flex;
flex-direction: column;
gap: 5px;
}

.rank {
color: #262019;
font-family: Pangolin, cursive;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 100%; /* 14px */
display: flex;
align-items: center;
justify-content: center;
width: 20px;
}


.firstRanks {
color: white;
display: flex;
align-items: center;
justify-content: center;
width: 20px;
}

.rankFirst {
background: #CEA250;
}

.rankSecond {
background: #9CAEAE;
}

.rankThird {
background: #ED7E4C;
}

.characterContainer {
display: flex;
gap: 5px;
}

.characterImage {
height: 24px;
width: 24px;
}

.characterInfo {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
flex-grow: 2;
}

.characterName {
/*overflow: hidden;*/
color: #262019;
text-overflow: ellipsis;
font-family: Bangers, cursive;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 100%; /* 14px */
}

.characterId {
color: #8EAB8F;
text-align: center;
font-family: Bangers, cursive;
font-size: 10px;
font-style: normal;
font-weight: 400;
line-height: 100%; /* 10px */
}

.score {
color: #262019;
text-align: right;
font-family: Pangolin, cursive;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}

@media (min-width: 768px) {
.tableHeader, .tableContent {
grid-template-columns: 193px auto 60px;
}
}


77 changes: 77 additions & 0 deletions web/src/components/leaderboard/Leaderboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useInfiniteQuery } from "react-query";
import React, { useEffect, useRef } from "react";
import { fetchLeaderboardData, LeaderboardEntry } from "./leaderboards";
import { useGameContext } from "../../contexts/GameContext";
import styles from "./Leaderboard.module.css";
import { Spinner } from "@chakra-ui/react";
import LeaderboardItem from "./LeaderboardItem";
import LeaderboardHeader from "./LeaderboardHeader";

function Leaderboard({ leaderboardId }: { leaderboardId: string }) {
const entriesPerPage = 15;
const listRef = useRef<HTMLDivElement>(null); // Reference to the div that scrolls
const { tokensCache } = useGameContext();

const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = useInfiniteQuery(
["leaderboard", leaderboardId],
({ pageParam = 0 }) =>
fetchLeaderboardData(leaderboardId, entriesPerPage, pageParam, tokensCache),
{
getNextPageParam: (lastPage, pages) => {
if (lastPage.length < entriesPerPage) return undefined; // Check if there are more pages
return pages.length * entriesPerPage; // Calculate the offset for the next page
},
},
);

useEffect(() => {
const handleScroll = () => {
if (!listRef.current) {
return;
}
const current: HTMLDivElement = listRef.current;

const { scrollTop, scrollHeight, clientHeight } = current;
if (scrollTop + clientHeight >= scrollHeight - 30) {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}
};

const currentList = listRef.current;
if (currentList) {
currentList.addEventListener("scroll", handleScroll);
}

return () => {
if (currentList) {
currentList.removeEventListener("scroll", handleScroll);
}
};
}, [fetchNextPage, hasNextPage, isFetchingNextPage]);

if (status === "loading") return <Spinner color={"#8B8B8B"} />;
if (status === "error") return <p>Error :(</p>;

return (
<div className={styles.container}>
<LeaderboardHeader />
<div className={styles.divider} />
<div ref={listRef} className={styles.dataContainer}>
{data?.pages.map((page, i) => (
<div className={styles.pageContainer} key={i}>
{page.map((entry: LeaderboardEntry) => (
<LeaderboardItem entry={entry} key={entry.address + entry.id} />
))}
</div>
))}
{isFetchingNextPage && (
<p style={{ fontSize: "11px", textAlign: "center" }}>Loading more...</p>
)}
</div>
</div>
);
}

export default Leaderboard;
16 changes: 16 additions & 0 deletions web/src/components/leaderboard/LeaderboardHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import styles from "./Leaderboard.module.css";
import React from "react";

const LeaderboardHeader = () => {
return (
<div className={styles.tableHeader}>
<div className={styles.header}>Rank</div>
<div className={styles.header}>Character</div>
<div className={styles.header} style={{ textAlign: "end" }}>
Score
</div>
</div>
);
};

export default LeaderboardHeader;
34 changes: 34 additions & 0 deletions web/src/components/leaderboard/LeaderboardItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import styles from "./Leaderboard.module.css";
import { Image } from "@chakra-ui/react";
import React from "react";
import { LeaderboardEntry } from "./leaderboards";

const LeaderboardItem = ({ entry }: { entry: LeaderboardEntry }) => {
return (
<div key={entry.id} className={styles.tableContent}>
<div
className={`${entry.rank < 4 ? styles.firstRanks : styles.rank} ${
entry.rank === 1
? styles.rankFirst
: entry.rank === 2
? styles.rankSecond
: entry.rank === 3
? styles.rankThird
: ""
}`}
>
{entry.rank}
</div>
<div className={styles.characterContainer}>
<Image alt={""} className={styles.characterImage} src={entry.image} />
<div className={styles.characterInfo}>
<div className={styles.characterName}>{entry.name}</div>
<div className={styles.characterId}>{entry.id}</div>
</div>
</div>
<div className={styles.score}>{entry.score}</div>
</div>
);
};

export default LeaderboardItem;
Loading
Loading