TikTok Clone Using Cloudinary and React

Banner for a MediaJam post

Nwokocha Wisdom Maduabuchi

Introduction:

The TikTok app is one of the most engaging apps online. It has lots of videos ranging from comic, educational, and fun content. you will be creating a clone of the app using React and Cloudinary Video SDK. You will begin by creating your React project that will integrate the Cloudinary Video SDK using different video transformation functions in Cloudinary.

Prerequisite:

  • Good knowledge of HTML, CSS, Javascript
  • Good knowledge of react.js
  • Have Firebase installed
  • Good knowledge of the Cloudinary SDK

To create a react app, you will use the following command

1npx create-react-app TikTok-clone

After you have created the project you will go to the firebase app to create our project. Link to firebase app. Use the following steps to create your project on TikTok Click on the get started button, it will take you to your console page.

  • Click on the add project to create a new project.
  • Enter the name of your new project, and tick on the box to agree to the terms and conditions stated.
  • Click on the get started button, it will take you to your console page. Click on the add project to create a new project. Enter the name of your new project, and tick on the box to agree to the terms and conditions stated.
  • Click on the continue button.
  • Click on the second continue button.
  • Select the default account for firebase.
  • Click on the create project button.
  • After your project has been successfully created, click on the continue button.
  • It will take you to your console, click on the web button to register your project.
  • Click on the register app button.
  • Click on the project set to copy the configuration file
  • Copy the configuration file.
  • Go to your react project, create a firebase.js file and paste the configuration file.

Project:

To start your project, create a component folder, and create the following components:

  • The landing page component.
  • The profile page component.
  • The upload video page component.
  • The log-in page component.
  • The signup page component.

You will be creating each of these pages/components as you continue on this project. You will start by creating the header and side nav before creating the landing page and profile page. Let’s import react-bootstrap using the following command.

Header:

  • Create a new folder in the component folder, call it header.
  • Create a component file called Header.jsx.
  • Create a file called header.css
  • In the Header. jsx component import your react component.
  • create a div with a class of nav-bar . This will contain all the contents of the header.Here is the code for the header component.
1import React from "react";
2import tiktoklogo from "../../images/tiktoklogo.png";
3import dots from "../../images/menu-dots.svg";
4import search from "../../images/search.svg";
5import { Link } from "react-router-dom";
6import "./header.css";
7
8function Header() {
9 return (
10 <div className="nav-bar">
11 <img src={tiktoklogo} className="app-logos" alt="logo" />
12 <form className="form__banner">
13 <input
14 className="search__input"
15 type="text"
16 placeholder="Search accounts and videos"
17 name="search"
18 />
19 <button className="search__submit" type="submit">
20 <img src={search} alt="logo" />
21 </button>
22 </form>
23 <ul className="nav-links">
24 <li>Upload</li>
25
26 <Link to="/profile" style={{ textDecoration: "none" }}>
27 <li className="btn">Log in</li>
28 </Link>
29 <li>
30 <img src={dots} alt="logo" />
31 </li>
32 </ul>
33 </div>
34 );
35}
36
37export default Header;

Sidebar

The TikTok sidebar has two components, the followers and the microcard.

  • You will create a component called followers.
  • This component will be for the top content of the microcard the side has of the app.
  • Just like you did for the header, use the RFCe command to create the followers component.
  • Create a div with a class name 'followers-banner' that will contain the contents and elements as seen below.
1import React from "react";
2import videot from "../../images/videot.svg";
3import MicroCard from "../microcard/MicroCard";
4import classes from "./followers.module.css";
5
6function Followers() {
7 return (
8 <div className={classes.followers_banner}>
9 <div className={classes.followers_section}>
10 <div className={classes.home} />
11 <h4 className={classes.bold_content}>For You</h4>
12 </div>
13 <div className={classes.followers_section}>
14 <div className={classes.following} />
15 <h4>Following</h4>
16 </div>
17 <div className={classes.followers_section}>
18 <div className={classes.live}>
19 <img src={videot} alt="logo" />
20 </div>
21 <h4>Live</h4>
22 </div>
23 <div className={classes.line}></div>
24 <h5 className={classes.suggest_account}>Suggested accounts</h5>
25 <MicroCard />
26 <div className={classes.line}></div>
27 <div className={classes.followers_accounts}>
28 <h3>Following accounts</h3>
29 <h4>Accounts you follow will appear here</h4>
30 </div>
31 </div>
32 );
33}
34
35export default Followers;

Below is the CSS for the followers component.

