prescottprue/fireadmin

View on GitHub
src/routes/Projects/routes/Project/routes/Permissions/components/PermissionsTableRow/PermissionsTableRow.js

Summary

Maintainability
C
1 day
Test Coverage
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { map, capitalize } from 'lodash'
import { useFirestore, useUser, useFirestoreDocData } from 'reactfire'
import { Controller, useForm } from 'react-hook-form'
import Button from '@material-ui/core/Button'
import Accordion from '@material-ui/core/Accordion'
import AccordionDetails from '@material-ui/core/AccordionDetails'
import AccordionSummary from '@material-ui/core/AccordionSummary'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import InputLabel from '@material-ui/core/InputLabel'
import FormControl from '@material-ui/core/FormControl'
import Typography from '@material-ui/core/Typography'
import Divider from '@material-ui/core/Divider'
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
import IconButton from '@material-ui/core/IconButton'
import Menu from '@material-ui/core/Menu'
import Select from '@material-ui/core/Select'
import MenuItem from '@material-ui/core/MenuItem'
import MoreVertIcon from '@material-ui/icons/MoreVert'
import DeleteIcon from '@material-ui/icons/Delete'
import { makeStyles } from '@material-ui/core/styles'
import { triggerAnalyticsEvent, createProjectEvent } from 'utils/analytics'
import useNotifications from 'modules/notification/useNotifications'
import styles from './PermissionsTableRow.styles'
import { PROJECTS_COLLECTION } from 'constants/firebasePaths'

const useStyles = makeStyles(styles)

const editOptions = ['Delete']
const ITEM_HEIGHT = 48

function PermissionsTableRow({
  displayName,
  projectId,
  roleKey,
  uid,
  onDeleteClick,
  initialValues
}) {
  const classes = useStyles()
  const [anchorEl, setAnchorEl] = useState(null)
  const handleMenuClick = (e) => setAnchorEl(e.target)
  const handleMenuClose = (e) => setAnchorEl(null)

  const firestore = useFirestore()
  const { FieldValue } = useFirestore
  const {
    reset,
    control,
    handleSubmit,
    formState: { isValid, isSubmitting }
  } = useForm({ defaultValues: initialValues, mode: 'onChange' })
  const { showSuccess } = useNotifications()
  const user = useUser()

  const projectRef = firestore.doc(`${PROJECTS_COLLECTION}/${projectId}`)
  const project = useFirestoreDocData(projectRef)
  const roleOptions = map(project.roles, ({ name }, value) => ({ value, name }))

  async function updatePermission(roleUpdates) {
    await projectRef.set(
      {
        permissions: {
          [uid]: {
            ...roleUpdates,
            updatedAt: FieldValue.serverTimestamp()
          }
        }
      },
      { merge: true }
    )
    // Write event to project events
    await createProjectEvent(
      { projectId, firestore, FieldValue },
      {
        eventType: 'updatePermission',
        eventData: { ...roleUpdates, userUpdated: uid },
        createdBy: user.uid
      }
    )
    showSuccess('Member updated successfully!')
    triggerAnalyticsEvent('updatePermission', {
      projectId,
      newRole: roleUpdates.role,
      userUpdated: uid
    })
  }

  function closeAndCallDelete() {
    handleMenuClose()
    onDeleteClick(uid)
  }

  return (
    <Accordion className={classes.root}>
      <AccordionSummary
        expandIcon={<ExpandMoreIcon />}
        data-test="member-expand">
        <Typography className={classes.displayName}>
          {displayName || uid}
        </Typography>
        <Typography className={classes.permission}>
          {capitalize(roleKey)}
        </Typography>
      </AccordionSummary>
      <AccordionDetails style={{ paddingTop: 0 }}>
        <form
          className={classes.content}
          onSubmit={handleSubmit(updatePermission)}>
          <Divider />
          <div className={classes.menu}>
            <IconButton
              aria-label="More"
              aria-owns="long-menu"
              aria-haspopup="true"
              onClick={handleMenuClick}
              data-test={`member-more-${uid}`}>
              <MoreVertIcon />
            </IconButton>
            <Menu
              id="long-menu"
              anchorEl={anchorEl}
              open={Boolean(anchorEl)}
              onClose={handleMenuClose}
              PaperProps={{
                style: {
                  maxHeight: ITEM_HEIGHT * 4.5,
                  width: 200
                }
              }}>
              {editOptions.map((option) => (
                <MenuItem
                  key={option}
                  onClick={closeAndCallDelete}
                  data-test="member-delete">
                  <ListItemIcon className={classes.icon}>
                    <DeleteIcon />
                  </ListItemIcon>
                  <ListItemText
                    classes={{ primary: classes.primary }}
                    primary="Delete Member"
                  />
                </MenuItem>
              ))}
            </Menu>
          </div>
          <div>
            <div className={classes.roleSelect}>
              <FormControl className={classes.field}>
                <InputLabel htmlFor="role">Role</InputLabel>
                <Controller
                  as={
                    <Select fullWidth data-test="role-select">
                      {roleOptions.map((option, idx) => (
                        <MenuItem
                          key={`Role-Option-${option.value}-${idx}`}
                          value={option.value}
                          disabled={option.disabled}
                          data-test={`role-option-${option.value}`}>
                          <ListItemText
                            primary={option.name || capitalize(option.value)}
                          />
                        </MenuItem>
                      ))}
                    </Select>
                  }
                  name="role"
                  control={control}
                  defaultValue={initialValues.role || ''}
                />
              </FormControl>
            </div>
          </div>
          <div className={classes.buttons}>
            <Button
              disabled={isSubmitting}
              color="secondary"
              aria-label="Cancel"
              onClick={reset}
              style={{ marginRight: '2rem' }}>
              Cancel
            </Button>
            <Button
              disabled={!isValid || isSubmitting}
              color="primary"
              variant="contained"
              aria-label="Update Member"
              type="submit"
              data-test="update-member-button">
              {isSubmitting ? 'Updating...' : 'Update Member'}
            </Button>
          </div>
        </form>
      </AccordionDetails>
    </Accordion>
  )
}

PermissionsTableRow.propTypes = {
  projectId: PropTypes.string.isRequired,
  initialValues: PropTypes.object,
  onDeleteClick: PropTypes.func.isRequired,
  displayName: PropTypes.string,
  roleKey: PropTypes.string,
  uid: PropTypes.string.isRequired
}

export default PermissionsTableRow