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

Implemented backend and frontend CRUD operations for job advertisements

Location:
jobvista-frontend/src
Files:
15 added
12 edited

Legend:

Unmodified
Added
Removed
  • jobvista-frontend/src/App.css

    rd8b6c91 r19398ad  
    55  background-color: rgb(243, 242, 241);
    66  height: 100vh;
    7 }
    8 
    9 .card {
    10   border: 1px solid black;
    11   border-radius: 5px;
    12   padding: 10px;
     7  overflow-y: auto;
    138}
    149
     
    3530}
    3631
     32.container {
     33  width: 80% !important;
     34  max-width: 1500px !important;
     35}
     36
    3737
    3838/*font-family: 'Ubuntu', sans-serif;*/
    3939/*font-family: 'Cairo', sans-serif;*/
     40
     41.react-responsive-modal-overlay {
     42  backdrop-filter: blur(2px);
     43}
     44
     45/*CARDS*/
     46
     47.col {
     48  height: 280px !important;
     49}
     50
     51.custom-card {
     52  //border: 1px solid lightgray;
     53  border-radius: 8px;
     54  background-color: white;
     55  transition: all 0.3s ease;
     56  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
     57  transform: translate(0, 0);
     58  height: 260px;
     59}
     60
     61.custom-card:hover {
     62  transform: translate(0, 8px);
     63  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
     64}
     65
     66.custom-card .card-head {
     67  padding: 25px;
     68  padding-bottom: 0 !important;
     69}
     70
     71.custom-card .card-head .job-type {
     72  font-size: 12px !important;
     73  margin-left: 6px !important;
     74  position: relative;
     75  bottom: 3px;
     76}
     77
     78.custom-card .card-head .expired {
     79  font-size: 12px !important;
     80  margin-left: 8px !important;
     81  position: relative;
     82  bottom: 3px;
     83}
     84
     85.custom-card .card-head .card-management-btns {
     86  float: right;
     87  scale: 130%;
     88  display: flex;
     89  gap: 5px;
     90  transition: 0.2s;
     91}
     92.custom-card .card-head .card-management-btns i:hover {
     93  opacity: 0.5;
     94  cursor: pointer;
     95}
     96
     97.custom-card .card-body {
     98  padding: 25px;
     99  padding-top: 15px !important;
     100  height: 100%;
     101}
     102.custom-card .card-body span{
     103  font-size: 15px;
     104}
     105
     106.custom-card .card-body .card-title {
     107  margin-top: 10px;
     108}
     109
     110
     111.custom-card .card-body .card-title h5 {
     112  display: inline;
     113}
     114
     115
     116.custom-card .card-body .hourly-salary {
     117  margin-bottom: 30px;
     118  display: inline;
     119}
     120
     121.custom-card .card-body .card-info {
     122  color: gray;
     123  margin: 13px 0;
     124}
     125
     126.custom-card .card-body .aligned {
     127  display: flex;
     128  justify-content: center;
     129  gap: 8px;
     130  text-align: center;
     131  margin-top: 25px;
     132  position: relative;
     133
     134}
     135
     136.custom-card .card-body .aligned a {
     137  text-decoration: none;
     138}
     139
     140
     141.card-button {
     142  border: 0;
     143  border-radius: 8px;
     144  width: 45%;
     145  background-color: rgba(207, 235, 255, 1);
     146  //background-size: 200% auto;
     147  //background-image: linear-gradient(to right, #a1c4fd 0%, aliceblue 61%, #a1c4fd 100%);
     148  color: black;
     149  font-weight: bold;
     150  padding: 5px 10px;
     151  transition: 0.2s;
     152}
     153
     154.card-button:only-child {
     155  width: 70%;
     156}
     157.card-button:not(.disabled):hover{
     158  background-color: rgb(187, 215, 235);
     159  //background-position: right center;
     160  color: black;
     161}
     162
     163.disabled {
     164  cursor: default !important;
     165  opacity: 0.6;
     166}
     167
     168
     169
  • jobvista-frontend/src/auth/RoutesConfig.js

    rd8b6c91 r19398ad  
    55import {SignUpRecruiterForm} from "../views/auth/SignUpRecruiterForm";
    66import {SignUpJobSeekerForm} from "../views/auth/SignUpJobSeekerForm";
     7import {JobAdvertisements} from "../views/job_advertisements/JobAdvertisements";
     8import {JobAdDetails} from "../views/job_advertisements/JobAdDetails";
    79export const RoutesConfig = () => {
    810
     
    1315                <Route path="/signup/recruiter" element={<SignUpRecruiterForm/>}></Route>
    1416                <Route path="/signup/job-seeker" element={<SignUpJobSeekerForm/>}></Route>
     17                <Route path="/my-job-advertisements" element={<JobAdvertisements/>}></Route>
     18                <Route path="/job-advertisements/view/:id" element={<JobAdDetails/>}></Route>
     19                <Route path="/my-job-advertisements/view/:id" element={<JobAdDetails/>}></Route>
    1520            </Routes>
    1621    )
  • jobvista-frontend/src/redux/actionTypes.js

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

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

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

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

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

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

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

    rd8b6c91 r19398ad  
    33    padding: 5px 5px;
    44}
     5
     6.form-container {
     7    margin-bottom: 80px;
     8}
     9
     10.auth-primary-btn{
     11    background-color: rgba(1,38,90,0.80);
     12    //background-color: #a1c4fd ;
     13    color: white;
     14}
     15.auth-primary-btn:hover {
     16    background-color: rgba(1,38,90,1);
     17    color: white;
     18}
     19
     20.auth-secondary-btn {
     21    border: 2px solid rgba(1,38,90,0.7);
     22    color: midnightblue;
     23}
     24
     25.auth-secondary-btn:hover {
     26    background-color: rgba(1,38,90,0.8);
     27    color: white;
     28}
  • jobvista-frontend/src/views/dashboard/Dashboard.js

    rd8b6c91 r19398ad  
     1import "./Dashboard.css"
     2
     3import {useDispatch, useSelector} from "react-redux";
     4import {useEffect, useState} from "react";
     5import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";
     6import {formatRelativeTime, sortElementsByDateCreated} from "../../utils/utils";
     7import {dataRangeOptions, industryOptions, industryOptionsFilter, sortOptions} from "../selectOptions";
     8import Select from "react-select";
     9import Roles from "../../enumerations/Roles";
     10import {Link} from "react-router-dom";
     11import JobType from "../../enumerations/JobType";
     12
    113export const Dashboard = () => {
     14
     15    const dispatch = useDispatch();
     16
     17    const [jobAdvertisements, setJobAdvertisements] = useState([]);
     18    let jobAdvertisementsState = useSelector(state => state.jobAd.jobAdvertisements)
     19    const auth = useSelector(state => state.auth.currentUser);
     20
     21    const [role, setRole] = useState("");
     22    const [sortOrder, setSortOrder] = useState("newest");
     23    const [selectedDateRange, setSelectedDateRange] = useState("all");
     24    const [searchTerm, setSearchTerm] = useState("");
     25    const [dispatched, setDispatched] = useState(false)
     26
     27    useEffect(() => {
     28        if (auth) {
     29            setRole(auth.role);
     30        }
     31    }, [auth]);
     32
     33    useEffect(() => {
     34        if(!dispatched && jobAdvertisementsState.length == 0) {
     35            dispatch(JobAdvertisementActions.fetchJobAdvertisements((success, response) => {
     36                if (success && response.data.length > 0) {
     37                    setJobAdvertisements(sortElementsByDateCreated(response.data))
     38                }
     39                setDispatched(true)
     40                console.log("Fetch all job advertisements GET")
     41                console.log(response.data)
     42            }))
     43
     44        } else {
     45            setJobAdvertisements(jobAdvertisementsState)
     46            console.log("Fetch all job advertisements STATE")
     47        }
     48    }, [])
    249
    350
    451    return (
    552        <div className="container">
    6             <div className="row justify-content">
    7                 <div className="col-md-4">
    8                     <div className="card">
    9                         <h5 className="card-title">Job Listing Name</h5>
    10                         <p>Job Listing Data</p>
     53            <div className="head-dashboard-box">
     54                <div className="row">
     55                    <div className="col-md-3">
     56                        <div className="search-container head-component">
     57                            <i className="fa-solid fa-magnifying-glass blue-colored"></i>
     58                            <input
     59                                className="search-input"
     60                                type="text"
     61                                placeholder="Search job advertisement by title..."
     62                                //value={searchTerm}
     63                                //onChange={event => setSearchTerm(event.target.value)}
     64                            />
     65                        </div>
    1166                    </div>
    12                 </div>
    13                 <div className="col-md-4">
    14                     <div className="card">
    15                         <h5 className="card-title">Job Listing Name</h5>
    16                         <p>Job Listing Data</p>
    17                     </div>
    18                 </div>
    19                 <div className="col-md-4">
    20                     <div className="card">
    21                         <h5 className="card-title">Job Listing Name</h5>
    22                         <p>Job Listing Data</p>
     67                    <div className="col-md-9">
     68                        <div className="sort-section item">
     69                            <Select
     70                                defaultValue={{value: "all", label: "All industries"}}
     71                                //value={sortOrder.value}
     72                                //onChange ={option => setSortOrder(option.value)}
     73                                options={industryOptionsFilter}
     74                                className="sort-range sort"
     75                            />
     76                        </div>
     77                        <div className="sort-section item">
     78                            <Select
     79                                defaultValue={{value: "newest", label: "Date (Newest First)"}}
     80                                //value={sortOrder.value}
     81                                //onChange ={option => setSortOrder(option.value)}
     82                                options = {sortOptions}
     83                                className="sort-range sort"
     84                            />
     85                        </div>
     86                        <div className="date-range-section item">
     87                            <Select
     88                                defaultValue={{value: "all", label: "Lifetime"}}
     89                                //value={selectedDateRange.value}
     90                                //onChange={option => setSelectedDateRange(option.value)}
     91                                options={dataRangeOptions}
     92                                className="date-range sort"
     93                            />
     94                        </div>
    2395                    </div>
    2496                </div>
    2597            </div>
     98            <div className="row row-cols-1 row-cols-md-4 g-4">
    2699
     100                {jobAdvertisements &&
     101                    jobAdvertisements.map((jobAd, index) => (
     102                        <div key={index} className="col">
     103                            <div className="custom-card">
     104                                <div className="card-head">
     105                                    <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span>
     106                                    <span className="job-type"> {jobAd.jobType===JobType.JOB ? "Job" : "Internship"}</span>
     107                                    {!jobAd.active && <span className="expired">Expired</span>}
     108                                </div>
     109                                <div className="card-body">
     110                                    <h5 className="card-title">{jobAd.title}</h5>
     111                                    <span>{jobAd.industry} • <span style={{color: "black", fontWeight: "bold"}}>{formatRelativeTime(jobAd.postedOn)}</span></span>
     112                                    <div className="card-info">
     113                                        <span><i className="fa-solid fa-building" style={{color: "#000000"}}></i> Company: <span style={{color: "black", fontWeight: "bold"}}>{jobAd.recruiterName}</span></span> <br/>
     114                                    </div>
     115
     116                                    <div className="aligned">
     117                                        <Link to={`/job-advertisements/view/${jobAd.id}`} className="card-button">Read more</Link>
     118                                        {role===Roles.JOBSEEKER &&
     119                                            <>
     120                                                {jobAd.active && <button className="card-button">Apply now</button> }
     121                                                {!jobAd.active && <button className="card-button disabled">Apply now</button> }
     122                                            </>
     123                                        }
     124                                    </div>
     125
     126                                </div>
     127                            </div>
     128                        </div>
     129                    ))}
     130
     131            </div>
    27132        </div>
    28133    )
  • jobvista-frontend/src/views/static/Header.js

    rd8b6c91 r19398ad  
    55import {AuthActions} from "../../redux/actions/authActions";
    66import Roles from "../../enumerations/Roles";
     7import {useNavigate} from "react-router";
    78
    8 export const Header = () => {
     9export const Header = (props) => {
    910
    1011    const auth = useSelector(state => state.auth.currentUser);
    1112    const dispatch = useDispatch();
     13    const navigator = useNavigate();
    1214
    13     const [role, setRole] = useState(null);
    14     const [username, setUsername] = useState(null);
     15    const [role, setRole] = useState("");
     16    const [username, setUsername] = useState("");
    1517
    1618    const signOut = () => {
     
    2022
    2123    useEffect(() => {
    22 
    2324        if (auth) {
    2425            setRole(auth.role);
     
    3435                <div className="collapse navbar-collapse" id="navbarSupportedContent">
    3536                    <ul className="navbar-nav me-auto mb-2 mb-lg-0">
    36                         <NavLink to="/" className="nav-item nav-link" activeClassName="active">Home</NavLink>
     37                        <NavLink to="/" className="nav-item nav-link">Home</NavLink>
    3738                        {role==Roles.JOBSEEKER &&
    3839                            <>
    39                                 <NavLink to="/applications" className="nav-item nav-link" activeClassName="active">Applications</NavLink>
    40                                 <NavLink to="/favoritejobs" className="nav-item nav-link" activeClassName="active">Saved</NavLink>
     40                                <NavLink to="/applications" className="nav-item nav-link" >Applications</NavLink>
     41                                <NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink>
    4142                            </>
    4243
     
    4445                        {role==Roles.RECRUITER &&
    4546                            <>
    46                                 <NavLink to="/jobadvertisements" className="nav-item nav-link" activeClassName="active">Job Advertisements</NavLink>
    47                                 <NavLink to="/favoritejobs" className="nav-item nav-link" activeClassName="active">Saved</NavLink>
     47                                <NavLink to="/my-job-advertisements" className="nav-item nav-link" >Job Advertisements</NavLink>
     48                                <NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink>
    4849                            </>
    4950                        }
     
    6162
    6263
    63                             <Link onClick={signOut} className="btn btn-outline-secondary">Log out</Link>
     64                            <Link onClick={signOut} className="btn auth-secondary-btn">Log out</Link>
    6465                        </> :
    6566                        <>
    66                             <Link to="/signin" className="btn btn-outline-secondary">Sign in</Link>
     67                            <Link to="/signin" className="btn auth-secondary-btn">Sign in</Link>
    6768                        </>
    6869                    }
Note: See TracChangeset for help on using the changeset viewer.