1.followers_banner {
2 min-width: 230px;
3 height: 700px;
4 padding: 10px;
5 margin-left: 150px;
6 position: fixed;
7 overflow-y: scroll;
8 overflow-x: hidden;
9}
10::-webkit-scrollbar {
11 width: 7px;
12}
13::-webkit-scrollbar-track {
14 box-shadow: inset 0 0 5px #f5f5f5;
15 border-radius: 10px;
16}
17
18::-webkit-scrollbar-thumb {
19 background: #f5f5f5;
20 border-radius: 10px;
21}
22::-webkit-scrollbar-thumb:hover {
23 background: lightgrey;
24}
25
26.bold_content {
27 font-weight: 600;
28 color: #fe2c55;
29}
30.suggest_account {
31 color: #707278;
32}
33
34.followers_section {
35 display: flex;
36 flex-direction: row;
37 margin: 10px 0 5px 0px;
38 align-items: center;
39}
40.following {
41 width: 30px;
42 height: 29px;
43 background-image: url("https://i.imgur.com/Aq7L3jv.png");
44 background-size: 30px;
45 margin-right: 15px;
46}
47.home {
48 width: 30px;
49 height: 29px;
50 background-image: url("https://i.imgur.com/Q582Dp0.png");
51 background-size: 30px;
52 margin-right: 15px;
53}
54.live {
55 height: 29px;
56 background-size: 30px;
57 margin-right: 15px;
58}
59.line {
60 width: 400px;
61 height: 2px;
62 background-color: #f1f1f2;
63 border-radius: 5px;
64}
65
66@media only screen and (max-width: 850px) {
67 .followers_banner {
68 min-width: 20px;
69 height: 100%;
70 padding: 5px;
71 margin-left: 20px;
72 position: fixed;
73 overflow-y: hidden;
74 overflow-x: hidden;
75 border-right: 1px solid lightgrey;
76 }
77 .followers_section h4 {
78 display: none;
79 }
80 .suggest_account {
81 display: none;
82 }
83 .followers_accounts {
84 display: none;
85 }
86 .line {
87 width: 30px;
88 height: 2px;
89 background-color: #f1f1f2;
90 border-radius: 5px;
91 }
92}

For the down part of the sideway.

  • Create a component called Microcard.
  • This will have all the suggested accounts you should follow.
  • Once you have created that, created a div and give it a class name "microcard" and create another div that will contain the user image and the content.
  • Give it a class name "microcard-section" as seen below.
1import React from "react";
2import profile2 from "../../images/goddy.jpg";
3import profile from "../../images/pese.jpg";
4import profile3 from "../../images/jus.jpg";
5import profile4 from "../../images/godison.jpg";
6import "./microcard.css";
7
8function MicroCard() {
9 return (
10 <div className="microcard">
11 <div className="microcard-section ">
12 <img className="img-profile" src={profile2} alt="profile" />
13 <div className="micro-content">
14 <h5 className="bold">Dabza</h5>
15 <p>Dabibi tere</p>
16 </div>
17 </div>
18 <div className="microcard-section">
19 <img className="img-profile" src={profile} alt="profile" />
20 <div className="micro-content">
21 <h5 className="bold">dabza</h5>
22 <p>Dabibi tere</p>
23 </div>
24 </div>
25 <div className="microcard-section ">
26 <img className="img-profile" src={profile3} alt="profile" />
27 <div className="micro-content">
28 <h5 className="bold">Perry</h5>
29 <p>Dabibi tere</p>
30 </div>
31 </div>
32 <div className="microcard-section">
33 <img className="img-profile" src={profile4} alt="profile" />
34 <div className="micro-content">
35 <h5 className="bold">Mercy</h5>
36 <p>Dabibi tere</p>
37 </div>
38 </div>
39 <div>
40 <h4 className="next">See all</h4>
41 </div>
42 </div>
43 );
44}
45
46export default MicroCard;

Embed the MicroCard component in the followers as seen below

1<div className="followers-banner">
2 ...
3 <h5 className="suggest-account">Suggested accounts</h5>
4 <MicroCard />
5 <div className="line"></div>
6 <div className="followers-accounts">
7 <h3>Following accounts</h3>
8 <h4>Accounts you follow will appear here</h4>
9 </div>
10 </div>

Card Section

You will also have to create a card component.

  • This component will contain the videos uploaded and videos from other Tiktoker. -The heading of the contents.
  • The like icon, comment icon, and share icon.
  • Create a div with a class "card-container" that houses the contents in the component.
  • create another div with the class name "center-div"
  • This div will have the image and the name of the Tiktoker.
  • You will also have the videos element in a div.
  • Create another inside the main div that will contain all the icons as seen below.
1import React, { useContext, useState } from "react";
2import classes from "./card.module.css";
3import profile from "../../images/goddy.jpg";
4import share from "../../images/share.svg";
5import heart from "../../images/heart.svg";
6import comment from "../../images/comment.svg";
7import { Cloudinary } from "@cloudinary/url-gen";
8import { AdvancedVideo } from "@cloudinary/react";
9import {
10 reverse,
11 blur,
12 deshake,
13 noise,
14 loop,
15 boomerang,
16} from "@cloudinary/url-gen/actions/effect";
17
18import { VideContext } from "../../App";
19function Card() {
20 const { uploads } = useContext(VideContext);
21 console.log(uploads);
22 const cld = new Cloudinary({
23 cloud: {
24 cloudName: "demo",
25 },
26 });
27
28 return (
29 <div className={classes.cardContainer}>
30 {uploads.map((item) => (
31 <div key={item} className={classes.center_div}>
32 <div className={classes.break} />
33 <div className={classes.card_section}>
34 <div className={classes.user_info}>
35 <img
36 className={classes.user_profile}
37 src={profile}
38 alt="profile"
39 />
40 <div>
41 <div className={classes.section_profile}>
42 <div>
43 <h3 className={classes.bold}>Emmzy</h3>
44 <p className={classes.username}>Emma Mbonu</p>
45 </div>
46 {/* <div>
47 <Minicard />
48 </div> */}
49 </div>
50
51 <h5>{item.caption}</h5>
52 </div>
53 </div>
54 </div>
55 <div className={classes.video_socials}>
56 <div
57
58 >
59 <AdvancedVideo
60 className={classes.video}
61 cldVid={
62 cld
63 .video(item.videoUrl)
64 .effect(blur().strength(item.transformState.blur))
65 .effect(
66 deshake().shakeStrength(item.transformState.deshake)
67 )
68 .effect(boomerang(item.transformState.boomerang))
69
70
71 .effect(loop(item.transformState.loop))
72
73 }
74 controls
75 />
76
77 </div>
78
79 <div className={classes.socials}>
80 <div className={classes.icon}>
81 <img src={heart} alt="heart" />
82 </div>
83
84 <div className={classes.social_tag}>5.1m</div>
85 <div className={classes.icon}>
86 <img src={comment} alt="comment" />
87 </div>
88
89 <div className={classes.social_tag}>20.3k</div>
90 <div className={classes.icon}>
91 <img src={share} alt="share" />
92 </div>
93
94 <div className={classes.social_tag}>4000</div>
95 </div>
96 </div>
97 </div>
98 ))}
99 <div></div>
100 </div>
101 );
102}
103
104export default Card;

