Hire+Plus! For Employees Here's how I built it (UI - Profile)
Tags: react, webdev, programming, tutorial
Published: 2022-08-06 08:00:49

## 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. 

```javascript
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;

```

---

## 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.

```javascript
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 */} )
}
```

### 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.

```javascript
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;

```

### Screenshots

![profile view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/373ofvvbt7e7dchffkja.png)

![profile view 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t53qoevfz3lif6sbmqr3.png)

---

## 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. 

```javascript
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';
```

### 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

```javascript
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;

```

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

```javascript
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;

```

### Screenshots

**Editing state**
![edit profile view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3wy88llq2vmdsutjjtk.png)

![skills and experience view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cfvz0djevnydwsnkq65d.png)

![project view](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vzw71ac4whf7a67t2gcj.png)

**Non-editing state**
![non-editing state1 ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6gh9e9vzb7j81zb21s8.png)

![non-editing state2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6dqxecyat25qdqmiba1h.png)

![non-editing state3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4kqyxqtxdll65fdbmdx.png)


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