How to Build Portfolio Website with React-Part5

I am following the youtube video React Portfolio Website Tutorial From Scratch – Build & Deploy React JS Portfolio Website (2022) by EGATOR. Thanks to him!

I am using Windows10 and vscode to build this.

1. Prerequisites

2. Add contents on Testimonials.jsx

If you want the explanations, watch his video from 2:20:44. (for using SwiperJS from 2:33:40)

We are using SwiperJS to make the carousel/slides.
Before we use them, we need to make the contents. Here is the code for contents before working on the slide. Also, like doing in the portfolio.jsx, define data first using array, and using map method to assign data to the html.

import React from 'react'
import './testimonials.css'
import AVTR1 from '../../assets/avatar1.jpg'
import AVTR2 from '../../assets/avatar2.jpg'
import AVTR3 from '../../assets/avatar3.jpg'
import AVTR4 from '../../assets/avatar4.jpg'

const data = [
	{
		avatar: AVTR1,
		name: 'Tina Snow',
		review: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dicta recusandae et quam asperiores? Necessitatibus quia distinctio laudantium veritatis ratione dicta quaerat perferendis? Deleniti ratione doloribus sunt possimus ea libero quia.'
	},
	{
		avatar: AVTR2,
		name: 'Shatta Wale',
		review: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dicta recusandae et quam asperiores? Necessitatibus quia distinctio laudantium veritatis ratione dicta quaerat perferendis? Deleniti ratione doloribus sunt possimus ea libero quia.'
	},
	{
		avatar: AVTR3,
		name: 'Kwame Despite',
		review: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dicta recusandae et quam asperiores? Necessitatibus quia distinctio laudantium veritatis ratione dicta quaerat perferendis? Deleniti ratione doloribus sunt possimus ea libero quia.'
	},
	{
		avatar: AVTR4,
		name: 'Nana Ama McBrown',
		review: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dicta recusandae et quam asperiores? Necessitatibus quia distinctio laudantium veritatis ratione dicta quaerat perferendis? Deleniti ratione doloribus sunt possimus ea libero quia.'
	},
]

const Testimonials = () => {
  return (
		<section id='testimonials'>
			<h5>My Recent Work</h5>
			<h2>Portfolio</h2>

			<div className="container testimonials__container">
				{
					data.map(({avatar, name, review}, index) => {
						return (
							<article key={index}className="testimonial">
								<div className="client__avatar">
									<img src={avatar} />
								</div>
								<h5 className="client__name">{name}</h5>
									<small className="client__review">
										{review}
									</small>
							</article>
						)
					})
				}
			</div>
		</section>
  )
}

export default Testimonials

To see how it works step by step, it would be better to style the contents first. You can style by copy/paste the code on 3. Styling testimonials.css first and come back to here.

Now, you can choose the style from SwiperJS Demos. In this tutorial, we are using the pagination. And you can see the documentation for react as reference.

To use SwiperJS, install it with npm install swiper command. And import necessary libraries.

replace first div after the h2 tag contains container class with Swiper tag. and replace article tag with SwiperSlide tag. And, add some more properties on Swiper tag.

Here is the full contents for Testimonials.jsx file.

import React from 'react'
import './testimonials.css'
import AVTR1 from '../../assets/avatar1.jpg'
import AVTR2 from '../../assets/avatar2.jpg'
import AVTR3 from '../../assets/avatar3.jpg'
import AVTR4 from '../../assets/avatar4.jpg'

// import Swiper core and required modules
import { Pagination } from 'swiper';

import { Swiper, SwiperSlide } from 'swiper/react';

// Import Swiper styles
import 'swiper/css';
import 'swiper/css/pagination';


