@newskit-render/my-account
TypeScript icon, indicating that this package has built-in type declarations

7.32.0 • Public • Published

@newskit-render/my-account

Contributing Guidelines

Contribution Guidelines can be found in CONTRIBUTING.md

Getting Started

There are two ways to start with @newskit-render/my-account:

1. If you want to build a project from scratch, that will have everything from Newskit Solutions, you need to run create-render-app (See Newskit-Solutions Getting started).

2. If you already have a next application and you want to include my-account package you just need to run yarn add @newskit-render/my-account or npm install @newskit-render/my-account based on which package manager you use. Next you need to create an account folder inside your pages folder, and you can add your account pages there:

import { PersonalDetails, getProviderProps } from '@newskit-render/my-account'

export default PersonalDetails

export const getServerSideProps = async (context) =>
  getProviderProps({ ...context, provider: 'PersonalDetails' })

Authentication

If you have already included my account into your application, you will be able to see account pages, but they won't be populated with any data. That's because my-account needs the following environment variables in order to connect with CPS (formerly MAIN).

MAIN_GRAPHQL_URL="" // This is going to be deprecated and removed soon. Please use ACCOUNT_PROVIDER_GRAPHQL_URL
ACCOUNT_PROVIDER_GRAPHQL_URL=""
ACCOUNT_PROVIDER_INTERNAL_AUTH0_DOMAIN=""
ACCOUNT_PROVIDER_INTERNAL_CLIENT_ID=""
ACCOUNT_PROVIDER_INTERNAL_CONNECTION=""

ACCOUNT_PROVIDER_GRAPHQL_URL: This is the main graphql endpoint - you can ask someone from your team, or a member from CPS to provide it for you. ACCOUNT_PROVIDER_INTERNAL_AUTH0_DOMAIN, ACCOUNT_PROVIDER_INTERNAL_CLIENT_ID, and ACCOUNT_PROVIDER_INTERNAL_CONNECTION are needed for the changing password and email functionality and like ACCOUNT_PROVIDER_GRAPHQL_URL can be provided by CPS.

If you are interested in my-account - CPS integration and you want some in-depth info about it, you can have a look at our space in confluence

Step-up MFA

To enable step-up MFA both @newskit-render/api and @newskit-render/my-account should have following environment variables:

ENABLE_STEPUP_MFA="true"
STEPUP_MFA_GO_TO_URI=""

ENABLE_STEPUP_MFA: disables MFA if set to anything other then 'true' or excluded STEPUP_MFA_GO_TO_URI: used to decide where to redirect user on succesfull MFA calls.

Tracking user interactions - Tealium config

We rely on Tealium in combination with Newskit to track user interaction. In the my-account package we track user interactions ( such as clicking ) in two ways, depending on what component we want to track:

When using Newskit components that are by nature interactive ( buttons, switches, links, etc. ) we pass an eventContext object, with the following shape:

eventContext: {
    key[string]: string
}

which contains the Tealium key-value pairs needed for analytics (e.g. event_navigation_name: "sidebar option selection") And an

eventOriginator: string

used to pass information on which interactive element was used to trigger the event ( e.g. eventOriginator: "primary button"). Wrapper components, exported form shared-components, such as the ButtonGroup, use the eventOriginator and eventContext internally.

For example, when using the ButtonGroup component, exported from shared-components, we can pass the Teallium configuration for the buttons as such:

props: {
        buttonGroupProps: {
          primaryButton: {
            text: 'Save',
            eventContext: {
              event_navigation_action: 'navigation',
              event_navigation_name: 'button:save',
              event_navigation_browsing_method: EventTrigger.Click,
            },
            ariaLabel: 'Save and go back to ...',
          },
          secondaryButton: {
            eventContext: {
              event_navigation_action: 'navigation',
              event_navigation_name: 'button:cancel',
              event_navigation_browsing_method: EventTrigger.Click,
            },
            ariaLabel: 'Cancel and go back to ...',
            href: '[baseURL]/my-url',
          },
        },
        ...moreContext,
      },

The ButtonGroup will add the event originator automatically.

For the components that are NOT naturally interactive ( like the StructuredListItem ), but we have added interactivity to them, the Tealium tracking can be added with the function createClickEvent, which is exported from shared-components package.

It takes the following params:

createClickEvent(
originator: string,
text: string,
label?: string,
articleParentName?: string,
pageName?: string,
event_navigation_action = 'navigation'
)

And returns a Tealium configuration object for a click event.

When using this function, we rely on the PUBLISHER environment variable to keep the tracking title agnostic. In the future, we will move the Teallium configuration for these cases to the contexts.

Page loading events

In many cases we want to track visitor’s landing on a page. To do this, we use the usePageViewTracking hook, exported from shared-components. The hook accepts an object as an argument:

interface TealiumEventContext {
  originator: string
  context: Record<string, string>
}

And is triggered only if such an object is passed to it via the page’s context.

The context’s key for page view tracking is tealiumContext. For example:

