re-structure state

- use reselect
 - flatten it
 - fixes #115 #111 #106
This commit is contained in:
Sérgio Ramos 2016-12-16 12:28:27 +00:00
parent 1049ed88b6
commit c6dd90a0e5
14 changed files with 207 additions and 239 deletions

View File

@ -21,8 +21,10 @@
"dependencies": { "dependencies": {
"constant-case": "^2.0.0", "constant-case": "^2.0.0",
"express": "^4.14.0", "express": "^4.14.0",
"force-array": "^3.1.0",
"inherits": "^2.0.3", "inherits": "^2.0.3",
"locale": "^0.1.0", "locale": "^0.1.0",
"lodash.find": "^4.6.0",
"lodash.isempty": "^4.4.0", "lodash.isempty": "^4.4.0",
"lodash.template": "^4.4.0", "lodash.template": "^4.4.0",
"lodash.uniq": "^4.5.0", "lodash.uniq": "^4.5.0",
@ -43,6 +45,7 @@
"redux-logger": "^2.7.4", "redux-logger": "^2.7.4",
"redux-promise-middleware": "^4.2.0", "redux-promise-middleware": "^4.2.0",
"redux-thunk": "^2.1.0", "redux-thunk": "^2.1.0",
"reselect": "^2.5.4",
"st": "^1.2.0", "st": "^1.2.0",
"styled-components": "^1.1.3", "styled-components": "^1.1.3",
"url-loader": "^0.5.7" "url-loader": "^0.5.7"

View File

@ -1,72 +0,0 @@
const React = require('react');
const ReactIntl = require('react-intl');
const ReactRedux = require('react-redux');
const ReactRouter = require('react-router');
const H1 = require('@ui/components/h1');
const Li = require('@ui/components/horizontal-list/li');
const Redirect = require('@components/redirect');
const Ul = require('@ui/components/horizontal-list/ul');
const Projects = require('./projects');
const Settings = require('./settings');
const {
FormattedMessage
} = ReactIntl;
const {
connect
} = ReactRedux;
const {
Link,
Match,
Miss
} = ReactRouter;
const Dashboard = ({
pathname = '',
user = {}
}) => {
return (
<div>
<H1>
<FormattedMessage id='your-dashboard' />
</H1>
<Ul>
<Li>
<Link activeClassName='active' to={`/${user.id}/projects`}>
<FormattedMessage id='projects' />
</Link>
</Li>
<Li>
<Link activeClassName='active' to={`/${user.id}/settings`}>
<FormattedMessage id='settings' />
</Link>
</Li>
</Ul>
<Match component={Projects} pattern={`/${user.id}/projects`} />
<Match component={Settings} pattern={`/${user.id}/settings`} />
<Miss component={Redirect(`/${user.id}/projects`)} />
</div>
);
};
Dashboard.propTypes = {
pathname: React.PropTypes.string,
user: React.PropTypes.shape({
id: React.PropTypes.string,
name: React.PropTypes.string
})
};
const mapStateToProps = (state) => ({
projects: state.session.data.projects,
user: {
id: state.session.data.name,
name: state.session.data.name
}
});
module.exports = connect(mapStateToProps)(Dashboard);

View File

@ -1,13 +0,0 @@
const ReactRedux = require('react-redux');
const Projects = require('@components/projects');
const {
connect
} = ReactRedux;
const mapStateToProps = (state) => ({
projects: state.session.data.projects
});
module.exports = connect(mapStateToProps)(Projects);

View File

@ -1,43 +0,0 @@
const React = require('react');
const ReactIntl = require('react-intl');
const ReactRedux = require('react-redux');
// const ReactRouter = require('react-router');
//
const Column = require('@ui/components/column');
// const Button = require('@ui/components/button');
const Row = require('@ui/components/row');
const {
FormattedMessage
} = ReactIntl;
const {
connect
} = ReactRedux;
// const {
// Link,
// Match,
// Miss,
// Redirect
// } = ReactRouter;
const Settings = () => {
return (
<Row>
<Column xs={12}>
<FormattedMessage id='your-settings' />
</Column>
</Row>
);
};
Settings.propTypes = {
Settings: React.PropTypes.array
};
const mapStateToProps = (state) => ({
Settings: state.session.data.Settings
});
module.exports = connect(mapStateToProps)(Settings);

View File

@ -1,19 +1,16 @@
const React = require('react'); const React = require('react');
const ReactIntl = require('react-intl');
const ReactRedux = require('react-redux'); const ReactRedux = require('react-redux');
const ReactRouter = require('react-router'); const ReactRouter = require('react-router');
const Styled = require('styled-components'); const Styled = require('styled-components');
const selectors = require('@state/selectors');
const Container = require('@ui/components/container'); const Container = require('@ui/components/container');
const Dashboard = require('@containers/dashboard');
const Li = require('@ui/components/horizontal-list/li'); const Li = require('@ui/components/horizontal-list/li');
const Org = require('@containers/org'); const Org = require('@containers/org');
const Redirect = require('@components/redirect'); const Redirect = require('@components/redirect');
const Ul = require('@ui/components/horizontal-list/ul'); const Ul = require('@ui/components/horizontal-list/ul');
const NotFound = require('@containers/not-found');
const {
FormattedMessage
} = ReactIntl;
const { const {
connect connect
@ -29,14 +26,16 @@ const {
default: styled default: styled
} = Styled; } = Styled;
const {
orgsSelector
} = selectors;
const StyledNav = styled.div` const StyledNav = styled.div`
background-color: #f2f2f2; background-color: #f2f2f2;
`; `;
const Home = ({ const Home = ({
orgs = [], orgs = []
pathname = '/',
user = {}
}) => { }) => {
const navLinks = orgs.map(({ const navLinks = orgs.map(({
id, id,
@ -53,47 +52,40 @@ const Home = ({
); );
}); });
const notFound = !orgs.length
? <NotFound />
: Redirect(`/${orgs[0].id}`);
return ( return (
<div> <div>
<StyledNav> <StyledNav>
<Container> <Container>
<Ul> <Ul>
<Li key={pathname}>
<Link activeClassName='active' to={`/${user.id}`}>
<FormattedMessage id='your-dashboard' />
</Link>
</Li>
{navLinks} {navLinks}
</Ul> </Ul>
</Container> </Container>
</StyledNav> </StyledNav>
<Container> <Container>
<Match component={Dashboard} pattern={`/${user.id}/:section?`} /> <Match component={Org} pattern='/:org/:section?' />
<Match component={Org} pattern='/:org' /> <Miss component={notFound} />
<Miss component={Redirect(`/${user.id}`)} />
</Container> </Container>
</div> </div>
); );
}; };
Home.propTypes = { Home.propTypes = {
orgs: React.PropTypes.arrayOf(React.PropTypes.shape({ orgs: React.PropTypes.arrayOf(
id: React.PropTypes.string, React.PropTypes.shape({
name: React.PropTypes.string owner: React.PropTypes.string,
})), uuid: React.PropTypes.string,
pathname: React.PropTypes.string,
user: React.PropTypes.shape({
id: React.PropTypes.string, id: React.PropTypes.string,
name: React.PropTypes.string name: React.PropTypes.string
}) })
)
}; };
const mapStateToProps = (state) => ({ const mapStateToProps = (state) => ({
orgs: state.session.data.orgs, orgs: orgsSelector(state)
user: {
id: state.session.data.name,
name: state.session.data.name
}
}); });
module.exports = connect(mapStateToProps)(Home); module.exports = connect(mapStateToProps)(Home);

View File

@ -4,15 +4,19 @@ const ReactIntl = require('react-intl');
const ReactRedux = require('react-redux'); const ReactRedux = require('react-redux');
const ReactRouter = require('react-router'); const ReactRouter = require('react-router');
const selectors = require('@state/selectors');
const H1 = require('@ui/components/h1'); const H1 = require('@ui/components/h1');
const Li = require('@ui/components/horizontal-list/li'); const Li = require('@ui/components/horizontal-list/li');
const Ul = require('@ui/components/horizontal-list/ul'); const Ul = require('@ui/components/horizontal-list/ul');
const NotFound = require('@containers/not-found'); const NotFound = require('@containers/not-found');
const Redirect = require('@components/redirect');
const People = require('./people'); const SectionComponents = {
const Projects = require('./projects'); people: require('./people'),
const Settings = require('./settings'); projects: require('./projects'),
settings: require('./settings'),
};
const { const {
FormattedMessage FormattedMessage
@ -25,75 +29,71 @@ const {
const { const {
Link, Link,
Match, Match,
Miss, Miss
Redirect
} = ReactRouter; } = ReactRouter;
const {
orgByIdSelector,
orgSectionsSelector
} = selectors;
const Org = ({ const Org = ({
org = {}, org = {},
params = {}, sections = []
user = {}
}) => { }) => {
if (user.id === params.org) {
return null;
}
if (isEmpty(org)) { if (isEmpty(org)) {
return ( return (
<NotFound /> <NotFound />
); );
} }
const navLinks = sections.map((name) => (
<Li key={name}>
<Link activeClassName='active' to={`/${org.id}/${name}`}>
<FormattedMessage id={name} />
</Link>
</Li>
));
const navMatches = sections.map((name) => (
<Match
component={SectionComponents[name]}
key={name}
pattern={`/:org/${name}`}
/>
));
const missMatch = !sections.length ? null : (
<Miss component={Redirect(`/${org.id}/${sections[0]}`)} />
);
return ( return (
<div> <div>
<H1>{org.name}</H1> <H1>{org.name}</H1>
<Ul> <Ul>
<Li> {navLinks}
<Link activeClassName='active' to={`/${org.id}/projects`}>
<FormattedMessage id='projects' />
</Link>
</Li>
<Li>
<Link activeClassName='active' to={`/${org.id}/people`}>
<FormattedMessage id='people' />
</Link>
</Li>
<Li>
<Link activeClassName='active' to={`/${org.id}/settings`}>
<FormattedMessage id='settings' />
</Link>
</Li>
</Ul> </Ul>
<Match component={Projects} pattern='/:org/projects' /> {navMatches}
<Match component={People} pattern='/:org/people' /> {missMatch}
<Match component={Settings} pattern='/:org/settings' />
<Miss component={Redirect(`/${org.id}/projects`)} />
</div> </div>
); );
}; };
Org.propTypes = { Org.propTypes = {
org: React.PropTypes.shape({ org: React.PropTypes.shape({
owner: React.PropTypes.string,
uuid: React.PropTypes.string,
id: React.PropTypes.string, id: React.PropTypes.string,
name: React.PropTypes.string name: React.PropTypes.string
}), }),
params: React.PropTypes.shape({ sections: React.PropTypes.arrayOf(
org: React.PropTypes.string React.PropTypes.string
}), )
user: React.PropTypes.shape({
id: React.PropTypes.string,
name: React.PropTypes.string
})
}; };
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
org: state.session.data.orgs.filter((org) => { org: orgByIdSelector(ownProps.params.org)(state),
return org.id === ownProps.params.org; sections: orgSectionsSelector(ownProps.params.org)(state)
}).pop(),
user: {
id: state.session.data.name,
name: state.session.data.name
}
}); });
module.exports = connect(mapStateToProps)(Org); module.exports = connect(mapStateToProps)(Org);

View File

@ -1,17 +1,18 @@
const ReactRedux = require('react-redux');
const Projects = require('@components/projects'); const Projects = require('@components/projects');
const ReactRedux = require('react-redux');
const selectors = require('@state/selectors');
const { const {
connect connect
} = ReactRedux; } = ReactRedux;
const mapStateToProps = (state, ownProps) => { const {
return { projectsByOrgIdSelector
projects: (state.session.data.orgs.filter((org) => { } = selectors;
return org.id === ownProps.params.org;
}).pop() || {}).projects
}; const mapStateToProps = (state, ownProps) => ({
}; projects: projectsByOrgIdSelector(ownProps.params.org)(state)
});
module.exports = connect(mapStateToProps)(Projects); module.exports = connect(mapStateToProps)(Projects);

View File

@ -24,32 +24,57 @@ const {
} = ReactRouter; } = ReactRouter;
const store = Store({ const store = Store({
session: { projects: {
data: { data: [{
id: 'nicola', uuid: 'e0ea0c02-55cc-45fe-8064-3e5176a59401',
name: 'Nicola', org: 'e12ad7db-91b2-4154-83dd-40dcfc700dcc',
projects: [],
orgs: [{
id: 'biz-tech',
name: 'BizTech',
pinned: true,
projects: [{
id: 'forest-foundation-dev', id: 'forest-foundation-dev',
name: 'Forest Foundation Dev', name: 'Forest Foundation Dev',
plan: '20.05$ per day' plan: '20.05$ per day'
}, { }, {
uuid: '9fcb374d-a267-4c2a-9d9c-ba469b804639',
org: 'e12ad7db-91b2-4154-83dd-40dcfc700dcc',
id: 'forest-foundation-testing', id: 'forest-foundation-testing',
name: 'Forest Foundation Testing', name: 'Forest Foundation Testing',
plan: '20.05$ per day' plan: '20.05$ per day'
}, { }, {
uuid: 'ac2c2498-e865-4ee3-9e26-8c75a81cbe25',
org: 'e12ad7db-91b2-4154-83dd-40dcfc700dcc',
id: 'forest-foundation-production', id: 'forest-foundation-production',
name: 'Forest Foundation Production', name: 'Forest Foundation Production',
plan: '100.17$ per day' plan: '100.17$ per day'
}], }]
},
orgs: {
ui: {
sections: [
'projects',
'people',
'settings'
]
},
data: [{
hide: ['people'],
owner: 'b94033c1-3665-4c36-afab-d9c3d0b51c01',
id: 'nicola',
name: 'Your Dashboard'
}, { }, {
owner: 'b94033c1-3665-4c36-afab-d9c3d0b51c01',
uuid: 'e12ad7db-91b2-4154-83dd-40dcfc700dcc',
id: 'biz-tech',
name: 'BizTech'
}, {
owner: 'b94033c1-3665-4c36-afab-d9c3d0b51c01',
uuid: '551f316d-e414-480f-9787-b4c408db3edd',
id: 'make-us-proud', id: 'make-us-proud',
name: 'Make Us Proud', name: 'Make Us Proud',
}] }]
},
account: {
data: {
uuid: 'b94033c1-3665-4c36-afab-d9c3d0b51c01',
id: 'nicola',
name: 'Nicola'
} }
} }
}); });

View File

@ -6,8 +6,10 @@ const {
module.exports = () => { module.exports = () => {
return combineReducers({ return combineReducers({
account: require('@state/reducers/account'),
app: require('@state/reducers/app'), app: require('@state/reducers/app'),
intl: require('@state/reducers/intl'), intl: require('@state/reducers/intl'),
session: require('@state/reducers/session') orgs: require('@state/reducers/orgs'),
projects: require('@state/reducers/projects')
}); });
}; };

View File

@ -0,0 +1,9 @@
const ReduxActions = require('redux-actions');
const {
handleActions
} = ReduxActions;
module.exports = handleActions({
'x': (state) => state // somehow handleActions needs at least one reducer
}, {});

View File

@ -0,0 +1,9 @@
const ReduxActions = require('redux-actions');
const {
handleActions
} = ReduxActions;
module.exports = handleActions({
'x': (state) => state // somehow handleActions needs at least one reducer
}, {});

View File

@ -0,0 +1,37 @@
const find = require('lodash.find');
const forceArray = require('force-array');
const reselect = require('reselect');
const {
createSelector
} = reselect;
const account = (state) => state.account.data;
const orgUiSections = (state) => state.orgs.ui.sections;
const orgs = (state) => state.orgs.data;
const projects = (state) => state.projects.data;
const orgById = (id) => createSelector(
orgs,
(orgs) => find(orgs, ['id', id])
);
const projectsByOrgId = (orgId) => createSelector(
[projects, orgById(orgId)],
(projects, org) => projects.filter((p) => p.org === org.uuid)
);
const orgSections = (orgId) => createSelector(
[orgById(orgId), orgUiSections],
(org, sections) => sections.filter(
(section) => forceArray(org.hide).indexOf(section) < 0
)
);
module.exports = {
accountSelector: account,
orgByIdSelector: orgById,
orgsSelector: orgs,
orgSectionsSelector: orgSections,
projectsByOrgIdSelector: projectsByOrgId
};

View File

@ -2480,6 +2480,12 @@ for-own@^0.1.4:
dependencies: dependencies:
for-in "^0.1.5" for-in "^0.1.5"
force-array:
version "3.1.0"
resolved "https://registry.yarnpkg.com/force-array/-/force-array-3.1.0.tgz#a060f6d4188dc7daa6fe562df39aeaabca404784"
dependencies:
is-array "^1.0.1"
foreach@^2.0.5: foreach@^2.0.5:
version "2.0.5" version "2.0.5"
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
@ -2961,6 +2967,10 @@ irregular-plurals@^1.0.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.2.0.tgz#38f299834ba8c00c30be9c554e137269752ff3ac" resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.2.0.tgz#38f299834ba8c00c30be9c554e137269752ff3ac"
is-array@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-array/-/is-array-1.0.1.tgz#e9850cc2cc860c3bc0977e84ccf0dd464584279a"
is-arrayish@^0.2.1: is-arrayish@^0.2.1:
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@ -3434,6 +3444,10 @@ lodash.filter@^4.4.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace" resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
lodash.find:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
lodash.flatten@^4.2.0: lodash.flatten@^4.2.0:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
@ -4742,6 +4756,10 @@ requires-port@1.0.x, requires-port@1.x.x:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
reselect:
version "2.5.4"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-2.5.4.tgz#b7d23fdf00b83fa7ad0279546f8dbbbd765c7047"
resolve-cwd@^1.0.0: resolve-cwd@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-1.0.0.tgz#4eaeea41ed040d1702457df64a42b2b07d246f9f" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-1.0.0.tgz#4eaeea41ed040d1702457df64a42b2b07d246f9f"