fix #14 create the all topics page with the filter.
This commit is contained in:
parent
3d20857fc1
commit
ed1fffca88
|
@ -5,7 +5,7 @@ import './App.css';
|
||||||
import 'font-awesome/css/font-awesome.min.css';
|
import 'font-awesome/css/font-awesome.min.css';
|
||||||
import { Route, Switch } from 'react-router-dom';
|
import { Route, Switch } from 'react-router-dom';
|
||||||
import Home from './containers/Home';
|
import Home from './containers/Home';
|
||||||
import TopicsList from './containers/TopicsList';
|
import TopicsListContainer from './containers/TopicsListContainer';
|
||||||
import store from './store/configureStore';
|
import store from './store/configureStore';
|
||||||
import history from './history';
|
import history from './history';
|
||||||
import Header from "./components/Header";
|
import Header from "./components/Header";
|
||||||
|
@ -25,7 +25,7 @@ class App extends Component {
|
||||||
<Search/>
|
<Search/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact={true} path="/" component={Home}/>
|
<Route exact={true} path="/" component={Home}/>
|
||||||
<Route path="/topics" component={TopicsList}/>
|
<Route path="/topics" component={TopicsListContainer}/>
|
||||||
<Route path="/videos/:videoId" component={Video}/>
|
<Route path="/videos/:videoId" component={Video}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
<Nav/>
|
<Nav/>
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
export function getAllTopics() {
|
export function getAllTopics() {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
dispatch(startLoadingAllTopics());
|
dispatch(startLoadingAllTopics());
|
||||||
|
// this calls the API
|
||||||
fetchAllTopics()
|
fetchAllTopics()
|
||||||
.then(topics => {
|
.then(topics => {
|
||||||
dispatch(loadedTopics(topics));
|
dispatch(loadedTopics(topics));
|
||||||
|
@ -29,6 +30,7 @@ function startLoadingAllTopics() {
|
||||||
};
|
};
|
||||||
|
|
||||||
function loadedTopics(topics) {
|
function loadedTopics(topics) {
|
||||||
|
// cache
|
||||||
setItem('allTopics', JSON.stringify(topics));
|
setItem('allTopics', JSON.stringify(topics));
|
||||||
setItem('topicsUpdatedAt', new Date());
|
setItem('topicsUpdatedAt', new Date());
|
||||||
return {
|
return {
|
||||||
|
@ -36,7 +38,9 @@ function loadedTopics(topics) {
|
||||||
payload: topics
|
payload: topics
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// actions :
|
||||||
|
// - async actions
|
||||||
|
// - simple: return action type and payload.
|
||||||
export function getRandomTopicVideos(allTopics, numVideos=4) {
|
export function getRandomTopicVideos(allTopics, numVideos=4) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
const randomTopic = getRandomTopic(allTopics);
|
const randomTopic = getRandomTopic(allTopics);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import VideoItem from "./VideoItem";
|
import VideoItem from "./VideoItem";
|
||||||
import RandomItemTitle from "./SectionHeading";
|
import RandomItemTitle from "./SectionHeading";
|
||||||
|
import {Link} from 'react-router-dom';
|
||||||
class RandomTopic extends React.Component {
|
class RandomTopic extends React.Component {
|
||||||
|
|
||||||
shuffleTopic() {
|
shuffleTopic() {
|
||||||
|
@ -22,13 +22,14 @@ class RandomTopic extends React.Component {
|
||||||
width={450}
|
width={450}
|
||||||
>
|
>
|
||||||
{/* View all topics button*/}
|
{/* View all topics button*/}
|
||||||
<button className="view-all-topics" onClick="">
|
<Link to="/topics">
|
||||||
<span className="english-text">All Topics - </span>
|
<button className="view-all-topics">
|
||||||
<span className="arabic-text"> جميع المواضيع </span>
|
<span className="english-text">All Topics - </span>
|
||||||
|
<span className="arabic-text"> جميع المواضيع </span>
|
||||||
</button>
|
</button>
|
||||||
|
</Link>
|
||||||
{/* Shuffle button */}
|
{/* 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="english-text">Shuffle Topics - </span>
|
||||||
<span className="arabic-text"> موضوع عشوائي </span>
|
<span className="arabic-text"> موضوع عشوائي </span>
|
||||||
<i className="fa fa-random"></i>
|
<i className="fa fa-random"></i>
|
||||||
|
@ -41,12 +42,6 @@ class RandomTopic extends React.Component {
|
||||||
{this.props.videos.map(video =>
|
{this.props.videos.map(video =>
|
||||||
<VideoItem id={video.id} key={video.id} title={video.title}/>
|
<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>
|
</Grid>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
57
src/components/TopicsList.js
Normal file
57
src/components/TopicsList.js
Normal 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"> </i>
|
||||||
|
Filter Topics :
|
||||||
|
<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;
|
|
@ -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;
|
|
46
src/containers/TopicsListContainer.js
Normal file
46
src/containers/TopicsListContainer.js
Normal 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);
|
|
@ -80,4 +80,4 @@ a {
|
||||||
.normal-link{
|
.normal-link{
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
color: $normal-link-color;
|
color: $normal-link-color;
|
||||||
}
|
}
|
||||||
|
|
24
src/stylesheets/items-list.scss
Normal file
24
src/stylesheets/items-list.scss
Normal 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;
|
||||||
|
}
|
||||||
|
|
|
@ -37,4 +37,12 @@
|
||||||
.search-box::placeholder {
|
.search-box::placeholder {
|
||||||
font-size: .75em;
|
font-size: .75em;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter
|
||||||
|
.filter-box{
|
||||||
|
background: $light-gray-color;
|
||||||
|
padding: 1em;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
|
@ -7,4 +7,5 @@
|
||||||
@import "video-item";
|
@import "video-item";
|
||||||
@import "video-player";
|
@import "video-player";
|
||||||
@import "footer";
|
@import "footer";
|
||||||
|
@import "items-list";
|
||||||
@import "~video-react/styles/scss/video-react";
|
@import "~video-react/styles/scss/video-react";
|
|
@ -30,7 +30,7 @@ export function callApi(action, data) {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json()) // convert the response to json (instead of response object).
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.status.code === 200) {
|
if (data.status.code === 200) {
|
||||||
return data.data;
|
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>}
|
@returns {Promise<Array>} array of topic objects like: {'name': '<topicName>', 'items': <no_of_items>}
|
||||||
*/
|
*/
|
||||||
export function fetchAllTopics(startRange=0, endRange=1000) {
|
export function fetchAllTopics(startRange=0, endRange=1000) {
|
||||||
|
// the object that we need to send to the api
|
||||||
const data = {
|
const data = {
|
||||||
"query": {
|
"query": {
|
||||||
"conditions": [],
|
"conditions": [],
|
||||||
|
@ -209,7 +210,7 @@ export function fetchRandomPlace() {
|
||||||
"operator": "-"
|
"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') {
|
export function fetchVideosByPlace(placeId, startRange=0, endRange=100, sortKey='random') {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user