From be021be2d7c9e4fe232f5cf78ecb3f317253f597 Mon Sep 17 00:00:00 2001 From: Jorge Polanco Date: Thu, 25 Mar 2021 00:59:56 -0600 Subject: [PATCH 1/2] - Made big refactor in the application - Add router - Delete missing files - create constant - fetch data changing approach - Move Card into a component instead of page folder --- src/components/App/App.component.jsx | 52 +------ src/components/Button/Button.jsx | 4 +- src/components/Card/Card.page.jsx | 54 ++++++++ src/components/Card/Card.page.styled.js | 28 ++++ .../Card/Card.page.test.jsx | 0 src/{pages => components}/Card/index.js | 0 src/components/Login/Login.component.jsx | 83 ++++++++++++ src/components/Login/Login.styled.js | 74 ++++++++++ src/components/Login/index.js | 1 + src/components/Routes/Routes.component.jsx | 23 ++++ src/components/Routes/index.js | 1 + src/global.css | 53 -------- src/pages/Card/Card.page.jsx | 40 ------ src/pages/Error/404_images.jpg | Bin 0 -> 32127 bytes src/pages/Error/Error.page.jsx | 16 +++ src/pages/Error/index.js | 1 + src/pages/Favorites/Favorite.page.jsx | 44 ++++++ src/pages/Favorites/index.js | 1 + src/pages/Header/Header.page.jsx | 128 ++++++++++++------ src/pages/Header/Header.page.styled.js | 26 ++++ src/pages/Home/Home.page.jsx | 25 +--- src/pages/Video/Video.page.jsx | 92 ++++++++----- src/pages/VideoList/VideoList.page.jsx | 27 ++++ .../VideoList.page.test.jsx} | 12 +- .../VideoList.styles.css} | 4 +- src/pages/VideoList/index.js | 1 + src/states/constants.js | 9 ++ src/states/fetchReducerData.jsx | 32 +++++ src/states/provider.jsx | 41 ++++-- src/states/reducer.js | 85 ++++++++++++ src/states/theme.js | 13 ++ src/states/useFetchData.jsx | 16 ++- 32 files changed, 726 insertions(+), 260 deletions(-) create mode 100644 src/components/Card/Card.page.jsx create mode 100644 src/components/Card/Card.page.styled.js rename src/{pages => components}/Card/Card.page.test.jsx (100%) rename src/{pages => components}/Card/index.js (100%) create mode 100644 src/components/Login/Login.component.jsx create mode 100644 src/components/Login/Login.styled.js create mode 100644 src/components/Login/index.js create mode 100644 src/components/Routes/Routes.component.jsx create mode 100644 src/components/Routes/index.js delete mode 100644 src/global.css delete mode 100644 src/pages/Card/Card.page.jsx create mode 100644 src/pages/Error/404_images.jpg create mode 100644 src/pages/Error/Error.page.jsx create mode 100644 src/pages/Error/index.js create mode 100644 src/pages/Favorites/Favorite.page.jsx create mode 100644 src/pages/Favorites/index.js create mode 100644 src/pages/Header/Header.page.styled.js create mode 100644 src/pages/VideoList/VideoList.page.jsx rename src/pages/{Home/Home.page.test.jsx => VideoList/VideoList.page.test.jsx} (90%) rename src/pages/{Home/Home.styles.css => VideoList/VideoList.styles.css} (65%) create mode 100644 src/pages/VideoList/index.js create mode 100644 src/states/constants.js create mode 100644 src/states/fetchReducerData.jsx create mode 100644 src/states/reducer.js create mode 100644 src/states/theme.js diff --git a/src/components/App/App.component.jsx b/src/components/App/App.component.jsx index 794ee8575..330429249 100644 --- a/src/components/App/App.component.jsx +++ b/src/components/App/App.component.jsx @@ -1,55 +1,13 @@ -import React, { useState } from 'react'; -import { ThemeProvider, createGlobalStyle } from 'styled-components'; -import HomePage from '../../pages/Home'; +import React from 'react'; import HeaderSection from '../../pages/Header/Header.page'; import DataProvider from '../../states/provider'; -import Button from '../Button'; -import useFetchData from '../../states/useFetchData'; -import VideoPage from '../../pages/Video/Video.page'; - -const lightTheme = { - bg: '#fff', - text: '#121212', -}; - -const darkTheme = { - bg: '#121212', - text: '#fff', -}; - -const GlobalStyles = createGlobalStyle`body{ - color: ${(props) => props.theme.text}; - background-color: ${(props) => props.theme.bg}; - transition: 0.5s; -}`; +import Routes from '../Routes/Routes.component'; function App() { - const [mode, setMode] = useState('light'); - const [videoDetails, setVideoDetails] = useState(); - const { setSearch, response } = useFetchData(); return ( - - - - -
- - {videoDetails ? ( - - ) : ( - - )} -
-
+ + + ); } diff --git a/src/components/Button/Button.jsx b/src/components/Button/Button.jsx index 97604214f..e1aa54741 100644 --- a/src/components/Button/Button.jsx +++ b/src/components/Button/Button.jsx @@ -2,10 +2,10 @@ import styled from 'styled-components'; const Button = styled.button` background: transparent; - border: none; + border: 5px; font-size: ${(props) => props.size}; cursor: pointer; - color: #919191; + color: #f0eeee; `; export default Button; diff --git a/src/components/Card/Card.page.jsx b/src/components/Card/Card.page.jsx new file mode 100644 index 000000000..c08828128 --- /dev/null +++ b/src/components/Card/Card.page.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { useData } from '../../states/provider'; +import { ACTIONS } from '../../states/reducer'; +import Emoji from '../Emoji/Emoji'; +import Button from '../Button/Button'; +import Container, { Styled } from './Card.page.styled'; + +function Card({ item }) { + const { videoId } = item.id; + const title = item?.snippet?.title ?? ''; + const description = item?.snippet?.description ?? ''; + const image = item?.snippet?.thumbnails?.default.url; + const width = item?.snippet?.thumbnails?.default?.width ?? 120; + const height = item?.snippet?.thumbnails?.default?.height ?? 90; + const { data, dispatch } = useData(); + + function toggleFavoriteVideo() { + dispatch({ + type: data.favoriteVideoList.has(videoId) + ? ACTIONS.REMOVE_FROM_FAVORITES + : ACTIONS.ADD_TO_FAVORITES, + payload: { video: item }, + }); + } + + function handleSelectVideo() { + const selectedVideo = item; + + dispatch({ + type: ACTIONS.CHANGE_SELECTED_VIDEO, + payload: { + selectedVideo, + }, + }); + } + + return ( + +
+ {' '} + {' '} + +

{title}

+ {title} +

{description}

+
+
+
+ ); +} + +export default Card; diff --git a/src/components/Card/Card.page.styled.js b/src/components/Card/Card.page.styled.js new file mode 100644 index 000000000..2170a3716 --- /dev/null +++ b/src/components/Card/Card.page.styled.js @@ -0,0 +1,28 @@ +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; +import useData from '../../states/provider'; + +const Container = styled.div` + width: 22%; + height: 20em; + border-style: solid; + margin: 1em; + border-radius: 1em; + display: flex; + justify-content: space-between; + float: left; + background-color: #d47b7b; + text-overflow: ellipsis; + overflow: hidden; +`; + +const StyledLink = styled(Link)` + text-decoration: 'none'; +`; + +const Styled = { + Link: StyledLink, +}; + +export { Styled }; +export default Container; diff --git a/src/pages/Card/Card.page.test.jsx b/src/components/Card/Card.page.test.jsx similarity index 100% rename from src/pages/Card/Card.page.test.jsx rename to src/components/Card/Card.page.test.jsx diff --git a/src/pages/Card/index.js b/src/components/Card/index.js similarity index 100% rename from src/pages/Card/index.js rename to src/components/Card/index.js diff --git a/src/components/Login/Login.component.jsx b/src/components/Login/Login.component.jsx new file mode 100644 index 000000000..cd590c478 --- /dev/null +++ b/src/components/Login/Login.component.jsx @@ -0,0 +1,83 @@ +import React, { useState } from 'react'; +import Styled from './Login.styled'; +import { useData } from '../../states/provider'; +import { ACTIONS } from '../../states/reducer'; + +function ModalLogin({ show }) { + const { data, dispatch } = useData(); + const [userName, setUserName] = useState(undefined); + const [password, setPassword] = useState(undefined); + const [errorMessage, setErrorMessage] = useState(false); + + function validateUserLogin() { + if (data.loginUser) { + show(false); + } else { + setErrorMessage('usuario Invalido'); + } + } + + function handlerLogin() { + console.log(data); + if (userName && userName.trim().length > 0) { + if (password && password.trim().length > 0) { + dispatch({ + type: ACTIONS.LOGIN, + payload: { user: userName }, + }); + + setTimeout(validateUserLogin, 300); + } else { + setErrorMessage('Please enter a password'); + } + } else { + setErrorMessage('Please enter a username'); + } + } + + return ( +
+ {data.loginUser ? ( + '' + ) : ( + + + show(false)}> + × + + + + Username + + setUserName(event.target.value)} + /> + + + Password + + setPassword(event.target.value)} + /> + {errorMessage || ''} + + + Login + + + + + )} +
+ ); +} + +export default ModalLogin; diff --git a/src/components/Login/Login.styled.js b/src/components/Login/Login.styled.js new file mode 100644 index 000000000..eeae6cf59 --- /dev/null +++ b/src/components/Login/Login.styled.js @@ -0,0 +1,74 @@ +import styled from 'styled-components'; + +const ModalBackground = styled.div` + display: block; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgb(0, 0, 0); + background-color: rgba(0, 0, 0, 0.4); +`; + +const ModalContent = styled.div` + background-color: #2183d3; + margin: 15% auto; + padding: 20px; + border: 1px solid #888; + width: 30%; + border-radius: 1em; + border-style: solid 5px; +`; + +const CloseButton = styled.span` + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + &:hover, + &:focus { + color: black; + text-decoration: none; + cursor: pointer; + } +`; + +const LoginForm = styled.div` + display: flex; + flex-direction: column; +`; + +const FormLabel = styled.label` + margin: 10px 0; +`; + +const Input = styled.input` + margin: 10px 0; + border-radius: 5px; + border: 1px solid black; + line-height: 1.5em; + padding: 5px; +`; + +const LoginButton = styled.button` + border-radius: 5px; + border: 1px solid black; + padding: 10px; + margin: 10px 0; + cursor: pointer; +`; + +const Styled = { + ModalBackground, + ModalContent, + CloseButton, + LoginForm, + FormLabel, + Input, + LoginButton, +}; + +export default Styled; diff --git a/src/components/Login/index.js b/src/components/Login/index.js new file mode 100644 index 000000000..26b4a1a8e --- /dev/null +++ b/src/components/Login/index.js @@ -0,0 +1 @@ +export { default } from './ModalLogin.component'; diff --git a/src/components/Routes/Routes.component.jsx b/src/components/Routes/Routes.component.jsx new file mode 100644 index 000000000..5df124bd0 --- /dev/null +++ b/src/components/Routes/Routes.component.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import HomePage from '../../pages/Home/Home.page'; +import FavoritePage from '../../pages/Favorites/Favorite.page'; +import ErrorPage from '../../pages/Error/Error.page'; +import VideoPage from '../../pages/Video/Video.page'; +import VideoListPage from '../../pages/VideoList/VideoList.page'; + +export default function Routes() { + return ( + <> + + + + + + + + + + ); +} diff --git a/src/components/Routes/index.js b/src/components/Routes/index.js new file mode 100644 index 000000000..860cf0e34 --- /dev/null +++ b/src/components/Routes/index.js @@ -0,0 +1 @@ +export { default } from './Routes.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/pages/Card/Card.page.jsx b/src/pages/Card/Card.page.jsx deleted file mode 100644 index aae6581d8..000000000 --- a/src/pages/Card/Card.page.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; - -const Container = styled.div` - width: 22%; - height: 20em; - border-style: solid; - margin: 1em; - border-radius: 1em; - display: flex; - justify-content: space-between; - float: left; - background-color: #d47b7b; - text-overflow: ellipsis; - overflow: hidden; -`; - -function Card({ videoId, title, description, image, width, height, selectVideo }) { - return ( - { - const val = { - videoId, - title, - description, - }; - - selectVideo(val); - }} - > -
-

