├── src ├── components │ ├── Gaming │ │ ├── index.css │ │ ├── styledComponents.js │ │ └── index.js │ ├── SavedVideos │ │ ├── index.css │ │ ├── styledComponents.js │ │ └── index.js │ ├── Navbar │ │ ├── index.css │ │ ├── styledComponents.js │ │ └── index.js │ ├── ContactUs │ │ ├── index.js │ │ └── styledComponents.js │ ├── Home │ │ ├── index.css │ │ ├── styledComponents.js │ │ └── index.js │ ├── Trending │ │ ├── index.css │ │ ├── styledComponents.js │ │ └── index.js │ ├── Loader │ │ ├── styledComponents.js │ │ └── index.js │ ├── VideoCard │ │ ├── index.css │ │ ├── styledComponents.js │ │ └── index.js │ ├── Header │ │ ├── index.css │ │ ├── styledComponents.js │ │ └── index.js │ ├── NotFound │ │ ├── styledComponents.js │ │ └── index.js │ ├── ErrorImage │ │ ├── styledComponent.js │ │ └── index.js │ └── Login │ │ ├── styledComponents.js │ │ └── index.js ├── context │ └── Theme.js ├── setupTests.js ├── index.js ├── App.css └── App.js ├── .gitattributes ├── .eslintignore ├── .prettierignore ├── .npmrc ├── public ├── img │ ├── favicon.ico │ ├── logo192.png │ └── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── craco.config.js ├── .gitignore ├── .prettierrc ├── .eslintrc ├── package.json └── README.md /src/components/Gaming/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /src/components/SavedVideos/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | build 4 | -------------------------------------------------------------------------------- /src/components/Navbar/index.css: -------------------------------------------------------------------------------- 1 | a { 2 | text-decoration: none; 3 | } -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | package-lock=true 3 | save-exact=true 4 | -------------------------------------------------------------------------------- /public/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naveenM6/nxtWatch/HEAD/public/img/favicon.ico -------------------------------------------------------------------------------- /public/img/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naveenM6/nxtWatch/HEAD/public/img/logo192.png -------------------------------------------------------------------------------- /public/img/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naveenM6/nxtWatch/HEAD/public/img/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/components/ContactUs/index.js: -------------------------------------------------------------------------------- 1 | const ContactUs = () =>

hi

