DEV Community

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

Posted on

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

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


Profile Route Page

inside routes > profile > profile-page.tsx
imports that assist with functions to call.
On mount, I fetch the profile by id I get from useParams. If editing is set to true, I render the edit profile component. If not, I render the actual profile component.

import { useEffect } from 'react';
import { useParams } from 'react-router';
import { getProfileById } from '../../app/features/profile/profileSlice';
import BeatLoader from 'react-spinners/BeatLoader';
import { useAppSelector, useAppDispatch } from '../../app/hooks';
import Profile from '../../components/profile/profile.component';
import EditProfile from '../../components/profile/edit-profile.component';

const ProfilePage = () => {
    const { id } = useParams();
    const dispatch = useAppDispatch();
    const { isLoading, isEditting } = useAppSelector((state) => state.profile);
    useEffect(() => {
        dispatch(getProfileById(id));
    }, [id, dispatch]);
    return (
        <>
            {isLoading ? (
                <div className="text-center p-20">
                    <BeatLoader color="#ffffff" />
                </div>
            ) : (
                <>{isEditting ? <EditProfile /> : <Profile />}</>
            )}
        </>
    );
};

export default ProfilePage;

Enter fullscreen mode Exit fullscreen mode

Profile Component

inside components > profile > profile.component.tsx
imports that assist with functions to call.

Functionality

settingEditView toggles isEditting to determine whether the edit profile component should show or not. removeItem removes projects (by filter) and sets new result in project state. I use currentUser for displaying info in UI.

import { setEditView, setProjects } from '../../app/features/profile/profileSlice';
import { useAppDispatch, useAppSelector } from '../../app/hooks';
import Experience from '../experience/experience-component';
import Project from '../project/project-component';

const Profile = () => {
    const dispatch = useAppDispatch();
    const { profile, isEditting } = useAppSelector((state) => state.profile);
    const { currentUser } = useAppSelector((state) => state.auth);

    const settingEditView = () => {
        dispatch(setEditView(!isEditting));
    };
    const removeItem = (id: number) => {
        const newProjects = profile.projects.filter((_, i) => i !== id);
        dispatch(setProjects(newProjects));
    };

    return ( {/* removed for simplicity */} )
}
Enter fullscreen mode Exit fullscreen mode

UI

Renders profile info (projects, experiences, skills, summary, website url, etc.). It will not render edit profile button if current user id and profile id's don't match. Projects and Experiences are both separate components. By default, it shows no data until you edit the profile.

