-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fe22ef5
commit d49f985
Showing
12 changed files
with
341 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
@import "scss/vars"; | ||
@import "scss/mixins"; | ||
|
||
.root { | ||
position: relative; | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
gap: 1rem; | ||
padding: 0.5rem 1rem; | ||
margin-bottom: 2rem; | ||
color: #fff; | ||
border: 1px solid; | ||
border-radius: 3px; | ||
|
||
&.error { | ||
background: rgba($colorDanger, 0.8); | ||
border-color: $colorDanger; | ||
} | ||
|
||
&.success { | ||
background: rgba($colorAccent, 0.8); | ||
border-color: $colorAccent; | ||
} | ||
|
||
@include shadow(low); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'; | ||
import React from 'react'; | ||
import { Alert } from './Alert'; | ||
|
||
describe('Alert', () => { | ||
it('renders the given message', async () => { | ||
render(<Alert message="this is a message" />); | ||
|
||
await waitFor(() => | ||
expect(screen.getByText('this is a message')).toBeInTheDocument(), | ||
); | ||
}); | ||
|
||
it('calls close handler when close button is clicked', async () => { | ||
const handleClose = jest.fn(); | ||
|
||
render(<Alert message="this is a message" onClose={handleClose} />); | ||
fireEvent.click(screen.getByRole('button')); | ||
|
||
await waitFor(() => expect(handleClose).toHaveBeenCalled()); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import classNames from 'classnames'; | ||
import { CloseIcon } from 'components/icons'; | ||
import IconButton from 'components/icons/IconButton/IconButton'; | ||
import React, { ReactElement } from 'react'; | ||
import styles from './Alert.module.scss'; | ||
|
||
type Props = { | ||
message: string; | ||
onClose?: () => void; | ||
}; | ||
|
||
function BaseAlert({ | ||
message, | ||
className, | ||
onClose, | ||
}: Props & { className?: string }): ReactElement | null { | ||
if (!message.length) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className={classNames(styles.root, className)}> | ||
{message} | ||
<IconButton onClick={onClose} icon={<CloseIcon />} /> | ||
</div> | ||
); | ||
} | ||
|
||
export function ErrorAlert(props: Props): ReactElement { | ||
return <BaseAlert {...props} className={styles.error} />; | ||
} | ||
|
||
export function SuccessAlert(props: Props): ReactElement { | ||
return <BaseAlert {...props} className={styles.success} />; | ||
} | ||
|
||
export function Alert(props: Props): ReactElement { | ||
return <BaseAlert {...props} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
web/src/components/settings/PasswordChanging/PasswordChanging.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import React from 'react'; | ||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'; | ||
import PasswordChanging from './PasswordChanging'; | ||
import { rest } from 'msw'; | ||
import { setupServer } from 'msw/node'; | ||
import { ApiError, ChangePasswordRequestDto } from 'leafplayer-common'; | ||
|
||
const server = setupServer(); | ||
|
||
beforeAll(() => server.listen()); | ||
afterEach(() => server.resetHandlers()); | ||
afterAll(() => server.close()); | ||
|
||
describe('PasswordChanging', () => { | ||
it('successfully changes password', async () => { | ||
server.use( | ||
rest.post<ChangePasswordRequestDto>( | ||
'/api/auth/password', | ||
(req, res, ctx) => { | ||
return res(ctx.status(204)); | ||
}, | ||
), | ||
); | ||
|
||
render(<PasswordChanging />); | ||
|
||
await fillAndSubmitForm(); | ||
|
||
await waitFor(() => | ||
expect( | ||
screen.getByText(/you changed your password successfully/i), | ||
).toBeInTheDocument(), | ||
); | ||
}); | ||
|
||
it('displays potential errors', async () => { | ||
server.use( | ||
rest.post('/api/auth/password', (req, res, ctx) => { | ||
return res.once( | ||
ctx.status(401), | ||
ctx.json<ApiError>({ | ||
error: 'Unauthorized', | ||
message: 'invalid password', | ||
statusCode: 401, | ||
}), | ||
); | ||
}), | ||
); | ||
|
||
render(<PasswordChanging />); | ||
|
||
await fillAndSubmitForm(); | ||
|
||
await waitFor(() => | ||
expect(screen.getByText(/invalid password/i)).toBeInTheDocument(), | ||
); | ||
}); | ||
}); | ||
|
||
async function fillAndSubmitForm() { | ||
const currentPasswordInput = await screen.findByLabelText('Current Password'); | ||
const newPasswordPasswordInput = await screen.findByLabelText('New Password'); | ||
const repeatPasswordInput = await screen.findByLabelText( | ||
'Repeat New Password', | ||
); | ||
const submitButton = await screen.findByRole('button'); | ||
|
||
fireEvent.input(currentPasswordInput, { | ||
target: { value: 'supersecret' }, | ||
}); | ||
fireEvent.input(newPasswordPasswordInput, { | ||
target: { value: 'timeforachange' }, | ||
}); | ||
fireEvent.input(repeatPasswordInput, { | ||
target: { value: 'timeforachange' }, | ||
}); | ||
fireEvent.click(submitButton); | ||
} |
115 changes: 115 additions & 0 deletions
115
web/src/components/settings/PasswordChanging/PasswordChanging.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { ErrorAlert, SuccessAlert } from 'components/Alert/Alert'; | ||
import { ButtonPrimary } from 'components/form/Button/Button'; | ||
import Input from 'components/form/Input/Input'; | ||
import { useFormik } from 'formik'; | ||
import { ChangePasswordRequestDto } from 'leafplayer-common'; | ||
import { isApiError, makeApiPostRequest } from 'modules/api'; | ||
import React, { ReactElement, useState } from 'react'; | ||
|
||
function PasswordChanging(): ReactElement { | ||
const [error, setError] = useState(''); | ||
const [isSuccess, setIsSuccess] = useState(false); | ||
|
||
const formik = useFormik({ | ||
initialValues: { | ||
currentPassword: '', | ||
newPassword: '', | ||
passwordRepeat: '', | ||
}, | ||
|
||
validate: values => { | ||
const errors: { [key: string]: string } = {}; | ||
|
||
if (!values.currentPassword) { | ||
errors.inviteCode = 'Required'; | ||
} | ||
|
||
if (!values.newPassword) { | ||
errors.username = 'Required'; | ||
} | ||
|
||
if (!values.passwordRepeat) { | ||
errors.password = 'Required'; | ||
} | ||
|
||
if ( | ||
values.newPassword.length > 0 && | ||
values.passwordRepeat !== values.newPassword | ||
) { | ||
errors.passwordRepeat = 'Passwords do not match'; | ||
} | ||
|
||
return errors; | ||
}, | ||
|
||
onSubmit: async ({ currentPassword, newPassword }) => { | ||
setError(''); | ||
setIsSuccess(false); | ||
|
||
const result = await makeApiPostRequest<never, ChangePasswordRequestDto>( | ||
'auth/password', | ||
{ | ||
currentPassword, | ||
newPassword, | ||
}, | ||
); | ||
|
||
if (isApiError(result)) { | ||
setError(result.message); | ||
} else { | ||
setIsSuccess(true); | ||
} | ||
}, | ||
}); | ||
|
||
return ( | ||
<> | ||
<ErrorAlert message={error} onClose={() => setError('')} /> | ||
|
||
{isSuccess && ( | ||
<SuccessAlert | ||
message="You changed your password successfully" | ||
onClose={() => setIsSuccess(false)} | ||
/> | ||
)} | ||
|
||
<form onSubmit={formik.handleSubmit}> | ||
<Input | ||
type="password" | ||
label="Current Password" | ||
name="currentPassword" | ||
value={formik.values.currentPassword} | ||
onBlur={formik.handleBlur} | ||
onInput={formik.handleChange} | ||
error={ | ||
formik.touched.currentPassword && formik.errors.currentPassword | ||
} | ||
/> | ||
|
||
<Input | ||
type="password" | ||
label="New Password" | ||
name="newPassword" | ||
value={formik.values.newPassword} | ||
onBlur={formik.handleBlur} | ||
onInput={formik.handleChange} | ||
error={formik.touched.newPassword && formik.errors.newPassword} | ||
/> | ||
|
||
<Input | ||
type="password" | ||
label="Repeat New Password" | ||
name="passwordRepeat" | ||
value={formik.values.passwordRepeat} | ||
onBlur={formik.handleBlur} | ||
onInput={formik.handleChange} | ||
error={formik.touched.passwordRepeat && formik.errors.passwordRepeat} | ||
/> | ||
|
||
<ButtonPrimary type="submit">Change Password</ButtonPrimary> | ||
</form> | ||
</> | ||
); | ||
} | ||
|
||
export default PasswordChanging; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.container { | ||
@media screen and (min-width: 1200px) { | ||
display: grid; | ||
grid-template-columns: 1fr 1fr; | ||
gap: 4rem; | ||
} | ||
} |
Oops, something went wrong.