diff --git a/api/src/main/java/cz/cvut/fel/sem/controller/QuizController.java b/api/src/main/java/cz/cvut/fel/sem/controller/QuizController.java index a763f9b09647417162b62aa062b3eba2a85bacf1..beb2a38bff7ce89799630c585fd742e50856f19c 100644 --- a/api/src/main/java/cz/cvut/fel/sem/controller/QuizController.java +++ b/api/src/main/java/cz/cvut/fel/sem/controller/QuizController.java @@ -24,13 +24,16 @@ public class QuizController { } @CrossOrigin - @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Void> createQuiz(@RequestBody QuizDto quizDto) { - quizService.saveQuiz(quizDto); + @PostMapping(value = "/{userId}", consumes = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<Void> createQuiz(@PathVariable Long userId, @RequestBody QuizDto quizDto) { + quizService.saveQuiz(quizDto, userId); final HttpHeaders headers = RestUtils.createLocationHeaderFromCurrentUri("/{id}", 1); return new ResponseEntity<>(headers, HttpStatus.CREATED); } + /** + @param id id of the quiz user wants to fetch + */ @CrossOrigin @DeleteMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.NO_CONTENT) @@ -38,15 +41,21 @@ public class QuizController { quizService.deleteQuizById(id); } +// /** +// @param id id of the quiz user wants to fetch +// */ +// @CrossOrigin +// @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) +// public Quiz getQuiz(@PathVariable Long id) { +// return quizService.getQuizById(id); +// } + + /** + @param userId id of the user whose quizzes are being fetched + */ @CrossOrigin - @GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public Quiz getQuiz(@PathVariable Long id) { - return quizService.getQuizById(id); - } - - @CrossOrigin - @GetMapping(produces = MediaType.APPLICATION_JSON_VALUE) - public List<QuizDto> getAll() { - return quizService.getAllQuizzes(); + @GetMapping(value = "/{userId}", produces = MediaType.APPLICATION_JSON_VALUE) + public List<QuizDto> getAll(@PathVariable Long userId) { + return quizService.getAllQuizzesOfUser(userId); } } diff --git a/api/src/main/java/cz/cvut/fel/sem/controller/UserController.java b/api/src/main/java/cz/cvut/fel/sem/controller/UserController.java index 204ae01d2f8105b80dd6e0c424a3e13e4893ba13..3d367ed59a45fe19a8dbc71856820f61972d559d 100644 --- a/api/src/main/java/cz/cvut/fel/sem/controller/UserController.java +++ b/api/src/main/java/cz/cvut/fel/sem/controller/UserController.java @@ -1,6 +1,7 @@ package cz.cvut.fel.sem.controller; import cz.cvut.fel.sem.controller.util.RestUtils; +import cz.cvut.fel.sem.dto.user.UserDto; import cz.cvut.fel.sem.model.User; import cz.cvut.fel.sem.service.UserService; import org.springframework.beans.factory.annotation.Autowired; @@ -28,10 +29,10 @@ public class UserController { @CrossOrigin @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<Void> createUser(@RequestBody User user) { - userService.persist(user); + public ResponseEntity<Long> createUser(@RequestBody User user) { + Long userId = userService.persist(user); final HttpHeaders headers = RestUtils.createLocationHeaderFromCurrentUri("/{id}", user.getId()); - return new ResponseEntity<>(headers, HttpStatus.CREATED); + return new ResponseEntity<Long>(userId, headers, HttpStatus.CREATED); } @CrossOrigin @@ -40,8 +41,11 @@ public class UserController { return userService.exists(email); } - /*@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public User getUserById(@PathVariable Long id) { - return userService.getUserById(id); - }*/ + @CrossOrigin + @PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<UserDto> loginUser(@RequestBody User user) { + final HttpHeaders headers = RestUtils.createLocationHeaderFromCurrentUri("/value"); + UserDto userToReturn = userService.checkLoginUser(user); + return new ResponseEntity<>(userToReturn, headers, HttpStatus.OK); + } } diff --git a/api/src/main/java/cz/cvut/fel/sem/dto/user/UserDto.java b/api/src/main/java/cz/cvut/fel/sem/dto/user/UserDto.java new file mode 100644 index 0000000000000000000000000000000000000000..3bf4eddfa771b359d632cccca2a405fe4d3974d0 --- /dev/null +++ b/api/src/main/java/cz/cvut/fel/sem/dto/user/UserDto.java @@ -0,0 +1,13 @@ +package cz.cvut.fel.sem.dto.user; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class UserDto { + private Long id; + private String email; +} diff --git a/api/src/main/java/cz/cvut/fel/sem/model/User.java b/api/src/main/java/cz/cvut/fel/sem/model/User.java index a7c21e4f537681ec390fced39d4363222f686f68..7ce3bf6c0d50bde1de2ef8b5661df9195afbeb4c 100644 --- a/api/src/main/java/cz/cvut/fel/sem/model/User.java +++ b/api/src/main/java/cz/cvut/fel/sem/model/User.java @@ -1,9 +1,20 @@ package cz.cvut.fel.sem.model; +import cz.cvut.fel.sem.model.quizQuestion.Quiz; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import org.springframework.security.crypto.password.PasswordEncoder; import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; +@Getter +@AllArgsConstructor +@Setter +@NoArgsConstructor @Entity // We can't name the table User, as it is a reserved table name in some dbs, including Postgres @Table(name = "USER_TABLE") @@ -20,21 +31,8 @@ public class User extends AbstractEntity { @Column(nullable = false) private String password; - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } + @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) + private List<Quiz> quizzes = new ArrayList<>(); public void encodePassword(PasswordEncoder passwordEncoder){ this.password = passwordEncoder.encode(this.password); diff --git a/api/src/main/java/cz/cvut/fel/sem/model/quizQuestion/Quiz.java b/api/src/main/java/cz/cvut/fel/sem/model/quizQuestion/Quiz.java index 98df499fbf39e5f3556f0bcba398a48706da9588..a1815006a4f276ac05e36e09a8df9e256f501dde 100644 --- a/api/src/main/java/cz/cvut/fel/sem/model/quizQuestion/Quiz.java +++ b/api/src/main/java/cz/cvut/fel/sem/model/quizQuestion/Quiz.java @@ -1,6 +1,7 @@ package cz.cvut.fel.sem.model.quizQuestion; import cz.cvut.fel.sem.model.AbstractEntity; +import cz.cvut.fel.sem.model.User; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -8,6 +9,7 @@ import lombok.Setter; import javax.persistence.CascadeType; import javax.persistence.Entity; +import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import java.util.List; @@ -21,7 +23,6 @@ public class Quiz extends AbstractEntity { private List<Question> questions; private String name; - //implementing with user will be done later - //@ManyToOne - //private User owner; + @ManyToOne + private User owner; } diff --git a/api/src/main/java/cz/cvut/fel/sem/repository/QuizRepository.java b/api/src/main/java/cz/cvut/fel/sem/repository/QuizRepository.java index 0a96f6b2a4b4e1c96cb6f364d17bffb18dda50a9..bab79fbde872f2308227a6f240539c74724838ed 100644 --- a/api/src/main/java/cz/cvut/fel/sem/repository/QuizRepository.java +++ b/api/src/main/java/cz/cvut/fel/sem/repository/QuizRepository.java @@ -1,8 +1,11 @@ package cz.cvut.fel.sem.repository; +import cz.cvut.fel.sem.model.User; import cz.cvut.fel.sem.model.quizQuestion.Quiz; import org.springframework.data.jpa.repository.JpaRepository; -public interface QuizRepository extends JpaRepository<Quiz, Long> { +import java.util.List; +public interface QuizRepository extends JpaRepository<Quiz, Long> { + public List<Quiz> findAllByOwner(User user); } diff --git a/api/src/main/java/cz/cvut/fel/sem/service/QuizService.java b/api/src/main/java/cz/cvut/fel/sem/service/QuizService.java index 99dbd0ef77a302e14a9ebc7e7fe7e23a33d509c5..615a3bfc29af1d44a42c4339e5916f2a8a0b6d4b 100644 --- a/api/src/main/java/cz/cvut/fel/sem/service/QuizService.java +++ b/api/src/main/java/cz/cvut/fel/sem/service/QuizService.java @@ -3,10 +3,12 @@ package cz.cvut.fel.sem.service; import cz.cvut.fel.sem.dto.question.QuizDto; import cz.cvut.fel.sem.exception.NotFoundException; import cz.cvut.fel.sem.mapper.QuizMapper; +import cz.cvut.fel.sem.model.User; import cz.cvut.fel.sem.model.quizQuestion.Question; import cz.cvut.fel.sem.model.quizQuestion.Quiz; import cz.cvut.fel.sem.repository.QuestionRepository; import cz.cvut.fel.sem.repository.QuizRepository; +import cz.cvut.fel.sem.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -14,23 +16,28 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; @Service public class QuizService { private final QuizRepository quizRepository; private final QuizMapper quizMapper; private final QuestionRepository questionRepository; + private final UserRepository userRepository; @Autowired - public QuizService(QuizRepository quizRepository, QuizMapper quizMapper, QuestionRepository questionRepository) { + public QuizService(QuizRepository quizRepository, QuizMapper quizMapper, QuestionRepository questionRepository, UserRepository userRepository) { this.quizRepository = quizRepository; this.quizMapper = quizMapper; this.questionRepository = questionRepository; + this.userRepository = userRepository; } @Transactional - public void saveQuiz(QuizDto quizDto) { + public void saveQuiz(QuizDto quizDto, Long userId) { Objects.requireNonNull(quizDto); + Objects.requireNonNull(userId); + User owner = getUser(userId); //If the quiz is being updated, delete its questions first to prevent database duplicating if(quizDto.getId() != null){ Quiz originalQuiz = quizRepository.getOne(quizDto.getId()); @@ -39,6 +46,7 @@ public class QuizService { } } Quiz quizToSave = quizMapper.mapToModel(quizDto); + quizToSave.setOwner(owner); quizRepository.save(quizToSave); } @@ -58,12 +66,23 @@ public class QuizService { } @Transactional - public List<QuizDto> getAllQuizzes(){ - List<Quiz> quizzes = quizRepository.findAll(); + public List<QuizDto> getAllQuizzesOfUser(Long id){ + User owner = getUser(id); + List<Quiz> quizzes = quizRepository.findAllByOwner(owner); List<QuizDto> quizDtos = new ArrayList<>(); + for(Quiz quiz : quizzes){ quizDtos.add(quizMapper.mapToDto(quiz)); } return quizDtos; } + + @Transactional + public User getUser(Long id){ + Optional<User> ownerOptional = userRepository.findById(id); + if(ownerOptional.isEmpty()){ + throw new NotFoundException("User with id " + id + " was not found"); + } + return ownerOptional.get(); + } } diff --git a/api/src/main/java/cz/cvut/fel/sem/service/UserService.java b/api/src/main/java/cz/cvut/fel/sem/service/UserService.java index 574ffda4d10a52ba74127b30608c8228a05cbfac..d62fc03d3efc33505613100e8585b745efae4043 100644 --- a/api/src/main/java/cz/cvut/fel/sem/service/UserService.java +++ b/api/src/main/java/cz/cvut/fel/sem/service/UserService.java @@ -1,9 +1,12 @@ package cz.cvut.fel.sem.service; +import cz.cvut.fel.sem.dto.user.UserDto; import cz.cvut.fel.sem.exception.NotFoundException; import cz.cvut.fel.sem.model.User; import cz.cvut.fel.sem.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,10 +28,11 @@ public class UserService { } @Transactional - public void persist(User user) { + public Long persist(User user) { Objects.requireNonNull(user); user.encodePassword(passwordEncoder); userRepository.save(user); + return user.getId(); } @Transactional @@ -55,4 +59,20 @@ public class UserService { public boolean exists(String email) { return userRepository.findByEmail(email) != null; } + + private boolean checkCredentials(User user, User userToCheck){ + return passwordEncoder.matches(user.getPassword(), userToCheck.getPassword()); + } + + @Transactional + public UserDto checkLoginUser(User loginDataFromUser){ + if(!exists(loginDataFromUser.getEmail())){ + return new UserDto(0L, ""); + } + User userToCheck = getUserByEmail(loginDataFromUser.getEmail()); + if(checkCredentials(loginDataFromUser, userToCheck)){ + return new UserDto(userToCheck.getId(), userToCheck.getEmail()); + } + return new UserDto(0L, ""); + } } diff --git a/api/src/test/java/cz/cvut/fel/sem/mapper/QuizMapperTest.java b/api/src/test/java/cz/cvut/fel/sem/mapper/QuizMapperTest.java index 13dde49322d71ed1c1d9f604f9f2af32317fd4af..fea2aa5b91c6c8e2c688171cb9382e71c0e1205a 100644 --- a/api/src/test/java/cz/cvut/fel/sem/mapper/QuizMapperTest.java +++ b/api/src/test/java/cz/cvut/fel/sem/mapper/QuizMapperTest.java @@ -58,7 +58,8 @@ public class QuizMapperTest { public void mapToDto_validArgumentsProvided_dtoObjectReturned(){ //Arrange List<Question> questionList = List.of(new Question(), new Question()); - Quiz quiz = new Quiz(questionList, "quizName"); + //TODO rewrite this, it does not work after adding owner to the quiz + Quiz quiz = new Quiz(questionList, "quizName", null); List<QuestionDto> dtoList = List.of(new QuestionDto(), new QuestionDto()); when(questionMapper.mapListToDto(quiz.getQuestions())).thenReturn(dtoList); diff --git a/api/src/test/java/cz/cvut/fel/sem/service/QuizServiceTest.java b/api/src/test/java/cz/cvut/fel/sem/service/QuizServiceTest.java index b12f7c7d8ff641533f9df2395642c7634c094ce5..ca33fb6f55398c1cac04a7cd7720c4d5e0b36f1f 100644 --- a/api/src/test/java/cz/cvut/fel/sem/service/QuizServiceTest.java +++ b/api/src/test/java/cz/cvut/fel/sem/service/QuizServiceTest.java @@ -55,7 +55,8 @@ public class QuizServiceTest { when(quizRepository.findAll()).thenReturn(quizList); //Act - List<QuizDto> dtoQuizzes = sut.getAllQuizzes(); + //TODO rewrite this, it does not work after adding owner to the quiz + List<QuizDto> dtoQuizzes = sut.getAllQuizzesOfUser(0L); //Verify assertEquals(2, dtoQuizzes.size()); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..48e341a0954d5f8c2accf3a6731be28e5bb9c0de --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3 @@ +{ + "lockfileVersion": 1 +} diff --git a/ui/src/Layout/Header.js b/ui/src/Layout/Header.js index 9850b9ef1eb3f39a79fcfc810fe5eaa9b623c9cf..2b992d819e7aaa3a277ae87f81b9ffc1ddc20a6a 100644 --- a/ui/src/Layout/Header.js +++ b/ui/src/Layout/Header.js @@ -1,13 +1,15 @@ import { Grid, Button, Popover, TextField, Typography } from "@mui/material"; -import React from "react"; -import { useLocation, useHistory } from "react-router-dom"; -import { useRef, useState } from 'react'; +import React, { useRef, useState } from "react"; +import { useLocation, useHistory, NavLink } from "react-router-dom"; +import { useUser, UserStatus } from "../context/UserContext" export default function Header(props) { const location = useLocation(); const history = useHistory(); const quizName = useRef(null); + const currentUser = useUser() + //state used to define where the popover, which contains the new question type options, should get displayed (around which tag) const [anchorEl, setAnchorEl] = useState(null); @@ -34,97 +36,119 @@ export default function Header(props) { return ( <> - <header> - <Grid - container - direction="row" - spacing={0} - alignItems="center" - justifyContent="space-between" - sx={{ - height:45, - minHeight:45, - borderBottom:1 - }} - - > - {/* - <Link to="/"> - <img alt="Carved Rock Fitness" src="/images/logo.png" /> - </Link> - */} - <Grid item sx={{paddingLeft:2}}> - </Grid> - <Grid item> - {location.pathname === '/home' && - <Button - color="primary" - variant="contained" - size="small" - onClick={handlePopoverChange} - sx={{textTransform: 'none'}} - > - Create new quiz - </Button> - } - {location.pathname === '/quiz' && - <Typography sx={{fontWeight: 'bold'}}> - Currently creating/updating quiz: {history.location.state.name} - </Typography> - } - </Grid> - <Grid item sx={{paddingRight:2}}> - {/* <NavLink activeStyle={activeStyle} to="/"> - Logout - </NavLink> */} - </Grid> + {location.pathname === '/login' || location.pathname === '/registration' ? + <> + </> + : + <> + <header> + <Grid + container + direction="row" + spacing={0} + alignItems="center" + justifyContent="space-between" + sx={{ + height:45, + minHeight:45, + borderBottom:1 + }} + > + {/* + <Link to="/"> + <img alt="Carved Rock Fitness" src="/images/logo.png" /> + </Link> + */} + <Grid item sx={{paddingLeft:2}}> + {(location.pathname === '/' && currentUser.status === UserStatus.Logged) && + <div style={{ fontWeight: 'bold' }}> + {currentUser.email.split("@")[0]} + </div> + } + </Grid> + <Grid item> + {(location.pathname === '/' && currentUser.status === UserStatus.Logged) && + <Button + color="primary" + variant="contained" + size="small" + onClick={handlePopoverChange} + sx={{textTransform: 'none'}} + > + Create new quiz + </Button> + } + {location.pathname === '/quiz' && + <Typography sx={{fontWeight: 'bold'}}> + Currently creating/updating quiz: {history.location.state.name} + </Typography> + } + </Grid> + <Grid item sx={{paddingRight:2}}> + {(location.pathname === '/' && currentUser.status === UserStatus.Logged) && + <NavLink + style={({ isActive }) => ({ + color: isActive ? '#fff' : '#545e6f', + borderRadius: '4px', + padding: 2, + fontWeight: 'bold' + })} + to={"/login"} + > + Logout + </NavLink> + } + </Grid> + + </Grid> + </header> + <Popover + id={id} + open={open} + anchorEl={anchorEl} + onClose= {handlePopoverClose} + anchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'center' + }} + > + <Grid + container + alignItems="center" + spacing={1} + justifyContent="flex-start" + sx={{padding:2, width: 300}} + > + <Grid item xs={12}> + <TextField + id="theQuestion" + size='small' + inputProps={{min: 0, style: { textAlign: 'center' }}} + placeholder="Name of quiz" + autoComplete="new-password" + fullWidth + inputRef={quizName} + /> + </Grid> + <Grid item xs={12} sx={{display: 'flex', justifyContent: 'center'}}> + <Button + color="secondary" + variant="contained" + sx={{textTransform: 'none'}} + onClick = {handleCreateClick} + > + Create + </Button> </Grid> - </header> - <Popover - id={id} - open={open} - anchorEl={anchorEl} - onClose= {handlePopoverClose} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'center', - }} - transformOrigin={{ - vertical: 'top', - horizontal: 'center' - }} - > - <Grid - container - alignItems="center" - spacing={1} - justifyContent="flex-start" - sx={{padding:2, width: 300}} - > - <Grid item xs={12}> - <TextField - id="theQuestion" - size='small' - inputProps={{min: 0, style: { textAlign: 'center' }}} - placeholder="Name of quiz" - autoComplete="new-password" - fullWidth - inputRef={quizName} - /> - </Grid> - <Grid item xs={12} sx={{display: 'flex', justifyContent: 'center'}}> - <Button - color="secondary" - variant="contained" - sx={{textTransform: 'none'}} - onClick = {handleCreateClick} - > - Create - </Button> </Grid> - </Grid> - </Popover> + </Popover> + </> +} </> ); } \ No newline at end of file diff --git a/ui/src/common/types.ts b/ui/src/common/types.ts index 61c17c764f26ac6e05182ce5e582aec2723e1666..99fb1de527a099a3ce4affbf02740995cf4642a1 100644 --- a/ui/src/common/types.ts +++ b/ui/src/common/types.ts @@ -60,3 +60,9 @@ export interface AnswerValues { bottomLeftAnswer: string; bottomRightAnswer: string; } + +export interface UserType { + id: number; + email: string; + status: string; +} \ No newline at end of file diff --git a/ui/src/context/UserContext.tsx b/ui/src/context/UserContext.tsx new file mode 100644 index 0000000000000000000000000000000000000000..82a17758cae955d625040d93165c6965a41b6c69 --- /dev/null +++ b/ui/src/context/UserContext.tsx @@ -0,0 +1,40 @@ +import React, { useContext, createContext, useState } from "react"; +import { UserType } from "../common/types"; + +export const UserStatus = { + Logged: "Logged", + NotLogged: "NotLogged" +} + +const initialLoggedUserState: UserType = { + id: 0, + email: "", + status: UserStatus.NotLogged +} + +const UserContext = createContext<UserType>(initialLoggedUserState) +const UserContextUpdate = createContext({}) + +export function useUser() { + return useContext(UserContext) +} + +export function useUserUpdate() { + return useContext(UserContextUpdate) +} + +export const UserProvider = ({ children }) => { + const [currentUser, setCurrentUser] = useState<UserType>(initialLoggedUserState); + + function changeUser(newUser: UserType) { + setCurrentUser(newUser) + } + + return ( + <UserContext.Provider value={currentUser}> + <UserContextUpdate.Provider value={changeUser}> + {children} + </UserContextUpdate.Provider> + </UserContext.Provider> + ) +} diff --git a/ui/src/pages/App.js b/ui/src/pages/App.js index c1e4444511845cfe98adc686cfb2e50fc2a60386..2e0237ec9bd7d699a99a78e1952fa0c7399d75b1 100644 --- a/ui/src/pages/App.js +++ b/ui/src/pages/App.js @@ -7,20 +7,23 @@ import Header from '../Layout/Header'; import Home from './home/Home'; import StartQuiz from './quizSession/teacher/StartQuiz'; import JoinQuiz from './quizSession/student/JoinQuiz'; +import { UserProvider } from '../context/UserContext' function App() { return ( <> - <Header /> - <Switch> - <Route exact path='/' component={LoginPage} /> - <Route path='/registration' component={RegistrationPage} /> - <Route path='/quiz' component={CreateQuiz} /> - <Route path='/startQuiz' component={StartQuiz} /> - <Route path='/joinQuiz' component={JoinQuiz} /> - <Route path='/home' component={Home}/> - <Route component={PageNotFound} /> - </Switch> + <UserProvider> + <Header /> + <Switch> + <Route exact path='/' component={Home} /> + <Route path='/registration' component={RegistrationPage} /> + <Route path='/quiz' component={CreateQuiz} /> + <Route path='/startQuiz' component={StartQuiz} /> + <Route path='/joinQuiz' component={JoinQuiz} /> + <Route path='/login' component={LoginPage}/> + <Route component={PageNotFound} /> + </Switch> + </UserProvider> </> ); } diff --git a/ui/src/pages/home/Home.js b/ui/src/pages/home/Home.js deleted file mode 100644 index 9eadccef82a845b96469ceb8a9abad76bce41ac4..0000000000000000000000000000000000000000 --- a/ui/src/pages/home/Home.js +++ /dev/null @@ -1,182 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Grid, Typography, TextField, Menu, MenuItem, InputAdornment } from "@mui/material"; -import { NavLink, useLocation } from "react-router-dom"; -import SettingsIcon from '@mui/icons-material/Settings'; - -const Home = () => { - - const [quizes, setQuizes] = useState([]); - - const findQuizById = (id) => { - let quizToReturn; - quizes.forEach(quiz => { - if(quiz.id === id){ - quizToReturn = quiz - } - }) - return quizToReturn - } - - const location = useLocation(); - - const anchorInitial = { - element: null, - id: 0 - } - - const [anchorEl, setAnchorEl] = React.useState(anchorInitial); - const open = Boolean(anchorEl.element); - - const handleOptionsOpen = (event, idToSet) => { - setAnchorEl({ - element: event.currentTarget, - id: idToSet - }); - }; - - const handleClose = () => { - setAnchorEl(anchorInitial); - }; - - const fetchAllQuizzes = () => { - fetch("http://localhost:8080/betterKahoot/quiz", { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - } - }) - .then(response => { - if(response.status === 200){ - return response.json() - } - }) - .then((quizes) => { - setQuizes(quizes); - }) - .catch(error => console.log(error)); - } - - const handleDeleteQuiz = () => { - console.log(anchorEl.id) - fetch("http://localhost:8080/betterKahoot/quiz/" + anchorEl.id, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - } - }) - .then(response => { - if(response.status === 204){ - handleClose() - fetchAllQuizzes() - } - }) - .catch(error => console.log(error)); - } - - useEffect(() => { - fetchAllQuizzes() - }, [location]) - - return ( - <Grid - container - direction="row" - alignItems="flex-start" - spacing={0} - justifyContent="flex-start" - sx={{ height:'calc(100vh - 45px)', width:'100%'}} - > - <Grid item xs={2} sx={{backgroundColor: '#E8FFFF', height: '100%'}}> - - </Grid> - <Grid item xs sx={{backgroundColor: 'white', height: '100%', padding: '20px 0 0 20px'}}> - <Grid - container - direction="column" - spacing={2} - > - {quizes.length === 0 ? - <Typography>You sadly don't have any created quizzes, create one using the button at the top!</Typography> - : - <> - <Grid item> - <Typography> - Your quizzes: - </Typography> - </Grid> - {quizes.map((quiz) => - <Grid - item key={quiz.id} id={quiz.id} - > - <TextField - id="demo-positioned-button" - disabled - value={quiz.name} - aria-controls={open ? 'demo-positioned-menu' : undefined} - aria-haspopup="true" - aria-expanded={open ? 'true' : undefined} - onClick={(event) => handleOptionsOpen(event, quiz.id)} - color="neutral" - size="small" - sx={{width: '300px', textTransform: 'none', borderRadius: 0}} - InputProps={{ - endAdornment: - <InputAdornment position="end"> - <SettingsIcon - onMouseEnter={event => event.currentTarget.style.cursor = "pointer"} - onMouseLeave={event => event.currentTarget.style.cursor = "default"} - /> - </InputAdornment>, - style: {fontWeight: "bold"} - } - } - /> - <Menu - id="demo-positioned-menu" - aria-labelledby="demo-positioned-button" - anchorEl={anchorEl.element} - open={open} - onClose={handleClose} - anchorOrigin={{ - vertical: 'center', - horizontal: 'right', - }} - transformOrigin={{ - vertical: 'center', - horizontal: 'left' - }} - > - <MenuItem - component={NavLink} - to={{ - pathname:"/quiz", - state: findQuizById(anchorEl.id) - }} - onClick={handleClose} - > - Edit - </MenuItem> - <MenuItem onClick={handleDeleteQuiz}>Delete</MenuItem> - <MenuItem - component={NavLink} - to={{ - pathname:"/startQuiz", - state: findQuizById(anchorEl.id) - }} - onClick={handleClose} - > - Start - </MenuItem> - </Menu> - </Grid> - )} - </> - } - - </Grid> - </Grid> - </Grid> - ) -} - -export default Home; \ No newline at end of file diff --git a/ui/src/pages/home/Home.tsx b/ui/src/pages/home/Home.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4dec510465d9acc062e7e457082a4f70d0a86e8e --- /dev/null +++ b/ui/src/pages/home/Home.tsx @@ -0,0 +1,110 @@ +import React, { useEffect, useState } from "react"; +import { useLocation } from "react-router-dom"; +import { useUser, UserStatus } from "../../context/UserContext"; +import { UserType } from "../../common/types"; +import LoggedUserHome from "./loggedUser/LoggedUserHome"; +import GuestHome from "./guest/GuestHome" +import { Quiz } from "../../common/types"; + +export interface AnchorType { + element: Element | null; + id: number; +} + +const Home = () => { + const [quizes, setQuizes] = useState<Array<Quiz>>([]); + + const currentUser: UserType = useUser(); + + const location = useLocation(); + + const findQuizById = (id: number): Quiz => { + let quizToReturn; + quizes.forEach((quiz) => { + if (quiz.id === id) { + quizToReturn = quiz; + } + }); + return quizToReturn; + }; + + const anchorInitial: AnchorType = { + element: null, + id: 0, + }; + const [anchorEl, setAnchorEl] = React.useState<AnchorType>(anchorInitial); + const open = Boolean(anchorEl.element); + + const handleOptionsOpen = (event, idToSet: number) => { + setAnchorEl({ + element: event.currentTarget, + id: idToSet, + }); + }; + + const handleClose = () => { + setAnchorEl(anchorInitial); + }; + + const fetchAllQuizzes = (id: number) => { + fetch("http://localhost:8080/betterKahoot/quiz/" + id, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status !== 200) { + throw new Error("Something went wrong :("); + } + return response.json(); + }) + .then((quizes) => { + setQuizes(quizes); + }) + .catch((error) => console.log(error)); + }; + + const handleDeleteQuiz = () => { + fetch("http://localhost:8080/betterKahoot/quiz/" + anchorEl.id, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => { + if (response.status === 204) { + handleClose(); + fetchAllQuizzes(currentUser.id); + } + }) + .catch((error) => console.log(error)); + }; + + useEffect(() => { + if(currentUser.status !== UserStatus.Logged){ + return + } + fetchAllQuizzes(currentUser.id); + }, [location]); + + return ( + <> + {currentUser.status === UserStatus.Logged ? + <LoggedUserHome + quizes={quizes} + anchorEl={anchorEl} + handleClose={handleClose} + findQuizById={findQuizById} + handleDeleteQuiz={handleDeleteQuiz} + open={open} + handleOptionsOpen={handleOptionsOpen} + /> + : + <GuestHome /> + } + </> + ); +}; + +export default Home; diff --git a/ui/src/pages/home/guest/GuestHome.tsx b/ui/src/pages/home/guest/GuestHome.tsx new file mode 100644 index 0000000000000000000000000000000000000000..812f98fe1a54177345668acc3f54fec0ae93b245 --- /dev/null +++ b/ui/src/pages/home/guest/GuestHome.tsx @@ -0,0 +1,68 @@ +import { Button, Grid } from "@mui/material" +import { useHistory } from 'react-router-dom' + + + +const GuestHome = () => { + const history = useHistory() + + const handleButtons = (event): void => { + history.push("/" + event.target.id) + } + + return ( + <> + <Grid + container + direction="column" + alignItems="center" + spacing={0} + justifyContent="center" + sx={{ + height: "calc(100vh - 45px)", + width: "100%", + margin: 0 + }} + > + <Grid item> + <Button + id="joinQuiz" + color="primary" + variant="contained" + size="large" + sx={{width: 250, marginBottom: 5}} + onClick={handleButtons} + > + Join a quiz + </Button> + </Grid> + <Grid item> + <Button + id="login" + color="primary" + variant="contained" + size="large" + sx={{width: 250, marginBottom: 5}} + onClick={handleButtons} + > + Login + </Button> + </Grid> + <Grid item> + <Button + id="registration" + color="primary" + variant="contained" + size="large" + sx={{width: 250}} + onClick={handleButtons} + > + Register + </Button> + </Grid> + </Grid> + </> + ) +} + +export default GuestHome \ No newline at end of file diff --git a/ui/src/pages/home/loggedUser/LoggedUserHome.tsx b/ui/src/pages/home/loggedUser/LoggedUserHome.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8e654566609014c192d4c27df7ad3a9c987ac79e --- /dev/null +++ b/ui/src/pages/home/loggedUser/LoggedUserHome.tsx @@ -0,0 +1,150 @@ +//@ts-ignore + +import React from "react"; + +import { + Grid, + Typography, + TextField, + Menu, + MenuItem, + InputAdornment, +} from "@mui/material"; +import SettingsIcon from "@mui/icons-material/Settings"; +import { NavLink } from "react-router-dom"; + +import { AnchorType } from "../Home"; +import { Quiz } from "../../../common/types"; + +interface LoggedUserHomeProps { + quizes: Array<Quiz>; + findQuizById(id: number): Quiz; + handleDeleteQuiz(): void; + handleClose(): void; + anchorEl: AnchorType; + open: boolean; + handleOptionsOpen(event, id: number): void; +} + +export default function + + +LoggedUserHome({ + quizes, + findQuizById, + handleDeleteQuiz, + handleClose, + anchorEl, + open, + handleOptionsOpen, +}: LoggedUserHomeProps) { + return ( + <Grid + container + direction="row" + spacing={0} + sx={{ + height: "calc(100vh - 45px)", + width: "100%", + }} + > + <Grid + item + xs + sx={{ + backgroundColor: "white", + height: "100%", + padding: "40px 0 0 20px", + }} + > + <Grid container direction="column" spacing={2} alignItems="center" justifyContent="center"> + {quizes.length === 0 ? ( + <Typography fontWeight={"bold"}> + You sadly don't have any created quizzes, create one using the + button at the top! + </Typography> + ) : ( + <> + <Grid item> + <Typography fontWeight={"bold"}>Your quizzes:</Typography> + </Grid> + {quizes.map((quiz) => ( + <Grid item key={quiz.id} id={quiz.id?.toString()}> + <TextField + id="demo-positioned-button" + disabled + value={quiz.name} + aria-controls={open ? "demo-positioned-menu" : undefined} + aria-haspopup="true" + aria-expanded={open ? "true" : undefined} + onClick={(event) => handleOptionsOpen(event, quiz.id)} + size="small" + sx={{ + width: "300px", + textTransform: "none", + borderRadius: 0, + }} + InputProps={{ + endAdornment: ( + <InputAdornment position="end"> + <SettingsIcon + onMouseEnter={(event) => + (event.currentTarget.style.cursor = "pointer") + } + onMouseLeave={(event) => + (event.currentTarget.style.cursor = "default") + } + /> + </InputAdornment> + ), + style: { + fontWeight: "bold", + }, + }} + /> + <Menu + id="demo-positioned-menu" + aria-labelledby="demo-positioned-button" + anchorEl={anchorEl.element} + open={open} + onClose={handleClose} + anchorOrigin={{ + vertical: "center", + horizontal: "right", + }} + transformOrigin={{ + vertical: "center", + horizontal: "left", + }} + > + <MenuItem + component={NavLink} + to={{ + pathname: "/quiz", + state: findQuizById(anchorEl.id), + }} + onClick={handleClose} + > + Edit + </MenuItem> + <MenuItem onClick={handleDeleteQuiz}>Delete</MenuItem> + <MenuItem + component={NavLink} + to={{ + pathname: "/startQuiz", + state: findQuizById(anchorEl.id), + }} + onClick={handleClose} + > + Start + </MenuItem> + </Menu> + </Grid> + ))} + </> + )} + </Grid> + </Grid> + </Grid> + ); +} diff --git a/ui/src/pages/login/LoginForm.js b/ui/src/pages/login/LoginForm.js deleted file mode 100644 index 7e9619fac0568fc1488ba472a69dd90ac529cc5b..0000000000000000000000000000000000000000 --- a/ui/src/pages/login/LoginForm.js +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react' -import { useState } from 'react'; -import { Link } from "react-router-dom"; -import { TextField, Grid, Box, Typography, Button } from "@mui/material"; -import { makeStyles } from '@mui/styles'; - -const useStyles = makeStyles((theme) => ({ - box: { - padding: theme.spacing(3), - }, -})); - -const LoginForm = () => { - const classes = useStyles(); - - const emptyLoginData = { - username: "", - password: "", - }; - - const [loginData, setLoginData] = useState(emptyLoginData); - - function handleChange(event){ - event.persist(); - setLoginData((loginData) => { - return {...loginData, [event.target.id]: event.target.value,} - }) - } - - function handleSubmit(event){ - event.preventDefault() - console.log(loginData) - } - - return ( - <Box className={classes.box} maxWidth="xs"> - <form onSubmit={handleSubmit}> - <Grid - container - direction="column" - alignItems="center" - spacing={3} - justifyContent="center" - > - <Grid item xs={3}> - <Typography variant="h4">Login</Typography> - </Grid> - <Grid item xs={3}> - <TextField - id="username" - label="Username" - size="small" - onChange={handleChange} - value={loginData.username} - sx={{width: 300}} - /> - </Grid> - <Grid item xs={3}> - <TextField - id="password" - label="Password" - size="small" - onChange={handleChange} - value={loginData.password} - sx={{width: 300}} - /> - </Grid> - <Grid item xs={3}> - <Button color="primary" variant="contained" type="submit">Submit</Button> - </Grid> - <Grid item xs={3}> - <Link to='registration'>Don't have an account yet?</Link> - </Grid> - </Grid> - </form> - </Box> - ) -} - -export default LoginForm; \ No newline at end of file diff --git a/ui/src/pages/login/LoginForm.tsx b/ui/src/pages/login/LoginForm.tsx new file mode 100644 index 0000000000000000000000000000000000000000..081a60d8fb827035fc4cbdd90920ada88cac2790 --- /dev/null +++ b/ui/src/pages/login/LoginForm.tsx @@ -0,0 +1,149 @@ +import React, { useEffect } from 'react' +import { useState } from 'react'; +import { Link } from "react-router-dom"; +import { TextField, Grid, Box, Typography, Button } from "@mui/material"; +import { makeStyles } from '@mui/styles'; +import { useUser, useUserUpdate, UserStatus } from '../../context/UserContext'; +import { UserType } from '../../common/types'; +import { ToastContainer } from 'react-toastify' +import { toast } from 'react-toastify' +import { useHistory } from 'react-router-dom' + +const useStyles = makeStyles((theme) => ({ + box: { + //@ts-ignore + padding: theme.spacing(3), + }, +})); + +const LoginForm = () => { + const classes = useStyles(); + const history = useHistory() + + const currentUser = useUser() + + const emptyLoginData = { + username: "", + password: "", + }; + + const [loginData, setLoginData] = useState(emptyLoginData); + const changeUser = useUserUpdate() + + useEffect(() => { + if(currentUser.status === UserStatus.Logged){ + history.push("/") + } + }, [currentUser, history]) + + function handleChange(event){ + event.persist(); + setLoginData((loginData) => { + return {...loginData, [event.target.id]: event.target.value} + }) + } + + const loginUser = (userData) => { + //if id is 0, the credentials user typed are not correct + if(userData.id === 0){ + toast.warn("The password and email don't match :(", { + position: "top-center", + autoClose: 7000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + }) + return + } + const userToLogin: UserType = { + id: userData.id, + email: userData.email, + status: UserStatus.Logged + } + //@ts-ignore + changeUser(userToLogin) + } + + function handleSubmit(event){ + event.preventDefault() + const dataToSend = {email: loginData.username, password: loginData.password} + fetch("http://localhost:8080/betterKahoot/users/login", { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(dataToSend), // body data type must match "Content-Type" header + }) + .then(response => { + if(response.status !== 200){ + throw new Error("Server was unable to handle the request") + } + return response.json() + }) + .then(responseData => loginUser(responseData)) + .catch(error => console.log(error)); + } + + return ( + <> + {/* Notifications for user, when he doesn't type correct email and password */} + <ToastContainer + position="top-center" + autoClose={7000} + hideProgressBar={false} + newestOnTop={false} + closeOnClick + rtl={false} + pauseOnFocusLoss + draggable + pauseOnHover + /> + <Box className={classes.box} maxWidth="xs"> + <form onSubmit={handleSubmit}> + <Grid + container + direction="column" + alignItems="center" + spacing={3} + justifyContent="center" + > + <Grid item xs={3}> + <Typography variant="h4">Login</Typography> + </Grid> + <Grid item xs={3}> + <TextField + id="username" + label="Username" + size="small" + onChange={handleChange} + value={loginData.username} + sx={{width: 300}} + /> + </Grid> + <Grid item xs={3}> + <TextField + id="password" + label="Password" + size="small" + type={"password"} + onChange={handleChange} + value={loginData.password} + sx={{width: 300}} + /> + </Grid> + <Grid item xs={3}> + <Button color="primary" variant="contained" type="submit">Submit</Button> + </Grid> + <Grid item xs={3}> + <Link to='registration'>Don't have an account yet?</Link> + </Grid> + </Grid> + </form> + </Box> + </> + ) +} + +export default LoginForm; \ No newline at end of file diff --git a/ui/src/pages/quiz/questionParameters/QuestionCreator.js b/ui/src/pages/quiz/questionParameters/QuestionCreator.js index 74c99dd8ecb522f0fca7f02c3eaa0d4a8cba84b5..ffb1abf6c0820e3dc8fff888e748d0e5ab652646 100644 --- a/ui/src/pages/quiz/questionParameters/QuestionCreator.js +++ b/ui/src/pages/quiz/questionParameters/QuestionCreator.js @@ -12,6 +12,7 @@ import { useHistory } from "react-router-dom"; import { toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { newQuestionTypes, languageTypes } from '../../../common/types'; +import { useUser } from '../../../context/UserContext'; const useStyles = makeStyles((theme) => ({ bottomButtons: { @@ -35,6 +36,8 @@ const QuestionCreator = (props) => { handleAnswerValueChange, validate, disabled, questionName, codeTextProp, languageProp} = props const currentQuestions = !disabled ? props.currentQuestions.questions : null; + const currentUser = useUser(); + //this is going to be sent to API on save the quiz button click let finalQuestions = [] @@ -184,7 +187,7 @@ const QuestionCreator = (props) => { return; } saveTheQuestion(true) - fetch("http://localhost:8080/betterKahoot/quiz", { + fetch("http://localhost:8080/betterKahoot/quiz/" + currentUser.id, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/ui/src/pages/quizSession/student/JoinQuiz.tsx b/ui/src/pages/quizSession/student/JoinQuiz.tsx index bbc5fe068a7547c363a497aaf67c71d60f9e616a..3bedf9f7d65f0c06bc52df5295f0503b4211ae9a 100644 --- a/ui/src/pages/quizSession/student/JoinQuiz.tsx +++ b/ui/src/pages/quizSession/student/JoinQuiz.tsx @@ -298,6 +298,7 @@ const JoinQuiz = () => { <> {/* Prompt which should be displayed to user when he leaves the page */} <Prompt + when={LayoutType.JoiningQuiz !== currentLayout} message="If you leave now, you won't be able to join this session anymore" /> {/* Notifications for user, for example when user tries to join the session which doesn't exist */} diff --git a/ui/src/pages/quizSession/teacher/StartQuiz.tsx b/ui/src/pages/quizSession/teacher/StartQuiz.tsx index 02625b14a4c4305f8a9713ca1980d03187672254..cdfeb99b29191aa105e272759dd1f488cf163740 100644 --- a/ui/src/pages/quizSession/teacher/StartQuiz.tsx +++ b/ui/src/pages/quizSession/teacher/StartQuiz.tsx @@ -56,12 +56,12 @@ interface EndSessionRequest { reason: string; } -export interface StudentScores { +export interface StudentScoresType { [key: string]: number } -interface StudentResults { - studentScores: StudentScores +interface StudentResultsType { + studentScores: StudentScoresType } const MessageType = { @@ -108,7 +108,7 @@ const StartQuiz = (props) => { const [currentQuizKey, setCurrentQuizKey] = useState<number>(0) const [currentLayout, setCurrentLayout] = useState<string>(LayoutType.UsersJoining) const [currentQuestionEvaluation, setCurrentQuestionEvaluation] = useState<QuestionEvaluationType | null>(null); - const [studentScores, setStudentScores] = useState<StudentScores | null>(null) + const [studentScores, setStudentScores] = useState<StudentScoresType | null>(null) //In current implementation when user closes the page, the function which sends the request to end the session is called. //This is unnecessary at the end of the quiz, this state helps to determine whether the request should be called or not const quizAlreadyEnded = useRef(false); @@ -146,7 +146,7 @@ const StartQuiz = (props) => { setCurrentLayout(LayoutType.ShowEvaluation) break case MessageType.StudentResults: - const studentResults: StudentResults = payloadData + const studentResults: StudentResultsType = payloadData setStudentScores(studentResults.studentScores) } } @@ -205,7 +205,7 @@ const StartQuiz = (props) => { if(lastQuestion){ sendRequestToEndTheQuiz(EndSessionReason.ENDOFQUIZ) quizAlreadyEnded.current = true - history.push("/home") + history.push("/") return } const nextMessageRequest: NextMessageRequest = { diff --git a/ui/src/pages/quizSession/teacher/studentResults/StudentResults.tsx b/ui/src/pages/quizSession/teacher/studentResults/StudentResults.tsx index f5aae0fb99b3afbe6facf2cb747c935929e0480c..69995bc5a8a225e2d4388d1b526eb16a7f1f1924 100644 --- a/ui/src/pages/quizSession/teacher/studentResults/StudentResults.tsx +++ b/ui/src/pages/quizSession/teacher/studentResults/StudentResults.tsx @@ -3,7 +3,7 @@ import MaterialTable from 'material-table' import './index.css' import { Button } from "@mui/material"; -import { StudentScores as StudentScoresType } from '../StartQuiz' +import { StudentScoresType } from '../StartQuiz' interface StudentResultsProps { studentScores: StudentScoresType | null; @@ -31,7 +31,7 @@ const getTopThree = (studentScores: StudentScoresType): Array<StudentInTable> => for (const [key, value] of Object.entries(studentScores)){ if(mostSuccessfulStudent === null){ mostSuccessfulStudent = {position: position, name: key, score: value} - } + } else if(mostSuccessfulStudent.score <= value){ mostSuccessfulStudent = {position: position, name: key, score: value} } diff --git a/ui/src/pages/registration/RegistrationForm.js b/ui/src/pages/registration/RegistrationForm.tsx similarity index 88% rename from ui/src/pages/registration/RegistrationForm.js rename to ui/src/pages/registration/RegistrationForm.tsx index 1e94c83597bc8e8ff074e72a9e596288c1680e8a..0498b8c3658eb5403b716a26184fc52e7567a04a 100644 --- a/ui/src/pages/registration/RegistrationForm.js +++ b/ui/src/pages/registration/RegistrationForm.tsx @@ -2,9 +2,13 @@ import React, { useState, useEffect } from 'react' import { Link } from "react-router-dom"; import { TextField, Grid, Box, Typography, Button } from "@mui/material"; import { makeStyles } from '@mui/styles'; +import { useUserUpdate, UserStatus } from '../../context/UserContext'; +import { UserType } from '../../common/types'; +import { useHistory } from 'react-router-dom' const useStyles = makeStyles((theme) => ({ box: { + //@ts-ignore padding: theme.spacing(3), }, error: { @@ -15,7 +19,11 @@ const useStyles = makeStyles((theme) => ({ })); const RegistrationForm = () => { - const classes = useStyles(); + const classes = useStyles() + + const history = useHistory() + + const changeUser = useUserUpdate() const touchedInitial = { email: false, @@ -88,7 +96,7 @@ const RegistrationForm = () => { checkErrors(); }, [registrationData, touched]) - function handleChange(event){ + function handleChange(event): void{ event.persist(); // To enable submit button // Without this user would first have to blur out of the text field to enable submit button @@ -102,14 +110,14 @@ const RegistrationForm = () => { }) } - function handleBlur(event){ + function handleBlur(event): void{ event.persist(); setTouched((cur) => { return { ...cur, [event.target.id]: true, lastTouched: event.target.id }; }); } - function handleSubmit(event){ + function handleSubmit(event): void{ event.preventDefault() const dataToSend = {email: registrationData.email, password: registrationData.password} fetch("http://localhost:8080/betterKahoot/users", { @@ -120,9 +128,20 @@ const RegistrationForm = () => { body: JSON.stringify(dataToSend), // body data type must match "Content-Type" header }) .then(response => { - if(response.status === 201){ - alert("you are now registered") + if(response.status !== 201){ + throw new Error("Something went wrong, sorry about that") + } + return response.json() + }) + .then(responseData => { + const loggedUser: UserType = { + id: parseInt(responseData), + email: registrationData.email, + status: UserStatus.Logged } + //@ts-ignore + changeUser(loggedUser) + history.push("/") }) .catch(error => console.log(error)); } @@ -164,7 +183,6 @@ const RegistrationForm = () => { value={registrationData.email} sx={{width: 300}} /> - {/* {errors.email && touched.email && <p className={classes.error} role="alert">{errors.email}</p>} */} </Grid> <Grid item xs={3}> <TextField @@ -199,7 +217,7 @@ const RegistrationForm = () => { </Button> </Grid> <Grid item xs={3}> - <Link to='/'>Already have an accout?</Link> + <Link to='/login'>Already have an account?</Link> </Grid> </Grid> </form> diff --git a/ui/src/pages/registration/RegistrationPage.js b/ui/src/pages/registration/RegistrationPage.js index 9065d2cc84f055f79bed0977066fdc8836f6d2f0..ca65f0ed924ae2e35f6e7b2027cc01ce4a78dd81 100644 --- a/ui/src/pages/registration/RegistrationPage.js +++ b/ui/src/pages/registration/RegistrationPage.js @@ -1,6 +1,6 @@ import React from "react"; import { Grid } from "@mui/material"; -import RegistrationForm from "./RegistrationForm.js"; +import RegistrationForm from "./RegistrationForm"; const RegistrationPage = () => (