diff --git a/packages/components-faraday/src/components/Admin/AddEditUser.js b/packages/components-faraday/src/components/Admin/AddEditUser.js index 2b76bde79831c4296b5bfd4c555fa5c178c538e7..80e7f45e7c3afc77c4c12c75be94696e261dccc0 100644 --- a/packages/components-faraday/src/components/Admin/AddEditUser.js +++ b/packages/components-faraday/src/components/Admin/AddEditUser.js @@ -58,6 +58,7 @@ const AddEditUser = ({ error={error} journal={journal} roles={getRoleOptions(journal)} + subtitle={user.email} user={user} /> ) : ( diff --git a/packages/components-faraday/src/components/Admin/EditUserForm.js b/packages/components-faraday/src/components/Admin/EditUserForm.js index 95025eea1ab0eaf5174df5a7abd2d5c357890daa..e40fd416eb7befb6c674ef2f2911c265031e98d7 100644 --- a/packages/components-faraday/src/components/Admin/EditUserForm.js +++ b/packages/components-faraday/src/components/Admin/EditUserForm.js @@ -1,13 +1,20 @@ -import React from 'react' +import React, { Fragment } from 'react' import styled from 'styled-components' import { ValidatedField, TextField, Menu, Checkbox, th } from '@pubsweet/ui' import { required } from 'xpub-validators' -const EditUserForm = ({ roles, journal, user, error }) => ( - <div> - <Title>Edit user</Title> - <Subtitle>{user.email}</Subtitle> +const EditUserForm = ({ + roles = false, + journal, + user, + error, + title = 'Edit user', + subtitle = null, +}) => ( + <Fragment> + <Title>{title}</Title> + {subtitle && <Subtitle>{subtitle}</Subtitle>} <Row> <RowItem> <Label> First name* </Label> @@ -43,60 +50,62 @@ const EditUserForm = ({ roles, journal, user, error }) => ( /> </RowItem> </Row> - <Row> - <RowItem> - <Label> Roles*</Label> - <ValidatedField - component={input => ( - <Checkbox - checked - readonly - type="checkbox" - {...input} - label="Author" - /> - )} - name="author" - /> - <ValidatedField - component={input => ( - <Checkbox - checked={input.value} - type="checkbox" - {...input} - label="Editor in Chief" - /> - )} - name="editorInChief" - /> - <ValidatedField - component={input => ( - <Checkbox - checked={input.value} - type="checkbox" - {...input} - label="Handling Editor" - /> - )} - name="handlingEditor" - /> - <ValidatedField - component={input => ( - <Checkbox - checked={input.value} - type="checkbox" - {...input} - label="Admin" - /> - )} - name="admin" - /> - </RowItem> - </Row> + {roles && ( + <Row> + <RowItem> + <Label> Roles*</Label> + <ValidatedField + component={input => ( + <Checkbox + checked + readonly + type="checkbox" + {...input} + label="Author" + /> + )} + name="author" + /> + <ValidatedField + component={input => ( + <Checkbox + checked={input.value} + type="checkbox" + {...input} + label="Editor in Chief" + /> + )} + name="editorInChief" + /> + <ValidatedField + component={input => ( + <Checkbox + checked={input.value} + type="checkbox" + {...input} + label="Handling Editor" + /> + )} + name="handlingEditor" + /> + <ValidatedField + component={input => ( + <Checkbox + checked={input.value} + type="checkbox" + {...input} + label="Admin" + /> + )} + name="admin" + /> + </RowItem> + </Row> + )} <Row> <RowItem>{error && <ErrorMessage>{error}</ErrorMessage>}</RowItem> </Row> - </div> + </Fragment> ) export default EditUserForm diff --git a/packages/components-faraday/src/components/AppBar/AppBar.js b/packages/components-faraday/src/components/AppBar/AppBar.js index 22f1f147483990676db2bebf431ade8038b2ce4e..ae7ea506464b18eb0eb96a6c2b4f4ae92739e2f7 100644 --- a/packages/components-faraday/src/components/AppBar/AppBar.js +++ b/packages/components-faraday/src/components/AppBar/AppBar.js @@ -37,7 +37,9 @@ const AppBar = ({ </div> {expanded && ( <Dropdown> - <DropdownOption>Settings</DropdownOption> + <DropdownOption onClick={goTo('/profile')}> + My profile + </DropdownOption> {currentUser.admin && ( <DropdownOption onClick={goTo('/admin')}> Admin dashboard diff --git a/packages/components-faraday/src/components/UIComponents/BreadcrumbsHeader.js b/packages/components-faraday/src/components/UIComponents/BreadcrumbsHeader.js new file mode 100644 index 0000000000000000000000000000000000000000..38ea5522a77e6b9cd124ac0c4537b9ef912b0c00 --- /dev/null +++ b/packages/components-faraday/src/components/UIComponents/BreadcrumbsHeader.js @@ -0,0 +1,62 @@ +import React from 'react' +import styled, { css } from 'styled-components' +import { th } from '@pubsweet/ui' + +const BreadcrumbsHeader = ({ + history, + showBack, + title = '', + info = '', + underlined, +}) => ( + <Root underlined={underlined}> + {showBack && <BackButton onClick={() => history.goBack()}>Back</BackButton>} + {title && <Title>{title}</Title>} + {info && <Content>{info}</Content>} + </Root> +) + +export default BreadcrumbsHeader + +// #region styles +const Root = styled.div` + display: flex; + flex-direction: row; + align-items: center; + margin-bottom: ${th('gridUnit')}; + border-bottom: ${({ underlined }) => + underlined ? th('borderDefault') : 'none'}; + padding-bottom: ${({ underlined }) => (underlined ? th('gridUnit') : '0')}; +` + +const defaultText = css` + color: ${th('colorPrimary')}; + font-family: ${th('fontHeading')}; + font-size: ${th('fontSizeBase')}; + text-align: left; + padding: 0 calc(${th('subGridUnit')}*2); +` + +const Title = styled.span` + ${defaultText}; + font-size: ${th('fontSizeHeading5')}; + font-weight: bold; +` + +const Content = styled.span` + ${defaultText}; +` +const BackButton = styled.span` + color: ${th('colorPrimary')}; + font-size: ${th('fontSizeBase')}; + text-align: left; + cursor: pointer; + padding-right: calc(${th('subGridUnit')}*2); + + &:before { + content: '<'; + padding-right: calc(${th('subGridUnit')}*2); + } +` + +// #endregion diff --git a/packages/components-faraday/src/components/UIComponents/FormItems.js b/packages/components-faraday/src/components/UIComponents/FormItems.js index 8f6ebf89faf00b45808573e56a929b1635470ee5..5f9a14cd9017944ed4657bccbd510161f8530e05 100644 --- a/packages/components-faraday/src/components/UIComponents/FormItems.js +++ b/packages/components-faraday/src/components/UIComponents/FormItems.js @@ -11,6 +11,16 @@ export const DefaultText = styled.div` ${defaultText}; ` +export const LinkText = styled.div` + ${defaultText}; + color: ${th('colorPrimary')}; + cursor: pointer; + text-decoration: underline; + &:hover { + color: ${th('colorText')}; + } +` + export const RootContainer = styled.div` background-color: ${th('backgroundColorReverse')}; border: ${({ bordered }) => (bordered ? th('borderDefault') : 'none')}; @@ -82,6 +92,11 @@ export const Label = styled.div` text-transform: uppercase; ` +export const LabelTitle = Label.extend` + margin: calc(${th('subGridUnit')} * 2) 0 0; + line-height: 1; +` + export const Err = styled.span` color: ${th('colorError')}; font-family: ${th('fontReading')}; @@ -135,3 +150,12 @@ export const PrivatePolicy = styled.div` ` export const TextAreaField = input => <Textarea {...input} height={70} /> + +export const LabelHeader = styled.div` + color: ${th('colorPrimary')}; + font-family: ${th('fontHeading')}; + font-size: ${th('fontSizeBaseSmall')}; + margin: ${th('subGridUnit')} 0; + text-transform: uppercase; + font-weight: bold; +` diff --git a/packages/components-faraday/src/components/UIComponents/index.js b/packages/components-faraday/src/components/UIComponents/index.js index 5f22ea1c71cc274efe11bf2fdc777e0649a7377b..c5652eb8813f71e1c0d63e958f7ca835eb55f851 100644 --- a/packages/components-faraday/src/components/UIComponents/index.js +++ b/packages/components-faraday/src/components/UIComponents/index.js @@ -8,3 +8,4 @@ export { default as InfoPage } from './InfoPage' export { default as ErrorPage } from './ErrorPage' export { default as DateParser } from './DateParser' export { default as ConfirmationPage } from './ConfirmationPage' +export { default as BreadcrumbsHeader } from './BreadcrumbsHeader' diff --git a/packages/components-faraday/src/components/UserProfile/AccountDetails.js b/packages/components-faraday/src/components/UserProfile/AccountDetails.js new file mode 100644 index 0000000000000000000000000000000000000000..59e1c07877d233f746ca086328c9b73dfcaf55cb --- /dev/null +++ b/packages/components-faraday/src/components/UserProfile/AccountDetails.js @@ -0,0 +1,56 @@ +import React from 'react' +import styled from 'styled-components' +import { reduxForm } from 'redux-form' +import { withJournal } from 'xpub-journal' +import { compose, withProps, withHandlers, withState } from 'recompose' +import { onSubmitUser as onSubmit } from '../utils' + +import AccountDetailsCard from './AccountDetailsCard' +import AccountDetailsEdit from './AccountDetailsEdit' + +const AccountDetails = ({ + user, + isEdit, + setEditMode, + journal, + handleSubmit, +}) => ( + <Root> + {isEdit ? ( + <form onSubmit={handleSubmit}> + <AccountDetailsEdit + journal={journal} + setEditMode={setEditMode} + user={user} + /> + </form> + ) : ( + <AccountDetailsCard + journal={journal} + setEditMode={setEditMode} + user={user} + /> + )} + </Root> +) + +export default compose( + withJournal, + withState('isEdit', 'setEdit', false), + withHandlers({ + setEditMode: ({ setEdit }) => value => setEdit(value), + }), + withProps(({ user }) => ({ initialValues: user })), + reduxForm({ + form: 'userManagement', + onSubmit, + }), +)(AccountDetails) + +// #region styles +const Root = styled.div` + display: flex; + flex-direction: column; + max-width: 30em; +` +// #endregion diff --git a/packages/components-faraday/src/components/UserProfile/AccountDetailsCard.js b/packages/components-faraday/src/components/UserProfile/AccountDetailsCard.js new file mode 100644 index 0000000000000000000000000000000000000000..db9172efa60d07a2951ffc92d505042d8f593f7f --- /dev/null +++ b/packages/components-faraday/src/components/UserProfile/AccountDetailsCard.js @@ -0,0 +1,57 @@ +import React, { Fragment } from 'react' + +import { + Row, + RowItem, + LabelHeader, + LabelTitle, + DefaultText, + LinkText, +} from '../UIComponents/FormItems' +import { getUserTitle } from '../utils' + +const AccountDetailsCard = ({ + user: { firstName = '', lastName = '', affiliation = '-', title = '' }, + journal, + setEditMode, +}) => ( + <Fragment> + <Row noMargin> + <RowItem> + <LabelHeader> Account Details</LabelHeader> + </RowItem> + </Row> + <Row noMargin> + <RowItem> + <LabelTitle> First Name </LabelTitle> + <LabelTitle> Last Name </LabelTitle> + </RowItem> + </Row> + <Row noMargin> + <RowItem> + <DefaultText>{firstName}</DefaultText> + <DefaultText>{lastName}</DefaultText> + </RowItem> + </Row> + <Row noMargin> + <RowItem> + <LabelTitle>Affiliation</LabelTitle> + <LabelTitle>Title</LabelTitle> + </RowItem> + </Row> + <Row noMargin> + <RowItem> + <DefaultText>{affiliation}</DefaultText> + <DefaultText>{getUserTitle(journal, title)}</DefaultText> + </RowItem> + </Row> + <Row noMargin> + <RowItem> + <LinkText onClick={() => setEditMode(true)}>Edit details</LinkText> + <LinkText>Change Password</LinkText> + </RowItem> + </Row> + </Fragment> +) + +export default AccountDetailsCard diff --git a/packages/components-faraday/src/components/UserProfile/AccountDetailsEdit.js b/packages/components-faraday/src/components/UserProfile/AccountDetailsEdit.js new file mode 100644 index 0000000000000000000000000000000000000000..1e8b4b2b47830a4ae207c475393febd30926164b --- /dev/null +++ b/packages/components-faraday/src/components/UserProfile/AccountDetailsEdit.js @@ -0,0 +1,34 @@ +import React from 'react' +import styled from 'styled-components' +import { Button, th } from '@pubsweet/ui' + +import EditUserForm from '../Admin/EditUserForm' + +const AccountDetailsEdit = ({ journal, user, setEditMode }) => ( + <Root> + <EditUserForm journal={journal} title="Edit account details" user={user} /> + <Row> + <Button onClick={() => setEditMode(false)}>Cancel</Button> + <Button primary type="submit"> + Save + </Button> + </Row> + </Root> +) + +export default AccountDetailsEdit + +// #region styles +const Root = styled.div` + background-color: ${th('colorBackground')}; + padding: ${th('gridUnit')}; + border: ${th('borderDefault')}; +` + +const Row = styled.div` + align-items: flex-start; + display: flex; + flex-direction: row; + justify-content: ${({ justify }) => justify || 'space-evenly'}; +` +// #endregion diff --git a/packages/components-faraday/src/components/UserProfile/EmailNotifications.js b/packages/components-faraday/src/components/UserProfile/EmailNotifications.js new file mode 100644 index 0000000000000000000000000000000000000000..853c24047d9ff22f56f13d0ffa96eb1ff9044961 --- /dev/null +++ b/packages/components-faraday/src/components/UserProfile/EmailNotifications.js @@ -0,0 +1,37 @@ +import React from 'react' +import styled from 'styled-components' + +import { Row, RowItem, LabelHeader, LinkText } from '../UIComponents/FormItems' + +const EmailNotifications = ({ subscribed = '' }) => ( + <Root> + <Row noMargin> + <RowItem> + <LabelHeader>Email Notifications </LabelHeader> + </RowItem> + </Row> + {!subscribed ? ( + <Row noMargin> + <RowItem> + <LinkText>Re-subscribe</LinkText> + </RowItem> + </Row> + ) : ( + <Row noMargin> + <RowItem> + <LinkText>Unsubscribe</LinkText> + </RowItem> + </Row> + )} + </Root> +) + +export default EmailNotifications + +// #region styles +const Root = styled.div` + display: flex; + flex-direction: column; + max-width: 30em; +` +// #endregion diff --git a/packages/components-faraday/src/components/UserProfile/LinkOrcID.js b/packages/components-faraday/src/components/UserProfile/LinkOrcID.js new file mode 100644 index 0000000000000000000000000000000000000000..827c75eda4d4f1fea800e04d02f8112ae3f197c2 --- /dev/null +++ b/packages/components-faraday/src/components/UserProfile/LinkOrcID.js @@ -0,0 +1,44 @@ +import React from 'react' +import styled from 'styled-components' + +import { + Row, + RowItem, + LabelHeader, + LabelTitle, + LinkText, +} from '../UIComponents/FormItems' + +const LinkOrcID = ({ orcid = '' }) => ( + <Root> + <Row noMargin> + <RowItem> + <LabelHeader>OrcID Account </LabelHeader> + </RowItem> + </Row> + {!orcid ? ( + <Row noMargin> + <RowItem> + <LinkText>Link</LinkText> + </RowItem> + </Row> + ) : ( + <Row noMargin> + <RowItem> + <LabelTitle> {orcid} </LabelTitle> + <LinkText>Unlink</LinkText> + </RowItem> + </Row> + )} + </Root> +) + +export default LinkOrcID + +// #region styles +const Root = styled.div` + display: flex; + flex-direction: column; + max-width: 30em; +` +// #endregion diff --git a/packages/components-faraday/src/components/UserProfile/UserProfilePage.js b/packages/components-faraday/src/components/UserProfile/UserProfilePage.js new file mode 100644 index 0000000000000000000000000000000000000000..75f207cfbe9f5f499f07316e96b1c2ce95052a2d --- /dev/null +++ b/packages/components-faraday/src/components/UserProfile/UserProfilePage.js @@ -0,0 +1,41 @@ +import React from 'react' +import { get } from 'lodash' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import styled from 'styled-components' +import { selectCurrentUser } from 'xpub-selectors' +import { BreadcrumbsHeader } from 'pubsweet-components-faraday/src/components' + +import AccountDetails from './AccountDetails' +import LinkOrcID from './LinkOrcID' +import EmailNotifications from './EmailNotifications' + +const UserProfilePage = ({ history, user }) => ( + <Root> + <BreadcrumbsHeader + history={history} + info={get(user, 'email')} + showBack + title="Account Settings" + underlined + /> + <AccountDetails user={user} /> + <EmailNotifications subscribed={get(user, 'subscription')} /> + <LinkOrcID orcid={get(user, 'orcid')} /> + </Root> +) + +export default compose( + connect(state => ({ + user: selectCurrentUser(state), + })), +)(UserProfilePage) + +// #region styles +const Root = styled.div` + display: flex; + flex-direction: column; + margin: auto; + max-width: 60em; +` +// #endregion diff --git a/packages/components-faraday/src/components/index.js b/packages/components-faraday/src/components/index.js index 525ddeb0919f6fc43f7c3a9869b490d110e55adb..54fdcb5734f6e26063cb699cc36f1a125707fdc1 100644 --- a/packages/components-faraday/src/components/index.js +++ b/packages/components-faraday/src/components/index.js @@ -8,9 +8,16 @@ export { default as AppBar } from './AppBar/AppBar' export { default as AuthorList } from './AuthorList/AuthorList' export { default as withVersion } from './Dashboard/withVersion.js' export { default as SortableList } from './SortableList/SortableList' +export { default as UserProfilePage } from './UserProfile/UserProfilePage' export { Decision } export { Components } export { Recommendation } export { DragHandle } from './AuthorList/FormItems' -export { Dropdown, DateParser, Logo, Spinner } from './UIComponents' +export { + Dropdown, + DateParser, + Logo, + Spinner, + BreadcrumbsHeader, +} from './UIComponents' diff --git a/packages/components-faraday/src/components/utils.js b/packages/components-faraday/src/components/utils.js index 60a5639770622876a45f7c913667856f5be5b5fd..e6dbf37dfb89054bb010ff4761149584eed08515 100644 --- a/packages/components-faraday/src/components/utils.js +++ b/packages/components-faraday/src/components/utils.js @@ -1,5 +1,7 @@ +import { actions } from 'pubsweet-client' import { SubmissionError } from 'redux-form' -import { get, find, capitalize } from 'lodash' +import { get, find, capitalize, pick } from 'lodash' +import { update } from 'pubsweet-client/src/helpers/api' export const parseTitle = version => { const title = get(version, 'metadata.title') @@ -30,6 +32,9 @@ export const parseVersion = version => ({ export const parseJournalIssue = (journal, metadata) => journal.issueTypes.find(t => t.value === get(metadata, 'issue')) +export const getUserTitle = (journal, title) => + get(journal.title.find(t => t.value === title), 'label') + export const mapStatusToLabel2 = status => { switch (status) { case 'he-invited': @@ -103,3 +108,26 @@ export const parseSearchParams = url => { /* eslint-enable */ return parsedObject } + +export const parseUpdateUser = values => { + const valuesToSave = [ + 'admin', + 'firstName', + 'lastName', + 'affiliation', + 'title', + 'roles', + 'editorInChief', + 'handlingEditor', + ] + + return pick(values, valuesToSave) +} + +export const onSubmitUser = (values, dispatch, { setEditMode }) => + update(`/users/${values.id}`, parseUpdateUser(values)) + .then(user => { + setEditMode(false) + dispatch(actions.getCurrentUser()) + }) + .catch(handleFormError) diff --git a/packages/xpub-faraday/app/routes.js b/packages/xpub-faraday/app/routes.js index ae7f6e6409763c8dbbaa162403ea7e087937c500..77dff3d32b1d050dcbb4dee794d1d9d913416960 100644 --- a/packages/xpub-faraday/app/routes.js +++ b/packages/xpub-faraday/app/routes.js @@ -3,9 +3,11 @@ import { Route, Switch } from 'react-router-dom' import { AuthenticatedComponent } from 'pubsweet-client' import { Wizard } from 'pubsweet-component-wizard/src/components' +import { UserProfilePage } from 'pubsweet-components-faraday/src/components' import { ManuscriptPage } from 'pubsweet-component-manuscript/src/components' import DashboardPage from 'pubsweet-components-faraday/src/components/Dashboard' import LoginPage from 'pubsweet-components-faraday/src/components/Login/LoginPage' + import { NotFound, InfoPage, @@ -80,6 +82,7 @@ const Routes = () => ( /> <Route component={ConfirmAccount} exact path="/confirm-signup" /> <PrivateRoute component={DashboardPage} exact path="/" /> + <PrivateRoute component={UserProfilePage} exact path="/profile" /> <PrivateRoute component={ConfirmationPage} exact