const data = [
	{
		avatar: AVTR1,
		name: 'Tina Snow',
		review: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dicta recusandae et quam asperiores? Necessitatibus quia distinctio laudantium veritatis ratione dicta quaerat perferendis? Deleniti ratione doloribus sunt possimus ea libero quia.'
	},
	{
		avatar: AVTR2,
		name: 'Shatta Wale',
		review: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dicta recusandae et quam asperiores? Necessitatibus quia distinctio laudantium veritatis ratione dicta quaerat perferendis? Deleniti ratione doloribus sunt possimus ea libero quia.'
	},
	{
		avatar: AVTR3,
		name: 'Kwame Despite',
		review: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dicta recusandae et quam asperiores? Necessitatibus quia distinctio laudantium veritatis ratione dicta quaerat perferendis? Deleniti ratione doloribus sunt possimus ea libero quia.'
	},
	{
		avatar: AVTR4,
		name: 'Nana Ama McBrown',
		review: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dicta recusandae et quam asperiores? Necessitatibus quia distinctio laudantium veritatis ratione dicta quaerat perferendis? Deleniti ratione doloribus sunt possimus ea libero quia.'
	},
]

const Testimonials = () => {
  return (
		<section id='testimonials'>
			<h5>My Recent Work</h5>
			<h2>Portfolio</h2>

			<Swiper className="container testimonials__container"
							// install Swiper modules
							modules={[Pagination]}
							spaceBetween={40}
							slidesPerView={1}
							pagination={{ clickable: true }}
	  	>
				{
					data.map(({avatar, name, review}, index) => {
						return (
							<SwiperSlide key={index}className="testimonial">
								<div className="client__avatar">
									<img src={avatar} />
								</div>
								<h5 className="client__name">{name}</h5>
									<small className="client__review">
										{review}
									</small>
							</SwiperSlide>
						)
					})
				}
			</Swiper>
		</section>
  )
}

export default Testimonials

3. Styling testimonials.css

If you want the explanations, watch his video from 2:24:03.

FYI, the padding-bottom of .container.testimonials__container and below screenshot are related to slider paginations.

.container.testimonials__container {
	width: 40%;
	padding-bottom: 4rem;
}

.client__avatar {
	width: 4rem;
	aspect-ratio: 1/1;
	overflow: hidden;
	border-radius: 50%;
	margin: 0 auto 1rem;
	border: 0.4rem solid var(--color-primary-variant);
}

.testimonial {
	background: var(--color-bg-variant);
	text-align: center;
	padding: 2rem;
	border-radius: 2rem;
	user-select: none;
}

.client__review {
	color: var(--color-light);
	font-weight: 300;
	display: block;
	width: 80%;
	margin: 0.8rem auto 0;
}

.swiper-pagination-clickable .swiper-pagination-bullet {
	background: var(--color-primary);
}

/* MEDIA QUERIES (MEDIUM DEVICES) */
@media screen and (max-width:1024px) {
	.container.testimonials__container {
		width: 60%;
	}
}

/* MEDIA QUERIES (SMALL DEVICES) */
@media screen and (max-width:600px) {
	.container.testimonials__container {
		width: var(--container-width-sm);
	}

	.client__review {
		width: var(--container-width-sm);
	}
}

4. Add contents on Contact.jsx

If you want the explanations, watch his video from 2:40:23.

We are using EmailJS to submit the form.
Before we use them, we need to make the contents. Here is the code for contents before working on the submit form with EmailJS.

import React from 'react'
import './contact.css'
import {MdOutlineEmail} from 'react-icons/md'
import {RiMessengerLine} from 'react-icons/ri'
import {BsWhatsapp} from 'react-icons/bs'

const Contact = () => {
  return (
		<section id='contact'>
			<h5>Get In Touch</h5>
			<h2>Contact Me</h2>

			<div className="container contact__container">
				<div className="contact__options">
					<article className="contact__option">
						<MdOutlineEmail className='contact__option-icon'/>
						<h4>Email</h4>
						<h5 className='text-light'>[email protected]</h5>
						<a href="mailto:[email protected]" target="_blank">Send a message</a>
					</article>

					<article className="contact__option">
						<RiMessengerLine className='contact__option-icon'/>
						<h4>Messenger</h4>
						<h5 className='text-light'>test</h5>
						<a href="https://m.me/test" target="_blank">Send a message</a>
					</article>

					<article className="contact__option">
						<BsWhatsapp className='contact__option-icon'/>
						<h4>WhatsApp</h4>
						<h5 className='text-light'>+123456789</h5>
						<a href="https://api.whatsapp.com/send?phone+2334551351" target="_blank">Send a message</a>
					</article>
				</div>
				{/* END OF CONTACT OPTIONS */}
				<form action="">
					<input type="text" name='name' placeholder='Your Full Name' required />
					<input type="email" name='email' placeholder='Your Email' required />
					<textarea name="message" rows="7" placeholder='Your Message' required></textarea>
					<button type='submit' className='btn btn-primary'>Send Message</button>
				</form>
			</div>
		</section>
  )
}

export default Contact

To see how it works step by step, it would be better to style the contents first. You can style by copy/paste the code on 5. Styling contact.css first and come back to here.

Now, We will use EmailJS to connect the form to submit to your email. You can see the documentation for react as reference.

First, sign up/sign in to EmailJS. and add New Service.

Create New Email Template, and test it.

Make sure add {{email}} to Reply To on the right side. In the tutorial video it did not change it, but as I tested, it will reply to the email from the submit even though incoming mail is from myself like screenshot below.

Now, go back to the VSCode, and Install EmailJS by typing npm install emailjs-com --save in command.

Import EmailJS and add/edit content.

Make sure you change your service_id, template_id and public_key from EmailJS. ** In the tutorial video uses user id instead of public key, but I guess EmailJS updated. You can find the public_key from the Account tab.
Here is the full code of Contact.jsx.

import React from 'react'
import './contact.css'
import {MdOutlineEmail} from 'react-icons/md'
import {RiMessengerLine} from 'react-icons/ri'
import {BsWhatsapp} from 'react-icons/bs'
import { useRef } from 'react';
import emailjs from 'emailjs-com';

const Contact = () => {
	const form = useRef();

  const sendEmail = (e) => {
    e.preventDefault();

    emailjs.sendForm('YOUR_SERVICE_ID', 'YOUR_TEMPLATE_ID', form.current, 'YOUR_PUBLIC_KEY')

		e.target.reset()
  };

  return (
		<section id='contact'>
			<h5>Get In Touch</h5>
			<h2>Contact Me</h2>

			<div className="container contact__container">
				<div className="contact__options">
					<article className="contact__option">
						<MdOutlineEmail className='contact__option-icon'/>
						<h4>Email</h4>
						<h5 className='text-light'>[email protected]</h5>
						<a href="mailto:[email protected]" target="_blank">Send a message</a>
					</article>

					<article className="contact__option">
						<RiMessengerLine className='contact__option-icon'/>
						<h4>Messenger</h4>
						<h5 className='text-light'>test</h5>
						<a href="https://m.me/test" target="_blank">Send a message</a>
					</article>

					<article className="contact__option">
						<BsWhatsapp className='contact__option-icon'/>
						<h4>WhatsApp</h4>
						<h5 className='text-light'>+123456789</h5>
						<a href="https://api.whatsapp.com/send?phone+2334551351" target="_blank">Send a message</a>
					</article>
				</div>
				{/* END OF CONTACT OPTIONS */}
				<form ref={form} onSubmit={sendEmail}>
					<input type="text" name='name' placeholder='Your Full Name' required />
					<input type="email" name='email' placeholder='Your Email' required />
					<textarea name="message" rows="7" placeholder='Your Message' required></textarea>
					<button type='submit' className='btn btn-primary'>Send Message</button>
				</form>
			</div>
		</section>
  )
}

export default Contact

5. Styling contact.css

If you want the explanations, watch his video from 2:51:02.

.container.contact__container {
	width: 58%;
	display: grid;
	grid-template-columns: 30% 58%;
	gap: 12%;
}

.contact__options {
	display: flex;
	flex-direction: column;
	gap: 1.2rem;
}

.contact__option {
	background: var(--color-bg-variant);
	padding: 1.2rem;
	border-radius: 1.2rem;
	text-align: center;
	border: 1px solid transparent;
	transition: var(--transition);
}

