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;
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 */} )
}
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;
Screenshots
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';
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;
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;
Screenshots
That's all for the UI/Profile portion of the project, stay tuned!
Top comments (0)