Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
UserProfile.js 5.93 KiB
/* eslint-disable handle-callback-err  */
import React, { Fragment } from 'react'
import countrylist from 'country-list'
import styled from 'styled-components'
import { reduxForm } from 'redux-form'
import { th } from '@pubsweet/ui-toolkit'
import { required as requiredValidator } from 'xpub-validators'
import { compose, withStateHandlers, withProps } from 'recompose'
import { H3, Spinner, ValidatedField, TextField, Menu } from '@pubsweet/ui'

import {
  ShadowedBox,
  IconButton,
  Row,
  Item,
  Label,
  Text,
  ActionLink,
  RowOverrideAlert,
} from './'

const countries = countrylist()

const countryList = countries
  .getNames()
  .map(c => ({ value: countries.getCode(c), label: c }))

const Profile = ({
  title,
  toggleEdit,
  user: { affiliation, firstName, lastName, title: userTitle, country = '' },
  ...rest
}) => (
  <ShadowedBox position="relative" {...rest}>
    <IconButton
      icon="edit-2"
      iconSize={2}
      onClick={toggleEdit}
      right={8}
      top={12}
    />

    <Fragment>
      <Row alignItems="center">
        <Item>
          <H3>Account Details</H3>
        </Item>
        <Item>
          <ActionLink internal to="/change-password">
            Change password
          </ActionLink>
        </Item>
      </Row>

      <Row alignItems="baseline" mt={2}>
        <Item vertical>
          <Label>First Name</Label>
          <Text secondary>{firstName}</Text>
        </Item>
        <Item vertical>
          <Label>Last Name</Label>
          <Text secondary>{lastName}</Text>
        </Item>
      </Row>

      <Row alignItems="baseline" mt={2}>
        <Item vertical>
          <Label>Title</Label>
          <Text secondary>{title.find(t => t.value === userTitle).label}</Text>
        </Item>
        <Item vertical>
          <Label>Country</Label>
          <Text secondary>{countries.getName(country)}</Text>
        </Item>
      </Row>

      <Row alignItems="baseline" mt={2}>
        <Item vertical>
          <Label>Affiliation</Label>
          <Text secondary>{affiliation}</Text>
        </Item>
      </Row>
    </Fragment>
  </ShadowedBox>
)

const EditModeIcons = ({ toggleEdit, onSaveChanges }) => (
  <Fragment>
    <IconButton
      icon="x-circle"
      iconSize={2}
      onClick={toggleEdit}
      right={36}
      top={12}
    />
    <IconButton
      icon="check-circle"
      iconSize={2}
      onClick={onSaveChanges}
      right={8}
      top={12}
    />
  </Fragment>
)

const EditUserProfile = compose(
  withProps(
    ({
      user: { affiliation, firstName, lastName, title: userTitle, country },
    }) => ({
      initialValues: {
        country,
        lastName,
        firstName,
        affiliation,
        title: userTitle,
      },
    }),
  ),
  reduxForm({
    form: 'profile',
    onSubmit: (values, dispatch, { onSave, ...props }) => {
      typeof onSave === 'function' && onSave(values, props)
    },
  }),
)(({ toggleEdit, title, handleSubmit, loading, errorMessage, ...rest }) => (
  <ShadowedBox position="relative" {...rest}>
    {loading ? (
      <StyledSpinner>
        <Spinner />
      </StyledSpinner>
    ) : (
      <EditModeIcons onSaveChanges={handleSubmit} toggleEdit={toggleEdit} />
    )}
    <Fragment>
      <Row>
        <Item>
          <H3>Edit Account Details</H3>
        </Item>
      </Row>

      <Row alignItems="baseline" mt={2}>
        <Item mr={1} vertical>
          <Label required>First Name</Label>
          <ValidatedField
            component={TextField}
            name="firstName"
            validate={[requiredValidator]}
          />
        </Item>
        <Item ml={1} vertical>
          <Label required>Last Name</Label>
          <ValidatedField
            component={TextField}
            name="lastName"
            validate={[requiredValidator]}
          />
        </Item>
      </Row>

      <RowOverrideAlert alignItems="baseline" mt={2}>
        <Item mr={1} vertical>
          <Label required>Title</Label>
          <ValidatedField
            component={input => <Menu {...input} options={title} />}
            name="title"
            validate={[requiredValidator]}
          />
        </Item>
        <Item ml={1} vertical>
          <Label required>Country</Label>
          <ValidatedField
            component={input => <Menu {...input} options={countryList} />}
            name="country"
            validate={[requiredValidator]}
          />
        </Item>
      </RowOverrideAlert>

      <Row alignItems="baseline" mt={2}>
        <Item vertical>
          <Label required>Affiliation</Label>
          <ValidatedField
            component={TextField}
            name="affiliation"
            validate={[requiredValidator]}
          />
        </Item>
      </Row>

      {errorMessage && (
        <Row alignItems="center" mt={2}>
          <Text error>{errorMessage}</Text>
        </Row>
      )}
    </Fragment>
  </ShadowedBox>
))

const UserProfile = ({
  user,
  onSave,
  loading,
  editMode,
  setError,
  toggleEdit,
  setLoading,
  errorMessage,
  journal: { title = [] },
  ...rest
}) =>
  !editMode ? (
    <Profile title={title} toggleEdit={toggleEdit} user={user} {...rest} />
  ) : (
    <EditUserProfile
      errorMessage={errorMessage}
      loading={loading}
      onSave={onSave}
      setError={setError}
      setLoading={setLoading}
      title={title}
      toggleEdit={toggleEdit}
      user={user}
      {...rest}
    />
  )

export default compose(
  withStateHandlers(
    {
      loading: false,
      editMode: false,
      errorMessage: '',
    },
    {
      toggleEdit: ({ editMode }) => () => ({
        editMode: !editMode,
        errorMessage: '',
        loading: false,
      }),
      setError: ({ errorMessage }) => msg => ({
        errorMessage: msg,
        loading: false,
      }),
      setLoading: ({ loading }) => value => ({
        loading: value,
      }),
    },
  ),
)(UserProfile)

// #region styles
const StyledSpinner = styled.div`
  position: absolute;
  top: ${th('gridUnit')};
  right: ${th('gridUnit')};
`
// #endregion