Skip to content

Commit b1a28eb

Browse files
authored
Show and hide password for better usability (#333)
1 parent 52b8a29 commit b1a28eb

File tree

6 files changed

+95
-52
lines changed

6 files changed

+95
-52
lines changed

integration_tests/pages/LoginPage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ export class LoginPage extends BasePage {
44
email = this.page.getByTestId("email");
55
password = this.page.getByTestId("password");
66
loginBtn = this.page.getByTestId("loginBtn");
7+
showPasswordBtn = this.page.locator("[aria-label='toggle password visibility']");
78
}
9+

integration_tests/test/login.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@ test("can login", async ({ loginPage }) => {
1919
password: "123456",
2020
});
2121
});
22+
23+
test("show and hide password", async ({ loginPage }) => {
24+
await loginPage.password.type("123456");
25+
26+
await loginPage.showPasswordBtn.click();
27+
expect(await loginPage.password.getAttribute('type')).toBe('text');
28+
29+
await loginPage.showPasswordBtn.click();
30+
expect(await loginPage.password.getAttribute('type')).toBe('password');
31+
});

src/components/LoginForm.tsx

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useUserDispatch, login } from "../contexts";
1212
import { LOCATOR_LOGIN_FORM, routes } from "../constants";
1313
import { useSnackbar } from "notistack";
1414
import { TextValidator, ValidatorForm } from "react-material-ui-form-validator";
15+
import PasswordField from "../components/PasswordField";
1516

1617
const LoginForm = () => {
1718
const location = useLocation();
@@ -36,8 +37,6 @@ const LoginForm = () => {
3637
);
3738
};
3839

39-
const errorForTwoChar = "Enter at least two characters.";
40-
4140
return (
4241
<ValidatorForm onSubmit={handleSubmit} instantValidate>
4342
<Card variant="outlined">
@@ -64,22 +63,9 @@ const LoginForm = () => {
6463
</Grid>
6564

6665
<Grid item>
67-
<TextValidator
68-
validators={["minStringLength:2"]}
69-
errorMessages={[errorForTwoChar]}
70-
id="password"
71-
name="password"
72-
value={password}
73-
label={"Password"}
74-
type="password"
75-
variant="outlined"
76-
required
77-
fullWidth
78-
inputProps={{
79-
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
80-
setPassword(event.target.value),
81-
"data-testid": "password",
82-
}}
66+
<PasswordField
67+
password={password}
68+
setPassword={setPassword}
8369
/>
8470
</Grid>
8571
</Grid>

src/components/PasswordField.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { useState } from "react";
2+
import {
3+
IconButton,
4+
InputAdornment,
5+
} from "@mui/material";
6+
import { Visibility, VisibilityOff } from "@mui/icons-material";
7+
import { TextValidator } from "react-material-ui-form-validator";
8+
9+
interface PasswordFieldProps {
10+
password: string;
11+
setPassword: React.Dispatch<React.SetStateAction<string>>;
12+
label?: string;
13+
}
14+
15+
const PasswordField = ({password, setPassword, label = "Password" }: PasswordFieldProps) => {
16+
const [showPassword, setShowPassword] = useState<boolean>(false);
17+
18+
const handleClickShowPassword = () => setShowPassword((show) => !show);
19+
20+
const handleMouseDownPassword = (
21+
event: React.MouseEvent<HTMLButtonElement>,
22+
) => {
23+
event.preventDefault();
24+
};
25+
26+
const errorForTwoChar = "Enter at least two characters.";
27+
28+
return (
29+
<TextValidator
30+
validators={["minStringLength:2"]}
31+
errorMessages={[errorForTwoChar]}
32+
id="password"
33+
name="password"
34+
value={password}
35+
label={label}
36+
type={showPassword ? "text" : "password"}
37+
InputProps={{
38+
endAdornment: (
39+
<InputAdornment position="end">
40+
<IconButton
41+
aria-label="toggle password visibility"
42+
onClick={handleClickShowPassword}
43+
onMouseDown={handleMouseDownPassword}
44+
edge="end"
45+
>
46+
{showPassword ? <VisibilityOff /> : <Visibility />}
47+
</IconButton>
48+
</InputAdornment>
49+
),
50+
}}
51+
variant="outlined"
52+
required
53+
fullWidth
54+
inputProps={{
55+
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
56+
setPassword(event.target.value),
57+
"data-testid": "password",
58+
}}
59+
/>
60+
);
61+
};
62+
63+
export default PasswordField;

src/components/RegisterForm.tsx

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import React, { useState, FormEvent } from "react";
2-
import { Button, Grid, Card, CardContent, CardActions } from "@mui/material";
2+
import {
3+
Button,
4+
Grid,
5+
Card,
6+
CardContent,
7+
CardActions,
8+
} from "@mui/material";
39
import { useUserDispatch, login } from "../contexts";
410
import { usersService } from "../services";
511
import { useSnackbar } from "notistack";
612
import { TextValidator, ValidatorForm } from "react-material-ui-form-validator";
713
import { useNavigate } from "react-router-dom";
814
import { routes } from "../constants";
15+
import PasswordField from "../components/PasswordField";
916

1017
const RegisterForm = () => {
1118
const navigate = useNavigate();
@@ -32,7 +39,7 @@ const RegisterForm = () => {
3239
}),
3340
);
3441
};
35-
42+
3643
const errorForTwoChar = "Enter at least two characters.";
3744

3845
return (
@@ -98,22 +105,9 @@ const RegisterForm = () => {
98105
/>
99106
</Grid>
100107
<Grid item>
101-
<TextValidator
102-
validators={["minStringLength:2"]}
103-
errorMessages={[errorForTwoChar]}
104-
id="password"
105-
name="password"
106-
value={password}
107-
label={"Password"}
108-
type="password"
109-
variant="outlined"
110-
required
111-
fullWidth
112-
inputProps={{
113-
onChange: (event: React.FormEvent<HTMLInputElement>) =>
114-
setPassword(event.target.value),
115-
"data-testid": "password",
116-
}}
108+
<PasswordField
109+
password={password}
110+
setPassword={setPassword}
117111
/>
118112
</Grid>
119113
</Grid>

src/pages/ProfilePage.tsx

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { useSnackbar } from "notistack";
2727
import ProjectSelect from "../components/ProjectSelect";
2828
import { TextValidator, ValidatorForm } from "react-material-ui-form-validator";
2929
import { Role } from "../types";
30+
import PasswordField from "../components/PasswordField";
3031

3132
const ProfilePage = () => {
3233
const { enqueueSnackbar } = useSnackbar();
@@ -201,23 +202,10 @@ const ProfilePage = () => {
201202
<CardContent>
202203
<Grid container spacing={2}>
203204
<Grid item xs={12}>
204-
<TextValidator
205-
validators={["minStringLength:2"]}
206-
errorMessages={[errorForTwoChar]}
207-
id="password"
208-
name="password"
209-
value={password}
205+
<PasswordField
206+
password={password}
207+
setPassword={setPassword}
210208
label={"New password"}
211-
type="password"
212-
variant="outlined"
213-
required
214-
fullWidth
215-
inputProps={{
216-
onChange: (
217-
event: React.ChangeEvent<HTMLInputElement>,
218-
) => setPassword(event.target.value),
219-
"data-testid": "password",
220-
}}
221209
/>
222210
</Grid>
223211
</Grid>

0 commit comments

Comments
 (0)