Huge mess
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
ic3w0lf 2024-12-18 22:09:08 -07:00
parent 02d77ab421
commit 9bd3eb69f9
15 changed files with 399 additions and 55 deletions

View File

@ -6,11 +6,24 @@ const nextConfig: NextConfig = {
rewrites: async () => { rewrites: async () => {
return [ return [
{ {
source: "/v1/submissions/:submissionid/status/:statustype", source: "/api/:path*",
destination: "http://submissions:8082/v1/submissions/:submissionid/status/:statustype" destination: "http://localhost:8082/v1/:path*",
// source: "/v1/submissions/:submissionid/status/:statustype",
// destination: "http://submissions:8082/v1/submissions/:submissionid/status/:statustype"
} }
] ]
} },
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'api.ic3.space',
pathname: '/strafe/map-images/**',
port: '',
search: '',
},
],
},
}; };
export default nextConfig; export default nextConfig;

View File

@ -18,10 +18,10 @@ export default function Header() {
return ( return (
<header className="header-bar"> <header className="header-bar">
<nav className="left"> <nav className="left">
<HeaderButton name="Maps" href=""/> <HeaderButton name="Submissions" href="/submissions"/>
</nav> </nav>
<nav className="right"> <nav className="right">
<HeaderButton name="Home" href=""/> <HeaderButton name="Submit" href="/submit"/>
<HeaderButton name="Need" href=""/> <HeaderButton name="Need" href=""/>
<HeaderButton name="Menu" href=""/> <HeaderButton name="Menu" href=""/>
<HeaderButton name="Items" href=""/> <HeaderButton name="Items" href=""/>

View File

@ -2,8 +2,8 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
width: 100vw; width: 100%;
height: 60px; height: var(--header-height);
background: var(--header-grad-left); background: var(--header-grad-left);
background: linear-gradient(180deg, var(--header-grad-left) 0%, var(--header-grad-right) 100%); background: linear-gradient(180deg, var(--header-grad-left) 0%, var(--header-grad-right) 100%);
@ -24,7 +24,7 @@
.right { .right {
display: flex; display: flex;
gap: 7px; gap: 7px;
margin-right: 50px; margin-right: 35px;
button { button {
font-size: 1rem; font-size: 1rem;

View File

@ -10,6 +10,8 @@ $form-label-fontsize: 1.3rem;
:root { :root {
color-scheme: light dark; color-scheme: light dark;
--header-height: 60px;
--page: white; --page: white;
--header-grad-left: #363b40; --header-grad-left: #363b40;
--header-grad-right: #353a40; --header-grad-right: #353a40;

View File

@ -0,0 +1,19 @@
@forward "./page/card.scss";
@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) }
a:visited { text-decoration: none; color:rgb(255, 255, 255); }
a:hover { text-decoration: none; color:rgb(255, 255, 255); }
a:focus { text-decoration: none; color:rgb(255, 255, 255); }
a:hover, a:active { text-decoration: none; color:rgb(192, 192, 192) }

View File

@ -0,0 +1,12 @@
@use "../../../globals.scss";
.submissionCard {
@include globals.border-with-radius;
background-color: #2020207c;
border: 1px solid #501717;
border-radius: 6px;
padding: 6px;
text-align: center;
font-size: 14px;
}

View File

@ -1,4 +1,5 @@
import { Button, ButtonOwnProps } from "@mui/material"; import { Button, ButtonOwnProps } from "@mui/material";
import { SubmissionInfo } from "@/app/ts/Submission";
type Review = "Completed" | "Submit" | "Reject" | "Revoke" | "Accept" | "Validate" | "Upload" type Review = "Completed" | "Submit" | "Reject" | "Revoke" | "Accept" | "Validate" | "Upload"
type Action = "completed" | "submit" | "reject" | "revoke" | "trigger-validate" | "trigger-upload" type Action = "completed" | "submit" | "reject" | "revoke" | "trigger-validate" | "trigger-upload"
@ -9,12 +10,8 @@ interface ReviewButton {
color: ButtonOwnProps["color"] color: ButtonOwnProps["color"]
} }
interface ReviewId {
submissionId: string
}
function ReviewButtonClicked(action: Action, submissionId: string) { function ReviewButtonClicked(action: Action, submissionId: string) {
fetch(`http://localhost:3000/v1/submissions/${submissionId}/status/${action}`, { fetch(`/api/submissions/${submissionId}/status/${action}`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-type": "application/json", "Content-type": "application/json",
@ -29,16 +26,16 @@ function ReviewButton(props: ReviewButton) {
onClick={() => { ReviewButtonClicked(props.action, props.submissionId) }}>{props.name}</Button> onClick={() => { ReviewButtonClicked(props.action, props.submissionId) }}>{props.name}</Button>
} }
export default function ReviewButtons(props: ReviewId) { export default function ReviewButtons(props: SubmissionInfo) {
return ( return (
<section className="review-set"> <section className="review-set">
<ReviewButton color="info" name="Submit" action="submit" submissionId={props.submissionId}/> <ReviewButton color="info" name="Submit" action="submit" submissionId={props.ID}/>
<ReviewButton color="info" name="Revoke" action="revoke" submissionId={props.submissionId}/> <ReviewButton color="info" name="Revoke" action="revoke" submissionId={props.ID}/>
<ReviewButton color="info" name="Accept" action="trigger-validate" submissionId={props.submissionId}/> <ReviewButton color="info" name="Accept" action="trigger-validate" submissionId={props.ID}/>
<ReviewButton color="info" name="Validate" action="trigger-validate" submissionId={props.submissionId}/> <ReviewButton color="info" name="Validate" action="trigger-validate" submissionId={props.ID}/>
<ReviewButton color="error" name="Reject" action="reject" submissionId={props.submissionId}/> <ReviewButton color="error" name="Reject" action="reject" submissionId={props.ID}/>
<ReviewButton color="info" name="Upload" action="trigger-upload" submissionId={props.submissionId}/> <ReviewButton color="info" name="Upload" action="trigger-upload" submissionId={props.ID}/>
<ReviewButton color="info" name="Completed" action="completed" submissionId={props.submissionId}/> <ReviewButton color="info" name="Completed" action="completed" submissionId={props.ID}/>
</section> </section>
) )
} }

View File

@ -1,6 +1,6 @@
"use client" "use client"
import { SubmissionStatus, SubmissionStatusToString } from "@/app/ts/Submission"; import { SubmissionInfo, SubmissionStatus, SubmissionStatusToString } from "@/app/ts/Submission";
import type { CreatorAndReviewStatus } from "./_comments"; import type { CreatorAndReviewStatus } from "./_comments";
import { MapImage } from "./_map"; import { MapImage } from "./_map";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
@ -10,13 +10,10 @@ import Comments from "./_comments";
import Webpage from "@/app/_components/webpage"; import Webpage from "@/app/_components/webpage";
import Window from "./_window"; import Window from "./_window";
import Link from "next/link"; import Link from "next/link";
import { useState, useEffect } from "react";
import "./(styles)/page.scss"; import "./(styles)/page.scss";
interface ReviewId {
submissionId: string
}
function Ratings() { function Ratings() {
return ( return (
<Window className="rating-window" title="Rating"> <Window className="rating-window" title="Rating">
@ -38,14 +35,15 @@ function Ratings() {
) )
} }
function RatingArea(submission: ReviewId) { function RatingArea(submission: SubmissionInfo) {
return ( return (
<aside className="review-area"> <aside className="review-area">
<section className="map-image-area"> <section className="map-image-area">
<MapImage/> <MapImage/>
</section> </section>
<Ratings/> <Ratings/>
<ReviewButtons submissionId={submission.submissionId}/> {ReviewButtons(submission)}
{/* <ReviewButtons submissionId={submission.ID}/> */}
</aside> </aside>
) )
} }
@ -57,6 +55,7 @@ function TitleAndComments(stats: CreatorAndReviewStatus) {
<main className="review-info"> <main className="review-info">
<div> <div>
<h1>{stats.name}</h1> <h1>{stats.name}</h1>
{/* TODO: Fix review status, I think its displaying the wrong information idk */}
<aside data-review-status={stats.review} className="review-status"> <aside data-review-status={stats.review} className="review-status">
<p>{Review}</p> <p>{Review}</p>
</aside> </aside>
@ -71,12 +70,29 @@ function TitleAndComments(stats: CreatorAndReviewStatus) {
export default function SubmissionInfoPage() { export default function SubmissionInfoPage() {
const dynamicId = useParams<{submissionId: string}>() const dynamicId = useParams<{submissionId: string}>()
const [submission, setSubmission] = useState(null)
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)
async function getSubmission() {
const res = await fetch(`/api/submissions/${dynamicId.submissionId}`)
const data = await res.json()
setSubmission(data)
}
getSubmission()
}, [])
if (!submission) return (
<Webpage>
{/* TODO: Add skeleton loading thingy ? Maybe ? (https://mui.com/material-ui/react-skeleton/) */}
</Webpage>
)
return ( return (
<Webpage> <Webpage>
<main className="map-page-main"> <main className="map-page-main">
<section className="review-section"> <section className="review-section">
<RatingArea submissionId={dynamicId.submissionId}/> {RatingArea(submission)}
<TitleAndComments name={dynamicId.submissionId} creator="Quaternions" review={SubmissionStatus.Accepted} comments={[]}/> <TitleAndComments name={submission.DisplayName} creator={submission.Creator} review={submission.StatusID} comments={[]}/>
</section> </section>
</main> </main>
</Webpage> </Webpage>

View File

@ -0,0 +1,17 @@
import React from "react";
import Image from "next/image";
import Link from "next/link";
export default function SubmissionCard({ id, displayName, author, rating }) {
return (
<Link href={`/submissions/${id}`}>
<div className="submissionCard">
{/* TODO: Grab image of model */}
<Image height={200} width={200} priority={true} src="https://api.ic3.space/strafe/map-images/11222350808" style={{ width: `100%` }} alt={displayName} />
<h3>{displayName}</h3>
<p>By {author}</p>
<p> {rating}</p> {/* TODO: paste the star element from submission/1 page */}
</div>
</Link>
);
}

View File

@ -0,0 +1,40 @@
import React, { useState, useEffect } from 'react';
import { Grid, Skeleton } from '@mui/material';
const SkeletonGrid = () => {
const [skeletonCount, setSkeletonCount] = useState(0);
const calculateSkeletonCount = () => {
const viewportWidth = window.innerWidth - 100 * 2;
const elementWidth = 220;
const count = Math.floor(viewportWidth / elementWidth);
setSkeletonCount(count * 2);
};
useEffect(() => {
calculateSkeletonCount();
window.addEventListener('resize', calculateSkeletonCount);
return () => {
window.removeEventListener('resize', calculateSkeletonCount);
};
}, []);
return (
<Grid
container
spacing={2}
alignItems="center"
justifyContent="center"
style={{ maxWidth: 'calc(100vw - 100px)', margin: '0 auto' }}
>
{Array.from({ length: skeletonCount }).map((_, index) => (
<Grid item key={index}>
<Skeleton variant="rectangular" width={215} height={340} />
</Grid>
))}
</Grid>
);
};
export default SkeletonGrid;

View File

@ -0,0 +1,20 @@
interface WindowStruct {
className: string,
title: string,
children: React.ReactNode
}
export default function Window(window: WindowStruct) {
return (
<section className={window.className}>
<header>
<p>{window.title}</p>
</header>
<main>{window.children}</main>
</section>
)
}
export {
type WindowStruct
}

View File

@ -0,0 +1,75 @@
'use client'
import React, { useState, useEffect } from 'react'
import Webpage from "@/app/_components/webpage";
import { Grid2 as Grid } from '@mui/material';
import SubmissionCard from "./_card";
import SkeletonGrid from './_loading';
import "./(styles)/page.scss";
export default function SubmissionInfoPage() {
const [submissions, setSubmissions] = useState(null)
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)
async function fetchSubmissions() {
const res = await fetch('/api/submissions?Page=1&Limit=100')
const data = await res.json()
setSubmissions(data)
}
setTimeout(() => { // testing loading screen made by chatGerbertPT
fetchSubmissions()
}, 250);
}, [])
if (!submissions) return (
<Webpage>
<main style={{ display: 'flex', justifyContent: 'center', padding: '1rem' }}>
<SkeletonGrid />
</main>
</Webpage>
)
return (
// TODO: Add filter settings & searchbar & page selector
<Webpage>
<main style={{ display: 'flex', justifyContent: 'center', padding: '1rem' }}>
<Grid
container
spacing={2}
alignItems="center"
justifyContent="center"
style={{ maxWidth: 'calc(100vw - 100px)', margin: '0 auto' }}
>
{submissions.map((submission) => (
<Grid key={submission.ID}>
<SubmissionCard
id={submission.ID}
assetId={submission.AssetId}
displayName={submission.DisplayName}
author={submission.Creator}
rating={submission.StatusID}
/>
</Grid>
))}
</Grid>
</main>
</Webpage>
)
// {
// "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
// }
}

View File

@ -25,7 +25,6 @@
} }
& fieldset { & fieldset {
border-color: rgb(100,100,100); border-color: rgb(100,100,100);
} }
& span { & span {
color: white; color: white;

View File

@ -0,0 +1,65 @@
import { FormControl, Select, InputLabel, MenuItem } from "@mui/material";
import { styled } from '@mui/material/styles';
import InputBase from '@mui/material/InputBase';
import React from "react";
// TODO: Properly style everything instead of pasting 🤚
type GameSelectionProps = {
game: string;
setGame: React.Dispatch<React.SetStateAction<string>>;
};
const BootstrapInput = styled(InputBase)(({ theme }) => ({
'label + &': {
marginTop: theme.spacing(3),
},
'& .MuiInputBase-input': {
backgroundColor: '#0000',
color: '#FFF',
border: '1px solid rgba(175, 175, 175, 0.66)',
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',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
'&:focus': {
borderRadius: 4,
borderColor: '#80bdff',
boxShadow: '0 0 0 0.2rem rgba(0,123,255,.25)',
},
},
}));
export default function GameSelection({ game, setGame }: GameSelectionProps) {
const handleChange = (event: SelectChangeEvent) => {
setGame(event.target.value);
};
return (
<FormControl>
<InputLabel sx={{ color: "#646464" }}>Game</InputLabel>
<Select
value={game}
label="Game"
onChange={handleChange}
input={<BootstrapInput />}
>
<MenuItem value={1}>Bhop</MenuItem>
<MenuItem value={2}>Surf</MenuItem>
<MenuItem value={3}>Fly Trials</MenuItem>
</Select>
</FormControl>
);
}

View File

@ -1,12 +1,25 @@
"use client" "use client"
import { FormControl, FormLabel, RadioGroup, FormControlLabel, Button, TextField } from "@mui/material" import { FormControl, FormLabel, RadioGroup, FormControlLabel, Button, TextField, Checkbox } from "@mui/material"
import GameSelection from "./_game";
import SendIcon from '@mui/icons-material/Send'; import SendIcon from '@mui/icons-material/Send';
import Webpage from "@/app/_components/webpage" import Webpage from "@/app/_components/webpage"
import Radio from '@mui/material/Radio'; import Radio from '@mui/material/Radio';
import React, { useState } from "react";
import "./(styles)/page.scss" import "./(styles)/page.scss"
interface SubmissionPayload {
DisplayName: string;
Creator: string;
GameID: number;
AssetID: number;
AssetVersion: number;
Submitter: number;
SubmissionType: number;
}
const enum Map { const enum Map {
New, New,
Fix, Fix,
@ -23,28 +36,84 @@ function TargetAsset() {
} }
export default function SubmissionInfoPage() { export default function SubmissionInfoPage() {
return ( const [game, setGame] = useState(1);
<Webpage> const [isFixingMap, setIsFixingMap] = useState(false);
<main>
<header> const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
<h1>Submit Asset</h1> event.preventDefault();
<span className="spacer form-spacer"></span>
</header> const form = event.currentTarget;
<form> const formData = new FormData(form);
<TextField className="form-field" id="display-name" label="Display Name" variant="outlined"/> const data = {
<TextField className="form-field" id="creator" label="Creator" variant="outlined"/> displayName: formData.get("display-name"),
<TextField className="form-field" id="game-id" label="Game ID" variant="outlined"/> creator: formData.get("creator"),
<TextField className="form-field" id="asset-id" label="Asset ID" variant="outlined"/> assetId: formData.get("asset-id"),
<TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> game,
<TargetAsset/> isFixingMap,
</form> };
<span className="spacer form-spacer"></span>
<Button variant="contained" startIcon={<SendIcon/>} sx={{ console.log(data)
width: "400px",
height: "50px", const payload: SubmissionPayload = {
marginInline: "auto" DisplayName: data.displayName,
}}>Submit</Button> Creator: data.creator,
</main> GameID: 1073741824, // TODO: Change this!!
</Webpage> AssetID: Number(data.assetId),
AssetVersion: 0,
SubmissionType: 1,
};
console.log(payload)
console.log(JSON.stringify(payload))
try {
// Send the POST request
const response = await fetch("/api/submissions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
// Allow any HTTP status
const responseText = await response.text();
console.log("Response Text:", responseText);
} catch (error) {
console.error("Error submitting data:", error);
}
};
return (
<Webpage>
<main>
<header>
<h1>Submit Asset</h1>
<span className="spacer form-spacer"></span>
</header>
<form onSubmit={handleSubmit}>
{/* TODO: Add form data for mapfixes, such as changes they did, and any times that need to be deleted & what styles */}
<TextField className="form-field" id="display-name" name="display-name" label="Display Name" variant="outlined"/>
<TextField className="form-field" id="creator" name="creator" label="Creator" variant="outlined"/>
<TextField className="form-field" id="asset-id" name="asset-id" label="Asset ID" variant="outlined"/>
{/* I think this is Quat's job to figure this one out (to be set when someone clicks review(?)) */} {/* <TextField className="form-field" id="asset-version" label="Asset Version" variant="outlined"/> */}
<GameSelection game={game} setGame={setGame} />
<FormControl>
<FormControlLabel control={<Checkbox sx={{
color: "#646464",
'&.Mui-checked': {
color: "#66BB6A",
},
}} onChange={(e) => setIsFixingMap(e.target.checked)} />} label="Fixing an Existing Map?" />
</FormControl>
{/* <TargetAsset/> */}
<span className="spacer form-spacer"></span>
<Button type="submit" variant="contained" startIcon={<SendIcon/>} sx={{
width: "400px",
height: "50px",
marginInline: "auto"
}}>Submit</Button>
</form>
</main>
</Webpage>
) )
} }