DEV Community

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

Posted on

2 1

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

Project Modal Component

inside components > modal > project-modal.component.tsx
ProjectPopupModalProps, defines the types of props being passed into this component. defaultFormFields fields of the modal to edit. Used defaultFormFields as the default state for projectFields.

import React, { ChangeEvent, useState } from 'react';
import { setProjects } from '../../app/features/profile/profileSlice';
import { useAppDispatch, useAppSelector } from '../../app/hooks';

interface ProjectPopupModalProps {
    isProjOpen: boolean;
    closeProjModal: () => void;
}
const defaultFormFields = {
    date: '',
    title: '',
    summary: '',
    github: '',
    projectUrl: '',
};
Enter fullscreen mode Exit fullscreen mode

Functionality

onHandleChange, onTextareaChange - handle state changes for the form fields.

resetFormFields - resets form fields after submission.

addProject - set projects for the profile.projects state within profileSlice, resets form, and close modal afterward.

const ProjectPopupModal: React.FC<ProjectPopupModalProps> = ({
    isProjOpen,
    closeProjModal,
}) => {
    const dispatch = useAppDispatch();

    const { profile } = useAppSelector((state) => state.profile);

    const [projectFields, setProjectFields] = useState(defaultFormFields);

    const onHandleChange = (e: ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        setProjectFields({ ...projectFields, [name]: value });
    };
    const onTextareaChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
        const { value } = e.target;
        setProjectFields({ ...projectFields, summary: value });
    };
    const resetFormFields = () => {
        setProjectFields(defaultFormFields);
    };
    const addProject = (e: ChangeEvent<HTMLFormElement>) => {
        e.preventDefault();
        dispatch(
            setProjects([
                {
                    date: projectFields.date,
                    title: projectFields.title,
                    summary: projectFields.summary,
                    github: projectFields.github,
                    projectUrl: projectFields.projectUrl,
                },
                ...profile.projects,
            ])
        );
        resetFormFields();
        setProjectFields(projectFields);
        closeProjModal();
    };
    return ( {/* removed for simplicity */} );
};

export default ProjectPopupModal;

Enter fullscreen mode Exit fullscreen mode

UI

Renders the pop-up modal if the isProjOpen is set to true, and all the fields to edit.

