fix: manage backend authXService errors (#3190)

This commit is contained in:
Abhineet Jain
2022-07-26 15:39:45 -04:00
committed by GitHub
parent b19cf701c5
commit 0128ca6bd1
15 changed files with 230 additions and 118 deletions
@@ -9,7 +9,7 @@ import { ApiError, getErrorDetail, getErrorMessage } from "api/errors"
import { Stack } from "components/Stack/Stack"
import { FC, useState } from "react"
const Language = {
export const Language = {
retryMessage: "Retry",
unknownErrorMessage: "An unknown error has occurred",
moreDetails: "More",
@@ -91,7 +91,6 @@ interface StyleProps {
const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
root: {
background: darken(theme.palette.error.main, 0.6),
margin: `${theme.spacing(2)}px`,
padding: `${theme.spacing(2)}px`,
borderRadius: theme.shape.borderRadius,
gap: 0,
@@ -0,0 +1,53 @@
import { Story } from "@storybook/react"
import { AccountForm, AccountFormProps } from "./SettingsAccountForm"
export default {
title: "components/SettingsAccountForm",
component: AccountForm,
argTypes: {
onSubmit: { action: "Submit" },
},
}
const Template: Story<AccountFormProps> = (args: AccountFormProps) => <AccountForm {...args} />
export const Example = Template.bind({})
Example.args = {
email: "test-user@org.com",
isLoading: false,
initialValues: {
username: "test-user",
},
updateProfileError: undefined,
onSubmit: () => {
return Promise.resolve()
},
}
export const Loading = Template.bind({})
Loading.args = {
...Example.args,
isLoading: true,
}
export const WithError = Template.bind({})
WithError.args = {
...Example.args,
updateProfileError: {
response: {
data: {
message: "Username is invalid",
validations: [
{
field: "username",
detail: "Username is too long.",
},
],
},
},
isAxiosError: true,
},
initialTouched: {
username: true,
},
}
@@ -1,9 +1,9 @@
import FormHelperText from "@material-ui/core/FormHelperText"
import TextField from "@material-ui/core/TextField"
import { FormikContextType, FormikErrors, useFormik } from "formik"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import { FC } from "react"
import * as Yup from "yup"
import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formUtils"
import { getFormHelpersWithError, nameValidator, onChangeTrimmed } from "../../util/formUtils"
import { LoadingButton } from "../LoadingButton/LoadingButton"
import { Stack } from "../Stack/Stack"
@@ -21,15 +21,14 @@ const validationSchema = Yup.object({
username: nameValidator(Language.usernameLabel),
})
export type AccountFormErrors = FormikErrors<AccountFormValues>
export interface AccountFormProps {
email: string
isLoading: boolean
initialValues: AccountFormValues
onSubmit: (values: AccountFormValues) => void
formErrors?: AccountFormErrors
error?: string
updateProfileError?: Error | unknown
// initialTouched is only used for testing the error state of the form.
initialTouched?: FormikTouched<AccountFormValues>
}
export const AccountForm: FC<AccountFormProps> = ({
@@ -37,20 +36,22 @@ export const AccountForm: FC<AccountFormProps> = ({
isLoading,
onSubmit,
initialValues,
formErrors = {},
error,
updateProfileError,
initialTouched,
}) => {
const form: FormikContextType<AccountFormValues> = useFormik<AccountFormValues>({
initialValues,
validationSchema,
onSubmit,
initialTouched,
})
const getFieldHelpers = getFormHelpers<AccountFormValues>(form, formErrors)
const getFieldHelpers = getFormHelpersWithError<AccountFormValues>(form, updateProfileError)
return (
<>
<form onSubmit={form.handleSubmit}>
<Stack>
{updateProfileError && <ErrorSummary error={updateProfileError} />}
<TextField
disabled
fullWidth
@@ -67,8 +68,6 @@ export const AccountForm: FC<AccountFormProps> = ({
variant="outlined"
/>
{error && <FormHelperText error>{error}</FormHelperText>}
<div>
<LoadingButton loading={isLoading} type="submit" variant="contained">
{isLoading ? "" : Language.updateSettings}
@@ -0,0 +1,54 @@
import { Story } from "@storybook/react"
import { SecurityForm, SecurityFormProps } from "./SettingsSecurityForm"
export default {
title: "components/SettingsSecurityForm",
component: SecurityForm,
argTypes: {
onSubmit: { action: "Submit" },
},
}
const Template: Story<SecurityFormProps> = (args: SecurityFormProps) => <SecurityForm {...args} />
export const Example = Template.bind({})
Example.args = {
isLoading: false,
initialValues: {
old_password: "",
password: "",
confirm_password: "",
},
updateSecurityError: undefined,
onSubmit: () => {
return Promise.resolve()
},
}
export const Loading = Template.bind({})
Loading.args = {
...Example.args,
isLoading: true,
}
export const WithError = Template.bind({})
WithError.args = {
...Example.args,
updateSecurityError: {
response: {
data: {
message: "Old password is incorrect",
validations: [
{
field: "old_password",
detail: "Old password is incorrect.",
},
],
},
},
isAxiosError: true,
},
initialTouched: {
old_password: true,
},
}
@@ -1,9 +1,9 @@
import FormHelperText from "@material-ui/core/FormHelperText"
import TextField from "@material-ui/core/TextField"
import { FormikContextType, FormikErrors, useFormik } from "formik"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import React from "react"
import * as Yup from "yup"
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
import { getFormHelpersWithError, onChangeTrimmed } from "../../util/formUtils"
import { LoadingButton } from "../LoadingButton/LoadingButton"
import { Stack } from "../Stack/Stack"
@@ -40,33 +40,35 @@ const validationSchema = Yup.object({
}),
})
export type SecurityFormErrors = FormikErrors<SecurityFormValues>
export interface SecurityFormProps {
isLoading: boolean
initialValues: SecurityFormValues
onSubmit: (values: SecurityFormValues) => void
formErrors?: SecurityFormErrors
error?: string
updateSecurityError?: Error | unknown
// initialTouched is only used for testing the error state of the form.
initialTouched?: FormikTouched<SecurityFormValues>
}
export const SecurityForm: React.FC<SecurityFormProps> = ({
isLoading,
onSubmit,
initialValues,
formErrors = {},
error,
updateSecurityError,
initialTouched,
}) => {
const form: FormikContextType<SecurityFormValues> = useFormik<SecurityFormValues>({
initialValues,
validationSchema,
onSubmit,
initialTouched,
})
const getFieldHelpers = getFormHelpers<SecurityFormValues>(form, formErrors)
const getFieldHelpers = getFormHelpersWithError<SecurityFormValues>(form, updateSecurityError)
return (
<>
<form onSubmit={form.handleSubmit}>
<Stack>
{updateSecurityError && <ErrorSummary error={updateSecurityError} />}
<TextField
{...getFieldHelpers("old_password")}
onChange={onChangeTrimmed(form)}
@@ -95,8 +97,6 @@ export const SecurityForm: React.FC<SecurityFormProps> = ({
type="password"
/>
{error && <FormHelperText error>{error}</FormHelperText>}
<div>
<LoadingButton loading={isLoading} type="submit" variant="contained">
{isLoading ? "" : Language.updatePassword}
@@ -6,7 +6,6 @@ export default {
component: SignInForm,
argTypes: {
isLoading: "boolean",
authErrorMessage: "string",
onSubmit: { action: "Submit" },
},
}
@@ -16,7 +15,7 @@ const Template: Story<SignInFormProps> = (args: SignInFormProps) => <SignInForm
export const SignedOut = Template.bind({})
SignedOut.args = {
isLoading: false,
authErrorMessage: undefined,
authError: undefined,
onSubmit: () => {
return Promise.resolve()
},
@@ -33,12 +32,31 @@ Loading.args = {
}
export const WithLoginError = Template.bind({})
WithLoginError.args = { ...SignedOut.args, authErrorMessage: "Email or password was invalid" }
WithLoginError.args = {
...SignedOut.args,
authError: {
response: {
data: {
message: "Email or password was invalid",
validations: [
{
field: "password",
detail: "Password is invalid.",
},
],
},
},
isAxiosError: true,
},
initialTouched: {
password: true,
},
}
export const WithAuthMethodsError = Template.bind({})
WithAuthMethodsError.args = {
...SignedOut.args,
methodsErrorMessage: "Failed to fetch auth methods",
methodsError: new Error("Failed to fetch auth methods"),
}
export const WithGithub = Template.bind({})
+45 -49
View File
@@ -1,14 +1,15 @@
import Button from "@material-ui/core/Button"
import FormHelperText from "@material-ui/core/FormHelperText"
import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles"
import TextField from "@material-ui/core/TextField"
import GitHubIcon from "@material-ui/icons/GitHub"
import { FormikContextType, useFormik } from "formik"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { Stack } from "components/Stack/Stack"
import { FormikContextType, FormikTouched, useFormik } from "formik"
import { FC } from "react"
import * as Yup from "yup"
import { AuthMethods } from "../../api/typesGenerated"
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
import { getFormHelpersWithError, onChangeTrimmed } from "../../util/formUtils"
import { Welcome } from "../Welcome/Welcome"
import { LoadingButton } from "./../LoadingButton/LoadingButton"
@@ -39,17 +40,6 @@ const validationSchema = Yup.object({
})
const useStyles = makeStyles((theme) => ({
loginBtnWrapper: {
marginTop: theme.spacing(6),
borderTop: `1px solid ${theme.palette.action.disabled}`,
paddingTop: theme.spacing(3),
},
loginTextField: {
marginTop: theme.spacing(2),
},
submitBtn: {
marginTop: theme.spacing(2),
},
buttonIcon: {
width: 14,
height: 14,
@@ -78,19 +68,22 @@ const useStyles = makeStyles((theme) => ({
export interface SignInFormProps {
isLoading: boolean
redirectTo: string
authErrorMessage?: string
methodsErrorMessage?: string
authError?: Error | unknown
methodsError?: Error | unknown
authMethods?: AuthMethods
onSubmit: ({ email, password }: { email: string; password: string }) => Promise<void>
// initialTouched is only used for testing the error state of the form.
initialTouched?: FormikTouched<BuiltInAuthFormValues>
}
export const SignInForm: FC<SignInFormProps> = ({
authMethods,
redirectTo,
isLoading,
authErrorMessage,
methodsErrorMessage,
authError,
methodsError,
onSubmit,
initialTouched,
}) => {
const styles = useStyles()
@@ -106,43 +99,46 @@ export const SignInForm: FC<SignInFormProps> = ({
// field), or after a submission attempt.
validateOnBlur: false,
onSubmit,
initialTouched,
})
const getFieldHelpers = getFormHelpers<BuiltInAuthFormValues>(form)
const getFieldHelpers = getFormHelpersWithError<BuiltInAuthFormValues>(form, authError)
return (
<>
<Welcome />
<form onSubmit={form.handleSubmit}>
<TextField
{...getFieldHelpers("email")}
onChange={onChangeTrimmed(form)}
autoFocus
autoComplete="email"
className={styles.loginTextField}
fullWidth
label={Language.emailLabel}
type="email"
variant="outlined"
/>
<TextField
{...getFieldHelpers("password")}
autoComplete="current-password"
className={styles.loginTextField}
fullWidth
id="password"
label={Language.passwordLabel}
type="password"
variant="outlined"
/>
{authErrorMessage && <FormHelperText error>{authErrorMessage}</FormHelperText>}
{methodsErrorMessage && (
<FormHelperText error>{Language.methodsErrorMessage}</FormHelperText>
)}
<div className={styles.submitBtn}>
<LoadingButton loading={isLoading} fullWidth type="submit" variant="contained">
{isLoading ? "" : Language.passwordSignIn}
</LoadingButton>
</div>
<Stack>
{authError && (
<ErrorSummary error={authError} defaultMessage={Language.authErrorMessage} />
)}
{methodsError && (
<ErrorSummary error={methodsError} defaultMessage={Language.methodsErrorMessage} />
)}
<TextField
{...getFieldHelpers("email")}
onChange={onChangeTrimmed(form)}
autoFocus
autoComplete="email"
fullWidth
label={Language.emailLabel}
type="email"
variant="outlined"
/>
<TextField
{...getFieldHelpers("password")}
autoComplete="current-password"
fullWidth
id="password"
label={Language.passwordLabel}
type="password"
variant="outlined"
/>
<div>
<LoadingButton loading={isLoading} fullWidth type="submit" variant="contained">
{isLoading ? "" : Language.passwordSignIn}
</LoadingButton>
</div>
</Stack>
</form>
{authMethods?.github && (
<>