diff --git a/web/src/app/globals.scss b/web/src/app/globals.scss index c14848c..9534a49 100644 --- a/web/src/app/globals.scss +++ b/web/src/app/globals.scss @@ -10,7 +10,7 @@ $form-label-fontsize: 1.3rem; :root { color-scheme: light dark; - --header-height: 60px; + --header-height: 45px; --page: white; --header-grad-left: #363b40; diff --git a/web/src/app/submissions/(styles)/page.scss b/web/src/app/submissions/(styles)/page.scss index 7e40626..bf98951 100644 --- a/web/src/app/submissions/(styles)/page.scss +++ b/web/src/app/submissions/(styles)/page.scss @@ -2,15 +2,6 @@ @use "../../globals.scss"; -.container { - display: flex; - justify-content: center; - align-items: center; - height: calc(100vh - var(--header-height)); - overflow: hidden; - position: relative; -} - a { color:rgb(255, 255, 255); @@ -21,4 +12,64 @@ a { &:active { color: rgb(192, 192, 192) } +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + grid-template-rows: repeat(3, 1fr); + gap: 16px; + max-width: 100%; + margin: 0 auto; + overflow-x: hidden; + box-sizing: border-box; +} + +@media (max-width: 768px) { + .grid { + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + } +} + +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + margin: 0.3rem; +} + +.pagination button { + padding: 0.25rem 0.5rem; + font-size: 1.15rem; + border: none; + border-radius: 0.35rem; + background-color: #33333350; + color: #fff; + cursor: pointer; +} + +.pagination button:disabled { + background-color: #5555559a; + cursor: not-allowed; +} + +.pagination-dots { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; + justify-content: center; + width: 100%; +} + +.dot { + width: 10px; + height: 10px; + border-radius: 50%; + background-color: #bbb; + cursor: pointer; +} + +.dot.active { + background-color: #333; } \ No newline at end of file diff --git a/web/src/app/submissions/(styles)/page/card.scss b/web/src/app/submissions/(styles)/page/card.scss index 9f37197..dce43eb 100644 --- a/web/src/app/submissions/(styles)/page/card.scss +++ b/web/src/app/submissions/(styles)/page/card.scss @@ -1,12 +1,87 @@ @use "../../../globals.scss"; .submissionCard { - @include globals.border-with-radius; - + display: flex; background-color: #2020207c; - border: 1px solid #501717; - border-radius: 6px; - padding: 6px; - text-align: center; - font-size: 14px; + border: 1px solid #97979783; + border-radius: 10px; + box-sizing: border-box; + flex-direction: column; + justify-content: space-between; + height: 100%; + min-width: 180px; + max-width: 340px; +} + +.content { + display: flex; + flex-grow: 1; + flex-direction: column; + justify-content: space-between; +} + +.details { + padding: 2px 4px; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin: 3px 0px; +} + +.footer { + display: flex; + justify-content: space-between; + align-items: center; + margin: 3px 0px; +} + +.map-image { + border-radius: 10px 10px 0 0; + overflow: hidden; + + > img { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.displayName { + font-size: 1rem; + font-weight: bold; + overflow: hidden; + max-width: 70%; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rating { + flex-shrink: 0; +} + +.author { + display: flex; + align-items: center; + gap: 0.5rem; + flex-grow: 1; + min-width: 0; +} + +.author span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.rating { + margin-left: auto; + flex-shrink: 0; +} + +.avatar { + border-radius: 50%; + object-fit: cover; } \ No newline at end of file diff --git a/web/src/app/submissions/[submissionId]/_comments.tsx b/web/src/app/submissions/[submissionId]/_comments.tsx index cc36c3e..463802f 100644 --- a/web/src/app/submissions/[submissionId]/_comments.tsx +++ b/web/src/app/submissions/[submissionId]/_comments.tsx @@ -9,7 +9,7 @@ interface CommentersProps { } interface CreatorAndReviewStatus { - asset_id: SubmissionInfo["AssetID"], + asset_id: SubmissionInfo["AssetID"], creator: SubmissionInfo["DisplayName"], review: SubmissionInfo["StatusID"], comments: Comment[], diff --git a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx index 23a8230..61aa1fa 100644 --- a/web/src/app/submissions/[submissionId]/_reviewButtons.tsx +++ b/web/src/app/submissions/[submissionId]/_reviewButtons.tsx @@ -1,5 +1,4 @@ import { Button, ButtonOwnProps } from "@mui/material"; -import { SubmissionInfo } from "@/app/ts/Submission"; type Actions = "Completed" | "Submit" | "Reject" | "Revoke" type Review = Actions | "Accept" | "Validate" | "Upload" @@ -12,6 +11,10 @@ interface ReviewButton { color: ButtonOwnProps["color"] } +interface ReviewId { + submissionId: string +} + function ReviewButtonClicked(action: Action, submissionId: string) { fetch(`/api/submissions/${submissionId}/status/${action}`, { method: "POST", @@ -28,8 +31,8 @@ function ReviewButton(props: ReviewButton) { onClick={() => { ReviewButtonClicked(props.action, props.submissionId) }}>{props.name} } -export default function ReviewButtons(props: SubmissionInfo) { - const submissionId = props.ID.toString() +export default function ReviewButtons(props: ReviewId) { + const submissionId = props.submissionId // When is each button visible? // Multiple buttons can be visible at once. // Action | Role | When Current Status is One of: diff --git a/web/src/app/submissions/[submissionId]/page.tsx b/web/src/app/submissions/[submissionId]/page.tsx index 93f0420..f5e7ffe 100644 --- a/web/src/app/submissions/[submissionId]/page.tsx +++ b/web/src/app/submissions/[submissionId]/page.tsx @@ -14,6 +14,10 @@ import { useState, useEffect } from "react"; import "./(styles)/page.scss"; +interface ReviewId { + submissionId: string +} + function Ratings() { return ( @@ -35,15 +39,14 @@ function Ratings() { ) } -function RatingArea(submission: SubmissionInfo) { +function RatingArea(submission: ReviewId) { return ( ) } @@ -91,7 +94,7 @@ export default function SubmissionInfoPage() {
- {RatingArea(submission)} +
diff --git a/web/src/app/submissions/_card.tsx b/web/src/app/submissions/_card.tsx index 34dbdf5..29a6793 100644 --- a/web/src/app/submissions/_card.tsx +++ b/web/src/app/submissions/_card.tsx @@ -1,6 +1,7 @@ import React from "react"; import Image from "next/image"; import Link from "next/link"; +import { Rating } from "@mui/material"; interface SubmissionCardProps { displayName: string; @@ -14,11 +15,26 @@ export default function SubmissionCard(props: SubmissionCardProps) { return (
- {/* TODO: Grab image of model */} - {props.displayName} -

{props.displayName}

-

By {props.author}

-

⭐ {props.rating}

{/* TODO: paste the star element from submission/1 page */} +
+
+ {/* TODO: Grab image of model */} + {props.displayName} +
+
+
+ {props.displayName} +
+ +
+
+
+
+ {props.author}/ + {props.author} +
+
+
+
); diff --git a/web/src/app/submissions/_loading.tsx b/web/src/app/submissions/_loading.tsx deleted file mode 100644 index f7a936d..0000000 --- a/web/src/app/submissions/_loading.tsx +++ /dev/null @@ -1,43 +0,0 @@ -//This can all be solved using 0 JavaScript, -//display: grid, ->1fr unit<- - -import React, { useState, useEffect } from 'react'; -import { Grid, Skeleton } from '@mui/material'; - -const elementWidth = 220; - -function calculateSkeletonCount(setState: React.Dispatch>) { - const viewportWidth = window.innerWidth - 100 * 2; - setState(Math.floor(viewportWidth / elementWidth) * 2); -}; - -function SkeletonGrid() { - const [skeletonCount, setSkeletonCount] = useState(0); - - useEffect(() => { - calculateSkeletonCount(setSkeletonCount); - window.addEventListener('resize', () => { calculateSkeletonCount(setSkeletonCount) }); - - return () => { - window.removeEventListener('resize', () => { calculateSkeletonCount(setSkeletonCount) }); - }; - }, []); - - return ( - - {Array.from({ length: skeletonCount }).map((_, index) => ( - - - - ))} - - ); -}; - -export default SkeletonGrid; diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index 074121a..154c368 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -1,33 +1,53 @@ 'use client' -import React, { useState, useEffect } from 'react' -import { SubmissionInfo } from '../ts/Submission'; -import { Grid2 as Grid } from '@mui/material'; +import React, { useState, useEffect } from "react"; +import { SubmissionInfo } from "../ts/Submission"; import SubmissionCard from "./_card"; -import SkeletonGrid from './_loading'; import Webpage from "@/app/_components/webpage"; import "./(styles)/page.scss"; export default function SubmissionInfoPage() { const [submissions, setSubmissions] = useState([]) + const [currentPage, setCurrentPage] = useState(0); + const cardsPerPage = 24; // built to fit on a 1920x1080 monitor - useEffect(() => { // needs to be client sided since server doesn't have a session, nextjs got mad at me for exporting an async function: (https://nextjs.org/docs/messages/no-async-client-component) + const totalPages = Math.ceil(submissions.length / cardsPerPage); + + const currentCards = submissions.slice( + currentPage * cardsPerPage, + (currentPage + 1) * cardsPerPage + ); + + const nextPage = () => { + if (currentPage < totalPages - 1) { + setCurrentPage(currentPage + 1); + } + }; + + const prevPage = () => { + if (currentPage > 0) { + setCurrentPage(currentPage - 1); + } + }; + + useEffect(() => { async function fetchSubmissions() { const res = await fetch('/api/submissions?Page=1&Limit=100') if (res.ok) { setSubmissions(await res.json()) } } - setTimeout(() => { // testing loading screen made by chatGerbertPT + + setTimeout(() => { fetchSubmissions() - }, 250); + }, 50); }, []) - if (!submissions) { + if (!submissions || submissions.length == 0) { return -
- +
+ Loading...
} @@ -35,43 +55,48 @@ export default function SubmissionInfoPage() { return ( // TODO: Add filter settings & searchbar & page selector -
- - {submissions.map((submission) => ( - - - +
+
+ {Array.from({ length: totalPages }).map((_, index) => ( + setCurrentPage(index)} + > ))} - +
+
+ + + Page {currentPage + 1} of {totalPages} + + +
+
+ {currentCards.map((submission) => ( + + ))} +
) - - // { - // "ID": 1, - // "DisplayName": "81bfc7a", - // "Creator": "79fbe8d", - // "GameID": 1073741824, - // "CreatedAt": 1734490019, - // "UpdatedAt": 1734565641, - // "Submitter": 1, - // "AssetID": 6438937102481093, - // "AssetVersion": 1225679040570074, - // "Completed": false, - // "SubmissionType": 0, - // "TargetAssetID": 1057095197389979, - // "StatusID": 4 - // } } \ No newline at end of file diff --git a/web/src/app/submit/_game.tsx b/web/src/app/submit/_game.tsx index 273470f..e754601 100644 --- a/web/src/app/submit/_game.tsx +++ b/web/src/app/submit/_game.tsx @@ -22,7 +22,6 @@ const BootstrapInput = styled(InputBase)(({ theme }) => ({ fontSize: 16, padding: '10px 26px 10px 12px', transition: theme.transitions.create(['border-color', 'box-shadow']), - // Use the system font instead of the default Roboto font. fontFamily: [ '-apple-system', 'BlinkMacSystemFont',