Skip to content

Commit 865c1c5

Browse files
committed
add stories, refactor code
1 parent c37b4ec commit 865c1c5

File tree

6 files changed

+170
-81
lines changed

6 files changed

+170
-81
lines changed

site/src/components/SignInForm/SignInForm.tsx

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,24 @@ interface BuiltInAuthFormValues {
2323
password: string
2424
}
2525

26+
export enum LoginErrors {
27+
AUTH_ERROR = "authError",
28+
GET_USER_ERROR = "getUserError",
29+
CHECK_PERMISSIONS_ERROR = "checkPermissionsError",
30+
GET_METHODS_ERROR = "getMethodsError",
31+
}
32+
2633
export const Language = {
2734
emailLabel: "Email",
2835
passwordLabel: "Password",
2936
emailInvalid: "Please enter a valid email address.",
3037
emailRequired: "Please enter an email address.",
31-
authErrorMessage: "Incorrect email or password.",
32-
getUserErrorMessage: "Unable to fetch user details.",
33-
checkPermissionsErrorMessage: "Unable to fetch user permissions.",
34-
getMethodsErrorMessage: "Unable to fetch auth methods.",
38+
errorMessages: {
39+
[LoginErrors.AUTH_ERROR]: "Incorrect email or password.",
40+
[LoginErrors.GET_USER_ERROR]: "Unable to fetch user details.",
41+
[LoginErrors.CHECK_PERMISSIONS_ERROR]: "Unable to fetch user permissions.",
42+
[LoginErrors.GET_METHODS_ERROR]: "Unable to fetch auth methods.",
43+
},
3544
passwordSignIn: "Sign In",
3645
githubSignIn: "GitHub",
3746
}
@@ -67,17 +76,10 @@ const useStyles = makeStyles((theme) => ({
6776
},
6877
}))
6978

