15 min read

Marketing Company Website with React, Styled Components and Typescript

Last Update: 1 June, 2022

You will be able to follow along if you have minimal reactjs and typescript knowledge. We will use intersection observer and add a functional map with mapbox-gl. You can use this website as a template for your future projects and add it to your personal portfolio. Let's begin!

You can find the finished, live website here.

You can find the source code for this project here. If you feel like you are lost or there is a weird error you can't fix, feel free to check this repository out.

§This Is What We Are Creating

This website is a landing page-ish project for a corporation. In this particular project I wanted to go with a marketing company example. However, I think this website can fit many other companies with some minimal changes. You can see that we included some forms for clients (or potential clients) to contact us but since we don't have backend for this project we will only log these informations.

We will show the visitors the projects we have done, our partners, recent work, blog posts and finally our (made up) partners. We will allow users to follow us on social media and subscribe to our weekly newsletter.

§Project Setup

We can simply initialize our react projects with typescript with this command:

npx create-react-app corporate-react-ts --template typescript

When the installation is over, I usually delete everything inside App.tsx file that is unnecessary. Go ahead and delete all the starter code inside our App.tsx and start writing the code necessary for our project. We will be using styled components, this way we can keep the css necessary for the component inside the component easily and don't need to create css files. Our App.tsx will look like this now:

import React from 'react'; import styled from 'styled-components'; function App() { return ( <AppWrapper> App file </AppWrapper> ); } export default App; const AppWrapper = styled.div` min-height: 100vh; display: flex width: 100vw; `;

We can create a global css file using styled components but in this project I didn't think it would be necessary so we are still keeping the index.css file and with some basic changes. We will also be adding some colors that I think fit our websites. Even though I don't use all of the colors in my projects I like having the colors that go well together around in my project for future improvement and additions. You can find the full code necessary until this point for index.css here:

