From dcbb2df31962cf545f59c0e02799f87e7e2c8f04 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 16:03:09 -0400 Subject: [PATCH 01/14] Rework submission/mapfix list views --- web/src/app/_components/mapCard.tsx | 314 +++++++++++++++++++++++----- web/src/app/layout.tsx | 9 +- web/src/app/lib/theme.tsx | 91 ++++++++ web/src/app/mapfixes/page.tsx | 136 ++++++------ web/src/app/submissions/page.tsx | 121 ++++++----- web/src/app/ts/Mapfix.ts | 2 +- 6 files changed, 496 insertions(+), 177 deletions(-) create mode 100644 web/src/app/lib/theme.tsx diff --git a/web/src/app/_components/mapCard.tsx b/web/src/app/_components/mapCard.tsx index 539c08e..0683aa7 100644 --- a/web/src/app/_components/mapCard.tsx +++ b/web/src/app/_components/mapCard.tsx @@ -1,71 +1,269 @@ import React from "react"; -import Image from "next/image"; -import Link from "next/link"; -import { Rating } from "@mui/material"; +import {Avatar, Box, Card, CardActionArea, CardContent, CardMedia, Chip, Divider, Typography} from "@mui/material"; +import {Grid} from "@mui/system"; +import {Cancel, CheckCircle, Explore, Pending, Person2} from "@mui/icons-material"; -interface SubmissionCardProps { +interface MapCardProps { displayName: string; assetId: number; authorId: number; author: string; rating: number; id: number; + statusID: number; + gameID: number; + created: number; + type: 'mapfix' | 'submission'; } -export function SubmissionCard(props: SubmissionCardProps) { - return ( - -
-
-
- {/* TODO: Grab image of model */} - {props.displayName} -
-
-
- {props.displayName} -
- -
-
-
-
- {props.author}/ - {props.author} -
-
-
-
-
- - ); -} +const CARD_WIDTH = 270; -export function MapfixCard(props: SubmissionCardProps) { +export function MapCard(props: MapCardProps) { + const StatusChip = ({status}: { status: number }) => { + let color: 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning' = 'default'; + let icon: JSX.Element = ; + let label: string = 'Unknown'; + + switch (status) { + case 0: + color = 'info'; + icon = ; + label = 'Under Construction'; + break; + case 1: + color = 'warning'; + icon = ; + label = 'Changes Requested'; + break; + case 2: + color = 'info'; + icon = ; + label = 'Submitting'; + break; + case 3: + color = 'info'; + icon = ; + label = 'Submitted'; + break; + case 4: + color = 'warning'; + icon = ; + label = 'Accepted Unvalidated'; + break; + case 5: + color = 'info'; + icon = ; + label = 'Validating'; + break; + case 6: + color = 'success'; + icon = ; + label = 'Validated'; + break; + case 7: + color = 'info'; + icon = ; + label = 'Uploading'; + break; + case 8: + color = 'success'; + icon = ; + label = 'Uploaded'; + break; + case 9: + color = 'error'; + icon = ; + label = 'Rejected'; + break; + case 10: + color = 'success'; + icon = ; + label = 'Released'; + break; + default: + color = 'default'; + icon = ; + label = 'Unknown'; + break; + } + + return ( + + ); + }; return ( - -
-
-
- {/* TODO: Grab image of model */} - {props.displayName} -
-
-
- {props.displayName} -
- -
-
-
-
- {props.author}/ - {props.author} -
-
-
-
-
- - ); + + + + + + + + + + + + + + {props.displayName} + + + + + {props.gameID === 1 ? 'Bhop' : props.gameID === 2 ? 'Surf' : props.gameID === 3 ? 'Fly Trials' : props.gameID === 4 ? 'Deathrun' : 'Unknown'} + + + + + + {props.author} + + + + + + + + + {/*In the future author should be the username of the submitter not the info from the map*/} + {props.author} - {new Date(props.created * 1000).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + })} + + + + + + + + + ) } diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx index d524fd0..3fb5ba9 100644 --- a/web/src/app/layout.tsx +++ b/web/src/app/layout.tsx @@ -1,9 +1,16 @@ +'use client'; import "./globals.scss"; +import {theme} from "@/app/lib/theme"; +import {ThemeProvider} from "@mui/material"; export default function RootLayout({children}: Readonly<{children: React.ReactNode}>) { return ( - {children} + + + {children} + + ); } \ No newline at end of file diff --git a/web/src/app/lib/theme.tsx b/web/src/app/lib/theme.tsx new file mode 100644 index 0000000..07d46c7 --- /dev/null +++ b/web/src/app/lib/theme.tsx @@ -0,0 +1,91 @@ +import {createTheme} from "@mui/material"; + +export const theme = createTheme({ + palette: { + mode: 'dark', + primary: { + main: '#90caf9', + }, + secondary: { + main: '#f48fb1', + }, + background: { + default: '#121212', + paper: '#1e1e1e', + }, + }, + typography: { + fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', + h5: { + fontWeight: 500, + letterSpacing: '0.5px', + }, + subtitle1: { + fontWeight: 500, + fontSize: '0.95rem', + }, + body2: { + fontSize: '0.875rem', + }, + caption: { + fontSize: '0.75rem', + }, + }, + shape: { + borderRadius: 8, + }, + components: { + MuiCard: { + styleOverrides: { + root: { + borderRadius: 8, + overflow: 'hidden', + boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', + transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out', + '&:hover': { + transform: 'translateY(-4px)', + boxShadow: '0 8px 16px rgba(0, 0, 0, 0.2)', + }, + }, + }, + }, + MuiCardMedia: { + styleOverrides: { + root: { + borderBottom: '1px solid rgba(255, 255, 255, 0.1)', + }, + }, + }, + MuiCardContent: { + styleOverrides: { + root: { + padding: 16, + '&:last-child': { + paddingBottom: 16, + }, + }, + }, + }, + MuiChip: { + styleOverrides: { + root: { + fontWeight: 500, + }, + }, + }, + MuiDivider: { + styleOverrides: { + root: { + borderColor: 'rgba(255, 255, 255, 0.1)', + }, + }, + }, + MuiPaper: { + styleOverrides: { + root: { + backgroundImage: 'none', + }, + }, + }, + }, +}); \ No newline at end of file diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index e4674b6..a78ab3a 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -2,69 +2,65 @@ import { useState, useEffect } from "react"; import { MapfixList } from "../ts/Mapfix"; -import { MapfixCard } from "../_components/mapCard"; +import {MapCard} from "../_components/mapCard"; import Webpage from "@/app/_components/webpage"; // TODO: MAKE MAPFIX & SUBMISSIONS USE THE SAME COMPONENTS :angry: (currently too lazy) import "./(styles)/page.scss"; import { ListSortConstants } from "../ts/Sort"; +import {Box, Breadcrumbs, CircularProgress, Pagination, Typography} from "@mui/material"; +import Link from "next/link"; export default function MapfixInfoPage() { const [mapfixes, setMapfixes] = useState(null) const [currentPage, setCurrentPage] = useState(1); + const [isLoading, setIsLoading] = useState(false); const cardsPerPage = 24; // built to fit on a 1920x1080 monitor useEffect(() => { - async function fetchMapfixes() { - const res = await fetch(`/api/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`) + const controller = new AbortController(); + + async function fetchMapFixes() { + setIsLoading(true); + const res = await fetch(`/api/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`, { + signal: controller.signal, + }); if (res.ok) { - setMapfixes(await res.json()) + setMapfixes(await res.json()); } + setIsLoading(false); } - setTimeout(() => { - fetchMapfixes() - }, 50); - }, [currentPage]) + fetchMapFixes(); - if (!mapfixes) { + return () => controller.abort(); // Cleanup to avoid fetch conflicts on rapid page changes + }, [currentPage]); + + if (isLoading || !mapfixes) { return -
- Loading... +
+ + + + Loading submissions... + +
- + ; } const totalPages = Math.ceil(mapfixes.Total / cardsPerPage); - - const currentCards = mapfixes.Mapfixes.slice( - (currentPage - 1) * cardsPerPage, - currentPage * cardsPerPage - ); - - const nextPage = () => { - if (currentPage < totalPages) { - setCurrentPage(currentPage + 1); - } - }; - - const prevPage = () => { - if (currentPage > 1) { - setCurrentPage(currentPage - 1); - } - }; - - if (mapfixes.Total == 0) { - return -
- Mapfixes list is empty. -
-
- } + const currentCards = mapfixes.Mapfixes; return ( - // TODO: Add filter settings & searchbar & page selector
-
- {Array.from({ length: totalPages }).map((_, index) => ( - setCurrentPage(index+1)} - > - ))} -
-
- - - Page {currentPage} of {totalPages} - - -
-
- {currentCards.map((mapfix) => ( - + + Home + + Mapfixes + +
+ {currentCards.map((submission) => ( + ))}
+
+ setCurrentPage(page)} + variant="outlined" + shape="rounded" + /> +
) diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index bd67b33..b480c29 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -1,68 +1,72 @@ 'use client' -import { useState, useEffect } from "react"; -import { SubmissionList } from "../ts/Submission"; -import { SubmissionCard } from "../_components/mapCard"; +import {useState, useEffect} from "react"; +import {SubmissionList} from "../ts/Submission"; +import {MapCard} from "../_components/mapCard"; import Webpage from "@/app/_components/webpage"; import "./(styles)/page.scss"; -import { ListSortConstants } from "../ts/Sort"; +import {ListSortConstants} from "../ts/Sort"; +import {Breadcrumbs, Pagination, Typography, CircularProgress, Box} from "@mui/material"; +import Link from "next/link"; export default function SubmissionInfoPage() { - const [submissions, setSubmissions] = useState(null) + const [submissions, setSubmissions] = useState(null); const [currentPage, setCurrentPage] = useState(1); + const [isLoading, setIsLoading] = useState(false); const cardsPerPage = 24; // built to fit on a 1920x1080 monitor useEffect(() => { + const controller = new AbortController(); + async function fetchSubmissions() { - const res = await fetch(`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`) + setIsLoading(true); + const res = await fetch(`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`, { + signal: controller.signal, + }); if (res.ok) { - setSubmissions(await res.json()) + setSubmissions(await res.json()); } + setIsLoading(false); } - setTimeout(() => { - fetchSubmissions() - }, 50); - }, [currentPage]) + fetchSubmissions(); - if (!submissions) { + return () => controller.abort(); // Cleanup to avoid fetch conflicts on rapid page changes + }, [currentPage]); + + if (isLoading || !submissions) { return -
- Loading... +
+ + + + Loading submissions... + +
- + ; } const totalPages = Math.ceil(submissions.Total / cardsPerPage); + const currentCards = submissions.Submissions; - const currentCards = submissions.Submissions.slice( - (currentPage - 1) * cardsPerPage, - currentPage * cardsPerPage - ); - - const nextPage = () => { - if (currentPage < totalPages) { - setCurrentPage(currentPage + 1); - } - }; - - const prevPage = () => { - if (currentPage > 1) { - setCurrentPage(currentPage - 1); - } - }; - - if (submissions.Total == 0) { + if (submissions.Total === 0) { return
Submissions list is empty.
-
+ ; } return ( - // TODO: Add filter settings & searchbar & page selector
-
- {Array.from({ length: totalPages }).map((_, index) => ( - setCurrentPage(index+1)} - > - ))} -
-
- - - Page {currentPage} of {totalPages} - - -
-
+ + + Home + + Submissions + +
{currentCards.map((submission) => ( - ))}
+
+ setCurrentPage(page)} + variant="outlined" + shape="rounded" + /> +
) diff --git a/web/src/app/ts/Mapfix.ts b/web/src/app/ts/Mapfix.ts index c4a9bce..a09ed2b 100644 --- a/web/src/app/ts/Mapfix.ts +++ b/web/src/app/ts/Mapfix.ts @@ -17,7 +17,7 @@ interface MapfixInfo { readonly DisplayName: string, readonly Creator: string, readonly GameID: number, - readonly Date: number, + readonly CreatedAt: number, readonly Submitter: number, readonly AssetID: number, readonly AssetVersion: number, -- 2.49.1 From 33036c368545600e4743bbfb6e1bc3ef6d9bcda8 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 16:08:06 -0400 Subject: [PATCH 02/14] Fix missing creation date in submissions --- web/src/app/submissions/page.tsx | 1 + web/src/app/ts/Submission.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index b480c29..e7b9619 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -108,6 +108,7 @@ export default function SubmissionInfoPage() { rating={submission.StatusID} statusID={submission.StatusID} gameID={submission.GameID} + created={submission.CreatedAt} type="submission" /> ))} diff --git a/web/src/app/ts/Submission.ts b/web/src/app/ts/Submission.ts index 4904726..89d9cd8 100644 --- a/web/src/app/ts/Submission.ts +++ b/web/src/app/ts/Submission.ts @@ -17,7 +17,7 @@ interface SubmissionInfo { readonly DisplayName: string, readonly Creator: string, readonly GameID: number, - readonly Date: number, + readonly CreatedAt: number, readonly Submitter: number, readonly AssetID: number, readonly AssetVersion: number, -- 2.49.1 From 197e75c8c6e4fe31cb28f4c97e93daac4a999c3e Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 16:13:34 -0400 Subject: [PATCH 03/14] Fix wrong term in loading spinner... --- web/src/app/mapfixes/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index a78ab3a..5b2bd4d 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -50,7 +50,7 @@ export default function MapfixInfoPage() { - Loading submissions... + Loading mapfixes...
-- 2.49.1 From fb82dbd0ea6702a8c39f9391d900baced251cc07 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 16:17:59 -0400 Subject: [PATCH 04/14] Add missing import --- web/src/app/_components/mapCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/app/_components/mapCard.tsx b/web/src/app/_components/mapCard.tsx index 0683aa7..39b462b 100644 --- a/web/src/app/_components/mapCard.tsx +++ b/web/src/app/_components/mapCard.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, {JSX} from "react"; import {Avatar, Box, Card, CardActionArea, CardContent, CardMedia, Chip, Divider, Typography} from "@mui/material"; import {Grid} from "@mui/system"; import {Cancel, CheckCircle, Explore, Pending, Person2} from "@mui/icons-material"; -- 2.49.1 From 8b184b1e8aa2200dfa30f3c04ec083100e899f1e Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 16:23:55 -0400 Subject: [PATCH 05/14] Fix import so we're using the wonderfully deprecated Grid --- web/src/app/_components/mapCard.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/app/_components/mapCard.tsx b/web/src/app/_components/mapCard.tsx index 39b462b..42dd0a3 100644 --- a/web/src/app/_components/mapCard.tsx +++ b/web/src/app/_components/mapCard.tsx @@ -1,6 +1,5 @@ import React, {JSX} from "react"; -import {Avatar, Box, Card, CardActionArea, CardContent, CardMedia, Chip, Divider, Typography} from "@mui/material"; -import {Grid} from "@mui/system"; +import {Avatar, Box, Card, CardActionArea, CardContent, CardMedia, Chip, Divider, Grid, Typography} from "@mui/material"; import {Cancel, CheckCircle, Explore, Pending, Person2} from "@mui/icons-material"; interface MapCardProps { -- 2.49.1 From 3d5da108b4f4a71847f471a359de499702873eed Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 18:13:56 -0400 Subject: [PATCH 06/14] Refactor navbar --- web/src/app/_components/header.tsx | 162 ++++++++++++++++++++--------- 1 file changed, 114 insertions(+), 48 deletions(-) diff --git a/web/src/app/_components/header.tsx b/web/src/app/_components/header.tsx index b611533..7dab4df 100644 --- a/web/src/app/_components/header.tsx +++ b/web/src/app/_components/header.tsx @@ -3,62 +3,128 @@ import Link from "next/link" import Image from "next/image"; -import "./styles/header.scss" -import { UserInfo } from "@/app/ts/User"; -import { useState, useEffect } from "react"; +import {UserInfo} from "@/app/ts/User"; +import {useState, useEffect} from "react"; + +import AppBar from "@mui/material/AppBar"; +import Toolbar from "@mui/material/Toolbar"; +import Button from "@mui/material/Button"; +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; interface HeaderButton { - name: string, - href: string + name: string; + href: string; } + function HeaderButton(header: HeaderButton) { - return ( - - - - ) + return ( + + ); } export default function Header() { - const handleLoginClick = () => { - window.location.href = "/auth/oauth2/login?redirect=" + window.location.href; - }; + const handleLoginClick = () => { + window.location.href = + "/auth/oauth2/login?redirect=" + window.location.href; + }; - const [valid, setValid] = useState(false) - const [user, setUser] = useState(null) + const [valid, setValid] = useState(false); + const [user, setUser] = useState(null); + const [anchorEl, setAnchorEl] = useState(null); - useEffect(() => { - async function getLoginInfo() { - const [validateData, userData] = await Promise.all([ - fetch("/api/session/validate").then(validateResponse => validateResponse.json()), - fetch("/api/session/user").then(userResponse => userResponse.json()) - ]); - setValid(validateData) - setUser(userData) - } - getLoginInfo() - }, []) + const handleMenuOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; - return ( -
- - -
- ) + const handleMenuClose = () => { + setAnchorEl(null); + }; + + useEffect(() => { + async function getLoginInfo() { + try { + const response = await fetch("/api/session/user"); + + if (!response.ok) { + setValid(false); + setUser(null); + return; + } + + const userData = await response.json(); + const isLoggedIn = userData && 'UserID' in userData; + + setValid(isLoggedIn); + setUser(isLoggedIn ? userData : null); + } catch (error) { + console.error("Error fetching user data:", error); + setValid(false); + setUser(null); + } + } + + getLoginInfo(); + }, []); + + return ( + + + + + + + + + + {valid && user ? ( + + + + + Manage + + + + ) : ( + + )} + + + + ); } -- 2.49.1 From 9ce21399f75603a8f55cfcbd39f220097e3abea4 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 18:21:07 -0400 Subject: [PATCH 07/14] Make submit button more obvious --- web/src/app/_components/header.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/app/_components/header.tsx b/web/src/app/_components/header.tsx index 7dab4df..edc2d7e 100644 --- a/web/src/app/_components/header.tsx +++ b/web/src/app/_components/header.tsx @@ -80,7 +80,9 @@ export default function Header() { - + {valid && user ? (
+
) } diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index e7b9619..c81e090 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -7,7 +7,7 @@ import Webpage from "@/app/_components/webpage"; import "./(styles)/page.scss"; import {ListSortConstants} from "../ts/Sort"; -import {Breadcrumbs, Pagination, Typography, CircularProgress, Box} from "@mui/material"; +import {Breadcrumbs, Pagination, Typography, CircularProgress, Box, Container} from "@mui/material"; import Link from "next/link"; export default function SubmissionInfoPage() { @@ -68,61 +68,70 @@ export default function SubmissionInfoPage() { return ( -
- - - Home - - Submissions - -
+
- {currentCards.map((submission) => ( - - ))} -
-
- setCurrentPage(page)} - variant="outlined" - shape="rounded" - /> -
-
+ + + Home + + Submissions + + + Submissions + + + Explore all submitted maps from the community. + +
+ {currentCards.map((submission) => ( + + ))} +
+ +
+ setCurrentPage(page)} + variant="outlined" + shape="rounded" + /> +
+
+ +
) } -- 2.49.1 From 08bf4412c847f5cf41be8bec8c08e7d985fbe759 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 19:34:02 -0400 Subject: [PATCH 09/14] Rework map list page --- web/src/app/maps/page.tsx | 330 ++++++++++++++++++++++++++++++++------ 1 file changed, 284 insertions(+), 46 deletions(-) diff --git a/web/src/app/maps/page.tsx b/web/src/app/maps/page.tsx index 7e62df3..4cc9ce7 100644 --- a/web/src/app/maps/page.tsx +++ b/web/src/app/maps/page.tsx @@ -1,60 +1,298 @@ "use client"; +import {useState, useEffect} from "react"; import Image from "next/image"; -import { useState, useEffect } from "react"; +import {useRouter} from "next/navigation"; import Webpage from "@/app/_components/webpage"; - -import "./(styles)/page.scss"; +import { + Box, + Container, + Typography, + Grid, + Card, + CardContent, + CardMedia, + CardActionArea, + TextField, + InputAdornment, + Pagination, + CircularProgress, + FormControl, + InputLabel, + Select, + MenuItem, + SelectChangeEvent, Breadcrumbs +} from "@mui/material"; +import {Search as SearchIcon} from "@mui/icons-material"; +import Link from "next/link"; interface Map { - ID: number; - DisplayName: string; - Creator: string; - GameID: number; - Date: number; + ID: number; + DisplayName: string; + Creator: string; + GameID: number; + Date: number; } -// TODO: should rewrite this entire page, just wanted to get a simple page working. This was written by chatgippity - export default function MapsPage() { - const [maps, setMaps] = useState([]); + const router = useRouter(); + const [maps, setMaps] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(""); + const [currentPage, setCurrentPage] = useState(1); + const [gameFilter, setGameFilter] = useState("0"); // 0 means "All Maps" + const mapsPerPage = 12; + const requestPageSize = 100; - useEffect(() => { - const fetchMaps = async () => { - const res = await fetch("/api/maps?Page=1&Limit=100"); - const data: Map[] = await res.json(); - setMaps(data); - }; + useEffect(() => { + const fetchMaps = async () => { + // Just send it and load all maps hoping for the best + try { + setLoading(true); + let allMaps: Map[] = []; + let page = 1; + let hasMore = true; - fetchMaps(); - }, []); + while (hasMore) { + const res = await fetch(`/api/maps?Page=${page}&Limit=${requestPageSize}`); + const data: Map[] = await res.json(); + allMaps = [...allMaps, ...data]; + hasMore = data.length === requestPageSize; + page++; + } - const customLoader = ({ src }: { src: string }) => { - return src; - }; + setMaps(allMaps); + } catch (error) { + console.error("Failed to fetch maps:", error); + } finally { + setLoading(false); + } + }; - return ( - -
- {maps.map((map) => ( - - ))} -
-
- ); + fetchMaps(); + }, []); + + const handleGameFilterChange = (event: SelectChangeEvent) => { + setGameFilter(event.target.value); + setCurrentPage(1); + }; + + // Filter maps based on search query and game filter + const filteredMaps = maps.filter(map => { + const matchesSearch = + map.DisplayName.toLowerCase().includes(searchQuery.toLowerCase()) || + map.Creator.toLowerCase().includes(searchQuery.toLowerCase()); + + const matchesGameFilter = + gameFilter === "0" || // "All Maps" + map.GameID === parseInt(gameFilter); + + return matchesSearch && matchesGameFilter; + }); + + // Calculate pagination + const totalPages = Math.ceil(filteredMaps.length / mapsPerPage); + const currentMaps = filteredMaps.slice( + (currentPage - 1) * mapsPerPage, + currentPage * mapsPerPage + ); + + const handlePageChange = (_event: React.ChangeEvent, page: number) => { + setCurrentPage(page); + window.scrollTo({top: 0, behavior: 'smooth'}); + }; + + const handleMapClick = (mapId: number) => { + router.push(`/maps/${mapId}`); + }; + + const formatDate = (timestamp: number) => { + return new Date(timestamp * 1000).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + }; + + const getGameName = (gameId: number) => { + switch (gameId) { + case 1: + return "Bhop"; + case 2: + return "Surf"; + case 3: + return "Fly Trials"; + default: + return "Unknown"; + } + }; + + const getGameLabelStyles = (gameId: number) => { + switch (gameId) { + case 1: // Bhop + return { + bgcolor: "info.main", + color: "white", + }; + case 2: // Surf + return { + bgcolor: "success.main", + color: "white", + }; + case 3: // Fly Trials + return { + bgcolor: "warning.main", + color: "white", + }; + default: // Unknown + return { + bgcolor: "grey.500", + color: "white", + }; + } + }; + + return ( + + + + + + Home + + Maps + + + Map Collection + + + Browse all community-created maps or find your favorites + + { + setSearchQuery(e.target.value); + setCurrentPage(1); + }} + InputProps={{ + startAdornment: ( + + + + ), + }} + sx={{mb: 4}} + /> + + {loading ? ( + + + + ) : ( + <> + + + Showing {filteredMaps.length} {filteredMaps.length === 1 ? 'map' : 'maps'} + + + + Filter by Game + + + + + + {currentMaps.map((map) => ( + + + handleMapClick(map.ID)}> + + + {getGameName(map.GameID)} + + {map.DisplayName} + + + + {map.DisplayName} + + + By {map.Creator} + + + Added {formatDate(map.Date)} + + + + + + ))} + + + {totalPages > 1 && ( + + + + )} + + )} + + + + ); } \ No newline at end of file -- 2.49.1 From 188fd94e229a566986d6410c9d670de58d083936 Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 20:54:03 -0400 Subject: [PATCH 10/14] Update state color scheme --- web/src/app/_components/mapCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/app/_components/mapCard.tsx b/web/src/app/_components/mapCard.tsx index 42dd0a3..515ca09 100644 --- a/web/src/app/_components/mapCard.tsx +++ b/web/src/app/_components/mapCard.tsx @@ -25,7 +25,7 @@ export function MapCard(props: MapCardProps) { switch (status) { case 0: - color = 'info'; + color = 'warning'; icon = ; label = 'Under Construction'; break; @@ -40,7 +40,7 @@ export function MapCard(props: MapCardProps) { label = 'Submitting'; break; case 3: - color = 'info'; + color = 'warning'; icon = ; label = 'Submitted'; break; -- 2.49.1 From 5d0818d1c6a16a2b4823077582dc554ee3a74d4e Mon Sep 17 00:00:00 2001 From: itzaname Date: Sat, 7 Jun 2025 21:49:40 -0400 Subject: [PATCH 11/14] Hide submit button if not logged in --- web/src/app/_components/header.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/src/app/_components/header.tsx b/web/src/app/_components/header.tsx index edc2d7e..463d62e 100644 --- a/web/src/app/_components/header.tsx +++ b/web/src/app/_components/header.tsx @@ -80,9 +80,11 @@ export default function Header() { - + {valid && user && ( + + )} {valid && user ? (