Skip to content

Commit 0128ca6

Browse files
authored
fix: manage backend authXService errors (#3190)
1 parent b19cf70 commit 0128ca6

File tree

15 files changed

+230
-118
lines changed

15 files changed

+230
-118
lines changed

site/src/components/ErrorSummary/ErrorSummary.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { ApiError, getErrorDetail, getErrorMessage } from "api/errors"
99
import { Stack } from "components/Stack/Stack"
1010
import { FC, useState } from "react"
1111

12-
const Language = {
12+
export const Language = {
1313
retryMessage: "Retry",
1414
unknownErrorMessage: "An unknown error has occurred",
1515
moreDetails: "More",
@@ -91,7 +91,6 @@ interface StyleProps {
9191
const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
9292
root: {
9393
background: darken(theme.palette.error.main, 0.6),
94-
margin: `${theme.spacing(2)}px`,
9594
padding: `${theme.spacing(2)}px`,
9695
borderRadius: theme.shape.borderRadius,
9796
gap: 0,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Story } from "@storybook/react"
2+
import { AccountForm, AccountFormProps } from "./SettingsAccountForm"
3+
4+
export default {
5+
title: "components/SettingsAccountForm",
6+
component: AccountForm,
7+
argTypes: {
8+
onSubmit: { action: "Submit" },
9+
},
10+
}
11+
12+
const Template: Story<AccountFormProps> = (args: AccountFormProps) => <AccountForm {...args} />
13+
14+
export const Example = Template.bind({})
15+
Example.args = {
16+
email: "test-user@org.com",
17+
isLoading: false,
18+
initialValues: {
19+
username: "test-user",
20+
},
21+
updateProfileError: undefined,
22+
onSubmit: () => {
23+
return Promise.resolve()
24+
},
25+
}
26+
27+
export const Loading = Template.bind({})
28+
Loading.args = {
29+
...Example.args,
30+
isLoading: true,
31+
}
32+
33+
export const WithError = Template.bind({})
34+
WithError.args = {
35+
...Example.args,
36+
updateProfileError: {
37+
response: {
38+
data: {
39+
message: "Username is invalid",
40+
validations: [
41+
{
42+
field: "username",
43+
detail: "Username is too long.",
44+
},
45+
],
46+
},
47+
},
48+
isAxiosError: true,
49+
},
50+
initialTouched: {
51+
username: true,
52+
},
53+
}

site/src/components/SettingsAccountForm/SettingsAccountForm.tsx

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import FormHelperText from "@material-ui/core/FormHelperText"
21
import TextField from "@material-ui/core/TextField"
3-
import { FormikContextType, FormikErrors, useFormik } from "formik"
2+
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
3+
import { FormikContextType, FormikTouched, useFormik } from "formik"
44
import { FC } from "react"
55
import * as Yup from "yup"
6-
import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formUtils"
6+
import { getFormHelpersWithError, nameValidator, onChangeTrimmed } from "../../util/formUtils"
77
import { LoadingButton } from "../LoadingButton/LoadingButton"
88
import { Stack } from "../Stack/Stack"
99

@@ -21,36 +21,37 @@ const validationSchema = Yup.object({
2121
username: nameValidator(Language.usernameLabel),
2222
})
2323

24-
export type AccountFormErrors = FormikErrors<AccountFormValues>
25-
2624
export interface AccountFormProps {
2725
email: string
2826
isLoading: boolean
2927
initialValues: AccountFormValues
3028
onSubmit: (values: AccountFormValues) => void
31-
formErrors?: AccountFormErrors
32-
error?: string
29+
updateProfileError?: Error | unknown
30+
// initialTouched is only used for testing the error state of the form.
31+
initialTouched?: FormikTouched<AccountFormValues>
3332
}
3433

3534
export const AccountForm: FC<AccountFormProps> = ({
3635
email,
3736
isLoading,
3837
onSubmit,
3938
initialValues,
40-
formErrors = {},
41-
error,
39+
updateProfileError,
40+
initialTouched,
4241
}) => {
4342
const form: FormikContextType<AccountFormValues> = useFormik<AccountFormValues>({
4443
initialValues,
4544
validationSchema,
4645
onSubmit,
46+
initialTouched,
4747
})
48-
const getFieldHelpers = getFormHelpers<AccountFormValues>(form, formErrors)
48+
const getFieldHelpers = getFormHelpersWithError<AccountFormValues>(form, updateProfileError)
4949

5050
return (
5151
<>
5252
<form onSubmit={form.handleSubmit}>
5353
<Stack>
54+
{updateProfileError && <ErrorSummary error={updateProfileError} />}
5455
<TextField
5556
disabled
5657
fullWidth
@@ -67,8 +68,6 @@ export const AccountForm: FC<AccountFormProps> = ({
6768
variant="outlined"
6869
/>
6970

70-
{error && <FormHelperText error>{error}</FormHelperText>}
71-
7271
<div>
7372
<LoadingButton loading={isLoading} type="submit" variant="contained">
7473
{isLoading ? "" : Language.updateSettings}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Story } from "@storybook/react"
2+
import { SecurityForm, SecurityFormProps } from "./SettingsSecurityForm"
3+
4+
export default {
5+
title: "components/SettingsSecurityForm",
6+
component: SecurityForm,
7+
argTypes: {
8+
onSubmit: { action: "Submit" },
9+
},
10+
}
11+
12+
const Template: Story<SecurityFormProps> = (args: SecurityFormProps) => <SecurityForm {...args} />
13+
14+
export const Example = Template.bind({})
15+
Example.args = {
16+
isLoading: false,
17+
initialValues: {
18+
old_password: "",
19+
password: "",
20+
confirm_password: "",
21+
},
22+
updateSecurityError: undefined,
23+
onSubmit: () => {
24+
return Promise.resolve()
25+
},
26+
}
27+
28+
export const Loading = Template.bind({})
29+
Loading.args = {
30+
...Example.args,
31+
isLoading: true,
32+
}
33+
34+
export const WithError = Template.bind({})
35+
WithError.args = {
36+
...Example.args,
37+
updateSecurityError: {
38+
response: {
39+
data: {
40+
message: "Old password is incorrect",
41+
validations: [
42+
{
43+
field: "old_password",
44+
detail: "Old password is incorrect.",
45+
},
46+
],
47+
},
48+
},
49+
isAxiosError: true,
50+
},
51+
initialTouched: {
52+
old_password: true,
53+
},
54+
}

site/src/components/SettingsSecurityForm/SettingsSecurityForm.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import FormHelperText from "@material-ui/core/FormHelperText"
21
import TextField from "@material-ui/core/TextField"
3-
import { FormikContextType, FormikErrors, useFormik } from "formik"
2+
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
3+
import { FormikContextType, FormikTouched, useFormik } from "formik"
44
import React from "react"
55
import * as Yup from "yup"
6-
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
6+
import { getFormHelpersWithError, onChangeTrimmed } from "../../util/formUtils"
77
import { LoadingButton } from "../LoadingButton/LoadingButton"
88
import { Stack } from "../Stack/Stack"
99

@@ -40,33 +40,35 @@ const validationSchema = Yup.object({
4040
}),
4141
})
4242

43-
export type SecurityFormErrors = FormikErrors<SecurityFormValues>
4443
export interface SecurityFormProps {
4544
isLoading: boolean
4645
initialValues: SecurityFormValues
4746
onSubmit: (values: SecurityFormValues) => void
48-
formErrors?: SecurityFormErrors
49-
error?: string
47+
updateSecurityError?: Error | unknown
48+
// initialTouched is only used for testing the error state of the form.
49+
initialTouched?: FormikTouched<SecurityFormValues>
5050
}
5151

5252
export const SecurityForm: React.FC<SecurityFormProps> = ({
5353
isLoading,
5454
onSubmit,
5555
initialValues,
56-
formErrors = {},
57-
error,
56+
updateSecurityError,
57+
initialTouched,
5858
}) => {
5959
const form: FormikContextType<SecurityFormValues> = useFormik<SecurityFormValues>({
6060
initialValues,
6161
validationSchema,
6262
onSubmit,
63+
initialTouched,
6364
})
64-
const getFieldHelpers = getFormHelpers<SecurityFormValues>(form, formErrors)
65+
const getFieldHelpers = getFormHelpersWithError<SecurityFormValues>(form, updateSecurityError)
6566

6667
return (
6768
<>
6869
<form onSubmit={form.handleSubmit}>
6970
<Stack>
71+
{updateSecurityError && <ErrorSummary error={updateSecurityError} />}
7072
<TextField
7173
{...getFieldHelpers("old_password")}
7274
onChange={onChangeTrimmed(form)}
@@ -95,8 +97,6 @@ export const SecurityForm: React.FC<SecurityFormProps> = ({
9597
type="password"
9698
/>
9799

98-
{error && <FormHelperText error>{error}</FormHelperText>}
99-
100100
<div>
101101
<LoadingButton loading={isLoading} type="submit" variant="contained">
102102
{isLoading ? "" : Language.updatePassword}

site/src/components/SignInForm/SignInForm.stories.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export default {
66
component: SignInForm,
77
argTypes: {
88
isLoading: "boolean",
9-
authErrorMessage: "string",
109
onSubmit: { action: "Submit" },
1110
},
1211
}
@@ -16,7 +15,7 @@ const Template: Story<SignInFormProps> = (args: SignInFormProps) => <SignInForm
1615
export const SignedOut = Template.bind({})
1716
SignedOut.args = {
1817
isLoading: false,
19-
authErrorMessage: undefined,
18+
authError: undefined,
2019
onSubmit: () => {
2120
return Promise.resolve()
2221
},
@@ -33,12 +32,31 @@ Loading.args = {
3332
}
3433

3534
export const WithLoginError = Template.bind({})
36-
WithLoginError.args = { ...SignedOut.args, authErrorMessage: "Email or password was invalid" }
35+
WithLoginError.args = {
36+
...SignedOut.args,
37+
authError: {
38+
response: {
39+
data: {
40+
message: "Email or password was invalid",
41+
validations: [
42+
{
43+
field: "password",
44+
detail: "Password is invalid.",
45+
},
46+
],
47+
},
48+
},
49+
isAxiosError: true,
50+
},
51+
initialTouched: {
52+
password: true,
53+
},
54+
}
3755

3856
export const WithAuthMethodsError = Template.bind({})
3957
WithAuthMethodsError.args = {
4058
...SignedOut.args,
41-
methodsErrorMessage: "Failed to fetch auth methods",
59+
methodsError: new Error("Failed to fetch auth methods"),
4260
}
4361

4462
export const WithGithub = Template.bind({})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy