Ignore:
Timestamp:
06/17/24 21:59:14 (2 weeks ago)
Author:
223021 <daniel.ilievski.2@…>
Branches:
main
Children:
08f82ec
Parents:
b248810
Message:

Added an edit profile page for both job seekers and recruiters, where they can upload profile pictures/company logos and edit their profile data. Added profile page specifically for recruiters. Refactored existing code.

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  
    44import {useState, useEffect} from "react";
    55import {AdminActions} from "../../redux/actions/adminActions";
     6import {notifyAccessUpdate} from "../../utils/toastUtils";
    67
    78export const AdminPanel = () => {
     
    2425            setRecruiters(recruitersState)
    2526            console.log("Fetch all recruiters STATE")
     27            console.log(recruitersState)
     28
    2629        }
    2730    }, [recruitersState])
     
    3235    };
    3336
    34     const handleAccessChange = (recruiterId, newAccessStatus) => {
     37    const handleAccessChange = (recruiterId, companyName, newAccessStatus) => {
     38
    3539        setRecruiters(prevState =>
    3640            prevState.map(recruiter =>
     
    4145        );
    4246
    43         console.log(recruiterId + " " + newAccessStatus)
    44 
    4547        dispatch(AdminActions.changeAccess(recruiterId, newAccessStatus, (success, response) => {
    4648            if(success) {
    47                 console.log("Access changed")
     49                notifyAccessUpdate(companyName)
    4850            }
    4951        }));
     
    5153
    5254    return (
    53         <div className="applications-container mt-5">
     55        <div className="custom-container mt-5">
    5456            <table className="table table-striped">
    5557                <thead>
     
    6466                </thead>
    6567                <tbody>
    66                 {recruiters.map((recruiter) => (
     68                {recruitersState.map((recruiter) => (
    6769                    <tr key={recruiter.id}>
    6870                        <th scope="row">{recruiter.id}</th>
     
    7072                        <td>{recruiter.email}</td>
    7173                        <td>{recruiter.companyName}</td>
    72                         <td>{recruiter.phoneNumber}</td>
     74                        <td>{recruiter.contactPhoneNumber}</td>
    7375                        <td>
    7476                            <label className="switch">
     
    7678                                    type="checkbox"
    7779                                    checked={recruiter.hasAccess}
    78                                     onChange={(e) => handleAccessChange(recruiter.id, e.target.checked)}
     80                                    onChange={(e) => handleAccessChange(recruiter.id, recruiter.companyName, e.target.checked)}
    7981                                />
    8082                                <span className="slider"></span>
  • jobvista-frontend/src/views/applications/ApplicationDetailsModal.js

    rb248810 rbefb988  
    11import React, {useEffect, useState} from "react";
    2 import "../job_advertisements/Form.css";
     2import "../shared_css/Modal.css";
    33
    44import 'react-responsive-modal/styles.css';
     
    1919
    2020
    21 export const ViewApplicationDetails = (props) => {
     21export const ApplicationDetailsModal = (props) => {
    2222    const {application} = props
    2323    const [modal, setModal] = useState(false);
     
    5454                        <div className="col-md-6">
    5555                            <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="applictaion-textarea"/>
     56                            <textarea disabled type="text" defaultValue={application.questionAnswers[0]} disabled placeholder="Write your answer here..." className="application-textarea"/>
    5757
    5858
    5959                            <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="applictaion-textarea"/>
     60                            <textarea disabled type="text" defaultValue={application.questionAnswers[1]}  placeholder="Write your answer here..." className="application-textarea"/>
    6161
    6262
    6363                            <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="applictaion-textarea"/>
     64                            <textarea disabled type="text" defaultValue={application.questionAnswers[2]}  placeholder="Write your answer here..." className="application-textarea"/>
    6565
    6666                        </div>
     
    7373                            <br/>
    7474                            <label className="label">Message to the recruiter</label>
    75                             <textarea disabled type="text" defaultValue={application.message} placeholder="Optional..." className="applictaion-textarea"/>
     75                            <textarea disabled type="text" defaultValue={application.message} placeholder="Optional..." className="application-textarea"/>
    7676
    7777                        </div>
  • jobvista-frontend/src/views/applications/Applications.css

    rb248810 rbefb988  
    1 .applications-container {
     1.custom-container {
    22    width: 65% !important;
    33    max-width: 1500px !important;
     
    66
    77.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;
    1111    display: flex;
    12     justify-content: center;
     12    justify-content: start;
     13    border-bottom: 1px solid #CFCFCF;
    1314}
    14 .application-title h1 {
    15     font-weight: bold;
     15.application-title h3 {
     16    font-weight: 500;
    1617}
    1718
     
    2829    transition: all 0.3s ease;
    2930    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    30     transform: translate(0, 0);
    3131    height: auto;
    3232    padding: 20px 20px;
    3333    display: flex;
    3434    justify-content: center;
     35    gap: 20px;
    3536    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%;
    3645}
    3746
    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;
    4154}
    4255
    43 .application-card .left-box .jobAd-title {
     56.application-card .app-info .jobAd-title {
    4457    font-weight: 600;
    4558    /*text-transform: uppercase;*/
    4659    font-family: 'Segoe UI', sans-serif;
    47     font-size: 22px;
     60    /*font-size: 22px;*/
    4861}
     62.application-card .app-info .jobAd-title
    4963
    50 .application-card .left-box .contact-info {
     64.application-card .app-info .contact-info {
    5165    display: inline-flex;
    52     gap: 10px;
     66    gap: 30px;
    5367}
    54 .application-card .left-box .contact-info .contact-item {
     68.application-card .app-info .contact-info .contact-item {
    5569    display: inline-flex;
    5670    align-items: center;
    5771    gap: 5px;
     72    margin-right: 10px
    5873}
    5974
    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%;
    6281    display: inline-flex;
    6382    justify-content: end;
     
    6685}
    6786
    68 .application-card .right-box .select {
    69     width: 35%;
     87.application-card .app-status .select {
     88    width: 45%;
    7089    display: inline-block;
    7190}
     91
     92.status {
     93    color: white;
     94    padding: 5px 10px;
     95    border-radius: 5px;
     96    width: 150px;
     97    text-align: center;
     98}
     99
    72100
    73101.application-button {
     
    75103    border-radius: 8px;
    76104    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;
    82111}
     112
    83113.application-button:hover {
    84     background-color: rgb(187, 215, 235);
     114    background: linear-gradient(to bottom, #bbbbbb, #dddddd);
     115    color: #333333;
    85116}
    86117
  • jobvista-frontend/src/views/applications/ApplicationsByJobAd.js

    rb248810 rbefb988  
    1 import {useDispatch} from "react-redux";
     1import {useDispatch, useSelector} from "react-redux";
    22import {useEffect, useState} from "react";
    33import {useParams} from "react-router";
    44import {ApplicationActions} from "../../redux/actions/applicationActions";
    5 import {ViewApplicationDetails} from "./ViewApplicationDetails";
     5import {ApplicationDetailsModal} from "./ApplicationDetailsModal";
    66import "./Applications.css"
    77import Select from "react-select";
     8import {sortElementsBy} from "../../utils/utils";
     9import {JobSeekerActions} from "../../redux/actions/JobSeekerActions";
     10import {notifyAppStatusUpdate} from "../../utils/toastUtils";
    811
    912export const ApplicationsByJobAd = () => {
     
    1114    const dispatch = useDispatch();
    1215    let {advertisement_id} = useParams();
     16
    1317    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
    1425    const [jobAdTitle, setJobAdTitle] = useState("");
    1526
    1627    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}))
    2166            }
    2267        }))
    23     }, [])
     68    }
     69
    2470
    2571    const options = [
     
    3783        dispatch(ApplicationActions.updateApplicationStatus(id, selectedOption.value, (success, response) => {
    3884            if(success) {
    39                 console.log("Status updated.")
     85                // console.log("Status updated.")
     86                notifyAppStatusUpdate()
    4087            }
    4188        }))
     
    4390
    4491
    45     return (<div className="applications-container">
     92    return (<div className="custom-container">
    4693        <div className="application-title">
    4794            {jobAdTitle ?
    48                 <h1>Applications for <span>{jobAdTitle}</span></h1> :
     95                <h3>Applications for <b>{jobAdTitle}</b></h3> :
    4996                <h1></h1>
    5097            }
    51 
    5298        </div>
    5399
    54100        {applicationsByJobAd && applicationsByJobAd.map((application, index) => (
    55101            <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">
    57111                    <span>Submitted on <b>{new Date(application.submittedOn).toLocaleString('default', {
    58112                        day: 'numeric',
     
    60114                        year: 'numeric'
    61115                    })}</b></span>
    62                     <br/><br/>
    63116                    <div className="contact-info">
    64117                        <div className="contact-item">
    65118                            <i className="fa-solid fa-user"></i> <span>{application.jobSeekerName}</span>
    66                         </div>
    67                         <div className="contact-item">
     119                        </div> <div className="contact-item">
    68120                            <i className="fa-solid fa-envelope"></i> <span>{application.jobSeekerEmail}</span>
    69                         </div>
    70                         <div className="contact-item">
     121                        </div> <div className="contact-item">
    71122                            <i className="fa-solid fa-phone"></i> <span>{application.jobSeekerPhoneNumber}</span>
    72123                        </div>
     
    74125                </div>
    75126
    76                 <div className="right-box">
    77                     <ViewApplicationDetails application={application}/>
     127                <div className="app-status">
     128                    <ApplicationDetailsModal application={application}/>
    78129                    <div className="select">
    79130                        <Select options={options}  onChange={(selectedOption) => handleChangeStatus(selectedOption, application.id)} defaultValue={handleDefaultStatus(application.status)}/>
  • jobvista-frontend/src/views/applications/ApplicationsByJobSeeker.js

    rb248810 rbefb988  
    22import {useEffect, useState} from "react";
    33import {ApplicationActions} from "../../redux/actions/applicationActions";
    4 import {ViewApplicationDetails} from "./ViewApplicationDetails";
     4import {ApplicationDetailsModal} from "./ApplicationDetailsModal";
    55import Select from "react-select";
     6
     7import {RecruiterActions} from "../../redux/actions/recruiterActions";
     8import {sortElementsBy} from "../../utils/utils";
    69
    710export const ApplicationsByJobSeeker = () => {
    811    const dispatch = useDispatch();
     12    const auth = useSelector(state => state.auth.currentUser);
     13
    914    const [applicationsByJobSeeker, setApplicationsByJobSeeker] = useState([]);
    1015    let applicationsByJobSeekerState = useSelector(state => state.appl.applicationsByJobSeeker);
    1116    const [dispatched, setDispatched] = useState(false);
    1217
     18    const [logos, setLogos] = useState({});
     19    let logosState = useSelector(state => state.images.logos)
     20    const [logoDispatched, setLogoDispatched] = useState(false);
     21
     22
     23
    1324    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) => {
    1627                if(success && response.data.length > 0) {
    17                     setApplicationsByJobSeeker(response.data);
     28                    setApplicationsByJobSeeker(sortElementsBy(response.data, "submittedOn"));
    1829                }
     30                setDispatched(true)
    1931                console.log("Fetch applications by job seeker GET")
    2032            }))
    21             setDispatched(true)
     33
    2234        } else {
    23             setApplicationsByJobSeeker(applicationsByJobSeekerState);
     35            setApplicationsByJobSeeker(sortElementsBy(applicationsByJobSeekerState, "submittedOn"));
    2436            console.log("Fetch applications by job seeker STATE")
    2537        }
    2638    }, [applicationsByJobSeekerState])
    2739
     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
    2868    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>}
    3373    ];
    3474
     
    4080
    4181    return (
    42         <div className="applications-container">
     82        <div className="custom-container">
     83
    4384            <div className="application-title">
    44 
     85                <h3>Application history</h3>
    4586            </div>
    4687            {applicationsByJobSeeker && applicationsByJobSeeker.map((application, index) => (
    4788                <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>
    52100                        <div className="contact-info">
    53101                            <div className="contact-item">
     
    68116                    </div>
    69117
    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>*/}
    75124
    76125                    </div>
  • jobvista-frontend/src/views/applications/ApplyToJobAdModal.js

    rb248810 rbefb988  
    11import React, {useState} from "react";
    2 import "../job_advertisements/Form.css";
     2import "../shared_css/Modal.css";
    33
    44import 'react-responsive-modal/styles.css';
    55import {Modal} from 'react-responsive-modal';
    6 import Select from "react-select";
    76
    87//Validation
     
    1211
    1312
    14 import {employmentStatusOptions, industryOptions, jobTypeOptions} from "../selectOptions";
    1513import {useDispatch, useSelector} from "react-redux";
    16 import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";
    1714import Roles from "../../enumerations/Roles";
    1815import {ApplicationActions} from "../../redux/actions/applicationActions";
     16import {notifyJobAdApply} from "../../utils/toastUtils";
    1917
    2018
     
    5553                formData,(success, response) => {
    5654                    if(success) {
    57                         console.log("Job Advertisement added")
    5855                        toggleModal()
     56                        notifyJobAdApply()
    5957                    }
    6058                }
     
    7068        {role===Roles.JOBSEEKER &&
    7169            <>
    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> }
    7371                {!jobAd.active && <button className="card-button apply disabled">Apply now</button> }
    7472            </>
     
    8684                            <label className="label">Why are you interested in joining our company?</label>
    8785                            <textarea type="text" placeholder="Write your answer here..."
    88                                       {...register("answerOne")} className="applictaion-textarea"/>
     86                                      {...register("answerOne")} className="application-textarea"/>
    8987                            <p style={{color: "red"}}>{errors.answerOne?.message}</p>
    9088
    9189                            <label className="label">What makes you a good fit for this position?</label>
    9290                            <textarea type="text" placeholder="Write your answer here..."
    93                                       {...register("answerTwo")} className="applictaion-textarea"/>
     91                                      {...register("answerTwo")} className="application-textarea"/>
    9492                            <p style={{color: "red"}}>{errors.answerTwo?.message}</p>
    9593
    9694                            <label className="label">What do you hope to achieve in your first 6 months in this role?</label>
    9795                            <textarea type="text"  placeholder="Write your answer here..."
    98                                    {...register("answerThree")} className="applictaion-textarea"/>
     96                                   {...register("answerThree")} className="application-textarea"/>
    9997                            <p style={{color: "red"}}>{errors.answerThree?.message}</p>
    10098
     
    103101                            <label htmlFor="start">Curriculum vitae (CV)</label>
    104102                            <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"/>
    106104
    107105                            <br/>
    108106                            <label className="label">Message to the recruiter</label>
    109107                            <textarea type="text" placeholder="Optional..."
    110                                       {...register("messageToRecruiter")} className="applictaion-textarea"/>
     108                                      {...register("messageToRecruiter")} className="application-textarea"/>
    111109
    112110                            <br/><br/>
     
    115113                    </div>
    116114
    117                     <div className="aligned">
     115                    <div className="modal-buttons">
     116                        <div className="cancel-btn" onClick={toggleModal}> Cancel</div>
    118117                        <button className="submit-btn"> Submit</button>
    119118                    </div>
  • jobvista-frontend/src/views/auth/SignInForm.js

    rb248810 rbefb988  
    88import {yupResolver} from "@hookform/resolvers/yup";
    99import {AuthActions} from "../../redux/actions/authActions";
     10import {notifyIncorrectEmailOrPassword} from "../../utils/toastUtils";
    1011
    1112export const SignInForm = () => {
     
    2728    const signIn = async (values) => {
    2829        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                    }
    3736                }));
    3837        } catch (err) {
    39             console.error(err);
     38            // console.error(err);
    4039        }
    4140    }
  • jobvista-frontend/src/views/dashboard/Dashboard.js

    rb248810 rbefb988  
    1 import "./Dashboard.css"
     1import "../shared_css/Random.css"
    22
    33import {useDispatch, useSelector} from "react-redux";
     
    1212import {AUTH_TOKEN} from "../../axios/axiosInstance";
    1313import {jwtDecode} from "jwt-decode";
    14 
    15 export const Dashboard = (props) => {
     14import {RecruiterActions} from "../../redux/actions/recruiterActions";
     15
     16export const Dashboard = () => {
    1617
    1718    const dispatch = useDispatch();
     
    1920    const [jobAdvertisements, setJobAdvertisements] = useState([]);
    2021    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
    2128    const auth = useSelector(state => state.auth);
    2229
     
    2532    const [selectedIndustry, setSelectedIndustry] = useState("all");
    2633    const [searchTerm, setSearchTerm] = useState("");
    27     const [dispatched, setDispatched] = useState(false)
     34
     35
    2836
    2937    // const [user, setUser] = useState(null);
     
    5462
    5563    useEffect(() => {
    56         if (!dispatched && jobAdvertisementsState.length == 0) {
     64        if (!jobDispatched && jobAdvertisementsState.length == 0) {
    5765            dispatch(JobAdvertisementActions.fetchJobAdvertisements((success, response) => {
    5866                if (success && response.data.length > 0) {
    59                     setJobAdvertisements(sortElementsBy(response.data))
     67                    setJobAdvertisements(sortElementsBy(response.data, "postedOn"))
    6068                }
    61                 setDispatched(true)
     69                setJobDispatched(true)
    6270                console.log("Fetch all job advertisements GET")
    6371            }))
     
    6674            setJobAdvertisements(jobAdvertisementsState)
    6775            console.log("Fetch all job advertisements STATE")
     76
     77
    6878        }
    6979    }, [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    };
    70105
    71106    let filterJobAdvertisements = () => {
     
    86121
    87122        <div className="container">
    88             <div className="head-dashboard-box">
     123            <div className="filter-container">
    89124                <div className="row">
    90                     <div className="col-md-12 filter-container">
     125                    <div className="col-md-12 filter-box">
    91126                        <div className="search-container">
    92127                            <i className="fa-solid fa-magnifying-glass search-icon"></i>
     
    117152                            />
    118153                        </div>
    119                         <button onClick={filterJobAdvertisements} className="btn-open-modal">Find jobs</button>
     154                        <button onClick={filterJobAdvertisements} className="blue-submit-button">Find jobs</button>
    120155                    </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>*/}
    133156                </div>
    134157            </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">
    137159                {jobAdvertisements &&
    138160                    jobAdvertisements.map((jobAd, index) => (
     
    146168                                </div>
    147169                                <div className="card-body">
     170                                    <img
     171                                        // loading gif
     172                                        src={logos[jobAd.recruiterId]}
     173                                        alt=""
     174                                        width={100} height={100}
     175                                    />
    148176                                    <h5 className="card-title">{jobAd.title}</h5>
    149177                                    <span>{jobAd.industry} • <span style={{
  • jobvista-frontend/src/views/job_advertisements/AddJobAdModal.js

    rb248810 rbefb988  
    11import React, {useState} from "react";
    2 import "./Form.css";
     2import "../shared_css/Modal.css";
    33
    44import { Editor } from 'primereact/editor';
     
    1717import {useDispatch, useSelector} from "react-redux";
    1818import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";
     19import {notifyJobAdPost} from "../../utils/toastUtils";
    1920
    2021
     
    3233        description: yup.string().required("Please enter a description"),
    3334        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"),
    3536        jobType: yup.mixed().required("Select job type"),
    3637        employmentStatus: yup.mixed().required("Select employment status"),
     
    4647            dispatch(JobAdvertisementActions.addJobAdvertisement(
    4748                {
    48                     email: auth.email,
     49                    id: auth.id,
    4950                    title: values.title,
    5051                    description: values.description,
     
    5657                }, (success, response) => {
    5758                    if (success) {
    58                         console.log("Job Advertisement added")
     59                        // console.log("Job Advertisement added")
    5960                        toggleModal()
     61                        notifyJobAdPost()
    6062                    }
    6163                }
     
    7577        </div>
    7678        {/*<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>
    7880            <div className="head-modal">
    7981                <h3>Post Job Advertisement</h3>
     
    8284
    8385            <div className="modal-content">
    84                 <form onSubmit={handleSubmit(addJobAdvertisement)}>
     86                <form>
    8587                    <div className="row">
    8688                        <div className="col-md-7">
     
    111113                        <div className="col-md-5">
    112114                            <label className="label">Hourly rate:</label>
    113                             <input {...register("startingSalary")}/>
     115                            <input type="number" {...register("startingSalary")}/>
    114116                            <p style={{color: "red"}}>{errors.startingSalary?.message}</p>
    115117
     
    157159                    </div>
    158160
    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>
    161164                    </div>
    162165
  • jobvista-frontend/src/views/job_advertisements/DeleteJobAdModal.js

    rb248810 rbefb988  
    11import React, {useState} from "react";
    2 import "./Form.css";
     2import "../shared_css/Modal.css";
    33
    44import 'react-responsive-modal/styles.css';
     
    1515import {useDispatch, useSelector} from "react-redux";
    1616import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";
     17import {notifyJobAdDelete} from "../../utils/toastUtils";
    1718
    1819
     
    2930            dispatch(JobAdvertisementActions.deleteJobAdvertisement(jobAd.props.id, (success, response) => {
    3031                if (success) {
    31                     console.log("Job Advertisement deleted")
     32                    // console.log("Job Advertisement deleted")
    3233                    toggleModal()
     34                    notifyJobAdDelete()
    3335                }
    3436            }))
  • jobvista-frontend/src/views/job_advertisements/EditJobAdModal.js

    rb248810 rbefb988  
    11import React, {useEffect, useState} from "react";
    2 import "./Form.css";
     2import "../shared_css/Modal.css";
    33
    44import 'react-responsive-modal/styles.css';
     
    1616import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";
    1717import {Editor} from "primereact/editor";
     18import {notifyJobAdDelete, notifyJobAdEdit} from "../../utils/toastUtils";
    1819
    1920
     
    2324    const auth = useSelector(state => state.auth.currentUser)
    2425    const toggleModal = () => {
     26        console.log("NAD SET")
    2527        setModal(!modal);
     28        console.log("POD SET")
     29
    2630    };
    2731
     
    5256                }, jobAd.props.id, (success, response) => {
    5357                    if(success) {
    54                         console.log("Job Advertisement edited")
     58                        // console.log("Job Advertisement edited")
    5559                        toggleModal()
     60                        notifyJobAdEdit()
    5661                    }
    5762                }
     
    7883
    7984            <div className="modal-content">
    80                 <form onSubmit={handleSubmit(editJobAdvertisement)}>
     85                <form>
    8186                    <div className="row">
    8287                        <div className="col-md-7">
     
    9297                                name="description"
    9398                                control={control}
     99                                defaultValue={jobAd.props.description}
    94100                                render={({ field }) => (
    95101                                    <Editor
    96                                         defaultValue={jobAd.props.description}
     102                                        // defaultValue={jobAd.props.description}
    97103                                        value={jobAd.props.description}
    98104                                        onTextChange={(e) => field.onChange(e.htmlValue)}
     
    107113                        <div className="col-md-5">
    108114                            <label className="label">Hourly rate:</label>
    109                             <input defaultValue={jobAd.props.startingSalary} {...register("startingSalary")}/>
     115                            <input type="number" defaultValue={jobAd.props.startingSalary} {...register("startingSalary")}/>
    110116                            <p style={{color: "red"}}>{errors.startingSalary?.message}</p>
    111117
     
    166172                    </div>
    167173
    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>
    170177                    </div>
    171 
    172178                </form>
    173179            </div>
  • jobvista-frontend/src/views/job_advertisements/JobAdDetails.css

    rb248810 rbefb988  
    1010.details-wrap {
    1111    width: 100%;
    12     //height: auto;
    13     height: 80vh;
     12    height: auto;
     13    max-height: 80vh;
    1414    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
    1534    background-color: #fff;
    1635    border-radius: 12px;
     
    5675}
    5776
     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
    5898.apply:not(.expired) {
    5999    width: 20% !important;
     
    65105    background-position: right center;
    66106}
     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  
    1010import {AddJobAdModal} from "./AddJobAdModal";
    1111import {ApplyToJobAdModal} from "../applications/ApplyToJobAdModal";
     12import {Link} from "react-router-dom";
     13import {RecruiterActions} from "../../redux/actions/recruiterActions";
    1214
    1315
     
    2022    const auth = useSelector(state => state.auth.currentUser);
    2123
     24    let logosState = useSelector(state => state.images.logos)
     25    const [logoDispatched, setLogoDispatched] = useState(false)
     26    const [logoView, setLogoView] = useState(null);
     27
    2228    useEffect(() => {
    2329        setRole(auth.role)
    2430    }, [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])
    2548
    2649
     
    4164            <div className="col-md-9">
    4265                <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>
    4373
    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>
    4896                    </div>
    4997
    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>
    5798
    5899                    <h4>About the job</h4>
     
    66107            <div className="col-md-3">
    67108                <div className="details-wrap">
    68                     <h3>{jobAd.recruiterName}</h3>
     109                    <Link className="recruiter-link" to={`/recruiters/${jobAd.recruiterId}`}>{jobAd.recruiterName} </Link>
    69110
    70                     {/*TO DO - AFTER IMPLEMENTING FORM FOR UPDATING PERSONAL INFO*/}
    71111                    <h4>About the company</h4>
    72112                    <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>
    76120
     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> </>}
    77125                    </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>
    81130                </div>
    82131            </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  
    22
    33import "./JobAdvertisements.css"
     4import "../shared_css/Random.css"
     5
    46import {useDispatch, useSelector} from "react-redux";
    57import {useEffect, useState} from "react";
     
    1214import {Link} from "react-router-dom";
    1315import JobType from "../../enumerations/JobType";
     16import {RecruiterActions} from "../../redux/actions/recruiterActions";
    1417
    15 export const JobAdvertisements = (props) => {
     18
     19export const Workspace = (props) => {
    1620
    1721    const dispatch = useDispatch();
     22    const [dispatched, setDispatched] = useState(false)
     23
     24    const auth = useSelector(state => (state.auth.currentUser))
     25
    1826    const [jobAdvertisementsByRecruiter, setJobAdvertisementsByRecruiter] = useState([]);
    19     const auth = useSelector(state => (state.auth.currentUser))
    2027    let jobAdvertisementsByRecruiterState = useSelector(state => (state.jobAd.jobAdvertisementsByRecruiter))
    2128
    22     const [role, setRole] = useState("");
     29    const [recruiterDetails, setRecruiterDetails] = useState(null);
     30
    2331    const [selectedSortOrder, setSelectedSortOrder] = useState("date_newest");
    2432    const [selectedIndustry, setSelectedIndustry] = useState("all");
    2533    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]);
    2746
    2847
    2948    useEffect(() => {
    30         if (auth) {
    31             setRole(auth.role);
    32         }
    33     }, [auth]);
    34 
    35     useEffect(() => {
    3649        if (!dispatched && jobAdvertisementsByRecruiterState.length === 0) {
    37             dispatch(JobAdvertisementActions.fetchJobAdvertisementsByRecruiter((success, response) => {
     50            dispatch(JobAdvertisementActions.fetchJobAdvertisementsByRecruiter(auth.id, (success, response) => {
    3851                if (success && response.data.length > 0) {
    3952                    setJobAdvertisementsByRecruiter(sortElementsBy(response.data))
     
    4659            setJobAdvertisementsByRecruiter(jobAdvertisementsByRecruiterState)
    4760            console.log("Fetch job advertisements by recruiter STATE")
     61
     62            setActiveJobListingsCount(countActiveJobListings(jobAdvertisementsByRecruiterState));
    4863        }
     64
    4965    }, [jobAdvertisementsByRecruiterState])
    5066
    5167    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);
    6173            }
    62         )
     74        })
    6375    }
    6476
    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">
    6890                <div className="row">
    69                     <div className="col-md-12 filter-container">
     91                    <div className="col-md-12 filter-box">
    7092                        <div className="search-container">
    7193                            <i className="fa-solid fa-magnifying-glass search-icon"></i>
     
    96118                            />
    97119                        </div>
    98                         <button onClick={filterJobAdvertisements} className="btn-open-modal">Find jobs</button>
     120                        <button onClick={filterJobAdvertisements} className="blue-submit-button">Find jobs</button>
    99121                    </div>
    100122                </div>
    101123            </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">
    103125                <AddJobAdModal/>
    104126
    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}/>
    118138                                </div>
    119                                 <div className="card-body">
    120                                     <h5 className="card-title">{jobAd.title}</h5>
    121                                     <span>{jobAd.industry} • <span style={{
    122                                         color: "black",
    123                                         fontWeight: "bold"
    124                                     }}>{formatRelativeTime(jobAd.postedOn)}</span></span>
    125                                     <div className="card-info">
     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">
    126146                                        <span><i className="fa-solid fa-building"
    127147                                                 style={{color: "#000000"}}></i> Company: <span style={{
    128                                             color: "black",
    129                                             fontWeight: "bold"
     148                                            color: "black", fontWeight: "bold"
    130149                                        }}>{jobAd.recruiterName}</span></span> <br/>
    131                                     </div>
     150                                </div>
    132151
    133                                     <div className="aligned">
    134                                         <Link to={`/my-job-advertisements/${jobAd.id}/applications`}
    135                                               className="card-button solo">View applications</Link>
    136                                     </div>
     152                                <div className="aligned">
     153                                    <Link to={`/job-management-hub/applications/${jobAd.id}`}
     154                                          className="card-button solo">View applications</Link>
     155                                </div>
    137156
    138                                 </div>
    139157                            </div>
    140158                        </div>
    141                     ))}
    142 
     159                    </div>))}
    143160            </div>
    144161        </div>
     162
     163
    145164    )
    146165}
  • jobvista-frontend/src/views/shared_css/Modal.css

    rb248810 rbefb988  
    22.modal-wrap {
    33    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;*/
    154}
    165
     
    2615    background: #f1f1f1;
    2716    border-radius: 10px;
    28     width: 1300px !important;
    29     //min-width: 300px;
     17    min-width: 1000px !important;
     18    max-width: 1000px !important;
    3019    height: auto;
    3120    padding: 0 !important;
     
    4130    border-top-right-radius: 10px;
    4231    background-color: rgba(1,38,90,0.9);
     32    /*background-color: white;*/
    4333    background-size: cover;
    4434    padding: 14px 48px;
     35    /*color: #3A3B3C;*/
    4536    color: white;
     37    border-bottom: 1px solid #E5E4E7;
     38    display: flex;
     39    justify-content: left;
    4640}
    4741
     
    5044    font-weight: bold;
    5145    display: inline;
     46    margin: 0;
    5247}
    5348
     
    6055}
    6156
    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;
    6462}
    6563
     
    6967}
    7068
    71 input {
     69.react-responsive-modal-modal .modal-content form input {
    7270    display: block;
    7371    padding: 5px 10px;
     
    7775}
    7876
    79 textarea {
     77.react-responsive-modal-modal .modal-content form textarea {
    8078    width: 100%;
    8179    resize: none;
     
    8987}
    9088
    91 .applictaion-textarea {
     89.application-textarea {
    9290    height: 100px;
    9391}
     
    108106}
    109107
    110 .submit-btn {
     108.modal-buttons button, .modal-buttons div {
    111109    position: relative;
    112110    padding: 5px 20px;
     
    114112    border: none;
    115113    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;
    121116    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;
    122137}
    123138
     
    156171
    157172
    158 .cancel-btn, .delete-btn {
     173.modal-delete-buttons .cancel-btn, .modal-delete-buttons .delete-btn {
    159174    display: inline;
    160175    border: none;
     
    163178}
    164179
    165 .delete-btn {
     180.modal-delete-buttons .delete-btn {
    166181    background-color: red;
    167182    color: white;
  • jobvista-frontend/src/views/static/Header.css

    rb248810 rbefb988  
    66    background-size: contain;
    77    margin-right: 0;
     8
    89}
    910
     
    1819
    1920.navbar {
     21    width: calc(100% - 17px);
    2022    height: 80px;
    21     background-color: #535C91;
     23    background-color: #f8f9fa;
    2224    font-family: Poppins, sans-serif;
     25    position: fixed;
     26    z-index: 10;
     27    box-sizing: border-box;
    2328}
    2429
    2530.navbar .nav-item {
    26 
    2731    color: rgba(1,38,90,0.9);
    2832    margin-right: 15px;
     
    3135    font-size: 20px;
    3236    font-weight: 600;
    33     //font-family: 'Ubuntu', sans-serif;
    3437    font-family: Poppins, sans-serif;
    3538}
     
    3740    color: white;
    3841    background-color: rgba(1,38,90,0.9);
    39     /*border-bottom: 3px solid rgba(1,38,90,0.9);*/
    4042}
    4143.active {
     
    4446}
    4547
    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;
    5992    color: black;
    6093    padding-top: 10px;
    61     margin-bottom: 0;
    62 
    63 }
    64 
    65 .role {
     94
     95}
     96
     97.navigation .user-box .role {
    6698    color: darkgray;
    6799    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  
    11import {Link, NavLink} from "react-router-dom";
    22import "./Header.css"
    3 import { jwtDecode } from "jwt-decode";
     3import {jwtDecode} from "jwt-decode";
    44import {useDispatch, useSelector} from 'react-redux';
    55import {useEffect, useState} from "react";
     
    88import {useNavigate} from "react-router";
    99import {AUTH_TOKEN} from "../../axios/axiosInstance";
     10import {JobSeekerActions} from "../../redux/actions/JobSeekerActions";
     11import {RecruiterActions} from "../../redux/actions/recruiterActions";
    1012
    1113export const Header = (props) => {
     
    1719    const [role, setRole] = useState("");
    1820    const [username, setUsername] = useState("");
     21    const [user, setUser] = useState("");
    1922
    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);
    2130
    2231    const signOut = () => {
    2332        dispatch(AuthActions.signOut());
    2433        window.location = "/";
     34        //navigator("/")
    2535    }
     36    const [isActive, setIsActive] = useState(false);
     37
     38    const toggleMenu = () => {
     39        setIsActive(!isActive);
     40    };
    2641
    2742    useEffect(() => {
    2843        const token = localStorage.getItem(AUTH_TOKEN);
    29         if (token!=null) {
     44        if (token != null) {
    3045            try {
    3146                const decodedToken = jwtDecode(token);
     
    3449                    role: decodedToken.role,
    3550                    hasAccess: decodedToken.hasAccess,
     51                    id: decodedToken.id
    3652                });
    3753            } catch (error) {
     
    4561            setRole(auth.role);
    4662            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            }
    4781        }
    4882    }, [auth]);
     
    5185        <nav className="navbar navbar-expand-lg bg-light">
    5286            <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">
    5690                    <ul className="navbar-nav me-auto mb-2 mb-lg-0">
    5791                        <NavLink to="/" className="nav-item nav-link">Home</NavLink>
    58                         {role==Roles.JOBSEEKER &&
     92                        {role == Roles.JOBSEEKER &&
    5993                            <>
    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>
    6995                            </>
    7096                        }
    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 &&
    72103                            <>
    73104                                <NavLink to="/admin-panel" className="nav-item nav-link">Admin Panel</NavLink>
    74105                            </>
     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>
    75126
    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                                }
    80144
    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                }
    101155            </div>
    102156        </nav>
Note: See TracChangeset for help on using the changeset viewer.