├── 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 |
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;
--------------------------------------------------------------------------------