Skip to content

Commit 12262f4

Browse files
Newsletter Signup Form - signed in and out
1 parent 79c2dbc commit 12262f4

11 files changed

Lines changed: 1610 additions & 64 deletions

dotcom-rendering/.storybook/preview.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ sb.mock(import('../src/lib/useNewsletterSubscription.ts'), { spy: true });
2929
sb.mock(import('../src/lib/useAuthStatus.ts'), { spy: true });
3030
// @ts-ignore -- Storybook wants the file extension, TS does not.
3131
sb.mock(import('../src/lib/fetchEmail.ts'), { spy: true });
32+
// @ts-ignore -- Storybook wants the file extension, TS does not.
33+
sb.mock(import('../src/lib/useNewsletterSignupForm.ts'), { spy: true });
3234

3335
// Prevent components being lazy rendered when we're taking Chromatic snapshots
3436
Lazy.disabled = isChromatic();

dotcom-rendering/src/components/EmailSignUpWrapper.island.tsx

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Breakpoint } from '@guardian/source/foundations';
2+
import { useIsSignedIn } from '../lib/useAuthStatus';
23
import { useNewsletterSubscription } from '../lib/useNewsletterSubscription';
34
import { useConfig } from './ConfigContext';
45
import type { EmailSignUpProps } from './EmailSignup';
@@ -7,6 +8,7 @@ import { InlineSkipToWrapper } from './InlineSkipToWrapper';
78
import { Island } from './Island';
89
import { NewsletterPrivacyMessage } from './NewsletterPrivacyMessage';
910
import { NewsletterSignupCardContainer } from './NewsletterSignupCardContainer';
11+
import { NewsletterSignupForm } from './NewsletterSignupForm.island';
1012
import { Placeholder } from './Placeholder';
1113
import { SecureSignup } from './SecureSignup.island';
1214

@@ -65,14 +67,12 @@ export const EmailSignUpWrapper = ({
6567
showNewNewsletterSignupCard = false,
6668
}: EmailSignUpWrapperProps) => {
6769
const { renderingTarget } = useConfig();
68-
const shouldCheckSubscription =
69-
hideNewsletterSignupComponentForSubscribers &&
70-
!showNewNewsletterSignupCard;
7170
const isSubscribed = useNewsletterSubscription(
7271
listId,
7372
idApiUrl,
74-
shouldCheckSubscription,
73+
hideNewsletterSignupComponentForSubscribers,
7574
);
75+
const isSignedIn = useIsSignedIn();
7676

7777
// When the new card design is enabled, always show it regardless of subscription status
7878
if (showNewNewsletterSignupCard) {
@@ -91,14 +91,20 @@ export const EmailSignUpWrapper = ({
9191
category={category}
9292
exampleUrl={exampleUrl}
9393
renderingTarget={renderingTarget}
94+
isSignedIn={isSignedIn}
9495
>
95-
<Island priority="feature" defer={{ until: 'visible' }}>
96-
<SecureSignup
97-
newsletterId={identityName}
98-
successDescription={successDescription}
99-
/>
100-
</Island>
101-
{!hidePrivacyMessage && <NewsletterPrivacyMessage />}
96+
{(openPreview) => (
97+
<Island priority="feature" defer={{ until: 'visible' }}>
98+
<NewsletterSignupForm
99+
newsletterId={identityName}
100+
newsletterName={name}
101+
frequency={frequency}
102+
hidePrivacyMessage={isSignedIn === true}
103+
onPreviewClick={openPreview}
104+
isAlreadySubscribed={isSubscribed === true}
105+
/>
106+
</Island>
107+
)}
102108
</NewsletterSignupCardContainer>
103109
</InlineSkipToWrapper>
104110
);

dotcom-rendering/src/components/EmailSignUpWrapper.stories.tsx

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { StoryObj } from '@storybook/react-webpack5';
2-
import { mocked } from 'storybook/test';
2+
import { mocked, within } from 'storybook/test';
33
import preview from '../../.storybook/preview';
44
import { lazyFetchEmailWithTimeout } from '../lib/fetchEmail';
55
import { useIsSignedIn } from '../lib/useAuthStatus';
@@ -24,6 +24,15 @@ const defaultArgs = {
2424
successDescription: "We'll send you The Recap every week",
2525
theme: 'sport',
2626
idApiUrl: 'https://idapi.theguardian.com',
27+
exampleUrl: 'https://www.theguardian.com/email/the-recap',
28+
} satisfies Story['args'];
29+
30+
const newCardArgs = {
31+
...defaultArgs,
32+
showNewNewsletterSignupCard: true,
33+
hideNewsletterSignupComponentForSubscribers: true,
34+
illustrationSquare:
35+
'https://i.guim.co.uk/img/uploads/2023/11/01/SaturdayEdition_-_5-3.jpg?width=220&dpr=2&s=none&crop=5%3A3',
2736
} satisfies Story['args'];
2837

2938
// Loading state - shows placeholder while auth status is being determined
@@ -33,7 +42,7 @@ export const Placeholder = meta.story({
3342
hidePrivacyMessage: false,
3443
...defaultArgs,
3544
},
36-
async beforeEach() {
45+
beforeEach() {
3746
mocked(useNewsletterSubscription).mockReturnValue(undefined);
3847
},
3948
});
@@ -44,7 +53,7 @@ export const DefaultStory = meta.story({
4453
hidePrivacyMessage: true,
4554
...defaultArgs,
4655
},
47-
async beforeEach() {
56+
beforeEach() {
4857
mocked(useNewsletterSubscription).mockReturnValue(false);
4958
mocked(useIsSignedIn).mockReturnValue(false);
5059
mocked(lazyFetchEmailWithTimeout).mockReturnValue(() =>
@@ -58,7 +67,7 @@ export const DefaultStoryWithPrivacy = meta.story({
5867
hidePrivacyMessage: false,
5968
...defaultArgs,
6069
},
61-
async beforeEach() {
70+
beforeEach() {
6271
mocked(useNewsletterSubscription).mockReturnValue(false);
6372
mocked(useIsSignedIn).mockReturnValue(false);
6473
mocked(lazyFetchEmailWithTimeout).mockReturnValue(() =>
@@ -73,7 +82,7 @@ export const SignedInNotSubscribed = meta.story({
7382
hidePrivacyMessage: false,
7483
...defaultArgs,
7584
},
76-
async beforeEach() {
85+
beforeEach() {
7786
mocked(useNewsletterSubscription).mockReturnValue(false);
7887
mocked(useIsSignedIn).mockReturnValue(true);
7988
mocked(lazyFetchEmailWithTimeout).mockReturnValue(() =>
@@ -91,7 +100,7 @@ export const SignedInAlreadySubscribed = meta.story({
91100
...defaultArgs,
92101
hideNewsletterSignupComponentForSubscribers: true,
93102
},
94-
async beforeEach() {
103+
beforeEach() {
95104
mocked(useNewsletterSubscription).mockReturnValue(true);
96105
},
97106
});
@@ -104,7 +113,7 @@ export const FeatureFlagDisabled = meta.story({
104113
...defaultArgs,
105114
hideNewsletterSignupComponentForSubscribers: false,
106115
},
107-
async beforeEach() {
116+
beforeEach() {
108117
// Even though we mock this to return true (subscribed),
109118
// the feature flag being disabled means it won't be checked
110119
mocked(useNewsletterSubscription).mockReturnValue(false);
@@ -121,3 +130,63 @@ export const FeatureFlagDisabled = meta.story({
121130
},
122131
},
123132
});
133+
134+
export const NewsletterSignupCardSignedInNotSubscribed = meta.story({
135+
args: newCardArgs,
136+
beforeEach() {
137+
mocked(useNewsletterSubscription).mockReturnValue(false);
138+
mocked(useIsSignedIn).mockReturnValue(true);
139+
mocked(lazyFetchEmailWithTimeout).mockReturnValue(() =>
140+
Promise.resolve('[email protected]'),
141+
);
142+
},
143+
});
144+
145+
export const NewsletterSignupCardSignedOutNotSubscribed = meta.story({
146+
args: newCardArgs,
147+
beforeEach() {
148+
mocked(useNewsletterSubscription).mockReturnValue(false);
149+
mocked(useIsSignedIn).mockReturnValue(false);
150+
mocked(lazyFetchEmailWithTimeout).mockReturnValue(() =>
151+
Promise.resolve(null),
152+
);
153+
},
154+
});
155+
156+
export const NewsletterSignupCardSignedInAlreadySubscribed = meta.story({
157+
args: newCardArgs,
158+
beforeEach() {
159+
mocked(useNewsletterSubscription).mockReturnValue(true);
160+
mocked(useIsSignedIn).mockReturnValue(true);
161+
mocked(lazyFetchEmailWithTimeout).mockReturnValue(() =>
162+
Promise.resolve('[email protected]'),
163+
);
164+
},
165+
});
166+
167+
export const NewsletterSignupCardSignedOutAlreadySubscribed = meta.story({
168+
args: newCardArgs,
169+
beforeEach() {
170+
mocked(useNewsletterSubscription).mockReturnValue(true);
171+
mocked(useIsSignedIn).mockReturnValue(false);
172+
mocked(lazyFetchEmailWithTimeout).mockReturnValue(() =>
173+
Promise.resolve(null),
174+
);
175+
},
176+
});
177+
178+
export const NewsletterSignupCardFocused = meta.story({
179+
args: newCardArgs,
180+
beforeEach() {
181+
mocked(useNewsletterSubscription).mockReturnValue(false);
182+
mocked(useIsSignedIn).mockReturnValue(false);
183+
mocked(lazyFetchEmailWithTimeout).mockReturnValue(() =>
184+
Promise.resolve(null),
185+
);
186+
},
187+
async play({ canvasElement }) {
188+
const canvas = within(canvasElement);
189+
const emailInput = await canvas.findByLabelText('Enter your email');
190+
emailInput.focus();
191+
},
192+
});

dotcom-rendering/src/components/NewsletterSignupCard.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { css } from '@emotion/react';
22
import {
3+
from,
34
headlineMedium20,
45
space,
56
textSans14,
@@ -17,9 +18,9 @@ export type NewsletterSignupCardProps = {
1718
};
1819

1920
const containerStyles = css`
21+
clear: left;
2022
background-color: ${themePalette('--newsletter-card-background')};
21-
margin-bottom: ${space[6]}px;
22-
padding: ${space[2]}px ${space[2]}px ${space[4]}px ${space[2]}px;
23+
padding: ${space[3]}px ${space[3]}px ${space[4]}px ${space[3]}px;
2324
`;
2425

2526
const dividerStyles = css`
@@ -45,7 +46,7 @@ const titleAndMetaStyles = css`
4546

4647
const titleStyles = css`
4748
${headlineMedium20};
48-
margin-bottom: ${space[2]}px;
49+
margin-bottom: ${space[1]}px;
4950
color: ${themePalette('--newsletter-card-title')};
5051
`;
5152

@@ -68,17 +69,22 @@ const frequencyTagStyles = css`
6869
const descriptionStyles = css`
6970
${textSans14};
7071
line-height: 1.15;
71-
margin-bottom: ${space[1]}px;
72+
margin-bottom: ${space[2]}px;
7273
clear: both;
7374
color: ${themePalette('--newsletter-card-description')};
7475
`;
7576

7677
const illustrationStyles = css`
7778
flex-shrink: 0;
78-
width: 100px;
79-
height: 100px;
79+
width: 90px;
80+
height: 90px;
8081
border-radius: 50%;
8182
object-fit: cover;
83+
84+
${from.tablet} {
85+
width: 100px;
86+
height: 100px;
87+
}
8288
`;
8389

8490
const NewsletterSignupHeader = (props: {

dotcom-rendering/src/components/NewsletterSignupCardContainer.test.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ describe('NewsletterSignupCardContainer', () => {
1818
name="Morning Briefing"
1919
frequency="Every weekday"
2020
description="Start your day with top stories."
21-
/>,
21+
>
22+
{(openPreview) => (
23+
<button type="button" onClick={openPreview}>
24+
Preview latest
25+
</button>
26+
)}
27+
</NewsletterSignupCardContainer>,
2228
);
2329

2430
fireEvent.click(screen.getByRole('button', { name: 'Preview latest' }));

dotcom-rendering/src/components/NewsletterSignupCardContainer.tsx

Lines changed: 28 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
import { css } from '@emotion/react';
22
import { palette as sourcePalette, space } from '@guardian/source/foundations';
3-
import { Button, SvgEye } from '@guardian/source/react-components';
43
import { useCallback, useState } from 'react';
54
import { submitComponentEvent } from '../client/ophan/ophan';
65
import { buildNewsletterPreviewUrl } from '../lib/newsletterPreviewUrl';
7-
import { palette } from '../palette';
86
import type { RenderingTarget } from '../types/renderingTarget';
97
import { NewsletterPreviewModal } from './NewsletterPreviewModal';
8+
import { NewsletterPrivacyMessage } from './NewsletterPrivacyMessage';
109
import type { NewsletterSignupCardProps } from './NewsletterSignupCard';
1110
import { NewsletterSignupCard } from './NewsletterSignupCard';
1211

13-
const previewButtonStyles = css`
14-
margin-top: ${space[1]}px;
15-
margin-bottom: ${space[4]}px;
16-
`;
17-
1812
type PreviewEventDescription = 'preview-open' | 'preview-close';
1913

2014
const sendPreviewTracking = ({
@@ -50,13 +44,18 @@ const sendPreviewTracking = ({
5044
);
5145
};
5246

53-
type Props = NewsletterSignupCardProps & {
47+
type Props = Omit<NewsletterSignupCardProps, 'children'> & {
5448
identityName: string;
5549
category?: string;
5650
exampleUrl?: string;
5751
renderingTarget: RenderingTarget;
5852
theme: string;
59-
children?: React.ReactNode;
53+
/**
54+
* Pass the signed-in status so the container can render the privacy message
55+
* below the card (rather than inside the form) when the user is signed in.
56+
*/
57+
isSignedIn?: boolean | 'Pending';
58+
children?: (openPreview: (() => void) | undefined) => React.ReactNode;
6059
};
6160

6261
const themeColorStyles = (theme: string) => css`
@@ -76,7 +75,9 @@ export const NewsletterSignupCardContainer = ({
7675
description,
7776
illustrationSquare,
7877
children,
78+
isSignedIn,
7979
}: Props) => {
80+
const showPrivacyMessageOutside = isSignedIn === true;
8081
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
8182

8283
const renderUrl = buildNewsletterPreviewUrl({
@@ -126,37 +127,26 @@ export const NewsletterSignupCardContainer = ({
126127
onClose={closePreview}
127128
/>
128129
)}
129-
<NewsletterSignupCard
130-
name={name}
131-
frequency={frequency}
132-
description={description}
133-
illustrationSquare={illustrationSquare}
130+
<div
131+
css={css`
132+
display: flex;
133+
flex-direction: column;
134+
gap: ${space[2]}px;
135+
margin-bottom: ${space[6]}px;
136+
`}
134137
>
135-
{hasPreviewUrl && (
136-
<Button
137-
size="small"
138-
priority="tertiary"
139-
icon={<SvgEye size="small" />}
140-
iconSide="left"
141-
onClick={openPreview}
142-
cssOverrides={previewButtonStyles}
143-
theme={{
144-
textTertiary: palette(
145-
'--newsletter-preview-button-text',
146-
),
147-
borderTertiary: palette(
148-
'--newsletter-preview-button-border',
149-
),
150-
backgroundTertiaryHover: palette(
151-
'--newsletter-preview-button-hover',
152-
),
153-
}}
154-
>
155-
Preview latest
156-
</Button>
138+
<NewsletterSignupCard
139+
name={name}
140+
frequency={frequency}
141+
description={description}
142+
illustrationSquare={illustrationSquare}
143+
>
144+
{children?.(hasPreviewUrl ? openPreview : undefined)}
145+
</NewsletterSignupCard>
146+
{showPrivacyMessageOutside && (
147+
<NewsletterPrivacyMessage textColor="regular" />
157148
)}
158-
{children}
159-
</NewsletterSignupCard>
149+
</div>
160150
</div>
161151
);
162152
};

0 commit comments

Comments
 (0)