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)