From f88570d038be3b40122e361f8a9828a9601a4370 Mon Sep 17 00:00:00 2001 From: andriluccahannes <lb214@hdm-stuttgart.de> Date: Tue, 23 Jan 2024 22:45:45 +0100 Subject: [PATCH] 16.01.2024 - Fix User Authentication Stuff and Clean Up Frontend #8 --- sth-backend/pom.xml | 11 +- .../controller/AuthenticationController.java | 30 +-- .../hdm/mi/sthbackend/dto/ApiResponseDTO.java | 15 ++ .../exeptions/GlobalExceptionHandler.java | 8 +- .../sthbackend/model/ConfirmationToken.java | 2 +- .../java/hdm/mi/sthbackend/model/User.java | 2 + .../repository/IUserRepository.java | 2 + .../service/AuthenticationService.java | 19 +- .../service/ConfirmationTokenService.java | 4 + .../src/main/resources/application.yaml | 4 + .../templates/successfulVerification.html | 49 +++++ sth-frontend/src/App.jsx | 8 +- .../features/auth/components/LoginForm.jsx | 12 +- .../features/auth/components/RegisterForm.jsx | 8 +- .../auth/services/authenticationService.js | 61 ++++++ .../landingpage/components/Navbar.jsx | 113 ---------- .../features/landingpage/components/index.jsx | 2 - .../features/tournament/components/Match.jsx | 4 +- .../tournament/components/Tournament.jsx | 2 +- .../components => layouts}/Footer.jsx | 4 +- sth-frontend/src/layouts/Navbar.jsx | 198 ++++++++++++++++-- .../landingpage => pages}/Landingpage.jsx | 4 +- sth-frontend/src/styles/tailwind.css | 2 +- sth-frontend/src/{pages => trash}/Home.jsx | 4 +- .../src/{layouts => trash}/Sidebar.jsx | 0 sth-frontend/src/utils/router.jsx | 42 ++-- 26 files changed, 400 insertions(+), 210 deletions(-) create mode 100644 sth-backend/src/main/java/hdm/mi/sthbackend/dto/ApiResponseDTO.java create mode 100644 sth-backend/src/main/resources/templates/successfulVerification.html create mode 100644 sth-frontend/src/features/auth/services/authenticationService.js delete mode 100644 sth-frontend/src/features/landingpage/components/Navbar.jsx rename sth-frontend/src/{features/landingpage/components => layouts}/Footer.jsx (96%) rename sth-frontend/src/{features/landingpage => pages}/Landingpage.jsx (71%) rename sth-frontend/src/{pages => trash}/Home.jsx (71%) rename sth-frontend/src/{layouts => trash}/Sidebar.jsx (100%) diff --git a/sth-backend/pom.xml b/sth-backend/pom.xml index 962a345..2a414ac 100644 --- a/sth-backend/pom.xml +++ b/sth-backend/pom.xml @@ -67,16 +67,7 @@ <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-api</artifactId> - <version>2.22.0</version> - </dependency> - <dependency> - <groupId>org.apache.logging.log4j</groupId> - <artifactId>log4j-core</artifactId> - <version>2.22.0</version> - </dependency> + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> diff --git a/sth-backend/src/main/java/hdm/mi/sthbackend/controller/AuthenticationController.java b/sth-backend/src/main/java/hdm/mi/sthbackend/controller/AuthenticationController.java index a00ecde..824e449 100644 --- a/sth-backend/src/main/java/hdm/mi/sthbackend/controller/AuthenticationController.java +++ b/sth-backend/src/main/java/hdm/mi/sthbackend/controller/AuthenticationController.java @@ -1,37 +1,42 @@ package hdm.mi.sthbackend.controller; +import hdm.mi.sthbackend.dto.ApiResponseDTO; import hdm.mi.sthbackend.dto.AuthenticationDTO; -import hdm.mi.sthbackend.exeptions.UserAlreadyTakenException; -import hdm.mi.sthbackend.exeptions.UserNotFoundException; import hdm.mi.sthbackend.service.AuthenticationService; import hdm.mi.sthbackend.types.UserType; -import jakarta.servlet.http.Cookie; import lombok.AllArgsConstructor; +import lombok.extern.log4j.Log4j; +import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + @RestController @RequestMapping( "/api/v1/auth" ) @AllArgsConstructor @CrossOrigin( origins = "*" ) +@Slf4j public class AuthenticationController { - private final Logger log = LogManager.getLogger( this - .getClass() - .getSimpleName() ); + private final AuthenticationService authenticationService; + private final TemplateEngine templateEngine; @PostMapping( "/signup" ) - public ResponseEntity<String> signUpUser(@RequestBody UserType.UserSignUpType userSignUpType) { + public ResponseEntity<ApiResponseDTO> signUpUser(@RequestBody UserType.UserSignUpType userSignUpType) { log.debug( "Signing up user: " + userSignUpType ); authenticationService.signUpUser( userSignUpType ); + return ResponseEntity - .status( HttpStatus.CREATED ) - .body( "Registration Successful" ); + .status(HttpStatus.CREATED) + .body(new ApiResponseDTO( "User registered successfully")); + } @PostMapping( "/signin" ) @@ -42,9 +47,10 @@ public class AuthenticationController { } @GetMapping( "/confirm" ) - public void confirmUser(@RequestParam( "token" ) String token) { + public String confirmUser(@RequestParam( "token" ) String token) { + + log.info( "Confirming user with token: " + token ); - log.debug( "Confirming user with token: " + token ); - authenticationService.confirmToken( token ); + return authenticationService.confirmToken( token ); } } diff --git a/sth-backend/src/main/java/hdm/mi/sthbackend/dto/ApiResponseDTO.java b/sth-backend/src/main/java/hdm/mi/sthbackend/dto/ApiResponseDTO.java new file mode 100644 index 0000000..b2a83eb --- /dev/null +++ b/sth-backend/src/main/java/hdm/mi/sthbackend/dto/ApiResponseDTO.java @@ -0,0 +1,15 @@ +package hdm.mi.sthbackend.dto; + +import lombok.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class ApiResponseDTO { + + String message; + +} diff --git a/sth-backend/src/main/java/hdm/mi/sthbackend/exeptions/GlobalExceptionHandler.java b/sth-backend/src/main/java/hdm/mi/sthbackend/exeptions/GlobalExceptionHandler.java index b7817bc..d6d631a 100644 --- a/sth-backend/src/main/java/hdm/mi/sthbackend/exeptions/GlobalExceptionHandler.java +++ b/sth-backend/src/main/java/hdm/mi/sthbackend/exeptions/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package hdm.mi.sthbackend.exeptions; +import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.http.HttpStatus; @@ -11,11 +12,10 @@ import org.springframework.web.context.request.WebRequest; import java.util.Date; @ControllerAdvice +@Slf4j public class GlobalExceptionHandler { - private final Logger log = LogManager.getLogger( this - .getClass() - .getSimpleName() ); + @ExceptionHandler( { UserAlreadyTakenException.class } ) public ResponseEntity<ErrorObject> handleUserAlreadyTakenException(UserAlreadyTakenException e, @@ -25,7 +25,7 @@ public class GlobalExceptionHandler { ErrorObject errorObject = ErrorObject .builder() .message( e.getMessage() ) - .status( HttpStatus.NOT_FOUND ) + .status( HttpStatus.CONFLICT ) .timestamp( new Date() ) .build(); diff --git a/sth-backend/src/main/java/hdm/mi/sthbackend/model/ConfirmationToken.java b/sth-backend/src/main/java/hdm/mi/sthbackend/model/ConfirmationToken.java index cf7a0f8..4d1466f 100644 --- a/sth-backend/src/main/java/hdm/mi/sthbackend/model/ConfirmationToken.java +++ b/sth-backend/src/main/java/hdm/mi/sthbackend/model/ConfirmationToken.java @@ -21,7 +21,7 @@ public class ConfirmationToken { private LocalDateTime expiresAt; @Setter @Getter - private LocalDateTime confirmedAt; + private LocalDateTime confirmedAt = null; @Getter private User user; diff --git a/sth-backend/src/main/java/hdm/mi/sthbackend/model/User.java b/sth-backend/src/main/java/hdm/mi/sthbackend/model/User.java index 984b941..297ad05 100644 --- a/sth-backend/src/main/java/hdm/mi/sthbackend/model/User.java +++ b/sth-backend/src/main/java/hdm/mi/sthbackend/model/User.java @@ -4,6 +4,7 @@ import hdm.mi.sthbackend.enums.Role; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; +import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.security.core.GrantedAuthority; @@ -22,6 +23,7 @@ import java.util.UUID; @Setter public class User implements UserDetails { + @Id private final UUID userId; private final String email; private final String password; diff --git a/sth-backend/src/main/java/hdm/mi/sthbackend/repository/IUserRepository.java b/sth-backend/src/main/java/hdm/mi/sthbackend/repository/IUserRepository.java index 88ea9f2..14605d1 100644 --- a/sth-backend/src/main/java/hdm/mi/sthbackend/repository/IUserRepository.java +++ b/sth-backend/src/main/java/hdm/mi/sthbackend/repository/IUserRepository.java @@ -10,4 +10,6 @@ import java.util.UUID; @Repository public interface IUserRepository extends MongoRepository<User, UUID> { Optional<User> findByEmail(String email); + + Optional<User> findByUserId(String sessionToken); } diff --git a/sth-backend/src/main/java/hdm/mi/sthbackend/service/AuthenticationService.java b/sth-backend/src/main/java/hdm/mi/sthbackend/service/AuthenticationService.java index ca897ed..31e200b 100644 --- a/sth-backend/src/main/java/hdm/mi/sthbackend/service/AuthenticationService.java +++ b/sth-backend/src/main/java/hdm/mi/sthbackend/service/AuthenticationService.java @@ -9,6 +9,7 @@ import hdm.mi.sthbackend.model.User; import hdm.mi.sthbackend.repository.IUserRepository; import hdm.mi.sthbackend.types.UserType; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.beans.factory.annotation.Value; @@ -25,11 +26,9 @@ import java.util.UUID; @Service @RequiredArgsConstructor +@Slf4j public class AuthenticationService { - private final Logger log = LogManager.getLogger( this - .getClass() - .getSimpleName() ); private final IUserRepository userRepository; private final PasswordEncoder passwordEncoder; @@ -79,12 +78,12 @@ public class AuthenticationService { String URL = UriComponentsBuilder .fromUriString( baseURL ) - .path( "/api/v1/user/confirm" ) + .path( "/api/v1/auth/confirm" ) .queryParam( "token", token ) .build() .toUriString(); - log.debug( "Confirmation URL: " + URL ); + log.info( "Confirmation URL: " + URL ); emailService.send( userSignUpType.email(), "STH Confirmation Mail", @@ -105,7 +104,7 @@ public class AuthenticationService { .build(); } - public void confirmToken(String token) { + public String confirmToken(String token) { ConfirmationToken confirmationToken = confirmationTokenService.getToken( token ); @@ -120,9 +119,14 @@ public class AuthenticationService { } confirmationTokenService.setConfirmedAt( token ); - this.verifyUser( confirmationToken + + verifyUser( confirmationToken .getUser() .getUserId() ); + + Context context = new Context(); + context.setVariable( "userEmail", confirmationToken.getUser().getEmail()); + return templateEngine.process( "successfulVerification", context ); } @@ -141,6 +145,7 @@ public class AuthenticationService { user.getTournaments(), user.getUserProfile(), user.getRole() ); + userRepository.save( enabledUser ); } diff --git a/sth-backend/src/main/java/hdm/mi/sthbackend/service/ConfirmationTokenService.java b/sth-backend/src/main/java/hdm/mi/sthbackend/service/ConfirmationTokenService.java index 470e09b..3fad64e 100644 --- a/sth-backend/src/main/java/hdm/mi/sthbackend/service/ConfirmationTokenService.java +++ b/sth-backend/src/main/java/hdm/mi/sthbackend/service/ConfirmationTokenService.java @@ -14,6 +14,9 @@ import java.time.LocalDateTime; @AllArgsConstructor public class ConfirmationTokenService { + private final Logger log = LogManager.getLogger( this + .getClass() + .getSimpleName() ); private final IConfirmationTokenRepository confirmationTokenRepository; public void saveConfirmationToken(ConfirmationToken token) { @@ -33,6 +36,7 @@ public class ConfirmationTokenService { ConfirmationToken confirmationToken = confirmationTokenRepository .findByConfirmationToken( token ) .orElseThrow( () -> new ConfirmationTokenNotFound( ) ); + log.info( "Setting confirmed at date for token: " + confirmationToken ); confirmationToken.setConfirmedAt( LocalDateTime.now() ); confirmationTokenRepository.save( confirmationToken ); } diff --git a/sth-backend/src/main/resources/application.yaml b/sth-backend/src/main/resources/application.yaml index d607644..497dc6c 100644 --- a/sth-backend/src/main/resources/application.yaml +++ b/sth-backend/src/main/resources/application.yaml @@ -26,3 +26,7 @@ security: jwt: secret: 52110fe737fdc73e0679156a189dff24fa9017b54a66ab0ab5ae885bf52ea82e expirationMs: 3600000 +logging: + level: + root: info + diff --git a/sth-backend/src/main/resources/templates/successfulVerification.html b/sth-backend/src/main/resources/templates/successfulVerification.html new file mode 100644 index 0000000..dd1ddb3 --- /dev/null +++ b/sth-backend/src/main/resources/templates/successfulVerification.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html lang="en" xmlns:th="http://www.thymeleaf.org"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Email Verification Success</title> + <style> + body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f4f4f4; + margin: 0; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + } + + #verification-container { + background-color: #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + padding: 20px; + border-radius: 8px; + text-align: center; + } + + #verification-title { + color: #2c3e50; + } + + .verification-info { + color: #555; + margin-bottom: 20px; + } + + #user-email { + color: #3498db; + font-weight: bold; + } + </style> +</head> +<body> +<div id="verification-container"> + <h1 id="verification-title">Email Verification Successful</h1> + <p class="verification-info">Thank you for verifying your email address: <span id="user-email" th:text="${userEmail}"></span></p> + <p class="verification-info">Your account is now active. You can log in to access your account.</p> +</div> +</body> +</html> diff --git a/sth-frontend/src/App.jsx b/sth-frontend/src/App.jsx index 6946370..9eb53b5 100644 --- a/sth-frontend/src/App.jsx +++ b/sth-frontend/src/App.jsx @@ -1,13 +1,15 @@ import Navbar from "./layouts/Navbar"; import {Outlet} from "react-router-dom"; import "./index.css" -import Button from "./components/Button"; -import Home from "./pages/Home"; +import React from "react"; function App() { return ( <> - <Home /> + <Navbar/> + <div className={'mt-16'}> + <Outlet/> + </div> </> ); } diff --git a/sth-frontend/src/features/auth/components/LoginForm.jsx b/sth-frontend/src/features/auth/components/LoginForm.jsx index 59111ec..8a02afd 100644 --- a/sth-frontend/src/features/auth/components/LoginForm.jsx +++ b/sth-frontend/src/features/auth/components/LoginForm.jsx @@ -1,7 +1,17 @@ import React from "react"; import {Link} from "react-router-dom"; +import {login} from "../services/authenticationService"; function LoginForm() { + +const handleSubmit = async (e) => { + e.preventDefault(); + const email = e.target.email.value; + const password = e.target.password.value; + + await login(email, password) +} + return ( <div className='bg-deepPurple text-lightGray m-auto w-1/2 h-full max-sm:w-full'> <div className='py-16 flex justify-around'> @@ -23,7 +33,7 @@ function LoginForm() { </p> </div> <div className='flex flex-col w-2/3 gap-10 py-10'> - <form className='flex flex-col gap-10'> + <form onSubmit={handleSubmit} className='flex flex-col gap-10'> <div className='flex flex-col gap-10 m-auto w-full'> <div className='flex flex-col gap-2'> <p className='text-2xl'>E-Mail</p> diff --git a/sth-frontend/src/features/auth/components/RegisterForm.jsx b/sth-frontend/src/features/auth/components/RegisterForm.jsx index 2187dad..869ab85 100644 --- a/sth-frontend/src/features/auth/components/RegisterForm.jsx +++ b/sth-frontend/src/features/auth/components/RegisterForm.jsx @@ -1,6 +1,7 @@ import React, {useEffect} from "react"; import {Link, useLocation} from "react-router-dom"; import {useEmail} from "./EmailContext"; +import {register} from "../services/authenticationService"; function RegisterForm() { const location = useLocation() @@ -25,9 +26,12 @@ function RegisterForm() { return emailRegex.test(email) } - const handleFormSubmit = (e) => { + const handleFormSubmit = async (e) => { e.preventDefault(); - window.location.href = '/register' + const email = e.target.email.value; + const password = e.target.password.value; + + await register(email, password) } return ( diff --git a/sth-frontend/src/features/auth/services/authenticationService.js b/sth-frontend/src/features/auth/services/authenticationService.js new file mode 100644 index 0000000..5987ff8 --- /dev/null +++ b/sth-frontend/src/features/auth/services/authenticationService.js @@ -0,0 +1,61 @@ +import {HttpResponseError} from "../../../utils/errors/httpResponseError"; + +const baseURL = import.meta.env.VITE_BASE_URL; +async function login(email, password){ + + const url = `${baseURL}/api/v1/auth/signin` + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + mode: 'cors', + credentials: 'omit', + body: JSON.stringify({email, password}) + }; + + try { + console.debug(`fetching ${url}`); + + const response = await fetch(url, options); + const data = await response.json() + + if (!response.ok) { + throw new HttpResponseError('Bad fetch', response); + } + return data; + } catch (error) { + console.error(error); + } +} + +async function register(email, password){ + + const url = `${baseURL}/api/v1/auth/signup` + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + mode: 'cors', + credentials: 'omit', + body: JSON.stringify({email, password}) + }; + + try { + console.debug(`fetching ${url}`); + + const response = await fetch(url, options); + const data = await response.json() + + if (!response.ok) { + throw new HttpResponseError('Bad fetch', data); + } + return data; + } catch (error) { + + console.error(error.response); + } + } + +export {login, register} \ No newline at end of file diff --git a/sth-frontend/src/features/landingpage/components/Navbar.jsx b/sth-frontend/src/features/landingpage/components/Navbar.jsx deleted file mode 100644 index b9b87b0..0000000 --- a/sth-frontend/src/features/landingpage/components/Navbar.jsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, {useState} from "react"; -import {Link} from "react-router-dom"; -import menu from '../../../assets/svg/menu.svg' -import user from '../../../assets/svg/user.svg' - -const Navbar = () => { - const [darkMode, setDarkMode] = useState(true) - const [showMenu, setShowMenu] = useState(false) - const [showUser, setShowUser] = useState(false) - - const UserMenu = () => { - return ( - <div className='absolute top-16 right-6 flex flex-col items-center bg-lightGray text-darkGray p-2 border border-darkGray rounded-md'> - <Link to={'/login'} className='hover:underline'>Login</Link> - <Link to={'/register'} className='hover:underline'>Register</Link> - </div> - ) - } - - const toggleUserMenu = () => { - setShowUser(!showUser) - } - - const toggleDarkMode = () => { - setDarkMode(!darkMode) - }; - - const toggleMenu = () => { - setShowMenu(!showMenu) - } - - const backgroundColorClasses = darkMode ? 'bg-darkGray': 'bg-lightGray'; - const textClass = darkMode ? 'text-lightGray': 'text-darkGray'; - const moonOrSunIcon = darkMode ? '☾': '☼' ; - - - return ( - <div className={`${backgroundColorClasses} fixed top-0 z-50 m-auto`}> - <nav className={`flex ${textClass} gap-3 items-center justify-center`}> - <div className='flex flex-row gap-5 items-center justify-center flex-shrink-0 w-screen relative max-sm:justify-evenly'> - <button onClick={toggleMenu} className='hidden max-[1024px]:flex'> - <img src={menu} alt='Menu' className='w-6 h-6'/> - </button> - {showMenu && ( - <div className="lg:hidden absolute top-16 left-0 right-0 bg-lightGray text-darkGray"> - <ul className="flex flex-col items-start justify-start w-full px-4 py-2"> - <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> - <Link to="/landingpage"> - Home - </Link> - </li> - <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> - <Link to="/landingpage"> - Tournaments - </Link> - </li> - <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> - <Link to="/landingpage"> - Support - </Link> - </li> - <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> - <Link to="/landingpage"> - Options - </Link> - </li> - </ul> - </div> - )} - <div - className='flex flex-row flex-shrink-0 gap-0 items-center justify-start relative p-2.5 text-5xl'>TJ1 - </div> - <div className='flex flex-col items-start justify-start flex-shrink-0 relative gap-2.5 p-2.5 max-[1024px]:hidden'> - <ul className='flex flex-row items-start justify-start flex-shrink-0 w-1/3 relative px-4 gap-8 text-xl'> - <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> - <Link to="/landingpage">Home</Link> - </li> - <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> - <Link to="/landingpage">Tournaments</Link> - </li> - <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> - <Link to="/landingpage">Support</Link> - </li> - <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> - <Link to="/landingpage">Options</Link> - </li> - </ul> - </div> - <div onClick={toggleDarkMode} className='cursor-pointer text-3xl'> - {moonOrSunIcon} - </div> - <div className='flex flex-row items-center justify-start flex-shrink-0 relative gap-5 max-sm:hidden'> - <button - className='w-40 rounded-3xl border-2 border-lightGray px-12 py-2 bg-lightGray text-darkGray hover:bg-darkGray hover:text-lightGray bg-[#C2C2C2] hover: border-[#C2C2C2]'> - <Link to={"/register"}>Register</Link> - </button> - <button className='w-40 rounded-3xl border-2 px-12 py-2 hover:bg-deepPurple'> - <Link to={"/login"}>Login</Link> - </button> - </div> - <div className='hidden max-sm:flex'> - <button onClick={toggleUserMenu} className=''> - <img src={user} alt='User Menu' className='w-10 h-10'/> - </button> - {showUser && <UserMenu/>} - </div> - </div> - </nav> - </div> - ) -} - -export default Navbar \ No newline at end of file diff --git a/sth-frontend/src/features/landingpage/components/index.jsx b/sth-frontend/src/features/landingpage/components/index.jsx index ca5c781..c6d4beb 100644 --- a/sth-frontend/src/features/landingpage/components/index.jsx +++ b/sth-frontend/src/features/landingpage/components/index.jsx @@ -1,6 +1,4 @@ export { default as Features } from "/src/features/landingpage/components/Features"; -export { default as Footer } from "/src/features/landingpage/components/Footer"; export { default as Header } from "/src/features/landingpage/components/Header"; export { default as Modes } from "/src/features/landingpage/components/Modes"; -export { default as Navbar } from "/src/features/landingpage/components/Navbar"; export { default as Partner } from "/src/features/landingpage/components/Partner"; diff --git a/sth-frontend/src/features/tournament/components/Match.jsx b/sth-frontend/src/features/tournament/components/Match.jsx index a1b3def..a09093f 100644 --- a/sth-frontend/src/features/tournament/components/Match.jsx +++ b/sth-frontend/src/features/tournament/components/Match.jsx @@ -38,12 +38,12 @@ export default function Match({tournament, handleWinner, style, index, bracketRo return ( - <div className={`m-2 hover:shadow-lg border-4 rounded-xl`}> + <div className={`bg-gray-700 m-2 hover:shadow-lg border-4 rounded-xl`}> <Team name={team1?.teamName} score={teamOneScore} setScore={setTeamOneScore} winning={teamOneScore > teamTwoScore}/> <Team name={team2?.teamName} score={teamTwoScore} setScore={setTeamTwoScore} winning={teamTwoScore > teamOneScore}/> - <button className={"mx-auto bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"} + <button className={"w-full mx-auto bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"} onClick={() => {handleWinner(bracketRoundIndex, match.matchId)}}>Close Match </button> </div> diff --git a/sth-frontend/src/features/tournament/components/Tournament.jsx b/sth-frontend/src/features/tournament/components/Tournament.jsx index 91ecf20..3368d79 100644 --- a/sth-frontend/src/features/tournament/components/Tournament.jsx +++ b/sth-frontend/src/features/tournament/components/Tournament.jsx @@ -292,7 +292,7 @@ export default function Tournament() { return <div>Loading...</div>; } return ( - <div> + <div className={''}> <Bracket tournament={tournament} handleWinner={handleMatchWinner} /> </div> ) diff --git a/sth-frontend/src/features/landingpage/components/Footer.jsx b/sth-frontend/src/layouts/Footer.jsx similarity index 96% rename from sth-frontend/src/features/landingpage/components/Footer.jsx rename to sth-frontend/src/layouts/Footer.jsx index f420ced..d8f4d5e 100644 --- a/sth-frontend/src/features/landingpage/components/Footer.jsx +++ b/sth-frontend/src/layouts/Footer.jsx @@ -1,6 +1,6 @@ import React from "react"; -import accessibility from '../../../assets/svg/Accessibility.svg' -import world from '../../../assets/svg/World.svg' +import accessibility from '../assets/svg/Accessibility.svg' +import world from '../assets/svg/World.svg' const DATAHUB = ['Tournament', 'MVP', 'Extras', 'Options', 'Types'] const DATAINFO = ['Infos', 'Demo', 'Stuff', 'Options', 'Types'] diff --git a/sth-frontend/src/layouts/Navbar.jsx b/sth-frontend/src/layouts/Navbar.jsx index 0de1e1c..65ec5c9 100644 --- a/sth-frontend/src/layouts/Navbar.jsx +++ b/sth-frontend/src/layouts/Navbar.jsx @@ -1,34 +1,194 @@ +import React, {useState} from "react"; import {Link} from "react-router-dom"; -import Modal from "../components/Modal"; -import {useState} from "react"; +import menu from '../assets/svg/menu.svg' +import user from '../assets/svg/user.svg' import TournamentForm from "../components/TournamentForm"; +import Modal from "../components/Modal"; -export default function Navbar() { +const Navbar = () => { + const [darkMode, setDarkMode] = useState(true) + const [showMenu, setShowMenu] = useState(false) + const [showUser, setShowUser] = useState(false) const [isOpen, setIsOpen] = useState(false) + + const UserMenu = () => { + return ( + <div className='absolute top-16 right-6 flex flex-col items-center bg-lightGray text-darkGray p-2 border border-darkGray rounded-md'> + <Link to={'/login'} className='hover:underline'>Login</Link> + <Link to={'/register'} className='hover:underline'>Register</Link> + </div> + ) + } - if (true) { + const toggleUserMenu = () => { + setShowUser(!showUser) + } + const toggleDarkMode = () => { + setDarkMode(!darkMode) + }; + + const toggleMenu = () => { + setShowMenu(!showMenu) } - return ( - <nav className="flex items-center justify-end h-14 bg-gray-100 fixed w-full top-0 left-0 z-10 hover:shadow duration-100"> + const backgroundColorClasses = darkMode ? 'bg-lightGray' : 'bg-darkGray' ; + const textClass = darkMode ? 'text-darkGray' : 'text-lightGray' ; + const moonOrSunIcon = darkMode ? '☾': '☼' ; + + const loggedIn = true; + if(loggedIn) { + return ( + <div className={`${backgroundColorClasses} fixed top-0 z-50 m-auto`}> <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}> <TournamentForm setIsOpen={setIsOpen}/> </Modal> - <div className={'mr-12'}> - <ul className="flex justify-evenly items-center w-full h-full text-gray-400"> - <button className={'flex items-center justify-center w-24 h-8 rounded-xl hover:bg-gray-200'} onClick={() => setIsOpen(true)}> - Create + <nav className={`flex ${textClass} gap-3 items-center justify-center`}> + <div className='flex flex-row gap-5 items-center justify-center flex-shrink-0 w-screen relative max-sm:justify-evenly'> + <button onClick={toggleMenu} className='hidden max-[1024px]:flex'> + <img src={menu} alt='Menu' className='w-6 h-6'/> </button> + {showMenu && ( + <div className="lg:hidden absolute top-16 left-0 right-0 bg-lightGray text-darkGray"> + <ul className="flex flex-col items-start justify-start w-full px-4 py-2"> + <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> + <Link to="/landingpage"> + Home + </Link> + </li> + <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> + <Link to="/landingpage"> + Tournaments + </Link> + </li> + <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> + <Link to="/landingpage"> + Support + </Link> + </li> + <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> + <Link to="/landingpage"> + Options + </Link> + </li> + </ul> + </div> + )} + <div + className='flex flex-row flex-shrink-0 gap-0 items-center justify-start relative p-2.5 text-5xl'>TJ1 + </div> + <div className='flex flex-col items-start justify-start flex-shrink-0 relative gap-2.5 p-2.5 max-[1024px]:hidden'> + <ul className='flex flex-row items-start justify-start flex-shrink-0 w-1/3 relative px-4 gap-8 text-xl'> + <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> + <Link to="/landingpage">Home</Link> + </li> + <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> + <Link to="/landingpage">Tournaments</Link> + </li> + <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> + <Link to="/landingpage">Support</Link> + </li> + <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> + <Link to="/landingpage">Options</Link> + </li> + </ul> + </div> + <div onClick={toggleDarkMode} className='cursor-pointer text-3xl'> + {moonOrSunIcon} + </div> + <div + className='flex flex-row items-center justify-start flex-shrink-0 relative gap-5 max-sm:hidden'> + <button className='w-40 rounded-3xl border-2 px-12 py-2 hover:bg-deepPurple' + onClick={() => setIsOpen(true)}> + Create + </button> + </div> + <div className='hidden max-sm:flex'> + <button onClick={toggleUserMenu} className=''> + <img src={user} alt='User Menu' className='w-10 h-10'/> + </button> + {showUser && <UserMenu/>} + </div> + </div> + </nav> + </div> + ) + } - <li className={'flex items-center justify-center w-24 h-8 rounded-xl hover:bg-gray-200'}> - <Link to="/">Home</Link> - </li> - <li className={'flex items-center justify-center w-24 h-8 rounded-lg h-full hover:bg-gray-200 duration-100'}> - <Link to="/login">Login</Link> - </li> - </ul> + return ( + <div className={`${backgroundColorClasses} fixed top-0 z-50 m-auto`}> + <nav className={`flex ${textClass} gap-3 items-center justify-center`}> + <div className='flex flex-row gap-5 items-center justify-center flex-shrink-0 w-screen relative max-sm:justify-evenly'> + <button onClick={toggleMenu} className='hidden max-[1024px]:flex'> + <img src={menu} alt='Menu' className='w-6 h-6'/> + </button> + {showMenu && ( + <div className="lg:hidden absolute top-16 left-0 right-0 bg-lightGray text-darkGray"> + <ul className="flex flex-col items-start justify-start w-full px-4 py-2"> + <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> + <Link to="/landingpage"> + Home + </Link> + </li> + <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> + <Link to="/landingpage"> + Tournaments + </Link> + </li> + <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> + <Link to="/landingpage"> + Support + </Link> + </li> + <li className="hover:underline hover:underline-offset-4 hover:decoration-2"> + <Link to="/landingpage"> + Options + </Link> + </li> + </ul> + </div> + )} + <div + className='flex flex-row flex-shrink-0 gap-0 items-center justify-start relative p-2.5 text-5xl'>TJ1 + </div> + <div className='flex flex-col items-start justify-start flex-shrink-0 relative gap-2.5 p-2.5 max-[1024px]:hidden'> + <ul className='flex flex-row items-start justify-start flex-shrink-0 w-1/3 relative px-4 gap-8 text-xl'> + <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> + <Link to="/landingpage">Home</Link> + </li> + <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> + <Link to="/landingpage">Tournaments</Link> + </li> + <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> + <Link to="/landingpage">Support</Link> + </li> + <li className='hover:underline hover:underline-offset-4 hover:decoration-2'> + <Link to="/landingpage">Options</Link> + </li> + </ul> + </div> + <div onClick={toggleDarkMode} className='cursor-pointer text-3xl'> + {moonOrSunIcon} + </div> + <div className='flex flex-row items-center justify-start flex-shrink-0 relative gap-5 max-sm:hidden'> + <button + className='w-40 rounded-3xl border-2 border-lightGray px-12 py-2 bg-lightGray text-darkGray hover:bg-darkGray hover:text-lightGray bg-[#C2C2C2] hover: border-[#C2C2C2]'> + <Link to={"/register"}>Register</Link> + </button> + <button className='w-40 rounded-3xl border-2 px-12 py-2 hover:bg-deepPurple'> + <Link to={"/login"}>Login</Link> + </button> + </div> + <div className='hidden max-sm:flex'> + <button onClick={toggleUserMenu} className=''> + <img src={user} alt='User Menu' className='w-10 h-10'/> + </button> + {showUser && <UserMenu/>} + </div> </div> </nav> - ); -} \ No newline at end of file + </div> + ) +} + +export default Navbar \ No newline at end of file diff --git a/sth-frontend/src/features/landingpage/Landingpage.jsx b/sth-frontend/src/pages/Landingpage.jsx similarity index 71% rename from sth-frontend/src/features/landingpage/Landingpage.jsx rename to sth-frontend/src/pages/Landingpage.jsx index 2c789b1..8f94b54 100644 --- a/sth-frontend/src/features/landingpage/Landingpage.jsx +++ b/sth-frontend/src/pages/Landingpage.jsx @@ -1,10 +1,10 @@ import React from "react"; -import {Features, Footer, Header, Modes, Navbar, Partner} from './components' +import {Features, Header, Modes, Partner} from '../features/landingpage/components' +import Footer from "../layouts/Footer"; const Landingpage = () => { return ( <div className='bg-darkGray scroll-smooth'> - <Navbar/> <Header/> <Modes/> <Features/> diff --git a/sth-frontend/src/styles/tailwind.css b/sth-frontend/src/styles/tailwind.css index 1a40185..3cb0031 100644 --- a/sth-frontend/src/styles/tailwind.css +++ b/sth-frontend/src/styles/tailwind.css @@ -4,6 +4,6 @@ @layer base { body { - @apply bg-gray-100 p-0 m-0 + @apply p-0 m-0 bg-darkGray } } \ No newline at end of file diff --git a/sth-frontend/src/pages/Home.jsx b/sth-frontend/src/trash/Home.jsx similarity index 71% rename from sth-frontend/src/pages/Home.jsx rename to sth-frontend/src/trash/Home.jsx index 00ff157..cc83edf 100644 --- a/sth-frontend/src/pages/Home.jsx +++ b/sth-frontend/src/trash/Home.jsx @@ -1,11 +1,11 @@ -import Navbar from "../layouts/Navbar"; +import Navbar from "./NavbarTemp"; import {Outlet} from "react-router-dom"; export default function Home() { return ( <div> <Navbar /> - <div className={'p-6'}> + <div className={''}> <Outlet /> </div> </div> diff --git a/sth-frontend/src/layouts/Sidebar.jsx b/sth-frontend/src/trash/Sidebar.jsx similarity index 100% rename from sth-frontend/src/layouts/Sidebar.jsx rename to sth-frontend/src/trash/Sidebar.jsx diff --git a/sth-frontend/src/utils/router.jsx b/sth-frontend/src/utils/router.jsx index 8204e7b..5d7d01b 100644 --- a/sth-frontend/src/utils/router.jsx +++ b/sth-frontend/src/utils/router.jsx @@ -1,7 +1,7 @@ import {createBrowserRouter} from "react-router-dom"; import Tournament from "../features/tournament/components/Tournament"; import App from "../App"; -import Landingpage from "../features/landingpage/Landingpage"; +import Landingpage from "../pages/Landingpage"; import LoginPage from "../pages/LoginPage"; import Registerpage from "../pages/RegisterPage"; import {EmailProvider} from "../features/auth/components/EmailContext"; @@ -12,33 +12,23 @@ const router = createBrowserRouter([ element: <App/>, children: [ {path: "", element: <Tournament/>}, - {path: "/login", element: <LoginPage />} + {path: "/login", element: <LoginPage/>}, + { + path: "/register", + element: ( + <EmailProvider> + <Registerpage/> + </EmailProvider> + ) + }, + {path: "/landingpage", element: <EmailProvider><Landingpage/></EmailProvider>}, + { + path: "/tournament/:tournamentId", + element: <Tournament/> + }, ] }, - { - path:"/landingpage", - element: ( - <EmailProvider> - <Landingpage /> - </EmailProvider> - ) - }, - { - path: "/tournament/:tournamentId", - element: <Tournament /> - }, - { - path: "/login", - element: <LoginPage /> - }, - { - path: "/register", - element: ( - <EmailProvider> - <Registerpage/> - </EmailProvider> - ) - } + ]) -- GitLab