diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a9f6d57
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,14 @@
+FROM node:10
+
+WORKDIR /app
+
+EXPOSE 3000
+
+COPY package.json package.json
+RUN yarn install
+
+COPY . /app
+
+RUN yarn build
+
+CMD [ "yarn", "start" ]
\ No newline at end of file
diff --git a/package.json b/package.json
index 970e0b4..c49302a 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,8 @@
"license": "MIT",
"scripts": {
"start-next": "next -p 3008",
- "start": "next"
+ "start": "next",
+ "build": "next build"
},
"dependencies": {
"babel-cli": "^6.26.0",
diff --git a/swapi/actions.js b/swapi/actions.js
index 9476209..9c278a8 100644
--- a/swapi/actions.js
+++ b/swapi/actions.js
@@ -1,10 +1,26 @@
import * as types from './types';
+import { relativeTimeRounding } from 'moment';
+
const sleep = (seconds, withValue) => new Promise(resolve => setTimeout(resolve, seconds * 1000, withValue));
export const onChooseEndpoint = (endpoint) => (dispatch, getState) => {
dispatch({ type: types.PICK_ENDPOINT, payload: endpoint });
- dispatch({ type: types.START_LOAD });
+
+ const dataCached = getState().data[endpoint]
+
+ if (!dataCached || dataCached.length === 0) {
+ dispatch({ type: types.START_LOAD });
+
+ return loadData('https://swapi.co/api/' + endpoint + '/')
+ .catch((err) => {
+ console.error(err);
+ alert('It went wrong.')
+ })
+ .then(() => dispatch({ type: types.DONE_LOAD }))
+ } else {
+ return;
+ }
function loadData(url, loadedData = []) {
dispatch({ type: types.START_LOAD });
@@ -13,7 +29,9 @@ export const onChooseEndpoint = (endpoint) => (dispatch, getState) => {
.then((response) => response.json())
.then((json) => {
const result = json.results.map((item) => ({ ...item, name: item.title || item.name, kind: endpoint }));
- loadedData = loadedData.concat(result);
+
+ loadedData = loadedData.concat(result)
+ loadedData = loadedData.sort((a, b) => (a.name.toString()).localeCompare(b.name.toString(), 'en'));
dispatch({
type: types.SET_DATA,
@@ -34,11 +52,21 @@ export const onChooseEndpoint = (endpoint) => (dispatch, getState) => {
.catch(() => console.error('Oops'))
}
+};
+
+export const onExpandToggle = (item) => (dispatch) => {
+ dispatch({ type: types.TOGGLE_EXPAND_ITEM, payload: item });
+};
+
+export const onMergeLists = (endpoint) => (dispatch, getState) => {
+ dispatch({ type: types.START_LOAD });
+ dispatch({ type: types.PICK_ENDPOINT, payload: endpoint });
+
+ const dataCached = getState().data[endpoint]
+
+ if (!dataCached) {
+ dispatch({ type: types.MERGE_LISTS, payload: endpoint });
+ }
- return loadData('https://swapi.co/api/' + endpoint + '/')
- .catch((err) => {
- console.error(err);
- alert('It went wrong.')
- })
- .then(() => dispatch({ type: types.DONE_LOAD }))
+ dispatch({ type: types.DONE_LOAD });
};
diff --git a/swapi/components/item-display.js b/swapi/components/item-display.js
index 67d26c4..13c772e 100644
--- a/swapi/components/item-display.js
+++ b/swapi/components/item-display.js
@@ -1,37 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
+import classNames from 'classnames';
export default class ItemDisplay extends React.PureComponent {
static propTypes = {
children: PropTypes.shape({
- url: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
kind: PropTypes.string.isRequired,
- })
- };
-
- state = {
- isExpanded: false,
+ url: PropTypes.string.isRequired,
+ expanded: PropTypes.bool,
+ episode_id: PropTypes.number,
+ gender: PropTypes.string,
+ }),
};
- handleExpandToggle = () => {
- this.setState((state) => ({ isExpanded: !state.isExpanded }));
+ expandToggle = () => {
+ this.props.setExpandToggle(this.props.children.url)
};
render() {
- const { children: { name }, onExpandToggle} = this.props;
- const { isExpanded } = this.state;
+ const { children: { name, kind, expanded, gender, episode_id, charactersNames }, endpoint} = this.props;
+
+ const iconKindClass = classNames('fa', {
+ 'fa-female': kind === 'people' && gender === 'female',
+ 'fa-male': kind === 'people' && gender === 'male',
+ 'fa-android': kind === 'people' && (gender === 'none' || gender === 'n/a'),
+ 'fa-transgender-alt': kind === 'people' && gender === 'hermaphrodite',
+ 'fa-flag': episode_id,
+ });
- return (
-
- {' '}
- {name}
- { isExpanded ? {JSON.stringify(this.props.children, null, 4)} : null}
- );
+ return (
+
+
+
+ {' '}
+ {name} -
+ {' '}
+ { episode_id &&
+ Episode {episode_id}
+ }
+ {' '}
+ { endpoint === 'charactersNames' &&
+ - {charactersNames.length}
+ }
+ { endpoint === 'charactersNames'
+ ? expanded ? {JSON.stringify(charactersNames, null, 4)} : null
+ : expanded ? {JSON.stringify(this.props.children, null, 4)} : null
+ }
+ );
}
}
diff --git a/swapi/components/millennium-falcon-connected.js b/swapi/components/millennium-falcon-connected.js
index b2e74f3..e716c0a 100644
--- a/swapi/components/millennium-falcon-connected.js
+++ b/swapi/components/millennium-falcon-connected.js
@@ -3,22 +3,27 @@ import { connect } from 'react-redux';
import MillenniumFalcon from '../components/millennium-falcon';
import { onChooseEndpoint } from '../actions';
+import { onExpandToggle } from '../actions';
+import { onMergeLists } from '../actions';
const mapStateToProps = (state) => {
const { endpoint, data } = state;
const list = (data && data[endpoint]) || [];
- console.log(state.operations);
+
return {
list,
endpoint,
loading: state.operations > 0,
+ data,
}
};
const mapDispatchToProps = (dispatch) => {
return bindActionCreators({
onChooseEndpoint: onChooseEndpoint,
+ onExpandToggle: onExpandToggle,
+ onMergeLists: onMergeLists,
}, dispatch);
};
diff --git a/swapi/components/millennium-falcon.js b/swapi/components/millennium-falcon.js
index 72b7884..74c56f0 100644
--- a/swapi/components/millennium-falcon.js
+++ b/swapi/components/millennium-falcon.js
@@ -7,6 +7,8 @@ export default class MillenniumFalcon extends React.PureComponent {
static propTypes = {
onChooseEndpoint: PropTypes.func.isRequired,
+ onExpandToggle: PropTypes.func.isRequired,
+ onMergeLists: PropTypes.func.isRequired,
list: PropTypes.arrayOf(PropTypes.shape({
url: PropTypes.string.isRequired,
})).isRequired,
@@ -14,7 +16,9 @@ export default class MillenniumFalcon extends React.PureComponent {
};
render() {
- const { loading, list, onChooseEndpoint } = this.props;
+ const { loading, list, onChooseEndpoint, onExpandToggle, onMergeLists, endpoint } = this.props;
+
+ const listName = endpoint === 'people' ? endpoint : 'films'
const iconClass = classNames('fa', {
'fa-refresh': loading,
@@ -22,17 +26,39 @@ export default class MillenniumFalcon extends React.PureComponent {
'fa-star': !loading,
});
- return (
What do you want to see?
-
+
+ {' '}
+ {loading
+ ? Loading {endpoint}, please wait...
+ : What do you want to see?
+ }
+
+ {!this.props.data.films || !this.props.data.people
+ ? Hint: A secret button will appear when you have seen both the lists of characters and films
+ : You can now see the new button
+ }
+
{!loading && endpoint && + {listName.toUpperCase()}({this.props.data[endpoint].length}) + } +
+