Skip to content

Commit

Permalink
complete UI search results page
Browse files Browse the repository at this point in the history
  • Loading branch information
phongduybui committed Apr 18, 2021
1 parent 0660277 commit 1062c94
Show file tree
Hide file tree
Showing 17 changed files with 386 additions and 65 deletions.
37 changes: 28 additions & 9 deletions src/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SET_IS_FETCHING_DATA,
TOGGLE_DARK_MODE,
FETCH_VIDEOS_BY_TERM,
FETCH_CHANNEL_SEARCH_RESULTS,
} from "./types";
import youtube from "../apis/youtube";
import _ from "lodash";
Expand Down Expand Up @@ -106,20 +107,38 @@ const _fetchChannel = _.memoize(async (channelId, dispatch) => {
dispatch({ type: FETCH_CHANNEL, payload: response.data.items });
});

export const fetchVideosByTerm = term => dispatch => _fetchVideosByTerm(term, dispatch);
const _fetchVideosByTerm = _.memoize(async (term, dispatch) => {
export const fetchVideosByTerm = term => async dispatch => {
const response = await youtube.get('/search', {
part: "snippet, contentDetails, statistics",
q: term,
maxResults: 20
params: {
part: "snippet",
q: term,
type: 'video',
maxResults: 20,
}
});

dispatch({ type: FETCH_VIDEOS_BY_TERM, payload: response.data });
});
}

export const fetchChannelSearchResults = (id) => dispatch => {
_fetchChannelSearchResults(id, dispatch)
};
const _fetchChannelSearchResults = _.memoize(async (id, dispatch) => {
const response = await youtube.get('/channels', {
params: {
part: "snippet, statistics",
id: id,
}
})

dispatch({ type: FETCH_CHANNEL_SEARCH_RESULTS, payload: response.data.items})
})