html { scroll-behavior: smooth; } * { padding:0; margin:0; box-sizing: border-box; } :root { --white: rgb(255, 255, 255); --black: rgb(0,0,0); --gray: rgb(69, 69, 69); --gray-2: rgb(149, 149, 149); --yellow: #ffbe0b; --pink-lavender: #cdb4dbff; --orchid-pink: #ffc8ddff; --nadeshiko-pink: #ffafccff; --uranian-blue: #bde0feff; --darker-blue: rgb(63, 108, 151); --baby-blue-eyes: #a2d2ffff; --top-margin: 100px; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; }

We will be using some visuals to make our project look and feel as real as possible. You can of course use your own images or visit one of the many websites that provide free visuals and use some of theirs. I have gathered most of the visuals used in this project by visiting websites such as unsplash, pexels etc. I have resized and changed the format of my visuals to keep them lighter. This is also suggested and can be handled for free online. To save your visuals inside the src folder create a folder called assets. Save your visuals here.

Since we are creating a single page we don't need to deal with routing for this project. I like to start creating from top to bottom. So first of all we need to create the navbar, then the sections and finally the footer for our website.

§Keep A Consistent Padding With A Wrapper Component

CSS libraries such as tailwindcss and bootstrap offer solutions for spacing and padding in different screen sizes. Not having to do it manually is a time saver but we are using styled components in this particular project. So we need to create a component to wrap other components that we want to have the same padding. As we will be using this component in other components it's a better idea to create this component inside the components folder.

Create a file called ContainerComponent.tsx inside components folder. This component will get the display value of flex and direction will decided with the prop we set on it. Again we need to set a type for the prop. Thanks to styled components we can set the value for flex-direction: column when necessary. The padding setting will be pretty basic, smaller the screen smaller padding value.I also like to keep a max-width so in really big screens our web page will keep this simple and clean look. Full code for this file is here:

import React, { ReactNode } from 'react' import styled from 'styled-components' interface PropType { children: ReactNode column: boolean } const ContainerComponent = (props:PropType ) => { return ( <Container column={props.column}> {props.children} </Container> ) } export default ContainerComponent; const Container = styled.div<PropType>` display:flex; flex-direction: ${props => props.column ? "column": "row"}; width: 100%; padding-left: 10px; padding-right: 10px; max-width: 1920px; @media (max-width: 1920px) { padding-left: 80px; padding-right: 80px; } @media (max-width: 1440px) { padding-left: 60px; padding-right: 60px; } @media (max-width: 1200px) { padding-left: 50px; padding-right: 50px; } @media (max-width: 960px) { padding-left: 40px; padding-right: 40px; } @media (max-width: 600px) { padding-left: 30px; padding-right: 30px; } @media (max-width: 480px) { padding-left: 20px; padding-right: 20px; } `

That's it now our content will look aligned no matter the screen size.

§Creating Our Responsive Navbar

Go ahead and create a folder called components inside the src folder. We will keep our components here. Inside this folder create a navbar folder. In the navbar we are going to need the logo of our company. We will also use this logo as a home button that will take us to the top of the page. There are a bunch of websites that allows you to design logos for free or if you prefer you can just use an image as well. I turned my logo into a component and this is the simple Logo.tsx component I created inside the navbar fodler:

import React from 'react' import styled from 'styled-components'; import logo from '../../assets/marketing-co.png'; const Logo = () => { return ( <a href='/#'> <Img src={logo} alt="brand logo" /> </a> ) } export default Logo const Img = styled.img` border-radius: 50%; margin: 5px; width: 94px; `

We will also need links for different sections of our web page. We will also turn these links into components to keep my code as clean as possible. We will call this component NavbarLink.tsx. This components will take a string as prop so we can send our visitors where they wanna go in our web page. We also need to replace the spaces with "-" of course. Other than that it's just a simple component as you can see here:

import React from 'react' import styled from 'styled-components' interface PropType { to: string; } const NavbarLink = ({ to }: PropType) => { const link = to.replace(' ', '-') return ( <A href={`#${link}`}>{to}</A> ) } export default NavbarLink const A = styled.a` text-decoration: none; color: var(--baby-blue-eyes); font-size: 1.6rem; margin: 10px; text-transform: uppercase; transition: 0.4s; :hover { color: var(--nadeshiko-pink); } `

Now that the basic navbar for larger screens is done we can think of smaller screen sizes. In most of my projects I think of desktop first, contrary to what's suggested. That's just a habit of mine. Anyways, we need a side bar instead of a large navbar at the top of the screen and we need a hamburger button to toggle this side bar for navigation.

I think an explanation will work better after seeing these components work together. Now let's create a component called HamburgerComponent.tsx inside the navbar folder. This component will toggle the side bar we will create. The code used in this component is here:

import React, { useState } from 'react' import styled from 'styled-components' import RightNav from './RightNav' const Hamburger = () => { const [open, setOpen] = useState<boolean>(false) return (<> <StyledHamburger open={open} onClick={() => {setOpen(!open)}}> <div></div> <div></div> <div></div> </StyledHamburger> <RightNav open={open} /> </>) } export default Hamburger; const StyledHamburger = styled.div<{ open: boolean }>` position: ${({ open }) => open ? "fixed" : 'absolute'}; top: 20px; right: 50px; width: 40px; z-index: 20; height: 40px; display: flex; flex-direction: column; justify-content: space-around; align-items: center; &:hover { cursor: pointer; } @media screen and (min-width: 1201px) { display: none; } div { width: 40px; height: 0.25rem; background-color: ${({ open }) => open ? "var(--nadeshiko-pink)" : 'var(--baby-blue-eyes)'}; border-radius: 10px; transform-origin: 1px; transition: all 0.3s ease-in-out; &:nth-child(1) { transform: ${({ open }) => open ? 'rotate(45deg)' : 'rotate(0)'}; } &:nth-child(2) { transform: ${({ open }) => open ? 'translateX(100%)' : 'translateX(0)'}; opacity: ${({ open }) => open ? 0 : 1}; } &:nth-child(3) { transform: ${({ open }) => open ? 'rotate(-45deg)' : 'rotate(0)'}; } } `

As you can see, this component will be visible only when the screen size is smaller than 1200 pixels. We are also passing the boolean value we set to our RightNav component. Now we can create our RightNav.tsx file inside the navbar folder. I set the type for the value for RightBarWrapper as follows. I wanted to keep it this way to show that we can define the types separately this way as well.The code is very simple here as you can see:

import React from 'react' import styled from 'styled-components' import RightNavbarLink from './RightNavbarLink'; interface propType { open:boolean } const RightNav = ( props: propType) => { return ( <RightBarWrapper open={props.open}> <LinkContainer> <RightNavbarLink to="about" /> <RightNavbarLink to="we offer" /> <RightNavbarLink to="portfolio" /> <RightNavbarLink to="recent work" /> <RightNavbarLink to="contact" /> </LinkContainer> </RightBarWrapper> ) } export default RightNav const RightBarWrapper = styled.div<{open:boolean}>` position: fixed; display: flex; border-left: 4px solid var(--baby-blue-eyes); justify-items: center; transition: all 0.3s ease-in-out; background: var(--white); transform: ${({ open }) => open ? 'translateX(0)' : 'translateX(100%)'}; top: 0; right: 0; height: 100vh; width: 250px; z-index:10; @media screen and (min-width: 1201px) { display: none; } ` const LinkContainer = styled.div` margin-top: 100px; display: flex; flex-direction: column; width: 100%; margin-left: 30px; `

Here we have a component called RightNavbarLink that we don't have yet. So let's go ahead and create. This will very similar to the component we created for our links. Create RightNavbarLink.tsx file in the navbar folder. Here you can see the full code for this component:

import React from 'react' import styled from 'styled-components'; interface propType { to: string; } const RightNavbarLink = ({ to }: propType) => { const link = to.replace(' ', '-') return ( <A href={`#${link}`}>{to}</A> ) } export default RightNavbarLink const A = styled.a` transition: 0.2s; color: var(--baby-blue-eyes); text-decoration: none; text-transform: uppercase; transform: 1s; margin-bottom: 20px; font-size: 1.4rem; :hover { color: var(--nadeshiko-pink); } `

Now that we have all the parts necessary for our navigation bar we can create this bar. Inside the navbar folder we need to create the Navbar.tsx file to everything. Our navbar will change background color when the banner component is not visible. This functionality will be added later when we create our Banner component. We will be able to handle it easily with the help of intersection observer.

We are going to set the height value of this component to a value of our choosing. It's good practise to keep this value as a variable. This way if we need to change the height value, the layout will not break nor look odd. Our navbar will have "position:fixed", this way we will be seeing it wherever we are on the page for easy navigation. Also setting the "z-index:100" will assure that our component appears on top of all the others.

The code for this file should look something like this:

import React from 'react' import styled from 'styled-components' import ContainerComponent from '../ContainerComponent' import Hamburger from './HamburgerComponent' import Logo from './Logo' import NavbarLink from './NavbarLink' const Navbar = (props: { bgColor: string }) => { return ( <Nav bgColor={props.bgColor} id="navbar"> <ContainerComponent column={false}> <Logo /> <RightAlign> <NavbarLink to="about" /> <NavbarLink to="we offer" /> <NavbarLink to="portfolio" /> <NavbarLink to="recent work" /> <NavbarLink to="contact" /> </RightAlign> <Hamburger /> </ContainerComponent> </Nav> ) } export default Navbar const Nav = styled.nav<{ bgColor: string }>` background: ${props => props.bgColor}; height: var(--top-margin); position: fixed; transition: .4s; transition-delay: 1s; width: 100%; max-width: 100vw; display: flex; flex-direction: row; z-index: 100; ` const RightAlign = styled.div` margin-left: auto; display: flex; flex-direction: row; align-items: center; @media (max-width: 1200px) { display: none; } `

§We Need A Cool Banner

The banner is very important for landing pages. You need a clean banner with your pitch or slogan. Since this is project is a template we will just mimic this with a simple slogan and some lorem ipsum text. Using an image in the background will cost you in terms of performance definitely but it can be beneficial from a marketing point of view. For this project I used an image, of course after resizing and changing the format to webp.

The tricky part in this component is the intersection observer. Thankfully there's an npm package we can use for this very purpose. Install the react-intersection-observer package with the following command below:

npm i react-intersection-observer

We will also need a button for the visitors to easily scroll down to the next section. We might use this button in other parts of our website, so it would be better to create a component that we can import. This way we will be able to keep a consistent style throughout our websites. In our components folder let's create the Button.tsx component. Button should take a text prop of course but at this point we don't know if we wanna add "onClick" functionality yet. The code for this component:

import React from 'react' import styled from 'styled-components'; interface propType { text: string, onClick?: Function, } const Button = (prop: propType) => { return ( <StyledButton> {prop.text} </StyledButton> ) } export default Button const StyledButton = styled.button` padding: 14px 20px; font-size: 1.4rem; border-radius: 30px; border: 2px solid white; color: var(--white); background: linear-gradient(25deg, var(--uranian-blue), var(--nadeshiko-pink)); text-transform: capitalize; transition: .4s; :hover { border: 2px solid black; background: inherit; cursor: pointer; color: var(--black); } `

We want to keep everything in the banner component in the center as a column. Since we have already created our container component, we can just pass the column value as "true" this will be a piece of cake. App.tsx will be the parent component that we will create the the state here. We will pass down the setter our banner when the banner is in view the value will be true. With this boolean value we will toggle the bgColor and pass it down to the navbar. This way when the banner is not in view our navbar will have a background color of our choosing. App.tsx file until this point will look like this:

import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import Banner from './components/Banner'; import Navbar from './components/navbar/Navbar'; function App() { const [ onScreen, setOnScreen ] = useState(); const [ bgColor, setBGColor ] = useState("inherit"); useEffect(() => { if(!onScreen) setBGColor("var(--white)"); else if(onScreen) setBGColor("inherit"); }, [onScreen]) return ( <AppWrapper> <Navbar bgColor={bgColor}/> <Banner setOnScreen={setOnScreen}/> </AppWrapper> ); } export default App; const AppWrapper = styled.div` min-height: 100vh; display: flex width: 100vw; `;

Now let's create the Banner.tsx inside the components with the following code:

import React, { useEffect } from 'react' import styled from 'styled-components' import bgImg from '../assets/office-designers-smallerx3.webp'; import Button from './Button'; import ContainerComponent from './ContainerComponent'; import { useInView } from 'react-intersection-observer'; const Banner = (props: any) => { const { ref, inView } = useInView({ rootMargin: "0px", threshold: 1, }); useEffect(() => { if(inView) props.setOnScreen(true); else if(!inView) props.setOnScreen(false); }, [inView, props]) return ( <BannerWrapper ref={ref}> <ContainerComponent column={true}> <BannerContent> <H1>YOU BRAND WILL GROW HERE</H1> <H2>Lorem ipsum, dolor sit</H2> <P>Lorem ipsum dolor sit amet consectetur adipisicing elit. Rem mollitia voluptatum minus deserunt delectus ipsum cupiditate. Unde, repudiandae molestiae? Mollitia temporibus unde beatae. Odio quod, accusamus dicta fugiat molestiae recusandae?</P> <a href="#about"><Button text={"discover"}/></a> </BannerContent> </ContainerComponent> </BannerWrapper> ) } export default Banner const BannerWrapper = styled.section` background: linear-gradient( rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.7) ), url(${bgImg}); background-position: bottom; background-repeat: no-repeat; background-size: cover; min-height: 100vh; width: 100%; padding-top: var(--top-margin); margin-top: 0; ` const BannerContent = styled.div` display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: white; margin-top: var(--top-margin); ` const H1 = styled.h1` font-size: 1.4rem; text-transform: uppercase; @media (max-width: 1440px) { font-size: 1.2rem; } @media (max-width: 1200px) { font-size: 1.1rem; } ` const H2 = styled.h2` font-size: 4rem; margin-bottom: 16px; text-transform: uppercase; color: var(--baby-blue-eyes); @media (max-width: 1440px) { font-size: 3.6rem; } @media (max-width: 1200px) { font-size: 3rem; } @media (max-width: 720px) { text-align: center; font-size: 2.6rem; } @media (max-width: 480px) { font-size: 2rem; } ` const P = styled.p` text-align: center; max-width: 500px; font-size: 1.2rem; margin-bottom: 100px; margin-top: 20px; line-height: 1.7; @media (max-width: 960px) { font-size: 1.1rem; line-height: 1.6; } @media (max-width: 480px) { font-size: 1rem; line-height: 1.3; } `

There are minor changes for different screen sizes of course but other than that there's nothing special as you can see.

§About Section And The Reasons Our Company Exists

This section will be very simple as well. It should be cleaner than the banner, we won't want to tire tire the visitors' eyes. We will add a grid layout for this section with an image in the side and the content on the other side. The content should be about why the company started, the purpose and maybe a summary of your journey up to this point. The full code for About.tsx file is here:

import React from 'react' import styled from 'styled-components' import cactusImg from '../assets/yellow-cactus-white-bg-smaller.webp'; const About = () => { return ( <AboutWrapper id="about"> <Img /> <TextBox> <TextBoxTitle> Lorem ipsum dolor sit, amet consectetur adipisicing elit. Modi molestias </TextBoxTitle> <TextBoxTitleText> Odio labore sequi consequatur ipsa cupiditate pariatur ducimus itaque consequuntur nulla nostrum!cupiditate pariatur ducimus itaque consequuntur nulla nostrum! </TextBoxTitleText> <div> <InfoBoxTitle> Some Title </InfoBoxTitle> <InfoText>Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium hic ipsa numquam quos ullam quo eum? Sit a dicta assumenda est doloremque unde eius consequuntur sint vero, dolorem non illo?</InfoText> </div> <div> <InfoBoxTitle> Another Title </InfoBoxTitle> <InfoText>Lorem ipsum, dolor sit amet consectetur adipisicing elit. Quae neque cupiditate consequuntur perferendis laboriosam mollitia vitae ipsum expedita. </InfoText> </div> </TextBox> </AboutWrapper> ) } export default About const AboutWrapper = styled.section` display:grid; grid-template-columns: 1fr 1fr; @media (max-width: 1200px) { grid-template-columns: 1fr; } ` const Img = styled.div` background-image: url(${cactusImg}); background-position: bottom; background-repeat: no-repeat; background-size: cover; width: 100%; padding-top: var(--top-margin); padding-bottom: var(--top-margin); @media (max-width: 1200px) { height: 500px; } @media (max-width: 960px) { height: 400px; } @media (max-width: 720px) { height: 300px; } ` const TextBox = styled.div` padding: 100px 140px 140px 20px; background: #ebebf5; display: grid; grid-template-columns: 1fr 1fr; grid-gap: 30px; @media (max-width: 1200px) { padding: 120px; } @media (max-width: 960px) { padding: 60px; } @media (max-width: 480px) { display: flex; flex-direction: column; } ` const TextBoxTitle = styled.h2` grid-column: span 2; margin-bottom: 20px; color: var(--darker-blue); font-size: 1.8rem; ` const InfoBoxTitle = styled.h3` font-size: 1.6rem; margin-bottom: 14px; ` const InfoText = styled.p` font-size: 1.1rem; line-height: 1.2; ` const TextBoxTitleText = styled.p` grid-column: span 2; font-size: 1.1rem; line-height: 1.2; `

Responsive design is an important part of front-end web development. Instead of going for screen sizes that are the most popular, I like to just check my design on different screen sizes. If I see that it's breaking or starting to look bad somehow, I play with the design on that screen size. Just a little tip.

Now import this file inside App.tsx below the banner component.

§Showing What Do We Have to Offer

In this section we are going to showcase what do we have to offer to our potential clients. We will show how fast we are, how our developers / engineers are the best etc. This component that I created consists of two main parts. The main part will have cards that reveal more information when the users hover over them.

To keep our component clean and easy to read I created the data necessary in another folder. In src folder create a folder called data. There are other parts that include similar behaviour, so we will be creating different more data files here in this folder. In the data folder create a file called offerData.ts. Inside this file we will have some icons. No go ahead and run this commands to install fontawesome and get the icons necessary:

npm i @fortawesome/fontawesome-svg-core @fortawesome/free-brands-svg-icons @fortawesome/free-solid-svg-icons @fortawesome/react-fontawesome

Now we can start importing and using our icons. With the addition of the icons our offerData.ts will looks like this:

import { faRocket, faCode, faRobot, faGift, IconDefinition } from '@fortawesome/free-solid-svg-icons'; interface OfferData { title: string text: string detail: string icon: IconDefinition }; const data:OfferData[] = [ { title: "We Are Fast", icon: faRocket, detail: "Lorem, ipsum dolor sit amet consectetur adipisicing elit. Accusantium eligendi assumenda deserunt ullam perspiciatis modi libero inventore earum voluptas iure?", text: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere ab deserunt iste veritatis officia" }, { title: "Expert Developers", icon: faCode, detail: "Lorem, ipsum dolor sit amet consectetur adipisicing elit. Accusantium eligendi assumenda deserunt ullam perspiciatis modi libero inventore earum voluptas iure?", text: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere ab deserunt iste veritatis officia" }, { title: "Lots of Promotions", icon: faGift, detail: "Lorem, ipsum dolor sit amet consectetur adipisicing elit. Accusantium eligendi assumenda deserunt ullam perspiciatis modi libero inventore earum voluptas iure?", text: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere ab deserunt iste veritatis officia" }, { title: "Automize Your Growth", icon: faRobot, detail: "Lorem, ipsum dolor sit amet consectetur adipisicing elit. Accusantium eligendi assumenda deserunt ullam perspiciatis modi libero inventore earum voluptas iure?", text: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere ab deserunt iste veritatis officia" }, ] export default data

We will import this data into our Offer.tsx file and create a grid item for each object with the help of "map()" method. The finished code for the Offer.tsx file will have the OfferBottom.tsx component which we did not create yet. So if you see an error because of the just ignore it until it is created.

Now go ahead and create a folder inside components with the name offer. We will create our 2 components here, Offer.tsx and OfferBottom.tsx files. The code is here for Offer.tsx file:

import React from 'react' import styled from 'styled-components' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import OfferBottom from './OfferBottom'; import data from '../../data/offerData'; const Offer = () => { return ( <> <OfferWrapper id='we-offer'> <Content> <OfferHeader> <OfferTitle> Lorem ipsum dolor sit amet consectetur adipisicing elit. </OfferTitle> <OfferSubTitle>Corporis modi optio, dolorum fugiat magni</OfferSubTitle> </OfferHeader> {data.map(d => <OfferBox key={d.title}> <DetailInfo> <DetailText>{d.detail}</DetailText> </DetailInfo> <OfferInfo> <IconSpan> <FontAwesomeIcon icon={d.icon}></FontAwesomeIcon> </IconSpan> <BoxTitle>{d.title}</BoxTitle> <BoxText>{d.text}</BoxText> </OfferInfo> </OfferBox>)} </Content> </OfferWrapper> <OfferBottom /> </> ) } export default Offer const OfferWrapper = styled.section` display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 100px; @media (max-width: 1200px) { padding:60px; } @media (max-width: 960px) { padding: 40px; } @media (max-width: 720px) { padding:20px; } ` const Content = styled.div` display: grid; grid-template-columns: repeat(4 ,1fr ); grid-gap: 30px; @media (max-width: 1200px) { grid-template-columns: 1fr 1fr; grid-gap: 10px; } @media (max-width: 720px) { grid-template-columns: 1fr; grid-gap: 6px; } ` const OfferHeader = styled.div` grid-column: 1/-1; text-align: center; margin-bottom: 60px; ` const OfferTitle = styled.h2` font-size: 1.8rem; margin-top: 50px; ` const OfferSubTitle = styled.p` margin-top:20px; ` const DetailInfo = styled.div` width: 100%; height: 0%; transition: 1s; opacity: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; ` const DetailText = styled.p` margin-bottom: 20px; line-height: 1.7; text-align: center; ` const OfferInfo = styled.div` display: flex; flex-direction: column; justify-content: center; ` const OfferBox = styled.div` transition : 0.4s; cursor: pointer; border-radius: 10px; :hover{ box-shadow:0 0 30px var(--nadeshiko-pink); ${DetailInfo} { height: 100%; opacity: 1; } ${OfferInfo} { transform: scale(0); } } padding: 40px 20px; @media (max-width: 1200px) { padding: 30px 16px; } @media (max-width: 720px) { padding: 12px 10; } ` const IconSpan = styled.span` font-size: 2.4rem; color: var(--baby-blue-eyes); ` const BoxTitle = styled.h3` margin-bottom: 26px; width: 100%; ` const BoxText = styled.p` line-height: 1.6; font-size: 1.07rem; `

When the visitor hovers over the card they will see the text content behind and there is a cool fade out / slide animation. If you don't like something about it feel free to change it, it's your project.

The bottom part of this section will be created as another component and have 2 images with some information. We will again use the grid layout because it's just so much easier to handle layouts using grid and it's a lifesaver when it comes to responsiveness. The code for the OfferBottom.tsx file is as follows:

import React from 'react' import styled from 'styled-components' import call from '../../assets/callcenter-employees-smaller.webp'; import meeting from '../../assets/office-people-smaller.webp'; const OfferBottom = () => { return ( <Wrapper> <Img src={call} alt="Call center team" /> <InfoBox> <AboveTitle> CONTACT US </AboveTitle> <InfoTitle> WE ARE READY TO HELP 24/7 </InfoTitle> <InfoText> Lorem ipsum dolor, sit amet consectetur adipisicing elit. Nesciunt quis excepturi quia magnam dolores laudantium reiciendis eveniet, ab accusamus sequi? </InfoText> </InfoBox> <Img src={meeting} alt="a typical team meeting" /> <InfoBox> <AboveTitle> SIMPLE SOLUTIONS FOR COMPLEX PROBLEMS </AboveTitle> <InfoTitle> NO MATTER HOW DIFFICULT THE TASK </InfoTitle> <InfoText> Lorem ipsum dolor sit amet consectetur adipisicing elit. Possimus voluptates sed culpa ratione sapiente mollitia. </InfoText> </InfoBox> </Wrapper> ) } export default OfferBottom const Wrapper = styled.div` width: 100%; display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 10px; background: var(--black); @media (max-width: 1200px) { grid-template-columns: 1fr 1fr; } @media (max-width: 720px) { grid-template-columns: 1fr; } ` const Img = styled.img` height:100%; width:100%; object-fit: cover; ` const InfoBox = styled.div` padding: 20px 40px; ` const InfoTitle = styled.h3` color: var(--baby-blue-eyes); margin-bottom: 20px; font-weight: bold; font-size: 2rem; ` const InfoText = styled.p` font-size: 1.2rem; line-height: 1.6; color: var(--gray-2); ` const AboveTitle = styled.p` color: var(--gray-2); margin-top: 10px; `

Just a reminder, you can check the repository if you have any problems so far.

Now import the Offer.tsx inside App.tsx and place it under the about component.

§Portfolio Section

We will place our portfolio section below the offer section. You will see that as we progress, the complexity increases. Now go ahead and create a portfolio folder inside components folder. There will be two main parts here in our portfolio section. In the first one we will show the visitor the companies we created solutions for.

Each company we worked for will be represented with an image, these should be the images of companies or something related. Since we don't actually have any companies we created solutions for we can use any cool image we want. Which is what I did. These images are placed in a grid layout. When the visitors hover over an image the invisible element (opacity:0) that covers the image will slowly become visible and by clicking on it visitor will be able to visit the company's website. Of course in our case all the link just send to the banner.

To create this component we will need some data of course. Now inside the data folder create a file called portfolioData.ts. The file I created looks like this:

import mechanic from '../assets/black-white-mechanic-smaller.webp'; import view from '../assets/blue-red-view-smaller.webp'; import flower from '../assets/flower-blue-smaller.webp'; import lines from '../assets/lines-orange-white-smaller.webp'; import neuron from '../assets/neuron-smaller.webp'; import tree from '../assets/tree-green.webp'; import map from '../assets/map-pins-smaller.webp'; interface PortfolioData { img: string alt: string cols: string link: string }; const data:PortfolioData[] = [ {img: mechanic, alt: "some cool company", cols: "4", link: "#"}, {img: view, alt: "some cool company", cols: "4", link: "#"}, {img: map, alt: "some cool company", cols: "4", link: "#"}, {img: lines, alt: "some cool company", cols: "5", link: "#"}, {img: flower, alt: "some cool company", cols: "7", link: "#"}, {img: neuron, alt: "some cool company", cols: "6", link: "#"}, {img: tree, alt: "some cool company", cols: "6", link: "#"} ] export default data

The "cols" values determine how much space they will occupy inside our grid layout. We don't need to worry about the order in which we create these objects because we are going to set "grid-auto-flow: row dense;". This way our grid fill up the empty spaces for us. How cool is that!

We will import this data into our Portfolio.tsx file, which is located in the portfolio folder. To keep the component clean we are going to create another component that will accept the information and render as cards.

We are going to create the PortfolioItem.tsx, this will be the component that takes data as props. We will map and create a card for each data object.

import React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faPaperPlane } from '@fortawesome/free-solid-svg-icons'; import styled from 'styled-components'; interface propTypes { imgSrc: string alt: string colSpan: string link: string } interface styleProp { colSpan: string } const PortfolioItem = (props:propTypes) => { return ( <Wrapper colSpan={props.colSpan}> <A href={props.link}> <FontAwesomeIcon icon={faPaperPlane}></FontAwesomeIcon> </A> <Img loading='lazy' src={props.imgSrc} alt={props.alt} /> </Wrapper> ) } export default PortfolioItem const A = styled.a` width: 100%; height: 100%; opacity: 0; position: absolute; background: rgba(255,255,255); transition: 0.4s; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 1; font-size: 2rem; ` const Img = styled.img` width: 100%; height: 340px; object-fit: cover; ` const Wrapper = styled.div<styleProp>` position: relative; grid-column: span ${props => props.colSpan}; @media (max-width: 600px){ grid-column: 1/-1; } :hover { ${A} { opacity: 0.7; } } `