const Profile = () => {
    {/* removed for simplicity */}
    return (
        <>
            {profile && (
                <>
                    <section style={{ backgroundColor: '#252731' }}>
                        <div className="container mx-auto py-5 px-5 md:px-0 text-right text-white flex justify-between">
                            <div className="flex justify-center text-md text-slate-200">
                                {profile.isForHire ? (
                                    <p>Actively looking for work</p>
                                ) : (
                                    <p>Not currently looking for work</p>
                                )}
                            </div>
                            {currentUser.uid !== profile.id ? null : (
                                <button
                                    onClick={settingEditView}
                                    className="underline text-md text-indigo-500"
                                >
                                    Edit Profile
                                </button>
                            )}
                        </div>
                        <div className="md:px-12 lg:px-24 max-w-7xl relative items-center w-full px-5 py-5 mx-auto">
                            <div className="mx-auto flex flex-col w-full max-w-lg mb-12 text-center">
                                <p className="mb-5 font-medium text-2xl text-white">
                                    {currentUser.displayName}
                                </p>
                                <img
                                    alt="testimonial"
                                    className="inline-block object-cover object-center w-20 h-20 mx-auto mb-8 rounded-full"
                                    src="https://picsum.photos/200"
                                />
                                <div className="flex justify-center">
                                    {profile.headline ? (
                                        <p className="text-base leading-relaxed font-color pr-2">
                                            {profile.headline}
                                        </p>
                                    ) : null}
                                    {profile.websiteURL ? (
                                        <>
                                            <a
                                                href={profile.websiteURL}
                                                className="text-base leading-relaxed text-indigo-500 border-l-2 border-gray-500 pl-2"
                                            >
                                                Live Website
                                            </a>
                                        </>
                                    ) : (
                                        <p className="font-color">No website to show</p>
                                    )}
                                </div>
                            </div>
                        </div>
                    </section>
                    <div className="divide-y divide-gray-700" key={profile.id}>
                        <section className="text-gray-600 body-font mt-2">
                            <div className="container px-10 py-20 mx-auto">
                                <div className="flex flex-col w-full mx-auto">
                                    <div className="w-full mx-auto">
                                        <h2 className="sm:text-3xl text-2xl my-5 font-bold">
                                            About Me
                                        </h2>
                                        <p className="lg:w-3/4 about-me font-color">
                                            {profile.summary ? profile.summary : 'No info to show'}
                                        </p>
                                    </div>
                                </div>
                            </div>
                        </section>
                        <section className="text-gray-600 body-font overflow-hidden">
                            <div className="container px-10 py-20 mx-auto">
                                <div className="flex flex-col w-full mx-auto">
                                    <div className="w-full mx-auto">
                                        <h2 className="sm:text-3xl text-2xl mb-5 font-bold">
                                            Skills
                                        </h2>
                                        {profile.skills.length ? (
                                            <ul className="flex flex-wrap">
                                                {profile.skills.map((skill, id) => {
                                                    return (
                                                        <li
                                                            className="mr-2 my-2 text-white px-4 py-2 rounded-3xl bg-indigo-700 cursor-pointer["
                                                            key={id}
                                                        >
                                                            {skill}
                                                        </li>
                                                    );
                                                })}
                                            </ul>
                                        ) : (
                                            <p className="font-color">No skills to show</p>
                                        )}
                                    </div>
                                </div>
                            </div>
                        </section>
                        {/* Experience starts */}
                        <section className="text-gray-600 body-font overflow-hidden">
                            <div className="container px-10 py-20 mx-auto">
                                <div className="-my-8 mx-auto">
                                    <h2 className="text-3xl my-8 mb-5 font-bold">Experience</h2>
                                    {profile.experience.length ? (
                                        <ol className="border-l-2 border-indigo-700 mt-10">
                                            {profile.experience.map((exp, index) => {
                                                return (
                                                    <Experience
                                                        experienceData={exp}
                                                        key={index}
                                                        itemIndex={index}
                                                    />
                                                );
                                            })}
                                        </ol>
                                    ) : (
                                        <p className="font-color">No experiences to show</p>
                                    )}
                                </div>
                            </div>
                        </section>
                        {/* Projects starts */}
                        <section className="text-gray-600 body-font">
                            <div className="container px-10 py-24 mx-auto">
                                <h2 className="sm:text-3xl text-2xl font-bold title-font mb-5">
                                    Projects
                                </h2>
                                {profile.projects.length ? (
                                    <div className="container py-5 mx-auto">
                                        <div className="flex flex-wrap -m-4">
                                            {profile.projects.map((proj, index) => {
                                                return (
                                                    <Project
                                                        project={proj}
                                                        key={index}
                                                        itemIndex={index}
                                                        removeItem={removeItem}
                                                    />
                                                );
                                            })}
                                        </div>
                                    </div>
                                ) : (
                                    <p className="font-color">No projects to show</p>
                                )}
                            </div>
                        </section>
                    </div>
                </>
            )}
        </>
    );
};

export default Profile;

Enter fullscreen mode Exit fullscreen mode

Screenshots

profile view

profile view 2


Edit Profile Component

inside components > profile > edit-profile.component.tsx
Imports: TagsInput react package for adding tags, I use this for adding skills, ExperiencePopupModal pop-up modal for adding job experiences to profile, ProjectPopupModal pop-up modal for adding projects. The rest are funcs from profileReducer to call when appropriate.

import { ChangeEvent, useState } from 'react';
import { useNavigate } from 'react-router';
import { TagsInput } from 'react-tag-input-component';
import {
    setEditView,
    setProjects,
    updateProfileById,
} from '../../app/features/profile/profileSlice';
import { useAppDispatch, useAppSelector } from '../../app/hooks';
import Experience from '../experience/experience-component';
import ExperiencePopupModal from '../modal/experience-modal.component';
import ProjectPopupModal from '../modal/project-modal.component';
import Project from '../project/project-component';
Enter fullscreen mode Exit fullscreen mode

Functionality

I handle all the fields that need to be updated formFields, skills, and checked for toggling is for hire state. isOpen for open/close experience modal. isProjOpen for open/close project modal.

handleCheckedChange - toggle isForHire state, determines
whether user is looking for work.