const ProjectPopupModal: React.FC<ProjectPopupModalProps> = ({
    isProjOpen,
    closeProjModal,
}) => {
    {/* removed for simplicity */}
    return (
        <div
            className="py-12 bg-gray-700 transition duration-150 ease-in-out z-10 absolute right-0 bottom-0 left-0 h-screen"
            id="modal"
            style={{ display: `${isProjOpen ? 'block' : 'none'}`, top: '850px' }}
        >
            <div role="alert" className="container mx-auto w-11/12 md:w-2/3 max-w-lg">
                <div className="relative py-8 px-5 md:px-10 bg-white shadow-md rounded border border-gray-400">
                    <div className="w-full flex justify-center text-gray-600 mb-3">
                        <svg
                            xmlns="http://www.w3.org/2000/svg"
                            className="icon icon-tabler icon-tabler-wallet"
                            width="52"
                            height="52"
                            viewBox="0 0 24 24"
                            strokeWidth="1"
                            stroke="currentColor"
                            fill="none"
                            strokeLinecap="round"
                            strokeLinejoin="round"
                        >
                            <path stroke="none" d="M0 0h24v24H0z" />
                            <path d="M17 8v-3a1 1 0 0 0 -1 -1h-10a2 2 0 0 0 0 4h12a1 1 0 0 1 1 1v3m0 4v3a1 1 0 0 1 -1 1h-12a2 2 0 0 1 -2 -2v-12" />
                            <path d="M20 12v4h-4a2 2 0 0 1 0 -4h4" />
                        </svg>
                    </div>
                    <h1 className="text-gray-800 font-lg font-bold tracking-normal leading-tight mb-4 text-center text-lg">
                        Add Project
                    </h1>
                    <form onSubmit={addProject}>
                        <label
                            htmlFor="name"
                            className="text-gray-800 text-sm font-bold leading-tight tracking-normal"
                        >
                            Project Name
                        </label>
                        <input
                            type="text"
                            required
                            name="title"
                            onChange={onHandleChange}
                            value={projectFields.title}
                            id="name"
                            className="mb-5 mt-2 text-gray-600 focus:outline-none focus:border focus:border-indigo-700 font-normal w-full h-10 flex items-center pl-3 text-sm border-gray-300 rounded border"
                            placeholder="Full stack react"
                        />
                        {/* Project Date */}
                        <label
                            htmlFor="date"
                            className="text-gray-800 text-sm font-bold leading-tight tracking-normal"
                        >
                            Date
                        </label>
                        <div className="relative mb-5 mt-2">
                            <div className="absolute right-0 text-gray-600 flex items-center pr-3 h-full cursor-pointer">
                                <svg
                                    xmlns="http://www.w3.org/2000/svg"
                                    className="icon icon-tabler icon-tabler-calendar-event"
                                    width="20"
                                    height="20"
                                    viewBox="0 0 24 24"
                                    strokeWidth="1.5"
                                    stroke="currentColor"
                                    fill="none"
                                    strokeLinecap="round"
                                    strokeLinejoin="round"
                                >
                                    <path stroke="none" d="M0 0h24v24H0z" />
                                    <rect x="4" y="5" width="16" height="16" rx="2" />
                                    <line x1="16" y1="3" x2="16" y2="7" />
                                    <line x1="8" y1="3" x2="8" y2="7" />
                                    <line x1="4" y1="11" x2="20" y2="11" />
                                    <rect x="8" y="15" width="2" height="2" />
                                </svg>
                            </div>
                            <input
                                name="date"
                                onChange={onHandleChange}
                                value={projectFields.date}
                                id="date"
                                className="text-gray-600 focus:outline-none focus:border focus:border-indigo-700 font-normal w-full h-10 flex items-center pl-3 text-sm border-gray-300 rounded border"
                                placeholder="MM/YY"
                            />
                        </div>
                        {/* project github */}
                        <label
                            htmlFor="github"
                            className="text-gray-800 text-sm font-bold leading-tight tracking-normal"
                        >
                            Project Github
                        </label>
                        <input
                            type="text"
                            name="github"
                            onChange={onHandleChange}
                            value={projectFields.github}
                            id="github"
                            className="mb-5 mt-2 text-gray-600 focus:outline-none focus:border focus:border-indigo-700 font-normal w-full h-10 flex items-center pl-3 text-sm border-gray-300 rounded border"
                            placeholder="www.projectOne@github.com"
                            required
                        />
                        <label
                            htmlFor="projectUrl"
                            className="text-gray-800 text-sm font-bold leading-tight tracking-normal"
                        >
                            Project URL
                        </label>
                        <input
                            name="projectUrl"
                            onChange={onHandleChange}
                            value={projectFields.projectUrl}
                            id="projectUrl"
                            className="mb-5 mt-2 text-gray-600 focus:outline-none focus:border focus:border-indigo-700 font-normal w-full h-10 flex items-center pl-3 text-sm border-gray-300 rounded border"
                            placeholder="www.coolwebapp.com"
                            required
                        />
                        <label
                            htmlFor="description"
                            className="text-gray-800 text-sm font-bold leading-tight tracking-normal"
                        >
                            Project Summary
                        </label>
                        <textarea
                            name="summary"
                            onChange={onTextareaChange}
                            value={projectFields.summary}
                            maxLength={300}
                            id="description"
                            className="mb-5 mt-2 text-gray-600 focus:outline-none focus:border focus:border-indigo-700 font-normal w-full flex items-center px-3 py-3 text-sm border-gray-300 rounded border"
                            placeholder="Authentication, testing, etc."
                            required
                        />
                        <div className="flex items-center justify-start w-full">
                            <button
                                type="submit"
                                className="focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-700 transition duration-150 ease-in-out hover:bg-indigo-600 bg-indigo-700 rounded text-white px-8 py-2 text-sm"
                            >
                                Submit
                            </button>
                            <button
                                type="button"
                                onClick={closeProjModal}
                                aria-label="close modal"
                                className="focus:outline-none focus:ring-2 focus:ring-offset-2  focus:ring-gray-400 ml-3 bg-gray-100 transition duration-150 text-gray-600 ease-in-out hover:border-gray-400 hover:bg-gray-300 border rounded px-8 py-2 text-sm"
                            >
                                Cancel
                            </button>
                        </div>
                    </form>
                    <button
                        className="cursor-pointer absolute top-0 right-0 mt-4 mr-5 text-gray-400 hover:text-gray-600 transition duration-150 ease-in-out rounded focus:ring-2 focus:outline-none focus:ring-gray-600"
                        aria-label="close modal"
                        onClick={closeProjModal}
                    >
                        <svg
                            xmlns="http://www.w3.org/2000/svg"
                            className="icon icon-tabler icon-tabler-x"
                            width="20"
                            height="20"
                            viewBox="0 0 24 24"
                            strokeWidth="2.5"
                            stroke="currentColor"
                            fill="none"
                            strokeLinecap="round"
                            strokeLinejoin="round"
                        >
                            <path stroke="none" d="M0 0h24v24H0z" />
                            <line x1="18" y1="6" x2="6" y2="18" />
                            <line x1="6" y1="6" x2="18" y2="18" />
                        </svg>
                    </button>
                </div>
            </div>
        </div>
    );
};

