From d85b518be051a879f0f2d223f327b05187d4eeb8 Mon Sep 17 00:00:00 2001 From: Victor Hugo Ferreira Silva Date: Fri, 17 Nov 2023 11:46:20 -0300 Subject: [PATCH] :sparkles: feat: Implement Login Validations #20 #19, Implement Token and Name Local Storage Persistence #18, Home Route Definition #16, Product Backlog Route Definition #17, Implement Spinner in Login Page #21. Co-authored-by: ajuliamm Co-authored-by: CaiquePrado Co-authored-by: ChaiCaroline --- .../controller/AuthController.java | 21 ++- .../advice/ExceptionControllerAdvice.java | 8 + .../scrumtrackerapi/model/TokenMessage.java | 67 +++++++++ .../security/config/CorsConfig.java | 34 ----- .../security/config/SecurityFilters.java | 1 - .../security/service/TokenService.java | 2 +- .../src/main/resources/banner.txt | 6 + .../scrum-tracker-ui/package-lock.json | 43 +++++- .../scrum-tracker-ui/package.json | 2 + .../src/components/Header/index.tsx | 2 +- .../src/components/ProductBackLog/index.tsx | 8 +- .../src/components/ProductBackLog/list.tsx | 138 +++++++++--------- .../src/components/ProductBackLog/styles.ts | 22 ++- .../src/components/Projects/styles.ts | 2 +- .../src/components/Spinner/index.tsx | 16 ++ .../src/components/Spinner/styles.ts | 90 ++++++++++++ .../src/connections/api/index.ts | 2 +- .../src/contexts/UserContext.tsx | 14 ++ .../scrum-tracker-ui/src/pages/Home/index.tsx | 25 +++- .../src/pages/Login/index.tsx | 72 +++++++-- .../src/utlis/TokenService.tsx | 19 +++ .../scrum-tracker-ui/src/utlis/Validation.tsx | 40 +++++ 22 files changed, 496 insertions(+), 138 deletions(-) create mode 100644 scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/model/TokenMessage.java delete mode 100644 scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/config/CorsConfig.java create mode 100644 scrum-tracker-back-end/scrum-tracker-api/src/main/resources/banner.txt create mode 100644 scrum-tracker-front-end/scrum-tracker-ui/src/components/Spinner/index.tsx create mode 100644 scrum-tracker-front-end/scrum-tracker-ui/src/components/Spinner/styles.ts create mode 100644 scrum-tracker-front-end/scrum-tracker-ui/src/utlis/TokenService.tsx create mode 100644 scrum-tracker-front-end/scrum-tracker-ui/src/utlis/Validation.tsx diff --git a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/controller/AuthController.java b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/controller/AuthController.java index 26b7246..e1e9df4 100644 --- a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/controller/AuthController.java +++ b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/controller/AuthController.java @@ -2,6 +2,7 @@ import javax.naming.AuthenticationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; @@ -9,14 +10,20 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; + +import com.db.scrumtrackerapi.model.Customer; +import com.db.scrumtrackerapi.model.TokenMessage; import com.db.scrumtrackerapi.model.dto.LoginDTO; import com.db.scrumtrackerapi.security.service.TokenService; +import com.db.scrumtrackerapi.services.CustomerService; @RestController +@CrossOrigin("http://localhost:5173/") public class AuthController { @Autowired @@ -25,14 +32,20 @@ public class AuthController { @Autowired private TokenService tokenService; - @RequestMapping(value = "/login", method = RequestMethod.GET) - public String login(@RequestBody LoginDTO login) throws AuthenticationException { + @Autowired + private CustomerService customerService; + + @RequestMapping(value = "/login", method = RequestMethod.POST) + public ResponseEntity login(@RequestBody LoginDTO login) throws AuthenticationException { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(login.getEmail(), login.getPassword()); try { Authentication authenticate = this.authenticationManager.authenticate(usernamePasswordAuthenticationToken); UserDetails user = (UserDetails) authenticate.getPrincipal(); - return tokenService.generateToken(user); + Customer customer = customerService.findByEmail(user.getUsername()).get(); + String token = tokenService.generateToken(user); + TokenMessage responseBody = new TokenMessage(token, customer.getName(), customer.getLastName()); + return ResponseEntity.ok().body(responseBody); } catch (DisabledException ex) { throw new AuthenticationException("User " + login.getEmail() + " is disabled."); @@ -44,6 +57,4 @@ public String login(@RequestBody LoginDTO login) throws AuthenticationException throw new AuthenticationException("Bad credentials."); } } - - } diff --git a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/controller/advice/ExceptionControllerAdvice.java b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/controller/advice/ExceptionControllerAdvice.java index e577061..dce5b6b 100644 --- a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/controller/advice/ExceptionControllerAdvice.java +++ b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/controller/advice/ExceptionControllerAdvice.java @@ -11,9 +11,17 @@ import com.db.scrumtrackerapi.model.ErrorMessage; import jakarta.persistence.EntityExistsException; +import jakarta.validation.ConstraintViolationException; @ControllerAdvice public class ExceptionControllerAdvice { + + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ResponseEntity manipularExcecoesDeValidacao(ConstraintViolationException ex) { + ErrorMessage response = new ErrorMessage("Validation failed.", HttpStatus.BAD_REQUEST.value(), ex.getMessage()); + return ResponseEntity.badRequest().body(response); + } @ExceptionHandler(EntityExistsException.class) public ResponseEntity handleEntityExistsException(EntityExistsException ex) { diff --git a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/model/TokenMessage.java b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/model/TokenMessage.java new file mode 100644 index 0000000..1d8be8f --- /dev/null +++ b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/model/TokenMessage.java @@ -0,0 +1,67 @@ +package com.db.scrumtrackerapi.model; +import java.util.Objects; + +public class TokenMessage { + private String Token; + private String name; + private String lastName; + + protected TokenMessage() { + } + + public TokenMessage(String Token, String name, String lastName) { + this.Token = Token; + this.name = name; + this.lastName = lastName; + } + + public String getToken() { + return this.Token; + } + + public void setToken(String Token) { + this.Token = Token; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLastName() { + return this.lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + @Override + public boolean equals(Object o) { + if (o == this) + return true; + if (!(o instanceof TokenMessage)) { + return false; + } + TokenMessage tokenMessage = (TokenMessage) o; + return Objects.equals(Token, tokenMessage.Token) && Objects.equals(name, tokenMessage.name) && Objects.equals(lastName, tokenMessage.lastName); + } + + @Override + public int hashCode() { + return Objects.hash(Token, name, lastName); + } + + @Override + public String toString() { + return "{" + + " Token='" + getToken() + "'" + + ", name='" + getName() + "'" + + ", lastName='" + getLastName() + "'" + + "}"; + } + +} diff --git a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/config/CorsConfig.java b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/config/CorsConfig.java deleted file mode 100644 index d276050..0000000 --- a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/config/CorsConfig.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.db.scrumtrackerapi.security.config; - -import java.util.Arrays; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -/** - * Configuration class for CORS (Cross-Origin Resource Sharing). - * - *