Below is the CSS for the card component.

1.break {
2 height: 10px;
3}
4.cardContainer {
5 display: flex;
6 justify-content: center;
7 flex-direction: column;
8 margin: 50px 600px auto;
9 border-bottom: 1px solid rgb(227, 227, 228) !important;
10 width: 100%;
11 max-width: 850px;
12 height: calc(100vh-90px);
13}
14
15.card_section {
16 display: flex;
17 flex-direction: row;
18 margin: 0;
19}
20.section_profile {
21 display: flex;
22 line-height: 5px;
23}
24/* .section_content {
25 display: flex;
26 flex-direction: column;
27} */
28.icon {
29 background-color: #f1f1f2;
30 border-radius: 50%;
31 height: 30px;
32 width: 30px;
33 /* padding: 5px 5px; */
34 text-align: center;
35}
36.icon img {
37 width: 18px;
38 margin-top: 5px;
39}
40.bold {
41 font-weight: 600;
42}
43.user_profile {
44 width: 70px;
45 height: 70px;
46 background-color: red;
47 border-radius: 50%;
48 margin-right: 15px;
49
50 object-fit: cover;
51}
52
53.user_info {
54 display: flex;
55 flex-direction: row;
56 width: 412px;
57}
58.username {
59 margin-left: 70px;
60 margin-top: -26px;
61}
62
63.follow_button {
64 border: 1px solid #fe2c55;
65 border-radius: 3px;
66 width: 88px;
67 height: 16px;
68 color: #fe2c55;
69 font-weight: 600;
70 padding: 2px 0 3px;
71 text-align: center;
72 font-size: 14px;
73}
74
75.followed_button {
76 border: 1px solid rgb(227, 227, 228);
77 border-radius: 3px;
78 width: 88px;
79 height: 16px;
80 font-weight: 600;
81 padding: 2px 0 3px;
82 text-align: center;
83 font-size: 14px;
84}
85.social_tag {
86 margin: 0 20px 0 10px;
87 font-size: 15px;
88}
89.video_banner {
90 border: 1px solid red;
91 background-color: green;
92 /* snap begins */
93 width: 100%;
94 height: 100%;
95 position: relative;
96 scroll-snap-align: start;
97 /* snap ends */
98}
99.video__player {
100 width: 100%;
101 height: 100%;
102 object-fit: fill;
103}
104
105.video {
106 width: 320px;
107 /* height: 480px; */
108 border-radius: 15px;
109 margin-left: 70px;
110}
111.socials {
112 margin-left: 12px;
113 margin-top: 110px;
114 gap: 10px;
115 display: flex;
116 flex-direction: column;
117}
118.video_socials {
119 display: flex;
120}
121
122@media only screen and (max-width: 850px) {
123 .cardContainer {
124 display: flex;
125 justify-content: center;
126 flex-direction: column;
127 margin: 50px 0px auto 100px;
128 border-bottom: 1px solid rgb(227, 227, 228) !important;
129 width: 100%;
130 max-width: 850px;
131 height: calc(100vh-90px);
132 }
133 .socials {
134 margin-left: 10px;
135 margin-top: 10px;
136 gap: 8px;
137 display: flex;
138 flex-direction: column;
139 }
140 .social_tag {
141 margin: 0 20px 0 10px;
142 font-size: 13px;
143 }
144 .content_card {
145 margin-top: 150px;
146 border-bottom: 1px solid rgb(227, 227, 228) !important;
147 width: 300px !important;
148 height: 450px !important;
149 }
150 .icon {
151 background-color: #f1f1f2;
152 border-radius: 50%;
153 width: 30px;
154 height: 30px;
155 /* padding: 8px 8px; */
156 text-align: center;
157 }
158 .icon img {
159 width: 16px;
160 }
161 .video {
162 max-width: 200px;
163 /* height: 300px; */
164 border-radius: 15px;
165 margin-left: 13px;
166 }
167}

You will create the minicard component that you will use will contain the follow text. This will be aligned at the end of the card.Here is the minicard component.