export default ProjectPopupModal;

Enter fullscreen mode Exit fullscreen mode

Screenshot

project modal


Experience Modal Component

inside components > modal > experience-modal.component.tsx
ExperiencePopupModalProps, defines the types of props being passed into this component. defaultFormFields fields of the modal to edit. Used defaultFormFields as the default state for experienceFields.

import { ChangeEvent, useState } from 'react';
import { setExperiences } from '../../app/features/profile/profileSlice';
import { useAppDispatch, useAppSelector } from '../../app/hooks';

interface ExperiencePopupModalProps {
    isOpen: boolean;
    closeModal: () => void;
}
const defaultFormFields = {
    date: '',
    position: '',
    positionSummary: '',
};
Enter fullscreen mode Exit fullscreen mode

Functionality

onHandleChange, onTextareaChange - handle state changes for the form fields.

resetFormFields - resets form fields after submission.

addExperience - set experiences for the profile.experience state within profileSlice, resets form, and close modal afterward.

const ExperiencePopupModal: React.FC<ExperiencePopupModalProps> = ({
    isOpen,
    closeModal,
}) => {
    const dispatch = useAppDispatch();
    const { profile } = useAppSelector((state) => state.profile);
    const [experienceFields, setExperienceFields] = useState(defaultFormFields);

    const onHandleChange = (e: ChangeEvent<HTMLInputElement>) => {
        const { name, value } = e.target;
        setExperienceFields({ ...experienceFields, [name]: value });
    };
    const onTextareaChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
        const { value } = e.target;
        setExperienceFields({ ...experienceFields, positionSummary: value });
    };
    const resetFormFields = () => {
        setExperienceFields(defaultFormFields);
    };

    const addExperience = (e: ChangeEvent<HTMLFormElement>) => {
        e.preventDefault();
        dispatch(
            setExperiences([
                {
                    position: experienceFields.position,
                    positionSummary: experienceFields.positionSummary,
                    date: experienceFields.date,
                },
                ...profile.experience,
            ])
        );
        resetFormFields();
        closeModal();
    };
    return ( {/* removed for simplicity */} );
};

export default ExperiencePopupModal;

Enter fullscreen mode Exit fullscreen mode

UI

Renders the pop-up modal and all the fields to edit.

