Skip to content

Commit

Permalink
migrate header files from support-dotcom-components
Browse files Browse the repository at this point in the history
  • Loading branch information
cemms1 committed Apr 26, 2024
1 parent 8d8070a commit 35b618c
Show file tree
Hide file tree
Showing 8 changed files with 766 additions and 1 deletion.
2 changes: 1 addition & 1 deletion dotcom-rendering/src/components/SupportTheG.importable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ const ReaderRevenueLinksNative = ({
};

/**
* Container for `ReaderRevenueLinksRemote` or `ReaderRevenueLinksRemote`
* Container for `ReaderRevenueLinksRemote` or `ReaderRevenueLinksNative`
*
* ## Why does this need to be an Island?
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @file
* This file was migrated from:
* https://github.com/guardian/support-dotcom-components/blob/4925ef1e0ced5d221f1122afe79f93bd7448e0e5/packages/modules/src/modules/headers/Header.stories.tsx
*/
import type { Meta, StoryFn } from '@storybook/react';
import { HeaderDecorator } from './common/HeaderDecorator';
import { HeaderUnvalidated as Header } from './Header';

export default {
component: Header,
title: 'Headers/Header',
decorators: [HeaderDecorator],
} as Meta<typeof Header>;

const Template: StoryFn<typeof Header> = (props) => <Header {...props} />;

export const DefaultHeader = Template.bind({});
DefaultHeader.args = {
content: {
heading: 'Support the Guardian',
subheading: 'Available for everyone, funded by readers',
primaryCta: {
baseUrl: 'https://support.theguardian.com/contribute',
text: 'Contribute',
},
secondaryCta: {
baseUrl: '',
text: 'Subscribe',
},
},
mobileContent: {
heading: '',
subheading: '',
primaryCta: {
baseUrl: 'https://support.theguardian.com/contribute',
text: 'Support us',
},
},
tracking: {
ophanPageId: 'pvid',
platformId: 'GUARDIAN_WEB',
referrerUrl: 'https://theguardian.com/uk',
clientName: 'dcr',
abTestName: 'test-name',
abTestVariant: 'variant-name',
campaignCode: 'campaign-code',
componentType: 'ACQUISITIONS_HEADER',
},
countryCode: 'GB',
};
141 changes: 141 additions & 0 deletions dotcom-rendering/src/components/marketing/header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* @file
* This file was migrated from:
* https://github.com/guardian/support-dotcom-components/blob/4925ef1e0ced5d221f1122afe79f93bd7448e0e5/packages/modules/src/modules/headers/Header.tsx
*/
import { css } from '@emotion/react';
import {
from,
headline,
palette as sourcePalette,
textSans,
} from '@guardian/source-foundations';
import {
Hide,
LinkButton,
SvgArrowRightStraight,
themeButtonReaderRevenueBrand,
} from '@guardian/source-react-components';
import type { ReactComponent } from '../lib/ReactComponent';
import type { HeaderRenderProps } from './HeaderWrapper';
import { headerWrapper, validatedHeaderWrapper } from './HeaderWrapper';

const messageStyles = (isThankYouMessage: boolean) => css`
color: ${sourcePalette.brandAlt[400]};
${headline.xxsmall({ fontWeight: 'bold' })};
margin-bottom: 3px;
${from.desktop} {
${headline.xsmall({ fontWeight: 'bold' })}
}
${from.leftCol} {
${isThankYouMessage
? headline.small({ fontWeight: 'bold' })
: headline.medium({ fontWeight: 'bold' })}
}
`;

const linkStyles = css`
height: 32px;
min-height: 32px;
${textSans.small({ fontWeight: 'bold' })};
border-radius: 16px;
padding: 0 12px 0 12px;
line-height: 18px;
margin-right: 10px;
margin-bottom: 6px;
svg {
width: 24px;
}
`;

const subMessageStyles = css`
color: ${sourcePalette.neutral[100]};
${textSans.medium()};
margin: 5px 0;
`;

// override user agent styles
const headingStyles = css`
margin: 0;
font-size: 100%;
`;

const Header: ReactComponent<HeaderRenderProps> = (
props: HeaderRenderProps,
) => {
const { heading, subheading, primaryCta, secondaryCta } = props.content;

const onClick = () => {
props.onCtaClick?.();
};
return (
<div>
<Hide below="tablet">
<div css={messageStyles(false)}>
<h2 css={headingStyles}>{heading}</h2>
</div>

<div css={subMessageStyles}>
<div>{subheading}</div>
</div>
</Hide>

{primaryCta && (
<>
<Hide until="mobileLandscape">
<LinkButton
theme={themeButtonReaderRevenueBrand}
priority="primary"
href={primaryCta.ctaUrl}
onClick={onClick}
icon={<SvgArrowRightStraight />}
iconSide="right"
nudgeIcon={true}
css={linkStyles}
>
{primaryCta.ctaText}
</LinkButton>
</Hide>

<Hide from="mobileLandscape">
<LinkButton
theme={themeButtonReaderRevenueBrand}
priority="primary"
href={
props.mobileContent?.primaryCta?.ctaUrl ??
primaryCta.ctaUrl
}
css={linkStyles}
>
{props.mobileContent?.primaryCta?.ctaText ??
primaryCta.ctaText}
</LinkButton>
</Hide>
</>
)}

{secondaryCta && (
<Hide until="tablet">
<LinkButton
theme={themeButtonReaderRevenueBrand}
priority="primary"
href={secondaryCta.ctaUrl}
icon={<SvgArrowRightStraight />}
iconSide="right"
nudgeIcon={true}
css={linkStyles}
>
{secondaryCta.ctaText}
</LinkButton>
</Hide>
)}
</div>
);
};

const unvalidated = headerWrapper(Header);
const validated = validatedHeaderWrapper(Header);
export { validated as Header, unvalidated as HeaderUnvalidated };
186 changes: 186 additions & 0 deletions dotcom-rendering/src/components/marketing/header/HeaderWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/**
* @file
* This file was migrated from:
* https://github.com/guardian/support-dotcom-components/blob/4925ef1e0ced5d221f1122afe79f93bd7448e0e5/packages/modules/src/modules/headers/HeaderWrapper.tsx
*/
import type {
Cta,
HeaderProps,
OphanAction,
} from '@guardian/support-dotcom-components/dist/shared/src/types';
import { headerPropsSchema } from '@guardian/support-dotcom-components/dist/shared/src/types';
import { useCallback, useEffect } from 'react';
import { type HasBeenSeen, useHasBeenSeen } from '../hooks/useHasBeenSeen';
import type { ReactComponent } from '../lib/ReactComponent';
import {
addRegionIdAndTrackingParamsToSupportUrl,
addTrackingParamsToProfileUrl,
createClickEventFromTracking,
isProfileUrl,
} from '../lib/tracking';
import { withParsedProps } from '../shared/ModuleWrapper';

export interface HeaderEnrichedCta {
ctaUrl: string;
ctaText: string;
}

export interface HeaderRenderedContent {
heading: string;
subheading: string;
primaryCta: HeaderEnrichedCta | null;
secondaryCta: HeaderEnrichedCta | null;
benefits: string[] | null;
}

export interface HeaderRenderProps {
content: HeaderRenderedContent;
mobileContent?: HeaderRenderedContent;
onCtaClick?: () => void; // only used by sign in prompt header
}

export const headerWrapper = (
Header: ReactComponent<HeaderRenderProps>,
): ReactComponent<HeaderProps> => {
const Wrapped: ReactComponent<HeaderProps> = ({
content,
mobileContent,
tracking,
countryCode,
submitComponentEvent,
numArticles,
}) => {
const buildEnrichedCta = (cta: Cta): HeaderEnrichedCta => {
if (isProfileUrl(cta.baseUrl)) {
return {
ctaUrl: addTrackingParamsToProfileUrl(
cta.baseUrl,
tracking,
),
ctaText: cta.text,
};
}
return {
ctaUrl: addRegionIdAndTrackingParamsToSupportUrl(
cta.baseUrl,
tracking,
numArticles,
countryCode,
),
ctaText: cta.text,
};
};

const primaryCta = content.primaryCta
? buildEnrichedCta(content.primaryCta)
: null;
const secondaryCta = content.secondaryCta
? buildEnrichedCta(content.secondaryCta)
: null;
const benefits = content.benefits ?? null;

const renderedContent: HeaderRenderedContent = {
heading: content.heading,
subheading: content.subheading,
primaryCta,
secondaryCta,
benefits,
};

const mobilePrimaryCta = mobileContent?.primaryCta
? buildEnrichedCta(mobileContent.primaryCta)
: primaryCta;

const mobileSecondaryCta = mobileContent?.secondaryCta
? buildEnrichedCta(mobileContent.secondaryCta)
: secondaryCta;

const renderedMobileContent = mobileContent
? ({
heading: mobileContent.heading,
subheading: mobileContent.subheading,
primaryCta: mobilePrimaryCta,
secondaryCta: mobileSecondaryCta,
} as HeaderRenderedContent)
: undefined;

const { abTestName, abTestVariant, componentType, campaignCode } =
tracking;

const onCtaClick = (componentId: string) => {
return (): void => {
const componentClickEvent = createClickEventFromTracking(
tracking,
`${componentId} : cta`,
);
if (submitComponentEvent) {
submitComponentEvent(componentClickEvent);
}
};
};

const sendOphanEvent = useCallback(
(action: OphanAction): void => {
if (submitComponentEvent) {
submitComponentEvent({
component: {
componentType,
id: campaignCode,
campaignCode,
},
action,
abTest: {
name: abTestName,
variant: abTestVariant,
},
});
}
},
[
abTestName,
abTestVariant,
campaignCode,
componentType,
submitComponentEvent,
],
);

const [hasBeenSeen, setNode] = useHasBeenSeen(
{
threshold: 0,
},
true,
) as HasBeenSeen;

useEffect(() => {
if (hasBeenSeen) {
sendOphanEvent('VIEW');
}
}, [hasBeenSeen, sendOphanEvent]);

useEffect(() => {
sendOphanEvent('INSERT');
}, [sendOphanEvent]);

return (
<div ref={setNode}>
<Header
content={renderedContent}
mobileContent={renderedMobileContent}
onCtaClick={onCtaClick(campaignCode)}
/>
</div>
);
};
return Wrapped;
};

const validate = (props: unknown): props is HeaderProps => {
const result = headerPropsSchema.safeParse(props);
return result.success;
};

export const validatedHeaderWrapper = (
Header: ReactComponent<HeaderRenderProps>,
): ReactComponent<HeaderProps> =>
withParsedProps(headerWrapper(Header), validate);
Loading

0 comments on commit 35b618c

Please sign in to comment.