├── 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 |
--------------------------------------------------------------------------------
/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 | [](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 |

54 | );
55 | })}
56 |
57 |
61 | {images.length &&
62 | images.map((url, id) => {
63 | return (
64 |

70 | );
71 | })}
72 |
73 |
77 | {images.length &&
78 | images.map((url, id) => {
79 | return (
80 |

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 |
88 | );
89 | })}
90 |
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 |
161 |
162 |
168 |
171 |
172 |
173 | );
174 | });
175 |
176 | UploadForm.displayName = "UploadForm";
177 |
178 | export default UploadForm;
179 |
--------------------------------------------------------------------------------
/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 | import Link from "next/link";
3 | import { getSession, signOut } from "next-auth/react";
4 | import { useEffect, useRef, useState } from "react";
5 |
6 | const NavBar = () => {
7 | let session = null;
8 | const [authenticated, setAuthenticated] = useState(false);
9 | const [info, setInfo] = useState(null);
10 | const dropDownRef = useRef(null);
11 |
12 | useEffect(() => {
13 | let fetchSession = async () => {
14 | let session = await getSession();
15 | if (session) {
16 | setAuthenticated(true);
17 | setInfo(session.user);
18 | } else setAuthenticated(false);
19 | };
20 | fetchSession();
21 | }, [session]);
22 |
23 | let handleDropDown = () => {
24 | dropDownRef.current.classList.toggle("hidden");
25 | };
26 |
27 | return (
28 |
29 |
84 |
85 |
86 | );
87 | };
88 |
89 | export default NavBar;
90 |
--------------------------------------------------------------------------------