Changeset 19398ad
- Timestamp:
- 05/16/24 23:09:21 (6 months ago)
- Branches:
- main
- Children:
- 28b3398
- Parents:
- d8b6c91
- Files:
-
- 28 added
- 33 edited
Legend:
- Unmodified
- Added
- Removed
-
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/JwtAuthFilter.java
rd8b6c91 r19398ad 44 44 token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 45 45 46 // SecurityContext securityContext = SecurityContextHolder.createEmptyContext();47 // securityContext.setAuthentication(token);48 // SecurityContextHolder.setContext(securityContext);49 50 46 SecurityContextHolder.getContext().setAuthentication(token); 51 47 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/SecurityConfiguration.java
rd8b6c91 r19398ad 2 2 3 3 import lombok.RequiredArgsConstructor; 4 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.enumerations.Role;5 4 import org.springframework.context.annotation.Bean; 6 5 import org.springframework.context.annotation.Configuration; … … 25 24 26 25 private final JwtAuthFilter jwtAuthFilter; 27 //private final UserService userService;28 26 private final UserDetailsService userDetailsService; 29 27 … … 31 29 public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 32 30 http.csrf(AbstractHttpConfigurer::disable) 33 .authorizeHttpRequests(request -> request.requestMatchers("/api/auth/**") 34 .permitAll() 35 .requestMatchers("/api/admin").hasAnyAuthority(Role.ROLE_ADMIN.name()) 36 .requestMatchers("/api/job-seeker").hasAnyAuthority(Role.ROLE_JOBSEEKER.name()) 37 .requestMatchers("/api/recruiter").hasAnyAuthority(Role.ROLE_RECRUITER.name()) 31 .authorizeHttpRequests(request -> request 32 .requestMatchers("/api/job-advertisements/all","/api/job-advertisements/view/**","/api/recruiter/info/**", "/api/auth/**").permitAll() 33 //.requestMatchers("/api/job-advertisements/**").hasAnyAuthority(Role.ROLE_RECRUITER.name()) 38 34 .anyRequest().authenticated()) 39 35 .sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/AdminController.java
rd8b6c91 r19398ad 2 2 3 3 import lombok.RequiredArgsConstructor; 4 import org.springframework.http.ResponseEntity;5 import org.springframework.web.bind.annotation.GetMapping;6 4 import org.springframework.web.bind.annotation.RequestMapping; 7 5 import org.springframework.web.bind.annotation.RestController; … … 12 10 public class AdminController { 13 11 14 15 @GetMapping16 public ResponseEntity<String> sayHello() {17 return ResponseEntity.ok("Hi Admin");18 }19 12 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/AuthController.java
rd8b6c91 r19398ad 12 12 import org.springframework.web.bind.annotation.*; 13 13 14 @CrossOrigin(origins = "*")15 14 @RestController 16 15 @RequestMapping("/api/auth") 17 16 @AllArgsConstructor 17 @CrossOrigin(origins = "*") 18 18 public class AuthController { 19 19 -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/JobSeekerController.java
rd8b6c91 r19398ad 2 2 3 3 4 import lombok.RequiredArgsConstructor; 4 import lombok.AllArgsConstructor; 5 import org.springframework.web.bind.annotation.CrossOrigin; 5 6 import org.springframework.web.bind.annotation.RequestMapping; 6 7 import org.springframework.web.bind.annotation.RestController; … … 8 9 @RestController 9 10 @RequestMapping("/api/job-seeker") 10 @RequiredArgsConstructor 11 @AllArgsConstructor 12 @CrossOrigin(origins = "*") 11 13 public class JobSeekerController { 12 14 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/RecruiterController.java
rd8b6c91 r19398ad 1 1 package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.controllers; 2 2 3 import lombok.RequiredArgsConstructor; 3 import lombok.AllArgsConstructor; 4 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.RecruiterDetailsDTO; 5 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.RecruiterService; 6 import org.springframework.http.HttpStatus; 4 7 import org.springframework.http.ResponseEntity; 5 import org.springframework.web.bind.annotation.GetMapping; 6 import org.springframework.web.bind.annotation.RequestMapping; 7 import org.springframework.web.bind.annotation.RestController; 8 import org.springframework.web.bind.annotation.*; 8 9 9 10 @RestController 10 11 @RequestMapping("/api/recruiter") 11 @RequiredArgsConstructor 12 @AllArgsConstructor 13 @CrossOrigin(origins = "*") 12 14 public class RecruiterController { 13 15 14 @GetMapping 15 public ResponseEntity<String> sayHello() { 16 return ResponseEntity.ok("Hi Recruiter"); 16 private final RecruiterService recruiterService; 17 18 @GetMapping("/info/{id}") 19 public ResponseEntity<?> getRecruiterDetailsById(@PathVariable("id") Long id) { 20 RecruiterDetailsDTO recruiterDetailsDTO = recruiterService.getRecruiterDetailsById(id); 21 return new ResponseEntity<>(recruiterDetailsDTO, HttpStatus.OK); 17 22 } 18 23 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/Admin.java
rd8b6c91 r19398ad 4 4 import jakarta.persistence.Table; 5 5 import lombok.AllArgsConstructor; 6 import lombok.Builder;7 6 import lombok.Data; 8 7 import lombok.NoArgsConstructor; 9 8 10 9 @Entity 11 //@Data 12 //@NoArgsConstructor 13 //@AllArgsConstructor 14 //@Builder 10 @Data 11 @NoArgsConstructor 15 12 @Table(name = "admins") 16 13 public class Admin extends User { 14 15 // private String name; 16 // private String surname; 17 17 18 @Override 18 19 public String getName() { 19 20 return "Admin"; 20 21 } 21 // private String name;22 // private String surname;23 22 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/DTO/JwtAuthResponse.java
rd8b6c91 r19398ad 10 10 @NoArgsConstructor 11 11 public class JwtAuthResponse { 12 private Long id; 12 13 private String email; 13 14 private String name; -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/JobSeeker.java
rd8b6c91 r19398ad 5 5 import jakarta.persistence.Table; 6 6 import lombok.AllArgsConstructor; 7 import lombok.Builder;8 7 import lombok.Data; 9 8 import lombok.NoArgsConstructor; … … 14 13 @NoArgsConstructor 15 14 @AllArgsConstructor 16 @Builder17 15 @Table(name = "job_seekers") 18 16 public class JobSeeker extends User { -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/User.java
rd8b6c91 r19398ad 15 15 16 16 @Entity 17 @Inheritance(strategy = InheritanceType.JOINED)18 @Table(name = "users")19 17 @Data 20 18 @NoArgsConstructor 21 19 @AllArgsConstructor 20 @Inheritance(strategy = InheritanceType.JOINED) 21 @Table(name = "users") 22 22 public abstract class User implements UserDetails { 23 23 -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/mappers/JobSeekerMapper.java
rd8b6c91 r19398ad 26 26 } 27 27 28 29 28 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/mappers/RecruiterMapper.java
rd8b6c91 r19398ad 2 2 3 3 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.RecruiterDTO; 4 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.RecruiterDetailsDTO; 4 5 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter; 5 6 … … 11 12 recruiter.getPassword(), 12 13 recruiter.getCompanyName(), 14 recruiter.getPhoneNumber() 15 ); 16 } 17 18 public static RecruiterDetailsDTO mapToRecruiterDetailsDTO(Recruiter recruiter) { 19 return new RecruiterDetailsDTO( 20 recruiter.getEmail(), 21 recruiter.getCompanyName(), 22 recruiter.getCompanyDescription(), 13 23 recruiter.getPhoneNumber() 14 24 ); -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/repositories/RecruiterRepository.java
rd8b6c91 r19398ad 2 2 3 3 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter; 4 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.mappers.RecruiterMapper; 4 5 import org.springframework.data.jpa.repository.JpaRepository; 5 6 6 7 public interface RecruiterRepository extends JpaRepository<Recruiter, Long> { 8 Recruiter findRecruiterByEmail(String email); 7 9 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/AuthServiceImpl.java
rd8b6c91 r19398ad 31 31 private final JwtService jwtService; 32 32 33 34 33 @Override 35 34 public User signUpJobSeeker(JobSeeker jobSeeker) { … … 50 49 String refreshJwt = jwtService.generateRefreshToken(new HashMap<>(), user); 51 50 52 return new JwtAuthResponse(user.get Email(), user.getName(), user.getRole().name(), jwt, refreshJwt);51 return new JwtAuthResponse(user.getId(), user.getEmail(), user.getName(), user.getRole().name(), jwt, refreshJwt); 53 52 } 54 53 … … 59 58 String jwt = jwtService.generateToken(user); 60 59 61 return new JwtAuthResponse(user.get Email(), user.getName(), user.getRole().name(), jwt, refreshTokenRequest.getToken());60 return new JwtAuthResponse(user.getId(), user.getEmail(), user.getName(), user.getRole().name(), jwt, refreshTokenRequest.getToken()); 62 61 } 63 62 return null; -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/JwtServiceImpl.java
rd8b6c91 r19398ad 41 41 } 42 42 43 public String extractUsername(String token) {44 return extractClaim(token, Claims::getSubject);45 }46 47 43 private Claims extractAllClaims(String token) { 48 44 return Jwts.parserBuilder().setSigningKey(getSigninKey()).build().parseClaimsJws(token).getBody(); … … 54 50 } 55 51 52 @Override 53 public String extractUsername(String token) { 54 return extractClaim(token, Claims::getSubject); 55 } 56 57 @Override 56 58 public boolean isTokenValid(String token, UserDetails userDetails) { 57 59 final String username = extractUsername(token); … … 59 61 } 60 62 61 62 p rivateboolean isTokenExpired(String token) {63 @Override 64 public boolean isTokenExpired(String token) { 63 65 return extractClaim(token, Claims::getExpiration).before(new Date()); 64 66 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/UserServiceImpl.java
rd8b6c91 r19398ad 1 1 package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.impl; 2 2 3 import lombok.AllArgsConstructor; 4 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.JobSeekerRepository; 5 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.RecruiterRepository; 6 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.UserRepository; 7 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.JwtService; 8 import org.springframework.security.authentication.AuthenticationManager; 9 import org.springframework.security.crypto.password.PasswordEncoder; 3 import lombok.RequiredArgsConstructor; 10 4 import org.springframework.stereotype.Service; 5 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.UserService; 11 6 12 7 @Service 13 @AllArgsConstructor 8 @RequiredArgsConstructor 9 public class UserServiceImpl implements UserService { 14 10 15 public class UserServiceImpl {16 private final UserRepository userRepository;17 private final RecruiterRepository recruiterRepository;18 private final JobSeekerRepository jobSeekerRepository;19 private final PasswordEncoder passwordEncoder;20 private final AuthenticationManager authenticationManager;21 private final JwtService jwtService;22 23 24 // @Override25 // public User registerJobSeeker(JobSeeker jobSeeker) {26 // jobSeeker.setPassword(passwordEncoder.encode(jobSeeker.getPassword()));27 // return jobSeekerRepository.save(jobSeeker);28 // }29 //30 // @Override31 // public User registerRecruiter(Recruiter recruiter) {32 // recruiter.setPassword(passwordEncoder.encode(recruiter.getPassword()));33 // return recruiterRepository.save(recruiter);34 // }35 //36 // @Override37 // public JwtAuthenticationResponse login(JwtAuthenticationRequest jwtAuthenticationRequest) {38 // authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(jwtAuthenticationRequest.getEmail(), jwtAuthenticationRequest.getPassword()));39 //40 // var user = userRepository.findByEmail(jwtAuthenticationRequest.getEmail()).orElseThrow(() -> new IllegalArgumentException("Email or password is incorrect"));41 // var jwt = jwtService.generateToken(user);42 // var refreshToken = jwtService.generateRefreshToken(new HashMap<>(), user);43 //44 // JwtAuthenticationResponse jwtAuthenticationResponse = new JwtAuthenticationResponse();45 // jwtAuthenticationResponse.setToken(jwt);46 // jwtAuthenticationResponse.setRefreshToken(refreshToken);47 // return jwtAuthenticationResponse;48 // }49 //50 // @Override51 // public JwtAuthenticationResponse refreshToken(RefreshTokenRequest refreshTokenRequest) {52 // String userEmail = jwtService.extractUsername(refreshTokenRequest.getToken());53 // User user = userRepository.findByEmail(userEmail).orElseThrow();54 // if(jwtService.isTokenValid(refreshTokenRequest.getToken(), user)) {55 // var jwt = jwtService.generateToken(user);56 //57 // JwtAuthenticationResponse jwtAuthenticationResponse = new JwtAuthenticationResponse();58 // jwtAuthenticationResponse.setToken(jwt);59 // jwtAuthenticationResponse.setRefreshToken(refreshTokenRequest.getToken());60 // return jwtAuthenticationResponse;61 // }62 // return null;63 // }64 11 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/JwtService.java
rd8b6c91 r19398ad 6 6 7 7 public interface JwtService { 8 9 8 String generateToken(UserDetails userDetails); 9 String generateRefreshToken(Map<String, Object> extraClaims, UserDetails userDetails); 10 10 String extractUsername(String token); 11 11 boolean isTokenValid(String token, UserDetails userDetails); 12 String generateRefreshToken(Map<String, Object> extraClaims, UserDetails userDetails);12 boolean isTokenExpired(String token); 13 13 14 14 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/UserService.java
rd8b6c91 r19398ad 1 1 package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef; 2 2 3 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.SignInDTO;4 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.JwtAuthResponse;5 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.RefreshTokenRequest;6 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.JobSeeker;7 3 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter; 8 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.User;9 4 10 5 public interface UserService { 11 User registerJobSeeker(JobSeeker jobSeeker); 12 User registerRecruiter(Recruiter recruiter); 13 JwtAuthResponse login(SignInDTO signInDTO); 14 JwtAuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest); 6 15 7 } -
jobvista-frontend/package-lock.json
rd8b6c91 r19398ad 21 21 "react": "^18.3.1", 22 22 "react-dom": "^18.3.1", 23 "react-hook-form": "^7.51.4", 23 24 "react-jwt": "^1.2.1", 24 25 "react-redux": "^9.1.2", 26 "react-responsive-modal": "^6.4.2", 25 27 "react-router": "^6.23.0", 26 28 "react-router-dom": "^6.23.0", 27 29 "react-scripts": "5.0.1", 30 "react-select": "^5.8.0", 28 31 "redux": "^5.0.1", 29 32 "redux-thunk": "^3.1.0", … … 2036 2039 "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" 2037 2040 }, 2041 "node_modules/@bedrock-layout/use-forwarded-ref": { 2042 "version": "1.6.1", 2043 "resolved": "https://registry.npmjs.org/@bedrock-layout/use-forwarded-ref/-/use-forwarded-ref-1.6.1.tgz", 2044 "integrity": "sha512-GD9A9AFLzFNjr7k6fgerSqxfwDWl+wsPS11PErOKe1zkVz0y7RGC9gzlOiX/JrgpyB3NFHWIuGtoOQqifJQQpw==", 2045 "dependencies": { 2046 "@bedrock-layout/use-stateful-ref": "^1.4.1" 2047 }, 2048 "peerDependencies": { 2049 "react": "^16.8 || ^17 || ^18" 2050 } 2051 }, 2052 "node_modules/@bedrock-layout/use-stateful-ref": { 2053 "version": "1.4.1", 2054 "resolved": "https://registry.npmjs.org/@bedrock-layout/use-stateful-ref/-/use-stateful-ref-1.4.1.tgz", 2055 "integrity": "sha512-4eKO2KdQEXcR5LI4QcxqlJykJUDQJWDeWYAukIn6sRQYoabcfI5kDl61PUi6FR6o8VFgQ8IEP7HleKqWlSe8SQ==", 2056 "peerDependencies": { 2057 "react": "^16.8 || ^17 || ^18" 2058 } 2059 }, 2038 2060 "node_modules/@csstools/normalize.css": { 2039 2061 "version": "12.1.1", … … 6319 6341 "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 6320 6342 }, 6343 "node_modules/body-scroll-lock": { 6344 "version": "3.1.5", 6345 "resolved": "https://registry.npmjs.org/body-scroll-lock/-/body-scroll-lock-3.1.5.tgz", 6346 "integrity": "sha512-Yi1Xaml0EvNA0OYWxXiYNqY24AfWkbA6w5vxE7GWxtKfzIbZM+Qw+aSmkgsbWzbHiy/RCSkUZBplVxTA+E4jJg==" 6347 }, 6321 6348 "node_modules/bonjour-service": { 6322 6349 "version": "1.2.1", … … 6599 6626 "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", 6600 6627 "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==" 6628 }, 6629 "node_modules/classnames": { 6630 "version": "2.5.1", 6631 "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", 6632 "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" 6601 6633 }, 6602 6634 "node_modules/clean-css": { … … 13177 13209 "node": ">= 4.0.0" 13178 13210 } 13211 }, 13212 "node_modules/memoize-one": { 13213 "version": "6.0.0", 13214 "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", 13215 "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" 13179 13216 }, 13180 13217 "node_modules/merge-descriptors": { … … 15706 15743 "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.4.tgz", 15707 15744 "integrity": "sha512-V14i8SEkh+V1gs6YtD0hdHYnoL4tp/HX/A45wWQN15CYr9bFRmmRdYStSO5L65lCCZRF+kYiSKhm9alqbcdiVA==", 15708 "peer": true,15709 15745 "engines": { 15710 15746 "node": ">=12.22.0" … … 15765 15801 "engines": { 15766 15802 "node": ">=0.10.0" 15803 } 15804 }, 15805 "node_modules/react-responsive-modal": { 15806 "version": "6.4.2", 15807 "resolved": "https://registry.npmjs.org/react-responsive-modal/-/react-responsive-modal-6.4.2.tgz", 15808 "integrity": "sha512-ARjGEKE5Gu5CSvyA8U9ARVbtK4SMAtdXsjtzwtxRlQIHC99RQTnOUctLpl7+/sp1Kg1OJZ6yqvp6ivd4TBueEw==", 15809 "dependencies": { 15810 "@bedrock-layout/use-forwarded-ref": "^1.3.1", 15811 "body-scroll-lock": "^3.1.5", 15812 "classnames": "^2.3.1" 15813 }, 15814 "funding": { 15815 "url": "https://github.com/sponsors/pradel" 15816 }, 15817 "peerDependencies": { 15818 "react": "^16.8.0 || ^17 || ^18", 15819 "react-dom": "^16.8.0 || ^17 || ^18" 15767 15820 } 15768 15821 }, … … 15869 15922 } 15870 15923 }, 15924 "node_modules/react-select": { 15925 "version": "5.8.0", 15926 "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz", 15927 "integrity": "sha512-TfjLDo58XrhP6VG5M/Mi56Us0Yt8X7xD6cDybC7yoRMUNm7BGO7qk8J0TLQOua/prb8vUOtsfnXZwfm30HGsAA==", 15928 "dependencies": { 15929 "@babel/runtime": "^7.12.0", 15930 "@emotion/cache": "^11.4.0", 15931 "@emotion/react": "^11.8.1", 15932 "@floating-ui/dom": "^1.0.1", 15933 "@types/react-transition-group": "^4.4.0", 15934 "memoize-one": "^6.0.0", 15935 "prop-types": "^15.6.0", 15936 "react-transition-group": "^4.3.0", 15937 "use-isomorphic-layout-effect": "^1.1.2" 15938 }, 15939 "peerDependencies": { 15940 "react": "^16.8.0 || ^17.0.0 || ^18.0.0", 15941 "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" 15942 } 15943 }, 15871 15944 "node_modules/react-transition-group": { 15872 15945 "version": "4.4.5", … … 18060 18133 "querystringify": "^2.1.1", 18061 18134 "requires-port": "^1.0.0" 18135 } 18136 }, 18137 "node_modules/use-isomorphic-layout-effect": { 18138 "version": "1.1.2", 18139 "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", 18140 "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", 18141 "peerDependencies": { 18142 "react": "^16.8.0 || ^17.0.0 || ^18.0.0" 18143 }, 18144 "peerDependenciesMeta": { 18145 "@types/react": { 18146 "optional": true 18147 } 18062 18148 } 18063 18149 }, -
jobvista-frontend/package.json
rd8b6c91 r19398ad 16 16 "react": "^18.3.1", 17 17 "react-dom": "^18.3.1", 18 "react-hook-form": "^7.51.4", 18 19 "react-jwt": "^1.2.1", 19 20 "react-redux": "^9.1.2", 21 "react-responsive-modal": "^6.4.2", 20 22 "react-router": "^6.23.0", 21 23 "react-router-dom": "^6.23.0", 22 24 "react-scripts": "5.0.1", 25 "react-select": "^5.8.0", 23 26 "redux": "^5.0.1", 24 27 "redux-thunk": "^3.1.0", -
jobvista-frontend/public/index.html
rd8b6c91 r19398ad 16 16 user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ 17 17 --> 18 <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />18 <!-- <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />--> 19 19 <!-- 20 20 Notice the use of %PUBLIC_URL% in the tags above. … … 27 27 --> 28 28 29 <!-- ICONS--> 29 30 <link rel="preconnect" href="https://fonts.gstatic.com"> 31 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> 32 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" /> 33 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" /> 30 34 <title>Job Vista</title> 31 35 </head> -
jobvista-frontend/src/App.css
rd8b6c91 r19398ad 5 5 background-color: rgb(243, 242, 241); 6 6 height: 100vh; 7 } 8 9 .card { 10 border: 1px solid black; 11 border-radius: 5px; 12 padding: 10px; 7 overflow-y: auto; 13 8 } 14 9 … … 35 30 } 36 31 32 .container { 33 width: 80% !important; 34 max-width: 1500px !important; 35 } 36 37 37 38 38 /*font-family: 'Ubuntu', sans-serif;*/ 39 39 /*font-family: 'Cairo', sans-serif;*/ 40 41 .react-responsive-modal-overlay { 42 backdrop-filter: blur(2px); 43 } 44 45 /*CARDS*/ 46 47 .col { 48 height: 280px !important; 49 } 50 51 .custom-card { 52 //border: 1px solid lightgray; 53 border-radius: 8px; 54 background-color: white; 55 transition: all 0.3s ease; 56 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 57 transform: translate(0, 0); 58 height: 260px; 59 } 60 61 .custom-card:hover { 62 transform: translate(0, 8px); 63 box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2); 64 } 65 66 .custom-card .card-head { 67 padding: 25px; 68 padding-bottom: 0 !important; 69 } 70 71 .custom-card .card-head .job-type { 72 font-size: 12px !important; 73 margin-left: 6px !important; 74 position: relative; 75 bottom: 3px; 76 } 77 78 .custom-card .card-head .expired { 79 font-size: 12px !important; 80 margin-left: 8px !important; 81 position: relative; 82 bottom: 3px; 83 } 84 85 .custom-card .card-head .card-management-btns { 86 float: right; 87 scale: 130%; 88 display: flex; 89 gap: 5px; 90 transition: 0.2s; 91 } 92 .custom-card .card-head .card-management-btns i:hover { 93 opacity: 0.5; 94 cursor: pointer; 95 } 96 97 .custom-card .card-body { 98 padding: 25px; 99 padding-top: 15px !important; 100 height: 100%; 101 } 102 .custom-card .card-body span{ 103 font-size: 15px; 104 } 105 106 .custom-card .card-body .card-title { 107 margin-top: 10px; 108 } 109 110 111 .custom-card .card-body .card-title h5 { 112 display: inline; 113 } 114 115 116 .custom-card .card-body .hourly-salary { 117 margin-bottom: 30px; 118 display: inline; 119 } 120 121 .custom-card .card-body .card-info { 122 color: gray; 123 margin: 13px 0; 124 } 125 126 .custom-card .card-body .aligned { 127 display: flex; 128 justify-content: center; 129 gap: 8px; 130 text-align: center; 131 margin-top: 25px; 132 position: relative; 133 134 } 135 136 .custom-card .card-body .aligned a { 137 text-decoration: none; 138 } 139 140 141 .card-button { 142 border: 0; 143 border-radius: 8px; 144 width: 45%; 145 background-color: rgba(207, 235, 255, 1); 146 //background-size: 200% auto; 147 //background-image: linear-gradient(to right, #a1c4fd 0%, aliceblue 61%, #a1c4fd 100%); 148 color: black; 149 font-weight: bold; 150 padding: 5px 10px; 151 transition: 0.2s; 152 } 153 154 .card-button:only-child { 155 width: 70%; 156 } 157 .card-button:not(.disabled):hover{ 158 background-color: rgb(187, 215, 235); 159 //background-position: right center; 160 color: black; 161 } 162 163 .disabled { 164 cursor: default !important; 165 opacity: 0.6; 166 } 167 168 169 -
jobvista-frontend/src/auth/RoutesConfig.js
rd8b6c91 r19398ad 5 5 import {SignUpRecruiterForm} from "../views/auth/SignUpRecruiterForm"; 6 6 import {SignUpJobSeekerForm} from "../views/auth/SignUpJobSeekerForm"; 7 import {JobAdvertisements} from "../views/job_advertisements/JobAdvertisements"; 8 import {JobAdDetails} from "../views/job_advertisements/JobAdDetails"; 7 9 export const RoutesConfig = () => { 8 10 … … 13 15 <Route path="/signup/recruiter" element={<SignUpRecruiterForm/>}></Route> 14 16 <Route path="/signup/job-seeker" element={<SignUpJobSeekerForm/>}></Route> 17 <Route path="/my-job-advertisements" element={<JobAdvertisements/>}></Route> 18 <Route path="/job-advertisements/view/:id" element={<JobAdDetails/>}></Route> 19 <Route path="/my-job-advertisements/view/:id" element={<JobAdDetails/>}></Route> 15 20 </Routes> 16 21 ) -
jobvista-frontend/src/redux/actionTypes.js
rd8b6c91 r19398ad 4 4 export const UPDATE_TOKEN = "UPDATE_TOKEN" 5 5 export const CURRENT_USER = "CURRENT_USER" 6 7 export const ADD_JOB_ADVERTISEMENT = "ADD_JOB_ADVERTISEMENT" 8 export const EDIT_JOB_ADVERTISEMENT = "EDIT_JOB_ADVERTISEMENT" 9 export const DELETE_JOB_ADVERTISEMENT = "DELETE_JOB_ADVERTISEMENT" 10 export const FETCH_JOB_ADVERTISEMENTS = "FETCH_JOB_ADVERTISEMENTS" 11 export const FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER = "FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER" -
jobvista-frontend/src/redux/actions/authActions.js
rd8b6c91 r19398ad 43 43 const response = jwtResponse.data; 44 44 const token = response.token; 45 //const refreshToken = response.refreshToken; // Corrected typo45 //const refreshToken = response.refreshToken; 46 46 const user = { 47 id: response.id, 47 48 email: response.email, 48 49 name: response.name, -
jobvista-frontend/src/redux/reducers/authReducer.js
rd8b6c91 r19398ad 21 21 case UPDATE_TOKEN: 22 22 let token = action.payload; 23 let currentUser = null23 let currentUser = ""; 24 24 if(!isExpired(token)) { 25 25 localStorage.setItem(AUTH_TOKEN, token); … … 37 37 }; 38 38 case SIGN_OUT: 39 console.log("BRISAM")40 39 localStorage.removeItem(CURRENT_USER); 41 40 localStorage.removeItem(AUTH_TOKEN); 42 41 return { 43 42 ...state, 44 currentUser: null,45 token: null43 currentUser: "", 44 token: "", 46 45 } 47 46 -
jobvista-frontend/src/redux/store.js
rd8b6c91 r19398ad 2 2 import { combineReducers } from 'redux'; 3 3 import authReducer from "./reducers/authReducer"; 4 import { thunk } from 'redux-thunk';4 import jobAdReducer from "./reducers/jobAdvertisementReducer"; 5 5 6 6 // const rootReducer = combineReducers({ … … 15 15 export const store = configureStore({ 16 16 reducer: { 17 auth: authReducer 17 auth: authReducer, 18 jobAd: jobAdReducer 18 19 }, 19 20 }); -
jobvista-frontend/src/views/auth/SignInForm.js
rd8b6c91 r19398ad 70 70 <div className="d-grid mb-3"> 71 71 <button 72 className="btn btn-lg btn-primarytext-uppercase fw-bold mb-2"72 className="btn btn-lg auth-primary-btn text-uppercase fw-bold mb-2" 73 73 type="submit">Sign in 74 74 </button> … … 85 85 <div className="row"> 86 86 <div className="col-md-6"> 87 <Link to="/signup/recruiter" className="btn btn-outline-primarytext-uppercase fw-bold mb-2 w-100">SIGN UP AS RECRUITER</Link>87 <Link to="/signup/recruiter" className="btn auth-secondary-btn text-uppercase fw-bold mb-2 w-100">SIGN UP AS RECRUITER</Link> 88 88 </div> 89 89 <div className="col-md-6"> 90 <Link to="/signup/job-seeker" className="btn btn-outline-primarytext-uppercase fw-bold mb-2 w-100">SIGN UP AS JOB SEEKER</Link>90 <Link to="/signup/job-seeker" className="btn auth-secondary-btn text-uppercase fw-bold mb-2 w-100">SIGN UP AS JOB SEEKER</Link> 91 91 </div> 92 92 </div> -
jobvista-frontend/src/views/auth/SignUpJobSeekerForm.js
rd8b6c91 r19398ad 83 83 <div className="d-grid mb-3"> 84 84 <button 85 className="btn btn-lg btn-primarytext-uppercase fw-bold mb-2"85 className="btn btn-lg auth-primary-btn text-uppercase fw-bold mb-2" 86 86 type="submit">Submit 87 87 </button> -
jobvista-frontend/src/views/auth/SignUpRecruiterForm.js
rd8b6c91 r19398ad 80 80 <div className="d-grid mb-3"> 81 81 <button 82 className="btn btn-lg btn-primarytext-uppercase fw-bold mb-2"82 className="btn btn-lg auth-primary-btn text-uppercase fw-bold mb-2" 83 83 type="submit">Submit 84 84 </button> -
jobvista-frontend/src/views/auth/auth.css
rd8b6c91 r19398ad 3 3 padding: 5px 5px; 4 4 } 5 6 .form-container { 7 margin-bottom: 80px; 8 } 9 10 .auth-primary-btn{ 11 background-color: rgba(1,38,90,0.80); 12 //background-color: #a1c4fd ; 13 color: white; 14 } 15 .auth-primary-btn:hover { 16 background-color: rgba(1,38,90,1); 17 color: white; 18 } 19 20 .auth-secondary-btn { 21 border: 2px solid rgba(1,38,90,0.7); 22 color: midnightblue; 23 } 24 25 .auth-secondary-btn:hover { 26 background-color: rgba(1,38,90,0.8); 27 color: white; 28 } -
jobvista-frontend/src/views/dashboard/Dashboard.js
rd8b6c91 r19398ad 1 import "./Dashboard.css" 2 3 import {useDispatch, useSelector} from "react-redux"; 4 import {useEffect, useState} from "react"; 5 import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions"; 6 import {formatRelativeTime, sortElementsByDateCreated} from "../../utils/utils"; 7 import {dataRangeOptions, industryOptions, industryOptionsFilter, sortOptions} from "../selectOptions"; 8 import Select from "react-select"; 9 import Roles from "../../enumerations/Roles"; 10 import {Link} from "react-router-dom"; 11 import JobType from "../../enumerations/JobType"; 12 1 13 export const Dashboard = () => { 14 15 const dispatch = useDispatch(); 16 17 const [jobAdvertisements, setJobAdvertisements] = useState([]); 18 let jobAdvertisementsState = useSelector(state => state.jobAd.jobAdvertisements) 19 const auth = useSelector(state => state.auth.currentUser); 20 21 const [role, setRole] = useState(""); 22 const [sortOrder, setSortOrder] = useState("newest"); 23 const [selectedDateRange, setSelectedDateRange] = useState("all"); 24 const [searchTerm, setSearchTerm] = useState(""); 25 const [dispatched, setDispatched] = useState(false) 26 27 useEffect(() => { 28 if (auth) { 29 setRole(auth.role); 30 } 31 }, [auth]); 32 33 useEffect(() => { 34 if(!dispatched && jobAdvertisementsState.length == 0) { 35 dispatch(JobAdvertisementActions.fetchJobAdvertisements((success, response) => { 36 if (success && response.data.length > 0) { 37 setJobAdvertisements(sortElementsByDateCreated(response.data)) 38 } 39 setDispatched(true) 40 console.log("Fetch all job advertisements GET") 41 console.log(response.data) 42 })) 43 44 } else { 45 setJobAdvertisements(jobAdvertisementsState) 46 console.log("Fetch all job advertisements STATE") 47 } 48 }, []) 2 49 3 50 4 51 return ( 5 52 <div className="container"> 6 <div className="row justify-content"> 7 <div className="col-md-4"> 8 <div className="card"> 9 <h5 className="card-title">Job Listing Name</h5> 10 <p>Job Listing Data</p> 53 <div className="head-dashboard-box"> 54 <div className="row"> 55 <div className="col-md-3"> 56 <div className="search-container head-component"> 57 <i className="fa-solid fa-magnifying-glass blue-colored"></i> 58 <input 59 className="search-input" 60 type="text" 61 placeholder="Search job advertisement by title..." 62 //value={searchTerm} 63 //onChange={event => setSearchTerm(event.target.value)} 64 /> 65 </div> 11 66 </div> 12 </div> 13 <div className="col-md-4"> 14 <div className="card"> 15 <h5 className="card-title">Job Listing Name</h5> 16 <p>Job Listing Data</p> 17 </div> 18 </div> 19 <div className="col-md-4"> 20 <div className="card"> 21 <h5 className="card-title">Job Listing Name</h5> 22 <p>Job Listing Data</p> 67 <div className="col-md-9"> 68 <div className="sort-section item"> 69 <Select 70 defaultValue={{value: "all", label: "All industries"}} 71 //value={sortOrder.value} 72 //onChange ={option => setSortOrder(option.value)} 73 options={industryOptionsFilter} 74 className="sort-range sort" 75 /> 76 </div> 77 <div className="sort-section item"> 78 <Select 79 defaultValue={{value: "newest", label: "Date (Newest First)"}} 80 //value={sortOrder.value} 81 //onChange ={option => setSortOrder(option.value)} 82 options = {sortOptions} 83 className="sort-range sort" 84 /> 85 </div> 86 <div className="date-range-section item"> 87 <Select 88 defaultValue={{value: "all", label: "Lifetime"}} 89 //value={selectedDateRange.value} 90 //onChange={option => setSelectedDateRange(option.value)} 91 options={dataRangeOptions} 92 className="date-range sort" 93 /> 94 </div> 23 95 </div> 24 96 </div> 25 97 </div> 98 <div className="row row-cols-1 row-cols-md-4 g-4"> 26 99 100 {jobAdvertisements && 101 jobAdvertisements.map((jobAd, index) => ( 102 <div key={index} className="col"> 103 <div className="custom-card"> 104 <div className="card-head"> 105 <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span> 106 <span className="job-type"> {jobAd.jobType===JobType.JOB ? "Job" : "Internship"}</span> 107 {!jobAd.active && <span className="expired">Expired</span>} 108 </div> 109 <div className="card-body"> 110 <h5 className="card-title">{jobAd.title}</h5> 111 <span>{jobAd.industry} • <span style={{color: "black", fontWeight: "bold"}}>{formatRelativeTime(jobAd.postedOn)}</span></span> 112 <div className="card-info"> 113 <span><i className="fa-solid fa-building" style={{color: "#000000"}}></i> Company: <span style={{color: "black", fontWeight: "bold"}}>{jobAd.recruiterName}</span></span> <br/> 114 </div> 115 116 <div className="aligned"> 117 <Link to={`/job-advertisements/view/${jobAd.id}`} className="card-button">Read more</Link> 118 {role===Roles.JOBSEEKER && 119 <> 120 {jobAd.active && <button className="card-button">Apply now</button> } 121 {!jobAd.active && <button className="card-button disabled">Apply now</button> } 122 </> 123 } 124 </div> 125 126 </div> 127 </div> 128 </div> 129 ))} 130 131 </div> 27 132 </div> 28 133 ) -
jobvista-frontend/src/views/static/Header.js
rd8b6c91 r19398ad 5 5 import {AuthActions} from "../../redux/actions/authActions"; 6 6 import Roles from "../../enumerations/Roles"; 7 import {useNavigate} from "react-router"; 7 8 8 export const Header = ( ) => {9 export const Header = (props) => { 9 10 10 11 const auth = useSelector(state => state.auth.currentUser); 11 12 const dispatch = useDispatch(); 13 const navigator = useNavigate(); 12 14 13 const [role, setRole] = useState( null);14 const [username, setUsername] = useState( null);15 const [role, setRole] = useState(""); 16 const [username, setUsername] = useState(""); 15 17 16 18 const signOut = () => { … … 20 22 21 23 useEffect(() => { 22 23 24 if (auth) { 24 25 setRole(auth.role); … … 34 35 <div className="collapse navbar-collapse" id="navbarSupportedContent"> 35 36 <ul className="navbar-nav me-auto mb-2 mb-lg-0"> 36 <NavLink to="/" className="nav-item nav-link" activeClassName="active">Home</NavLink>37 <NavLink to="/" className="nav-item nav-link">Home</NavLink> 37 38 {role==Roles.JOBSEEKER && 38 39 <> 39 <NavLink to="/applications" className="nav-item nav-link" activeClassName="active">Applications</NavLink>40 <NavLink to="/favoritejobs" className="nav-item nav-link" activeClassName="active">Saved</NavLink>40 <NavLink to="/applications" className="nav-item nav-link" >Applications</NavLink> 41 <NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink> 41 42 </> 42 43 … … 44 45 {role==Roles.RECRUITER && 45 46 <> 46 <NavLink to="/ jobadvertisements" className="nav-item nav-link" activeClassName="active">Job Advertisements</NavLink>47 <NavLink to="/favoritejobs" className="nav-item nav-link" activeClassName="active">Saved</NavLink>47 <NavLink to="/my-job-advertisements" className="nav-item nav-link" >Job Advertisements</NavLink> 48 <NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink> 48 49 </> 49 50 } … … 61 62 62 63 63 <Link onClick={signOut} className="btn btn-outline-secondary">Log out</Link>64 <Link onClick={signOut} className="btn auth-secondary-btn">Log out</Link> 64 65 </> : 65 66 <> 66 <Link to="/signin" className="btn btn-outline-secondary">Sign in</Link>67 <Link to="/signin" className="btn auth-secondary-btn">Sign in</Link> 67 68 </> 68 69 }
Note:
See TracChangeset
for help on using the changeset viewer.