@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.
- package/CHANGELOG.md +19 -0
- package/dist/index.es.js +21827 -12280
- package/package.json +5 -3
- package/src/CtaWidget.tsx +31 -9
- package/src/api/ctaBanner.ts +1 -2
- package/src/components/banner/BannerActions.tsx +26 -0
- package/src/components/banner/BulletList.tsx +34 -21
- package/src/components/banner/CloseButton.tsx +20 -1
- package/src/components/banner/CtaBanner.snapshot.stories.tsx +10 -1
- package/src/components/banner/CtaBanner.tsx +22 -14
- package/src/components/banner/CtaMainText.tsx +23 -0
- package/src/components/modal/ConsentModal.snapshot.stories.tsx +28 -25
- package/src/components/modal/ConsentModal.tsx +18 -30
- package/src/components/modal/ConsentModalContent.tsx +63 -0
- package/src/components/modal/FundingSteps.tsx +16 -8
- package/src/components/modal/ModalFooter.tsx +13 -18
- package/src/components/modal/TemplateWithUrl.tsx +30 -0
- package/src/config/applicationVersion.ts +1 -0
- package/src/config/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useCopy.ts +30 -0
- package/src/main.tsx +0 -1
- package/src/utils/buildBannerConfig.ts +26 -0
- package/src/utils/getBannerConfig.ts +44 -0
- package/src/utils/getModalItems.ts +19 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/injectUrlInTemplate.ts +6 -0
- package/src/utils/replacePlaceholdersForMainText.ts +29 -0
- package/tsconfig.json +2 -1
- package/vite-env.d.ts +1 -0
- package/vite.config.mts +3 -0
- package/src/components/banner/BannerActionsDesktop.tsx +0 -13
- package/src/components/banner/CtaBannerContent.tsx +0 -37
- package/src/components/banner/FooterActions.tsx +0 -8
- package/src/components/banner/HeaderActions.tsx +0 -21
- package/src/components/banner/ProceedFundingButton.tsx +0 -63
- package/src/constants/index.ts +0 -1
- 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.
|
|
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.
|
|
40
|
-
"@wf-financing/
|
|
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 {
|
|
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 =
|
|
19
|
+
const showBanner = cta && copy && !isWidgetDismissed;
|
|
20
|
+
|
|
21
|
+
if (!showBanner) return null;
|
|
14
22
|
|
|
15
23
|
const handleExitComplete = () => {
|
|
16
|
-
if ((!isLoading && !
|
|
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
|
-
{
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
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
|
};
|
package/src/api/ctaBanner.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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}
|
|
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 {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
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
|
-
|
|
65
|
+
bannerConfig: BannerConfig;
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
export const CtaBanner = ({
|
|
69
|
-
const { isMobile
|
|
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
|
-
|
|
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
|
-
<
|
|
90
|
-
|
|
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
|
-
<
|
|
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 {
|
|
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
|
|
20
|
+
const meta: Meta<typeof ConsentModalContent> = {
|
|
21
21
|
title: 'ConsentModal',
|
|
22
|
-
component:
|
|
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
|
|
31
|
+
type Story = StoryObj<typeof ConsentModalContent>;
|
|
32
32
|
|
|
33
|
-
export const
|
|
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
|
-
<
|
|
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 {
|
|
2
|
-
import { useIntl } from 'react-intl';
|
|
1
|
+
import type { Dispatch, SetStateAction } from 'react';
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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:
|
|
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 {
|
|
23
|
-
const
|
|
24
|
-
|
|
12
|
+
const { data: copy } = useCopy();
|
|
13
|
+
const consentModal = copy?.consentModal;
|
|
14
|
+
|
|
15
|
+
if (!consentModal) return null;
|
|
25
16
|
|
|
26
|
-
const
|
|
17
|
+
const { imageUrl, buttons, termsAndConditions, descriptions, title } = consentModal;
|
|
27
18
|
|
|
28
19
|
return (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 {
|
|
4
|
+
import { getModalItems } from '../../utils';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
{
|
|
14
|
-
<Flex key={
|
|
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
|
-
{
|
|
26
|
+
{title}
|
|
19
27
|
</Text>
|
|
20
|
-
<Text color="placeholder">{
|
|
28
|
+
<Text color="placeholder">{description}</Text>
|
|
21
29
|
</Flex>
|
|
22
30
|
</Flex>
|
|
23
31
|
))}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { Button, Flex,
|
|
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
|
|
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 }:
|
|
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
|
|
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
|
-
|
|
41
|
+
{buttons.accept}
|
|
42
42
|
{!isLoading && <IconArrowOnSquareUpRight16Line />}
|
|
43
43
|
</Button>
|
|
44
44
|
<Button fullWidth variant="Secondary" onClick={() => setOpen(false)}>
|
|
45
|
-
|
|
45
|
+
{buttons.close}
|
|
46
46
|
</Button>
|
|
47
47
|
</Flex>
|
|
48
|
-
<
|
|
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
|
};
|