зеркало из https://github.com/mozilla/fxa.git
fxa-settings(storybook): add account_recovery_reset_password
Because: * We're making React versions of content server's remaining backbone pages, and moving them first into Storybook. This commit: * Sets up the page view metric, basic tests, and storybook stories for the account_recovery_reset_password page. Closes #https://mozilla-hub.atlassian.net/browse/FXA-6343 Update packages/fxa-settings/src/pages/AccountRecoveryResetPassword/en.ftl Co-authored-by: Bryan Olsson <bolsson@mozilla.com> Update packages/fxa-settings/src/pages/AccountRecoveryResetPassword/en.ftl Co-authored-by: Bryan Olsson <bolsson@mozilla.com> Update packages/fxa-settings/src/pages/AccountRecoveryResetPassword/en.ftl Co-authored-by: Bryan Olsson <bolsson@mozilla.com> Update packages/fxa-settings/src/pages/AccountRecoveryResetPassword/en.ftl Co-authored-by: Bryan Olsson <bolsson@mozilla.com> Update packages/fxa-settings/src/pages/AccountRecoveryResetPassword/en.ftl Co-authored-by: Bryan Olsson <bolsson@mozilla.com>
This commit is contained in:
Родитель
8eec5f829d
Коммит
9873858162
|
@ -0,0 +1,14 @@
|
|||
## Account recovery reset password page
|
||||
|
||||
# Appears when a link to reset password has expired
|
||||
password-link-expired-header = Reset password link expired
|
||||
# Appears when a link to reset password is damaged
|
||||
password-link-damaged-header = Reset password link damaged
|
||||
# Header for form to create new password
|
||||
create-new-password-header = Create new password
|
||||
# Link that user can click to receive a new reset password link
|
||||
receive-new-link = Receive new link
|
||||
confirm-account-recovery-key-button = Reset password
|
||||
account-restored-success-message = You have successfully restored your account using your account recovery key. Create a new password to secure your data, and store it in a safe location.
|
||||
password-link-damaged-message = The link you clicked was missing characters, and may have been broken by your email client. Copy the address carefully, and try again.
|
||||
password-link-expired-message = The link you clicked to reset your password is expired.
|
|
@ -0,0 +1,38 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from 'react';
|
||||
import AccountRecoveryResetPassword, {
|
||||
AccountRecoveryResetPasswordProps,
|
||||
} from '.';
|
||||
import AppLayout from '../../components/AppLayout';
|
||||
import { LocationProvider } from '@reach/router';
|
||||
import { Meta } from '@storybook/react';
|
||||
|
||||
export default {
|
||||
title: 'pages/AccountRecoveryResetPassword',
|
||||
component: AccountRecoveryResetPassword,
|
||||
} as Meta;
|
||||
|
||||
const storyWithProps = (props: AccountRecoveryResetPasswordProps) => {
|
||||
const story = () => (
|
||||
<LocationProvider>
|
||||
<AppLayout>
|
||||
<AccountRecoveryResetPassword {...props} />
|
||||
</AppLayout>
|
||||
</LocationProvider>
|
||||
);
|
||||
return story;
|
||||
};
|
||||
|
||||
export const WithBrokenLink = storyWithProps({ linkStatus: 'broken' });
|
||||
|
||||
export const WithExpiredLink = storyWithProps({ linkStatus: 'expired' });
|
||||
|
||||
export const WithValidLink = storyWithProps({ linkStatus: 'valid' });
|
||||
|
||||
export const CanGoBack = storyWithProps({
|
||||
canGoBack: true,
|
||||
linkStatus: 'valid',
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import React from 'react';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
// import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils';
|
||||
// import { FluentBundle } from '@fluent/bundle';
|
||||
import { usePageViewEvent } from '../../lib/metrics';
|
||||
import AccountRecoveryResetPassword from '.';
|
||||
|
||||
jest.mock('../../lib/metrics', () => ({
|
||||
usePageViewEvent: jest.fn(),
|
||||
logViewEvent: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
jest.mock('@reach/router', () => ({
|
||||
...jest.requireActual('@reach/router'),
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
describe('PageAccountRecoveryResetPassword', () => {
|
||||
// TODO: enable l10n tests when they've been updated to handle embedded tags in ftl strings
|
||||
// TODO: in FXA-6461
|
||||
// let bundle: FluentBundle;
|
||||
// beforeAll(async () => {
|
||||
// bundle = await getFtlBundle('settings');
|
||||
// });
|
||||
|
||||
it('renders as expected with valid link', () => {
|
||||
render(<AccountRecoveryResetPassword linkStatus="valid" />);
|
||||
// testAllL10n(screen, bundle);
|
||||
|
||||
const headingEl = screen.getByRole('heading', { level: 1 });
|
||||
expect(headingEl).toHaveTextContent('Create new password');
|
||||
expect(screen.getByLabelText('New password')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Current password')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Reset password' })
|
||||
).toBeInTheDocument();
|
||||
// when 'canGoBack: false' or not passed as prop, the optional RememberPassword link component should not be rendered
|
||||
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows a different message when given a broken link', () => {
|
||||
render(<AccountRecoveryResetPassword linkStatus="broken" />);
|
||||
const headingEl = screen.getByRole('heading', { level: 1 });
|
||||
expect(headingEl).toHaveTextContent(`Reset password link damaged`);
|
||||
});
|
||||
|
||||
it('shows a different message for an expired link, with a button for getting a new link', () => {
|
||||
render(<AccountRecoveryResetPassword linkStatus="expired" />);
|
||||
const headingEl = screen.getByRole('heading', { level: 1 });
|
||||
expect(headingEl).toHaveTextContent(`Reset password link expired`);
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'Receive new link' })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a "Remember your password?" link if "canGoBack: true"', () => {
|
||||
render(
|
||||
<AccountRecoveryResetPassword canGoBack={true} linkStatus="valid" />
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('link', { name: 'Remember your password? Sign in' })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('emits a metrics event on render', () => {
|
||||
render(<AccountRecoveryResetPassword linkStatus="valid" />);
|
||||
expect(usePageViewEvent).toHaveBeenCalledWith(
|
||||
`account-recovery-reset-password`,
|
||||
{
|
||||
entrypoint_variation: 'react',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,233 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useNavigate } from '@reach/router';
|
||||
import { logViewEvent, usePageViewEvent } from '../../lib/metrics';
|
||||
import { useAccount, useAlertBar } from '../../models';
|
||||
import { FtlMsg } from 'fxa-react/lib/utils';
|
||||
import { useFtlMsgResolver } from '../../models/hooks';
|
||||
|
||||
import { InputText } from '../../components/InputText';
|
||||
import LinkRememberPassword from '../../components/LinkRememberPassword';
|
||||
// --canGoBack-- determines if the user can navigate back to an fxa entrypoint
|
||||
|
||||
export type AccountRecoveryResetPasswordProps = {
|
||||
canGoBack?: boolean;
|
||||
linkStatus: LinkStatus;
|
||||
};
|
||||
|
||||
type FormData = {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
};
|
||||
|
||||
type LinkStatus = 'expired' | 'broken' | 'valid';
|
||||
|
||||
const AccountRecoveryResetPassword = ({
|
||||
canGoBack,
|
||||
linkStatus,
|
||||
}: AccountRecoveryResetPasswordProps & RouteComponentProps) => {
|
||||
usePageViewEvent('account-recovery-reset-password', {
|
||||
entrypoint_variation: 'react',
|
||||
});
|
||||
|
||||
const [currentPassword, setCurrentPassword] = useState<string>('');
|
||||
const [newPassword, setNewPassword] = useState<string>('');
|
||||
const [currentPasswordErrorText, setCurrentPasswordErrorText] =
|
||||
useState<string>('');
|
||||
const [newPasswordErrorText, setNewPasswordErrorText] = useState<string>('');
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const alertBar = useAlertBar();
|
||||
const account = useAccount();
|
||||
const navigate = useNavigate();
|
||||
const onFocusMetricsEvent = 'account-recovery-reset-password.engage';
|
||||
const ftlMsgResolver = useFtlMsgResolver();
|
||||
|
||||
const { handleSubmit } = useForm<FormData>({
|
||||
mode: 'onBlur',
|
||||
criteriaMode: 'all',
|
||||
defaultValues: {
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
},
|
||||
});
|
||||
|
||||
const onFocus = () => {
|
||||
if (!isFocused && onFocusMetricsEvent) {
|
||||
logViewEvent('flow', onFocusMetricsEvent, {
|
||||
entrypoint_variation: 'react',
|
||||
});
|
||||
setIsFocused(true);
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToHome = useCallback(() => {
|
||||
navigate('/settings', { replace: true });
|
||||
}, [navigate]);
|
||||
|
||||
// TO-DO:
|
||||
// * Set tooltip error message reflecting password requirements.
|
||||
// const setErrorMessage = (errorText: string) => {
|
||||
// setEmailErrorText(errorText);
|
||||
// };
|
||||
// * Set up metrics for all events
|
||||
// - submitting the new password
|
||||
// - focusing the password inputs if desired
|
||||
// - remembering one's password
|
||||
// - asking for a new link.
|
||||
// * Hook up the functionality for sending a user a new verification link,
|
||||
// instead of this dummy function.
|
||||
const sendNewLinkEmail = () => {};
|
||||
|
||||
const onSubmit = () => {
|
||||
try {
|
||||
account.changePassword(currentPassword, newPassword);
|
||||
navigateToHome();
|
||||
} catch (e) {
|
||||
const errorAccountRecoveryResetPassword = ftlMsgResolver.getMsg(
|
||||
'reset-password-error-general',
|
||||
'Sorry, there was a problem resetting your password'
|
||||
);
|
||||
alertBar.error(errorAccountRecoveryResetPassword);
|
||||
}
|
||||
};
|
||||
|
||||
const newPasswordLabel = ftlMsgResolver.getMsg(
|
||||
'new-password-label',
|
||||
'New password'
|
||||
);
|
||||
const currentPasswordLabel = ftlMsgResolver.getMsg(
|
||||
'current-password-label',
|
||||
'Current password'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{linkStatus === 'valid' && (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<h1 className="card-header">
|
||||
<FtlMsg id="create-new-password-header">
|
||||
Create new password
|
||||
</FtlMsg>
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-sm mb-6">
|
||||
<FtlMsg id="account-restored-success-message">
|
||||
You have successfully restored your account using your account
|
||||
recovery key. Create a new password to secure your data, and store
|
||||
it in a safe location.
|
||||
</FtlMsg>
|
||||
</p>
|
||||
<form
|
||||
noValidate
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
data-testid="account-recovery-reset-password-form"
|
||||
>
|
||||
<InputText
|
||||
type="password"
|
||||
label={newPasswordLabel}
|
||||
onChange={(e) => {
|
||||
setNewPassword(e.target.value);
|
||||
// clear error tooltip if user types in the field
|
||||
if (newPasswordErrorText) {
|
||||
setNewPasswordErrorText('');
|
||||
}
|
||||
}}
|
||||
onFocusCb={onFocusMetricsEvent ? onFocus : undefined}
|
||||
autoFocus
|
||||
errorText={newPasswordErrorText}
|
||||
className="text-start"
|
||||
anchorStart
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
prefixDataTestId="account-recovery-reset-password-new-password"
|
||||
/>
|
||||
<InputText
|
||||
type="password"
|
||||
label={currentPasswordLabel}
|
||||
onChange={(e) => {
|
||||
setCurrentPassword(e.target.value);
|
||||
// clear error tooltip if user types in the field
|
||||
if (currentPasswordErrorText) {
|
||||
setCurrentPasswordErrorText('');
|
||||
}
|
||||
}}
|
||||
onFocusCb={onFocusMetricsEvent ? onFocus : undefined}
|
||||
errorText={currentPasswordErrorText}
|
||||
className="text-start"
|
||||
anchorStart
|
||||
autoComplete="off"
|
||||
spellCheck={false}
|
||||
prefixDataTestId="account-recovery-reset-password-current-password"
|
||||
/>
|
||||
|
||||
<FtlMsg id="confirm-account-recovery-key-button">
|
||||
<button
|
||||
data-testid="confirm-account-recovery-key-button"
|
||||
type="submit"
|
||||
className="cta-primary cta-xl"
|
||||
>
|
||||
Reset password
|
||||
</button>
|
||||
</FtlMsg>
|
||||
</form>
|
||||
|
||||
{canGoBack && <LinkRememberPassword />}
|
||||
</>
|
||||
)}
|
||||
{linkStatus === 'broken' && (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<h1 className="card-header">
|
||||
<FtlMsg id="password-link-damaged-header">
|
||||
Reset password link damaged
|
||||
</FtlMsg>
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-sm mb-6">
|
||||
<FtlMsg id="password-link-damaged-message">
|
||||
The link you clicked was missing characters, and may have been
|
||||
broken by your email client. Copy the address carefully, and try
|
||||
again.
|
||||
</FtlMsg>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
{linkStatus === 'expired' && (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<h1 className="card-header">
|
||||
<FtlMsg id="password-link-expired-header">
|
||||
Reset password link expired
|
||||
</FtlMsg>
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-sm mb-6">
|
||||
<FtlMsg id="password-link-expired-message">
|
||||
The link you clicked to reset your password is expired.
|
||||
</FtlMsg>
|
||||
</p>
|
||||
<div className="text-sm mt-6">
|
||||
<FtlMsg id="receive-new-link">
|
||||
<button
|
||||
onClick={sendNewLinkEmail}
|
||||
className="link-blue text-sm"
|
||||
id="remember-password"
|
||||
>
|
||||
Receive new link
|
||||
</button>
|
||||
</FtlMsg>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountRecoveryResetPassword;
|
Загрузка…
Ссылка в новой задаче