WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
007094f
more updates to cypress e2e tests to reflect the new signin/register …
Sep 4, 2025
846d4d7
make new /passcode endpoint that sign in and register share to input …
Sep 10, 2025
57713a4
update more cypress tests
Sep 29, 2025
d397448
add the welcome/complete-account route to the combined signin/registe…
Oct 10, 2025
136012a
add the change your settings terms back onto the bottom of the regist…
Oct 14, 2025
f1c6e33
refactor the consents setting on the complete-account page so that to…
Oct 29, 2025
0f90c76
Reapply "Make the /signin route a combined signin or register journey…
Oct 29, 2025
79d76bd
add 'self' to the connect-src CSP directive in the server tests
Oct 29, 2025
b1b3b11
do not auto consent users in the combined signin journey (who are reg…
Oct 30, 2025
19e4a1e
create new route for the onboarding iframed signin journey
Oct 31, 2025
711db4e
create new paths/endpoints to support signing in via an iframe on the…
Nov 10, 2025
0e2bff4
add 'self' to the connect-src CSP directive in the server tests
Nov 15, 2025
91f4b95
add iframed register route
Nov 17, 2025
93a0617
make sure the iframed register route uses the register server controller
Nov 18, 2025
fce4289
remove unused submit-consent server post endpoint.
Nov 18, 2025
2fc5176
split signin and register server GET routes into own handlers instead…
Nov 18, 2025
0fe69c5
style amends to the register/email, signin and passcode iframed pages
Nov 18, 2025
647fe07
disable iframed passcode form on submit and show spinning wheel
Nov 19, 2025
d1e1403
Force light theme on the iframed signin/register pages :s ... support…
Nov 19, 2025
71803b1
add global style to the html tag if the page is iframed in order to h…
Nov 19, 2025
19f17ae
allow client side url params to be passed and still allow the client …
Nov 20, 2025
7ea7c4b
create a readonly email input component for the iframed signin and re…
Nov 20, 2025
8bb7ae9
add optional container to the header and lead text of the MinimalLayo…
Nov 24, 2025
3f84b9e
show error message on passcode input field error when the form is sub…
Nov 25, 2025
fb47fb1
resize iframed page height when input field error are present and the…
Nov 25, 2025
046ca3e
remove duplicate client route
Nov 25, 2025
98063a2
style amends to iframed pages
Nov 25, 2025
295015d
ditinguish between iframed async form submit handler function and non…
Nov 26, 2025
8d0d0c2
remove unnecessary comment in Terms.tsx component
Nov 26, 2025
abe2b8e
amend registration e2e test button copy assertion
Nov 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cypress/integration/ete/registration_1.6.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ const existingUserSendEmailAndValidatePasscode = ({
cy.get('input[name=code]').type(code!);

cy.contains("You're signed in! Welcome to the Guardian.");
cy.get('a').contains('Continue').click();
cy.contains('Save and continue');

cy.get('[data-cy="main-form-submit-button"]').click();
cy.url().should('include', expectedReturnUrl);

cy.getTestOktaUser(emailAddress).then((user) => {
Expand Down
3 changes: 3 additions & 0 deletions src/client/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export const App = (props: Props) => {
ABTestAPI.registerCompleteEvents(allRunnableTests);
}, [ABTestAPI]);

const isPageIframed = props.location.startsWith('/iframed/');

return (
<>
<Global
Expand All @@ -37,6 +39,7 @@ export const App = (props: Props) => {
html {
height: 100%;
box-sizing: border-box;
${isPageIframed ? 'overflow: hidden;' : ''}
}
body {
height: 100%;
Expand Down
12 changes: 10 additions & 2 deletions src/client/components/EmailSentInformationBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import ThemedLink from '@/client/components/ThemedLink';
import { EmailSentProps } from '@/client/pages/EmailSent';
import { useCountdownTimer } from '@/client/lib/hooks/useCountdownTimer';

type EmailSentInformationBoxTheme = 'primary' | 'secondary';

type EmailSentInformationBoxProps = Pick<
EmailSentProps,
| 'email'
Expand All @@ -32,6 +34,8 @@ type EmailSentInformationBoxProps = Pick<
setRecaptchaErrorMessage: React.Dispatch<React.SetStateAction<string>>;
sendAgainTimerInSeconds?: number;
showSignInWithPasswordOption?: boolean;
theme?: EmailSentInformationBoxTheme;
isIframed?: boolean;
};

const sendAgainFormWrapperStyles = css`
Expand All @@ -52,11 +56,14 @@ export const EmailSentInformationBox = ({
shortRequestId,
sendAgainTimerInSeconds,
showSignInWithPasswordOption,
theme = 'primary',
isIframed = false,
}: EmailSentInformationBoxProps) => {
const timer = useCountdownTimer(sendAgainTimerInSeconds || 0);

const BoxContainer = theme === 'primary' ? InformationBox : 'div';
return (
<InformationBox>
<BoxContainer>
<InformationBoxText>
Didn’t get the email? Check your spam&#8288;
{email && resendEmailAction && (
Expand All @@ -83,6 +90,7 @@ export const EmailSentInformationBox = ({
hideRecaptchaMessage
shortRequestId={shortRequestId}
disabled={!!(sendAgainTimerInSeconds && !timer.isComplete)}
isIframed={isIframed}
>
<EmailInput defaultValue={email} hidden hideLabel />
</MainForm>
Expand Down Expand Up @@ -123,6 +131,6 @@ export const EmailSentInformationBox = ({
{SUPPORT_EMAIL}
</ExternalLink>
</InformationBoxText>
</InformationBox>
</BoxContainer>
);
};
16 changes: 16 additions & 0 deletions src/client/components/IframeThemedEmailInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Meta } from '@storybook/react';
import React from 'react';
import IframeThemedEmailInput from '@/client/components/IframeThemedEmailInput';

export default {
title: 'Components/IframeThemedEmailInput',
component: IframeThemedEmailInput,
parameters: {
layout: 'padded',
},
} as Meta;

export const Default = () => {
return <IframeThemedEmailInput label="Themed text input" />;
};
Default.storyName = 'default';
19 changes: 19 additions & 0 deletions src/client/components/IframeThemedEmailInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { TextInput, TextInputProps } from '@guardian/source/react-components';

const textInputTheme = {
textLabel: 'var(--color-input-label)',
textUserInput: 'var(--color-input-text)',
border: 'var(--color-input-border)',
backgroundInput: 'var(--color-input-highlight)',
};

const IframeThemedEmailInput = (props: Omit<TextInputProps, 'theme'>) => {
return (
<div>
<TextInput {...props} theme={textInputTheme} />
</div>
);
};

export default IframeThemedEmailInput;
130 changes: 92 additions & 38 deletions src/client/components/MainForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import {
RecaptchaWrapper,
UseRecaptchaReturnValue,
} from '@/client/lib/hooks/useRecaptcha';
import { CaptchaErrors, GatewayError } from '@/shared/model/Errors';
import {
CaptchaErrors,
GatewayError,
SubmitHandlerErrorObject,
} from '@/shared/model/Errors';
import { DetailedRecaptchaError } from '@/client/components/DetailedRecaptchaError';
import { RefTrackingFormFields } from '@/client/components/RefTrackingFormFields';
import { trackFormFocusBlur, trackFormSubmit } from '@/client/lib/ophan';
Expand All @@ -30,13 +34,16 @@ import {
} from '@/client/components/InformationBox';
import {
mainSectionStyles,
mainSectionStylesLargerGap,
primaryButtonStyles,
secondaryButtonStyles,
} from '@/client/styles/Shared';
import locations from '@/shared/lib/locations';
import { GatewayErrorSummary } from '@/client/components/GatewayErrorSummary';
import { NoScript } from './NoScript';

type TermsStyle = 'primary' | 'secondary';

export interface MainFormProps {
wideLayout?: boolean;
formAction: string;
Expand All @@ -57,26 +64,27 @@ export interface MainFormProps {
formErrorContextFromParent?: ReactNode;
hasGuardianTerms?: boolean;
hasJobsTerms?: boolean;
onSubmit?: (e: React.FormEvent<HTMLFormElement>) =>
| {
errorOccurred: boolean;
}
| undefined;
onSubmit?: (
e: React.FormEvent<HTMLFormElement>,
) => SubmitHandlerErrorObject | Promise<SubmitHandlerErrorObject> | undefined;
onInvalid?: React.FormEventHandler<HTMLFormElement> | undefined;
formTrackingName?: string;
disableOnSubmit?: boolean;
isIframed?: boolean;
largeFormMarginTop?: boolean;
displayInline?: boolean;
submitButtonLink?: boolean;
hideRecaptchaMessage?: boolean;
additionalTerms?: ReactNode[];
primaryTermsPosition?: boolean;
termsStyle?: TermsStyle;
shortRequestId?: string;
disabled?: boolean;
formRef?: React.RefObject<HTMLFormElement | null>;
}

const formStyles = (displayInline: boolean) => css`
${mainSectionStyles};
const formStyles = (displayInline: boolean, largeGap: boolean) => css`
${largeGap ? mainSectionStylesLargerGap : mainSectionStyles};
a {
${textSansBold15};
}
Expand Down Expand Up @@ -106,12 +114,15 @@ export const MainForm = ({
onInvalid,
formTrackingName,
disableOnSubmit = false,
isIframed = false,
formErrorMessageFromParent,
formErrorContextFromParent,
displayInline = false,
submitButtonLink,
hideRecaptchaMessage,
additionalTerms,
primaryTermsPosition = true,
termsStyle = 'primary',
shortRequestId,
disabled = false,
// eslint-disable-next-line react-hooks/rules-of-hooks -- allow a formRef to be passed in or use a default value, either way a ref will be defined
Expand All @@ -138,6 +149,32 @@ export const MainForm = ({

const showFormLevelReportUrl = !!formLevelErrorContext;

const submitHandlerResponseIsErrorObject = (
submitHandler:
| SubmitHandlerErrorObject
| Promise<SubmitHandlerErrorObject>
| undefined,
): submitHandler is SubmitHandlerErrorObject => {
if (!submitHandler) {
return false;
}
return !!(submitHandler as SubmitHandlerErrorObject)?.errorOccurred;
};

useEffect(() => {
if (isIframed) {
const height = document.body.scrollHeight;
window.parent.postMessage(
{
context: 'supporterOnboarding',
type: 'iframeHeightChange',
value: height,
},
'*',
);
}
}, [isIframed, disabled]);

/**
* Executes the reCAPTCHA check and form submit tracking.
* Prevents the form from submitting until the reCAPTCHA check is complete.
Expand All @@ -149,24 +186,29 @@ export const MainForm = ({
if (formTrackingName) {
trackFormSubmit(formTrackingName);
}
setIsFormDisabled(disableOnSubmit);

const errorInSubmitHandler = onSubmit?.(event)?.errorOccurred;
void (async () => {
const submitHandler = await onSubmit?.(event);
const errorInSubmitHandler =
submitHandlerResponseIsErrorObject(submitHandler);

if (disableOnSubmit) {
if (errorInSubmitHandler === undefined) {
if (!isFormDisabled) {
setIsFormDisabled(true);
if (disableOnSubmit) {
if (errorInSubmitHandler === undefined) {
if (!isFormDisabled) {
setIsFormDisabled(true);
}
} else {
const formSubmitSuccess = !errorInSubmitHandler;
setIsFormDisabled(formSubmitSuccess);
}
} else {
const formSubmitSuccess = !errorInSubmitHandler;
setIsFormDisabled(formSubmitSuccess);
}
}

if (recaptchaEnabled && !recaptchaState?.token) {
event.preventDefault();
recaptchaState?.executeCaptcha();
}
if (recaptchaEnabled && !recaptchaState?.token) {
event.preventDefault();
recaptchaState?.executeCaptcha();
}
})();
},
[
formTrackingName,
Expand Down Expand Up @@ -263,9 +305,35 @@ export const MainForm = ({
setRecaptchaErrorMessage,
]);

const Terms = ({ theme }: { theme: TermsStyle }) => {
const BoxContainer = theme === 'primary' ? InformationBox : 'div';
return (
<>
{(additionalTerms ||
hasGuardianTerms ||
hasJobsTerms ||
(recaptchaEnabled && !hideRecaptchaMessage)) && (
<BoxContainer>
{hasGuardianTerms && <GuardianTerms />}
{hasJobsTerms && <JobsTerms />}
{additionalTerms &&
additionalTerms.map((specificTermsItem) => {
return (
<InformationBoxText>{specificTermsItem}</InformationBoxText>
);
})}
{recaptchaEnabled && !hideRecaptchaMessage && (
<RecaptchaTerms isIframed={isIframed} />
)}
</BoxContainer>
)}
</>
);
};

return (
<form
css={formStyles(displayInline)}
css={formStyles(displayInline, isIframed)}
method="post"
action={formAction}
onSubmit={handleSubmit}
Expand Down Expand Up @@ -299,22 +367,7 @@ export const MainForm = ({
<CsrfFormField />
<RefTrackingFormFields />
{children}
{(additionalTerms ||
hasGuardianTerms ||
hasJobsTerms ||
(recaptchaEnabled && !hideRecaptchaMessage)) && (
<InformationBox>
{hasGuardianTerms && <GuardianTerms />}
{hasJobsTerms && <JobsTerms />}
{additionalTerms &&
additionalTerms.map((specificTermsItem) => {
return (
<InformationBoxText>{specificTermsItem}</InformationBoxText>
);
})}
{recaptchaEnabled && !hideRecaptchaMessage && <RecaptchaTerms />}
</InformationBox>
)}
{primaryTermsPosition && <Terms theme={termsStyle} />}

{submitButtonLink ? (
<ButtonLink
Expand Down Expand Up @@ -344,6 +397,7 @@ export const MainForm = ({
{submitButtonText}
</Button>
)}
{!primaryTermsPosition && <Terms theme={termsStyle} />}
</form>
);
};
15 changes: 14 additions & 1 deletion src/client/components/PasscodeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface PasscodeInputProps extends TextInputProps {
passcode?: string;
fieldErrors?: FieldError[];
formRef: React.RefObject<HTMLFormElement | null>;
isIframed?: boolean;
}

const passcodeInputStyles = css`
Expand All @@ -22,6 +23,7 @@ export const PasscodeInput = ({
fieldErrors,
formRef,
autoFocus,
isIframed = false,
}: PasscodeInputProps) => {
/**
* In gateway we normally avoid using client side javascript, but in this case
Expand Down Expand Up @@ -62,7 +64,18 @@ export const PasscodeInput = ({
button?.dispatchEvent(new MouseEvent('click'));
}
}
}, [fieldErrors, formRef, input.value, userInteracted]);
if (isIframed) {
const height = document.body.scrollHeight;
window.parent.postMessage(
{
context: 'supporterOnboarding',
type: 'iframeHeightChange',
value: height,
},
'*',
);
}
}, [fieldErrors, formRef, input.value, userInteracted, isIframed]);

// Update the input value on user input
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down
1 change: 1 addition & 0 deletions src/client/components/RegistrationConsents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface RegistrationConsentsProps {
geolocation?: GeoLocation;
appName?: AppName;
isJobs?: boolean;
onChange?: (id: string, checked: boolean) => void;
}

export const RegistrationConsents = ({
Expand Down
Loading
Loading