Now we can create the Portfolio.tsx, the code for this component is very simple as well:

import React from 'react' import styled from 'styled-components' import ContainerComponent from '../ContainerComponent' import data from '../../data/portfolioData'; import PortfolioItem from './PortfolioItem'; import Numbers from './Numbers'; const Portfolio = () => { return ( <Wrapper id='portfolio'> <ContainerComponent column={true}> <H2>Some of The Companies That Love Our Solutions</H2> <SubTitle> Lorem ipsum dolor sit amet consectetur adipisicing elit. </SubTitle> <Grid> {data.map(d => { return <PortfolioItem key={d.img} imgSrc={d.img} alt={d.alt} colSpan={d.cols} link={d.link}/> })} </Grid> </ContainerComponent> <Numbers /> </Wrapper> ) } export default Portfolio const Wrapper = styled.section` ` const H2 = styled.h2` margin-top: 160px; text-align: center; width: 100%; font-size: 2.4rem; @media (max-width: 1200px){ margin-top: 100px; } @media (max-width: 960px){ margin-top: 80px; } ` const SubTitle = styled.p` text-align: center; margin-top: 20px; margin-bottom: 80px; ` const Grid = styled.div` display: grid; grid-template-columns: repeat(12, 1fr); grid-gap: 20px; grid-auto-flow: dense; align-items: center; @media (max-width: 600px){ grid-template-columns: 1fr; } `