2 | 3 | export default ContactUs 4 | -------------------------------------------------------------------------------- /src/components/ContactUs/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const DivContainer = styled.div`` 4 | -------------------------------------------------------------------------------- /src/components/Home/index.css: -------------------------------------------------------------------------------- 1 | .logo-div { 2 | margin: 10px; 3 | } 4 | .link-light { 5 | color: #000000; 6 | } 7 | 8 | .link-dark{ 9 | color: #ffffff; 10 | } -------------------------------------------------------------------------------- /src/components/Trending/index.css: -------------------------------------------------------------------------------- 1 | .logo-div { 2 | margin: 10px; 3 | } 4 | 5 | .trend-icon { 6 | color: red; 7 | } 8 | 9 | .light-icon,.dark-icon{ 10 | border-radius: 50%; 11 | padding: 5px; 12 | } -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | babel: { 3 | plugins: [ 4 | [ 5 | 'babel-plugin-styled-components', 6 | { 7 | fileName: false, 8 | }, 9 | ], 10 | ], 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/context/Theme.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const AppTheme = React.createContext({ 4 | activeTheme: 'light', 5 | savedVideos: [], 6 | addSavedVideos: () => {}, 7 | onChangeTheme: () => {}, 8 | }) 9 | 10 | export default AppTheme 11 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import '@testing-library/jest-dom' 4 | import {configure} from '@testing-library/react' 5 | import {configure as eConfigure} from 'enzyme' 6 | import Adapter from '@wojtekmaj/enzyme-adapter-react-17' 7 | 8 | eConfigure({adapter: new Adapter()}) 9 | -------------------------------------------------------------------------------- /src/components/Loader/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const LoaderEl = styled.div` 4 | height: 100vh; 5 | width: 100%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | @media (max-width: 767px) { 10 | height: 95vh; 11 | } 12 | ` 13 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import {BrowserRouter} from 'react-router-dom' 4 | 5 | import App from './App' 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | document.getElementById('root'), 14 | ) 15 | -------------------------------------------------------------------------------- /src/components/VideoCard/index.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 767px) { 2 | .react-player { 3 | width: 100% !important; 4 | height: 50vh !important; 5 | padding: 10px; 6 | } 7 | } 8 | 9 | @media (min-width: 768px) { 10 | .react-player { 11 | width: 100% !important; 12 | height: 50vh; 13 | padding: 20px; 14 | padding-bottom: 10px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 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 | .idea/ 24 | .eslintcache 25 | .vscode/ 26 | .results -------------------------------------------------------------------------------- /src/components/Loader/index.js: -------------------------------------------------------------------------------- 1 | import Loader from 'react-loader-spinner' 2 | import 'react-loader-spinner/dist/loader/css/react-spinner-loader.css' 3 | 4 | import {LoaderEl} from './styledComponents' 5 | 6 | const LoaderComp = () => ( 7 | 8 | 9 | 10 | ) 11 | 12 | export default LoaderComp 13 | -------------------------------------------------------------------------------- /src/components/Header/index.css: -------------------------------------------------------------------------------- 1 | a { 2 | text-decoration: none; 3 | } 4 | span { 5 | padding-left: 10px; 6 | } 7 | .animate:hover { 8 | animation: rotate 3s; 9 | } 10 | .blacked { 11 | color: black; 12 | } 13 | .whiter { 14 | color: white; 15 | } 16 | 17 | @keyframes rotate { 18 | 0% { 19 | transform: rotateZ(90deg); 20 | } 21 | 100% { 22 | transform: rotateZ(-90deg); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "app_short_name", 3 | "name": "App_name", 4 | "icons": [ 5 | { 6 | "src": "img/favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "img/logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "img/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 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": false, 4 | "endOfLine": "lf", 5 | "htmlWhitespaceSensitivity": "css", 6 | "insertPragma": false, 7 | "jsxBracketSameLine": false, 8 | "jsxSingleQuote": false, 9 | "printWidth": 80, 10 | "overrides": [ 11 | { 12 | "files": "*.md", 13 | "options": { 14 | "printWidth": 1000 15 | } 16 | } 17 | ], 18 | "proseWrap": "always", 19 | "quoteProps": "as-needed", 20 | "requirePragma": false, 21 | "semi": false, 22 | "singleQuote": true, 23 | "tabWidth": 2, 24 | "trailingComma": "all", 25 | "useTabs": false 26 | } 27 | -------------------------------------------------------------------------------- /src/components/NotFound/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const ImageEl = styled.img` 4 | width: 90%; 5 | @media (min-width: 768px) { 6 | width: 40%; 7 | } 8 | ` 9 | export const DivEl = styled.div` 10 | height: 100%; 11 | background-color: ${props => props.bgColor}; 12 | color: ${props => props.color}; 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: center; 16 | align-items: center; 17 | @media (max-width: 767px) { 18 | padding: 20px 20px; 19 | padding-bottom: 0; 20 | height: 100vh; 21 | overflow: hidden; 22 | } 23 | ` 24 | export const Header = styled.h1`` 25 | 26 | export const Para = styled.p`` 27 | -------------------------------------------------------------------------------- /src/components/ErrorImage/styledComponent.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const ErrorContainer = styled.div` 4 | height: 100vh; 5 | display: flex; 6 | flex-direction: column; 7 | align-items: center; 8 | justify-content: center; 9 | @media (max-width: 767px) { 10 | padding: 25px; 11 | padding-top: 50px; 12 | } 13 | ` 14 | export const ErrorImg = styled.img` 15 | width: 35%; 16 | object-fit: contain; 17 | @media (max-width: 767px) { 18 | width: 50%; 19 | } 20 | ` 21 | export const Head = styled.h1`` 22 | 23 | export const Para = styled.p`` 24 | 25 | export const Button = styled.button` 26 | background-color: #4f46e5; 27 | color: #ffffff; 28 | outline: none; 29 | border: none; 30 | cursor: pointer; 31 | padding: 10px; 32 | border-radius: 4px; 33 | ` 34 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | height: 100vh; 13 | } 14 | 15 | .main-frame-container { 16 | padding-top: 50px; 17 | } 18 | 19 | @media (min-width: 768px) { 20 | body { 21 | overflow: hidden; 22 | } 23 | .light { 24 | background-color: #ffffff; 25 | } 26 | 27 | .dark { 28 | background-color: #231f20; 29 | } 30 | .main-frame-container { 31 | display: flex; 32 | height: 100vh; 33 | } 34 | .content { 35 | overflow: auto; 36 | width: 100%; 37 | } 38 | .content::-webkit-scrollbar { 39 | display: none; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/Navbar/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const DivContainer = styled.div` 4 | width: 25%; 5 | ` 6 | 7 | export const ListContainer = styled.ul` 8 | list-style-type: none; 9 | padding: 0; 10 | @media (max-width: 767px) { 11 | position: absolute; 12 | width: 100%; 13 | height: 100vh; 14 | opacity: 1; 15 | padding: 0; 16 | display: flex; 17 | flex-direction: column; 18 | align-items: center; 19 | color: #ffffff; 20 | display: none; 21 | } 22 | ` 23 | 24 | export const ListItems = styled.li` 25 | color: ${props => props.color}; 26 | padding: 10px 0; 27 | display: flex; 28 | align-items: center; 29 | padding: 10px 25px; 30 | transition: background-color 0.5s; 31 | transform-origin: center center; 32 | 33 | :hover { 34 | background-color: ${props => props.bgColor}; 35 | color: black; 36 | .nav-icons { 37 | color: red; 38 | } 39 | } 40 | ` 41 | 42 | export const SpanEl = styled.span` 43 | padding: 0 10px; 44 | ` 45 | -------------------------------------------------------------------------------- /src/components/NotFound/index.js: -------------------------------------------------------------------------------- 1 | import {ImageEl, DivEl, Header, Para} from './styledComponents' 2 | 3 | import AppTheme from '../../context/Theme' 4 | 5 | const NotFound = () => ( 6 | 7 | {value => { 8 | const {activeTheme} = value 9 | const bgColor = activeTheme === 'light' ? '#ffffff' : '#000000' 10 | const color = activeTheme === 'light' ? '#000000' : '#ffffff' 11 | return ( 12 | 13 | {activeTheme === 'light' ? ( 14 | <> 15 | 16 | 17 | ) : ( 18 | <> 19 | 20 | 21 | )} 22 |
Page Not Found
23 | We are sorry,the page you requested could not be found. 24 |
25 | ) 26 | }} 27 |
28 | ) 29 | 30 | export default NotFound 31 | -------------------------------------------------------------------------------- /src/components/ErrorImage/index.js: -------------------------------------------------------------------------------- 1 | import {ErrorContainer, Para, Head, Button, ErrorImg} from './styledComponent' 2 | 3 | import AppTheme from '../../context/Theme' 4 | 5 | const ErrorImage = props => ( 6 | 7 | {value => { 8 | const {activeTheme} = value 9 | 10 | const refreshPage = () => { 11 | props.refresh() 12 | } 13 | 14 | return ( 15 | 16 | {activeTheme === 'light' ? ( 17 | 18 | ) : ( 19 | 20 | )} 21 | Oops! Something Went Wrong 22 | 23 | We are having some trouble to complete your request. Please try 24 | again. 25 | 26 | 27 | 28 | ) 29 | }} 30 | 31 | ) 32 | 33 | export default ErrorImage 34 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 19 | 20 | 21 | 22 | NxtWatch/NaveenM 23 | 29 | 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/Login/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const LoginContainer = styled.div` 4 | display: flex; 5 | height: 100vh; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-items: center; 9 | ` 10 | 11 | export const ShadowContainer = styled.div` 12 | padding: 40px 20px; 13 | box-shadow: 0px 0px 20px 5px #c6c9cc; 14 | @media (max-width: 767px) { 15 | width: 90%; 16 | } 17 | ` 18 | 19 | export const LoginDivContainer = styled.div` 20 | display: flex; 21 | flex-direction: ${props => (props.direction === 'row' ? 'row' : 'column')}; 22 | margin-top: 10px; 23 | align-self: center; 24 | ` 25 | 26 | export const ImageEl = styled.img` 27 | width: 60%; 28 | object-fit: contain; 29 | margin-bottom: 20px; 30 | margin-left: 20%; 31 | ` 32 | 33 | export const LoginFormContainer = styled.form`` 34 | 35 | export const LabelEl = styled.label` 36 | margin-bottom: 2px; 37 | font-weight: bold; 38 | cursor: ${props => props.cursor}; 39 | ` 40 | 41 | export const InputEl = styled.input` 42 | padding: 10px; 43 | outline: none; 44 | } 45 | ` 46 | 47 | export const ButtonEl = styled.button` 48 | color: #ffffff; 49 | background-color: blue; 50 | width: 100%; 51 | cursor: pointer; 52 | border: 0px; 53 | outline: none; 54 | padding: 10px; 55 | border-radius: 4px; 56 | margin-top: 15px; 57 | ` 58 | export const ErrorMsg = styled.p` 59 | color: red; 60 | ` 61 | -------------------------------------------------------------------------------- /src/components/Gaming/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const HomeContainer = styled.div` 4 | background-color: ${props => props.bgColor}; 5 | color: ${props => props.color}; 6 | @media (max-width: 767px) { 7 | margin-top: -25px; 8 | } 9 | ` 10 | 11 | export const HeadDiv = styled.div` 12 | @media (max-width: 767px) { 13 | margin-top: 45px; 14 | } 15 | ` 16 | 17 | export const HeaderEl = styled.h1` 18 | background-color: ${props => props.bgColor}; 19 | color: ${props => props.color}; 20 | padding: 20px 0; 21 | padding-left: 40px; 22 | display: flex; 23 | align-items: center; 24 | ` 25 | 26 | export const ListContainer = styled.ul` 27 | list-style-type: none; 28 | padding: 0; 29 | margin-bottom: 25px; 30 | @media (min-width: 768px) { 31 | display: flex; 32 | flex-direction: column; 33 | } 34 | ` 35 | 36 | export const ListItem = styled.li` 37 | margin-right: 20px; 38 | display: flex; 39 | cursor: pointer; 40 | ` 41 | 42 | export const ImageTag = styled.img` 43 | width: ${props => props.width}; 44 | object-fit: contain; 45 | @media (max-width: 767px) { 46 | width: 150px; 47 | } 48 | ` 49 | export const ContentDiv = styled.div` 50 | display: flex; 51 | flex-wrap: wrap; 52 | padding-left: 30px; 53 | @media (max-width: 767px) { 54 | justify-content: center; 55 | } 56 | ` 57 | 58 | export const ParaTag = styled.p` 59 | font-size: ${props => props.fontSize}; 60 | ` 61 | -------------------------------------------------------------------------------- /src/components/VideoCard/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const VideoContainer = styled.div` 4 | height: 100vh; 5 | width: 100%; 6 | overflow-y: auto; 7 | background-color: ${props => props.bgColor}; 8 | color: ${props => props.color}; 9 | overflow: auto; 10 | padding: 20px 5px; 11 | ` 12 | 13 | export const VideoFrameContainer = styled.div` 14 | width: 100%; 15 | overflow: auto; 16 | @media (min-width: 768px) { 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | } 21 | ` 22 | export const ParaEl = styled.p` 23 | font-size: 15px; 24 | padding-left: 20px; 25 | padding-bottom: ${props => props.padding}; 26 | @media (min-width: 768px) { 27 | align-self: flex-start; 28 | } 29 | @media (max-width: 767px) { 30 | font-size: 13px; 31 | } 32 | ` 33 | export const AttributesContainer = styled.div` 34 | display: flex; 35 | justify-content: space-between; 36 | flex-wrap: wrap; 37 | padding-bottom: 0; 38 | align-items: center; 39 | border-bottom: 2px solid ${props => props.color}; 40 | margin: 0 10px; 41 | ` 42 | export const ChannelContainer = styled.div` 43 | display: flex; 44 | padding: 10px; 45 | align-items: center; 46 | ` 47 | 48 | export const ContentContainer = styled.div`` 49 | 50 | export const ImageEl = styled.img` 51 | height: 40px; 52 | ` 53 | export const IconParas = styled.p` 54 | cursor: pointer; 55 | color: ${props => props.iconColor}; 56 | font-size: 15px; 57 | font-weight: 600; 58 | padding: 0 20px; 59 | display: flex; 60 | align-items: center; 61 | ` 62 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "parser": "babel-eslint", 7 | "extends": ["react-app", "airbnb", "prettier"], 8 | "parserOptions": { 9 | "ecmaFeatures": { 10 | "jsx": true 11 | }, 12 | "ecmaVersion": 12, 13 | "sourceType": "module" 14 | }, 15 | "plugins": ["prettier"], 16 | "overrides": [ 17 | { 18 | "files": ["styledComponents.js"], 19 | "rules": { 20 | "import/prefer-default-export": "off" 21 | } 22 | } 23 | ], 24 | "rules": { 25 | "prettier/prettier": "error", 26 | "react/jsx-filename-extension": [1, {"extensions": [".js", ".jsx"]}], 27 | "react/state-in-constructor": "off", 28 | "react/react-in-jsx-scope": "off", 29 | "react/jsx-uses-react": "off", 30 | "no-console": "off", 31 | "react/prop-types": "off", 32 | "jsx-a11y/label-has-associated-control": [ 33 | 2, 34 | { 35 | "labelAttributes": ["htmlFor"] 36 | } 37 | ], 38 | "jsx-a11y/click-events-have-key-events": 0, 39 | "jsx-a11y/no-noninteractive-element-interactions": [ 40 | "off", 41 | { 42 | "handlers": ["onClick"] 43 | } 44 | ], 45 | "react/prefer-stateless-function": [ 46 | 0, 47 | { 48 | "ignorePureComponents": true 49 | } 50 | ], 51 | "no-unused-vars": "warn", 52 | "jsx-a11y/alt-text": 1, 53 | "react/no-unused-state": "warn", 54 | "react/button-has-type": "warn", 55 | "react/no-unescaped-entities": "warn", 56 | "react/jsx-props-no-spreading": "off", 57 | "operator-assignment": ["warn", "always"], 58 | "radix": "off" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/components/SavedVideos/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const SavedVideosMainDiv = styled.div` 4 | color: ${props => props.color}; 5 | background-color: ${props => props.bgColor}; 6 | height: 100vh; 7 | @media (max-width: 767px) { 8 | height: fit-content; 9 | } 10 | ` 11 | export const MainHeader = styled.h1` 12 | background-color: ${props => props.bgColor}; 13 | padding: 20px; 14 | ` 15 | 16 | export const UnSavedVideosDiv = styled.div` 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | color: ${props => props.color}; 21 | @media (max-width: 767px) { 22 | padding-left: 20px; 23 | } 24 | ` 25 | 26 | export const SavedVideosDiv = styled.div` 27 | color: ${props => props.color}; 28 | display: flex; 29 | @media (min-width: 768px) { 30 | padding: 30px 30px; 31 | padding-bottom: 0px; 32 | } 33 | @media (max-width: 767px) { 34 | flex-direction: column; 35 | align-items: center; 36 | padding: 10px; 37 | } 38 | ` 39 | 40 | export const ListContainer = styled.ul` 41 | list-style-type: none; 42 | @media (max-width: 767px) { 43 | align-self: baseline; 44 | padding: 10px; 45 | } 46 | ` 47 | 48 | export const ListItems = styled.li` 49 | padding: 10px; 50 | font-size: ${props => props.fs}; 51 | @media (max-width: 767px) { 52 | padding: 5px; 53 | font-size: 15px; 54 | } 55 | ` 56 | 57 | export const VideosImageEl = styled.img` 58 | @media (min-width: 768px) { 59 | width: 40%; 60 | height: 200px; 61 | } 62 | @media (max-width: 767px) { 63 | width: 100%; 64 | object-fit: contain; 65 | } 66 | ` 67 | 68 | export const NoVideosImageEl = styled.img` 69 | @media (max-width: 767px) { 70 | width: 100%; 71 | object-fit: contain; 72 | padding-top: 5%; 73 | } 74 | @media (min-width: 768px) { 75 | width: 50%; 76 | object-fit: contain; 77 | padding: 30px 40px; 78 | } 79 | ` 80 | export const NotFoundHead = styled.h2`` 81 | 82 | export const NotFoundPara = styled.p`` 83 | -------------------------------------------------------------------------------- /src/components/Trending/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const HomeContainer = styled.div` 4 | background-color: ${props => props.bgColor}; 5 | color: ${props => props.color}; 6 | @media (max-width: 767px) { 7 | margin-top: -21px; 8 | } 9 | ` 10 | 11 | export const ErrorContainer = styled.div` 12 | height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | ` 18 | 19 | export const HeadDiv = styled.div` 20 | @media (max-width: 767px) { 21 | margin-top: 45px; 22 | } 23 | ` 24 | 25 | export const HeaderEl = styled.h1` 26 | background-color: ${props => props.bgColor}; 27 | color: ${props => props.color}; 28 | padding: 20px 0; 29 | padding-left: 40px; 30 | display: flex; 31 | align-items: center; 32 | ` 33 | 34 | export const ListContainer = styled.ul` 35 | list-style-type: none; 36 | padding: 0; 37 | margin-bottom: 25px; 38 | @media (min-width: 768px) { 39 | display: flex; 40 | } 41 | ` 42 | 43 | export const ListItem = styled.li` 44 | margin-right: 20px; 45 | display: flex; 46 | cursor: pointer; 47 | ` 48 | 49 | export const ImageTag = styled.img` 50 | width: ${props => props.width}; 51 | object-fit: contain; 52 | @media (max-width: 767px) { 53 | width: 100%; 54 | } 55 | ` 56 | export const LogoImage = styled.img` 57 | width: ${props => props.width}; 58 | object-fit: contain; 59 | ` 60 | 61 | export const ContentDiv = styled.div` 62 | display: flex; 63 | flex-wrap: wrap; 64 | padding-left: 30px; 65 | ` 66 | 67 | export const ParaTag = styled.p` 68 | font-size: ${props => props.fontSize}; 69 | ` 70 | export const ErrorImg = styled.img` 71 | width: 35%; 72 | object-fit: contain; 73 | ` 74 | export const Head = styled.h1`` 75 | 76 | export const Para = styled.p`` 77 | 78 | export const Button = styled.button` 79 | background-color: #4f46e5; 80 | color: #ffffff; 81 | outline: none; 82 | border: none; 83 | cursor: pointer; 84 | padding: 10px; 85 | border-radius: 4px; 86 | ` 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nxt-watch", 3 | "private": true, 4 | "version": "1.0.0", 5 | "engines": { 6 | "node": "^10.13 || 12 || 14 || 15", 7 | "npm": ">=6" 8 | }, 9 | "dependencies": { 10 | "@craco/craco": "6.1.1", 11 | "@testing-library/jest-dom": "5.11.9", 12 | "@testing-library/react": "11.2.5", 13 | "@testing-library/user-event": "12.6.2", 14 | "@wojtekmaj/enzyme-adapter-react-17": "0.6.3", 15 | "chalk": "4.1.0", 16 | "enzyme": "3.11.0", 17 | "date-fns": "2.23.0", 18 | "history": "5.0.1", 19 | "jest-styled-components": "7.0.5", 20 | "js-cookie": "3.0.0", 21 | "msw": "0.34.0", 22 | "react": "17.0.1", 23 | "react-dom": "17.0.1", 24 | "react-icons": "4.2.0", 25 | "react-loader-spinner": "4.0.0", 26 | "react-player": "2.9.0", 27 | "react-router-dom": "5.2.0", 28 | "reactjs-popup": "2.0.5", 29 | "styled-components": "5.3.0", 30 | "surge": "0.23.0" 31 | }, 32 | "devDependencies": { 33 | "babel-plugin-styled-components": "1.13.2", 34 | "eslint-config-airbnb": "18.2.1", 35 | "eslint-config-prettier": "8.1.0", 36 | "eslint-plugin-prettier": "3.3.1", 37 | "husky": "4.3.8", 38 | "lint-staged": "10.5.4", 39 | "npm-run-all": "4.1.5", 40 | "prettier": "2.2.1", 41 | "react-scripts": "4.0.3" 42 | }, 43 | "scripts": { 44 | "start": "craco start", 45 | "build": "craco build", 46 | "test": "craco test", 47 | "lint": "eslint .", 48 | "lint:fix": "eslint --fix src/", 49 | "format": "prettier --write \"./src\"", 50 | "run-all": "npm-run-all --parallel test lint:fix" 51 | }, 52 | "lint-staged": { 53 | "*.js": [ 54 | "npm run lint:fix" 55 | ], 56 | "*.{js, jsx, json, html, css}": [ 57 | "npm run format" 58 | ] 59 | }, 60 | "husky": { 61 | "hooks": { 62 | "pre-commit": "lint-staged" 63 | } 64 | }, 65 | "jest": { 66 | "collectCoverageFrom": [ 67 | "src/**/*.js" 68 | ] 69 | }, 70 | "browserslist": { 71 | "development": [ 72 | "last 2 chrome versions", 73 | "last 2 firefox versions", 74 | "last 2 edge versions" 75 | ], 76 | "production": [ 77 | ">1%", 78 | "last 4 versions", 79 | "Firefox ESR", 80 | "not ie < 11" 81 | ] 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/Navbar/index.js: -------------------------------------------------------------------------------- 1 | import {HiHome} from 'react-icons/hi' 2 | import {AiFillFire} from 'react-icons/ai' 3 | import {SiYoutubegaming} from 'react-icons/si' 4 | import {MdPlaylistAdd} from 'react-icons/md' 5 | 6 | import {Link} from 'react-router-dom' 7 | import {Component} from 'react' 8 | 9 | // import ContactUs from '../ContactUs' 10 | import AppTheme from '../../context/Theme' 11 | import { 12 | DivContainer, 13 | ListContainer, 14 | ListItems, 15 | SpanEl, 16 | } from './styledComponents' 17 | 18 | import './index.css' 19 | 20 | class Navbar extends Component { 21 | render() { 22 | return ( 23 | 24 | {value => { 25 | const {activeTheme} = value 26 | const color = activeTheme === 'light' ? '#000000' : '#ffffff' 27 | const hoverBgColor = activeTheme === 'light' ? '#616e7c' : '#475569' 28 | 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | {' '} 37 | Home 38 | 39 | 40 | 41 | 42 | 43 | 44 | {' '} 45 | Trending 46 | 47 | 48 | 49 | 50 | 51 | 52 | {' '} 53 | Gaming 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Saved videos 62 | 63 | 64 | 65 | 66 | ) 67 | }} 68 | 69 | ) 70 | } 71 | } 72 | 73 | export default Navbar 74 | -------------------------------------------------------------------------------- /src/components/Header/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled, {keyframes} from 'styled-components' 2 | 3 | const FadeIn = keyframes` 4 | 0%{ 5 | opacity:0; 6 | } 7 | 100%{ 8 | opacity:1; 9 | } 10 | ` 11 | 12 | export const HeaderContainer = styled.div` 13 | position: fixed; 14 | width: 100%; 15 | display: flex; 16 | justify-content: space-between; 17 | padding: 15px; 18 | background-color: ${props => props.bgColor}; 19 | @media (max-width: 767px) { 20 | padding-bottom: 29px; 21 | } 22 | ` 23 | 24 | export const HeaderContentsSmallContainer = styled.div` 25 | display: flex; 26 | align-items: center; 27 | @media (min-width: 768px) { 28 | display: none; 29 | } 30 | ` 31 | export const HeaderContentsLargeContainer = styled.div` 32 | display: flex; 33 | align-items: center; 34 | @media (max-width: 767px) { 35 | display: none; 36 | } 37 | ` 38 | 39 | export const ImageEl = styled.img` 40 | cursor: ${props => props.cursor}; 41 | @media (max-width: 767px) { 42 | height: ${props => props.height}; 43 | display: ${props => props.display}; 44 | } 45 | @media (min-width: 768px) { 46 | height: 30px; 47 | margin: 0px ${props => props.margin}; 48 | } 49 | ` 50 | 51 | export const ButtonElSmall = styled.button` 52 | background: none; 53 | border: none; 54 | outline: none; 55 | color: ${props => props.color}; 56 | } 57 | ` 58 | 59 | export const ButtonElLarge = styled.button` 60 | color: ${props => props.color}; 61 | border: ${props => props.border}; 62 | border-color: ${props => props.color}; 63 | background: transparent; 64 | outline: none; 65 | padding: ${props => props.padding}; 66 | cursor: pointer; 67 | ` 68 | export const ListContainer = styled.ul` 69 | list-style-type: none; 70 | display: flex; 71 | flex-direction: column; 72 | justify-content: center; 73 | position: fixed; 74 | background: ${props => props.bgColor}; 75 | width: 103%; 76 | height: 105vh; 77 | top: -16px; 78 | left: -9px; 79 | padding-left: 40%; 80 | @media (min-width: 768px) { 81 | display: none; 82 | } 83 | ` 84 | export const ExtraDiv = styled.div` 85 | display: ${props => props.display}; 86 | animation: ${FadeIn} 0.5s; 87 | ` 88 | 89 | export const ListItem = styled.li` 90 | padding: 10px 0; 91 | :hover { 92 | background-color: ${props => props.bgColor}; 93 | color: ${props => props.color}; 94 | .nav-icons { 95 | color: red; 96 | } 97 | } 98 | ` 99 | export const Para = styled.p` 100 | position: absolute; 101 | top: 45px; 102 | right: 50px; 103 | color: ${props => props.color}; 104 | ` 105 | -------------------------------------------------------------------------------- /src/components/Home/styledComponents.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const HomeContainer = styled.div` 4 | padding: 30px; 5 | background-color: ${props => props.bgColor}; 6 | color: ${props => props.color}; 7 | @media (max-width: 767px) { 8 | margin-top: 24px; 9 | padding: 30px 0 30px 20px; 10 | } 11 | ` 12 | 13 | export const HeadDiv = styled.div` 14 | border: 1px solid black; 15 | width: fit-content; 16 | border-radius: 4px; 17 | display: flex; 18 | background-color: #ffffff; 19 | align-items: center; 20 | }` 21 | 22 | export const SearchIp = styled.input` 23 | width: 300px; 24 | outline: none; 25 | padding: 5px; 26 | border: none; 27 | @media (max-width:767px){ 28 | width: 100%; 29 | } 30 | }` 31 | 32 | export const ButtonEl = styled.button` 33 | border: none; 34 | outline: none; 35 | padding: 10px; 36 | cursor: pointer; 37 | @media (max-width: 767px) { 38 | padding: 3px; 39 | } 40 | ` 41 | 42 | export const ListContainer = styled.ul` 43 | list-style-type: none; 44 | padding: 0; 45 | margin-bottom: 25px; 46 | @media (min-width: 768px) { 47 | width: 300px; 48 | height: 270px; 49 | } 50 | ` 51 | 52 | export const ListItem = styled.li` 53 | margin-right: 20px; 54 | display: flex; 55 | cursor: pointer; 56 | ` 57 | 58 | export const ImageTag = styled.img` 59 | width: ${props => props.width}; 60 | object-fit: contain; 61 | ` 62 | export const ContentDiv = styled.div` 63 | display: flex; 64 | flex-wrap: wrap; 65 | ` 66 | 67 | export const ParaTag = styled.p` 68 | font-size: ${props => props.fontSize}; 69 | ` 70 | 71 | export const NoResults = styled.div` 72 | display: flex; 73 | flex-direction: column; 74 | align-items: center; 75 | width: 100%; 76 | height: 100vh; 77 | @media (max-width: 767px) { 78 | justify-content: center; 79 | } 80 | ` 81 | 82 | export const NoVideosImage = styled.img` 83 | width: 30%; 84 | object-fit: contain; 85 | @media (max-width: 767px) { 86 | width: 100%; 87 | } 88 | @media (min-width: 768px) { 89 | padding-top: 30px; 90 | } 91 | ` 92 | export const NoResultsHeading = styled.h1` 93 | @media (max-width: 767px) { 94 | font-size: large; 95 | } 96 | ` 97 | 98 | export const NoResultsPara = styled.p` 99 | @media (max-width: 767px) { 100 | font-size: medium; 101 | } 102 | ` 103 | 104 | export const NoResultsButton = styled.button` 105 | background-color: #4f46e5; 106 | color: #ffffff; 107 | outline: none; 108 | border: none; 109 | cursor: pointer; 110 | padding: 10px; 111 | border-radius: 4px; 112 | ` 113 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import {Switch, Route} from 'react-router-dom' 3 | import './App.css' 4 | 5 | import AppTheme from './context/Theme' 6 | import Login from './components/Login' 7 | import Header from './components/Header' 8 | import Navbar from './components/Navbar' 9 | import Home from './components/Home' 10 | import Trending from './components/Trending' 11 | import Gaming from './components/Gaming' 12 | import SavedVideos from './components/SavedVideos' 13 | import VideoCard from './components/VideoCard' 14 | import NotFound from './components/NotFound' 15 | 16 | class App extends Component { 17 | state = {activeTheme: 'light', savedVideos: []} 18 | 19 | changeTheme = activeTheme => { 20 | this.setState({activeTheme}) 21 | } 22 | 23 | addSavedVideos = async data => { 24 | const {savedVideos} = this.state 25 | if (savedVideos.length > 0) { 26 | const checkSavedVideos = savedVideos.filter(item => item.id === data.id) 27 | if (checkSavedVideos.length === 0) { 28 | await this.setState({ 29 | savedVideos: [...savedVideos, data], 30 | }) 31 | } 32 | } else { 33 | await this.setState({ 34 | savedVideos: [...savedVideos, data], 35 | }) 36 | } 37 | } 38 | 39 | render() { 40 | const {activeTheme, savedVideos} = this.state 41 | const bgColor = activeTheme === 'light' ? 'light' : 'dark' 42 | 43 | return ( 44 | 52 | <> 53 |
54 | 55 | 56 | <> 57 |
58 |
59 | 60 |
61 | 62 | 63 | 64 | 65 | 70 | 71 | 72 | 73 |
74 |
75 | 76 | 77 |
78 | 79 |
80 | ) 81 | } 82 | } 83 | 84 | export default App 85 | -------------------------------------------------------------------------------- /src/components/SavedVideos/index.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import {Link, Redirect} from 'react-router-dom' 3 | import Cookies from 'js-cookie' 4 | 5 | import {MdPlaylistAdd} from 'react-icons/md' 6 | 7 | import AppTheme from '../../context/Theme' 8 | 9 | import { 10 | SavedVideosMainDiv, 11 | UnSavedVideosDiv, 12 | SavedVideosDiv, 13 | NotFoundHead, 14 | NotFoundPara, 15 | NoVideosImageEl, 16 | VideosImageEl, 17 | ListContainer, 18 | ListItems, 19 | MainHeader, 20 | } from './styledComponents' 21 | 22 | class SavedVideos extends Component { 23 | render() { 24 | const jwtToken = Cookies.get('jwt_token') 25 | if (jwtToken === undefined) { 26 | return 27 | } 28 | 29 | return ( 30 | 31 | {values => { 32 | const {activeTheme, savedVideos} = values 33 | const bgColor = activeTheme === 'light' ? '#ffffff' : '#000000' 34 | const color = activeTheme === 'light' ? '#000000' : '#ffffff' 35 | return ( 36 | 37 | {savedVideos.length === 0 ? ( 38 | 39 | 43 | No saved videos found 44 | 45 | You can save your videos while watching them 46 | 47 | 48 | ) : ( 49 | <> 50 | 53 | Saved Videos 54 | 55 | {savedVideos.map(data => ( 56 | 63 | 64 | 68 | 69 | {data.title} 70 | {data.channel.name} 71 | 72 | {data.viewCount} Views . {data.publishedAt} 73 | 74 | 75 | 76 | 77 | ))} 78 | 79 | )} 80 | 81 | ) 82 | }} 83 | 84 | ) 85 | } 86 | } 87 | 88 | export default SavedVideos 89 | -------------------------------------------------------------------------------- /src/components/Login/index.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import Cookies from 'js-cookie' 3 | 4 | import {Redirect} from 'react-router-dom' 5 | 6 | import { 7 | LoginContainer, 8 | ShadowContainer, 9 | LoginDivContainer, 10 | ImageEl, 11 | LoginFormContainer, 12 | InputEl, 13 | LabelEl, 14 | ButtonEl, 15 | ErrorMsg, 16 | } from './styledComponents' 17 | 18 | class Login extends Component { 19 | state = { 20 | visibility: false, 21 | username: '', 22 | password: '', 23 | showSubmitError: false, 24 | errorMsg: '', 25 | } 26 | 27 | showPassword = event => { 28 | if (event.target.checked) { 29 | this.setState({visibility: true}) 30 | } else { 31 | this.setState({visibility: false}) 32 | } 33 | } 34 | 35 | onChangeUsername = e => { 36 | this.setState({username: e.target.value}) 37 | } 38 | 39 | onChangePassword = e => { 40 | this.setState({password: e.target.value}) 41 | } 42 | 43 | onSubmitSuccess = jwtToken => { 44 | const {history} = this.props 45 | Cookies.set('jwt_token', jwtToken, { 46 | expires: 30, 47 | path: '/', 48 | }) 49 | 50 | history.replace('/') 51 | } 52 | 53 | onSubmitFailure = errorMsg => { 54 | this.setState({showSubmitError: true, errorMsg}) 55 | } 56 | 57 | formSubmit = async event => { 58 | event.preventDefault() 59 | const {username, password} = this.state 60 | const url = 'https://apis.ccbp.in/login' 61 | const options = { 62 | method: 'POST', 63 | body: JSON.stringify({username, password}), 64 | } 65 | const response = await fetch(url, options) 66 | const data = await response.json() 67 | if (response.ok === true) { 68 | this.onSubmitSuccess(data.jwt_token) 69 | } else { 70 | this.onSubmitFailure(data.error_msg) 71 | } 72 | } 73 | 74 | render() { 75 | const { 76 | visibility, 77 | username, 78 | password, 79 | showSubmitError, 80 | errorMsg, 81 | } = this.state 82 | 83 | const jwtToken = Cookies.get('jwt_token') 84 | if (jwtToken !== undefined) { 85 | return 86 | } 87 | 88 | return ( 89 | 90 | 91 | 95 | 96 | 97 | USERNAME 98 | 104 | 105 | 106 | PASSWORD 107 | 113 | 114 | 115 | 120 | 121 | Show Password 122 | 123 | 124 | Login 125 | {showSubmitError && ( 126 | *{errorMsg} 127 | )} 128 | 129 | 130 | 131 | ) 132 | } 133 | } 134 | 135 | export default Login 136 | -------------------------------------------------------------------------------- /src/components/Gaming/index.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import Cookies from 'js-cookie' 3 | import {SiYoutubegaming} from 'react-icons/si' 4 | import {Link, Redirect} from 'react-router-dom' 5 | import LoaderComp from '../Loader' 6 | 7 | import './index.css' 8 | 9 | import AppTheme from '../../context/Theme' 10 | 11 | import { 12 | HomeContainer, 13 | ListContainer, 14 | ListItem, 15 | ContentDiv, 16 | ImageTag, 17 | ParaTag, 18 | HeadDiv, 19 | HeaderEl, 20 | } from './styledComponents' 21 | 22 | import ErrorImage from '../ErrorImage' 23 | 24 | class Gaming extends Component { 25 | state = {dataArray: [], isLoading: true, status: ''} 26 | 27 | componentDidMount() { 28 | this.getVideos() 29 | } 30 | 31 | getVideos = async () => { 32 | const jwtToken = Cookies.get('jwt_token') 33 | const url = 'https://apis.ccbp.in/videos/gaming' 34 | const options = { 35 | headers: { 36 | Authorization: `Bearer ${jwtToken}`, 37 | }, 38 | method: 'GET', 39 | } 40 | try { 41 | const response = await fetch(url, options) 42 | if (response.ok) { 43 | const data = await response.json() 44 | await this.setState({dataArray: data.videos, status: true}) 45 | } 46 | } catch { 47 | await this.setState({status: false}) 48 | } 49 | this.setState({isLoading: false}) 50 | } 51 | 52 | render() { 53 | const {dataArray, isLoading, status} = this.state 54 | const jwtToken = Cookies.get('jwt_token') 55 | if (jwtToken === undefined) { 56 | return 57 | } 58 | return ( 59 | 60 | {value => { 61 | const {activeTheme} = value 62 | 63 | const color = activeTheme === 'light' ? '#000000' : '#ffffff' 64 | const bgColor = activeTheme === 'light' ? '#f9f9f9' : '#000000' 65 | 66 | return ( 67 | 72 | {isLoading ? ( 73 | 74 | ) : ( 75 | <> 76 | {status ? ( 77 | <> 78 | 79 | 85 | {' '} 89 | Gaming 90 | 91 | 92 | 93 | {dataArray.map(item => ( 94 | 103 | 104 | 105 | 110 | 111 | 112 | {item.title} 113 | 114 | 115 | 116 | {item.view_count} Watching Worldwide 117 | 118 | 119 | 120 | 121 | ))} 122 | 123 | 124 | ) : ( 125 | 126 | )} 127 | 128 | )} 129 | 130 | ) 131 | }} 132 | 133 | ) 134 | } 135 | } 136 | 137 | export default Gaming 138 | -------------------------------------------------------------------------------- /src/components/Trending/index.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import Cookies from 'js-cookie' 3 | import {Link, Redirect} from 'react-router-dom' 4 | 5 | import {AiFillFire} from 'react-icons/ai' 6 | 7 | import LoaderComp from '../Loader' 8 | 9 | import AppTheme from '../../context/Theme' 10 | 11 | import './index.css' 12 | 13 | import { 14 | HomeContainer, 15 | ListContainer, 16 | ListItem, 17 | ImageTag, 18 | LogoImage, 19 | HeadDiv, 20 | HeaderEl, 21 | ContentDiv, 22 | ParaTag, 23 | } from './styledComponents' 24 | 25 | import ErrorImage from '../ErrorImage' 26 | 27 | class Trending extends Component { 28 | state = {dataArray: [], isLoading: true, status: ''} 29 | 30 | componentDidMount() { 31 | this.getVideos() 32 | } 33 | 34 | getVideos = async () => { 35 | this.setState({isLoading: true}) 36 | const jwtToken = Cookies.get('jwt_token') 37 | const url = 'https://apis.ccbp.in/videos/trending' 38 | const options = { 39 | headers: { 40 | Authorization: `Bearer ${jwtToken}`, 41 | }, 42 | method: 'GET', 43 | } 44 | try { 45 | const response = await fetch(url, options) 46 | if (response.ok) { 47 | const data = await response.json() 48 | await this.setState({dataArray: data.videos, status: true}) 49 | } 50 | } catch { 51 | await this.setState({status: false}) 52 | } 53 | this.setState({isLoading: false}) 54 | } 55 | 56 | render() { 57 | const {dataArray, isLoading, status} = this.state 58 | const jwtToken = Cookies.get('jwt_token') 59 | if (jwtToken === undefined) { 60 | return 61 | } 62 | return ( 63 | 64 | {value => { 65 | const {activeTheme} = value 66 | 67 | const color = activeTheme === 'light' ? '#000000' : '#ffffff' 68 | const bgColor = activeTheme === 'light' ? '#f9f9f9' : '#000000' 69 | 70 | return ( 71 | 72 | {isLoading ? ( 73 | 74 | ) : ( 75 | <> 76 | {status ? ( 77 | <> 78 | 79 | 85 | {' '} 89 | Trending 90 | 91 | 92 | 93 | {dataArray.map(item => ( 94 | 103 | 104 | 105 | 109 | 110 | 111 |
112 | 116 |
117 |
118 | 119 | {item.title} 120 | 121 | 122 | {item.channel.name} 123 | 124 | 125 | {item.view_count} views .{' '} 126 | {item.published_at} 127 | 128 |
129 |
130 |
131 | 132 | ))} 133 |
134 | 135 | ) : ( 136 | 137 | )} 138 | 139 | )} 140 |
141 | ) 142 | }} 143 |
144 | ) 145 | } 146 | } 147 | 148 | export default Trending 149 | -------------------------------------------------------------------------------- /src/components/VideoCard/index.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import Cookies from 'js-cookie' 3 | import ReactPlayer from 'react-player' 4 | 5 | import {AiOutlineLike, AiOutlineDislike} from 'react-icons/ai' 6 | import {MdPlaylistAdd} from 'react-icons/md' 7 | 8 | import { 9 | VideoFrameContainer, 10 | VideoContainer, 11 | ParaEl, 12 | AttributesContainer, 13 | ChannelContainer, 14 | ImageEl, 15 | ContentContainer, 16 | IconParas, 17 | } from './styledComponents' 18 | 19 | import AppTheme from '../../context/Theme' 20 | 21 | import './index.css' 22 | 23 | class VideoCard extends Component { 24 | state = { 25 | videoDetails: {}, 26 | channelDataObj: {}, 27 | liked: false, 28 | disliked: false, 29 | saved: false, 30 | } 31 | 32 | componentDidMount() { 33 | this.getData() 34 | } 35 | 36 | componentWillUnmount() { 37 | this.mounted = false 38 | } 39 | 40 | getData = async () => { 41 | this.mounted = true 42 | const {match} = this.props 43 | const {params} = match 44 | const {id} = params 45 | const jwtToken = Cookies.get('jwt_token') 46 | const url = `https://apis.ccbp.in/videos/${id}` 47 | const options = { 48 | headers: { 49 | Authorization: `Bearer ${jwtToken}`, 50 | }, 51 | method: 'GET', 52 | } 53 | const response = await fetch(url, options) 54 | if (response.ok) { 55 | const responseData = await response.json() 56 | const data = responseData.video_details 57 | const convertedData = { 58 | channel: data.channel, 59 | description: data.description, 60 | id: data.id, 61 | publishedAt: data.published_at, 62 | thumbnailUrl: data.thumbnail_url, 63 | title: data.title, 64 | videoUrl: data.video_url, 65 | viewCount: data.view_count, 66 | } 67 | const channelData = { 68 | name: data.channel.name, 69 | profileImageUrl: data.channel.profile_image_url, 70 | subscriberCount: data.channel.subscriber_count, 71 | } 72 | if (this.mounted) { 73 | await this.setState({ 74 | videoDetails: convertedData, 75 | channelDataObj: channelData, 76 | }) 77 | } 78 | } 79 | } 80 | 81 | isDisliked = () => { 82 | const {liked, disliked} = this.state 83 | if (liked) { 84 | this.setState({liked: false}) 85 | } 86 | if (disliked) { 87 | this.setState({disliked: false}) 88 | } else { 89 | this.setState({disliked: true}) 90 | } 91 | } 92 | 93 | isLiked = () => { 94 | const {liked, disliked} = this.state 95 | if (disliked) { 96 | this.setState({disliked: false}) 97 | } 98 | if (liked) { 99 | this.setState({liked: false}) 100 | } else { 101 | this.setState({liked: true}) 102 | } 103 | } 104 | 105 | isSaved = async () => { 106 | const {saved} = this.state 107 | if (saved) { 108 | await this.setState({saved: false}) 109 | } else { 110 | await this.setState({saved: true}) 111 | } 112 | } 113 | 114 | render() { 115 | const {videoDetails, channelDataObj, liked, disliked, saved} = this.state 116 | const {videoUrl, title, viewCount, publishedAt, description} = videoDetails 117 | return ( 118 | 119 | {values => { 120 | const {activeTheme, addSavedVideos} = values 121 | const bgColor = activeTheme === 'light' ? '#ffffff' : '#000000' 122 | const color = activeTheme === 'light' ? '#000000' : '#ffffff' 123 | 124 | const onSave = () => { 125 | this.isSaved() 126 | addSavedVideos(videoDetails) 127 | } 128 | 129 | return ( 130 | 131 | 132 | 133 | 134 | {title} 135 | 136 | 137 | 138 | 139 | {viewCount} views . {publishedAt} 140 | 141 | 142 | 146 | Like 147 | 148 | 152 | Dislike 153 | 154 | 158 | {saved ? 'Saved' : 'Save'} 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | {channelDataObj.name} 167 | 168 | {channelDataObj.subscriberCount} 169 | 170 | 171 | {description} 172 | 173 | ) 174 | }} 175 | 176 | ) 177 | } 178 | } 179 | 180 | export default VideoCard 181 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import {HiHome} from 'react-icons/hi' 3 | import {AiFillFire} from 'react-icons/ai' 4 | import {SiYoutubegaming} from 'react-icons/si' 5 | import {MdPlaylistAdd} from 'react-icons/md' 6 | import {BsMoon, BsBrightnessHigh} from 'react-icons/bs' 7 | import {GiHamburgerMenu} from 'react-icons/gi' 8 | import {FiLogOut} from 'react-icons/fi' 9 | import {ImCross} from 'react-icons/im' 10 | 11 | import {Link, withRouter} from 'react-router-dom' 12 | import Cookies from 'js-cookie' 13 | 14 | import './index.css' 15 | 16 | import AppTheme from '../../context/Theme' 17 | 18 | import { 19 | HeaderContainer, 20 | HeaderContentsSmallContainer, 21 | HeaderContentsLargeContainer, 22 | ImageEl, 23 | ButtonElSmall, 24 | ButtonElLarge, 25 | ListContainer, 26 | ListItem, 27 | Para, 28 | ExtraDiv, 29 | } from './styledComponents' 30 | 31 | class Header extends Component { 32 | state = {displayHeader: 'none'} 33 | 34 | showHeader = () => { 35 | this.setState({displayHeader: 'block'}) 36 | } 37 | 38 | hideHeader = () => { 39 | this.setState({displayHeader: 'none'}) 40 | } 41 | 42 | logOut = () => { 43 | const {history} = this.props 44 | Cookies.remove('jwt_token') 45 | history.replace('/login') 46 | } 47 | 48 | onClickLogo = () => { 49 | const {history} = this.props 50 | history.replace('/') 51 | } 52 | 53 | render() { 54 | const {displayHeader} = this.state 55 | return ( 56 | 57 | {value => { 58 | const {activeTheme, changeTheme} = value 59 | const color = activeTheme === 'light' ? '#000000' : '#ffffff' 60 | const bgColor = activeTheme === 'light' ? '#ffffff' : '#231f20' 61 | const navColor = activeTheme === 'light' ? 'blacked' : 'whiter' 62 | const onChangeTheme = () => { 63 | const val = activeTheme === 'light' ? 'dark' : 'light' 64 | changeTheme(val) 65 | } 66 | 67 | return ( 68 | 69 | 81 | 82 | 83 | {activeTheme === 'light' ? ( 84 | 85 | ) : ( 86 | 87 | )} 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 100 | 101 | 104 | 105 | 106 | 107 | Home 108 | 109 | 110 | 111 | 112 | Trending 113 | 114 | 115 | 116 | 117 | {' '} 118 | Gaming 119 | 120 | 121 | 122 | 123 | 124 | Saved Videos 125 | 126 | 127 | 128 | 129 | 130 | 135 | {activeTheme === 'light' ? ( 136 | 137 | ) : ( 138 | 139 | )} 140 | 141 | 146 | 151 | Logout 152 | 153 | 154 | 155 | ) 156 | }} 157 | 158 | ) 159 | } 160 | } 161 | 162 | export default withRouter(Header) 163 | -------------------------------------------------------------------------------- /src/components/Home/index.js: -------------------------------------------------------------------------------- 1 | import {Component} from 'react' 2 | import Cookies from 'js-cookie' 3 | import {Link, Redirect} from 'react-router-dom' 4 | import {AiOutlineSearch} from 'react-icons/ai' 5 | import LoaderComp from '../Loader' 6 | 7 | import './index.css' 8 | 9 | import AppTheme from '../../context/Theme' 10 | 11 | import ErrorImage from '../ErrorImage' 12 | 13 | import { 14 | HomeContainer, 15 | HeadDiv, 16 | SearchIp, 17 | ButtonEl, 18 | ListContainer, 19 | ListItem, 20 | ImageTag, 21 | ContentDiv, 22 | ParaTag, 23 | NoVideosImage, 24 | NoResults, 25 | NoResultsHeading, 26 | NoResultsPara, 27 | NoResultsButton, 28 | } from './styledComponents' 29 | 30 | class Home extends Component { 31 | state = {dataArray: [], isLoading: true, status: '', searchIp: ''} 32 | 33 | componentDidMount() { 34 | this.getVideos() 35 | } 36 | 37 | getVideos = async (searchVal = '') => { 38 | const jwtToken = Cookies.get('jwt_token') 39 | const url = `https://apis.ccbp.in/videos/all?search=${searchVal}` 40 | const options = { 41 | headers: { 42 | Authorization: `Bearer ${jwtToken}`, 43 | }, 44 | method: 'GET', 45 | } 46 | try { 47 | const response = await fetch(url, options) 48 | if (response.ok) { 49 | const data = await response.json() 50 | await this.setState({dataArray: data.videos, status: true}) 51 | } 52 | } catch { 53 | this.setState({status: false}) 54 | } 55 | this.setState({isLoading: false}) 56 | } 57 | 58 | onChange = e => { 59 | this.setState({searchIp: e.target.value}) 60 | } 61 | 62 | onSearch = () => { 63 | const {searchIp} = this.state 64 | this.getVideos(searchIp) 65 | } 66 | 67 | onKey = e => { 68 | if (e.key.toLowerCase() === 'enter') { 69 | this.onSearch() 70 | } 71 | } 72 | 73 | retry = () => { 74 | this.onSearch() 75 | } 76 | 77 | render() { 78 | const {dataArray, isLoading, status, searchIp} = this.state 79 | 80 | const jwtToken = Cookies.get('jwt_token') 81 | if (jwtToken === undefined) { 82 | return 83 | } 84 | 85 | return ( 86 | 87 | {value => { 88 | const {activeTheme} = value 89 | const color = activeTheme === 'light' ? '#000000' : '#ffffff' 90 | const bgColor = activeTheme === 'light' ? '#f9f9f9' : '#000000' 91 | 92 | return ( 93 | 94 | {isLoading ? ( 95 | 96 | ) : ( 97 | <> 98 | {status ? ( 99 | <> 100 | 101 | 108 | 109 | 110 | 111 | 112 | <> 113 | {dataArray.length === 0 ? ( 114 | 115 | 119 | 120 | No Search results found 121 | 122 | 123 | Try different keywords or remove search filter 124 | 125 | 126 | Retry 127 | 128 | 129 | ) : ( 130 | 131 | {dataArray.map(item => ( 132 | 141 | 142 | 143 | 147 | 148 | 149 |
150 | 154 |
155 |
156 | 157 | {item.title} 158 | 159 | 160 | {item.channel.name} 161 | 162 | 163 | {item.view_count} views .{' '} 164 | {item.published_at} 165 | 166 |
167 |
168 |
169 | 170 | ))} 171 |
172 | )} 173 | 174 | 175 | ) : ( 176 | 177 | )} 178 | 179 | )} 180 |
181 | ) 182 | }} 183 |
184 | ) 185 | } 186 | } 187 | 188 | export default Home 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | In this assignment let's build an **Nxt Watch** by applying the concepts we have learned till now. 2 | 3 | ### Refer to videos below: 4 | 5 | **Success View**
6 | 7 |
8 | 11 |
12 |
13 | 14 | **Failure View**
15 | 16 |
17 | 20 |
21 |
22 | 23 | ### Design Files 24 | 25 |
26 | Login Route 27 | 28 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Login - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-login-light-theme-sm-output.png) 29 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Login - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-login-dark-theme-sm-output.png) 30 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Login Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-login-failure-light-theme-sm-output.png) 31 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Login Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-login-failure-dark-theme-sm-output.png) 32 | 33 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Login - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-login-light-theme-lg-output.png) 34 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Login - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-login-dark-theme-lg-output.png) 35 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Login Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-login-failure-light-theme-lg-output.png) 36 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Login Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-login-failure-dark-theme-lg-output-v0.png) 37 | 38 |
39 | 40 |
41 | Home Route 42 | 43 | - [Extra Small (Size < 576px) - Home - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-success-light-theme-xs-output.png) 44 | - [Extra Small (Size < 576px) - Home - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-success-dark-theme-xs-output.png) 45 | 46 | - [Small (Size >= 576px) - Home - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-success-light-theme-sm-output.png) 47 | - [Small (Size >= 576px) - Home - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-success-dark-theme-sm-output.png) 48 | 49 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Home - No search results - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-no-videos-light-theme-sm-output.png) 50 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Home - No search results - Dark theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-no-videos-dark-theme-sm-output.png) 51 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Home Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-failure-light-theme-sm-output.png) 52 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Home Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-failure-dark-theme-sm-output.png) 53 | 54 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Home - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-success-light-theme-lg-output.png) 55 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Home - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-success-dark-theme-lg-output.png) 56 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Home - No search results - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-no-videos-light-theme-lg-output.png) 57 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Home - No search results - Dark theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-no-videos-dark-theme-lg-output.png) 58 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Home Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-failure-light-theme-lg-output.png) 59 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Home Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-failure-dark-theme-lg-output.png) 60 | 61 |
62 | 63 |
64 | Trending Route 65 | 66 | - [Extra Small (Size < 576px) - Trending - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-success-light-theme-xs-output.png) 67 | - [Extra Small (Size < 576px) - Trending - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-success-dark-theme-xs-output.png) 68 | 69 | - [Small (Size >= 576px) - Trending - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-success-light-theme-sm-output.png) 70 | - [Small (Size >= 576px) - Trending - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-success-dark-theme-sm-output.png) 71 | 72 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Trending Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-failure-light-theme-sm-output.png) 73 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Trending Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-failure-dark-theme-sm-output.png) 74 | 75 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Trending - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-success-light-theme-lg-output.png) 76 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Trending - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-success-dark-theme-lg-output.png) 77 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Trending Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-failure-light-theme-lg-output.png) 78 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Trending Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-failure-dark-theme-lg-output.png) 79 | 80 |
81 | 82 |
83 | Gaming Route 84 | 85 | - [Extra Small (Size < 576px) - Gaming - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-success-light-theme-xs-output.png) 86 | - [Extra Small (Size < 576px) - Gaming - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-success-dark-theme-xs-output.png) 87 | 88 | - [Small (Size >= 576px) - Gaming - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-success-light-theme-sm-output.png) 89 | - [Small (Size >= 576px) - Gaming - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-success-dark-theme-sm-output.png) 90 | 91 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Gaming Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-failure-light-theme-sm-output.png) 92 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Gaming Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-failure-dark-theme-sm-output.png) 93 | 94 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Gaming - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-success-light-theme-lg-output.png) 95 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Gaming - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-success-dark-theme-lg-output.png) 96 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Gaming Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-failure-light-theme-lg-output.png) 97 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Gaming Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-failure-dark-theme-lg-output.png) 98 | 99 |
100 | 101 |
102 | Video Item Details Route 103 | 104 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - VideoItemDetails - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-success-light-theme-sm-output.png) 105 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - VideoItemDetails - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-success-dark-theme-sm-output.png) 106 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - VideoItemDetails Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-failure-light-theme-sm-output.png) 107 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - VideoItemDetails Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-failure-dark-theme-sm-output.png) 108 | 109 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - VideoItemDetails - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-success-light-theme-lg-output.png) 110 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - VideoItemDetails - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-success-dark-theme-lg-output.png) 111 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - VideoItemDetails Failure - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-failure-light-theme-lg-output.png) 112 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - VideoItemDetails Failure - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-failure-dark-theme-lg-output.png) 113 | 114 |
115 | 116 |
117 | SavedVideos Route 118 | 119 | - [Extra Small (Size < 576px) - No SavedVideos - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-no-saved-videos-light-theme-sm-output.png) 120 | - [Extra Small (Size < 576px) - No SavedVideos - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-no-saved-videos-dark-theme-sm-output.png) 121 | 122 | - [Small (Size >= 576px) - SavedVideos - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-saved-videos-light-theme-sm-output.png) 123 | - [Small (Size >= 576px) - SavedVideos - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-saved-videos-dark-theme-sm-output.png) 124 | 125 | - [Extra Small (Size < 576px) - SavedVideos - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-saved-videos-light-theme-xs-output.png) 126 | - [Extra Small (Size < 576px) - SavedVideos - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-saved-videos-dark-theme-xs-output.png) 127 | 128 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - No SavedVideos - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-no-saved-videos-light-theme-lg-output.png) 129 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - No SavedVideos - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-no-saved-videos-dark-theme-lg-output.png) 130 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - SavedVideos - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-saved-videos-light-theme-lg-output.png) 131 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - SavedVideos - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-saved-videos-dark-theme-lg-output.png) 132 | 133 |
134 | 135 |
136 | Popup Design Files 137 | 138 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Logout Popup - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-logout-popup-light-theme-sm-output.png) 139 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Logout Popup - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-logout-popup-dark-theme-sm-output.png) 140 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Menu - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-menu-popup-light-theme-sm-output.png) 141 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Menu - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-menu-popup-dark-theme-sm-output.png) 142 | 143 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Logout Popup - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-logout-popup-light-theme-lg-output.png) 144 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Logout Popup - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-logout-popup-dark-theme-lg-output.png) 145 | 146 |
147 | 148 |
149 | Not Found Route 150 | 151 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Not Found - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-page-not-found-light-theme-sm-output.png) 152 | - [Extra Small (Size < 576px) and Small (Size >= 576px) - Not Found - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-page-not-found-dark-theme-sm-output.png) 153 | 154 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Not Found - Light Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-page-not-found-light-theme-lg-output.png) 155 | - [Medium (Size >= 768px), Large (Size >= 992px) and Extra Large (Size >= 1200px) - Not Found - Dark Theme](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-page-not-found-dark-theme-lg-output.png) 156 | 157 |
158 | 159 | ### Set Up Instructions 160 | 161 |
162 | Click to view 163 | 164 | - Download dependencies by running `npm install` 165 | - Start up the app using `npm start` 166 |
167 | 168 | ### Completion Instructions 169 | 170 |
171 | Functionality to be added 172 |
173 | 174 | The app must have the following functionalities 175 | 176 | - Initially, the app should be in **light** theme 177 | 178 | - **Login Route** 179 | 180 | - When a invalid username and password are provided and the Login button is clicked, then the respective error message received from the response should be displayed 181 | - When a valid username and password are provided and the Login button is clicked, then the page should be navigated to the **Home** route 182 | - When an _unauthenticated_ user, tries to access the `HomeRoute`, `TrendingRoute`, `GamingRoute`, `SavedVideosRoute`, `VideoDetailsRoute`, then the page should be navigated to **Login** route 183 | - When an _authenticated_ user, tries to access the `HomeRoute`, `TrendingRoute`, `GamingRoute`, `SavedVideosRoute`, `VideoDetailsRoute`, then the page should be navigated to the respective route 184 | - When an authenticated user tries to access the `LoginRoute`, then the page should be navigated to the **Home** route 185 | - When show password checkbox is checked, then the password should be shown 186 | - When show password checkbox is unchecked, then the password should be masked 187 | 188 | - **Home Route** 189 | 190 | - When an authenticated user opens the **Home** Route, 191 | - An HTTP GET request should be made to **homeVideosApiUrl** with query parameter as `search` and its initial value as empty string 192 | - **_Loader_** should be displayed while the HTTP request is fetching the data 193 | - After the data is fetched successfully, display the list of videos received in the response 194 | - If the HTTP GET request made is unsuccessful, then the [Failure view](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-failure-light-theme-lg-output.png) should be displayed 195 | - When the **Retry** button is clicked, an HTTP GET request should be made to **homeVideosApiUrl** 196 | - When a non-empty value is provided in the Search Input and button with search icon is clicked 197 | - Make an HTTP GET request to the **homeVideosApiUrl** with `jwt_token` in the Cookies and query parameter `search` with value as the text provided in the Search Input 198 | - **_Loader_** should be displayed while the HTTP request is fetching the data 199 | - After the data is fetched successfully, display the list of videos received in the response 200 | - When the HTTP GET request made to the **homeVideosApiUrl** returns an empty list for videos then [No Videos View](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-home-no-videos-light-theme-lg-output.png) should be displayed 201 | - When the **website logo** image is clicked, the page should be navigated to the **Home** route 202 | - When a **Video** is clicked, the page should be navigated to the **Video Item Details** route 203 | - Clicks on the **Trending** link in the Sidebar is clicked, then the page should be navigated to the **Trending** route 204 | - Clicks on the **Gaming** link in the Sidebar is clicked, then the page should be navigated to the **Gaming** route 205 | - Clicks on the **Saved Videos** link in the Sidebar is clicked, then the page should be navigated to the **SavedVideos** route 206 | 207 | - **Trending Route** 208 | 209 | - When an authenticated user opens the **Trending** Route, 210 | - An HTTP GET request should be made to **trendingVideosApiUrl** 211 | - **_Loader_** should be displayed while the HTTP request is fetching the data 212 | - After the data is fetched successfully, display the list of videos received in the response 213 | - If the HTTP GET request made is unsuccessful, then the [Failure view](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-trending-failure-light-theme-lg-output.png) should be displayed 214 | - When the **Retry** button is clicked, an HTTP GET request should be made to **trendingVideosApiUrl** 215 | - When the **website logo** image is clicked, the page should be navigated to the **Home** route 216 | - When a **Video** is clicked, the page should be navigated to the **Video Item Details** route 217 | - Clicks on the **Home** link in the Sidebar is clicked, then the page should be navigated to the **Home** route 218 | - Clicks on the **Gaming** link in the Sidebar is clicked, then the page should be navigated to the **Gaming** route 219 | - Clicks on the **Saved Videos** link in the Sidebar is clicked, then the page should be navigated to the **SavedVideos** route 220 | 221 | - **Gaming Route** 222 | 223 | - When an authenticated user opens the **Gaming** Route, 224 | - An HTTP GET request should be made to **gamingVideosApiUrl** 225 | - **_Loader_** should be displayed while the HTTP request is fetching the data 226 | - After the data is fetched successfully, display the list of videos received in the response 227 | - If the HTTP GET request made is unsuccessful, then the [Failure view](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-gaming-failure-light-theme-lg-output.png) should be displayed 228 | - When the **Retry** button is clicked, an HTTP GET request should be made to **gamingVideosApiUrl** 229 | - When the **website logo** image is clicked, the page should be navigated to the **Home** route 230 | - When a **Video** is clicked, the page should be navigated to the **Video Item Details** route 231 | - Clicks on the **Home** link in the Sidebar is clicked, then the page should be navigated to the **Home** route 232 | - Clicks on the **Trending** link in the Sidebar is clicked, then the page should be navigated to the **Trending** route 233 | - Clicks on the **Saved Videos** link in the Sidebar is clicked, then the page should be navigated to the **SavedVideos** route 234 | 235 | - **Video Item Details Route** 236 | 237 | - When an authenticated user opens the **Video Item Details** route 238 | - An HTTP GET request should be made to **videoItemDetailsApiUrl** with `jwt_token` in the Cookies and `video_id` as path parameter 239 | - **_loader_** should be displayed while the HTTP request is fetching the data 240 | - After the HTTP request is successful, the response received should be displayed 241 | - If the HTTP GET request made is unsuccessful, then the [Failure view](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-video-item-details-failure-light-theme-lg-output.png) should be displayed 242 | - When the **Retry** button is clicked, an HTTP GET request should be made to **videoItemDetailsApiUrl** 243 | - Corresponding video should be displayed using `react-player` package 244 | - Initially, all the three buttons (Like, Dislike, Save) will be inactive 245 | - When the **Like** button is clicked, 246 | - It will change to an active state 247 | - If the **Dislike** button is already in the active state, then the **Dislike** button needs to be changed to the inactive state 248 | - When the **Dislike** button is clicked, 249 | 250 | - It will change to an active state 251 | - If the **Like** button is already in the active state, then the **Like** button needs to be changed to the inactive state 252 | 253 | - When the **Save** button is clicked 254 | - The button will change to an active state and the respective video details should be added to the list of saved videos 255 | - **Save** button text will be changed to **Saved** 256 | - When the **Saved** button is clicked 257 | - The button will change to an inactive state and the respective video details will be removed from the list of saved videos 258 | - **Saved** button text will be changed to **Save** 259 | 260 | - **SavedVideos Route** 261 | 262 | - When an authenticated user opens the **SavedVideos** Route, 263 | - If the list of saved videos is empty, then [No Saved Videos Found View](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-no-saved-videos-light-theme-lg-output.png) should be displayed 264 | - The **Videos** in the list of saved videos should be displayed as a list of videos 265 | - When the **website logo** image is clicked, the page should be navigated to the **Home** route 266 | - When a **Video** is clicked, the page should be navigated to the **Video Item Details** route 267 | - Clicks on the **Home** link in the Sidebar is clicked, then the page should be navigated to the **Home** route 268 | - Clicks on the **Trending** link in the Sidebar is clicked, then the page should be navigated to the **Trending** route 269 | - Clicks on the **Gaming** link in the Sidebar is clicked, then the page should be navigated to the **Gaming** route 270 | 271 | - **Not Found Route** 272 | 273 | - When a random path is provided in the URL then the page should navigate to the **Not Found** route 274 | 275 | - When the **theme** button in the header is clicked, then the theme should be changed accordingly 276 | 277 | - **Logout** 278 | - When the **Logout** button in the header is clicked, then the [Logout Popup](https://assets.ccbp.in/frontend/content/react-js/nxt-watch-logout-popup-light-theme-lg-output.png) should be displayed 279 | - When **Cancel** button is clicked, then the popup should be closed and the page should not be navigated 280 | - When **Confirm** button is clicked, then the page should be navigated to the **Login** route 281 | 282 |
283 | 284 |
285 | 286 | API Requests & Responses 287 |
288 | 289 | **loginApiUrl** 290 | 291 | #### API: `https://apis.ccbp.in/login` 292 | 293 | #### Method: `POST` 294 | 295 | #### Description: 296 | 297 | Returns a response containing the jwt_token 298 | 299 | #### Success Response 300 | 301 | ```json 302 | { 303 | "jwt_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJhaHVsIiwicm9sZSI6IlBSSU1FX1VTRVIiLCJpYXQiOjE2MTk2Mjg2MTN9. nZDlFsnSWArLKKeF0QbmdVfLgzUbx1BGJsqa2kc_21Y" 304 | } 305 | ``` 306 | 307 | #### Failure Response 308 | 309 | ```json 310 | { 311 | "status_code": 404, 312 | "error_msg": "Username is not found" 313 | } 314 | ``` 315 | 316 | **homeVideosApiUrl** 317 | 318 | #### API: `https://apis.ccbp.in/videos/all?search=` 319 | 320 | #### Method: `GET` 321 | 322 | #### Description: 323 | 324 | Returns a response containing the list of all videos 325 | 326 | #### Response 327 | 328 | ```json 329 | { 330 | "total": 60, 331 | "videos": [ 332 | { 333 | "id": "30b642bd-7591-49f4-ac30-5c538f975b15", 334 | "title": "Sehwag shares his batting experience in iB Cricket | iB Cricket Super Over League", 335 | "thumbnail_url": "https://assets.ccbp.in/frontend/react-js/nxt-watch/ibc-sol-1-img.png", 336 | "channel": { 337 | "name": "iB Cricket", 338 | "profile_image_url": "https://assets.ccbp.in/frontend/react-js/nxt-watch/ib-cricket-img.png" 339 | }, 340 | "view_count": "1.4K", 341 | "published_at": "Apr 19, 2019" 342 | }, 343 | ... 344 | ], 345 | } 346 | ``` 347 | 348 | **trendingVideosApiUrl** 349 | 350 | #### API: `https://apis.ccbp.in/videos/trending` 351 | 352 | #### Method: `GET` 353 | 354 | #### Description: 355 | 356 | Returns a response containing the list of trending videos 357 | 358 | #### Response 359 | 360 | ```json 361 | { 362 | "total": 30, 363 | "videos": [ 364 | { 365 | "id": "ad9822d2-5763-41d9-adaf-baf9da3fd490", 366 | "title": "iB Hubs Announcement Event", 367 | "thumbnail_url": "https://assets.ccbp.in/frontend/react-js/nxt-watch/ibhubs-img.png", 368 | "channel": { 369 | "name": "iB Hubs", 370 | "profile_image_url": "https://assets.ccbp.in/frontend/react-js/nxt-watch/ib-hubs-img.png" 371 | }, 372 | "view_count": "26K", 373 | "published_at": "Nov 29, 2016" 374 | }, 375 | ... 376 | ] 377 | } 378 | ``` 379 | 380 | **gamingVideosApiUrl** 381 | 382 | #### API: `https://apis.ccbp.in/videos/gaming` 383 | 384 | #### Method: `GET` 385 | 386 | #### Description: 387 | 388 | Returns a response containing the list of gaming videos 389 | 390 | #### Response 391 | 392 | ```json 393 | { 394 | "total": 30, 395 | "videos": [ 396 | { 397 | "id": "b214dc8a-b126-4d15-8523-d37404318347", 398 | "title": "Drop Stack Ball", 399 | "thumbnail_url": "https://assets.ccbp.in/frontend/react-js/nxt-watch/drop-stack-ball-img.png", 400 | "view_count": "44K" 401 | }, 402 | ... 403 | ] 404 | } 405 | ``` 406 | 407 | **videoItemDetailsApiUrl** 408 | 409 | #### API: `https://apis.ccbp.in/videos/:id` 410 | 411 | #### Example: `https://apis.ccbp.in/videos/802fcd20-1490-43c5-9e66-ce6dfefb40d1` 412 | 413 | #### Method: `GET` 414 | 415 | #### Description: 416 | 417 | Returns a response containing the list of gaming videos 418 | 419 | #### Response 420 | 421 | ```json 422 | { 423 | "video_details": { 424 | "id": "ad9822d2-5763-41d9-adaf-baf9da3fd490", 425 | "title": "iB Hubs Announcement Event", 426 | "video_url": "https://www.youtube.com/watch?v=pT2ojWWjum8", 427 | "thumbnail_url": "https://assets.ccbp.in/frontend/react-js/nxt-watch/ibhubs-img.png", 428 | "channel": { 429 | "name": "iB Hubs", 430 | "profile_image_url": "https://assets.ccbp.in/frontend/react-js/nxt-watch/ib-hubs-img.png", 431 | "subscriber_count": "1M" 432 | }, 433 | "view_count": "26K", 434 | "published_at": "Nov 29, 2016", 435 | "description": "iB Hubs grandly celebrated its Announcement Event in November 13, 2016, in the presence of many eminent personalities from the Government, Industry, and Academia with Shri Amitabh Kant, CEO, NITI Aayog as the Chief Guest." 436 | } 437 | } 438 | ``` 439 | 440 |
441 | 442 | ### Quick Tips 443 | 444 |
445 | Click to view 446 |
447 | 448 | - To build this project, take a look at the React Popup and React Video Player reading materials 449 | 450 | - To style popup content use `.popup-content` class 451 | 452 | ```jsx 453 | 460 | //write code here 461 | 462 | ``` 463 | 464 | - Use `formatDistanceToNow` function to find the difference between the given date and now in words. 465 | 466 | ```jsx 467 | import {formatDistanceToNow} from 'date-fns' 468 | console.log(formatDistanceToNow(new Date(2021, 8, 20))) 469 | // Return the distance between the given date and now in words. 470 | ``` 471 | 472 |
473 | 474 | ### Important Note 475 | 476 |
477 | Click to view 478 | 479 |
480 | 481 | **The following instructions are required for the tests to pass** 482 | 483 | - `Home` route should consist of `/` in the URL path 484 | - `Login` route should consist of `/login` in the URL path 485 | - `Trending` route should consist of `/trending` in the URL path 486 | - `Gaming` route should consist of `/gaming` in the URL path 487 | - `SavedVideos` route should consist of `/saved-videos` in the URL path 488 | - `VideoItemDetails` route should consist of `/videos/:id` in the URL path 489 | - No need to use the `BrowserRouter` in `App.js` as we have already included in `index.js` 490 | 491 | - User credentials 492 | 493 | ```text 494 | username: rahul 495 | password: rahul@2021 496 | 497 | ``` 498 | 499 | - Wrap the `Loader` component with an HTML container element and add the `data-testid` attribute value as `loader` to it 500 | 501 | ```jsx 502 |
503 | 504 |
505 | ``` 506 | 507 | - The HTML button element in Home Route has the `data-testid` attribute value as `searchButton` to it 508 | 509 | - **Styled Components** should be used for styling purposes. 510 | - The theme button should have the `data-testid` as `theme`. 511 | - The Sidebar should consists of 512 | - Facebook logo 513 | - Twitter Logo 514 | - Each Route consists of respective banner as shown in the design files and it should have the `data-testid` as `banner`. 515 | - The thumbnail images in the Route should have the alt attribute value as **video thumbnail**. 516 | - The channel logo images in Home Route should have the alt attribute value as **channel logo**. 517 | 518 | - **Home Route** 519 | 520 | - The Route should consist of an HTML container element with `data-testid` as `home`. 521 | - The Route should consist of an HTML image element with attribute value as `nxt watch logo` and src as the value of the given nxt watch logo URL should be displayed in the banner. 522 | - The Route should consist of a banner and it contains a close button element with `data-testid` as `close`. 523 | - The HTML container element with `data-testid` as `home` should have the background color. 524 | - If the Dark theme is applied, then the **#181818** color should be applied as a background-color. 525 | - If the Light theme is applied, then the **#f9f9f9** color should be applied as a background-color. 526 | 527 | - **Trending Route** 528 | 529 | - The Route should consist of an HTML container element with `data-testid` as `trending`. 530 | - The HTML container element with `data-testid` as `trending` should maintain the background color theme. 531 | - If the Dark theme is applied, then the **#0f0f0f** color should be applied as a background-color. 532 | - If the Light theme is applied, then the **#f9f9f9** color should be applied as a background-color. 533 | 534 | - **Gaming Route** 535 | 536 | - The Route should consist of an HTML container element with `data-testid` as `gaming`. 537 | - The HTML container element with `data-testid` as `gaming` should maintain the background color theme. 538 | - If the Dark theme is applied, then the **#0f0f0f** color should be applied as a background-color. 539 | - If the Light theme is applied, then the **#f9f9f9** color should be applied as a background-color. 540 | 541 | - **SavedVideos Route** 542 | 543 | - The **SavedVideos** Route should consist of an HTML container element with `data-testid` as `savedVideos`. 544 | - The HTML container element with `data-testid` as `savedVideos` should maintain the background color theme. 545 | - If the Dark theme is applied, then the **#0f0f0f** color should be applied as a background-color. 546 | - If the Light theme is applied, then the **#f9f9f9** color should be applied as a background-color. 547 | 548 | - **VideoItemDetails Route** 549 | 550 | - The **VideoItemDetails** Route should consist of an HTML container element with `data-testid` as `videoItemDetails`. 551 | - The HTML container element with `data-testid` as `videoItemDetails` should maintain the background color theme. 552 | - If the Dark theme is applied, then the **#0f0f0f** color should be applied as a background-color. 553 | - If the Light theme is applied, then the **#f9f9f9** color should be applied as a background-color. 554 | 555 | - The **Website Logo** image for Light theme and Dark theme should have the alt attribute value as `website logo` 556 | - The **Failure** image for Light theme and Dark theme should have the alt attribute value as `failure view` 557 | - The **Not found** image for Light theme and Dark theme should have the alt attribute value as `not found` 558 | - In the **VideoItemDetails** Route, the **#2563eb** color should be applied as `color` for any button i.e (Like, Dislike, Save) if the button is active. 559 | - In the **VideoItemDetails** Route, the **#64748b** color should be applied as `color` for any button i.e (Like, Dislike, Save) if the button is inactive. 560 | 561 |
562 | 563 | ### Resources 564 | 565 |
566 | Image URLs 567 | 568 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-logo-light-theme-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-logo-light-theme-img.png) 569 | 570 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-logo-dark-theme-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-logo-dark-theme-img.png) 571 | 572 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-profile-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-profile-img.png) alt should be **profile** 573 | 574 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-failure-view-light-theme-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-failure-view-light-theme-img.png) 575 | 576 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-failure-view-dark-theme-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-failure-view-dark-theme-img.png) 577 | 578 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-no-search-results-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-no-search-results-img.png) alt should be **no videos** 579 | 580 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-no-saved-videos-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-no-saved-videos-img.png) alt should be **no saved videos** 581 | 582 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-not-found-light-theme-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-not-found-light-theme-img.png) 583 | 584 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-not-found-dark-theme-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-not-found-dark-theme-img.png) 585 | 586 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-banner-bg.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-banner-bg.png) **Banner Background image** 587 | 588 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-facebook-logo-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-facebook-logo-img.png) alt should be **facebook logo** 589 | 590 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-twitter-logo-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-twitter-logo-img.png) alt should be **twitter logo** 591 | 592 | - [https://assets.ccbp.in/frontend/react-js/nxt-watch-linked-in-logo-img.png](https://assets.ccbp.in/frontend/react-js/nxt-watch-linked-in-logo-img.png) alt should be **linked in logo** 593 | 594 |
595 | 596 |
597 | Colors 598 | 599 |
600 | 601 |
Hex: #0f0f0f
602 |
Hex: #f9f9f9
603 |
Hex: #f8fafc
604 |
Hex: #1e293b
605 |
Hex: #f1f5f9
606 |
Hex: #475569
607 |
Hex: #f1f1f1
608 |
Hex: #181818
609 |
Hex: #e2e8f0
610 |
Hex: #94a3b8
611 |
Hex: #4f46e5
612 |
Hex: #64748b
613 |
Hex: #231f20
614 |
Hex: #ffffff
615 |
Hex: #212121
616 |
Hex: #616e7c
617 |
Hex: #3b82f6
618 |
Hex: #00306e
619 |
Hex: #ebebeb
620 |
Hex: #7e858e
621 |
Hex: #d7dfe9
622 |
Hex: #cbd5e1
623 |
Hex: #000000
624 |
Hex: #ff0b37
625 |
Hex: #ff0000
626 |
Hex: #383838
627 |
Hex: #606060
628 |
Hex: #909090
629 |
Hex: #cccccc
630 |
Hex: #424242
631 |
Hex: #313131
632 |
Hex: #f4f4f4
633 |
Hex: #424242
634 | 635 |
636 | 637 |
638 | Font-families 639 | 640 | - Roboto 641 | 642 |
643 | 644 | > ### _Things to Keep in Mind_ 645 | > 646 | > - All components you implement should go in the `src/components` directory. 647 | > - Don't change the component folder names as those are the files being imported into the tests. 648 | > - **Do not remove the pre-filled code** 649 | > - Want to quickly review some of the concepts you’ve been learning? Take a look at the Cheat Sheets. 650 | --------------------------------------------------------------------------------