Skip to content

Commit

Permalink
Merge branch 'main' of github.com:guardian/support-frontend into gene…
Browse files Browse the repository at this point in the history
…ric-checkout-design
  • Loading branch information
jamesgorrie committed May 15, 2024
2 parents 651d120 + ccb1661 commit 61e92f9
Showing 1 changed file with 187 additions and 117 deletions.
304 changes: 187 additions & 117 deletions support-frontend/assets/pages/[countryGroupId]/checkout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Radio,
RadioGroup,
TextInput,
textInputThemeDefault,
} from '@guardian/source-react-components';
import {
FooterLinks,
Expand All @@ -33,6 +34,7 @@ import { LoadingOverlay } from 'components/loadingOverlay/loadingOverlay';
import { ContributionsOrderSummary } from 'components/orderSummary/contributionsOrderSummary';
import { PageScaffold } from 'components/page/pageScaffold';
import { DefaultPaymentButton } from 'components/paymentButton/defaultPaymentButton';
import { paymentMethodData } from 'components/paymentMethodSelector/paymentMethodData';
import { PayPalButton } from 'components/payPalPaymentButton/payPalButton';
import { StateSelect } from 'components/personalDetails/stateSelect';
import { Recaptcha } from 'components/recaptcha/recaptcha';
Expand Down Expand Up @@ -68,12 +70,12 @@ import type { IsoCountry } from 'helpers/internationalisation/country';
import { productCatalogDescription } from 'helpers/productCatalog';
import { NoFulfilmentOptions } from 'helpers/productPrice/fulfilmentOptions';
import { NoProductOptions } from 'helpers/productPrice/productOptions';
import { get } from 'helpers/storage/cookie';
import {
getOphanIds,
getReferrerAcquisitionData,
} from 'helpers/tracking/acquisitions';
import { trackComponentClick } from 'helpers/tracking/behaviour';
import { getUser } from 'helpers/user/user';
import type { GeoId } from 'pages/geoIdConfig';
import { getGeoIdConfig } from 'pages/geoIdConfig';
import { CheckoutDivider } from 'pages/supporter-plus-landing/components/checkoutDivider';
Expand All @@ -87,7 +89,8 @@ validateWindowGuardian(window.guardian);
const isTestUser = true as boolean;
const csrf = window.guardian.csrf.token;

const isSignedIn = !!get('GU_U');
const user = getUser();
const isSignedIn = user.isSignedIn;
const countryId: IsoCountry = CountryHelper.detect();

const productCatalog = window.guardian.productCatalog;
Expand Down Expand Up @@ -149,6 +152,39 @@ const fieldset = css`
}
`;

const paymentMethodSelected = css`
box-shadow: inset 0 0 0 2px ${textInputThemeDefault.textInput.borderActive};
margin-top: ${space[2]}px;
border-radius: 4px;
`;

const paymentMethodNotSelected = css`
/* Using box shadows prevents layout shift when the rows are expanded */
box-shadow: inset 0 0 0 1px ${textInputThemeDefault.textInput.border};
margin-top: ${space[2]}px;
border-radius: 4px;
`;

const paymentMethodBody = css`
padding: ${space[5]}px ${space[3]}px ${space[6]}px;
`;

const paymentMethodRadioWithImage = css`
display: inline-flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: ${space[2]}px ${space[3]}px;
font-weight: bold;
`;
const paymentMethodRadioWithImageSelected = css`
background-image: linear-gradient(
to top,
${palette.brand[500]} 2px,
transparent 2px
);
`;

/**
* This method removes the `pending` state by retrying,
* resolving on success or failure only.
Expand Down Expand Up @@ -423,13 +459,12 @@ function CheckoutComponent({ geoId }: Props) {
const [recaptchaToken, setRecaptchaToken] = useState<string>();

/** Personal details */
const [firstName, setFirstName] = useState('');
const [firstName, setFirstName] = useState(user.firstName ?? '');
const [firstNameError, setFirstNameError] = useState<string>();
const [lastName, setLastName] = useState('');
const [lastName, setLastName] = useState(user.lastName ?? '');
const [lastNameError, setLastNameError] = useState<string>();
const [email, setEmail] = useState('');
const [email, setEmail] = useState(user.email ?? '');
const [emailError, setEmailError] = useState<string>();

/** Delivery and billing addresses */
const [deliveryPostcode, setDeliveryPostcode] = useState('');
const [deliveryLineOne, setDeliveryLineOne] = useState('');
Expand Down Expand Up @@ -995,7 +1030,7 @@ function CheckoutComponent({ geoId }: Props) {
)}
</>
)}
<fieldset>
<fieldset css={fieldset}>
<legend css={legend}>
2. Payment method
<SecureTransactionIndicator
Expand All @@ -1004,116 +1039,151 @@ function CheckoutComponent({ geoId }: Props) {
/>
</legend>

{validPaymentMethods.map((paymentMethod) => {
return (
<div>
<Radio
label={paymentMethod}
name="paymentMethod"
value={paymentMethod}
onChange={() => {
setPaymentMethod(paymentMethod);
}}
/>
</div>
);
})}

{paymentMethod === 'Stripe' && (
<>
<input
type="hidden"
name="recaptchaToken"
value={recaptchaToken}
/>
<StripeCardForm
onCardNumberChange={() => {
// no-op
}}
onExpiryChange={() => {
// no-op
}}
onCvcChange={() => {
// no-op
}}
errors={{}}
recaptcha={
<Recaptcha
// We could change the parents type to Promise and uses await here, but that has
// a lot of refactoring with not too much gain
onRecaptchaCompleted={(token) => {
setStripeClientSecretInProgress(true);
setRecaptchaToken(token);
void fetch(
'/stripe/create-setup-intent/recaptcha',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
isTestUser,
stripePublicKey,
token,
}),
},
)
.then((resp) => resp.json())
.then((json) => {
setStripeClientSecret(
(json as Record<string, string>)
.client_secret,
);
setStripeClientSecretInProgress(false);
});
}}
onRecaptchaExpired={() => {
// no-op
}}
/>
}
/>
</>
)}

