@wf-financing/ui 3.11.0 → 3.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/index.es.js +21827 -12280
  3. package/package.json +5 -3
  4. package/src/CtaWidget.tsx +31 -9
  5. package/src/api/ctaBanner.ts +1 -2
  6. package/src/components/banner/BannerActions.tsx +26 -0
  7. package/src/components/banner/BulletList.tsx +34 -21
  8. package/src/components/banner/CloseButton.tsx +20 -1
  9. package/src/components/banner/CtaBanner.snapshot.stories.tsx +10 -1
  10. package/src/components/banner/CtaBanner.tsx +22 -14
  11. package/src/components/banner/CtaMainText.tsx +23 -0
  12. package/src/components/modal/ConsentModal.snapshot.stories.tsx +28 -25
  13. package/src/components/modal/ConsentModal.tsx +18 -30
  14. package/src/components/modal/ConsentModalContent.tsx +63 -0
  15. package/src/components/modal/FundingSteps.tsx +16 -8
  16. package/src/components/modal/ModalFooter.tsx +13 -18
  17. package/src/components/modal/TemplateWithUrl.tsx +30 -0
  18. package/src/config/applicationVersion.ts +1 -0
  19. package/src/config/index.ts +1 -0
  20. package/src/hooks/index.ts +1 -0
  21. package/src/hooks/useCopy.ts +30 -0
  22. package/src/main.tsx +0 -1
  23. package/src/utils/buildBannerConfig.ts +26 -0
  24. package/src/utils/getBannerConfig.ts +44 -0
  25. package/src/utils/getModalItems.ts +19 -0
  26. package/src/utils/index.ts +5 -0
  27. package/src/utils/injectUrlInTemplate.ts +6 -0
  28. package/src/utils/replacePlaceholdersForMainText.ts +29 -0
  29. package/tsconfig.json +2 -1
  30. package/vite-env.d.ts +1 -0
  31. package/vite.config.mts +3 -0
  32. package/src/components/banner/BannerActionsDesktop.tsx +0 -13
  33. package/src/components/banner/CtaBannerContent.tsx +0 -37
  34. package/src/components/banner/FooterActions.tsx +0 -8
  35. package/src/components/banner/HeaderActions.tsx +0 -21
  36. package/src/components/banner/ProceedFundingButton.tsx +0 -63
  37. package/src/constants/index.ts +0 -1
  38. package/src/constants/modalItems.ts +0 -26
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wf-financing/ui",
3
- "version": "3.11.0",
3
+ "version": "3.12.1",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./dist/index.es.js",
@@ -35,9 +35,11 @@
35
35
  "framer-motion": "^12.23.0",
36
36
  "react-aria": "^3.41.1",
37
37
  "react-intl": "^6.2.5",
38
+ "react-markdown": "^10.1.0",
38
39
  "styled-components": "^6.1.19",
39
- "@wf-financing/embedded-types": "0.6.0",
40
- "@wf-financing/logger": "1.1.0"
40
+ "@wf-financing/embedded-types": "0.7.0",
41
+ "@wf-financing/ui-assets": "0.2.0",
42
+ "@wf-financing/logger": "1.1.1"
41
43
  },
