DEV Community

Cover image for Hire+Plus! For Employees Here's how I built it (UI - Job)
AjeaS
AjeaS

Posted on

Hire+Plus! For Employees Here's how I built it (UI - Job)

Overview: All views and functionality related to the Job, all funcs called are coming from the jobReducer.


Job Route Page

inside routes > job > job-page.tsx
imports that assist with components to render, and functions to call. On mount, I fetch all jobs. I'm handling search and filter states using searchInput, and filteredData.

searchItems - filters jobs by search term and sets results to filteredData. If nothing was searched, filteredData is set to default jobs.

import { useEffect, useState } from 'react'; import BeatLoader from 'react-spinners/BeatLoader'; import { getPostedJobs } from '../../app/features/job/jobSlice'; import { JobData } from '../../app/features/job/jobTypes'; import { useAppDispatch, useAppSelector } from '../../app/hooks'; import Job from '../../components/job/job.component'; const JobsPage = () => { const dispatch = useAppDispatch(); const { jobs, isLoading } = useAppSelector((state) => state.job); const [searchInput, setSearchInput] = useState<string>(''); const [filteredData, setfilteredData] = useState<JobData[]>([]); useEffect(() => { dispatch(getPostedJobs()); }, []); const searchItems = (value: string) => { setSearchInput(value); if (searchInput !== '') { const filtered = jobs.filter((item) => { return Object.values(item) .join('') .toLowerCase() .includes(searchInput.toLowerCase()); }); setfilteredData(filtered); } else { setfilteredData(jobs); } }; return ({/* removed for simplicity */}); }; export default JobsPage; 
Enter fullscreen mode Exit fullscreen mode

UI

In a nutshell, it renders all jobs with a search bar. Searching by job title, and or location will show filtered results in Job component.

const JobsPage = () => { {/* removed for simplicity */} return ( <> {isLoading ? ( <div className="text-center p-20"> <BeatLoader color="#ffffff" /> </div>  ) : ( <> <div className="flex justify-center pt-20"> <div className="mb-3 w-1/2"> <div className="input-group relative flex items-stretch w-full mb-4"> <input value={searchInput} onChange={(e) => searchItems(e.target.value)} type="search" className="form-control relative flex-auto min-w-0 block w-full px-5 py-3 text-base font-normal font-color secondary-bg-color bg-clip-padding border border-solid border-gray-300 rounded-full transition ease-in-out m-0 focus:text-slate-200 focus:secondary-bg-color focus:border-indigo-700 focus:outline-none" placeholder="Search for a job..." aria-label="Search" aria-describedby="button-addon2" /> </div>  </div>  </div>  <section className="text-gray-600 body-font overflow-hidden"> <div className="container px-5 py-24 mx-auto"> <div className="-my-8 divide-y-2 divide-gray-700"> {searchInput.length ? filteredData.map((job, index) => { return <Job job={job} key={index} />;  }) : jobs.map((job, index) => { return <Job job={job} key={index} />;  })} </div>  </div>  </section>  </>  )} </>  ); }; export default JobsPage; 
Enter fullscreen mode Exit fullscreen mode

Screenshots

  1. when it's not filtered
  2. when it's filtered

Jobs unfiltered results

Job filtered results

inside routes > job > job-detail.tsx
imports that assist with functions to call. On mount, I fetch a single job by id I get from useParams. I parse the result and set it to local state jobData.

import { useEffect, useState } from 'react'; import { useParams } from 'react-router'; import BeatLoader from 'react-spinners/BeatLoader'; import { getPostedJobById } from '../../app/features/job/jobSlice'; import { JobData } from '../../app/features/job/jobTypes'; import { useAppDispatch, useAppSelector } from '../../app/hooks'; const JobDetail = () => { const { id } = useParams(); const [jobData, setjobData] = useState<JobData>({} as JobData); const { isLoading } = useAppSelector((state) => state.job); const dispatch = useAppDispatch(); useEffect(() => { dispatch(getPostedJobById(id)) .unwrap() .then((val) => { setjobData(JSON.parse(val)); }); }, [dispatch, id]); return ( {/* removed for simplicity */} ); }; export default JobDetail; 
Enter fullscreen mode Exit fullscreen mode

