diff --git a/sth-backend/pom.xml b/sth-backend/pom.xml index 962a3454c1b2ed33f5d841ef24ff6fb2b2d1594b..2a414ac5ec05879e10ea9fa8b5e76d5fb3f3b53f 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 a00ecde935646154f54e8750c02dec1faee19d76..824e449a2a1beb57f84f7700135a9c000a88a5e7 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 0000000000000000000000000000000000000000..b2a83eb50f37dd79e66aa9d63d93b8a2ab8f7e5d --- /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 b7817bc674dfde66074547129b98af42edf0cdbe..d6d631a12762eb077847b7e4422b19852b338d6c 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 cf7a0f83fe9570915d9a0ce9c607d6ea4d8c035a..4d1466fb69c4ef11637050b41a3331fdcff2f17f 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 984b9411faf09d506846992c62ab10be376d0eb8..297ad05b7b5195297ee0f2384a57d80128245979 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 88ea9f26679f9537d073d529ead3e80a86afd745..14605d1415ed8d6ae7ada17f5ca0b751187620ea 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 ca897edd2fa0cbd361c1654b119c4e2a4dff5210..31e200be57fb293450bbc37278708bca6c354c0c 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 470e09b0a851699b18e38de5e52cf7240a7ec3d7..3fad64e0bc23032773d4d903704946c7b67f8f18 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 d60764458c51703145b00d2be5508303528addd5..497dc6c25bacf4979facfce1a9f5dbc5cfb49425 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 0000000000000000000000000000000000000000..dd1ddb344b1b4b028db550caf71dc5b818708983 --- /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 694637039d4829b3151bdbf8c2f5ea80a971b532..9eb53b5e7a470a9897cfafe6962a183730eef869 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 59111eceffc4aa40ef2c633cc371758b108d6815..8a02afd52b7c194f52179a2079091e0e44e82253 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 2187dad685fcc23a8ea4b660588192a7fe8653c5..869ab851c57495ec25695222c90f5a1faab2d4bd 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 0000000000000000000000000000000000000000..5987ff8e2bad8dd7edf23f537ae7ce979e68f98b --- /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 b9b87b0298b36e7bcfbaa5ad9db004e65b13ee23..0000000000000000000000000000000000000000 --- 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 ca5c7819b030d9bafd2715a737ad38b77bae007e..c6d4beb5c00531d24e307490f0d264e361e5f463 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 a1b3deff2fd91ce050ef85584795efe1e36947ff..a09093ff8f553f5e824909458771b44686a7edce 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 91ecf20d8f308845ae891904d5d567ee7f39c7bb..3368d79619f72e81f8419d31f7ef78921fbe0314 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 f420ceddc9ada0cbdb8c3105b1bf9b98c471c60a..d8f4d5e4fa8d20a17813aa16410990689b10f155 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 0de1e1cda0505f7d2b97be0511fcceb162f0fe61..65ec5c9ab289ef20c32c28d39fddde1db22189bc 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 2c789b15794b27d2b678e5ddb27cab1f1c297616..8f94b54af56544c0e9beb7a407f11a8801e12ef9 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 1a401856c0da5e7744517c293bfb6e68820fdaa7..3cb0031e795a45edf7864a0e2aa2b85c85e708ca 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 00ff1578b7666493048c797261dd9c68a2cba8c1..cc83edf5d1e702a3fb953189be5b7e58493b7190 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 8204e7b1d4e4fd9894f32744aad980e0f5ff8018..5d7d01b8a3c48a0dbc0de3530b8524f68a5bd2eb 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> - ) - } + ])