Changeset 08f82ec
- Timestamp:
- 06/20/24 11:57:13 (5 months ago)
- Branches:
- main
- Children:
- 0f0add0
- Parents:
- befb988
- 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 31 31 http.csrf(AbstractHttpConfigurer::disable) 32 32 .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/**", 36 37 "/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()) 47 52 .anyRequest().authenticated()) 48 53 .sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/ApplicationController.java
rbefb988 r08f82ec 42 42 } 43 43 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); 47 47 return ResponseEntity.ok() 48 48 .contentType(MediaType.APPLICATION_PDF) … … 51 51 } 52 52 53 @PostMapping("/ job-advertisements/apply")53 @PostMapping("/applications/submit") 54 54 public ResponseEntity<ApplicationDetailsDTO> submitApplication( 55 55 @RequestParam("jobSeekerId") Long jobSeekerId, … … 61 61 @RequestParam("messageToRecruiter") String messageToRecruiter) { 62 62 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); 64 65 ApplicationDetailsDTO applicationDetailsDTO = applicationService.submitApplication(applicationDTO); 65 66 return new ResponseEntity<>(applicationDetailsDTO, HttpStatus.OK); 66 67 } 67 68 68 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/JobAdvertisementController.java
rbefb988 r08f82ec 46 46 47 47 @GetMapping("/recruiter/{id}") 48 public ResponseEntity<?> findA LlJobAdvertisementsByRecruiterId(@PathVariable Long id) {48 public ResponseEntity<?> findAllJobAdvertisementsByRecruiterId(@PathVariable Long id) { 49 49 List<JobAdDetailsDTO> jobAdDetailsDTOS = jobAdvertisementService.findAllJobAdvertisementsByRecruiterId(id); 50 50 return new ResponseEntity<>(jobAdDetailsDTOS, HttpStatus.OK); -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/applications/Application.java
rbefb988 r08f82ec 32 32 private JobAdvertisement jobAdvertisement; 33 33 34 @Column(name = "resume_file_name", nullable = false)35 private String resumeFile Name;34 @Column(name = "resume_file_name", nullable = true) 35 private String resumeFilePath; 36 36 37 37 @ElementCollection … … 45 45 private ApplicationStatus status; 46 46 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) { 48 48 this.jobSeeker = jobSeeker; 49 49 this.jobAdvertisement = jobAdvertisement; 50 this.resumeFile Name = resumeFileName;50 this.resumeFilePath = ""; 51 51 this.questionAnswers = answers; 52 52 this.message = message; … … 69 69 application.getJobAdvertisement().getTitle(), 70 70 application.getQuestionAnswers(), 71 application.getResumeFile Name(),71 application.getResumeFilePath(), 72 72 application.getMessage(), 73 73 application.getSubmittedOn(), -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/ApplicationServiceImpl.java
rbefb988 r08f82ec 41 41 public ApplicationServiceImpl(@Value("${file.upload-dir}") String uploadDir, UserRepository userRepository, ApplicationRepository applicationRepository, JobAdvertisementRepository jobAdvertisementRepository, 42 42 JobSeekerRepository jobSeekerRepository) { 43 this.fileStorageLocation = Paths.get(uploadDir ).toAbsolutePath().normalize();43 this.fileStorageLocation = Paths.get(uploadDir + "/applications").toAbsolutePath().normalize(); 44 44 45 45 try { … … 58 58 public ApplicationDetailsDTO submitApplication(ApplicationDTO applicationDTO) { 59 59 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 60 65 if (applicationDTO.getResumeFile().isEmpty()) { 61 66 throw new RuntimeException("Failed to store empty file."); 62 67 } 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"));73 68 74 69 List<String> answers = new ArrayList<>(); … … 77 72 answers.add(applicationDTO.getAnswerThree()); 78 73 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 81 93 return Application.mapToApplicationDetailsDTO(application); 82 94 } … … 95 107 96 108 @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 98 116 try { 99 Path filePath = fileStorageLocation.resolve(fileName).normalize();100 117 Resource resource = new UrlResource(filePath.toUri()); 101 118 if (resource.exists()) { 102 119 return resource; 103 120 } else { 104 throw new RuntimeException("File not found " + fileName);121 throw new RuntimeException("File not found: " + relativeFilePath); 105 122 } 106 123 } catch (IOException ex) { 107 throw new RuntimeException("File not found " + fileName, ex);124 throw new RuntimeException("File path is invalid: " + relativeFilePath, ex); 108 125 } 109 126 } -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/JobAdvertisementServiceImpl.java
rbefb988 r08f82ec 17 17 18 18 import java.time.LocalDate; 19 import java.time.LocalDateTime;20 19 import java.util.Comparator; 21 20 import java.util.List; … … 24 23 @RequiredArgsConstructor 25 24 public class JobAdvertisementServiceImpl implements JobAdvertisementService { 26 private final UserRepository userRepository;27 25 private final JobAdvertisementRepository jobAdvertisementRepository; 28 26 private final RecruiterRepository recruiterRepository; … … 30 28 @Override 31 29 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")); 33 32 JobAdvertisement jobAdvertisement = new JobAdvertisement( 34 33 recruiter, … … 47 46 @Override 48 47 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")); 50 50 jobAdvertisement.setTitle(jobAdvertisementDTO.getTitle()); 51 51 jobAdvertisement.setDescription(jobAdvertisementDTO.getDescription()); -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/RecruiterServiceImpl.java
rbefb988 r08f82ec 32 32 this.recruiterRepository = recruiterRepository; 33 33 34 this.logoStorageLocation = Paths.get(uploadDir + "/ logo").toAbsolutePath().normalize();34 this.logoStorageLocation = Paths.get(uploadDir + "/recruiters").toAbsolutePath().normalize(); 35 35 try { 36 36 Files.createDirectories(this.logoStorageLocation); … … 70 70 public void submitLogo(Long recruiterId, MultipartFile logoFile) { 71 71 72 Path recruiterLogoDir = this.logoStorageLocation.resolve(String.valueOf(recruiterId)) ;72 Path recruiterLogoDir = this.logoStorageLocation.resolve(String.valueOf(recruiterId)).resolve("logos"); 73 73 try { 74 74 Files.createDirectories(recruiterLogoDir); … … 82 82 Recruiter recruiter = recruiterRepository.findById(recruiterId) 83 83 .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(); 85 85 recruiter.setLogoFilePath(relativePath); 86 86 recruiterRepository.save(recruiter); -
jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/ApplicationService.java
rbefb988 r08f82ec 14 14 List<ApplicationDetailsDTO> findAllByJobAdvertisementId(Long jobId); 15 15 List<ApplicationDetailsDTO> findAllByJobSeekerId(Long jobSeekerId); 16 Resource loadResumeAsResource( String fileName);16 Resource loadResumeAsResource(Long applicationId); 17 17 ApplicationStatusDTO updateApplicationStatus(Long id, String status); 18 18 } -
jobvista-frontend/src/App.css
rbefb988 r08f82ec 14 14 .App { 15 15 /*background-color: rgb(243, 242, 241);*/ 16 background-color: # F5F5F5;16 background-color: #E6E6E6; 17 17 /*background-color: #F4F2EE;*/ 18 18 /*background-color: #0F0F0F;*/ … … 84 84 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 85 85 transform: translate(0, 0); 86 height: 2 60px;86 height: 270px; 87 87 width: 100%; 88 88 color: grey; … … 101 101 102 102 .custom-card { 103 //border: 1px solid lightgray;104 103 border-radius: 8px; 105 104 background-color: white; … … 107 106 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 108 107 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; 109 121 height: auto; 110 } 122 width: 100%; 123 bottom: 20px; 124 } 125 126 127 111 128 112 129 .custom-card:hover { … … 118 135 padding: 25px; 119 136 padding-bottom: 0 !important; 137 height: 20%; 120 138 } 121 139 … … 147 165 148 166 .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%; 152 170 } 153 171 .custom-card .card-body span{ … … 175 193 } 176 194 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 190 196 191 197 -
jobvista-frontend/src/auth/RoutesConfig.js
rbefb988 r08f82ec 4 4 import {SignUpRecruiterForm} from "../views/auth/SignUpRecruiterForm"; 5 5 import {SignUpJobSeekerForm} from "../views/auth/SignUpJobSeekerForm"; 6 import {Workspace} from "../views/job_advertisements/ RecruiterWorkspace";6 import {Workspace} from "../views/job_advertisements/JobManagementHub"; 7 7 import {JobAdDetails} from "../views/job_advertisements/JobAdDetails"; 8 8 import {ApplicationsByJobAd} from "../views/applications/ApplicationsByJobAd"; … … 10 10 11 11 import {AdminPanel} from "../views/admin_panel/AdminPanel"; 12 import {RecruiterProfile} from "../views/ recruiters/RecruiterProfile";12 import {RecruiterProfile} from "../views/profiles/RecruiterProfile"; 13 13 import {AboutUs} from "../views/static/AboutUs"; 14 import {JobSeekerEditProfile} from "../views/ edit_profile/JobSeekerEditProfile";15 import {RecruiterEditProfile} from "../views/ edit_profile/RecruiterEditProfile";14 import {JobSeekerEditProfile} from "../views/profiles/JobSeekerEditProfile"; 15 import {RecruiterEditProfile} from "../views/profiles/RecruiterEditProfile"; 16 16 import Roles from "../enumerations/Roles"; 17 17 import {useSelector} from "react-redux"; 18 18 import {useEffect, useState} from "react"; 19 19 import {ErrorPage} from "../views/static/ErrorPage"; 20 import {NoAccess} from "../views/static/NoAccess"; 20 21 21 22 export const PrivateRoutes = [ … … 158 159 Roles.JOBSEEKER, 159 160 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, 160 171 ] 161 172 }, -
jobvista-frontend/src/redux/actions/applicationActions.js
rbefb988 r08f82ec 10 10 submitApplication: (application, callback) => { 11 11 return dispatch => { 12 axios.post("/ job-advertisements/apply", application, {12 axios.post("/applications/submit", application, { 13 13 headers: { 14 14 'Content-Type': 'multipart/form-data' … … 26 26 }) 27 27 } 28 29 28 }, 30 29 updateApplicationStatus: (id, status, callback) => { 31 30 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 } 48 46 }, 49 47 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.data56 })57 callback(true, response)58 }).catch(error => {59 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 } 62 60 }, 63 61 … … 77 75 } 78 76 }, 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 }) 87 87 88 .catch(error => {89 callback(false, error)90 })91 }92 88 } 93 89 } -
jobvista-frontend/src/redux/actions/jobAdvertisementActions.js
rbefb988 r08f82ec 10 10 addJobAdvertisement: (jobAdvertisement, callback) => { 11 11 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) 17 13 .then(response => { 18 14 dispatch({ … … 20 16 jobAdvertisement: response.data 21 17 }) 22 callback(true , response)18 callback(true) 23 19 }).catch((error) => { 24 callback(false, error) 20 console.error(error) 21 callback(false) 25 22 }) 26 23 } … … 34 31 jobAdvertisement: response.data 35 32 }) 36 callback(true , response)33 callback(true) 37 34 }).catch((error) => { 38 callback(false, error) 35 console.error(error) 36 callback(false) 39 37 }) 40 38 } … … 50 48 callback(true) 51 49 }).catch(error => { 52 callback(false, error) 50 console.error(error) 51 callback(false) 53 52 }) 54 53 … … 83 82 callback(true, response) 84 83 }).catch((error) => { 85 callback(false, error) }) 84 callback(false, error) 85 }) 86 86 87 87 }, … … 96 96 jobAdvertisementsByRecruiter: response.data, 97 97 }) 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 => { 98 110 callback(true, response) 99 111 }).catch((error) => { … … 116 128 117 129 fetchRecruiterDetailsById: (id, callback) => { 118 axios.get("/recruiter/" +id+"/info")130 axios.get("/recruiter/" + id + "/info") 119 131 .then(response => { 120 132 callback(true, response) -
jobvista-frontend/src/utils/utils.js
rbefb988 r08f82ec 7 7 } 8 8 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 15 9 export const formatRelativeTime = (dateString) => { 16 10 const date = new Date(dateString); … … 18 12 const diffTime = now - date; 19 13 20 // Define time intervals in milliseconds21 14 const minute = 60 * 1000; 22 15 const hour = minute * 60; … … 25 18 const month = day * 30; 26 19 27 // Calculate the relative time28 20 if (diffTime < minute) { 29 21 return 'just now'; -
jobvista-frontend/src/views/admin_panel/AdminPanel.css
rbefb988 r08f82ec 3 3 } 4 4 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 } 5 12 /* The switch - the box around the slider */ 6 13 .switch { -
jobvista-frontend/src/views/admin_panel/AdminPanel.js
rbefb988 r08f82ec 54 54 return ( 55 55 <div className="custom-container mt-5"> 56 <table className="table table-striped">56 <table className="table"> 57 57 <thead> 58 58 <tr> -
jobvista-frontend/src/views/applications/ApplicationDetailsModal.js
rbefb988 r08f82ec 32 32 33 33 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) { 37 37 setResumeUrl(response); 38 38 } 39 }) )39 }) 40 40 } 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 } 42 50 43 51 return (<div className="modal-wrap"> 44 52 <button onClick={toggleModal} className="application-button">View application</button> 45 <Modal open={modal} onClose={toggleModal} center 53 <Modal open={modal} onClose={toggleModal} center> 46 54 <div className="head-modal"> 47 55 <h3>{application.jobSeekerName}'s application for {application.jobAdTitle}</h3> … … 49 57 </div> 50 58 51 <div className="modal-content" 59 <div className="modal-content"> 52 60 <form> 53 61 <div className="row"> 54 62 <div className="col-md-6"> 55 63 <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/> 59 67 <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"/> 65 75 66 76 </div> … … 68 78 <label htmlFor="start">Curriculum vitae (CV)</label> 69 79 <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> 71 82 <br/> 72 83 73 84 <br/> 74 85 <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"/> 76 88 77 89 </div> -
jobvista-frontend/src/views/applications/Applications.css
rbefb988 r08f82ec 32 32 padding: 20px 20px; 33 33 display: flex; 34 justify-content: center;35 gap: 20px;34 /*justify-content: center;*/ 35 /*gap: 20px;*/ 36 36 margin: 15px 0; 37 37 /*z-index: -1000;*/ … … 39 39 .application-card .app-job-seeker-pic { 40 40 border: 1px solid gray; 41 border-radius: 50% 41 border-radius: 50%; 42 margin-right: 20px; 42 43 } 43 44 .application-card .app-job-seeker-pic img { … … 45 46 } 46 47 48 .application-card .app-company-logo { 49 width: 8%; 50 } 51 47 52 .application-card .app-info { 48 width: 6 0%;53 width: 65%; 49 54 display: inline-flex; 50 55 flex-direction: column; … … 56 61 .application-card .app-info .jobAd-title { 57 62 font-weight: 600; 63 font-size: 1.2rem; 58 64 /*text-transform: uppercase;*/ 59 65 font-family: 'Segoe UI', sans-serif; 60 /*font-size: 22px;*/ 66 text-decoration: none; 67 color: black 61 68 } 62 .application-card .app-info .jobAd-title 69 70 . 63 71 64 72 .application-card .app-info .contact-info { … … 78 86 79 87 .application-card .app-status { 80 width: 40%;88 width: 28%; 81 89 display: inline-flex; 82 90 justify-content: end; -
jobvista-frontend/src/views/applications/ApplicationsByJobSeeker.js
rbefb988 r08f82ec 7 7 import {RecruiterActions} from "../../redux/actions/recruiterActions"; 8 8 import {sortElementsBy} from "../../utils/utils"; 9 import {Link} from "react-router-dom"; 9 10 10 11 export const ApplicationsByJobSeeker = () => { … … 97 98 98 99 <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>*/} 100 102 <div className="contact-info"> 101 103 <div className="contact-item"> -
jobvista-frontend/src/views/applications/ApplyToJobAdModal.js
rbefb988 r08f82ec 51 51 52 52 dispatch(ApplicationActions.submitApplication( 53 formData, (success, response) => {54 if (success) {53 formData, (success) => { 54 if (success) { 55 55 toggleModal() 56 56 notifyJobAdApply() … … 66 66 67 67 return (<div className="modal-wrap"> 68 {role ===Roles.JOBSEEKER &&68 {role === Roles.JOBSEEKER && 69 69 <> 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>} 72 72 </> 73 73 } 74 <Modal open={modal} onClose={toggleModal} center 74 <Modal open={modal} onClose={toggleModal} center> 75 75 <div className="head-modal"> 76 76 <h3>Applying to {jobAd.title} at {jobAd.recruiterName}</h3> … … 78 78 </div> 79 79 80 <div className="modal-content" 80 <div className="modal-content"> 81 81 <form onSubmit={handleSubmit(submitApplication)}> 82 82 <div className="row"> … … 92 92 <p style={{color: "red"}}>{errors.answerTwo?.message}</p> 93 93 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"/> 97 98 <p style={{color: "red"}}>{errors.answerThree?.message}</p> 98 99 … … 101 102 <label htmlFor="start">Curriculum vitae (CV)</label> 102 103 <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"/> 104 107 105 108 <br/> … … 109 112 110 113 <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> 112 116 </div> 113 117 </div> -
jobvista-frontend/src/views/auth/SignUpRecruiterForm.js
rbefb988 r08f82ec 13 13 const schema = yup.object().shape({ 14 14 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"), 16 16 companyEmailReg: yup.string().required("Email is required.").email("Email is not valid."), 17 17 passwordReg: yup.string().min(6, "Password must be at least 6 characters.").required("Password is required."), … … 32 32 // theme: success ? 'success' : 'error' 33 33 // }); 34 success && navigate("/ ");34 success && navigate("/no-access"); 35 35 })); 36 36 } catch (err) { -
jobvista-frontend/src/views/dashboard/Dashboard.js
rbefb988 r08f82ec 34 34 35 35 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 63 36 useEffect(() => { 64 37 if (!jobDispatched && jobAdvertisementsState.length == 0) { … … 72 45 73 46 } else { 74 setJobAdvertisements( jobAdvertisementsState)47 setJobAdvertisements(sortElementsBy(jobAdvertisementsState, "postedOn")) 75 48 console.log("Fetch all job advertisements STATE") 76 49 … … 160 133 jobAdvertisements.map((jobAd, index) => ( 161 134 <div key={index} className="col"> 162 <div className="custom-card ">135 <div className="custom-card dashboard-card"> 163 136 <div className="card-head"> 164 137 <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span> … … 187 160 </div> 188 161 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> 194 166 </div> 195 167 </div> -
jobvista-frontend/src/views/job_advertisements/AddJobAdModal.js
rbefb988 r08f82ec 43 43 44 44 const addJobAdvertisement = async (values) => { 45 //const description = values.description.replace(/\n/g, "\\n");46 45 try { 47 46 dispatch(JobAdvertisementActions.addJobAdvertisement( … … 55 54 jobType: values.jobType.value, 56 55 employmentStatus: values.employmentStatus.value, 57 }, (success , response) => {56 }, (success) => { 58 57 if (success) { 59 // console.log("Job Advertisement added")60 58 toggleModal() 61 59 notifyJobAdPost() -
jobvista-frontend/src/views/job_advertisements/DeleteJobAdModal.js
rbefb988 r08f82ec 26 26 }; 27 27 28 const addJobAdvertisement = async () => {28 const deleteJobAdvertisement = async () => { 29 29 try { 30 dispatch(JobAdvertisementActions.deleteJobAdvertisement(jobAd.props.id, (success , response) => {30 dispatch(JobAdvertisementActions.deleteJobAdvertisement(jobAd.props.id, (success) => { 31 31 if (success) { 32 // console.log("Job Advertisement deleted")33 32 toggleModal() 34 33 notifyJobAdDelete() … … 54 53 <div className="modal-delete-buttons"> 55 54 <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> 57 56 </div> 58 57 </div> -
jobvista-frontend/src/views/job_advertisements/EditJobAdModal.js
rbefb988 r08f82ec 54 54 jobType: values.jobType.value, 55 55 employmentStatus: values.employmentStatus.value, 56 }, jobAd.props.id, (success , response) => {56 }, jobAd.props.id, (success) => { 57 57 if(success) { 58 // console.log("Job Advertisement edited")59 58 toggleModal() 60 59 notifyJobAdEdit() -
jobvista-frontend/src/views/job_advertisements/JobAdDetails.css
rbefb988 r08f82ec 20 20 /*scrollbar-width: thin; !* "auto" hides scrollbar on some browsers, "thin" shows a thin scrollbar *!*/ 21 21 /*scrollbar-color: #999999 #fff;*/ 22 } 23 24 .min-wrap { 25 min-height: 80vh; 22 26 } 23 27 -
jobvista-frontend/src/views/job_advertisements/JobAdDetails.js
rbefb988 r08f82ec 63 63 <div className="row"> 64 64 <div className="col-md-9"> 65 <div className="details-wrap ">65 <div className="details-wrap min-wrap"> 66 66 <div className="row"> 67 67 <div className="col-md-9"> -
jobvista-frontend/src/views/job_advertisements/JobManagementHub.js
rbefb988 r08f82ec 66 66 67 67 let filterJobAdvertisements = () => { 68 JobAdvertisementActions.filterJobAdvertisementsByRecruiter( {68 JobAdvertisementActions.filterJobAdvertisementsByRecruiter(auth.id, { 69 69 searchTerm: searchTerm, industry: selectedIndustry, sortOrder: selectedSortOrder 70 70 }, (success, response) => { … … 127 127 {jobAdvertisementsByRecruiter && jobAdvertisementsByRecruiter.map((jobAd, index) => ( 128 128 <div key={index} className="col"> 129 <div className="custom-card ">129 <div className="custom-card hub-card"> 130 130 <div className="card-head"> 131 131 <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span> … … 150 150 </div> 151 151 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> 156 153 154 155 <div className="card-foot"> 156 <Link to={`/job-management-hub/applications/${jobAd.id}`} 157 className="card-button">View applications</Link> 157 158 </div> 158 159 </div> -
jobvista-frontend/src/views/shared_css/Modal.css
rbefb988 r08f82ec 89 89 .application-textarea { 90 90 height: 100px; 91 /*margin-bottom: 20px;*/ 91 92 } 92 93 -
jobvista-frontend/src/views/static/Header.css
rbefb988 r08f82ec 19 19 20 20 .navbar { 21 width: calc(100% - 17px);21 width: 100%; 22 22 height: 80px; 23 23 background-color: #f8f9fa;
Note:
See TracChangeset
for help on using the changeset viewer.