├── screenshots ├── Readme.md ├── first.PNG ├── second.PNG └── third.PNG ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── index.js ├── Components │ ├── Loader.jsx │ ├── Navbar.jsx │ ├── index.js │ ├── Videos.jsx │ ├── SearchFeed.jsx │ ├── Sidebar.jsx │ ├── SearchBar.jsx │ ├── Feed.jsx │ ├── ChannelDetail.jsx │ ├── VideoCard.jsx │ ├── ChannelCard.jsx │ └── VideoDetail.jsx ├── Utils │ ├── fetchFromAPI.js │ └── Constants.js ├── App.js └── index.css ├── README.md ├── .gitignore └── package.json /screenshots/Readme.md: -------------------------------------------------------------------------------- 1 | images 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandran-jr/YouTubeRapidAPI/main/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandran-jr/YouTubeRapidAPI/main/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandran-jr/YouTubeRapidAPI/main/public/logo512.png -------------------------------------------------------------------------------- /screenshots/first.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandran-jr/YouTubeRapidAPI/main/screenshots/first.PNG -------------------------------------------------------------------------------- /screenshots/second.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandran-jr/YouTubeRapidAPI/main/screenshots/second.PNG -------------------------------------------------------------------------------- /screenshots/third.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandran-jr/YouTubeRapidAPI/main/screenshots/third.PNG -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import './index.css' 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')); 7 | 8 | root.render(); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YouTube RapidAPI App 2 | ## A cool YouTube clone built using React.js and Material UI for frontend, and RapidAPI for youtube api calls, integrated with various endpoints, search functionality, and different UIs. 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Components/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, CircularProgress, Stack } from '@mui/material'; 3 | 4 | const Loader = () => ( 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | 12 | export default Loader; -------------------------------------------------------------------------------- /.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/Utils/fetchFromAPI.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const BASE_URL = 'https://youtube-v31.p.rapidapi.com'; 4 | 5 | const options = { 6 | method: 'GET', 7 | url: BASE_URL, 8 | params: { 9 | maxResults: '50' 10 | }, 11 | headers: { 12 | 'X-RapidAPI-Key': 'PUT YOUR API KEY HERE', 13 | 'X-RapidAPI-Host': 'youtube-v31.p.rapidapi.com' 14 | } 15 | }; 16 | 17 | export const fetchFromAPI = async (url) => { 18 | const { data } = await axios.get(`${BASE_URL}/${url}`, options); 19 | 20 | return data; 21 | }; 22 | -------------------------------------------------------------------------------- /src/Components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { Stack } from "@mui/material"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { logo } from "../Utils/Constants"; 5 | import { SearchBar } from "./"; 6 | 7 | const Navbar = () => ( 8 | 9 | 10 | logo 11 | 12 | 13 | 14 | ); 15 | 16 | export default Navbar; -------------------------------------------------------------------------------- /src/Components/index.js: -------------------------------------------------------------------------------- 1 | export { default as ChannelCard } from './ChannelCard'; 2 | export { default as VideoCard } from './VideoCard'; 3 | export { default as ChannelDetail } from './ChannelDetail'; 4 | export { default as Feed } from './Feed'; 5 | export { default as Loader } from './Loader'; 6 | export { default as Navbar } from './Navbar'; 7 | export { default as SearchBar } from './SearchBar'; 8 | export { default as VideoDetail } from './VideoDetail'; 9 | export { default as SearchFeed } from './SearchFeed'; 10 | export { default as Videos } from './Videos'; 11 | export { default as Sidebar } from './Sidebar'; -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/Components/Videos.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Stack, Box } from "@mui/material"; 3 | 4 | import { ChannelCard, Loader, VideoCard } from "./"; 5 | 6 | const Videos = ({ videos, direction }) => { 7 | if(!videos?.length) return ; 8 | 9 | return ( 10 | 11 | {videos.map((item, idx) => ( 12 | 13 | {item.id.videoId && } 14 | {item.id.channelId && } 15 | 16 | ))} 17 | 18 | ); 19 | } 20 | 21 | export default Videos; -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 3 | import {Box} from '@mui/material'; 4 | import {Navbar, Feed, ChannelDetail, VideoDetail, SearchFeed} from './Components'; 5 | 6 | const App = () => ( 7 | 8 | 9 | 10 | 11 | 12 | } /> 13 | } /> 14 | } /> 15 | } /> 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | 23 | export default App -------------------------------------------------------------------------------- /src/Components/SearchFeed.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { Typography, Box } from "@mui/material"; 3 | import { useParams } from "react-router-dom"; 4 | 5 | import { fetchFromAPI } from "../Utils/fetchFromAPI"; 6 | import { Videos } from "./"; 7 | 8 | const SearchFeed = () => { 9 | const [videos, setVideos] = useState(null); 10 | const { searchTerm } = useParams(); 11 | 12 | useEffect(() => { 13 | fetchFromAPI(`search?part=snippet&q=${searchTerm}`) 14 | .then((data) => setVideos(data.items)) 15 | }, [searchTerm]); 16 | 17 | return ( 18 | 19 | 20 | Search Results for {searchTerm} videos 21 | 22 | 23 | 24 | {} 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default SearchFeed; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "youtubeapi", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.10.0", 7 | "@emotion/styled": "^11.10.0", 8 | "@mui/icons-material": "^5.8.4", 9 | "@mui/material": "^5.9.3", 10 | "axios": "^0.27.2", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-player": "^2.10.1", 14 | "react-router-dom": "^6.3.0", 15 | "react-scripts": "^5.0.1" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Stack } from "@mui/material"; 3 | 4 | import { categories } from "../Utils/Constants"; 5 | 6 | const Categories = ({ selectedCategory, setSelectedCategory }) => ( 7 | 15 | {categories.map((category) => ( 16 | 32 | ))} 33 | 34 | ); 35 | 36 | export default Categories; -------------------------------------------------------------------------------- /src/Components/SearchBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useNavigate } from "react-router-dom"; 3 | import { Paper, IconButton } from '@mui/material'; 4 | import SearchIcon from '@mui/icons-material/Search'; 5 | 6 | const SearchBar = () => { 7 | const [searchTerm, setSearchTerm] = useState(''); 8 | const navigate = useNavigate(); 9 | 10 | const onhandleSubmit = (e) => { 11 | e.preventDefault(); 12 | 13 | if (searchTerm) { 14 | navigate(`/search/${searchTerm}`); 15 | 16 | setSearchTerm(''); 17 | } 18 | }; 19 | 20 | return ( 21 | 32 | setSearchTerm(e.target.value)} 37 | /> 38 | 39 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | export default SearchBar; -------------------------------------------------------------------------------- /src/Components/Feed.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Box, Stack, Typography } from "@mui/material"; 3 | 4 | import { fetchFromAPI } from "../Utils/fetchFromAPI"; 5 | import { Videos, Sidebar } from "./"; 6 | 7 | const Feed = () => { 8 | const [selectedCategory, setSelectedCategory] = useState("New"); 9 | const [videos, setVideos] = useState(null); 10 | 11 | useEffect(() => { 12 | setVideos(null); 13 | 14 | fetchFromAPI(`search?part=snippet&q=${selectedCategory}`) 15 | .then((data) => setVideos(data.items)) 16 | }, [selectedCategory]); 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | Copyright © 2023 Govind Chandran 25 | 26 | 27 | 28 | 29 | 30 | {selectedCategory} videos 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default Feed; -------------------------------------------------------------------------------- /src/Components/ChannelDetail.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import { Box } from "@mui/material"; 4 | 5 | import { Videos, ChannelCard } from "./"; 6 | import { fetchFromAPI } from "../Utils/fetchFromAPI"; 7 | 8 | const ChannelDetail = () => { 9 | const [channelDetail, setChannelDetail] = useState(); 10 | const [videos, setVideos] = useState(null); 11 | 12 | const { id } = useParams(); 13 | 14 | useEffect(() => { 15 | const fetchResults = async () => { 16 | const data = await fetchFromAPI(`channels?part=snippet&id=${id}`); 17 | 18 | setChannelDetail(data?.items[0]); 19 | 20 | const videosData = await fetchFromAPI(`search?channelId=${id}&part=snippet%2Cid&order=date`); 21 | 22 | setVideos(videosData?.items); 23 | console.log(JSON.stringify(videosData)) 24 | }; 25 | 26 | fetchResults(); 27 | }, [id]); 28 | 29 | return ( 30 | 31 | 32 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default ChannelDetail; -------------------------------------------------------------------------------- /src/Components/VideoCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from "react-router-dom"; 3 | import { Typography, Card, CardContent, CardMedia } from "@mui/material"; 4 | import CheckCircleIcon from "@mui/icons-material/CheckCircle"; 5 | 6 | import { demoThumbnailUrl, demoVideoUrl, demoVideoTitle, demoChannelUrl, demoChannelTitle } from "../Utils/Constants"; 7 | 8 | const VideoCard = ({ video: { id: { videoId }, snippet } }) => ( 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | {snippet?.title.slice(0, 60) || demoVideoTitle.slice(0, 60)} 19 | 20 | 21 | 22 | 23 | {snippet?.channelTitle || demoChannelTitle} 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | export default VideoCard -------------------------------------------------------------------------------- /src/Components/ChannelCard.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, CardContent, CardMedia, Typography } from '@mui/material'; 3 | import CheckCircleIcon from '@mui/icons-material/CheckCircle'; 4 | import { Link } from 'react-router-dom'; 5 | import { demoProfilePicture } from '../Utils/Constants'; 6 | 7 | const ChannelCard = ({ channelDetail, marginTop }) => ( 8 | 21 | 22 | 23 | 28 | 29 | {channelDetail?.snippet?.title}{' '} 30 | 31 | 32 | {channelDetail?.statistics?.subscriberCount && ( 33 | 34 | {parseInt(channelDetail?.statistics?.subscriberCount).toLocaleString('en-US')} Subscribers 35 | 36 | )} 37 | 38 | 39 | 40 | ); 41 | 42 | export default ChannelCard; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | } 7 | 8 | a { 9 | text-decoration: none; 10 | color: black; 11 | } 12 | 13 | ::-webkit-scrollbar { 14 | width: 0px; 15 | height: 5px; 16 | } 17 | 18 | ::-webkit-scrollbar-thumb { 19 | background-color: rgb(114, 113, 113); 20 | border-radius: 10px; 21 | height: 200px; 22 | } 23 | 24 | ::-webkit-scrollbar-track { 25 | background-color: transparent; 26 | } 27 | 28 | .category-btn:hover { 29 | background-color: #FC1503 !important; 30 | color: white !important; 31 | } 32 | 33 | .category-btn:hover span { 34 | color: white !important; 35 | } 36 | 37 | .react-player { 38 | height: 77vh !important; 39 | width: 100% !important; 40 | } 41 | 42 | .search-bar { 43 | border: none; 44 | outline: none; 45 | width: 350px; 46 | } 47 | 48 | .category-btn { 49 | font-weight: bold !important; 50 | text-transform: capitalize; 51 | display: flex; 52 | align-items: center; 53 | justify-content: start; 54 | cursor: pointer; 55 | background: transparent; 56 | outline: none; 57 | border: none; 58 | 59 | padding: 7px 15px; 60 | margin: 10px 0px; 61 | border-radius: 20px; 62 | transition: all 0.3s ease; 63 | } 64 | 65 | @media screen and (max-width: 900px) { 66 | .category-btn { 67 | margin: 10px; 68 | } 69 | } 70 | 71 | @media screen and (max-width:800px) { 72 | .copyright { 73 | display: none !important; 74 | } 75 | } 76 | 77 | @media screen and (max-width: 600px) { 78 | .scroll-horizontal { 79 | overflow: auto !important; 80 | } 81 | 82 | .react-player { 83 | height: 45vh !important; 84 | } 85 | 86 | .search-bar { 87 | width: 200px; 88 | } 89 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 22 | 26 | 35 | YouTube Rapid API 36 | 37 | 38 | 39 |
40 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/Utils/Constants.js: -------------------------------------------------------------------------------- 1 | import MusicNoteIcon from '@mui/icons-material/MusicNote'; 2 | import HomeIcon from '@mui/icons-material/Home'; 3 | import CodeIcon from '@mui/icons-material/Code'; 4 | import OndemandVideoIcon from '@mui/icons-material/OndemandVideo'; 5 | import SportsEsportsIcon from '@mui/icons-material/SportsEsports'; 6 | import LiveTvIcon from '@mui/icons-material/LiveTv'; 7 | import SchoolIcon from '@mui/icons-material/School'; 8 | import FaceRetouchingNaturalIcon from '@mui/icons-material/FaceRetouchingNatural'; 9 | import CheckroomIcon from '@mui/icons-material/Checkroom'; 10 | import GraphicEqIcon from '@mui/icons-material/GraphicEq'; 11 | import TheaterComedyIcon from '@mui/icons-material/TheaterComedy'; 12 | import FitnessCenterIcon from '@mui/icons-material/FitnessCenter'; 13 | import DeveloperModeIcon from '@mui/icons-material/DeveloperMode'; 14 | 15 | export const logo = 'https://i.ibb.co/s9Qys2j/logo.png'; 16 | 17 | export const categories = [ 18 | { name: 'New', icon: , }, 19 | { name: 'Coding', icon: , }, 20 | { name: 'ReactJS', icon: , }, 21 | { name: 'NextJS', icon: , }, 22 | { name: 'Music', icon: }, 23 | { name: 'Education', icon: , }, 24 | { name: 'Podcast', icon: , }, 25 | { name: 'Movie', icon: , }, 26 | { name: 'Gaming', icon: , }, 27 | { name: 'Live', icon: , }, 28 | { name: 'Sport', icon: , }, 29 | { name: 'Fashion', icon: , }, 30 | { name: 'Beauty', icon: , }, 31 | { name: 'Comedy', icon: , }, 32 | { name: 'Gym', icon: , }, 33 | { name: 'Crypto', icon: , }, 34 | ]; 35 | 36 | export const demoThumbnailUrl = 'https://i.ibb.co/G2L2Gwp/API-Course.png'; 37 | export const demoChannelUrl = '/channel/UCmXmlB4-HJytD7wek0Uo97A'; 38 | export const demoVideoUrl = '/video/GDa8kZLNhJ4'; 39 | export const demoChannelTitle = 'JavaScript Mastery'; 40 | export const demoVideoTitle = 'Build and Deploy 5 JavaScript & React API Projects in 10 Hours - Full Course | RapidAPI'; 41 | export const demoProfilePicture = 'http://dergipark.org.tr/assets/app/images/buddy_sample.png' -------------------------------------------------------------------------------- /src/Components/VideoDetail.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link, useParams } from "react-router-dom"; 3 | import ReactPlayer from "react-player"; 4 | import { Typography, Box, Stack } from "@mui/material"; 5 | import CheckCircleIcon from "@mui/icons-material/CheckCircle"; 6 | 7 | import { Videos, Loader } from "./"; 8 | import { fetchFromAPI } from "../Utils/fetchFromAPI"; 9 | 10 | const VideoDetail = () => { 11 | const [videoDetail, setVideoDetail] = useState(null); 12 | const [videos, setVideos] = useState(null); 13 | const { id } = useParams(); 14 | 15 | useEffect(() => { 16 | fetchFromAPI(`videos?part=snippet,statistics&id=${id}`) 17 | .then((data) => setVideoDetail(data.items[0])) 18 | 19 | fetchFromAPI(`search?part=snippet&relatedToVideoId=${id}&type=video`) 20 | .then((data) => setVideos(data.items)) 21 | }, [id]); 22 | 23 | if(!videoDetail?.snippet) return ; 24 | 25 | const { snippet: { title, channelId, channelTitle }, statistics: { viewCount, likeCount } } = videoDetail; 26 | 27 | return ( 28 | 29 | 30 | 31 | 32 | 33 | 34 | {title} 35 | 36 | 37 | 38 | 39 | {channelTitle} 40 | 41 | 42 | 43 | 44 | 45 | {parseInt(viewCount).toLocaleString()} views 46 | 47 | 48 | {parseInt(likeCount).toLocaleString()} likes 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ); 60 | }; 61 | 62 | export default VideoDetail; --------------------------------------------------------------------------------