const personalDetailsContext = {
  …otherContextValues,
  tealiumContext: {
    originator: 'page view',
    context: {
      page_site_name: ‘you ‘site name,
      page_section: 'my account',
      page_type: ‘personal ‘details,
      page_restrictions: 'restricted',
      page_name: 'personal details',
      ‘some_other_key’: ‘some other value’
    },
  },
}

For contributors: the rules for keeping the package title agnostic are the same.

Theming

The my-account package appearance can be controlled by a custom theme. The package uses NewsKit's theming system to enable customisation. See Newskit theming

To override the My Account base theme you need to pass a new theme as a prop of a page component:

import React from 'react'
import { PersonalDetails, getProviderProps } from '@newskit-render/my-account'
import { sharedTheme } from '@newskit-render/shared-components'
import { createTheme } from 'newskit'

const customTheme = createTheme({
  name: 'new-theme',
  baseTheme: sharedTheme,
  overrides: {
    stylePresets: {
      ADD YOUR CUSTOM PRESETS
    }
  }
})

const AccountPersonalDetails = (props) => (
  <PersonalDetails {...props} customTheme={customTheme}  />
)

export default AccountPersonalDetails

export const getServerSideProps = async (context) =>
  getProviderProps({ ...context, provider: 'PersonalDetails' })

Use the sharedTheme as the base to any new theme you create. You can see this theme here

Currently the app created with create-render-app has a dropdown theme selector that you can see by pressing '`' key. This is also implemented in Account. When you add a custom theme you should remove the AppContext and createThemeDropdownObject logic.

Page types

Currently provides a number of default pages, however they are based on 3 page types

  • Display
  • Edit
  • Add

The current Display pages (pages/account/) are Personal Details page (PersonalDetails component), Subscription and Billing page (SubscriptionAndBilling component), Newsletters & Alerts page (NewslettersAndAlerts component) and Newsletters page (Newsletters component). They differ from Edit pages by using sections data type to display information (see Page overrides and Properties of ContextOptions for information on sections).

The DynamicPage component

The DynamicPage component was introduced to ensure the rendering of all dynamic pages is done in an uniform way. It takes a mapping object, a page name, a react component and its props. DynamicPage checks if the page exists in the mapping object and eiter renders the passed Component with its props, or the NotFound page. It is not madatory to use this new way of rendering the dynamic pages, and no need to change anything in your code if you are using an old version of Render.

All Edit pages are currently accessed from pages/account/edit/[field].tsx (EditField component, passed to DynamicPage). They differ from Display pages by using forms data type to display forms you can edit user data with (see Page overrides and Properties of ContextOptions for information on forms).

