├── styles ├── Home.module.css └── globals.css ├── .eslintrc.json ├── public ├── favicon.ico ├── google.png ├── tiktok.png ├── upload.png └── vercel.svg ├── postcss.config.js ├── pages ├── index.js ├── _app.js ├── api │ ├── auth │ │ └── [...nextauth].js │ └── database │ │ └── [...database].js ├── login.jsx └── home.jsx ├── components ├── Landing.jsx ├── LandingHeader.jsx ├── HomeNav.jsx ├── ImageGrid.jsx ├── Video.jsx ├── UploadForm.jsx └── Navbar.jsx ├── next.config.js ├── .gitignore ├── package.json ├── tailwind.config.js └── README.md /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prajyu/TikTok_Clone/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prajyu/TikTok_Clone/HEAD/public/google.png -------------------------------------------------------------------------------- /public/tiktok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prajyu/TikTok_Clone/HEAD/public/tiktok.png -------------------------------------------------------------------------------- /public/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prajyu/TikTok_Clone/HEAD/public/upload.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /pages/index.js: -------------------------------------------------------------------------------- 1 | import Landing from "../components/Landing"; 2 | import Head from "next/head"; 3 | 4 | export default function Home() { 5 | return ( 6 |
7 | 8 | Tik Tok Clone 9 | 10 | 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /components/Landing.jsx: -------------------------------------------------------------------------------- 1 | import LandingHeader from "./LandingHeader"; 2 | import ImageGrid from "./ImageGrid"; 3 | import NavBar from "./Navbar"; 4 | 5 | const Landing = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default Landing; 16 | -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | import { SessionProvider } from "next-auth/react"; 3 | import NavBar from "../components/Navbar"; 4 | 5 | function App({ Component, pageProps, session }) { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ["lh3.googleusercontent.com", "picsum.photos"], 5 | }, 6 | reactStrictMode: true, 7 | swcMinify: true, 8 | env: { 9 | GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, 10 | GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, 11 | }, 12 | }; 13 | 14 | module.exports = nextConfig; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tiktokclone", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "mongodb": "^4.8.1", 13 | "next": "12.2.3", 14 | "next-auth": "^4.10.2", 15 | "react": "18.2.0", 16 | "react-dom": "18.2.0", 17 | "react-icons": "^4.4.0", 18 | "react-intersection-observer": "^9.4.0", 19 | "sharp": "^0.30.7", 20 | "url": "^0.11.0" 21 | }, 22 | "devDependencies": { 23 | "autoprefixer": "^10.4.7", 24 | "eslint": "8.20.0", 25 | "eslint-config-next": "12.2.3", 26 | "postcss": "^8.4.14", 27 | "tailwindcss": "^3.1.6" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Heebo:wght@700&display=swap"); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | html, 8 | body { 9 | padding: 0; 10 | margin: 0; 11 | font-family: "Heebo", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, 12 | Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 13 | } 14 | 15 | a { 16 | color: inherit; 17 | text-decoration: none; 18 | } 19 | 20 | *::-webkit-scrollbar { 21 | display: none; 22 | } 23 | 24 | * { 25 | box-sizing: border-box; 26 | } 27 | 28 | .like { 29 | filter: invert(27%) sepia(77%) saturate(2890%) hue-rotate(334deg) 30 | brightness(113%) contrast(110%); 31 | } 32 | 33 | .white { 34 | filter: invert(100%) sepia(0%) saturate(7483%) hue-rotate(117deg) 35 | brightness(100%) contrast(102%); 36 | } 37 | 38 | @media (prefers-color-scheme: dark) { 39 | html { 40 | color-scheme: dark; 41 | } 42 | body { 43 | color: white; 44 | background: black; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | 8 | theme: { 9 | extend: { 10 | height: { 11 | px: "1px", 12 | }, 13 | keyframes: { 14 | move: { 15 | "0%": { transform: "translateX(30%)" }, 16 | "100%": { transform: "translate(-80%)" }, 17 | }, 18 | vertical: { 19 | "0%": { transform: "translateY(100%)" }, 20 | "100%": { transform: "translateY(0)" }, 21 | }, 22 | down: { 23 | "0%": { transform: "translateY(0)" }, 24 | "100%": { transform: "translateY(100%)", display: "none" }, 25 | }, 26 | }, 27 | animation: { 28 | move: "move 6s linear infinite", 29 | vertical: "vertical 0.5s ease-in-out", 30 | down: "down 0.5s ease-in-out forwards", 31 | spin: "spin 3s linear infinite", 32 | }, 33 | }, 34 | }, 35 | plugins: [], 36 | }; 37 | -------------------------------------------------------------------------------- /pages/api/auth/[...nextauth].js: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import GoogleProvider from "next-auth/providers/google"; 3 | 4 | export default NextAuth({ 5 | providers: [ 6 | GoogleProvider({ 7 | clientId: process.env.GOOGLE_CLIENT_ID, 8 | clientSecret: process.env.GOOGLE_CLIENT_SECRET, 9 | authorization: { 10 | params: { 11 | prompt: "consent", 12 | access_type: "offline", 13 | response_type: "code", 14 | }, 15 | }, 16 | }), 17 | ], 18 | jwt: { 19 | encryption: true, 20 | }, 21 | 22 | secret: process.env.JWT_SECRET, 23 | theme: { 24 | colorScheme: "dark", // "auto" | "dark" | "light" 25 | brandColor: "#fff", // Hex color code 26 | logo: "/google.png", // Absolute URL to image 27 | buttonText: "#ff0000", // Hex color code 28 | }, 29 | 30 | callbacks: { 31 | session: async ({ session, token }) => { 32 | if (session?.user && token?.sub) { 33 | session.user.id = token.sub; 34 | } 35 | return session; 36 | }, 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /pages/login.jsx: -------------------------------------------------------------------------------- 1 | import { signIn, getSession } from "next-auth/react"; 2 | import NavBar from "../components/Navbar"; 3 | import Head from "next/head"; 4 | 5 | const login = () => { 6 | return ( 7 | <> 8 | 9 | Tik Tok Clone 10 | 11 | 12 |
13 | 14 | Login / Register for Tik Tok Clone 15 | 16 | 17 | Create a profile, follow other accounts,make your own videos, and more 18 | 19 | 20 |
21 | 28 |
29 |
30 | 31 | ); 32 | }; 33 | 34 | export const getServerSideProps = async (context) => { 35 | const session = await getSession(context); 36 | if (session) { 37 | return { 38 | redirect: { 39 | destination: "/home", 40 | }, 41 | }; 42 | } 43 | 44 | return { 45 | props: {}, 46 | }; 47 | }; 48 | 49 | export default login; 50 | -------------------------------------------------------------------------------- /components/LandingHeader.jsx: -------------------------------------------------------------------------------- 1 | import { BsPlay } from "react-icons/bs"; 2 | import { IconContext } from "react-icons"; 3 | import Link from "next/link"; 4 | import Image from "next/image"; 5 | 6 | const LandingHeader = () => { 7 | return ( 8 |
9 |

10 | Make 11 | 12 | 13 | 19 | 20 | 21 | Your 22 | 23 | 24 | 25 | Day 26 |

27 | 28 | Real People. Real Videos 29 | 30 | 31 |
32 | 37 |
38 | 39 |
40 |
41 | Watch Now 42 |
43 | 44 |
45 | ); 46 | }; 47 | 48 | export default LandingHeader; 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TikTok Clone 2 | 3 | ## _Full Stack tiktok clone using nextjs_ 4 | 5 | 6 | [](https://nextjs.org/) 7 | 8 | **TikTok Clone build using next js.** 9 | 10 | [![N|APP](https://raw.githubusercontent.com/prajyu/TikTok_Clone/main/public/tiktok.png)](https://tiktokclone-prajyu.vercel.app/) 11 | Click the icon above to access demo app ☝️ 12 | ## Features 13 | 14 | - Upload videos. 15 | - Google Authentication 16 | - Watch and like them 17 | 18 | ## Tech 19 | 20 | - [Next js](https://nextjs.org/) - The React Framework 21 | for Production 22 | - [Mognodb](https://www.mongodb.com/) - No SQL database 23 | 24 | ## Installation 25 | 26 | TikTok Clone requires [Node.js](https://nodejs.org/) to run. 27 | 28 | prequisites: 29 | 30 | - node.js 31 | - npm 32 | - next.js 33 | - mongodb (driver) 34 | 35 | Create a   `.env`  file 36 | 37 | ``` 38 | GOOGLE_CLIENT_ID=XXXXXXXXXXXXXX // Obtain google client id to make google auth work 39 | GOOGLE_CLIENT_SECRET=XXXXXXXXXXXXXX // Obtain google client secret to make google auth work 40 | NEXTAUTH_URL=http://localhost:3000/ // Change this to your deployed url in production 41 | JWT_SECRET=XXXXXXXXXXXXXX // A random string to use in JWT 42 | ``` 43 | 44 | Install the dependencies and start the server. 45 | 46 | ```sh 47 | git clone https://github.com/prajyu/TikTok-Clone.git 48 | cd TikTok-Clone 49 | npm i 50 | npm run dev 51 | ``` 52 | 53 | For production environments... 54 | 55 | edit the   `.env`   file and add these three enviornment variables 56 | 57 | ``` 58 | USER=XXXXXXXXXXXXXX // Mongo DB credentials 59 | PASSWORD=XXXXXXXXXXXXXX // Mongo DB credentials 60 | DATABASE=XXXXXXXXXXXXXX // Mongo DB credentials 61 | ``` 62 | 63 | ```sh 64 | npm run build 65 | npm start 66 | ``` 67 | 68 | Verify the deployment by navigating to your server address in 69 | your preferred browser. 70 | 71 | ```sh 72 | localhost:3000 73 | ``` 74 | 75 | ## License 76 | 77 | MIT 78 | -------------------------------------------------------------------------------- /components/HomeNav.jsx: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import UploadForm from "./UploadForm"; 3 | import { useRouter } from "next/router"; 4 | 5 | const HomeNav = () => { 6 | const uploadRef = useRef(); 7 | const router = useRouter(); 8 | const handleUpload = async () => { 9 | uploadRef.current.classList.remove("animate-down"); 10 | uploadRef.current.classList.add("animate-vertical"); 11 | uploadRef.current.classList.remove("translate-y-full"); 12 | uploadRef.current.classList.remove("hidden"); 13 | uploadRef.current.classList.add("block"); 14 | }; 15 | 16 | return ( 17 | <> 18 |
19 |
20 |
21 | 32 | 43 | 51 | 52 | 63 | 74 |
75 |
76 |
77 | 78 | 79 | ); 80 | }; 81 | 82 | export default HomeNav; 83 | -------------------------------------------------------------------------------- /components/ImageGrid.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from "react"; 2 | 3 | const ImageGrid = () => { 4 | const [images, setImages] = useState([]); 5 | const gridOneRef = useRef(); 6 | const gridTwoRef = useRef(); 7 | const gridThreeRef = useRef(); 8 | 9 | useEffect(() => { 10 | let autoScroll = async (ref) => { 11 | if (!ref?.current) return false; 12 | let children = ref.current.children; 13 | let random = Math.floor(Math.random() * children.length); 14 | await children[random]?.scrollIntoView({ behavior: "smooth" }); 15 | }; 16 | const intervalId = setInterval(() => { 17 | let refs = [gridOneRef, gridTwoRef, gridThreeRef]; 18 | let randomIndex = Math.floor(Math.random() * refs.length); 19 | let randomRef = refs[randomIndex]; 20 | autoScroll(randomRef); 21 | }, 3000); 22 | 23 | return () => clearInterval(intervalId); 24 | }, []); 25 | 26 | useEffect(() => { 27 | async function fetchImages() { 28 | let page = Math.floor(Math.random() * 20) + 1; 29 | let json = await ( 30 | await fetch(`https://picsum.photos/v2/list?limit=6&&page=${page}`) 31 | ).json(); 32 | json = json.map((e) => e.download_url); 33 | 34 | setImages(json); 35 | } 36 | fetchImages(); 37 | }, []); 38 | return ( 39 |
40 |
41 |
45 | {images.length && 46 | images.map((url, id) => { 47 | return ( 48 | image 54 | ); 55 | })} 56 |
57 |
61 | {images.length && 62 | images.map((url, id) => { 63 | return ( 64 | image 70 | ); 71 | })} 72 |
73 |
77 | {images.length && 78 | images.map((url, id) => { 79 | return ( 80 | image 86 | ); 87 | })} 88 |
89 |
90 |
91 | ); 92 | }; 93 | 94 | export default ImageGrid; 95 | -------------------------------------------------------------------------------- /pages/home.jsx: -------------------------------------------------------------------------------- 1 | import { getSession, useSession } from "next-auth/react"; 2 | import Video from "../components/Video"; 3 | import HomeNav from "../components/HomeNav"; 4 | import Head from "next/head"; 5 | import { useState, useRef, useEffect } from "react"; 6 | 7 | const User = () => { 8 | const { data: session, status } = useSession(); 9 | 10 | const [videos, setVideos] = useState([]); 11 | const [end, setEnd] = useState(false); 12 | 13 | useEffect(() => { 14 | if (!session?.user) return; 15 | let fetchVideos = async () => { 16 | let videoList = await ( 17 | await fetch("/api/database/get", { 18 | method: "post", 19 | headers: { 20 | "content-type": "application/json", 21 | }, 22 | body: JSON.stringify({ 23 | user: session?.user?.email, 24 | }), 25 | }) 26 | ).json(); 27 | if (videoList?.response.length) 28 | setVideos([...videos, ...videoList?.response]); 29 | }; 30 | fetchVideos(); 31 | }, [session, end]); 32 | 33 | const videoContainerRef = useRef(); 34 | 35 | const onScroll = () => { 36 | if (videoContainerRef.current) { 37 | const { scrollTop, scrollHeight, clientHeight, offsetHeight } = 38 | videoContainerRef.current; 39 | let totalContentHeight = Math.round( 40 | scrollTop + offsetHeight + clientHeight 41 | ); 42 | if (totalContentHeight === scrollHeight) { 43 | setEnd(!end); 44 | } 45 | } 46 | }; 47 | 48 | if (status === "authenticated") { 49 | return ( 50 |
51 | 52 | Tik Tok Clone 53 | 54 |
59 | {videos && 60 | videos?.map((e, id) => { 61 | let { 62 | videoUrl, 63 | comments, 64 | imageUrl, 65 | songName, 66 | description, 67 | likes, 68 | user, 69 | _id, 70 | liked, 71 | userImage, 72 | } = e; 73 | 74 | return ( 75 |
91 | 92 | 93 |
94 | ); 95 | } 96 | }; 97 | 98 | export const getServerSideProps = async (context) => { 99 | const session = await getSession(context); 100 | if (!session) { 101 | return { 102 | redirect: { 103 | destination: "/login", 104 | }, 105 | }; 106 | } 107 | 108 | return { 109 | props: { session }, 110 | }; 111 | }; 112 | 113 | export default User; 114 | -------------------------------------------------------------------------------- /pages/api/database/[...database].js: -------------------------------------------------------------------------------- 1 | import { MongoClient, ObjectId } from "mongodb"; 2 | import { URL } from "url"; 3 | import { getToken } from "next-auth/jwt"; 4 | 5 | const dbName = "tiktokClone"; 6 | 7 | const secret = process.env.JWT_SECRET; 8 | 9 | let collection = null; 10 | 11 | let main = async () => { 12 | let url = `mongodb+srv://${process.env.USER}:${process.env.PASSWORD}@videos.xpaadu5.mongodb.net/${process.env.DATABASE}?retryWrites=true&w=majority`; 13 | if (!process.env.USER || !process.env.PASSWORD || !process.env.DATABASE) { 14 | url = "mongodb://localhost:27017"; 15 | const client = new MongoClient(url); 16 | await client.connect(); 17 | const db = client.db(dbName); 18 | collection = db.collection("videos"); 19 | // const deleteResult = await collection.deleteMany({}); 20 | } else { 21 | const client = new MongoClient(url); 22 | let handler = await client.connect(); 23 | let db = handler.db("videos"); 24 | collection = db.collection("videos"); 25 | // const deleteResult = await collection.deleteMany({}); 26 | } 27 | }; 28 | 29 | let createPost = async ({ 30 | videoUrl, 31 | songName, 32 | imageUrl, 33 | description, 34 | user, 35 | userImage, 36 | }) => { 37 | if ( 38 | !verifyUrl(videoUrl) || 39 | !verifyUrl(imageUrl) || 40 | !songName || 41 | !user || 42 | !description 43 | ) 44 | return false; 45 | let schema = { 46 | videoUrl, 47 | songName, 48 | imageUrl, 49 | description, 50 | user, 51 | userImage, 52 | likes: [], 53 | comments: [], 54 | }; 55 | return schema; 56 | }; 57 | 58 | const verifyUrl = (string) => { 59 | try { 60 | new URL(string); 61 | return true; 62 | } catch (err) { 63 | return false; 64 | } 65 | }; 66 | 67 | const insertOne = async (schema) => { 68 | const insertResult = await collection.insertOne(schema); 69 | return insertResult; 70 | }; 71 | 72 | const getThree = async () => { 73 | if (!collection) return false; 74 | let count = await collection.count(); 75 | let findResult = null; 76 | if (count <= 3) { 77 | findResult = await collection.find({}).limit(3).toArray(); 78 | } else { 79 | findResult = await collection 80 | .aggregate([{ $sample: { size: 3 } }]) 81 | .toArray(); 82 | } 83 | return findResult; 84 | }; 85 | 86 | const updateLikes = async (schema, id) => { 87 | let updateResult = await collection.updateOne( 88 | { _id: ObjectId(id) }, 89 | { $set: schema } 90 | ); 91 | return updateResult; 92 | }; 93 | const findVideo = async (videoId) => { 94 | const findResult = await collection 95 | .find({ _id: ObjectId(videoId) }) 96 | .toArray(); 97 | return findResult; 98 | }; 99 | 100 | export default async function handler(req, res) { 101 | const token = await getToken({ req, secret }); 102 | if (!token) return res.status(401).redirect("/login"); 103 | 104 | if (!collection) await main(); 105 | const url = req.query; 106 | const route = url.database[0]; 107 | 108 | if (!route) return res.redirect("/"); 109 | 110 | if (req.method === "POST") { 111 | if (route === "create") { 112 | const { videoUrl, imageUrl, songName, description, user, userImage } = 113 | req.body; 114 | let response = await handleCreate({ 115 | videoUrl, 116 | imageUrl, 117 | songName, 118 | description, 119 | user, 120 | userImage, 121 | }); 122 | if (!response) return res.status(422).json({ error: true }); 123 | res.json({ success: true }); 124 | } 125 | 126 | if (route === "get") { 127 | let response = await handleGet(); 128 | let { user } = req.body; 129 | if (!response || !user) return res.status(422).json({ success: false }); 130 | response = response.map((e) => { 131 | const liked = e.likes.find((j) => j.user === user); 132 | 133 | e.likes = e.likes.length; 134 | e.liked = liked ? true : false; 135 | return e; 136 | }); 137 | // console.log(response); 138 | return res.status(200).json({ response }); 139 | } 140 | 141 | if (route === "like") { 142 | const { user, videoId, liked } = req.body; 143 | if (!user || !videoId) return res.status(422).json({ error: true }); 144 | let response = handleLike({ user, videoId, liked }); 145 | return res.status(200).json({ success: true }); 146 | } 147 | } 148 | } 149 | 150 | let handleCreate = async ({ 151 | videoUrl, 152 | imageUrl, 153 | songName, 154 | description, 155 | user, 156 | userImage, 157 | }) => { 158 | let response = await createPost({ 159 | videoUrl, 160 | imageUrl, 161 | songName, 162 | description, 163 | user, 164 | userImage, 165 | }); 166 | if (!response) return false; 167 | let insertResponse = await insertOne(response); 168 | delete response._id; 169 | return response; 170 | }; 171 | 172 | let handleGet = async () => { 173 | let videoList = await getThree(); 174 | 175 | return videoList; 176 | }; 177 | 178 | let handleLike = async ({ user, videoId, liked }) => { 179 | let video = await findVideo(videoId); 180 | if (!video?.length) return false; 181 | let { likes } = video[0]; 182 | let isLiked = likes?.find((e) => e.user === user); 183 | if (liked && isLiked) { 184 | return true; 185 | } else if (!liked && isLiked) { 186 | let likes = video[0].likes.filter((e) => e.user !== user); 187 | video[0].likes = likes; 188 | let result = await updateLikes(video[0], videoId); 189 | return true; 190 | } else if (liked && !isLiked) { 191 | let newLike = { user }; 192 | video[0].likes = [...likes, newLike]; 193 | let result = await updateLikes(video[0], videoId); 194 | return true; 195 | } else { 196 | return false; 197 | } 198 | }; 199 | -------------------------------------------------------------------------------- /components/Video.jsx: -------------------------------------------------------------------------------- 1 | import { useSession } from "next-auth/react"; 2 | import { useRef, useState, useEffect, useLayoutEffect } from "react"; 3 | import Image from "next/image"; 4 | import { InView } from "react-intersection-observer"; 5 | 6 | const Video = ({ 7 | videoUrl, 8 | imageUrl, 9 | likes, 10 | liked, 11 | comment, 12 | songName, 13 | description, 14 | user, 15 | videoId, 16 | userImage, 17 | }) => { 18 | /*forwardRef( 19 | ({ videoUrl, imageUrl, likeCount, comment, songName }, ref) => { 20 | useImperativeHandle(ref, () => ({ 21 | playVideo: (e) => { 22 | videoRef.current.play(); 23 | }, 24 | pauseVideo: (e) => { 25 | videoRef.current.pause(); 26 | }, 27 | }));*/ 28 | 29 | const { data: session } = useSession(); 30 | 31 | const [like, setLike] = useState(false); 32 | const [likeCount, setLikeCount] = useState(0); 33 | const [inScreen, setInScreen] = useState(false); 34 | 35 | const videoRef = useRef(); 36 | const likeRef = useRef(); 37 | 38 | useEffect(() => { 39 | setLikeCount(likes); 40 | setLike(liked); 41 | videoRef.current.disableRemotePlayback = true; 42 | }, [likes, liked]); 43 | 44 | useEffect(() => { 45 | if (inScreen) { 46 | updateData(); 47 | } 48 | }, [likeCount, inScreen]); 49 | 50 | useEffect(() => { 51 | if (!like) { 52 | likeRef.current.classList.add("white"); 53 | likeRef.current.classList.remove("like"); 54 | } else { 55 | likeRef.current.classList.add("like"); 56 | likeRef.current.classList.remove("white"); 57 | } 58 | }, [like]); 59 | 60 | let updateData = async () => { 61 | if (!session.user || !videoId) return false; 62 | const body = JSON.stringify({ 63 | user: session.user.email, 64 | videoId, 65 | liked: like, 66 | }); 67 | let response = await ( 68 | await fetch("/api/database/like", { 69 | method: "post", 70 | headers: { 71 | "content-type": "application/json", 72 | }, 73 | body, 74 | }) 75 | ).json(); 76 | }; 77 | 78 | let handleVideo = () => { 79 | if (!videoRef.current) return false; 80 | if (videoRef.current.paused) playVideo(); 81 | if (videoRef.current.muted) { 82 | videoRef.current.muted = false; 83 | } else { 84 | videoRef.current.muted = true; 85 | } 86 | }; 87 | 88 | let pauseVideo = () => { 89 | if (!videoRef.current) return false; 90 | videoRef.current.pause(); 91 | }; 92 | 93 | let playVideo = () => { 94 | if (!videoRef.current) return false; 95 | videoRef.current.play(); 96 | }; 97 | 98 | let handleLike = async () => { 99 | setLike(!like); 100 | if (like) { 101 | setLikeCount(likeCount - 1); 102 | } else { 103 | setLikeCount(likeCount + 1); 104 | } 105 | }; 106 | 107 | return ( 108 | { 111 | setInScreen(inView); 112 | if (inView) { 113 | playVideo(); 114 | } else { 115 | pauseVideo(); 116 | } 117 | }} 118 | className="w-screen h-full relative snap-start bg-[#1c1c1c]" 119 | threshold={1} 120 | > 121 | 133 |
134 | 135 | 144 | 145 | 146 | 157 | 158 | 159 | 160 | 174 | 175 | 176 | 177 | 189 | 190 |
191 |
192 | 193 | @{user} 194 | 195 | 196 | {description} 197 | 198 |
199 |
200 | 201 | {songName} 202 | 203 |
204 | 205 | 210 |
211 |
212 |
213 | ); 214 | }; 215 | 216 | export default Video; 217 | -------------------------------------------------------------------------------- /components/UploadForm.jsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useState } from "react"; 2 | import { IoIosCloseCircleOutline } from "react-icons/io"; 3 | import { IconContext } from "react-icons"; 4 | import { useSession } from "next-auth/react"; 5 | 6 | const UploadForm = forwardRef((props, ref) => { 7 | const [video, setVideo] = useState(""); 8 | const [image, setimage] = useState(""); 9 | const [song, setSong] = useState(""); 10 | const [description, setDescription] = useState(""); 11 | const handleClose = () => { 12 | ref.current.classList.add("animate-down"); 13 | ref.current.classList.remove("animate-vertical"); 14 | ref.current.classList.add("translate-y-full"); 15 | 16 | ref.current.classList.remove("block"); 17 | let id = setTimeout(() => { 18 | ref.current.classList.add("hidden"); 19 | clearTimeout(id); 20 | }, 400); 21 | }; 22 | 23 | const { data: session } = useSession(); 24 | 25 | let handleUpload = async (e) => { 26 | e.preventDefault(); 27 | const URL = "/api/database/create"; 28 | 29 | const user = session.user.name.replaceAll(" ", "_").toLowerCase(); 30 | 31 | let props = { 32 | videoUrl: video, 33 | songName: song, 34 | imageUrl: image, 35 | description: description, 36 | user: user, 37 | userImage: session?.user?.image, 38 | }; 39 | 40 | let res = await ( 41 | await fetch(URL, { 42 | method: "POST", 43 | headers: { 44 | "content-type": "application/json", 45 | }, 46 | body: JSON.stringify(props), 47 | }) 48 | ).json(); 49 | 50 | if (res?.success) setAllNull(); 51 | else alert("Some Error Occured"); 52 | }; 53 | 54 | let setAllNull = () => { 55 | setVideo(""); 56 | setimage(""); 57 | setSong(""); 58 | setDescription(""); 59 | }; 60 | 61 | return ( 62 |
66 |
67 |

68 | Create the next viral video.... 69 |

70 |
74 |
75 |
76 |
77 | setVideo(e.target.value)} 84 | value={video} 85 | required 86 | autoComplete="off" 87 | /> 88 | 94 |
95 |
96 | setimage(e.target.value)} 103 | value={image} 104 | required 105 | autoComplete="off" 106 | /> 107 | 113 |
114 |
115 |
116 |
117 | setSong(e.target.value)} 125 | required 126 | autoComplete="off" 127 | /> 128 | 134 |
135 |
136 |