From 38391c7ed1a640d5ef9e9c61b57276643699ee61 Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:30:13 +0000 Subject: [PATCH 1/9] refactor: remove noUncheckedIndexedAccess override and fix all occurences --- .../components/forms/customFields/options.tsx | 2 +- .../forms/customFields/sortedOptions.tsx | 6 ++-- .../assets/components/gridImage/gridImage.tsx | 5 ++++ .../PaymentMethodSelectorContainer.tsx | 1 + .../hooks/payerDetails.ts | 2 +- .../address/addressFields.tsx | 3 +- .../address/postcodeFinder.tsx | 5 ++-- .../paymentMethodSelector.tsx | 1 + .../supportReminder/supportReminderItems.tsx | 5 ++++ .../thankYou/thankYouModuleData.tsx | 6 +++- .../assets/helpers/abTests/abtest.ts | 20 +++++-------- .../assets/helpers/abTests/helpers.ts | 8 ++--- .../assets/helpers/campaigns/campaigns.tsx | 13 ++++---- .../helpers/customHooks/useHasBeenSeen.ts | 2 +- .../globalsAndSwitches/globals.test.ts | 2 +- .../assets/helpers/images/theGrid.ts | 5 ++++ .../internationalisation/classes/country.ts | 4 +-- .../redux/checkout/addressMeta/reducer.ts | 2 +- .../checkout/product/selectors/productType.ts | 2 +- .../assets/helpers/redux/user/reducer.ts | 5 ++-- .../assets/helpers/storage/cookie.ts | 5 ++-- .../subscriptionsForms/deliveryDays.ts | 5 +++- .../supporterPlus/benefitsThreshold.ts | 16 ++++++---- .../assets/helpers/tracking/acquisitions.ts | 19 +++++++----- .../helpers/tracking/googleTagManager.ts | 5 ++-- .../assets/helpers/tracking/quantumMetric.ts | 4 +-- .../tracking/thirdPartyTrackingConsent.ts | 5 +++- support-frontend/assets/helpers/urls/url.ts | 2 +- support-frontend/assets/helpers/user/user.ts | 4 +++ .../checkout/helpers/getProductFields.ts | 4 ++- .../components/checkoutComponent.tsx | 10 +++++-- .../components/headerCards.tsx | 6 ++-- .../pages/aus-moment-map/ausMomentMap.tsx | 4 +-- .../components/testimonialsContainer.tsx | 2 +- .../aus-moment-map/hooks/useWindowWidth.ts | 5 +--- .../setup/setUpRedux.ts | 2 +- .../components/deliveryAgentsSelect.tsx | 4 +++ .../components/paperCheckoutForm.test.tsx | 2 +- .../helpers/__tests__/deliveryDays.test.ts | 30 +++++++++---------- .../helpers/homeDeliveryDays.ts | 19 ++++++++---- .../helpers/options.ts | 2 +- .../helpers/subsCardDays.ts | 7 +++-- .../twoStepPages/threeTierLanding.tsx | 21 ++++++------- .../components/weeklyCheckoutForm.test.tsx | 4 +-- .../weeklyCheckoutFormGifting.test.tsx | 2 +- .../helpers/__tests__/deliveryDays.ts | 6 ++-- .../helpers/deliveryDays.ts | 7 +++-- .../weeklySubscriptionCheckout.tsx | 2 +- .../PaymentFrequencyTabs.stories.tsx | 2 +- support-frontend/tsconfig.json | 5 ---- 50 files changed, 185 insertions(+), 125 deletions(-) diff --git a/support-frontend/assets/components/forms/customFields/options.tsx b/support-frontend/assets/components/forms/customFields/options.tsx index 57e526dd74..7a5e82ef1f 100644 --- a/support-frontend/assets/components/forms/customFields/options.tsx +++ b/support-frontend/assets/components/forms/customFields/options.tsx @@ -6,7 +6,7 @@ const options = (optionsForMapping: Record): JSX.Element => { return ( <> {Object.keys(optionsForMapping).map((key) => ( - + ))} ); diff --git a/support-frontend/assets/components/forms/customFields/sortedOptions.tsx b/support-frontend/assets/components/forms/customFields/sortedOptions.tsx index ff15212cee..6a653fb19d 100644 --- a/support-frontend/assets/components/forms/customFields/sortedOptions.tsx +++ b/support-frontend/assets/components/forms/customFields/sortedOptions.tsx @@ -7,11 +7,13 @@ function sortedOptions(optionsForSorting: Record): JSX.Element { <> {Object.keys(optionsForSorting) .sort((a, b) => - optionsForSorting[a].localeCompare(optionsForSorting[b]), + (optionsForSorting[a] ?? '').localeCompare( + optionsForSorting[b] ?? '', + ), ) .map((key) => ( ))} diff --git a/support-frontend/assets/components/gridImage/gridImage.tsx b/support-frontend/assets/components/gridImage/gridImage.tsx index ffa32fa594..63e2536e2a 100644 --- a/support-frontend/assets/components/gridImage/gridImage.tsx +++ b/support-frontend/assets/components/gridImage/gridImage.tsx @@ -30,6 +30,11 @@ export default function GridImage(props: PropTypes): JSX.Element | null { const sorted = props.srcSizes.sort(ascending); const srcSet = gridSrcset(props.gridId, sorted, props.imgType); const fallbackSize = sorted.find((_) => _ > MIN_IMG_WIDTH) ?? sorted[0]; + + if (!fallbackSize) { + return null; + } + const fallbackSrc = gridUrl(props.gridId, fallbackSize, props.imgType); return ( { availablePaymentMethods.length === 1 && + availablePaymentMethods[0] && dispatch(setPaymentMethod({ paymentMethod: availablePaymentMethods[0] })); }, []); diff --git a/support-frontend/assets/components/paymentRequestButton/hooks/payerDetails.ts b/support-frontend/assets/components/paymentRequestButton/hooks/payerDetails.ts index 0d74df0662..475a1f5eae 100644 --- a/support-frontend/assets/components/paymentRequestButton/hooks/payerDetails.ts +++ b/support-frontend/assets/components/paymentRequestButton/hooks/payerDetails.ts @@ -24,7 +24,7 @@ function setPayerName( const nameParts = payerName.trim().replace(/\s+/g, ' ').split(' '); if (nameParts.length > 1) { - dispatch(setFirstName(nameParts[0])); + dispatch(setFirstName(nameParts[0] ?? '')); dispatch(setLastName(nameParts.slice(1).join(' '))); } else if (nameParts.length === 1) { logException( diff --git a/support-frontend/assets/components/subscriptionCheckouts/address/addressFields.tsx b/support-frontend/assets/components/subscriptionCheckouts/address/addressFields.tsx index 4360b3a789..b297bd4b4c 100644 --- a/support-frontend/assets/components/subscriptionCheckouts/address/addressFields.tsx +++ b/support-frontend/assets/components/subscriptionCheckouts/address/addressFields.tsx @@ -202,7 +202,8 @@ export function AddressFields({ scope, countryGroupId, ...props }: PropTypes) { if (countryGroupId !== selectedCountryGroup) { const pathname = window.location.pathname; - const currentInternationalisationId = pathname.split('/')[1]; + const currentInternationalisationId = + pathname.split('/')[1] ?? ''; const selectedInternationalisationId = countryGroups[selectedCountryGroup as CountryGroupId] .supportInternationalisationId; diff --git a/support-frontend/assets/components/subscriptionCheckouts/address/postcodeFinder.tsx b/support-frontend/assets/components/subscriptionCheckouts/address/postcodeFinder.tsx index a37117088e..9673420dea 100644 --- a/support-frontend/assets/components/subscriptionCheckouts/address/postcodeFinder.tsx +++ b/support-frontend/assets/components/subscriptionCheckouts/address/postcodeFinder.tsx @@ -101,8 +101,9 @@ interface AddressSelectorProps { function AddressSelector({ results, onAddressSelected }: AddressSelectorProps) { function handleAddressSelected(e: React.ChangeEvent) { const resultIndex = Number.parseInt(e.currentTarget.value); - if (results[resultIndex]) { - onAddressSelected(results[resultIndex]); + const result = results[resultIndex]; + if (result) { + onAddressSelected(result); } } diff --git a/support-frontend/assets/components/subscriptionCheckouts/paymentMethodSelector.tsx b/support-frontend/assets/components/subscriptionCheckouts/paymentMethodSelector.tsx index 3b4f8cfd77..4c052df8e0 100644 --- a/support-frontend/assets/components/subscriptionCheckouts/paymentMethodSelector.tsx +++ b/support-frontend/assets/components/subscriptionCheckouts/paymentMethodSelector.tsx @@ -104,6 +104,7 @@ function PaymentMethodSelector({ }: PropTypes): JSX.Element { useEffect(() => { availablePaymentMethods.length === 1 && + availablePaymentMethods[0] && setPaymentMethod(availablePaymentMethods[0]); }, []); diff --git a/support-frontend/assets/components/thankYou/supportReminder/supportReminderItems.tsx b/support-frontend/assets/components/thankYou/supportReminder/supportReminderItems.tsx index 930b52682d..8abf40d4c7 100644 --- a/support-frontend/assets/components/thankYou/supportReminder/supportReminderItems.tsx +++ b/support-frontend/assets/components/thankYou/supportReminder/supportReminderItems.tsx @@ -191,6 +191,11 @@ export function SupportReminderCTAandPrivacy({ } & SupportReminderState): JSX.Element { const setReminder = () => { const choice = reminderChoices[supportReminderState.selectedChoiceIndex]; + + if (!choice) { + return; + } + const url = getReminderUrl(choice); if (!isCodeOrProd()) { diff --git a/support-frontend/assets/components/thankYou/thankYouModuleData.tsx b/support-frontend/assets/components/thankYou/thankYouModuleData.tsx index 8452189bd9..7be65ef9d9 100644 --- a/support-frontend/assets/components/thankYou/thankYouModuleData.tsx +++ b/support-frontend/assets/components/thankYou/thankYouModuleData.tsx @@ -242,7 +242,11 @@ export const getThankYouModuleData = ( bodyCopy: ( <> ), diff --git a/support-frontend/assets/helpers/abTests/abtest.ts b/support-frontend/assets/helpers/abTests/abtest.ts index 7df326a844..2a44a04380 100644 --- a/support-frontend/assets/helpers/abTests/abtest.ts +++ b/support-frontend/assets/helpers/abTests/abtest.ts @@ -1,7 +1,3 @@ -/* eslint "@typescript-eslint/no-unnecessary-condition": "off" -- this is while we are fixing `noUncheckedIndexedAccess` errors */ - -// ----- Imports ----- // - import seedrandom from 'seedrandom'; import { contributionsOnlyAmountsTestName } from 'helpers/contributions'; import type { Settings } from 'helpers/globalsAndSwitches/settings'; @@ -427,16 +423,10 @@ function getAmountsTestVariant( const { testName, liveTestName, seed, variants, isLive } = targetedTest; - if (!variants.length) { - return { - selectedAmountsVariant: getFallbackAmounts(countryGroupId), - }; - } - const selectVariant = ( isLive: boolean, variants: AmountsVariant[], - ): AmountsVariant => { + ): AmountsVariant | undefined => { if (isLive && variants.length > 1) { const assignmentIndex = randomNumber(mvt, seed) % variants.length; @@ -453,9 +443,15 @@ function getAmountsTestVariant( const amountsParticipation = buildParticipation( targetedTest, currentTestName, - variant.variantName, + variant?.variantName ?? '', ); + if (!variant) { + return { + selectedAmountsVariant: getFallbackAmounts(countryGroupId), + }; + } + return { selectedAmountsVariant: { ...variant, diff --git a/support-frontend/assets/helpers/abTests/helpers.ts b/support-frontend/assets/helpers/abTests/helpers.ts index 3cee31bab2..e6f345b065 100644 --- a/support-frontend/assets/helpers/abTests/helpers.ts +++ b/support-frontend/assets/helpers/abTests/helpers.ts @@ -4,7 +4,7 @@ import type { } from 'helpers/contributions'; import type { CountryGroupId } from 'helpers/internationalisation/countryGroup'; -export const FALLBACK_AMOUNTS: AmountsTest[] = [ +export const FALLBACK_AMOUNTS = [ { testName: 'FALLBACK_AMOUNTS__GBPCountries', liveTestName: '', @@ -250,16 +250,14 @@ export const FALLBACK_AMOUNTS: AmountsTest[] = [ }, ], }, -]; +] as const satisfies readonly AmountsTest[]; export function getFallbackAmounts( countryGroupId: CountryGroupId, ): SelectedAmountsVariant { // Create fallback data - the amounts card must always have data to dsplay const fallbackTest = FALLBACK_AMOUNTS.find( - (t) => - t.targeting.targetingType === 'Region' && - t.targeting.region === countryGroupId, + (t) => t.targeting.region === countryGroupId, ); if (fallbackTest) { return { diff --git a/support-frontend/assets/helpers/campaigns/campaigns.tsx b/support-frontend/assets/helpers/campaigns/campaigns.tsx index 5ab38e4d60..0e7ab63989 100644 --- a/support-frontend/assets/helpers/campaigns/campaigns.tsx +++ b/support-frontend/assets/helpers/campaigns/campaigns.tsx @@ -175,11 +175,14 @@ export function getCampaignSettings( promoCode?: string | null, ): CampaignSettings | null { for (const campaignId in campaigns) { - const isEligible = - isCampaignEnabled(campaignId) && - campaigns[campaignId].isEligible(countryGroupId, promoCode); - if (isEligible || forceCampaign(campaignId)) { - return campaigns[campaignId]; + const campaign = campaigns[campaignId]; + if (campaign) { + const isEligible = + isCampaignEnabled(campaignId) && + campaign.isEligible(countryGroupId, promoCode); + if (isEligible || forceCampaign(campaignId)) { + return campaign; + } } } return null; diff --git a/support-frontend/assets/helpers/customHooks/useHasBeenSeen.ts b/support-frontend/assets/helpers/customHooks/useHasBeenSeen.ts index 9e8b2c618a..c46b67fc5d 100644 --- a/support-frontend/assets/helpers/customHooks/useHasBeenSeen.ts +++ b/support-frontend/assets/helpers/customHooks/useHasBeenSeen.ts @@ -16,7 +16,7 @@ const useHasBeenSeen = ( // Enabling debouncing ensures the target element intersects for at least // 200ms before the callback is executed const intersectionFn: IntersectionObserverCallback = ([entry]) => { - if (entry.isIntersecting) { + if (entry?.isIntersecting) { setHasBeenSeen(true); } }; diff --git a/support-frontend/assets/helpers/globalsAndSwitches/globals.test.ts b/support-frontend/assets/helpers/globalsAndSwitches/globals.test.ts index d4b39bc41b..bb2d627799 100644 --- a/support-frontend/assets/helpers/globalsAndSwitches/globals.test.ts +++ b/support-frontend/assets/helpers/globalsAndSwitches/globals.test.ts @@ -20,7 +20,7 @@ const getSpecifiedRegionAmountsFromGlobal = ( if (!testArray.length) { return {}; } - return testArray[0]; + return testArray[0] ?? {}; }; describe('getGlobal', () => { diff --git a/support-frontend/assets/helpers/images/theGrid.ts b/support-frontend/assets/helpers/images/theGrid.ts index 51908840d3..bf5e9509c1 100644 --- a/support-frontend/assets/helpers/images/theGrid.ts +++ b/support-frontend/assets/helpers/images/theGrid.ts @@ -19,6 +19,11 @@ export function gridUrl( imgType: ImageType = 'jpg', ): string { const gridReference = imageCatalogue[gridId]; + + if (!gridReference) { + return ''; + } + const originalWidth = gridReference.split('_')[2]; const path = `/img/media/${imageCatalogue[gridId]}/master/${originalWidth}.${imgType}?dpr=1&s=none&width=${size}`; const url = new URL(path, GRID_DOMAIN); diff --git a/support-frontend/assets/helpers/internationalisation/classes/country.ts b/support-frontend/assets/helpers/internationalisation/classes/country.ts index 575721ff77..9c04a3b9a6 100644 --- a/support-frontend/assets/helpers/internationalisation/classes/country.ts +++ b/support-frontend/assets/helpers/internationalisation/classes/country.ts @@ -44,7 +44,7 @@ export class Country { ? searchUppercase : Object.keys(states).find( (key) => - states[key].toUpperCase() === searchUppercase || + states[key]?.toUpperCase() === searchUppercase || (searchUppercase.length === 3 && searchUppercase.startsWith(key)), ); } @@ -223,7 +223,7 @@ export class Country { if ( path !== paths[targetCountryGroup][0] && - !path.startsWith(paths[targetCountryGroup][1]) && + !path.startsWith(paths[targetCountryGroup][1] ?? '') && countryGroupId !== targetCountryGroup ) { return null; diff --git a/support-frontend/assets/helpers/redux/checkout/addressMeta/reducer.ts b/support-frontend/assets/helpers/redux/checkout/addressMeta/reducer.ts index 0263c7fcfb..9d40fedb95 100644 --- a/support-frontend/assets/helpers/redux/checkout/addressMeta/reducer.ts +++ b/support-frontend/assets/helpers/redux/checkout/addressMeta/reducer.ts @@ -32,7 +32,7 @@ export const addressMetaSlice = createSlice({ state.deliveryAgent.isLoading = false; state.deliveryAgent.response = action.payload; if (action.payload.agents?.length === 1) { - state.deliveryAgent.chosenAgent = action.payload.agents[0].agentId; + state.deliveryAgent.chosenAgent = action.payload.agents[0]?.agentId; } }, ); diff --git a/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts b/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts index 097c3f84e6..ade100c5f6 100644 --- a/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts +++ b/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts @@ -45,7 +45,7 @@ function getSubscriptionTypeFromURL(): SubscriptionProduct { }; const [, match] = urlPathRegex.exec(window.location.pathname) ?? []; if (match) { - return productsToUrlPath[match]; + return productsToUrlPath[match] ?? DigitalPack; } return DigitalPack; } diff --git a/support-frontend/assets/helpers/redux/user/reducer.ts b/support-frontend/assets/helpers/redux/user/reducer.ts index 2b500f3441..584d633e17 100644 --- a/support-frontend/assets/helpers/redux/user/reducer.ts +++ b/support-frontend/assets/helpers/redux/user/reducer.ts @@ -11,8 +11,9 @@ export const userSlice = createSlice({ state.isSignedIn = action.payload; }, setTestUserStatus(state, action: PayloadAction>) { - state.isTestUser = action.payload.isTestUser; - state.isPostDeploymentTestUser = action.payload.isPostDeploymentTestUser; + state.isTestUser = action.payload.isTestUser ?? false; + state.isPostDeploymentTestUser = + action.payload.isPostDeploymentTestUser ?? false; }, setIsReturningContributor(state, action: PayloadAction) { state.isReturningContributor = action.payload; diff --git a/support-frontend/assets/helpers/storage/cookie.ts b/support-frontend/assets/helpers/storage/cookie.ts index 3af2571487..81d7b5d43f 100644 --- a/support-frontend/assets/helpers/storage/cookie.ts +++ b/support-frontend/assets/helpers/storage/cookie.ts @@ -15,8 +15,9 @@ export function get(name: string): string | null | undefined { const cookies = document.cookie.split('; '); for (let i = cookies.length - 1; i >= 0; i -= 1) { - if (cookies[i].startsWith(name)) { - return cookies[i].substr(cookies[i].indexOf('=') + 1); + const cookie = cookies[i]; + if (cookie && cookie.startsWith(name)) { + return cookie.substr(cookie.indexOf('=') + 1); } } diff --git a/support-frontend/assets/helpers/subscriptionsForms/deliveryDays.ts b/support-frontend/assets/helpers/subscriptionsForms/deliveryDays.ts index 7dca4f8b23..5827506ab2 100644 --- a/support-frontend/assets/helpers/subscriptionsForms/deliveryDays.ts +++ b/support-frontend/assets/helpers/subscriptionsForms/deliveryDays.ts @@ -34,7 +34,10 @@ const getDeliveryDays = ( const deliveryDays = [initial]; for (let i = 1; i <= length; i += 1) { - deliveryDays.push(getNextDeliveryDay(deliveryDays[i - 1])); + const previousDay = deliveryDays[i - 1]; + if (previousDay) { + deliveryDays.push(getNextDeliveryDay(previousDay)); + } } return deliveryDays; diff --git a/support-frontend/assets/helpers/supporterPlus/benefitsThreshold.ts b/support-frontend/assets/helpers/supporterPlus/benefitsThreshold.ts index e2379dd5c2..243ff15fac 100644 --- a/support-frontend/assets/helpers/supporterPlus/benefitsThreshold.ts +++ b/support-frontend/assets/helpers/supporterPlus/benefitsThreshold.ts @@ -27,9 +27,11 @@ export function getLowerBenefitThreshold( ): number { const supporterPlusRatePlan = contributionType === 'ANNUAL' ? 'Annual' : 'Monthly'; - return productCatalog.SupporterPlus.ratePlans[supporterPlusRatePlan].pricing[ - currencyId - ]; + return ( + productCatalog.SupporterPlus?.ratePlans[supporterPlusRatePlan]?.pricing[ + currencyId + ] ?? 0 + ); } export function getLowerProductBenefitThreshold( @@ -49,9 +51,11 @@ export function getLowerProductBenefitThreshold( const ratePlanSupporterPlus = contributionType === 'ANNUAL' ? 'Annual' : 'Monthly'; - return productCatalog[product].ratePlans[ - product === 'SupporterPlus' ? ratePlanSupporterPlus : ratePlanTier3 - ].pricing[currencyId]; + return ( + productCatalog[product]?.ratePlans[ + product === 'SupporterPlus' ? ratePlanSupporterPlus : ratePlanTier3 + ]?.pricing[currencyId] ?? 0 + ); } // This is a function overload that means if the caller has already determined that contributionType is recurring diff --git a/support-frontend/assets/helpers/tracking/acquisitions.ts b/support-frontend/assets/helpers/tracking/acquisitions.ts index c15b80e72a..61a8c6b057 100644 --- a/support-frontend/assets/helpers/tracking/acquisitions.ts +++ b/support-frontend/assets/helpers/tracking/acquisitions.ts @@ -102,7 +102,7 @@ function getCampaign( return ( Object.keys(campaigns).find((campaign) => - campaigns[campaign].includes(campaignCode), + campaigns[campaign]?.includes(campaignCode), ) ?? null ); } @@ -285,15 +285,20 @@ function getAcquisitionDataFromUtmParams(): ); // All must be present in the URL for them to be accepted - if (utmParamNames.every((paramName) => utmParameters[paramName].length > 0)) { + if ( + utmParamNames.every((paramName) => { + const param = utmParameters[paramName]; + return param && param.length > 0; + }) + ) { return { - campaignCode: utmParameters.utm_campaign, + campaignCode: utmParameters.utm_campaign as string, abTest: { - name: utmParameters.utm_content, - variant: utmParameters.utm_term, + name: utmParameters.utm_content as string, + variant: utmParameters.utm_term as string, }, - source: utmParameters.utm_source, - componentType: utmParameters.utm_medium, + source: utmParameters.utm_source as string, + componentType: utmParameters.utm_medium as string, queryParameters: toAcquisitionQueryParameters(getAllQueryParams()), }; } diff --git a/support-frontend/assets/helpers/tracking/googleTagManager.ts b/support-frontend/assets/helpers/tracking/googleTagManager.ts index 1ec14453c6..2a4688130e 100644 --- a/support-frontend/assets/helpers/tracking/googleTagManager.ts +++ b/support-frontend/assets/helpers/tracking/googleTagManager.ts @@ -196,7 +196,7 @@ function addTagManagerScript() { processQueues(); }; - if (firstScript.parentNode) { + if (firstScript?.parentNode) { firstScript.parentNode.insertBefore(googleTagManagerScript, firstScript); scriptAdded = true; } @@ -220,7 +220,8 @@ async function init(): Promise { * Update userConsentsToGTM value when * consent changes via the CMP library. */ - userConsentsToGTM = thirdPartyTrackingConsent[googleTagManagerKey]; + userConsentsToGTM = + thirdPartyTrackingConsent[googleTagManagerKey] ?? false; if (userConsentsToGTM) { if (!scriptAdded) { diff --git a/support-frontend/assets/helpers/tracking/quantumMetric.ts b/support-frontend/assets/helpers/tracking/quantumMetric.ts index 28aecc0bac..992233ad1b 100644 --- a/support-frontend/assets/helpers/tracking/quantumMetric.ts +++ b/support-frontend/assets/helpers/tracking/quantumMetric.ts @@ -172,9 +172,9 @@ function sendEventAcquisitionDataFromQueryParamEvent( Object.keys(acquisitionDataKeysToLog).forEach((key) => { const acquisitionDataValueToLog = acquisitionData[key as keyof ReferrerAcquisitionData]?.toString(); - if (acquisitionDataValueToLog) { + if (acquisitionDataValueToLog && acquisitionDataKeysToLog[key]) { sendEvent( - acquisitionDataKeysToLog[key], + acquisitionDataKeysToLog[key] ?? 0, false, acquisitionDataValueToLog.toString(), ); diff --git a/support-frontend/assets/helpers/tracking/thirdPartyTrackingConsent.ts b/support-frontend/assets/helpers/tracking/thirdPartyTrackingConsent.ts index 7e9f437cbb..cc9bcd6eb7 100644 --- a/support-frontend/assets/helpers/tracking/thirdPartyTrackingConsent.ts +++ b/support-frontend/assets/helpers/tracking/thirdPartyTrackingConsent.ts @@ -48,7 +48,10 @@ const onConsentChangeEvent = async ( (accumulator, vendorKey) => { const vendorId = vendorIds[vendorKey]; - if (state.tcfv2?.vendorConsents[vendorId] !== undefined) { + if ( + vendorId && + state.tcfv2?.vendorConsents[vendorId] !== undefined + ) { return { ...accumulator, [vendorKey]: state.tcfv2.vendorConsents[vendorId], diff --git a/support-frontend/assets/helpers/urls/url.ts b/support-frontend/assets/helpers/urls/url.ts index bfde02361a..2d5cd14d0f 100644 --- a/support-frontend/assets/helpers/urls/url.ts +++ b/support-frontend/assets/helpers/urls/url.ts @@ -39,7 +39,7 @@ const getAllQueryParams = (): Array<[string, string]> => const getAllQueryParamsWithExclusions = ( excluded: string[], ): Array<[string, string]> => - getAllQueryParams().filter((p: string[]) => !excluded.includes(p[0])); + getAllQueryParams().filter((p: string[]) => !excluded.includes(p[0] ?? '')); // Takes a mapping of query params and adds to an absolute or relative URL. function addQueryParamsToURL( diff --git a/support-frontend/assets/helpers/user/user.ts b/support-frontend/assets/helpers/user/user.ts index c60118bb15..78c4d3dd6d 100644 --- a/support-frontend/assets/helpers/user/user.ts +++ b/support-frontend/assets/helpers/user/user.ts @@ -68,6 +68,10 @@ const getEmailValidatedFromUserCookie = ( if (guuCookie) { const tokens = guuCookie.split('.'); + if (tokens[0] === undefined) { + return false; + } + try { const parsed = JSON.parse( Buffer.from(tokens[0], 'base64').toString(), diff --git a/support-frontend/assets/pages/[countryGroupId]/checkout/helpers/getProductFields.ts b/support-frontend/assets/pages/[countryGroupId]/checkout/helpers/getProductFields.ts index 970feb2641..b5aca9c93a 100644 --- a/support-frontend/assets/pages/[countryGroupId]/checkout/helpers/getProductFields.ts +++ b/support-frontend/assets/pages/[countryGroupId]/checkout/helpers/getProductFields.ts @@ -26,7 +26,9 @@ export const getProductFields = ({ const { currencyKey, finalAmount, originalAmount, contributionAmount } = financial; - const ratePlanDescription = productDescription.ratePlans[ratePlanKey]; + const ratePlanDescription = productDescription.ratePlans[ratePlanKey] ?? { + billingPeriod: 'Monthly', + }; /** * This is the data structure used by the `/subscribe/create` endpoint. diff --git a/support-frontend/assets/pages/[countryGroupId]/components/checkoutComponent.tsx b/support-frontend/assets/pages/[countryGroupId]/components/checkoutComponent.tsx index b378978e73..2eccb8c031 100644 --- a/support-frontend/assets/pages/[countryGroupId]/components/checkoutComponent.tsx +++ b/support-frontend/assets/pages/[countryGroupId]/components/checkoutComponent.tsx @@ -244,7 +244,9 @@ export function CheckoutComponent({ const productDescription = showNewspaperArchiveBenefit ? productCatalogDescriptionNewBenefits(countryGroupId)[productKey] : productCatalogDescription[productKey]; - const ratePlanDescription = productDescription.ratePlans[ratePlanKey]; + const ratePlanDescription = productDescription.ratePlans[ratePlanKey] ?? { + billingPeriod: 'Monthly', + }; const productFields = getProductFields({ product: { @@ -267,7 +269,9 @@ export function CheckoutComponent({ let isInvalidAmount = false; if (productKey === 'Contribution') { const supporterPlusRatePlanPrice = - productCatalog.SupporterPlus.ratePlans[ratePlanKey].pricing[currencyKey]; + productCatalog.SupporterPlus?.ratePlans[ratePlanKey]?.pricing[ + currencyKey + ]; const { selectedAmountsVariant } = getAmountsTestVariant( countryId, @@ -278,7 +282,7 @@ export function CheckoutComponent({ isInvalidAmount = true; } if (!isContributionsOnlyCountry(selectedAmountsVariant)) { - if (originalAmount >= supporterPlusRatePlanPrice) { + if (originalAmount >= (supporterPlusRatePlanPrice ?? 0)) { isInvalidAmount = true; } } diff --git a/support-frontend/assets/pages/[countryGroupId]/guardianLightLanding/components/headerCards.tsx b/support-frontend/assets/pages/[countryGroupId]/guardianLightLanding/components/headerCards.tsx index e7ad9b1215..0ffcb38d3d 100644 --- a/support-frontend/assets/pages/[countryGroupId]/guardianLightLanding/components/headerCards.tsx +++ b/support-frontend/assets/pages/[countryGroupId]/guardianLightLanding/components/headerCards.tsx @@ -83,15 +83,15 @@ export function HeaderCards({ geoId }: HeaderCardsProps): JSX.Element { const { currencyKey } = getGeoIdConfig(geoId); const currency = currencies[currencyKey]; const price = - productCatalog.GuardianLight.ratePlans[contributionType].pricing[ + productCatalog.GuardianLight?.ratePlans[contributionType]?.pricing[ currencyKey ]; - const formattedPrice = simpleFormatAmount(currency, price); + const formattedPrice = simpleFormatAmount(currency, price ?? 0); const card1UrlParams = new URLSearchParams({ product: 'GuardianLight', ratePlan: contributionType, - contribution: price.toString(), + contribution: price?.toString() ?? '', }); const checkoutLink = `checkout?${card1UrlParams.toString()}`; const card1 = { diff --git a/support-frontend/assets/pages/aus-moment-map/ausMomentMap.tsx b/support-frontend/assets/pages/aus-moment-map/ausMomentMap.tsx index 3c01d8a59c..abe25f1df7 100644 --- a/support-frontend/assets/pages/aus-moment-map/ausMomentMap.tsx +++ b/support-frontend/assets/pages/aus-moment-map/ausMomentMap.tsx @@ -67,9 +67,9 @@ function AusMomentMap(): JSX.Element { if (selectedTerritory) { const index = (territories.indexOf(selectedTerritory) + 1) % territories.length; - setSelectedTerritory(territories[index]); + setSelectedTerritory(territories[index] ?? null); } else { - setSelectedTerritory(territories[0]); + setSelectedTerritory(territories[0] ?? null); } setShouldScrollIntoView(true); diff --git a/support-frontend/assets/pages/aus-moment-map/components/testimonialsContainer.tsx b/support-frontend/assets/pages/aus-moment-map/components/testimonialsContainer.tsx index 20fb2f1e5e..101d60c2e7 100644 --- a/support-frontend/assets/pages/aus-moment-map/components/testimonialsContainer.tsx +++ b/support-frontend/assets/pages/aus-moment-map/components/testimonialsContainer.tsx @@ -339,7 +339,7 @@ export const TestimonialsContainer = React.forwardRef( (territory) => props.testimonialsCollection && ( boolean -> => { +export const useWindowWidth = () => { function getWindowWidth() { return window.innerWidth; } diff --git a/support-frontend/assets/pages/digital-subscriber-checkout/setup/setUpRedux.ts b/support-frontend/assets/pages/digital-subscriber-checkout/setup/setUpRedux.ts index d8ce9fc55d..457bc3d24a 100644 --- a/support-frontend/assets/pages/digital-subscriber-checkout/setup/setUpRedux.ts +++ b/support-frontend/assets/pages/digital-subscriber-checkout/setup/setUpRedux.ts @@ -76,7 +76,7 @@ function getInitialContributionType( ); return defaultContributionType ? defaultContributionType.contributionType - : contributionTypes[countryGroupId][0].contributionType; + : contributionTypes[countryGroupId][0]?.contributionType ?? 'ONE_OFF'; } function selectInitialAmounts( diff --git a/support-frontend/assets/pages/paper-subscription-checkout/components/deliveryAgentsSelect.tsx b/support-frontend/assets/pages/paper-subscription-checkout/components/deliveryAgentsSelect.tsx index ffae09dd33..d90b1e0b8f 100644 --- a/support-frontend/assets/pages/paper-subscription-checkout/components/deliveryAgentsSelect.tsx +++ b/support-frontend/assets/pages/paper-subscription-checkout/components/deliveryAgentsSelect.tsx @@ -43,6 +43,10 @@ export function DeliveryAgentsSelect( if (props.deliveryAgentsResponse?.type === 'Covered' && !postcodeError) { if (props.deliveryAgentsResponse.agents?.length === 1) { + if (!props.deliveryAgentsResponse.agents[0]) { + return null; + } + return ( { }); fireEvent.click(addressIsNotSame); const allCountrySelects = await screen.findAllByLabelText('Country'); - fireEvent.change(allCountrySelects[1], { + fireEvent.change(allCountrySelects[1]!, { target: { value: 'IM', }, diff --git a/support-frontend/assets/pages/paper-subscription-checkout/helpers/__tests__/deliveryDays.test.ts b/support-frontend/assets/pages/paper-subscription-checkout/helpers/__tests__/deliveryDays.test.ts index ef7a9eac8d..174d0c2586 100644 --- a/support-frontend/assets/pages/paper-subscription-checkout/helpers/__tests__/deliveryDays.test.ts +++ b/support-frontend/assets/pages/paper-subscription-checkout/helpers/__tests__/deliveryDays.test.ts @@ -104,65 +104,65 @@ describe('deliveryDays', () => { }); it('delivers the Sunday paper on the next Sunday', () => { const days = getHomeDeliveryDays(wednesday, 'Sunday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-03'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-03'); }); it('delivers first Sixday paper 6 days later if ordered on a Thursday', () => { const days = getHomeDeliveryDays(thursday, 'Sixday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-06'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-06'); }); it('delivers first Weekend paper 9 days later if ordered on a Thursday', () => { const days = getHomeDeliveryDays(thursday, 'Weekend'); - expect(formatMachineDate(days[0])).toEqual('2019-03-09'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-09'); }); it('delivers first Sunday paper 10 days later if ordered on a Thursday', () => { const days = getHomeDeliveryDays(thursday, 'Sunday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-10'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-10'); }); it('delivers first Weekend paper 5 days later if ordered on a Monday', () => { const days = getHomeDeliveryDays(monday, 'Weekend'); - expect(formatMachineDate(days[0])).toEqual('2019-03-02'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-02'); }); it('delivers first Everyday paper 4 days later if ordered on a Saturday', () => { const days = getHomeDeliveryDays(saturday, 'Everyday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-06'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-06'); }); }); describe('getVoucherDays', () => { it('outside of the fast delivery window, delivers the Saturday paper on the next Saturday, four weeks after the preceding Monday', () => { const days = getVoucherDays(wednesday, 'Saturday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-30'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-30'); }); it('outside the fast delivery window (early morning), delivers Sixday on the next Monday, four weeks after the preceding Monday', () => { const days = getVoucherDays(thursday4am, 'Sixday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-25'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-25'); }); it('outside the fast delivery window, delivers Sixday on the next Monday, four weeks after the preceding Monday', () => { const days = getVoucherDays(wednesday, 'Sixday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-25'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-25'); }); it('outside the fast delivery window (on a Sunday), delivers Sixday on the next Monday, four weeks after the preceding Monday', () => { const days = getVoucherDays(sunday, 'Sixday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-25'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-25'); }); it('in the fast delivery window, delivers the Saturday paper on the next Saturday, three weeks after the preceding Monday', () => { const days = getVoucherDays(tuesday, 'Saturday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-23'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-23'); }); it('just inside the fast delivery window, delivers the Saturday paper on the next Saturday, three weeks after the preceding Monday', () => { const days = getVoucherDays(wednesday5amGMT, 'Saturday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-23'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-23'); }); it('inside the fast delivery window, delivers Sixday on the next Monday, three weeks after the preceding Monday', () => { const days = getVoucherDays(tuesday, 'Sixday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-18'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-18'); }); it('inside the fast delivery window, delivers Sixday on the next Monday, three weeks after the preceding Monday - even on a Monday', () => { const days = getVoucherDays(monday, 'Sixday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-18'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-18'); }); it('inside the fast delivery window, delivers Sunday on the next Sunday, three weeks after the preceding Monday', () => { const days = getVoucherDays(tuesday, 'Sunday'); - expect(formatMachineDate(days[0])).toEqual('2019-03-24'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-24'); }); }); describe('getPaymentStartDate', () => { diff --git a/support-frontend/assets/pages/paper-subscription-checkout/helpers/homeDeliveryDays.ts b/support-frontend/assets/pages/paper-subscription-checkout/helpers/homeDeliveryDays.ts index 4664bf6444..e64ec67c87 100644 --- a/support-frontend/assets/pages/paper-subscription-checkout/helpers/homeDeliveryDays.ts +++ b/support-frontend/assets/pages/paper-subscription-checkout/helpers/homeDeliveryDays.ts @@ -6,11 +6,20 @@ import { numberOfWeeksWeDeliverTo, } from 'helpers/subscriptionsForms/deliveryDays'; -const everyDayAndSixDay = [3, 3, 3, 3, 6, 5, 4]; -const weekendAndSaturday = [6, 5, 4, 3, 9, 8, 7]; -const sunday = [7, 6, 5, 4, 10, 9, 8]; +export type DayOfWeekIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6; -const getDaysToAdd = (today: number, product: ProductOptions): number => { +const everyDayAndSixDay = [ + 3, 3, 3, 3, 6, 5, 4, +] as const satisfies readonly number[]; +const weekendAndSaturday = [ + 6, 5, 4, 3, 9, 8, 7, +] as const satisfies readonly number[]; +const sunday = [7, 6, 5, 4, 10, 9, 8] as const satisfies readonly number[]; + +const getDaysToAdd = ( + today: DayOfWeekIndex, + product: ProductOptions, +): number => { switch (product) { case 'Everyday': case 'EverydayPlus': @@ -47,7 +56,7 @@ const getHomeDeliveryDays = ( today: number, product: ProductOptions, ): Date[] => { - const currentWeekday = new Date(today).getDay(); + const currentWeekday = new Date(today).getDay() as DayOfWeekIndex; const delayDays = getDaysToAdd(currentWeekday, product); const deliveryDay: Day = ((currentWeekday + delayDays) % 7) as Day; const canMakeNextDelivery = canDeliverOnNextDeliveryDay( diff --git a/support-frontend/assets/pages/paper-subscription-checkout/helpers/options.ts b/support-frontend/assets/pages/paper-subscription-checkout/helpers/options.ts index 97abc8edb3..52729e4583 100644 --- a/support-frontend/assets/pages/paper-subscription-checkout/helpers/options.ts +++ b/support-frontend/assets/pages/paper-subscription-checkout/helpers/options.ts @@ -47,6 +47,6 @@ const getStartDate = ( fulfilmentOption: FulfilmentOptions, productOption: ProductOptions, ): DateYMDString => - formatMachineDate(getDays(fulfilmentOption, productOption)[0]); + formatMachineDate(getDays(fulfilmentOption, productOption)[0] as Date); export { getProductOption, getFulfilmentOption, getDays, getStartDate }; diff --git a/support-frontend/assets/pages/paper-subscription-checkout/helpers/subsCardDays.ts b/support-frontend/assets/pages/paper-subscription-checkout/helpers/subsCardDays.ts index 7f42e4c97d..e71d0bac04 100644 --- a/support-frontend/assets/pages/paper-subscription-checkout/helpers/subsCardDays.ts +++ b/support-frontend/assets/pages/paper-subscription-checkout/helpers/subsCardDays.ts @@ -1,5 +1,6 @@ import type { ActivePaperProducts } from 'helpers/productPrice/productOptions'; import { formatMachineDate } from 'helpers/utilities/dateConversions'; +import type { DayOfWeekIndex } from './homeDeliveryDays'; const additionalDays = [ { @@ -86,7 +87,9 @@ const additionalDays = [ SundayPlus: 15, SaturdayPlus: 14, }, -]; +] as const satisfies ReadonlyArray< + Record +>; const monthText = [ 'January', 'February', @@ -114,7 +117,7 @@ const getPaymentStartDate = ( date: number, productOption: ActivePaperProducts, ): Date => { - const day = new Date(date).getDay(); + const day = new Date(date).getDay() as DayOfWeekIndex; const delay = additionalDays[day][productOption]; const delayInMils = delay * milsInADay; return new Date(date + delayInMils); diff --git a/support-frontend/assets/pages/supporter-plus-landing/twoStepPages/threeTierLanding.tsx b/support-frontend/assets/pages/supporter-plus-landing/twoStepPages/threeTierLanding.tsx index 2ce4679ee8..74fa6e7995 100644 --- a/support-frontend/assets/pages/supporter-plus-landing/twoStepPages/threeTierLanding.tsx +++ b/support-frontend/assets/pages/supporter-plus-landing/twoStepPages/threeTierLanding.tsx @@ -317,7 +317,7 @@ export function ThreeTierLanding({ ); const tierPlanPeriod = contributionType.toLowerCase(); - const billingPeriod = (tierPlanPeriod[0].toUpperCase() + + const billingPeriod = (tierPlanPeriod[0]?.toUpperCase() + tierPlanPeriod.slice(1)) as BillingPeriod; // Handle which countdown to show (if any). @@ -373,7 +373,7 @@ export function ThreeTierLanding({ : ['MONTHLY', 'ANNUAL']; const handlePaymentFrequencyBtnClick = (buttonIndex: number) => { - setContributionType(paymentFrequencies[buttonIndex]); + setContributionType(paymentFrequencies[buttonIndex] as ContributionType); }; const selectedContributionRatePlan = @@ -394,8 +394,10 @@ export function ThreeTierLanding({ countryGroupId, window.guardian.settings, ); - const monthlyRecurringAmount = amounts.amountsCardData.MONTHLY.amounts[0]; - const annualRecurringAmount = amounts.amountsCardData.ANNUAL.amounts[0]; + const monthlyRecurringAmount = amounts.amountsCardData.MONTHLY + .amounts[0] as number; + const annualRecurringAmount = amounts.amountsCardData.ANNUAL + .amounts[0] as number; const recurringAmount = contributionType === 'MONTHLY' ? monthlyRecurringAmount @@ -422,10 +424,9 @@ export function ThreeTierLanding({ /** Tier 2: SupporterPlus */ const supporterPlusRatePlan = contributionType === 'ANNUAL' ? 'Annual' : 'Monthly'; - const tier2Pricing = - productCatalog.SupporterPlus.ratePlans[supporterPlusRatePlan].pricing[ - currencyId - ]; + const tier2Pricing = productCatalog.SupporterPlus?.ratePlans[ + supporterPlusRatePlan + ]?.pricing[currencyId] as number; const tier2UrlParams = new URLSearchParams({ product: 'SupporterPlus', @@ -486,8 +487,8 @@ export function ThreeTierLanding({ }; const tier3RatePlan = getTier3RatePlan(); - const tier3Pricing = - productCatalog.TierThree.ratePlans[tier3RatePlan].pricing[currencyId]; + const tier3Pricing = productCatalog.TierThree?.ratePlans[tier3RatePlan] + ?.pricing[currencyId] as number; const tier3UrlParams = new URLSearchParams({ product: 'TierThree', diff --git a/support-frontend/assets/pages/weekly-subscription-checkout/components/weeklyCheckoutForm.test.tsx b/support-frontend/assets/pages/weekly-subscription-checkout/components/weeklyCheckoutForm.test.tsx index a6c2e9f15f..e93530f2a9 100644 --- a/support-frontend/assets/pages/weekly-subscription-checkout/components/weeklyCheckoutForm.test.tsx +++ b/support-frontend/assets/pages/weekly-subscription-checkout/components/weeklyCheckoutForm.test.tsx @@ -287,12 +287,12 @@ describe('Guardian Weekly checkout form', () => { const [Germany, UnitedKingdom] = ['DE', 'GB']; const [deliveryAddressCountry, billingAddressCountry] = await screen.findAllByLabelText('Country'); - fireEvent.change(deliveryAddressCountry, { + fireEvent.change(deliveryAddressCountry!, { target: { value: Germany, }, }); - fireEvent.change(billingAddressCountry, { + fireEvent.change(billingAddressCountry!, { target: { value: UnitedKingdom, }, diff --git a/support-frontend/assets/pages/weekly-subscription-checkout/components/weeklyCheckoutFormGifting.test.tsx b/support-frontend/assets/pages/weekly-subscription-checkout/components/weeklyCheckoutFormGifting.test.tsx index b2fca0a7b7..6970082f35 100644 --- a/support-frontend/assets/pages/weekly-subscription-checkout/components/weeklyCheckoutFormGifting.test.tsx +++ b/support-frontend/assets/pages/weekly-subscription-checkout/components/weeklyCheckoutFormGifting.test.tsx @@ -119,7 +119,7 @@ describe('Guardian Weekly checkout form', () => { }); fireEvent.click(addressIsNotSame); const allCountrySelects = await screen.findAllByLabelText('Country'); - fireEvent.change(allCountrySelects[1], { + fireEvent.change(allCountrySelects[1]!, { target: { value: 'FR', }, diff --git a/support-frontend/assets/pages/weekly-subscription-checkout/helpers/__tests__/deliveryDays.ts b/support-frontend/assets/pages/weekly-subscription-checkout/helpers/__tests__/deliveryDays.ts index cc0fa77f25..ae998b639c 100644 --- a/support-frontend/assets/pages/weekly-subscription-checkout/helpers/__tests__/deliveryDays.ts +++ b/support-frontend/assets/pages/weekly-subscription-checkout/helpers/__tests__/deliveryDays.ts @@ -32,15 +32,15 @@ describe('deliveryDays', () => { describe('getWeeklyDays', () => { it('if you order after wednesday, it delivers the Weekly on Friday fortnight', () => { const days = getWeeklyDays(thursday); - expect(formatMachineDate(days[0])).toEqual('2019-03-22'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-22'); }); it('if you order before wednesday midnight, it delivers the Weekly on Friday week', () => { const days = getWeeklyDays(tuesday); - expect(formatMachineDate(days[0])).toEqual('2019-03-08'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-08'); }); it('if you order on a Sunday, it delivers the Weekly on Friday week', () => { const days = getWeeklyDays(sunday); - expect(formatMachineDate(days[0])).toEqual('2019-03-22'); + expect(formatMachineDate(days[0]!)).toEqual('2019-03-22'); }); it('should not offer the 27th of December as a possible delivery date', () => { const days = getWeeklyDays(ninthOfDecember); diff --git a/support-frontend/assets/pages/weekly-subscription-checkout/helpers/deliveryDays.ts b/support-frontend/assets/pages/weekly-subscription-checkout/helpers/deliveryDays.ts index 2a077221e0..d4eb972149 100644 --- a/support-frontend/assets/pages/weekly-subscription-checkout/helpers/deliveryDays.ts +++ b/support-frontend/assets/pages/weekly-subscription-checkout/helpers/deliveryDays.ts @@ -26,9 +26,10 @@ const getWeeklyDays = (today?: number): Date[] => { const nonChrismassy = allDeliveryDays.filter((d) => !isChrismassy(d)); if (allDeliveryDays.length > nonChrismassy.length) { - nonChrismassy.push( - getNextDeliveryDay(nonChrismassy[nonChrismassy.length - 1]), - ); + const lastNonChrismassy = nonChrismassy[nonChrismassy.length - 1]; + if (lastNonChrismassy) { + nonChrismassy.push(getNextDeliveryDay(lastNonChrismassy)); + } } return nonChrismassy.splice(weeksToAdd); diff --git a/support-frontend/assets/pages/weekly-subscription-checkout/weeklySubscriptionCheckout.tsx b/support-frontend/assets/pages/weekly-subscription-checkout/weeklySubscriptionCheckout.tsx index e6b8160f66..52c1335ba6 100644 --- a/support-frontend/assets/pages/weekly-subscription-checkout/weeklySubscriptionCheckout.tsx +++ b/support-frontend/assets/pages/weekly-subscription-checkout/weeklySubscriptionCheckout.tsx @@ -29,7 +29,7 @@ const initialBillingPeriod: WeeklyBillingPeriod = billingPeriodInUrl === 'Annual' ? billingPeriodInUrl : 'Monthly'; -const startDate = formatMachineDate(getWeeklyDays()[0]); +const startDate = formatMachineDate(getWeeklyDays()[0] as Date); const store = initReduxForSubscriptions( GuardianWeekly, diff --git a/support-frontend/stories/checkouts/PaymentFrequencyTabs.stories.tsx b/support-frontend/stories/checkouts/PaymentFrequencyTabs.stories.tsx index 48179f782c..3d3275273e 100644 --- a/support-frontend/stories/checkouts/PaymentFrequencyTabs.stories.tsx +++ b/support-frontend/stories/checkouts/PaymentFrequencyTabs.stories.tsx @@ -44,7 +44,7 @@ export default { function Template(args: PaymentFrequencyTabsProps) { const [selectedTab, setSelectedTab] = useState( - args.tabs.find((tab) => tab.selected)?.id ?? args.tabs[0].id, + args.tabs.find((tab) => tab.selected)?.id ?? args.tabs[0]!.id, ); const [tabList, setTabList] = useState(args.tabs); diff --git a/support-frontend/tsconfig.json b/support-frontend/tsconfig.json index bf01624253..f21927a9ed 100644 --- a/support-frontend/tsconfig.json +++ b/support-frontend/tsconfig.json @@ -16,11 +16,6 @@ "ophan": ["../node_modules/ophan-tracker-js/build/ophan.support"], "@modules/product-catalog/*": ["../node_modules/@guardian/support-service-lambdas/modules/product-catalog/src/*"], }, - - // These override @guardian/tsconfig as they have introduced a lot of errors. - // The number of errors indicated below is at the time of writing to give an idea of the scale of the fix needed. - // We should look at removing these as soon as we can. - "noUncheckedIndexedAccess": false, // 209 errors in 63 files. "types": ["@testing-library/jest-dom"] }, "include": [ From d0d0a37eac1ae3bafe2b4372f4713301de50efe0 Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Mon, 6 Jan 2025 13:43:41 +0000 Subject: [PATCH 2/9] fix merge --- .../guardianLightLanding/components/headerCards.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/support-frontend/assets/pages/[countryGroupId]/guardianLightLanding/components/headerCards.tsx b/support-frontend/assets/pages/[countryGroupId]/guardianLightLanding/components/headerCards.tsx index 9bf1fa5b31..0efdaa77b0 100644 --- a/support-frontend/assets/pages/[countryGroupId]/guardianLightLanding/components/headerCards.tsx +++ b/support-frontend/assets/pages/[countryGroupId]/guardianLightLanding/components/headerCards.tsx @@ -96,7 +96,6 @@ export function HeaderCards({ product: 'GuardianLight', ratePlan: contributionType, contribution: price?.toString() ?? '', - }); }; const card1UrlParams = new URLSearchParams( returnLink From 6eb68a24f2c93be7ca2b7ad610441e25d8e66f50 Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Mon, 6 Jan 2025 15:56:34 +0000 Subject: [PATCH 3/9] replace readonly type --- support-frontend/assets/helpers/abTests/helpers.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/support-frontend/assets/helpers/abTests/helpers.ts b/support-frontend/assets/helpers/abTests/helpers.ts index e6f345b065..88f3e97138 100644 --- a/support-frontend/assets/helpers/abTests/helpers.ts +++ b/support-frontend/assets/helpers/abTests/helpers.ts @@ -1,7 +1,3 @@ -import type { - AmountsTest, - SelectedAmountsVariant, -} from 'helpers/contributions'; import type { CountryGroupId } from 'helpers/internationalisation/countryGroup'; export const FALLBACK_AMOUNTS = [ @@ -250,11 +246,9 @@ export const FALLBACK_AMOUNTS = [ }, ], }, -] as const satisfies readonly AmountsTest[]; +] as const; -export function getFallbackAmounts( - countryGroupId: CountryGroupId, -): SelectedAmountsVariant { +export function getFallbackAmounts(countryGroupId: CountryGroupId) { // Create fallback data - the amounts card must always have data to dsplay const fallbackTest = FALLBACK_AMOUNTS.find( (t) => t.targeting.region === countryGroupId, From afaaa48c31cb5ad23f57d22398a45537d37fa9df Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:39:40 +0000 Subject: [PATCH 4/9] create type that guarantees array entry --- .../assets/helpers/abTests/helpers.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/support-frontend/assets/helpers/abTests/helpers.ts b/support-frontend/assets/helpers/abTests/helpers.ts index 88f3e97138..8b21b70536 100644 --- a/support-frontend/assets/helpers/abTests/helpers.ts +++ b/support-frontend/assets/helpers/abTests/helpers.ts @@ -1,6 +1,20 @@ +import type { + AmountsTest, + AmountsVariant, + SelectedAmountsVariant, +} from 'helpers/contributions'; import type { CountryGroupId } from 'helpers/internationalisation/countryGroup'; -export const FALLBACK_AMOUNTS = [ +type AmountsTestWithVariants = AmountsTest & { + variants: [AmountsVariant, ...AmountsVariant[]]; +}; + +type NonEmptyAmountsTestArray = [ + AmountsTestWithVariants, + ...AmountsTestWithVariants[], +]; + +export const FALLBACK_AMOUNTS: NonEmptyAmountsTestArray = [ { testName: 'FALLBACK_AMOUNTS__GBPCountries', liveTestName: '', @@ -246,12 +260,16 @@ export const FALLBACK_AMOUNTS = [ }, ], }, -] as const; +]; -export function getFallbackAmounts(countryGroupId: CountryGroupId) { +export function getFallbackAmounts( + countryGroupId: CountryGroupId, +): SelectedAmountsVariant { // Create fallback data - the amounts card must always have data to dsplay const fallbackTest = FALLBACK_AMOUNTS.find( - (t) => t.targeting.region === countryGroupId, + (t) => + t.targeting.targetingType === 'Region' && + t.targeting.region === countryGroupId, ); if (fallbackTest) { return { From 05b688f97f2df749fa1b9b4e78c7936afda254dc Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Tue, 7 Jan 2025 09:40:53 +0000 Subject: [PATCH 5/9] refactor: remove satisfies --- .../helpers/homeDeliveryDays.ts | 10 +++------- .../helpers/subsCardDays.ts | 4 +--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/support-frontend/assets/pages/paper-subscription-checkout/helpers/homeDeliveryDays.ts b/support-frontend/assets/pages/paper-subscription-checkout/helpers/homeDeliveryDays.ts index e64ec67c87..9ad021f22a 100644 --- a/support-frontend/assets/pages/paper-subscription-checkout/helpers/homeDeliveryDays.ts +++ b/support-frontend/assets/pages/paper-subscription-checkout/helpers/homeDeliveryDays.ts @@ -8,13 +8,9 @@ import { export type DayOfWeekIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6; -const everyDayAndSixDay = [ - 3, 3, 3, 3, 6, 5, 4, -] as const satisfies readonly number[]; -const weekendAndSaturday = [ - 6, 5, 4, 3, 9, 8, 7, -] as const satisfies readonly number[]; -const sunday = [7, 6, 5, 4, 10, 9, 8] as const satisfies readonly number[]; +const everyDayAndSixDay = [3, 3, 3, 3, 6, 5, 4] as const; +const weekendAndSaturday = [6, 5, 4, 3, 9, 8, 7] as const; +const sunday = [7, 6, 5, 4, 10, 9, 8] as const; const getDaysToAdd = ( today: DayOfWeekIndex, diff --git a/support-frontend/assets/pages/paper-subscription-checkout/helpers/subsCardDays.ts b/support-frontend/assets/pages/paper-subscription-checkout/helpers/subsCardDays.ts index e71d0bac04..7bda9dfaf7 100644 --- a/support-frontend/assets/pages/paper-subscription-checkout/helpers/subsCardDays.ts +++ b/support-frontend/assets/pages/paper-subscription-checkout/helpers/subsCardDays.ts @@ -87,9 +87,7 @@ const additionalDays = [ SundayPlus: 15, SaturdayPlus: 14, }, -] as const satisfies ReadonlyArray< - Record ->; +] as const; const monthText = [ 'January', 'February', From 6c6f7c6b417f1d41849228c7bb8250bf0f9a88a0 Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Tue, 7 Jan 2025 10:07:45 +0000 Subject: [PATCH 6/9] refactor: check for undefined number explicitly --- support-frontend/assets/components/gridImage/gridImage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/support-frontend/assets/components/gridImage/gridImage.tsx b/support-frontend/assets/components/gridImage/gridImage.tsx index 63e2536e2a..6b1234ae26 100644 --- a/support-frontend/assets/components/gridImage/gridImage.tsx +++ b/support-frontend/assets/components/gridImage/gridImage.tsx @@ -31,7 +31,7 @@ export default function GridImage(props: PropTypes): JSX.Element | null { const srcSet = gridSrcset(props.gridId, sorted, props.imgType); const fallbackSize = sorted.find((_) => _ > MIN_IMG_WIDTH) ?? sorted[0]; - if (!fallbackSize) { + if (fallbackSize === undefined) { return null; } From 6ea700b8d455fc8ad7d74b404dc9ed38a16b8a8f Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:12:50 +0000 Subject: [PATCH 7/9] put undefined check in if statement --- .../assets/helpers/globalsAndSwitches/globals.test.ts | 4 ++-- .../helpers/redux/checkout/product/selectors/productType.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/support-frontend/assets/helpers/globalsAndSwitches/globals.test.ts b/support-frontend/assets/helpers/globalsAndSwitches/globals.test.ts index bb2d627799..7fa9b726d6 100644 --- a/support-frontend/assets/helpers/globalsAndSwitches/globals.test.ts +++ b/support-frontend/assets/helpers/globalsAndSwitches/globals.test.ts @@ -17,10 +17,10 @@ const getSpecifiedRegionAmountsFromGlobal = ( (t) => t.targeting.targetingType === 'Region' && t.targeting.region === target, ); - if (!testArray.length) { + if (!testArray.length || !testArray[0]) { return {}; } - return testArray[0] ?? {}; + return testArray[0]; }; describe('getGlobal', () => { diff --git a/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts b/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts index ade100c5f6..29fd4fe786 100644 --- a/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts +++ b/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts @@ -44,8 +44,8 @@ function getSubscriptionTypeFromURL(): SubscriptionProduct { weekly: GuardianWeekly, }; const [, match] = urlPathRegex.exec(window.location.pathname) ?? []; - if (match) { - return productsToUrlPath[match] ?? DigitalPack; + if (match && productsToUrlPath[match]) { + return productsToUrlPath[match]; } return DigitalPack; } From f0aefef024b14ed47041f82f515e574c481fc8c8 Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:27:15 +0000 Subject: [PATCH 8/9] fix tsc error --- .../redux/checkout/product/selectors/productType.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts b/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts index 29fd4fe786..dab02ce57e 100644 --- a/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts +++ b/support-frontend/assets/helpers/redux/checkout/product/selectors/productType.ts @@ -44,8 +44,11 @@ function getSubscriptionTypeFromURL(): SubscriptionProduct { weekly: GuardianWeekly, }; const [, match] = urlPathRegex.exec(window.location.pathname) ?? []; - if (match && productsToUrlPath[match]) { - return productsToUrlPath[match]; + if (match) { + const product = productsToUrlPath[match]; + if (product) { + return product; + } } return DigitalPack; } From f3a8351d299269d7a9c1ef3ecfffefb13a028976 Mon Sep 17 00:00:00 2001 From: andrewHEguardian <114918544+andrewHEguardian@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:42:26 +0000 Subject: [PATCH 9/9] refactor acquisitions utm params Co-Authored-By: Rupert Bates --- .../assets/helpers/tracking/acquisitions.ts | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/support-frontend/assets/helpers/tracking/acquisitions.ts b/support-frontend/assets/helpers/tracking/acquisitions.ts index 61a8c6b057..5d157e6648 100644 --- a/support-frontend/assets/helpers/tracking/acquisitions.ts +++ b/support-frontend/assets/helpers/tracking/acquisitions.ts @@ -270,35 +270,30 @@ function getReferrerAcquisitionDataFromSessionStorage(): function getAcquisitionDataFromUtmParams(): | Record - | null | undefined { // Same order of fields as https://reader-revenue-lynx.s3.eu-west-1.amazonaws.com/v3.html - const utmParamNames = [ - 'utm_campaign', - 'utm_content', - 'utm_term', - 'utm_source', - 'utm_medium', - ]; - const utmParameters = Object.fromEntries( - utmParamNames.map((paramName) => [paramName, getQueryParameter(paramName)]), - ); + const utmCampaign = getQueryParameter('utm_campaign'); + const utmContent = getQueryParameter('utm_content'); + const utmTerm = getQueryParameter('utm_term'); + const utmSource = getQueryParameter('utm_source'); + const utmMedium = getQueryParameter('utm_medium'); // All must be present in the URL for them to be accepted if ( - utmParamNames.every((paramName) => { - const param = utmParameters[paramName]; - return param && param.length > 0; - }) + utmCampaign !== '' && + utmContent !== '' && + utmTerm !== '' && + utmSource !== '' && + utmMedium !== '' ) { return { - campaignCode: utmParameters.utm_campaign as string, + campaignCode: utmCampaign, abTest: { - name: utmParameters.utm_content as string, - variant: utmParameters.utm_term as string, + name: utmContent, + variant: utmTerm, }, - source: utmParameters.utm_source as string, - componentType: utmParameters.utm_medium as string, + source: utmSource, + componentType: utmMedium, queryParameters: toAcquisitionQueryParameters(getAllQueryParams()), }; }