Just ignore the error caused by the non-existent Numbers component. You can comment it out and see how it is looking so far.

We will start working on our numbers now. This component will include a count up and an intersection observer. When this component comes into view our numbers will start counting up to the numbers we will define. Now let's create numbersData.ts file inside our data folder. In this file we will have some simple array of objects, the code for this file is here :

interface NumData { num: string text: string }; const data:NumData[] = [ {num: "2365", text: "Projects Completed"}, {num: "7012", text: "Happy Clients"}, {num: "10524", text: "Campaigns Automized"}, {num: "364", text: "Professionals"}, {num: "1842", text: "Cups of Coffee Taken"}, ] export default data

To make our jobs easier instead of creating a function that will start counting when the component comes into view, we will install a package. With the bash npm i react-countup command we can install the necessary package.

This part will also be a grid and render a component as a grid item. Our component should accept a number to count up to, a text explaining what the number is for and finally a boolean value to verify if the component is in view. The full code for the NumbersItem.tsx component is as follows:

import React, { useEffect, useState } from 'react' import styled from 'styled-components' import CountUp from 'react-countup'; interface PropTypes { num: string, text: string, inView: boolean } const NumbersItem = (props: PropTypes) => { const [ activated, setActivated ] = useState<boolean>(false); useEffect(() => { if(props.inView) setActivated(true) }, [props.inView]) return ( <Wrapper> <Num>{activated && <CountUp start={0} end={parseInt(props.num)} duration={2.5} />}</Num> <p>{props.text}</p> </Wrapper> ) } export default NumbersItem const Wrapper = styled.div` padding: 20px; display: flex; flex-direction: column; justify-content: center; @media (max-width: 480px){ align-items: center; } ` const Num = styled.p` font-size: 2.8rem; font-weight: bold; `

Now we can create the Numbers.tsx component:

import React from 'react' import styled from 'styled-components' import ContainerComponent from '../ContainerComponent' import data from '../../data/numbersData' import NumbersItem from './NumbersItem' import { useInView } from 'react-intersection-observer' const Numbers = () => { const { ref, inView } = useInView({ rootMargin: "0px", threshold: 1, }); return ( <Wrapper> <ContainerComponent column={false}> <Grid ref={ref}> {data.map(d => <NumbersItem key={d.num} inView={inView} num={d.num} text={d.text}/>)} </Grid> </ContainerComponent> </Wrapper> ) } export default Numbers const Wrapper = styled.div` margin-top: 160px; margin-bottom: 40px; width: 100%; background: linear-gradient(0.25turn, var(--orchid-pink), var(--baby-blue-eyes)); min-height: 300px; display: flex; aşign-items: center; ` const Grid = styled.div` flex-grow: 1; display: grid; grid-template-columns: repeat(5, 1fr); grid-gap: 40px; @media (max-width: 1200px) { grid-gap: 20px } @media (max-width: 960px) { grid-template-columns: repeat(3, 1fr); grid-gap: 10px } @media (max-width: 600px) { grid-template-columns: 1fr 1fr; } @media (max-width: 480px) { grid-template-columns: 1fr; } `

Now we handled our portfolio section. If everything is ok so far you should see the numbers starting to count up when this component comes into view. Don't forget to import the Portfolio.tsx inside App.tsx and place it under the offer section.

§Recent Work and Brands We Worked With

In this section we will show our visitors the blog posts written in our blog and the some brands of the companies we worked with. Now let's create our simplest data file yet. Inside the data folder create brandData.ts file. Inside the file we will just import some brand images. I created some made up brands to use here:

import awesome from '../assets/awesome-logo-smaller.webp'; import cool from '../assets/cool-brand-smaller.webp'; import international from '../assets/international-brand-smaller.webp'; import quality from '../assets/quality-brand-smaller.webp'; import known from '../assets/well-known-brand-smaller.webp'; interface BrandData { img: string }; const data:BrandData[] = [ {img: awesome}, {img: cool}, {img: known}, {img: international}, {img: quality } ] export default data

You can also add some more information like the name of the brand or some small information about the brand here. I assume everybody can recognize my made up brands here and didn't include anything. I think it looks better like this but again, change whichever part you think can be improved.

We are going to need another file for the recent blogs data. Inside the data folder now create recentWorkCardData.ts. This file will export an array objects. Each object has a title, a text explaining the post, name of the author and an avatar of the author. The full code is here:

import woman from '../assets/woman-avatar-smaller.webp' interface recentWorkCardData { title: string text: string name: string avatar: string } const data:recentWorkCardData[] = [ { title: "5 Methods to Increase Visibility", text: "Praesent porta enim augue, et iaculis sem scelerisque id. Nulla sagittis fringilla imperdiet. Donec vestibulum leo a diam pharetra, vel ullamcorper mauris convallis.", name: "Jane Doe", avatar: woman }, { title: "How To Gain More Customers", text: "Praesent porta enim augue, et iaculis sem scelerisque id. Nulla sagittis fringilla imperdiet. Donec vestibulum leo a diam pharetra, vel ullamcorper mauris convallis.", name: "Jane Doe", avatar: woman }, { title: "Free AI Tools You Can Use Today", text: "Praesent porta enim augue, et iaculis sem scelerisque id. Nulla sagittis fringilla imperdiet. Donec vestibulum leo a diam pharetra, vel ullamcorper mauris convallis.", name: "Jane Doe", avatar: woman }, ]; export default data;

This data will be mapped and rendered into a component called RecentWorkCard.tsx. Create a folder called recentWork and create the RecentWorkCard.tsx here. The code for this component simple as well:

import React from 'react' import styled from 'styled-components' import lines from '../../assets/lines-orange-white-smaller.webp' interface PropTypes { title: string text: string name: string avatar: string } const RecentWorkCard = (props:PropTypes) => { return ( <Wrapper> <InfoBox> <H3>{props.title}</H3> <WriterP>{props.text}</WriterP> </InfoBox> <WriterBox> <Img src={props.avatar} alt="" /> <WriterInfo> <p>{props.name}</p> <p>Sr. Web Developer</p> </WriterInfo> </WriterBox> </Wrapper> ) } export default RecentWorkCard const InfoBox = styled.div` margin-bottom: 20px; transition: 0.4s; ` const Wrapper = styled.div` width: 100%; border-radius: 5px; padding: 30px; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; min-height: 400px; background: linear-gradient( rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7) ), url(${lines}); background-position: bottom; background-repeat: no-repeat; background-size: cover; color: white; :hover { cursor: pointer; ${InfoBox} { color: var(--baby-blue-eyes); } } ` const WriterBox = styled.div` width: 100%; display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 20px; ` const WriterInfo = styled.div` height: 100%; display: flex; flex-direction: column; justify-content: space-between; ` const H3 = styled.h3` font-size: 1.4rem; @media (max-width: 960px){ font-size: 2rem; } margin-bottom: 20px; ` const WriterP = styled.p` margin-top: 6px; @media (max-width: 960px){ font-size: 1.4rem; } ` const Img = styled.img` width: 100%; height: 60px; border-radius: 5px; object-fit: cover; `

As you can see there's nothing fancy here.

We can go ahead and create the RecentWork.tsx file as well even though we don't yet have the brands component. Just ignore the error cause by its absence. The code for this file is here:

import React from 'react' import styled from 'styled-components' import ContainerComponent from '../ContainerComponent' import data from '../../data/recentWorkCardData' import RecentWorkCard from './RecentWorkCard' import Brands from './Brands' const RecentWork = () => { return ( <Wrapper id='recent-work'> <ContainerComponent column={true}> <H2>Our most recent publications</H2> <SubTitle>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Id quas maxime a doloremque esse.</SubTitle> <RecentWorkGrid> {data.map(d => <RecentWorkCard key={d.title} title={d.title} text={d.text} name={d.name} avatar={d.avatar} />)} </RecentWorkGrid> <Brands /> </ContainerComponent> </Wrapper> ) } export default RecentWork const Wrapper = styled.section` width: 100%; margin-bottom: 60px; margin-top: 120px; ` const H2 = styled.h2` width: 100%; text-align: center; font-size: 2.8rem; font-weight: thin; ` const SubTitle = styled.p` margin-top: 6px; text-align: center; font-size: 1.2rem; color: var(--gray-2); ` const RecentWorkGrid = styled.div` display: grid; grid-template-columns: repeat(3, 1fr); grid-gap: 60px; width: 100%; margin: auto; margin-top: 60px; @media (max-width: 960px){ grid-template-columns: 1fr; } `

Again, nothing complicated.

Time to create the part that contains the brand. The code for Brands.tsx file is here:

import React from 'react' import styled from 'styled-components' import data from '../../data/brandData' import BrandItem from './BrandItem' const Brands = () => { return ( <Wrapper> <H3>You may know our partners</H3> {data.map(d => <BrandItem img={d.img} />)} </Wrapper> ) } export default Brands const Wrapper = styled.div` margin-top: 120px; width: 100%; display: grid; grid-template-columns: repeat(5, 1fr); grid-gap: 100px; @media (max-width: 1200px) { grid-gap: 20px; } @media (max-width: 720px) { grid-template-columns: repeat(3, 1fr); grid-gap: 30px; } @media (max-width: 480px) { grid-template-columns: 1fr 1fr; grid-gap: 20px; } ` const H3 = styled.h3` grid-column: 1/ -1; margin-bottom: 0; font-size: 1.8rem; margin-left: 10px; `

Now the component we are will use to render all our brands inside the BrandItem.tsx file:

import React from 'react' import styled from 'styled-components' interface PropTypes { img: string } const BrandItem = (props:PropTypes) => { return ( <Wrapper> <Img src={props.img} alt="" /> </Wrapper> ) } export default BrandItem const Wrapper = styled.div` padding: 10px; border-radius: 5px; display: flex; flex-direction: column; align-items: center; ` const Img = styled.img` width: 100%; transition: 0.4s; max-width: 160px; border-radius: 5px; :hover { box-shadow: 0px 0px 32px var(--nadeshiko-pink); } `

With this component added, we finalized our recent work. Import this component inside the App.tsx and render it under the portfolio section.

§Contact Section and Our Location

We of course need to give the visitors the option to contact us easily when they want to and show our location. The contact section will consist of these two main parts. Let's create a folder inside the components called contact and begin.

Inside the contact folder, we can start by creating our map. Instead of going with google maps, I decided to use mapbox for this map. Dont forget to get your api key to be able to use this map. Save your api key in the .env.local file in the root of your project. I named my api key REACT_APP_MAPBOX_ACCESS_TOKEN. Initializing the variable with the "REACTAPP" makes it visible for us in our app.

There were some performance issues but now I think it's acceptable in terms of performance. We will be using a web worker to not to block the main thread. This helps increase performance in this part. There will be a button to make the map fullscreen, a pin showing the location of our office and another button to show the location of the visitor. Now let's install the necessary packages for this part with this command:

npm i mapbox-gl react-map-gl worker-loader

Inside the contact folder create the MapComponent.tsx file and the add this code:

import React from 'react'; import styled from 'styled-components'; import ReactMapGL, { Marker, FullscreenControl, GeolocateControl, NavigationControl } from 'react-map-gl'; import pin from '../../assets/pin.png' import mapboxgl from 'mapbox-gl'; // @ts-ignore // eslint-disable-next-line import/no-webpack-loader-syntax mapboxgl.workerClass = require('worker-loader!mapbox-gl/dist/mapbox-gl-csp-worker').default; const MapComponent = () => { return ( <Wrapper> <ReactMapGL initialViewState={{ latitude: 48.87, longitude: 2.3, zoom: 5.5 }} mapStyle="mapbox://styles/mapbox/streets-v9" style={{ height:"100%", width: "100%"}} attributionControl={false} mapboxAccessToken={process.env.REACT_APP_MAPBOX_ACCESS_TOKEN} > <GeolocateControl positionOptions={{enableHighAccuracy: true}} trackUserLocation={false} /> <FullscreenControl /> <Marker longitude={2.3} latitude={48.87} anchor="bottom" > <img style={{width:"21px", height:"14px",top:"-11px", bottom: "-7px"}} src={pin} alt="pin" /> </Marker> <NavigationControl visualizePitch={true}/> </ReactMapGL> </Wrapper> ); }; export default MapComponent const Wrapper = styled.div` width: 100%; height: 100%; @media (max-width: 960px) { grid-column: 1/-1; } min-height: 400px; `

You will see that there are some performance issues you can fix by just doing some googling. There are more customizations and configurations can be also done in the map. I suggest that you read the official documents and explore your options. I'm sure you will have some use for it in the future.

Now let's handle our contact form. Since we don't have a backend or any means to handle our mailing yet we will just log the data given to us by the visitors. Create a file called ContactForm.tsx and add the following code:

import React, { useState } from 'react' import styled from 'styled-components' import Button from '../Button' const ContactForm = () => { const [name, setName] = useState<string>(""); const [email, setEmail] = useState<string>(""); const [subject, setSubject] = useState<string>(""); const [text, setText] = useState<string>(""); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); console.log("the info to send", name, email, subject, text) } return ( <Wrapper> <H3>Feel free to contact us!</H3> <P>We will respond to you as soon as we can</P> <Form onSubmit={(e) => handleSubmit(e)}> <Label htmlFor="f-name"> Enter your name <Input required type="text" name="f-name" placeholder='John Doe' onChange={(e) => setName(e.target.value)} /> </Label> <Label htmlFor="f-email"> Enter your email address <Input type="email" name="f-email" required placeholder='example@gmail.com' onChange={(e) => setEmail(e.target.value)} /> </Label> <Label htmlFor="f-subject"> Enter a subject <Input type="text" name="f-subject" required placeholder='Marketing campaign' onChange={(e) => setSubject(e.target.value)} /> </Label> <Label htmlFor="f-textarea"> What would you like to share with us </Label> <Textarea required rows={12} onChange={(e) => setText(e.target.value)} /> <Button text='submit'/> </Form> </Wrapper> ) } export default ContactForm const Wrapper = styled.div` width: 100%; display: flex; flex-direction: column; padding: 20px 0; ` const H3 = styled.h3` font-size: 1.8rem; ` const P = styled.p` color: var(--gray-2); ` const Form = styled.form` margin-top: 10px; width: 100%; display: flex; flex-direction: column; ` const Label = styled.label` font-size: .8rem; margin-top: 14px; ` const Input = styled.input` width: 100%; padding: 8px; font-size: 1.4rem; transition: 0.4s; :focus { box-shadow: 0px 0px 32px var(--baby-blue-eyes); } ` const Textarea = styled.textarea` width: 100%; padding: 10px; font-size: 1.1rem; transition: 0.4s; :focus { box-shadow: 0px 0px 32px var(--baby-blue-eyes); } margin-bottom: 10px; `

Nothing special in this part. As you can see we found another use for the button component we created, this is the reason creating components instead of repeating code is just very simple.

We will now create the Contact.tsx file and import these two components we just created. The code for this component is here:

import React from 'react' import styled from 'styled-components' import ContactForm from './ContactForm' import ContainerComponent from '../ContainerComponent' import Map from './MapComponent' const Contact = () => { return ( <ContainerComponent column={false}> <Wrapper id='contact'> <Map /> <ContactForm /> </Wrapper> </ContainerComponent> ) } export default Contact const Wrapper = styled.section` display: grid; grid-template-columns: 1fr 1fr; width: 100%; min-height: 600px; grid-gap: 40px; @media (max-width: 960px) { grid-template-columns: 1fr; } `

That's it, we have a cool contact section now. This part is also responsive as all the other parts we have created so far. If there is anything you think you can improve, go ahead!

Import this component below the recent work. Inside the index.css add the following code in the first line in order to render the map correctly:

@import 'mapbox-gl/dist/mapbox-gl.css';

The footer is the part that you visitor will see the last. Our footer will have 3 main parts. One part for the visitors to contact us via phone. Another part for asking our visitors to subscribe to our newsletter. The last part for the bottom of the footer which will include the copyright text and the social media links allowing visitors to follow our company from social media.

Let's begin by creating our footer folder inside the components folder. The first file we will create for this part is Contact.tsx. Since the code is very basic I will just share it with you right away:

