Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM node:10

WORKDIR /app

EXPOSE 3000

COPY package.json package.json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mangler der måske også en yarn.lock?

RUN yarn install

COPY . /app
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hvis jeg allerede har en node_modules liggende, vil den overskrive de node_modules som lige er blevet installeret på linje 8.


RUN yarn build
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jeg kan ikke bygge din docker image:

Step 7/8 : RUN yarn build
 ---> Running in 585b63465f34
yarn run v1.13.0
error Command "build" not found.


CMD [ "yarn", "start" ]
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
44 changes: 36 additions & 8 deletions swapi/actions.js
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vil det sige at du ikke stoler på cache, hvis den indeholder 0 items?

dispatch({ type: types.START_LOAD });

return loadData('https://swapi.co/api/' + endpoint + '/')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overvej template string

.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 });
Expand All @@ -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,
Expand All @@ -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) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jeg har lidt svært med at gennemskue meningen med denne action? Og da jeg ikke kan bygge, og dermed heller ikke kan køre, din docker image kan jeg heller ikke se/opleve hvad den gør i browseren.

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 });
};
61 changes: 41 additions & 20 deletions swapi/components/item-display.js
Original file line number Diff line number Diff line change
@@ -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 (<span>
<button className="btn btn-xs btn-default" onClick={this.handleExpandToggle} aria-label={isExpanded ? 'Collapse' : 'Expand'}>
{isExpanded
? <i className="fa fa-chevron-circle-up" />
: <i className="fa fa-chevron-circle-down" />}
</button>
{' '}
{name}
{ isExpanded ? <pre>{JSON.stringify(this.props.children, null, 4)}</pre> : null}
</span>);
return (
<span>

<button className="btn btn-xs btn-default" onClick={this.expandToggle} aria-label={expanded > 0 ? 'Collapse' : 'Expand'}>
{expanded > 0
? <i className="fa fa-chevron-circle-up" />
: <i className="fa fa-chevron-circle-down" />
}
</button>
{' '}
{name} - <i className={iconKindClass}/>
{' '}
{ episode_id &&
<span> Episode {episode_id}</span>
}
{' '}
{ endpoint === 'charactersNames' &&
<span> - <span className='fa fa-users'> {charactersNames.length}</span></span>
}
{ endpoint === 'charactersNames'
? expanded ? <pre>{JSON.stringify(charactersNames, null, 4)}</pre> : null
: expanded ? <pre>{JSON.stringify(this.props.children, null, 4)}</pre> : null
}
</span>);
}
}
7 changes: 6 additions & 1 deletion swapi/components/millennium-falcon-connected.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down
50 changes: 38 additions & 12 deletions swapi/components/millennium-falcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,58 @@ 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,
side: PropTypes.string,
};

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,
'fa-spin': loading,
'fa-star': !loading,
});

return (<div>
<p><i className={iconClass} /> What do you want to see?</p>
<div className="btn-group">
<button disabled={loading} onClick={() => onChooseEndpoint('people')} className="btn btn-danger">People</button>
<button disabled={loading} onClick={() => onChooseEndpoint('films')} className="btn btn-danger">Films</button>
return (
<div>
<p>
<i className={iconClass} />
{' '}
{loading
? <span>Loading {endpoint}, please wait...</span>
: <span>What do you want to see?</span>
}
<br />
{!this.props.data.films || !this.props.data.people
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jeg kan ikke se at componenten modtage en data prop ud fra PropTypes, er det en forglemmelse eller var det for at holde den hemmelige knap lidt mere hemmeligt.

? <span>Hint: A secret button will appear when you have seen both the lists of characters and films</span>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lidt sjov hemmelighed, hvis du afslører den med det samme 🤣

: <span>You can now see the new button</span>
}
</p>
<div className="btn-group">
<button disabled={loading} onClick={() => onChooseEndpoint('people')} className={"btn " + (endpoint === 'people' ? 'btn-success' : 'btn-primary')}><i className="fa fa-male"/><i className="fa fa-female"/> Characters</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Læs evt her hvorfor arrow functions (eller bind) direkte i JSX sjældent er en god ide

https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

<button disabled={loading} onClick={() => onChooseEndpoint('films')} className={"btn " + (endpoint === 'films' ? 'btn-success' : 'btn-primary')}><i className="fa fa-film"/> Films</button>
{this.props.data.films && this.props.data.people && !loading &&
<button onClick={() => onMergeLists('charactersNames')} className={"btn " + (endpoint === 'charactersNames' ? 'btn-success' : 'btn-primary')}><i className="fa fa-users"/> Cast</button>
}
</div>
<br /><br />
<p>{!loading && endpoint &&
<span>{listName.toUpperCase()}({this.props.data[endpoint].length})</span>
}
</p>
<table className="table">
<tbody>
{list.map((item) => <tr key={item.url}><td><ItemDisplay endpoint={endpoint} setExpandToggle={onExpandToggle}>{item}</ItemDisplay></td></tr>)}
</tbody>
</table>
</div>
<br /><br />
<table className="table"><tbody>
{list.map((item) => <tr key={item.url}><td><ItemDisplay>{item}</ItemDisplay></td></tr>)}
</tbody>
</table>
</div>);
);
}
}
24 changes: 24 additions & 0 deletions swapi/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,30 @@ const reducer = (state = {}, action) => {
operations: state.operations - 1,
}
}

case types.TOGGLE_EXPAND_ITEM: {
return {
...state,
data: {
...state.data,
[state.endpoint]: state.data[state.endpoint].map(item => item.url === action.payload ? { ...item, expanded: !item.expanded } : item)
}
}

}

case types.MERGE_LISTS: {
return {
...state,
data: {
...state.data,
[action.payload]: state.data.films.map(film => ({ ...film, charactersNames: film.characters.map(character => ({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kan gøres nemmere at læse med lidt linebreaks de rigtige steder.

name: state.data.people.filter(person => person.url === character)[0].name
})).sort((a, b) => (a.name.toString()).localeCompare(b.name.toString(), 'en')) }))
}
}

}
}

return state;
Expand Down
2 changes: 2 additions & 0 deletions swapi/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export const PICK_ENDPOINT = 'PICK_ENDPOINT';
export const SET_DATA = 'SET_DATA';
export const START_LOAD = 'START_LOAD';
export const DONE_LOAD = 'DONE_LOAD';
export const TOGGLE_EXPAND_ITEM = 'TOGGLE_EXPAND_ITEM';
export const MERGE_LISTS = 'MERGE_LISTS';