├── .env ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── logo.png ├── profile.jpg └── vite.svg ├── src ├── App.jsx ├── assets │ └── react.svg ├── components │ ├── Home.jsx │ ├── ListItems.jsx │ ├── Navbar.jsx │ ├── PlayingVideo.jsx │ ├── Search.jsx │ ├── SearchCard.jsx │ ├── Sidebar.jsx │ ├── SuggestedVideo.jsx │ └── Video.jsx ├── context │ └── AuthProvider.jsx ├── index.css ├── loader │ ├── Loading.jsx │ └── Time.jsx ├── main.jsx └── utils │ └── rapidapi.js ├── tailwind.config.js └── vite.config.js /.env: -------------------------------------------------------------------------------- 1 | VITE_YOUTUBE_API_KEY="6e79ac4f15msh1642f74265ebcb9p1faf31jsn3035a07d8221" -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yt-clone", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "axios": "^1.7.2", 14 | "js-abbreviation-number": "^1.4.0", 15 | "moment": "^2.30.1", 16 | "react": "^18.3.1", 17 | "react-avatar": "^5.0.3", 18 | "react-dom": "^18.3.1", 19 | "react-icons": "^5.2.1", 20 | "react-player": "^2.16.0", 21 | "react-router-dom": "^6.24.1" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^18.3.3", 25 | "@types/react-dom": "^18.3.0", 26 | "@vitejs/plugin-react": "^4.3.1", 27 | "autoprefixer": "^10.4.19", 28 | "eslint": "^8.57.0", 29 | "eslint-plugin-react": "^7.34.2", 30 | "eslint-plugin-react-hooks": "^4.6.2", 31 | "eslint-plugin-react-refresh": "^0.4.7", 32 | "postcss": "^8.4.39", 33 | "tailwindcss": "^3.4.4", 34 | "vite": "^5.3.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearnCodingOfficial/yt-clone/75d0c928312db9cb2ec9f8de26d852007ae88226/public/logo.png -------------------------------------------------------------------------------- /public/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LearnCodingOfficial/yt-clone/75d0c928312db9cb2ec9f8de26d852007ae88226/public/profile.jpg -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Navbar from "./components/Navbar"; 3 | import { Route, Routes } from "react-router-dom"; 4 | import Home from "./components/Home"; 5 | import Search from "./components/Search"; 6 | import PlayingVideo from "./components/PlayingVideo"; 7 | import { useAuth } from "./context/AuthProvider"; 8 | import Loading from "./loader/Loading"; 9 | 10 | function App() { 11 | const { loading } = useAuth(); 12 | return ( 13 |
14 | {loading && } 15 | 16 | 17 | } /> 18 | } /> 19 | } /> 20 | 21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Sidebar from "./Sidebar.jsx"; 3 | import Video from "./Video"; 4 | import { useAuth } from "../context/AuthProvider.jsx"; 5 | import ListItems from "./ListItems.jsx"; 6 | function Home() { 7 | const { data, loading } = useAuth(); 8 | console.log(data); 9 | return ( 10 |
11 | 12 |
13 | 14 |
15 | {!loading && 16 | data.map((item) => { 17 | if (item.type !== "video") return false; 18 | return
21 |
22 |
23 | ); 24 | } 25 | 26 | export default Home; 27 | -------------------------------------------------------------------------------- /src/components/ListItems.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function ListItems() { 4 | const categories = [ 5 | "All", 6 | "Music", 7 | "React routers", 8 | "Computer programming", 9 | "Reverberation", 10 | "Movie musicals", 11 | "India national cricket team", 12 | "News", 13 | "Mixes", 14 | "1990s", 15 | "Telugu cinema", 16 | "Live", 17 | "Dramedy", 18 | "Dubbing", 19 | "Indian soap opera", 20 | "Cricket", 21 | "Football", 22 | "Learn Coding", 23 | ]; 24 | return ( 25 |
26 |
27 | {categories.map((category) => { 28 | return ( 29 |
33 | {category} 34 |
35 | ); 36 | })} 37 |
38 |
39 | ); 40 | } 41 | 42 | export default ListItems; 43 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Avatar from "react-avatar"; 3 | 4 | import { AiOutlineMenu } from "react-icons/ai"; 5 | import { CiSearch } from "react-icons/ci"; 6 | import { IoMdMic } from "react-icons/io"; 7 | import { RiVideoAddLine } from "react-icons/ri"; 8 | import { AiOutlineBell } from "react-icons/ai"; 9 | 10 | import logo from "../../public/logo.png"; 11 | import profile from "../../public/profile.jpg"; 12 | import { useNavigate } from "react-router-dom"; 13 | function Navbar() { 14 | const [searchQuery, setSearchQuery] = useState(""); 15 | 16 | const navigate = useNavigate(); 17 | 18 | const searchQueryHandler = (event) => { 19 | if ( 20 | (event?.key === "Enter" || event === "searchButton") && 21 | searchQuery?.length > 0 22 | ) { 23 | navigate(`/search/${searchQuery}`); 24 | setSearchQuery(""); 25 | } 26 | }; 27 | 28 | return ( 29 |
30 |
31 | 32 | 33 |
34 |
35 |
36 | setSearchQuery(e.target.value)} 41 | onKeyUp={searchQueryHandler} 42 | value={searchQuery} 43 | /> 44 |
45 | 51 | 55 |
56 |
57 | 58 | 59 | 60 |
61 |
62 | ); 63 | } 64 | 65 | export default Navbar; 66 | -------------------------------------------------------------------------------- /src/components/PlayingVideo.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import { fetchData } from "../utils/rapidapi"; 4 | import ReactPlayer from "react-player"; 5 | import { AiOutlineLike } from "react-icons/ai"; 6 | import { abbreviateNumber } from "js-abbreviation-number"; 7 | import SuggestedVideo from "./SuggestedVideo"; 8 | import { BsFillCheckCircleFill } from "react-icons/bs"; 9 | 10 | function PlayingVideo() { 11 | const [video, setVideo] = useState(); 12 | const [realatedVideo, setRelativeVideo] = useState(); 13 | const { id } = useParams(); 14 | 15 | useEffect(() => { 16 | fetchVideoDetails(); 17 | fetchRelatedVideo(); 18 | }, [id]); 19 | 20 | const fetchVideoDetails = () => { 21 | fetchData(`video/details/?id=${id}`).then((res) => { 22 | console.log(res); 23 | setVideo(res); 24 | }); 25 | }; 26 | const fetchRelatedVideo = () => { 27 | fetchData(`video/related-contents/?id=${id}`).then((res) => { 28 | console.log(res); 29 | setRelativeVideo(res); 30 | }); 31 | }; 32 | 33 | return ( 34 |
35 |
36 |
37 |
38 | 46 |
47 |
48 | {video?.title} 49 |
50 |
51 |
52 |
53 |
54 | 58 |
59 |
60 |
61 |
62 |
63 | {video?.author?.title} 64 | {video?.author?.badges[0]?.type === "VERIFIED_CHANNEL" && ( 65 | 66 | )} 67 |
68 |
69 | {video?.author?.stats?.subscribersText} 70 |
71 |
72 | 73 | Subscribe 74 | 75 |
76 |
77 |
78 |
79 | 80 | {`${abbreviateNumber(video?.stats?.likes, 2)} Likes`} 81 |
82 |
83 | {`${abbreviateNumber(video?.stats?.views, 2)} Views`} 84 |
85 |
86 |
87 |
88 | {video?.description} 89 |
90 |
91 | {video?.stats?.comments}

Comments

92 |
93 |
94 |
95 | {realatedVideo?.contents?.map((item, index) => { 96 | if (item?.type !== "video") return false; 97 | return ; 98 | })} 99 |
100 |
101 |
102 | ); 103 | } 104 | 105 | export default PlayingVideo; 106 | -------------------------------------------------------------------------------- /src/components/Search.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import { fetchData } from "../utils/rapidapi"; 4 | import Sidebar from "./Sidebar.jsx"; 5 | import SearchCard from "./SearchCard.jsx"; 6 | 7 | function Search() { 8 | const [result, setResult] = useState(); 9 | const { searchQuery } = useParams(); 10 | 11 | useEffect(() => { 12 | fetchSearchResults(); 13 | }, [searchQuery]); 14 | 15 | const fetchSearchResults = () => { 16 | fetchData(`search/?q=${searchQuery}`).then(({ contents }) => { 17 | console.log(contents); 18 | setResult(contents); 19 | }); 20 | }; 21 | 22 | return ( 23 |
24 |
25 | 26 |
27 |
28 | {result?.map((item, index) => { 29 | if (item?.type !== "video") return false; 30 | return ; 31 | })} 32 |
33 |
34 |
35 |
36 | ); 37 | } 38 | 39 | export default Search; 40 | -------------------------------------------------------------------------------- /src/components/SearchCard.jsx: -------------------------------------------------------------------------------- 1 | import { abbreviateNumber } from "js-abbreviation-number"; 2 | import React from "react"; 3 | import { BsFillCheckCircleFill } from "react-icons/bs"; 4 | import Time from "../loader/Time"; 5 | import { Link } from "react-router-dom"; 6 | 7 | function SearchCard({ video }) { 8 | console.log(video); 9 | return ( 10 |
11 | 12 |
13 |
14 | 18 | {video?.lengthSeconds &&
20 |
21 | 22 | {video?.title} 23 | 24 | 25 | {video?.descriptionSnippet} 26 | 27 |
28 |
29 |
30 | 34 |
35 |
36 |
37 | 38 | {video?.author?.title} 39 | {video?.author?.badges[0]?.type === "VERIFIED_CHANNEL" && ( 40 | 41 | )} 42 | 43 |
44 | {`${abbreviateNumber( 45 | video?.stats?.views, 46 | 2 47 | )} views`} 48 | 49 | . 50 | 51 | {video?.publishedTimeText} 52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 | ); 60 | } 61 | 62 | export default SearchCard; 63 | -------------------------------------------------------------------------------- /src/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { GoHome } from "react-icons/go"; 3 | import { SiYoutubeshorts } from "react-icons/si"; 4 | import { MdOutlineSubscriptions, MdHistory } from "react-icons/md"; 5 | import { PiUserSquareThin } from "react-icons/pi"; 6 | import { IoGameControllerOutline } from "react-icons/io5"; 7 | import { AiOutlineLike } from "react-icons/ai"; 8 | import { FaChevronRight } from "react-icons/fa6"; 9 | import { FaYoutube } from "react-icons/fa"; 10 | import { SiYoutubestudio } from "react-icons/si"; 11 | import { SiYoutubekids } from "react-icons/si"; 12 | import { MdOutlineWatchLater } from "react-icons/md"; 13 | import { SiYoutubemusic } from "react-icons/si"; 14 | import { SiTrendmicro } from "react-icons/si"; 15 | import { HiOutlineShoppingBag } from "react-icons/hi2"; 16 | import { PiFilmSlateLight } from "react-icons/pi"; 17 | import { CgMediaLive } from "react-icons/cg"; 18 | import { SiYoutubegaming } from "react-icons/si"; 19 | import { FaRegNewspaper } from "react-icons/fa"; 20 | import { TfiCup } from "react-icons/tfi"; 21 | import { PiLightbulbLight } from "react-icons/pi"; 22 | import { SiStylelint } from "react-icons/si"; 23 | import { MdPodcasts } from "react-icons/md"; 24 | import { BiVideo } from "react-icons/bi"; 25 | import { GiLinkedRings } from "react-icons/gi"; 26 | function Sidebar() { 27 | const sidebarItems = [ 28 | { 29 | id: 1, 30 | name: "Home", 31 | icon: , 32 | }, 33 | { 34 | id: 2, 35 | name: "Shorts", 36 | icon: , 37 | }, 38 | { 39 | id: 3, 40 | name: "Subscriptions", 41 | icon: , 42 | }, 43 | ]; 44 | const sidebarItems2 = [ 45 | { 46 | id: 1, 47 | name: "Your Channel", 48 | icon: , 49 | }, 50 | { 51 | id: 2, 52 | name: "History", 53 | icon: , 54 | }, 55 | { 56 | id: 3, 57 | name: "Playlists", 58 | icon: , 59 | }, 60 | { 61 | id: 4, 62 | name: "Your Videos", 63 | icon: , 64 | }, 65 | { 66 | id: 5, 67 | name: "Watch later", 68 | icon: , 69 | }, 70 | { 71 | id: 6, 72 | name: "Liked videos", 73 | icon: , 74 | }, 75 | ]; 76 | const sidebarItems3 = [ 77 | { 78 | id: 1, 79 | name: "Trending", 80 | icon: , 81 | }, 82 | { 83 | id: 2, 84 | name: "Shopping", 85 | icon: , 86 | }, 87 | { 88 | id: 3, 89 | name: "Music", 90 | icon: , 91 | }, 92 | { 93 | id: 4, 94 | name: "Films", 95 | icon: , 96 | }, 97 | { 98 | id: 5, 99 | name: "Live", 100 | icon: , 101 | }, 102 | { 103 | id: 6, 104 | name: "Gaming", 105 | icon: , 106 | }, 107 | { 108 | id: 7, 109 | name: "News", 110 | icon: , 111 | }, 112 | { 113 | id: 8, 114 | name: "Sport", 115 | icon: , 116 | }, 117 | { 118 | id: 9, 119 | name: "Courses", 120 | icon: , 121 | }, 122 | { 123 | id: 10, 124 | name: "Fashion & beauty", 125 | icon: , 126 | }, 127 | { 128 | id: 11, 129 | name: "Padcasts", 130 | icon: , 131 | }, 132 | ]; 133 | const sidebarItems4 = [ 134 | { 135 | id: 1, 136 | name: "Youtube Premium", 137 | icon: , 138 | }, 139 | { 140 | id: 2, 141 | name: "Youtube Studio", 142 | icon: , 143 | }, 144 | { 145 | id: 3, 146 | name: "Youtube Music", 147 | icon: , 148 | }, 149 | { 150 | id: 4, 151 | name: "Youtube Kids", 152 | icon: , 153 | }, 154 | ]; 155 | return ( 156 |
157 | {/* Home */} 158 |
159 | {sidebarItems.map((item) => { 160 | return ( 161 |
165 |
{item.icon}
166 | {item.name} 167 |
168 | ); 169 | })} 170 |
171 |
172 |
173 | {/* You */} 174 |
175 |
176 |

You

177 | 178 |
179 | {sidebarItems2.map((item) => { 180 | return ( 181 |
185 |
{item.icon}
186 | {item.name} 187 |
188 | ); 189 | })} 190 |
191 |
192 |
193 | {/* Explore */} 194 |
195 |
196 |

Explore

197 |
198 | {sidebarItems3.map((item) => { 199 | return ( 200 |
204 |
{item.icon}
205 | {item.name} 206 |
207 | ); 208 | })} 209 |
210 |
211 |
212 | {/* More section */} 213 |
214 |
215 |

More From Youtube

216 |
217 | {sidebarItems4.map((item) => { 218 | return ( 219 |
223 |
224 | {item.icon} 225 |
226 | {item.name} 227 |
228 | ); 229 | })} 230 |
231 |
232 |
233 | 234 | About Press Copyright
Contact us Creators
Advertise 235 | Developers
236 |

Terms Privacy Policy & Safety

How YouTube works{" "} 237 |
Test new features 238 |
239 |
240 |

© 2024 Learn Coding

241 |
242 | ); 243 | } 244 | 245 | export default Sidebar; 246 | -------------------------------------------------------------------------------- /src/components/SuggestedVideo.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import Time from "../loader/Time"; 4 | import { BsFillCheckCircleFill } from "react-icons/bs"; 5 | import { abbreviateNumber } from "js-abbreviation-number"; 6 | 7 | function SuggestedVideo({ video }) { 8 | console.log(video); 9 | return ( 10 |
11 | 12 |
13 |
14 | 18 | {video?.lengthSeconds &&
20 |
21 | 22 | {video?.title} 23 | 24 | 25 | {video?.author?.title} 26 | {video?.author?.badges[0]?.type === "VERIFIED_CHANNEL" && ( 27 | 28 | )} 29 | 30 |
31 | {`${abbreviateNumber(video?.stats?.views, 2)} views`} 32 | 33 | . 34 | 35 | {video?.publishedTimeText} 36 |
37 |
38 |
39 | 40 |
41 | ); 42 | } 43 | 44 | export default SuggestedVideo; 45 | -------------------------------------------------------------------------------- /src/components/Video.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import Time from "../loader/Time"; 4 | import { BsFillCheckCircleFill } from "react-icons/bs"; 5 | import { abbreviateNumber } from "js-abbreviation-number"; 6 | 7 | function Video({ video }) { 8 | console.log(video); 9 | 10 | return ( 11 |
12 | 13 |
14 | {/* thumbnail & duration */} 15 |
16 | 21 | {video?.lengthSeconds &&
23 | {/* channerl logo & title */} 24 |
25 |
26 |
27 | 32 |
33 |
34 |
35 | 36 | {video?.title} 37 | 38 | 39 | {video?.author?.title} 40 | {video?.author?.badges[0]?.type === "VERIFIED_CHANNEL" && ( 41 | 42 | )} 43 | 44 |
45 | {`${abbreviateNumber( 46 | video?.stats?.views, 47 | 2 48 | )} views`} 49 | 50 | . 51 | 52 | {video?.publishedTimeText} 53 |
54 |
55 |
56 |
57 | 58 |
59 | ); 60 | } 61 | 62 | export default Video; 63 | -------------------------------------------------------------------------------- /src/context/AuthProvider.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useState } from "react"; 2 | import { fetchData } from "../utils/rapidapi"; 3 | 4 | export const AuthContext = createContext(); 5 | 6 | export default function AuthProvider({ children }) { 7 | const [loading, setLoading] = useState(false); 8 | const [data, setData] = useState([]); 9 | const [value, setValue] = useState("New"); 10 | 11 | useEffect(() => { 12 | fetchAlldata(value); 13 | }, [value]); 14 | 15 | const fetchAlldata = (query) => { 16 | setLoading(true); 17 | fetchData(`search/?q=${query}`).then(({ contents }) => { 18 | console.log(contents); 19 | setData(contents); 20 | setLoading(false); 21 | }); 22 | }; 23 | return ( 24 | 25 | {children} 26 | 27 | ); 28 | } 29 | 30 | export const useAuth = () => useContext(AuthContext); 31 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /src/loader/Loading.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function Loading() { 4 | return ( 5 |
6 | 7 | ... 8 | 9 |
10 | ); 11 | } 12 | 13 | export default Loading; 14 | -------------------------------------------------------------------------------- /src/loader/Time.jsx: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | import React from "react"; 3 | 4 | function Time({ time }) { 5 | const videoTime = moment()?.startOf("day")?.seconds(time)?.format("H:mm:ss"); 6 | return ( 7 |
8 | 9 | {videoTime} 10 | 11 |
12 | ); 13 | } 14 | 15 | export default Time; 16 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import "./index.css"; 5 | import AuthProvider from "./context/AuthProvider.jsx"; 6 | import { BrowserRouter } from "react-router-dom"; 7 | 8 | ReactDOM.createRoot(document.getElementById("root")).render( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/utils/rapidapi.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const API_KEY=import.meta.env.VITE_YOUTUBE_API_KEY; 4 | const BASE_URL="https://youtube138.p.rapidapi.com" 5 | 6 | const options = { 7 | headers: { 8 | 'x-rapidapi-key': API_KEY, 9 | 'x-rapidapi-host': 'youtube138.p.rapidapi.com' 10 | } 11 | }; 12 | 13 | export const fetchData=async(url)=>{ 14 | try{ 15 | const {data} = await axios.get(`${BASE_URL}/${url}`,options); 16 | return data; 17 | }catch(error){ 18 | console.error("error fetching api data: ",error); 19 | throw error; 20 | } 21 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------