UI

In a nutshell, it renders the details of a job.

const JobDetail = () => { {/* removed for simplicity */} return ( <> {isLoading ? ( <BeatLoader /> ) : ( <> <section style={{ backgroundColor: '#252731' }}> <div className="md:px-12 lg:px-24 max-w-7xl relative items-center w-full px-5 py-20 mx-auto"> <div className="mx-auto flex flex-col w-full max-w-lg text-center"> <p className="mb-5 font-medium text-3xl text-white"> {jobData.position} </p>  <div> <span className="font-color"> {jobData.location} - {jobData.salary} </span>  </div>  </div>  </div>  </section>  <section> <div className="divide-y divide-gray-700"> <section className="text-gray-600 body-font mt-2"> <div className="container px-5 py-20 mx-auto"> <div className="flex flex-col w-full mx-auto"> <div className="w-full mx-auto"> <h2 className="xs:text-3xl text-2xl my-5 font-bold"> Job Description </h2>  <div> <p className="font-color">{jobData.description}</p>  </div>  </div>  </div>  </div>  </section>  <section className="text-gray-600 body-font mt-2"> <div className="container px-5 py-20 mx-auto"> <div className="flex flex-col w-full mx-auto"> <div className="w-full mx-auto"> <h2 className="xs:text-3xl text-2xl my-5 font-bold"> Job-Type </h2>  <div> <p className="font-color">{jobData.jobType}</p>  </div>  </div>  </div>  </div>  </section>  </div>  </section>  </>  )} </>  ); }; export default JobDetail; 
Enter fullscreen mode Exit fullscreen mode

Screenshot

Job detail


Job Component

inside components > job > job.component.tsx
I created a helper func truncateString for text that's too long. If text reaches max length it shows ...

I get the job data from props, since I'm using typescript I defined what the data type of the prop should be, in my case JobData.

import React from 'react'; import { Link } from 'react-router-dom'; import { JobData } from '../../app/features/job/jobTypes'; import { truncateString } from '../../utils/truncateString'; interface JobProps { job: JobData; } 
Enter fullscreen mode Exit fullscreen mode

UI

Job card with links to view details, apply for the job, and or view the company that posted it.

const Job: React.FC<JobProps> = ({ job }) => { return ( <div className="py-8 flex flex-wrap md:flex-nowrap"> <div className="md:w-64 md:mb-0 mb-6 flex-shrink-0 flex flex-col"> <span className="font-semibold title-font text-indigo-500"> {job.companyName.toUpperCase()} </span>  <span className="mt-1 font-color text-sm">{job.datePosted}</span>  </div>  <div className="md:flex-grow"> <h2 className="text-2xl font-medium text-white title-font mb-2"> {job.position}{' '} <span className="text-indigo-500 text-sm"> ({job.location}) - ({job.jobType}) </span>  </h2>  <p className="leading-relaxed font-color max-w-3xl"> {truncateString(job.description, 250)} </p>  <a href={job.applyUrl} className="text-indigo-500 inline-flex items-center mt-4 mr-4" > APPLY NOW <svg className="w-4 h-4 ml-2" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" > <path d="M5 12h14"></path>  <path d="M12 5l7 7-7 7"></path>  </svg>  </a>  <Link to={`job/${job.id}`} className="text-indigo-500 inline-flex items-center mt-4 mr-3" > VIEW JOB <svg className="w-4 h-4 ml-2" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" > <path d="M5 12h14"></path>  <path d="M12 5l7 7-7 7"></path>  </svg>  </Link>  <Link to={`company/${job.id}`} className="text-indigo-500 inline-flex items-center mt-4" > VIEW COMPANY <svg className="w-4 h-4 ml-2" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round" > <path d="M5 12h14"></path>  <path d="M12 5l7 7-7 7"></path>  </svg>  </Link>  </div>  </div>  ); }; export default Job; 
Enter fullscreen mode Exit fullscreen mode

That's all for the UI/Job portion of the project, stay tuned!

Top comments (0)