import React from 'react' import styled from 'styled-components' const ContactComponent = () => { return ( <Contact> <H3>Let's talk!</H3> <Text>Feel free to contact us 24/7. We can discuss an ongoing project or start talking about a new one. You will be a ble to reach an employee whenever you call.</Text> <Number>0555-5555-555555-555</Number> <Number>0555-5555-555555-554</Number> </Contact> ) } export default ContactComponent const Contact = styled.div` padding: 100px 0px 120px; @media (max-width: 960px){ padding: 40px 0 0 0 ; } ` const H3 = styled.h3` color: var(--white); font-size: 2rem; margin-bottom: 40px; ` const Text = styled.p` margin-bottom: 20px; ` const Number = styled.p` color: var(--baby-blue-eyes); font-size: 1.4rem; `

Now let's create our newsletter. We don't have a backend for this part either so we will just log the email address given to us by the visitor. Create Newsletter.tsx file here and add the following code:

import React, { useState } from 'react' import styled from 'styled-components'; import Button from '../Button'; const NewsletterComponent = () => { const [email, setEmail] = useState<string>(""); const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); console.log("sent email is ==> ",email) } return ( <Newsletter> <H3>Sign up to our newsletter.</H3> <Text>If you wanna find out the latest tips and tricks we think that can help your business grow, sign up to our newsletter. You can subscribe easily if you don't like our weekly emails, no worries.</Text> <Form onSubmit={e => handleSubmit(e)}> <label htmlFor=""> <Input type="email" required onChange={e => setEmail(e.target.value)} /> </label> <Button text='Sign Up'/> </Form> </Newsletter> ) } export default NewsletterComponent; const H3 = styled.h3` color: var(--white); font-size: 2rem; margin-bottom: 40px; ` const Text = styled.p` margin-bottom: 20px; margin-left: auto; ` const Newsletter = styled.div` padding: 100px 0px 120px; width: 100%; @media (max-width: 960px){ padding: 0 ; } ` const Form = styled.form` margin-top: 10px; width: 100%; display: flex; flex-direction: column; ` const Input = styled.input` margin-top: 20px; margin-bottom: 20px; width: 100%; padding: 8px; font-size: 1.4rem; transition: 0.4s; :focus { box-shadow: 0px 0px 32px var(--baby-blue-eyes); } `

As you can see just a simple form.

For the bottom part of our footer we will be using some icons from fontawesome. This part will direct the visitors to my blog and github profile if you do not change the href values so go ahead and change them. The code for the FooterBottom.tsx file is here:

import React from 'react' import styled from 'styled-components' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faFacebook, faTwitter, faGithub } from '@fortawesome/free-brands-svg-icons'; import { faBlog } from '@fortawesome/free-solid-svg-icons'; const FooterBottomComponent = () => { return ( <FooterBottom> <Icons> <IconA href="/#"> <FontAwesomeIcon icon={faFacebook} /> </IconA> <IconA href="https://wwizard-blog.netlify.app/"> <FontAwesomeIcon icon={faBlog} /> </IconA> <IconA href="https://github.com/ilci66"> <FontAwesomeIcon icon={faGithub} /> </IconA> <IconA href="/#"> <FontAwesomeIcon icon={faTwitter} /> </IconA> </Icons> <Text>You can find the full code <A href='#'>here</A> and the blog post <A href='/#'>here</A>. Ilker AKBIYIK</Text> </FooterBottom> ) } export default FooterBottomComponent const FooterBottom = styled.div` width: 100%; display: flex; flex-direction: row; grid-column: 1/-1; align-items:center; @media (max-width: 960px){ padding-top: 20px; } @media (max-width: 720px){ flex-direction: column; } ` const A = styled.a` color: var(--baby-blue-eyes); text-decoration: none; ` const Icons = styled.div` display: flex; flex-direction: row; @media (max-width: 720px){ width: 100%; justify-content: space-between; } ` const IconA = styled.a` font-size: 2.4rem; color: var(--baby-blue-eyes); background: var(--gray-2); margin: 10px; padding: 10px 20px; transition: 0.4s; :hover { background: var(--white); color: var(--nadeshiko-pink); } @media (max-width: 600px){ padding: 6px 8px; margin: 4px; } ` const Text = styled.p` margin-bottom: 20px; margin-left: auto; @media (max-width: 720px){ margin: auto; } `

Now we can wrap this all up with the Footer.tsx file. This file will mostly handle the placing of the components. The code is here:

import React from 'react' import styled from 'styled-components' import ContainerComponent from '../ContainerComponent' import Contact from './Contact'; import Newsletter from './Newsletter'; import FooterBottomComponent from './FooterBottom'; const Footer = () => { return ( <Wrapper> <ContainerComponent column={true}> <ContactNewsletter> <Contact /> <Newsletter /> </ContactNewsletter> <FooterBottomComponent /> </ContainerComponent> </Wrapper> ) } export default Footer const Wrapper = styled.section` width: 100%; background: var(--gray); margin-top: 40px; color: var(--gray-2); @media (max-width: 960px){ padding-bottom: 40px; } ` const ContactNewsletter = styled.div` display: grid; grid-template-columns: 1fr 1fr; grid-gap: 10vw; width: 100%; @media (max-width: 960px){ grid-template-columns: 1fr; grid-gap: 20px; } `

Add this component inside the App.tsx and finally our App.tsx file will look like this:

import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; import Banner from './components/Banner'; import Navbar from './components/navbar/Navbar'; import About from './components/About'; import Offer from './components/offer/Offer'; import Portfolio from './components/portfolio/Portfolio'; import RecentWork from './components/recentWork/RecentWork'; import Contact from './components/contact/Contact'; import Footer from './components/footer/Footer'; function App() { const [ onScreen, setOnScreen ] = useState(); const [ bgColor, setBGColor ] = useState("inherit"); useEffect(() => { if(!onScreen) setBGColor("var(--white)"); else if(onScreen) setBGColor("inherit"); }, [onScreen]) return ( <AppWrapper> <Navbar bgColor={bgColor}/> <Banner setOnScreen={setOnScreen}/> <About /> <Offer /> <Portfolio /> <RecentWork /> <Contact /> <Footer /> </AppWrapper> ); } export default App; const AppWrapper = styled.div` min-height: 100vh; display: flex width: 100vw; `;

§Conclusion

I think this project is very beginner friendly and yet includes some complexity for your to improve your react skills. It's a bit unconventional to create a landing page type of project using reactjs instead of plain HTML and CSS but understanding that with react you can still easily create a website like this was fun. You will see that there are some performance issues you can fix by just doing some googling. Some are simple like resizing images and using the right format.

Using styled components can cost you in terms of performance. Had you developped this project using plain CSS your website would be slightly faster. You as a developer need to make the decisions for each project about what tools to use. You need to have experience with multiple tools to know what do you need to use for each project. Every project is different with different necessities and potential issues.

I hope you gained some knowledge and had some fun developing this project, I know I did. Don't stop here improve upon it, add other pages, create a backend, handle mailing etc. You are free to use this as a template for your future projects.

Ilker Akbiyik