Files
coder/site/components/SignIn/SignInForm.tsx
T
Bryan 4183a4e01c feat: Initial Login flow (#42)
This just implements a basic sign-in flow, using the new endpoints in #29 :
![2022-01-20 12 35 30](https://user-images.githubusercontent.com/88213859/150418044-85900d1f-8890-4c60-baae-234342de71fa.gif)

This brings over several dependencies that are necessary:
- `formik`
- `yep`

Ports over some v1 code to bootstrap it:
- `FormTextField`
- `PasswordField`
- `CoderIcon`

And implements basic sign-in:
Fixes #37 
Fixes #43

This does not implement it navbar integration (importantly - there is no way to sign out yet, unless you manually delete your `session_token`). I'll do that in the next PR - figured this was big enough to get reviewed.
2022-01-21 11:34:26 -08:00

147 lines
3.9 KiB
TypeScript

import { makeStyles } from "@material-ui/core/styles"
import { FormikContextType, useFormik } from "formik"
import { useRouter } from "next/router"
import React from "react"
import { useSWRConfig } from "swr"
import * as Yup from "yup"
import { Welcome } from "./Welcome"
import { formTextFieldFactory } from "../Form"
import * as API from "./../../api"
import { LoadingButton } from "./../Button"
/**
* BuiltInAuthFormValues describes a form using built-in (email/password)
* authentication. This form may not always be present depending on external
* auth providers available and administrative configurations
*/
interface BuiltInAuthFormValues {
email: string
password: string
}
const validationSchema = Yup.object({
email: Yup.string().required("Email is required."),
password: Yup.string(),
})
const FormTextField = formTextFieldFactory<BuiltInAuthFormValues>()
const useStyles = makeStyles((theme) => ({
loginBtnWrapper: {
marginTop: theme.spacing(6),
borderTop: `1px solid ${theme.palette.action.disabled}`,
paddingTop: theme.spacing(3),
},
loginTypeToggleWrapper: {
marginTop: theme.spacing(2),
display: "flex",
justifyContent: "center",
},
loginTypeToggleBtn: {
color: theme.palette.text.primary,
// We want opacity so that this isn't super highlighted for the user.
// In most cases, they shouldn't want to switch login types.
opacity: 0.5,
"&:hover": {
cursor: "pointer",
opacity: 1,
textDecoration: "underline",
},
},
loginTypeToggleBtnFocusVisible: {
opacity: 1,
textDecoration: "underline",
},
loginTypeBtn: {
backgroundColor: "#2A2B45",
textTransform: "none",
"&:not(:first-child)": {
marginTop: theme.spacing(2),
},
},
submitBtn: {
marginTop: theme.spacing(2),
},
}))
export interface SignInProps {
loginHandler?: (email: string, password: string) => Promise<void>
}
export const SignInForm: React.FC<SignInProps> = ({
loginHandler = (email: string, password: string) => API.login(email, password),
}) => {
const router = useRouter()
const styles = useStyles()
const { mutate } = useSWRConfig()
const form: FormikContextType<BuiltInAuthFormValues> = useFormik<BuiltInAuthFormValues>({
initialValues: {
email: "",
password: "",
},
validationSchema,
onSubmit: async ({ email, password }, helpers) => {
try {
await loginHandler(email, password)
// Tell SWR to invalidate the cache for the user endpoint
mutate("/api/v2/user")
router.push("/")
} catch (err) {
helpers.setFieldError("password", "The username or password is incorrect.")
}
},
})
return (
<>
<Welcome />
<form onSubmit={form.handleSubmit}>
<div>
<FormTextField
autoComplete="email"
autoFocus
eventTransform={(email: string) => email.trim()}
form={form}
formFieldName="email"
fullWidth
inputProps={{
id: "signin-form-inpt-email",
}}
margin="none"
placeholder="Email"
variant="outlined"
/>
<FormTextField
autoComplete="current-password"
form={form}
formFieldName="password"
fullWidth
inputProps={{
id: "signin-form-inpt-password",
}}
isPassword
margin="none"
placeholder="Password"
variant="outlined"
/>
</div>
<div className={styles.submitBtn}>
<LoadingButton
color="primary"
loading={form.isSubmitting}
fullWidth
id="signin-form-submit"
type="submit"
variant="contained"
>
Sign In
</LoadingButton>
</div>
</form>
</>
)
}