Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Confirm email - use pattern validation #6751

Merged
merged 12 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
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
3 changes: 2 additions & 1 deletion support-e2e/tests/test/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export const testCheckout = (testDetails: TestDetails) => {
context,
baseURL,
}) => {
const url = `/${internationalisationId.toLowerCase()}/checkout?product=${product}&ratePlan=${ratePlan}`;
// Temporary opt out of this test
const url = `/${internationalisationId.toLowerCase()}/checkout?product=${product}&ratePlan=${ratePlan}#ab-confirmEmail=control`;
const page = await context.newPage();
await setupPage(page, context, baseURL, url);

Expand Down
21 changes: 21 additions & 0 deletions support-frontend/assets/helpers/abTests/abtestDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,25 @@ export const tests: Tests = {
targetPage: pageUrlRegexes.contributions.allLandingPagesAndThankyouPages,
excludeContributionsOnlyCountries: true,
},
confirmEmail: {
variants: [
{
id: 'control',
},
{
id: 'variant',
},
],
audiences: {
ALL: {
offset: 0,
size: 1,
},
},
isActive: true,
referrerControlled: false, // ab-test name not needed to be in paramURL
seed: 5,
targetPage: pageUrlRegexes.contributions.genericCheckoutOnly,
excludeContributionsOnlyCountries: false,
},
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { TextInput } from '@guardian/source/react-components';
import { useState } from 'react';
import escapeStringRegexp from 'escape-string-regexp';
import { useRef, useState } from 'react';
import {
doesNotContainExtendedEmojiOrLeadingSpace,
preventDefaultValidityMessage,
Expand All @@ -14,6 +15,9 @@ type PersonalDetailsFieldsProps = {
email: string;
setEmail: (value: string) => void;
isEmailAddressReadOnly: boolean;
requireConfirmedEmail: boolean;
confirmedEmail: string;
setConfirmedEmail: (value: string) => void;
};

export function PersonalDetailsFields({
Expand All @@ -25,10 +29,16 @@ export function PersonalDetailsFields({
email,
setEmail,
isEmailAddressReadOnly,
requireConfirmedEmail,
confirmedEmail,
setConfirmedEmail,
}: PersonalDetailsFieldsProps) {
const [firstNameError, setFirstNameError] = useState<string>();
const [lastNameError, setLastNameError] = useState<string>();
const [emailError, setEmailError] = useState<string>();
const [confirmedEmailError, setConfirmedEmailError] = useState<string>();

const confirmEmailRef = useRef<HTMLDivElement>(null);

return (
<>
Expand All @@ -45,6 +55,7 @@ export function PersonalDetailsFields({
}}
onBlur={(event) => {
event.target.checkValidity();
confirmEmailRef.current?.querySelector('input')?.checkValidity();
GHaberis marked this conversation as resolved.
Show resolved Hide resolved
}}
readOnly={isEmailAddressReadOnly}
name="email"
Expand All @@ -66,6 +77,42 @@ export function PersonalDetailsFields({
}}
/>
</div>
{requireConfirmedEmail && !isEmailAddressReadOnly && (
<div ref={confirmEmailRef}>
Copy link
Contributor Author

@andrewHEguardian andrewHEguardian Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't put the ref on the source component, see this discussion

<TextInput
id="confirm-email"
data-qm-masking="blocklist"
label="Confirm email address"
value={confirmedEmail}
type="email"
autoComplete="email"
onChange={(event) => {
setConfirmedEmail(event.currentTarget.value);
}}
onBlur={(event) => {
event.target.checkValidity();
}}
name="confirm-email"
required
maxLength={80}
error={confirmedEmailError}
pattern={escapeStringRegexp(email)}
onInvalid={(event) => {
preventDefaultValidityMessage(event.currentTarget);
const validityState = event.currentTarget.validity;
if (validityState.valid) {
setConfirmedEmailError(undefined);
} else {
if (validityState.valueMissing) {
setConfirmedEmailError('Please confirm your email address.');
} else {
setConfirmedEmailError('The email addresses do not match.');
}
}
}}
/>
</div>
)}
{children}
<div>
<TextInput
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ export function CheckoutComponent({
abParticipations.newspaperArchiveBenefit ?? '',
);

const inConfirmEmailVariant = abParticipations.confirmEmail === 'variant';

const productDescription = showNewspaperArchiveBenefit
? productCatalogDescriptionNewBenefits(countryGroupId)[productKey]
: productCatalogDescription[productKey];
Expand Down Expand Up @@ -368,6 +370,7 @@ export function CheckoutComponent({
const [firstName, setFirstName] = useState(user?.firstName ?? '');
const [lastName, setLastName] = useState(user?.lastName ?? '');
const [email, setEmail] = useState(user?.email ?? '');
const [confirmedEmail, setConfirmedEmail] = useState('');

/** Delivery and billing addresses */
const [deliveryPostcode, setDeliveryPostcode] = useState('');
Expand Down Expand Up @@ -425,6 +428,7 @@ export function CheckoutComponent({

const formOnSubmit = async (formData: FormData) => {
setIsProcessingPayment(true);

/**
* The validation for this is currently happening on the client side form validation
* So we'll assume strings are not null.
Expand Down Expand Up @@ -558,10 +562,6 @@ export function CheckoutComponent({
labels: ['generic-checkout'],
};

if (stripeExpressCheckoutPaymentType === 'link') {
referrerAcquisitionData.labels.push('express-checkout-link');
}

if (paymentMethod && paymentFields) {
/** TODO
* - add debugInfo
Expand Down Expand Up @@ -871,6 +871,8 @@ export function CheckoutComponent({

event.billingDetails?.email &&
setEmail(event.billingDetails.email);
event.billingDetails?.email &&
setConfirmedEmail(event.billingDetails.email);

setPaymentMethod('StripeExpressCheckoutElement');
setStripeExpressCheckoutPaymentType(
Expand Down Expand Up @@ -934,6 +936,11 @@ export function CheckoutComponent({
setLastName={(lastName) => setLastName(lastName)}
email={email}
setEmail={(email) => setEmail(email)}
requireConfirmedEmail={inConfirmEmailVariant}
confirmedEmail={confirmedEmail}
setConfirmedEmail={(confirmedEmail) =>
setConfirmedEmail(confirmedEmail)
}
>
<Signout isSignedIn={isSignedIn} />
</PersonalDetailsFields>
Expand Down
1 change: 1 addition & 0 deletions support-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
"@types/uuid": "^9.0.2",
"classnames": "^2.5.1",
"dompurify": "^3.2.0",
"escape-string-regexp": "5.0.0",
"framer-motion": "^1.11.1",
"lodash.debounce": "^4.0.6",
"mockdate": "^3.0.5",
Expand Down
5 changes: 5 additions & 0 deletions support-frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5700,6 +5700,11 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=

[email protected]:
version "5.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==

escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
Expand Down