This configuration allows requests from specified origins and methods. - */ -@Configuration -public class CorsConfig { - - /** - * Configures CORS settings. - * - * @return CorsConfigurationSource with the specified CORS settings. - */ - @Bean - CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173")); - configuration.setAllowedMethods(Arrays.asList("GET","POST")); - configuration.setMaxAge(3600L); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - return source; - } -} diff --git a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/config/SecurityFilters.java b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/config/SecurityFilters.java index f3d561c..c2c157a 100644 --- a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/config/SecurityFilters.java +++ b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/config/SecurityFilters.java @@ -32,7 +32,6 @@ public class SecurityFilters { @Bean public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http - .cors(withDefaults()) //should be enabled in production environment .csrf(csrf -> csrf.disable()) .sessionManagement(session -> diff --git a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/service/TokenService.java b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/service/TokenService.java index a470820..a0da418 100644 --- a/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/service/TokenService.java +++ b/scrum-tracker-back-end/scrum-tracker-api/src/main/java/com/db/scrumtrackerapi/security/service/TokenService.java @@ -17,7 +17,7 @@ public class TokenService { public String generateToken(UserDetails userDetails) { return JWT.create().withIssuer(issuer) .withSubject(userDetails.getUsername()) - .withExpiresAt(LocalDateTime.now().plusMinutes(10).toInstant(ZoneOffset.of("-03:00"))) + .withExpiresAt(LocalDateTime.now().plusHours(1L).toInstant(ZoneOffset.of("-03:00"))) .sign(Algorithm.HMAC256(secret)); } diff --git a/scrum-tracker-back-end/scrum-tracker-api/src/main/resources/banner.txt b/scrum-tracker-back-end/scrum-tracker-api/src/main/resources/banner.txt new file mode 100644 index 0000000..206f126 --- /dev/null +++ b/scrum-tracker-back-end/scrum-tracker-api/src/main/resources/banner.txt @@ -0,0 +1,6 @@ +/=\ /=\ | + \ /=: /= | | /=\=\ | /= /=| /=: =/ /=\ /= +\=/ \=: | \=/ | | | | | \=| \=: |\ \= | + +Scrum Tracker v1.0.0 +Powered by Spring Boot v3.1.5 diff --git a/scrum-tracker-front-end/scrum-tracker-ui/package-lock.json b/scrum-tracker-front-end/scrum-tracker-ui/package-lock.json index b8a2264..d9f9a49 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/package-lock.json +++ b/scrum-tracker-front-end/scrum-tracker-ui/package-lock.json @@ -9,9 +9,11 @@ "version": "0.0.0", "dependencies": { "axios": "^1.6.1", + "cors": "^2.8.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.18.0", + "react-toastify": "^9.1.3", "styled-components": "^6.1.1", "ts-jest": "^29.1.1" }, @@ -4513,6 +4515,14 @@ "node": ">=12" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4574,6 +4584,18 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -8590,7 +8612,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9094,6 +9115,18 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", "dev": true }, + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -9948,6 +9981,14 @@ "node": ">=10.12.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", diff --git a/scrum-tracker-front-end/scrum-tracker-ui/package.json b/scrum-tracker-front-end/scrum-tracker-ui/package.json index 6711fa2..d75854c 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/package.json +++ b/scrum-tracker-front-end/scrum-tracker-ui/package.json @@ -12,9 +12,11 @@ }, "dependencies": { "axios": "^1.6.1", + "cors": "^2.8.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.18.0", + "react-toastify": "^9.1.3", "styled-components": "^6.1.1", "ts-jest": "^29.1.1" }, diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/components/Header/index.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/components/Header/index.tsx index 9efced0..518a85f 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/components/Header/index.tsx +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/components/Header/index.tsx @@ -37,7 +37,7 @@ const Header = () => { {modalOpenSignOut && } - Fulano
Souza + {localStorage.getItem("name")}
{localStorage.getItem("lastName")}
diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/index.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/index.tsx index b7f091d..6d13b58 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/index.tsx +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/index.tsx @@ -1,4 +1,4 @@ -import { ContainerProduct } from './styles'; +import { ContainerProduct, Status } from './styles'; import { teste } from './list' export default function ProductBackLog() { @@ -18,11 +18,11 @@ export default function ProductBackLog() { {teste.map((product) => { return ( -

{product.nome}

+

{product.name}

{product.history}

{product.status}

-

{product.prioridade}

-

{product.estimativa}

+

{product.priority}

+

{product.estimate}

) })} diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/list.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/list.tsx index 018c8ee..fad1c58 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/list.tsx +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/list.tsx @@ -1,187 +1,187 @@ export const teste = [ { id: 0, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 1, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "low", + estimate: "3 dias" }, { id: 2, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "medium", + estimate: "3 dias" }, { id: 3, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 4, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 5, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "low", + estimate: "3 dias" }, { id: 6, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "low", + estimate: "3 dias" }, { id: 7, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "low", + estimate: "3 dias" }, { id: 8, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "low", + estimate: "3 dias" }, { id: 9, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 10, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 11, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 12, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 13, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 14, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 15, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "medium", + estimate: "3 dias" }, { id: 16, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "medium", + estimate: "3 dias" }, { id: 17, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "medium", + estimate: "3 dias" }, { id: 18, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "medium", + estimate: "3 dias" }, { id: 19, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 20, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 21, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" }, { id: 22, - nome: "Chaiene", + name: "Chaiene", history: "Lorem ipsum dolor sit amet consectetur, adipisicing elit. Quam obcaecati aperiam molestiae enim, beatae quas accusantium sed ad possimus magni autem ullam culpa non fugiat facere doloremque! Enim itaque non eaque, ea asperiores magni dolor dolores odio vitae at alias, exercitationem vel totam dolorum id atque tenetur. Id, dicta deserunt.", status: "Em andamento", - prioridade: "alta", - estimativa: "3 dias" + priority: "high", + estimate: "3 dias" } ] \ No newline at end of file diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/styles.ts b/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/styles.ts index 9e5c993..fbcc519 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/styles.ts +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/components/ProductBackLog/styles.ts @@ -1,6 +1,6 @@ import styled from "styled-components" -export const ContainerProduct = styled.div` +export const ContainerProduct = styled.section` width: 88%; background-color: ${props => props.theme.COLORS.white}; @@ -81,3 +81,23 @@ table{ } ` + +const STATUS_COLOR = { + medium: "yellow-200", + low: "green-300", + high: "red-500", +} as const; + +interface StatusProps { + $statusColor: keyof typeof STATUS_COLOR; +} + +export const Status = styled.span` + display: flex; + align-items: center; + justify-content:center; + + border-radius:${props => props.theme.BORDERRADIUS.lg}; + background-color: ${(props) => + props.theme.COLORS[STATUS_COLOR[props.$statusColor]]}; +`; \ No newline at end of file diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/components/Projects/styles.ts b/scrum-tracker-front-end/scrum-tracker-ui/src/components/Projects/styles.ts index 63b5a69..fd8233a 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/components/Projects/styles.ts +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/components/Projects/styles.ts @@ -1,6 +1,6 @@ import styled from "styled-components" -export const ProjectsMain = styled.div` +export const ProjectsMain = styled.section` h1{ ${props => props.theme.HEADLINE.xl} diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/components/Spinner/index.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/components/Spinner/index.tsx new file mode 100644 index 0000000..395230f --- /dev/null +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/components/Spinner/index.tsx @@ -0,0 +1,16 @@ +import { SpinnerContainer } from "./styles" + +export default function Spinner() { + return ( + +
+
+
+
+
+
+
+
+
+ ) +} diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/components/Spinner/styles.ts b/scrum-tracker-front-end/scrum-tracker-ui/src/components/Spinner/styles.ts new file mode 100644 index 0000000..6079063 --- /dev/null +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/components/Spinner/styles.ts @@ -0,0 +1,90 @@ +import styled from "styled-components" + +export const SpinnerContainer = styled.div` + margin: 0 auto; + align-self: center; + justify-self: center; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + > div { + width: 2rem; + height: 2rem; + position: relative; + animation: sk-chase 2.5s infinite linear both; + .sk-chase-dot { + width: 100%; + height: 100%; + position: absolute; + left: 0; + top: 0; + animation: sk-chase-dot 2s infinite ease-in-out both; + } + .sk-chase-dot:before { + content: ""; + display: block; + width: 25%; + height: 25%; + background-color: ${(props) => props.theme.COLORS["green-300"]}; + border-radius: 100%; + animation: sk-chase-dot-before 2s infinite ease-in-out both; + } + .sk-chase-dot:nth-child(1) { + animation-delay: -1.1s; + } + .sk-chase-dot:nth-child(2) { + animation-delay: -1s; + } + .sk-chase-dot:nth-child(3) { + animation-delay: -0.9s; + } + .sk-chase-dot:nth-child(4) { + animation-delay: -0.8s; + } + .sk-chase-dot:nth-child(5) { + animation-delay: -0.7s; + } + .sk-chase-dot:nth-child(6) { + animation-delay: -0.6s; + } + .sk-chase-dot:nth-child(1):before { + animation-delay: -1.1s; + } + .sk-chase-dot:nth-child(2):before { + animation-delay: -1s; + } + .sk-chase-dot:nth-child(3):before { + animation-delay: -0.9s; + } + .sk-chase-dot:nth-child(4):before { + animation-delay: -0.8s; + } + .sk-chase-dot:nth-child(5):before { + animation-delay: -0.7s; + } + .sk-chase-dot:nth-child(6):before { + animation-delay: -0.6s; + } + @keyframes sk-chase { + 100% { + transform: rotate(360deg); + } + } + @keyframes sk-chase-dot { + 80%, + 100% { + transform: rotate(360deg); + } + } + @keyframes sk-chase-dot-before { + 50% { + transform: scale(0.4); + } + 100%, + 0% { + transform: scale(1); + } + } + } +` diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/connections/api/index.ts b/scrum-tracker-front-end/scrum-tracker-ui/src/connections/api/index.ts index c4ade78..59873b1 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/connections/api/index.ts +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/connections/api/index.ts @@ -3,7 +3,7 @@ import axios from "axios"; const Api = axios.create({ baseURL: "http://localhost:8080", timeout: 10000, - headers: { "Contente-Type": "application/json" } + headers: { "Contente-Type": "application/json"} }); export default Api; \ No newline at end of file diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/contexts/UserContext.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/contexts/UserContext.tsx index 99e8c52..392ade2 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/contexts/UserContext.tsx +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/contexts/UserContext.tsx @@ -9,6 +9,10 @@ interface ContextProps { setModalOpenSignOut: React.Dispatch>; openModal:boolean; setOpenModal: React.Dispatch>; + openComponentProject: boolean; + setOpenComponentProject: React.Dispatch>; + openComponentProduct: boolean; + setOpenComponentProduct: React.Dispatch>; } const GlobalContext = createContext({ @@ -16,17 +20,27 @@ const GlobalContext = createContext({ setModalOpenSignOut: (): boolean => false, openModal: false, setOpenModal: (): boolean => false, + openComponentProject: true, + setOpenComponentProject: (): boolean => true, + openComponentProduct: false, + setOpenComponentProduct: (): boolean => false, }); export const GlobalContextProvider = ({ children }: GlobalContextProviderProps) => { const [modalOpenSignOut, setModalOpenSignOut] = useState(false); const [openModal, setOpenModal] = useState(false); + const [openComponentProject, setOpenComponentProject] = useState(true); + const [openComponentProduct, setOpenComponentProduct] = useState(false); const props = { modalOpenSignOut, setModalOpenSignOut, openModal, setOpenModal, + openComponentProject, + setOpenComponentProject, + openComponentProduct, + setOpenComponentProduct }; return ( diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/pages/Home/index.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/pages/Home/index.tsx index 3960792..0065594 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/pages/Home/index.tsx +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/pages/Home/index.tsx @@ -3,11 +3,27 @@ import { useEffect } from 'react'; import { useGlobalContext } from '../../contexts/UserContext'; import { HomeContainer, NavBar, HomeContent } from './styles'; import ModalRegisterProduct from '../../components/ModalRegisterProduct'; +import Projects from '../../components/Projects'; +import ProductBackLog from '../../components/ProductBackLog'; export default function Home(){ - const {openModal, setOpenModal} = useGlobalContext(); + const { openModal, setOpenModal, openComponentProject, setOpenComponentProject, openComponentProduct, setOpenComponentProduct } = useGlobalContext(); + + function handleComponent(event: { event?: Event | undefined }) { + + const saida = (event.event?.target as HTMLElement).textContent; + if (saida === "Home") { + setOpenComponentProject(true); + setOpenComponentProduct(false) + } else if (saida === "Product Backlog") { + setOpenComponentProject(false); + setOpenComponentProduct(true) + } + + } + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -30,8 +46,8 @@ export default function Home(){
    -
  • Home
  • -
  • Product Backlog
  • +
  • handleComponent({ event })}>Home
  • +
  • handleComponent({ event })}>Product Backlog
  • Sprints
@@ -42,7 +58,8 @@ export default function Home(){
-

Meus projetos

+ {openComponentProject && } + {openComponentProduct && }
diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/pages/Login/index.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/pages/Login/index.tsx index d62ceda..719aec9 100644 --- a/scrum-tracker-front-end/scrum-tracker-ui/src/pages/Login/index.tsx +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/pages/Login/index.tsx @@ -1,29 +1,70 @@ -import {Container, FormContainer} from "./styles"; +import { Container, FormContainer } from "./styles"; import Logo from '../../assets/Logo.svg'; import IconEmail from '../../assets/email.svg'; import IconSenha from '../../assets/senha.svg'; -import {useState} from "react"; +import { useState } from "react"; import api from "../../connections/api"; +import { toast } from "react-toastify"; +import 'react-toastify/dist/ReactToastify.css'; +import { validateEmail } from "../../utlis/Validation"; +import { useNavigate } from "react-router-dom"; +import { tokenService } from "../../utlis/TokenService"; +import Spinner from "../../components/Spinner"; +import { AxiosError } from 'axios'; + export default function Login(){ const [email, setEmail] = useState("") const [password, setPassword] = useState(""); + const [loading,setLoading] = useState(false); + + const navigate = useNavigate(); async function handleSubmit(e: React.FormEvent){ e.preventDefault() + + //Valida o Campo Email + const validationEmail = validateEmail(email) + if(!validationEmail.isValid){ + return( + toast.error(validationEmail.message, { + }))} + + if(!password){ + return( + toast.error("Campo Senha é obrigatório") + ) + } try { - const response = await api.get("/hello/authenticated",{ - headers : { - authorization: `Basic ${btoa(email+":"+password)}` - }}); - - console.log(response); - - } catch(error){ - return console.log(error); - - } + setLoading(true); + const response = await api.post("/login",{ + email: email, + password: password + }); + const token = response.data.token; + + //salvando no localStorage + tokenService.save("token", token) + tokenService.save("name", response.data.name) + tokenService.save("lastName", response.data.lastName) + + if(tokenService.get("token")){ + setLoading(false); + toast.success("Login efetuado com sucesso", {}); + navigate("/"); + } + + } catch(error){ + //console.log(error) + if(error instanceof AxiosError && error.response?.data.exceptionMessage === "Bad credentials."){ + return ( + setLoading(false), + toast.error("Email ou senha inválido", { + }) + ) + } + } } return ( @@ -38,7 +79,7 @@ export default function Login(){
- )=>{setEmail(e.target.value)}} type="email" placeholder="Digite seu e-mail"/> + )=>{setEmail(e.target.value)}} type="text" placeholder="Digite seu e-mail"/> Ícone de e-mail
@@ -46,7 +87,8 @@ export default function Login(){ )=>{setPassword(e.target.value)}} placeholder="Digite sua senha"/> Ícone de senha
- + {loading ? : } + diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/utlis/TokenService.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/utlis/TokenService.tsx new file mode 100644 index 0000000..56fc010 --- /dev/null +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/utlis/TokenService.tsx @@ -0,0 +1,19 @@ +export const tokenService = { + + save(key: string, value: string){ + return localStorage.setItem(key,value); + }, + + get(key: string){ + return localStorage?.getItem(key); + }, + + delete(key: string){ + return localStorage?.removeItem(key); + }, + + clear(){ + return localStorage.clear(); + } +} + diff --git a/scrum-tracker-front-end/scrum-tracker-ui/src/utlis/Validation.tsx b/scrum-tracker-front-end/scrum-tracker-ui/src/utlis/Validation.tsx new file mode 100644 index 0000000..4ec0c7a --- /dev/null +++ b/scrum-tracker-front-end/scrum-tracker-ui/src/utlis/Validation.tsx @@ -0,0 +1,40 @@ +function validateEmail(email: string) { + if (!email || email.trim() === '') { + return { isValid: false, message: 'O campo E-mail é obrigatório' }; + } + + if (email !== email.trim()) { + return { isValid: false, message: 'E-mail inválido' }; + } + + if (!email.includes('@') || !email.includes('.')) { + return { isValid: false, message: 'E-mail inválido' }; + } + + if (email[0] === '.' || email[email.length - 1] === '.') { + return { isValid: false, message: 'E-mail inválido' }; + } + + const atIndex = email.indexOf('@'); + const dotIndex = email.indexOf('.', atIndex); + + if (dotIndex === 0 || dotIndex === email.length - 1) { + return { isValid: false, message: 'E-mail inválido' }; + } + + for (let i = 1; i < email.length - 1; i++) { + if (email[i] === '.' && email[i - 1] === '.') { + return { isValid: false, message: 'E-mail inválido' }; + } + if (email[i] === '.' || email[i] === '@') { + if (email[i - 1] === '.' || email[i - 1] === '@' || email[i + 1] === '.' || email[i + 1] === '@') { + return { isValid: false, message: 'E-mail inválido' }; + } + } + } + + return { isValid: true }; +} + + +export {validateEmail} \ No newline at end of file