@wf-financing/ui 3.10.0 → 3.12.0

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 (43) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/index.es.js +21897 -12225
  3. package/global.d.ts +1 -1
  4. package/index.ts +2 -2
  5. package/package.json +5 -2
  6. package/src/App.tsx +5 -2
  7. package/src/CtaWidget.tsx +31 -9
  8. package/src/api/ctaBanner.ts +1 -2
  9. package/src/api/getHeadlessSdkInstance.ts +4 -2
  10. package/src/components/banner/BannerActions.tsx +26 -0
  11. package/src/components/banner/BulletList.tsx +34 -21
  12. package/src/components/banner/CloseButton.tsx +22 -2
  13. package/src/components/banner/CtaBanner.snapshot.stories.tsx +11 -1
  14. package/src/components/banner/CtaBanner.tsx +23 -8
  15. package/src/components/banner/CtaMainText.tsx +23 -0
  16. package/src/components/modal/ConsentModal.snapshot.stories.tsx +28 -25
  17. package/src/components/modal/ConsentModal.tsx +18 -30
  18. package/src/components/modal/ConsentModalContent.tsx +63 -0
  19. package/src/components/modal/FundingSteps.tsx +16 -8
  20. package/src/components/modal/ModalFooter.tsx +15 -17
  21. package/src/components/modal/TemplateWithUrl.tsx +30 -0
  22. package/src/config/applicationVersion.ts +1 -0
  23. package/src/config/index.ts +1 -0
  24. package/src/hooks/index.ts +1 -0
  25. package/src/hooks/useCopy.ts +30 -0
  26. package/src/main.tsx +8 -1
  27. package/src/utils/buildBannerConfig.ts +26 -0
  28. package/src/utils/getBannerConfig.ts +44 -0
  29. package/src/utils/getModalItems.ts +19 -0
  30. package/src/utils/index.ts +5 -0
  31. package/src/utils/injectUrlInTemplate.ts +6 -0
  32. package/src/utils/parseJwt.ts +4 -2
  33. package/src/utils/replacePlaceholdersForMainText.ts +29 -0
  34. package/tsconfig.json +2 -1
  35. package/vite-env.d.ts +1 -0
  36. package/vite.config.mts +3 -0
  37. package/src/components/banner/BannerActionsDesktop.tsx +0 -13
  38. package/src/components/banner/CtaBannerContent.tsx +0 -37
  39. package/src/components/banner/FooterActions.tsx +0 -8
  40. package/src/components/banner/HeaderActions.tsx +0 -21
  41. package/src/components/banner/ProceedFundingButton.tsx +0 -61
  42. package/src/constants/index.ts +0 -1
  43. package/src/constants/modalItems.ts +0 -26
package/global.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IWayflyerUiSdkConstructor, IWayflyerHeadlessSdkConstructor } from '@wf-financing/embedded-types';
1
+ import { IWayflyerHeadlessSdkConstructor, IWayflyerUiSdkConstructor } from '@wf-financing/embedded-types';
2
2
 
