diff --git a/Dockerfile b/Dockerfile index 88b5a40e2e356674b3875599e610feece9c8c5cc..d4c54e7bc25a8d80fbf948e73db3523cd524c7b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,6 @@ RUN [ "rm", "-rf", "/npm-packages-offline-cache"] WORKDIR ${HOME}/packages/xpub-faraday ENV NODE_ENV "production" -RUN echo $NODE_ENV RUN [ "npx", "pubsweet", "build"] diff --git a/docker-compose.yml b/docker-compose.yml index b4aa1dc1be2196cb8b1290417b8af9e915fac345..fd007f79c56accb3f01b7c7d21839d5ffdd12903 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,11 +5,11 @@ services: build: context: . dockerfile: ./Dockerfile-development - command: sh -c "cd packages/xpub-faraday && yarn install --frozen-lockfile && ./scripts/wait-for-it.sh postgres:5432 -s -t 40 -- npx pubsweet server" + command: sh -c "./scripts/wait-for-it.sh postgres:5432 -s -t 40 -- npx pubsweet server" ports: - ${PORT:-3000}:3000 volumes: - - ./:/home/xpub + - /home/xpub/node_modules depends_on: - postgres environment: diff --git a/packages/component-email-templating/package.json b/packages/component-email-templating/package.json index 4fee496276eb29c32a0a3399c91fc3b7cdc195fc..6213511389e1790492814e3306657251f4dc4209 100644 --- a/packages/component-email-templating/package.json +++ b/packages/component-email-templating/package.json @@ -15,11 +15,6 @@ "peerDependencies": { "@pubsweet/logger": "^0.0.1" }, - "repository": { - "type": "git", - "url": "https://gitlab.coko.foundation/pubsweet/pubsweet", - "path": "SendEmail" - }, "jest": { "verbose": true, "testRegex": "/tests/.*.test.js$" diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index c948890e60bff98dd99aeeeb1c9c7ee062e0b62c..d2f185a9fbddc740533870307d2d9b5653e9b59b 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -42,10 +42,10 @@ export const canInviteReviewers = (state, collection = {}) => { return false const { id: userId } = selectCurrentUser(state) - const isAdmin = currentUserIs(state, 'isAdmin') + const isAdminEiC = currentUserIs(state, 'adminEiC') const { isAccepted, id: heId } = get(collection, 'handlingEditor', {}) - return isAccepted && (userId === heId || isAdmin) + return isAccepted && (userId === heId || isAdminEiC) } const cannotViewReviewersDetails = [ @@ -71,6 +71,7 @@ const authorCanViewReportsDetailsStatuses = [ 'pendingApproval', 'rejected', 'accepted', + 'reviewCompleted', 'inQa', ] @@ -198,7 +199,12 @@ export const getHERecommendation = (state, collectionId, fragmentId) => { ) } -const canMakeDecisionStatuses = ['submitted', 'pendingApproval'] +const canMakeDecisionStatuses = [ + 'submitted', + 'pendingApproval', + 'underReview', + 'reviewCompleted', +] export const canMakeDecision = (state, collection = {}) => { const status = get(collection, 'status', 'draft') diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js index f0e5d8a3fd2392f511108c906a7861d73a73cec8..dc5314f613c215bb949c5735f25c395f83e8b5f6 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptEicDecision.js @@ -63,7 +63,7 @@ const ManuscriptEicDecision = ({ <Menu {...input} options={ - get(collection, 'status', 'submitted') === 'submitted' + get(collection, 'status', 'submitted') !== 'pendingApproval' ? [last(eicDecisions)] : eicDecisions } diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index fd01d44a1b4009c558fb8900d8991e9bc47f7b04..6d08c78bb1b32a5c536ab1b63b3a1807ab241799 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -146,19 +146,20 @@ const ManuscriptLayout = ({ /> )} - {get(currentUser, 'permissions.canReview', false) && ( - <ReviewerReportForm - changeForm={changeForm} - expanded={reviewerRecommendationExpanded} - formValues={get(formValues, 'reviewerReport', {})} - modalKey="reviewer-report" - project={collection} - review={pendingOwnRecommendation} - toggle={toggleReviewerRecommendations} - token={get(currentUser, 'token')} - version={fragment} - /> - )} + {isLatestVersion && + get(currentUser, 'permissions.canReview', false) && ( + <ReviewerReportForm + changeForm={changeForm} + expanded={reviewerRecommendationExpanded} + formValues={get(formValues, 'reviewerReport', {})} + modalKey="reviewer-report" + project={collection} + review={pendingOwnRecommendation} + toggle={toggleReviewerRecommendations} + token={get(currentUser, 'token')} + version={fragment} + /> + )} {get(currentUser, 'isInvitedHE', false) && ( <ResponseToInvitation @@ -200,6 +201,7 @@ const ManuscriptLayout = ({ getSignedUrl={getSignedUrl} highlight={ reviewerReports.length === 0 && + currentUser.handlingEditor && !cannotViewReviewersDetails.includes( get(collection, 'status', 'draft'), ) diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index 84d6059a9c3187ed2c5c5c648a1f4fe33af233a4..685daeab9d5d245ca9caa4f966bebe64aab6adf7 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -377,7 +377,10 @@ export default compose( this.props.toggleEditorialComments() } - if (get(fragment, 'responseToReviewers.content', false)) { + if ( + get(fragment, 'responseToReviewers.content', false) && + !editorialRecommendations.length + ) { this.props.toggleResponseToRevisionRequest() } }, diff --git a/packages/pubsweet-component-login/CHANGELOG.md b/packages/pubsweet-component-login/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..053b9a9ceb89f38e0ccc02a60386a493de816d58 --- /dev/null +++ b/packages/pubsweet-component-login/CHANGELOG.md @@ -0,0 +1,204 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +<a name="1.2.0"></a> +# [1.2.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.18...pubsweet-component-login@1.2.0) (2018-11-05) + + +### Features + +* GraphQL Login component ([70df3de](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/70df3de)) +* GraphQL Xpub submit component ([ba07060](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/ba07060)) + + + + +<a name="1.1.18"></a> +## [1.1.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.17...pubsweet-component-login@1.1.18) (2018-10-08) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.17"></a> +## [1.1.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.16...pubsweet-component-login@1.1.17) (2018-09-27) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.16"></a> +## [1.1.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.15...pubsweet-component-login@1.1.16) (2018-09-19) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.15"></a> +## [1.1.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.14...pubsweet-component-login@1.1.15) (2018-09-06) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.14"></a> +## [1.1.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.13...pubsweet-component-login@1.1.14) (2018-09-04) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.13"></a> +## [1.1.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.12...pubsweet-component-login@1.1.13) (2018-08-20) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.12"></a> +## [1.1.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.11...pubsweet-component-login@1.1.12) (2018-08-17) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.11"></a> +## [1.1.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.10...pubsweet-component-login@1.1.11) (2018-08-02) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.10"></a> +## [1.1.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.9...pubsweet-component-login@1.1.10) (2018-07-27) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.9"></a> +## [1.1.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.8...pubsweet-component-login@1.1.9) (2018-07-12) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.8"></a> +## [1.1.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.7...pubsweet-component-login@1.1.8) (2018-07-09) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.7"></a> +## [1.1.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.6...pubsweet-component-login@1.1.7) (2018-07-03) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.6"></a> +## [1.1.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.5...pubsweet-component-login@1.1.6) (2018-07-02) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.5"></a> +## [1.1.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.4...pubsweet-component-login@1.1.5) (2018-06-28) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.4"></a> +## [1.1.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.3...pubsweet-component-login@1.1.4) (2018-06-28) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.3"></a> +## [1.1.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.2...pubsweet-component-login@1.1.3) (2018-06-19) + + +### Bug Fixes + +* **pubsweet-ui:** tests are failing ([0e57798](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/0e57798)) + + + + +<a name="1.1.2"></a> +## [1.1.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.1...pubsweet-component-login@1.1.2) (2018-04-03) + + + + +**Note:** Version bump only for package pubsweet-component-login + +<a name="1.1.1"></a> +## [1.1.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.1.0...pubsweet-component-login@1.1.1) (2018-03-15) + + +### Bug Fixes + +* **login:** add missing recompose dependency ([a3b5a80](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/a3b5a80)), closes [#353](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/353) + + + + +<a name="1.1.0"></a> +# [1.1.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.0.1...pubsweet-component-login@1.1.0) (2018-03-05) + + +### Bug Fixes + +* **components:** login example ([6dfd66c](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/6dfd66c)) +* **components:** login tests were failing after refactor ([62be047](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/62be047)) +* **components:** signup and login error examples ([3f991ec](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3f991ec)) + + +### Features + +* **elife-theme:** add elife theme ([e406e0d](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/e406e0d)) + + + + +<a name="1.0.1"></a> + +## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@1.0.0...pubsweet-component-login@1.0.1) (2018-02-08) + +### Bug Fixes + +* **components:** update react-router-redux version to match client ([3d257ef](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3d257ef)) + +<a name="1.0.0"></a> + +# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-login@0.6.0...pubsweet-component-login@1.0.0) (2018-02-02) + +### Features + +* **client:** upgrade React to version 16 ([626cf59](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/626cf59)), closes [#65](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/65) + +### BREAKING CHANGES + +* **client:** Upgrade React to version 16 diff --git a/packages/pubsweet-component-login/Login.jsx b/packages/pubsweet-component-login/Login.jsx new file mode 100644 index 0000000000000000000000000000000000000000..6cc5a2d8597fa53e053e4c5615f2502093e17280 --- /dev/null +++ b/packages/pubsweet-component-login/Login.jsx @@ -0,0 +1,70 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { Field } from 'formik' +import { isEmpty } from 'lodash' +import { + CenteredColumn, + ErrorText, + H1, + Link, + Button, + TextField, +} from '@pubsweet/ui' +import styled from 'styled-components' + +// These enable tests to select components +const Signup = styled.div`` +const ResetPassword = styled.div`` + +const UsernameInput = props => <TextField label="Username" {...props.field} /> +const PasswordInput = props => ( + <TextField label="Password" {...props.field} type="password" /> +) + +const Login = ({ + errors, + handleSubmit, + signup = true, + passwordReset = true, +}) => ( + <CenteredColumn small> + <H1>Login</H1> + + {!isEmpty(errors) && <ErrorText>{errors}</ErrorText>} + <form onSubmit={handleSubmit}> + <Field component={UsernameInput} name="username" /> + <Field component={PasswordInput} name="password" /> + <Button primary type="submit"> + Login + </Button> + </form> + + {signup && ( + <Signup> + <span>Don't have an account? </span> + <Link to="/signup">Sign up</Link> + </Signup> + )} + + {passwordReset && ( + <ResetPassword> + <span>Forgot your password? </span> + <Link to="/password-reset">Reset password</Link> + </ResetPassword> + )} + </CenteredColumn> +) + +Login.propTypes = { + error: PropTypes.string, + actions: PropTypes.object, + location: PropTypes.object, + signup: PropTypes.bool, + passwordReset: PropTypes.bool, +} + +// used by tests +export { Login, ErrorText, Signup, ResetPassword } + +// used by consumers +export default Login diff --git a/packages/pubsweet-component-login/Login.md b/packages/pubsweet-component-login/Login.md new file mode 100644 index 0000000000000000000000000000000000000000..8efa765dd57c83028d3d4e6d9885797bac8245b7 --- /dev/null +++ b/packages/pubsweet-component-login/Login.md @@ -0,0 +1,40 @@ +A login form + +```js +const { withFormik } = require('formik') + +const LoginForm = withFormik({ + initialValues: { + username: '', + password: '', + }, + mapPropsToValues: props => ({ + username: props.username, + password: props.password, + }), + displayName: 'login', + handleSubmit: val => console.log(val), +})(Login) +;<LoginForm /> +``` + +Which can have an error message: + +```js +const { withFormik } = require('formik') + +const LoginForm = withFormik({ + initialValues: { + username: '', + password: '', + }, + mapPropsToValues: props => ({ + username: props.username, + password: props.password, + }), + displayName: 'login', + handleSubmit: (values, { setErrors }) => + setErrors('Wrong username or password.'), +})(Login) +;<LoginForm /> +``` diff --git a/packages/pubsweet-component-login/Login.test.jsx b/packages/pubsweet-component-login/Login.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f696b5eaa420beee8fb6fdf3311461b1ed28f97a --- /dev/null +++ b/packages/pubsweet-component-login/Login.test.jsx @@ -0,0 +1,38 @@ +import { shallow } from 'enzyme' +import React from 'react' + +import { Login, ErrorText, Signup, ResetPassword } from './Login' + +describe('<Login/>', () => { + const makeWrapper = (props = {}) => shallow(<Login {...props} />) + + it('renders the login form', () => { + expect(makeWrapper()).toMatchSnapshot() + }) + + it('shows error', () => { + const wrapper = makeWrapper({ errors: 'Yikes!' }) + expect(wrapper.find(ErrorText)).toHaveLength(1) + }) + + it('can hide sign up link', () => { + const wrapper1 = makeWrapper() + const wrapper2 = makeWrapper({ signup: false }) + expect(wrapper1.find(Signup)).toHaveLength(1) + expect(wrapper2.find(Signup)).toHaveLength(0) + }) + + it('can hide password reset link', () => { + const wrapper1 = makeWrapper() + const wrapper2 = makeWrapper({ passwordReset: false }) + expect(wrapper1.find(ResetPassword)).toHaveLength(1) + expect(wrapper2.find(ResetPassword)).toHaveLength(0) + }) + + it('triggers submit handler', () => { + const handleSubmit = jest.fn() + const wrapper = makeWrapper({ handleSubmit }) + wrapper.find('form').simulate('submit') + expect(handleSubmit).toHaveBeenCalled() + }) +}) diff --git a/packages/pubsweet-component-login/LoginContainer.js b/packages/pubsweet-component-login/LoginContainer.js new file mode 100644 index 0000000000000000000000000000000000000000..250dd2d75b9adeb1686805b4a9d8ecd7d8b4291b --- /dev/null +++ b/packages/pubsweet-component-login/LoginContainer.js @@ -0,0 +1,26 @@ +import { withFormik } from 'formik' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import { loginUser } from './actions' + +import Login from './Login' +import redirectPath from './redirect' + +const handleSubmit = (values, { props: { dispatch, location }, setErrors }) => { + dispatch(loginUser(values, redirectPath({ location }), setErrors)) +} + +const enhancedFormik = withFormik({ + initialValues: { + username: '', + password: '', + }, + mapPropsToValues: props => ({ + username: props.username, + password: props.password, + }), + displayName: 'login', + handleSubmit, +})(Login) + +export default compose(connect(state => state))(enhancedFormik) diff --git a/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap b/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap new file mode 100644 index 0000000000000000000000000000000000000000..c37d2096241bcb88782ac550b9bd8c8f121e1e52 --- /dev/null +++ b/packages/pubsweet-component-login/__snapshots__/Login.test.jsx.snap @@ -0,0 +1,473 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Login/> renders the login form 1`] = ` +ShallowWrapper { + Symbol(enzyme.__root__): [Circular], + Symbol(enzyme.__unrendered__): <Login />, + Symbol(enzyme.__renderer__): Object { + "batchedUpdates": [Function], + "getNode": [Function], + "render": [Function], + "simulateError": [Function], + "simulateEvent": [Function], + "unmount": [Function], + }, + Symbol(enzyme.__node__): Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": Array [ + <styled.h1> + Login + </styled.h1>, + false, + <form> + <C + component={[Function]} + name="username" + /> + <C + component={[Function]} + name="password" + /> + <styled.button + primary={true} + type="submit" + > + Login + </styled.button> + </form>, + <styled.div> + <span> + Don't have an account? + </span> + <Styled(Link) + to="/signup" + > + Sign up + </Styled(Link)> + </styled.div>, + <styled.div> + <span> + Forgot your password? + </span> + <Styled(Link) + to="/password-reset" + > + Reset password + </Styled(Link)> + </styled.div>, + ], + "small": true, + }, + "ref": null, + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": "Login", + }, + "ref": null, + "rendered": "Login", + "type": [Function], + }, + false, + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "children": Array [ + <C + component={[Function]} + name="username" + />, + <C + component={[Function]} + name="password" + />, + <styled.button + primary={true} + type="submit" + > + Login + </styled.button>, + ], + "onSubmit": undefined, + }, + "ref": null, + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "component": [Function], + "name": "username", + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "component": [Function], + "name": "password", + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": "Login", + "primary": true, + "type": "submit", + }, + "ref": null, + "rendered": "Login", + "type": [Function], + }, + ], + "type": "form", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": Array [ + <span> + Don't have an account? + </span>, + <Styled(Link) + to="/signup" + > + Sign up + </Styled(Link)>, + ], + }, + "ref": null, + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "children": "Don't have an account? ", + }, + "ref": null, + "rendered": "Don't have an account? ", + "type": "span", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": "Sign up", + "to": "/signup", + }, + "ref": null, + "rendered": "Sign up", + "type": [Function], + }, + ], + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": Array [ + <span> + Forgot your password? + </span>, + <Styled(Link) + to="/password-reset" + > + Reset password + </Styled(Link)>, + ], + }, + "ref": null, + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "children": "Forgot your password? ", + }, + "ref": null, + "rendered": "Forgot your password? ", + "type": "span", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": "Reset password", + "to": "/password-reset", + }, + "ref": null, + "rendered": "Reset password", + "type": [Function], + }, + ], + "type": [Function], + }, + ], + "type": [Function], + }, + Symbol(enzyme.__nodes__): Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": Array [ + <styled.h1> + Login + </styled.h1>, + false, + <form> + <C + component={[Function]} + name="username" + /> + <C + component={[Function]} + name="password" + /> + <styled.button + primary={true} + type="submit" + > + Login + </styled.button> + </form>, + <styled.div> + <span> + Don't have an account? + </span> + <Styled(Link) + to="/signup" + > + Sign up + </Styled(Link)> + </styled.div>, + <styled.div> + <span> + Forgot your password? + </span> + <Styled(Link) + to="/password-reset" + > + Reset password + </Styled(Link)> + </styled.div>, + ], + "small": true, + }, + "ref": null, + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": "Login", + }, + "ref": null, + "rendered": "Login", + "type": [Function], + }, + false, + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "children": Array [ + <C + component={[Function]} + name="username" + />, + <C + component={[Function]} + name="password" + />, + <styled.button + primary={true} + type="submit" + > + Login + </styled.button>, + ], + "onSubmit": undefined, + }, + "ref": null, + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "component": [Function], + "name": "username", + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "component": [Function], + "name": "password", + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": "Login", + "primary": true, + "type": "submit", + }, + "ref": null, + "rendered": "Login", + "type": [Function], + }, + ], + "type": "form", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": Array [ + <span> + Don't have an account? + </span>, + <Styled(Link) + to="/signup" + > + Sign up + </Styled(Link)>, + ], + }, + "ref": null, + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "children": "Don't have an account? ", + }, + "ref": null, + "rendered": "Don't have an account? ", + "type": "span", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": "Sign up", + "to": "/signup", + }, + "ref": null, + "rendered": "Sign up", + "type": [Function], + }, + ], + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": Array [ + <span> + Forgot your password? + </span>, + <Styled(Link) + to="/password-reset" + > + Reset password + </Styled(Link)>, + ], + }, + "ref": null, + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "children": "Forgot your password? ", + }, + "ref": null, + "rendered": "Forgot your password? ", + "type": "span", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "class", + "props": Object { + "children": "Reset password", + "to": "/password-reset", + }, + "ref": null, + "rendered": "Reset password", + "type": [Function], + }, + ], + "type": [Function], + }, + ], + "type": [Function], + }, + ], + Symbol(enzyme.__options__): Object { + "adapter": ReactSixteenAdapter { + "options": Object { + "enableComponentDidUpdateOnSetState": true, + "lifecycles": Object { + "componentDidUpdate": Object { + "onSetState": true, + }, + "getDerivedStateFromProps": true, + "getSnapshotBeforeUpdate": true, + "setState": Object { + "skipsComponentDidUpdateOnNullish": true, + }, + }, + }, + }, + }, +} +`; diff --git a/packages/pubsweet-component-login/actions.js b/packages/pubsweet-component-login/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..e74909742d021bb2f66d46674e968b5a3eed2919 --- /dev/null +++ b/packages/pubsweet-component-login/actions.js @@ -0,0 +1,84 @@ +import * as api from 'pubsweet-client/src/helpers/api' +import { + LOGIN_REQUEST, + LOGIN_SUCCESS, + LOGIN_FAILURE, + LOGOUT_SUCCESS, + LOGOUT_REQUEST, +} from 'pubsweet-client/src/actions/types' + +import { push } from 'react-router-redux' + +// TODO: This will break when rendered on a server +const localStorage = window.localStorage || undefined + +// There are three possible states for our login +// process and we need actions for each of them +function loginRequest(credentials) { + return { + type: LOGIN_REQUEST, + credentials, + } +} + +function loginSuccess(user) { + return { + type: LOGIN_SUCCESS, + token: user.token, + user, + } +} + +function loginFailure(message) { + return { + type: LOGIN_FAILURE, + error: message, + } +} + +// Calls the API to get a token and +// dispatches actions along the way +export function loginUser(credentials, redirectTo, setErrors) { + return dispatch => { + dispatch(loginRequest(credentials)) + return api.create('/users/authenticate', credentials).then( + user => { + localStorage.setItem('token', user.token) + dispatch(loginSuccess(user)) + if (redirectTo) dispatch(push(redirectTo)) + }, + err => { + setErrors(JSON.parse(err.response).message) + dispatch(loginFailure(err)) + }, + ) + } +} + +function logoutRequest() { + return { + type: LOGOUT_REQUEST, + isFetching: true, + isAuthenticated: true, + } +} + +function logoutSuccess() { + return { + type: LOGOUT_SUCCESS, + isFetching: false, + isAuthenticated: false, + } +} + +// Logs the user out +// Since we are using JWTs, we just need to remove the token +// from localStorage. +export function logoutUser(redirectTo) { + return dispatch => { + dispatch(logoutRequest()) + localStorage.removeItem('token') + dispatch(logoutSuccess()) + if (redirectTo) dispatch(push(redirectTo)) + } +} diff --git a/packages/pubsweet-component-login/graphql/LoginContainer.js b/packages/pubsweet-component-login/graphql/LoginContainer.js new file mode 100644 index 0000000000000000000000000000000000000000..83715d8019cbd8eb24bf086d9b7ebdb733daf80e --- /dev/null +++ b/packages/pubsweet-component-login/graphql/LoginContainer.js @@ -0,0 +1,43 @@ +import { compose } from 'recompose' +import { withFormik } from 'formik' +import { graphql } from 'react-apollo' + +import mutations from './mutations' +import Login from '../Login' +import redirectPath from '../redirect' + +const localStorage = window.localStorage || undefined + +const handleSubmit = (values, { props, setSubmitting, setErrors }) => + props + .loginUser({ variables: { input: values } }) + .then(({ data, errors }) => { + if (!errors) { + localStorage.setItem('token', data.loginUser.token) + props.history.push(redirectPath({ location: props.location })) + setSubmitting(true) + } + }) + .catch(e => { + if (e.graphQLErrors) { + setSubmitting(false) + setErrors(e.graphQLErrors[0].message) + } + }) + +const enhancedFormik = withFormik({ + initialValues: { + username: '', + password: '', + }, + mapPropsToValues: props => ({ + username: props.username, + password: props.password, + }), + displayName: 'login', + handleSubmit, +})(Login) + +export default compose(graphql(mutations.LOGIN_USER, { name: 'loginUser' }))( + enhancedFormik, +) diff --git a/packages/pubsweet-component-login/graphql/LoginContainer.test.jsx b/packages/pubsweet-component-login/graphql/LoginContainer.test.jsx new file mode 100644 index 0000000000000000000000000000000000000000..7df5518babca59528ec37fa596338e687f192365 --- /dev/null +++ b/packages/pubsweet-component-login/graphql/LoginContainer.test.jsx @@ -0,0 +1,144 @@ +import React from 'react' +import { mount } from 'enzyme' +import { MockedProvider } from 'react-apollo/test-utils' +import { ThemeProvider } from 'styled-components' +import { MemoryRouter, Route } from 'react-router-dom' + +import wait from 'waait' +import { LOGIN_USER } from './mutations' +import LoginContainer from './LoginContainer' + +const user1 = { + id: 'user1', + username: 'admin', + password: 'adminadmin', + admin: true, + teams: [], +} + +const mocks = currentUser => [ + { + request: { + query: LOGIN_USER, + variables: { + input: { + username: currentUser.username, + password: currentUser.password, + }, + }, + }, + result: { + data: { + loginUser: { + user: currentUser, + token: 'greatToken', + }, + }, + }, + }, + { + request: { + query: LOGIN_USER, + variables: { + input: { + username: currentUser.username, + password: 'wrongPassword', + }, + }, + }, + result: { + data: { loginUser: null }, + errors: [{ message: 'Wrong username or password.' }], + }, + }, +] + +let globalLocation + +function makeDeepWrapper(currentUser, props = {}) { + // A theme is needed because some components use colors + // specified in the theme to render themselves (e.g. Link) + const theme = { + colorPrimary: '#fff', + colorSecondary: '#fff', + } + + return mount( + <ThemeProvider theme={theme}> + <MockedProvider addTypename={false} mocks={mocks(currentUser)}> + <MemoryRouter initialEntries={['/login']}> + <Route + {...props} + render={p => { + globalLocation = p.location + return <LoginContainer {...p} /> + }} + /> + </MemoryRouter> + </MockedProvider> + </ThemeProvider>, + ) +} + +describe('LoginContainer', () => { + beforeEach(() => { + window.localStorage.clear() + globalLocation = undefined + }) + + it('renders the login form', () => { + const wrapper = makeDeepWrapper(user1) + wrapper.update() + + const fields = wrapper.find('Login') + expect(fields).toHaveLength(1) + }) + + it('submits login information and logs the user in', async () => { + const wrapper = makeDeepWrapper(user1) + wrapper.update() + + const usernameField = wrapper.find('TextField[label="Username"] input') + usernameField.getDOMNode().value = user1.username + usernameField.simulate('change') + const passwordField = wrapper.find('TextField[label="Password"] input') + passwordField.getDOMNode().value = user1.password + passwordField.simulate('change') + + wrapper.update() + const button = wrapper.find('button') + button.simulate('submit') + + wrapper.update() + await wait(50) + + expect(window.localStorage.token).toEqual('greatToken') + expect(globalLocation.pathname).toEqual('/testRedirect') + }) + + it('does not log in user with incorrect credentials', async () => { + const wrapper = makeDeepWrapper(user1) + wrapper.update() + + const usernameField = wrapper.find('TextField[label="Username"] input') + usernameField.getDOMNode().value = user1.username + usernameField.simulate('change') + const passwordField = wrapper.find('TextField[label="Password"] input') + passwordField.getDOMNode().value = 'wrongPassword' + passwordField.simulate('change') + + wrapper.update() + const button = wrapper.find('button') + + button.simulate('submit') + + wrapper.update() + await wait(50) + + expect(wrapper.find('Login').text()).toContain( + 'Wrong username or password.', + ) + expect(window.localStorage.token).toEqual(undefined) + expect(globalLocation.pathname).toEqual('/login') + }) +}) diff --git a/packages/pubsweet-component-login/graphql/mutations/index.js b/packages/pubsweet-component-login/graphql/mutations/index.js new file mode 100644 index 0000000000000000000000000000000000000000..ee44b493da283da233533820994539bc15529084 --- /dev/null +++ b/packages/pubsweet-component-login/graphql/mutations/index.js @@ -0,0 +1,13 @@ +import gql from 'graphql-tag' + +const LOGIN_USER = gql` + mutation($input: LoginUserInput) { + loginUser(input: $input) { + token + } + } +` + +module.exports = { + LOGIN_USER, +} diff --git a/packages/pubsweet-component-login/index.js b/packages/pubsweet-component-login/index.js new file mode 100644 index 0000000000000000000000000000000000000000..155554e77ad3dfe5c27ec6c2d4b5b623dcb3d135 --- /dev/null +++ b/packages/pubsweet-component-login/index.js @@ -0,0 +1,7 @@ +module.exports = { + frontend: { + components: [() => require('./LoginContainer')], + actions: () => require('./actions'), + reducers: () => require('./reducers'), + }, +} diff --git a/packages/pubsweet-component-login/package.json b/packages/pubsweet-component-login/package.json new file mode 100644 index 0000000000000000000000000000000000000000..66896d9c43c7e256d5522d2e692e2d92e58a2654 --- /dev/null +++ b/packages/pubsweet-component-login/package.json @@ -0,0 +1,26 @@ +{ + "name": "pubsweet-component-login", + "version": "1.2.0", + "description": "Basic login component for PubSweet", + "main": "index.js", + "author": "Collaborative Knowledge Foundation", + "license": "MIT", + "dependencies": { + "@pubsweet/ui": "^8.8.0", + "formik": "1.3.0", + "prop-types": "^15.5.10", + "react-redux": "^5.0.6", + "react-router-dom": "^4.2.2", + "react-router-redux": "^5.0.0-alpha.9", + "recompose": "^0.26.0" + }, + "peerDependencies": { + "pubsweet-client": ">=1.0.0", + "react": ">=15" + }, + "repository": { + "type": "git", + "url": "https://gitlab.coko.foundation/pubsweet/pubsweet", + "path": "Login" + } +} diff --git a/packages/pubsweet-component-login/redirect.js b/packages/pubsweet-component-login/redirect.js new file mode 100644 index 0000000000000000000000000000000000000000..53d58f1e3e4d1fe5a5d53584048d7a389c340887 --- /dev/null +++ b/packages/pubsweet-component-login/redirect.js @@ -0,0 +1,10 @@ +import { get } from 'lodash' +import config from 'config' + +const allowedRedirect = pathname => + !['/logout', '/login', '/signup'].includes(pathname) + +export default ({ location: { state } }) => + state && state.from && allowedRedirect(state.from.pathname) + ? state.from.pathname + : get(config, 'pubsweet-client.login-redirect', '/') diff --git a/packages/pubsweet-component-login/reducers.js b/packages/pubsweet-component-login/reducers.js new file mode 100644 index 0000000000000000000000000000000000000000..d1f637a01e310491d842ac045d78ac1a0a879527 --- /dev/null +++ b/packages/pubsweet-component-login/reducers.js @@ -0,0 +1,58 @@ +import { + LOGIN_REQUEST, + LOGIN_SUCCESS, + LOGIN_FAILURE, + LOGOUT_SUCCESS, + LOGOUT_REQUEST, +} from 'pubsweet-client/src/actions/types' + +// TODO: This will break when rendered on a server +const localStorage = window.localStorage || undefined + +export default function userLogin( + state = { + isFetching: false, + isAuthenticated: false, + token: localStorage.getItem('token'), + }, + action, +) { + switch (action.type) { + case LOGIN_REQUEST: + return { + ...state, + isFetching: true, + isAuthenticated: false, + username: action.credentials.username, + } + case LOGIN_SUCCESS: + return { + ...state, + isFetching: false, + isAuthenticated: true, + user: action.user, + token: action.token, + } + case LOGIN_FAILURE: + return { + ...state, + isFetching: false, + isAuthenticated: false, + error: action.error, + } + case LOGOUT_SUCCESS: + return { + ...state, + isFetching: false, + isAuthenticated: false, + } + case LOGOUT_REQUEST: + return { + ...state, + isFetching: false, + isAuthenticated: false, + } + default: + return state + } +} diff --git a/packages/pubsweet-component-login/reducers.test.js b/packages/pubsweet-component-login/reducers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a3733cf35bff6ae0ac033e66fdec726ce557d509 --- /dev/null +++ b/packages/pubsweet-component-login/reducers.test.js @@ -0,0 +1,73 @@ +import { + LOGIN_FAILURE, + LOGIN_REQUEST, + LOGIN_SUCCESS, + LOGOUT_REQUEST, + LOGOUT_SUCCESS, +} from 'pubsweet-client/src/actions/types' + +jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => undefined) + +const reducer = require('./reducers').default + +describe('Login reducer', () => { + it('returns initial state', () => { + const newState = reducer(undefined, {}) + expect(newState).toEqual({ + isFetching: false, + isAuthenticated: false, + token: undefined, + }) + }) + + it('stores username on login request', () => { + const action = { + type: LOGIN_REQUEST, + credentials: { username: 'milo minderbinder' }, + } + const newState = reducer(undefined, action) + expect(newState).toMatchObject({ + isFetching: true, + username: 'milo minderbinder', + }) + }) + + it('stores user and token on login success', () => { + const action = { + type: LOGIN_SUCCESS, + user: { username: 'nurse duckett' }, + token: 't0k3n', + } + const newState = reducer(undefined, action) + expect(newState).toMatchObject({ + isAuthenticated: true, + user: action.user, + token: action.token, + }) + }) + + it('stores error on login failure', () => { + const action = { type: LOGIN_FAILURE, error: new Error('Flies in eyes') } + const newState = reducer({ isAuthenticated: true }, action) + expect(newState).toMatchObject({ + isAuthenticated: false, + error: action.error, + }) + }) + + it('logs out on request', () => { + const action = { type: LOGOUT_REQUEST } + const newState = reducer({ isAuthenticated: true }, action) + expect(newState).toMatchObject({ + isAuthenticated: false, + }) + }) + + it('logs out on logout success', () => { + const action = { type: LOGOUT_SUCCESS } + const newState = reducer({ isAuthenticated: true }, action) + expect(newState).toMatchObject({ + isAuthenticated: false, + }) + }) +}) diff --git a/packages/pubsweet-component-login/types.js b/packages/pubsweet-component-login/types.js new file mode 100644 index 0000000000000000000000000000000000000000..3b66211c7193bd42a3e8aa9f3c63b46a57172a43 --- /dev/null +++ b/packages/pubsweet-component-login/types.js @@ -0,0 +1,7 @@ +export const LOGIN_REQUEST = 'LOGIN_REQUEST' +export const LOGIN_SUCCESS = 'LOGIN_SUCCESS' +export const LOGIN_FAILURE = 'LOGIN_FAILURE' + +export const LOGOUT_REQUEST = 'LOGOUT_REQUEST' +export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS' +export const LOGOUT_FAILURE = 'LOGOUT_FAILURE' diff --git a/packages/pubsweet-component-signup/CHANGELOG.md b/packages/pubsweet-component-signup/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..6e2faa066f967fc6027e4223c8bef79a09ba016b --- /dev/null +++ b/packages/pubsweet-component-signup/CHANGELOG.md @@ -0,0 +1,278 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +<a name="1.0.33"></a> +## [1.0.33](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.32...pubsweet-component-signup@1.0.33) (2018-11-05) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.32"></a> +## [1.0.32](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.31...pubsweet-component-signup@1.0.32) (2018-10-08) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.31"></a> +## [1.0.31](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.30...pubsweet-component-signup@1.0.31) (2018-09-27) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.30"></a> +## [1.0.30](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.29...pubsweet-component-signup@1.0.30) (2018-09-19) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.29"></a> +## [1.0.29](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.28...pubsweet-component-signup@1.0.29) (2018-09-06) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.28"></a> +## [1.0.28](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.27...pubsweet-component-signup@1.0.28) (2018-09-04) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.27"></a> +## [1.0.27](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.26...pubsweet-component-signup@1.0.27) (2018-08-20) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.26"></a> +## [1.0.26](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.25...pubsweet-component-signup@1.0.26) (2018-08-17) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.25"></a> +## [1.0.25](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.24...pubsweet-component-signup@1.0.25) (2018-08-02) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.24"></a> +## [1.0.24](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.23...pubsweet-component-signup@1.0.24) (2018-07-27) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.23"></a> +## [1.0.23](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.22...pubsweet-component-signup@1.0.23) (2018-07-12) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.22"></a> +## [1.0.22](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.21...pubsweet-component-signup@1.0.22) (2018-07-09) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.21"></a> +## [1.0.21](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.20...pubsweet-component-signup@1.0.21) (2018-07-03) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.20"></a> +## [1.0.20](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.19...pubsweet-component-signup@1.0.20) (2018-07-02) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.19"></a> +## [1.0.19](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.18...pubsweet-component-signup@1.0.19) (2018-06-28) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.18"></a> +## [1.0.18](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.17...pubsweet-component-signup@1.0.18) (2018-06-28) + + +### Bug Fixes + +* **monorepo:** fix versions of ui across repo ([72ada07](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/72ada07)) + + + + +<a name="1.0.17"></a> +## [1.0.17](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.16...pubsweet-component-signup@1.0.17) (2018-05-21) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.16"></a> +## [1.0.16](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.15...pubsweet-component-signup@1.0.16) (2018-05-18) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.15"></a> +## [1.0.15](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.14...pubsweet-component-signup@1.0.15) (2018-05-10) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.14"></a> +## [1.0.14](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.13...pubsweet-component-signup@1.0.14) (2018-05-09) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.13"></a> +## [1.0.13](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.12...pubsweet-component-signup@1.0.13) (2018-05-03) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.12"></a> +## [1.0.12](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.11...pubsweet-component-signup@1.0.12) (2018-04-24) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.11"></a> +## [1.0.11](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.10...pubsweet-component-signup@1.0.11) (2018-04-11) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.10"></a> +## [1.0.10](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.9...pubsweet-component-signup@1.0.10) (2018-04-03) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.9"></a> +## [1.0.9](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.8...pubsweet-component-signup@1.0.9) (2018-03-30) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.8"></a> +## [1.0.8](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.7...pubsweet-component-signup@1.0.8) (2018-03-28) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.7"></a> +## [1.0.7](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.6...pubsweet-component-signup@1.0.7) (2018-03-27) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.6"></a> +## [1.0.6](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.5...pubsweet-component-signup@1.0.6) (2018-03-19) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.5"></a> +## [1.0.5](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.4...pubsweet-component-signup@1.0.5) (2018-03-15) + + + + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.4"></a> + +## [1.0.4](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.3...pubsweet-component-signup@1.0.4) (2018-03-09) + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.3"></a> + +## [1.0.3](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.2...pubsweet-component-signup@1.0.3) (2018-03-06) + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.2"></a> + +## [1.0.2](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.1...pubsweet-component-signup@1.0.2) (2018-03-05) + +### Bug Fixes + +* **components:** add dependency on pubsweet/ui ([f0a1926](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/f0a1926)) +* **components:** signup and login error examples ([3f991ec](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/3f991ec)) + +<a name="1.0.1"></a> + +## [1.0.1](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@1.0.0...pubsweet-component-signup@1.0.1) (2018-02-16) + +**Note:** Version bump only for package pubsweet-component-signup + +<a name="1.0.0"></a> + +# [1.0.0](https://gitlab.coko.foundation/pubsweet/pubsweet/compare/pubsweet-component-signup@0.5.0...pubsweet-component-signup@1.0.0) (2018-02-02) + +### Features + +* **client:** upgrade React to version 16 ([626cf59](https://gitlab.coko.foundation/pubsweet/pubsweet/commit/626cf59)), closes [#65](https://gitlab.coko.foundation/pubsweet/pubsweet/issues/65) + +### BREAKING CHANGES + +* **client:** Upgrade React to version 16 diff --git a/packages/pubsweet-component-signup/Signup.jsx b/packages/pubsweet-component-signup/Signup.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e6c5bc700d0f2415cecf50ec3f8201d9042dcb28 --- /dev/null +++ b/packages/pubsweet-component-signup/Signup.jsx @@ -0,0 +1,42 @@ +import React from 'react' +import { Field } from 'redux-form' +import { + CenteredColumn, + Link, + H1, + ErrorText, + Button, + TextField, +} from '@pubsweet/ui' + +const UsernameInput = props => <TextField label="Username" {...props.input} /> +const EmailInput = props => ( + <TextField label="Email" {...props.input} type="email" /> +) +const PasswordInput = props => ( + <TextField label="Password" {...props.input} type="password" /> +) + +const Signup = ({ error, handleSubmit }) => ( + <CenteredColumn small> + <H1>Sign up</H1> + + {error && <ErrorText>{error}</ErrorText>} + + <form onSubmit={handleSubmit}> + <Field component={UsernameInput} name="username" /> + <Field component={EmailInput} name="email" /> + <Field component={PasswordInput} name="password" /> + <Button primary type="submit"> + Sign up + </Button> + </form> + + <div> + <span>Already have an account? </span> + <Link to="/login">Login</Link> + </div> + </CenteredColumn> +) + +export default Signup diff --git a/packages/pubsweet-component-signup/Signup.md b/packages/pubsweet-component-signup/Signup.md new file mode 100644 index 0000000000000000000000000000000000000000..6ba7e53aba41eb4e195bf18eeeb1a3197de0f3d0 --- /dev/null +++ b/packages/pubsweet-component-signup/Signup.md @@ -0,0 +1,26 @@ +A login form + +```js +const { reduxForm } = require('redux-form') + +const SignupForm = reduxForm({ + form: 'signup', + onChange: values => console.log(values), +})(Signup) +;<SignupForm /> +``` + +Which can have an error message: + +```js +const { reduxForm, SubmissionError } = require('redux-form') + +const SignupForm = reduxForm({ + form: 'signup-error', + onSubmit: val => { + console.log(val) + return Promise.reject(new SubmissionError({ _error: 'Error message' })) + }, +})(Signup) +;<SignupForm /> +``` diff --git a/packages/pubsweet-component-signup/SignupContainer.js b/packages/pubsweet-component-signup/SignupContainer.js new file mode 100644 index 0000000000000000000000000000000000000000..a1cd2947b2b339ac0de9dd67703f5d9f2c5ca64a --- /dev/null +++ b/packages/pubsweet-component-signup/SignupContainer.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import { reduxForm } from 'redux-form' + +import { signupUser } from './actions' +import Signup from './Signup' + +const onSubmit = (values, dispatch) => { + dispatch(signupUser(values)) +} + +export default compose( + reduxForm({ + form: 'signup', + onSubmit, + }), + connect(state => ({ + error: state.error, + })), +)(Signup) diff --git a/packages/pubsweet-component-signup/actions.js b/packages/pubsweet-component-signup/actions.js new file mode 100644 index 0000000000000000000000000000000000000000..3c47ea703cb29ba051eaaf04d9bf5e744d2e6b29 --- /dev/null +++ b/packages/pubsweet-component-signup/actions.js @@ -0,0 +1,36 @@ +import { push } from 'react-router-redux' +import * as api from 'pubsweet-client/src/helpers/api' +import * as T from './types' + +function signupRequest() { + return { + type: T.SIGNUP_REQUEST, + } +} + +function signupSuccess(user) { + return { + type: T.SIGNUP_SUCCESS, + user, + } +} + +function signupFailure(message) { + return { + type: T.SIGNUP_FAILURE, + error: message, + } +} + +export function signupUser(user) { + return dispatch => { + dispatch(signupRequest()) + return api.create('/users', user).then( + user => { + dispatch(signupSuccess(user)) + dispatch(push('/login')) + }, + err => dispatch(signupFailure(err)), + ) + } +} diff --git a/packages/pubsweet-component-signup/index.js b/packages/pubsweet-component-signup/index.js new file mode 100644 index 0000000000000000000000000000000000000000..97d462d9b9beb987d214e03505412ba1e7389ef5 --- /dev/null +++ b/packages/pubsweet-component-signup/index.js @@ -0,0 +1,7 @@ +module.exports = { + frontend: { + components: [() => require('./SignupContainer')], + actions: () => require('./actions'), + reducers: () => require('./reducers'), + }, +} diff --git a/packages/pubsweet-component-signup/package.json b/packages/pubsweet-component-signup/package.json new file mode 100644 index 0000000000000000000000000000000000000000..40c23e43373c1e2f500ccb787ca3b06126574663 --- /dev/null +++ b/packages/pubsweet-component-signup/package.json @@ -0,0 +1,25 @@ +{ + "name": "pubsweet-component-signup", + "version": "1.0.33", + "description": "Basic signup form component for PubSweet", + "main": "index.js", + "author": "Collaborative Knowledge Foundation", + "license": "MIT", + "dependencies": { + "@pubsweet/ui": "^8.8.0", + "prop-types": "^15.5.10", + "react-bootstrap": "^0.32.0", + "react-redux": "^5.0.6", + "react-router": "^4.2.0", + "redux": "^3.7.2" + }, + "peerDependencies": { + "pubsweet-client": ">=1.0.0", + "react": ">=16" + }, + "repository": { + "type": "git", + "url": "https://gitlab.coko.foundation/pubsweet/pubsweet", + "path": "Signup" + } +} diff --git a/packages/pubsweet-component-signup/reducers.js b/packages/pubsweet-component-signup/reducers.js new file mode 100644 index 0000000000000000000000000000000000000000..3b1268c9e07d333e59170867cf9dd481df7dd64d --- /dev/null +++ b/packages/pubsweet-component-signup/reducers.js @@ -0,0 +1,21 @@ +import { SIGNUP_SUCCESS } from './types' + +export default function userSignup( + state = { + isFetching: false, + isAuthenticated: false, + }, + action, +) { + switch (action.type) { + case SIGNUP_SUCCESS: + return { + ...state, + isFetching: false, + isAuthenticated: true, + user: action.user, + } + default: + return state + } +} diff --git a/packages/pubsweet-component-signup/types.js b/packages/pubsweet-component-signup/types.js new file mode 100644 index 0000000000000000000000000000000000000000..8cea7b558eb965d7997e7abb8fa05c42429942d3 --- /dev/null +++ b/packages/pubsweet-component-signup/types.js @@ -0,0 +1,5 @@ +// Action types + +export const SIGNUP_REQUEST = 'SIGNUP_REQUEST' +export const SIGNUP_SUCCESS = 'SIGNUP_SUCCESS' +export const SIGNUP_FAILURE = 'SIGNUP_FAILURE' diff --git a/packages/xpub-faraday/config/components.json b/packages/xpub-faraday/config/components.json index b6f94243de5cfd48a8c828c21cda98ddfef5a6b4..01db85a3f575c70a8ad95fd3625ef8383ba1d27d 100644 --- a/packages/xpub-faraday/config/components.json +++ b/packages/xpub-faraday/config/components.json @@ -1,6 +1,6 @@ [ - "pubsweet-component-signup", "pubsweet-component-login", + "pubsweet-component-signup", "pubsweet-component-wizard", "pubsweet-component-modal", "pubsweet-components-faraday", diff --git a/packages/xpub-faraday/package.json b/packages/xpub-faraday/package.json index b764cfc8cf4f6be99e1b433106621f44f04e4f6b..3f7c75d27236aafd60a30c2d22b6ce2fbfbc3760 100644 --- a/packages/xpub-faraday/package.json +++ b/packages/xpub-faraday/package.json @@ -10,6 +10,7 @@ "dependencies": { "@pubsweet/component-aws-s3": "^1.2.0", "@pubsweet/component-send-email": "0.2.4", + "@pubsweet/component-email-templating": "0.0.1", "@pubsweet/styleguide": "3.1.4", "@pubsweet/ui": "^8.6.0", "@pubsweet/ui-toolkit": "latest", @@ -29,7 +30,7 @@ "pubsweet": "3.0.6", "pubsweet-client": "^4.0.4", "pubsweet-component-login": "^1.2.0", - "pubsweet-component-signup": "^1.0.0", + "pubsweet-component-signup": "^1.0.33", "pubsweet-server": "10.0.1", "react": "^16.4.2", "react-dnd": "^2.5.4", @@ -81,7 +82,8 @@ "start": "pubsweet start", "start:services": "docker-compose up postgres", "server": "pubsweet server", - "start-now": "echo $secret > config/local-development.json && npm run server", + "start-now": + "echo $secret > config/local-development.json && npm run server", "build": "NODE_ENV=production pubsweet build", "clean": "rm -rf node_modules", "debug": "pgrep -f startup/start.js | xargs kill -sigusr1", diff --git a/yarn.lock b/yarn.lock index d518be423a9302e25dd44b161213a68fb535e779..e3c5febd641a145da11845c9776b9ae98481342e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9297,10 +9297,11 @@ pubsweet-component-login@^1.2.0: react-router-redux "^5.0.0-alpha.9" recompose "^0.26.0" -pubsweet-component-signup@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/pubsweet-component-signup/-/pubsweet-component-signup-1.0.1.tgz#f18f3a18940c440615af41bc11a155eff91c9b2d" +pubsweet-component-signup@^1.0.33: + version "1.0.33" + resolved "https://registry.yarnpkg.com/pubsweet-component-signup/-/pubsweet-component-signup-1.0.33.tgz#a7696cb43b309a0901588736e228c5acef153e62" dependencies: + "@pubsweet/ui" "^8.8.0" prop-types "^15.5.10" react-bootstrap "^0.32.0" react-redux "^5.0.6"