fix #14 create the all topics page with the filter.

This commit is contained in:
Ahmed-Ayman 2018-12-13 18:19:33 +02:00
parent 3d20857fc1
commit ed1fffca88
11 changed files with 155 additions and 43 deletions

View File

@ -5,7 +5,7 @@ import './App.css';
import 'font-awesome/css/font-awesome.min.css';
import { Route, Switch } from 'react-router-dom';
import Home from './containers/Home';
import TopicsList from './containers/TopicsList';
import TopicsListContainer from './containers/TopicsListContainer';
import store from './store/configureStore';
import history from './history';
import Header from "./components/Header";
@ -25,7 +25,7 @@ class App extends Component {
<Search/>
<Switch>
<Route exact={true} path="/" component={Home}/>
<Route path="/topics" component={TopicsList}/>
<Route path="/topics" component={TopicsListContainer}/>
<Route path="/videos/:videoId" component={Video}/>
</Switch>
<Nav/>

View File

@ -13,6 +13,7 @@ import {
export function getAllTopics() {
return dispatch => {
dispatch(startLoadingAllTopics());
// this calls the API
fetchAllTopics()
.then(topics => {
dispatch(loadedTopics(topics));
@ -29,6 +30,7 @@ function startLoadingAllTopics() {
};
function loadedTopics(topics) {
// cache
setItem('allTopics', JSON.stringify(topics));
setItem('topicsUpdatedAt', new Date());
return {
@ -36,7 +38,9 @@ function loadedTopics(topics) {
payload: topics
};
};
// actions :
// - async actions
// - simple: return action type and payload.
export function getRandomTopicVideos(allTopics, numVideos=4) {
return dispatch => {
const randomTopic = getRandomTopic(allTopics);

View File

@ -3,7 +3,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import VideoItem from "./VideoItem";
import RandomItemTitle from "./SectionHeading";
import {Link} from 'react-router-dom';
class RandomTopic extends React.Component {
shuffleTopic() {
@ -22,13 +22,14 @@ class RandomTopic extends React.Component {
width={450}
>
{/* View all topics button*/}
<button className="view-all-topics" onClick="">
<span className="english-text">All Topics - </span>
<span className="arabic-text"> جميع المواضيع </span>
</button>
<Link to="/topics">
<button className="view-all-topics">
<span className="english-text">All Topics - </span>
<span className="arabic-text"> جميع المواضيع </span>
</button>
</Link>
{/* Shuffle button */}
<button className="shuffle-button" onClick={ this.shuffleTopic.bind(this) }>
<button className="shuffle-button" onClick={this.shuffleTopic.bind(this)}>
<span className="english-text">Shuffle Topics - </span>
<span className="arabic-text"> موضوع عشوائي </span>
<i className="fa fa-random"></i>
@ -41,12 +42,6 @@ class RandomTopic extends React.Component {
{this.props.videos.map(video =>
<VideoItem id={video.id} key={video.id} title={video.title}/>
)}
{/*<Link to="/topics">*/}
{/*<span className="view-all-parents">See All Topics</span>*/}
{/*</Link>*/}
{/*<Link to="/topics">*/}
{/*<span className="view-all-items"> See All videos of {this.props.topicName} ({this.props.topicCount})</span>*/}
{/*</Link>*/}
</Grid>
</section>

View File

@ -0,0 +1,57 @@
import React from 'react';
import {Link} from 'react-router-dom';
class TopicsList extends React.Component {
constructor() {
super();
this.state = {
currentFilter: "",
filteredTopics: [],
}
}
componentDidMount() {
this.setState({
filteredTopics: this.props.allTopics
})
}
filterTopics(event) {
// find the topics that contain that string
// return filtered topics.
// this.
const input = event.target.value.toLowerCase();
this.setState({
filteredTopics: this.props.allTopics.filter(topic => topic.name.toLowerCase().indexOf(input) !== -1)
})
}
render() {
return (
<main className="topics-list">
<section className="filter-box">
<label className="label" htmlFor="search-topics">
<i className="fa fa-filter">&nbsp;</i>
Filter Topics : &nbsp;
<input type="text" id="search-topics" ref="filter-element" onChange={event => this.filterTopics(event)}/>
</label>
</section>
{this.state.filteredTopics.map(topic => {
return (
<section className="topic-item" key={topic.name}>
<Link to={"/topic/" + topic.name}>
<h3>{topic.name} ({topic.items} videos)</h3>
</Link>
</section>
)
}
)}
</main>
)
}
}
export default TopicsList;

View File

@ -1,24 +0,0 @@
import React from 'react';
import VideoItem from "../components/VideoItem";
import Grid from "react-css-grid";
import SectionHeading from "../components/SectionHeading";
class TopicsList extends React.Component {
render() {
return (
<section>
<SectionHeading title="Random Topic"/>
<Grid
width={450}
gap={24}>
<VideoItem/>
<VideoItem/>
<VideoItem/>
<VideoItem/>
</Grid>
</section>
);
}
}
export default TopicsList;

View File

@ -0,0 +1,46 @@
import React from 'react';
import SectionHeading from "../components/SectionHeading";
import TopicsList from "../components/TopicsList";
import {getAllTopics} from "../actions/topics";
import {connect} from 'react-redux'
class TopicsListContainer extends React.Component {
componentDidMount(){
// if the data isn't there call the action.
if (!this.props.allTopics){
this.props.getAllTopics();
}
}
render() {
return (
<section>
<SectionHeading title="All Topics"/>
{/* TODO: get the topics objects*/}
<TopicsList allTopics={this.props.allTopics}/>
</section>
);
}
}
// function ->
// - recieves the global state.
// - maps the global state to the local props of the component.
// - maps the global actions to the local props of the component.
/**
*
* @param state
* @returns object
*/
const mapStateToProps = state => ({
allTopics: state.topics.allTopics,
loadingAllTopics: state.topics.loadingAllTopics,
});
/**
*
* @param dispatch: Redux's action dispatcher.
* @returns {{}}
*/
const mapDispatchToProps = dispatch => ({
getAllTopics: () => dispatch(getAllTopics()),
});
export default connect(mapStateToProps, mapDispatchToProps)(TopicsListContainer);

View File

@ -80,4 +80,4 @@ a {
.normal-link{
text-decoration: underline;
color: $normal-link-color;
}
}

View File

@ -0,0 +1,24 @@
@import "variables";
@import "globals";
#search-topics {
padding: 1em;
border: .5px solid gray;
}
.label {
font-size: 1.5em;
}
.topic-item {
h3 {
text-align: left;
padding: 1em;
font-size: 30px;
}
border-left: 2px solid black;
border-bottom: 6px solid black;
max-width: 1000px;
margin-bottom: 2em;
}

View File

@ -37,4 +37,12 @@
.search-box::placeholder {
font-size: .75em;
}
}
// filter
.filter-box{
background: $light-gray-color;
padding: 1em;
margin-bottom: 2em;
text-align: left;
}

View File

@ -7,4 +7,5 @@
@import "video-item";
@import "video-player";
@import "footer";
@import "items-list";
@import "~video-react/styles/scss/video-react";

View File

@ -30,7 +30,7 @@ export function callApi(action, data) {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(response => response.json()) // convert the response to json (instead of response object).
.then(data => {
if (data.status.code === 200) {
return data.data;
@ -50,6 +50,7 @@ export function callApi(action, data) {
@returns {Promise<Array>} array of topic objects like: {'name': '<topicName>', 'items': <no_of_items>}
*/
export function fetchAllTopics(startRange=0, endRange=1000) {
// the object that we need to send to the api
const data = {
"query": {
"conditions": [],
@ -209,7 +210,7 @@ export function fetchRandomPlace() {
"operator": "-"
}]
};
return callApi('findPlaces', data).then(data => data.items[0]);
return callApi('findPlaces', data).then(data => data.items[0]);
}
export function fetchVideosByPlace(placeId, startRange=0, endRange=100, sortKey='random') {