settingEditView - toggle editing view

onHandleChange - handle changes for the formFields state

onTextAreaChange - handle changes for textarea

updateProfile - updates the profile, set editing view to false, redirects to the main page.

removeItem - removes projects by id (using a filter).

closeModal - closes experience modal

closeProjModal - closes project modal

const EditProfile = () => {
    const { profile, isEditting } = useAppSelector((state) => state.profile);
    const { currentUser } = useAppSelector((state) => state.auth);
    const dispatch = useAppDispatch();
    const navigate = useNavigate();

    const [formFields, setFormFields] = useState({
        headline: profile.headline ? profile.headline : '',
        summary: profile.summary ? profile.summary : '',
        websiteURL: profile.websiteURL ? profile.websiteURL : '',
    });

    const [skills, setSkills] = useState<string[]>(
        profile.skills ? profile.skills : []
    );

    const [checked, setChecked] = useState<boolean>(profile.isForHire);
    const [isOpen, setIsOpen] = useState(false);
    const [isProjOpen, setIsProjOpen] = useState(false);

    const handleCheckedChange = () => {
        setChecked(!checked);
    };

    const settingEditView = () => {
        dispatch(setEditView(!isEditting));
    };

    const onHandleChange = (event: ChangeEvent<HTMLInputElement>) => {
        const { name, value } = event.target;
        setFormFields({ ...formFields, [name]: value });
    };
    const onTextAreaChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
        setFormFields({ ...formFields, summary: event.target.value });
    };

    const updateProfile = async () => {
        dispatch(
            updateProfileById({
                id: profile.id,
                headline: formFields.headline,
                summary: formFields.summary,
                isForHire: checked,
                websiteURL: formFields.websiteURL,
                skills: skills,
                experience: profile.experience,
                projects: profile.projects,
            })
        );
        dispatch(setEditView(false));
        navigate('/app');
    };
    const removeItem = (id: number) => {
        const newProjects = profile.projects.filter((_, i) => i !== id);
        dispatch(setProjects(newProjects));
    };
    const closeModal = () => {
        setIsOpen(false);
    };
    const closeProjModal = () => {
        setIsProjOpen(false);
    };

    return ( {/* removed for simplicity */} );
};

export default EditProfile;

Enter fullscreen mode Exit fullscreen mode

UI

Renders all fields that you can edit. Along with buttons to open either experience modal or project modal.

