Changeset 08f82ec


Ignore:
Timestamp:
06/20/24 11:57:13 (9 days ago)
Author:
223021 <daniel.ilievski.2@…>
Branches:
main
Children:
0f0add0
Parents:
befb988
Message:

Did more refactoring

Files:
56 added
28 edited
3 moved

Legend:

Unmodified
Added
Removed
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/SecurityConfiguration.java

    rbefb988 r08f82ec  
    3131        http.csrf(AbstractHttpConfigurer::disable)
    3232                .authorizeHttpRequests(request -> request
    33                         // TO DO: FIX PERMISSIONS
    34                         .requestMatchers("/api/job-advertisements/**",
    35                                 "/api/job-advertisements/view/**",
     33                        .requestMatchers(
     34                                "/api/auth/**",
     35                                "/api/job-advertisements/**",
     36                                "/api/applications/**",
    3637                                "/api/recruiter/**",
    37                                 "/api/job-seeker/**",
    38                                 "/api/recruiter/{id}/info",
    39                                 "/api/recruiter/{id}/edit-info",
    40                                 "/api/job-advertisements/apply/**",
    41                                 "/api/auth/**",
    42                                 "/api/resume/**",
    43                                 "/api/my-applications/**",
    44                                 "/api/applications/{id}/update",
    45                                 "/api/admin/**").permitAll()
    46 //                        .requestMatchers("/api/recruiter").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     38                                "/api/job-seeker/**"
     39                        ).permitAll()
     40                        .requestMatchers("/api/admin/**").hasAnyAuthority(Role.ROLE_ADMIN.name())
     41                        .requestMatchers("/api/recruiter/{id}/edit-info").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     42                        .requestMatchers("/api/recruiter/submit-logo").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     43                        .requestMatchers("/api/job-seeker/{id}/edit-info").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     44                        .requestMatchers("/api/job-seeker/submit-profile-pic").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
     45                        .requestMatchers("/api/job-advertisements/add").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     46                        .requestMatchers("/api/job-advertisements/edit/{id}").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     47                        .requestMatchers("/api/job-advertisements/delete/{id}").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     48                        .requestMatchers("/api/applications/{id}/update").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     49                        .requestMatchers("/api/job-advertisements/{advertisement_id}/applications").hasAnyAuthority(Role.ROLE_RECRUITER.name())
     50                        .requestMatchers("/api/applications/submit").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
     51                        .requestMatchers("/api/my-applications/{id}").hasAnyAuthority(Role.ROLE_JOBSEEKER.name())
    4752                        .anyRequest().authenticated())
    4853                .sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/ApplicationController.java

    rbefb988 r08f82ec  
    4242    }
    4343
    44     @GetMapping("/resume/{fileName:.+}")
    45     public ResponseEntity<Resource> downloadResume(@PathVariable("fileName") String fileName) {
    46         Resource resource = applicationService.loadResumeAsResource(fileName);
     44    @GetMapping("/applications/{id}/download-resume")
     45    public ResponseEntity<Resource> downloadResume(@PathVariable("id") Long applicationId) {
     46        Resource resource = applicationService.loadResumeAsResource(applicationId);
    4747        return ResponseEntity.ok()
    4848                .contentType(MediaType.APPLICATION_PDF)
     
    5151    }
    5252
    53     @PostMapping("/job-advertisements/apply")
     53    @PostMapping("/applications/submit")
    5454    public ResponseEntity<ApplicationDetailsDTO> submitApplication(
    5555            @RequestParam("jobSeekerId") Long jobSeekerId,
     
    6161            @RequestParam("messageToRecruiter") String messageToRecruiter) {
    6262
    63         ApplicationDTO applicationDTO = new ApplicationDTO(jobSeekerId, jobAdId, resumeFile, answerOne, answerTwo, answerThree, messageToRecruiter);
     63        ApplicationDTO applicationDTO = new ApplicationDTO(jobSeekerId, jobAdId,
     64                resumeFile, answerOne, answerTwo, answerThree, messageToRecruiter);
    6465        ApplicationDetailsDTO applicationDetailsDTO = applicationService.submitApplication(applicationDTO);
    6566        return new ResponseEntity<>(applicationDetailsDTO, HttpStatus.OK);
    6667    }
    67 
    6868}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/JobAdvertisementController.java

    rbefb988 r08f82ec  
    4646
    4747    @GetMapping("/recruiter/{id}")
    48     public ResponseEntity<?> findALlJobAdvertisementsByRecruiterId(@PathVariable Long id) {
     48    public ResponseEntity<?> findAllJobAdvertisementsByRecruiterId(@PathVariable Long id) {
    4949        List<JobAdDetailsDTO> jobAdDetailsDTOS = jobAdvertisementService.findAllJobAdvertisementsByRecruiterId(id);
    5050        return new ResponseEntity<>(jobAdDetailsDTOS, HttpStatus.OK);
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/Application.java

    rbefb988 r08f82ec  
    3232    private JobAdvertisement jobAdvertisement;
    3333
    34     @Column(name = "resume_file_name", nullable = false)
    35     private String resumeFileName;
     34    @Column(name = "resume_file_name", nullable = true)
     35    private String resumeFilePath;
    3636
    3737    @ElementCollection
     
    4545    private ApplicationStatus status;
    4646
    47     public Application(JobSeeker jobSeeker, JobAdvertisement jobAdvertisement, String resumeFileName, List<String> answers, String message) {
     47    public Application(JobSeeker jobSeeker, JobAdvertisement jobAdvertisement, List<String> answers, String message) {
    4848        this.jobSeeker = jobSeeker;
    4949        this.jobAdvertisement = jobAdvertisement;
    50         this.resumeFileName = resumeFileName;
     50        this.resumeFilePath = "";
    5151        this.questionAnswers = answers;
    5252        this.message = message;
     
    6969                application.getJobAdvertisement().getTitle(),
    7070                application.getQuestionAnswers(),
    71                 application.getResumeFileName(),
     71                application.getResumeFilePath(),
    7272                application.getMessage(),
    7373                application.getSubmittedOn(),
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/ApplicationServiceImpl.java

    rbefb988 r08f82ec  
    4141    public ApplicationServiceImpl(@Value("${file.upload-dir}") String uploadDir, UserRepository userRepository, ApplicationRepository applicationRepository, JobAdvertisementRepository jobAdvertisementRepository,
    4242                                  JobSeekerRepository jobSeekerRepository) {
    43         this.fileStorageLocation = Paths.get(uploadDir).toAbsolutePath().normalize();
     43        this.fileStorageLocation = Paths.get(uploadDir + "/applications").toAbsolutePath().normalize();
    4444
    4545        try {
     
    5858    public ApplicationDetailsDTO submitApplication(ApplicationDTO applicationDTO) {
    5959
     60        JobSeeker jobSeeker = jobSeekerRepository.findById(applicationDTO.getJobSeekerId())
     61                .orElseThrow(() -> new IllegalArgumentException("User not found."));
     62        JobAdvertisement jobAdvertisement = jobAdvertisementRepository.findById(applicationDTO.getJobAdId())
     63                .orElseThrow(() -> new IllegalArgumentException("Job advertisement not found."));
     64
    6065        if (applicationDTO.getResumeFile().isEmpty()) {
    6166            throw new RuntimeException("Failed to store empty file.");
    6267        }
    63 
    64         Path targetLocation = this.fileStorageLocation.resolve(applicationDTO.getResumeFile().getOriginalFilename());
    65         try {
    66             Files.copy(applicationDTO.getResumeFile().getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
    67         } catch (IOException e) {
    68             throw new RuntimeException(e);
    69         }
    70 
    71         JobSeeker jobSeeker = jobSeekerRepository.findById(applicationDTO.getJobSeekerId()).orElseThrow(() -> new IllegalArgumentException("User not found"));
    72         JobAdvertisement jobAdvertisement = jobAdvertisementRepository.findById(applicationDTO.getJobAdId()).orElseThrow(() -> new IllegalArgumentException("Job Ad not found"));
    7368
    7469        List<String> answers = new ArrayList<>();
     
    7772        answers.add(applicationDTO.getAnswerThree());
    7873
    79         Application application = new Application(jobSeeker, jobAdvertisement, applicationDTO.getResumeFile().getOriginalFilename(), answers, applicationDTO.getMessageToRecruiter());
    80         applicationRepository.save(application);
     74        Application application = new Application(jobSeeker, jobAdvertisement,
     75                answers,applicationDTO.getMessageToRecruiter());
     76        application = applicationRepository.save(application);
     77
     78        Path filePath = this.fileStorageLocation.resolve(String.valueOf(application.getId())).resolve("resume");
     79        Path targetLocation = filePath.resolve(applicationDTO.getResumeFile().getOriginalFilename());
     80
     81        try {
     82            Files.createDirectories(filePath);
     83            Files.copy(applicationDTO.getResumeFile().getInputStream(), targetLocation);
     84        } catch (IOException e) {
     85            throw new RuntimeException(e);
     86        }
     87
     88        String relativePath = Paths.get("uploads","applications",String.valueOf(application.getId()),
     89                "resume", applicationDTO.getResumeFile().getOriginalFilename()).toString();
     90        application.setResumeFilePath(relativePath);
     91        application = applicationRepository.save(application);
     92
    8193        return Application.mapToApplicationDetailsDTO(application);
    8294    }
     
    95107
    96108    @Override
    97     public Resource loadResumeAsResource(String fileName) {
     109    public Resource loadResumeAsResource(Long applicationId) {
     110        Application application = applicationRepository.findById(applicationId).
     111                orElseThrow(() -> new IllegalArgumentException("Application not found"));
     112
     113        String relativeFilePath = application.getResumeFilePath();
     114        Path filePath = fileStorageLocation.getParent().getParent().resolve(relativeFilePath).normalize();
     115
    98116        try {
    99             Path filePath = fileStorageLocation.resolve(fileName).normalize();
    100117            Resource resource = new UrlResource(filePath.toUri());
    101118            if (resource.exists()) {
    102119                return resource;
    103120            } else {
    104                 throw new RuntimeException("File not found " + fileName);
     121                throw new RuntimeException("File not found: " + relativeFilePath);
    105122            }
    106123        } catch (IOException ex) {
    107             throw new RuntimeException("File not found " + fileName, ex);
     124            throw new RuntimeException("File path is invalid: " + relativeFilePath, ex);
    108125        }
    109126    }
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/JobAdvertisementServiceImpl.java

    rbefb988 r08f82ec  
    1717
    1818import java.time.LocalDate;
    19 import java.time.LocalDateTime;
    2019import java.util.Comparator;
    2120import java.util.List;
     
    2423@RequiredArgsConstructor
    2524public class JobAdvertisementServiceImpl implements JobAdvertisementService {
    26     private final UserRepository userRepository;
    2725    private final JobAdvertisementRepository jobAdvertisementRepository;
    2826    private final RecruiterRepository recruiterRepository;
     
    3028    @Override
    3129    public JobAdDetailsDTO addJobAdvertisement(JobAdvertisementDTO jobAdvertisementDTO) {
    32         Recruiter recruiter = recruiterRepository.findById(jobAdvertisementDTO.getId()).orElseThrow(() -> new IllegalArgumentException("User not found"));
     30        Recruiter recruiter = recruiterRepository.findById(jobAdvertisementDTO.getId())
     31                .orElseThrow(() -> new IllegalArgumentException("User not found"));
    3332        JobAdvertisement jobAdvertisement = new JobAdvertisement(
    3433                recruiter,
     
    4746    @Override
    4847    public JobAdDetailsDTO editJobAdvertisement(Long id, JobAdvertisementDTO jobAdvertisementDTO) {
    49         JobAdvertisement jobAdvertisement = jobAdvertisementRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Job Advertisement not found"));
     48        JobAdvertisement jobAdvertisement = jobAdvertisementRepository.findById(id)
     49                .orElseThrow(() -> new IllegalArgumentException("Job Advertisement not found"));
    5050        jobAdvertisement.setTitle(jobAdvertisementDTO.getTitle());
    5151        jobAdvertisement.setDescription(jobAdvertisementDTO.getDescription());
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/RecruiterServiceImpl.java

    rbefb988 r08f82ec  
    3232        this.recruiterRepository = recruiterRepository;
    3333
    34         this.logoStorageLocation = Paths.get(uploadDir + "/logo").toAbsolutePath().normalize();
     34        this.logoStorageLocation = Paths.get(uploadDir + "/recruiters").toAbsolutePath().normalize();
    3535        try {
    3636            Files.createDirectories(this.logoStorageLocation);
     
    7070    public void submitLogo(Long recruiterId, MultipartFile logoFile) {
    7171
    72         Path recruiterLogoDir = this.logoStorageLocation.resolve(String.valueOf(recruiterId));
     72        Path recruiterLogoDir = this.logoStorageLocation.resolve(String.valueOf(recruiterId)).resolve("logos");
    7373        try {
    7474            Files.createDirectories(recruiterLogoDir);
     
    8282                Recruiter recruiter = recruiterRepository.findById(recruiterId)
    8383                        .orElseThrow(() -> new RuntimeException("Recruiter not found"));
    84                 String relativePath = Paths.get("uploads", "logo", String.valueOf(recruiterId), originalFilename).toString();
     84                String relativePath = Paths.get("uploads","recruiters", String.valueOf(recruiterId), "logos", originalFilename).toString();
    8585                recruiter.setLogoFilePath(relativePath);
    8686                recruiterRepository.save(recruiter);
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/ApplicationService.java

    rbefb988 r08f82ec  
    1414    List<ApplicationDetailsDTO> findAllByJobAdvertisementId(Long jobId);
    1515    List<ApplicationDetailsDTO> findAllByJobSeekerId(Long jobSeekerId);
    16     Resource loadResumeAsResource(String fileName);
     16    Resource loadResumeAsResource(Long applicationId);
    1717    ApplicationStatusDTO updateApplicationStatus(Long id, String status);
    1818}
  • jobvista-frontend/src/App.css

    rbefb988 r08f82ec  
    1414.App {
    1515  /*background-color: rgb(243, 242, 241);*/
    16   background-color: #F5F5F5;
     16  background-color: #E6E6E6;
    1717  /*background-color: #F4F2EE;*/
    1818  /*background-color: #0F0F0F;*/
     
    8484  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    8585  transform: translate(0, 0);
    86   height: 260px;
     86  height: 270px;
    8787  width: 100%;
    8888  color: grey;
     
    101101
    102102.custom-card {
    103   //border: 1px solid lightgray;
    104103  border-radius: 8px;
    105104  background-color: white;
     
    107106  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    108107  transform: translate(0, 0);
     108  height: 360px;
     109}
     110
     111.hub-card {
     112  height: 270px !important;
     113}
     114
     115.custom-card .card-foot {
     116  position: absolute;
     117  display: flex;
     118  justify-content: center;
     119  gap: 8px;
     120  text-align: center;
    109121  height: auto;
    110 }
     122  width: 100%;
     123  bottom: 20px;
     124}
     125
     126
     127
    111128
    112129.custom-card:hover {
     
    118135  padding: 25px;
    119136  padding-bottom: 0 !important;
     137  height: 20%;
    120138}
    121139
     
    147165
    148166.custom-card .card-body {
    149   padding: 25px;
    150   padding-top: 15px !important;
    151   height: 100%;
     167  padding: 0 25px;
     168  padding-top: 5px !important;
     169  height: 50%;
    152170}
    153171.custom-card .card-body span{
     
    175193}
    176194
    177 .custom-card .card-body .aligned {
    178   display: flex;
    179   justify-content: center;
    180   gap: 8px;
    181   text-align: center;
    182   margin-top: 25px;
    183   position: relative;
    184 
    185 }
    186 
    187 .custom-card .card-body .aligned a {
    188   text-decoration: none;
    189 }
     195
    190196
    191197
  • jobvista-frontend/src/auth/RoutesConfig.js

    rbefb988 r08f82ec  
    44import {SignUpRecruiterForm} from "../views/auth/SignUpRecruiterForm";
    55import {SignUpJobSeekerForm} from "../views/auth/SignUpJobSeekerForm";
    6 import {Workspace} from "../views/job_advertisements/RecruiterWorkspace";
     6import {Workspace} from "../views/job_advertisements/JobManagementHub";
    77import {JobAdDetails} from "../views/job_advertisements/JobAdDetails";
    88import {ApplicationsByJobAd} from "../views/applications/ApplicationsByJobAd";
     
    1010
    1111import {AdminPanel} from "../views/admin_panel/AdminPanel";
    12 import {RecruiterProfile} from "../views/recruiters/RecruiterProfile";
     12import {RecruiterProfile} from "../views/profiles/RecruiterProfile";
    1313import {AboutUs} from "../views/static/AboutUs";
    14 import {JobSeekerEditProfile} from "../views/edit_profile/JobSeekerEditProfile";
    15 import {RecruiterEditProfile} from "../views/edit_profile/RecruiterEditProfile";
     14import {JobSeekerEditProfile} from "../views/profiles/JobSeekerEditProfile";
     15import {RecruiterEditProfile} from "../views/profiles/RecruiterEditProfile";
    1616import Roles from "../enumerations/Roles";
    1717import {useSelector} from "react-redux";
    1818import {useEffect, useState} from "react";
    1919import {ErrorPage} from "../views/static/ErrorPage";
     20import {NoAccess} from "../views/static/NoAccess";
    2021
    2122export const PrivateRoutes = [
     
    158159            Roles.JOBSEEKER,
    159160            Roles.ADMIN
     161        ]
     162    },
     163    {
     164        component: NoAccess,
     165        path: '/no-access',
     166        title: 'No Access',
     167        exact: true,
     168        permission: [
     169            Roles.GUEST,
     170            Roles.RECRUITER,
    160171        ]
    161172    },
  • jobvista-frontend/src/redux/actions/applicationActions.js

    rbefb988 r08f82ec  
    1010    submitApplication: (application, callback) => {
    1111        return dispatch => {
    12             axios.post("/job-advertisements/apply", application, {
     12            axios.post("/applications/submit", application, {
    1313                headers: {
    1414                    'Content-Type': 'multipart/form-data'
     
    2626            })
    2727        }
    28 
    2928    },
    3029    updateApplicationStatus: (id, status, callback) => {
    3130        console.log(status)
    32       return dispatch => {
    33             // TO DO: REFACTOR
    34           axios.post("/applications/" + id + "/update", {
    35               id: id,
    36               status: status
    37           })
    38               .then(response => {
    39                   dispatch({
    40                       type: UPDATE_APPLICATION_STATUS,
    41                       application: response.data,
    42                   })
    43                   callback(true, response)
    44               }).catch(error => {
    45               callback(false, error)
    46           })
    47       }
     31        return dispatch => {
     32            axios.post("/applications/" + id + "/update", {
     33                id: id,
     34                status: status
     35            })
     36                .then(response => {
     37                    dispatch({
     38                        type: UPDATE_APPLICATION_STATUS,
     39                        application: response.data,
     40                    })
     41                    callback(true, response)
     42                }).catch(error => {
     43                callback(false, error)
     44            })
     45        }
    4846    },
    4947    fetchApplicationsByJobSeeker: (jobSeekerId, callback) => {
    50       return dispatch => {
    51           axios.get("/my-applications/" + jobSeekerId)
    52               .then(response => {
    53                   dispatch({
    54                       type: FETCH_APPLICATIONS_BY_JOB_SEEKER_ID,
    55                       applicationsByJobSeeker: response.data
    56                   })
    57                   callback(true, response)
    58               }).catch(error => {
    59                   callback(false, error)
    60           })
    61       }
     48        return dispatch => {
     49            axios.get("/my-applications/" + jobSeekerId)
     50                .then(response => {
     51                    dispatch({
     52                        type: FETCH_APPLICATIONS_BY_JOB_SEEKER_ID,
     53                        applicationsByJobSeeker: response.data
     54                    })
     55                    callback(true, response)
     56                }).catch(error => {
     57                callback(false, error)
     58            })
     59        }
    6260    },
    6361
     
    7775        }
    7876    },
    79     downloadResume: (fileName, callback) => {
    80         return dispatch => {
    81             return axios.get("/resume/" + fileName, {responseType: "blob"})
    82                 .then(response => {
    83                     const blob = new Blob([response.data], { type: 'application/pdf' });
    84                     const url = window.URL.createObjectURL(blob);
    85                     callback(true, url);
    86                 })
     77    downloadResume: (id, callback) => {
     78        return axios.get("/applications/" + id + "/download-resume", {responseType: "blob"})
     79            .then(response => {
     80                const blob = new Blob([response.data], {type: 'application/pdf'});
     81                const url = window.URL.createObjectURL(blob);
     82                callback(true, url);
     83            })
     84            .catch(error => {
     85                callback(false, error)
     86            })
    8787
    88                 .catch(error => {
    89                     callback(false, error)
    90                 })
    91         }
    9288    }
    9389}
  • jobvista-frontend/src/redux/actions/jobAdvertisementActions.js

    rbefb988 r08f82ec  
    1010    addJobAdvertisement: (jobAdvertisement, callback) => {
    1111        return dispatch => {
    12             axios.post("/job-advertisements/add",  jobAdvertisement, {
    13                 headers: {
    14                     'Content-Type': 'application/json'
    15                 },
    16             })
     12            axios.post("/job-advertisements/add", jobAdvertisement)
    1713                .then(response => {
    1814                    dispatch({
     
    2016                        jobAdvertisement: response.data
    2117                    })
    22                     callback(true, response)
     18                    callback(true)
    2319                }).catch((error) => {
    24                 callback(false, error)
     20                console.error(error)
     21                callback(false)
    2522            })
    2623        }
     
    3431                        jobAdvertisement: response.data
    3532                    })
    36                     callback(true, response)
     33                    callback(true)
    3734                }).catch((error) => {
    38                 callback(false, error)
     35                console.error(error)
     36                callback(false)
    3937            })
    4038        }
     
    5048                    callback(true)
    5149                }).catch(error => {
    52                 callback(false, error)
     50                console.error(error)
     51                callback(false)
    5352            })
    5453
     
    8382                callback(true, response)
    8483            }).catch((error) => {
    85             callback(false, error)        })
     84            callback(false, error)
     85        })
    8686
    8787    },
     
    9696                        jobAdvertisementsByRecruiter: response.data,
    9797                    })
     98                    callback(true, response)
     99                }).catch((error) => {
     100                callback(false, error)
     101            })
     102        }
     103    },
     104
     105    fetchJobAdvertisementsByRecruiterProfile: (recruiterId, callback) => {
     106        return dispatch => {
     107            let currentUser = JSON.parse(localStorage.getItem(CURRENT_USER));
     108            axios.get("/job-advertisements/recruiter/" + recruiterId)
     109                .then(response => {
    98110                    callback(true, response)
    99111                }).catch((error) => {
     
    116128
    117129    fetchRecruiterDetailsById: (id, callback) => {
    118         axios.get("/recruiter/"+id+"/info")
     130        axios.get("/recruiter/" + id + "/info")
    119131            .then(response => {
    120132                callback(true, response)
  • jobvista-frontend/src/utils/utils.js

    rbefb988 r08f82ec  
    77}
    88
    9 export const sortElementsBySubmissionDate = (array) => {
    10     return array.slice().sort((a, b) => {
    11         return new Date(b).getTime() - new Date(a.postedOn).getTime()
    12     });
    13 }
    14 
    159export const formatRelativeTime = (dateString) => {
    1610    const date = new Date(dateString);
     
    1812    const diffTime = now - date;
    1913
    20     // Define time intervals in milliseconds
    2114    const minute = 60 * 1000;
    2215    const hour = minute * 60;
     
    2518    const month = day * 30;
    2619
    27     // Calculate the relative time
    2820    if (diffTime < minute) {
    2921        return 'just now';
  • jobvista-frontend/src/views/admin_panel/AdminPanel.css

    rbefb988 r08f82ec  
    33}
    44
     5.table tbody tr:nth-child(even) {
     6    background-color: #fff; /* Light gray */
     7}
     8
     9.table tbody tr:nth-child(odd) {
     10    background-color: #f2f2f2; /* White */
     11}
    512/* The switch - the box around the slider */
    613.switch {
  • jobvista-frontend/src/views/admin_panel/AdminPanel.js

    rbefb988 r08f82ec  
    5454    return (
    5555        <div className="custom-container mt-5">
    56             <table className="table table-striped">
     56            <table className="table">
    5757                <thead>
    5858                <tr>
  • jobvista-frontend/src/views/applications/ApplicationDetailsModal.js

    rbefb988 r08f82ec  
    3232
    3333    useEffect(() => {
    34         if(application) {
    35             dispatch(ApplicationActions.downloadResume(application.fileName, (success, response) => {
    36                 if(success) {
     34        if (application) {
     35            ApplicationActions.downloadResume(application.id, (success, response) => {
     36                if (success) {
    3737                    setResumeUrl(response);
    3838                }
    39             }))
     39            })
    4040        }
    41     }, [application])
     41    }, [])
     42
     43    function getFileName(path) {
     44        let fileName = path.split('\\').pop().split('/').pop();
     45
     46        fileName = fileName.trim();
     47
     48        return fileName;
     49    }
    4250
    4351    return (<div className="modal-wrap">
    4452        <button onClick={toggleModal} className="application-button">View application</button>
    45         <Modal open={modal} onClose={toggleModal} center >
     53        <Modal open={modal} onClose={toggleModal} center>
    4654            <div className="head-modal">
    4755                <h3>{application.jobSeekerName}'s application for {application.jobAdTitle}</h3>
     
    4957            </div>
    5058
    51             <div className="modal-content" >
     59            <div className="modal-content">
    5260                <form>
    5361                    <div className="row">
    5462                        <div className="col-md-6">
    5563                            <label className="label">Why are you interested in joining our company?</label>
    56                             <textarea disabled type="text" defaultValue={application.questionAnswers[0]} disabled placeholder="Write your answer here..." className="application-textarea"/>
    57 
    58 
     64                            <textarea disabled type="text" defaultValue={application.questionAnswers[0]} disabled
     65                                      placeholder="Write your answer here..." className="application-textarea"/>
     66                            <br/><br/>
    5967                            <label className="label">What makes you a good fit for this position?</label>
    60                             <textarea disabled type="text" defaultValue={application.questionAnswers[1]}  placeholder="Write your answer here..." className="application-textarea"/>
    61 
    62 
    63                             <label className="label">What do you hope to achieve in your first 6 months in this role?</label>
    64                             <textarea disabled type="text" defaultValue={application.questionAnswers[2]}  placeholder="Write your answer here..." className="application-textarea"/>
     68                            <textarea disabled type="text" defaultValue={application.questionAnswers[1]}
     69                                      placeholder="Write your answer here..." className="application-textarea"/>
     70                            <br/><br/>
     71                            <label className="label">What do you hope to achieve in your first 6 months in this
     72                                role?</label>
     73                            <textarea disabled type="text" defaultValue={application.questionAnswers[2]}
     74                                      placeholder="Write your answer here..." className="application-textarea"/>
    6575
    6676                        </div>
     
    6878                            <label htmlFor="start">Curriculum vitae (CV)</label>
    6979                            <br/>
    70                             <a className="resume-link" href={resumeUrl} target="_blank" rel="noopener noreferrer">{application.fileName}</a>
     80                            <a className="resume-link" href={resumeUrl} target="_blank"
     81                               rel="noopener noreferrer">{getFileName(application.fileName)}</a>
    7182                            <br/>
    7283
    7384                            <br/>
    7485                            <label className="label">Message to the recruiter</label>
    75                             <textarea disabled type="text" defaultValue={application.message} placeholder="Optional..." className="application-textarea"/>
     86                            <textarea disabled type="text" defaultValue={application.message} placeholder="Optional..."
     87                                      className="application-textarea"/>
    7688
    7789                        </div>
  • jobvista-frontend/src/views/applications/Applications.css

    rbefb988 r08f82ec  
    3232    padding: 20px 20px;
    3333    display: flex;
    34     justify-content: center;
    35     gap: 20px;
     34    /*justify-content: center;*/
     35    /*gap: 20px;*/
    3636    margin: 15px 0;
    3737    /*z-index: -1000;*/
     
    3939.application-card .app-job-seeker-pic {
    4040    border: 1px solid gray;
    41     border-radius: 50%
     41    border-radius: 50%;
     42    margin-right: 20px;
    4243}
    4344.application-card .app-job-seeker-pic img {
     
    4546}
    4647
     48.application-card .app-company-logo {
     49    width: 8%;
     50}
     51
    4752.application-card .app-info {
    48     width: 60%;
     53    width: 65%;
    4954    display: inline-flex;
    5055    flex-direction: column;
     
    5661.application-card .app-info .jobAd-title {
    5762    font-weight: 600;
     63    font-size: 1.2rem;
    5864    /*text-transform: uppercase;*/
    5965    font-family: 'Segoe UI', sans-serif;
    60     /*font-size: 22px;*/
     66    text-decoration: none;
     67    color: black
    6168}
    62 .application-card .app-info .jobAd-title
     69
     70.
    6371
    6472.application-card .app-info .contact-info {
     
    7886
    7987.application-card .app-status {
    80     width: 40%;
     88    width: 28%;
    8189    display: inline-flex;
    8290    justify-content: end;
  • jobvista-frontend/src/views/applications/ApplicationsByJobSeeker.js

    rbefb988 r08f82ec  
    77import {RecruiterActions} from "../../redux/actions/recruiterActions";
    88import {sortElementsBy} from "../../utils/utils";
     9import {Link} from "react-router-dom";
    910
    1011export const ApplicationsByJobSeeker = () => {
     
    9798
    9899                    <div className="app-info">
    99                         <div className="jobAd-title">{application.jobAdTitle}</div>
     100                        <Link to={`/job-advertisements/${application.jobAdId}`} className="jobAd-title">{application.jobAdTitle}</Link>
     101                        {/*<h5 className="jobAd-title"></h5>*/}
    100102                        <div className="contact-info">
    101103                            <div className="contact-item">
  • jobvista-frontend/src/views/applications/ApplyToJobAdModal.js

    rbefb988 r08f82ec  
    5151
    5252            dispatch(ApplicationActions.submitApplication(
    53                 formData,(success, response) => {
    54                     if(success) {
     53                formData, (success) => {
     54                    if (success) {
    5555                        toggleModal()
    5656                        notifyJobAdApply()
     
    6666
    6767    return (<div className="modal-wrap">
    68         {role===Roles.JOBSEEKER &&
     68        {role === Roles.JOBSEEKER &&
    6969            <>
    70                 {jobAd.active && <button onClick={toggleModal} className="apply-button apply">Apply now</button> }
    71                 {!jobAd.active && <button className="card-button apply disabled">Apply now</button> }
     70                {jobAd.active && <button onClick={toggleModal} className="apply-button apply">Apply now</button>}
     71                {!jobAd.active && <button className="card-button apply disabled">Apply now</button>}
    7272            </>
    7373        }
    74         <Modal open={modal} onClose={toggleModal} center >
     74        <Modal open={modal} onClose={toggleModal} center>
    7575            <div className="head-modal">
    7676                <h3>Applying to {jobAd.title} at {jobAd.recruiterName}</h3>
     
    7878            </div>
    7979
    80             <div className="modal-content" >
     80            <div className="modal-content">
    8181                <form onSubmit={handleSubmit(submitApplication)}>
    8282                    <div className="row">
     
    9292                            <p style={{color: "red"}}>{errors.answerTwo?.message}</p>
    9393
    94                             <label className="label">What do you hope to achieve in your first 6 months in this role?</label>
    95                             <textarea type="text"  placeholder="Write your answer here..."
    96                                    {...register("answerThree")} className="application-textarea"/>
     94                            <label className="label">What do you hope to achieve in your first 6 months in this
     95                                role?</label>
     96                            <textarea type="text" placeholder="Write your answer here..."
     97                                      {...register("answerThree")} className="application-textarea"/>
    9798                            <p style={{color: "red"}}>{errors.answerThree?.message}</p>
    9899
     
    101102                            <label htmlFor="start">Curriculum vitae (CV)</label>
    102103                            <br/>
    103                             <input {...register("file")} className="resume-link" onChange={(e) => setResumeFile(e.target.files[0])} required type="file" id="fileUpload" accept=".pdf"/>
     104                            <input {...register("file")} className="resume-link"
     105                                   onChange={(e) => setResumeFile(e.target.files[0])} required type="file"
     106                                   id="fileUpload" accept=".pdf"/>
    104107
    105108                            <br/>
     
    109112
    110113                            <br/><br/>
    111                             <p style={{color: "red"}}>Please note that your personal data from your account will be used to identify and process your application.</p>
     114                            <p style={{color: "red"}}>Please note that your personal data from your account will be used
     115                                to identify and process your application.</p>
    112116                        </div>
    113117                    </div>
  • jobvista-frontend/src/views/auth/SignUpRecruiterForm.js

    rbefb988 r08f82ec  
    1313    const schema = yup.object().shape({
    1414        companyNameReg: yup.string().required("Company name is required."),
    15         phoneNumberReg: yup.number().required("Phone number is required"),
     15        phoneNumberReg: yup.string().required("Phone number is required"),
    1616        companyEmailReg: yup.string().required("Email is required.").email("Email is not valid."),
    1717        passwordReg: yup.string().min(6, "Password must be at least 6 characters.").required("Password is required."),
     
    3232                    //     theme: success ? 'success' : 'error'
    3333                    // });
    34                     success && navigate("/");
     34                    success && navigate("/no-access");
    3535                }));
    3636        } catch (err) {
  • jobvista-frontend/src/views/dashboard/Dashboard.js

    rbefb988 r08f82ec  
    3434
    3535
    36 
    37     // const [user, setUser] = useState(null);
    38     //
    39     // useEffect(() => {
    40     //     const token = localStorage.getItem(AUTH_TOKEN);
    41     //     if (token!=null) {
    42     //         try {
    43     //             const decodedToken = jwtDecode(token);
    44     //             setUser({
    45     //                 name: decodedToken.name,
    46     //                 role: decodedToken.role,
    47     //                 hasAccess: decodedToken.access,
    48     //             });
    49     //         } catch (error) {
    50     //             console.error('Failed to decode token', error);
    51     //         }
    52     //     }
    53     //     console.log(user)
    54     // }, [auth]);
    55 
    56     // useEffect(() => {
    57     //     if (auth) {
    58     //         setRole(auth.role);
    59     //     }
    60     //     console.log(props)
    61     // }, [auth]);
    62 
    6336    useEffect(() => {
    6437        if (!jobDispatched && jobAdvertisementsState.length == 0) {
     
    7245
    7346        } else {
    74             setJobAdvertisements(jobAdvertisementsState)
     47            setJobAdvertisements(sortElementsBy(jobAdvertisementsState, "postedOn"))
    7548            console.log("Fetch all job advertisements STATE")
    7649
     
    160133                    jobAdvertisements.map((jobAd, index) => (
    161134                        <div key={index} className="col">
    162                             <div className="custom-card">
     135                            <div className="custom-card dashboard-card">
    163136                                <div className="card-head">
    164137                                    <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span>
     
    187160                                    </div>
    188161
    189                                     <div className="aligned">
    190                                         <Link to={`/job-advertisements/${jobAd.id}`} className="card-button">Read
    191                                             more</Link>
    192                                     </div>
    193 
     162                                </div>
     163                                <div className="card-foot">
     164                                    <Link to={`/job-advertisements/${jobAd.id}`} className="card-button">Read
     165                                        more</Link>
    194166                                </div>
    195167                            </div>
  • jobvista-frontend/src/views/job_advertisements/AddJobAdModal.js

    rbefb988 r08f82ec  
    4343
    4444    const addJobAdvertisement = async (values) => {
    45         //const description = values.description.replace(/\n/g, "\\n");
    4645        try {
    4746            dispatch(JobAdvertisementActions.addJobAdvertisement(
     
    5554                    jobType: values.jobType.value,
    5655                    employmentStatus: values.employmentStatus.value,
    57                 }, (success, response) => {
     56                }, (success) => {
    5857                    if (success) {
    59                         // console.log("Job Advertisement added")
    6058                        toggleModal()
    6159                        notifyJobAdPost()
  • jobvista-frontend/src/views/job_advertisements/DeleteJobAdModal.js

    rbefb988 r08f82ec  
    2626    };
    2727
    28     const addJobAdvertisement = async () => {
     28    const deleteJobAdvertisement = async () => {
    2929        try {
    30             dispatch(JobAdvertisementActions.deleteJobAdvertisement(jobAd.props.id, (success, response) => {
     30            dispatch(JobAdvertisementActions.deleteJobAdvertisement(jobAd.props.id, (success) => {
    3131                if (success) {
    32                     // console.log("Job Advertisement deleted")
    3332                    toggleModal()
    3433                    notifyJobAdDelete()
     
    5453                <div className="modal-delete-buttons">
    5554                    <button className="cancel-btn" onClick={toggleModal}>Cancel</button>
    56                     <button className="delete-btn" onClick={addJobAdvertisement}> Delete</button>
     55                    <button className="delete-btn" onClick={deleteJobAdvertisement}> Delete</button>
    5756                </div>
    5857            </div>
  • jobvista-frontend/src/views/job_advertisements/EditJobAdModal.js

    rbefb988 r08f82ec  
    5454                    jobType: values.jobType.value,
    5555                    employmentStatus: values.employmentStatus.value,
    56                 }, jobAd.props.id, (success, response) => {
     56                }, jobAd.props.id, (success) => {
    5757                    if(success) {
    58                         // console.log("Job Advertisement edited")
    5958                        toggleModal()
    6059                        notifyJobAdEdit()
  • jobvista-frontend/src/views/job_advertisements/JobAdDetails.css

    rbefb988 r08f82ec  
    2020    /*scrollbar-width: thin; !* "auto" hides scrollbar on some browsers, "thin" shows a thin scrollbar *!*/
    2121    /*scrollbar-color: #999999 #fff;*/
     22}
     23
     24.min-wrap {
     25    min-height: 80vh;
    2226}
    2327
  • jobvista-frontend/src/views/job_advertisements/JobAdDetails.js

    rbefb988 r08f82ec  
    6363        <div className="row">
    6464            <div className="col-md-9">
    65                 <div className="details-wrap">
     65                <div className="details-wrap min-wrap">
    6666                    <div className="row">
    6767                        <div className="col-md-9">
  • jobvista-frontend/src/views/job_advertisements/JobManagementHub.js

    rbefb988 r08f82ec  
    6666
    6767    let filterJobAdvertisements = () => {
    68         JobAdvertisementActions.filterJobAdvertisementsByRecruiter({
     68        JobAdvertisementActions.filterJobAdvertisementsByRecruiter(auth.id, {
    6969            searchTerm: searchTerm, industry: selectedIndustry, sortOrder: selectedSortOrder
    7070        }, (success, response) => {
     
    127127                {jobAdvertisementsByRecruiter && jobAdvertisementsByRecruiter.map((jobAd, index) => (
    128128                    <div key={index} className="col">
    129                         <div className="custom-card">
     129                        <div className="custom-card hub-card">
    130130                            <div className="card-head">
    131131                                <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span>
     
    150150                                </div>
    151151
    152                                 <div className="aligned">
    153                                     <Link to={`/job-management-hub/applications/${jobAd.id}`}
    154                                           className="card-button solo">View applications</Link>
    155                                 </div>
     152                            </div>
    156153
     154
     155                            <div className="card-foot">
     156                                <Link to={`/job-management-hub/applications/${jobAd.id}`}
     157                                      className="card-button">View applications</Link>
    157158                            </div>
    158159                        </div>
  • jobvista-frontend/src/views/shared_css/Modal.css

    rbefb988 r08f82ec  
    8989.application-textarea {
    9090    height: 100px;
     91    /*margin-bottom: 20px;*/
    9192}
    9293
  • jobvista-frontend/src/views/static/Header.css

    rbefb988 r08f82ec  
    1919
    2020.navbar {
    21     width: calc(100% - 17px);
     21    width: 100%;
    2222    height: 80px;
    2323    background-color: #f8f9fa;
Note: See TracChangeset for help on using the changeset viewer.