export const fetchVideoSAndChannelsByTerm = (term) => async (dispatch, getState) => {
export const fetchVideosAndChannelsByTerm = (term) => async (dispatch, getState) => {
await dispatch(fetchVideosByTerm(term));

const channelIds = _.uniq(_.map(getState().searchResults, video => video.snippet.id));
channelIds.forEach(id => dispatch(fetchChannel(id)));
const channelIds = _.uniq(_.map(getState().searchResults.videos, video => {
return video.snippet.channelId
}));
channelIds.forEach(id => dispatch(fetchChannelSearchResults(id)));
}
1 change: 1 addition & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export const FETCH_CHANNEL = 'FETCH_CHANNEL';
export const SET_IS_FETCHING_DATA = 'SET_IS_FETCHING_DATA';
export const TOGGLE_DARK_MODE = 'TOGGLE_DARK_MODE';
export const FETCH_VIDEOS_BY_TERM = 'FETCH_VIDEOS_BY_TERM';
export const FETCH_CHANNEL_SEARCH_RESULTS = 'FETCH_CHANNEL_SEARCH_RESULTS';
export const FETCH_VIDEOS_AND_CHANNELS_BY_TERM = 'FETCH_VIDEOS_AND_CHANNELS_BY_TERM';
6 changes: 3 additions & 3 deletions src/apis/youtube.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import axios from 'axios';

const API_KEY = 'AIzaSyD6-G_GH5O5QT_f8oeDHZjSybzThcBXPJo';
// const API_KEY_SECONDARY = 'AIzaSyDTP6O2vSfH2gF3976Y9SRkIHAtoApipz8';
// const API_KEY = 'AIzaSyD6-G_GH5O5QT_f8oeDHZjSybzThcBXPJo';
const API_KEY_SECONDARY = 'AIzaSyDTP6O2vSfH2gF3976Y9SRkIHAtoApipz8';
// const API_KEY_THIRD = 'AIzaSyCasS9wwE-zCFuE-thapTMKUYb7tJjCbPs';

export default axios.create({
baseURL: 'https://www.googleapis.com/youtube/v3',
params: {
key: API_KEY
key: API_KEY_SECONDARY
}
})
32 changes: 29 additions & 3 deletions src/components/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ body {
margin: 0 var(--margin-lr-container);
}

.main {
margin-left: calc(var(--side-bar-width) - var(--margin-lr-container));
margin-top: var(--header-height);
margin-right: calc(var(--margin-lr-container) * -1);
background-color: var(--gray-background-color);
padding: 25px 17px;
overflow: hidden;
}

.main.collapse {
margin-left: calc(var(--side-bar-tablet-width) - var(--margin-lr-container));
}

.dark-mode {
--background-color: #202020;
--nav-text-color: #fff;
Expand All @@ -66,19 +79,32 @@ body {
--search-bar-input-color: #fff;
--border-color: #303030;
}
.dark-mode .react-icons {
color: white;
}

.react-icons {
color: var(--secondary-text-color);
font-size: 2.2rem;
vertical-align: middle;
}

.dark-mode .react-icons {
color: white;
}

@media (max-width: 599px) {
:root {
--margin-lr-container: 10px;
}

.main {
margin-left: 0;
padding: 20px 0 0 0;
/* Full width */
margin-left: calc(var(--margin-lr-container) * -1);
}
}

@media (min-width: 600px) and (max-width: 1023px) {
.main {
margin-left: calc(var(--side-bar-tablet-width) - var(--margin-lr-container));
}
}
16 changes: 13 additions & 3 deletions src/components/App.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import './App.css'
import React from 'react';
import { connect } from 'react-redux';
import { BrowserRouter, Route } from 'react-router-dom';
import { IconContext } from 'react-icons/lib';
import useWindowDimensions from '../hooks/useWindowDimensions';
import Header from './Header';
import SideBar from './SideBar';
import ListVideos from './ListVideos';
import SearchResults from './SearchResults';

const App = () => {
const App = ({ isBarCollapse }) => {
const { width } = useWindowDimensions();
const sideBarStatus = isBarCollapse && width >= 1024 ? 'collapse' : '';

return (
<BrowserRouter>
Expand All @@ -15,10 +20,15 @@ const App = () => {
<Header />
<SideBar />
</IconContext.Provider>
<ListVideos />
<div className={`main ${sideBarStatus}`}>
<Route path="/" exact component={ListVideos} />
<Route path="/search-results" exact component={SearchResults} />
</div>
</div>
</BrowserRouter>
)
}

export default App;
const mapStateToProps = state => ({ isBarCollapse: state.isBarClick });

export default connect(mapStateToProps)(App);
25 changes: 25 additions & 0 deletions src/components/LineLoader.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.line-loader {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 2px;
background-color: red;
z-index: 99999;
animation: lineLoader 4s ease-in;
}

@keyframes lineLoader {
0% {
width: 20%;
}
40% {
width: 40%;
}
80% {
width: 80%;
}
100% {
width: 100%;
}
}
31 changes: 31 additions & 0 deletions src/components/LineLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import './LineLoader.css';
import React, { useRef, useEffect } from 'react';
import { connect } from 'react-redux'

const LineLoader = ({ isFetchingData }) => {
const ref = useRef();

useEffect(() => {
let timeoutId = 0;

if(ref.current && isFetchingData) {
ref.current.style.display = 'block';
ref.current.style.animationDuration = '20s';
}

if(ref.current && !isFetchingData) {
ref.current.style.animationDuration = '1.2s';
timeoutId = setTimeout(() => {
ref.current.style.display = 'none';
}, 2000);
}

return () => clearTimeout(timeoutId);
}, [isFetchingData])

return <div ref={ref} className="line-loader"></div>;
}

const mapStateToProps = state => ({ isFetchingData: state.isFetchingData });

export default connect(mapStateToProps)(LineLoader)
28 changes: 12 additions & 16 deletions src/components/ListVideos.css
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
.list-videos {
margin-left: calc(var(--side-bar-width) - var(--margin-lr-container));
margin-top: var(--header-height);
background-color: var(--gray-background-color);
padding: 25px 17px;
}

.list-videos.bars-collapsed {
margin-left: 50px;
}

.list-videos-wrapper {
box-sizing: border-box;
max-width: 100%;
display: flex;
justify-content: space-between;
justify-content: flex-start;
flex-wrap: wrap;
}

.list-videos.collapse {
/* .list-videos {
margin-left: calc(var(--side-bar-width) - var(--margin-lr-container));
margin-top: var(--header-height);
background-color: var(--gray-background-color);
padding: 25px 17px;
} */

/* .list-videos.collapse {
margin-left: calc(var(--side-bar-tablet-width) - var(--margin-lr-container));
}
} */

@media (min-width: 886px) and (max-width: 1023px) {
/* @media (min-width: 886px) and (max-width: 1023px) {
.list-videos {
margin-left: 50px;
}
Expand All @@ -38,4 +34,4 @@
margin-left: 0;
padding: 20px 0 0 0;
}
}
} */
17 changes: 5 additions & 12 deletions src/components/ListVideos.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,19 @@ import "./ListVideos.css";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { fetchHomeVideosAndChannels, setIsFetchingData } from "../actions";
import useWindowDimensions from '../hooks/useWindowDimensions';
import convertDuration from "../helpers/convertDuration";
import convertViewCount from "../helpers/convertViewCount";
import VideoItem from "./VideoItem";
import LineLoader from "./LineLoader";

const ListVideos = ({
homeVideos,
isBarClick,
nextPageToken,
fetchHomeVideosAndChannels,
setIsFetchingData,
isFetchingData
}) => {

const { width } = useWindowDimensions();

useEffect(() => {
setIsFetchingData(true);
fetchHomeVideosAndChannels();
Expand All @@ -40,7 +37,6 @@ const ListVideos = ({
if (homeVideos) {
return homeVideos.map((video) => {
if(typeof video === 'object') {

const {
title,
channelTitle,
Expand All @@ -57,8 +53,8 @@ const ListVideos = ({
channelTitle={channelTitle}
thumbnails={thumbnails}
publishedAt={publishedAt}
duration={convertDuration(video.contentDetails.duration)}
viewCount={convertViewCount(video.statistics.viewCount)}
duration={convertDuration(video.contentDetails?.duration)}
viewCount={convertViewCount(video.statistics?.viewCount)}
key={video.id}
/>
);
Expand All @@ -77,11 +73,9 @@ const ListVideos = ({
}
}

const sideBarStatus = isBarClick && width >= 1024 ? 'collapse' : '';

return (
<div className={`list-videos ${sideBarStatus}`}>

<div className="list-videos">
<LineLoader />
<div className="list-videos-wrapper">
{renderHomeVideos()}
{renderSkeleton()}
Expand All @@ -91,7 +85,6 @@ const ListVideos = ({
}

const mapStateToProps = (state) => ({
isBarClick: state.isBarClick,
homeVideos: Object.values(state.homeVideos),
nextPageToken: state.homeVideos.nextPageToken,
isFetchingData: state.isFetchingData,
Expand Down
Loading

1 comment on commit 1062c94

@vercel
Copy link

@vercel vercel bot commented on 1062c94 Apr 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.