1import React from "react";
2import profile2 from "../../images/goddy.jpg";
3import "./minicard.css";
4
5function Minicard() {
6 return (
7 <div className="section-mini minicard">
8 <h5>Follow</h5>
9 </div>
10 );
11}
12
13export default Minicard;

Put the minicard component in the card component as shown below.

1...
2 <div className="suggested-box">
3 <div className="suggested">
4 <Minicard />
5 </div>
6 </div>
7...

To bring all these components together create a component called Home

  • Call the header component
  • The followers component
  • The micro card components. This will look like this.
1import React from "react";
2import Card from "../card/Card";
3import Followers from "../followers/Followers";
4import Header from "./../header/Header";
5import classes from "./home.module.css";
6
7function Home() {
8 return (
9 <div className={classes.home_banner}>
10 <Header />
11 <Followers />
12 <Card />
13 </div>
14 );
15}
16
17export default Home;

Below is the CSS for the card component.

1hr {
2 display: block;
3 height: 1px;
4 border: 0;
5 border-top: 1px solid rgb(227, 227, 228);
6 width: 50px;
7}
8.home_banner {
9 width: 100%;
10 height: 100vh;
11 overflow-y: auto;
12}

The home page will look like this:

Image description

You have been able to create the Home page.

  • You will create the login and Register component.
  • Create a login component just as you did for the other components.
  • Give it a div that will serve as the container element.
  • This is what the login page looks like.

Image description To achieve this you will create a div by creating the following elements in the div as seen below.

1import React, { useState } from "react";
2import "./login.css";
3
4import user from "../../images/user.svg";
5import facebook from "../../images/facebook.svg";
6import twitter from "../../images/twitter.svg";
7import instagram from "../../images/instagram.svg";
8import google from "../../images/google.svg";
9import apple from "../../images/apple.svg";
10
11const Login = () => {
12 return (
13 <div className="base">
14 <div className="container">
15 <h3>Log in to TikTok</h3>
16 <p>
17 {" "}
18 Manage your account,check notifications,comment on videos, and more .
19 </p>
20 <div className="btnClass">
21 <button className="btn-social">
22 <span className="btn__icon">
23 <img src={user} alt="logo" />
24 </span>
25 <span className="btn__text"> Use phone/email/username</span>
26 </button>
27 <button className="btn-social">
28 <span className="btn__icon">
29 {" "}
30 <img src={facebook} alt="logo" />
31 </span>
32 <span className="btn__text">Continue with Facebook</span>
33 </button>
34 <button className="btn-social">
35 <span className="btn__icon">
36 <img src={google} alt="logo" />
37 </span>
38 <span className="btn__text"> Continue with Google</span>
39 </button>
40 <button className="btn-social">
41 <span className="btn__icon">
42 {" "}
43 <img src={twitter} alt="logo" />
44 </span>
45 <span className="btn__text"> Continue with Twitter</span>
46 </button>
47 <button className="btn-social">
48 <span className="btn__icon">
49 {" "}
50 <img src={apple} alt="logo" />
51 </span>
52 <span className="btn__text"> Continue with Apple</span>
53 </button>
54 <button className="btn-social">
55 <span className="btn__icon">
56 <img src={instagram} alt="logo" />
57 </span>
58 <span className="btn__text">Continue with Instagram</span>
59 </button>
60 </div>
61 <footer>
62 {" "}
63 <p>
64 Dont have an account? <a href="d">Sign up</a>
65 </p>
66 </footer>
67 </div>
68 </div>
69 );
70};
71
72export default Login;

Do the same for the Register page, this is what the Register page will look like.

Image description Now let’s create the register component and it will look as seen below.

1import React from "react";
2import "./register.css";
3import user from "../../images/user.svg";
4import facebook from "../../images/facebook.svg";
5import twitter from "../../images/twitter.svg";
6import instagram from "../../images/instagram.svg";
7import google from "../../images/google.svg";
8import apple from "../../images/apple.svg";
9
10const Register = () => {
11 return (
12 <div className="base">
13 <div className="container">
14 <h3>Sign up for TikTok</h3>
15 <p>
16 {" "}
17 Create a profile,follow other accounts,make your own videos, and more
18 .
19 </p>
20 <div className="btnClass">
21 <button className="btn-social">
22 <span className="btn__icon">
23 <img src={user} alt="logo" />
24 </span>
25 <span className="btn__text"> Use phone or email</span>
26 </button>
27 <button className="btn-social">
28 <span className="btn__icon">
29 {" "}
30 <img src={facebook} alt="logo" />
31 </span>
32 <span className="btn__text">Continue with Facebook</span>
33 </button>
34 <button className="btn-social">
35 <span className="btn__icon">
36 <img src={google} alt="logo" />
37 </span>
38 <span className="btn__text"> Continue with Google</span>
39 </button>
40 <button className="btn-social">
41 <span className="btn__icon">
42 {" "}
43 <img src={twitter} alt="logo" />
44 </span>
45 <span className="btn__text"> Continue with Twitter</span>
46 </button>
47 </div>
48 <div className="terms-text">
49 {" "}
50 <p>
51 By continuing you agree to TikTok's <a href="">Terms of Service</a>{" "}
52 and confirm that you have read TikTok's{" "}
53 <a href="">Privacy Policy</a>
54 </p>
55 </div>
56 <div className="footer-text">
57 <p>
58 Dont have an account? <a href="">Log in</a>
59 </p>
60 </div>
61 </div>
62 </div>
63 );
64};
65
66export default Register;

