diff --git a/jordi/jordi-ask/routes/routes.js b/jordi/jordi-ask/routes/routes.js index 1d767eda..577db3fd 100644 --- a/jordi/jordi-ask/routes/routes.js +++ b/jordi/jordi-ask/routes/routes.js @@ -17,7 +17,10 @@ module.exports = function (app, questionsRepository) { // Return questions without answer const answerLessQuestions = result.map(q => { - const {answer, ...rest} = q; + const {answer, statements, ...rest} = q; + const statement = statements[Math.floor(Math.random() * statements.length)] + rest.statement = statement; + rest.options = rest.options.sort(() => Math.random() - 0.5); return rest; }); diff --git a/webapp/public/index.html b/webapp/public/index.html index 49b4f51b..a65f7421 100644 --- a/webapp/public/index.html +++ b/webapp/public/index.html @@ -6,7 +6,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webapp/src/media/graveRanking.svg b/webapp/src/media/graveRanking.svg new file mode 100644 index 00000000..f24dff6b --- /dev/null +++ b/webapp/src/media/graveRanking.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webapp/src/views/About.jsx b/webapp/src/views/About.jsx index dbf4441b..57f3279b 100644 --- a/webapp/src/views/About.jsx +++ b/webapp/src/views/About.jsx @@ -2,54 +2,69 @@ import React from 'react'; import { Container, Typography, Paper, Link } from '@mui/material'; import {ReactComponent as Logo} from "../media/logoL.svg"; -export default function About() { +const FirstCard = () => { return ( - + + + About Us + - - - About Us - - - - - + + + - - Welcome to our project! We are dedicated to providing quality content and services to our users. - - - Our mission is to provide a fun way of learning and practicing general knowledge through a Q&A game. - - - Feel free to explore our site and discover more about what we have to offer. - - - If you have any questions or feedback, please don't hesitate to contact us. - - - Thank you for visiting! - - + + Welcome to our project! We are dedicated to providing quality content and services to our users. + + + Our mission is to provide a fun way of learning and practicing general knowledge through a Q&A game. + + + Feel free to explore our site and discover more about what we have to offer. + + + If you have any questions or feedback, please don't hesitate to contact us. + + + Thank you for visiting! + + + ) +} - - - Contact Us - - - You can reach us via the following channels: - - - Mailing Info: uo288787@uniovi.es - - - Mailing Info: uo289295@uniovi.es - - - Checkout our github page! - - +const ContactCard = ({mails}) => { + return ( + + + Contact Us + + + You can reach us via the following channels: + + {mails.map(mail => { + return ( + + Mailing Info: {mail} + + ) + })} + + Checkout our github page! + + + ) +} +export default function About() { + const mails = [ + "uo289295@uniovi.es", + "uo288787@uniovi.es" + ] + + return ( + + + ); diff --git a/webapp/src/views/Account.jsx b/webapp/src/views/Account.jsx index 31e906fe..f234d5b7 100644 --- a/webapp/src/views/Account.jsx +++ b/webapp/src/views/Account.jsx @@ -1,10 +1,10 @@ -import {Fragment} from "react"; import ProtectedComponent from "./components/ProtectedComponent"; +// TODO export default function Account() { return ( - + <> - + ) } \ No newline at end of file diff --git a/webapp/src/views/Error.jsx b/webapp/src/views/Error.jsx index 1e47b9ea..09534910 100644 --- a/webapp/src/views/Error.jsx +++ b/webapp/src/views/Error.jsx @@ -1,16 +1,31 @@ -import {Button, Container, Paper, Typography} from "@mui/material"; -import {Link} from "react-router-dom"; +import { Button, Container, Paper, Typography } from "@mui/material"; +import { Link } from "react-router-dom"; export default function Error() { - return ( - - - Oh no - Oh no, an error occurred. - It seems the requested URL does no exist or you don't have enough privileges to see it. - Go back sinner. - - - - ) -} \ No newline at end of file + return ( + + + Oh no + + Oh no, an error occurred. + + + It seems the requested URL does no exist or you don't have enough + privileges to see it. + + + Go back sinner. + + + + + ); +} diff --git a/webapp/src/views/Game.jsx b/webapp/src/views/Game.jsx index 167228dd..2a532f64 100644 --- a/webapp/src/views/Game.jsx +++ b/webapp/src/views/Game.jsx @@ -1,205 +1,233 @@ import { Button, Container, Divider, Paper, Typography, LinearProgress, Box } from "@mui/material"; -import { useState, useEffect, useRef, Fragment, useContext, useCallback } from "react"; +import { useState, useEffect, useRef, useContext, useCallback } from "react"; import { useParams, useNavigate } from "react-router-dom"; import ProtectedComponent from "./components/ProtectedComponent"; import axios from "axios"; -import { AuthContext } from "../App"; -import coinImage from '../media/coin.svg'; - -const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; +import { AuthContext } from "../views/context/AuthContext"; +import coinImage from "../media/coin.svg"; + +const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000"; + +const buttonStyle = { + height: "10rem", + width: { xs: "auto", md: "10rem" }, + fontSize: "4rem", +}; + +// Change button color +const changeButtonColor = (i, color) => { + const button = document.getElementById(`button${i}`); + if (button != null) { + button.style.backgroundColor = color; + setTimeout(() => { + button.style.backgroundColor = ""; + }, 500); + } +}; + +const MiLinea = ({ progressBarPercent }) => + progressBarPercent > 80 ? ( + + ) : ( + + ); + +const Coin = ({ pointsUpdated }) => { + return ( + + + {pointsUpdated} + + Coin + + ); +}; + +const Questions = ({ current }) => { + return ( + + {current.statement} + + + + {current.options.map((option, i) => ( + + {String.fromCharCode(97 + i).toUpperCase()}. {option} + + ))} + + ); +}; + +const Line = ({ timeLeft, progressBarPercent }) => { + return ( + + + + Time left: {timeLeft} + + + + + + + + ); +}; + +const Buttons = ({ answer, n }) => { + return ( + + {Array.from({ length: n }, (_, i) => ( + + ))} + + ); +}; export default function Game() { - - const { category } = useParams(); - const { getUser } = useContext(AuthContext) - - //State storing all questions - const [questions, setQuestions] = useState([]); - - // State to track the index of the current question - const [current, setCurrent] = useState(0); - - // State to see correct answers - // const [correctAnswers, setCorrectAnswers] = useState(0); - - // Linear time bar - const initialTime = 10; // seconds - - const correctPoints = 100; - const wrongPoints = -20; - - const [timeLeft, setTimeLeft] = useState(initialTime); - - const [progressBarPercent, setProgressBarPercent] = useState(0); - - const [pointsUpdated, setPointsUpdated] = useState(0); - - const timerId = useRef(); - - const navigate = useNavigate(); - - // Next question - const next = useCallback(() => { - if (current === questions.length - 1) { - navigate("/menu"); - } - - setCurrent(current + 1); - setTimeLeft(initialTime); - setProgressBarPercent(0); - }, [current, questions.length, initialTime, navigate]); - - // Timer - useEffect(() => { - if (initialTime) { - timerId.current = window.setInterval(() => { - setTimeLeft((prevProgress) => prevProgress - 1); - }, 1000); - - return () => { - clearInterval(timerId.current); - }; - } - }, []); - - // Update progress bar - useEffect(() => { - if (initialTime) { - if (progressBarPercent < 100) { - let updateProgressPercent = Math.round( - ((initialTime - (timeLeft - 1)) / initialTime) * 100 - ); - setProgressBarPercent(updateProgressPercent); - } - - if (timeLeft === 0 && timerId.current) { - next(); - } - } - }, [timeLeft, progressBarPercent, current, next]); - - - //Fetch questions just at the beginning - useEffect(() => { - fetchQuestions(`${apiEndpoint}/questions/${category}/10`); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // Function to fetch questions - const fetchQuestions = async (url) => { - const {data} = await axios.get(url); - setQuestions(data); + const initialTime = 10; // seconds + const correctPoints = 100; + const wrongPoints = -20; + const [questions, setQuestions] = useState([]); + const [current, setCurrent] = useState(0); + const [timeLeft, setTimeLeft] = useState(initialTime); + const [progressBarPercent, setProgressBarPercent] = useState(0); + const [pointsUpdated, setPointsUpdated] = useState(0); + const timerId = useRef(); + const navigate = useNavigate(); + const { category } = useParams(); + const { getUser } = useContext(AuthContext); + + // Next question + const next = useCallback(() => { + // TODO - Change and redirect to summary + if (current === questions.length - 1) navigate("/menu"); + setCurrent(current + 1); + setTimeLeft(initialTime); + setProgressBarPercent(0); + }, [current, questions.length, initialTime, navigate]); + + // Timer + useEffect(() => { + if (initialTime) { + timerId.current = window.setInterval(() => { + setTimeLeft((prevProgress) => prevProgress - 1); + }, 1000); + + return () => { + clearInterval(timerId.current); + }; } - - // Function to answer a question - const answer = async (i) => { - //Server-side validation - const params = { - token: getUser()["token"], - id: questions[current]._id, - answer: questions[current].options[i] - } - - //Fetch correct answer - let response; - try{ - response = await axios.post(`${apiEndpoint}/game/answer`, params) - }catch(error){ - console.log("Error fetching response"); - } - - // Mark in red the incorrect answers and in green the correct one - const correct = questions[current].options.filter( o => o == response.data.answer); - const correctIndex = questions[current].options.indexOf(correct[0]); - - if(i != correct) changeButtonColor(i, "red"); - - changeButtonColor(correctIndex, "green"); - const newPoints = pointsUpdated + (i === correctIndex ? correctPoints : wrongPoints); - setPointsUpdated(newPoints); - - setTimeout(() => { - next(); - }, 200); - + }, []); + + // Update progress bar + useEffect(() => { + if (initialTime) { + if (progressBarPercent < 100) { + let updateProgressPercent = Math.round( + ((initialTime - (timeLeft - 1)) / initialTime) * 100 + ); + setProgressBarPercent(updateProgressPercent); + } + + if (timeLeft === 0 && timerId.current) { + next(); + } } - - - // Change button color - const changeButtonColor = (i, color) => { - const button = document.getElementById(`button${i}`); - if(button != null){ - button.style.backgroundColor = color; - setTimeout(() => { - button.style.backgroundColor = ""; - }, 500); - } - } - - const buttonStyle = { - height: "10rem", - width: { xs: "auto", md: "10rem" }, - fontSize: "4rem" - } - - const MiLinea = () => { - if (progressBarPercent > 80) { - return () - } else { - return () - } - + }, [timeLeft, progressBarPercent, current, next]); + + //Fetch questions just at the beginning + useEffect(() => { + fetchQuestions(`${apiEndpoint}/questions/${category}/10`); + // eslint-disable-next-line + }, []); + + // Function to fetch questions + const fetchQuestions = async (url) => { + const { data } = await axios.get(url); + setQuestions(data); + }; + + // Function to answer a question + const answer = async (i) => { + //Server-side validation + const params = { + token: getUser()["token"], + id: questions[current]._id, + answer: questions[current].options[i], + }; + + //Fetch correct answer + let response; + try { + response = await axios.post(`${apiEndpoint}/game/answer`, params); + } catch (error) { + console.log("Error fetching response"); } - if (questions.length === 0) - return null; - - return ( - - - - - - {pointsUpdated} - - Coin - - - - {questions[current].statement} - - - - - {questions[current].options.map((option, i) => ( - - {String.fromCharCode(97 + i).toUpperCase()}. {option} - - ))} - - - - - - - - Time left: {timeLeft} - - - - - - - - - - - - - - - - - - ) -} \ No newline at end of file + // Mark in red the incorrect answers and in green the correct one + const correct = questions[current].options.filter( + (o) => o === response.data.answer + ); + const correctIndex = questions[current].options.indexOf(correct[0]); + + if (i !== correct) changeButtonColor(i, "red"); + + changeButtonColor(correctIndex, "green"); + const newPoints = + pointsUpdated + (i === correctIndex ? correctPoints : wrongPoints); + setPointsUpdated(newPoints); + + setTimeout(() => { + next(); + }, 200); + }; + + // TODO - Show error template + if (questions.length === 0) return null; + + return ( + <> + + + + + + + + + ); +} diff --git a/webapp/src/views/Home.jsx b/webapp/src/views/Home.jsx index b5bdb84a..e367ce85 100644 --- a/webapp/src/views/Home.jsx +++ b/webapp/src/views/Home.jsx @@ -1,46 +1,44 @@ -import Container from "@mui/material/Container"; -import { Button } from "@mui/material"; -import { Fragment } from "react"; +import { Button, Container } from "@mui/material"; import { Link } from "react-router-dom"; -import { ReactComponent as Logo } from '../media/logoM.svg'; +import { ReactComponent as Logo } from "../media/logoM.svg"; function Home() { + return ( + <> + + + + - return ( - - - - - - - - - - - - - - - ) + + + + + + ); } -export default Home; \ No newline at end of file +export default Home; diff --git a/webapp/src/views/Login.jsx b/webapp/src/views/Login.jsx index ffb53ca8..c69ec2a0 100644 --- a/webapp/src/views/Login.jsx +++ b/webapp/src/views/Login.jsx @@ -1,85 +1,69 @@ import React, { useState, useContext } from 'react'; import axios from 'axios'; -import {Container, Typography, TextField, Button, Snackbar, Paper} from '@mui/material'; -import {Link} from "react-router-dom" +import { useNavigate } from "react-router-dom" import { Navigate } from 'react-router'; -import { AuthContext } from '../App'; +import { AuthContext } from '../views/context/AuthContext'; +import CustomForm from './components/CustomForm'; + +const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; export default function Login() { - const { setUser, isAuthenticated, logout } = useContext(AuthContext); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); - const [error, setError] = useState(''); + const { isAuthenticated, setUser, logout } = useContext(AuthContext); + const navigate = useNavigate(); - const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; - const loginUser = async () => { - try { - const response = await axios.post(`${apiEndpoint}/login`, { username, password }); + const suggestion = { + text: "Don't have an account?", + linkText: "Sign up", + link: "/signup", + } - if (response.data.error) { - setError(response.data.error); - logout() - return; + const formData = { + title: "Login", + submitButtonTx: "Login", + submit: async (callback) => { + try { + const response = await axios.post(`${apiEndpoint}/login`, { username, password }); + + if (response.data.error) { + callback(response.data.error); + logout() + return; + } + + const { token, username: user } = response.data; + + setUser({"token": token, "username": user}) + navigate('/menu'); + } catch (error) { + callback(error.response.data.error); } + }, + fields: [ + { + required: true, + displayed: "Username", + name: "username", + value: username, + type: "text", + changeHandler: e => setUsername(e.target.value) + }, + { + required: true, + displayed: "Password", + name: "password", + value: password, + type: "password", + changeHandler: e => setPassword(e.target.value) + }, + ] + + } - const { token, username: user } = response.data; - - setUser({"token": token, "username": user}) - } catch (error) { - setError(error.response.data.error); - } - }; - - return ( - - - {isAuthenticated() ? ( - - ) : ( -
- - Login - - setUsername(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - loginUser(); - } - }} - /> - setPassword(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - loginUser(); - } - }} - /> - - {error && ( - setError('')} message={`Error: ${error}`} /> - )} -
- )} - - - Don't have an account? Signup - -
-
+ return isAuthenticated() + ? + : ( + ); }; \ No newline at end of file diff --git a/webapp/src/views/Menu.jsx b/webapp/src/views/Menu.jsx index 74333e9c..980fb1a5 100644 --- a/webapp/src/views/Menu.jsx +++ b/webapp/src/views/Menu.jsx @@ -1,74 +1,82 @@ -import {Button, Container, Paper, Typography} from "@mui/material"; -import {Link} from "react-router-dom"; -import axios from 'axios'; -import { Fragment, useEffect, useState } from "react"; +import { Button, Container, Paper, Typography } from "@mui/material"; +import { Link } from "react-router-dom"; +import axios from "axios"; +import { useEffect, useState } from "react"; import ProtectedComponent from "./components/ProtectedComponent"; +import grave from "../media/graveJordi.svg"; +import ServiceDownMessage from "./components/ServiceDownMessage"; const buttonConfig = { - width: "9rem", - height: "5rem", -} + width: "9rem", + height: "5rem", +}; const buttonGroup = { - display: "flex", - flexFlow: "row wrap", - justifyContent: "space-evenly", - margin: "1rem 0", - gap: "1rem" -} + display: "flex", + flexFlow: "row wrap", + justifyContent: "space-evenly", + margin: "1rem 0", + gap: "1rem", +}; -const MyButton = ({text, link}) => { - return ( - - ) -} +const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000"; -export default function GameMenu () { +const MyButton = ({ text, link }) => ( + +); - const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; - const [categories, setCategories] = useState([]) +const Buttons = ({ categories }) => { + if (!categories || categories.length === 0) + return ; + return ( + + + Choose a category to play + + + {categories.map((category, i) => ( + + ))} + + + ); +}; - const getCategories = async () => { - try { - const response = await axios.get(`${apiEndpoint}/categories`); - setCategories(response.data); - } catch (error) { - setCategories(['Service down Whoops! :(']) - } - }; +export default function GameMenu() { + const [categories, setCategories] = useState([]); - useEffect(() => { - getCategories(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + useEffect(() => { + axios.get(`${apiEndpoint}/categories`) + .then((response) => { + // FIXME - Modify backend to return another status code rather than 200 to prevent app crash + if (response) setCategories(response.data); + }); + }, []); - return ( - - - - - - Menu - - {/* - - Options - - - - - - */} - - Choose a category to play - - {categories.map((category, i) => ( - - ))} - - - - - - ) -} \ No newline at end of file + return ( + <> + + + + + Menu + + + + + + ); +} diff --git a/webapp/src/views/Ranking.jsx b/webapp/src/views/Ranking.jsx index 85f2a926..8c004968 100644 --- a/webapp/src/views/Ranking.jsx +++ b/webapp/src/views/Ranking.jsx @@ -1,32 +1,48 @@ -import React from 'react'; -import { Paper, Typography, List, ListItem, ListItemAvatar, Avatar, ListItemText, Container } from '@mui/material'; +import React from "react"; +import { Paper, Typography, List, ListItem, ListItemAvatar, Avatar, ListItemText, Container } from "@mui/material"; +import grave from "../media/graveRanking.svg" +import ServiceDownMessage from "./components/ServiceDownMessage"; +// TODO - Recover users from the API const users = [ - { id: 1, name: 'John Doe', score: 150 }, - { id: 2, name: 'Jane Smith', score: 120 }, - { id: 3, name: 'Bob Johnson', score: 100 }, - { id: 4, name: 'Alice Lee', score: 90 }, + { id: 1, name: "John Doe", score: 150 }, + { id: 2, name: "Jane Smith", score: 120 }, + { id: 3, name: "Bob Johnson", score: 100 }, + { id: 4, name: "Alice Lee", score: 90 }, ]; -export default function Ranking() { - return ( +const RankingList = ({ users }) => { + if (!users || users.length === 0) + return + return ( + + {users.map((user, index) => ( + + + {index + 1} + + + + ))} + + ); +}; - - - - Global Ranking - - - {users.map((user, index) => ( - - - {index + 1} - - - - ))} - - - - ); -} \ No newline at end of file +export default function Ranking() { + return ( + + + + Global Ranking + + + + + ); +} diff --git a/webapp/src/views/Signup.jsx b/webapp/src/views/Signup.jsx index b51b06a6..c742e3d3 100644 --- a/webapp/src/views/Signup.jsx +++ b/webapp/src/views/Signup.jsx @@ -1,73 +1,62 @@ -// src/components/AddUser.jsx import React, { useState, useContext } from 'react'; -import axios from 'axios'; -import {Container, Typography, TextField, Button, Snackbar, Paper} from '@mui/material'; -import { Link, useNavigate } from "react-router-dom"; -import { AuthContext } from '../App'; +import { Navigate } from "react-router-dom"; +import { AuthContext } from '../views/context/AuthContext'; +import CustomForm from "./components/CustomForm" +import { useNavigate } from "react-router-dom"; +import axios from "axios" const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; export default function Signup() { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - const { setUser } = useContext(AuthContext); + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const { setUser, isAuthenticated } = useContext(AuthContext); const navigate = useNavigate(); - const addUser = () => { - axios.post(`${apiEndpoint}/adduser`, { username, password }) - .then(({data}) => { - setUser({"token": data.token, "username": data.username}) - navigate('/home'); - }) - .catch(({ response }) => setError(response.data.error)); + const suggestion = { + text: "Already have an account?", + linkText: "Login", + link: "/login", } - return ( - - - - Signup - - setUsername(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - addUser(); - } - }} - /> - setPassword(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - addUser(); - } - }} + const formData = { + title: "Signup", + submitButtonTx: "Create account", + submit: (callback) => { + axios + .post(`${apiEndpoint}/adduser`, { username, password }) + .then(({ data }) => { + setUser({ token: data.token, username: data.username }); + navigate("/home"); + }) + .catch(({ response }) => callback(response.data.error)); + }, + fields: [ + { + required: true, + displayed: "Username", + name: "username", + value: username, + type: "text", + changeHandler: e => setUsername(e.target.value) + }, + { + required: true, + displayed: "Password", + name: "password", + value: password, + type: "password", + changeHandler: e => setPassword(e.target.value) + }, + ] + } + + return isAuthenticated() + ? + : ( + - - {error && ( - setError('')} message={`Error: ${error}`} /> - )} - - Already have an account? Login - - - ); }; diff --git a/webapp/src/views/components/CustomForm.jsx b/webapp/src/views/components/CustomForm.jsx new file mode 100644 index 00000000..3536b8b0 --- /dev/null +++ b/webapp/src/views/components/CustomForm.jsx @@ -0,0 +1,74 @@ +import React, { useState } from "react"; +import { Container, Typography, TextField, Button, Snackbar, Paper } from "@mui/material"; + +import { Link } from "react-router-dom"; + +const introHandler = (event, submit) => { + if (event.key === "Enter") submit(); +} + +const SuggestionText = ({ text, linkText, link }) => { + return ( + + {text} {linkText} + + ); +}; + +const Form = ({ formData }) => { + const [error, setError] = useState(""); + + return ( + <> + + {formData.title || "Form"} + + { + formData.fields.map((field, i) => { + return ( + introHandler(e, (e) => formData.submit(e, setError))} + /> + ) + }) + } + + {error && ( + setError("")} + message={`Error: ${error}`} + /> + )} + + ); +}; + +const CustomForm = ({ formData, suggestion }) => { + return ( + + +
+ {suggestion && ( + )} + + + ); +}; + +export default CustomForm; diff --git a/webapp/src/views/components/Footer.jsx b/webapp/src/views/components/Footer.jsx index 8fb03ec5..d3f264ab 100644 --- a/webapp/src/views/components/Footer.jsx +++ b/webapp/src/views/components/Footer.jsx @@ -1,21 +1,17 @@ import React from "react"; -import AppBar from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; +import AppBar from "@mui/material/AppBar"; +import Toolbar from "@mui/material/Toolbar"; import Typography from "@mui/material/Typography"; -import Container from "@mui/material/Container"; - +import { Container } from "@mui/material"; export default function Footer() { - return ( - - - - - © {new Date().getFullYear()} ASW - WIQ05b - - - - - - ); -} \ No newline at end of file + return ( + + + + © {new Date().getFullYear()} ASW - WIQ05b + + + + ); +} diff --git a/webapp/src/views/components/Nav.jsx b/webapp/src/views/components/Nav.jsx index 4435a8a8..e34dabe2 100644 --- a/webapp/src/views/components/Nav.jsx +++ b/webapp/src/views/components/Nav.jsx @@ -1,122 +1,161 @@ import React, { useState, useEffect, useContext } from 'react'; -import { AppBar, Box, Container, IconButton, Toolbar, Tooltip, Avatar, Button, Typography, Menu, MenuItem } from '@mui/material'; +import { AppBar, Box, Container, IconButton, Toolbar, Tooltip, Avatar, Button, Typography, Menu, MenuItem, Divider } from '@mui/material'; import MenuIcon from '@mui/icons-material/Menu'; import { Link } from 'react-router-dom'; import { ReactComponent as CustomIcon } from '../../media/logoS.svg'; -import { AuthContext } from '../../App'; +import { AuthContext } from '../context/AuthContext'; import { ConfigContext } from '../context/ConfigContext'; const pages = [ - { displayed: 'Home', link: '/home' }, - { displayed: 'Global Ranking', link: '/ranking' }, - { displayed: 'About', link: '/about' } + { displayed: 'Home', link: '/home', logged: false}, + { displayed: 'Menu', link: '/menu', logged: true}, + { displayed: 'Global Ranking', link: '/ranking', logged: false}, + { displayed: 'About', link: '/about', logged: false} ]; const settings = [ { displayed: 'Account', link: '/account', logged: true }, - { displayed: 'Sign Up', link: '/signup', logged: false }, { displayed: 'Login', link: '/login', logged: false }, + { displayed: 'Sign Up', link: '/signup', logged: false }, { displayed: 'Logout', link: '/logout', logged: true } ]; -export default function Nav() { - - const { isAuthenticated, getUser, logout } = useContext(AuthContext) - const [anchorElNav, setAnchorElNav] = useState(null); - const [anchorElUser, setAnchorElUser] = useState(null); - const [width, setWidth] = useState(window.innerWidth); - const threshold = 899; +const threshold = 899; - const handleOpenNavMenu = (event) => setAnchorElNav(event.currentTarget); - const handleOpenUserMenu = (event) => setAnchorElUser(event.currentTarget); - const handleCloseNavMenu = () => setAnchorElNav(null); - const handleCloseUserMenu = () => setAnchorElUser(null); +const JordiButton = () => { + const { swapConfig } = useContext(ConfigContext); - useEffect(() => { - const handleResizeWindow = () => setWidth(window.innerWidth); - window.addEventListener('resize', handleResizeWindow); - return () => window.removeEventListener('resize', handleResizeWindow); - }, []); + return ( + + ); +}; - const NavIcon = () => ( - threshold ? '0.5rem' : '1rem auto' }} to="/home"> - - - ); +const MyAvatar = () => { + const { isAuthenticated, getUser } = useContext(AuthContext) + if (!isAuthenticated()) return + return ( + + {getUser()["username"] ? getUser()["username"].charAt(0) : ""} + + ) +} - const DropDownMenu = () => ( - - - - - { + const { isAuthenticated } = useContext(AuthContext) + return ( + + {pages.map((page) => { + if (page.logged && !isAuthenticated()) return null + return ( + - - ); + {page.displayed} + + ) + })} + + ) +} - const NavMenu = () => ( - - {pages.map((page) => ( - - ))} - - ); +const NavIcon = ({width}) => ( + threshold ? '0.5rem' : '1rem auto' }} to="/home"> + + +); - const MyAvatar = () => { - if (!isAuthenticated()) return - return {getUser()["username"] ? getUser()["username"].charAt(0) : ""} - } +const DropDownMenu = ({handleCloseNavMenu, anchorElNav, setAnchorElNav}) => { + const handleOpenNavMenu = (event) => setAnchorElNav(event.currentTarget); + const { swapConfig } = useContext(ConfigContext); + return ( + + + + + + {pages.map((page) => ( + + {page.displayed} + + ))} + + {swapConfig();handleCloseNavMenu()}}> + Swap + + + + ) +} - const JordiButton = () => { - const { swapConfig } = useContext(ConfigContext); +export default function Nav() { + const { isAuthenticated, logout } = useContext(AuthContext) + const [anchorElNav, setAnchorElNav] = useState(null); + const [anchorElUser, setAnchorElUser] = useState(null); + const [width, setWidth] = useState(window.innerWidth); - const handleClick = () => { - swapConfig(); - }; + const handleOpenUserMenu = (event) => setAnchorElUser(event.currentTarget); + const handleCloseNavMenu = () => setAnchorElNav(null); + const handleCloseUserMenu = () => setAnchorElUser(null); + const handleWindowResize = () => { + const handleResizeWindow = () => setWidth(window.innerWidth); + window.addEventListener('resize', handleResizeWindow); + return () => window.removeEventListener('resize', handleResizeWindow); + } - return ( - - ); - }; + useEffect(handleWindowResize, []) - const generateMenuItems = () => { - return settings.map((setting) => { - if (isAuthenticated() !== setting.logged) return null - return ( - {handleCloseUserMenu(); logout()} : handleCloseUserMenu} - component={Link} - to={setting.link === '/logout' ? "/" : setting.link}> - {setting.displayed} - - ) - }) - } + const generateMenuItems = () => { + return settings.map((setting) => { + if (isAuthenticated() !== setting.logged) return null + return ( + {handleCloseUserMenu(); logout()} : handleCloseUserMenu} + component={Link} + to={setting.link === '/logout' ? "/" : setting.link}> + {setting.displayed} + + ) + }) + } return ( - - - + + + diff --git a/webapp/src/views/components/ProtectedComponent.jsx b/webapp/src/views/components/ProtectedComponent.jsx index 490be290..ce21b066 100644 --- a/webapp/src/views/components/ProtectedComponent.jsx +++ b/webapp/src/views/components/ProtectedComponent.jsx @@ -1,5 +1,5 @@ import { useContext } from "react"; -import { AuthContext } from "../../App"; +import { AuthContext } from "../context/AuthContext"; import { Navigate } from "react-router"; export default function ProtectedComponent() { diff --git a/webapp/src/views/components/ServiceDownMessage.jsx b/webapp/src/views/components/ServiceDownMessage.jsx new file mode 100644 index 00000000..55208c92 --- /dev/null +++ b/webapp/src/views/components/ServiceDownMessage.jsx @@ -0,0 +1,14 @@ +import { Container, Typography } from "@mui/material"; + +const ServiceDownMessage = ({ grave }) => { + return ( + + Grave + + The service seems to be down, please try again later. + + + ); +}; + +export default ServiceDownMessage; diff --git a/webapp/src/views/components/config/particles-config-graph.js b/webapp/src/views/components/config/particles-config-graph.js deleted file mode 100644 index 2c792a39..00000000 --- a/webapp/src/views/components/config/particles-config-graph.js +++ /dev/null @@ -1,73 +0,0 @@ -const config = { - "particles": { - "number": { - "value": 70, - "density": { - "enable": true, - "value_area": 800 - } - }, - "color": { - "value": "#000000" - }, - "shape": { - "type": "circle", - "stroke": { - "width": 0, - "color": "#000000" - }, - "polygon": { - "nb_sides": 3 - }, - "image": { - "src": "img/github.svg", - "width": 100, - "height": 100 - } - }, - "opacity": { - "value": 0.5, - "random": false, - "anim": { - "enable": false, - "speed": 1, - "opacity_min": 0.1, - "sync": false - } - }, - "size": { - "value": 6, - "random": true, - "anim": { - "enable": false, - "speed": 40, - "size_min": 0.1, - "sync": false - } - }, - "line_linked": { - "enable": true, - "distance": 150, - "color": "#104280", - "opacity": 0.4, - "width": 3 - }, - "move": { - "enable": true, - "speed": 1, - "direction": "none", - "random": false, - "straight": false, - "out_mode": "out", - "bounce": false, - "attract": { - "enable": false, - "rotateX": 600, - "rotateY": 1200 - } - } -}, - "retina_detect": true -} - -export default config; \ No newline at end of file diff --git a/webapp/src/views/components/config/particles-config-graph.json b/webapp/src/views/components/config/particles-config-graph.json new file mode 100644 index 00000000..7fcec75a --- /dev/null +++ b/webapp/src/views/components/config/particles-config-graph.json @@ -0,0 +1,71 @@ +{ + "particles": { + "number": { + "value": 70, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#000000" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 3 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.5, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 6, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#104280", + "opacity": 0.4, + "width": 3 + }, + "move": { + "enable": true, + "speed": 1, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "bounce": false, + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "retina_detect": true +} diff --git a/webapp/src/views/context/AuthContext.jsx b/webapp/src/views/context/AuthContext.jsx new file mode 100644 index 00000000..a8b9a44b --- /dev/null +++ b/webapp/src/views/context/AuthContext.jsx @@ -0,0 +1,3 @@ +import React from 'react'; + +export const AuthContext = React.createContext(); \ No newline at end of file