diff --git a/frontend/package.json b/frontend/package.json index b94ee9ac..41c2703f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,8 +21,10 @@ "dependencies": { "constant-case": "^2.0.0", "express": "^4.14.0", + "force-array": "^3.1.0", "inherits": "^2.0.3", "locale": "^0.1.0", + "lodash.find": "^4.6.0", "lodash.isempty": "^4.4.0", "lodash.template": "^4.4.0", "lodash.uniq": "^4.5.0", @@ -43,6 +45,7 @@ "redux-logger": "^2.7.4", "redux-promise-middleware": "^4.2.0", "redux-thunk": "^2.1.0", + "reselect": "^2.5.4", "st": "^1.2.0", "styled-components": "^1.1.3", "url-loader": "^0.5.7" diff --git a/frontend/src/containers/dashboard/index.js b/frontend/src/containers/dashboard/index.js deleted file mode 100644 index 9c4c7f2b..00000000 --- a/frontend/src/containers/dashboard/index.js +++ /dev/null @@ -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 ( -
-

- -

- - - - -
- ); -}; - -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); diff --git a/frontend/src/containers/dashboard/projects.js b/frontend/src/containers/dashboard/projects.js deleted file mode 100644 index 033bd8b8..00000000 --- a/frontend/src/containers/dashboard/projects.js +++ /dev/null @@ -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); diff --git a/frontend/src/containers/dashboard/settings.js b/frontend/src/containers/dashboard/settings.js deleted file mode 100644 index 058aad75..00000000 --- a/frontend/src/containers/dashboard/settings.js +++ /dev/null @@ -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 ( - - - - - - ); -}; - -Settings.propTypes = { - Settings: React.PropTypes.array -}; - -const mapStateToProps = (state) => ({ - Settings: state.session.data.Settings -}); - -module.exports = connect(mapStateToProps)(Settings); diff --git a/frontend/src/containers/home/index.js b/frontend/src/containers/home/index.js index 266cbef8..0ea7e3eb 100644 --- a/frontend/src/containers/home/index.js +++ b/frontend/src/containers/home/index.js @@ -1,19 +1,16 @@ const React = require('react'); -const ReactIntl = require('react-intl'); const ReactRedux = require('react-redux'); const ReactRouter = require('react-router'); const Styled = require('styled-components'); +const selectors = require('@state/selectors'); + const Container = require('@ui/components/container'); -const Dashboard = require('@containers/dashboard'); const Li = require('@ui/components/horizontal-list/li'); const Org = require('@containers/org'); const Redirect = require('@components/redirect'); const Ul = require('@ui/components/horizontal-list/ul'); - -const { - FormattedMessage -} = ReactIntl; +const NotFound = require('@containers/not-found'); const { connect @@ -29,14 +26,16 @@ const { default: styled } = Styled; +const { + orgsSelector +} = selectors; + const StyledNav = styled.div` background-color: #f2f2f2; `; const Home = ({ - orgs = [], - pathname = '/', - user = {} + orgs = [] }) => { const navLinks = orgs.map(({ id, @@ -53,47 +52,40 @@ const Home = ({ ); }); + const notFound = !orgs.length + ? + : Redirect(`/${orgs[0].id}`); + return (
    -
  • - - - -
  • {navLinks}
- - - + +
); }; Home.propTypes = { - orgs: React.PropTypes.arrayOf(React.PropTypes.shape({ - id: React.PropTypes.string, - name: React.PropTypes.string - })), - pathname: React.PropTypes.string, - user: React.PropTypes.shape({ - id: React.PropTypes.string, - name: React.PropTypes.string - }) + orgs: React.PropTypes.arrayOf( + React.PropTypes.shape({ + owner: React.PropTypes.string, + uuid: React.PropTypes.string, + id: React.PropTypes.string, + name: React.PropTypes.string + }) + ) }; const mapStateToProps = (state) => ({ - orgs: state.session.data.orgs, - user: { - id: state.session.data.name, - name: state.session.data.name - } + orgs: orgsSelector(state) }); module.exports = connect(mapStateToProps)(Home); diff --git a/frontend/src/containers/org/index.js b/frontend/src/containers/org/index.js index ebdeded3..588a5b4b 100644 --- a/frontend/src/containers/org/index.js +++ b/frontend/src/containers/org/index.js @@ -4,15 +4,19 @@ const ReactIntl = require('react-intl'); const ReactRedux = require('react-redux'); const ReactRouter = require('react-router'); +const selectors = require('@state/selectors'); + const H1 = require('@ui/components/h1'); const Li = require('@ui/components/horizontal-list/li'); const Ul = require('@ui/components/horizontal-list/ul'); - const NotFound = require('@containers/not-found'); +const Redirect = require('@components/redirect'); -const People = require('./people'); -const Projects = require('./projects'); -const Settings = require('./settings'); +const SectionComponents = { + people: require('./people'), + projects: require('./projects'), + settings: require('./settings'), +}; const { FormattedMessage @@ -25,75 +29,71 @@ const { const { Link, Match, - Miss, - Redirect + Miss } = ReactRouter; +const { + orgByIdSelector, + orgSectionsSelector +} = selectors; + const Org = ({ org = {}, - params = {}, - user = {} + sections = [] }) => { - if (user.id === params.org) { - return null; - } - if (isEmpty(org)) { return ( ); } + const navLinks = sections.map((name) => ( +
  • + + + +
  • + )); + + const navMatches = sections.map((name) => ( + + )); + + const missMatch = !sections.length ? null : ( + + ); + return (

    {org.name}

      -
    • - - - -
    • -
    • - - - -
    • -
    • - - - -
    • + {navLinks}
    - - - - + {navMatches} + {missMatch}
    ); }; Org.propTypes = { org: React.PropTypes.shape({ + owner: React.PropTypes.string, + uuid: React.PropTypes.string, id: React.PropTypes.string, name: React.PropTypes.string }), - params: React.PropTypes.shape({ - org: React.PropTypes.string - }), - user: React.PropTypes.shape({ - id: React.PropTypes.string, - name: React.PropTypes.string - }) + sections: React.PropTypes.arrayOf( + React.PropTypes.string + ) }; const mapStateToProps = (state, ownProps) => ({ - org: state.session.data.orgs.filter((org) => { - return org.id === ownProps.params.org; - }).pop(), - user: { - id: state.session.data.name, - name: state.session.data.name - } + org: orgByIdSelector(ownProps.params.org)(state), + sections: orgSectionsSelector(ownProps.params.org)(state) }); module.exports = connect(mapStateToProps)(Org); diff --git a/frontend/src/containers/org/projects.js b/frontend/src/containers/org/projects.js index f26adbd9..77e4b431 100644 --- a/frontend/src/containers/org/projects.js +++ b/frontend/src/containers/org/projects.js @@ -1,17 +1,18 @@ -const ReactRedux = require('react-redux'); - const Projects = require('@components/projects'); +const ReactRedux = require('react-redux'); +const selectors = require('@state/selectors'); const { connect } = ReactRedux; -const mapStateToProps = (state, ownProps) => { - return { - projects: (state.session.data.orgs.filter((org) => { - return org.id === ownProps.params.org; - }).pop() || {}).projects - }; -}; +const { + projectsByOrgIdSelector +} = selectors; + + +const mapStateToProps = (state, ownProps) => ({ + projects: projectsByOrgIdSelector(ownProps.params.org)(state) +}); module.exports = connect(mapStateToProps)(Projects); diff --git a/frontend/src/root.js b/frontend/src/root.js index b31938d5..3d7f17fa 100644 --- a/frontend/src/root.js +++ b/frontend/src/root.js @@ -24,32 +24,57 @@ const { } = ReactRouter; const store = Store({ - session: { - data: { + projects: { + data: [{ + uuid: 'e0ea0c02-55cc-45fe-8064-3e5176a59401', + org: 'e12ad7db-91b2-4154-83dd-40dcfc700dcc', + id: 'forest-foundation-dev', + name: 'Forest Foundation Dev', + plan: '20.05$ per day' + }, { + uuid: '9fcb374d-a267-4c2a-9d9c-ba469b804639', + org: 'e12ad7db-91b2-4154-83dd-40dcfc700dcc', + id: 'forest-foundation-testing', + name: 'Forest Foundation Testing', + plan: '20.05$ per day' + }, { + uuid: 'ac2c2498-e865-4ee3-9e26-8c75a81cbe25', + org: 'e12ad7db-91b2-4154-83dd-40dcfc700dcc', + id: 'forest-foundation-production', + name: 'Forest Foundation Production', + plan: '100.17$ per day' + }] + }, + orgs: { + ui: { + sections: [ + 'projects', + 'people', + 'settings' + ] + }, + data: [{ + hide: ['people'], + owner: 'b94033c1-3665-4c36-afab-d9c3d0b51c01', id: 'nicola', - name: 'Nicola', - projects: [], - orgs: [{ - id: 'biz-tech', - name: 'BizTech', - pinned: true, - projects: [{ - id: 'forest-foundation-dev', - name: 'Forest Foundation Dev', - plan: '20.05$ per day' - }, { - id: 'forest-foundation-testing', - name: 'Forest Foundation Testing', - plan: '20.05$ per day' - }, { - id: 'forest-foundation-production', - name: 'Forest Foundation Production', - plan: '100.17$ per day' - }], - }, { - id: 'make-us-proud', - name: 'Make Us Proud', - }] + 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', + name: 'Make Us Proud', + }] + }, + account: { + data: { + uuid: 'b94033c1-3665-4c36-afab-d9c3d0b51c01', + id: 'nicola', + name: 'Nicola' } } }); diff --git a/frontend/src/state/reducers/session.js b/frontend/src/state/reducers/account.js similarity index 100% rename from frontend/src/state/reducers/session.js rename to frontend/src/state/reducers/account.js diff --git a/frontend/src/state/reducers/index.js b/frontend/src/state/reducers/index.js index deb52fc5..05960668 100644 --- a/frontend/src/state/reducers/index.js +++ b/frontend/src/state/reducers/index.js @@ -6,8 +6,10 @@ const { module.exports = () => { return combineReducers({ + account: require('@state/reducers/account'), app: require('@state/reducers/app'), intl: require('@state/reducers/intl'), - session: require('@state/reducers/session') + orgs: require('@state/reducers/orgs'), + projects: require('@state/reducers/projects') }); }; diff --git a/frontend/src/state/reducers/orgs.js b/frontend/src/state/reducers/orgs.js new file mode 100644 index 00000000..7fec06fb --- /dev/null +++ b/frontend/src/state/reducers/orgs.js @@ -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 +}, {}); diff --git a/frontend/src/state/reducers/projects.js b/frontend/src/state/reducers/projects.js new file mode 100644 index 00000000..7fec06fb --- /dev/null +++ b/frontend/src/state/reducers/projects.js @@ -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 +}, {}); diff --git a/frontend/src/state/selectors.js b/frontend/src/state/selectors.js new file mode 100644 index 00000000..a71cf761 --- /dev/null +++ b/frontend/src/state/selectors.js @@ -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 +}; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1c996fe3..caac1c72 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2480,6 +2480,12 @@ for-own@^0.1.4: dependencies: 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: version "2.0.5" 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" 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: version "0.2.1" 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" 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: version "4.4.0" 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" 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: version "1.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-1.0.0.tgz#4eaeea41ed040d1702457df64a42b2b07d246f9f"