Changeset 19398ad


Ignore:
Timestamp:
05/16/24 23:09:21 (2 weeks ago)
Author:
223021 <daniel.ilievski.2@…>
Branches:
main
Parents:
d8b6c91
Message:

Implemented backend and frontend CRUD operations for job advertisements

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  
    4444                token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
    4545
    46 //                SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
    47 //                securityContext.setAuthentication(token);
    48 //                SecurityContextHolder.setContext(securityContext);
    49 
    5046                SecurityContextHolder.getContext().setAuthentication(token);
    5147            }
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/SecurityConfiguration.java

    rd8b6c91 r19398ad  
    22
    33import lombok.RequiredArgsConstructor;
    4 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.enumerations.Role;
    54import org.springframework.context.annotation.Bean;
    65import org.springframework.context.annotation.Configuration;
     
    2524
    2625    private final JwtAuthFilter jwtAuthFilter;
    27     //private final UserService userService;
    2826    private final UserDetailsService userDetailsService;
    2927
     
    3129    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    3230        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())
    3834                        .anyRequest().authenticated())
    3935                .sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/AdminController.java

    rd8b6c91 r19398ad  
    22
    33import lombok.RequiredArgsConstructor;
    4 import org.springframework.http.ResponseEntity;
    5 import org.springframework.web.bind.annotation.GetMapping;
    64import org.springframework.web.bind.annotation.RequestMapping;
    75import org.springframework.web.bind.annotation.RestController;
     
    1210public class AdminController {
    1311
    14 
    15     @GetMapping
    16     public ResponseEntity<String> sayHello() {
    17         return ResponseEntity.ok("Hi Admin");
    18     }
    1912}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/AuthController.java

    rd8b6c91 r19398ad  
    1212import org.springframework.web.bind.annotation.*;
    1313
    14 @CrossOrigin(origins = "*")
    1514@RestController
    1615@RequestMapping("/api/auth")
    1716@AllArgsConstructor
     17@CrossOrigin(origins = "*")
    1818public class AuthController {
    1919
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/JobSeekerController.java

    rd8b6c91 r19398ad  
    22
    33
    4 import lombok.RequiredArgsConstructor;
     4import lombok.AllArgsConstructor;
     5import org.springframework.web.bind.annotation.CrossOrigin;
    56import org.springframework.web.bind.annotation.RequestMapping;
    67import org.springframework.web.bind.annotation.RestController;
     
    89@RestController
    910@RequestMapping("/api/job-seeker")
    10 @RequiredArgsConstructor
     11@AllArgsConstructor
     12@CrossOrigin(origins = "*")
    1113public class JobSeekerController {
    1214}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/RecruiterController.java

    rd8b6c91 r19398ad  
    11package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.controllers;
    22
    3 import lombok.RequiredArgsConstructor;
     3import lombok.AllArgsConstructor;
     4import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.RecruiterDetailsDTO;
     5import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.RecruiterService;
     6import org.springframework.http.HttpStatus;
    47import 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;
     8import org.springframework.web.bind.annotation.*;
    89
    910@RestController
    1011@RequestMapping("/api/recruiter")
    11 @RequiredArgsConstructor
     12@AllArgsConstructor
     13@CrossOrigin(origins = "*")
    1214public class RecruiterController {
    1315
    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);
    1722    }
    1823}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/Admin.java

    rd8b6c91 r19398ad  
    44import jakarta.persistence.Table;
    55import lombok.AllArgsConstructor;
    6 import lombok.Builder;
    76import lombok.Data;
    87import lombok.NoArgsConstructor;
    98
    109@Entity
    11 //@Data
    12 //@NoArgsConstructor
    13 //@AllArgsConstructor
    14 //@Builder
     10@Data
     11@NoArgsConstructor
    1512@Table(name = "admins")
    1613public class Admin extends User {
     14
     15//    private String name;
     16//    private String surname;
     17
    1718    @Override
    1819    public String getName() {
    1920        return "Admin";
    2021    }
    21 //    private String name;
    22 //    private String surname;
    2322}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/DTO/JwtAuthResponse.java

    rd8b6c91 r19398ad  
    1010@NoArgsConstructor
    1111public class JwtAuthResponse {
     12    private Long id;
    1213    private String email;
    1314    private String name;
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/JobSeeker.java

    rd8b6c91 r19398ad  
    55import jakarta.persistence.Table;
    66import lombok.AllArgsConstructor;
    7 import lombok.Builder;
    87import lombok.Data;
    98import lombok.NoArgsConstructor;
     
    1413@NoArgsConstructor
    1514@AllArgsConstructor
    16 @Builder
    1715@Table(name = "job_seekers")
    1816public class JobSeeker extends User {
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/User.java

    rd8b6c91 r19398ad  
    1515
    1616@Entity
    17 @Inheritance(strategy = InheritanceType.JOINED)
    18 @Table(name = "users")
    1917@Data
    2018@NoArgsConstructor
    2119@AllArgsConstructor
     20@Inheritance(strategy = InheritanceType.JOINED)
     21@Table(name = "users")
    2222public abstract class User implements UserDetails {
    2323
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/mappers/JobSeekerMapper.java

    rd8b6c91 r19398ad  
    2626    }
    2727
    28 
    2928}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/users/mappers/RecruiterMapper.java

    rd8b6c91 r19398ad  
    22
    33import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.RecruiterDTO;
     4import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.DTO.RecruiterDetailsDTO;
    45import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter;
    56
     
    1112                recruiter.getPassword(),
    1213                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(),
    1323                recruiter.getPhoneNumber()
    1424        );
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/repositories/RecruiterRepository.java

    rd8b6c91 r19398ad  
    22
    33import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter;
     4import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.mappers.RecruiterMapper;
    45import org.springframework.data.jpa.repository.JpaRepository;
    56
    67public interface RecruiterRepository extends JpaRepository<Recruiter, Long> {
     8    Recruiter findRecruiterByEmail(String email);
    79}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/AuthServiceImpl.java

    rd8b6c91 r19398ad  
    3131    private final JwtService jwtService;
    3232
    33 
    3433    @Override
    3534    public User signUpJobSeeker(JobSeeker jobSeeker) {
     
    5049        String refreshJwt = jwtService.generateRefreshToken(new HashMap<>(), user);
    5150
    52         return new JwtAuthResponse(user.getEmail(), user.getName(), user.getRole().name(), jwt, refreshJwt);
     51        return new JwtAuthResponse(user.getId(), user.getEmail(), user.getName(), user.getRole().name(), jwt, refreshJwt);
    5352    }
    5453   
     
    5958            String jwt = jwtService.generateToken(user);
    6059
    61             return new JwtAuthResponse(user.getEmail(), user.getName(), user.getRole().name(), jwt, refreshTokenRequest.getToken());
     60            return new JwtAuthResponse(user.getId(), user.getEmail(), user.getName(), user.getRole().name(), jwt, refreshTokenRequest.getToken());
    6261        }
    6362        return null;
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/JwtServiceImpl.java

    rd8b6c91 r19398ad  
    4141    }
    4242
    43     public String extractUsername(String token) {
    44         return extractClaim(token, Claims::getSubject);
    45     }
    46 
    4743    private Claims extractAllClaims(String token) {
    4844        return Jwts.parserBuilder().setSigningKey(getSigninKey()).build().parseClaimsJws(token).getBody();
     
    5450    }
    5551
     52    @Override
     53    public String extractUsername(String token) {
     54        return extractClaim(token, Claims::getSubject);
     55    }
     56
     57    @Override
    5658    public boolean isTokenValid(String token, UserDetails userDetails) {
    5759        final String username = extractUsername(token);
     
    5961    }
    6062
    61 
    62     private boolean isTokenExpired(String token) {
     63    @Override
     64    public boolean isTokenExpired(String token) {
    6365        return extractClaim(token, Claims::getExpiration).before(new Date());
    6466    }
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/UserServiceImpl.java

    rd8b6c91 r19398ad  
    11package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.impl;
    22
    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;
     3import lombok.RequiredArgsConstructor;
    104import org.springframework.stereotype.Service;
     5import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.UserService;
    116
    127@Service
    13 @AllArgsConstructor
     8@RequiredArgsConstructor
     9public class UserServiceImpl implements UserService {
    1410
    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 //    @Override
    25 //    public User registerJobSeeker(JobSeeker jobSeeker) {
    26 //        jobSeeker.setPassword(passwordEncoder.encode(jobSeeker.getPassword()));
    27 //        return jobSeekerRepository.save(jobSeeker);
    28 //    }
    29 //
    30 //    @Override
    31 //    public User registerRecruiter(Recruiter recruiter) {
    32 //        recruiter.setPassword(passwordEncoder.encode(recruiter.getPassword()));
    33 //        return recruiterRepository.save(recruiter);
    34 //    }
    35 //
    36 //    @Override
    37 //    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 //    @Override
    51 //    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 //    }
    6411}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/JwtService.java

    rd8b6c91 r19398ad  
    66
    77public interface JwtService {
    8 
    98    String generateToken(UserDetails userDetails);
     9    String generateRefreshToken(Map<String, Object> extraClaims, UserDetails userDetails);
    1010    String extractUsername(String token);
    1111    boolean isTokenValid(String token, UserDetails userDetails);
    12     String generateRefreshToken(Map<String, Object> extraClaims, UserDetails userDetails);
     12    boolean isTokenExpired(String token);
    1313
    1414}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/UserService.java

    rd8b6c91 r19398ad  
    11package mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef;
    22
    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;
    73import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter;
    8 import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.User;
    94
    105public interface UserService {
    11     User registerJobSeeker(JobSeeker jobSeeker);
    12     User registerRecruiter(Recruiter recruiter);
    13     JwtAuthResponse login(SignInDTO signInDTO);
    14     JwtAuthResponse refreshToken(RefreshTokenRequest refreshTokenRequest);
     6
    157}
  • jobvista-frontend/package-lock.json

    rd8b6c91 r19398ad  
    2121        "react": "^18.3.1",
    2222        "react-dom": "^18.3.1",
     23        "react-hook-form": "^7.51.4",
    2324        "react-jwt": "^1.2.1",
    2425        "react-redux": "^9.1.2",
     26        "react-responsive-modal": "^6.4.2",
    2527        "react-router": "^6.23.0",
    2628        "react-router-dom": "^6.23.0",
    2729        "react-scripts": "5.0.1",
     30        "react-select": "^5.8.0",
    2831        "redux": "^5.0.1",
    2932        "redux-thunk": "^3.1.0",
     
    20362039      "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="
    20372040    },
     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    },
    20382060    "node_modules/@csstools/normalize.css": {
    20392061      "version": "12.1.1",
     
    63196341      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
    63206342    },
     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    },
    63216348    "node_modules/bonjour-service": {
    63226349      "version": "1.2.1",
     
    65996626      "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz",
    66006627      "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=="
    66016633    },
    66026634    "node_modules/clean-css": {
     
    1317713209        "node": ">= 4.0.0"
    1317813210      }
     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=="
    1317913216    },
    1318013217    "node_modules/merge-descriptors": {
     
    1570615743      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.4.tgz",
    1570715744      "integrity": "sha512-V14i8SEkh+V1gs6YtD0hdHYnoL4tp/HX/A45wWQN15CYr9bFRmmRdYStSO5L65lCCZRF+kYiSKhm9alqbcdiVA==",
    15708       "peer": true,
    1570915745      "engines": {
    1571015746        "node": ">=12.22.0"
     
    1576515801      "engines": {
    1576615802        "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"
    1576715820      }
    1576815821    },
     
    1586915922      }
    1587015923    },
     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    },
    1587115944    "node_modules/react-transition-group": {
    1587215945      "version": "4.4.5",
     
    1806018133        "querystringify": "^2.1.1",
    1806118134        "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        }
    1806218148      }
    1806318149    },
  • jobvista-frontend/package.json

    rd8b6c91 r19398ad  
    1616    "react": "^18.3.1",
    1717    "react-dom": "^18.3.1",
     18    "react-hook-form": "^7.51.4",
    1819    "react-jwt": "^1.2.1",
    1920    "react-redux": "^9.1.2",
     21    "react-responsive-modal": "^6.4.2",
    2022    "react-router": "^6.23.0",
    2123    "react-router-dom": "^6.23.0",
    2224    "react-scripts": "5.0.1",
     25    "react-select": "^5.8.0",
    2326    "redux": "^5.0.1",
    2427    "redux-thunk": "^3.1.0",
  • jobvista-frontend/public/index.html

    rd8b6c91 r19398ad  
    1616      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    1717    -->
    18     <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
     18<!--    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />-->
    1919    <!--
    2020      Notice the use of %PUBLIC_URL% in the tags above.
     
    2727    -->
    2828
     29    <!--    ICONS-->
    2930    <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" />
    3034    <title>Job Vista</title>
    3135  </head>
  • jobvista-frontend/src/App.css

    rd8b6c91 r19398ad  
    55  background-color: rgb(243, 242, 241);
    66  height: 100vh;
    7 }
    8 
    9 .card {
    10   border: 1px solid black;
    11   border-radius: 5px;
    12   padding: 10px;
     7  overflow-y: auto;
    138}
    149
     
    3530}
    3631
     32.container {
     33  width: 80% !important;
     34  max-width: 1500px !important;
     35}
     36
    3737
    3838/*font-family: 'Ubuntu', sans-serif;*/
    3939/*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  
    55import {SignUpRecruiterForm} from "../views/auth/SignUpRecruiterForm";
    66import {SignUpJobSeekerForm} from "../views/auth/SignUpJobSeekerForm";
     7import {JobAdvertisements} from "../views/job_advertisements/JobAdvertisements";
     8import {JobAdDetails} from "../views/job_advertisements/JobAdDetails";
    79export const RoutesConfig = () => {
    810
     
    1315                <Route path="/signup/recruiter" element={<SignUpRecruiterForm/>}></Route>
    1416                <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>
    1520            </Routes>
    1621    )
  • jobvista-frontend/src/redux/actionTypes.js

    rd8b6c91 r19398ad  
    44export const UPDATE_TOKEN = "UPDATE_TOKEN"
    55export const CURRENT_USER = "CURRENT_USER"
     6
     7export const ADD_JOB_ADVERTISEMENT = "ADD_JOB_ADVERTISEMENT"
     8export const EDIT_JOB_ADVERTISEMENT = "EDIT_JOB_ADVERTISEMENT"
     9export const DELETE_JOB_ADVERTISEMENT = "DELETE_JOB_ADVERTISEMENT"
     10export const FETCH_JOB_ADVERTISEMENTS = "FETCH_JOB_ADVERTISEMENTS"
     11export const FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER = "FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER"
  • jobvista-frontend/src/redux/actions/authActions.js

    rd8b6c91 r19398ad  
    4343                const response = jwtResponse.data;
    4444                const token = response.token;
    45                 //const refreshToken = response.refreshToken; // Corrected typo
     45                //const refreshToken = response.refreshToken;
    4646                const user = {
     47                    id: response.id,
    4748                    email: response.email,
    4849                    name: response.name,
  • jobvista-frontend/src/redux/reducers/authReducer.js

    rd8b6c91 r19398ad  
    2121        case UPDATE_TOKEN:
    2222            let token = action.payload;
    23             let currentUser = null
     23            let currentUser = "";
    2424            if(!isExpired(token)) {
    2525                localStorage.setItem(AUTH_TOKEN, token);
     
    3737            };
    3838        case SIGN_OUT:
    39             console.log("BRISAM")
    4039            localStorage.removeItem(CURRENT_USER);
    4140            localStorage.removeItem(AUTH_TOKEN);
    4241            return {
    4342                ...state,
    44                 currentUser: null,
    45                 token: null
     43                currentUser: "",
     44                token: "",
    4645            }
    4746
  • jobvista-frontend/src/redux/store.js

    rd8b6c91 r19398ad  
    22import { combineReducers } from 'redux';
    33import authReducer from "./reducers/authReducer";
    4 import { thunk } from 'redux-thunk';
     4import jobAdReducer from "./reducers/jobAdvertisementReducer";
    55
    66// const rootReducer = combineReducers({
     
    1515export const store = configureStore({
    1616    reducer: {
    17         auth: authReducer
     17        auth: authReducer,
     18        jobAd: jobAdReducer
    1819    },
    1920});
  • jobvista-frontend/src/views/auth/SignInForm.js

    rd8b6c91 r19398ad  
    7070                            <div className="d-grid mb-3">
    7171                                <button
    72                                     className="btn btn-lg btn-primary text-uppercase fw-bold mb-2"
     72                                    className="btn btn-lg auth-primary-btn text-uppercase fw-bold mb-2"
    7373                                    type="submit">Sign in
    7474                                </button>
     
    8585                        <div className="row">
    8686                            <div className="col-md-6">
    87                                 <Link to="/signup/recruiter" className="btn btn-outline-primary text-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>
    8888                            </div>
    8989                            <div className="col-md-6">
    90                                 <Link to="/signup/job-seeker" className="btn btn-outline-primary text-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>
    9191                            </div>
    9292                        </div>
  • jobvista-frontend/src/views/auth/SignUpJobSeekerForm.js

    rd8b6c91 r19398ad  
    8383                            <div className="d-grid mb-3">
    8484                                <button
    85                                     className="btn btn-lg btn-primary text-uppercase fw-bold mb-2"
     85                                    className="btn btn-lg auth-primary-btn text-uppercase fw-bold mb-2"
    8686                                    type="submit">Submit
    8787                                </button>
  • jobvista-frontend/src/views/auth/SignUpRecruiterForm.js

    rd8b6c91 r19398ad  
    8080                            <div className="d-grid mb-3">
    8181                                <button
    82                                     className="btn btn-lg btn-primary text-uppercase fw-bold mb-2"
     82                                    className="btn btn-lg auth-primary-btn text-uppercase fw-bold mb-2"
    8383                                    type="submit">Submit
    8484                                </button>
  • jobvista-frontend/src/views/auth/auth.css

    rd8b6c91 r19398ad  
    33    padding: 5px 5px;
    44}
     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  
     1import "./Dashboard.css"
     2
     3import {useDispatch, useSelector} from "react-redux";
     4import {useEffect, useState} from "react";
     5import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";
     6import {formatRelativeTime, sortElementsByDateCreated} from "../../utils/utils";
     7import {dataRangeOptions, industryOptions, industryOptionsFilter, sortOptions} from "../selectOptions";
     8import Select from "react-select";
     9import Roles from "../../enumerations/Roles";
     10import {Link} from "react-router-dom";
     11import JobType from "../../enumerations/JobType";
     12
    113export 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    }, [])
    249
    350
    451    return (
    552        <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>
    1166                    </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>
    2395                    </div>
    2496                </div>
    2597            </div>
     98            <div className="row row-cols-1 row-cols-md-4 g-4">
    2699
     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>
    27132        </div>
    28133    )
  • jobvista-frontend/src/views/static/Header.js

    rd8b6c91 r19398ad  
    55import {AuthActions} from "../../redux/actions/authActions";
    66import Roles from "../../enumerations/Roles";
     7import {useNavigate} from "react-router";
    78
    8 export const Header = () => {
     9export const Header = (props) => {
    910
    1011    const auth = useSelector(state => state.auth.currentUser);
    1112    const dispatch = useDispatch();
     13    const navigator = useNavigate();
    1214
    13     const [role, setRole] = useState(null);
    14     const [username, setUsername] = useState(null);
     15    const [role, setRole] = useState("");
     16    const [username, setUsername] = useState("");
    1517
    1618    const signOut = () => {
     
    2022
    2123    useEffect(() => {
    22 
    2324        if (auth) {
    2425            setRole(auth.role);
     
    3435                <div className="collapse navbar-collapse" id="navbarSupportedContent">
    3536                    <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>
    3738                        {role==Roles.JOBSEEKER &&
    3839                            <>
    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>
    4142                            </>
    4243
     
    4445                        {role==Roles.RECRUITER &&
    4546                            <>
    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>
    4849                            </>
    4950                        }
     
    6162
    6263
    63                             <Link onClick={signOut} className="btn btn-outline-secondary">Log out</Link>
     64                            <Link onClick={signOut} className="btn auth-secondary-btn">Log out</Link>
    6465                        </> :
    6566                        <>
    66                             <Link to="/signin" className="btn btn-outline-secondary">Sign in</Link>
     67                            <Link to="/signin" className="btn auth-secondary-btn">Sign in</Link>
    6768                        </>
    6869                    }
Note: See TracChangeset for help on using the changeset viewer.