{paymentMethod === 'DirectDebit' && (
<DirectDebitForm
countryGroupId={countryGroupId}
accountHolderName={accountHolderName}
accountNumber={accountNumber}
accountHolderConfirmation={accountHolderConfirmation}
sortCode={sortCode}
recaptchaCompleted={false}
updateAccountHolderName={(name: string) => {
setAccountHolderName(name);
}}
updateAccountNumber={(number: string) => {
setAccountNumber(number);
}}
updateSortCode={(sortCode: string) => {
setSortCode(sortCode);
}}
updateAccountHolderConfirmation={(
confirmation: boolean,
) => {
setAccountHolderConfirmation(confirmation);
}}
recaptcha={
<Recaptcha
// We could change the parents type to Promise and uses await here, but that has
// a lot of refactoring with not too much gain
onRecaptchaCompleted={(token) => {
setRecaptchaToken(token);
}}
onRecaptchaExpired={() => {
// no-op
}}
/>
}
formError={''}
errors={{}}
/>
)}
<RadioGroup>
{validPaymentMethods.map((validPaymentMethod) => {
const selected = paymentMethod === validPaymentMethod;
const { label, icon } =
paymentMethodData[validPaymentMethod];
return (
<div
css={
selected
? paymentMethodSelected
: paymentMethodNotSelected
}
>
<div
css={[
paymentMethodRadioWithImage,
selected
? paymentMethodRadioWithImageSelected
: undefined,
]}
>
<Radio
label={label}
name="paymentMethod"
value={validPaymentMethod}
onChange={() => {
setPaymentMethod(validPaymentMethod);
}}
/>
<div>{icon}</div>
</div>
{validPaymentMethod === 'Stripe' && selected && (
<div css={paymentMethodBody}>
<input
type="hidden"
name="recaptchaToken"
value={recaptchaToken}
/>
<StripeCardForm
onCardNumberChange={() => {
// no-op
}}
onExpiryChange={() => {
// no-op
}}
onCvcChange={() => {
// no-op
}}
errors={{}}
recaptcha={
<Recaptcha
// We could change the parents type to Promise and uses await here, but that has
// a lot of refactoring with not too much gain
onRecaptchaCompleted={(token) => {
setStripeClientSecretInProgress(true);
setRecaptchaToken(token);
void fetch(
'/stripe/create-setup-intent/recaptcha',
{
method: 'POST',
headers: {
'Content-Type':
'application/json',
},
body: JSON.stringify({
isTestUser,
stripePublicKey,
token,
}),
},
)
.then((resp) => resp.json())
.then((json) => {
setStripeClientSecret(
(json as Record<string, string>)
.client_secret,
);
setStripeClientSecretInProgress(
false,
);
});
}}
onRecaptchaExpired={() => {
// no-op
}}
/>
}
/>
</div>
)}

{validPaymentMethod === 'DirectDebit' &&
selected && (
<div
css={css`
padding: ${space[5]}px ${space[3]}px
${space[6]}px;
`}
>
<DirectDebitForm
countryGroupId={countryGroupId}
accountHolderName={accountHolderName}
accountNumber={accountNumber}
accountHolderConfirmation={
accountHolderConfirmation
}
sortCode={sortCode}
recaptchaCompleted={false}
updateAccountHolderName={(name: string) => {
setAccountHolderName(name);
}}
updateAccountNumber={(number: string) => {
setAccountNumber(number);
}}
updateSortCode={(sortCode: string) => {
setSortCode(sortCode);
}}
updateAccountHolderConfirmation={(
confirmation: boolean,
) => {
setAccountHolderConfirmation(
confirmation,
);
}}
recaptcha={
<Recaptcha
// We could change the parents type to Promise and uses await here, but that has
// a lot of refactoring with not too much gain
onRecaptchaCompleted={(token) => {
setRecaptchaToken(token);
}}
onRecaptchaExpired={() => {
// no-op
}}
/>
}
formError={''}
errors={{}}
/>
</div>
)}
</div>
);
})}
</RadioGroup>
</fieldset>
</BoxContents>
</Box>
Expand Down

0 comments on commit 61e92f9

Please sign in to comment.