Skip to content

Commit 0a5bde6

Browse files
Ashish KumarAshish Kumar
authored andcommitted
chore: PWA support added
1 parent a69de04 commit 0a5bde6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1543
-114
lines changed

client/App.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import React, {Component} from "react";
22
import { renderRoutes } from "react-router-config";
33
import Routes from "./Routes";
4-
4+
import styles from "./scss/root.scss";
5+
import withStyles from 'isomorphic-style-loader/withStyles'
56
class App extends Component {
67
render() {
78
return (
8-
<div className="page--container">
9+
<div className={`container`}>
910
{renderRoutes(Routes)}
1011
</div>
1112
);
1213
}
1314
}
1415

15-
export default App;
16+
export default withStyles(styles)(App);

client/Routes.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import React from "react";
22
import loadable from '@loadable/component';
3+
import {fetchStories} from "./actions";
34

45
const Loading = () => <h1>Loading...</h1>;
56

6-
const AppContainer = loadable(() => import(/* webpackChunkName: "appcontainer", webpackPrefetch: true */ "./AppContainer"),{
7-
fallback: Loading,
7+
const AppContainer = loadable(() => import(/* webpackChunkName: "appcontainer" */ "./AppContainer"),{
8+
fallback: <Loading />,
89
ssr: true
910
});
1011

11-
const Home = loadable(() => import(/* webpackChunkName: "home", webpackPrefetch: true */ "./containers/Home"),{
12-
fallback: Loading,
12+
const Home = loadable(() => import(/* webpackChunkName: "home" */ "./containers/Home"),{
13+
fallback: <Loading/>,
1314
ssr: true
1415
});
1516

@@ -22,6 +23,15 @@ export default [
2223
exact: false,
2324
path: "/",
2425
component: Home,
26+
loadData: (store,route,path,queryParams,urlParams) => {
27+
/**
28+
* this function will be called on server side.
29+
* Please check /server/helpers/ServeWeb.js for better understanding
30+
*/
31+
return [
32+
store.dispatch(fetchStories(queryParams && (queryParams.page || 0)))
33+
]
34+
}
2535
},
2636
]
2737
}

client/actions/index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {
2+
FETCH_STORIES_FAILURE,
3+
FETCH_STORIES_PENDING,
4+
FETCH_STORIES_SUCCESS
5+
} from "./../constants/ActionTypes";
6+
7+
export const fetchStories = (page=0) => async(dispatch, getState, api) => {
8+
try{
9+
dispatch({type: FETCH_STORIES_PENDING});
10+
11+
const res = await api.get(`/search?page=${page}&hitsPerPage=30`);
12+
// console.log(res);
13+
dispatch({
14+
type: FETCH_STORIES_SUCCESS,
15+
payload: res.data
16+
});
17+
}catch(err){
18+
console.log(err);
19+
dispatch({
20+
type: FETCH_STORIES_FAILURE
21+
});
22+
}
23+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React,{Component} from "react";
2+
import {connect} from "react-redux";
3+
import {withRouter} from "react-router";
4+
import {Link} from "react-router-dom";
5+
6+
class PaginationButton extends Component{
7+
render(){
8+
const {data} = this.props;
9+
const currPage = data.page;
10+
const maxPage = data.nbPages;
11+
return(
12+
<React.Fragment>
13+
<Link
14+
to={`/?page=${currPage >= 1 ? currPage - 1 : 0}`}
15+
className={`${currPage == 0 ? "disabled" : ""} nav-btn text-orange`}
16+
>
17+
Previous
18+
</Link>
19+
20+
&nbsp;&nbsp;|&nbsp;&nbsp;
21+
22+
<Link
23+
to={`/?page=${(currPage >= 0 && currPage < maxPage) ? currPage + 1 : maxPage}`}
24+
className={`${currPage == maxPage ? "disabled" : ""} nav-btn text-orange`}
25+
>
26+
Next
27+
</Link>
28+
</React.Fragment>
29+
)
30+
}
31+
}
32+
33+
const mapStateToProps = ({stories}) => ({
34+
data: stories.data
35+
});
36+
37+
export default withRouter(connect(mapStateToProps,{
38+
})(PaginationButton));
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import React from "react";
2+
3+
const getOriginFromUrl = (str) => {
4+
if(!str){
5+
return null;
6+
}
7+
try{
8+
const url = new window.URL(str);
9+
return url.origin;
10+
}catch(err){
11+
// console.log(err);
12+
return str;
13+
}
14+
}
15+
16+
export default (props) => {
17+
const {
18+
num_comments, // Number of comments
19+
points, // Vote Count
20+
title,
21+
url,
22+
author,
23+
created_at
24+
} = props.data;
25+
if(!title){
26+
return null;
27+
}
28+
29+
const getClassForPoints = (points=0) => {
30+
if(points >= 50 && points < 100){
31+
return "text-brown"
32+
}else if(points >= 100){
33+
return "text-orange";
34+
}else{
35+
return null;
36+
}
37+
}
38+
const className = getClassForPoints(points);
39+
return (
40+
<tr className="row-item">
41+
<td className="row-item__td">{num_comments || "-"}</td>
42+
<td className={`row-item__td ${className}`}>{points || "-"}</td>
43+
<td className="row-item__td">
44+
<span className="caret-upvote"></span>
45+
</td>
46+
<td className="row-item__td">
47+
<div className="row-item__info clearfix">
48+
<div className="row-item__info--title">
49+
<a href={url} target="__blank" suppressHydrationWarning={true}>
50+
{title}
51+
</a>
52+
<small>{getOriginFromUrl(url)}</small>
53+
</div>
54+
<div className="row-item__info--meta">
55+
<div className="row-item__info--meta-block">
56+
<small>by <strong>{author}</strong></small>
57+
</div>
58+
<div className="row-item__info--meta-block">
59+
<small>
60+
<a href="#">hide</a>
61+
</small>
62+
</div>
63+
</div>
64+
</div>
65+
</td>
66+
</tr>
67+
)
68+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React from "react";
2+
3+
const TableLoader = ({rows = 1, columns=1}) => {
4+
const loaderRows = new Array(parseInt(rows)).fill(0);
5+
const loaderColumns = new Array(parseInt(columns)).fill(0);
6+
return (
7+
<tbody>
8+
{
9+
loaderRows.map((row,i) => {
10+
return (
11+
<tr key={i} className="row-item">
12+
{
13+
loaderColumns.map((col,j) => {
14+
return <td key={j} className="row-item__td">
15+
<span className={`w80 td-animated animate`}/>
16+
</td>
17+
})
18+
}
19+
</tr>
20+
)
21+
})
22+
}
23+
<tr></tr>
24+
</tbody>
25+
)
26+
}
27+
28+
export default TableLoader;

client/components/StoryList/index.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React,{Component} from "react";
2+
import {connect} from "react-redux";
3+
import PaginationButton from "./../PaginationButton";
4+
import ListItem from "./ListItem";
5+
import {fetchStories} from "../../actions";
6+
import {withRouter} from "react-router";
7+
import TableLoader from "./TableLoader";
8+
9+
const parseQueryString = (str) => {
10+
if(!str) return {};
11+
12+
// remove ? from start of the string
13+
const varStrings = str.slice(1);
14+
const values = varStrings.split("&");
15+
return values.reduce((acc,val) => {
16+
const a = val.split("=");
17+
acc[a[0]] = a[1];
18+
return acc;
19+
},{});
20+
}
21+
22+
class StoryList extends Component{
23+
constructor(props){
24+
super(props);
25+
this.state = {
26+
currPage: (props.data && props.data.page) || 0
27+
}
28+
29+
}
30+
31+
static getDerivedStateFromProps(props,state){
32+
const {search} = props.location;
33+
const qParams = parseQueryString(search);
34+
try{
35+
if(parseInt(qParams.page) != parseInt(state.currPage)){
36+
props.fetchStories(qParams.page);
37+
return {
38+
currPage: qParams.page
39+
}
40+
}
41+
}catch(e){
42+
43+
}
44+
return null;
45+
}
46+
render(){
47+
const {data, loading} = this.props;
48+
return (
49+
<table>
50+
<thead>
51+
<tr>
52+
<th>Comments</th>
53+
<th>Vote Count</th>
54+
<th>UpVote</th>
55+
<th>News Details</th>
56+
</tr>
57+
</thead>
58+
{
59+
loading ?
60+
<TableLoader rows={30} columns={4}/>
61+
:
62+
<React.Fragment>
63+
<tbody>
64+
{
65+
data && data.hits.map((item,i) => {
66+
return <ListItem data={item} key={i}/>
67+
})
68+
}
69+
<tr className="footer">
70+
<td colSpan="4" style={{
71+
textAlign: 'center',
72+
padding: "1.5em 1em"
73+
}}>
74+
<PaginationButton />
75+
</td>
76+
</tr>
77+
</tbody>
78+
</React.Fragment>
79+
}
80+
</table>
81+
)
82+
}
83+
}
84+
85+
const mapStateToProps = ({stories}) => ({
86+
data: stories.data,
87+
loading: stories.loading
88+
});
89+
90+
export default withRouter(connect(mapStateToProps,{
91+
fetchStories
92+
})(StoryList));

client/config/api.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import axios from 'axios';
22

33
const axiosInstance = axios.create({
4-
baseURL: `/api`,
4+
baseURL: `${process.env.API_URL}/api/v1`,
55
});
66

77
export default axiosInstance;

client/config/store.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ if (process.env.NODE_ENV == 'production') {
1414
applyMiddleware(thunk.withExtraArgument(API)),
1515
);
1616
}
17-
const preloadedState = window.INITIAL_STATE || {};
18-
17+
const preloadedState = window.__INITIAL_STATE__ || {};
18+
delete window.__INITIAL_STATE__
1919
const store = createStore(
2020
reducers,//reducers
2121
preloadedState,

client/constants/ActionTypes.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const FETCH_STORIES_PENDING = "FETCH_STORIES_PENDING";
2+
export const FETCH_STORIES_SUCCESS = "FETCH_STORIES_SUCCESS";
3+
export const FETCH_STORIES_FAILURE = "FETCH_STORIES_FAILURE";

0 commit comments

Comments
 (0)