const ExperiencePopupModal: React.FC<ExperiencePopupModalProps> = ({
    isOpen,
    closeModal,
}) => {
    {/* removed for simplicity */}
    return (
        <div
            className="py-12 bg-gray-700 transition duration-150 ease-in-out z-10 absolute right-0 bottom-0 left-0 h-screen"
            id="modal"
            style={{ display: `${isOpen ? 'block' : 'none'}`, top: '880px' }}
        >
            <div role="alert" className="container mx-auto w-11/12 md:w-2/3 max-w-lg">
                <div className="relative py-8 px-5 md:px-10 bg-white shadow-md rounded border border-gray-400">
                    <div className="w-full flex justify-center text-gray-600 mb-3">
                        <svg
                            xmlns="http://www.w3.org/2000/svg"
                            className="icon icon-tabler icon-tabler-wallet"
                            width="52"
                            height="52"
                            viewBox="0 0 24 24"
                            strokeWidth="1"
                            stroke="currentColor"
                            fill="none"
                            strokeLinecap="round"
                            strokeLinejoin="round"
                        >
                            <path stroke="none" d="M0 0h24v24H0z" />
                            <path d="M17 8v-3a1 1 0 0 0 -1 -1h-10a2 2 0 0 0 0 4h12a1 1 0 0 1 1 1v3m0 4v3a1 1 0 0 1 -1 1h-12a2 2 0 0 1 -2 -2v-12" />
                            <path d="M20 12v4h-4a2 2 0 0 1 0 -4h4" />
                        </svg>
                    </div>
                    <h1 className="text-gray-800 font-lg font-bold tracking-normal leading-tight mb-4 text-center text-lg">
                        Add Job Experience
                    </h1>
                    <form onSubmit={addExperience}>
                        <label
                            htmlFor="title"
                            className="text-gray-800 text-sm font-bold leading-tight tracking-normal"
                        >
                            Position Title
                        </label>
                        <input
                            required
                            name="position"
                            onChange={onHandleChange}
                            value={experienceFields.position}
                            id="title"
                            className="mb-5 mt-2 text-gray-600 focus:outline-none focus:border focus:border-indigo-700 font-normal w-full h-10 flex items-center pl-3 text-sm border-gray-300 rounded border"
                            placeholder="Front-End Developer"
                        />
                        <label
                            htmlFor="description"
                            className="text-gray-800 text-sm font-bold leading-tight tracking-normal"
                        >
                            Position Summary
                        </label>
                        <textarea
                            required
                            name="description"
                            onChange={onTextareaChange}
                            value={experienceFields.positionSummary}
                            maxLength={1000}
                            id="description"
                            className="mb-5 mt-2 text-gray-600 focus:outline-none focus:border focus:border-indigo-700 font-normal w-full flex items-center px-3 py-3 text-sm border-gray-300 rounded border"
                            placeholder="Job details..."
                        />
                        <label
                            htmlFor="date"
                            className="text-gray-800 text-sm font-bold leading-tight tracking-normal"
                        >
                            Date
                        </label>
                        <div className="relative mb-5 mt-2">
                            <div className="absolute right-0 text-gray-600 flex items-center pr-3 h-full cursor-pointer">
                                <svg
                                    xmlns="http://www.w3.org/2000/svg"
                                    className="icon icon-tabler icon-tabler-calendar-event"
                                    width="20"
                                    height="20"
                                    viewBox="0 0 24 24"
                                    strokeWidth="1.5"
                                    stroke="currentColor"
                                    fill="none"
                                    strokeLinecap="round"
                                    strokeLinejoin="round"
                                >
                                    <path stroke="none" d="M0 0h24v24H0z" />
                                    <rect x="4" y="5" width="16" height="16" rx="2" />
                                    <line x1="16" y1="3" x2="16" y2="7" />
                                    <line x1="8" y1="3" x2="8" y2="7" />
                                    <line x1="4" y1="11" x2="20" y2="11" />
                                    <rect x="8" y="15" width="2" height="2" />
                                </svg>
                            </div>
                            <input
                                required
                                name="date"
                                onChange={onHandleChange}
                                value={experienceFields.date}
                                id="date"
                                className="text-gray-600 focus:outline-none focus:border focus:border-indigo-700 font-normal w-full h-10 flex items-center pl-3 text-sm border-gray-300 rounded border"
                                placeholder="MM/YY"
                            />
                        </div>

                        <div className="flex items-center justify-start w-full">
                            <button
                                type="submit"
                                className="focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-700 transition duration-150 ease-in-out hover:bg-indigo-600 bg-indigo-700 rounded text-white px-8 py-2 text-sm"
                            >
                                Submit
                            </button>
                            <button
                                aria-label="close modal"
                                type="button"
                                onClick={closeModal}
                                className="focus:outline-none focus:ring-2 focus:ring-offset-2  focus:ring-gray-400 ml-3 bg-gray-100 transition duration-150 text-gray-600 ease-in-out hover:border-gray-400 hover:bg-gray-300 border rounded px-8 py-2 text-sm"
                            >
                                Cancel
                            </button>
                        </div>
                    </form>
                    <button
                        className="cursor-pointer absolute top-0 right-0 mt-4 mr-5 text-gray-400 hover:text-gray-600 transition duration-150 ease-in-out rounded focus:ring-2 focus:outline-none focus:ring-gray-600"
                        aria-label="close modal"
                        onClick={closeModal}
                    >
                        <svg
                            xmlns="http://www.w3.org/2000/svg"
                            className="icon icon-tabler icon-tabler-x"
                            width="20"
                            height="20"
                            viewBox="0 0 24 24"
                            strokeWidth="2.5"
                            stroke="currentColor"
                            fill="none"
                            strokeLinecap="round"
                            strokeLinejoin="round"
                        >
                            <path stroke="none" d="M0 0h24v24H0z" />
                            <line x1="18" y1="6" x2="6" y2="18" />
                            <line x1="6" y1="6" x2="18" y2="18" />
                        </svg>
                    </button>
                </div>
            </div>
        </div>
    );
};

export default ExperiencePopupModal;

Enter fullscreen mode Exit fullscreen mode

Screenshot

experience modal

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

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

The best way to debug slow web pages cover image

The best way to debug slow web pages

Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications.

Watch video