For the profile page, you will do the same thing you did for the Home page, the only difference is the profile header. Instead of using the header component use the profile header, the follower components, the micro card component, and then the card components. Next, you are going to create the upload page. To this create a component called upload and create a container div called….. this div will contain two divs that you will flex one part of the div will contain the upload input file, the other part will contain the input contents as seen below. To achieve this gives the first div a class name of the first wrapper and the second div a class name of the second wrapper as seen below. Create a component called ‘UploadPage’ this will have the profile header and the upload component just as you did for the Home component this is how it will look like.

You will be creating an extra component called the 'Upload'. This component will be used to do all your video effects before you will upload them to Tiktok. Install Cloudinary using the command below.

1npm i @cloudinary/url-gen @cloudinary/react

For you to be able to upload your video from your application to Cloudinary using react, you will have to create a function called handleSubmit that fetches the upload API using the post method. This API bears the cloud name. To get your cloud name, log in to your Cloudinary account, click on the dashboard at top of your screen, and copy the cloud name.

Image description You will need to append your upload presets name/code to enable you to upload any media content to Cloudinary. To get your upload presets name/code, on your Cloudinary account, click on settings, click on upload and scroll down till you see the upload presets. Click on mode to change it from signed to unsigned, and copy the name.

Image description Go to the upload component and create a function called handleSubmit.

  • In the handleSubmit function, create a function called formData and assign a new formData to it.
  • Create a state called file and append it to the formData.
  • Create a function called handle event change, create a function in this function called to read, and assign an array of files to it. Call the files state.
  • The body will be the formData.
  • The response is a JSON return.
  • To return our video after upload, create a state called VideoState and pass the response to it. Also, pass the public_id to the response.

Below is the upload component codebase.