42
44
  "publishConfig": {
43
45
  "access": "public"
package/src/CtaWidget.tsx CHANGED
@@ -1,30 +1,52 @@
1
1
  import { AnimatePresence } from 'framer-motion';
2
2
  import { useState } from 'react';
3
+ import { ContinueHostedApplicationResponseType } from '@wf-financing/embedded-types';
3
4
 
4
5
  import { AnimationWrapper } from './components/banner/AnimationWrapper';
5
6
  import { CtaBanner } from './components/banner/CtaBanner';
6
- import { useCtaBanner, usePartnerContext } from './hooks';
7
+ import { ConsentModal } from './components/modal/ConsentModal.tsx';
8
+ import { useCopy, useCtaBanner, usePartnerContext, useContinueHostedApplication } from './hooks';
9
+ import { getBannerConfig } from './utils';
7
10
 
8
11
  export const CtaWidget = () => {
9
- const { isLoading, data } = useCtaBanner();
12
+ const { isLoading, data: cta } = useCtaBanner();
13
+ const { data: copy } = useCopy();
10
14
  const { onWidgetClose, isWidgetDismissed, options } = usePartnerContext();
11
15
  const [skipAnimation] = useState(() => !!options?.skipAnimations);
16
+ const { mutate: continueHostedApplicationMutaion } = useContinueHostedApplication();
17
+ const [isModalOpen, setIsModalOpen] = useState(false);
12
18
 
13
- const showBanner = !isLoading && data && !isWidgetDismissed;
19
+ const showBanner = cta && copy && !isWidgetDismissed;
20
+
21
+ if (!showBanner) return null;
14
22
 
15
23
  const handleExitComplete = () => {
16
- if ((!isLoading && !data) || isWidgetDismissed) {
24
+ if ((!isLoading && !cta) || isWidgetDismissed) {
17
25
  onWidgetClose();
18
26
  }
19
27
  };
20
28
 
29
+ const handleIsModalOpen = () => setIsModalOpen((isModalOpen) => !isModalOpen);
30
+ const handleContinueHostedApplication = () => {
31
+ continueHostedApplicationMutaion(undefined, {
32
+ onSuccess: (nextUrl: ContinueHostedApplicationResponseType) => {
33
+ const { next } = nextUrl;
34
+ window.open(next);
35
+ },
36
+ onError: (error) => {
37
+ console.error('Failed to continue application', error);
38
+ },
39
+ });
40
+ };
41
+
42
+ const bannerConfig = getBannerConfig(cta, copy, handleIsModalOpen, handleContinueHostedApplication);
43
+
21
44
  return (
22
45
  <AnimatePresence mode="wait" onExitComplete={handleExitComplete}>
23
- {showBanner && (
24
- <AnimationWrapper skipAnimation={skipAnimation}>
25
- <CtaBanner skipAnimation={skipAnimation} bannerState={data.state} />
26
- </AnimationWrapper>
27
- )}
46
+ <AnimationWrapper skipAnimation={skipAnimation}>
47
+ <CtaBanner bannerConfig={bannerConfig} skipAnimation={skipAnimation} />
48
+ <ConsentModal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} />
49
+ </AnimationWrapper>
28
50
  </AnimatePresence>
29
51
  );
30
52
  };
@@ -4,7 +4,6 @@ import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
4
4
 
5
5
  export const fetchCtaBanner = async (companyToken: string, options?: SdkOptionsType) => {
6
6
  const sdk = await getHeadlessSdkInstance(companyToken, options);
7
- const cta = await sdk.getCta();
8
7
 
9
- return cta;
8
+ return await sdk.getCta();
10
9
  };
@@ -0,0 +1,26 @@
1
+ import { Button, Flex, useDetectDeviceSize } from '@wayflyer/flyui';
2
+
3
+ type BannerActionsProps = {
4
+ isOnDarkTheme: boolean;
5
+ buttonText: string;
6
+ buttonAction: () => void;
7
+ };
8
+
9
+ export const BannerActions = ({ isOnDarkTheme, buttonAction, buttonText }: BannerActionsProps) => {
10
+ const { isMobile } = useDetectDeviceSize();
11
+
12
+ if (isMobile)
13
+ return (
14
+ <Button variant={isOnDarkTheme ? 'PrimaryOnDark' : 'Primary'} fullWidth onClick={buttonAction}>
15
+ {buttonText}
16
+ </Button>
17
+ );
18
+
19
+ return (
20
+ <Flex gap="4">
21
+ <Button variant={isOnDarkTheme ? 'PrimaryOnDark' : 'Primary'} fullWidth onClick={buttonAction}>
22
+ {buttonText}
23
+ </Button>
24
+ </Flex>
25
+ );
26
+ };
@@ -1,23 +1,36 @@
1
- import { Flex, Text, Icon } from '@wayflyer/flyui';
1
+ import { Flex, Text, Icon, useDetectDeviceSize } from '@wayflyer/flyui';
2
2
  import { IconCheck12Line } from '@wayflyer/flyui-icons/12/line';
3
3
 
4
- export const BulletList = ({ items, isOnDarkTheme }: { items: string[]; isOnDarkTheme: boolean }) => (
5
- <Flex gap="4">
6
- {items.map((bullet_point: string) => (
7
- <Flex key={bullet_point} gap="1" align="center">
8
- <Icon color={isOnDarkTheme ? 'onDark' : 'default'}>
9
- <IconCheck12Line />
10
- </Icon>
11
- <Text
12
- size="sm"
13
- fontWeight="medium"
14
- fontStyle="regular"
15
- lineHeight="tight"
16
- color={isOnDarkTheme ? 'onDark' : 'default'}
17
- >
18
- {bullet_point}
19
- </Text>
20
- </Flex>
21
- ))}
22
- </Flex>
23
- );
4
+ type BulletListProps = {
5
+ isOnDarkTheme: boolean;
6
+ bulletPoints?: string[];
7
+ };
8
+
9
+ export const BulletList = ({ isOnDarkTheme, bulletPoints }: BulletListProps) => {
10
+ const { isMobile, isTablet } = useDetectDeviceSize();
11
+
12
+ return (
13
+ <>
14
+ {!(isTablet || isMobile) && bulletPoints && (
15
+ <Flex gap="4">
16
+ {bulletPoints.map((bulletPoint: string) => (
17
+ <Flex key={bulletPoint} gap="1" align="center">
18
+ <Icon color={isOnDarkTheme ? 'onDark' : 'default'}>
19
+ <IconCheck12Line />
20
+ </Icon>
21
+ <Text
22
+ size="sm"
23
+ fontWeight="medium"
24
+ fontStyle="regular"
25
+ lineHeight="tight"
26
+ color={isOnDarkTheme ? 'onDark' : 'default'}
27
+ >
28
+ {bulletPoint}
29
+ </Text>
30
+ </Flex>
31
+ ))}
32
+ </Flex>
33
+ )}
34
+ </>
35
+ );
36
+ };
@@ -1,11 +1,18 @@
1
- import { Button, Flex } from '@wayflyer/flyui';
1
+ import { Button, Flex, useDetectDeviceSize } from '@wayflyer/flyui';
2
2
  import { IconX16Line } from '@wayflyer/flyui-icons/16/line';
3
3
 
4
4
  import { Logger } from '@wf-financing/logger';
5
5
  import { useDismissCta, usePartnerContext } from '../../hooks';
6
+ import styled from 'styled-components';
7
+
8
+ const MobileCloseContainer = styled.div`
9
+ margin-top: calc(0px - var(--sizes-spacing-3));
10
+ margin-right: calc(0px - var(--sizes-spacing-2));
11
+ `;
6
12
 
7
13
  export const CloseButton = ({ isOnDarkTheme }: { isOnDarkTheme: boolean }) => {
8
14
  const { onWidgetDismiss } = usePartnerContext();
15
+ const { isMobile } = useDetectDeviceSize();
9
16
  const dismissCta = useDismissCta();
10
17
 
11
18
  const handleCloseWidget = () => {
@@ -18,6 +25,18 @@ export const CloseButton = ({ isOnDarkTheme }: { isOnDarkTheme: boolean }) => {
18
25
  });
19
26
  };
20
27
 
28
+ if (isMobile) {
29
+ return (
30
+ <MobileCloseContainer>
31
+ <Button onClick={handleCloseWidget} variant={isOnDarkTheme ? 'TertiaryOnDark' : 'Tertiary'}>
32
+ <Flex padding="2">
33
+ <IconX16Line />
34
+ </Flex>
35
+ </Button>
36
+ </MobileCloseContainer>
37
+ );
38
+ }
39
+
21
40
  return (
22
41
  <Button onClick={handleCloseWidget} variant={isOnDarkTheme ? 'TertiaryOnDark' : 'Tertiary'}>
23
42
  <Flex padding="2">
@@ -1,10 +1,18 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
+
2
3
  import { PartnerContext } from '../../utils';
3
4
  import { CtaBanner } from './CtaBanner';
4
5
 
5
6
  const portalContainer = document.createElement('div');
6
7
  document.body.append(portalContainer);
7
8
 
9
+ const bannerConfig = {
10
+ mainText: 'Business financing made easy. Unlock your next stage of growth with Wayflyer today.',
11
+ bulletPoints: ['Funds in 24 hours', 'No personal guarantees', '$5B+ in funding delivered'],
12
+ buttonText: 'Get funding',
13
+ buttonAction: () => {},
14
+ };
15
+
8
16
  const defaultArgs = {
9
17
  companyToken: 'demo-token',
10
18
  partnerCallback: () => {},
@@ -13,6 +21,7 @@ const defaultArgs = {
13
21
  isWidgetDismissed: false,
14
22
  skipAnimation: false,
15
23
  bannerState: 'test',
24
+ bannerConfig,
16
25
  portalContainer,
17
26
  };
18
27
 
@@ -20,7 +29,7 @@ type CtaBannerStoryArgs = typeof defaultArgs;
20
29
 
21
30
  const Template = (args: CtaBannerStoryArgs) => (
22
31
  <PartnerContext.Provider value={{ ...args }}>
23
- <CtaBanner skipAnimation={false} bannerState="test" />
32
+ <CtaBanner bannerConfig={bannerConfig} skipAnimation={false} />
24
33
  </PartnerContext.Provider>
25
34
  );
26
35
 
@@ -2,13 +2,13 @@ import { Flex, useDetectDeviceSize, useTheme } from '@wayflyer/flyui';
2
2
  import { motion, MotionProps } from 'framer-motion';
3
3
  import { css, styled } from 'styled-components';
4
4
 
5
- import { Logger } from '@wf-financing/logger';
6
- import { useEffect } from 'react';
7
5
  import { MODAL_LOGO_IMAGE_URL, STATIC_BASE_URL } from '../../config';
8
6
  import { usePreloadImage } from '../../hooks';
9
- import { CtaBannerContent } from './CtaBannerContent';
10
- import { FooterActions } from './FooterActions';
11
- import { HeaderActions } from './HeaderActions';
7
+ import { BannerActions } from './BannerActions';
8
+ import { CtaMainText } from './CtaMainText';
9
+ import { CloseButton } from './CloseButton';
10
+ import type { BannerConfig } from '../../utils';
11
+ import { BulletList } from './BulletList.tsx';
12
12
 
13
13
  const ON_DARK_THEMES = ['athena'];
14
14
  const LIGHT_BG_THEMES = ['conjura', 'rocketFuel'];
@@ -62,11 +62,11 @@ const bannerAnimationProps: MotionProps = {
62
62
 
63
63
  type Props = {
64
64
  skipAnimation: boolean;
65
- bannerState: string;
65
+ bannerConfig: BannerConfig;
66
66
  };
67
67
 
68
- export const CtaBanner = ({ skipAnimation, bannerState }: Props) => {
69
- const { isMobile, isTablet } = useDetectDeviceSize();
68
+ export const CtaBanner = ({ bannerConfig, skipAnimation }: Props) => {
69
+ const { isMobile } = useDetectDeviceSize();
70
70
  const logoImageUrl = `${STATIC_BASE_URL}${MODAL_LOGO_IMAGE_URL}`;
71
71
  const { themeName } = useTheme();
72
72
  const isOnDarkTheme = ON_DARK_THEMES.includes(themeName);
@@ -74,9 +74,7 @@ export const CtaBanner = ({ skipAnimation, bannerState }: Props) => {
74
74
  const isTertiaryTheme = TERTIARY_THEMES.includes(themeName);
75
75
  usePreloadImage(logoImageUrl);
76
76
 
77
- useEffect(() => {
78
- Logger.logEvent('banner_cta_visible', { bannerState });
79
- }, []);
77
+ const { buttonAction, buttonText, mainText, bulletPoints } = bannerConfig;
80
78
 
81
79
  return (
82
80
  <BannerContainer
@@ -86,10 +84,20 @@ export const CtaBanner = ({ skipAnimation, bannerState }: Props) => {
86
84
  initial={skipAnimation ? bannerAnimationProps.animate : bannerAnimationProps.initial}
87
85
  >
88
86
  <Flex gap="4" align={isMobile ? 'flex-start' : 'center'} justify="space-between" width="100%">
89
- <CtaBannerContent isMobile={isMobile} isTablet={isTablet} isOnDarkTheme={isOnDarkTheme} />
90
- <HeaderActions isOnDarkTheme={isOnDarkTheme} />
87
+ <Flex direction="column" gap="1">
88
+ <CtaMainText mainText={mainText} isOnDarkTheme={isOnDarkTheme} />
89
+ <BulletList isOnDarkTheme={isOnDarkTheme} bulletPoints={bulletPoints} />
90
+ </Flex>
91
+ {!isMobile ? (
92
+ <Flex gap="4">
93
+ <BannerActions isOnDarkTheme={isOnDarkTheme} buttonText={buttonText} buttonAction={buttonAction} />
94
+ <CloseButton isOnDarkTheme={isOnDarkTheme} />
95
+ </Flex>
96
+ ) : (
97
+ <CloseButton isOnDarkTheme={isOnDarkTheme} />
98
+ )}
91
99
  </Flex>
92
- <FooterActions isOnDarkTheme={isOnDarkTheme} />
100
+ {isMobile && <BannerActions isOnDarkTheme={isOnDarkTheme} buttonText={buttonText} buttonAction={buttonAction} />}
93
101
  </BannerContainer>
94
102
  );
95
103
  };
@@ -0,0 +1,23 @@
1
+ import { Text, Heading, useDetectDeviceSize } from '@wayflyer/flyui';
2
+
3
+ type CtaMainTextProps = {
4
+ mainText: string;
5
+ isOnDarkTheme: boolean;
6
+ };
7
+
8
+ export const CtaMainText = ({ mainText, isOnDarkTheme }: CtaMainTextProps) => {
9
+ const { isMobile } = useDetectDeviceSize();
10
+
11
+ const TextComponent = isMobile ? Text : Heading;
12
+
13
+ return (
14
+ <TextComponent
15
+ size={isMobile ? 'base' : 'lg'}
16
+ fontWeight="medium"
17
+ lineHeight="normal"
18
+ color={isOnDarkTheme ? 'onDark' : 'default'}
19
+ >
20
+ {mainText}
21
+ </TextComponent>
22
+ );
23
+ };
@@ -1,7 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
2
 
3
3
  import { PartnerContext } from '../../utils';
4
- import { ConsentModal } from './ConsentModal';
4
+ import { ConsentModalContent } from './ConsentModalContent.tsx';
5
5
 
6
6
  const portalContainer = document.createElement('div');
7
7
  document.body.append(portalContainer);
@@ -17,9 +17,9 @@ const appArgs = {
17
17
 
18
18
  const fn = (): void => {};
19
19
 
20
- const meta: Meta<typeof ConsentModal> = {
20
+ const meta: Meta<typeof ConsentModalContent> = {
21
21
  title: 'ConsentModal',
22
- component: ConsentModal,
22
+ component: ConsentModalContent,
23
23
  argTypes: {
24
24
  isModalOpen: { control: 'boolean', defaultValue: true },
25
25
  setIsModalOpen: { action: 'setIsModalOpen' },
@@ -28,36 +28,39 @@ const meta: Meta<typeof ConsentModal> = {
28
28
 
29
29
  export default meta;
30
30
 
31
- type Story = StoryObj<typeof ConsentModal>;
31
+ type Story = StoryObj<typeof ConsentModalContent>;
32
32
 
33
- export const Default: Story = {
34
- args: {
35
- isModalOpen: true,
36
- setIsModalOpen: fn,
37
- },
38
- render: (args) => (
39
- <PartnerContext.Provider value={{ ...appArgs }}>
40
- <ConsentModal {...args} />
41
- </PartnerContext.Provider>
42
- ),
43
- };
44
-
45
- export const WithContent: Story = {
33
+ export const ConsentModal: Story = {
46
34
  args: {
47
35
  isModalOpen: true,
48
36
  setIsModalOpen: fn,
37
+ title: 'Fuel your growth with capital from Wayflyer',
38
+ buttons: {
39
+ accept: 'Continue to Wayflyer',
40
+ close: 'Cancel',
41
+ },
42
+ imageUrl: 'https://static.wayflyer.com/flyui-assets/logos/wayflyer-ef.png',
43
+ termsAndConditions:
44
+ 'By proceeding, you consent to us sharing your information with Wayflyer so they can assess your eligibility for financing, in accordance with the [Wayflyer Privacy Policy]({termsUrl}).',
45
+ descriptions: [
46
+ {
47
+ title: 'Built for businesses like yours',
48
+ description: '$5B+ deployed to 5,000+ companies',
49
+ },
50
+ {
51
+ title: 'Your growth, your way',
52
+ description: 'Flexible terms that fit your business',
53
+ },
54
+ {
55
+ title: 'Quick to get started',
56
+ description: 'Apply in minutes and get capital in hours',
57
+ },
58
+ ],
49
59
  },
50
60
  render: (args) => (
51
61
  <>
52
62
  <PartnerContext.Provider value={{ ...appArgs }}>
53
- <ConsentModal {...args}>
54
- <div style={{ padding: '20px', background: 'white' }}>
55
- <h2>Consent Agreement</h2>
56
- <p>Please agree to the terms to proceed.</p>
57
- <button>Continue to Wayflyer</button>
58
- <button>Cancel</button>
59
- </div>
60
- </ConsentModal>
63
+ <ConsentModalContent {...args} />
61
64
  </PartnerContext.Provider>
62
65
  </>
63
66
  ),
@@ -1,42 +1,30 @@
1
- import { Flex, Image, Text, useDetectDeviceSize } from '@wayflyer/flyui';
2
- import { useIntl } from 'react-intl';
1
+ import type { Dispatch, SetStateAction } from 'react';
3
2
 
4
- import { styled } from 'styled-components';
5
- import { useDetectSmallScreen } from '../../hooks';
6
- import { FundingSteps } from './FundingSteps';
7
- import { Modal } from './Modal';
8
- import { ModalFooter } from './ModalFooter';
9
- import { STATIC_BASE_URL, MODAL_LOGO_IMAGE_URL } from '../../config';
3
+ import { useCopy } from '../../hooks';
4
+ import { ConsentModalContent } from './ConsentModalContent';
10
5
 
11
6
  type ConsentModalProps = {
12
7
  isModalOpen: boolean;
13
- setIsModalOpen: (isOpen: boolean) => void;
8
+ setIsModalOpen: Dispatch<SetStateAction<boolean>>;
14
9
  };
15
10
 
16
- const ImageContainer = styled(Image)`
17
- width: 100%;
18
- max-width: none;
19
- `;
20
-
21
11
  export const ConsentModal = ({ isModalOpen, setIsModalOpen }: ConsentModalProps) => {
22
- const { formatMessage } = useIntl();
23
- const isSmallScreen = useDetectSmallScreen();
24
- const { isMobile } = useDetectDeviceSize();
12
+ const { data: copy } = useCopy();
13
+ const consentModal = copy?.consentModal;
14
+
15
+ if (!consentModal) return null;
25
16
 
26
- const isReducedSpacing = isSmallScreen || isMobile;
17
+ const { imageUrl, buttons, termsAndConditions, descriptions, title } = consentModal;
27
18
 
28
19
  return (
29
- <Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen}>
30
- {!isSmallScreen && <ImageContainer src={`${STATIC_BASE_URL}${MODAL_LOGO_IMAGE_URL}`} />}
31
- <Flex direction="column" gap="8" padding={isReducedSpacing ? '4' : '6'}>
32
- <Flex direction="column" gap="6">
33
- <Text fontStyle="regular" fontWeight="medium" lineHeight="tight" size="2xl">
34
- {formatMessage({ id: 'ctaModal.title' })}
35
- </Text>
36
- <FundingSteps />
37
- </Flex>
38
- <ModalFooter setOpen={setIsModalOpen} />
39
- </Flex>
40
- </Modal>
20
+ <ConsentModalContent
21
+ imageUrl={imageUrl}
22
+ title={title}
23
+ descriptions={descriptions}
24
+ buttons={buttons}
25
+ termsAndConditions={termsAndConditions}
26
+ setIsModalOpen={setIsModalOpen}
27
+ isModalOpen={isModalOpen}
28
+ />
41
29
  );
42
30
  };
@@ -0,0 +1,63 @@
1
+ import type { Dispatch, SetStateAction } from 'react';
2
+ import { Flex, Image, Text, useDetectDeviceSize } from '@wayflyer/flyui';
3
+
4
+ import { useDetectSmallScreen } from '../../hooks';
5
+ import { FundingSteps } from './FundingSteps.tsx';
6
+ import { ModalFooter } from './ModalFooter.tsx';
7
+ import { Modal } from './Modal.tsx';
8
+ import { styled } from 'styled-components';
9
+
10
+ type ModalDescriptions = {
11
+ title: string;
12
+ description: string;
13
+ };
14
+
15
+ type Buttons = {
16
+ accept: string;
17
+ close: string;
18
+ };
19
+
20
+ type ConsentModalContentProps = {
21
+ imageUrl: string;
22
+ title: string;
23
+ descriptions: ModalDescriptions[];
24
+ buttons: Buttons;
25
+ termsAndConditions: string;
26
+ setIsModalOpen: Dispatch<SetStateAction<boolean>>;
27
+ isModalOpen: boolean;
28
+ };
29
+
30
+ const ImageContainer = styled(Image)`
31
+ width: 100%;
32
+ max-width: none;
33
+ `;
34
+
35
+ export const ConsentModalContent = ({
36
+ imageUrl,
37
+ title,
38
+ descriptions,
39
+ termsAndConditions,
40
+ buttons,
41
+ setIsModalOpen,
42
+ isModalOpen,
43
+ }: ConsentModalContentProps) => {
44
+ const isSmallScreen = useDetectSmallScreen();
45
+ const { isMobile } = useDetectDeviceSize();
46
+
47
+ const isReducedSpacing = isSmallScreen || isMobile;
48
+
49
+ return (
50
+ <Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen}>
51
+ {!isSmallScreen && <ImageContainer src={imageUrl} />}
52
+ <Flex direction="column" gap="8" padding={isReducedSpacing ? '4' : '6'}>
53
+ <Flex direction="column" gap="6">
54
+ <Text fontStyle="regular" fontWeight="medium" lineHeight="tight" size="2xl">
55
+ {title}
56
+ </Text>
57
+ <FundingSteps descriptions={descriptions} />
58
+ </Flex>
59
+ <ModalFooter setOpen={setIsModalOpen} buttons={buttons} termsAndConditions={termsAndConditions} />
60
+ </Flex>
61
+ </Modal>
62
+ );
63
+ };
@@ -1,23 +1,31 @@
1
1
  import { Flex, Text } from '@wayflyer/flyui';
2
- import { useIntl } from 'react-intl';
3
2
 
4
3
  import { useDetectSmallScreen } from '../../hooks';
5
- import { MODAL_ITEMS } from '../../constants';
4
+ import { getModalItems } from '../../utils';
6
5
 
7
- export const FundingSteps = () => {
8
- const { formatMessage } = useIntl();
6
+ type ModalDescriptions = {
7
+ title: string;
8
+ description: string;
9
+ };
10
+
11
+ type FundingStepsProps = {
12
+ descriptions: ModalDescriptions[];
13
+ };
14
+
15
+ export const FundingSteps = ({ descriptions }: FundingStepsProps) => {
9
16
  const isSmallScreen = useDetectSmallScreen();
17
+ const modalDescriptions = getModalItems(descriptions);
10
18
 
11
19
  return (
12
20
  <Flex direction="column" gap="2">
13
- {MODAL_ITEMS.map(({ id, title, subtitle, Icon }) => (
14
- <Flex key={id} direction="row" align="center" gap="2">
21
+ {modalDescriptions.map(({ title, description, Icon }) => (
22
+ <Flex key={title} direction="row" align="center" gap="2">
15
23
  {!isSmallScreen && <Icon />}
16
24
  <Flex direction="column">
17
25
  <Text color="default" fontWeight="medium">
18
- {formatMessage(title)}
26
+ {title}
19
27
  </Text>
20
- <Text color="placeholder">{formatMessage(subtitle)}</Text>
28
+ <Text color="placeholder">{description}</Text>
21
29
  </Flex>
22
30
  </Flex>
23
31
  ))}
@@ -1,16 +1,21 @@
1
- import { Button, Flex, Link, Text, useDetectDeviceSize } from '@wayflyer/flyui';
1
+ import { Button, Flex, useDetectDeviceSize } from '@wayflyer/flyui';
2
2
  import { IconArrowOnSquareUpRight16Line } from '@wayflyer/flyui-icons/16/line';
3
3
  import { StartHostedApplicationResponseType } from '@wf-financing/embedded-types';
4
4
  import { Logger } from '@wf-financing/logger';
5
- import { FormattedMessage } from 'react-intl';
6
5
 
6
+ import { TemplateWithUrl } from './TemplateWithUrl';
7
7
  import { useDetectSmallScreen, useStartHostedApplication } from '../../hooks';
8
8
 
9
- type ModalFooterType = {
9
+ type ModalFooterProps = {
10
+ buttons: {
11
+ accept: string;
12
+ close: string;
13
+ };
14
+ termsAndConditions: string;
10
15
  setOpen: (isOpen: boolean) => void;
11
16
  };
12
17
 
13
- export const ModalFooter = ({ setOpen }: ModalFooterType) => {
18
+ export const ModalFooter = ({ setOpen, buttons, termsAndConditions }: ModalFooterProps) => {
14
19
  const { isMobile } = useDetectDeviceSize();
15
20
  const { mutate, isPending: isLoading } = useStartHostedApplication();
16
21
  const isSmallScreen = useDetectSmallScreen();
@@ -24,33 +29,23 @@ export const ModalFooter = ({ setOpen }: ModalFooterType) => {
24
29
  window.open(next);
25
30
  },
26
31
  onError: (error) => {
27
- Logger.logError(`Failed to continue application ${error.message}`);
32
+ Logger.logError(`Failed to start application ${error.message}`);
28
33
  },
29
34
  });
30
35
  };
31
36
 
32
- const handleOpenExternalLink = () => {
33
- Logger.logEvent('cta_open_privacy_note');
34
- window.open('https://wayflyer.com/en/privacy-notice', '_blank', 'noopener,noreferrer');
35
- };
36
-
37
37
  return (
38
38
  <Flex direction="column" gap="3">
39
39
  <Flex gap="2" direction={isSmallScreen || isMobile ? 'column' : 'row'}>
40
40
  <Button fullWidth onClick={handleStartApplication} loading={isLoading} variant="Primary" external>
41
- <FormattedMessage id="ctaModal.action" />
41
+ {buttons.accept}
42
42
  {!isLoading && <IconArrowOnSquareUpRight16Line />}
43
43
  </Button>
44
44
  <Button fullWidth variant="Secondary" onClick={() => setOpen(false)}>
45
- <FormattedMessage id="common.cancel" />
45
+ {buttons.close}
46
46
  </Button>
47
47
  </Flex>
48
- <Text size="sm" lineHeight="normal">
49
- <FormattedMessage id="ctaModal.consent" />
50
- <Link onClick={handleOpenExternalLink}>
51
- <FormattedMessage id="ctaModal.consent.privacy_policy" />
52
- </Link>
53
- </Text>
48
+ <TemplateWithUrl templateString={termsAndConditions} />
54
49
  </Flex>
55
50
  );
56
51
  };