70-
type LoginErrors = {
71-
authError?: Error | unknown
72-
getUserError?: Error | unknown
73-
checkPermissionsError?: Error | unknown
74-
getMethodsError?: Error | unknown
75-
}
76-
7779
export interface SignInFormProps {
7880
isLoading: boolean
7981
redirectTo: string
80-
loginErrors: LoginErrors
82+
loginErrors: Record<LoginErrors, Error | unknown>
8183
authMethods?: AuthMethods
8284
onSubmit: ({ email, password }: { email: string; password: string }) => Promise<void>
8385
// initialTouched is only used for testing the error state of the form.
@@ -118,29 +120,14 @@ export const SignInForm: FC<SignInFormProps> = ({
118120
<Welcome />
119121
<form onSubmit={form.handleSubmit}>
120122
<Stack>
121-
{loginErrors.authError && (
122-
<ErrorSummary
123-
error={loginErrors.authError}
124-
defaultMessage={Language.authErrorMessage}
125-
/>
126-
)}
127-
{loginErrors.getUserError && (
128-
<ErrorSummary
129-
error={loginErrors.getUserError}
130-
defaultMessage={Language.getUserErrorMessage}
131-
/>
132-
)}
133-
{loginErrors.checkPermissionsError && (
134-
<ErrorSummary
135-
error={loginErrors.checkPermissionsError}
136-
defaultMessage={Language.checkPermissionsErrorMessage}
137-
/>
138-
)}
139-
{loginErrors.getMethodsError && (
140-
<ErrorSummary
141-
error={loginErrors.getMethodsError}
142-
defaultMessage={Language.getMethodsErrorMessage}
143-
/>
123+
{Object.keys(loginErrors).map((errorKey: string) =>
124+
loginErrors[errorKey as LoginErrors] ? (
125+
<ErrorSummary
126+
key={errorKey}
127+
error={loginErrors[errorKey as LoginErrors]}
128+
defaultMessage={Language.errorMessages[errorKey as LoginErrors]}
129+
/>
130+
) : null,
144131
)}
145132
<TextField
146133
{...getFieldHelpers("email")}

site/src/pages/LoginPage/LoginPage.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe("LoginPage", () => {
3030
server.use(
3131
// Make login fail
3232
rest.post("/api/v2/users/login", async (req, res, ctx) => {
33-
return res(ctx.status(500), ctx.json({ message: Language.authErrorMessage }))
33+
return res(ctx.status(500), ctx.json({ message: Language.errorMessages.authError }))
3434
}),
3535
)
3636

@@ -45,7 +45,7 @@ describe("LoginPage", () => {
4545
act(() => signInButton.click())
4646

4747
// Then
48-
const errorMessage = await screen.findByText(Language.authErrorMessage)
48+
const errorMessage = await screen.findByText(Language.errorMessages.authError)
4949
expect(errorMessage).toBeDefined()
5050
expect(history.location.pathname).toEqual("/login")
5151
})

site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.test.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { GlobalSnackbar } from "../../../components/GlobalSnackbar/GlobalSnackba
44
import { MockGitSSHKey, renderWithAuth } from "../../../testHelpers/renderHelpers"
55
import { Language as authXServiceLanguage } from "../../../xServices/auth/authXService"
66
import { Language as SSHKeysPageLanguage, SSHKeysPage } from "./SSHKeysPage"
7+
import { Language as SSHKeysPageViewLanguage } from "./SSHKeysPageView"
78

89
describe("SSH keys Page", () => {
910
it("shows the SSH key", async () => {
@@ -26,7 +27,7 @@ describe("SSH keys Page", () => {
2627

2728
// Click on the "Regenerate" button to display the confirm dialog
2829
const regenerateButton = screen.getByRole("button", {
29-
name: SSHKeysPageLanguage.regenerateLabel,
30+
name: SSHKeysPageViewLanguage.regenerateLabel,
3031
})
3132
fireEvent.click(regenerateButton)
3233
const confirmDialog = screen.getByRole("dialog")
@@ -72,7 +73,7 @@ describe("SSH keys Page", () => {
7273

7374
// Click on the "Regenerate" button to display the confirm dialog
7475
const regenerateButton = screen.getByRole("button", {
75-
name: SSHKeysPageLanguage.regenerateLabel,
76+
name: SSHKeysPageViewLanguage.regenerateLabel,
7677
})
7778
fireEvent.click(regenerateButton)
7879
const confirmDialog = screen.getByRole("dialog")
@@ -85,7 +86,7 @@ describe("SSH keys Page", () => {
8586
fireEvent.click(confirmButton)
8687

8788
// Check if the error message is displayed
88-
await screen.findByText(SSHKeysPageLanguage.errorRegenerateSSHKey)
89+
await screen.findByText(SSHKeysPageViewLanguage.errorRegenerateSSHKey)
8990

9091
// Check if the API was called correctly
9192
expect(API.regenerateUserSSHKey).toBeCalledTimes(1)

site/src/pages/UserSettingsPage/SSHKeysPage/SSHKeysPage.tsx

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
1-
import Box from "@material-ui/core/Box"
2-
import Button from "@material-ui/core/Button"
3-
import CircularProgress from "@material-ui/core/CircularProgress"
41
import { useActor } from "@xstate/react"
5-
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
62
import React, { useContext, useEffect } from "react"
7-
import { CodeExample } from "../../../components/CodeExample/CodeExample"
83
import { ConfirmDialog } from "../../../components/ConfirmDialog/ConfirmDialog"
94
import { Section } from "../../../components/Section/Section"
10-
import { Stack } from "../../../components/Stack/Stack"
115
import { XServiceContext } from "../../../xServices/StateContext"
6+
import { SSHKeysPageView } from "./SSHKeysPageView"
127

138
export const Language = {
149
title: "SSH keys",
1510
description:
1611
"Coder automatically inserts a private key into every workspace; you can add the corresponding public key to any services (such as Git) that you need access to from your workspace.",
17-
regenerateLabel: "Regenerate",
1812
regenerateDialogTitle: "Regenerate SSH key?",
1913
regenerateDialogMessage:
2014
"You will need to replace the public SSH key on services you use it with, and you'll need to rebuild existing workspaces.",
2115
confirmLabel: "Confirm",
2216
cancelLabel: "Cancel",
23-
errorRegenerateSSHKey: "Error on regenerate the SSH Key",
2417
}
2518

2619
export const SSHKeysPage: React.FC = () => {
@@ -32,42 +25,24 @@ export const SSHKeysPage: React.FC = () => {
3225
authSend({ type: "GET_SSH_KEY" })
3326
}, [authSend])
3427

28+
const isLoading = authState.matches("signedIn.ssh.gettingSSHKey")
29+
const hasLoaded = authState.matches("signedIn.ssh.loaded")
30+
31+
const onRegenerateClick = () => {
32+
authSend({ type: "REGENERATE_SSH_KEY" })
33+
}
34+
3535
return (
3636
<>
3737
<Section title={Language.title} description={Language.description}>
38-
{authState.matches("signedIn.ssh.gettingSSHKey") && (
39-
<Box p={4}>
40-
<CircularProgress size={26} />
41-
</Box>
42-
)}
43-
44-
<Stack>
45-
{/* Regenerating the key is not an option if getSSHKey fails.
46-
Only one of the error messages will exist at a single time */}
47-
{getSSHKeyError && <ErrorSummary error={getSSHKeyError} />}
48-
{regenerateSSHKeyError && (
49-
<ErrorSummary
50-
error={regenerateSSHKeyError}
51-
defaultMessage={Language.errorRegenerateSSHKey}
52-
dismissible
53-
/>
54-
)}
55-
{authState.matches("signedIn.ssh.loaded") && sshKey && (
56-
<>
57-
<CodeExample code={sshKey.public_key.trim()} />
58-
<div>
59-
<Button
60-
variant="outlined"
61-
onClick={() => {
62-
authSend({ type: "REGENERATE_SSH_KEY" })
63-
}}
64-
>
65-
{Language.regenerateLabel}
66-
</Button>
67-
</div>
68-
</>
69-
)}
70-
</Stack>
38+
<SSHKeysPageView
39+
isLoading={isLoading}
40+
hasLoaded={hasLoaded}
41+
getSSHKeyError={getSSHKeyError}
42+
regenerateSSHKeyError={regenerateSSHKeyError}
43+
sshKey={sshKey}
44+
onRegenerateClick={onRegenerateClick}
45+
/>
7146
</Section>
7247

7348
<ConfirmDialog
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Story } from "@storybook/react"
2+
import { SSHKeysPageView, SSHKeysPageViewProps } from "./SSHKeysPageView"
3+
4+
export default {
5+
title: "components/SSHKeysPageView",
6+
component: SSHKeysPageView,
7+
argTypes: {
8+
onRegenerateClick: { action: "Submit" },
9+
},
10+
}
11+
12+
const Template: Story<SSHKeysPageViewProps> = (args: SSHKeysPageViewProps) => (
13+
<SSHKeysPageView {...args} />
14+
)
15+
16+
export const Example = Template.bind({})
17+
Example.args = {
18+
isLoading: false,
19+
hasLoaded: true,
20+
sshKey: {
21+
user_id: "test-user-id",
22+
created_at: "2022-07-28T07:45:50.795918897Z",
23+
updated_at: "2022-07-28T07:45:50.795919142Z",
24+
public_key: "SSH-Key",
25+
},
26+
onRegenerateClick: () => {
27+
return Promise.resolve()
28+
},
29+
}
30+
31+
export const Loading = Template.bind({})
32+
Loading.args = {
33+
...Example.args,
34+
isLoading: true,
35+
}
36+
37+
export const WithGetSSHKeyError = Template.bind({})
38+
WithGetSSHKeyError.args = {
39+
...Example.args,
40+
hasLoaded: false,
41+
getSSHKeyError: {
42+
response: {
43+
data: {
44+
message: "Failed to get SSH key",
45+
},
46+
},
47+
isAxiosError: true,
48+
},
49+
}
50+
51+
export const WithRegenerateSSHKeyError = Template.bind({})
52+
WithRegenerateSSHKeyError.args = {
53+
...Example.args,
54+
regenerateSSHKeyError: {
55+
response: {
56+
data: {
57+
message: "Failed to regenerate SSH key",
58+
},
59+
},
60+
isAxiosError: true,
61+
},
62+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Box from "@material-ui/core/Box"
2+
import Button from "@material-ui/core/Button"
3+
import CircularProgress from "@material-ui/core/CircularProgress"
4+
import { GitSSHKey } from "api/typesGenerated"
5+
import { CodeExample } from "components/CodeExample/CodeExample"
6+
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
7+
import { Stack } from "components/Stack/Stack"
8+
import { FC } from "react"
9+
10+
export const Language = {
11+
errorRegenerateSSHKey: "Error on regenerating the SSH Key",
12+
regenerateLabel: "Regenerate",
13+
}
14+
15+
export interface SSHKeysPageViewProps {
16+
isLoading: boolean
17+
hasLoaded: boolean
18+
getSSHKeyError?: Error | unknown
19+
regenerateSSHKeyError?: Error | unknown
20+
sshKey?: GitSSHKey
21+
onRegenerateClick: () => void
22+
}
23+
24+
export const SSHKeysPageView: FC<SSHKeysPageViewProps> = ({
25+
isLoading,
26+
hasLoaded,
27+
getSSHKeyError,
28+
regenerateSSHKeyError,
29+
sshKey,
30+
onRegenerateClick,
31+
}) => {
32+
if (isLoading) {
33+
return (
34+
<Box p={4}>
35+
<CircularProgress size={26} />
36+
</Box>
37+
)
38+
}
39+
40+
return (
41+
<Stack>
42+
{/* Regenerating the key is not an option if getSSHKey fails.
43+
Only one of the error messages will exist at a single time */}
44+
{getSSHKeyError && <ErrorSummary error={getSSHKeyError} />}
45+
{regenerateSSHKeyError && (
46+
<ErrorSummary
47+
error={regenerateSSHKeyError}
48+
defaultMessage={Language.errorRegenerateSSHKey}
49+
dismissible
50+
/>
51+
)}
52+
{hasLoaded && sshKey && (
53+
<>
54+
<CodeExample code={sshKey.public_key.trim()} />
55+
<div>
56+
<Button variant="outlined" onClick={onRegenerateClick}>
57+
{Language.regenerateLabel}
58+
</Button>
59+
</div>
60+
</>
61+
)}
62+
</Stack>
63+
)
64+
}

0 commit comments

Comments
 (0)