Merge pull request #17403 from mozilla/FXA-8635

feat(react): Build out Index / email-first component
This commit is contained in:
Lauren Zugai 2024-08-28 14:53:06 -05:00 коммит произвёл GitHub
Родитель 18ecdc8ad7 a34dc09ecb
Коммит 3719aa8c7c
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 315 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,13 @@
## Index / home page
index-header = Enter your email
index-sync-header = Continue to your { -product-mozilla-account }
index-sync-subheader = Sync your passwords, tabs, and bookmarks everywhere you use { -brand-firefox }.
# $serviceName - the service (e.g., Pontoon) that the user is signing into with a Mozilla account
index-subheader-with-servicename = Continue to { $serviceName }
index-subheader-with-logo = Continue to <span>{ $serviceLogo }</span>
index-subheader-default = Continue to account settings
index-cta = Sign up or sign in
index-account-info = A { -product-mozilla-account } also unlocks access to more privacy-protecting products from { -brand-mozilla }.
index-email-input =
.label = Enter your email

Просмотреть файл

@ -0,0 +1,53 @@
/* 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 Index from '.';
import { Meta } from '@storybook/react';
import { withLocalization } from 'fxa-react/lib/storybooks';
import { IndexProps } from './interfaces';
import {
createMockIndexOAuthIntegration,
createMockIndexSyncIntegration,
Subject,
} from './mocks';
import {
MONITOR_CLIENTIDS,
POCKET_CLIENTIDS,
} from '../../models/integrations/client-matching';
import { MozServices } from '../../lib/types';
export default {
title: 'Pages/Index',
component: Index,
decorators: [withLocalization],
} as Meta;
const storyWithProps = ({
...props // overrides
}: Partial<IndexProps> = {}) => {
const story = () => <Subject {...props} />;
return story;
};
export const Default = storyWithProps();
export const Sync = storyWithProps({
integration: createMockIndexSyncIntegration(),
serviceName: MozServices.FirefoxSync,
});
export const Monitor = storyWithProps({
integration: createMockIndexOAuthIntegration({
clientId: MONITOR_CLIENTIDS[0],
}),
serviceName: MozServices.Monitor,
});
export const Pocket = storyWithProps({
integration: createMockIndexOAuthIntegration({
clientId: POCKET_CLIENTIDS[0],
}),
serviceName: MozServices.Pocket,
});

Просмотреть файл

@ -0,0 +1,99 @@
/* 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 { screen } from '@testing-library/react';
import {
createMockIndexOAuthIntegration,
createMockIndexSyncIntegration,
Subject,
} from './mocks';
import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider';
import { POCKET_CLIENTIDS } from '../../models/integrations/client-matching';
import { MozServices } from '../../lib/types';
const syncText =
'Sync your passwords, tabs, and bookmarks everywhere you use Firefox.';
const syncTextSecondary =
'A Mozilla account also unlocks access to more privacy-protecting products from Mozilla.';
function thirdPartyAuthWithSeparatorRendered() {
screen.getByText('or');
screen.getByRole('button', {
name: /Continue with Google/,
});
screen.getByRole('button', {
name: /Continue with Apple/,
});
}
describe('Index page', () => {
it('renders as expected with web integration', () => {
renderWithLocalizationProvider(<Subject />);
screen.getByRole('heading', { name: 'Enter your email' });
screen.getByText('Continue to account settings');
screen.getByLabelText('Enter your email');
screen.getByRole('button', { name: 'Sign up or sign in' });
expect(screen.queryByText(syncText)).not.toBeInTheDocument();
expect(screen.queryByText(syncTextSecondary)).not.toBeInTheDocument();
thirdPartyAuthWithSeparatorRendered();
expect(
screen.getByRole('link', {
name: /Terms of Service/,
})
).toHaveAttribute('href', '/legal/terms');
});
it('renders as expected when sync', () => {
renderWithLocalizationProvider(
<Subject
integration={createMockIndexSyncIntegration()}
serviceName={MozServices.FirefoxSync}
/>
);
screen.getByRole('heading', { name: 'Continue to your Mozilla account' });
screen.getByText(syncText);
screen.getByText(syncTextSecondary);
expect(
screen.queryByRole('button', { name: /Continue with Google/ })
).not.toBeInTheDocument();
expect(
screen.queryByRole('button', { name: /Continue with Apple/ })
).not.toBeInTheDocument();
expect(
screen.getByRole('link', {
name: /Terms of Service/,
})
).toHaveAttribute('href', '/legal/terms');
});
it('renders as expected when client is Pocket', () => {
renderWithLocalizationProvider(
<Subject
integration={createMockIndexOAuthIntegration({
clientId: POCKET_CLIENTIDS[0],
})}
serviceName={MozServices.Pocket}
/>
);
screen.getByRole('heading', { name: 'Enter your email' });
screen.getByAltText('Pocket');
thirdPartyAuthWithSeparatorRendered();
const tosLinks = screen.getAllByRole('link', {
name: /Terms of Service/,
});
expect(tosLinks[0]).toHaveAttribute('href', 'https://getpocket.com/tos/');
expect(tosLinks[1]).toHaveAttribute('href', '/legal/terms');
});
});

Просмотреть файл

@ -0,0 +1,78 @@
/* 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 { RouteComponentProps } from '@reach/router';
import { IndexProps } from './interfaces';
import AppLayout from '../../components/AppLayout';
import CardHeader from '../../components/CardHeader';
import InputText from '../../components/InputText';
import { FtlMsg } from 'fxa-react/lib/utils';
import ThirdPartyAuth from '../../components/ThirdPartyAuth';
import TermsPrivacyAgreement from '../../components/TermsPrivacyAgreement';
import { isOAuthIntegration } from '../../models';
import {
isClientMonitor,
isClientPocket,
} from '../../models/integrations/client-matching';
export const Index = ({
integration,
serviceName,
}: IndexProps & RouteComponentProps) => {
const clientId = integration.getService();
const isSync = integration.isSync();
const isOAuth = isOAuthIntegration(integration);
const isPocketClient = isOAuth && isClientPocket(clientId);
const isMonitorClient = isOAuth && isClientMonitor(clientId);
return (
<AppLayout>
{isSync ? (
<>
<h1 className="card-header">
<FtlMsg id="index-sync-header">
Continue to your Mozilla account
</FtlMsg>
</h1>
<p className="mt-1 mb-9 text-sm">
<FtlMsg id="index-sync-subheader">
Sync your passwords, tabs, and bookmarks everywhere you use
Firefox.
</FtlMsg>
</p>
</>
) : (
<CardHeader
headingText="Enter your email"
headingTextFtlId="index-header"
subheadingWithDefaultServiceFtlId="index-subheader-default"
subheadingWithCustomServiceFtlId="index-subheader-with-servicename"
subheadingWithLogoFtlId="index-subheader-with-logo"
{...{ clientId, serviceName }}
/>
)}
<FtlMsg id="index-email-input" attrs={{ label: true }}>
<InputText className="mt-8" type="email" label="Enter your email" />
</FtlMsg>
<div className="flex mt-5">
<button className="cta-primary cta-xl" type="submit">
<FtlMsg id="index-cta">Sign up or sign in</FtlMsg>
</button>
</div>
{isSync ? (
<p className="mt-5 text-xs text-grey-500">
<FtlMsg id="index-account-info">
A Mozilla account also unlocks access to more privacy-protecting
products from Mozilla.
</FtlMsg>
</p>
) : (
<ThirdPartyAuth showSeparator viewName="index" />
)}
<TermsPrivacyAgreement {...{ isPocketClient, isMonitorClient }} />
</AppLayout>
);
};
export default Index;

Просмотреть файл

@ -0,0 +1,16 @@
/* 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 { MozServices } from '../../lib/types';
import { Integration } from '../../models';
export type IndexIntegration = Pick<
Integration,
'type' | 'isSync' | 'getService'
>;
export interface IndexProps {
integration: IndexIntegration;
serviceName: MozServices;
}

Просмотреть файл

@ -0,0 +1,56 @@
/* 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 { LocationProvider } from '@reach/router';
import { MozServices } from '../../lib/types';
import { IntegrationType } from '../../models';
import { IndexIntegration } from './interfaces';
import Index from '.';
import { MOCK_CLIENT_ID } from '../mocks';
export function createMockIndexWebIntegration(): IndexIntegration {
return {
type: IntegrationType.Web,
isSync: () => false,
getService: () => undefined,
};
}
export function createMockIndexSyncIntegration(): IndexIntegration {
return {
type: IntegrationType.OAuth,
isSync: () => true,
getService: () => MOCK_CLIENT_ID,
};
}
export function createMockIndexOAuthIntegration({
clientId = MOCK_CLIENT_ID,
}): IndexIntegration {
return {
type: IntegrationType.OAuth,
isSync: () => false,
getService: () => clientId,
};
}
export const Subject = ({
integration = createMockIndexWebIntegration(),
serviceName = MozServices.Default,
}: {
integration?: IndexIntegration;
serviceName?: MozServices;
}) => {
return (
<LocationProvider>
<Index
{...{
integration,
serviceName,
}}
/>
</LocationProvider>
);
};