diff --git a/.eslintrc b/.eslintrc index ba178545c..e5e605cd2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,7 +3,7 @@ "plugins": ["prettier"], "rules": { "prettier/prettier": "error", - "react/jsx-filename-extension": "error", + "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], "react-hooks/exhaustive-deps": "warn", "import/no-unresolved": ["off", { "ignore": [".css$"] }], "import/prefer-default-export": "off", diff --git a/.gitignore b/.gitignore index 4d29575de..179eb2ebe 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +#netlify + +.netlify diff --git a/package.json b/package.json index 5bc0e0d0d..3c542715e 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "@testing-library/user-event": "^12.1.3", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-icons": "^4.2.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", - "react-scripts": "3.4.3" + "react-scripts": "3.4.3", + "styled-components": "^5.3.0" }, "scripts": { "start": "react-scripts start", @@ -34,6 +36,9 @@ "prettier": "^2.1.1", "pretty-quick": "^3.0.0" }, + "resolutions": { + "styled-components": "^5" + }, "browserslist": { "production": [ ">0.2%", diff --git a/src/components/App/App.component.jsx b/src/components/App/App.component.jsx index e372d6849..66144e8f9 100644 --- a/src/components/App/App.component.jsx +++ b/src/components/App/App.component.jsx @@ -1,57 +1,73 @@ -import React, { useLayoutEffect } from 'react'; +import React, { useState } from 'react'; import { BrowserRouter, Switch, Route } from 'react-router-dom'; +import { ThemeProvider } from 'styled-components'; import AuthProvider from '../../providers/Auth'; import HomePage from '../../pages/Home'; +import FavoritesPage from '../../pages/Favorites'; 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'; -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); - }; - }, []); +import { GlobalStyle, themes } from '../../globalStyles'; +import VideoPlayer from '../../pages/VideoPlayer'; +import ThemeContext from '../Context/ThemeContext'; +import VideoContext from '../Context/VideoContext'; +import { FavoritesProvider } from '../../providers/Favorites/Favorites.provider'; +import FavoriteDetailsPage from '../../pages/FavoriteDetails/FavoriteDetails.page'; +import VideoFavoriteContext from '../Context/VideoFavorite'; +function App() { + const [theme, setTheme] = useState('light'); + const [video, setVideo] = useState({}); + const [videoFavorite, setVideoFavorite] = useState({}); return ( - - - - - - - - - - - - - - - - - - - - - + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/components/CardVideo/CardVideo.component.jsx b/src/components/CardVideo/CardVideo.component.jsx new file mode 100644 index 000000000..9a3f2a14c --- /dev/null +++ b/src/components/CardVideo/CardVideo.component.jsx @@ -0,0 +1,92 @@ +import React, { useContext, useEffect, useState } from 'react'; +import VideoContext from '../Context/VideoContext'; +import VideoFavoriteContext from '../Context/VideoFavorite'; +import LinkVideo from '../Link.element'; +import { + CardVideoDisplayerContainer, + CardVideoContainer, + CardVideoImage, + CardVideoBottom, +} from './CardVideo.elements'; + +export function CardVideo({ video, id }) { + const urlVideo = `/watch?v=${id}`; + + const { setVideo } = useContext(VideoContext); + + const handleClick = () => { + setVideo(video); + }; + + return ( + <> + + + + + + + + ); +} + +export function CardVideoFavorite({ video, id }) { + const urlVideo = `favorites/${id}`; + + const { setVideoFavorite } = useContext(VideoFavoriteContext); + + const handleClick = () => { + setVideoFavorite(id); + }; + + return ( + <> + + + + + + + + ); +} + +function CardVideoDisplayer({ videos }) { + const [listVideos, setListVideo] = useState(); + + useEffect(() => { + if (videos) { + setListVideo( + videos.map((video) => ( + + )) + ); + } + }, [videos]); + + return ( + <> + {listVideos} + + ); +} + +export default CardVideoDisplayer; diff --git a/src/components/CardVideo/CardVideo.elements.jsx b/src/components/CardVideo/CardVideo.elements.jsx new file mode 100644 index 000000000..e0cb586cd --- /dev/null +++ b/src/components/CardVideo/CardVideo.elements.jsx @@ -0,0 +1,81 @@ +import React from 'react'; +import styled from 'styled-components'; + +export const CardVideoDisplayerContainer = styled.div` + display: flex; + flex-wrap: wrap; + position: absolute; + justify-content: center; + padding-bottom: 2rem; + + top: ${(props) => props.theme.navbar_height}; + left: ${(props) => props.theme.sidemenu_width}; + + width: calc(100vw - ${(props) => props.theme.sidemenu_width}); +`; + +export const CardVideoContainer = styled.div` + width: calc((100vw - ${({ theme }) => theme.sidemenu_width}) / 1 - 4rem - 0.01px); + aspect-ratio: 16/14; + margin-top: 2rem; + margin-left: 1rem; + margin-right: 1rem; + + @media (min-width: 750px) { + width: calc((100vw - ${({ theme }) => theme.sidemenu_width}) / 2 - 4rem - 0.01px); + } + + @media (min-width: 1000px) { + width: calc((100vw - ${({ theme }) => theme.sidemenu_width}) / 3 - 4rem - 0.01px); + } + + &:hover { + cursor: pointer; + } +`; + +export const CardVideoImage = styled.img` + width: 100%; + aspect-ratio: 16/9; + height: auto; +`; + +const CardVideoBottomContainer = styled.div` + display: flex; + flex-direction: column; + aspect-ratio: 16/5; + align-items: center; + justify-content: center; +`; + +const CardVideoTitle = styled.h2` + color: ${(props) => props.theme.text_color}; + font-size: 1.2rem; + padding: 0.5rem 0.3rem; + text-align: center; +`; + +const CardVideoDescription = styled.p` + color: ${(props) => props.theme.text_color}; + + padding-left: 0.5rem; + padding-right: 0.5rem; + padding-bottom: 0.5rem; + font-size: 1rem; +`; + +export function CardVideoBottom({ title, description }) { + function trucateText(text) { + const maxLength = 50; + return text.length <= maxLength ? text : `${text.substring(0, maxLength)}...`; + } + + return ( + <> + + {title} + {trucateText(description)} + + + ); +} diff --git a/src/components/CardVideo/CardVideo.test.js b/src/components/CardVideo/CardVideo.test.js new file mode 100644 index 000000000..f59feb363 --- /dev/null +++ b/src/components/CardVideo/CardVideo.test.js @@ -0,0 +1,37 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import CardVideoDisplayer from './CardVideo.component'; + +describe('navbar', () => { + beforeEach(() => { + const videos = [ + { + etag: 'sadlkjsakdljsa', + snippet: { + title: 'Wizeline', + description: 'hlksjdakjskldja asjdaks', + thumbnails: { + high: { + url: + 'https://yt3.ggpht.com/ytc/AAUvwnighSReQlmHl_S_vSfvnWBAG5Cw4A0YxtE0tm5OpQ=s800-c-k-c0xffffffff-no-rj-mo', + }, + }, + }, + }, + ]; + + render(); + }); + + test('should contains a title', () => { + const title = screen.queryByText('Wizeline'); + + expect(title).toBeInTheDocument(); + }); + + test('should contains a img', () => { + const img = screen.queryByText('hlksjdakjskldja asjdaks'); + + expect(img).toBeInTheDocument(); + }); +}); diff --git a/src/components/CardVideo/index.js b/src/components/CardVideo/index.js new file mode 100644 index 000000000..d38c3435c --- /dev/null +++ b/src/components/CardVideo/index.js @@ -0,0 +1 @@ +export { default, CardVideo } from './CardVideo.component'; diff --git a/src/components/Context/ThemeContext.js b/src/components/Context/ThemeContext.js new file mode 100644 index 000000000..54556a3fd --- /dev/null +++ b/src/components/Context/ThemeContext.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const ThemeContext = createContext({}); + +export default ThemeContext; diff --git a/src/components/Context/VideoContext.js b/src/components/Context/VideoContext.js new file mode 100644 index 000000000..737fb1583 --- /dev/null +++ b/src/components/Context/VideoContext.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const VideoContext = createContext({}); + +export default VideoContext; diff --git a/src/components/Context/VideoFavorite.js b/src/components/Context/VideoFavorite.js new file mode 100644 index 000000000..cb0ca5c31 --- /dev/null +++ b/src/components/Context/VideoFavorite.js @@ -0,0 +1,5 @@ +import { createContext } from 'react'; + +const VideoFavoriteContext = createContext({}); + +export default VideoFavoriteContext; diff --git a/src/components/Link.element.jsx b/src/components/Link.element.jsx new file mode 100644 index 000000000..f41a3f9b0 --- /dev/null +++ b/src/components/Link.element.jsx @@ -0,0 +1,8 @@ +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +const LinkVideo = styled(Link)` + text-decoration: none; +`; + +export default LinkVideo; diff --git a/src/components/Navbar/Navbar.component.jsx b/src/components/Navbar/Navbar.component.jsx new file mode 100644 index 000000000..7bc1511da --- /dev/null +++ b/src/components/Navbar/Navbar.component.jsx @@ -0,0 +1,26 @@ +import React, { createRef } from 'react'; +import { useHistory } from 'react-router'; +import { Nav, IconLogo, SearchBar, ProfileImg } from './Navbar.elements'; + +function Navbar() { + const history = useHistory(); + + const inputRef = createRef(); + + const handleSubmit = (event) => { + event.preventDefault(); + history.push(`/?q=${inputRef.current.value}`); + }; + + return ( + <> + + + ); +} + +export default Navbar; diff --git a/src/components/Navbar/Navbar.elements.jsx b/src/components/Navbar/Navbar.elements.jsx new file mode 100644 index 000000000..3ce77cc73 --- /dev/null +++ b/src/components/Navbar/Navbar.elements.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +import styled from 'styled-components'; +import { AiFillYoutube } from 'react-icons/ai'; +import { FaSearch } from 'react-icons/fa'; + +export const Nav = styled.nav` + background: ${(props) => props.theme.background_color}; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100vw; + height: ${(props) => props.theme.navbar_height}; + position: fixed; + top: 0; + z-index: 99; + padding: 0rem 1.5rem; +`; + +const LogoContainer = styled.div` + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-start; + align-items: center; + user-select: none; +`; + +const LogoText = styled.h1` + color: ${(props) => props.theme.text_color}; + margin-left: 0.5rem; + font-size: 2rem; + font-weight: 900; + @media (max-width: 600px) { + display: none; + } +`; + +export function IconLogo() { + return ( + + + WizeTube + + ); +} + +const FormSearch = styled.form` + display: flex; + display: row; +`; + +const InputSearch = styled.input` + padding: 0rem 0.5rem; + border: 1px solid ${(props) => props.theme.icon_color}; + margin-left: 1rem; + width: 350px; + + @media (max-width: 737px) { + width: 200px; + } +`; + +const SearchButton = styled.button` + color: ${(props) => props.theme.icon_color}; + display: flex; + justify-content: center; + align-content: center; + padding: 0.5rem 0.75rem; + margin-right: 1rem; +`; + +export function SearchBar({ inputRef, handleSubmit }) { + return ( + + + + + + + ); +} + +export const ProfileImg = styled.img` + border-radius: 50%; + width: 3.5rem; + height: auto; +`; diff --git a/src/components/Navbar/Navbar.test.js b/src/components/Navbar/Navbar.test.js new file mode 100644 index 000000000..f29f4e11b --- /dev/null +++ b/src/components/Navbar/Navbar.test.js @@ -0,0 +1,33 @@ +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import Navbar from './Navbar.component'; + +describe('navbar', () => { + beforeEach(() => { + render(); + }); + + test('should contains a title', () => { + const title = screen.queryByText(/wizetube/i); + + expect(title).toBeInTheDocument(); + }); + + test('should contains a profile picture', () => { + const picture = screen.queryByRole('img'); + + expect(picture).toBeInTheDocument(); + }); + + test('should contains a search input', () => { + const input = screen.queryByPlaceholderText(/search/i); + + expect(input).toBeInTheDocument(); + }); + + test('should contains a search button', () => { + const button = screen.queryByRole('button'); + + expect(button).toBeInTheDocument(); + }); +}); diff --git a/src/components/Navbar/index.js b/src/components/Navbar/index.js new file mode 100644 index 000000000..3b39ac26f --- /dev/null +++ b/src/components/Navbar/index.js @@ -0,0 +1 @@ +export { default } from './Navbar.component'; diff --git a/src/components/SideMenu/SideMenu.component.jsx b/src/components/SideMenu/SideMenu.component.jsx new file mode 100644 index 000000000..3c528c3a5 --- /dev/null +++ b/src/components/SideMenu/SideMenu.component.jsx @@ -0,0 +1,46 @@ +import React, { useContext } from 'react'; + +import { AiFillHome, AiFillStar } from 'react-icons/ai'; +import { FaSun, FaMoon } from 'react-icons/fa'; +import ThemeContext from '../Context/ThemeContext'; +import LinkVideo from '../Link.element'; +import { SideMenuContainer, SideMenuItem } from './SideMenu.elements'; + +import { useAuth } from '../../providers/Auth'; + +const PrivateLink = () => { + const { authenticated } = useAuth(); + + return authenticated ? ( + + + + ) : ( + + + + ); +}; + +function SideMenu() { + const { theme, setTheme } = useContext(ThemeContext); + + const icon = theme === 'light' ? FaMoon : FaSun; + const text = theme === 'light' ? 'Dark Mode' : 'Light Mode'; + + const handleTheme = () => setTheme(theme === 'light' ? 'dark' : 'light'); + + return ( + <> + + + + + + + + + ); +} + +export default SideMenu; diff --git a/src/components/SideMenu/SideMenu.elements.jsx b/src/components/SideMenu/SideMenu.elements.jsx new file mode 100644 index 000000000..3c4dfe2d3 --- /dev/null +++ b/src/components/SideMenu/SideMenu.elements.jsx @@ -0,0 +1,40 @@ +import React from 'react'; +import styled from 'styled-components'; + +export const SideMenuContainer = styled.ul` + display: flex; + flex-direction: column; + flex-wrap: wrap; + position: fixed; + top: ${(props) => props.theme.navbar_height}; + left: 0; + height: calc(100vh - ${(props) => props.theme.navbar_height}); + width: ${(props) => props.theme.sidemenu_width}; + background: ${(props) => props.theme.background_color}; +`; + +const SideMenuOption = styled.li` + color: ${(props) => props.theme.icon_color}; + text-decoration: bold; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: 0.5rem; + user-select: none; + font-weight: bold; + width: 100%; + + &:hover { + background: ${({ theme }) => theme.scrollbar_thumb_hover_color}; + } +`; + +export function SideMenuItem({ Icon, text, fun }) { + return ( + + + {text} + + ); +} diff --git a/src/components/SideMenu/index.js b/src/components/SideMenu/index.js new file mode 100644 index 000000000..26f24a2dd --- /dev/null +++ b/src/components/SideMenu/index.js @@ -0,0 +1 @@ +export { default } from './SideMenu.component'; diff --git a/src/components/VideoVisualizer/VideoVisualizer.component.jsx b/src/components/VideoVisualizer/VideoVisualizer.component.jsx new file mode 100644 index 000000000..aecc285e4 --- /dev/null +++ b/src/components/VideoVisualizer/VideoVisualizer.component.jsx @@ -0,0 +1,63 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { FaHeart } from 'react-icons/fa'; +import { + RelatedVideosContainer, + RelatedVideosTitle, + VideoPlayer, + VideoVisualizerContainer, +} from './VideoVisualizer.elements'; +import { CardVideo } from '../CardVideo/CardVideo.component'; +import VideoContext from '../Context/VideoContext'; +import { useFavorites } from '../../providers/Favorites'; + +function VideoVisualizer({ videoId, videos }) { + const [listVideos, setListVideo] = useState(); + const { video } = useContext(VideoContext); + useEffect(() => { + if (videos) { + setListVideo( + videos.map((videoRelated) => ( + + )) + ); + } + }, [videos]); + + const [buttonClicked, setButtonClicked] = useState(false); + const [state, dispatch] = useFavorites(); + console.log(state); + const handleFavClick = () => { + if (buttonClicked) { + dispatch({ + type: 'DELETE', + payload: video, + }); + setButtonClicked(false); + } else { + dispatch({ + type: 'ADD', + payload: video, + }); + setButtonClicked(true); + } + }; + + return ( + <> + + + + + Related Videos + {listVideos} + + + + ); +} + +export default VideoVisualizer; diff --git a/src/components/VideoVisualizer/VideoVisualizer.elements.jsx b/src/components/VideoVisualizer/VideoVisualizer.elements.jsx new file mode 100644 index 000000000..428139ea2 --- /dev/null +++ b/src/components/VideoVisualizer/VideoVisualizer.elements.jsx @@ -0,0 +1,55 @@ +import React from 'react'; +import styled from 'styled-components'; + +export const VideoVisualizerContainer = styled.div` + display: flex; + flex-wrap: wrap; + position: absolute; + justify-content: center; + padding: 2rem; + align-content: center; + + top: ${(props) => props.theme.navbar_height}; + left: ${(props) => props.theme.sidemenu_width}; + + width: calc(100vw - ${(props) => props.theme.sidemenu_width}); +`; + +export const RelatedVideosTitle = styled.h2` + width: 100%; + text-align: center; + font-size: 1.5rem; + margin-top: 1rem; + margin-bottom: 1rem; + color: ${(props) => props.theme.text_color}; +`; + +export const RelatedVideosContainer = styled.div` + display: flex; + flex-wrap: wrap; + justify-content: center; + padding-bottom: 2rem; + + width: 100%; +`; + +const VideoPlayerEmbeded = styled.iframe` + aspect-ratio: 16/9; + width: calc(40vw - ${(props) => props.theme.sidemenu_width}); + min-width: 350px; + margin-bottom: 2rem; +`; + +export function VideoPlayer({ videoId }) { + const source = `https://www.youtube.com/embed/${videoId}`; + + return ( + + ); +} diff --git a/src/components/VideoVisualizer/index.js b/src/components/VideoVisualizer/index.js new file mode 100644 index 000000000..7b618269f --- /dev/null +++ b/src/components/VideoVisualizer/index.js @@ -0,0 +1 @@ +export { default } from './VideoVisualizer.component'; diff --git a/src/global.css b/src/global.css deleted file mode 100644 index 4feb3c75e..000000000 --- a/src/global.css +++ /dev/null @@ -1,53 +0,0 @@ -html { - font-size: 1.125rem; - line-height: 1.6; - font-weight: 400; - font-family: sans-serif; - box-sizing: border-box; - scroll-behavior: smooth; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -} - -*, -*::before, -*::after { - box-sizing: inherit; -} - -body { - margin: 0; - padding: 0; - text-rendering: optimizeLegibility; - background-image: linear-gradient( - 120deg, - #eea2a2 0, - #bbc1bf 19%, - #57c6e1 42%, - #b49fda 79%, - #7ac5d8 100% - ); - background-size: 400% 400%; - background-position: var(--bg-position); - transition: background-position 2s ease; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.separator::before { - content: '•'; - color: white; - padding: 0.4rem; -} - -a { - text-decoration: none; - font-weight: bold; - color: white; -} - -a:active { - color: blueviolet; -} - -hr { -} diff --git a/src/globalStyles.js b/src/globalStyles.js new file mode 100644 index 000000000..140333a38 --- /dev/null +++ b/src/globalStyles.js @@ -0,0 +1,60 @@ +import { createGlobalStyle } from 'styled-components'; + +const NAVBAR_HEIGHT = '70px'; +const SIDE_MENU_WIDTH = '100px'; + +export const LightTheme = { + background_color: 'white', + text_color: 'black', + icon_color: '#606060', + scrollbar_track_color: 'white', + scrollbar_thumb_color: '#666', + scrollbar_thumb_hover_color: '#CCC', + navbar_height: NAVBAR_HEIGHT, + sidemenu_width: SIDE_MENU_WIDTH, +}; + +export const DarkTheme = { + background_color: '#202020', + text_color: 'white', + icon_color: '#909090', + scrollbar_track_color: '#202020', + navbar_height: NAVBAR_HEIGHT, + sidemenu_width: SIDE_MENU_WIDTH, +}; + +export const themes = { + light: LightTheme, + dark: DarkTheme, +}; + +export const GlobalStyle = createGlobalStyle` + * { + box-sizing: border-box; + margin: 0; + padding: 0; + font-size: 16px; + } + + body{ + background: ${(props) => props.theme.background_color}; + } + + body::-webkit-scrollbar { + width: 0.75rem; + } + + body::-webkit-scrollbar-track { + background: ${(props) => props.theme.scrollbar_track_color}; + } + + body::-webkit-scrollbar-thumb { + background: ${(props) => props.theme.scrollbar_thumb_color}; + border-radius: 1.25rem; + border: 0.15rem solid ${(props) => props.theme.scrollbar_track_color}; + } + + body::-webkit-scrollbar-thumb:hover { + background: ${(props) => props.theme.scrollbar_thumb_hover_color}; + } +`; diff --git a/src/index.js b/src/index.js index b93eaa337..1d527be7d 100644 --- a/src/index.js +++ b/src/index.js @@ -2,7 +2,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './components/App'; -import './global.css'; ReactDOM.render( diff --git a/src/pages/FavoriteDetails/FavoriteDetails.page.jsx b/src/pages/FavoriteDetails/FavoriteDetails.page.jsx new file mode 100644 index 000000000..0e28fd40d --- /dev/null +++ b/src/pages/FavoriteDetails/FavoriteDetails.page.jsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState, useContext } from 'react'; +import { FaHeart } from 'react-icons/fa'; +import Navbar from '../../components/Navbar'; +import SideMenu from '../../components/SideMenu/SideMenu.component'; +import { CardVideoFavorite } from '../../components/CardVideo/CardVideo.component'; +import { useFavorites } from '../../providers/Favorites'; +import { + RelatedVideosContainer, + RelatedVideosTitle, + VideoPlayer, + VideoVisualizerContainer, +} from '../../components/VideoVisualizer/VideoVisualizer.elements'; +import VideoContext from '../../components/Context/VideoContext'; +import VideoFavoriteContext from '../../components/Context/VideoFavorite'; + +function FavoriteDetailsPage() { + const { videoFavorite } = useContext(VideoFavoriteContext); + + const { video } = useContext(VideoContext); + + const [listVideos, setListVideo] = useState(); + const [state, dispatch] = useFavorites(); + console.log(dispatch); + + useEffect(() => { + if (state) { + setListVideo( + state.map((videoFavoriteRelated) => ( + + )) + ); + } + }, [state]); + + const [buttonClicked, setButtonClicked] = useState(false); + console.log(state); + const handleFavClick = () => { + if (buttonClicked) { + dispatch({ + type: 'DELETE', + payload: video, + }); + setButtonClicked(false); + } else { + dispatch({ + type: 'ADD', + payload: video, + }); + setButtonClicked(true); + } + }; + + return ( + <> + + + + + + + Other Favorite Videos + {listVideos} + + + + ); +} + +export default FavoriteDetailsPage; diff --git a/src/pages/FavoriteDetails/index.js b/src/pages/FavoriteDetails/index.js new file mode 100644 index 000000000..c5769fb50 --- /dev/null +++ b/src/pages/FavoriteDetails/index.js @@ -0,0 +1 @@ +export { default } from './FavoritesDetails.page'; diff --git a/src/pages/Favorites/Favorites.page.jsx b/src/pages/Favorites/Favorites.page.jsx new file mode 100644 index 000000000..211bd09d5 --- /dev/null +++ b/src/pages/Favorites/Favorites.page.jsx @@ -0,0 +1,38 @@ +import React, { useEffect, useState } from 'react'; + +import Navbar from '../../components/Navbar'; +import SideMenu from '../../components/SideMenu/SideMenu.component'; +import { CardVideoFavorite } from '../../components/CardVideo/CardVideo.component'; +import { useFavorites } from '../../providers/Favorites'; +import { + RelatedVideosContainer, + RelatedVideosTitle, +} from '../../components/VideoVisualizer/VideoVisualizer.elements'; + +function FavoritesPage() { + const [listVideos, setListVideo] = useState(); + const [state, dispatch] = useFavorites(); + console.log(dispatch); + + useEffect(() => { + if (state) { + setListVideo( + state.map((video) => ( + + )) + ); + } + }, [state]); + return ( + <> + + + + Favorite Videos + {listVideos} + + + ); +} + +export default FavoritesPage; diff --git a/src/pages/Favorites/index.js b/src/pages/Favorites/index.js new file mode 100644 index 000000000..ddb7677c3 --- /dev/null +++ b/src/pages/Favorites/index.js @@ -0,0 +1 @@ +export { default } from './Favorites.page'; diff --git a/src/pages/Home/Home.page.jsx b/src/pages/Home/Home.page.jsx index 08d1dd5c0..60748e07c 100644 --- a/src/pages/Home/Home.page.jsx +++ b/src/pages/Home/Home.page.jsx @@ -1,38 +1,22 @@ -import React, { useRef } from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import React from 'react'; -import { useAuth } from '../../providers/Auth'; -import './Home.styles.css'; +import { useLocation } from 'react-router'; +import Navbar from '../../components/Navbar'; +import SideMenu from '../../components/SideMenu/SideMenu.component'; +import CardVideoDisplayer from '../../components/CardVideo/CardVideo.component'; +import useVideo from '../../utils/hooks/useVideo'; function HomePage() { - const history = useHistory(); - const sectionRef = useRef(null); - const { authenticated, logout } = useAuth(); + const searchQuery = new URLSearchParams(useLocation().search).get('q'); - function deAuthenticate(event) { - event.preventDefault(); - logout(); - history.push('/'); - } + const { videos } = useVideo({ searchQuery }); return ( -
-

Hello stranger!

- {authenticated ? ( - <> -

Good to have you back

- - - ← logout - - - show me something cool → - - - ) : ( - let me in → - )} -
+ <> + + + + ); } diff --git a/src/pages/Home/Home.styles.css b/src/pages/Home/Home.styles.css deleted file mode 100644 index 5e0a702c3..000000000 --- a/src/pages/Home/Home.styles.css +++ /dev/null @@ -1,8 +0,0 @@ -.homepage { - text-align: center; -} - -.homepage h1 { - font-size: 3rem; - letter-spacing: -2px; -} diff --git a/src/pages/Login/Login.page.jsx b/src/pages/Login/Login.page.jsx index 89367f276..1d2f1bc20 100644 --- a/src/pages/Login/Login.page.jsx +++ b/src/pages/Login/Login.page.jsx @@ -11,7 +11,7 @@ function LoginPage() { function authenticate(event) { event.preventDefault(); login(); - history.push('/secret'); + history.push('/'); } return ( diff --git a/src/pages/VideoPlayer/VideoPlayer.page.jsx b/src/pages/VideoPlayer/VideoPlayer.page.jsx new file mode 100644 index 000000000..f2782daed --- /dev/null +++ b/src/pages/VideoPlayer/VideoPlayer.page.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { useLocation } from 'react-router'; +import Navbar from '../../components/Navbar'; +import SideMenu from '../../components/SideMenu/SideMenu.component'; +import VideoVisualizer from '../../components/VideoVisualizer/VideoVisualizer.component'; +import useVideo from '../../utils/hooks/useVideo'; + +function VideoPlayer() { + const relatedId = new URLSearchParams(useLocation().search).get('v'); + + const { videos } = useVideo({ relatedId }); + + return ( + <> + + + + + ); +} + +export default VideoPlayer; diff --git a/src/pages/VideoPlayer/index.js b/src/pages/VideoPlayer/index.js new file mode 100644 index 000000000..1cbe0f4f1 --- /dev/null +++ b/src/pages/VideoPlayer/index.js @@ -0,0 +1 @@ +export { default } from './VideoPlayer.page'; diff --git a/src/providers/Favorites/Favorites.provider.jsx b/src/providers/Favorites/Favorites.provider.jsx new file mode 100644 index 000000000..deb83929c --- /dev/null +++ b/src/providers/Favorites/Favorites.provider.jsx @@ -0,0 +1,26 @@ +import React, { createContext, useContext, useReducer } from 'react'; +import { FAVORITES_STORAGE_KEY } from '../../utils/constants'; +import { storage } from '../../utils/storage'; +import FavoritesReducer from './Favorites.reducer'; + +const favoriteVideos = storage.get(FAVORITES_STORAGE_KEY) || []; +const initialState = favoriteVideos.length ? JSON.parse(favoriteVideos) : []; +const FavoritesContext = createContext(); + +const useFavorites = () => { + const context = useContext(FavoritesContext); + if (!context) { + throw new Error(`Can't use "useFavorites" without an FavoritesProvider`); + } + return context; +}; + +const FavoritesProvider = ({ children }) => { + const favorites = useReducer(FavoritesReducer, initialState); + + return ( + {children} + ); +}; + +export { useFavorites, FavoritesProvider }; diff --git a/src/providers/Favorites/Favorites.reducer.js b/src/providers/Favorites/Favorites.reducer.js new file mode 100644 index 000000000..45f044b0a --- /dev/null +++ b/src/providers/Favorites/Favorites.reducer.js @@ -0,0 +1,23 @@ +import { FAVORITES_STORAGE_KEY } from '../../utils/constants'; +import { storage } from '../../utils/storage'; + +const FavoritesReducer = (state, action) => { + switch (action.type) { + case 'ADD': { + const favoriteVideos = state.concat(action.payload); + storage.set(FAVORITES_STORAGE_KEY, JSON.stringify(favoriteVideos)); + return [...favoriteVideos]; + } + case 'DELETE': { + const favoriteVideos = state.filter( + (video) => video.id.videoId !== action.payload.id.videoId + ); + storage.set(FAVORITES_STORAGE_KEY, JSON.stringify(favoriteVideos)); + return [...favoriteVideos]; + } + default: + return state; + } +}; + +export default FavoritesReducer; diff --git a/src/providers/Favorites/index.js b/src/providers/Favorites/index.js new file mode 100644 index 000000000..bc4f31fcc --- /dev/null +++ b/src/providers/Favorites/index.js @@ -0,0 +1 @@ +export { useFavorites, FavoritesProvider } from './Favorites.provider'; diff --git a/src/utils/constants.js b/src/utils/constants.js index 361273c9c..fba4356c6 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -1,3 +1,4 @@ const AUTH_STORAGE_KEY = 'wa_cert_authenticated'; +const FAVORITES_STORAGE_KEY = 'wa_cert_favorites'; -export { AUTH_STORAGE_KEY }; +export { AUTH_STORAGE_KEY, FAVORITES_STORAGE_KEY }; diff --git a/src/utils/hooks/useVideo.js b/src/utils/hooks/useVideo.js new file mode 100644 index 000000000..fb109c079 --- /dev/null +++ b/src/utils/hooks/useVideo.js @@ -0,0 +1,43 @@ +import { useState, useEffect } from 'react'; + +const API_URL = 'https://youtube.googleapis.com/youtube/v3/search?'; +const PART = 'snippet'; +const MAX_RESULTS = 15; +const TYPE = 'video'; +const YOUTUBE_API_KEY = process.env.REACT_APP_YOUTUBE_API_KEY; + +function useVideo(params) { + const [videos, setVideos] = useState([]); + const { relatedId, searchQuery } = params; + + useEffect(() => { + let url = `${API_URL}part=${PART}&maxResults=${MAX_RESULTS}`; + + if (relatedId) { + url += `&relatedToVideoId=${relatedId}`; + } + + if (searchQuery) { + url += `&q=${searchQuery}`; + } + + url += `&type=${TYPE}&key=${YOUTUBE_API_KEY}`; + + const getVideos = async () => { + try { + const response = await fetch(url); + const resultItem = await response.json(); + const list = resultItem.items; + setVideos(list); + } catch (error) { + console.error("That's too bad: ", error); + } + }; + + getVideos(); + }, [relatedId, searchQuery]); + + return { videos }; +} + +export default useVideo; diff --git a/test-coverage.txt b/test-coverage.txt new file mode 100644 index 000000000..835556d3c --- /dev/null +++ b/test-coverage.txt @@ -0,0 +1,65 @@ +yarn test --coverage --watchAll=false +yarn run v1.22.10 +$ react-scripts test --coverage --watchAll=false + PASS src/components/CardVideo/CardVideo.test.js + PASS src/components/Navbar/Navbar.test.js +--------------------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +--------------------------|----------|----------|----------|----------|-------------------| +All files | 26.83 | 0 | 35.71 | 26.83 | | + src | 0 | 100 | 0 | 0 | | + globalStyles.js | 0 | 100 | 0 | 0 |... 40,48,52,54,58 | + index.js | 0 | 100 | 100 | 0 | 6 | + src/components/App | 0 | 100 | 0 | 0 | | + App.component.jsx | 0 | 100 | 0 | 0 | 17,19 | + index.js | 0 | 0 | 0 | 0 | | + src/components/CardVideo | 100 | 100 | 100 | 100 | | + CardVideo.component.jsx | 100 | 100 | 100 | 100 | | + CardVideo.elements.jsx | 100 | 100 | 100 | 100 | | + index.js | 0 | 0 | 0 | 0 | | + src/components/Fortune | 0 | 100 | 0 | 0 | | + Fortune.component.jsx | 0 | 100 | 0 | 0 | 7,9 | + index.js | 0 | 0 | 0 | 0 | | + src/components/Layout | 0 | 100 | 0 | 0 | | + Layout.component.jsx | 0 | 100 | 0 | 0 | 6 | + index.js | 0 | 0 | 0 | 0 | | + src/components/Navbar | 100 | 100 | 100 | 100 | | + Navbar.component.jsx | 100 | 100 | 100 | 100 | | + Navbar.elements.jsx | 100 | 100 | 100 | 100 | | + index.js | 0 | 0 | 0 | 0 | | + src/components/Private | 0 | 0 | 0 | 0 | | + Private.component.jsx | 0 | 0 | 0 | 0 | 7,9,10 | + index.js | 0 | 0 | 0 | 0 | | + src/components/SideMenu | 0 | 0 | 0 | 0 | | + SideMenu.component.jsx | 0 | 0 | 0 | 0 | 8,9,11,13 | + SideMenu.elements.jsx | 0 | 100 | 0 | 0 |... 13,16,17,29,34 | + index.js | 0 | 0 | 0 | 0 | | + src/pages/Home | 0 | 100 | 0 | 0 | | + Home.page.jsx | 0 | 100 | 0 | 0 |... 17,18,20,24,27 | + index.js | 0 | 0 | 0 | 0 | | + src/pages/Login | 0 | 100 | 0 | 0 | | + Login.page.jsx | 0 | 100 | 0 | 0 | 8,9,12,13,14,17 | + index.js | 0 | 0 | 0 | 0 | | + src/pages/NotFound | 0 | 100 | 0 | 0 | | + NotFound.page.jsx | 0 | 100 | 0 | 0 | 7 | + index.js | 0 | 0 | 0 | 0 | | + src/pages/Secret | 0 | 100 | 0 | 0 | | + Secret.page.jsx | 0 | 100 | 0 | 0 | 5 | + index.js | 0 | 0 | 0 | 0 | | + src/providers/Auth | 0 | 0 | 0 | 0 | | + Auth.provider.jsx | 0 | 0 | 0 | 0 |... 28,31,32,33,36 | + index.js | 0 | 0 | 0 | 0 | | + src/utils | 0 | 100 | 0 | 0 | | + constants.js | 0 | 100 | 100 | 0 | 1 | + fns.js | 0 | 100 | 0 | 0 | 2 | + storage.js | 0 | 100 | 0 | 0 | 1,3,4,5,7,8,13 | + src/utils/hooks | 0 | 100 | 0 | 0 | | + useFortune.js | 0 | 100 | 0 | 0 |... 17,19,21,25,28 | +--------------------------|----------|----------|----------|----------|-------------------| + +Test Suites: 2 passed, 2 total +Tests: 6 passed, 6 total +Snapshots: 0 total +Time: 4.964s +Ran all test suites. +Done in 6.05s. \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0530c750a..fb28ba1df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16,6 +16,13 @@ dependencies: "@babel/highlight" "^7.10.4" +"@babel/code-frame@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" + integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== + dependencies: + "@babel/highlight" "^7.14.5" + "@babel/compat-data@^7.10.4", "@babel/compat-data@^7.11.0", "@babel/compat-data@^7.9.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.11.0.tgz#e9f73efe09af1355b723a7f39b11bad637d7c99c" @@ -78,6 +85,22 @@ jsesc "^2.5.1" source-map "^0.6.1" +"@babel/generator@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.8.tgz#bf86fd6af96cf3b74395a8ca409515f89423e070" + integrity sha512-cYDUpvIzhBVnMzRoY1fkSEhK/HmwEVwlyULYgn/tMQYd6Obag3ylCjONle3gdErfXBW61SVTlR9QR7uWlgeIkg== + dependencies: + "@babel/types" "^7.14.8" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61" + integrity sha512-EivH9EgBIb+G8ij1B2jAwSH36WnGvkQSEC6CkX/6v6ZFlw5fVOHvsgGF4uiEHO2GzMvunZb6tDLQEQSdrdocrA== + dependencies: + "@babel/types" "^7.14.5" + "@babel/helper-annotate-as-pure@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" @@ -167,6 +190,15 @@ "@babel/template" "^7.10.4" "@babel/types" "^7.10.4" +"@babel/helper-function-name@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz#89e2c474972f15d8e233b52ee8c480e2cfcd50c4" + integrity sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ== + dependencies: + "@babel/helper-get-function-arity" "^7.14.5" + "@babel/template" "^7.14.5" + "@babel/types" "^7.14.5" + "@babel/helper-get-function-arity@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" @@ -174,6 +206,13 @@ dependencies: "@babel/types" "^7.10.4" +"@babel/helper-get-function-arity@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz#25fbfa579b0937eee1f3b805ece4ce398c431815" + integrity sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg== + dependencies: + "@babel/types" "^7.14.5" + "@babel/helper-hoist-variables@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz#d49b001d1d5a68ca5e6604dda01a6297f7c9381e" @@ -181,6 +220,13 @@ dependencies: "@babel/types" "^7.10.4" +"@babel/helper-hoist-variables@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz#e0dd27c33a78e577d7c8884916a3e7ef1f7c7f8d" + integrity sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ== + dependencies: + "@babel/types" "^7.14.5" + "@babel/helper-member-expression-to-functions@^7.10.4", "@babel/helper-member-expression-to-functions@^7.10.5": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz#ae69c83d84ee82f4b42f96e2a09410935a8f26df" @@ -188,6 +234,13 @@ dependencies: "@babel/types" "^7.11.0" +"@babel/helper-module-imports@^7.0.0": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3" + integrity sha512-SwrNHu5QWS84XlHwGYPDtCxcA0hrSlL2yhWYLgeOc0w7ccOl2qv4s/nARI0aYZW+bSwAL5CukeXA47B/1NKcnQ== + dependencies: + "@babel/types" "^7.14.5" + "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz#4c5c54be04bd31670a7382797d75b9fa2e5b5620" @@ -269,11 +322,23 @@ dependencies: "@babel/types" "^7.11.0" +"@babel/helper-split-export-declaration@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz#22b23a54ef51c2b7605d851930c1976dd0bc693a" + integrity sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA== + dependencies: + "@babel/types" "^7.14.5" + "@babel/helper-validator-identifier@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== +"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.8.tgz#32be33a756f29e278a0d644fa08a2c9e0f88a34c" + integrity sha512-ZGy6/XQjllhYQrNw/3zfWRwZCTVSiBLZ9DHVZxn9n2gip/7ab8mv2TWlKPIBk26RwedCBoWdjLmn+t9na2Gcow== + "@babel/helper-wrap-function@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" @@ -302,11 +367,25 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" + integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== + dependencies: + "@babel/helper-validator-identifier" "^7.14.5" + chalk "^2.0.0" + js-tokens "^4.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0", "@babel/parser@^7.9.0": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== +"@babel/parser@^7.14.5", "@babel/parser@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.8.tgz#66fd41666b2d7b840bd5ace7f7416d5ac60208d4" + integrity sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA== + "@babel/plugin-proposal-async-generator-functions@^7.10.4", "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz#3491cabf2f7c179ab820606cec27fed15e0e8558" @@ -1124,6 +1203,15 @@ "@babel/parser" "^7.10.4" "@babel/types" "^7.10.4" +"@babel/template@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" + integrity sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/parser" "^7.14.5" + "@babel/types" "^7.14.5" + "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0", "@babel/traverse@^7.9.0": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" @@ -1139,6 +1227,21 @@ globals "^11.1.0" lodash "^4.17.19" +"@babel/traverse@^7.4.5": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.8.tgz#c0253f02677c5de1a8ff9df6b0aacbec7da1a8ce" + integrity sha512-kexHhzCljJcFNn1KYAQ6A5wxMRzq9ebYpEDV4+WdNyr3i7O44tanbDOR/xjiG2F3sllan+LgwK+7OMk0EmydHg== + dependencies: + "@babel/code-frame" "^7.14.5" + "@babel/generator" "^7.14.8" + "@babel/helper-function-name" "^7.14.5" + "@babel/helper-hoist-variables" "^7.14.5" + "@babel/helper-split-export-declaration" "^7.14.5" + "@babel/parser" "^7.14.8" + "@babel/types" "^7.14.8" + debug "^4.1.0" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.7.0", "@babel/types@^7.9.0": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" @@ -1148,6 +1251,14 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.14.5", "@babel/types@^7.14.8": + version "7.14.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.8.tgz#38109de8fcadc06415fbd9b74df0065d4d41c728" + integrity sha512-iob4soQa7dZw8nodR/KlOQkPh9S4I8RwCxwRIFuiMRYjOzH/KJzdUfDgz6cGi5dDaclXF4P2PAhCdrBJNIg68Q== + dependencies: + "@babel/helper-validator-identifier" "^7.14.8" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -1166,6 +1277,28 @@ resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-10.1.0.tgz#f0950bba18819512d42f7197e56c518aa491cf18" integrity sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg== +"@emotion/is-prop-valid@^0.8.8": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== + dependencies: + "@emotion/memoize" "0.7.4" + +"@emotion/memoize@0.7.4": + version "0.7.4" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" + integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== + +"@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@^0.7.4": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + "@hapi/address@2.x.x": version "2.1.4" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -2390,6 +2523,21 @@ babel-plugin-named-asset-import@^0.3.6: resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.6.tgz#c9750a1b38d85112c9e166bf3ef7c5dbc605f4be" integrity sha512-1aGDUfL1qOOIoqk9QKGIo2lANk+C7ko/fqH0uIyC71x3PEGz0uVP8ISgfEsFuG+FKmjHTvFK/nNM8dowpmUxLA== +"babel-plugin-styled-components@>= 1.12.0": + version "1.13.2" + resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.2.tgz#ebe0e6deff51d7f93fceda1819e9b96aeb88278d" + integrity sha512-Vb1R3d4g+MUfPQPVDMCGjm3cDocJEUTR7Xq7QS95JWWeksN1wdFRYpD2kulDgI3Huuaf1CZd+NK4KQmqUFh5dA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-module-imports" "^7.0.0" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.11" + +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -2837,6 +2985,11 @@ camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== +camelize@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" + integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= + caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -3416,6 +3569,11 @@ css-blank-pseudo@^0.1.4: dependencies: postcss "^7.0.5" +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" + integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= + css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -3487,6 +3645,15 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" +css-to-react-native@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" + integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" @@ -5348,7 +5515,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -9113,6 +9280,11 @@ react-error-overlay@^6.0.7: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" integrity sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA== +react-icons@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.2.0.tgz#6dda80c8a8f338ff96a1851424d63083282630d0" + integrity sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ== + react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -9868,6 +10040,11 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -10391,6 +10568,22 @@ style-loader@0.23.1: loader-utils "^1.1.0" schema-utils "^1.0.0" +styled-components@^5, styled-components@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.0.tgz#e47c3d3e9ddfff539f118a3dd0fd4f8f4fb25727" + integrity sha512-bPJKwZCHjJPf/hwTJl6TbkSZg/3evha+XPEizrZUGb535jLImwDUdjTNxXqjjaASt2M4qO4AVfoHJNe3XB/tpQ== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/traverse" "^7.4.5" + "@emotion/is-prop-valid" "^0.8.8" + "@emotion/stylis" "^0.8.4" + "@emotion/unitless" "^0.7.4" + babel-plugin-styled-components ">= 1.12.0" + css-to-react-native "^3.0.0" + hoist-non-react-statics "^3.0.0" + shallowequal "^1.1.0" + supports-color "^5.5.0" + stylehacks@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5" @@ -10405,7 +10598,7 @@ supports-color@^2.0.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==