3
3
  declare global {
4
4
  interface Window {
package/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { WayflyerUiSdk } from './sdk';
2
2
 
3
- const addHeadlessSdkToWindow = () => {
3
+ const addUiSdkToWindow = () => {
4
4
  window.WayflyerUiSdk = WayflyerUiSdk;
5
5
  };
6
6
 
7
- addHeadlessSdkToWindow();
7
+ addUiSdkToWindow();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wf-financing/ui",
3
- "version": "3.10.0",
3
+ "version": "3.12.0",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./dist/index.es.js",
@@ -35,8 +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/embedded-types": "0.7.0",
41
+ "@wf-financing/ui-assets": "0.2.0",
42
+ "@wf-financing/logger": "1.1.1"
40
43
  },
41
44
  "publishConfig": {
42
45
  "access": "public"
package/src/App.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
2
  import { FlyUIProvider } from '@wayflyer/flyui';
3
3
  import { PartnerCallbackType, SdkOptionsType } from '@wf-financing/embedded-types';
4
+ import { Logger } from '@wf-financing/logger';
4
5
  import { useState } from 'react';
5
6
  import { createIntl, createIntlCache, IntlShape } from 'react-intl';
6
7
 
@@ -46,6 +47,7 @@ export const App = ({
46
47
 
47
48
  const onWidgetDismiss = () => {
48
49
  setIsWidgetDismissed(true);
50
+ Logger.logEvent('ui_sdk_banner_dismissed');
49
51
  };
50
52
 
51
53
  return (
@@ -65,8 +67,9 @@ export const App = ({
65
67
  theme={theme}
66
68
  intl={intl}
67
69
  onThemeLoad={() => setIsThemeLoaded(true)}
68
- // TODO: implement error notification event after required BE endpoints become available
69
- onThemeLoadError={() => console.error('Error while loading theme')}
70
+ onThemeLoadError={() => {
71
+ Logger.logError(`Error while loading theme: ${theme}`);
72
+ }}
70
73
  >
71
74
  {isThemeLoaded && <CtaWidget />}
72
75
  </FlyUIProvider>
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} />
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
  };
@@ -1,5 +1,6 @@
1
1
  import { IWayflyerHeadlessSdk, SdkOptionsType } from '@wf-financing/embedded-types';
2
2
 
3
+ import { Logger } from '@wf-financing/logger';
3
4
  import { HEADLESS_SDK_URL, WAYFLYER_HEADLESS_SDK_ID } from '../config';
4
5
  import { initializeHeadlessSdk, loadScriptAndInitializeSdk } from '../utils';
5
6
 
@@ -28,14 +29,15 @@ export const getHeadlessSdkInstance = async (companyToken: string, options?: Sdk
28
29
  script.type = 'module';
29
30
  script.id = WAYFLYER_HEADLESS_SDK_ID;
30
31
  script.async = true;
32
+ script.onerror = () => Logger.logError('Error in loading headless SDK script');
31
33
 
32
34
  document.head.appendChild(script);
33
35
  const headlessSdk: IWayflyerHeadlessSdk = await loadScriptAndInitializeSdk(script, companyToken, options);
34
36
  cachedSdkInstance = headlessSdk;
35
37
 
36
38
  return headlessSdk;
37
- } catch (error) {
38
- console.error('Error in loading headless SDK:', error);
39
+ } catch {
40
+ Logger.logError('Error in loading headless SDK');
39
41
  throw new Error('Failed to load script');
40
42
  }
41
43
  };
@@ -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,10 +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
+ import { Logger } from '@wf-financing/logger';
4
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
+ `;
5
12
 
6
13
  export const CloseButton = ({ isOnDarkTheme }: { isOnDarkTheme: boolean }) => {
7
14
  const { onWidgetDismiss } = usePartnerContext();
15
+ const { isMobile } = useDetectDeviceSize();
8
16
  const dismissCta = useDismissCta();
9
17
 
10
18
  const handleCloseWidget = () => {
@@ -12,11 +20,23 @@ export const CloseButton = ({ isOnDarkTheme }: { isOnDarkTheme: boolean }) => {
12
20
 
13
21
  dismissCta.mutate(undefined, {
14
22
  onError: (error) => {
15
- console.error('Failed to dismiss CTA', error);
23
+ Logger.logError(`Failed to dismiss CTA ${error.message}`);
16
24
  },
17
25
  });
18
26
  };
19
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
+
20
40
  return (
21
41
  <Button onClick={handleCloseWidget} variant={isOnDarkTheme ? 'TertiaryOnDark' : 'Tertiary'}>
22
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: () => {},
@@ -12,6 +20,8 @@ const defaultArgs = {
12
20
  onWidgetDismiss: () => {},
13
21
  isWidgetDismissed: false,
14
22
  skipAnimation: false,
23
+ bannerState: 'test',
24
+ bannerConfig,
15
25
  portalContainer,
16
26
  };
17
27
 
@@ -19,7 +29,7 @@ type CtaBannerStoryArgs = typeof defaultArgs;
19
29
 
20
30
  const Template = (args: CtaBannerStoryArgs) => (
21
31
  <PartnerContext.Provider value={{ ...args }}>
22
- <CtaBanner skipAnimation={false} />
32
+ <CtaBanner bannerConfig={bannerConfig} skipAnimation={false} />
23
33
  </PartnerContext.Provider>
24
34
  );
25
35
 
@@ -4,9 +4,11 @@ import { css, styled } from 'styled-components';
4
4
 
5
5
  import { MODAL_LOGO_IMAGE_URL, STATIC_BASE_URL } from '../../config';
6
6
  import { usePreloadImage } from '../../hooks';
7
- import { CtaBannerContent } from './CtaBannerContent';
8
- import { FooterActions } from './FooterActions';
9
- 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';
10
12
 
11
13
  const ON_DARK_THEMES = ['athena'];
12
14
  const LIGHT_BG_THEMES = ['conjura', 'rocketFuel'];
@@ -60,10 +62,11 @@ const bannerAnimationProps: MotionProps = {
60
62
 
61
63
  type Props = {
62
64
  skipAnimation: boolean;
65
+ bannerConfig: BannerConfig;
63
66
  };
64
67
 
65
- export const CtaBanner = ({ skipAnimation }: Props) => {
66
- const { isMobile, isTablet } = useDetectDeviceSize();
68
+ export const CtaBanner = ({ bannerConfig, skipAnimation }: Props) => {
69
+ const { isMobile } = useDetectDeviceSize();
67
70
  const logoImageUrl = `${STATIC_BASE_URL}${MODAL_LOGO_IMAGE_URL}`;
68
71
  const { themeName } = useTheme();
69
72
  const isOnDarkTheme = ON_DARK_THEMES.includes(themeName);
@@ -71,6 +74,8 @@ export const CtaBanner = ({ skipAnimation }: Props) => {
71
74
  const isTertiaryTheme = TERTIARY_THEMES.includes(themeName);
72
75
  usePreloadImage(logoImageUrl);
73
76
 
77
+ const { buttonAction, buttonText, mainText, bulletPoints } = bannerConfig;
78
+
74
79
  return (
75
80
  <BannerContainer
76
81
  $isLightBgTheme={isLightBgTheme}
@@ -79,10 +84,20 @@ export const CtaBanner = ({ skipAnimation }: Props) => {
79
84
  initial={skipAnimation ? bannerAnimationProps.animate : bannerAnimationProps.initial}
80
85
  >
81
86
  <Flex gap="4" align={isMobile ? 'flex-start' : 'center'} justify="space-between" width="100%">
82
- <CtaBannerContent isMobile={isMobile} isTablet={isTablet} isOnDarkTheme={isOnDarkTheme} />
83
- <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
+ )}
84
99
  </Flex>
85
- <FooterActions isOnDarkTheme={isOnDarkTheme} />
100
+ {isMobile && <BannerActions isOnDarkTheme={isOnDarkTheme} buttonText={buttonText} buttonAction={buttonAction} />}
86
101
  </BannerContainer>
87
102
  );
88
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
+ };