.contact__option:hover {
	background: transparent;
	border-color: var(--color-primary-variant);
}

.contact__option-icon {
	font-size: 1.5rem;
	margin-bottom: 0.5rem;
}

.contact__option a {
	margin-top: 0.7rem;
	display: inline-block;
	font-size: 0.8rem;
}

/* Form */
form {
	display: flex;
	flex-direction: column;
	gap: 1.2rem;
}

input, textarea {
	width: 100%;
	padding: 1.5rem;
	border-radius: 0.5rem;
	background: transparent;
	border: 2px solid var(--color-primary-variant);
	resize: none;
	color: var(--color-white);
}

/* MEDIA QUERIES (MEDIUM DEVICES) */
@media screen and (max-width:1024px) {
	.container.contact__container {
		grid-template-columns: 1fr;
		gap: 2rem;
	}
}

/* MEDIA QUERIES (SMALL DEVICES) */
@media screen and (max-width:600px) {
	.container.contact__container {
		width: var(--container-width-sm);
	}
}

6. Add contents on Footer.jsx

If you want the explanations, watch his video from 3:07:43.

import React from 'react'
import './footer.css'
import {FaFacebookF} from 'react-icons/fa'
import {FiInstagram} from 'react-icons/fi'
import {IoLogoTwitter} from 'react-icons/io'

const footer = () => {
  return (
		<footer>
			<a href="#" className="footer__logo">Jisoo</a>

			<ul className="permalinks">
				<li><a href="#">Home</a></li>
				<li><a href="#about">About</a></li>
				<li><a href="#experience">Experience</a></li>
				<li><a href="#services">Services</a></li>
				<li><a href="#portfolio">Portfolio</a></li>
				<li><a href="#testimonials">Testimonials</a></li>
				<li><a href="#contact">Contact</a></li>
			</ul>

			<div className="footer__socials">
				<a href="https://facebook.com"><FaFacebookF /></a>
				<a href="https://instagram.com"><FiInstagram /></a>
				<a href="https://twitter.com"><IoLogoTwitter /></a>
			</div>

			<div className="footer__copyright">
				<small>&copy; Jisoo. All rights reserved.</small>
			</div>
		</footer>
  )
}

export default footer

7. Styling footer.css

If you want the explanations, watch his video from 3:11:53.

footer {
	background: var(--color-primary);
	padding: 3rem 0;
	text-align: center;
	font-size: 0.9rem;
	margin-top: 7rem;
}

footer a {
	color: var(--color-bg);
}

.footer__logo {
	font-size: 2rem;
	font-weight: 500;
	margin-bottom:2rem;
	display: inline-block;
}

.permalinks {
	display: flex;
	flex-wrap: wrap;
	justify-content: center;
	gap: 2rem;
	margin: 0 auto 3rem;
}

.footer__socials {
	display: flex;
	justify-content: center;
	gap: 1rem;
	margin-bottom: 4rem;
}

.footer__socials a {
	background: var(--color-bg);
	color: var(--color-white);
	padding: 0.8rem;
	border-radius: 0.7rem;
	display: flex;
	border: 1px solid transparent;
}

.footer__socials a:hover {
	background: transparent;
	color: var(--color-bg);
	border-color: var(--color-bg);
}

.footer__copyright {
	margin-bottom: 4rem;
	color: var(--color-bg);
}

/* MEDIA QUERIES (SMALL DEVICES) */
@media screen and (max-width:600px) {
	.permalinks {
		flex-direction: column;
		gap: 1.5rem;
	}

	.footer__socials {
		margin-bottom: 2.6rem;
	}
}

8. Deploying

I won’t follow his deployment steps because I will deploy to my own Raspberry Pi server. If you don’t know which hosting to choose and what or how to do deploy, you can watch his video from 3:18:50.

In case, you have RPi server setup already, and you want to deploy there, you can follow my post How to Deploy React Project to Raspberry Pi.

Thank you for following my posts.