1import React, { useContext, useState } from "react";
2import classes from "./upload.module.css";
3import upload from "../../images/upload.svg";
4import { AdvancedVideo } from "@cloudinary/react";
5import {
6 reverse,
7 accelerate,
8 blur,
9 deshake,
10 noise,
11} from "@cloudinary/url-gen/actions/effect";
12import { Cloudinary } from "@cloudinary/url-gen";
13import { VideContext } from "../../App";
14import { useNavigate } from "react-router-dom";
15
16function handleErrors(response) {
17 if (!response.ok) {
18 throw Error(response.statusText);
19 }
20 return response;
21}
22function Upload() {
23 const router = useNavigate();
24 const [file, setFile] = useState(null);
25 const [caption, setCaption] = useState("");
26 const [isPost, setIsPost] = useState(false);
27 const [videoState, setVideoState] = useState("");
28 const [transformState, setTransformState] = useState({
29 blur: 500,
30 deshake: 32,
31 noise: 50,
32 loop: "34",
33 reverse: "backwards",
34 boomerang: "5.0",
35 borders: "solid 5 red",
36 by3dLut: "iwltbap_aspen.3dl",
37 });
38
39 const [cldCloudName, setCldCloudName] = useState("");
40
41 const [loading, setLoading] = useState(false);
42 const [preset, setPreset] = useState("");
43
44 const handleCloudName = (e) => {
45 setCldCloudName(e.target.value);
46 };
47 const handlePresetName = (e) => {
48 setPreset(e.target.value);
49 };
50
51 const onChange = (e) => {
52 setTransformState({
53 ...transformState,
54 [e.target.name]: e.target.value,
55 });
56 };
57
58 const cld = new Cloudinary({
59 cloud: {
60 cloudName: "pueneh",
61 },
62 });
63
64 const handleEventChange = (e) => {
65 const read = e.target.files[0];
66 setFile(read);
67 };
68 const handleSubmit = () => {
69 const formData = new FormData();
70 formData.append("file", file);
71 formData.append("upload_preset", preset);
72 setLoading(true);
73 fetch(`https://api.cloudinary.com/v1_1/${cldCloudName}/upload`, {
74 method: "POST",
75 body: formData,
76 })
77 .then((res) => res.json())
78 // .then((res) => console.log(res))
79 .then((res) => {
80 console.log(res);
81 setVideoState(res.public_id);
82 setTransformState((prev) => ({
83 ...prev,
84 noise: res.noise,
85 blur: res.blur,
86 }));
87
88 setLoading(false);
89 setIsPost(true);
90 })
91 .then(handleErrors);
92 };
93 const { onHandleUpload } = useContext(VideContext);
94
95 const handleUpload = () => {
96 const data = {
97 caption,
98 videoUrl: videoState,
99 transformState,
100 };
101 onHandleUpload(data);
102 setIsPost(false);
103 router("/");
104 };
105
106 return (
107 <div className={`${classes.upload_banner} `}>
108 <div className={classes.upload_content}>
109 <div className={classes.first_wrapper}>
110 <div className={classes.first_wrapper__content}>
111 <h3>Upload video</h3>
112 <h4>Post a video to your account</h4>
113 </div>
114
115 <div className={classes.zone}>
116 <div className={classes.inner__content}>
117 <img src={upload} alt="logo" />
118 <div>
119 <h4>Select video to upload</h4>
120 </div>
121 <span>Or drag and drop a file</span>
122 <p>MP4 or WebM</p>
123 <span>
124 720x1280 resolution or higher up to 180 seconds Less than 1GB
125 </span>
126 <div className={classes.selectFile}>
127 <label for="file">Select file</label>
128 <input
129 className={classes.input_text}
130 type="file"
131 name="files[]"
132 id="file"
133 onChange={handleEventChange}
134 />
135 </div>
136 </div>
137 </div>
138 <div>
139 {loading && <p>Loading...</p>}
140 {videoState ? (
141 <AdvancedVideo
142 // src={}
143 cldVid={cld
144 .video(videoState)
145 .effect(
146 noise(transformState.noise)
147
148 )
149 .effect(blur(transformState.blur))
150 .effect(deshake(transformState.deshake))}
151 controls
152 />
153 ) : (
154 <div></div>
155 )}
156 </div>
157 </div>
158 <div className={classes.second_wrapper}>
159 <label htmlFor="">Caption</label>
160
161 <input
162 className={classes.caption}
163 type="text"
164 onChange={({ target }) => setCaption(target.value)}
165 value={caption}
166 name=""
167 placeholder="@ #"
168 />
169 <div className={classes.input_box}>
170 <label htmlFor="">
171 Cloud Name:
172 <input
173 onChange={handleCloudName}
174 type="text"
175 value={cldCloudName}
176 name="cloudname"
177 placeholder="Enter the cloud name here"
178 />
179 </label>
180
181 <label htmlFor="">
182 Upload Preset:{" "}
183 <input
184 onChange={handlePresetName}
185 type="text"
186 value={preset}
187 name="preset"
188 placeholder="Enter the upload presets here"
189 />
190 </label>
191 </div>
192
193 <div>
194 <div className={classes.first_effect}>
195 <div>
196 <label htmlFor="">
197 Blur:
198 <input
199 type="range"
200 name="blur"
201 className={classes.loop}
202 min="0"
203 max="2000"
204 onChange={onChange}
205 value={transformState.blur}
206 />
207 </label>
208 </div>
209 <div>
210 <label htmlFor="">
211 Virtual Noise:
212 <input
213 type="range"
214 name="noise"
215 className={classes.loop}
216 min="0"
217 max="2000"
218 onChange={onChange}
219 value={transformState.noise}
220 />
221 </label>
222 </div>
223 <div>
224 <label htmlFor="">
225 Deshake:{" "}
226 <input
227 type="range"
228 name="deshake"
229 className={classes.loop}
230 min="0"
231 max="64"
232 onChange={onChange}
233 value={transformState.deshake}
234 />
235 </label>
236 </div>
237 <div className={classes.visual}>
238 <label htmlFor="">
239 Loop:{" "}
240 <input
241 type="range"
242 name="loop"
243 className={classes.loop}
244 min="1"
245 max="10"
246 onChange={onChange}
247 value={transformState.loop}
248 />
249 </label>
250 </div>
251
252 </div>
253 <div className={classes.second_effect}>
254 <div>
255 <div className={classes.boomerang}>
256 <label htmlFor="">Boomerang:</label>
257 <select
258 name="startOffset"
259 id="startOffset"
260 className={classes.select_effect}>
261 <option value="">startOffset</option>
262 <option value="">1.0</option>
263 <option value="">2.o</option>
264 <option value="">3.0</option>
265 <option value="">4.0</option>
266 <option value="">5.0</option>
267 </select>
268 <select
269 name=" endOffSet"
270 id=" endOffSet"
271 className={classes.select_effect}>
272 <option value=" endOffSet"> endOffSet</option>
273 <option value="">1.0</option>
274 <option value="">2.o</option>
275 <option value="">3.0</option>
276 <option value="">4.0</option>
277 <option value="">5.0</option>
278 </select>
279 </div>
280 </div>
281 <div>
282 <button className={classes.btn_lut}>Lut</button>
283 </div>
284 <div>
285 <div className={classes.borders}>
286 <label htmlFor="">Borders:</label>
287 <select
288 name="width"
289 id="width"
290 className={classes.select_effect}>
291 <option value="">1</option>
292 <option value="">2</option>
293 <option value="">3</option>
294 <option value="">4</option>
295 <option value="">5</option>
296 </select>
297 <select
298 name="color"
299 id="color"
300 className={classes.select_effect}>
301 <option value="">red</option>
302 <option value="">green</option>
303 <option value="">blue</option>
304 <option value="">black</option>
305 <option value="">yellow</option>
306 </select>
307 </div>
308 </div>
309 </div>
310 <div>
311 {!isPost ? (
312 <button
313 className={classes.btn_submit}
314 onClick={handleSubmit}
315 disabled={(!cldCloudName, !preset)}>
316 Upload
317 </button>
318 ) : (
319 <button className={classes.btn_submit} onClick={handleUpload}>
320 post
321 </button>
322 )}
323 </div>
324 </div>
325
326 </div>
327 </div>
328 </div>
329 );
330}
331
332export default Upload;

Below is the CSS for the followers component.

