diff --git a/.eslintrc b/.eslintrc index ba178545c..51f0b59a3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,9 +1,10 @@ { "extends": ["react-app", "airbnb", "prettier", "prettier/react"], - "plugins": ["prettier"], + "plugins": ["prettier", "react-hooks"], "rules": { "prettier/prettier": "error", "react/jsx-filename-extension": "error", + "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "import/no-unresolved": ["off", { "ignore": [".css$"] }], "import/prefer-default-export": "off", diff --git a/.gitignore b/.gitignore index 4d29575de..b59ec02d0 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +.env diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..f56c13a0b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,32 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Chrome", + "request": "launch", + "type": "pwa-chrome", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}" + }, + { + "command": "npm start", + "name": "Run npm start", + "request": "launch", + "type": "node-terminal" + }, + { + "type": "browser-preview", + "name": "Browser Preview: Attach", + "request": "attach" + }, + { + "type": "browser-preview", + "request": "launch", + "name": "Browser Preview: Launch", + "url": "http://localhost:3000" + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 5bc0e0d0d..b8f88ba8f 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ "react-dom": "^16.13.1", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", - "react-scripts": "3.4.3" + "react-scripts": "3.4.3", + "react-uuid": "^1.0.2", + "styled-components": "^5.2.1" }, "scripts": { "start": "react-scripts start", diff --git a/public/youtube-request.js b/public/youtube-request.js new file mode 100644 index 000000000..dfb97e26a --- /dev/null +++ b/public/youtube-request.js @@ -0,0 +1,41 @@ +/* global gapi */ + +/** + * Sample JavaScript code for youtube.search.list + * See instructions for running APIs Explorer code samples locally: + * https://developers.google.com/explorer-help/guides/code_samples#javascript + */ + +function loadClient(key) { + gapi.client.setApiKey(key); + return gapi.client + .load('https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest') + .then( + function () { + console.log('GAPI client loaded for API'); + }, + function (err) { + console.error('Error loading GAPI client for API', err); + } + ); +} +// Make sure the client is loaded before calling this method. +function execute(search) { + return gapi.client.youtube.search + .list({ + part: ['snippet', 'id'], + maxResults: 25, + q: search, + type: ['video'], + }) + .then( + function (response) { + // Handle the results here (response.result has the parsed body). + console.log('Response', response); + return response.result; + }, + function (err) { + console.error('Execute error', err); + } + ); +} diff --git a/src/components/App/App.component.jsx b/src/components/App/App.component.jsx index e372d6849..f328741db 100644 --- a/src/components/App/App.component.jsx +++ b/src/components/App/App.component.jsx @@ -1,4 +1,4 @@ -import React, { useLayoutEffect } from 'react'; +import React from 'react'; import { BrowserRouter, Switch, Route } from 'react-router-dom'; import AuthProvider from '../../providers/Auth'; @@ -7,49 +7,35 @@ import LoginPage from '../../pages/Login'; import NotFound from '../../pages/NotFound'; import SecretPage from '../../pages/Secret'; import Private from '../Private'; -import Fortune from '../Fortune'; import Layout from '../Layout'; -import { random } from '../../utils/fns'; +import VideoProvider from '../../state'; +import ViewerPage from '../../pages/Viewer'; function App() { - useLayoutEffect(() => { - const { body } = document; - - function rotateBackground() { - const xPercent = random(100); - const yPercent = random(100); - body.style.setProperty('--bg-position', `${xPercent}% ${yPercent}%`); - } - - const intervalId = setInterval(rotateBackground, 3000); - body.addEventListener('click', rotateBackground); - - return () => { - clearInterval(intervalId); - body.removeEventListener('click', rotateBackground); - }; - }, []); - return ( - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + ); diff --git a/src/components/Header/Header.component.jsx b/src/components/Header/Header.component.jsx new file mode 100644 index 000000000..e4616315e --- /dev/null +++ b/src/components/Header/Header.component.jsx @@ -0,0 +1,174 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useVideo } from '../../state'; +import useImportScript from '../../utils/hooks/useImportScrip'; + +const Header = styled.header` + color: #fff; + background-color: #1c5476; + position: static; + width: 100%; + display: flex; + z-index: 1100; + box-sizing: border-box; + flex-shrink: 0; + flex-direction: column; +`; + +const NavBar = styled.div` + min-height: 64px; + color: white; + padding-left: 24px; + padding-right: 24px; + display: flex; + align-items: center; + position: relative; +`; + +const SvgIcon = styled.svg` + font-size: 1.5rem; + width: 1em; + height: 1em; + display: inline-block; + fill: currentColor; + user-select: none; +`; + +const Button = styled.button` + padding: 12px; + font-size: 1.5rem; + color: inherit; + background-color: transparent; + border: 0; + user-select: none; + margin-right: 16px; +`; + +const SearchBox = styled.div` + width: auto; + margin-left: 24px; + position: relative; + margin-right: 16px; + border-radius: 4px; + background-color: rgba(255, 255, 255, 0.15); +`; + +const SearchIconDiv = styled.div` + padding: 0px 16px; + position: absolute; + align-items: center; + pointer-events: none; + justify-content: center; +`; + +const SearchInputDiv = styled.div` + cursor: text; + display: inline-flex; + position: relative; + font-size: 1rem; + box-sizing: border-box; + align-items: center; + font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; + font-weight: 400; + line-height: 1.1876em; + letter-spacing: 0.00938em; +`; + +const SearchInput = styled.input` + width: 20ch; + font-size: inherit; + padding: 8px 8px 8px 0px; + transition: width 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + padding-left: calc(1em + 32px); + color: currentColor; + height: 1.1876em; + margin: 0; + display: block; + min-width: 0; + background: none; + box-sizing: content-box; + letter-spacing: inherit; + animation-duration: 10ms; + -webkit-tap-highlight-color: transparent; + + &:focus { + outline: 0; + } +`; + +const ToggleDiv = styled.div` + display: flex; +`; + +const ToggleLabel = styled.div` + display: flex; + width: 100%; + justify-content: center; +`; + +const ToggleButton = styled.span` + width: 20px; + height: 20px; + box-shadow: 0px 2px 1px -1px rgb(0 0 0 / 20%), 0px 1px 1px 0px rgb(0 0 0 / 14%), + 0px 1px 3px 0px rgb(0 0 0 / 12%); + border-radius: 50%; + background-color: currentColor; +`; + +const Client = () => { + useImportScript('https://apis.google.com/js/api.js'); + useImportScript('youtube-request.js'); + return null; +}; + +function HeaderComponent() { + const { state, dispatch } = useVideo(); + const { search } = state; + + async function submitHandler(event) { + event.preventDefault(); + await window.loadClient(process.env.REACT_APP_KEY); + const result = await window.execute(search); + dispatch({ type: 'SUBMIT', payload: result.items }); + } + + return ( +
+ + + + + + + + + + +
+ dispatch({ type: 'EDIT', payload: e.target.value })} + /> + +
+
+ + + + + + Dark mode +
+
+ ); +} + +export default HeaderComponent; diff --git a/src/components/Header/index.js b/src/components/Header/index.js new file mode 100644 index 000000000..4ee6d6404 --- /dev/null +++ b/src/components/Header/index.js @@ -0,0 +1 @@ +export { default } from './Header.component'; diff --git a/src/components/History/History.component.jsx b/src/components/History/History.component.jsx new file mode 100644 index 000000000..ec6fbac3a --- /dev/null +++ b/src/components/History/History.component.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import uuid from 'react-uuid'; + +import { useVideo } from '../../state'; + +function History() { + const { state } = useVideo(); + const { history } = state; + + return ( +
+

Search History

+ +
+ ); +} + +export default History; diff --git a/src/components/History/index.js b/src/components/History/index.js new file mode 100644 index 000000000..fe6d57fdc --- /dev/null +++ b/src/components/History/index.js @@ -0,0 +1 @@ +export { default } from './History.component'; diff --git a/src/components/Layout/Layout.component.jsx b/src/components/Layout/Layout.component.jsx index b82ea3517..822763484 100644 --- a/src/components/Layout/Layout.component.jsx +++ b/src/components/Layout/Layout.component.jsx @@ -1,9 +1,15 @@ import React from 'react'; +import Header from '../Header'; import './Layout.styles.css'; function Layout({ children }) { - return
{children}
; + return ( +
+
+
{children}
+
+ ); } export default Layout; diff --git a/src/components/Layout/Layout.styles.css b/src/components/Layout/Layout.styles.css index e873b7c07..e9856775c 100644 --- a/src/components/Layout/Layout.styles.css +++ b/src/components/Layout/Layout.styles.css @@ -1,9 +1,11 @@ .container { - width: 100vw; - height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; margin-top: -3rem; + @media (min-width: 1135px) + { + width: 1135px; + } } diff --git a/src/components/VideoCard/VideoCard.component.jsx b/src/components/VideoCard/VideoCard.component.jsx new file mode 100644 index 000000000..dd148c232 --- /dev/null +++ b/src/components/VideoCard/VideoCard.component.jsx @@ -0,0 +1,71 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useHistory } from 'react-router'; + +const Container = styled.div` + width: 345px; + height: 345px; + margin: 10px; + display: inline-block; + box-shadow: 0px 2px 1px -1px rgb(0 0 0 / 20%), 0px 1px 1px 0px rgb(0 0 0 / 14%), + 0px 1px 3px 0px rgb(0 0 0 / 12%); +`; + +const Button = styled.button` + width: 100%inherit; + margin: 0px; + padding: 0px; + border: 0px; + background-color: transparent; + + &:focus { + outline: 0px; + } +`; + +const Image = styled.div` + display: block; + background-image: url(${(props) => props.image}); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + height: 140px; +`; + +const TextArea = styled.div` + display: block; +`; + +const Title = styled.h2` + font-size: 1.25rem; + font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; + font-weight: 500; + line-height: 1.6; + letter-spacing: 0.0075em; +`; + +const Desc = styled.p` + font-size: 0.875rem; + font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; + font-weight: 400; + line-height: 1.43; + letter-spacing: 0.01071em; +`; + +const VideoCard = ({ image, title, desc, id }) => { + const history = useHistory(); + + return ( + + + + ); +}; + +export default VideoCard; diff --git a/src/components/VideoCard/index.js b/src/components/VideoCard/index.js new file mode 100644 index 000000000..a10e0c5e4 --- /dev/null +++ b/src/components/VideoCard/index.js @@ -0,0 +1 @@ +export { default } from './VideoCard.component'; diff --git a/src/components/VideosList/VideosList.component.jsx b/src/components/VideosList/VideosList.component.jsx new file mode 100644 index 000000000..9a2216c21 --- /dev/null +++ b/src/components/VideosList/VideosList.component.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useVideo } from '../../state'; +import VideoCard from '../VideoCard'; + +const Container = styled.div` + display: block; +`; + +const Title = styled.h2` + font-size: 3.75rem; + font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; + font-weight: 300; + line-height: 1.2; + letter-spacing: -0.00833em; +`; + +const List = styled.div``; + +function VideosListComponent({ listTitle }) { + const { state } = useVideo(); + const { list } = state; + + return ( + + {listTitle} + + {list + .filter(({ id: { kind } }) => kind === 'youtube#video') + .map( + ({ + snippet: { + title, + description, + thumbnails: { + medium: { url }, + }, + }, + id: { videoId }, + }) => ( + + ) + )} + + + ); +} + +export default VideosListComponent; diff --git a/src/components/VideosList/index.js b/src/components/VideosList/index.js new file mode 100644 index 000000000..3812945db --- /dev/null +++ b/src/components/VideosList/index.js @@ -0,0 +1 @@ +export { default } from './VideosList.component'; diff --git a/src/global.css b/src/global.css index 4feb3c75e..1eb5ac121 100644 --- a/src/global.css +++ b/src/global.css @@ -17,7 +17,7 @@ html { body { margin: 0; padding: 0; - text-rendering: optimizeLegibility; +/* text-rendering: optimizeLegibility; background-image: linear-gradient( 120deg, #eea2a2 0, @@ -30,7 +30,7 @@ body { background-position: var(--bg-position); transition: background-position 2s ease; -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + -moz-osx-font-smoothing: grayscale;*/ } .separator::before { diff --git a/src/index.js b/src/index.js index b93eaa337..507d8942c 100644 --- a/src/index.js +++ b/src/index.js @@ -4,9 +4,5 @@ import ReactDOM from 'react-dom'; import App from './components/App'; import './global.css'; -ReactDOM.render( - - - , - document.getElementById('root') -); +const app = React.createElement(App); +ReactDOM.render(app, document.getElementById('root')); diff --git a/src/pages/Home/Home.page.jsx b/src/pages/Home/Home.page.jsx index 08d1dd5c0..d297a9168 100644 --- a/src/pages/Home/Home.page.jsx +++ b/src/pages/Home/Home.page.jsx @@ -1,37 +1,26 @@ import React, { useRef } from 'react'; -import { Link, useHistory } from 'react-router-dom'; import { useAuth } from '../../providers/Auth'; import './Home.styles.css'; +import VideosList from '../../components/VideosList'; +import History from '../../components/History'; function HomePage() { - const history = useHistory(); + // const history = useHistory(); const sectionRef = useRef(null); - const { authenticated, logout } = useAuth(); + const { authenticated } = useAuth(); - function deAuthenticate(event) { + /* function deAuthenticate(event) { event.preventDefault(); logout(); history.push('/'); - } + } */ return (
-

Hello stranger!

- {authenticated ? ( - <> -

Good to have you back

- - - ← logout - - - show me something cool → - - - ) : ( - let me in → - )} + + + {authenticated ? <> : <>}
); } diff --git a/src/pages/Viewer/Viewer.page.jsx b/src/pages/Viewer/Viewer.page.jsx new file mode 100644 index 000000000..a83cd6ce6 --- /dev/null +++ b/src/pages/Viewer/Viewer.page.jsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { Link, useParams } from 'react-router-dom'; + +function ViewerPage() { + const { id } = useParams(); + + return ( +
+
+        
+          {' '}
+          ← go back
+        
+      
+