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..08784f0be --- /dev/null +++ b/src/components/Card/Card.page.styled.js @@ -0,0 +1,27 @@ +import styled from 'styled-components'; +import { Link } from 'react-router-dom'; + +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 000000000..1652c67e5 Binary files /dev/null and b/src/pages/Error/404_images.jpg differ 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); }