All Add pages are currenlty accessed from pages/account/add/[field].tsx (AddField component, passed to DynamicPage)`

Page overrides

The My Account package has data driven pages using React context. You can override defaults starting on a page basis.

The following examples will override aspects of the Personal Details page:

1. Override the page's header description and list items title styles:

import React from 'react'
import { PersonalDetails, getProviderProps, ContextOptions, personalDetailsContext } from '@newskit-render/my-account'

const overrideContext: ContextOptions = {
  ...personalDetailsContext,
  header: {
    ...personalDetailsContext.header,
    description:
      'This will override the default header details',
  },
  sectionsOverrides: {
    list: {
      titleOverrides: {
        marginBlockEnd: {
          xs: 'space040',
          md: 'space050',
        },
        typographyPreset: 'utilityHeading020',
        as: 'h3'
      },
    },
  },
}

const Page = (props) => <PersonalDetails context={overrideContext} {...props} />

export default Page

export const getServerSideProps = async (context) =>
  getProviderProps({ ...context, provider: 'PersonalDetails' })

2. Override the sections on the page

const overrideContext: ContextOptions = {
  ...personalDetailsContext,
  sections: [
    {
      type: 'digital',
      componentType: 'list',
      props: {
        introductionProps: {
          title: 'Section added'
        },
        items: [
          {
            label: 'Backup Email',
            type: 'email',
            default: '[You can add a backup email]',
            href: '[baseURL]/edit/backup-email',
            ariaLabel: 'Add bacup email',
          }
        ]
      }
    }
  ]
}

3. Add onto the existing sections:

const overrideContext: ContextOptions = {
  ...personalDetailsContext,
  sections: [
    ...personalDetailsContext.sections,
    {
      type: 'print',
      componentType: 'list',
      props: {
        introductionProps: {
          title: 'Another section added'
        },
        items: [
          {
            label: 'Landline phone number',
            default: [
              'Please enter your landline telephone number',
              'Invalid landline telephone number',
            ],
            href: '[baseURL]/edit/landline',
            type: 'landline',
            ariaLabel: 'Edit your landline number',
          },
        ]
      }
    }
  ]
}

The following examples will override aspects of Edit pages pages/account/edit/[field].tsx.

  1. Let's change 3 forms - Password, Name and Address:
import React from 'react'
import { EditField, getProviderProps, ContextOptions, editFieldContext } from '@newskit-render/my-account'
import { IconFilledInfo, IconFilledSearch } from 'newskit'
import validation from '../../../validation'

const overrideContext: ContextOptions = {
  ...editFieldContext,
  forms: {
    ...editFieldContext.forms,
    password: {
      header: {
        title: 'Change your Password',
        image: {
          src: '/assets/personal-details-header.svg',
          alt: '',
        },
        showDivider: true,
      },
      props: {
        text: [
          'For extra security we will send you a <b>change password link</b> to the email saved in your account.',
          'The link can only be used once and expires in 20 minutes.',
        ],
        textOverriders: {
          typographyPreset: {
            xs: 'utilityBody020',
            sm: 'utilityBody030',
          },
          stylePreset: 'inkBase',
          marginBlockEnd: {
            xs: 'space060',
            sm: 'space070',
          },
        },
        buttonGroupProps: {
          primaryText: 'Get Link',
        },
      },
    },
    name: {
      props: {
        inputText: {
          cells: {
            xs: 12,
            md: 6,
          },
        },
        infoPanel: {
          icon: <IconFilledInfo overrides={{ size: 'iconSize030' }} />,
          children:
            'We will only use your name to comunicate with you directly. When making comments on our websites and apps, your ‘display name’ is used to protect your identity.',
          infoPanelCells: {
            xs: 12,
            md: 8
          },
          overrides: {
            marginBlockEnd: 'space060',
          },
        },
      },
    },
    address: {
      header: {
        description: 'You can edit your address here.',
      },
      props: {
        inputText: {
          marginBlockEnd: 'space040',
        },
        fullAddressInput: {
          label: 'Search for Address',
          assistiveText: 'Type any part of address or postcode to search',
          cells: {
            xs: 12,
          },
          marginBlockEnd: {
            xs: 'space080',
            md: 'space090',
          },
          icon: (
            <IconFilledSearch
              overrides={{
                size: 'iconSize030',
              }}
            />
          ),
        },
        line1Input: {
          label: 'Address field 1',
          cells: {
            xs: 6,
          },
        },
        line2Input: {
          label: 'Address field 2',
          cells: {
            xs: 6,
          },
        },
        line3Input: {
          label: 'Town/City',
          cells: {
            xs: 6,
          },
        },
        line4Input: {
          label: 'County',
          cells: {
            xs: 6,
          },
        },
        line5Input: {
          label: 'Postcode',
          cells: {
            xs: 6,
          },
        },
        countryInput: {
          labelProps: {
            text: 'Country',
            overrides: {
              typographyPreset: 'utilityLabel020',
              marginBlockEnd: 'space030',
            },
          },
          cells: {
            xs: 6,
          },
          panelProps: {
            maxHeight: '130px',
            zIndex: 'no-layer',
          },
          optionsProps: {
            defaultValue: 'GBR',
          },
        },
      },
    }
  }
}
// If you are using an older version, you can keep this code
const AccountEditField = (props) => (

  <EditField {...props} validation={validation} context={overrideContext} />
)

export default AccountEditField
// If you are using the new DynamicPage component, the code will look like this:
const AccountEditField = (props) => (
    <DynamicPage
      objectMap={editComponentMap}
      pageName={props.data.type}
      Component={EditField}
      componentProps={props}
      validation={validation}
      context={overrideContext}
    />
)
export const getServerSideProps = async (context) =>
  getProviderProps({ ...context, provider: 'EditField' })

Section types

The section displays information based on the type of subscription and the type of component, used for display:

type: 'print' | 'digital' // the type of subscription
componentType: 'list'     // the component, used for displaying the data. Currently we only support `list`

Each item in this list also has a type. Currently the item types are:

Item types

  • 'name'
  • 'dob'
  • 'displayName'
  • 'email'
  • 'password'
  • 'mobile'
  • 'landline'
  • 'address'
  • 'subscription'
  • 'price'
  • 'customerNumber'
  • 'subDate'
  • 'benefits'
  • 'payment'
  • 'billDate'
  • 'amountDue'
  • 'newsletters'
  • 'commentingNotifications'
  • 'contactPreferences'

For more details, see below

Properties of ContextOptions

1. data - Do not touch - this will be overriden in my-account.

2. userData - Do not touch - this will be overriden in my-account.

3. head - currently lets you override the title tag of the page:

head: {
  pageTitle: string,
  siteTitle: string | false
}

4. navigationPrimary - Global property that changes the nav links in the top-banner area - an Array of nav objects or false to have no nav:

navigationPrimary: {
 nav?: {
     text: string
     link: string
     icon: React.ReactElement<NewsKitIconProps>
     ariaLabel?: string
  },
  title?: string,
  logoSrc?: string,                   // path to logo.svg
  logoWidth?: string,                 // width of logo
  titlePosition?: string,             // uses css top property to ajust title position
}

5. sideNav - Global property that changes sidebar navigation - an Array of nav objects:

sideNav: [
  {
   text: string,
   href: string,
   id: string,
  }
]

6. sideNavSelected : string - The Id of the selected sideNav

7. sideNavOverrides - The styling ovderrides, applied to the sideNav:

sideNavOverrides: {
  menuItemOverrides: {
    stylePreset: MQ<string>,
    typographyPreset: MQ<string>,
    paddingInline?: MQ<string>
    paddingBlock?: MQ<string>
  },
  backgroundColor: string,
}

8. pastDueBanner - Banner displayed when your subscription is passed due. If not provided, the following default will be used:

pastDueBanner: {
  firstNotice: {
    title: "We haven't been able to take payment",
    text:
      'You may need to update your payment details to keep your subscription.',
    button: 'Update payment details',
  },
  secondNotice: {
    title: 'Act now to keep your subscription',
    text:
      'We’ve tried several times, but haven’t been able to take payment. Please update your payment details to keep your subscription.',
    button: 'Update payment details',
  },
  terminated: {
    title: 'Your subscription has been terminated',
    phoneNumber: 'XXXX-XXX-XXXX',
    text:
      'We didn’t receive payment for your subscription. To reactivate it, please call ##PHONE_NUMBER##. Or click ##LINK##.',
    dismissDays: 7,
    link: {
      linkLocation: 'http://localhost:3000/account',
      linkText: 'here',
    },
  },
  cancelled: {
    title: 'Your subscription has been cancelled.',
    phoneNumber: 'XXXX-XXX-XXXX',
    text:
      'You’ll no longer have access to subscription benefits. To re-activate call ##PHONE_NUMBER##.',
    dismissDays: 7,
  },
  toBeCancelled: {
    title: 'Your subscription will end soon.',
    phoneNumber: 'XXXX-XXX-XXXX',
    text:
      'You have cancelled your subscription and will lose access to all benefits on ##DATE##. To re-activate your subscription call ##PHONE_NUMBER##.',
  },
  toBeCancelledWithRefund: {
    title: 'Your subscription will end soon.',
    phoneNumber: '0800 xxxx xxxxx',
    text:
      'We have successfully cancelled your subscription and will be processing your refund shortly. If you have any question please call ##PHONE_NUMBER##.',
    dismissDays: 7,
  },
  treshold: {
    firstNotice: 26,
    secondNotice: 30,
  },
}

Banner messages indicate different states of the subscription:

  • firstNotice: - Banner when the subscription past due is below first threshold
  • secondNotice: - Banner when the subscription past due is above than first threshold, but below that second threshold
  • terminated: Banner when the subscription past due is above the second threshold
  • toBeCancelled: Banner shown when user has cancelled their subscription but before end of billing cycle
  • cancelled: Banner shown when user has cancelled their subscription and is after end of billing cycle Banner thresholds are are numbers, representing a number of days since the last payment. They are used to calculate when to show the first and the second banner message.

A link to all the banner's defaults and typ[es can be found here

The pastDueBanner uses some custom stylePreset PastDueFirstNotice PastDueLastNotice UpdatePaymentButton see here

9. header - Changes Page header

header: {
  title: string,
  titleOverrides: TitleOverrides,
  fullWidthTitle: boolean,
  description: string,
  descriptionOverrides: DescriptionOverrides,
  backButton: {
    href: string
    'aria-label': string
    text: string
  }
  backButtonOverrides: {
    stylePreset: MQ<string>
    typographyPreset: MQ<string>
    paddingInline?: MQ<string>
    paddingBlock?: MQ<string>
    marginBlockEnd: MQ<string>
    iconSize: MQ<string>
    size: 'small' | 'medium' | 'large'
    asLink: boolean,     // (will render as LinkStandalone not Button)
  },
  image: ImageProps,
  marginBlockEnd: MQ<string>,
  showDivider: boolean,
  imageCell?: GridLayoutItemProps, //GridLayoutItemProps see NewsKit (can be used to change order of items)
  introductionCell?: GridLayoutItemProps,
  backButtonCell?: GridLayoutItemProps,
}

10. sections - an Array of section items:

sections: {
  type: SectionType,
  componentType: string,     // currently only 'list'
  props: ContentContainerProps
}

For all the sub-types, see the explanation here

The ContentListItem uses some custom stylePresets baseInteractivePrimary030 contentListView see here

11. sectionsOverrides - currently only for lists:

  sectionsOverrides?: {
    list: {
      titleOverrides?: TitleOverrides
      descriptionOverrides?: DescriptionOverrides
    }
  }

For all the sub-types, see the explanation here

12. forms - we use forms in the edit pages. Contexts differ between forms. Currently we support the following options for forms and their corresponding contexts:

form: {
  default?: FormContext,
  password?: PasswordContext,
  name?: FormContext,
  'display-name'?: FormContext,
  email?: FormContext,
  landline?: FormContext,
  mobile?: FormContext,
  payment?: FormContext,
  address?: AddressFormContext,
  'commenting-notifications'?: CommentingNotificationsContext,
  'delivery-instructions'?: DeliveryInstructionsContext,
  'holiday-stops'?: HolidayStopContext,
}

For all the context types, see below

13. cancellation - subscription cancellation:

cancellation: {
  reason: Partial<CancellationContext>
  confirm: Partial<CancellationContext>
}

For all the context types, see below

14. footer - Global property

footer:FooterContext =  {
  menuItemArray: [
    {
      text: string,
      href: string,
      id: string | number,
    }
  ],
legalText: string
}

For more about the FooterContext type, see below

15. footerOverides: - Global property.

Used to override the styling of the footer menu items, chat help (if any) and the legal text.

footerOverrides: FooterContextOverrides

For more details, see below

16. baseUrl: string - change if you are not going to stick to the default routing set up in the core package. Currently set as /account but will need to be changes if you change pages/account file structure in core.

17. buttonGroupProps - (override all here or individually by form)

buttonGroupProps {
  loading?: boolean
  secureFlag?: boolean
  secureFlagOverrides?: SecureFlagProps
  primaryButton?: ButtonProps
  secondaryButton?: ButtonProps
  stylePreset?: MQ<string>
  keepFixed?: boolean
  breakPoint?: BreakpointKeys
}

For more details about the ButtonGroupProps type, see below

The ButtonGroup uses custom stylePresets buttonGroupXs buttonGroupSm see here

18. zuoraCustomErrorMessages?: see Payment section

19. noSubscription?: SubscriptionStatusProps (see below)

20. previousSubscription?: SubscriptionStatusProps

interface SubscriptionStatusProps {
  header?: HeaderProps
  buttonGroupProps?: Partial<ButtonGroupProps>
}

For more on the SubscriptionStatusProps, see below

21. outsideDeliveryAddressModal

Modal to be shown when subscription type is either Print or Digi-Print and postcode is outside the delivery area. Currently it looks like this:

outsideDeliveryAddressModal?: {
closePosition:string
open:boolean
overrides:
  content?:
    paddingInline?: MQ<string>
    paddingBlock?: MQ<string>
  panel:
    width:MQ<string>
    height:MQ<string>
introductionProps: IntroductionProps
  title: string
  titleOverrides: TitleOverrides
  description: string
  descriptionOverrides: DescriptionOverrides
modalLink :
  links : Array<{
    href : string
    type : string
    text : string
  }>
  introductionProps: IntroductionProps
    title: string
    titleOverrides: TitleOverrides
    description: string
    descriptionOverrides: DescriptionOverrides
buttonGroupProps: ButtonGroupProps
redirectUrl : string
}

It is a part of EditFieldContextOptions, which you can see below

Change redirectUrl if you are not sticking to the default routing set up in the Core package. Currently set as /account/subscription-and-billing but will need to be changes if you change pages/account/subscription-and-billing file structure in the Core package.

22. infoBanner - Banner for informative messages.

This banner is visible only if the following data is provided in the Context Options:

infoBanner {
  bannerName: string
  preset?: string
  title?: string
  text?: string
  disableClose?: boolean
}

When the close button is clicked, a new object with the key as the banner name and the value as "dismissed" will be added to the local storage. The banner will remain invisible as long as this local storage value is present.

Address Form - Loqate

The Address form uses Loqate to add address lookup functionality. For it to work you need to add LOQACCOUNT_KEY to your CircleCi context.

To get the LOQACCOUNT_KEY please speak to the Solutions team.

Update Preference centre link

You can update the link to the Preference centre using the context. Example:

const overrideContext: ContextOptions = NewslettersAndAlertsContext;
(overrideContext.sections[0].props as ContentListViewProps).items[2].href = 'https://mypreferences.the-tls.co.uk/login'

const Page = (props) => <NewslettersAndAlerts context={overrideContext} {...props} />

export default Page

Reset password expiration

In the Password reset page, you can customize the lifetime of the password reset link. The time will be shown in the inline message to the users and will also indicate how long until the inline message is no longer shown.

To set a time, add the following row to your .env.local. Value is in seconds.

PASSWORD_URL_LIFETIME=900

If this is not set, the default value will be 900 seconds (15 minutes).

Past Due Banner

The Past Due Banner has been moved into @newskit-render/shared-components

The Past Due Banner is an exported Component, which can be rendered anywhere. Just import the Component and use it in a React application:

import { PastDueBannerExternal } from '@newskit-render/shared-components'

<PastDueBannerExternal
  pastDueBanner={pastDueBanner}
  user={user}
  wrapper={BannerContainer}
/>

Props: {
  className?: string
  pastDueBanner?: PastDueBannerType
  user: UserData
  wrapper?: React.ComponentType
  theme?: UncompiledTheme
}

The pastDueBanner prop must be in the following format:

interface PastDueBannerType {
  firstNotice: Notice
  secondNotice: Notice
  terminated: Notice
  toBeCancelled: Notice
  toBeCancelledWithRefund: Notice
  cancelled: Notice
  treshold: PastDueBannerTreshold
}

For Notice and PastDueBannerTreshold, see below

If past due banner context is not provied, the component will use it's default context!

For the user prop we are expecting information from MAIN API user: Example:

{
  User {
    paymentFailure {
      active
      startDate
    }
    subscriptions {
      serviceCancellationDate
    }
  }
}

please see MAIN's voyager

Payment

There are two payment providers - Stripe and Zuora. When you load Provider component, it will pick the provider based on the payment url. E.g. /payment/credit-card. The currently supported payment methods are creit-cardand direct-debit, both provided by Zuora.

If you are already using my-account package and you have used the /payment as your only payment route, you don't need to make any changes to your code. This is still supported, and will work as before.

Example /payment/index.tsx: It requires an environment variable called ZUORA_PAGE_ID

import {
  Payment,
  getProviderProps,
  PaymentProvider,
} from '@newskit-render/my-account'

export default Payment

export const getServerSideProps = async (context) => {

  return getProviderProps({
    ...context,
    provider: 'Payment',
    paymentProvider: PaymentProvider.Zuora, // this is no longer needed, but it's safe to let it stay there.
  })
}

If you have more than one payment method, e.g. both Credit card and Direct debit, you can use them this way: Example /payment/[paymentMethod].tsx with dynamic routing to the different payment methods: Each method needs its own environment variable that contains the Zuora page id. The env varibales names are:

  • ZUORA_DIRECT_DEBIT_PAGE_ID
  • ZUORA_CREDIT_CARD_PAGE_ID
import {
  getProviderProps,
  Payment,
  paymentMethodsMap,
  DynamicPage,
} from '@newskit-render/my-account'


const PaymentPage = (props) => {
  return (
    <DynamicPage
      objectMap={paymentMethodsMap}
      pageName={props.paymentType}
      Component={Payment}
      componentProps={props}
    />
  )
}

export default PaymentPage

export const getServerSideProps = async (context) => {

  return getProviderProps(
    {
      ...context,
      provider: 'Payment',
    },
    { featureFlags } /* cra-effected */
  )
}

The payment provider will be chosen based on the payment method in the url. Make sure, that you use credit-card or direct-debit.

The ZUORA_PAGE_ID is also supported and can be used for a third Zuora payment method.

  1. Stripe - requires a stripe key. The key can be passed by the env variable STRIPE_KEY, which should be loaded in env.local. If the variable is not passed, Stripe will be loaded with test account. Stripe key can be retrieved from here. More info on Stripe integration can be found here
  2. Zuora - Data for Zoura forms will come from CPS ( formerly MAIN ) and you should speak to the CPS team about setting up a form. You will need two more environment variable, in addition to the ones containing the page ids, for Zuora to work:

TITLE - CPS can give you this but it will need to be passed into your kubernetes using the Helm value-{env}.yml files:

TITLE: 'thesunuk'

ZUORA_RSA_SIGNATURE_URI - Please check (https://knowledgecenter.zuora.com/Billing/Billing_and_Payments/LA_Hosted_Payment_Pages/B_Payment_Pages_2.0/F_Generate_the_Digital_Signature_for_Payment_Pages_2.0)

Zoura's validation error messages can be overriden in the context:

zuoraCustomErrorMessages?: {
  creditCardHolderName: string
    creditCardNumber:
      one: string
      two: string
      three: string
    cardSecurityCode:
      one: string
      four: string
    creditCardExpiration: string
    creditCardPostalCode: string
    creditCardCountry: string
   unknownError: string
}

These correspond to Zoura's retured error code: 001: Required field not completed - detault for single property or one 002: Invalid card number - two 003: Invalid card type - three 004: Invalid CVV number - four

unknownError: when the error received from Zuora does not have any code or other identifier. Give this field a meaningful value, as it will be shown to the user in some cases. Back to

Book a Holiday Stop

import { HolidayStop } from '@newskit-render/my-account'
import newrelic from 'newrelic'
import React, { useContext } from 'react'
import { AppContext } from '../../../context/app-context'
import { createThemeDropdownObject } from '../../../helpers/createThemeDropdownObject'

const AccountHolidayStop = (props) => {
  const { theme, setTheme } = useContext(AppContext)
  const themeDropdownObject = createThemeDropdownObject(setTheme)
  return (
    <HolidayStop
      {...props}
      customTheme={theme}
      themeDropdownObject={themeDropdownObject}
    />
  )
}

export default AccountHolidayStop

The Book a Holiday Stop page has the following context which can be overridden. On the page different text is displayed for print and digital customers, this is under print and digital keys.

export const holidayStopContext: HolidayStopContextOptions = {
  head: {
    pageTitle: 'Book a holiday stop',
  },
  header: {
    title: 'Book a holiday stop',
    titleOverrides: {
      marginBlockEnd: {
        xs: 'space050',
        sm: 'space060',
      },
    },
    backButton: {
      text: 'Back',
      href: SUBSCRIPTION_BASE_URL,
      'aria-label': 'back',
    },
    showDivider: true,
    backButtonOverrides: {
      marginBlockEnd: { xs: 'space050', sm: 'space060', md: 'space070' },
      stylePreset: 'buttonOutlinedSecondary',
      typographyPreset: 'utilityLabel010',
      iconSize: 'iconSize010',
    },
    marginBlockEnd: {
      xs: 'space050',
      sm: 'space060',
      md: 'space080',
    },
  },
    subscriptionTypes: {
    print: {
      textList: [
        `You can pause your print deliveries at any time by adding a Holiday Stop to your account. You can benefit from pausing deliveries and receiving a refund over a total 5 weeks worth of deliveries within a 12 month period.`,
        `The latest you can submit a holiday request for the next day is 16:00 GMT.`,
      ],
      moreInfo: {
        text: `You may wish to still pause your deliveries even if you've exceeded your credited allowance of Holiday Stops. Doing so will pause deliveries on these days, however your account will not be credited.`,
        textList: [
          `Any days where delivery is paused will be credited back to your account and taken off your next bill (allowance and dates depending).`,
          `The credit due to you will be applied to an invoice no more than 8 weeks after the holiday start date.`,
        ],
      },
    },
    digital: {
      textList: [
        'You can add a holiday stop to your account at any time. You can benefit from pausing your vouchers and receiving a credit over a total of 5 weeks worth of Holiday Stops within a 12 month period.',
        'The latest you can submit a holiday request for the next day is 16:00 GMT.',
      ],
      moreInfo: {
        text:
          'Any arrangements you have for direct delivery by a local retailer will need to be cancelled by you.',
        textList: [
          'Any days where vouchers are paused will be credited back to your account and taken off your next bill.',
          'The credit due to you will be applied to an invoice no more than 8 weeks after the holiday start date.',
        ],
      },
    },
    'digi-print': {
      textList: [
        'You can add a holiday stop to your account at any time. You can benefit from pausing your vouchers and receiving a credit over a total of 5 weeks worth of Holiday Stops within a 12 month period.',
        'The latest you can submit a holiday request for the next day is 16:00 GMT.',
      ],
      moreInfo: {
        text:
          'Any arrangements you have for direct delivery by a local retailer will need to be cancelled by you.',
        textList: [
          'Any days where vouchers are paused will be credited back to your account and taken off your next bill.',
          'The credit due to you will be applied to an invoice no more than 8 weeks after the holiday start date.',
        ],
      },
    },
  },
  textOverrides: {
    typographyPreset: {
      xs: 'utilityBody020',
      sm: 'utilityBody030',
    },
    stylePreset: 'inkBase',
    marginBlockEnd: {
      xs: 'space040',
      md: 'space050',
    },
  },
  unorderedListOverrides: {
    content: {
      stylePreset: 'inkBase',
      typographyPreset: {
        xs: 'utilityBody020',
        sm: 'utilityBody030',
      },
    },
    marginBlockEnd: {
      xs: 'space040',
      md: 'space050',
    },
  },
  button: {
    label: 'Add a Holiday Stop',
    marginBlockStart: { xs: 'space070', md: 'space080' },
    marginBlockEnd: { xs: 'space070', md: 'space080' },
    overrides: {
      stylePreset: 'buttonSolidPrimary',
      width: { xs: '100%', sm: 'inherit' },
    },
  },
}

