rrebase/knboard

View on GitHub
frontend/src/features/auth/LoginDialog.tsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React, { useState } from "react";
import { useForm } from "react-hook-form";
import {
  Button,
  TextField,
  Dialog,
  DialogContent,
  DialogTitle,
  Grow,
} from "@material-ui/core";
import { css } from "@emotion/core";
import styled from "@emotion/styled";
import { useDispatch, useSelector } from "react-redux";
import { login, clearErrors } from "./AuthSlice";
import { RootState } from "store";
import { Alert } from "@material-ui/lab";
import Close from "components/Close";

const FormActions = styled.div`
  margin-top: 1rem;
  text-align: right;
`;

interface FormData {
  username: string;
  password: string;
}

const LoginDialog = () => {
  const dispatch = useDispatch();
  const [open, setOpen] = useState(false);
  const apiErrors = useSelector((state: RootState) => state.auth.loginErrors);
  const loading = useSelector((state: RootState) => state.auth.loginLoading);
  const { register, handleSubmit, errors } = useForm<FormData>();

  const handleOpen = () => {
    setOpen(true);
  };

  const handleClose = () => {
    setOpen(false);
    dispatch(clearErrors());
  };

  const onSubmit = handleSubmit(({ username, password }) => {
    dispatch(login({ username, password }));
  });

  return (
    <>
      <Button
        variant="contained"
        color="primary"
        onClick={handleOpen}
        data-testid="open-login-btn"
      >
        Login
      </Button>
      <Dialog
        open={open}
        onClose={handleClose}
        keepMounted={false}
        aria-labelledby="login-dialog-title"
        css={css`
          & .MuiDialog-paper {
            padding: 2rem 1.5rem;
          }
        `}
        maxWidth="xs"
        fullWidth
      >
        <Close onClose={handleClose} />
        <DialogTitle id="login-dialog-title">Login</DialogTitle>
        <form onSubmit={onSubmit}>
          <DialogContent>
            {apiErrors?.non_field_errors && (
              <Grow in timeout={100}>
                <Alert
                  severity="error"
                  css={css`
                    margin-bottom: 0.75rem;
                  `}
                >
                  {apiErrors.non_field_errors?.map((errorMsg) => (
                    <div key={errorMsg}>{errorMsg}</div>
                  ))}
                </Alert>
              </Grow>
            )}
            <TextField
              autoFocus
              name="username"
              margin="dense"
              id="username"
              label="Username"
              variant="outlined"
              inputRef={register({ required: "This field is required" })}
              helperText={errors.username?.message}
              error={Boolean(errors.username)}
              fullWidth
            />
            <TextField
              name="password"
              margin="dense"
              id="password"
              label="Password"
              variant="outlined"
              type="password"
              inputRef={register({ required: "This field is required" })}
              helperText={errors.password?.message}
              error={Boolean(errors.password)}
              fullWidth
            />
            <FormActions>
              <Button
                variant="contained"
                color="primary"
                type="submit"
                disabled={loading}
                data-testid="submit-login-btn"
              >
                Login
              </Button>
            </FormActions>
          </DialogContent>
        </form>
      </Dialog>
    </>
  );
};

export default LoginDialog;