1.upload_banner {
2 background-color: #f8f8f8;
3 width: 100%;
4 height: 100%;
5 overflow: hidden;
6}
7.upload_content {
8 display: flex;
9 justify-content: space-around;
10 max-width: 80%;
11 height: 100vh;
12 align-items: flex-start;
13 padding: 30px 30px;
14 margin: 30px auto 100px auto;
15 background: #ffffff;
16 border-radius: 10px;
17}
18.first_wrapper {
19 /* flex: 1; */
20 height: auto;
21 width: 100%;
22 max-width: 40%;
23 margin: 0 auto;
24 position: relative;
25 z-index: 2;
26}
27.second_wrapper {
28 margin: 80px auto auto auto;
29 width: 100%;
30 overflow: hidden;
31 line-height: 40px;
32 display: flex;
33 flex-direction: column;
34 justify-content: center;
35}
36.caption {
37 padding: 20px;
38 border: 1px solid #ccc;
39 border-radius: 5px;
40 outline: none;
41}
42.caption::placeholder {
43 text-align: right;
44 color: #000;
45 font-weight: bold;
46 font-size: 2vmin;
47}
48
49#cover {
50 padding: 10px;
51 border: 1px solid #ccc;
52 border-radius: 3px;
53 outline: none;
54 resize: none;
55}
56.form_content {
57 width: 300px !important;
58 padding: 10px;
59 border-radius: 5px;
60 border: 1px solid #ccc;
61 font-size: 2.5vmin;
62}
63.zone {
64 display: flex;
65 background: #fff;
66 width: 300px;
67 padding: 10px 10px;
68 border: 2px dashed lightgrey;
69 border-radius: 5px;
70 color: #000;
71 justify-content: center;
72 align-items: center;
73 flex-direction: column;
74 margin: auto;
75 height: 450px;
76 transition: all 0.3s ease-out;
77}
78.first_wrapper__content {
79 margin: 20px 0 30px 55px;
80}
81.inner__content {
82 justify-content: center;
83 align-items: center;
84 display: flex;
85 justify-content: center;
86 margin-top: 50px;
87 flex-direction: column;
88}
89
90.selectFile {
91 height: 200px;
92 margin: 20px auto;
93 width: 200px;
94}
95
96.selectFile label,
97.input_text {
98 cursor: pointer;
99 display: block;
100 left: 0;
101
102 top: 0;
103 width: 100%;
104 border-radius: 5px;
105}
106.selectFile label {
107 background: #fe2c55;
108 color: #fff;
109 display: inline-block;
110 font-size: 3vmin;
111 line-height: 50px;
112 padding: 0;
113 text-align: center;
114 white-space: nowrap;
115 text-transform: uppercase;
116 font-weight: 400;
117 box-shadow: 0 1px 1px gray;
118}
119.selectFile .input_text[type="file"] {
120 opacity: 0;
121}
122
123.comment_banner {
124 display: block;
125 position: relative;
126 padding-left: 35px;
127 margin-bottom: 12px;
128 cursor: pointer;
129 font-size: 2vmin;
130 user-select: none;
131}
132
133.comment_banner input {
134 position: absolute;
135 opacity: 0;
136 cursor: pointer;
137 height: 0;
138 width: 0;
139}
140
141.comment {
142 position: absolute;
143 top: 0;
144 left: 0;
145 height: 25px;
146 width: 25px;
147 border: 1px solid #fe2c55;
148 border-radius: 4px;
149}
150
151.comment_banner:hover input ~ .comment {
152 border: 1px solid #fe2c55;
153}
154
155.comment_banner input:checked ~ .comment {
156 background-color: #fe2c55;
157}
158
159.comment:after {
160 content: "";
161 position: absolute;
162 display: none;
163}
164
165.comment_banner input:checked ~ .comment:after {
166 display: block;
167}
168
169.comment_banner .comment:after {
170 left: 9px;
171 top: 5px;
172 width: 5px;
173 height: 10px;
174 border: solid white;
175 border-width: 0 3px 3px 0;
176 transform: rotate(45deg);
177}
178.second_wrapper label {
179 text-align: left;
180 font-weight: bold;
181 line-height: 60px;
182}
183.checked_input {
184 display: flex;
185 align-items: center;
186 gap: 12px;
187}
188.switch {
189 position: relative;
190 display: inline-block;
191 width: 60px;
192 height: 34px;
193}
194
195.switch input {
196 opacity: 0;
197 width: 0;
198 height: 0;
199}
200
201.slider {
202 position: absolute;
203 cursor: pointer;
204 top: 0;
205 left: 0;
206 right: 0;
207 bottom: 0;
208 background-color: #ccc;
209 transition: 0.4s;
210}
211
212.slider:before {
213 position: absolute;
214 content: "";
215 height: 26px;
216 width: 26px;
217 left: 4px;
218 bottom: 4px;
219 background-color: white;
220 transition: 0.4s;
221}
222
223input:checked + .slider {
224 background-color: #0be09b;
225}
226/*
227input:focus + .slider {
228 box-shadow: 0 0 1px #0be09b;
229} */
230
231input:checked + .slider:before {
232 transform: translateX(26px);
233}
234
235.slider.round {
236 border-radius: 34px;
237}
238
239.slider.round:before {
240 border-radius: 50%;
241}
242.first_effect,
243.second_effect {
244 display: flex;
245 justify-content: space-between;
246}
247.input_box {
248 display: flex;
249 justify-content: space-between;
250}
251.input_box label {
252 font-size: 2vmin;
253}
254.input_box input[type="text"] {
255 padding: 10px;
256 width: 200px;
257 margin-top: 20px;
258 margin-left: 8px;
259 border-radius: 4px;
260}
261.second_effect {
262 align-items: center;
263 margin-bottom: 20px;
264}
265.boomerang,
266.borders {
267 font-size: 2vmin;
268}
269select {
270 padding: 8px 8px;
271 border: 1px solid #000;
272 cursor: pointer;
273 user-select: none;
274 border-radius: 5px;
275 margin-left: 10px;
276}
277.btn_lut {
278 padding: 5px 5px;
279 width: 100px;
280 border-radius: 5px;
281 font-size: 2vmin;
282 background: transparent;
283 cursor: pointer;
284}
285.btn_submit {
286 padding: 7px 7px;
287 width: 200px;
288 border-radius: 5px;
289 display: block;
290 margin: auto;
291 font-size: 3vmin;
292 cursor: pointer;
293}
294@media only screen and (max-width: 1004px) {
295 .upload_content {
296 display: flex;
297 flex-direction: column;
298
299 max-width: 80%;
300 height: auto;
301 align-items: flex-start;
302 padding: 30px 30px;
303 margin: 30px auto 100px auto;
304 background: #ffffff;
305 border-radius: 10px;
306 }
307 .first_wrapper__content {
308 margin: 0px;
309 }
310}
311@media only screen and (max-width: 766px) {
312 .upload_content {
313 display: flex;
314 flex-direction: column;
315
316 max-width: 80%;
317 height: auto;
318 align-items: flex-start;
319 padding: 30px 30px;
320 margin: 30px auto 100px auto;
321 background: #ffffff;
322 border-radius: 10px;
323 }
324 .loop {
325 width: 100px;
326 }
327
328 select {
329 padding: 5px 5px;
330 border: 1px solid #000;
331 cursor: pointer;
332 width: 40px;
333 user-select: none;
334 border-radius: 5px;
335 margin-left: 10px;
336 }
337 .btn_lut {
338 padding: 5px 5px;
339 width: 50px;
340 border-radius: 5px;
341 font-size: 2vmin;
342 background: transparent;
343 cursor: pointer;
344 }
345}
346@media only screen and (max-width: 590px) {
347 .upload_content {
348 display: flex;
349 flex-direction: column;
350
351 max-width: 80%;
352 height: auto;
353 align-items: flex-start;
354 padding: 30px 30px;
355 margin: 30px auto 100px auto;
356 background: #ffffff;
357 border-radius: 10px;
358 }
359 .input_box {
360 flex-direction: column;
361 }
362
363 .input_box label {
364 font-size: 16px;
365 }
366 .zone {
367 display: flex;
368 background: #fff;
369 width: 250px;
370 padding: 10px 10px;
371 border: 2px dashed lightgrey;
372 border-radius: 5px;
373 color: #000;
374 justify-content: center;
375 align-items: center;
376 flex-direction: column;
377 margin-left: -70px;
378 height: 450px;
379 transition: all 0.3s ease-out;
380 }
381 .first_effect,
382 .second_effect {
383 flex-direction: column;
384 }
385 .second_effect label {
386 font-size: 16px;
387 margin-left: -5px;
388 }
389
390 .loop {
391 width: 150px;
392 }
393
394 select {
395 padding: 5px 5px;
396 border: 1px solid #000;
397 cursor: pointer;
398 width: 80px;
399 user-select: none;
400 border-radius: 5px;
401 /* margin-left: 10px; */
402 }
403 .btn_lut {
404 padding: 5px 5px;
405 width: 50px;
406 border-radius: 5px;
407 font-size: 3vmin;
408 background: transparent;
409 cursor: pointer;
410 }
411}

