From 4fb7df49ba2fc0a51cc8766a05b78f9a6a799647 Mon Sep 17 00:00:00 2001 From: Guilherme Ribeiro Date: Fri, 25 Oct 2024 15:24:29 +0200 Subject: [PATCH] PayMe and PayNow improvements (#2910) * PayNow improvements (#2889) * rendering instructions for mobile * using bento tokens * changeset * draft stepper * fixed stories * translations and css changes * Fixed test * adjusted timeline component and css fixes * fixed test * applied review comments * PayMe improvements (#2909) * Payconic labels fix (#2916) * using token fn --- .changeset/shaggy-poets-eat.md | 5 ++ .changeset/sharp-cars-clean.md | 5 ++ .changeset/tricky-pets-tap.md | 5 ++ .../src/components/BcmcMobile/BcmcMobile.ts | 4 +- .../src/components/PayMe/Instructions.scss | 15 ---- .../components/PayMe/Instructions.test.tsx | 22 ------ .../lib/src/components/PayMe/Instructions.tsx | 20 ----- packages/lib/src/components/PayMe/PayMe.ts | 7 +- .../PayMe/components/PayMeInstructions.tsx | 23 ++++++ .../PayMe/components/PayMeIntroduction.tsx | 21 ++++++ .../lib/src/components/PayNow/PayNow.test.ts | 56 ++++++++++++++ packages/lib/src/components/PayNow/PayNow.ts | 5 ++ .../PayNow/components/PayNowInstructions.scss | 13 ++++ .../PayNow/components/PayNowInstructions.tsx | 21 ++++++ .../PayNow/components/PayNowIntroduction.scss | 9 +++ .../PayNow/components/PayNowIntroduction.tsx | 32 ++++++++ packages/lib/src/components/Swish/Swish.scss | 3 + packages/lib/src/components/Swish/Swish.ts | 1 + .../helpers/QRLoaderContainer/types.ts | 5 +- .../ContentSeparator/ContentSeparator.scss | 7 +- .../internal/QRLoader/QRLoader.scss | 50 ++++++++----- .../components/internal/QRLoader/QRLoader.tsx | 24 ++++-- .../src/components/internal/QRLoader/types.ts | 2 +- .../internal/Timeline/Timeline.scss | 73 +++++++++++++++++++ .../components/internal/Timeline/Timeline.tsx | 39 ++++++++++ .../internal/Timeline/TimelineWrapper.scss | 8 ++ .../internal/Timeline/TimelineWrapper.tsx | 13 ++++ .../src/components/internal/Timeline/index.ts | 2 + packages/lib/src/utils/useIsMobile.ts | 25 +++++++ .../stories/components/PayMe.stories.tsx | 24 ++++++ .../stories/components/PayNow.stories.tsx | 24 ++++++ .../stories/components/Payconic.stories.tsx | 26 +++++++ .../stories/components/Swish.stories.tsx | 24 ++++++ .../stories/internals/Timeline.stories.tsx | 24 ++++++ packages/server/translations/is-IS.json | 2 +- 35 files changed, 541 insertions(+), 98 deletions(-) create mode 100644 .changeset/shaggy-poets-eat.md create mode 100644 .changeset/sharp-cars-clean.md create mode 100644 .changeset/tricky-pets-tap.md delete mode 100644 packages/lib/src/components/PayMe/Instructions.scss delete mode 100644 packages/lib/src/components/PayMe/Instructions.test.tsx delete mode 100644 packages/lib/src/components/PayMe/Instructions.tsx create mode 100644 packages/lib/src/components/PayMe/components/PayMeInstructions.tsx create mode 100644 packages/lib/src/components/PayMe/components/PayMeIntroduction.tsx create mode 100644 packages/lib/src/components/PayNow/components/PayNowInstructions.scss create mode 100644 packages/lib/src/components/PayNow/components/PayNowInstructions.tsx create mode 100644 packages/lib/src/components/PayNow/components/PayNowIntroduction.scss create mode 100644 packages/lib/src/components/PayNow/components/PayNowIntroduction.tsx create mode 100644 packages/lib/src/components/Swish/Swish.scss create mode 100644 packages/lib/src/components/internal/Timeline/Timeline.scss create mode 100644 packages/lib/src/components/internal/Timeline/Timeline.tsx create mode 100644 packages/lib/src/components/internal/Timeline/TimelineWrapper.scss create mode 100644 packages/lib/src/components/internal/Timeline/TimelineWrapper.tsx create mode 100644 packages/lib/src/components/internal/Timeline/index.ts create mode 100644 packages/lib/src/utils/useIsMobile.ts create mode 100644 packages/lib/storybook/stories/components/PayMe.stories.tsx create mode 100644 packages/lib/storybook/stories/components/PayNow.stories.tsx create mode 100644 packages/lib/storybook/stories/components/Payconic.stories.tsx create mode 100644 packages/lib/storybook/stories/components/Swish.stories.tsx create mode 100644 packages/lib/storybook/stories/internals/Timeline.stories.tsx diff --git a/.changeset/shaggy-poets-eat.md b/.changeset/shaggy-poets-eat.md new file mode 100644 index 0000000000..7f408215bd --- /dev/null +++ b/.changeset/shaggy-poets-eat.md @@ -0,0 +1,5 @@ +--- +'@adyen/adyen-web': patch +--- + +Payconic - Adjusted QR code message and removed unused button label. diff --git a/.changeset/sharp-cars-clean.md b/.changeset/sharp-cars-clean.md new file mode 100644 index 0000000000..981e4d45d1 --- /dev/null +++ b/.changeset/sharp-cars-clean.md @@ -0,0 +1,5 @@ +--- +'@adyen/adyen-web': minor +--- + +PayMe - Improved instructions UI diff --git a/.changeset/tricky-pets-tap.md b/.changeset/tricky-pets-tap.md new file mode 100644 index 0000000000..a857cfe4d6 --- /dev/null +++ b/.changeset/tricky-pets-tap.md @@ -0,0 +1,5 @@ +--- +'@adyen/adyen-web': minor +--- + +PayNow - Adding instructions to scan QR code on mobile view diff --git a/packages/lib/src/components/BcmcMobile/BcmcMobile.ts b/packages/lib/src/components/BcmcMobile/BcmcMobile.ts index c240756d48..178ed233d5 100644 --- a/packages/lib/src/components/BcmcMobile/BcmcMobile.ts +++ b/packages/lib/src/components/BcmcMobile/BcmcMobile.ts @@ -7,12 +7,10 @@ class BCMCMobileElement extends QRLoaderContainer { public static txVariants = [TxVariants.bcmc_mobile, TxVariants.bcmc_mobile_QR]; formatProps(props) { - const isMobile = window.matchMedia('(max-width: 768px)').matches && /Android|iPhone|iPod/.test(navigator.userAgent); - return { delay: STATUS_INTERVAL, countdownTime: COUNTDOWN_MINUTES, - buttonLabel: isMobile ? 'openApp' : 'generateQRCode', + timeToPay: 'payme.timeToPay', ...super.formatProps(props) }; } diff --git a/packages/lib/src/components/PayMe/Instructions.scss b/packages/lib/src/components/PayMe/Instructions.scss deleted file mode 100644 index d45cfb9100..0000000000 --- a/packages/lib/src/components/PayMe/Instructions.scss +++ /dev/null @@ -1,15 +0,0 @@ -@import 'styles/variable-generator'; - -.adyen-checkout-payme-instructions { - font-size: token(text-body-font-size); - text-align: center; - color: token(color-label-secondary); - line-height: token(text-body-line-height); - - &__steps { - list-style-position: inside; - padding-inline-start: 0; - margin: token(spacer-070) 0; - padding-bottom: token(spacer-040); - } -} diff --git a/packages/lib/src/components/PayMe/Instructions.test.tsx b/packages/lib/src/components/PayMe/Instructions.test.tsx deleted file mode 100644 index f7758d1a1a..0000000000 --- a/packages/lib/src/components/PayMe/Instructions.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { render, screen } from '@testing-library/preact'; -import { h } from 'preact'; -import { CoreProvider } from '../../core/Context/CoreProvider'; -import Instructions from './Instructions'; - -describe('Instructions', () => { - const customRender = (ui: h.JSX.Element) => { - return render( - - {ui} - - ); - }; - - test('should see a list of instructions and footnote', async () => { - customRender(); - expect(await screen.findByText('Open the PayMe app', { exact: false })).toBeInTheDocument(); - expect(await screen.findByText('Scan the QR code', { exact: false })).toBeInTheDocument(); - expect(await screen.findByText('Complete the payment in the app', { exact: false })).toBeInTheDocument(); - expect(await screen.findByText('Please do not close this page before the payment is completed', { exact: false })).toBeInTheDocument(); - }); -}); diff --git a/packages/lib/src/components/PayMe/Instructions.tsx b/packages/lib/src/components/PayMe/Instructions.tsx deleted file mode 100644 index 6311e67d19..0000000000 --- a/packages/lib/src/components/PayMe/Instructions.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useCoreContext } from '../../core/Context/CoreProvider'; -import { h } from 'preact'; -import './Instructions.scss'; - -export default function Instructions() { - const { i18n } = useCoreContext(); - const steps = i18n.get('payme.instructions.steps'); - const footnote = i18n.get('payme.instructions.footnote'); - - return ( -
-
    - {steps.split('%@').map((step, index) => ( -
  1. {step}
  2. - ))} -
- {footnote} -
- ); -} diff --git a/packages/lib/src/components/PayMe/PayMe.ts b/packages/lib/src/components/PayMe/PayMe.ts index f98a434008..09d60bd7f2 100644 --- a/packages/lib/src/components/PayMe/PayMe.ts +++ b/packages/lib/src/components/PayMe/PayMe.ts @@ -1,5 +1,6 @@ import QRLoaderContainer from '../helpers/QRLoaderContainer'; -import Instructions from './Instructions'; +import { PayMeInstructions } from './components/PayMeInstructions'; +import { PayMeIntroduction } from './components/PayMeIntroduction'; class PayMeElement extends QRLoaderContainer { public static type = 'payme'; @@ -11,10 +12,10 @@ class PayMeElement extends QRLoaderContainer { delay: PayMeElement.defaultDelay, countdownTime: PayMeElement.defaultCountdown, redirectIntroduction: 'payme.openPayMeApp', - introduction: 'payme.scanQrCode', timeToPay: 'payme.timeToPay', buttonLabel: 'payme.redirectButtonLabel', - instructions: Instructions, + introduction: PayMeIntroduction, + instructions: PayMeInstructions, ...super.formatProps(props) }; } diff --git a/packages/lib/src/components/PayMe/components/PayMeInstructions.tsx b/packages/lib/src/components/PayMe/components/PayMeInstructions.tsx new file mode 100644 index 0000000000..2f2074f376 --- /dev/null +++ b/packages/lib/src/components/PayMe/components/PayMeInstructions.tsx @@ -0,0 +1,23 @@ +import { useIsMobile } from '../../../utils/useIsMobile'; +import { useCoreContext } from '../../../core/Context/CoreProvider'; +import { Timeline, TimelineWrapper } from '../../internal/Timeline'; +import { h } from 'preact'; + +const PayMeInstructions = () => { + const { i18n } = useCoreContext(); + const { isMobileScreenSize } = useIsMobile(); + + if (isMobileScreenSize) { + return null; + } + + const instructions = i18n.get('payme.instructions.steps').split('%@'); + + return ( + + + + ); +}; + +export { PayMeInstructions }; diff --git a/packages/lib/src/components/PayMe/components/PayMeIntroduction.tsx b/packages/lib/src/components/PayMe/components/PayMeIntroduction.tsx new file mode 100644 index 0000000000..296a0c2c9b --- /dev/null +++ b/packages/lib/src/components/PayMe/components/PayMeIntroduction.tsx @@ -0,0 +1,21 @@ +import { useCoreContext } from '../../../core/Context/CoreProvider'; +import { useIsMobile } from '../../../utils/useIsMobile'; +import { Timeline, TimelineWrapper } from '../../internal/Timeline'; +import { Fragment, h } from 'preact'; + +const PayMeIntroduction = () => { + const { i18n } = useCoreContext(); + const { isMobileScreenSize } = useIsMobile(); + + const instructions = i18n.get('payme.instructions.steps').split('%@'); + + return isMobileScreenSize ? ( + + + + ) : ( + {i18n.get('payme.scanQrCode')} + ); +}; + +export { PayMeIntroduction }; diff --git a/packages/lib/src/components/PayNow/PayNow.test.ts b/packages/lib/src/components/PayNow/PayNow.test.ts index 8027f53e9f..bc58e748f6 100644 --- a/packages/lib/src/components/PayNow/PayNow.test.ts +++ b/packages/lib/src/components/PayNow/PayNow.test.ts @@ -1,4 +1,11 @@ import PayNow from './PayNow'; +import { render, screen, within } from '@testing-library/preact'; +import { mock } from 'jest-mock-extended'; +import { Resources } from '../../core/Context/Resources'; +import checkPaymentStatus from '../../core/Services/payment-status'; +import { SRPanel } from '../../core/Errors/SRPanel'; + +jest.mock('../../core/Services/payment-status'); describe('PayNow', () => { describe('isValid', () => { @@ -21,4 +28,53 @@ describe('PayNow', () => { expect(paynow.render()).not.toBe(null); }); }); + + test('should render mobile instructions', async () => { + // Mocks matchMedia to return 'matches: true' when checking (max-width: 1024px) + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(() => ({ + matches: true + })) + }); + + jest.useFakeTimers(); + + const srPanel = mock(); + const resources = mock(); + resources.getImage.mockReturnValue((icon: string) => `https://checkout-adyen.com/${icon}`); + + // @ts-ignore mockResolvedValue not inferred + checkPaymentStatus.mockResolvedValue({ + payload: 'Ab02b4c0!BQABAgBLLk9evMb+rScNdE...', + resultCode: 'pending', + type: 'complete' + }); + + const paynow = new PayNow(global.core, { + loadingContext: 'checkoutshopper.com/', + modules: { resources, analytics: global.analytics, srPanel }, + i18n: global.i18n, + paymentData: 'Ab02b4c0!BQABAgBH1f8hqfFxOvbfK..', + qrCodeImage: '', + paymentMethodType: 'paynow', + qrCodeData: '00020126580009SG...' + }); + + render(paynow.mount('body')); + + // Triggers the execution of the setTimeout that makes the /status API request + jest.runAllTimers(); + + await screen.findAllByText(/Scan the QR code using the PayNow app to complete the payment/); + + const div = within(screen.queryByTestId('paynow-introduction')); + div.getByText(/Take a screenshot of the QR code./); + div.getByText(/Open the PayNow bank or payment app./); + div.getByText(/Select the option to scan a QR code./); + div.getByText(/Choose the option to upload a QR and select the screenshot./); + div.getByText(/Complete the transaction./); + + jest.resetAllMocks(); + }); }); diff --git a/packages/lib/src/components/PayNow/PayNow.ts b/packages/lib/src/components/PayNow/PayNow.ts index e52ccfff21..d3db9050bb 100644 --- a/packages/lib/src/components/PayNow/PayNow.ts +++ b/packages/lib/src/components/PayNow/PayNow.ts @@ -1,12 +1,17 @@ import QRLoaderContainer from '../helpers/QRLoaderContainer/QRLoaderContainer'; import { delay, countdownTime } from './config'; import { TxVariants } from '../tx-variants'; +import { PayNowIntroduction } from './components/PayNowIntroduction'; +import { PayNowInstructions } from './components/PayNowInstructions'; class PayNowElement extends QRLoaderContainer { public static type = TxVariants.paynow; formatProps(props) { return { + introduction: PayNowIntroduction, + instructions: PayNowInstructions, + timeToPay: 'payme.timeToPay', delay, countdownTime, ...super.formatProps(props) diff --git a/packages/lib/src/components/PayNow/components/PayNowInstructions.scss b/packages/lib/src/components/PayNow/components/PayNowInstructions.scss new file mode 100644 index 0000000000..174ae7ff5e --- /dev/null +++ b/packages/lib/src/components/PayNow/components/PayNowInstructions.scss @@ -0,0 +1,13 @@ +@import 'styles/variable-generator'; + +.adyen-checkout-paynow__instructions { + font-size: token(text-body-font-size); + font-weight: token(text-body-font-weight); + line-height: token(text-body-line-height); + color: token(color-label-primary); + text-align: center; +} + +.adyen-checkout-paynow__instructions > p { + margin-bottom: 0; +} diff --git a/packages/lib/src/components/PayNow/components/PayNowInstructions.tsx b/packages/lib/src/components/PayNow/components/PayNowInstructions.tsx new file mode 100644 index 0000000000..eeee0ac284 --- /dev/null +++ b/packages/lib/src/components/PayNow/components/PayNowInstructions.tsx @@ -0,0 +1,21 @@ +import { h } from 'preact'; +import { useCoreContext } from '../../../core/Context/CoreProvider'; +import ContentSeparator from '../../internal/ContentSeparator'; +import './PayNowInstructions.scss'; +import { useIsMobile } from '../../../utils/useIsMobile'; + +const PayNowInstructions = () => { + const { i18n } = useCoreContext(); + const { isMobileScreenSize } = useIsMobile(); + + if (!isMobileScreenSize) return; + + return ( +
+ +

{i18n.get('paynow.scanQrCode')}

+
+ ); +}; + +export { PayNowInstructions }; diff --git a/packages/lib/src/components/PayNow/components/PayNowIntroduction.scss b/packages/lib/src/components/PayNow/components/PayNowIntroduction.scss new file mode 100644 index 0000000000..cdec4c3c63 --- /dev/null +++ b/packages/lib/src/components/PayNow/components/PayNowIntroduction.scss @@ -0,0 +1,9 @@ +@import 'styles/variable-generator'; + +.adyen-checkout-paynow__introduction { + font-size: token(text-body-font-size); + font-weight: token(text-body-font-weight); + line-height: token(text-body-line-height); + color: token(color-label-primary); + text-align: center; +} diff --git a/packages/lib/src/components/PayNow/components/PayNowIntroduction.tsx b/packages/lib/src/components/PayNow/components/PayNowIntroduction.tsx new file mode 100644 index 0000000000..2bd0e34080 --- /dev/null +++ b/packages/lib/src/components/PayNow/components/PayNowIntroduction.tsx @@ -0,0 +1,32 @@ +import { h } from 'preact'; +import { useCoreContext } from '../../../core/Context/CoreProvider'; +import './PayNowIntroduction.scss'; +import { TimelineWrapper, Timeline } from '../../internal/Timeline'; +import { useIsMobile } from '../../../utils/useIsMobile'; + +const PayNowIntroduction = () => { + const { i18n } = useCoreContext(); + const { isMobileScreenSize } = useIsMobile(); + + const instructions = [ + i18n.get('paynow.mobileViewInstruction.step1'), + i18n.get('paynow.mobileViewInstruction.step2'), + i18n.get('paynow.mobileViewInstruction.step3'), + i18n.get('paynow.mobileViewInstruction.step4'), + i18n.get('paynow.mobileViewInstruction.step5') + ]; + + return ( +
+ {isMobileScreenSize ? ( + + + + ) : ( + i18n.get('paynow.scanQrCode') + )} +
+ ); +}; + +export { PayNowIntroduction }; diff --git a/packages/lib/src/components/Swish/Swish.scss b/packages/lib/src/components/Swish/Swish.scss new file mode 100644 index 0000000000..70c0024044 --- /dev/null +++ b/packages/lib/src/components/Swish/Swish.scss @@ -0,0 +1,3 @@ +.adyen-checkout__qr-loader--swish > .adyen-checkout__qr-loader__instructions { + text-align: center; +} diff --git a/packages/lib/src/components/Swish/Swish.ts b/packages/lib/src/components/Swish/Swish.ts index 162186f72d..b7c6e6e7b9 100644 --- a/packages/lib/src/components/Swish/Swish.ts +++ b/packages/lib/src/components/Swish/Swish.ts @@ -1,5 +1,6 @@ import QRLoaderContainer from '../helpers/QRLoaderContainer/QRLoaderContainer'; import { TxVariants } from '../tx-variants'; +import './Swish.scss'; class SwishElement extends QRLoaderContainer { public static type = TxVariants.swish; diff --git a/packages/lib/src/components/helpers/QRLoaderContainer/types.ts b/packages/lib/src/components/helpers/QRLoaderContainer/types.ts index 66de326ec4..5729ffd99c 100644 --- a/packages/lib/src/components/helpers/QRLoaderContainer/types.ts +++ b/packages/lib/src/components/helpers/QRLoaderContainer/types.ts @@ -16,10 +16,11 @@ export interface QRLoaderConfiguration extends UIElementProps { brandLogo?: string; buttonLabel?: string; qrCodeImage?: string; + qrCodeData?: string; paymentData?: string; - introduction?: string; redirectIntroduction?: string; timeToPay?: string; - instructions?: string | (() => h.JSX.Element); copyBtn?: boolean; + introduction?: string | (() => h.JSX.Element); + instructions?: string | (() => h.JSX.Element); } diff --git a/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss b/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss index faef810749..eaa5722e52 100644 --- a/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss +++ b/packages/lib/src/components/internal/ContentSeparator/ContentSeparator.scss @@ -6,16 +6,17 @@ display: flex; justify-content: center; align-items: center; - color: token(color-label-secondary); + color: token(color-label-primary); white-space: nowrap; - font-size: 13px; + text-transform: capitalize; + font-size: token(text-body-font-size); line-height: token(text-caption-line-height); &::after, &::before { content: ''; flex: 1; - border-bottom: 1px solid token(color-outline-tertiary); + border-bottom: 1px solid token(color-separator-primary); } &::after { diff --git a/packages/lib/src/components/internal/QRLoader/QRLoader.scss b/packages/lib/src/components/internal/QRLoader/QRLoader.scss index e864b1847d..41b9fe5bd4 100644 --- a/packages/lib/src/components/internal/QRLoader/QRLoader.scss +++ b/packages/lib/src/components/internal/QRLoader/QRLoader.scss @@ -3,13 +3,17 @@ .adyen-checkout__qr-loader { background: token(color-background-primary); padding: token(spacer-110); + padding-bottom: token(spacer-090); border: token(border-width-s) solid token(color-outline-secondary); border-radius: token(border-radius-m); - text-align: center; + display: flex; + flex-direction: column; + align-items: center; } .adyen-checkout__qr-loader--result { padding: 100px; + gap: token(spacer-090); } .adyen-checkout__qr-loader--app { @@ -18,25 +22,24 @@ padding: 0; } +.adyen-checkout__qr-loader__brand-logo-wrapper { + border-radius: token(border-radius-s); + box-shadow: token(shadow-low); + margin-bottom: token(spacer-090); + overflow: hidden; +} + .adyen-checkout__qr-loader__brand-logo { - width: 74px; - border-radius: 3px; + width: 80px; + display: block; } .adyen-checkout__qr-loader__subtitle { max-width: 400px; - margin: token(spacer-100) auto 0; -} - -.adyen-checkout__qr-loader__subtitle--result { - margin-bottom: token(spacer-100); -} - -.adyen-checkout__qr-loader__subtitle, -.adyen-checkout__qr-loader__payment_amount { color: token(color-label-primary); - font-size: token(text-subtitle-font-size); - line-height: token(text-caption-line-height); + font-size: token(text-body-font-size); + line-height: token(text-body-line-height); + text-align: center; } .adyen-checkout__qr-loader__icon { @@ -45,16 +48,20 @@ } .adyen-checkout__qr-loader__payment_amount { - font-weight: bold; + color: token(color-label-primary); + font-weight: token(text-title-l-font-weight); + font-size: token(text-title-l-font-size); + line-height: token(text-body-wide-line-height); + margin-bottom: token(spacer-090); } .adyen-checkout__qr-loader__progress { height: token(spacer-020); background: token(color-outline-secondary); border-radius: token(border-radius-l); - margin: token(spacer-100) auto token(spacer-060); width: 152px; padding-right: 3%; + margin-bottom: token(spacer-060); [dir="rtl"] & { padding-right: 0; @@ -70,10 +77,11 @@ } .adyen-checkout__qr-loader__countdown { - color: token(color-label-secondary); - font-size: token(text-body-font-size); + font-size: token(text-caption-font-size); + text-align: center; } + .adyen-checkout__qr-loader > .adyen-checkout__spinner__wrapper { margin: 60px 0; } @@ -88,10 +96,11 @@ } .adyen-checkout__qr-loader__instructions { + max-width: 300px; color: token(color-label-tertiary); font-size: token(text-subtitle-font-size); line-height: 1.5; - margin-top: token(spacer-100); + margin-top: token(spacer-090); } .adyen-checkout__qr-loader__actions { @@ -101,8 +110,9 @@ margin-top: token(spacer-100); } -@media only screen and (max-device-width: 1200px) { +@media (max-width: 1024px) { .adyen-checkout__qr-loader__app-link { display: block; + min-width: 220px; } } diff --git a/packages/lib/src/components/internal/QRLoader/QRLoader.tsx b/packages/lib/src/components/internal/QRLoader/QRLoader.tsx index ba5b43c44d..0a10d5ce92 100644 --- a/packages/lib/src/components/internal/QRLoader/QRLoader.tsx +++ b/packages/lib/src/components/internal/QRLoader/QRLoader.tsx @@ -164,7 +164,7 @@ class QRLoader extends Component { src={getImage({ imageFolder: 'components/' })(image)} alt={status} /> -
{status}
+
{status}
); }; @@ -180,7 +180,11 @@ class QRLoader extends Component { if (loading) { return (
- {brandLogo && {brandName}} + {brandLogo && ( +
+ {brandName} +
+ )}
); @@ -193,7 +197,11 @@ class QRLoader extends Component { return (
- {brandLogo && {brandName}} + {brandLogo && ( +
+ {brandName} +
+ )} {amount && amount.value && amount.currency && (
{i18n.amount(amount.value, amount.currency)}
@@ -211,7 +219,7 @@ class QRLoader extends Component { {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
- {i18n.get(this.props.introduction)} + {typeof this.props.introduction === 'string' ? i18n.get(this.props.introduction) : this.props.introduction?.()}
{  {timeToPayString[1]}
- {typeof this.props.instructions === 'string' ? ( -
{i18n.get(this.props.instructions)}
- ) : ( - this.props.instructions?.() + {this.props.instructions && ( +
+ {typeof this.props.instructions === 'string' ? i18n.get(this.props.instructions) : this.props.instructions?.()} +
)} {this.props.copyBtn && ( diff --git a/packages/lib/src/components/internal/QRLoader/types.ts b/packages/lib/src/components/internal/QRLoader/types.ts index 2431f89a4f..c86c49fdd5 100644 --- a/packages/lib/src/components/internal/QRLoader/types.ts +++ b/packages/lib/src/components/internal/QRLoader/types.ts @@ -23,9 +23,9 @@ export interface QRLoaderProps { brandLogo?: string; brandName?: string; buttonLabel?: string; - introduction?: string; redirectIntroduction?: string; timeToPay?: string; + introduction?: string | (() => h.JSX.Element); instructions?: string | (() => h.JSX.Element); copyBtn?: boolean; onActionHandled?: (rtnObj: ActionHandledReturnObject) => void; diff --git a/packages/lib/src/components/internal/Timeline/Timeline.scss b/packages/lib/src/components/internal/Timeline/Timeline.scss new file mode 100644 index 0000000000..2091a468a3 --- /dev/null +++ b/packages/lib/src/components/internal/Timeline/Timeline.scss @@ -0,0 +1,73 @@ +@import 'styles/variable-generator'; + +.adyen-checkout-timeline { + scrollbar-color: token(color-background-quaternary) transparent; +} + +.adyen-checkout-timeline__items { + all: unset; +} + +.adyen-checkout-timeline-item { + scrollbar-color: token(color-background-quaternary) transparent; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + list-style-type: none; + max-width: 500px; + + &:last-child { + .adyen-checkout-timeline-item__separator { + display: none; + } + + .adyen-checkout-timeline-item__content { + padding-bottom: 0; + } + } +} + +.adyen-checkout-timeline-item__row { + display: flex; + gap: token(spacer-070); +} + +.adyen-checkout-timeline-item__marker { + display: flex; + flex-flow: column; + width: 100%; + max-width: token(spacer-070); + margin-top: token(spacer-010); +} + +.adyen-checkout-timeline-item__separator { + flex-grow: 1; + position: relative; + + &::before { + background-color: token(color-separator-secondary); + content: ""; + height: 100%; + left: 50%; + position: absolute; + transform: translate(-50%); + width: 1px; + } +} + +.adyen-checkout-timeline-item__content { + align-items: flex-start; + display: flex; + flex-direction: column; + flex-grow: 1; + padding-bottom: token(spacer-060); +} + +.adyen-checkout-timeline-item__title { + font-size: token(text-caption-font-size); + font-weight: token(text-body-font-weight); + line-height: token(text-body-line-height); + letter-spacing: 0; + text-align: left; + color: token(color-label-primary); + margin-bottom: token(spacer-010); +} diff --git a/packages/lib/src/components/internal/Timeline/Timeline.tsx b/packages/lib/src/components/internal/Timeline/Timeline.tsx new file mode 100644 index 0000000000..14852c12d5 --- /dev/null +++ b/packages/lib/src/components/internal/Timeline/Timeline.tsx @@ -0,0 +1,39 @@ +import { h } from 'preact'; +import './Timeline.scss'; + +interface Timeline { + instructions: string[]; +} + +const Timeline = ({ instructions }: Timeline) => { + if (!instructions || instructions.length === 0) { + return null; + } + + return ( +
+
    + {instructions.map((value, index) => ( +
  1. +
    + +
    +
    {value}
    +
    +
    +
  2. + ))} +
+
+ ); +}; + +export { Timeline }; diff --git a/packages/lib/src/components/internal/Timeline/TimelineWrapper.scss b/packages/lib/src/components/internal/Timeline/TimelineWrapper.scss new file mode 100644 index 0000000000..acecdc6e1c --- /dev/null +++ b/packages/lib/src/components/internal/Timeline/TimelineWrapper.scss @@ -0,0 +1,8 @@ +@import 'styles/variable-generator'; + +.adyen-checkout-timeline-wrapper { + padding: 16px; + border-radius: 8px; + max-width: 300px; + background-color: token(color-background-secondary); +} diff --git a/packages/lib/src/components/internal/Timeline/TimelineWrapper.tsx b/packages/lib/src/components/internal/Timeline/TimelineWrapper.tsx new file mode 100644 index 0000000000..870997ab7c --- /dev/null +++ b/packages/lib/src/components/internal/Timeline/TimelineWrapper.tsx @@ -0,0 +1,13 @@ +import { ComponentChildren, h } from 'preact'; +import './TimelineWrapper.scss'; + +interface TimelineWrapperProps { + children: ComponentChildren; + className?: string; +} + +const TimelineWrapper = ({ children, className }: TimelineWrapperProps) => { + return
{children}
; +}; + +export { TimelineWrapper }; diff --git a/packages/lib/src/components/internal/Timeline/index.ts b/packages/lib/src/components/internal/Timeline/index.ts new file mode 100644 index 0000000000..e4bfcc24a2 --- /dev/null +++ b/packages/lib/src/components/internal/Timeline/index.ts @@ -0,0 +1,2 @@ +export * from './TimelineWrapper'; +export * from './Timeline'; diff --git a/packages/lib/src/utils/useIsMobile.ts b/packages/lib/src/utils/useIsMobile.ts new file mode 100644 index 0000000000..c5a8a2249d --- /dev/null +++ b/packages/lib/src/utils/useIsMobile.ts @@ -0,0 +1,25 @@ +import { useCallback, useEffect, useState } from 'preact/hooks'; + +function isTabletWidthOrSmaller() { + return window.matchMedia('(max-width: 1024px)').matches; +} + +const useIsMobile = () => { + const [isMobileScreenSize, setIsMobileScreenSize] = useState(isTabletWidthOrSmaller()); + + const handleWindowResize = useCallback(() => { + const isMobileScreenSize = isTabletWidthOrSmaller(); + setIsMobileScreenSize(isMobileScreenSize); + }, []); + + useEffect(() => { + window.addEventListener('resize', handleWindowResize); + return () => { + window.removeEventListener('resize', handleWindowResize); + }; + }, [handleWindowResize]); + + return { isMobileScreenSize }; +}; + +export { useIsMobile }; diff --git a/packages/lib/storybook/stories/components/PayMe.stories.tsx b/packages/lib/storybook/stories/components/PayMe.stories.tsx new file mode 100644 index 0000000000..047700b86b --- /dev/null +++ b/packages/lib/storybook/stories/components/PayMe.stories.tsx @@ -0,0 +1,24 @@ +import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '../types'; +import { QRLoaderConfiguration } from '../../../src/types'; +import { ComponentContainer } from '../ComponentContainer'; +import { Checkout } from '../Checkout'; +import PayMe from '../../../src/components/PayMe/PayMe'; + +type PayMeStory = StoryConfiguration; + +const meta: MetaConfiguration = { + title: 'Components/PayMe' +}; + +const render = ({ componentConfiguration, ...checkoutConfig }: PaymentMethodStoryProps) => ( + {checkout => } +); + +export const Default: PayMeStory = { + render, + args: { + countryCode: 'HK' + } +}; + +export default meta; diff --git a/packages/lib/storybook/stories/components/PayNow.stories.tsx b/packages/lib/storybook/stories/components/PayNow.stories.tsx new file mode 100644 index 0000000000..8448c4765f --- /dev/null +++ b/packages/lib/storybook/stories/components/PayNow.stories.tsx @@ -0,0 +1,24 @@ +import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '../types'; +import { QRLoaderConfiguration } from '../../../src/types'; +import { ComponentContainer } from '../ComponentContainer'; +import { Checkout } from '../Checkout'; +import PayNow from '../../../src/components/PayNow/PayNow'; + +type PayNowStory = StoryConfiguration; + +const meta: MetaConfiguration = { + title: 'Components/PayNow' +}; + +const render = ({ componentConfiguration, ...checkoutConfig }: PaymentMethodStoryProps) => ( + {checkout => } +); + +export const Default: PayNowStory = { + render, + args: { + countryCode: 'SG' + } +}; + +export default meta; diff --git a/packages/lib/storybook/stories/components/Payconic.stories.tsx b/packages/lib/storybook/stories/components/Payconic.stories.tsx new file mode 100644 index 0000000000..6dc2571b74 --- /dev/null +++ b/packages/lib/storybook/stories/components/Payconic.stories.tsx @@ -0,0 +1,26 @@ +import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '../types'; +import { QRLoaderConfiguration } from '../../../src/types'; +import { ComponentContainer } from '../ComponentContainer'; +import { Checkout } from '../Checkout'; +import BcmcMobile from '../../../src/components/BcmcMobile'; + +type PayconicStory = StoryConfiguration; + +const meta: MetaConfiguration = { + title: 'Components/Payconic' +}; + +const render = ({ componentConfiguration, ...checkoutConfig }: PaymentMethodStoryProps) => ( + + {checkout => } + +); + +export const Default: PayconicStory = { + render, + args: { + countryCode: 'BE' + } +}; + +export default meta; diff --git a/packages/lib/storybook/stories/components/Swish.stories.tsx b/packages/lib/storybook/stories/components/Swish.stories.tsx new file mode 100644 index 0000000000..43a643e0fd --- /dev/null +++ b/packages/lib/storybook/stories/components/Swish.stories.tsx @@ -0,0 +1,24 @@ +import { MetaConfiguration, PaymentMethodStoryProps, StoryConfiguration } from '../types'; +import { QRLoaderConfiguration } from '../../../src/types'; +import { ComponentContainer } from '../ComponentContainer'; +import { Checkout } from '../Checkout'; +import Swish from '../../../src/components/Swish'; + +type SwishStory = StoryConfiguration; + +const meta: MetaConfiguration = { + title: 'Components/Swish' +}; + +const render = ({ componentConfiguration, ...checkoutConfig }: PaymentMethodStoryProps) => ( + {checkout => } +); + +export const Default: SwishStory = { + render, + args: { + countryCode: 'SE' + } +}; + +export default meta; diff --git a/packages/lib/storybook/stories/internals/Timeline.stories.tsx b/packages/lib/storybook/stories/internals/Timeline.stories.tsx new file mode 100644 index 0000000000..49ff2b2e88 --- /dev/null +++ b/packages/lib/storybook/stories/internals/Timeline.stories.tsx @@ -0,0 +1,24 @@ +import { Meta, StoryObj } from '@storybook/preact'; +import { Timeline } from '../../../src/components/internal/Timeline'; + +const meta: Meta = { + title: 'Internals/Timeline', + component: Timeline +}; + +export const Default: StoryObj = { + render: () => { + const instructions = [ + 'Open the banking app', + "Enter your email, ID, name, surname, fax number, grandmother's name, postal code, secret word", + 'Wait for the OTP', + 'Complete the payment in the app and wait for the confirmation here' + ]; + return ; + }, + parameters: { + controls: { exclude: ['useSessions', 'countryCode', 'shopperLocale', 'amount', 'showPayButton'] } + } +}; + +export default meta; diff --git a/packages/server/translations/is-IS.json b/packages/server/translations/is-IS.json index 01173c63e7..f01b99b0cb 100644 --- a/packages/server/translations/is-IS.json +++ b/packages/server/translations/is-IS.json @@ -325,4 +325,4 @@ "paynow.mobileViewInstruction.step3": "Veldu valkostinn til að skanna QR-kóða.", "paynow.mobileViewInstruction.step4": "Veldu valkostinn til að hlaða upp QR-kóða og veldu skjámyndina.", "paynow.mobileViewInstruction.step5": "Ljúktu færslunni." -} +} \ No newline at end of file