Newsletters

import { Newsletters } from '@newskit-render/my-account'
import newrelic from 'newrelic'
import React, { useContext } from 'react'
import { AppContext } from '../../../context/app-context'
import { createThemeDropdownObject } from '../../../helpers/createThemeDropdownObject'

const UserNewslettersPage = (props) => {
  const { theme, setTheme } = useContext(AppContext)
  const themeDropdownObject = createThemeDropdownObject(setTheme)
  return (
    <Newsletters
      {...props}
      customTheme={theme}
      themeDropdownObject={themeDropdownObject}
    />
  )
}

export default UserNewslettersPage

Thew newsletters page is designed to display user newsletters in a grid view. Each card in the grid contains an image, newsletter title, description, frequency, and a subscribe/unsubscribe switch button. This page has the following context which can be overridden:

const newslettersContext: NewslettersContextOptions = {
  head: {
    pageTitle: title,
  },
  header: {
    title,
    titleOverrides: {
      marginBlockEnd: {
        xs: 'space050',
        sm: 'space060',
      },
    },
    description:
      'Exclusive content and a curated selection of our top stories straight to your inbox.',
    descriptionOverrides: {
      marginBlockEnd: {
        xs: 'space000',
        sm: 'space000',
        md: 'space000',
      },
    },
    backButton: {
      text: 'Back',
      href: NEWSLETTERS_BASE_URL,
      'aria-label': 'back',
    },
    backButtonOverrides: {
      marginBlockEnd: {
        xs: 'space060',
        sm: 'space060',
        md: 'space060',
      },
      stylePreset: 'buttonOutlinedSecondary',
      typographyPreset: 'utilityLabel010',
      iconSize: 'iconSize010',
    },
    showDivider: false,
    marginBlockEnd: {
      xs: 'space060',
      sm: 'space060',
      md: 'space080',
      lg: 'space060',
      xl: 'space060',
    },
  },
  sideNavSelected: 'newsletters-and-alerts',
  footer,
  cardOverrides: {
    stylePreset: 'newslettersCard',
    maxWidth: {
      xs: 'none',
      sm: 'none',
      md: 'none',
      lg: 'none',
      xl: '250px',
    },
    maxHeight: {
      xs: 'none',
      sm: 'none',
      md: 'none',
      lg: 'none',
      xl: '250px',
    },
    minWidth: {
      xs: 'none',
      sm: 'none',
      md: 'none',
      lg: 'none',
      xl: '250px',
    },
    minHeight: {
      xs: 'none',
      sm: 'none',
      md: 'none',
      lg: 'none',
      xl: '250px',
    },
    paddingInline: {
      xs: '14px',
      md: '14px',
    },
    paddingBlock: {
      xs: '14px',
      md: '14px',
    },
  },
  cardContentOverrides: {
    rowGap: 'space050',
    rows: {
      xs: 'auto',
      sm: '1fr 0.5fr 0.3fr',
      md: '1fr 0.5fr 0.3fr',
      lg: '1fr 0.5fr 0.3fr',
      xl: '1fr 0.7fr 0.3fr',
    },
  },
  frequencyOverrides: {
    stylePreset: 'inkSubtle',
    typographyPreset: 'utilitySubheading010',
  },
  headlineOverrides: {
    content: {
      typographyPreset: {
        xs: 'editorialHeadline020',
        sm: 'editorialHeadline020',
        md: 'editorialHeadline030',
        lg: 'editorialHeadline030',
        xl: 'editorialHeadline030',
      },
      heading: {
        stylePreset: 'inkBrand010',
      },
      paddingInlineEnd: 'space020',
    },
  },
  textOverrides: {
    stylePreset: 'inkBrand010',
    typographyPreset: {
      xs: 'utilityBody010',
      sm: 'utilityBody010',
      md: 'utilityBody020',
      lg: 'utilityBody020',
      xl: 'utilityBody020',
    },
  },
  gridLayoutOverrides: {
    columns: {
      xs: '1fr 1fr',
      sm: '1fr 1fr',
      md: '1fr 1fr',
      lg: '1fr 1fr',
      xl: '1fr 1fr 1fr 1fr',
    },
    columnGap: '15px',
    autoFlow: 'row',
    rowGap: '15px',
  },
  switchButtonProps: {
    label: 'Subscribe',
    size: 'small',
    overrides: {
      onIcon: DoneIcon,
      offIcon: CloseIcon,
      input: {
        stylePreset: 'newslettersSwitchButton',
      },
    },
  },
  newsletterImages: {
    default: '/placeholder.png',
  },
  genericErrorMessage: {
    pending: 'Updating your ${field} newsletter subscription preferences',
    error:
      "Sorry, we're unable to save your ${field} newsletter subscription preferences right now. Please try again or come back later.",
    success: 'Your ${field} newsletter subscription has been updated.',
  },
  layoutMainContentOverrides: {
    fullWidth: true,
    width: {
      xs: '100%',
      md: '100%',
    },
    paddingInline: {
      xs: 'space045',
      sm: 'space060',
      md: 'space090',
      lg: 'space080',
      xl: 'space080',
    },
    paddingBlockStart: {
      xs: 'space060',
      sm: 'space080',
      md: 'space080',
      lg: 'space090',
      xl: 'space080',
    },
  },
}

