зеркало из https://github.com/mozilla/fxa.git
Merge pull request #17335 from mozilla/FXA-10053
feat(settings): Standardize UTM params for Bento Menu links
This commit is contained in:
Коммит
db3d150222
|
@ -12,7 +12,7 @@ import LinkExternal from 'fxa-react/components/LinkExternal';
|
|||
import InputPassword from '../InputPassword';
|
||||
import PasswordValidator from '../../lib/password-validator';
|
||||
import { useNavigateWithQuery as useNavigate } from '../../lib/hooks/useNavigateWithQuery';
|
||||
import { HomePath } from '../../constants';
|
||||
import { SETTINGS_PATH } from '../../constants';
|
||||
import { logViewEvent, settingsViewName } from '../../lib/metrics';
|
||||
|
||||
type FormPasswordProps = {
|
||||
|
@ -69,7 +69,7 @@ export const FormPassword = ({
|
|||
}: FormPasswordProps) => {
|
||||
const navigate = useNavigate();
|
||||
const goHome = useCallback(
|
||||
() => navigate(HomePath + '#password', { replace: true }),
|
||||
() => navigate(SETTINGS_PATH + '#password', { replace: true }),
|
||||
[navigate]
|
||||
);
|
||||
const passwordValidator = new PasswordValidator(primaryEmail);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import { renderWithRouter } from '../../../models/mocks';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { MockSettingsAppLayout } from './mocks';
|
||||
|
||||
it('renders the app with children', async () => {
|
||||
|
@ -16,7 +16,7 @@ it('renders the app with children', async () => {
|
|||
<p data-testid="test-child">Hello, world!</p>
|
||||
</MockSettingsAppLayout>
|
||||
);
|
||||
await navigate(HomePath);
|
||||
await navigate(SETTINGS_PATH);
|
||||
expect(screen.getByTestId('app')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('content-skip')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('header')).toBeInTheDocument();
|
||||
|
|
|
@ -37,6 +37,44 @@ describe('BentoMenu', () => {
|
|||
expect(screen.queryByTestId(dropDownId)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the expected product links', () => {
|
||||
renderWithLocalizationProvider(<BentoMenu />);
|
||||
|
||||
fireEvent.click(screen.getByTestId('drop-down-bento-menu-toggle'));
|
||||
expect(screen.queryByTestId(dropDownId)).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByRole('link', { name: /Firefox Browser for Desktop/ })
|
||||
).toHaveAttribute(
|
||||
'href',
|
||||
'https://www.mozilla.org/firefox/new/?utm_source=moz-account&utm_medium=mozilla-websites&utm_term=bento&utm_content=fx-desktop&utm_campaign=permanent'
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('link', { name: /Firefox Browser for Mobile/ })
|
||||
).toHaveAttribute(
|
||||
'href',
|
||||
'https://www.mozilla.org/firefox/mobile/?utm_source=moz-account&utm_medium=mozilla-websites&utm_term=bento&utm_content=fx-mobile&utm_campaign=permanent'
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('link', { name: /Mozilla Monitor/ })
|
||||
).toHaveAttribute(
|
||||
'href',
|
||||
'https://monitor.mozilla.org/?utm_source=moz-account&utm_medium=mozilla-websites&utm_term=bento&utm_content=monitor&utm_campaign=permanent'
|
||||
);
|
||||
expect(screen.getByRole('link', { name: /Pocket/ })).toHaveAttribute(
|
||||
'href',
|
||||
'https://app.adjust.com/hr2n0yz?redirect_macos=https%3A%2F%2Fgetpocket.com%2Fpocket-and-firefox&redirect_windows=https%3A%2F%2Fgetpocket.com%2Fpocket-and-firefox&engagement_type=fallback_click&fallback=https%3A%2F%2Fgetpocket.com%2Ffirefox_learnmore%3Fsrc%3Dff_bento&fallback_lp=https%3A%2F%2Fapps.apple.com%2Fapp%2Fpocket-save-read-grow%2Fid309601447'
|
||||
);
|
||||
expect(screen.getByRole('link', { name: /Firefox Relay/ })).toHaveAttribute(
|
||||
'href',
|
||||
'https://relay.firefox.com/?utm_source=moz-account&utm_medium=mozilla-websites&utm_term=bento&utm_content=relay&utm_campaign=permanent'
|
||||
);
|
||||
expect(screen.getByRole('link', { name: /Mozilla VPN/ })).toHaveAttribute(
|
||||
'href',
|
||||
'https://vpn.mozilla.org/?utm_source=moz-account&utm_medium=mozilla-websites&utm_term=bento&utm_content=vpn&utm_campaign=permanent'
|
||||
);
|
||||
});
|
||||
|
||||
it('closes on esc keypress', () => {
|
||||
renderWithLocalizationProvider(<BentoMenu />);
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ import { ReactComponent as BentoIcon } from './bento.svg';
|
|||
import { ReactComponent as CloseIcon } from '@fxa/shared/assets/images/close.svg';
|
||||
import { FtlMsg } from 'fxa-react/lib/utils';
|
||||
import { useFtlMsgResolver } from '../../../models/hooks';
|
||||
import { LINK } from '../../../constants';
|
||||
import { constructHrefWithUtm } from '../../../lib/utilities';
|
||||
|
||||
export const BentoMenu = () => {
|
||||
const [isRevealed, setRevealed] = useState(false);
|
||||
|
@ -34,6 +36,51 @@ export const BentoMenu = () => {
|
|||
'Mozilla products'
|
||||
);
|
||||
|
||||
const desktopLink = constructHrefWithUtm(
|
||||
LINK.FX_DESKTOP,
|
||||
'mozilla-websites',
|
||||
'moz-account',
|
||||
'bento',
|
||||
'fx-desktop',
|
||||
'permanent'
|
||||
);
|
||||
|
||||
const mobileLink = constructHrefWithUtm(
|
||||
LINK.FX_MOBILE,
|
||||
'mozilla-websites',
|
||||
'moz-account',
|
||||
'bento',
|
||||
'fx-mobile',
|
||||
'permanent'
|
||||
);
|
||||
|
||||
const monitorLink = constructHrefWithUtm(
|
||||
LINK.MONITOR,
|
||||
'mozilla-websites',
|
||||
'moz-account',
|
||||
'bento',
|
||||
'monitor',
|
||||
'permanent'
|
||||
);
|
||||
|
||||
const relayLink = constructHrefWithUtm(
|
||||
LINK.RELAY,
|
||||
'mozilla-websites',
|
||||
'moz-account',
|
||||
'bento',
|
||||
'relay',
|
||||
'permanent'
|
||||
);
|
||||
|
||||
const vpnLink = constructHrefWithUtm(
|
||||
LINK.VPN,
|
||||
'mozilla-websites',
|
||||
'moz-account',
|
||||
'bento',
|
||||
'vpn',
|
||||
'permanent'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative self-center flex mx-2" ref={bentoMenuInsideRef}>
|
||||
<button
|
||||
|
@ -75,7 +122,7 @@ export const BentoMenu = () => {
|
|||
<li>
|
||||
<LinkExternal
|
||||
data-testid="desktop-link"
|
||||
href="https://www.mozilla.org/firefox/new/?utm_source=firefox-accounts&utm_medium=referral&utm_campaign=bento&utm_content=desktop"
|
||||
href={desktopLink}
|
||||
className="block p-2 ps-6 hover:bg-grey-100"
|
||||
>
|
||||
<div className={iconClassNames}>
|
||||
|
@ -89,7 +136,7 @@ export const BentoMenu = () => {
|
|||
<li>
|
||||
<LinkExternal
|
||||
data-testid="mobile-link"
|
||||
href="http://mozilla.org/firefox/mobile?utm_source=firefox-accounts&utm_medium=referral&utm_campaign=bento&utm_content=desktop"
|
||||
href={mobileLink}
|
||||
className="block p-2 ps-6 hover:bg-grey-100"
|
||||
>
|
||||
<div className={iconClassNames}>
|
||||
|
@ -103,7 +150,7 @@ export const BentoMenu = () => {
|
|||
<li>
|
||||
<LinkExternal
|
||||
data-testid="monitor-link"
|
||||
href="https://monitor.mozilla.org"
|
||||
href={monitorLink}
|
||||
className="block p-2 ps-6 hover:bg-grey-100"
|
||||
>
|
||||
<div className={iconClassNames}>
|
||||
|
@ -115,7 +162,7 @@ export const BentoMenu = () => {
|
|||
<li>
|
||||
<LinkExternal
|
||||
data-testid="relay-link"
|
||||
href="https://relay.firefox.com/"
|
||||
href={relayLink}
|
||||
className="block p-2 ps-6 hover:bg-grey-100"
|
||||
>
|
||||
<div className={iconClassNames}>
|
||||
|
@ -129,7 +176,7 @@ export const BentoMenu = () => {
|
|||
<li>
|
||||
<LinkExternal
|
||||
data-testid="vpn-link"
|
||||
href="https://vpn.mozilla.org/?utm_source=accounts.firefox.com&utm_medium=referral&utm_campaign=fxa-settings&utm_content=bento-promo"
|
||||
href={vpnLink}
|
||||
className="block p-2 ps-6 hover:bg-grey-100"
|
||||
>
|
||||
<div className={iconClassNames}>
|
||||
|
|
|
@ -16,7 +16,7 @@ import { LockImage } from '../../images';
|
|||
import Banner, { BannerType } from '../../Banner';
|
||||
import { RecoveryKeyAction } from '../PageRecoveryKeyCreate';
|
||||
import { Link } from '@reach/router';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { getLocalizedErrorMessage } from '../../../lib/error-utils';
|
||||
|
||||
type FormData = {
|
||||
|
@ -177,7 +177,7 @@ export const FlowRecoveryKeyConfirmPwd = ({
|
|||
<FtlMsg id="flow-recovery-key-info-cancel-link">
|
||||
<Link
|
||||
className="link-blue text-sm mx-auto"
|
||||
to={HomePath}
|
||||
to={SETTINGS_PATH}
|
||||
onClick={() => {
|
||||
logViewEvent(`flow.${viewName}`, 'change-key.cancel');
|
||||
}}
|
||||
|
|
|
@ -11,7 +11,7 @@ import { FtlMsg } from 'fxa-react/lib/utils';
|
|||
import { logViewEvent } from '../../../lib/metrics';
|
||||
import { RecoveryKeyAction } from '../PageRecoveryKeyCreate';
|
||||
import { Link } from '@reach/router';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
|
||||
export type FlowRecoveryKeyInfoProps = {
|
||||
action?: RecoveryKeyAction;
|
||||
|
@ -103,7 +103,7 @@ export const FlowRecoveryKeyInfo = ({
|
|||
<FtlMsg id="flow-recovery-key-info-cancel-link">
|
||||
<Link
|
||||
className="link-blue text-sm mx-auto mt-4"
|
||||
to={HomePath}
|
||||
to={SETTINGS_PATH}
|
||||
onClick={() =>
|
||||
logViewEvent(`flow.${viewName}`, 'change-key.cancel')
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useAccount, useFtlMsgResolver } from '../../../models';
|
|||
import { useBooleanState } from 'fxa-react/lib/hooks';
|
||||
import { useLocation } from '@reach/router';
|
||||
import { useNavigateWithQuery as useNavigate } from '../../../lib/hooks/useNavigateWithQuery';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import {
|
||||
LinkedAccountProviderIds,
|
||||
UnlinkAccountLocationState,
|
||||
|
@ -42,7 +42,7 @@ export function LinkedAccount({
|
|||
|
||||
// Keep the user where they were, but update router state
|
||||
const resetLocationState = () =>
|
||||
navigate(HomePath + '#linked-accounts', {
|
||||
navigate(SETTINGS_PATH + '#linked-accounts', {
|
||||
replace: true,
|
||||
state: { wantsUnlinkProviderId: undefined },
|
||||
});
|
||||
|
@ -73,7 +73,7 @@ export function LinkedAccount({
|
|||
// If a user doesn't have a password, they must create one first. We send
|
||||
// a navigation state that's passed back to Settings on password create
|
||||
// success that we account for here by automatically re-opening the modal.
|
||||
navigate(HomePath + '/create_password', {
|
||||
navigate(SETTINGS_PATH + '/create_password', {
|
||||
state: { wantsUnlinkProviderId: providerId },
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import 'mutationobserver-shim';
|
|||
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { Account, AppContext } from '../../../models';
|
||||
import { Config } from '../../../lib/config';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { typeByTestIdFn } from '../../../lib/test-utils';
|
||||
import {
|
||||
MOCK_ACCOUNT,
|
||||
|
@ -156,7 +156,7 @@ it('allows user to finish', async () => {
|
|||
|
||||
await waitFor(() =>
|
||||
expect(mockNavigate).toHaveBeenCalledWith(
|
||||
HomePath + '#two-step-authentication',
|
||||
SETTINGS_PATH + '#two-step-authentication',
|
||||
{ replace: true }
|
||||
)
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ import FlowContainer from '../FlowContainer';
|
|||
import VerifiedSessionGuard from '../VerifiedSessionGuard';
|
||||
import DataBlock from '../../DataBlock';
|
||||
import InputText from '../../InputText';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import {
|
||||
useAccount,
|
||||
useAlertBar,
|
||||
|
@ -31,7 +31,7 @@ export const Page2faReplaceRecoveryCodes = (_: RouteComponentProps) => {
|
|||
const ftlMsgResolver = useFtlMsgResolver();
|
||||
|
||||
const goHome = () =>
|
||||
navigate(HomePath + '#two-step-authentication', { replace: true });
|
||||
navigate(SETTINGS_PATH + '#two-step-authentication', { replace: true });
|
||||
|
||||
const [subtitle, setSubtitle] = useState<string>(
|
||||
ftlMsgResolver.getMsg('tfa-replace-code-1-2', 'Step 1 of 2')
|
||||
|
@ -47,7 +47,7 @@ export const Page2faReplaceRecoveryCodes = (_: RouteComponentProps) => {
|
|||
'Account backup authentication codes updated'
|
||||
)
|
||||
);
|
||||
navigate(HomePath + '#two-step-authentication', { replace: true });
|
||||
navigate(SETTINGS_PATH + '#two-step-authentication', { replace: true });
|
||||
};
|
||||
|
||||
const onRecoveryCodeSubmit = async (_: RecoveryCodeForm) => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import React, { useCallback, useRef } from 'react';
|
|||
import { useNavigateWithQuery as useNavigate } from '../../../lib/hooks/useNavigateWithQuery';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { useAccount, useAlertBar } from '../../../models';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import ButtonIcon from '../ButtonIcon';
|
||||
|
||||
import { ReactComponent as AddIcon } from './add.svg';
|
||||
|
@ -35,7 +35,7 @@ export const RemovePhotoBtn = () => {
|
|||
const deleteAvatar = useCallback(async () => {
|
||||
try {
|
||||
await account.deleteAvatar();
|
||||
navigate(HomePath, { replace: true });
|
||||
navigate(SETTINGS_PATH, { replace: true });
|
||||
} catch (err) {
|
||||
alertBar.error(
|
||||
l10n.getString(
|
||||
|
@ -149,7 +149,7 @@ export const ConfirmBtns = ({
|
|||
<Localized id="avatar-page-cancel-button">
|
||||
<button
|
||||
className="cta-neutral cta-base-p mx-2 flex-1"
|
||||
onClick={() => navigate(HomePath, { replace: true })}
|
||||
onClick={() => navigate(SETTINGS_PATH, { replace: true })}
|
||||
data-testid="close-button"
|
||||
>
|
||||
Cancel
|
||||
|
|
|
@ -15,7 +15,7 @@ import 'react-easy-crop/react-easy-crop.css';
|
|||
|
||||
import { isMobileDevice } from '../../../lib/utilities';
|
||||
import { useAccount, useAlertBar } from '../../../models';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { onFileChange } from '../../../lib/file-utils';
|
||||
import { getCroppedImg } from '../../../lib/canvas-utils';
|
||||
import {
|
||||
|
@ -77,7 +77,7 @@ export const PageAddAvatar = (_: RouteComponentProps) => {
|
|||
try {
|
||||
await account.uploadAvatar(file);
|
||||
logViewEvent(settingsViewName, 'avatar.crop.submit.change');
|
||||
navigate(HomePath + '#profile-picture', { replace: true });
|
||||
navigate(SETTINGS_PATH + '#profile-picture', { replace: true });
|
||||
} catch (e) {
|
||||
onFileError();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import 'mutationobserver-shim';
|
||||
import React from 'react';
|
||||
import { act, fireEvent, screen } from '@testing-library/react';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { mockAppContext, renderWithRouter } from '../../../models/mocks';
|
||||
import PageChangePassword from '.';
|
||||
import {
|
||||
|
@ -93,7 +93,7 @@ it('shows an error when old and new password are the same', async () => {
|
|||
|
||||
it('redirects on success', async () => {
|
||||
await changePassword();
|
||||
expect(mockNavigate).toHaveBeenCalledWith(HomePath + '#password', {
|
||||
expect(mockNavigate).toHaveBeenCalledWith(SETTINGS_PATH + '#password', {
|
||||
replace: true,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@ import React, { useCallback, useState } from 'react';
|
|||
import { useForm } from 'react-hook-form';
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import { useNavigateWithQuery as useNavigate } from '../../../lib/hooks/useNavigateWithQuery';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import {
|
||||
logViewEvent,
|
||||
settingsViewName,
|
||||
|
@ -57,7 +57,7 @@ export const PageChangePassword = ({}: RouteComponentProps) => {
|
|||
alertBar.success(
|
||||
l10n.getString('pw-change-success-alert-2', null, 'Password updated')
|
||||
);
|
||||
navigate(HomePath + '#password', { replace: true });
|
||||
navigate(SETTINGS_PATH + '#password', { replace: true });
|
||||
}, [alertBar, l10n, navigate]);
|
||||
|
||||
const onFormSubmit = useCallback(
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
inputVerifyPassword,
|
||||
} from '../../FormPassword/index.test';
|
||||
import { act, fireEvent, screen } from '@testing-library/react';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { SettingsContext } from '../../../models/contexts/SettingsContext';
|
||||
import { LinkedAccountProviderIds } from '../../../lib/types';
|
||||
|
||||
|
@ -161,7 +161,7 @@ describe('PageCreatePassword', () => {
|
|||
it('redirects and displays alert bar on success', async () => {
|
||||
const alertBarInfo = await createPassword();
|
||||
expect(mockNavigate).toHaveBeenCalledTimes(1);
|
||||
expect(mockNavigate).toHaveBeenCalledWith(HomePath + '#password', {
|
||||
expect(mockNavigate).toHaveBeenCalledWith(SETTINGS_PATH + '#password', {
|
||||
replace: true,
|
||||
});
|
||||
expect(alertBarInfo.success).toHaveBeenCalledTimes(1);
|
||||
|
@ -177,7 +177,7 @@ describe('PageCreatePassword', () => {
|
|||
wantsUnlinkProviderId: LinkedAccountProviderIds.Google,
|
||||
};
|
||||
await createPassword();
|
||||
expect(mockNavigate).toHaveBeenCalledWith(HomePath + '#password', {
|
||||
expect(mockNavigate).toHaveBeenCalledWith(SETTINGS_PATH + '#password', {
|
||||
replace: true,
|
||||
state: {
|
||||
wantsUnlinkProviderId: LinkedAccountProviderIds.Google,
|
||||
|
|
|
@ -7,7 +7,7 @@ import { RouteComponentProps, useLocation } from '@reach/router';
|
|||
import { useNavigateWithQuery as useNavigate } from '../../../lib/hooks/useNavigateWithQuery';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import {
|
||||
logViewEvent,
|
||||
settingsViewName,
|
||||
|
@ -54,7 +54,7 @@ export const PageCreatePassword = ({}: RouteComponentProps) => {
|
|||
alertBar.success(
|
||||
ftlMsgResolver.getMsg('pw-create-success-alert-2', 'Password set')
|
||||
);
|
||||
navigate(HomePath + '#password', {
|
||||
navigate(SETTINGS_PATH + '#password', {
|
||||
replace: true,
|
||||
...(wantsUnlinkProviderId ? { state: { wantsUnlinkProviderId } } : {}),
|
||||
});
|
||||
|
|
|
@ -10,18 +10,7 @@ import { useAccount, useAlertBar } from '../../../models';
|
|||
import InputPassword from '../../InputPassword';
|
||||
import FlowContainer from '../FlowContainer';
|
||||
import VerifiedSessionGuard from '../VerifiedSessionGuard';
|
||||
import {
|
||||
AddonsLink,
|
||||
HomePath,
|
||||
HubsLink,
|
||||
MDNLink,
|
||||
MonitorLink,
|
||||
PocketLink,
|
||||
RelayLink,
|
||||
ROOTPATH,
|
||||
SyncLink,
|
||||
VPNLink,
|
||||
} from '../../../constants';
|
||||
import { LINK, SETTINGS_PATH } from '../../../constants';
|
||||
import { logViewEvent, usePageViewEvent } from '../../../lib/metrics';
|
||||
import { Checkbox } from '../Checkbox';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
|
@ -51,42 +40,42 @@ const deleteProducts = [
|
|||
{
|
||||
localizationId: 'delete-account-product-firefox-sync',
|
||||
productName: 'Syncing Firefox data',
|
||||
href: SyncLink,
|
||||
href: LINK.FX_SYNC,
|
||||
},
|
||||
{
|
||||
localizationId: 'delete-account-product-mozilla-vpn',
|
||||
productName: 'Mozilla VPN',
|
||||
href: VPNLink,
|
||||
href: LINK.VPN,
|
||||
},
|
||||
{
|
||||
localizationId: 'delete-account-product-firefox-relay',
|
||||
productName: 'Firefox Relay',
|
||||
href: RelayLink,
|
||||
href: LINK.RELAY,
|
||||
},
|
||||
{
|
||||
localizationId: 'delete-account-product-firefox-addons',
|
||||
productName: 'Firefox Add-ons',
|
||||
href: AddonsLink,
|
||||
href: LINK.AMO,
|
||||
},
|
||||
{
|
||||
localizationId: 'delete-account-product-mozilla-monitor',
|
||||
productName: 'Mozilla Monitor',
|
||||
href: MonitorLink,
|
||||
href: LINK.MONITOR,
|
||||
},
|
||||
{
|
||||
localizationId: 'delete-account-product-mdn-plus',
|
||||
productName: 'MDN Plus',
|
||||
href: MDNLink,
|
||||
href: LINK.MDN,
|
||||
},
|
||||
{
|
||||
localizationId: 'delete-account-product-mozilla-hubs',
|
||||
productName: 'Mozilla Hubs',
|
||||
href: HubsLink,
|
||||
href: LINK.HUBS,
|
||||
},
|
||||
{
|
||||
localizationId: 'delete-account-product-pocket',
|
||||
productName: 'Pocket',
|
||||
href: PocketLink,
|
||||
href: LINK.POCKET,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -152,7 +141,7 @@ export const PageDeleteAccount = (_: RouteComponentProps) => {
|
|||
'flow.settings.account-delete',
|
||||
'confirm-password.success'
|
||||
);
|
||||
hardNavigate(ROOTPATH, { delete_account_success: true }, true);
|
||||
hardNavigate('/', { delete_account_success: true }, true);
|
||||
} catch (e) {
|
||||
const localizedError = l10n.getString(
|
||||
getErrorFtlId(AuthUiErrors.INCORRECT_PASSWORD),
|
||||
|
@ -290,7 +279,9 @@ export const PageDeleteAccount = (_: RouteComponentProps) => {
|
|||
<button
|
||||
className="cta-neutral mx-2 px-10 py-2"
|
||||
onClick={() =>
|
||||
navigate(HomePath + '#delete-account', { replace: true })
|
||||
navigate(SETTINGS_PATH + '#delete-account', {
|
||||
replace: true,
|
||||
})
|
||||
}
|
||||
data-testid="cancel-button"
|
||||
>
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
mockSettingsContext,
|
||||
renderWithRouter,
|
||||
} from '../../../models/mocks';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { Account, AppContext } from '../../../models';
|
||||
import { SettingsContext } from '../../../models/contexts/SettingsContext';
|
||||
|
||||
|
@ -90,7 +90,7 @@ it('navigates back to settings home and shows a success message on a successful
|
|||
</AppContext.Provider>
|
||||
);
|
||||
await submitDisplayName('John Hope');
|
||||
expect(history.location.pathname).toBe(HomePath);
|
||||
expect(history.location.pathname).toBe(SETTINGS_PATH);
|
||||
expect(alertBarInfo.success).toHaveBeenCalledTimes(1);
|
||||
expect(alertBarInfo.success).toHaveBeenCalledWith('Display name updated');
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useNavigateWithQuery as useNavigate } from '../../../lib/hooks/useNavig
|
|||
import { useForm } from 'react-hook-form';
|
||||
import FlowContainer from '../FlowContainer';
|
||||
import InputText from '../../InputText';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { useAccount, useAlertBar } from '../../../models';
|
||||
|
||||
|
@ -21,7 +21,7 @@ export const PageDisplayName = (_: RouteComponentProps) => {
|
|||
const alertBar = useAlertBar();
|
||||
const { l10n } = useLocalization();
|
||||
const navigate = useNavigate();
|
||||
const goHome = () => navigate(HomePath, { replace: true });
|
||||
const goHome = () => navigate(SETTINGS_PATH, { replace: true });
|
||||
const alertSuccessAndGoHome = useCallback(() => {
|
||||
alertBar.success(
|
||||
l10n.getString(
|
||||
|
@ -30,7 +30,7 @@ export const PageDisplayName = (_: RouteComponentProps) => {
|
|||
'Display name updated'
|
||||
)
|
||||
);
|
||||
navigate(HomePath, { replace: true });
|
||||
navigate(SETTINGS_PATH, { replace: true });
|
||||
}, [alertBar, l10n, navigate]);
|
||||
const initialValue = account.displayName || '';
|
||||
const { register, handleSubmit, formState, trigger } = useForm<{
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import React, { useState } from 'react';
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import { useNavigateWithQuery as useNavigate } from '../../../lib/hooks/useNavigateWithQuery';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { usePageViewEvent } from '../../../lib/metrics';
|
||||
import { useAccount, useFtlMsgResolver } from '../../../models';
|
||||
import FlowRecoveryKeyConfirmPwd from '../FlowRecoveryKeyConfirmPwd';
|
||||
|
@ -35,7 +35,8 @@ export const PageRecoveryKeyCreate = (props: RouteComponentProps) => {
|
|||
const action = recoveryKey
|
||||
? RecoveryKeyAction.Change
|
||||
: RecoveryKeyAction.Create;
|
||||
const goHome = () => navigate(HomePath + '#recovery-key', { replace: true });
|
||||
const goHome = () =>
|
||||
navigate(SETTINGS_PATH + '#recovery-key', { replace: true });
|
||||
|
||||
const localizedPageTitle = ftlMsgResolver.getMsg(
|
||||
'recovery-key-create-page-title',
|
||||
|
@ -48,7 +49,7 @@ export const PageRecoveryKeyCreate = (props: RouteComponentProps) => {
|
|||
);
|
||||
|
||||
const navigateBackward = () => {
|
||||
navigate(HomePath);
|
||||
navigate(SETTINGS_PATH);
|
||||
};
|
||||
|
||||
const navigateForward = (e?: React.MouseEvent<HTMLElement>) => {
|
||||
|
@ -56,7 +57,7 @@ export const PageRecoveryKeyCreate = (props: RouteComponentProps) => {
|
|||
if (currentStep + 1 <= numberOfSteps) {
|
||||
setCurrentStep(currentStep + 1);
|
||||
} else {
|
||||
navigate(HomePath);
|
||||
navigate(SETTINGS_PATH);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Localized, useLocalization } from '@fluent/react';
|
|||
import { RouteComponentProps } from '@reach/router';
|
||||
import { useNavigateWithQuery as useNavigate } from '../../../lib/hooks/useNavigateWithQuery';
|
||||
import { logViewEvent, usePageViewEvent } from '../../../lib/metrics';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import InputText from '../../InputText';
|
||||
import FlowContainer from '../FlowContainer';
|
||||
import VerifiedSessionGuard from '../VerifiedSessionGuard';
|
||||
|
@ -29,7 +29,7 @@ export const PageSecondaryEmailAdd = (_: RouteComponentProps) => {
|
|||
const alertBar = useAlertBar();
|
||||
const account = useAccount();
|
||||
const goHome = () =>
|
||||
navigate(HomePath + '#secondary-email', { replace: true });
|
||||
navigate(SETTINGS_PATH + '#secondary-email', { replace: true });
|
||||
|
||||
const createSecondaryEmail = useCallback(
|
||||
async (email: string) => {
|
||||
|
|
|
@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import { useNavigateWithQuery as useNavigate } from '../../../lib/hooks/useNavigateWithQuery';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { logViewEvent } from '../../../lib/metrics';
|
||||
import { useAccount, useAlertBar } from '../../../models';
|
||||
import InputText from '../../InputText';
|
||||
|
@ -26,7 +26,7 @@ export const PageSecondaryEmailVerify = ({ location }: RouteComponentProps) => {
|
|||
});
|
||||
const navigate = useNavigate();
|
||||
const goHome = useCallback(
|
||||
() => navigate(HomePath + '#secondary-email', { replace: true }),
|
||||
() => navigate(SETTINGS_PATH + '#secondary-email', { replace: true }),
|
||||
[navigate]
|
||||
);
|
||||
const { l10n } = useLocalization();
|
||||
|
@ -41,7 +41,7 @@ export const PageSecondaryEmailVerify = ({ location }: RouteComponentProps) => {
|
|||
`${email} successfully added`
|
||||
)
|
||||
);
|
||||
navigate(HomePath + '#secondary-email', { replace: true });
|
||||
navigate(SETTINGS_PATH + '#secondary-email', { replace: true });
|
||||
},
|
||||
[alertBar, l10n, navigate]
|
||||
);
|
||||
|
@ -84,7 +84,7 @@ export const PageSecondaryEmailVerify = ({ location }: RouteComponentProps) => {
|
|||
|
||||
useEffect(() => {
|
||||
if (!email) {
|
||||
navigate(HomePath, { replace: true });
|
||||
navigate(SETTINGS_PATH, { replace: true });
|
||||
}
|
||||
}, [email, navigate]);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import LinkedAccounts from '../LinkedAccounts';
|
|||
|
||||
import * as Metrics from '../../../lib/metrics';
|
||||
import { useAccount } from '../../../models';
|
||||
import { DeleteAccountPath } from 'fxa-settings/src/constants';
|
||||
import { SETTINGS_PATH } from 'fxa-settings/src/constants';
|
||||
import { Localized } from '@fluent/react';
|
||||
import DataCollection from '../DataCollection';
|
||||
import GleanMetrics from '../../../lib/glean';
|
||||
|
@ -62,7 +62,7 @@ export const PageSettings = (_: RouteComponentProps) => {
|
|||
<Link
|
||||
data-testid="settings-delete-account"
|
||||
className="cta-caution text-sm transition-standard mt-12 py-2 px-5 mobileLandscape:py-1"
|
||||
to={DeleteAccountPath}
|
||||
to={SETTINGS_PATH + '/delete_account'}
|
||||
onClick={() => GleanMetrics.deleteAccount.settingsSubmit()}
|
||||
>
|
||||
Delete account
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
import React from 'react';
|
||||
import PageTwoStepAuthentication, { metricsPreInPostFix } from '.';
|
||||
import { checkCode, getCode } from '../../../lib/totp';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import * as Metrics from '../../../lib/metrics';
|
||||
import { Account, AppContext } from '../../../models';
|
||||
import { AuthUiErrors } from 'fxa-settings/src/lib/auth-errors/auth-errors';
|
||||
|
@ -286,7 +286,7 @@ describe('step 3', () => {
|
|||
expect(getCode).toBeCalledTimes(1);
|
||||
|
||||
expect(mockNavigate).toHaveBeenCalledWith(
|
||||
HomePath + '#two-step-authentication',
|
||||
SETTINGS_PATH + '#two-step-authentication',
|
||||
{ replace: true }
|
||||
);
|
||||
expect(alertBarInfo.success).toHaveBeenCalledTimes(1);
|
||||
|
|
|
@ -13,7 +13,7 @@ import VerifiedSessionGuard from '../VerifiedSessionGuard';
|
|||
import DataBlock from '../../DataBlock';
|
||||
import { useAccount, useAlertBar, useSession } from '../../../models';
|
||||
import { checkCode, copyRecoveryCodes, getCode } from '../../../lib/totp';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { logViewEvent, useMetrics } from '../../../lib/metrics';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { AuthUiErrors } from '../../../lib/auth-errors/auth-errors';
|
||||
|
@ -32,12 +32,12 @@ export const PageTwoStepAuthentication = (_: RouteComponentProps) => {
|
|||
const { l10n } = useLocalization();
|
||||
const alertBar = useAlertBar();
|
||||
const goHome = () =>
|
||||
navigate(HomePath + '#two-step-authentication', { replace: true });
|
||||
navigate(SETTINGS_PATH + '#two-step-authentication', { replace: true });
|
||||
const alertSuccessAndGoHome = useCallback(() => {
|
||||
alertBar.success(
|
||||
l10n.getString('tfa-enabled', null, 'Two-step authentication enabled')
|
||||
);
|
||||
navigate(HomePath + '#two-step-authentication', { replace: true });
|
||||
navigate(SETTINGS_PATH + '#two-step-authentication', { replace: true });
|
||||
}, [alertBar, l10n, navigate]);
|
||||
|
||||
const totpForm = useForm<TotpForm>({
|
||||
|
|
|
@ -4,13 +4,12 @@
|
|||
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import ProductPromo from '.';
|
||||
import ProductPromo, { monitorPromoLink } from '.';
|
||||
import { Account, AppContext } from '../../../models';
|
||||
import { MOCK_SERVICES } from '../ConnectedServices/mocks';
|
||||
import { MozServices } from '../../../lib/types';
|
||||
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
|
||||
import { mockAppContext } from '../../../models/mocks';
|
||||
import { MonitorLink } from '../../../constants';
|
||||
|
||||
// List all services this component handles
|
||||
const PRODUCT_PROMO_SERVICES = [MozServices.Monitor];
|
||||
|
@ -49,7 +48,7 @@ describe('ProductPromo', () => {
|
|||
);
|
||||
expect(screen.getByRole('link', { name: /Get free scan/ })).toHaveAttribute(
|
||||
'href',
|
||||
MonitorLink
|
||||
monitorPromoLink
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,9 +7,10 @@ import LinkExternal from 'fxa-react/components/LinkExternal';
|
|||
import monitorTextLogo from './monitor-text-logo.svg';
|
||||
import { FtlMsg } from 'fxa-react/lib/utils';
|
||||
import classNames from 'classnames';
|
||||
import { MonitorLink } from '../../../constants';
|
||||
import { MozServices } from '../../../lib/types';
|
||||
import { useAccount } from '../../../models';
|
||||
import { constructHrefWithUtm } from '../../../lib/utilities';
|
||||
import { LINK } from '../../../constants';
|
||||
|
||||
export enum ProductPromoType {
|
||||
Sidebar = 'sidebar',
|
||||
|
@ -21,6 +22,15 @@ export interface ProductPromoProps {
|
|||
// product?: MozServices;
|
||||
}
|
||||
|
||||
export const monitorPromoLink = constructHrefWithUtm(
|
||||
LINK.MONITOR,
|
||||
'product-partnership',
|
||||
'moz-account',
|
||||
'sidebar',
|
||||
'monitor-free',
|
||||
'settings-promo'
|
||||
);
|
||||
|
||||
export const ProductPromo = ({
|
||||
type = ProductPromoType.Sidebar,
|
||||
}: ProductPromoProps) => {
|
||||
|
@ -29,6 +39,7 @@ export const ProductPromo = ({
|
|||
const hasMonitor = attachedClients.some(
|
||||
({ name }) => name === MozServices.Monitor
|
||||
);
|
||||
|
||||
// const hasMonitorPlus = subscriptions.some(
|
||||
// ({ productName }) => productName === MozServices.MonitorPlus
|
||||
// );
|
||||
|
@ -73,7 +84,7 @@ export const ProductPromo = ({
|
|||
</FtlMsg>
|
||||
</p>
|
||||
{/* possible todo, link to their stage env in stage? can do with FXA-10147 */}
|
||||
<LinkExternal href={MonitorLink} className="link-blue">
|
||||
<LinkExternal href={monitorPromoLink} className="link-blue">
|
||||
<FtlMsg id="product-promo-monitor-cta">Get free scan</FtlMsg>
|
||||
</LinkExternal>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@ import React, { forwardRef } from 'react';
|
|||
import { useAccount } from '../../../models';
|
||||
import { UnitRow } from '../UnitRow';
|
||||
import { UnitRowSecondaryEmail } from '../UnitRowSecondaryEmail';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { FtlMsg } from 'fxa-react/lib/utils';
|
||||
import GleanMetrics from '../../../lib/glean';
|
||||
|
||||
|
@ -31,7 +31,7 @@ export const Profile = forwardRef<HTMLDivElement>((_, ref) => {
|
|||
header="Picture"
|
||||
headerId="profile-picture"
|
||||
headerValue={!avatar.isDefault}
|
||||
route={`${HomePath}/avatar`}
|
||||
route={`${SETTINGS_PATH}/avatar`}
|
||||
prefixDataTestId="avatar"
|
||||
{...{ avatar }}
|
||||
/>
|
||||
|
|
|
@ -7,7 +7,7 @@ import { screen } from '@testing-library/react';
|
|||
import UnitRow from '.';
|
||||
import { renderWithRouter } from '../../../models/mocks';
|
||||
import { Account, AppContext } from '../../../models';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
|
||||
describe('UnitRow', () => {
|
||||
it('renders as expected with minimal required attributes', () => {
|
||||
|
@ -133,7 +133,7 @@ describe('UnitRow', () => {
|
|||
header="Picture"
|
||||
headerId="profile-picture"
|
||||
headerValue={!account.avatar.isDefault}
|
||||
route={`${HomePath}/avatar`}
|
||||
route={`${SETTINGS_PATH}/avatar`}
|
||||
avatar={account.avatar}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
|
@ -158,7 +158,7 @@ describe('UnitRow', () => {
|
|||
header="Picture"
|
||||
headerId="profile-picture"
|
||||
headerValue={!account.avatar.isDefault}
|
||||
route={`${HomePath}/avatar`}
|
||||
route={`${SETTINGS_PATH}/avatar`}
|
||||
avatar={account.avatar}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import React from 'react';
|
||||
import { useBooleanState } from 'fxa-react/lib/hooks';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { UnitRow } from '.';
|
||||
import { Modal } from '../Modal';
|
||||
import { AppContext } from 'fxa-settings/src/models';
|
||||
|
@ -82,7 +82,7 @@ export const SubjectWithDefaultAvatar = () => {
|
|||
header="Picture"
|
||||
headerId="profile-picture"
|
||||
headerValue={!avatar.isDefault}
|
||||
route={`${HomePath}/avatar`}
|
||||
route={`${SETTINGS_PATH}/avatar`}
|
||||
{...{ avatar }}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
|
@ -101,7 +101,7 @@ export const SubjectWithCustomAvatar = () => {
|
|||
header="Picture"
|
||||
headerId="profile-picture"
|
||||
headerValue={!avatar.isDefault}
|
||||
route={`${HomePath}/avatar`}
|
||||
route={`${SETTINGS_PATH}/avatar`}
|
||||
{...{ avatar }}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
|
|
|
@ -10,7 +10,7 @@ import Modal from '../Modal';
|
|||
import UnitRow from '../UnitRow';
|
||||
import VerifiedSessionGuard from '../VerifiedSessionGuard';
|
||||
import { ButtonIconTrash } from '../ButtonIcon';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { FtlMsg } from 'fxa-react/lib/utils';
|
||||
import GleanMetrics from '../../../lib/glean';
|
||||
|
||||
|
@ -64,7 +64,7 @@ export const UnitRowRecoveryKey = () => {
|
|||
? ftlMsgResolver.getMsg('rk-enabled', 'Enabled')
|
||||
: ftlMsgResolver.getMsg('rk-not-set', 'Not Set')
|
||||
}
|
||||
route={`${HomePath}/account_recovery`}
|
||||
route={`${SETTINGS_PATH}/account_recovery`}
|
||||
ctaText={
|
||||
recoveryKey
|
||||
? ftlMsgResolver.getMsg('rk-action-change-button', 'Change')
|
||||
|
|
|
@ -9,7 +9,7 @@ import UnitRow from '../UnitRow';
|
|||
import ModalVerifySession from '../ModalVerifySession';
|
||||
import { ButtonIconTrash, ButtonIconReload } from '../ButtonIcon';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import GleanMetrics from '../../../lib/glean';
|
||||
|
||||
type UnitRowSecondaryEmailContentAndActionsProps = {
|
||||
|
@ -34,7 +34,7 @@ export const UnitRowSecondaryEmail = () => {
|
|||
async (email: string) => {
|
||||
try {
|
||||
await account.resendEmailCode(email);
|
||||
navigate(`${HomePath}/emails/verify`, { state: { email } });
|
||||
navigate(`${SETTINGS_PATH}/emails/verify`, { state: { email } });
|
||||
} catch (e) {
|
||||
alertBar.error(
|
||||
l10n.getString(
|
||||
|
@ -136,7 +136,7 @@ export const UnitRowSecondaryEmail = () => {
|
|||
headerId="secondary-email"
|
||||
prefixDataTestId="secondary-email"
|
||||
headerValue={null}
|
||||
route={`${HomePath}/emails`}
|
||||
route={`${SETTINGS_PATH}/emails`}
|
||||
ctaOnClickAction={() => GleanMetrics.accountPref.secondaryEmailSubmit()}
|
||||
{...{
|
||||
alertBarRevealed: alertBar.visible,
|
||||
|
|
|
@ -10,11 +10,11 @@ import UnitRow from '../UnitRow';
|
|||
import VerifiedSessionGuard from '../VerifiedSessionGuard';
|
||||
import { useAccount, useAlertBar } from '../../../models';
|
||||
import { ButtonIconReload } from '../ButtonIcon';
|
||||
import { HomePath } from '../../../constants';
|
||||
import { SETTINGS_PATH } from '../../../constants';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import GleanMetrics from '../../../lib/glean';
|
||||
|
||||
const route = `${HomePath}/two_step_authentication`;
|
||||
const route = `${SETTINGS_PATH}/two_step_authentication`;
|
||||
const replaceCodesRoute = `${route}/replace_codes`;
|
||||
|
||||
export const UnitRowTwoStepAuth = () => {
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '../../models/mocks';
|
||||
import { Config } from '../../lib/config';
|
||||
import * as NavTiming from 'fxa-shared/metrics/navigation-timing';
|
||||
import { HomePath } from '../../constants';
|
||||
import { SETTINGS_PATH } from '../../constants';
|
||||
import AppLocalizationProvider from 'fxa-react/lib/AppLocalizationProvider';
|
||||
import { Subject, createMockSettingsIntegration } from './mocks';
|
||||
|
||||
|
@ -115,10 +115,10 @@ describe('App component', () => {
|
|||
getByTestId,
|
||||
history: { navigate },
|
||||
} = renderWithRouter(<Subject />, {
|
||||
route: HomePath,
|
||||
route: SETTINGS_PATH,
|
||||
});
|
||||
|
||||
await navigate(HomePath);
|
||||
await navigate(SETTINGS_PATH);
|
||||
|
||||
expect(getByTestId('settings-profile')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -127,9 +127,9 @@ describe('App component', () => {
|
|||
const {
|
||||
getByTestId,
|
||||
history: { navigate },
|
||||
} = renderWithRouter(<Subject />, { route: HomePath });
|
||||
} = renderWithRouter(<Subject />, { route: SETTINGS_PATH });
|
||||
|
||||
await navigate(HomePath + '/display_name');
|
||||
await navigate(SETTINGS_PATH + '/display_name');
|
||||
|
||||
expect(getByTestId('input-label')).toHaveTextContent('Enter display name');
|
||||
});
|
||||
|
@ -139,10 +139,10 @@ describe('App component', () => {
|
|||
getAllByTestId,
|
||||
history: { navigate },
|
||||
} = renderWithRouter(<Subject />, {
|
||||
route: HomePath,
|
||||
route: SETTINGS_PATH,
|
||||
});
|
||||
|
||||
await navigate(HomePath + '/avatar');
|
||||
await navigate(SETTINGS_PATH + '/avatar');
|
||||
|
||||
expect(getAllByTestId('avatar-nondefault')[0]).toBeInTheDocument();
|
||||
});
|
||||
|
@ -156,10 +156,10 @@ describe('App component', () => {
|
|||
<AppContext.Provider value={mockAppContext({ session })}>
|
||||
<Subject />
|
||||
</AppContext.Provider>,
|
||||
{ route: HomePath }
|
||||
{ route: SETTINGS_PATH }
|
||||
);
|
||||
|
||||
await navigate(HomePath + '/change_password');
|
||||
await navigate(SETTINGS_PATH + '/change_password');
|
||||
|
||||
expect(getByTestId('change-password-requirements')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -173,10 +173,10 @@ describe('App component', () => {
|
|||
<AppContext.Provider value={mockAppContext({ session })}>
|
||||
<Subject />
|
||||
</AppContext.Provider>,
|
||||
{ route: HomePath }
|
||||
{ route: SETTINGS_PATH }
|
||||
);
|
||||
|
||||
await navigate(HomePath + '/emails');
|
||||
await navigate(SETTINGS_PATH + '/emails');
|
||||
|
||||
expect(getByTestId('secondary-email-input')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -190,10 +190,10 @@ describe('App component', () => {
|
|||
<AppContext.Provider value={mockAppContext({ session })}>
|
||||
<Subject />
|
||||
</AppContext.Provider>,
|
||||
{ route: HomePath }
|
||||
{ route: SETTINGS_PATH }
|
||||
);
|
||||
|
||||
await navigate(HomePath + '/emails/verify');
|
||||
await navigate(SETTINGS_PATH + '/emails/verify');
|
||||
|
||||
expect(getByTestId('secondary-email-verify-form')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -207,10 +207,10 @@ describe('App component', () => {
|
|||
<AppContext.Provider value={mockAppContext({ session })}>
|
||||
<Subject />
|
||||
</AppContext.Provider>,
|
||||
{ route: HomePath }
|
||||
{ route: SETTINGS_PATH }
|
||||
);
|
||||
|
||||
await navigate(HomePath + '/two_step_authentication');
|
||||
await navigate(SETTINGS_PATH + '/two_step_authentication');
|
||||
|
||||
expect(getByTestId('totp-input')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -224,10 +224,10 @@ describe('App component', () => {
|
|||
<AppContext.Provider value={mockAppContext({ session })}>
|
||||
<Subject />
|
||||
</AppContext.Provider>,
|
||||
{ route: HomePath }
|
||||
{ route: SETTINGS_PATH }
|
||||
);
|
||||
|
||||
await navigate(HomePath + '/two_step_authentication/replace_codes');
|
||||
await navigate(SETTINGS_PATH + '/two_step_authentication/replace_codes');
|
||||
|
||||
expect(getByTestId('2fa-recovery-codes')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -241,10 +241,10 @@ describe('App component', () => {
|
|||
<AppContext.Provider value={mockAppContext({ session })}>
|
||||
<Subject />
|
||||
</AppContext.Provider>,
|
||||
{ route: HomePath }
|
||||
{ route: SETTINGS_PATH }
|
||||
);
|
||||
|
||||
await navigate(HomePath + '/delete_account');
|
||||
await navigate(SETTINGS_PATH + '/delete_account');
|
||||
|
||||
expect(getByTestId('delete-account-confirm')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -254,10 +254,10 @@ describe('App component', () => {
|
|||
history,
|
||||
history: { navigate },
|
||||
} = renderWithRouter(<Subject />, {
|
||||
route: HomePath,
|
||||
route: SETTINGS_PATH,
|
||||
});
|
||||
|
||||
await navigate(HomePath + '/clients');
|
||||
await navigate(SETTINGS_PATH + '/clients');
|
||||
|
||||
expect(history.location.pathname).toBe('/settings#connected-services');
|
||||
});
|
||||
|
@ -267,10 +267,10 @@ describe('App component', () => {
|
|||
history,
|
||||
history: { navigate },
|
||||
} = renderWithRouter(<Subject />, {
|
||||
route: HomePath,
|
||||
route: SETTINGS_PATH,
|
||||
});
|
||||
|
||||
await navigate(HomePath + '/avatar/change');
|
||||
await navigate(SETTINGS_PATH + '/avatar/change');
|
||||
|
||||
expect(history.location.pathname).toBe('/settings/avatar');
|
||||
});
|
||||
|
@ -279,9 +279,9 @@ describe('App component', () => {
|
|||
const {
|
||||
history,
|
||||
history: { navigate },
|
||||
} = renderWithRouter(<Subject />, { route: HomePath });
|
||||
} = renderWithRouter(<Subject />, { route: SETTINGS_PATH });
|
||||
|
||||
await navigate(HomePath + '/create_password');
|
||||
await navigate(SETTINGS_PATH + '/create_password');
|
||||
expect(history.location.pathname).toBe('/settings/change_password');
|
||||
});
|
||||
|
||||
|
@ -308,29 +308,29 @@ describe('App component', () => {
|
|||
<Subject />
|
||||
</AppLocalizationProvider>
|
||||
</AppContext.Provider>,
|
||||
{ route: HomePath }
|
||||
{ route: SETTINGS_PATH }
|
||||
));
|
||||
});
|
||||
|
||||
it('redirects PageRecoveryKeyCreate', async () => {
|
||||
await history.navigate(HomePath + '/account_recovery');
|
||||
await history.navigate(SETTINGS_PATH + '/account_recovery');
|
||||
expect(history.location.pathname).toBe('/settings');
|
||||
});
|
||||
|
||||
it('redirects PageTwoStepAuthentication', async () => {
|
||||
await history.navigate(HomePath + '/two_step_authentication');
|
||||
await history.navigate(SETTINGS_PATH + '/two_step_authentication');
|
||||
expect(history.location.pathname).toBe('/settings');
|
||||
});
|
||||
|
||||
it('redirects Page2faReplaceRecoveryCodes', async () => {
|
||||
await history.navigate(
|
||||
HomePath + '/two_step_authentication/replace_codes'
|
||||
SETTINGS_PATH + '/two_step_authentication/replace_codes'
|
||||
);
|
||||
expect(history.location.pathname).toBe('/settings');
|
||||
});
|
||||
|
||||
it('redirects ChangePassword', async () => {
|
||||
await history.navigate(HomePath + '/change_password');
|
||||
await history.navigate(SETTINGS_PATH + '/change_password');
|
||||
expect(history.location.pathname).toBe('/settings/create_password');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,7 +24,7 @@ import PageTwoStepAuthentication from './PageTwoStepAuthentication';
|
|||
import { Page2faReplaceRecoveryCodes } from './Page2faReplaceRecoveryCodes';
|
||||
import { PageDeleteAccount } from './PageDeleteAccount';
|
||||
import { ScrollToTop } from './ScrollToTop';
|
||||
import { HomePath } from '../../constants';
|
||||
import { SETTINGS_PATH } from '../../constants';
|
||||
import { observeNavigationTiming } from 'fxa-shared/metrics/navigation-timing';
|
||||
import PageAvatar from './PageAvatar';
|
||||
import PageRecentActivity from './PageRecentActivity';
|
||||
|
@ -79,7 +79,7 @@ export const Settings = ({
|
|||
return (
|
||||
<AppLayout {...{ integration }}>
|
||||
<Head />
|
||||
<Router basepath={HomePath}>
|
||||
<Router basepath={SETTINGS_PATH}>
|
||||
<ScrollToTop default>
|
||||
<PageSettings path="/" />
|
||||
<PageDisplayName path="/display_name" />
|
||||
|
|
|
@ -2,17 +2,8 @@
|
|||
* 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/. */
|
||||
|
||||
export const ROOTPATH = '/';
|
||||
export const HomePath = '/settings';
|
||||
export const DeleteAccountPath = '/settings/delete_account';
|
||||
export const VPNLink = 'https://vpn.mozilla.org/';
|
||||
export const MonitorLink = 'https://monitor.mozilla.org/';
|
||||
export const SyncLink = 'https://www.mozilla.org/firefox/sync/';
|
||||
export const RelayLink = 'https://relay.firefox.com/';
|
||||
export const AddonsLink = 'https://addons.mozilla.org/';
|
||||
export const MDNLink = 'https://developer.mozilla.org/';
|
||||
export const HubsLink = 'https://hubs.mozilla.com/';
|
||||
export const PocketLink = 'https://getpocket.com/';
|
||||
export const SETTINGS_PATH = '/settings';
|
||||
|
||||
export const SHOW_BALLOON_TIMEOUT = 500;
|
||||
export const HIDE_BALLOON_TIMEOUT = 400;
|
||||
export const POLLING_INTERVAL_MS = 2000;
|
||||
|
@ -31,6 +22,19 @@ export enum ENTRYPOINTS {
|
|||
FIREFOX_FX_VIEW_ENTRYPOINT = 'fx-view',
|
||||
}
|
||||
|
||||
export enum LINK {
|
||||
AMO = 'https://addons.mozilla.org/',
|
||||
FX_DESKTOP = 'https://www.mozilla.org/firefox/new/',
|
||||
FX_MOBILE = 'https://www.mozilla.org/firefox/mobile/',
|
||||
FX_SYNC = 'https://www.mozilla.org/firefox/sync/',
|
||||
HUBS = 'https://hubs.mozilla.com/',
|
||||
MDN = 'https://developer.mozilla.org/',
|
||||
MONITOR = 'https://monitor.mozilla.org/',
|
||||
POCKET = 'https://getpocket.com/',
|
||||
RELAY = 'https://relay.firefox.com/',
|
||||
VPN = 'https://vpn.mozilla.org/',
|
||||
}
|
||||
|
||||
// DISPLAY_SAFE_UNICODE regex matches validation used for auth_server
|
||||
// Match display-safe unicode characters.
|
||||
// We're pretty liberal with what's allowed in a unicode string,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import {
|
||||
constructHrefWithUtm,
|
||||
deepMerge,
|
||||
isBase32Crockford,
|
||||
once,
|
||||
|
@ -147,3 +148,49 @@ describe('once', () => {
|
|||
expect(count).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructHrefWithUtm', () => {
|
||||
test('should construct URL with given UTM parameters', () => {
|
||||
const pathname = 'https://example.com';
|
||||
const utmMedium = 'mozilla-websites';
|
||||
const utmSource = 'moz-account';
|
||||
const utmTerm = 'bento';
|
||||
const utmContent = 'fx-desktop';
|
||||
const utmCampaign = 'permanent';
|
||||
|
||||
const result = constructHrefWithUtm(
|
||||
pathname,
|
||||
utmMedium,
|
||||
utmSource,
|
||||
utmTerm,
|
||||
utmContent,
|
||||
utmCampaign
|
||||
);
|
||||
|
||||
const expected =
|
||||
'https://example.com?utm_source=moz-account&utm_medium=mozilla-websites&utm_term=bento&utm_content=fx-desktop&utm_campaign=permanent';
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
test('should construct URL with different UTM parameters', () => {
|
||||
const pathname = 'https://example.com';
|
||||
const utmMedium = 'product-partnership';
|
||||
const utmSource = 'moz-account';
|
||||
const utmTerm = 'sidebar';
|
||||
const utmContent = 'vpn';
|
||||
const utmCampaign = 'connect-device';
|
||||
|
||||
const result = constructHrefWithUtm(
|
||||
pathname,
|
||||
utmMedium,
|
||||
utmSource,
|
||||
utmTerm,
|
||||
utmContent,
|
||||
utmCampaign
|
||||
);
|
||||
|
||||
const expected =
|
||||
'https://example.com?utm_source=moz-account&utm_medium=product-partnership&utm_term=sidebar&utm_content=vpn&utm_campaign=connect-device';
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -129,3 +129,32 @@ export function once(key: string, callback: () => void) {
|
|||
export function resetOnce() {
|
||||
calls = new Set<string>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a URL with UTM parameters appended to the query string.
|
||||
*
|
||||
* @param {string} pathname - The base URL path.
|
||||
* @param {'mozilla-websites' | 'product-partnership'} utmMedium - The medium through which the link is being shared.
|
||||
* @param {'moz-account'} utmSource - The source of the traffic.
|
||||
* @param {'bento' | 'sidebar'} utmTerm - The search term or keyword associated with the campaign.
|
||||
* @param {'fx-desktop' | 'fx-mobile' | 'monitor' | 'monitor-free' | 'monitor-plus' | 'relay' | 'vpn'} utmContent - The specific content or product that the link is associated with.
|
||||
* @param {'permanent' | 'settings-promo' | 'connect-device'} utmCampaign - The name of the marketing campaign.
|
||||
* @returns {string} - The constructed URL with UTM parameters.
|
||||
*/
|
||||
export const constructHrefWithUtm = (
|
||||
pathname: string,
|
||||
utmMedium: 'mozilla-websites' | 'product-partnership',
|
||||
utmSource: 'moz-account',
|
||||
utmTerm: 'bento' | 'sidebar',
|
||||
utmContent:
|
||||
| 'fx-desktop'
|
||||
| 'fx-mobile'
|
||||
| 'monitor'
|
||||
| 'monitor-free'
|
||||
| 'monitor-plus'
|
||||
| 'relay'
|
||||
| 'vpn',
|
||||
utmCampaign: 'permanent' | 'settings-promo' | 'connect-device'
|
||||
) => {
|
||||
return `${pathname}?utm_source=${utmSource}&utm_medium=${utmMedium}&utm_term=${utmTerm}&utm_content=${utmContent}&utm_campaign=${utmCampaign}`;
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче