Skip to content

Commit fbec79f

Browse files
refactor(site): refactor login screen (#10768)
1 parent 2895c10 commit fbec79f

11 files changed

+77
-147
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
"wsconncache",
171171
"wsjson",
172172
"xerrors",
173+
"xlarge",
173174
"yamux"
174175
],
175176
"cSpell.ignorePaths": ["site/package.json", ".vscode/settings.json"],

site/src/@types/mui.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ declare module "@mui/material/Button" {
3131
interface ButtonPropsColorOverrides {
3232
neutral: true;
3333
}
34+
35+
interface ButtonPropsSizeOverrides {
36+
xlarge: true;
37+
}
3438
}
3539

3640
declare module "@mui/system" {

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

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
} from "testHelpers/renderHelpers";
1111
import { server } from "testHelpers/server";
1212
import { LoginPage } from "./LoginPage";
13-
import * as TypesGen from "api/typesGenerated";
1413

1514
describe("LoginPage", () => {
1615
beforeEach(() => {
@@ -76,32 +75,4 @@ describe("LoginPage", () => {
7675
// Then
7776
await screen.findByText("Setup");
7877
});
79-
80-
it("hides password authentication if OIDC/GitHub is enabled and displays on click", async () => {
81-
const authMethods: TypesGen.AuthMethods = {
82-
password: { enabled: true },
83-
github: { enabled: true },
84-
oidc: { enabled: true, signInText: "", iconUrl: "" },
85-
};
86-
87-
// Given
88-
server.use(
89-
rest.get("/api/v2/users/authmethods", async (req, res, ctx) => {
90-
return res(ctx.status(200), ctx.json(authMethods));
91-
}),
92-
);
93-
94-
// When
95-
render(<LoginPage />);
96-
97-
// Then
98-
expect(screen.queryByText(Language.passwordSignIn)).not.toBeInTheDocument();
99-
await screen.findByText(Language.githubSignIn);
100-
101-
const showPasswordAuthLink = screen.getByText("Email and password");
102-
await userEvent.click(showPasswordAuthLink);
103-
104-
await screen.findByText(Language.passwordSignIn);
105-
await screen.findByText(Language.githubSignIn);
106-
});
10778
});

site/src/pages/LoginPage/LoginPageView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ const styles = {
7575

7676
container: {
7777
width: "100%",
78-
maxWidth: 385,
78+
maxWidth: 320,
7979
display: "flex",
8080
flexDirection: "column",
8181
alignItems: "center",

site/src/pages/LoginPage/OAuthSignInForm.tsx

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import Link from "@mui/material/Link";
21
import Button from "@mui/material/Button";
32
import GitHubIcon from "@mui/icons-material/GitHub";
43
import KeyIcon from "@mui/icons-material/VpnKey";
@@ -26,49 +25,47 @@ export const OAuthSignInForm: FC<OAuthSignInFormProps> = ({
2625
return (
2726
<Box display="grid" gap="16px">
2827
{authMethods?.github.enabled && (
29-
<Link
28+
<Button
29+
component="a"
3030
href={`/api/v2/users/oauth2/github/callback?redirect=${encodeURIComponent(
3131
redirectTo,
3232
)}`}
33+
variant="contained"
34+
startIcon={<GitHubIcon css={iconStyles} />}
35+
disabled={isSigningIn}
36+
fullWidth
37+
type="submit"
38+
size="xlarge"
3339
>
34-
<Button
35-
startIcon={<GitHubIcon css={iconStyles} />}
36-
disabled={isSigningIn}
37-
fullWidth
38-
type="submit"
39-
size="large"
40-
>
41-
{Language.githubSignIn}
42-
</Button>
43-
</Link>
40+
{Language.githubSignIn}
41+
</Button>
4442
)}
4543

4644
{authMethods?.oidc.enabled && (
47-
<Link
45+
<Button
46+
component="a"
4847
href={`/api/v2/users/oidc/callback?redirect=${encodeURIComponent(
4948
redirectTo,
5049
)}`}
50+
variant="contained"
51+
size="xlarge"
52+
startIcon={
53+
authMethods.oidc.iconUrl ? (
54+
<img
55+
alt="Open ID Connect icon"
56+
src={authMethods.oidc.iconUrl}
57+
css={iconStyles}
58+
/>
59+
) : (
60+
<KeyIcon css={iconStyles} />
61+
)
62+
}
63+
disabled={isSigningIn}
64+
fullWidth
65+
type="submit"
5166
>
52-
<Button
53-
size="large"
54-
startIcon={
55-
authMethods.oidc.iconUrl ? (
56-
<img
57-
alt="Open ID Connect icon"
58-
src={authMethods.oidc.iconUrl}
59-
css={iconStyles}
60-
/>
61-
) : (
62-
<KeyIcon css={iconStyles} />
63-
)
64-
}
65-
disabled={isSigningIn}
66-
fullWidth
67-
type="submit"
68-
>
69-
{authMethods.oidc.signInText || Language.oidcSignIn}
70-
</Button>
71-
</Link>
67+
{authMethods.oidc.signInText || Language.oidcSignIn}
68+
</Button>
7269
)}
7370
</Box>
7471
);

site/src/pages/LoginPage/PasswordSignInForm.tsx

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@ import { Stack } from "components/Stack/Stack";
22
import TextField from "@mui/material/TextField";
33
import { getFormHelpers, onChangeTrimmed } from "utils/formUtils";
44
import { Language } from "./SignInForm";
5-
import { FormikContextType, FormikTouched, useFormik } from "formik";
5+
import { useFormik } from "formik";
66
import * as Yup from "yup";
77
import { FC } from "react";
8-
import { BuiltInAuthFormValues } from "./SignInForm.types";
98
import LoadingButton from "@mui/lab/LoadingButton";
109

1110
type PasswordSignInFormProps = {
1211
onSubmit: (credentials: { email: string; password: string }) => void;
13-
initialTouched?: FormikTouched<BuiltInAuthFormValues>;
1412
isSigningIn: boolean;
13+
autoFocus: boolean;
1514
};
1615

1716
export const PasswordSignInForm: FC<PasswordSignInFormProps> = ({
1817
onSubmit,
19-
initialTouched,
2018
isSigningIn,
19+
autoFocus,
2120
}) => {
2221
const validationSchema = Yup.object({
2322
email: Yup.string()
@@ -27,25 +26,24 @@ export const PasswordSignInForm: FC<PasswordSignInFormProps> = ({
2726
password: Yup.string(),
2827
});
2928

30-
const form: FormikContextType<BuiltInAuthFormValues> =
31-
useFormik<BuiltInAuthFormValues>({
32-
initialValues: {
33-
email: "",
34-
password: "",
35-
},
36-
validationSchema,
37-
onSubmit,
38-
initialTouched,
39-
});
40-
const getFieldHelpers = getFormHelpers<BuiltInAuthFormValues>(form);
29+
const form = useFormik({
30+
initialValues: {
31+
email: "",
32+
password: "",
33+
},
34+
validationSchema,
35+
onSubmit,
36+
validateOnBlur: false,
37+
});
38+
const getFieldHelpers = getFormHelpers(form);
4139

4240
return (
4341
<form onSubmit={form.handleSubmit}>
4442
<Stack spacing={2.5}>
4543
<TextField
4644
{...getFieldHelpers("email")}
4745
onChange={onChangeTrimmed(form)}
48-
autoFocus
46+
autoFocus={autoFocus}
4947
autoComplete="email"
5048
fullWidth
5149
label={Language.emailLabel}
@@ -59,16 +57,14 @@ export const PasswordSignInForm: FC<PasswordSignInFormProps> = ({
5957
label={Language.passwordLabel}
6058
type="password"
6159
/>
62-
<div>
63-
<LoadingButton
64-
size="large"
65-
loading={isSigningIn}
66-
fullWidth
67-
type="submit"
68-
>
69-
{Language.passwordSignIn}
70-
</LoadingButton>
71-
</div>
60+
<LoadingButton
61+
size="xlarge"
62+
loading={isSigningIn}
63+
fullWidth
64+
type="submit"
65+
>
66+
{Language.passwordSignIn}
67+
</LoadingButton>
7268
</Stack>
7369
</form>
7470
);

site/src/pages/LoginPage/SignInForm.stories.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,6 @@ export const WithError: Story = {
3737
},
3838
],
3939
}),
40-
initialTouched: {
41-
password: true,
42-
},
4340
},
4441
};
4542

site/src/pages/LoginPage/SignInForm.tsx

Lines changed: 11 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import { type Interpolation, type Theme } from "@emotion/react";
2-
import { type FormikTouched } from "formik";
3-
import { type FC, useState } from "react";
2+
import { type FC } from "react";
43
import type { AuthMethods } from "api/typesGenerated";
54
import { PasswordSignInForm } from "./PasswordSignInForm";
65
import { OAuthSignInForm } from "./OAuthSignInForm";
7-
import { type BuiltInAuthFormValues } from "./SignInForm.types";
8-
import Button from "@mui/material/Button";
9-
import EmailIcon from "@mui/icons-material/EmailOutlined";
106
import { Alert } from "components/Alert/Alert";
117
import { ErrorAlert } from "components/Alert/ErrorAlert";
12-
import { getApplicationName } from "utils/appearance";
138

149
export const Language = {
1510
emailLabel: "Email",
@@ -71,8 +66,6 @@ export interface SignInFormProps {
7166
info?: string;
7267
authMethods?: AuthMethods;
7368
onSubmit: (credentials: { email: string; password: string }) => void;
74-
// initialTouched is only used for testing the error state of the form.
75-
initialTouched?: FormikTouched<BuiltInAuthFormValues>;
7669
}
7770

7871
export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
@@ -82,21 +75,15 @@ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
8275
error,
8376
info,
8477
onSubmit,
85-
initialTouched,
8678
}) => {
8779
const oAuthEnabled = Boolean(
8880
authMethods?.github.enabled || authMethods?.oidc.enabled,
8981
);
9082
const passwordEnabled = authMethods?.password.enabled ?? true;
91-
// Hide password auth by default if any OAuth method is enabled
92-
const [showPasswordAuth, setShowPasswordAuth] = useState(!oAuthEnabled);
93-
const applicationName = getApplicationName();
9483

9584
return (
9685
<div css={styles.root}>
97-
<h1 css={styles.title}>
98-
Sign in to <strong>{applicationName}</strong>
99-
</h1>
86+
<h1 css={styles.title}>Sign in</h1>
10087

10188
{Boolean(error) && (
10289
<div css={styles.alert}>
@@ -110,52 +97,33 @@ export const SignInForm: FC<React.PropsWithChildren<SignInFormProps>> = ({
11097
</div>
11198
)}
11299

113-
{passwordEnabled && showPasswordAuth && (
114-
<PasswordSignInForm
115-
onSubmit={onSubmit}
116-
initialTouched={initialTouched}
100+
{oAuthEnabled && (
101+
<OAuthSignInForm
117102
isSigningIn={isSigningIn}
103+
redirectTo={redirectTo}
104+
authMethods={authMethods}
118105
/>
119106
)}
120107

121-
{passwordEnabled && showPasswordAuth && oAuthEnabled && (
108+
{passwordEnabled && oAuthEnabled && (
122109
<div css={styles.divider}>
123110
<div css={styles.dividerLine} />
124111
<div css={styles.dividerLabel}>Or</div>
125112
<div css={styles.dividerLine} />
126113
</div>
127114
)}
128115

129-
{oAuthEnabled && (
130-
<OAuthSignInForm
116+
{passwordEnabled && (
117+
<PasswordSignInForm
118+
onSubmit={onSubmit}
119+
autoFocus={!oAuthEnabled}
131120
isSigningIn={isSigningIn}
132-
redirectTo={redirectTo}
133-
authMethods={authMethods}
134121
/>
135122
)}
136123

137124
{!passwordEnabled && !oAuthEnabled && (
138125
<Alert severity="error">No authentication methods configured!</Alert>
139126
)}
140-
141-
{passwordEnabled && !showPasswordAuth && (
142-
<>
143-
<div css={styles.divider}>
144-
<div css={styles.dividerLine} />
145-
<div css={styles.dividerLabel}>Or</div>
146-
<div css={styles.dividerLine} />
147-
</div>
148-
149-
<Button
150-
fullWidth
151-
size="large"
152-
onClick={() => setShowPasswordAuth(true)}
153-
startIcon={<EmailIcon css={styles.icon} />}
154-
>
155-
Email and password
156-
</Button>
157-
</>
158-
)}
159127
</div>
160128
);
161129
};

site/src/pages/LoginPage/SignInForm.types.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

site/src/theme/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const sidePadding = 24;
99
export const dashboardContentBottomPadding = 8 * 6;
1010

1111
// MUI does not have aligned heights for buttons and inputs so we have to "hack" it a little bit
12+
export const BUTTON_XL_HEIGHT = 44;
1213
export const BUTTON_LG_HEIGHT = 40;
1314
export const BUTTON_MD_HEIGHT = 36;
1415
export const BUTTON_SM_HEIGHT = 32;

0 commit comments

Comments
 (0)