const EditProfile = () => {
    {/* removed for simplicity */}
    return (
        <>
            <section style={{ backgroundColor: '#252731' }}>
                <div className="container mx-auto py-5 text-right text-white flex justify-between">
                    <div>
                        <label
                            htmlFor="toggle-example"
                            className="flex items-center cursor-pointer relative mb-4"
                        >
                            <input
                                onChange={handleCheckedChange}
                                checked={checked}
                                type="checkbox"
                                id="toggle-example"
                                className="sr-only"
                            />
                            <div className="toggle-bg bg-gray-200 border-2 border-gray-200 h-6 w-11 rounded-full"></div>
                            <span className="ml-3 text-slate-200 text-md font-medium">
                                Are you looking for work?
                            </span>
                        </label>
                    </div>
                    <div>
                        <button
                            onClick={updateProfile}
                            className="mr-2 text-lg text-indigo-500"
                        >
                            Update
                        </button>
                        <button onClick={settingEditView}>Go Back</button>
                    </div>
                </div>
                <div className="md:px-12 lg:px-24 max-w-7xl relative items-center w-full px-5 py-5 mx-auto">
                    <div className="mx-auto flex flex-col w-full max-w-lg mb-12 text-center">
                        <p className="mb-5 font-medium text-2xl text-white">
                            {currentUser.displayName}
                        </p>
                        <img
                            alt="testimonial"
                            className="inline-block object-cover object-center w-20 h-20 mx-auto mb-8 rounded-full"
                            src="https://picsum.photos/200"
                        />
                        <div className="flex justify-center">
                            <input
                                className="font-color mr-3 primary-bg-color input-border-color block w-full px-5 py-3 mt-2 text-base text-neutral-600 placeholder-gray-500 transition duration-500 ease-in-out transform border border-transparent rounded-lg focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300 apearance-none"
                                id="headline"
                                value={formFields.headline}
                                onChange={onHandleChange}
                                name="headline"
                                placeholder="e.g. Front-end Developer"
                            />
                            <input
                                className="font-color primary-bg-color input-border-color block w-full px-5 py-3 mt-2 text-base text-neutral-600 placeholder-gray-500 transition duration-500 ease-in-out transform border border-transparent rounded-lg focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300 apearance-none"
                                id="websiteURL"
                                value={formFields.websiteURL}
                                onChange={onHandleChange}
                                name="websiteURL"
                                placeholder="Add website url..."
                            />
                        </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="sm:text-3xl text-2xl my-5 font-bold">
                                        About Me
                                    </h2>
                                    <div>
                                        <textarea
                                            maxLength={4000}
                                            rows={5}
                                            className="font-color input-border-color secondary-bg-color block w-full px-5 py-3 mt-2 text-base text-neutral-600 placeholder-gray-500 transition duration-500 ease-in-out transform border border-transparent rounded-lg focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300 apearance-none autoexpand"
                                            id="summary"
                                            name="summary"
                                            value={formFields.summary}
                                            onChange={onTextAreaChange}
                                            placeholder="Message..."
                                        ></textarea>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </section>
                    <section className="text-gray-600 body-font overflow-hidden">
                        <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="sm:text-3xl text-2xl mb-5 font-bold">
                                        Skills
                                    </h2>
                                    <TagsInput
                                        value={skills}
                                        onChange={setSkills}
                                        name="skills"
                                        placeHolder="Add skills here..."
                                    />
                                </div>
                            </div>
                        </div>
                    </section>
                    {/* Experience starts */}
                    <section className="text-gray-600 body-font overflow-hidden">
                        <div className="container px-5 py-20 mx-auto">
                            <div className="-my-8 mx-auto">
                                <h2 className="sm:text-3xl text-2xl my-5 font-bold">
                                    Experience
                                </h2>
                                <button
                                    onClick={() => setIsOpen(true)}
                                    className="block mb-5 text-white bg-indigo-700 focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-indigo-600 dark:hover:bg-indigo-700 dark:focus:ring-indigo-800"
                                    type="button"
                                    data-modal-toggle="defaultModal"
                                >
                                    Add Experience
                                </button>
                                <ExperiencePopupModal isOpen={isOpen} closeModal={closeModal} />
                                <p className="mb-5 font-color">
                                    Be sure to <b>'Update'</b> for these changes to take effect :)
                                </p>
                                {profile.experience.length ? (
                                    <ol className="border-l-2 border-indigo-700">
                                        {profile.experience.map((exp, index) => {
                                            return (
                                                <Experience
                                                    experienceData={exp}
                                                    key={index}
                                                    itemIndex={index}
                                                />
                                            );
                                        })}
                                    </ol>
                                ) : null}
                            </div>
                        </div>
                    </section>
                    {/* Projects starts */}
                    <section className="text-gray-600 body-font">
                        <div className="container px-5 py-24 mx-auto">
                            <div className="text-left mb-5">
                                <h2 className="sm:text-3xl text-2xl font-bold title-font mb-5">
                                    Projects
                                </h2>
                                <button
                                    onClick={() => setIsProjOpen(true)}
                                    className="block text-white bg-indigo-700 focus:ring-4 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-indigo-600 dark:hover:bg-indigo-700 dark:focus:ring-indigo-800"
                                    type="button"
                                    data-modal-toggle="defaultModal"
                                >
                                    Add Project
                                </button>
                                <ProjectPopupModal
                                    isProjOpen={isProjOpen}
                                    closeProjModal={closeProjModal}
                                />
                            </div>
                            <p className="mb-5 font-color">
                                Be sure to <b>'Update'</b> for these changes to take effect :)
                            </p>
                            <div className="flex flex-wrap -m-4">
                                {profile.projects.length
                                    ? profile.projects.map((project, index) => {
                                            return (
                                                <Project
                                                    project={project}
                                                    key={index}
                                                    itemIndex={index}
                                                    removeItem={removeItem}
                                                />
                                            );
                                      })
                                    : null}
                            </div>
                        </div>
                    </section>
                </div>
            </section>
        </>
    );
};

export default EditProfile;

Enter fullscreen mode Exit fullscreen mode

Screenshots

Editing state
edit profile view

skills and experience view

project view

Non-editing state
non-editing state1

non-editing state2

non-editing state3

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

Top comments (0)