Changeset befb988 for jobvista-frontend/src/views
- Timestamp:
- 06/17/24 21:59:14 (5 months ago)
- Branches:
- main
- Children:
- 08f82ec
- Parents:
- b248810
- Location:
- jobvista-frontend/src/views
- Files:
-
- 1 deleted
- 15 edited
- 3 moved
Legend:
- Unmodified
- Added
- Removed
-
jobvista-frontend/src/views/admin_panel/AdminPanel.js
rb248810 rbefb988 4 4 import {useState, useEffect} from "react"; 5 5 import {AdminActions} from "../../redux/actions/adminActions"; 6 import {notifyAccessUpdate} from "../../utils/toastUtils"; 6 7 7 8 export const AdminPanel = () => { … … 24 25 setRecruiters(recruitersState) 25 26 console.log("Fetch all recruiters STATE") 27 console.log(recruitersState) 28 26 29 } 27 30 }, [recruitersState]) … … 32 35 }; 33 36 34 const handleAccessChange = (recruiterId, newAccessStatus) => { 37 const handleAccessChange = (recruiterId, companyName, newAccessStatus) => { 38 35 39 setRecruiters(prevState => 36 40 prevState.map(recruiter => … … 41 45 ); 42 46 43 console.log(recruiterId + " " + newAccessStatus)44 45 47 dispatch(AdminActions.changeAccess(recruiterId, newAccessStatus, (success, response) => { 46 48 if(success) { 47 console.log("Access changed")49 notifyAccessUpdate(companyName) 48 50 } 49 51 })); … … 51 53 52 54 return ( 53 <div className=" applications-container mt-5">55 <div className="custom-container mt-5"> 54 56 <table className="table table-striped"> 55 57 <thead> … … 64 66 </thead> 65 67 <tbody> 66 {recruiters .map((recruiter) => (68 {recruitersState.map((recruiter) => ( 67 69 <tr key={recruiter.id}> 68 70 <th scope="row">{recruiter.id}</th> … … 70 72 <td>{recruiter.email}</td> 71 73 <td>{recruiter.companyName}</td> 72 <td>{recruiter. phoneNumber}</td>74 <td>{recruiter.contactPhoneNumber}</td> 73 75 <td> 74 76 <label className="switch"> … … 76 78 type="checkbox" 77 79 checked={recruiter.hasAccess} 78 onChange={(e) => handleAccessChange(recruiter.id, e.target.checked)}80 onChange={(e) => handleAccessChange(recruiter.id, recruiter.companyName, e.target.checked)} 79 81 /> 80 82 <span className="slider"></span> -
jobvista-frontend/src/views/applications/ApplicationDetailsModal.js
rb248810 rbefb988 1 1 import React, {useEffect, useState} from "react"; 2 import "../ job_advertisements/Form.css";2 import "../shared_css/Modal.css"; 3 3 4 4 import 'react-responsive-modal/styles.css'; … … 19 19 20 20 21 export const ViewApplicationDetails= (props) => {21 export const ApplicationDetailsModal = (props) => { 22 22 const {application} = props 23 23 const [modal, setModal] = useState(false); … … 54 54 <div className="col-md-6"> 55 55 <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="applic taion-textarea"/>56 <textarea disabled type="text" defaultValue={application.questionAnswers[0]} disabled placeholder="Write your answer here..." className="application-textarea"/> 57 57 58 58 59 59 <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="applic taion-textarea"/>60 <textarea disabled type="text" defaultValue={application.questionAnswers[1]} placeholder="Write your answer here..." className="application-textarea"/> 61 61 62 62 63 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="applic taion-textarea"/>64 <textarea disabled type="text" defaultValue={application.questionAnswers[2]} placeholder="Write your answer here..." className="application-textarea"/> 65 65 66 66 </div> … … 73 73 <br/> 74 74 <label className="label">Message to the recruiter</label> 75 <textarea disabled type="text" defaultValue={application.message} placeholder="Optional..." className="applic taion-textarea"/>75 <textarea disabled type="text" defaultValue={application.message} placeholder="Optional..." className="application-textarea"/> 76 76 77 77 </div> -
jobvista-frontend/src/views/applications/Applications.css
rb248810 rbefb988 1 . applications-container {1 .custom-container { 2 2 width: 65% !important; 3 3 max-width: 1500px !important; … … 6 6 7 7 .application-title { 8 font-family: Ubuntu;9 text-transform: uppercase;10 margin : 25px auto;8 font-family: "Segoe UI"; 9 margin-top: 40px; 10 margin-bottom: 20px; 11 11 display: flex; 12 justify-content: center; 12 justify-content: start; 13 border-bottom: 1px solid #CFCFCF; 13 14 } 14 .application-title h 1{15 font-weight: bold;15 .application-title h3 { 16 font-weight: 500; 16 17 } 17 18 … … 28 29 transition: all 0.3s ease; 29 30 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 30 transform: translate(0, 0);31 31 height: auto; 32 32 padding: 20px 20px; 33 33 display: flex; 34 34 justify-content: center; 35 gap: 20px; 35 36 margin: 15px 0; 37 /*z-index: -1000;*/ 38 } 39 .application-card .app-job-seeker-pic { 40 border: 1px solid gray; 41 border-radius: 50% 42 } 43 .application-card .app-job-seeker-pic img { 44 border-radius: 50%; 36 45 } 37 46 38 .application-card .left-box { 39 width: 55%; 40 display: inline-block; 47 .application-card .app-info { 48 width: 60%; 49 display: inline-flex; 50 flex-direction: column; 51 align-items: flex-start; 52 justify-content: center; 53 gap: 10px; 41 54 } 42 55 43 .application-card . left-box.jobAd-title {56 .application-card .app-info .jobAd-title { 44 57 font-weight: 600; 45 58 /*text-transform: uppercase;*/ 46 59 font-family: 'Segoe UI', sans-serif; 47 font-size: 22px;60 /*font-size: 22px;*/ 48 61 } 62 .application-card .app-info .jobAd-title 49 63 50 .application-card . left-box.contact-info {64 .application-card .app-info .contact-info { 51 65 display: inline-flex; 52 gap: 10px;66 gap: 30px; 53 67 } 54 .application-card . left-box.contact-info .contact-item {68 .application-card .app-info .contact-info .contact-item { 55 69 display: inline-flex; 56 70 align-items: center; 57 71 gap: 5px; 72 margin-right: 10px 58 73 } 59 74 60 .application-card .right-box { 61 width: 45%; 75 .application-card .app-info .contact-info .contact-item span { 76 color: darkgray; 77 } 78 79 .application-card .app-status { 80 width: 40%; 62 81 display: inline-flex; 63 82 justify-content: end; … … 66 85 } 67 86 68 .application-card . right-box.select {69 width: 35%;87 .application-card .app-status .select { 88 width: 45%; 70 89 display: inline-block; 71 90 } 91 92 .status { 93 color: white; 94 padding: 5px 10px; 95 border-radius: 5px; 96 width: 150px; 97 text-align: center; 98 } 99 72 100 73 101 .application-button { … … 75 103 border-radius: 8px; 76 104 width: auto; 77 background-color: rgba(207, 235, 255, 1); 78 color: black; 79 font-weight: bold; 80 padding: 5px 10px; 81 transition: 0.2s; 105 background-image: linear-gradient(to bottom, #dddddd, #f0f0f0); 106 background-color: #dddddd; 107 color: #333333; 108 109 font-weight: 700; 110 padding: 8px 13px; 82 111 } 112 83 113 .application-button:hover { 84 background-color: rgb(187, 215, 235); 114 background: linear-gradient(to bottom, #bbbbbb, #dddddd); 115 color: #333333; 85 116 } 86 117 -
jobvista-frontend/src/views/applications/ApplicationsByJobAd.js
rb248810 rbefb988 1 import {useDispatch } from "react-redux";1 import {useDispatch, useSelector} from "react-redux"; 2 2 import {useEffect, useState} from "react"; 3 3 import {useParams} from "react-router"; 4 4 import {ApplicationActions} from "../../redux/actions/applicationActions"; 5 import { ViewApplicationDetails} from "./ViewApplicationDetails";5 import {ApplicationDetailsModal} from "./ApplicationDetailsModal"; 6 6 import "./Applications.css" 7 7 import Select from "react-select"; 8 import {sortElementsBy} from "../../utils/utils"; 9 import {JobSeekerActions} from "../../redux/actions/JobSeekerActions"; 10 import {notifyAppStatusUpdate} from "../../utils/toastUtils"; 8 11 9 12 export const ApplicationsByJobAd = () => { … … 11 14 const dispatch = useDispatch(); 12 15 let {advertisement_id} = useParams(); 16 13 17 const [applicationsByJobAd, setApplicationsByJobAd] = useState([]); 18 let applicationsByJobAdState = useSelector(state => state.appl.applicationsByJobAdId) 19 const [dispatched, setDispatched] = useState(false); 20 21 const [profilePics, setProfilePics] = useState({}); 22 let profilePicsState = useSelector(state => state.images.profilePictures) 23 const [profilePicsDispatched, setProfilePicsDispatched] = useState(false); 24 14 25 const [jobAdTitle, setJobAdTitle] = useState(""); 15 26 16 27 useEffect(() => { 17 dispatch(ApplicationActions.fetchApplicationsByJobAdId(advertisement_id, (success, reponse) => { 18 if (success && reponse.data.length > 0) { 19 setApplicationsByJobAd(reponse.data) 20 setJobAdTitle(reponse.data[0].jobAdTitle) 28 if(!dispatched && (applicationsByJobAdState.length === 0 || applicationsByJobAdState.length === 1)) { 29 dispatch(ApplicationActions.fetchApplicationsByJobAdId(advertisement_id, (success, reponse) => { 30 if (success && reponse.data.length > 0) { 31 setApplicationsByJobAd(sortElementsBy(reponse.data, "submittedOn")) 32 setJobAdTitle(reponse.data[0].jobAdTitle) 33 } 34 setDispatched(true) 35 console.log("Fetch applications by job ad GET") 36 })) 37 } else { 38 setApplicationsByJobAd(sortElementsBy(applicationsByJobAdState, "submittedOn")); 39 if(applicationsByJobAdState.length > 0) { 40 setJobAdTitle(applicationsByJobAdState[0].jobAdTitle) 41 } 42 43 } 44 45 }, [applicationsByJobAdState]) 46 47 useEffect(() => { 48 if(dispatched && !profilePicsDispatched) { 49 applicationsByJobAd.forEach(app => { 50 if(app.jobSeekerId && !profilePics[app.jobSeekerId]) { 51 fetchProfilePic(app.jobSeekerId) 52 } 53 }) 54 setProfilePicsDispatched(true); 55 console.log("Fetch all profile pics GET") 56 } else if(profilePicsDispatched) { 57 setProfilePics(profilePicsState) 58 console.log("Fetch all profile pics STATE") 59 } 60 }, [dispatched]) 61 62 const fetchProfilePic = (jobSeekerId) => { 63 dispatch(JobSeekerActions.downloadProfilePic(jobSeekerId, (success, response) => { 64 if(success) { 65 setProfilePics(prevState => ({...prevState, [jobSeekerId]: response})) 21 66 } 22 67 })) 23 }, []) 68 } 69 24 70 25 71 const options = [ … … 37 83 dispatch(ApplicationActions.updateApplicationStatus(id, selectedOption.value, (success, response) => { 38 84 if(success) { 39 console.log("Status updated.") 85 // console.log("Status updated.") 86 notifyAppStatusUpdate() 40 87 } 41 88 })) … … 43 90 44 91 45 return (<div className=" applications-container">92 return (<div className="custom-container"> 46 93 <div className="application-title"> 47 94 {jobAdTitle ? 48 <h 1>Applications for <span>{jobAdTitle}</span></h1> :95 <h3>Applications for <b>{jobAdTitle}</b></h3> : 49 96 <h1></h1> 50 97 } 51 52 98 </div> 53 99 54 100 {applicationsByJobAd && applicationsByJobAd.map((application, index) => ( 55 101 <div className="application-card"> 56 <div className="left-box"> 102 <div className="app-job-seeker-pic"> 103 <img 104 // loading gif 105 src={profilePicsState[application.jobSeekerId]} 106 alt="" 107 width={75} height={75} 108 /> 109 </div> 110 <div className="app-info"> 57 111 <span>Submitted on <b>{new Date(application.submittedOn).toLocaleString('default', { 58 112 day: 'numeric', … … 60 114 year: 'numeric' 61 115 })}</b></span> 62 <br/><br/>63 116 <div className="contact-info"> 64 117 <div className="contact-item"> 65 118 <i className="fa-solid fa-user"></i> <span>{application.jobSeekerName}</span> 66 </div> 67 <div className="contact-item"> 119 </div> <div className="contact-item"> 68 120 <i className="fa-solid fa-envelope"></i> <span>{application.jobSeekerEmail}</span> 69 </div> 70 <div className="contact-item"> 121 </div> <div className="contact-item"> 71 122 <i className="fa-solid fa-phone"></i> <span>{application.jobSeekerPhoneNumber}</span> 72 123 </div> … … 74 125 </div> 75 126 76 <div className=" right-box">77 < ViewApplicationDetailsapplication={application}/>127 <div className="app-status"> 128 <ApplicationDetailsModal application={application}/> 78 129 <div className="select"> 79 130 <Select options={options} onChange={(selectedOption) => handleChangeStatus(selectedOption, application.id)} defaultValue={handleDefaultStatus(application.status)}/> -
jobvista-frontend/src/views/applications/ApplicationsByJobSeeker.js
rb248810 rbefb988 2 2 import {useEffect, useState} from "react"; 3 3 import {ApplicationActions} from "../../redux/actions/applicationActions"; 4 import { ViewApplicationDetails} from "./ViewApplicationDetails";4 import {ApplicationDetailsModal} from "./ApplicationDetailsModal"; 5 5 import Select from "react-select"; 6 7 import {RecruiterActions} from "../../redux/actions/recruiterActions"; 8 import {sortElementsBy} from "../../utils/utils"; 6 9 7 10 export const ApplicationsByJobSeeker = () => { 8 11 const dispatch = useDispatch(); 12 const auth = useSelector(state => state.auth.currentUser); 13 9 14 const [applicationsByJobSeeker, setApplicationsByJobSeeker] = useState([]); 10 15 let applicationsByJobSeekerState = useSelector(state => state.appl.applicationsByJobSeeker); 11 16 const [dispatched, setDispatched] = useState(false); 12 17 18 const [logos, setLogos] = useState({}); 19 let logosState = useSelector(state => state.images.logos) 20 const [logoDispatched, setLogoDispatched] = useState(false); 21 22 23 13 24 useEffect(() => { 14 if(!dispatched && applicationsByJobSeekerState.length === 0) {15 dispatch(ApplicationActions.fetchApplicationsByJobSeeker( (success, response) => {25 if(!dispatched && (applicationsByJobSeekerState.length === 0 || applicationsByJobSeekerState.length === 1) ) { 26 dispatch(ApplicationActions.fetchApplicationsByJobSeeker(auth.id, (success, response) => { 16 27 if(success && response.data.length > 0) { 17 setApplicationsByJobSeeker( response.data);28 setApplicationsByJobSeeker(sortElementsBy(response.data, "submittedOn")); 18 29 } 30 setDispatched(true) 19 31 console.log("Fetch applications by job seeker GET") 20 32 })) 21 setDispatched(true) 33 22 34 } else { 23 setApplicationsByJobSeeker( applicationsByJobSeekerState);35 setApplicationsByJobSeeker(sortElementsBy(applicationsByJobSeekerState, "submittedOn")); 24 36 console.log("Fetch applications by job seeker STATE") 25 37 } 26 38 }, [applicationsByJobSeekerState]) 27 39 40 useEffect(() => { 41 42 if(dispatched && !logoDispatched) { 43 applicationsByJobSeeker.forEach(jobAd => { 44 if(jobAd.recruiterId && !logos[jobAd.recruiterId]) { 45 fetchLogo(jobAd.recruiterId); 46 } 47 }) 48 setLogoDispatched(true) 49 console.log("Fetch all logos GET") 50 } else if (logoDispatched){ 51 setLogos(logosState) 52 console.log("Fetch all logos STATE") 53 54 } 55 56 }, [dispatched, logosState]) 57 58 59 60 const fetchLogo = (recruiterId) => { 61 dispatch(RecruiterActions.downloadLogo(recruiterId, (success, response) => { 62 if(success) { 63 setLogos(prevLogos => ({...prevLogos, [recruiterId]: response})) 64 } 65 })); 66 }; 67 28 68 const options = [ 29 {value: 'PROPOSED', label: <span ><i className="fa-solid fa-paper-plane"></i> Proposed</span>},30 {value: 'UNDER_REVIEW', label: <span ><i className="fa-solid fa-file-pen"></i> Under Review</span>},31 {value: 'ACCEPTED', label: <span ><i className="fa-solid fa-user-check"></i> Accepted</span>},32 {value: 'DENIED', label: <span ><i className="fa-solid fa-user-slash"></i> Denied</span>}69 {value: 'PROPOSED', label: <span className="status" style={{backgroundColor: '#4A90E2'}}><i className="fa-solid fa-paper-plane"></i> Proposed</span>}, 70 {value: 'UNDER_REVIEW', label: <span className="status" style={{backgroundColor: '#F5A623'}}><i className="fa-solid fa-file-pen"></i> Under Review</span>}, 71 {value: 'ACCEPTED', label: <span className="status" style={{backgroundColor: '#7ED321'}}><i className="fa-solid fa-user-check"></i> Accepted</span>}, 72 {value: 'DENIED', label: <span className="status" style={{backgroundColor: '#D0021B'}}><i className="fa-solid fa-user-slash"></i> Denied</span>} 33 73 ]; 34 74 … … 40 80 41 81 return ( 42 <div className="applications-container"> 82 <div className="custom-container"> 83 43 84 <div className="application-title"> 44 85 <h3>Application history</h3> 45 86 </div> 46 87 {applicationsByJobSeeker && applicationsByJobSeeker.map((application, index) => ( 47 88 <div key={index} className="application-card"> 48 <div className="left-box"> 49 <span className="jobAd-title">{application.jobAdTitle}</span> 50 <br/> 51 <br/> 89 <div className="app-company-logo"> 90 <img 91 // loading gif 92 src={logosState[application.recruiterId]} 93 alt="" 94 width={75} height={75} 95 /> 96 </div> 97 98 <div className="app-info"> 99 <div className="jobAd-title">{application.jobAdTitle}</div> 52 100 <div className="contact-info"> 53 101 <div className="contact-item"> … … 68 116 </div> 69 117 70 <div className="right-box"> 71 <ViewApplicationDetails application={application}/> 72 <div className="select"> 73 <Select isDisabled={true} options={options} defaultValue={handleDefaultValue(application.status)}/> 74 </div> 118 <div className="app-status"> 119 <ApplicationDetailsModal application={application}/> 120 <> {handleDefaultValue(application.status).label}</> 121 {/*<div className="select">*/} 122 {/* <Select isDisabled={true} options={options} />*/} 123 {/*</div>*/} 75 124 76 125 </div> -
jobvista-frontend/src/views/applications/ApplyToJobAdModal.js
rb248810 rbefb988 1 1 import React, {useState} from "react"; 2 import "../ job_advertisements/Form.css";2 import "../shared_css/Modal.css"; 3 3 4 4 import 'react-responsive-modal/styles.css'; 5 5 import {Modal} from 'react-responsive-modal'; 6 import Select from "react-select";7 6 8 7 //Validation … … 12 11 13 12 14 import {employmentStatusOptions, industryOptions, jobTypeOptions} from "../selectOptions";15 13 import {useDispatch, useSelector} from "react-redux"; 16 import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";17 14 import Roles from "../../enumerations/Roles"; 18 15 import {ApplicationActions} from "../../redux/actions/applicationActions"; 16 import {notifyJobAdApply} from "../../utils/toastUtils"; 19 17 20 18 … … 55 53 formData,(success, response) => { 56 54 if(success) { 57 console.log("Job Advertisement added")58 55 toggleModal() 56 notifyJobAdApply() 59 57 } 60 58 } … … 70 68 {role===Roles.JOBSEEKER && 71 69 <> 72 {jobAd.active && <button onClick={toggleModal} className=" card-button apply">Apply now</button> }70 {jobAd.active && <button onClick={toggleModal} className="apply-button apply">Apply now</button> } 73 71 {!jobAd.active && <button className="card-button apply disabled">Apply now</button> } 74 72 </> … … 86 84 <label className="label">Why are you interested in joining our company?</label> 87 85 <textarea type="text" placeholder="Write your answer here..." 88 {...register("answerOne")} className="applic taion-textarea"/>86 {...register("answerOne")} className="application-textarea"/> 89 87 <p style={{color: "red"}}>{errors.answerOne?.message}</p> 90 88 91 89 <label className="label">What makes you a good fit for this position?</label> 92 90 <textarea type="text" placeholder="Write your answer here..." 93 {...register("answerTwo")} className="applic taion-textarea"/>91 {...register("answerTwo")} className="application-textarea"/> 94 92 <p style={{color: "red"}}>{errors.answerTwo?.message}</p> 95 93 96 94 <label className="label">What do you hope to achieve in your first 6 months in this role?</label> 97 95 <textarea type="text" placeholder="Write your answer here..." 98 {...register("answerThree")} className="applic taion-textarea"/>96 {...register("answerThree")} className="application-textarea"/> 99 97 <p style={{color: "red"}}>{errors.answerThree?.message}</p> 100 98 … … 103 101 <label htmlFor="start">Curriculum vitae (CV)</label> 104 102 <br/> 105 <input {...register("file")} onChange={(e) => setResumeFile(e.target.files[0])} required type="file" id="fileUpload" accept=".pdf"/>103 <input {...register("file")} className="resume-link" onChange={(e) => setResumeFile(e.target.files[0])} required type="file" id="fileUpload" accept=".pdf"/> 106 104 107 105 <br/> 108 106 <label className="label">Message to the recruiter</label> 109 107 <textarea type="text" placeholder="Optional..." 110 {...register("messageToRecruiter")} className="applic taion-textarea"/>108 {...register("messageToRecruiter")} className="application-textarea"/> 111 109 112 110 <br/><br/> … … 115 113 </div> 116 114 117 <div className="aligned"> 115 <div className="modal-buttons"> 116 <div className="cancel-btn" onClick={toggleModal}> Cancel</div> 118 117 <button className="submit-btn"> Submit</button> 119 118 </div> -
jobvista-frontend/src/views/auth/SignInForm.js
rb248810 rbefb988 8 8 import {yupResolver} from "@hookform/resolvers/yup"; 9 9 import {AuthActions} from "../../redux/actions/authActions"; 10 import {notifyIncorrectEmailOrPassword} from "../../utils/toastUtils"; 10 11 11 12 export const SignInForm = () => { … … 27 28 const signIn = async (values) => { 28 29 try { 29 dispatch(AuthActions.signIn(values.emailLog, values.passwordLog, 30 success => { 31 // createSnackbar({ 32 // message: success ? 'Successfully signed up.' : 'Error while signing up. Try again!', 33 // timeout: 2500, 34 // theme: success ? 'success' : 'error' 35 // }); 36 success && navigate("/"); 30 dispatch(AuthActions.signIn(values.emailLog, values.passwordLog, success => { 31 if(success) { 32 navigate("/") 33 } else { 34 notifyIncorrectEmailOrPassword() 35 } 37 36 })); 38 37 } catch (err) { 39 console.error(err);38 // console.error(err); 40 39 } 41 40 } -
jobvista-frontend/src/views/dashboard/Dashboard.js
rb248810 rbefb988 1 import ". /Dashboard.css"1 import "../shared_css/Random.css" 2 2 3 3 import {useDispatch, useSelector} from "react-redux"; … … 12 12 import {AUTH_TOKEN} from "../../axios/axiosInstance"; 13 13 import {jwtDecode} from "jwt-decode"; 14 15 export const Dashboard = (props) => { 14 import {RecruiterActions} from "../../redux/actions/recruiterActions"; 15 16 export const Dashboard = () => { 16 17 17 18 const dispatch = useDispatch(); … … 19 20 const [jobAdvertisements, setJobAdvertisements] = useState([]); 20 21 let jobAdvertisementsState = useSelector(state => state.jobAd.jobAdvertisements) 22 const [jobDispatched, setJobDispatched] = useState(false); 23 24 const [logos, setLogos] = useState({}); 25 let logosState = useSelector(state => state.images.logos) 26 const [logoDispatched, setLogoDispatched] = useState(false); 27 21 28 const auth = useSelector(state => state.auth); 22 29 … … 25 32 const [selectedIndustry, setSelectedIndustry] = useState("all"); 26 33 const [searchTerm, setSearchTerm] = useState(""); 27 const [dispatched, setDispatched] = useState(false) 34 35 28 36 29 37 // const [user, setUser] = useState(null); … … 54 62 55 63 useEffect(() => { 56 if (! dispatched && jobAdvertisementsState.length == 0) {64 if (!jobDispatched && jobAdvertisementsState.length == 0) { 57 65 dispatch(JobAdvertisementActions.fetchJobAdvertisements((success, response) => { 58 66 if (success && response.data.length > 0) { 59 setJobAdvertisements(sortElementsBy(response.data ))67 setJobAdvertisements(sortElementsBy(response.data, "postedOn")) 60 68 } 61 set Dispatched(true)69 setJobDispatched(true) 62 70 console.log("Fetch all job advertisements GET") 63 71 })) … … 66 74 setJobAdvertisements(jobAdvertisementsState) 67 75 console.log("Fetch all job advertisements STATE") 76 77 68 78 } 69 79 }, [jobAdvertisementsState]) 80 81 useEffect(() => { 82 83 if(jobDispatched && !logoDispatched) { 84 jobAdvertisements.forEach(jobAd => { 85 if(jobAd.recruiterId && !logos[jobAd.recruiterId]) { 86 fetchLogo(jobAd.recruiterId); 87 } 88 }) 89 setLogoDispatched(true) 90 console.log("Fetch all logos GET") 91 } else { 92 setLogos(logosState) 93 console.log("Fetch all logos STATE") 94 } 95 96 }, [jobDispatched, logosState]) 97 98 const fetchLogo = (recruiterId) => { 99 dispatch(RecruiterActions.downloadLogo(recruiterId, (success, response) => { 100 if(success) { 101 setLogos(prevLogos => ({...prevLogos, [recruiterId]: response})) 102 } 103 })); 104 }; 70 105 71 106 let filterJobAdvertisements = () => { … … 86 121 87 122 <div className="container"> 88 <div className=" head-dashboard-box">123 <div className="filter-container"> 89 124 <div className="row"> 90 <div className="col-md-12 filter- container">125 <div className="col-md-12 filter-box"> 91 126 <div className="search-container"> 92 127 <i className="fa-solid fa-magnifying-glass search-icon"></i> … … 117 152 /> 118 153 </div> 119 <button onClick={filterJobAdvertisements} className="b tn-open-modal">Find jobs</button>154 <button onClick={filterJobAdvertisements} className="blue-submit-button">Find jobs</button> 120 155 </div> 121 {/*<div className="col-md-3">*/}122 123 {/*<div className="date-range-section item">*/}124 {/* <Select*/}125 {/* defaultValue={{value: "all", label: "Lifetime"}}*/}126 {/* //value={selectedDateRange.value}*/}127 {/* //onChange={option => setSelectedDateRange(option.value)}*/}128 {/* options={dataRangeOptions}*/}129 {/* className="date-range sort"*/}130 {/* />*/}131 {/*</div>*/}132 {/*</div>*/}133 156 </div> 134 157 </div> 135 <div className="row row-cols-1 row-cols-md-4 g-4"> 136 158 <div className="row row-cols-2 row-cols-md-4 g-4"> 137 159 {jobAdvertisements && 138 160 jobAdvertisements.map((jobAd, index) => ( … … 146 168 </div> 147 169 <div className="card-body"> 170 <img 171 // loading gif 172 src={logos[jobAd.recruiterId]} 173 alt="" 174 width={100} height={100} 175 /> 148 176 <h5 className="card-title">{jobAd.title}</h5> 149 177 <span>{jobAd.industry} • <span style={{ -
jobvista-frontend/src/views/job_advertisements/AddJobAdModal.js
rb248810 rbefb988 1 1 import React, {useState} from "react"; 2 import ". /Form.css";2 import "../shared_css/Modal.css"; 3 3 4 4 import { Editor } from 'primereact/editor'; … … 17 17 import {useDispatch, useSelector} from "react-redux"; 18 18 import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions"; 19 import {notifyJobAdPost} from "../../utils/toastUtils"; 19 20 20 21 … … 32 33 description: yup.string().required("Please enter a description"), 33 34 industry: yup.mixed().required("Select industry"), 34 startingSalary: yup. number().required("Please enter the starting salary"),35 startingSalary: yup.string().required("Please enter the starting salary"), 35 36 jobType: yup.mixed().required("Select job type"), 36 37 employmentStatus: yup.mixed().required("Select employment status"), … … 46 47 dispatch(JobAdvertisementActions.addJobAdvertisement( 47 48 { 48 email: auth.email,49 id: auth.id, 49 50 title: values.title, 50 51 description: values.description, … … 56 57 }, (success, response) => { 57 58 if (success) { 58 console.log("Job Advertisement added")59 // console.log("Job Advertisement added") 59 60 toggleModal() 61 notifyJobAdPost() 60 62 } 61 63 } … … 75 77 </div> 76 78 {/*<button onClick={toggleModal} className="btn-open-modal">POST ADVERTISEMENT</button>*/} 77 <Modal open={modal} onClose={toggleModal} center classNames="job-advertisement-modal">79 <Modal open={modal} onClose={toggleModal} center> 78 80 <div className="head-modal"> 79 81 <h3>Post Job Advertisement</h3> … … 82 84 83 85 <div className="modal-content"> 84 <form onSubmit={handleSubmit(addJobAdvertisement)}>86 <form> 85 87 <div className="row"> 86 88 <div className="col-md-7"> … … 111 113 <div className="col-md-5"> 112 114 <label className="label">Hourly rate:</label> 113 <input {...register("startingSalary")}/>115 <input type="number" {...register("startingSalary")}/> 114 116 <p style={{color: "red"}}>{errors.startingSalary?.message}</p> 115 117 … … 157 159 </div> 158 160 159 <div className="aligned"> 160 <button className="submit-btn"> Submit</button> 161 <div className="modal-buttons"> 162 <div className="cancel-btn" onClick={toggleModal}> Cancel</div> 163 <button className="submit-btn" onClick={handleSubmit(addJobAdvertisement)}>Submit</button> 161 164 </div> 162 165 -
jobvista-frontend/src/views/job_advertisements/DeleteJobAdModal.js
rb248810 rbefb988 1 1 import React, {useState} from "react"; 2 import ". /Form.css";2 import "../shared_css/Modal.css"; 3 3 4 4 import 'react-responsive-modal/styles.css'; … … 15 15 import {useDispatch, useSelector} from "react-redux"; 16 16 import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions"; 17 import {notifyJobAdDelete} from "../../utils/toastUtils"; 17 18 18 19 … … 29 30 dispatch(JobAdvertisementActions.deleteJobAdvertisement(jobAd.props.id, (success, response) => { 30 31 if (success) { 31 console.log("Job Advertisement deleted")32 // console.log("Job Advertisement deleted") 32 33 toggleModal() 34 notifyJobAdDelete() 33 35 } 34 36 })) -
jobvista-frontend/src/views/job_advertisements/EditJobAdModal.js
rb248810 rbefb988 1 1 import React, {useEffect, useState} from "react"; 2 import ". /Form.css";2 import "../shared_css/Modal.css"; 3 3 4 4 import 'react-responsive-modal/styles.css'; … … 16 16 import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions"; 17 17 import {Editor} from "primereact/editor"; 18 import {notifyJobAdDelete, notifyJobAdEdit} from "../../utils/toastUtils"; 18 19 19 20 … … 23 24 const auth = useSelector(state => state.auth.currentUser) 24 25 const toggleModal = () => { 26 console.log("NAD SET") 25 27 setModal(!modal); 28 console.log("POD SET") 29 26 30 }; 27 31 … … 52 56 }, jobAd.props.id, (success, response) => { 53 57 if(success) { 54 console.log("Job Advertisement edited")58 // console.log("Job Advertisement edited") 55 59 toggleModal() 60 notifyJobAdEdit() 56 61 } 57 62 } … … 78 83 79 84 <div className="modal-content"> 80 <form onSubmit={handleSubmit(editJobAdvertisement)}>85 <form> 81 86 <div className="row"> 82 87 <div className="col-md-7"> … … 92 97 name="description" 93 98 control={control} 99 defaultValue={jobAd.props.description} 94 100 render={({ field }) => ( 95 101 <Editor 96 defaultValue={jobAd.props.description}102 // defaultValue={jobAd.props.description} 97 103 value={jobAd.props.description} 98 104 onTextChange={(e) => field.onChange(e.htmlValue)} … … 107 113 <div className="col-md-5"> 108 114 <label className="label">Hourly rate:</label> 109 <input defaultValue={jobAd.props.startingSalary} {...register("startingSalary")}/>115 <input type="number" defaultValue={jobAd.props.startingSalary} {...register("startingSalary")}/> 110 116 <p style={{color: "red"}}>{errors.startingSalary?.message}</p> 111 117 … … 166 172 </div> 167 173 168 <div className="aligned"> 169 <button className="submit-btn"> Submit</button> 174 <div className="modal-buttons"> 175 <div className="cancel-btn" onClick={toggleModal}> Cancel</div> 176 <button className="submit-btn" onClick={handleSubmit(editJobAdvertisement)}> Save changes</button> 170 177 </div> 171 172 178 </form> 173 179 </div> -
jobvista-frontend/src/views/job_advertisements/JobAdDetails.css
rb248810 rbefb988 10 10 .details-wrap { 11 11 width: 100%; 12 //height: auto;13 height: 80vh;12 height: auto; 13 max-height: 80vh; 14 14 overflow-y: auto; 15 background-color: #fff; 16 border-radius: 12px; 17 padding: 15px 20px; 18 margin-bottom: 20px; 19 20 /*scrollbar-width: thin; !* "auto" hides scrollbar on some browsers, "thin" shows a thin scrollbar *!*/ 21 /*scrollbar-color: #999999 #fff;*/ 22 } 23 24 .details-wrap .span-about { 25 color: darkgray; 26 } 27 28 29 30 .details-wrap-profile { 31 width: 100%; 32 height: auto; 33 15 34 background-color: #fff; 16 35 border-radius: 12px; … … 56 75 } 57 76 77 .apply-button { 78 border: 0; 79 border-radius: 8px; 80 width: 45%; 81 background-image: linear-gradient(to right, #a1c4fd 0%, aliceblue 61%, #a1c4fd 100%); 82 background-size: 200% auto; 83 font-weight: bold; 84 padding: 5px 10px; 85 transition: 0.4s; 86 } 87 88 .apply-button:not(.disabled):hover{ 89 background-color: rgb(187, 215, 235); 90 color: black; 91 } 92 93 .disabled { 94 cursor: default !important; 95 opacity: 0.6; 96 } 97 58 98 .apply:not(.expired) { 59 99 width: 20% !important; … … 65 105 background-position: right center; 66 106 } 107 108 .recruiter-link { 109 color: black; 110 font-size: 30px; 111 text-decoration:none; 112 } -
jobvista-frontend/src/views/job_advertisements/JobAdDetails.js
rb248810 rbefb988 10 10 import {AddJobAdModal} from "./AddJobAdModal"; 11 11 import {ApplyToJobAdModal} from "../applications/ApplyToJobAdModal"; 12 import {Link} from "react-router-dom"; 13 import {RecruiterActions} from "../../redux/actions/recruiterActions"; 12 14 13 15 … … 20 22 const auth = useSelector(state => state.auth.currentUser); 21 23 24 let logosState = useSelector(state => state.images.logos) 25 const [logoDispatched, setLogoDispatched] = useState(false) 26 const [logoView, setLogoView] = useState(null); 27 22 28 useEffect(() => { 23 29 setRole(auth.role) 24 30 }, [auth]) 31 32 useEffect(() => { 33 if (jobAd) { 34 if (!logoDispatched && !logosState[jobAd.recruiterId]) { 35 dispatch(RecruiterActions.downloadLogo(jobAd.recruiterId, (success, response) => { 36 if (success) { 37 setLogoView(response) 38 setLogoDispatched(true) 39 } 40 })) 41 } else { 42 setLogoView(logosState[jobAd.recruiterId]) 43 } 44 45 } 46 47 }, [jobAd]) 25 48 26 49 … … 41 64 <div className="col-md-9"> 42 65 <div className="details-wrap"> 66 <div className="row"> 67 <div className="col-md-9"> 68 <div className="title"> 69 <h2>{jobAd.title} </h2> 70 <span className="job-type"> {jobAd.jobType===JobType.JOB ? "Job" : "Internship"}</span> 71 {!jobAd.active && <span className="expired">Expired</span>} 72 </div> 43 73 44 <div className="title"> 45 <h2>{jobAd.title} </h2> 46 <span className="job-type"> {jobAd.jobType===JobType.JOB ? "Job" : "Internship"}</span> 47 {!jobAd.active && <span className="expired">Expired</span>} 74 <p className="details-head-info"> 75 <span><b>{jobAd.recruiterName}</b></span> • <span>{jobAd.industry}</span> • <span>{formatRelativeTime(jobAd.postedOn)}</span> 76 </p> 77 78 <p><i className="fa-solid fa-money-check-dollar"></i> <span>Hourly rate: ${jobAd.startingSalary}</span></p> 79 <p><i className="fa-solid fa-briefcase"></i> Employment status: {jobAd.employmentStatus==="FULL_TIME" ? "Full-time" : "Part-time"}</p> 80 <p><i className="fa-solid fa-calendar-days"></i> Active until: {new Date(jobAd.activeUntil).toLocaleString('default', { day: 'numeric', month: 'long', year: 'numeric' })}</p> 81 82 </div> 83 <div className="col-md-3"> 84 {jobAd.recruiterId && 85 <> 86 <img 87 // loading gif 88 src={logosState[jobAd.recruiterId]} 89 alt="" 90 width={200} height={200} 91 /> 92 </> 93 } 94 95 </div> 48 96 </div> 49 97 50 <p className="details-head-info">51 <span><b>{jobAd.recruiterName}</b></span> • <span>{jobAd.industry}</span> • <span>{formatRelativeTime(jobAd.postedOn)}</span>52 </p>53 54 <p><i className="fa-solid fa-money-check-dollar"></i> <span>Hourly rate: ${jobAd.startingSalary}</span></p>55 <p><i className="fa-solid fa-briefcase"></i> Employment status: {jobAd.employmentStatus==="FULL_TIME" ? "Full-time" : "Part-time"}</p>56 <p><i className="fa-solid fa-calendar-days"></i> Active until: {new Date(jobAd.activeUntil).toLocaleString('default', { day: 'numeric', month: 'long', year: 'numeric' })}</p>57 98 58 99 <h4>About the job</h4> … … 66 107 <div className="col-md-3"> 67 108 <div className="details-wrap"> 68 < h3>{jobAd.recruiterName}</h3>109 <Link className="recruiter-link" to={`/recruiters/${jobAd.recruiterId}`}>{jobAd.recruiterName} </Link> 69 110 70 {/*TO DO - AFTER IMPLEMENTING FORM FOR UPDATING PERSONAL INFO*/}71 111 <h4>About the company</h4> 72 112 <p> 73 For over two decades, we have been harnessing technology to drive meaningful change. Working side by side with leading brands, we build strategies, products and solutions tailored to unique needs –regardless of industry, region or scale. By combining world-class engineering, industry expertise and a people-centric mindset, we consult and partner with our customers to create technological solutions that drive innovation and transform businesses. 74 <br/><br/> 75 From ideation to production, we support our customers with bespoke solutions across various industries, including payments, insurance, finance and banking, technology, media and entertainment, telecommunications, retail and consumer goods, supply chain and logistics, healthcare and life sciences, energy and resources, government, automotive and travel. 113 {recruiterDetails.companyDescription 114 ? recruiterDetails.companyDescription.length > 710 115 ? `${recruiterDetails.companyDescription.substring(0, 710)}...` 116 : recruiterDetails.companyDescription 117 : "There is no info about this company yet." 118 } 119 </p> 76 120 121 <p> 122 <i className="fa-solid fa-envelope"></i> <span className="span-about"> {recruiterDetails.contactEmail}</span> <br/> 123 <i className="fa-solid fa-phone"></i> <span className="span-about"> {recruiterDetails.contactPhoneNumber}</span> <br/> 124 {recruiterDetails.receptionist && <><i className="fa-solid fa-user-tie"></i> <span className="span-about"> {recruiterDetails.receptionist}</span> </>} 77 125 </p> 78 <p><span><i className="fa-solid fa-envelope"></i> {recruiterDetails.email}</span> • <span> 79 <i className="fa-solid fa-phone"></i> {recruiterDetails.phoneNumber}</span> 80 </p> 126 127 <div className="d-flex justify-content-center mt-4"> 128 <Link className="card-button" to={`/recruiters/${jobAd.recruiterId}`}>Read more </Link> 129 </div> 81 130 </div> 82 131 </div> -
jobvista-frontend/src/views/job_advertisements/JobAdvertisements.css
rb248810 rbefb988 1 .photo-box { 2 position: relative; 3 margin-bottom: 140px; 4 } 5 6 .photo-box div img { 7 background-color: rgb(243, 242, 241); 8 } 9 10 .company-banner { 11 width: 100%; 12 height: 300px; 13 object-fit: cover; 14 object-position: initial; 15 display: block; 16 border-radius: 10px; 17 } 18 19 .company-logo { 20 position: absolute; 21 border-radius: 25px; 22 border: 5px solid rgb(243, 242, 241); 23 bottom: -110px; 24 left: 70px 25 } 26 27 .info-tab { 28 margin: 20px 10px; 29 position: absolute; 30 left: 280px; 31 font-family: Poppins; 32 } 33 34 .photo-box .edit-buttons { 35 display: flex; 36 gap: 10px; 37 position: absolute; 38 right: 20px; 39 bottom: -100px; 40 } 41 42 .edit-buttons .exclamation { 43 display: inline; 44 position: relative; 45 } 46 47 .fa-circle-exclamation { 48 color: #d80e0e; 49 50 } 51 52 .edit-buttons .modal-wrap .react-responsive-modal-modal .head-modal { 53 background-color: white !important; 54 color: black !important; 55 } 56 57 .edit-buttons button { 58 background-color: white; 59 border: none; 60 border-radius: 5px; 61 padding: 7px 13px; 62 transition: 0.1s; 63 } 64 65 .edit-buttons button:hover { 66 /*background-color: #F2F2F2;*/ 67 background-color: lightgray; 68 } 69 70 .my-workspace { 71 position: relative; 72 } 73 74 .custom-text-area { 75 height: 14rem !important; 76 } 77 78 .line-separator { 79 width: 95%; 80 border-top: 1px solid gray; 81 border-radius: 5px; 82 margin: auto; 83 margin-bottom: 30px; 84 } 85 86 87 .confirmation-bar { 88 position: fixed; 89 //top: 80px; 90 left: 0; 91 width: calc(100% - 17px); 92 background-color: rgba(0, 0, 0, 0.5); /* Adjust background color as needed */ 93 //background-color: #7D7D7A; /* Adjust background color as needed */ 94 padding: 10px; 95 z-index: 11; 96 97 } 98 .confirmation-bar-buttons { 99 display: flex; 100 justify-content: right; 101 gap: 10px; 102 margin-right: 60px 103 } 104 .confirmation-bar .confirmation-bar-buttons button { 105 padding: 5px 20px; 106 border: none; 107 border-radius: 8px; 108 color: white; 109 transition: 0.1s; 110 } 111 .confirmation-bar .confirmation-bar-buttons .cancel-changes { 112 background-color: #8D8D8B; 113 } 114 115 .confirmation-bar .confirmation-bar-buttons .cancel-changes:hover { 116 background-color: #9B9A99; 117 } 118 119 .confirmation-bar .confirmation-bar-buttons .save-changes { 120 background-color: #0866FF; 121 122 } 123 .confirmation-bar .confirmation-bar-buttons .save-changes:hover { 124 background-color: dodgerblue; 125 } 126 -
jobvista-frontend/src/views/job_advertisements/RecruiterWorkspace.js
rb248810 rbefb988 2 2 3 3 import "./JobAdvertisements.css" 4 import "../shared_css/Random.css" 5 4 6 import {useDispatch, useSelector} from "react-redux"; 5 7 import {useEffect, useState} from "react"; … … 12 14 import {Link} from "react-router-dom"; 13 15 import JobType from "../../enumerations/JobType"; 16 import {RecruiterActions} from "../../redux/actions/recruiterActions"; 14 17 15 export const JobAdvertisements = (props) => { 18 19 export const Workspace = (props) => { 16 20 17 21 const dispatch = useDispatch(); 22 const [dispatched, setDispatched] = useState(false) 23 24 const auth = useSelector(state => (state.auth.currentUser)) 25 18 26 const [jobAdvertisementsByRecruiter, setJobAdvertisementsByRecruiter] = useState([]); 19 const auth = useSelector(state => (state.auth.currentUser))20 27 let jobAdvertisementsByRecruiterState = useSelector(state => (state.jobAd.jobAdvertisementsByRecruiter)) 21 28 22 const [role, setRole] = useState(""); 29 const [recruiterDetails, setRecruiterDetails] = useState(null); 30 23 31 const [selectedSortOrder, setSelectedSortOrder] = useState("date_newest"); 24 32 const [selectedIndustry, setSelectedIndustry] = useState("all"); 25 33 const [searchTerm, setSearchTerm] = useState(""); 26 const [dispatched, setDispatched] = useState(false) 34 35 const [activeJobListingsCount, setActiveJobListingsCount] = useState(0); 36 37 useEffect(() => { 38 if (auth) { 39 dispatch(RecruiterActions.fetchRecruiterEditDetailsById(auth.id, (success, response) => { 40 if (success) { 41 setRecruiterDetails(response.data) 42 } 43 })) 44 } 45 }, [auth]); 27 46 28 47 29 48 useEffect(() => { 30 if (auth) {31 setRole(auth.role);32 }33 }, [auth]);34 35 useEffect(() => {36 49 if (!dispatched && jobAdvertisementsByRecruiterState.length === 0) { 37 dispatch(JobAdvertisementActions.fetchJobAdvertisementsByRecruiter( (success, response) => {50 dispatch(JobAdvertisementActions.fetchJobAdvertisementsByRecruiter(auth.id, (success, response) => { 38 51 if (success && response.data.length > 0) { 39 52 setJobAdvertisementsByRecruiter(sortElementsBy(response.data)) … … 46 59 setJobAdvertisementsByRecruiter(jobAdvertisementsByRecruiterState) 47 60 console.log("Fetch job advertisements by recruiter STATE") 61 62 setActiveJobListingsCount(countActiveJobListings(jobAdvertisementsByRecruiterState)); 48 63 } 64 49 65 }, [jobAdvertisementsByRecruiterState]) 50 66 51 67 let filterJobAdvertisements = () => { 52 JobAdvertisementActions.filterJobAdvertisementsByRecruiter( 53 { 54 searchTerm: searchTerm, 55 industry: selectedIndustry, 56 sortOrder: selectedSortOrder 57 }, (success, response) => { 58 if (success) { 59 setJobAdvertisementsByRecruiter(response.data); 60 } 68 JobAdvertisementActions.filterJobAdvertisementsByRecruiter({ 69 searchTerm: searchTerm, industry: selectedIndustry, sortOrder: selectedSortOrder 70 }, (success, response) => { 71 if (success) { 72 setJobAdvertisementsByRecruiter(response.data); 61 73 } 62 )74 }) 63 75 } 64 76 65 return ( 66 <div className="container"> 67 <div className="head-dashboard-box"> 77 function countActiveJobListings(jobAds) { 78 if (jobAds.length > 0) { 79 const activeJobListings = jobAds.filter(job => job.active) 80 return activeJobListings.length; 81 } 82 return 0; 83 } 84 85 return (<div className="container"> 86 87 {/*<div className="line-separator"></div>*/} 88 89 <div className="filter-container"> 68 90 <div className="row"> 69 <div className="col-md-12 filter- container">91 <div className="col-md-12 filter-box"> 70 92 <div className="search-container"> 71 93 <i className="fa-solid fa-magnifying-glass search-icon"></i> … … 96 118 /> 97 119 </div> 98 <button onClick={filterJobAdvertisements} className="b tn-open-modal">Find jobs</button>120 <button onClick={filterJobAdvertisements} className="blue-submit-button">Find jobs</button> 99 121 </div> 100 122 </div> 101 123 </div> 102 <div className="row row-cols-1 row-cols-md- 4 g-4">124 <div className="row row-cols-1 row-cols-md-5 g-3"> 103 125 <AddJobAdModal/> 104 126 105 {jobAdvertisementsByRecruiter && 106 jobAdvertisementsByRecruiter.map((jobAd, index) => ( 107 <div key={index} className="col"> 108 <div className="custom-card"> 109 <div className="card-head"> 110 <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span> 111 <span 112 className="job-type"> {jobAd.jobType === JobType.JOB ? "Job" : "Internship"}</span> 113 {!jobAd.active && <span className="expired">Expired</span>} 114 <div className="card-management-btns"> 115 <DeleteJobAdModal props={jobAd}/> 116 <EditJobAdModal props={jobAd}/> 117 </div> 127 {jobAdvertisementsByRecruiter && jobAdvertisementsByRecruiter.map((jobAd, index) => ( 128 <div key={index} className="col"> 129 <div className="custom-card"> 130 <div className="card-head"> 131 <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span> 132 <span 133 className="job-type"> {jobAd.jobType === JobType.JOB ? "Job" : "Internship"}</span> 134 {!jobAd.active && <span className="expired">Expired</span>} 135 <div className="card-management-btns"> 136 <DeleteJobAdModal props={jobAd}/> 137 <EditJobAdModal props={jobAd}/> 118 138 </div> 119 <div className="card-body">120 <h5 className="card-title">{jobAd.title}</h5>121 <span>{jobAd.industry} • <span style={{122 color: "black",123 124 125 139 </div> 140 <div className="card-body"> 141 <h5 className="card-title">{jobAd.title}</h5> 142 <span>{jobAd.industry} • <span style={{ 143 color: "black", fontWeight: "bold" 144 }}>{formatRelativeTime(jobAd.postedOn)}</span></span> 145 <div className="card-info"> 126 146 <span><i className="fa-solid fa-building" 127 147 style={{color: "#000000"}}></i> Company: <span style={{ 128 color: "black", 129 fontWeight: "bold" 148 color: "black", fontWeight: "bold" 130 149 }}>{jobAd.recruiterName}</span></span> <br/> 131 150 </div> 132 151 133 134 <Link to={`/my-job-advertisements/${jobAd.id}/applications`}135 136 152 <div className="aligned"> 153 <Link to={`/job-management-hub/applications/${jobAd.id}`} 154 className="card-button solo">View applications</Link> 155 </div> 137 156 138 </div>139 157 </div> 140 158 </div> 141 ))} 142 159 </div>))} 143 160 </div> 144 161 </div> 162 163 145 164 ) 146 165 } -
jobvista-frontend/src/views/shared_css/Modal.css
rb248810 rbefb988 2 2 .modal-wrap { 3 3 display: inline; 4 }5 .btn-open-modal {6 font-size: 18px;7 border-radius: 10px;8 padding: 5px 10px;9 background-color: #06367a;10 border: none;11 color: #fff;12 font-weight: 600;13 float: right;14 /*margin-left: 10px;*/15 4 } 16 5 … … 26 15 background: #f1f1f1; 27 16 border-radius: 10px; 28 width: 1300px !important;29 //min-width: 300px;17 min-width: 1000px !important; 18 max-width: 1000px !important; 30 19 height: auto; 31 20 padding: 0 !important; … … 41 30 border-top-right-radius: 10px; 42 31 background-color: rgba(1,38,90,0.9); 32 /*background-color: white;*/ 43 33 background-size: cover; 44 34 padding: 14px 48px; 35 /*color: #3A3B3C;*/ 45 36 color: white; 37 border-bottom: 1px solid #E5E4E7; 38 display: flex; 39 justify-content: left; 46 40 } 47 41 … … 50 44 font-weight: bold; 51 45 display: inline; 46 margin: 0; 52 47 } 53 48 … … 60 55 } 61 56 62 .react-responsive-modal-modal .modal-content form .aligned { 63 text-align: center; 57 .react-responsive-modal-modal .modal-content form .modal-buttons{ 58 display: flex; 59 justify-content: end; 60 gap: 10px; 61 margin-right: 10px; 64 62 } 65 63 … … 69 67 } 70 68 71 input {69 .react-responsive-modal-modal .modal-content form input { 72 70 display: block; 73 71 padding: 5px 10px; … … 77 75 } 78 76 79 textarea {77 .react-responsive-modal-modal .modal-content form textarea { 80 78 width: 100%; 81 79 resize: none; … … 89 87 } 90 88 91 .applic taion-textarea {89 .application-textarea { 92 90 height: 100px; 93 91 } … … 108 106 } 109 107 110 . submit-btn{108 .modal-buttons button, .modal-buttons div { 111 109 position: relative; 112 110 padding: 5px 20px; … … 114 112 border: none; 115 113 color: white; 116 text-transform: uppercase; 117 font-weight: bold; 118 width: 30%; 119 /*clip-path: polygon(100% 0, 100% 75%, 95% 100%, 0 100%, 0 0);*/ 120 background: linear-gradient(to right, #06367a, #06367a); 114 /*text-transform: uppercase;*/ 115 font-weight: 500; 121 116 margin-top: 15px; 117 cursor: pointer 118 } 119 120 .modal-buttons .submit-btn { 121 background: #06367a; 122 transition: background-color 0.3s ease, color 0.3s ease; 123 } 124 125 .modal-buttons .submit-btn:hover { 126 background-color: #2e579b; 127 } 128 129 .modal-buttons .cancel-btn { 130 color: #858B96; 131 background-color: #F0F0F0; 132 transition: background-color 0.3s ease, color 0.3s ease; 133 } 134 .modal-buttons .cancel-btn:hover { 135 background-color: #e0e0e0; 136 color: #555; 122 137 } 123 138 … … 156 171 157 172 158 . cancel-btn,.delete-btn {173 .modal-delete-buttons .cancel-btn, .modal-delete-buttons .delete-btn { 159 174 display: inline; 160 175 border: none; … … 163 178 } 164 179 165 . delete-btn {180 .modal-delete-buttons .delete-btn { 166 181 background-color: red; 167 182 color: white; -
jobvista-frontend/src/views/static/Header.css
rb248810 rbefb988 6 6 background-size: contain; 7 7 margin-right: 0; 8 8 9 } 9 10 … … 18 19 19 20 .navbar { 21 width: calc(100% - 17px); 20 22 height: 80px; 21 background-color: # 535C91;23 background-color: #f8f9fa; 22 24 font-family: Poppins, sans-serif; 25 position: fixed; 26 z-index: 10; 27 box-sizing: border-box; 23 28 } 24 29 25 30 .navbar .nav-item { 26 27 31 color: rgba(1,38,90,0.9); 28 32 margin-right: 15px; … … 31 35 font-size: 20px; 32 36 font-weight: 600; 33 //font-family: 'Ubuntu', sans-serif;34 37 font-family: Poppins, sans-serif; 35 38 } … … 37 40 color: white; 38 41 background-color: rgba(1,38,90,0.9); 39 /*border-bottom: 3px solid rgba(1,38,90,0.9);*/40 42 } 41 43 .active { … … 44 46 } 45 47 46 .user { 47 48 } 49 50 .auth-box { 51 display: inline-block; 52 margin-right: 35px; 53 margin-left: 5px; 54 //height: 50px; 55 56 } 57 58 .user { 48 /*NOVO*/ 49 50 :root { 51 --gray: #555; 52 --purple: #4e65ff; 53 --green-blue: #92effd; 54 --white: #fff; 55 } 56 57 .navigation { 58 position: fixed; 59 top: 10px; 60 right: 20px; 61 width: 120px; 62 height: 60px; 63 display: flex; 64 justify-content: space-between; 65 border-radius: 5px; 66 background: var(--white); 67 box-shadow: 0 25px 35px rgba(0, 0, 0, 0.3); 68 /*box-shadow: 0px 0px 18px 4px rgba(0,0,0,0.46);*/ 69 overflow: hidden; 70 transition: height 0.5s, width 0.5s; 71 transition-delay: 0s, 0.3s; 72 z-index: 15; 73 /*border: 2px solid var(--gray);*/ 74 } 75 76 .navigation .user-box { 77 position: relative; 78 width: 60px; 79 height: 60px; 80 display: flex; 81 align-items: center; 82 overflow: hidden; 83 transition: 0.3s; 84 transition-delay: 0.3s; 85 } 86 87 .navigation .user-box .username { 88 font-size: 1.2rem; 89 font-weight: bold; 90 white-space: nowrap; 91 margin: 0; 59 92 color: black; 60 93 padding-top: 10px; 61 margin-bottom: 0; 62 63 } 64 65 .role { 94 95 } 96 97 .navigation .user-box .role { 66 98 color: darkgray; 67 99 margin-bottom: 10px; 68 font-size: 15px; 69 70 } 71 72 100 font-size: 1rem; 101 } 102 103 .navigation .user-box .image-box { 104 position: relative; 105 min-width: 60px; 106 height: 60px; 107 background: var(--white); 108 border-radius: 50%; 109 overflow: hidden; 110 border: 10px solid var(--white); 111 112 } 113 114 .navigation .user-box .image-box img { 115 position: absolute; 116 top: 0; 117 left: 0; 118 width: 100%; 119 height: 100%; 120 object-fit: cover; 121 /*border: 1px solid black*/ 122 } 123 124 .navigation .menu-toggle { 125 position: relative; 126 width: 60px; 127 height: 60px; 128 display: flex; 129 justify-content: center; 130 align-items: center; 131 cursor: pointer; 132 } 133 134 .navigation .menu-toggle::before { 135 content: ""; 136 position: absolute; 137 width: 32px; 138 height: 2px; 139 background: var(--gray); 140 transform: translateY(-10px); 141 box-shadow: 0 10px var(--gray); 142 transition: 0.5s; 143 } 144 145 .navigation .menu-toggle::after { 146 content: ""; 147 position: absolute; 148 width: 32px; 149 height: 2px; 150 background: var(--gray); 151 transform: translateY(10px); 152 transition: 0.5s; 153 } 154 155 .menu { 156 position: absolute; 157 width: 100%; 158 /*height: calc(100% - 60px);*/ 159 margin-top: 60px; 160 padding: 0; 161 border-top: 1px solid rgba(0, 0, 0, 0.1); 162 } 163 164 .menu .menu-link { 165 text-decoration: none; 166 color: black; 167 display: flex; 168 align-items: center; 169 gap: 10px; 170 font-size: 1.1rem; 171 padding: 15px 30px; 172 } 173 .menu .menu-link:hover { 174 background-color: #EEEEEE; 175 } 176 177 178 .navigation.active .menu-toggle::before { 179 transform: translateY(0px) rotate(45deg); 180 box-shadow: none; 181 } 182 183 .navigation.active .menu-toggle::after { 184 transform: translateY(0px) rotate(-45deg); 185 } 186 187 .navigation.active { 188 width: 300px; 189 height: 175px; 190 transition: width 0.3s, height 0.3s; 191 transition-delay: 0s, 0.3s; 192 } 193 194 .navigation.active .user-box { 195 width: calc(100% - 60px); 196 transition-delay: 0s; 197 } 198 199 200 201 202 -
jobvista-frontend/src/views/static/Header.js
rb248810 rbefb988 1 1 import {Link, NavLink} from "react-router-dom"; 2 2 import "./Header.css" 3 import { jwtDecode} from "jwt-decode";3 import {jwtDecode} from "jwt-decode"; 4 4 import {useDispatch, useSelector} from 'react-redux'; 5 5 import {useEffect, useState} from "react"; … … 8 8 import {useNavigate} from "react-router"; 9 9 import {AUTH_TOKEN} from "../../axios/axiosInstance"; 10 import {JobSeekerActions} from "../../redux/actions/JobSeekerActions"; 11 import {RecruiterActions} from "../../redux/actions/recruiterActions"; 10 12 11 13 export const Header = (props) => { … … 17 19 const [role, setRole] = useState(""); 18 20 const [username, setUsername] = useState(""); 21 const [user, setUser] = useState(""); 19 22 20 const [user, setUser] = useState(""); 23 const [profilePics, setProfilePics] = useState({}); 24 let profilePicState = useSelector(state => state.images.profilePictures); 25 const [profilePicDispatched, setProfilePicDispatched] = useState(false); 26 27 const [logos, setLogos] = useState({}); 28 let logoState = useSelector(state => state.images.logos); 29 const [logoDispatched, setLogoDispatched] = useState(false); 21 30 22 31 const signOut = () => { 23 32 dispatch(AuthActions.signOut()); 24 33 window.location = "/"; 34 //navigator("/") 25 35 } 36 const [isActive, setIsActive] = useState(false); 37 38 const toggleMenu = () => { 39 setIsActive(!isActive); 40 }; 26 41 27 42 useEffect(() => { 28 43 const token = localStorage.getItem(AUTH_TOKEN); 29 if (token !=null) {44 if (token != null) { 30 45 try { 31 46 const decodedToken = jwtDecode(token); … … 34 49 role: decodedToken.role, 35 50 hasAccess: decodedToken.hasAccess, 51 id: decodedToken.id 36 52 }); 37 53 } catch (error) { … … 45 61 setRole(auth.role); 46 62 setUsername(auth.name); 63 64 console.log("ROLE: " + auth.role) 65 66 if (auth.role === Roles.JOBSEEKER) { 67 dispatch(JobSeekerActions.downloadProfilePic(auth.id, (success, reponse) => { 68 if (success) { 69 setProfilePics(prevState => ({...prevState, [auth.id]: reponse})) 70 console.log(reponse) 71 } 72 })) 73 } else if (auth.role === Roles.RECRUITER) { 74 dispatch(RecruiterActions.downloadLogo(auth.id, (success, reponse) => { 75 if (success) { 76 setLogos(prevState => ({...prevState, [auth.id]: reponse})) 77 console.log(reponse) 78 } 79 })) 80 } 47 81 } 48 82 }, [auth]); … … 51 85 <nav className="navbar navbar-expand-lg bg-light"> 52 86 <div className="container-fluid"> 53 <Link to="/" className="logo navbar-brand"/>54 <Link to="/" className="brand-name navbar-brand"/>55 <div className=" collapse navbar-collapse" id="navbarSupportedContent">87 <Link to="/" className="logo"/> 88 <Link to="/" className="brand-name"/> 89 <div className="navbar-collapse"> 56 90 <ul className="navbar-nav me-auto mb-2 mb-lg-0"> 57 91 <NavLink to="/" className="nav-item nav-link">Home</NavLink> 58 {role ==Roles.JOBSEEKER &&92 {role == Roles.JOBSEEKER && 59 93 <> 60 <NavLink to="/my-applications" className="nav-item nav-link" >My Applications</NavLink> 61 {/*<NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink>*/} 62 </> 63 64 } 65 {role==Roles.RECRUITER && 66 <> 67 <NavLink to="/my-job-advertisements" className="nav-item nav-link" >My Advertisements</NavLink> 68 {/*<NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink>*/} 94 <NavLink to="/my-applications" className="nav-item nav-link">My Applications</NavLink> 69 95 </> 70 96 } 71 {role===Roles.ADMIN && 97 {role == Roles.RECRUITER && 98 <> 99 <NavLink to="/job-management-hub" className="nav-item nav-link">Job Management Hub</NavLink> 100 </> 101 } 102 {role === Roles.ADMIN && 72 103 <> 73 104 <NavLink to="/admin-panel" className="nav-item nav-link">Admin Panel</NavLink> 74 105 </> 106 } 107 <NavLink to="/about" className="nav-item nav-link">About Us</NavLink> 108 {/*<NavLink to="/contact" className="nav-item nav-link">Support</NavLink>*/} 109 </ul> 110 </div> 111 {(auth.role === Roles.RECRUITER || auth.role === Roles.ADMIN || auth.role === Roles.JOBSEEKER) ? 112 <> 113 <div className={`navigation ${isActive ? 'active' : ''}`}> 114 <div className="user-box"> 115 <div className="image-box"> 116 {user.role === Roles.JOBSEEKER && <img src={profilePicState[auth.id]} /> } 117 {user.role === Roles.RECRUITER && <img src={logoState[auth.id]} /> } 118 {user.role === Roles.ADMIN && <img src="/images/admin.jpg"/> } 119 </div> 120 <div className="auth-box"> 121 <p className="username">{user.name}</p> 122 {user.role === Roles.RECRUITER && <p className="role">Recruiter</p>} 123 {user.role === Roles.JOBSEEKER && <p className="role">Job Seeker</p>} 124 {user.role === Roles.ADMIN && <p className="role">Admin</p>} 125 </div> 75 126 76 } 77 <NavLink to="/about" className="nav-item nav-link">About</NavLink> 78 <NavLink to="/contact" className="nav-item nav-link">Support</NavLink> 79 </ul> 127 </div> 128 <div className="menu-toggle" onClick={toggleMenu}></div> 129 <ul className="menu"> 130 {user.role == Roles.JOBSEEKER && 131 <> 132 <Link to="/job-seeker/edit-profile" onClick={toggleMenu} className="menu-link"> 133 <i className="fa-solid fa-pen-to-square"></i> Edit profile 134 </Link> 135 </> 136 } 137 {user.role == Roles.RECRUITER && 138 <> 139 <Link to="/recruiter/edit-profile" onClick={toggleMenu} className="menu-link"> 140 <i className="fa-solid fa-pen-to-square"></i> Edit profile 141 </Link> 142 </> 143 } 80 144 81 {auth ? 82 <> 83 <img src="/images/user.png" width="45" height="45"/> 84 <div className="auth-box"> 85 <p className="user"><b>{user.name}</b></p> 86 {user.role==Roles.RECRUITER && <p className="role">Recruiter</p>} 87 {user.role==Roles.JOBSEEKER && <p className="role">Job Seeker</p>} 88 {user.role==Roles.ADMIN && <p className="role">Admin</p>} 89 {/*<p className="role">{user.role==Roles.RECRUITER ? "Recruiter" : "Job Seeker"}</p>*/} 90 </div> 91 92 93 <Link onClick={signOut} className="btn auth-secondary-btn">Log out</Link> 94 </> : 95 <> 96 <Link to="/signin" className="btn auth-secondary-btn">Sign in</Link> 97 </> 98 } 99 100 </div> 145 <Link onClick={signOut} className="menu-link"> 146 <i className="fa-solid fa-right-from-bracket"></i> Log out 147 </Link> 148 </ul> 149 </div> 150 </> : 151 <> 152 <Link to="/signin" className="btn auth-secondary-btn">Sign in</Link> 153 </> 154 } 101 155 </div> 102 156 </nav>
Note:
See TracChangeset
for help on using the changeset viewer.