├── .eslintrc.json ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json └── src ├── App.css ├── App.js ├── App.test.js ├── components ├── Footer.css ├── Footer.js ├── SearchBar.css ├── SearchBar.js ├── VideoDetail.css ├── VideoDetail.js ├── VideoList.js ├── VideoListItem.css └── VideoListItem.js ├── index.css ├── index.js ├── logo.svg └── registerServiceWorker.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "prettier" 4 | ], 5 | "plugins": [ 6 | "prettier" 7 | ], 8 | "rules": { 9 | "prettier/prettier": "error" 10 | } 11 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | #webstorm 24 | .idea 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YouTube InstaSearch App in React 2 | 3 | A minimalistic UI for searching YouTube Videos. 4 | 5 | ➜ [Demo](https://amandeepmittal.github.io/react-youtube-search/) 6 | 7 | ### Using 8 | - YouTube API 9 | - React 10 | - react-timestamp 11 | - Bulma CSS 12 | 13 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). 14 | 15 | ### Installation 16 | - Clone this repository 17 | 18 | ```shell 19 | $ cd react-youtube-search 20 | $ npm install 21 | $ npm start 22 | ``` 23 | 24 | --- 25 | 26 | ### Todo 27 | - [ ] Add load more button to load more videos from API on `VideoList` column. 28 | 29 | ### License 30 | MIT -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-youtube-search", 3 | "version": "1.1.1", 4 | "description":"⚛ React with YouTube API ", 5 | "private": true, 6 | "dependencies": { 7 | "gh-pages": "1.0.0", 8 | "lodash.debounce": "4.0.8", 9 | "react": "^15.6.1", 10 | "react-dom": "^15.6.1", 11 | "react-scripts": "1.0.11", 12 | "react-timestamp": "4.2.1", 13 | "youtube-api-search": "0.0.5" 14 | }, 15 | "scripts": { 16 | "predeploy": "npm run build", 17 | "deploy": "gh-pages -d build", 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | }, 23 | "devDependencies": { 24 | "eslint-config-prettier": "2.3.0", 25 | "eslint-plugin-prettier": "2.2.0", 26 | "prettier": "1.5.3" 27 | }, 28 | "homepage": "https://amandeepmittal.github.io/react-youtube-search", 29 | "author": "Aman Mittal (http://amandeepmittal.github.io/)", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/amandeepmittal/react-youtube-search/issues" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amandeepmittal/react-youtube-search/fc30bf0db58a98af023813d6e84863712b861c77/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Youtube Search 24 | 25 | 26 | 29 |
30 | Tweet 31 | 32 | 42 | 43 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amandeepmittal/react-youtube-search/fc30bf0db58a98af023813d6e84863712b861c77/src/App.css -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import debounce from 'lodash.debounce'; 2 | import React, { Component } from 'react'; 3 | import './App.css'; 4 | import SearchBar from './components/SearchBar'; 5 | import VideoDetail from './components/VideoDetail'; 6 | import VideoList from './components/VideoList'; 7 | import YTSearch from 'youtube-api-search'; 8 | import Footer from './components/Footer'; 9 | 10 | const REACT_APP_API_KEY = 'AIzaSyANl999ACEi82UvWilvXMclcow8WbikDKY'; 11 | 12 | class App extends Component { 13 | constructor(props) { 14 | super(props); 15 | 16 | this.state = { 17 | videos: [], // holds 5 videos fetched from API 18 | selectedVideo: null // selected video is null, default set below ln:28 19 | }; 20 | 21 | this.videoSearch('reactjs'); // default search term 22 | } 23 | 24 | // function for search term 25 | videoSearch(term) { 26 | YTSearch( 27 | { 28 | key: REACT_APP_API_KEY, 29 | term: term 30 | }, 31 | videos => { 32 | this.setState({ videos: videos, selectedVideo: videos[0] }); // through states setting the default video 33 | } 34 | ); 35 | } 36 | 37 | render() { 38 | // for consistent ui such that it re-renders after 300ms on search 39 | const videoSearch = debounce(term => { 40 | this.videoSearch(term); 41 | }, 300); 42 | return ( 43 |
44 | 45 |
46 | 47 | { 50 | this.setState({ selectedVideo }); 51 | }} 52 | /> 53 |
54 |
56 | ); 57 | } 58 | } 59 | 60 | export default App; 61 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/Footer.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | color: #db7093; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import'./Footer.css'; 3 | const Footer = () => { 4 | return ( 5 | 21 | ); 22 | }; 23 | 24 | export default Footer; 25 | -------------------------------------------------------------------------------- /src/components/SearchBar.css: -------------------------------------------------------------------------------- 1 | .search-bar { 2 | margin: 20px; 3 | } 4 | 5 | .title { 6 | color: #db7093; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './SearchBar.css'; 3 | 4 | export default class SearchBar extends Component { 5 | constructor(props) { 6 | super(props); 7 | 8 | this.state = { term: '' }; 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 |
15 |

16 |   Youtube InstaSearch 17 |

18 | this.onInputChange(event.target.value)} 24 | /> 25 |
26 |
27 | ); 28 | } 29 | 30 | onInputChange(term) { 31 | this.setState({ term }); 32 | this.props.onSearchTermChange(term); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/VideoDetail.css: -------------------------------------------------------------------------------- 1 | @keyframes blink { 2 | 50% { 3 | color: transparent; 4 | } 5 | } 6 | .dot { 7 | animation: 1s blink infinite; 8 | } 9 | .dot:nth-child(2) { 10 | animation-delay: 250ms; 11 | } 12 | .dot:nth-child(3) { 13 | animation-delay: 500ms; 14 | } 15 | 16 | .video-container { 17 | position: relative; 18 | padding-bottom: 56.25%; 19 | padding-top: 35px; 20 | height: 0; 21 | overflow: hidden; 22 | } 23 | 24 | .video-container iframe { 25 | position: absolute; 26 | top: 0; 27 | left: 0; 28 | width: 100%; 29 | height: 100%; 30 | } 31 | 32 | .video-title { 33 | font-size: 1.8em; 34 | font-weight: 500; 35 | } 36 | 37 | .box { 38 | border-radius: 5px; 39 | box-shadow: 0 2px 3px rgba(17, 17, 17, .1), 0 0 0 1px rgba(17, 17, 17, .1); 40 | display: block; 41 | padding: 20px; 42 | background-color: #ffffff; 43 | } -------------------------------------------------------------------------------- /src/components/VideoDetail.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Timestamp from 'react-timestamp'; 3 | 4 | import './VideoDetail.css'; 5 | 6 | const VideoDetail = ({ video }) => { 7 | if (!video) { 8 | return ( 9 |
10 | {/**/} 11 |

12 | Loading 13 | . 14 | . 15 | . 16 |

17 |
18 | ); 19 | } 20 | 21 | const videoId = video.id.videoId; 22 | const url = `https://www.youtube.com/embed/${videoId}`; 23 | 24 | return ( 25 |
26 |
27 |