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}
-
-
-
-
-
-
-
-
-
- );
-}
+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.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,