Skip to content

Commit 7885d9c

Browse files
committed
Added proper error handling.
1 parent 4c70564 commit 7885d9c

14 files changed

Lines changed: 286 additions & 145 deletions

File tree

reactjs/api/recipe.js

Lines changed: 106 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,82 @@ import request from '../utils/request';
22
import * as transforms from '../utils/transforms';
33

44
/**
5-
* Returns all recipes on the backend.
5+
* Returns single recipes from the backend by uuid.
6+
*
7+
* @param id
8+
* Drupal UUID of the recipe.
9+
*
10+
* @returns {Promise<any>}
11+
*/
12+
export const get = id => new Promise((resolve, reject) => {
13+
request
14+
.get(`/recipes/${id}`)
15+
.query({
16+
'include': 'category,tags,image,image.thumbnail',
17+
'fields[recipes]': 'id,title,image,category,difficulty,ingredients,instructions,numberOfServices,tags,totalTime',
18+
'fields[categories]': 'id,name',
19+
'fields[tags]': 'id,name',
20+
'fields[images]': 'thumbnail',
21+
'fields[files]': 'url',
22+
})
23+
// Tell superagent to consider any valid Drupal response as successful.
24+
// Later we can capture error codes if needed.
25+
.ok(resp => resp.statusCode)
26+
.then((response) => {
27+
resolve({
28+
recipe: response.statusCode === 200 ? transforms.recipe(response.body.data) : {},
29+
statusCode: response.statusCode,
30+
});
31+
})
32+
.catch((error) => {
33+
console.error('Could not fetch the recipe.', error);
34+
reject(error);
35+
});
36+
});
37+
38+
/**
39+
* Returns all recipes from the backend.
40+
*
41+
* @param pageLimit
42+
* Recipes per page.
43+
*
44+
* @param pageOffset
45+
* Amount of recipes to skip from the beginning (for pagination).
46+
*
47+
* @returns {Promise<any>}
48+
*/
49+
export const getAll = (pageLimit = 24, pageOffset = 0) => new Promise((resolve, reject) => {
50+
request
51+
.get('/recipes')
52+
.query({
53+
'include': 'image,image.thumbnail',
54+
'fields[recipes]': 'id,title,image',
55+
'fields[categories]': 'name',
56+
'fields[images]': 'thumbnail',
57+
'fields[files]': 'url',
58+
'sort': '-created',
59+
'page[limit]': pageLimit,
60+
'page[offset]': pageOffset,
61+
})
62+
// Tell superagent to consider any valid Drupal response as successful.
63+
// Later we can capture error codes if needed.
64+
.ok(resp => resp.statusCode)
65+
.then((response) => {
66+
resolve({
67+
recipes: response.statusCode === 200 ? response.body.data.map(recipe => transforms.recipe(recipe)) : [],
68+
hasNextPage: !!response.body.links.next,
69+
hasPrevPage: !!response.body.links.prev,
70+
statusCode: response.statusCode,
71+
});
72+
})
73+
.catch((error) => {
74+
console.error('Could not fetch list of recipes.', error);
75+
reject(error);
76+
});
77+
});
78+
79+
/**
80+
* Returns promoted recipes from the backend.
681
*
782
* @param pageLimit
883
* Recipes per page.
@@ -12,29 +87,34 @@ import * as transforms from '../utils/transforms';
1287
*
1388
* @returns {Promise<any>}
1489
*/
15-
export const getAll = (pageLimit = 24, pageOffset = 0) =>
16-
new Promise((resolve, reject) => {
17-
request
18-
.get('/recipes')
19-
.query({
20-
'include': 'image,image.thumbnail',
21-
'fields[recipes]': 'id,title,image',
22-
'fields[categories]': 'name',
23-
'fields[images]': 'thumbnail',
24-
'fields[files]': 'url',
25-
'sort': '-created',
26-
'page[limit]': pageLimit,
27-
'page[offset]': pageOffset,
28-
})
29-
.then(response => {
30-
resolve({
31-
recipes: response.body.data.map(recipe => transforms.recipe(recipe)),
32-
hasNextPage: !!response.body.links.next,
33-
hasPrevPage: !!response.body.links.prev,
34-
});
35-
})
36-
.catch(error => {
37-
console.error('Could not fetch current user.', error);
38-
reject(error);
90+
export const getPromoted = (pageLimit = 4, pageOffset = 0) => new Promise((resolve, reject) => {
91+
request
92+
.get('/recipes')
93+
.query({
94+
'include': 'category,image,image.thumbnail',
95+
'fields[recipes]': 'id,category,title,image',
96+
'fields[categories]': 'name',
97+
'fields[images]': 'thumbnail',
98+
'fields[files]': 'url',
99+
'filter[isPromoted][value]': 1,
100+
'filter[isPublished][value]': 1,
101+
'sort': '-created',
102+
'page[limit]': pageLimit,
103+
'page[offset]': pageOffset,
104+
})
105+
// Tell superagent to consider any valid Drupal response as successful.
106+
// Later we can capture error codes if needed.
107+
.ok(resp => resp.statusCode)
108+
.then((response) => {
109+
resolve({
110+
recipes: response.statusCode === 200 ? response.body.data.map(recipe => transforms.recipe(recipe)) : [],
111+
hasNextPage: !!response.body.links.next,
112+
hasPrevPage: !!response.body.links.prev,
113+
statusCode: response.statusCode,
39114
});
40-
});
115+
})
116+
.catch((error) => {
117+
console.error('Could not fetch promoted recipes.', error);
118+
reject(error);
119+
});
120+
});

reactjs/components/01_atoms/Bootstrap/CardAsLink/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Link } from '../../../../routes';
66
const CardAsLink = ({ href, ...props }) => (
77
<Link to={href}>
88
<a className="card-link">
9-
<Card {...props} />
9+
<Card {...props} />
1010
</a>
1111
</Link>
1212
);

reactjs/components/01_atoms/Bootstrap/MiniPager/index.js

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { withRouter } from 'next/router';
55
import { Router } from '../../../../routes';
66

77
class MiniPager extends React.Component {
8-
98
constructor(props) {
109
super(props);
1110

@@ -14,10 +13,10 @@ class MiniPager extends React.Component {
1413
}
1514

1615
handlePrevPageClick() {
17-
const { router, prevPageAllowed } = this.props;
16+
const { router, hasPrevPage } = this.props;
1817
const currentPage = router.query.page ? parseInt(router.query.page, 10) : 0;
1918

20-
if (!prevPageAllowed) {
19+
if (!hasPrevPage) {
2120
return;
2221
}
2322

@@ -26,10 +25,10 @@ class MiniPager extends React.Component {
2625
}
2726

2827
handleNextPageClick() {
29-
const { router, nextPageAllowed } = this.props;
28+
const { router, hasNextPage } = this.props;
3029
const currentPage = router.query.page ? parseInt(router.query.page, 10) : 0;
3130

32-
if (!nextPageAllowed) {
31+
if (!hasNextPage) {
3332
return;
3433
}
3534

@@ -38,10 +37,10 @@ class MiniPager extends React.Component {
3837
}
3938

4039
render() {
41-
// TODO: Don't render pager when nothing is allowed.
42-
const { prevPageAllowed, nextPageAllowed } = this.props;
40+
const { hasPrevPage, hasNextPage } = this.props;
4341

44-
if (!prevPageAllowed && !nextPageAllowed) {
42+
// Don't render the pager if both next & prev page links can't be displayed.
43+
if (!hasPrevPage && !hasNextPage) {
4544
return null;
4645
}
4746

@@ -50,11 +49,11 @@ class MiniPager extends React.Component {
5049
<Row>
5150
<Col>
5251
<Pagination className="mini-pager">
53-
<PaginationItem disabled={!prevPageAllowed} onClick={this.handlePrevPageClick}>
54-
<PaginationLink previous/>
52+
<PaginationItem disabled={!hasPrevPage} onClick={this.handlePrevPageClick}>
53+
<PaginationLink previous />
5554
</PaginationItem>
56-
<PaginationItem disabled={!nextPageAllowed} onClick={this.handleNextPageClick}>
57-
<PaginationLink next/>
55+
<PaginationItem disabled={!hasNextPage} onClick={this.handleNextPageClick}>
56+
<PaginationLink next />
5857
</PaginationItem>
5958
</Pagination>
6059
</Col>
@@ -65,13 +64,13 @@ class MiniPager extends React.Component {
6564
}
6665

6766
MiniPager.propTypes = {
68-
prevPageAllowed: PropTypes.bool,
69-
nextPageAllowed: PropTypes.bool,
67+
hasPrevPage: PropTypes.bool,
68+
hasNextPage: PropTypes.bool,
7069
};
7170

7271
MiniPager.defaultProps = {
73-
prevPageAllowed: true,
74-
nextPageAllowed: true,
72+
hasPrevPage: true,
73+
hasNextPage: true,
7574
};
7675

7776
export default withRouter(MiniPager);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.error-message {
2+
text-align: center;
3+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Link } from '../../../routes';
4+
5+
const ErrorMessage = ({ statusCode }) => (
6+
<div className="error-message">
7+
Oops, {statusCode} error. <br />
8+
<Link to="/">To the Homepage</Link>
9+
</div>
10+
);
11+
12+
ErrorMessage.propTypes = {
13+
statusCode: PropTypes.number.isRequired,
14+
};
15+
16+
export default ErrorMessage;

reactjs/components/01_atoms/Logo/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import React from 'react';
22
import { Link } from '../../../routes';
33

44
const Logo = () => (
5-
<Link to="/">
6-
<a className="logo">
7-
<img src="/static/logo.svg" alt="Home" rel="index" />
8-
</a>
9-
</Link>
5+
<Link to="/">
6+
<a className="logo">
7+
<img src="/static/logo.svg" alt="Home" rel="index" />
8+
</a>
9+
</Link>
1010
);
1111

1212
export default Logo;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.no-results-message {
2+
text-align: center;
3+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const NoResultsMessage = ({ message }) => (
5+
<div className="no-results-message">
6+
{message}
7+
</div>
8+
);
9+
10+
NoResultsMessage.propTypes = {
11+
message: PropTypes.string.isRequired,
12+
};
13+
14+
export default NoResultsMessage;

reactjs/components/03_organisms/GlobalFooter/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const GlobalFooter = () => (
99
<strong>Umami magazine & Umami publications</strong> is a fictional
1010
magazine and publisher for illustrative purposes only.
1111
</Col>
12-
<Col md={3}/>
12+
<Col md={3} />
1313
<Col md={3}>
1414
© 2018 Terms & Conditions
1515
</Col>

reactjs/pages/_app.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import withRedux from 'next-redux-wrapper';
55
import withReduxSaga from 'next-redux-saga';
66
import HtmlHead from '../components/01_atoms/HtmlHead';
77
import configureStore from '../store/store';
8+
import ErrorMessage from '../components/01_atoms/ErrorMessage';
9+
import SiteLayout from '../components/04_templates/GlobalLayout';
810
import '../components/01_atoms/PageProgressBar'; // Beautiful page transition indicator.
911

1012
class Application extends App {
1113
static async getInitialProps({ Component, ctx }) {
12-
let initialProps = {
14+
const initialProps = {
1315
isServer: !!ctx.req,
1416
};
1517

@@ -31,12 +33,20 @@ class Application extends App {
3133

3234
render() {
3335
const { Component, store, ...pageProps } = this.props;
36+
const statusCode = pageProps.statusCode || 200;
3437
return (
3538
<Container>
3639
<Provider store={store}>
3740
<Fragment>
3841
<HtmlHead />
39-
<Component {...pageProps} />
42+
<SiteLayout>
43+
{statusCode === 200
44+
&& <Component {...pageProps} />
45+
}
46+
{statusCode !== 200
47+
&& <ErrorMessage statusCode={statusCode} />
48+
}
49+
</SiteLayout>
4050
</Fragment>
4151
</Provider>
4252
</Container>

0 commit comments

Comments
 (0)