diff --git a/.env b/.env new file mode 100644 index 000000000..859e9be42 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_YOUTUBE_API_KEY=AIzaSyCJDk0qSH8AeWBxdSnIFdjw9kOiblAiHI4 diff --git a/package.json b/package.json index 4addda755..794e9f40e 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@testing-library/user-event": "^12.1.3", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-player": "^2.9.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-scripts": "3.4.3", diff --git a/src/components/App/App.component.jsx b/src/components/App/App.component.jsx index 1739c0058..794ee8575 100644 --- a/src/components/App/App.component.jsx +++ b/src/components/App/App.component.jsx @@ -1,7 +1,11 @@ import React, { useState } from 'react'; import { ThemeProvider, createGlobalStyle } from 'styled-components'; import HomePage from '../../pages/Home'; +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', @@ -21,20 +25,32 @@ const GlobalStyles = createGlobalStyle`body{ function App() { const [mode, setMode] = useState('light'); - + const [videoDetails, setVideoDetails] = useState(); + const { setSearch, response } = useFetchData(); return ( - - - -
- -
-
+ + + + +
+ + {videoDetails ? ( + + ) : ( + + )} +
+
+
); } diff --git a/src/components/App/App.component.test.jsx b/src/components/App/App.component.test.jsx index c4fc3d59e..3f4d45262 100644 --- a/src/components/App/App.component.test.jsx +++ b/src/components/App/App.component.test.jsx @@ -1,5 +1,12 @@ import React from 'react'; -import { render, screen, queryByAttribute } from '@testing-library/react'; +import { + render, + screen, + queryByAttribute, + cleanup, + fireEvent, +} from '@testing-library/react'; + import App from '.'; describe('App Component tests', () => { diff --git a/src/components/Emoji/Emoji.jsx b/src/components/Emoji/Emoji.jsx index 6737b0288..38151a5ff 100644 --- a/src/components/Emoji/Emoji.jsx +++ b/src/components/Emoji/Emoji.jsx @@ -4,8 +4,8 @@ const Emoji = (props) => ( {props.symbol} diff --git a/src/pages/Card/Card.page.jsx b/src/pages/Card/Card.page.jsx index 0b6a8b4bd..aae6581d8 100644 --- a/src/pages/Card/Card.page.jsx +++ b/src/pages/Card/Card.page.jsx @@ -3,6 +3,7 @@ import styled from 'styled-components'; const Container = styled.div` width: 22%; + height: 20em; border-style: solid; margin: 1em; border-radius: 1em; @@ -10,20 +11,29 @@ const Container = styled.div` justify-content: space-between; float: left; background-color: #d47b7b; - word-wrap: break-word; + text-overflow: ellipsis; + overflow: hidden; `; -function Card({ title, description, image, width, height }) { +function Card({ videoId, title, description, image, width, height, selectVideo }) { return ( -
- -
-

{title}

- {title} -

{description}

-
-
-
+ { + const val = { + videoId, + title, + description, + }; + + selectVideo(val); + }} + > +
+

{title}

+ {title} +

{description}

+
+
); } diff --git a/src/pages/Card/Card.page.test.jsx b/src/pages/Card/Card.page.test.jsx index bb83ec966..eab9a567d 100644 --- a/src/pages/Card/Card.page.test.jsx +++ b/src/pages/Card/Card.page.test.jsx @@ -39,7 +39,7 @@ describe('Card Component tests', () => { height="90" /> ); - expect(screen.getByText('testTitle').tagName).toBe('H2'); + expect(screen.getByText('testTitle').tagName).toBe('H3'); }); it('Card Description type', () => { diff --git a/src/pages/Header/Header.page.jsx b/src/pages/Header/Header.page.jsx index dd0a3fb65..a93ea5f01 100644 --- a/src/pages/Header/Header.page.jsx +++ b/src/pages/Header/Header.page.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import styled from 'styled-components'; import Emoji from '../../components/Emoji'; import Button from '../../components/Button'; @@ -15,16 +15,50 @@ const CurrentDiv = styled.div` background-color: #2183d3; `; -function HeaderSection() { +function HeaderSection({ globalSetSearch, videoDetails }) { + const [search, setSearch] = useState(undefined); + + function dispatchEvent() { + if (!search) { + alert('please enter a valid search term'); + } else { + globalSetSearch(search); + videoDetails(undefined); + } + } + + function eventHandler(event) { + if (event.charCode === 13) { + dispatchEvent('onEnter'); + } + } + + function onSearchClick() { + dispatchEvent('onClick'); + } + return ( - ); diff --git a/src/pages/Home/Home.page.jsx b/src/pages/Home/Home.page.jsx index 94129754c..d5a98b80f 100644 --- a/src/pages/Home/Home.page.jsx +++ b/src/pages/Home/Home.page.jsx @@ -1,22 +1,27 @@ import React from 'react'; -import HeaderSection from '../Header/Header.page'; import Card from '../Card/Card.page'; -import mockedData from '../../data/mockItems.json'; +import { useData } from '../../states/provider'; + +function HomePage({ selectVideo }) { + const { data } = useData(); + const elements = data.data; -function HomePage() { return (
- - {mockedData.items.map((item) => ( - - ))} + {elements && + elements.items && + elements.items.map((item) => ( + + ))}
); } diff --git a/src/pages/Home/Home.page.test.jsx b/src/pages/Home/Home.page.test.jsx index 149a4ea9d..1c5420178 100644 --- a/src/pages/Home/Home.page.test.jsx +++ b/src/pages/Home/Home.page.test.jsx @@ -1,31 +1,26 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import HomePage from '.'; +import DataProvider from '../../states/provider'; +import mockedData from '../../data/mockItems.json'; describe('Home Component tests', () => { - it('Header Section Contains search button defined', () => { - render(); - expect(screen.getByText('🔍')).toBeDefined(); - }); - - it('Header Section Contains search button defined', () => { - render(); - expect(screen.getByText('☰')).toBeDefined(); - }); - - it('Header Section Contains search input defined', () => { - render(); - expect(screen.getByPlaceholderText('Search the site...')).toBeDefined(); - }); - it('Card Section Contain title', () => { - render(); + render( + + + + ); expect( screen.getByText('Video Tour | Welcome to Wizeline Guadalajara').tagName - ).toBe('H2'); + ).toBe('H3'); }); it('Card Section Contain Description', () => { - render(); + render( + + + + ); expect( screen.getByText( 'Follow Hector Padilla, Wizeline Director of Engineering, for a lively tour of our office. In 2018, Wizeline opened its stunning new office in Guadalajara, Jalisco, ...' @@ -33,20 +28,32 @@ describe('Home Component tests', () => { ).toBe('P'); }); it('Card Section Contain image', () => { - render(); + render( + + + + ); expect( screen.getByAltText('Video Tour | Welcome to Wizeline Guadalajara').tagName ).toBe('IMG'); }); it('Card Section image size width', () => { - render(); + render( + + + + ); const image = screen.getByAltText('Video Tour | Welcome to Wizeline Guadalajara'); expect(image.width).toEqual(120); }); it('Card Section image size height', () => { - render(); + render( + + + + ); const image = screen.getByAltText('Video Tour | Welcome to Wizeline Guadalajara'); expect(image.height).toEqual(90); }); diff --git a/src/pages/Video/Video.page.jsx b/src/pages/Video/Video.page.jsx new file mode 100644 index 000000000..1504dbf4c --- /dev/null +++ b/src/pages/Video/Video.page.jsx @@ -0,0 +1,71 @@ +import React from 'react'; +import ReactPlayer from 'react-player'; +import styled from 'styled-components'; +import useFetchData from '../../states/useFetchData'; +import Card from '../Card/Card.page'; + +const Video = styled.div` + border-style: solid; + margin: 1em; + height: 50%; + padding: 1em; + border-radius: 1em; + display: block; + justify-content: space-between; + background-color: #d47b7b; + word-wrap: break-word; + transition: 0.5s; +`; + +const RelatedVideos = styled.div` + margin: 1em; + height: 20%; + padding: 1em; + border-radius: 1em; + background-color: #d47b7b; + display: inline-block; +`; + +function VideoPage({ video, selectVideo }) { + const { videoId, title, description } = video; + const { response } = useFetchData(videoId); + + console.log('relatedVideos', response); + + return ( +
+ + + +

Related Videos

+
+
+ {response && + response.items && + response.items.map((item) => ( + + ))} +
+
+ ); +} + +export default VideoPage; diff --git a/src/pages/Video/index.js b/src/pages/Video/index.js new file mode 100644 index 000000000..146c86bb6 --- /dev/null +++ b/src/pages/Video/index.js @@ -0,0 +1 @@ +export { default } from './Video.page'; diff --git a/src/states/provider.jsx b/src/states/provider.jsx new file mode 100644 index 000000000..8c9a6429a --- /dev/null +++ b/src/states/provider.jsx @@ -0,0 +1,30 @@ +import React, { createContext, useContext } from 'react'; + +const initState = { + data: [], + history: [], +}; + +const DataContext = createContext({ + data: [], + history: [], +}); + +function useData() { + const context = useContext(DataContext); + if (!context) { + throw new Error(`Can't use "useData" without an DataProvider!`); + } + return context; +} + +function DataProvider({ response, children }) { + initState.data = response; + const [data] = [initState]; + + return {children}; +} + +export { useData }; + +export default DataProvider; diff --git a/src/states/useFetchData.jsx b/src/states/useFetchData.jsx new file mode 100644 index 000000000..d3b71a920 --- /dev/null +++ b/src/states/useFetchData.jsx @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react'; + +const SEARCH_URL = 'https://www.googleapis.com/youtube/v3/search'; + +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}`; + + useEffect(() => { + const fetchData = async () => { + try { + const responseData = await fetch(url); + const jsonResponse = await responseData.json(); + setResponse(jsonResponse); + } catch (e) { + console.error(e); + } + }; + + fetchData(); + }, [url]); + + return { setSearch, response }; +}