{title}

- {title} -

{description}

-
-
- ); -} - -export default Card; diff --git a/src/pages/Error/404_images.jpg b/src/pages/Error/404_images.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1652c67e5eef9cb68f536acccd724ceee22f42f9 GIT binary patch literal 32127 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 literal 0 HcmV?d00001 diff --git a/src/pages/Error/Error.page.jsx b/src/pages/Error/Error.page.jsx new file mode 100644 index 000000000..e9037d123 --- /dev/null +++ b/src/pages/Error/Error.page.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import image from './404_images.jpg'; + +function ErrorPage() { + + return ( +

+
+

bad URL path or you do not have permissions to this page

+
+ 404 +
+ ); +} + +export default ErrorPage; diff --git a/src/pages/Error/index.js b/src/pages/Error/index.js new file mode 100644 index 000000000..ba9375cf2 --- /dev/null +++ b/src/pages/Error/index.js @@ -0,0 +1 @@ +export { default } from './Error.page'; diff --git a/src/pages/Favorites/Favorite.page.jsx b/src/pages/Favorites/Favorite.page.jsx new file mode 100644 index 000000000..2e0319aed --- /dev/null +++ b/src/pages/Favorites/Favorite.page.jsx @@ -0,0 +1,44 @@ +import React, { useEffect } from 'react'; +import Card from '../../components/Card/Card.page'; +import { useData } from '../../states/provider'; +import fetchReducerData from '../../states/fetchReducerData'; +import Emoji from '../../components/Emoji/Emoji'; +import Button from '../../components/Button/Button'; +import ErrorPage from '../Error/Error.page'; + +function FavoritePage() { + const { dispatch, data } = useData(); + const videoId = data.selectedVideo && data.selectedVideo.videoId; + + useEffect(() => { + fetchReducerData(data.searchValue, videoId, dispatch); + }, [data.searchValue, dispatch, videoId]); + + const elements = { items: [...data.favoriteVideoList.values()] }; + + return ( +
+ {data.loginUser ? ( +
+ +
+
+ {elements && elements.items && elements.items.length > 0 ? ( + elements.items.map((item) => ( + + )) + ) : ( +

There's no Favorite videos yet

+ )} +
+
+ ) : ( + + )} +
+ ); +} + +export default FavoritePage; diff --git a/src/pages/Favorites/index.js b/src/pages/Favorites/index.js new file mode 100644 index 000000000..7d2322e2b --- /dev/null +++ b/src/pages/Favorites/index.js @@ -0,0 +1 @@ +export { default } from './Favorite.page'; diff --git a/src/pages/Header/Header.page.jsx b/src/pages/Header/Header.page.jsx index a93ea5f01..68ef673e4 100644 --- a/src/pages/Header/Header.page.jsx +++ b/src/pages/Header/Header.page.jsx @@ -1,29 +1,27 @@ import React, { useState } from 'react'; -import styled from 'styled-components'; import Emoji from '../../components/Emoji'; import Button from '../../components/Button'; +import { useData } from '../../states/provider'; +import { ACTIONS } from '../../states/reducer'; +import CONSTANTS from '../../states/constants'; +import CurrentDiv, { Styled } from './Header.page.styled'; +import Login from '../../components/Login/Login.component'; -const CurrentDiv = styled.div` - width: 100%; - height: 10%; - min-height: 6vh; - box-sizing: border-box; - display: flex; - justify-content: space-between; - border-radius: 1em; - border-style: solid 5px; - background-color: #2183d3; -`; - -function HeaderSection({ globalSetSearch, videoDetails }) { - const [search, setSearch] = useState(undefined); +function HeaderSection() { + const { data, dispatch } = useData(); + let localSearch; + const [showLogin, setShowLogin] = useState(false); function dispatchEvent() { - if (!search) { + if (!localSearch) { alert('please enter a valid search term'); } else { - globalSetSearch(search); - videoDetails(undefined); + dispatch({ + type: ACTIONS.CHANGE_SEARCH_VALUE, + payload: { searchValue: localSearch }, + }); + + document.getElementById('home_link').click(); } } @@ -37,30 +35,80 @@ function HeaderSection({ globalSetSearch, videoDetails }) { dispatchEvent('onClick'); } + function changeTheme(event) { + event.preventDefault(); + dispatch({ + type: ACTIONS.CHANGE_SELECTED_THEME, + payload: { + theme: + data.selectedTheme === CONSTANTS.themes.darkTheme + ? CONSTANTS.themes.lightTheme + : CONSTANTS.themes.darkTheme, + }, + }); + } + + function setSearch(value) { + localSearch = value; + } + + function handlerLogin() { + if (showLogin) { + dispatch({ type: ACTIONS.LOGOUT, payload: {} }); + } + + setShowLogin(!showLogin); + } + return ( - - - - + + + + {data.selectedVideo && ( + + )} + + + + {showLogin ? : ''} + ); } diff --git a/src/pages/Header/Header.page.styled.js b/src/pages/Header/Header.page.styled.js new file mode 100644 index 000000000..ac79d1af7 --- /dev/null +++ b/src/pages/Header/Header.page.styled.js @@ -0,0 +1,26 @@ +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +const CurrentDiv = styled.div` + width: 100%; + height: 10%; + min-height: 6vh; + box-sizing: border-box; + display: flex; + justify-content: space-between; + border-radius: 1em; + border-style: solid 5px; + background-color: #2183d3; +`; + +const StyledLink = styled(Link)` + padding-left: 0.5em; + font-size: 0.6em; +`; + +const Styled = { + Link: StyledLink, +}; + +export { Styled }; +export default CurrentDiv; diff --git a/src/pages/Home/Home.page.jsx b/src/pages/Home/Home.page.jsx index d5a98b80f..507e3d8b1 100644 --- a/src/pages/Home/Home.page.jsx +++ b/src/pages/Home/Home.page.jsx @@ -1,29 +1,12 @@ import React from 'react'; -import Card from '../Card/Card.page'; +import VideoPage from '../Video/Video.page'; +import VideoListPage from '../VideoList/VideoList.page'; import { useData } from '../../states/provider'; -function HomePage({ selectVideo }) { +function HomePage() { const { data } = useData(); - const elements = data.data; - return ( -
- {elements && - elements.items && - elements.items.map((item) => ( - - ))} -
- ); + return data.selectedVideo ? : ; } export default HomePage; diff --git a/src/pages/Video/Video.page.jsx b/src/pages/Video/Video.page.jsx index 1504dbf4c..821728293 100644 --- a/src/pages/Video/Video.page.jsx +++ b/src/pages/Video/Video.page.jsx @@ -1,8 +1,12 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import ReactPlayer from 'react-player'; import styled from 'styled-components'; -import useFetchData from '../../states/useFetchData'; -import Card from '../Card/Card.page'; +import fetchReducerData from '../../states/fetchReducerData'; +import { useData } from '../../states/provider'; +import VideoListPage from '../VideoList/VideoList.page'; +import { ACTIONS } from '../../states/reducer'; +import Emoji from '../../components/Emoji/Emoji'; +import Button from '../../components/Button/Button'; const Video = styled.div` border-style: solid; @@ -26,44 +30,60 @@ const RelatedVideos = styled.div` display: inline-block; `; -function VideoPage({ video, selectVideo }) { - const { videoId, title, description } = video; - const { response } = useFetchData(videoId); +function VideoPage() { + const { data, dispatch } = useData(); + const item = data.selectedVideo; + const { videoId } = item?.id ?? ''; + const title = item?.snippet?.title ?? ''; + const description = item?.snippet?.description ?? ''; - console.log('relatedVideos', response); + function toggleFavoriteVideo() { + dispatch({ + type: data.favoriteVideoList.has(videoId) + ? ACTIONS.REMOVE_FROM_FAVORITES + : ACTIONS.ADD_TO_FAVORITES, + payload: { video: { videoId, title, description } }, + }); + } + + useEffect(() => { + fetchReducerData(data.searchValue, videoId, dispatch); + }, [data.searchValue, dispatch, videoId]); return (
- - - -

Related Videos

-
-
- {response && - response.items && - response.items.map((item) => ( - +
+

{description}

+ + + +

Related Videos

+
+
+ +
+ + ) : ( +

There's no video selected

+ )}
); } diff --git a/src/pages/VideoList/VideoList.page.jsx b/src/pages/VideoList/VideoList.page.jsx new file mode 100644 index 000000000..b13835930 --- /dev/null +++ b/src/pages/VideoList/VideoList.page.jsx @@ -0,0 +1,27 @@ +import React, { useEffect } from 'react'; +import Card from '../../components/Card/Card.page'; +import { useData } from '../../states/provider'; +import fetchReducerData from '../../states/fetchReducerData'; + +function VideoListPage() { + const { dispatch, data } = useData(); + const videoId = data.selectedVideo && data.selectedVideo.videoId; + + useEffect(() => { + fetchReducerData(data.searchValue, videoId, dispatch); + }, [data.searchValue, dispatch, videoId]); + + const elements = data.videosAvailables; + + console.log(elements); + + return ( +
+ {elements && + elements.items && + elements.items.map((item) => )} +
+ ); +} + +export default VideoListPage; diff --git a/src/pages/Home/Home.page.test.jsx b/src/pages/VideoList/VideoList.page.test.jsx similarity index 90% rename from src/pages/Home/Home.page.test.jsx rename to src/pages/VideoList/VideoList.page.test.jsx index 1c5420178..4c96f3f42 100644 --- a/src/pages/Home/Home.page.test.jsx +++ b/src/pages/VideoList/VideoList.page.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import HomePage from '.'; +import VideoListPage from '.'; import DataProvider from '../../states/provider'; import mockedData from '../../data/mockItems.json'; @@ -8,7 +8,7 @@ describe('Home Component tests', () => { it('Card Section Contain title', () => { render( - + ); expect( @@ -18,7 +18,7 @@ describe('Home Component tests', () => { it('Card Section Contain Description', () => { render( - + ); expect( @@ -30,7 +30,7 @@ describe('Home Component tests', () => { it('Card Section Contain image', () => { render( - + ); expect( @@ -40,7 +40,7 @@ describe('Home Component tests', () => { it('Card Section image size width', () => { render( - + ); @@ -51,7 +51,7 @@ describe('Home Component tests', () => { it('Card Section image size height', () => { render( - + ); const image = screen.getByAltText('Video Tour | Welcome to Wizeline Guadalajara'); diff --git a/src/pages/Home/Home.styles.css b/src/pages/VideoList/VideoList.styles.css similarity index 65% rename from src/pages/Home/Home.styles.css rename to src/pages/VideoList/VideoList.styles.css index 5e0a702c3..0d56b8ad2 100644 --- a/src/pages/Home/Home.styles.css +++ b/src/pages/VideoList/VideoList.styles.css @@ -1,8 +1,8 @@ -.homepage { +.videoListPage { text-align: center; } -.homepage h1 { +.videoListPage h1 { font-size: 3rem; letter-spacing: -2px; } diff --git a/src/pages/VideoList/index.js b/src/pages/VideoList/index.js new file mode 100644 index 000000000..47b06570a --- /dev/null +++ b/src/pages/VideoList/index.js @@ -0,0 +1 @@ +export { default } from './VideoList.page'; diff --git a/src/states/constants.js b/src/states/constants.js new file mode 100644 index 000000000..77ba3fafd --- /dev/null +++ b/src/states/constants.js @@ -0,0 +1,9 @@ +import themes from './theme'; + +const CONSTANTS = { + themes, + FAVORITE_KEY: 'favorites', + SEARCH_URL_BASE: 'https://www.googleapis.com/youtube/v3/search', +}; + +export default CONSTANTS; diff --git a/src/states/fetchReducerData.jsx b/src/states/fetchReducerData.jsx new file mode 100644 index 000000000..410225936 --- /dev/null +++ b/src/states/fetchReducerData.jsx @@ -0,0 +1,32 @@ +import CONSTANTS from './constants'; +import { ACTIONS } from './reducer'; + +function getUrl(search, videoId) { + const relatedVideosUrl = videoId ? `&relatedToVideoId=${videoId}` : ''; + const maxResults = `&maxResults=${videoId ? '8' : '40'}`; + const url = `${CONSTANTS.SEARCH_URL_BASE}?part=snippet${maxResults}&q=${search}&key=${process.env.REACT_APP_YOUTUBE_API_KEY}&type=video${relatedVideosUrl}`; + return url; +} + +export default async function fetchReducerData(searchValue, videoId, dispatch) { + const url = getUrl(searchValue, videoId); + + function setData(data) { + dispatch({ type: ACTIONS.CHANGE_RESPONSE, payload: { data } }); + } + + function setSelectedVideo(selectedVideo) { + dispatch({ type: ACTIONS.selectedVideo, payload: { selectedVideo } }); + } + + try { + if (videoId) { + setSelectedVideo(videoId); + } + const responseData = await fetch(url); + const jsonResponse = await responseData.json(); + setData(jsonResponse); + } catch (e) { + console.error(e); + } +} diff --git a/src/states/provider.jsx b/src/states/provider.jsx index 8c9a6429a..dd728a2c1 100644 --- a/src/states/provider.jsx +++ b/src/states/provider.jsx @@ -1,13 +1,30 @@ -import React, { createContext, useContext } from 'react'; +import React, { createContext, useContext, useReducer } from 'react'; +import { ThemeProvider, createGlobalStyle } from 'styled-components'; +import { BrowserRouter as Router } from 'react-router-dom'; +import CONSTANTS from './constants'; +import reducer from './reducer'; + +const GlobalStyles = createGlobalStyle`body{ + color: ${(props) => props.theme.text}; + background-color: ${(props) => props.theme.bg}; + transition: 0.5s; + }`; const initState = { - data: [], - history: [], + videosAvailables: [], + selectedTheme: CONSTANTS.themes.darkTheme, + selectedVideo: undefined, + searchValue: 'wizeline', + loginUser: undefined, + favoriteVideoList: + (localStorage.getItem(CONSTANTS.FAVORITE_KEY) && + new Map(JSON.parse(localStorage.getItem(CONSTANTS.FAVORITE_KEY)))) || + new Map(), }; const DataContext = createContext({ - data: [], - history: [], + state: undefined, + dispatch: undefined, }); function useData() { @@ -18,11 +35,17 @@ function useData() { return context; } -function DataProvider({ response, children }) { - initState.data = response; - const [data] = [initState]; +function DataProvider({ children }) { + const [data, dispatch] = useReducer(reducer, initState); - return {children}; + return ( + + + + {children} + + + ); } export { useData }; diff --git a/src/states/reducer.js b/src/states/reducer.js new file mode 100644 index 000000000..3724a7c5a --- /dev/null +++ b/src/states/reducer.js @@ -0,0 +1,85 @@ +import CONSTANTS from './constants'; + +const ACTIONS = { + LOGIN: 'LOGIN', + LOGOUT: 'LOGOUT', + CHANGE_SELECTED_THEME: 'CHANGE_SELECTED_THEME', + CHANGE_RESPONSE: 'CHANGE_RESPONSE', + CHANGE_SEARCH_VALUE: 'CHANGE_SEARCH_VALUE', + CHANGE_SELECTED_VIDEO: 'CHANGE_SELECTED_VIDEO', + ADD_TO_FAVORITES: 'ADD_TO_FAVORITES', + REMOVE_FROM_FAVORITES: 'REMOVE_FROM_FAVORITES', +}; + +function handleFavorite(isAddOperation, video) { + const favorites = new Map(JSON.parse(localStorage.getItem(CONSTANTS.FAVORITE_KEY))); + + if (video && video.id.videoId) { + if (isAddOperation) { + favorites.set(video.id.videoId, video); + } else { + favorites.delete(video.id.videoId); + } + + localStorage.setItem( + CONSTANTS.FAVORITE_KEY, + JSON.stringify(Array.from(favorites.entries())) + ); + } else { + console.log( + `something happends ${ + isAddOperation ? 'adding' : 'deleting' + } a new favorite video`, + video + ); + } + + return favorites; +} + +function loginMockUp(userName) { + return userName === 'Wizeline'; +} + +function reducer(state, action) { + console.log(action); + + const { data, selectedVideo } = action.payload; + switch (action.type) { + case ACTIONS.LOGIN: { + const userInSystem = loginMockUp(action.payload.user) + ? action.payload.user + : undefined; + return { ...state, loginUser: userInSystem }; + } + case ACTIONS.LOGOUT: { + return { ...state, loginUser: undefined }; + } + case ACTIONS.CHANGE_SELECTED_THEME: { + return { ...state, selectedTheme: action.payload.theme }; + } + case ACTIONS.CHANGE_RESPONSE: { + return { ...state, videosAvailables: data }; + } + case ACTIONS.CHANGE_SEARCH_VALUE: { + return { ...state, searchValue: action.payload.searchValue }; + } + case ACTIONS.CHANGE_SELECTED_VIDEO: { + return { ...state, selectedVideo }; + } + case ACTIONS.ADD_TO_FAVORITES: { + const favoriteVideoList = handleFavorite(true, action.payload.video); + return { ...state, favoriteVideoList }; + } + case ACTIONS.REMOVE_FROM_FAVORITES: { + const favoriteVideoList = handleFavorite(false, action.payload.video); + return { ...state, favoriteVideoList }; + } + default: { + return state; + } + } +} + +export { ACTIONS }; +export default reducer; diff --git a/src/states/theme.js b/src/states/theme.js new file mode 100644 index 000000000..73babd415 --- /dev/null +++ b/src/states/theme.js @@ -0,0 +1,13 @@ +const lightTheme = { + bg: '#fff', + text: '#121212', +}; + +const darkTheme = { + bg: '#121212', + text: '#fff', +}; + +const themes = { lightTheme, darkTheme }; + +export default themes; diff --git a/src/states/useFetchData.jsx b/src/states/useFetchData.jsx index d3b71a920..5667177c8 100644 --- a/src/states/useFetchData.jsx +++ b/src/states/useFetchData.jsx @@ -1,13 +1,20 @@ import { useEffect, useState } from 'react'; +import data from '../data/mockItems.json'; +import CONSTANTS from './constants'; -const SEARCH_URL = 'https://www.googleapis.com/youtube/v3/search'; +function getUrl(search, videoId) { + console.log('useEffect', { search, videoId }); + + const relatedVideosUrl = videoId ? `&relatedToVideoId=${videoId}` : ''; + const maxResults = `&maxResults=${videoId ? '8' : '40'}`; + const url = `${CONSTANTS.SEARCH_URL_BASE}?part=snippet${maxResults}&q=${search}&key=${process.env.REACT_APP_YOUTUBE_API_KEY}&type=video${relatedVideosUrl}`; + return url; +} export default function useFetchData(videoId) { const [response, setResponse] = useState([]); const [search, setSearch] = useState('wizeline'); - const relatedVideosUrl = videoId ? `&relatedToVideoId=${videoId}` : ''; - const maxResults = `&maxResults=${videoId ? '8' : '40'}`; - const url = `${SEARCH_URL}?part=snippet${maxResults}&q=${search}&key=${process.env.REACT_APP_YOUTUBE_API_KEY}&type=video${relatedVideosUrl}`; + const url = getUrl(search, videoId); useEffect(() => { const fetchData = async () => { @@ -15,6 +22,7 @@ export default function useFetchData(videoId) { const responseData = await fetch(url); const jsonResponse = await responseData.json(); setResponse(jsonResponse); + //setResponse(data); } catch (e) { console.error(e); } From 72c44394f7c6e0b61a35e5c39fdfd844036399f6 Mon Sep 17 00:00:00 2001 From: Jorge Polanco Date: Thu, 25 Mar 2021 01:08:46 -0600 Subject: [PATCH 2/2] - remove unnecessary import --- src/components/Card/Card.page.styled.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Card/Card.page.styled.js b/src/components/Card/Card.page.styled.js index 2170a3716..08784f0be 100644 --- a/src/components/Card/Card.page.styled.js +++ b/src/components/Card/Card.page.styled.js @@ -1,6 +1,5 @@ import styled from 'styled-components'; import { Link } from 'react-router-dom'; -import useData from '../../states/provider'; const Container = styled.div` width: 22%;