You can now test your application. To do that, you will have to upload a video. Click on the login button on the home page header, which will take you to the profile page. On the profile page, click on the upload icon. The Cloudinary effects are visible on the upload page as seen below.

Image description Type in a caption, type in your cloud name and upload precept details. Select a video you want to upload and apply an effect to it. Click to upload. When it is done uploading, the button text will change to "post". On the click of the button, you will be redirected to the homepage. Below is what your homepage will look like.

Image description This is what the mobile view will look like.

Image description

View full source code on Github and sandbox: Github: https://github.com/wise4rmgod/tiktokreact Sandbox: https://codesandbox.io/s/mystifying-montalcini-rnjd7z

Conclusion:

Cloudinary has a lot of amazing effects you can add to your video apart from those you made use of. Go to Cloudinary Effects and Enhancements to see all the effects and add them to your TikTok or any application you are building using the examples in this article. Make sure you have Cloudinary installed and integrated into your application.

Nwokocha Wisdom Maduabuchi

Senior Technical Writer

A software engineer with considerable experience in native Android, Blockchain Technology, Smart Contracts, Etheruem, Developer Advocacy, UIUX Design, Building a static site using Eleventy, MKDocs, Hugo, Jekyll, and Docsify, DITA, Gitbook, AsciiDoc, Readme, confluence, Technical Content Manager, API Documentation, Documentation Engineer, Community building/management, Firebase.
• Developed apps from the ground up and published them successfully on Google Play
• Built open-source library and an open-source contributor/ maintainer at Gnome, Firebase, Kotlin
• Experience modern architecture, and development practices
• Write clean and maintainable codes