diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js
index ce8a596..30a436c 100644
--- a/.storybook/webpack.config.js
+++ b/.storybook/webpack.config.js
@@ -30,15 +30,16 @@ const env = process.env.NODE_ENV || 'development';
webpackConfig.plugins = [
new webpack.DefinePlugin({
'process.env': {
- 'NODE_ENV': JSON.stringify(env),
+ NODE_ENV: JSON.stringify(env),
},
- 'NODE_ENV': env,
- '__DEV__': env === 'development',
- '__PROD__': env === 'production',
- '__TEST__': env === 'test',
- '__DEBUG__': env === 'development' && !argv.no_debug,
- '__COVERAGE__': !argv.watch && env === 'test',
- '__BASENAME__': JSON.stringify(process.env.BASENAME || ''),
+ NODE_ENV: env,
+ __DEV__: env === 'development',
+ __PROD__: env === 'production',
+ __TEST__: env === 'test',
+ __DEBUG__: env === 'development' && !argv.no_debug,
+ __COVERAGE__: !argv.watch && env === 'test',
+ __BASENAME__: JSON.stringify(process.env.BASENAME || ''),
+ __CONTROLLER_API__: JSON.stringify('https://fake-controller.net'),
}),
];
diff --git a/config/ava.config.js b/config/ava.config.js
index 2b0b666..c71964f 100644
--- a/config/ava.config.js
+++ b/config/ava.config.js
@@ -3,6 +3,13 @@
import { argv } from 'yargs';
import fs from 'fs';
import config from '../config';
+import injectTapEventPlugin from 'react-tap-event-plugin';
+
+Object.keys(config.globals).forEach(key => {
+ if(typeof (config.globals[key]) === 'string') {
+ config.globals[key] = config.globals[key].replace(/['"]+/g, '');
+ };
+});
Object.assign(global, config.globals);
@@ -28,3 +35,4 @@ require.extensions['.html'] = (module, filename) => {
global.document = require('jsdom').jsdom(require('../src/index.html'));
global.window = document.defaultView;
global.navigator = window.navigator;
+injectTapEventPlugin(); // for component tests using onTouchTap event
diff --git a/config/index.js b/config/index.js
index 0b4d2e5..7a08337 100644
--- a/config/index.js
+++ b/config/index.js
@@ -33,7 +33,7 @@ const config = {
// ----------------------------------
// Controller Service Configuration
// ----------------------------------
- controller_service : process.env.CONTROLLER_SERVICE || 'https://not.set.net',
+ controller_service : process.env.CONTROLLER_SERVICE,
// ----------------------------------
// Compiler Configuration
@@ -72,6 +72,7 @@ Edit at Your Own Risk
// Environment
// ------------------------------------
// N.B.: globals added here must _also_ be added to .eslintrc
+
config.globals = {
'process.env' : {
'NODE_ENV' : JSON.stringify(config.env),
@@ -83,9 +84,7 @@ config.globals = {
'__DEBUG__' : config.env === 'development' && !argv.no_debug,
'__COVERAGE__' : !argv.watch && config.env === 'test',
'__BASENAME__' : JSON.stringify(process.env.BASENAME || ''),
- '__CONTROLLER_API__' : config.env === 'test' ?
- `${config.controller_service}/api/v1` :
- JSON.stringify(`${config.controller_service}/api/v1`),
+ '__CONTROLLER_API__' : JSON.stringify(config.controller_service || ''),
};
// ------------------------------------
diff --git a/package.json b/package.json
index 1761785..88e27a6 100644
--- a/package.json
+++ b/package.json
@@ -69,7 +69,8 @@
"command": "node_modules/.bin/nyc node --harmony-proxies ./node_modules/.bin/ava",
"env": {
"NODE_ENV": "test",
- "NODE_PATH": "src"
+ "NODE_PATH": "src",
+ "CONTROLLER_SERVICE": "https://fake-controller.net"
}
}
},
diff --git a/src/components/GlobalNav/GlobalNav.jsx b/src/components/GlobalNav/GlobalNav.jsx
index 739bbfc..f3a0594 100644
--- a/src/components/GlobalNav/GlobalNav.jsx
+++ b/src/components/GlobalNav/GlobalNav.jsx
@@ -2,33 +2,23 @@ import React from 'react';
import IconButton from 'material-ui/IconButton';
import { Toolbar, ToolbarGroup, ToolbarTitle, ToolbarSeparator } from 'material-ui/Toolbar';
import { Icon } from 'react-fa';
-import { palette } from 'styles/muiTheme';
-import RoleSwitcher from './RoleSwitcher';
+import { Link } from 'react-router';
+import RoleSwitcher from 'containers/RoleSwitcherContainer';
import classes from './GlobalNav.scss';
const styles = {
- toolbar: {
- backgroundColor: palette.primary1Color,
- },
separator: {
- backgroundColor: 'rgb(184, 222, 228)',
- margin: '0px 12px 0px 24px',
- },
- paragraph: {
- paddingLeft: '2rem',
- fontSize: '0.9rem',
- },
- title: {
- color: palette.alternateTextColor,
- fontSize: '1rem',
+ margin: '0px 0.75rem 0px 2rem',
},
};
export const GlobalNav = () => (
-
+
-
+
+
+
@@ -40,12 +30,13 @@ export const GlobalNav = () => (
className={classes.github}
/>
- Demo Settings
+
);
-GlobalNav.propTypes = {};
+GlobalNav.propTypes = {
+};
export default GlobalNav;
diff --git a/src/components/GlobalNav/GlobalNav.scss b/src/components/GlobalNav/GlobalNav.scss
index 7758d6d..d263065 100644
--- a/src/components/GlobalNav/GlobalNav.scss
+++ b/src/components/GlobalNav/GlobalNav.scss
@@ -1,49 +1,15 @@
-/* GlobalNav Styles */
-.globalNav {
- p {
- color: rgb(255, 255, 255);
- }
-}
+@import '~styles/base/colors';
.title {
padding-left: 2rem;
-}
-
-.separator {
- background-color: rgb(184, 222, 228);
+ color: $alternateTextColor;
}
.github {
font-size: 1.9rem;
- color: rgb(184, 222, 228);
+ color: $primary3Color;
&:hover {
- color: rgb(255, 255, 255);
- }
-}
-
-/* RoleSwitcher Styles */
-.stack {
- position: relative;
- cursor: pointer;
- margin-right: 30px;
-
- .user {
- font-size: 1.2rem;
- color: rgb(15, 147, 166);
- line-height: 3.3rem;
- margin-left: .3rem;
- }
-
- &:hover {
- .circle {
- color: rgb(255, 255, 255);
- }
- }
-
- .circle {
- font-size: 1.9rem;
- color: rgb(184, 222, 228);
- line-height: 3.3rem;
+ color: $alternateTextColor;
}
}
diff --git a/src/components/GlobalNav/GlobalNav.story.js b/src/components/GlobalNav/GlobalNav.story.js
index 8e0e6e2..0974313 100644
--- a/src/components/GlobalNav/GlobalNav.story.js
+++ b/src/components/GlobalNav/GlobalNav.story.js
@@ -3,15 +3,31 @@ import { storiesOf } from '@kadira/storybook';
import GlobalNav from './GlobalNav';
import RoleSwitcher from './RoleSwitcher';
+const users = [
+ {
+ id: 1,
+ role: 'supplychainmanager',
+ loggedIn: true,
+ },
+ {
+ id: 2,
+ role: 'retailstoremanager',
+ location: 'Austin, TX',
+ },
+];
+
storiesOf('GlobalNav', module)
.addDecorator((story) => (
{story()}
))
- .add('Default', () => (
+ .add('Not Logged in', () => (
))
+ .add('Logged in', () => (
+
+ ))
.add('Role Switcher', () => (
-
+
));
diff --git a/src/components/GlobalNav/GlobalNav.test.js b/src/components/GlobalNav/GlobalNav.test.js
index 804f89b..1e0d582 100644
--- a/src/components/GlobalNav/GlobalNav.test.js
+++ b/src/components/GlobalNav/GlobalNav.test.js
@@ -1,34 +1,22 @@
import test from 'ava';
import React from 'react';
import { shallow } from 'enzyme';
-// import { Icon } from 'react-fa';
import GlobalNav from './GlobalNav';
-
const setup = () => {
- // const spies = {
- // clicky: sinon.spy(),
- // };
- // const props = {
- // customProp: 'Test',
- // clicky: spies.clicky,
- // };
+ const spies = {
+ };
+ const props = {
+ };
const component = shallow( );
- return { component };
+ return { component, props, spies };
};
test('(Component) Has expected elements.', t => {
const { component } = setup();
- // t.is(component.find('Icon'), 1,
- // 'contains React-FA Github Icons.');
- // t.true(component.contains(' '),
- // 'contains RoleSwitcher component.');
- // t.true(component.contains(' '),
- // 'contains Toolbar component.');
-
- t.is(component.find('Icon').length, 1);
- t.is(component.find('RoleSwitcher').length, 1);
- t.is(component.find('Toolbar').length, 1);
+ t.is(component.find('Connect(RoleSwitcher)').length, 1);
+ t.is(component.find('Link').length, 1);
+ t.is(component.find('Link').first().props().to, '/');
});
diff --git a/src/components/GlobalNav/RoleItem.jsx b/src/components/GlobalNav/RoleItem.jsx
deleted file mode 100644
index 5fc786b..0000000
--- a/src/components/GlobalNav/RoleItem.jsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import React from 'react';
-import classNames from 'classnames';
-import classes from './RoleItem.scss';
-
-export class RoleItem extends React.Component {
- getItemType = () => {
- if (this.props.type === 'button') {
- return (
-
- {this.props.label}
-
- );
- }
- else if (this.props.location) {
- return (
-
-
{this.props.label}
-
{this.props.location}
-
- );
- }
-
- return (
-
- {this.props.label}
-
- );
- }
- render() {
- return (
-
-
- {this.props.type === 'button'
- ?
- :
- }
-
-
-
- {this.getItemType()}
-
-
- );
- }
-}
-
-RoleItem.propTypes = {
- selected: React.PropTypes.bool,
- type: React.PropTypes.string,
- location: React.PropTypes.string,
- icon: React.PropTypes.string.isRequired,
- label: React.PropTypes.string.isRequired,
-};
-
-export default RoleItem;
diff --git a/src/components/GlobalNav/RoleItem.scss b/src/components/GlobalNav/RoleItem.scss
deleted file mode 100644
index d5930b2..0000000
--- a/src/components/GlobalNav/RoleItem.scss
+++ /dev/null
@@ -1,76 +0,0 @@
-.item {
- min-height: 65px;
- display: flex;
- width: 100%;
- text-align: left;
- cursor: pointer;
- border: none;
- border-bottom: 1px solid rgb(219, 222, 224);
- background: rgb(255, 255, 255);
- color: rgb(72, 85, 102);
- margin: 0;
- padding: 0;
- padding-left: 5px;
- outline: none;
-
- &:last-child {
- border-bottom: none;
- }
-
- &.selected {
- padding-left: 0px;
- border-left: 5px solid rgb(145, 195, 131);
- }
-}
-
-/* Menu Icon Styles */
-.iconContainer {
- border-left: 5px solid transparent;
- align-self: center;
-
- .icon {
- font-size: 1.5rem;
- color: rgb(72, 85, 102);
- line-height: 2.6rem;
- margin: 0px 1rem;
- }
-
- .small {
- font-size: 0.8rem;
- margin-left: 1.2rem;
- }
-}
-
-/* Menu Item Text Styles */
-.textContainer {
- display: flex;
- align-items: center;
- justify-content: center;
- align-self: center;
-
- p {
- margin: 0;
- }
-
- .label {
- font-size: 0.7rem;
- font-weight: 600;
- text-transform: uppercase;
- align-self: center;
- }
-
- .label.center {
- align-self: flex-start;
- }
-
- .sublabel {
- font-size: 0.7rem;
- line-height: 0.7rem;
- font-style: italic;
- align-self: flex-end;
- }
-
- .button {
- font-weight: 500;
- }
-}
diff --git a/src/components/GlobalNav/RoleSwitcher.jsx b/src/components/GlobalNav/RoleSwitcher.jsx
deleted file mode 100644
index f3085b8..0000000
--- a/src/components/GlobalNav/RoleSwitcher.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import React from 'react';
-import IconMenu from 'material-ui/IconMenu';
-import { Icon } from 'react-fa';
-import RoleItem from './RoleItem';
-import classes from './GlobalNav.scss';
-
-// const styles = {
-// stack: {
-// position: 'relative',
-// cursor: 'pointer',
-// },
-// user: {
-// fontSize: 30,
-// color: 'rgb(15, 147, 166)',
-// lineHeight: '62px',
-// },
-// circle: {
-// fontSize: 34,
-// color: 'rgb(255, 255, 255)',
-// lineHeight: '56px',
-// },
-// };
-
-// Props have been removed since they are not currently needed.
-// To add props back in, simply change the line below to: ...RoleSwitcher = (props) => (...
-export const RoleSwitcher = () => (
- // TODO: Find out what's causing runtime error.
-
-
-
-
- }
- >
-
-
-
-
-
-);
-export default RoleSwitcher;
diff --git a/src/components/GlobalNav/RoleSwitcher/RoleItem.jsx b/src/components/GlobalNav/RoleSwitcher/RoleItem.jsx
new file mode 100644
index 0000000..f970106
--- /dev/null
+++ b/src/components/GlobalNav/RoleSwitcher/RoleItem.jsx
@@ -0,0 +1,76 @@
+import React from 'react';
+import classNames from 'classnames';
+import classes from './RoleItem.scss';
+
+export class RoleItem extends React.PureComponent {
+ iconContainer = (user) => (
+
+
+
+ )
+
+ textContainer = (user) => (
+
+
+ {user ? user.role : 'Create New Retail Manager' }
+
+ {user && user.location
+ ?
{user.location}
+ : ''
+ }
+
+ )
+
+ handleClick = () => {
+ const { user, roleAction } = this.props;
+
+ if (user && !user.loggedIn) {
+ roleAction(user.id);
+ }
+ else {
+ roleAction();
+ }
+ }
+
+ render() {
+ const user = this.props.user;
+
+ return (
+
+ {this.iconContainer(user)}
+ {this.textContainer(user)}
+
+ );
+ }
+}
+
+RoleItem.propTypes = {
+ user: React.PropTypes.shape({
+ id: React.PropTypes.number.isRequired,
+ role: React.PropTypes.string.isRequired,
+ location: React.PropTypes.string,
+ loggedIn: React.PropTypes.bool,
+ }),
+ roleAction: React.PropTypes.func.isRequired,
+};
+
+export default RoleItem;
diff --git a/src/components/GlobalNav/RoleSwitcher/RoleItem.scss b/src/components/GlobalNav/RoleSwitcher/RoleItem.scss
new file mode 100644
index 0000000..7c27265
--- /dev/null
+++ b/src/components/GlobalNav/RoleSwitcher/RoleItem.scss
@@ -0,0 +1,69 @@
+@import '~styles/base/colors';
+
+.item {
+ min-height: 65px;
+ display: flex;
+ width: 100%;
+ text-align: left;
+ cursor: pointer;
+ border: none;
+ border-bottom: 1px solid $borderColor;
+ background: rgb(255, 255, 255);
+ color: $primary2Color;
+ margin: 0;
+ padding: 0;
+ padding-left: 5px;
+ outline: none;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ &.selected {
+ padding-left: 0px;
+ border-left: 5px solid $accent1Color;
+ }
+}
+
+/* Menu Icon Styles */
+.iconContainer {
+ border-left: 5px solid transparent;
+ align-self: center;
+
+}
+
+.icon {
+ font-size: 1.5rem;
+ color: $primary2Color;
+ line-height: 2.6rem;
+ margin: 0px 1rem;
+}
+
+.small {
+ font-size: 0.8rem;
+ margin-left: 1.2rem;
+}
+
+/* Menu Item Text Styles */
+.textContainer {
+ display: flex;
+ align-self: center;
+ flex-wrap: wrap;
+ flex-direction: column;
+}
+
+.light {
+ font-weight: 500;
+}
+
+.sublabel {
+ font-size: 0.7rem;
+ line-height: 0.7rem;
+ font-style: italic;
+}
+
+.label {
+ font-size: 0.7rem;
+ font-weight: 600;
+ text-transform: uppercase;
+}
diff --git a/src/components/GlobalNav/RoleSwitcher/RoleItem.test.js b/src/components/GlobalNav/RoleSwitcher/RoleItem.test.js
new file mode 100644
index 0000000..c60ccd9
--- /dev/null
+++ b/src/components/GlobalNav/RoleSwitcher/RoleItem.test.js
@@ -0,0 +1,62 @@
+import React from 'react';
+import test from 'ava';
+import sinon from 'sinon';
+import { shallow } from 'enzyme';
+import RoleItem from './RoleItem';
+
+const retailManager = ({
+ id = 2,
+ role = 'retailstoremanager',
+ location = 'Austin, TX',
+ loggedIn = true,
+} = {}) => ({ id, role, location, loggedIn });
+
+const supplyManager = ({
+ id = 1,
+ role = 'supplychainmanager',
+} = {}) => ({ id, role });
+
+const setup = (user) => {
+ const spies = {
+ roleAction: sinon.spy(),
+ };
+ const props = {
+ user,
+ roleAction: spies.roleAction,
+ };
+ const component = shallow( );
+
+ return { component, props, spies };
+};
+
+test('(Component) Supply Chain Manager, not logged in, has expected elements.', t => {
+ const { component } = setup(supplyManager());
+
+ t.true(component.is('button'));
+ t.false(component.hasClass('selected'));
+ t.is(component.find('.fa-user').length, 1);
+ t.is(component.find('.sublabel').length, 0);
+ t.is(component.find('.light').length, 0);
+ t.is(component.find('.small').length, 0);
+});
+
+test('(Component) Retail Store Manager, logged in, has expected elements.', t => {
+ const { component, props } = setup(retailManager());
+
+ t.true(component.is('button'));
+ t.true(component.hasClass('selected'));
+ t.is(component.find('.fa-user').length, 1);
+ t.is(component.find('.sublabel').length, 1);
+ t.is(component.find('.light').length, 0);
+ t.is(component.find('.small').length, 0);
+ t.regex(component.text(), new RegExp(props.user.location));
+});
+
+test('(Component) No user passed in, has expected elements.', t => {
+ const { component } = setup();
+
+ t.false(component.hasClass('selected'));
+ t.is(component.find('.fa-plus').length, 1);
+ t.is(component.find('.light').length, 1);
+ t.is(component.find('.small').length, 1);
+});
diff --git a/src/components/GlobalNav/RoleSwitcher/RoleSwitcher.jsx b/src/components/GlobalNav/RoleSwitcher/RoleSwitcher.jsx
new file mode 100644
index 0000000..7f6ab80
--- /dev/null
+++ b/src/components/GlobalNav/RoleSwitcher/RoleSwitcher.jsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import IconMenu from 'material-ui/IconMenu';
+import IconButton from 'material-ui/IconButton';
+import AccountUser from 'material-ui/svg-icons/action/account-circle';
+import { palette } from 'styles/muiTheme';
+import RoleItem from './RoleItem';
+
+const iconStyles = {
+ width: '2rem',
+ height: '2rem',
+};
+
+const AccountButton = () => (
+
+
+
+);
+
+export const RoleSwitcher = ({ users, login, createUser }) => (
+ users
+ ?
+ {users.map((user, key) => (
+
+ ))}
+
+
+ : No Demo Session
+);
+
+RoleSwitcher.propTypes = {
+ users: React.PropTypes.arrayOf(React.PropTypes.shape({
+ id: React.PropTypes.number.isRequired,
+ role: React.PropTypes.string.isRequired,
+ location: React.PropTypes.string,
+ loggedIn: React.PropTypes.bool,
+ }).isRequired),
+ login: React.PropTypes.func.isRequired,
+ createUser: React.PropTypes.func.isRequired,
+};
+
+export default RoleSwitcher;
diff --git a/src/components/GlobalNav/RoleSwitcher/RoleSwitcher.scss b/src/components/GlobalNav/RoleSwitcher/RoleSwitcher.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/components/GlobalNav/RoleSwitcher/index.js b/src/components/GlobalNav/RoleSwitcher/index.js
new file mode 100644
index 0000000..0260bf4
--- /dev/null
+++ b/src/components/GlobalNav/RoleSwitcher/index.js
@@ -0,0 +1,3 @@
+import RoleSwitcher from './RoleSwitcher';
+
+export default RoleSwitcher;
diff --git a/src/containers/RoleSwitcherContainer.js b/src/containers/RoleSwitcherContainer.js
new file mode 100644
index 0000000..a09e332
--- /dev/null
+++ b/src/containers/RoleSwitcherContainer.js
@@ -0,0 +1,14 @@
+import { connect } from 'react-redux';
+import { login, createUser } from 'modules/demos';
+import RoleSwitcher from 'components/GlobalNav/RoleSwitcher';
+
+const mapActionCreators = {
+ login,
+ createUser,
+};
+
+const mapStateToProps = (state) => ({
+ users: state.demoSession.users,
+});
+
+export default connect(mapStateToProps, mapActionCreators)(RoleSwitcher);
diff --git a/src/modules/demos.js b/src/modules/demos.js
index efec4c7..084f7cd 100644
--- a/src/modules/demos.js
+++ b/src/modules/demos.js
@@ -1,44 +1,88 @@
+import { call, take, put, select } from 'redux-saga/effects';
+import api from 'services';
+
// ------------------------------------
// Constants
// ------------------------------------
-export const RECEIVE_DEMO_SUCCESS = 'demos/RECEIVE_DEMO_SUCCESS';
+export const GET_DEMO_SESSION = 'demos/GET_DEMO_SESSION';
+export const GET_DEMO_SUCCESS = 'demos/GET_DEMO_SUCCESS';
+export const LOGIN = 'demos/LOGIN';
export const LOGIN_SUCCESS = 'demos/LOGIN_SUCCESS';
+export const demoSelector = state => state.demoSession;
+
// ------------------------------------
// Actions
// ------------------------------------
-export const receiveDemoSuccess = (value) => ({
- type: RECEIVE_DEMO_SUCCESS,
- payload: value,
+export const getDemoSession = (guid) => ({
+ type: GET_DEMO_SESSION,
+ guid,
+});
+
+export const getDemoSuccess = (payload) => ({
+ type: GET_DEMO_SUCCESS,
+ demo: payload.demo,
+ retailers: payload.retailers,
+});
+
+// TODO finish this v
+export const createUser = () => ({
+ type: 'CREATE_USER',
+});
+
+export const login = (userid) => ({
+ type: LOGIN,
+ userid,
});
-export const loginSuccess = (value) => ({
+export const loginSuccess = ({ token, userid }) => ({
type: LOGIN_SUCCESS,
- payload: value,
+ token,
+ userid,
});
export const actions = {
- receiveDemoSuccess,
+ getDemoSuccess,
loginSuccess,
};
// ------------------------------------
// Action Handlers
// ------------------------------------
+const matchUserToLocation = (userid, retailers) => {
+ const location = retailers.find((retailer) => retailer.managerId === userid).address;
+ return `${location.city}, ${location.state}`;
+};
+
+const mapUserData = (users, retailers) =>
+ users.map(user => {
+ const isSupplyManager = user.roles[0].name.includes('supply');
+ const userMapping = {
+ id: user.id,
+ role: isSupplyManager ? 'Supply Chain Manager' : 'Retail Store Manager',
+ };
+
+ if (!isSupplyManager) {
+ userMapping.location = matchUserToLocation(user.id, retailers);
+ }
+
+ return userMapping;
+ });
+
const ACTION_HANDLERS = {
- [RECEIVE_DEMO_SUCCESS]: (state, action) => ({
+ [GET_DEMO_SUCCESS]: (state, { demo, retailers }) => ({
...state,
- name: action.payload.name,
- guid: action.payload.guid,
- users: [{
- id: action.payload.users[0].id,
- username: action.payload.users[0].username,
- type: action.payload.users[0].roles[0].name,
- }],
+ name: demo.name,
+ guid: demo.guid,
+ users: mapUserData(demo.users, retailers),
}),
- [LOGIN_SUCCESS]: (state, action) => ({
+ [LOGIN_SUCCESS]: (state, { token, userid }) => ({
...state,
- token: action.payload,
+ token: token.token,
+ users: state.users.map(user => ({
+ ...user,
+ loggedIn: user.id === userid,
+ })),
}),
};
@@ -57,7 +101,45 @@ export default demosReducer;
// ------------------------------------
// Sagas
// ------------------------------------
-export const demoSelector = state => state.demoSession;
+export function *watchGetDemoSession() {
+ while (true) {
+ const { guid } = yield take(GET_DEMO_SESSION);
+ let demoState = yield select(demoSelector);
+
+ if (demoState.guid !== guid) {
+ try {
+ const [demo, retailers] = yield [
+ call(api.getDemo, guid),
+ call(api.getRetailers, guid),
+ ];
+ yield put(getDemoSuccess({ demo, retailers }));
+ demoState = yield select(demoSelector);
+ yield put(login(demoState.users[0].id));
+ }
+ catch (error) {
+ console.log('Get Demo Failure: ', error);
+ // yield put(getDemoFailure(error));
+ }
+ }
+ }
+}
+
+export function *watchLogin() {
+ while (true) {
+ const { userid } = yield take(LOGIN);
+ const demoState = yield select(demoSelector);
+ try {
+ const token = yield call(api.login, userid, demoState.guid);
+ yield put(loginSuccess({ token, userid }));
+ }
+ catch (error) {
+ console.log('Login Failure: ', error);
+ // yield put(loginFailure(error));
+ }
+ }
+}
export const sagas = [
+ watchGetDemoSession,
+ watchLogin,
];
diff --git a/src/modules/demos.test.js b/src/modules/demos.test.js
index 659ebe1..3c00585 100644
--- a/src/modules/demos.test.js
+++ b/src/modules/demos.test.js
@@ -1,38 +1,70 @@
import test from 'ava';
import { reducerTest, actionTest } from 'redux-ava';
+import { call, take, select, put } from 'redux-saga/effects';
+import api from 'services';
+import mockApi from 'services/mockApi';
import {
- RECEIVE_DEMO_SUCCESS,
+ GET_DEMO_SESSION,
+ GET_DEMO_SUCCESS,
LOGIN_SUCCESS,
- receiveDemoSuccess,
+ LOGIN,
+ getDemoSession,
+ getDemoSuccess,
+ login,
loginSuccess,
demosReducer,
demoSelector,
+ watchGetDemoSession,
+ watchLogin,
} from './demos';
+
test('(Selector) returns the slice of state for demos.', t => {
t.deepEqual(demoSelector({ demoSession: { id: '123' } }), { id: '123' });
});
-test('(Constant) RECEIVE_DEMO_SUCCESS === "demos/RECEIVE_DEMO_SUCCESS"', t => {
- t.is(RECEIVE_DEMO_SUCCESS, 'demos/RECEIVE_DEMO_SUCCESS');
+test('(Constant) GET_DEMO_SESSION === "demos/GET_DEMO_SESSION"', t => {
+ t.is(GET_DEMO_SESSION, 'demos/GET_DEMO_SESSION');
+});
+
+test('(Constant) GET_DEMO_SUCCESS === "demos/GET_DEMO_SUCCESS"', t => {
+ t.is(GET_DEMO_SUCCESS, 'demos/GET_DEMO_SUCCESS');
+});
+
+test('(Constant) LOGIN === "demos/LOGIN"', t => {
+ t.is(LOGIN, 'demos/LOGIN');
});
test('(Constant) LOGIN_SUCCESS === "demos/LOGIN_SUCCESS"', t => {
t.is(LOGIN_SUCCESS, 'demos/LOGIN_SUCCESS');
});
-test('(Action) receiveDemoSuccess',
+test('(Action) getDemoSession',
actionTest(
- receiveDemoSuccess,
- { name: 'test' },
- { type: RECEIVE_DEMO_SUCCESS, payload: { name: 'test' } })
+ getDemoSession,
+ '1234',
+ { type: GET_DEMO_SESSION, guid: '1234' })
+ );
+
+test('(Action) getDemoSuccess',
+ actionTest(
+ getDemoSuccess,
+ { demo: 'test', retailers: [1, 2, 3] },
+ { type: GET_DEMO_SUCCESS, demo: 'test', retailers: [1, 2, 3] })
+ );
+
+test('(Action) login',
+ actionTest(
+ login,
+ '1234',
+ { type: LOGIN, userid: '1234' })
);
test('(Action) loginSuccess',
actionTest(
loginSuccess,
- { name: 'test' },
- { type: LOGIN_SUCCESS, payload: { name: 'test' } })
+ { token: { token: 'token' }, userid: 'userid' },
+ { type: LOGIN_SUCCESS, token: { token: 'token' }, userid: 'userid' })
);
test('(Reducer) initializes with empty state', t => {
@@ -46,35 +78,141 @@ test('(Reducer) return previous state when no action is matched', reducerTest(
{},
));
-test('(Reducer) stores token on loginSuccess', reducerTest(
+test('(Reducer) doesnt try to handle getDemoSession Saga', reducerTest(
demosReducer,
{},
- loginSuccess('login-token'),
- { token: 'login-token' },
+ getDemoSession('1234'),
+ {},
));
-const demoApiResponse = () => ({
- name: 'demo',
- guid: 'guid',
- id: 'demoid',
- users: [{
- id: 'userid',
- username: 'johndoe',
- roles: [{ name: 'supplychainmanager' }],
- }],
-});
+test('(Reducer) doesnt try to handle login Saga', reducerTest(
+ demosReducer,
+ {},
+ login('1234'),
+ {},
+));
+
+test('(Reducer) stores token on loginSuccess, and changes logged in user', reducerTest(
+ demosReducer,
+ {
+ name: 'demo',
+ guid: 'guid',
+ token: 'old-token',
+ users: [{
+ id: 100,
+ role: 'Supply Chain Manager',
+ loggedIn: true,
+ }, {
+ id: 200,
+ role: 'Retail Store Manager',
+ location: 'Austin, Texas',
+ loggedIn: false,
+ }],
+ },
+ loginSuccess({ token: mockApi.login('new-token'), userid: 200 }),
+ {
+ name: 'demo',
+ guid: 'guid',
+ token: 'new-token',
+ users: [{
+ id: 100,
+ role: 'Supply Chain Manager',
+ loggedIn: false,
+ }, {
+ id: 200,
+ role: 'Retail Store Manager',
+ location: 'Austin, Texas',
+ loggedIn: true,
+ }],
+ },
+));
-test('(Reducer) adds demo session to state on receiveDemoSuccess', reducerTest(
+test('(Reducer) adds demo session to state on getDemoSuccess', reducerTest(
demosReducer,
{},
- receiveDemoSuccess(demoApiResponse()),
+ getDemoSuccess({
+ demo: (() => {
+ const demo = mockApi.getDemo({ name: 'demo', guid: 'guid' });
+ demo.users.push(mockApi.getUser(200));
+ return demo;
+ })(),
+ retailers: [
+ mockApi.getRetailer({
+ city: 'Austin',
+ state: 'Texas',
+ managerId: 200,
+ }),
+ ],
+ }),
{
name: 'demo',
guid: 'guid',
users: [{
- id: 'userid',
- username: 'johndoe',
- type: 'supplychainmanager',
+ id: 100,
+ role: 'Supply Chain Manager',
+ }, {
+ id: 200,
+ role: 'Retail Store Manager',
+ location: 'Austin, Texas',
}],
},
));
+
+test('(Saga) watchGetDemoSession - new Guid, API Success', t => {
+ const saga = watchGetDemoSession();
+
+ t.deepEqual(saga.next().value, take(GET_DEMO_SESSION));
+
+ const newGuid = 'Another Guid';
+ const action = getDemoSession(newGuid);
+ t.deepEqual(saga.next(action).value, select(demoSelector));
+
+ // Saga should see that our guid doesn't match, so fetches new demo and logs in.
+
+ t.truthy(action.guid);
+ let demoState = demosReducer({}, { type: '@@@@@' });
+ t.deepEqual(
+ saga.next(demoState).value,
+ [call(api.getDemo, action.guid), call(api.getRetailers, action.guid)]
+ );
+
+ const demoPayload = mockApi.getDemo({ guid: newGuid });
+ const retailersPayload = [mockApi.getRetailer()];
+ t.deepEqual(
+ saga.next([demoPayload, retailersPayload]).value,
+ put(getDemoSuccess({ demo: demoPayload, retailers: retailersPayload }))
+ );
+ t.deepEqual(saga.next().value, select(demoSelector));
+
+ demoState = demosReducer(demoState, getDemoSuccess({ demo: demoPayload, retailers: retailersPayload }));
+ t.deepEqual(saga.next(demoState).value, put(login(demoState.users[0].id)));
+
+ // Saga loops back to beginning
+ t.deepEqual(saga.next().value, take(GET_DEMO_SESSION));
+});
+
+test.todo('(Saga) watchGetDemoSession - Write error handling logic');
+
+
+test('(Saga) watchLogin - Not logged in, API Success', t => {
+ const saga = watchLogin();
+ const demo = mockApi.getDemo();
+ demo.users.push(mockApi.getUser(200));
+ const retailers = [mockApi.getRetailer({ managerId: 200 })];
+
+ const action = login(100);
+ t.deepEqual(saga.next().value, take(LOGIN));
+
+ const demoState = demosReducer({}, getDemoSuccess({ demo, retailers }));
+ t.deepEqual(saga.next(action).value, select(demoSelector));
+
+ t.truthy(action.userid);
+ t.truthy(demoState.guid);
+ const token = mockApi.login();
+ t.deepEqual(saga.next(demoState).value, call(api.login, action.userid, demoState.guid));
+
+ t.deepEqual(saga.next(token).value, put(loginSuccess({ token, userid: action.userid })));
+
+ // Saga loops back to beginning
+ t.deepEqual(saga.next().value, take(LOGIN));
+});
diff --git a/src/routes/CreateDemo/modules/CreateDemo.js b/src/routes/CreateDemo/modules/CreateDemo.js
index 3c3e755..9dee082 100644
--- a/src/routes/CreateDemo/modules/CreateDemo.js
+++ b/src/routes/CreateDemo/modules/CreateDemo.js
@@ -1,9 +1,7 @@
-import { call, take, put, select } from 'redux-saga/effects';
+import { call, take, put } from 'redux-saga/effects';
import { push } from 'react-router-redux';
import api from 'services';
-import { receiveDemoSuccess, demoSelector } from 'modules/demos';
-
export const createDemoSelector = state => state.createDemo;
// ------------------------------------
// Constants
@@ -15,9 +13,13 @@ export const CREATE_DEMO_FAILURE = 'CreateDemo/CREATE_DEMO_FAILURE';
// ------------------------------------
// Actions
// ------------------------------------
-export const createDemo = (value) => ({
+export const createDemo = ({
+ name = `Test Demo ${Date.now()}`,
+ email,
+} = {}) => ({
type: CREATE_DEMO,
- payload: value,
+ name,
+ email,
});
export const createDemoFailure = (value) => ({
@@ -56,13 +58,11 @@ export default createDemoReducer;
// ------------------------------------
export function *watchCreateDemo() {
while (true) {
- const { payload } = yield take(CREATE_DEMO);
+ const action = yield take(CREATE_DEMO);
try {
- const demoSession = yield call(api.createDemo, payload.name, payload.email);
- yield put(receiveDemoSuccess(demoSession));
- const demoState = yield select(demoSelector);
- yield put(push(`/dashboard/${demoState.guid}`));
+ const demoSession = yield call(api.createDemo, action.name, action.email);
+ yield put(push(`/dashboard/${demoSession.guid}`));
}
catch (error) {
yield put(createDemoFailure(error));
diff --git a/src/routes/CreateDemo/modules/CreateDemo.test.js b/src/routes/CreateDemo/modules/CreateDemo.test.js
index 7a0ea3a..adb9dd5 100644
--- a/src/routes/CreateDemo/modules/CreateDemo.test.js
+++ b/src/routes/CreateDemo/modules/CreateDemo.test.js
@@ -1,9 +1,9 @@
import test from 'ava';
import { reducerTest, actionTest } from 'redux-ava';
-import { call, take, select, put } from 'redux-saga/effects';
-import { receiveDemoSuccess, demoSelector } from 'modules/demos';
+import { call, take, put } from 'redux-saga/effects';
import { push } from 'react-router-redux';
import api from 'services';
+import mockApi from 'services/mockApi';
import {
CREATE_DEMO,
CREATE_DEMO_FAILURE,
@@ -11,10 +11,11 @@ import {
createDemoFailure,
createDemoReducer,
watchCreateDemo,
+ createDemoSelector,
} from './CreateDemo';
test('(Selector) returns the slice of state for createDemo.', t => {
- t.deepEqual(demoSelector({ demoSession: { id: '123' } }), { id: '123' });
+ t.deepEqual(createDemoSelector({ createDemo: { stuff: '123' } }), { stuff: '123' });
});
test('(Constant) CREATE_DEMO === "CreateDemo/CREATE_DEMO"', t => {
@@ -29,7 +30,14 @@ test('(Action) createDemo',
actionTest(
createDemo,
{ name: 'test' },
- { type: CREATE_DEMO, payload: { name: 'test' } })
+ { type: CREATE_DEMO, name: 'test', email: undefined }),
+ );
+
+test('(Action) createDemo - with email',
+ actionTest(
+ createDemo,
+ { name: 'test', email: 'name@email.com' },
+ { type: CREATE_DEMO, name: 'test', email: 'name@email.com' }),
);
test('(Action) createDemoFailure',
@@ -67,21 +75,14 @@ test('(Reducer) doesnt try to handle saga', reducerTest(
test('(Saga) watchCreateDemo - API Success', t => {
const saga = watchCreateDemo();
- const payload = { name: 'test demo', email: 'name@email.com' };
- const createDemoAction = createDemo(payload);
- const demoSession = { mockResponse: 'blah blah' };
- const demoState = { guid: 1234 };
+ const action = createDemo();
+
+ t.deepEqual(saga.next().value, take(CREATE_DEMO));
+ t.deepEqual(saga.next(action).value, call(api.createDemo, action.name, action.email));
+
+ const response = mockApi.getDemo();
+ t.deepEqual(saga.next(response).value, put(push(`/dashboard/${response.guid}`)));
- t.deepEqual(saga.next().value, take(CREATE_DEMO),
- 'listens for CREATE_DEMO action.');
- t.deepEqual(saga.next(createDemoAction).value, call(api.createDemo, payload.name, payload.email),
- 'calls api with action payload as params.');
- t.deepEqual(saga.next(demoSession).value, put(receiveDemoSuccess(demoSession)),
- 'dispatches receiveDemoSuccess action.');
- t.deepEqual(saga.next().value, select(demoSelector),
- 'gets the updated state.');
- t.deepEqual(saga.next(demoState).value, put(push(`/dashboard/${demoState.guid}`)),
- 'dispatches route change to dashboard');
t.deepEqual(saga.next().value, take(CREATE_DEMO),
'saga resets, and begins listening for CREATE_DEMO again.');
});
@@ -89,14 +90,12 @@ test('(Saga) watchCreateDemo - API Success', t => {
test.todo('Build a meaningful action around api failure.');
test('(Saga) watchCreateDemo - API Failure', t => {
const saga = watchCreateDemo();
- const payload = { name: 'test demo', email: 'name@email.com' };
- const createDemoAction = createDemo(payload);
- const error = { message: 'bad email' };
+ const action = createDemo();
- t.deepEqual(saga.next().value, take(CREATE_DEMO),
- 'listens for CREATE_DEMO action.');
- t.deepEqual(saga.next(createDemoAction).value, call(api.createDemo, payload.name, payload.email),
- 'calls api with action payload as params.');
+ t.deepEqual(saga.next().value, take(CREATE_DEMO));
+ t.deepEqual(saga.next(action).value, call(api.createDemo, action.name, action.email));
+
+ const error = { message: 'bad email' };
t.deepEqual(saga.throw(error).value, put(createDemoFailure(error)),
'dispatches createDemoFailure if api call fails.');
t.deepEqual(saga.next().value, take(CREATE_DEMO),
diff --git a/src/routes/Dashboard/components/Dashboard.jsx b/src/routes/Dashboard/components/Dashboard.jsx
index be627cc..9a3111d 100644
--- a/src/routes/Dashboard/components/Dashboard.jsx
+++ b/src/routes/Dashboard/components/Dashboard.jsx
@@ -2,26 +2,24 @@ import React from 'react';
import classes from './Dashboard.scss';
export default class Dashboard extends React.PureComponent {
- constructor(props) {
- super(props);
- props.getAdminData(props.params.guid);
- }
-
render() {
return (
Dashboard - Yay, you created a demo!
Demo Name: {this.props.demoName || 'loading...'}
-
{this.props.dbdata ? JSON.stringify(this.props.dbdata, null, 2) : 'loading...'}
+
+ {
+ Object.keys(this.props.dbdata).length !== 0
+ ? JSON.stringify(this.props.dbdata, null, 2)
+ : 'loading...'
+ }
+
);
}
}
-
Dashboard.propTypes = {
- demoName: React.PropTypes.string.isRequired,
+ demoName: React.PropTypes.string,
dbdata: React.PropTypes.object.isRequired,
- getAdminData: React.PropTypes.func.isRequired,
- params: React.PropTypes.object.isRequired,
-};
\ No newline at end of file
+};
diff --git a/src/routes/Dashboard/components/Dashboard.test.js b/src/routes/Dashboard/components/Dashboard.test.js
index 7d27c55..2758a5c 100644
--- a/src/routes/Dashboard/components/Dashboard.test.js
+++ b/src/routes/Dashboard/components/Dashboard.test.js
@@ -1,22 +1,15 @@
import test from 'ava';
import React from 'react';
-import sinon from 'sinon';
-import { bindActionCreators } from 'redux';
import { shallow } from 'enzyme';
import Dashboard from './Dashboard';
const setup = () => {
const spies = {
- getAdminData: sinon.spy(),
- dispatch: sinon.spy(),
};
const props = {
demoName: 'Test Demo',
dbdata: { fakeData: 'fake stuff' },
params: { guid: '1234' },
- ...bindActionCreators({
- getAdminData: spies.getAdminData,
- }, spies.dispatch),
};
const component = shallow( );
@@ -27,16 +20,6 @@ test.todo('write tests for dashboard elements once complete.');
test('(Component) Renders with expected elements', t => {
const { props, component } = setup();
- t.true(component.is('div'),
- 'is wrapped by a div');
t.regex(component.find('p').first().text(), new RegExp(props.demoName, 'g'),
'renders demo name from props');
});
-
-test('(Component) Works as expected.', t => {
- const { spies } = setup();
-
- t.true(spies.getAdminData.calledOnce,
- 'getAdminData is called on creation');
- t.true(spies.dispatch.calledOnce);
-});
diff --git a/src/routes/Dashboard/containers/DashboardContainer.js b/src/routes/Dashboard/containers/DashboardContainer.js
index 5fc50c1..0ab9821 100644
--- a/src/routes/Dashboard/containers/DashboardContainer.js
+++ b/src/routes/Dashboard/containers/DashboardContainer.js
@@ -1,9 +1,7 @@
import { connect } from 'react-redux';
-import { getAdminData } from '../modules/Dashboard';
import Dashboard from '../components/Dashboard';
const mapActionCreators = {
- getAdminData,
};
const mapStateToProps = (state) => ({
diff --git a/src/routes/Dashboard/index.js b/src/routes/Dashboard/index.js
index 03b9237..16a8e7c 100644
--- a/src/routes/Dashboard/index.js
+++ b/src/routes/Dashboard/index.js
@@ -1,5 +1,6 @@
import { injectReducer } from 'store/reducers';
import { injectSagas } from 'store/sagas';
+import { getDemoSession } from 'modules/demos';
export default (store) => ({
path: 'dashboard/:guid',
@@ -12,7 +13,11 @@ export default (store) => ({
injectReducer(store, { key: 'dashboard', reducer });
injectSagas(store, { key: 'dashboard', sagas });
+ store.dispatch(getDemoSession(nextState.params.guid));
cb(null, Dashboard);
}, 'dashboard');
},
+ onEnter: (nextState) => {
+ store.dispatch(getDemoSession(nextState.params.guid));
+ },
});
diff --git a/src/routes/Dashboard/index.test.js b/src/routes/Dashboard/index.test.js
index 5d2d2d0..abd3f73 100644
--- a/src/routes/Dashboard/index.test.js
+++ b/src/routes/Dashboard/index.test.js
@@ -8,3 +8,8 @@ test('(Route) should return a route config object', t => {
test('(Route) Config should contain path "dashboard/:guid"', t => {
t.is(DashboardRoute({}).path, 'dashboard/:guid');
});
+
+test('(Route) Config should contain logic to dispatch getDemo action', t => {
+ t.pass();
+ // t.is(DashboardRoute({}).onEnter, Test Function Somehow?);
+});
diff --git a/src/routes/Dashboard/modules/Dashboard.js b/src/routes/Dashboard/modules/Dashboard.js
index 1aba537..c574d12 100644
--- a/src/routes/Dashboard/modules/Dashboard.js
+++ b/src/routes/Dashboard/modules/Dashboard.js
@@ -1,6 +1,8 @@
import { call, take, put, select } from 'redux-saga/effects';
import api from 'services';
-import { loginSuccess, receiveDemoSuccess, demoSelector } from 'modules/demos';
+import { demoSelector } from 'modules/demos';
+
+export const dashboardSelector = state => state.dashboard;
// ------------------------------------
// Constants
@@ -52,34 +54,10 @@ export default dashboardReducer;
// Sagas
// ------------------------------------
-// This is set up in `../index.js` as the key in `injectSagas(store, { key: 'dashboard', sagas });`
-export const dashboardSelector = state => state.dashboard;
-
export function *watchGetAdminData() {
while (true) {
- const { guid } = yield take(GET_ADMIN_DATA);
- let demoState = yield select(demoSelector);
- if (demoState.guid !== guid) {
- try {
- const demoSession = yield call(api.getDemo, guid);
- yield put(receiveDemoSuccess(demoSession));
- demoState = yield select(demoSelector);
- }
- catch (error) {
- // console.log(error);
- // yield put(receiveDemoFailure(error));
- }
-
- try {
- const { token } = yield call(api.login, demoState.users[0].id, demoState.guid);
- yield put(loginSuccess(token));
- demoState = yield select(demoSelector);
- }
- catch (error) {
- // console.log(error);
- // yield put(loginFailure(error));
- }
- }
+ yield take(GET_ADMIN_DATA);
+ const demoState = yield select(demoSelector);
try {
const adminData = yield call(api.getAdminData, demoState.token);
diff --git a/src/routes/Dashboard/modules/Dashboard.test.js b/src/routes/Dashboard/modules/Dashboard.test.js
index 43bb77b..403ab5e 100644
--- a/src/routes/Dashboard/modules/Dashboard.test.js
+++ b/src/routes/Dashboard/modules/Dashboard.test.js
@@ -1,7 +1,7 @@
import test from 'ava';
import { reducerTest, actionTest } from 'redux-ava';
import { call, take, select, put } from 'redux-saga/effects';
-import { demosReducer, loginSuccess, receiveDemoSuccess, demoSelector } from 'modules/demos';
+import { demosReducer, loginSuccess, getDemoSuccess, demoSelector } from 'modules/demos';
import api from 'services';
import mockApi from 'services/mockApi';
import {
@@ -66,62 +66,29 @@ test('(Reducer) doesnt try to handle saga', reducerTest(
{ mock: 'mock' },
));
-test('(Saga) watchGetAdminData - Not logged in, API Success', t => {
+test('(Saga) watchGetAdminData - API Success', t => {
const saga = watchGetAdminData();
- let demoState = demosReducer({}, receiveDemoSuccess(mockApi.getDemo()));
+ // Setup initial state
+ const demo = mockApi.getDemo();
+ demo.users.push(mockApi.getUser(200));
+ const retailers = [mockApi.getRetailer({ managerId: 200 })];
+ let demoState = demosReducer({}, getDemoSuccess({ demo, retailers }));
+ const token = mockApi.login();
+ demoState = demosReducer(demoState, loginSuccess({ token, userid: demoState.users[0].id }));
const newGuid = 'Another Guid';
const action = getAdminData(newGuid);
t.deepEqual(saga.next().value, take(GET_ADMIN_DATA));
- t.deepEqual(saga.next(action).value, select(demoSelector));
-
- // Saga should see that our guid doesn't match, so fetches new demo and logs in.
-
- t.truthy(action.guid);
- t.deepEqual(saga.next(demoState).value, call(api.getDemo, action.guid));
-
- const demoPayload = mockApi.getDemo(newGuid);
- t.deepEqual(saga.next(demoPayload).value, put(receiveDemoSuccess(demoPayload)));
- t.deepEqual(saga.next().value, select(demoSelector));
- demoState = demosReducer(demoState, receiveDemoSuccess(demoPayload));
-
- t.truthy(demoState.users[0].id);
- t.truthy(demoState.guid);
- t.deepEqual(saga.next(demoState).value, call(api.login, demoState.users[0].id, demoState.guid));
- const loginPayload = mockApi.login();
- t.deepEqual(saga.next(loginPayload).value, put(loginSuccess(loginPayload.token)));
- t.deepEqual(saga.next().value, select(demoSelector));
- demoState = demosReducer(demoState, loginSuccess(loginPayload.token));
-
- t.truthy(demoState.token);
- t.deepEqual(saga.next(demoState).value, call(api.getAdminData, demoState.token));
- const adminPayload = mockApi.getAdminData();
- t.deepEqual(saga.next(adminPayload).value, put(adminDataReceived(adminPayload)));
-
- // Saga loops back to beginning
- t.deepEqual(saga.next().value, take(GET_ADMIN_DATA));
-});
-test('(Saga) watchGetAdminData - Already logged in, API Success', t => {
- const saga = watchGetAdminData();
- let demoState = demosReducer({}, receiveDemoSuccess(mockApi.getDemo()));
- demoState = demosReducer(demoState, loginSuccess(mockApi.login().token));
-
- const action = getAdminData(demoState.guid);
- t.deepEqual(saga.next().value, take(GET_ADMIN_DATA));
t.deepEqual(saga.next(action).value, select(demoSelector));
- // Saga should see that our guid already matches here, so goes straight to api.getAdminData
-
+ const adminPayload = mockApi.getAdminData();
t.truthy(demoState.token);
t.deepEqual(saga.next(demoState).value, call(api.getAdminData, demoState.token));
-
- const adminPayload = mockApi.getAdminData();
t.deepEqual(saga.next(adminPayload).value, put(adminDataReceived(adminPayload)));
// Saga loops back to beginning
t.deepEqual(saga.next().value, take(GET_ADMIN_DATA));
});
-
-test.todo('Build a meaningful action around api failure.');
+test.todo('(Saga) watchGetAdminData - Write failure logic.');
diff --git a/src/routes/Home/components/LandingPage/LogisticsWizard/LogisticsWizard.jsx b/src/routes/Home/components/LandingPage/LogisticsWizard/LogisticsWizard.jsx
index 77ce10c..5c484df 100644
--- a/src/routes/Home/components/LandingPage/LogisticsWizard/LogisticsWizard.jsx
+++ b/src/routes/Home/components/LandingPage/LogisticsWizard/LogisticsWizard.jsx
@@ -9,8 +9,8 @@ export const LogisticsWizard = () => (
Logistics Wizard
A cognitive logistics solution that analyzes real-time data, provides
- intelligent recommendations, and presents your employees with a beautiful
- monitoring dashboard to help lead your supply chain management system into the future.
+ intelligent recommendations, and presents your employees with a beautiful
+ monitoring dashboard to help lead your supply chain management system into the future.
({
path: '/',
indexRoute: Home,
childRoutes: [
- CreateDemoRoute(store),
- DashboardRoute(store),
+ { component: CoreLayout,
+ childRoutes: [
+ CreateDemoRoute(store),
+ DashboardRoute(store),
+ ],
+ },
],
});
diff --git a/src/services/index.js b/src/services/index.js
index 05e0b3b..01f6004 100644
--- a/src/services/index.js
+++ b/src/services/index.js
@@ -1,7 +1,7 @@
-export const CONTROLLER_URL = __CONTROLLER_API__;
+export const controllerApi = `${__CONTROLLER_API__}/api/v1`;
export const callApi = (endpoint, {
- apiUrl = CONTROLLER_URL,
+ apiUrl = controllerApi,
headers = { 'Content-Type': 'application/json' },
method = 'GET',
body,
@@ -9,7 +9,7 @@ export const callApi = (endpoint, {
fetch(`${apiUrl}/${endpoint}`, {
headers,
method,
- body: body ? JSON.stringify(body) : undefined,
+ body: JSON.stringify(body),
})
.then(response => response.json().then(json => ({ json, response })))
.then(({ json, response }) => {
@@ -24,7 +24,7 @@ export const createDemo = (name, email) =>
body: { name, email },
});
-export const getDemo = (guid) => callApi(`demos/${guid}`);
+export const getDemo = guid => callApi(`demos/${guid}`);
export const login = (id, guid) =>
callApi(`demos/${guid}/login`, {
@@ -32,6 +32,8 @@ export const login = (id, guid) =>
body: { userId: id },
});
+export const getRetailers = guid => callApi(`demos/${guid}/retailers`);
+
export const getAdminData = token =>
callApi('admin', { headers: { Authorization: `Bearer ${token}` } });
@@ -39,6 +41,7 @@ export const api = {
createDemo,
getDemo,
login,
+ getRetailers,
getAdminData,
};
diff --git a/src/services/index.test.js b/src/services/index.test.js
index f5fa4f6..3aaa619 100644
--- a/src/services/index.test.js
+++ b/src/services/index.test.js
@@ -1,12 +1,13 @@
import test from 'ava';
import nock from 'nock';
import {
+ controllerApi,
createDemo,
getDemo,
login,
+ getRetailers,
getAdminData,
- CONTROLLER_URL,
-} from './';
+} from '../services';
test('(API) createDemo', function *(t) {
t.plan(2);
@@ -14,7 +15,7 @@ test('(API) createDemo', function *(t) {
const endpoint = '/demos';
const success = { id: 123, name: 'demo' };
- nock(CONTROLLER_URL)
+ nock(controllerApi)
.post(endpoint, {
name: 'demo',
email: 'email@company.com',
@@ -25,7 +26,7 @@ test('(API) createDemo', function *(t) {
t.deepEqual(response, success);
const fail = { message: 'Invalid email address' };
- nock(CONTROLLER_URL)
+ nock(controllerApi)
.post(endpoint, {
name: 'demo',
email: 'bademail',
@@ -47,7 +48,7 @@ test('(API) getDemo', function *(t) {
const endpoint = `/demos/${guid}`;
const success = { guid, id: 123, name: 'demo' };
- nock(CONTROLLER_URL)
+ nock(controllerApi)
.get(endpoint)
.reply(200, success);
@@ -55,7 +56,7 @@ test('(API) getDemo', function *(t) {
t.deepEqual(response, success);
const fail = { message: 'Demo does not exist' };
- nock(CONTROLLER_URL)
+ nock(controllerApi)
.get('/demos/1111')
.reply(404, fail);
@@ -76,7 +77,7 @@ test('(API) login', function *(t) {
const endpoint = `/demos/${guid}/login`;
const success = { token: 'logintoken' };
- nock(CONTROLLER_URL)
+ nock(controllerApi)
.post(endpoint, { userId })
.reply(200, success);
@@ -84,7 +85,7 @@ test('(API) login', function *(t) {
t.deepEqual(response, success);
const fail = { message: 'Demo or user does not exist' };
- nock(CONTROLLER_URL)
+ nock(controllerApi)
.post(endpoint, { userId })
.reply(404, fail);
@@ -97,6 +98,34 @@ test('(API) login', function *(t) {
}
});
+test('(API) getRetailers', function *(t) {
+ t.plan(2);
+
+ const guid = '1234';
+ const endpoint = `/demos/${guid}/retailers`;
+ const success = [1, 2, 3];
+
+ nock(controllerApi)
+ .get(endpoint)
+ .reply(200, success);
+
+ const response = yield getRetailers(guid);
+ t.deepEqual(response, success);
+
+ const fail = { message: 'Demo does not exist' };
+ nock(controllerApi)
+ .get(endpoint)
+ .reply(404, fail);
+
+ try {
+ yield getRetailers(guid);
+ }
+ catch (error) {
+ t.deepEqual(error, fail,
+ 'promise should fail with api message as the error');
+ }
+});
+
test('(API) getAdminData', function *(t) {
t.plan(2);
@@ -104,7 +133,7 @@ test('(API) getAdminData', function *(t) {
const endpoint = '/admin';
const success = { mockData: 'blahblah' };
- nock(CONTROLLER_URL)
+ nock(controllerApi)
.get(endpoint)
.reply(200, success);
@@ -112,7 +141,7 @@ test('(API) getAdminData', function *(t) {
t.deepEqual(response, success);
const fail = { message: 'Unauthorized' };
- nock(CONTROLLER_URL)
+ nock(controllerApi)
.get(endpoint)
.reply(401, fail);
diff --git a/src/services/mockApi.js b/src/services/mockApi.js
index 6277484..8adc96a 100644
--- a/src/services/mockApi.js
+++ b/src/services/mockApi.js
@@ -1,30 +1,49 @@
import faker from 'faker';
+export const getUser = (id, role = 'retailstoremanager') => ({
+ username: faker.name.firstName(),
+ email: faker.internet.email(),
+ created: null,
+ id,
+ demoId: faker.random.number(),
+ roles: [{
+ id: 1,
+ name: role,
+ description: null,
+ created: faker.date.recent(),
+ modified: faker.date.recent(),
+ }],
+});
+
export const getDemo = ({
+ name = faker.fake('{{lorem.word}} {{random.number}}'),
guid = faker.random.uuid(),
} = {}) => ({
createdAt: faker.date.recent(),
- name: faker.fake('{{lorem.word}} {{random.number}}'),
+ name,
guid,
id: faker.random.number(),
- users: [{
- username: faker.name.firstName(),
- email: faker.internet.email(),
- created: null,
- id: faker.random.number(),
- demoId: faker.random.number(),
- roles: [{
- id: 1,
- name: 'supplychainmanager',
- description: null,
- created: faker.date.recent(),
- modified: faker.date.recent(),
- }],
- }],
+ users: [
+ getUser(100, 'supplychainmanager'),
+ ],
});
-export const login = () => ({
- token: faker.random.uuid(),
+export const login = (token = faker.random.uuid()) => ({ token });
+
+export const getRetailer = ({
+ managerId = null,
+ state = faker.address.state(),
+ city = faker.address.city(),
+} = {}) => ({
+ managerId,
+ address: {
+ state,
+ city,
+ latitude: faker.address.latitude(),
+ country: faker.address.country(),
+ longitude: faker.address.longitude(),
+ },
+ id: 405,
});
export const getAdminData = () => ({
@@ -58,22 +77,14 @@ export const getAdminData = () => ({
toId: 405,
updatedAt: null,
}],
- retailers: [{
- managerId: null,
- address: {
- state: faker.address.state(),
- city: faker.address.city(),
- latitude: faker.address.latitude(),
- country: faker.address.country(),
- longitude: faker.address.longitude(),
- },
- id: 405,
- }],
+ retailers: [getRetailer()],
});
export const mockApi = {
+ getUser,
getDemo,
login,
+ getRetailer,
getAdminData,
};
diff --git a/src/styles/base/_colors.scss b/src/styles/base/_colors.scss
index 7b283d0..7e3e06e 100644
--- a/src/styles/base/_colors.scss
+++ b/src/styles/base/_colors.scss
@@ -1,12 +1,15 @@
-$primary1Color: #0F94a7;
+$primary1Color: rgba(15, 148, 167, 1);
+$primary2Color: rgba(72, 85, 102, 1);
+$primary3Color: rgba(184, 222, 228, 1);
+$accent1Color: rgba(145, 195, 131, 1);
+$alternateTextColor: rgba(255, 255, 255, 1);
+$borderColor: rgba(218, 221, 224, 1);
+$textColor: $primary2Color;
+
:export { primary1Color: $primary1Color; }
-$primary2Color: #485566;
:export { primary2Color: $primary2Color; }
-$accent1Color: #91C383;
+:export { primary3Color: $primary3Color; }
:export { accent1Color: $accent1Color; }
-$alternateTextColor: #FFFFFF;
:export { alternateTextColor: $alternateTextColor; }
-$borderColor: #DADDE0;
:export { borderColor: $borderColor; }
-$textColor: #485566;
:export { textColor: $textColor; }
diff --git a/src/styles/muiTheme.js b/src/styles/muiTheme.js
index 1dde9c0..c06d5da 100644
--- a/src/styles/muiTheme.js
+++ b/src/styles/muiTheme.js
@@ -4,18 +4,33 @@ import colors from './base/_colors.scss';
export const palette = {
primary1Color: colors.primary1Color,
primary2Color: colors.primary2Color,
+ primary3Color: colors.primary3Color,
accent1Color: colors.accent1Color,
+ // accent2Color: grey100,
+ // accent3Color: grey500,
+ textColor: colors.textColor,
+ // secondaryTextColor: fade(darkBlack, 0.54),
alternateTextColor: colors.alternateTextColor,
+ // canvasColor: white,
borderColor: colors.borderColor,
- textColor: colors.textColor,
+ // disabledColor: fade(darkBlack, 0.3),
+ // pickerHeaderColor: cyan500,
+ // clockCircleColor: fade(darkBlack, 0.07),
+ // shadowColor: fullBlack,
};
const muiTheme = getMuiTheme({
- fontFamily: 'Libre Franklin, sans-serif',
palette,
+ fontFamily: 'Libre Franklin, sans-serif',
raisedButton: {
border: '1px solid #FFF',
},
+ toolbar: {
+ titleFontSize: '1rem',
+ separatorColor: palette.primary3Color,
+ backgroundColor: palette.primary1Color,
+ iconColor: palette.alternateTextColor,
+ },
});
export default muiTheme;