Redirect unauthenticated users to custom URL

If you require a page to be inaccessible for unauthenticated users, you can utilize two additional properties within the getProviderProps options: checkUserAuthentication and unauthenticatedRedirectUrl.

If no user data is provided from CPS, the getServerSideProps function will redirect directly to the specified URL.

import { getProviderProps } from '@newskit-render/my-account'

export const getServerSideProps = async (context) => {
  return getProviderProps(
    { ...context, provider: 'Payment', // User authentication redirects are working with all providers },
    { checkUserAuthentication: true, unauthenticatedRedirectUrl: 'https://redirect-destination.url' }
  )
}

Type descriptions

  1. ContentContainerProps go back to sections

  2. Forms' contexts go back to forms

  3. CancellationReasonProps. back to cancellation

  4. ConfirmCancellationProps back to cancellation

  5. SubscriptionStatusProps back to noSubscription

  6. EditFieldContextOptions go back to outsideDeliveryAddressModal

  7. The full descriptions of all other types mentioned can be found here as they are exported from there.

Go back to: Override Page's Header Override Page's sections Add on to the existing sections PastDueBanner how to use Payment how to use Properties of Context Options

Readme

Keywords

none

Package Sidebar

Install

npm i @newskit-render/my-account

Weekly Downloads

1,590

Version

7.32.0

License

UNLICENSED

Unpacked Size

2.52 MB

Total Files

1472

Last publish

Collaborators

  • newskit