@wf-financing/ui 0.1.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.
- package/dist/index.es.js +91062 -0
- package/index.ts +18 -0
- package/module-types/embedded-sdk-cta-module.d.ts +4 -0
- package/package.json +38 -0
- package/src/App.tsx +40 -0
- package/src/CtaWidget.tsx +15 -0
- package/src/api/ctaBanner.ts +8 -0
- package/src/api/getHeadlessSdkInstance.ts +13 -0
- package/src/api/startHostedApplication.ts +10 -0
- package/src/components/banner/BulletList.tsx +17 -0
- package/src/components/banner/CtaBanner.tsx +34 -0
- package/src/components/banner/CtaBannerActions.tsx +19 -0
- package/src/components/banner/CtaBannerContent.tsx +26 -0
- package/src/components/banner/ProceedFundingButton.tsx +35 -0
- package/src/components/modal/ConsentModal.tsx +30 -0
- package/src/components/modal/FundingSteps.tsx +28 -0
- package/src/components/modal/Modal.tsx +60 -0
- package/src/components/modal/ModalFooter.tsx +47 -0
- package/src/components/modal/modalEnhancements.ts +40 -0
- package/src/constants/index.ts +1 -0
- package/src/constants/modalItems.ts +29 -0
- package/src/hooks/useCtaBanner.ts +20 -0
- package/src/hooks/useStartHostedApplication.ts +24 -0
- package/src/locales/en.json +16 -0
- package/src/main.tsx +33 -0
- package/src/utils/partnerContext.ts +8 -0
- package/tsconfig.json +9 -0
- package/vite.config.ts +26 -0
package/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Themes } from '@wayflyer/flyui';
|
|
2
|
+
|
|
3
|
+
import { mountToTarget } from './src/main';
|
|
4
|
+
|
|
5
|
+
export const mount = (
|
|
6
|
+
targetId: string,
|
|
7
|
+
partnerDesignId: Themes,
|
|
8
|
+
companyToken: string,
|
|
9
|
+
isMockedMode?: boolean,
|
|
10
|
+
) => {
|
|
11
|
+
if (document.readyState === 'loading') {
|
|
12
|
+
document.addEventListener('DOMContentLoaded', () =>
|
|
13
|
+
mountToTarget(targetId, partnerDesignId, companyToken, isMockedMode),
|
|
14
|
+
);
|
|
15
|
+
} else {
|
|
16
|
+
mountToTarget(targetId, partnerDesignId, companyToken, isMockedMode);
|
|
17
|
+
}
|
|
18
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wf-financing/ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"exports": {
|
|
5
|
+
".": {
|
|
6
|
+
"import": "./dist/index.es.js",
|
|
7
|
+
"types": "./dist/index.d.ts"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.cjs.js",
|
|
11
|
+
"module": "dist/index.es.js",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"typescript": "^5.0.0",
|
|
15
|
+
"vite": "^5.0.0",
|
|
16
|
+
"vite-plugin-dts": "^3.4.0"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@wayflyer/flyui": "188.3.0",
|
|
20
|
+
"@wayflyer/flyui-icons": "1.3.2",
|
|
21
|
+
"@tanstack/react-query": "5.81.5",
|
|
22
|
+
"framer-motion": "^12.23.0",
|
|
23
|
+
"react-aria": "^3.41.1",
|
|
24
|
+
"react-intl": "^6.2.5",
|
|
25
|
+
"styled-components": "^6.1.19",
|
|
26
|
+
"@wf-financing/embedded-types": "^0.1.3"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"clean": "rm -rf dist",
|
|
33
|
+
"dev": "vite",
|
|
34
|
+
"build": "vite build",
|
|
35
|
+
"preview": "vite preview",
|
|
36
|
+
"publish-sdk-cta-ui": "pnpm clean && pnpm build && pnpm publish --access public --no-git-checks"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
2
|
+
import { FlyUIProvider, Themes } from '@wayflyer/flyui';
|
|
3
|
+
import { createIntl, createIntlCache, IntlShape } from 'react-intl';
|
|
4
|
+
|
|
5
|
+
import { CtaWidget } from './CtaWidget';
|
|
6
|
+
import enMessages from './locales/en.json';
|
|
7
|
+
import { PartnerContext } from './utils/partnerContext';
|
|
8
|
+
|
|
9
|
+
type AppPropsType = {
|
|
10
|
+
partnerDesignId: Themes;
|
|
11
|
+
companyToken: string;
|
|
12
|
+
isMockedMode?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const queryClient = new QueryClient();
|
|
16
|
+
const messages = {
|
|
17
|
+
en: enMessages,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const App = ({ partnerDesignId, companyToken, isMockedMode }: AppPropsType) => {
|
|
21
|
+
const locale = 'en';
|
|
22
|
+
|
|
23
|
+
const intl: IntlShape = createIntl(
|
|
24
|
+
{
|
|
25
|
+
locale,
|
|
26
|
+
messages: messages[locale],
|
|
27
|
+
},
|
|
28
|
+
createIntlCache()
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<QueryClientProvider client={queryClient}>
|
|
33
|
+
<PartnerContext.Provider value={{ companyToken, isMockedMode }}>
|
|
34
|
+
<FlyUIProvider theme={partnerDesignId} intl={intl}>
|
|
35
|
+
<CtaWidget />
|
|
36
|
+
</FlyUIProvider>
|
|
37
|
+
</PartnerContext.Provider>
|
|
38
|
+
</QueryClientProvider>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CtaBanner } from './components/banner/CtaBanner';
|
|
2
|
+
import { useCtaBanner } from './hooks/useCtaBanner';
|
|
3
|
+
|
|
4
|
+
export const CtaWidget = () => {
|
|
5
|
+
const banner = useCtaBanner();
|
|
6
|
+
const handleCloseWidget = () => console.log('close widget');
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
{!banner.isLoading && (
|
|
11
|
+
<CtaBanner closeWidget={handleCloseWidget} />
|
|
12
|
+
)}
|
|
13
|
+
</>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
|
|
2
|
+
|
|
3
|
+
export const fetchCtaBanner = async (companyToken: string, isMockedMode: boolean) => {
|
|
4
|
+
const sdk = await getHeadlessSdkInstance(companyToken, isMockedMode);
|
|
5
|
+
const cta = await sdk.getCta();
|
|
6
|
+
|
|
7
|
+
return cta;
|
|
8
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { IHeadlessWayflyerCtaSdk } from '@wf-financing/embedded-types';
|
|
2
|
+
|
|
3
|
+
let cachedSdkInstance: IHeadlessWayflyerCtaSdk | null = null;
|
|
4
|
+
|
|
5
|
+
export const getHeadlessSdkInstance = async (companyToken: string, isMockedMode: boolean) => {
|
|
6
|
+
if (cachedSdkInstance) return cachedSdkInstance;
|
|
7
|
+
|
|
8
|
+
const module = await import('https://unpkg.com/@wf-financing/headless/dist/index.es.js');
|
|
9
|
+
const sdk: IHeadlessWayflyerCtaSdk = new module.HeadlessSdk(companyToken, isMockedMode);
|
|
10
|
+
|
|
11
|
+
cachedSdkInstance = sdk;
|
|
12
|
+
return sdk;
|
|
13
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { StartHostedApplicationRequestType } from '@wf-financing/embedded-types';
|
|
2
|
+
|
|
3
|
+
import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
|
|
4
|
+
|
|
5
|
+
export const startHostedApplication = async (companyToken: string, isMockedMode: boolean, partnerData: StartHostedApplicationRequestType) => {
|
|
6
|
+
const sdk = await getHeadlessSdkInstance(companyToken, isMockedMode);
|
|
7
|
+
const nextUrl = await sdk.startHostedApplication(partnerData);
|
|
8
|
+
|
|
9
|
+
return nextUrl;
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Flex, Text } from '@wayflyer/flyui';
|
|
2
|
+
import { IconCheck16Line } from '@wayflyer/flyui-icons/16/line';
|
|
3
|
+
|
|
4
|
+
export const BulletList = ({ items }: { items: string[] }) => {
|
|
5
|
+
return (
|
|
6
|
+
<Flex gap="4">
|
|
7
|
+
{items.map((bullet_point: string) => (
|
|
8
|
+
<Flex key={bullet_point} gap="1" align="center">
|
|
9
|
+
<IconCheck16Line />
|
|
10
|
+
<Text size="sm" fontWeight="semiBold" lineHeight="tight" textWrap="nowrap">
|
|
11
|
+
{bullet_point}
|
|
12
|
+
</Text>
|
|
13
|
+
</Flex>
|
|
14
|
+
))}
|
|
15
|
+
</Flex>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Flex,
|
|
3
|
+
useDetectDeviceSize,
|
|
4
|
+
useTheme,
|
|
5
|
+
} from '@wayflyer/flyui';
|
|
6
|
+
import { styled } from 'styled-components';
|
|
7
|
+
|
|
8
|
+
import { CtaBannerActions } from './CtaBannerActions';
|
|
9
|
+
import { CtaBannerContent } from './CtaBannerContent';
|
|
10
|
+
import { ProceedFundingButton } from './ProceedFundingButton';
|
|
11
|
+
|
|
12
|
+
export const CtaBanner = () => {
|
|
13
|
+
const { isMobile, isTablet } = useDetectDeviceSize();
|
|
14
|
+
const theme = useTheme();
|
|
15
|
+
|
|
16
|
+
const BannerContainer = styled.aside`
|
|
17
|
+
padding: ${theme.spacing(isMobile ? ['3', '3', '3', '4'] : ['4', '4', '4', '6'])};
|
|
18
|
+
box-shadow: ${theme.effects.shadow};
|
|
19
|
+
border-radius: ${theme.borderRadius(['md'])};
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: ${isMobile ? 'column' : 'row'};
|
|
22
|
+
gap: ${isMobile ? theme.spacing(['4']) : '0'};
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<BannerContainer>
|
|
27
|
+
<Flex gap="4" align="center" justify="space-between" width="100%">
|
|
28
|
+
<CtaBannerContent isMobile={isMobile} isTablet={isTablet} />
|
|
29
|
+
<CtaBannerActions isMobile={isMobile} />
|
|
30
|
+
</Flex>
|
|
31
|
+
{isMobile && <ProceedFundingButton />}
|
|
32
|
+
</BannerContainer>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Button, Flex } from '@wayflyer/flyui';
|
|
2
|
+
import { IconX16Line } from '@wayflyer/flyui-icons/16/line';
|
|
3
|
+
|
|
4
|
+
import { ProceedFundingButton } from './ProceedFundingButton';
|
|
5
|
+
|
|
6
|
+
type CtaBannerActionsType = {
|
|
7
|
+
isMobile: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const CtaBannerActions = ({ isMobile }: CtaBannerActionsType) => (
|
|
11
|
+
<Flex gap="4">
|
|
12
|
+
{!isMobile && (
|
|
13
|
+
<ProceedFundingButton />
|
|
14
|
+
)}
|
|
15
|
+
<Button variant="Tertiary">
|
|
16
|
+
<IconX16Line />
|
|
17
|
+
</Button>
|
|
18
|
+
</Flex>
|
|
19
|
+
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Flex, TextHeading } from '@wayflyer/flyui';
|
|
2
|
+
import type { CtaResponseType } from '@wf-financing/embedded-types';
|
|
3
|
+
|
|
4
|
+
import { useCtaBanner } from '../../hooks/useCtaBanner';
|
|
5
|
+
import { BulletList } from './BulletList';
|
|
6
|
+
|
|
7
|
+
type CtaBannerContent = {
|
|
8
|
+
isMobile: boolean;
|
|
9
|
+
isTablet: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const CtaBannerContent = ({ isMobile, isTablet }: CtaBannerContent) => {
|
|
13
|
+
const sdk = useCtaBanner();
|
|
14
|
+
const ctaData = sdk.data as CtaResponseType;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Flex direction="column" gap="1">
|
|
18
|
+
<TextHeading element="p" size={isMobile ? 'lg' : 'xl'} lineHeight="tight">
|
|
19
|
+
{ctaData?.data?.config?.text}
|
|
20
|
+
</TextHeading>
|
|
21
|
+
{!(isTablet || isMobile) && ctaData?.data?.config?.bullet_points && (
|
|
22
|
+
<BulletList items={ctaData?.data?.config?.bullet_points} />
|
|
23
|
+
)}
|
|
24
|
+
</Flex>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Button } from '@wayflyer/flyui';
|
|
2
|
+
import { CtaContinueFundingType, CtaGenericOfferType, CtaIndicativeOfferType, CtaStateType } from '@wf-financing/embedded-types';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { useCtaBanner } from '../../hooks/useCtaBanner';
|
|
6
|
+
import { ConsentModal } from '../modal/ConsentModal';
|
|
7
|
+
|
|
8
|
+
type CtaResponseType = CtaGenericOfferType | CtaIndicativeOfferType | CtaContinueFundingType;
|
|
9
|
+
|
|
10
|
+
export const ProceedFundingButton = () => {
|
|
11
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
12
|
+
const sdkResponse = useCtaBanner();
|
|
13
|
+
const sdk = sdkResponse.data as CtaResponseType
|
|
14
|
+
const { state, data: { config } } = sdk;
|
|
15
|
+
|
|
16
|
+
const handleButtonClick = () => {
|
|
17
|
+
switch (state) {
|
|
18
|
+
case CtaStateType.CONTINUE_APPLICATION:
|
|
19
|
+
window.open(config?.redirect_url, '_blank');
|
|
20
|
+
break;
|
|
21
|
+
default:
|
|
22
|
+
setIsModalOpen(true);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<Button variant="Primary" onClick={handleButtonClick}>
|
|
30
|
+
{config.button_label}
|
|
31
|
+
</Button>
|
|
32
|
+
<ConsentModal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen} />
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Flex, Image, Text } from '@wayflyer/flyui';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
|
|
4
|
+
import { FundingSteps } from './FundingSteps';
|
|
5
|
+
import { Modal } from './Modal';
|
|
6
|
+
import { ModalFooter } from './ModalFooter';
|
|
7
|
+
|
|
8
|
+
type ConsentModalProps = {
|
|
9
|
+
isModalOpen: boolean;
|
|
10
|
+
setIsModalOpen: (isOpen: boolean) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const ConsentModal = ({ isModalOpen, setIsModalOpen }: ConsentModalProps) => {
|
|
14
|
+
const { formatMessage } = useIntl();
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Modal isModalOpen={isModalOpen} setIsModalOpen={setIsModalOpen}>
|
|
18
|
+
<Image src="https://static.wayflyer.com/flyui-assets/logos/wayflyer-ef.png" />
|
|
19
|
+
<Flex margin={['6', '8', '6', '8']} direction="column" gap="10">
|
|
20
|
+
<Flex direction="column" gap="6">
|
|
21
|
+
<Text fontWeight="semiBold" size="2xl">
|
|
22
|
+
{formatMessage({ id: 'ctaModal.title' })}
|
|
23
|
+
</Text>
|
|
24
|
+
<FundingSteps />
|
|
25
|
+
</Flex>
|
|
26
|
+
<ModalFooter setOpen={setIsModalOpen} />
|
|
27
|
+
</Flex>
|
|
28
|
+
</Modal>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Badge, Flex, Text, useTheme } from '@wayflyer/flyui';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
|
|
4
|
+
import { MODAL_ITEMS } from '../../constants';
|
|
5
|
+
|
|
6
|
+
export const FundingSteps = () => {
|
|
7
|
+
const { formatMessage } = useIntl();
|
|
8
|
+
const theme = useTheme();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<Flex direction="column" gap="4">
|
|
12
|
+
{MODAL_ITEMS.map(({ id, title, subtitle, Icon, time }) => (
|
|
13
|
+
<Flex key={id} direction="column" gap="1">
|
|
14
|
+
<Flex gap="2" direction="row" align="center">
|
|
15
|
+
<Icon />
|
|
16
|
+
<Text fontWeight="semiBold">
|
|
17
|
+
{formatMessage(title)}
|
|
18
|
+
</Text>
|
|
19
|
+
<Badge>{formatMessage(time)}</Badge>
|
|
20
|
+
</Flex>
|
|
21
|
+
<div style={{ paddingLeft: theme.spacing('8') }}>
|
|
22
|
+
<Text color="placeholder">{formatMessage(subtitle)}</Text>
|
|
23
|
+
</div>
|
|
24
|
+
</Flex>
|
|
25
|
+
))}
|
|
26
|
+
</Flex>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Dialog
|
|
3
|
+
} from '@wayflyer/flyui';
|
|
4
|
+
import { AnimatePresence, LazyMotion, domAnimation } from 'framer-motion';
|
|
5
|
+
import { ReactNode, useId, useRef } from 'react';
|
|
6
|
+
import { UNSAFE_PortalProvider as PortalProvider } from 'react-aria';
|
|
7
|
+
import { DialogBody } from './modalEnhancements';
|
|
8
|
+
|
|
9
|
+
import { MotionModalOverlay, MotionModalPrimitive, fullSize, opaque, small, transparent } from './modalEnhancements';
|
|
10
|
+
|
|
11
|
+
type ModalProps = {
|
|
12
|
+
isModalOpen: boolean;
|
|
13
|
+
setIsModalOpen: (isOpen: boolean) => void;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const Modal = ({ isModalOpen, setIsModalOpen, children }: ModalProps) => {
|
|
18
|
+
const modalId = useId();
|
|
19
|
+
const scrollableElRef = useRef(null);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<LazyMotion features={domAnimation}>
|
|
23
|
+
<AnimatePresence>
|
|
24
|
+
{isModalOpen && (
|
|
25
|
+
<PortalProvider getContainer={() => document.getElementById('app-container') ?? document.body}>
|
|
26
|
+
<MotionModalPrimitive
|
|
27
|
+
isDismissable
|
|
28
|
+
shouldCloseOnInteractOutside={(e) => e.getAttribute('data-id') === 'modal-mask'}
|
|
29
|
+
isOpen={isModalOpen}
|
|
30
|
+
onOpenChange={() => setIsModalOpen(false)}
|
|
31
|
+
$size="normal"
|
|
32
|
+
initial={small}
|
|
33
|
+
animate={fullSize}
|
|
34
|
+
exit={small}
|
|
35
|
+
transition={{ duration: 0.3 }}
|
|
36
|
+
style={{ width: '438px', border: 'none' }}
|
|
37
|
+
>
|
|
38
|
+
<MotionModalOverlay
|
|
39
|
+
data-id="modal-mask"
|
|
40
|
+
data-testid="modal-mask"
|
|
41
|
+
initial={transparent}
|
|
42
|
+
animate={opaque}
|
|
43
|
+
exit={transparent}
|
|
44
|
+
transition={{ duration: 0.6 }}
|
|
45
|
+
/>
|
|
46
|
+
<Dialog
|
|
47
|
+
role="dialog"
|
|
48
|
+
aria-labelledby={modalId}
|
|
49
|
+
>
|
|
50
|
+
<DialogBody ref={scrollableElRef} data-testid="modal-body">
|
|
51
|
+
{children}
|
|
52
|
+
</DialogBody>
|
|
53
|
+
</Dialog>
|
|
54
|
+
</MotionModalPrimitive>
|
|
55
|
+
</PortalProvider>
|
|
56
|
+
)}
|
|
57
|
+
</AnimatePresence>
|
|
58
|
+
</LazyMotion>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Button, Flex, Text } from '@wayflyer/flyui';
|
|
2
|
+
import { StartHostedApplicationResponseType } from '@wf-financing/embedded-types';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
|
|
5
|
+
import { useStartHostedApplication } from '../../hooks/useStartHostedApplication';
|
|
6
|
+
|
|
7
|
+
type ModalFooterType = {
|
|
8
|
+
setOpen: (isOpen: boolean) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const ModalFooter = ({ setOpen }: ModalFooterType) => {
|
|
12
|
+
const { formatMessage } = useIntl();
|
|
13
|
+
const startHostedAppMutation = useStartHostedApplication();
|
|
14
|
+
|
|
15
|
+
const handleStartApplication = () => {
|
|
16
|
+
const partnerData = {};
|
|
17
|
+
|
|
18
|
+
startHostedAppMutation.mutate({
|
|
19
|
+
partnerData,
|
|
20
|
+
}, {
|
|
21
|
+
onSuccess: (nextUrl: StartHostedApplicationResponseType) => {
|
|
22
|
+
const { next } = nextUrl;
|
|
23
|
+
|
|
24
|
+
window.open(next);
|
|
25
|
+
},
|
|
26
|
+
onError: (error) => {
|
|
27
|
+
console.error('Failed to start application', error);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Flex direction="column" gap="3">
|
|
34
|
+
<Flex gap="2">
|
|
35
|
+
<Button onClick={handleStartApplication} variant="Primary">
|
|
36
|
+
{formatMessage({ id: 'ctaModal.action' })}
|
|
37
|
+
</Button>
|
|
38
|
+
<Button variant="TertiaryAccent" onClick={() => setOpen(false)}>
|
|
39
|
+
{formatMessage({ id: 'common.cancel' })}
|
|
40
|
+
</Button>
|
|
41
|
+
</Flex>
|
|
42
|
+
<Text size="sm" lineHeight="normal">
|
|
43
|
+
{formatMessage({ id: 'ctaModal.consent' })}
|
|
44
|
+
</Text>
|
|
45
|
+
</Flex>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ModalOverlay as OriginalModalOverlay,
|
|
3
|
+
ModalPrimitive as OriginalModalPrimitive
|
|
4
|
+
} from '@wayflyer/flyui';
|
|
5
|
+
import { m } from 'framer-motion';
|
|
6
|
+
import styled from 'styled-components';
|
|
7
|
+
|
|
8
|
+
export const MotionModalOverlay = m(OriginalModalOverlay);
|
|
9
|
+
export const MotionModalPrimitive = m(OriginalModalPrimitive);
|
|
10
|
+
|
|
11
|
+
export const small = { transform: 'scale(0.7)', opacity: 0 };
|
|
12
|
+
export const fullSize = { transform: 'scale(1)', opacity: 1 };
|
|
13
|
+
export const transparent = { backgroundColor: 'rgba(0, 0, 0, 0)' };
|
|
14
|
+
export const opaque = {
|
|
15
|
+
backgroundColor: 'var(--palette-utility-overlay)',
|
|
16
|
+
backdropFilter: 'blur(var(--effects-blur))',
|
|
17
|
+
opacity: 0.6,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const DialogBody = styled.div`
|
|
21
|
+
scrollbar-width: thin;
|
|
22
|
+
scrollbar-color: var(--palette-modal-border);
|
|
23
|
+
flex-shrink: 1;
|
|
24
|
+
overflow-y: auto;
|
|
25
|
+
background-color: var(--palette-modal-surface);
|
|
26
|
+
border-radius: var(--sizes-radius-md);
|
|
27
|
+
|
|
28
|
+
&::-webkit-scrollbar {
|
|
29
|
+
width: 12px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&::-webkit-scrollbar-track {
|
|
33
|
+
background-color: transparent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&::-webkit-scrollbar-thumb {
|
|
37
|
+
background-color: var(--palette-modal-border);
|
|
38
|
+
border-radius: var(--sizes-radius-xs);
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MODAL_ITEMS } from './modalItems';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IconArrowsCwHorizontal24Line,
|
|
3
|
+
IconCheckCircle24Line,
|
|
4
|
+
IconPersonCircle24Line,
|
|
5
|
+
} from '@wayflyer/flyui-icons/24/line';
|
|
6
|
+
|
|
7
|
+
export const MODAL_ITEMS = [
|
|
8
|
+
{
|
|
9
|
+
id: 'step1',
|
|
10
|
+
title: { id: 'ctaModal.step1.title' },
|
|
11
|
+
subtitle: { id: 'ctaModal.step1.subtitle' },
|
|
12
|
+
Icon: IconPersonCircle24Line,
|
|
13
|
+
time: { id: 'ctaModal.step1.time' },
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'step2',
|
|
17
|
+
title: { id: 'ctaModal.step2.title' },
|
|
18
|
+
subtitle: { id: 'ctaModal.step2.subtitle' },
|
|
19
|
+
Icon: IconArrowsCwHorizontal24Line,
|
|
20
|
+
time: { id: 'ctaModal.step2.time' },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'step3',
|
|
24
|
+
title: { id: 'ctaModal.step3.title' },
|
|
25
|
+
subtitle: { id: 'ctaModal.step3.subtitle' },
|
|
26
|
+
Icon: IconCheckCircle24Line,
|
|
27
|
+
time: { id: 'ctaModal.step3.time' },
|
|
28
|
+
},
|
|
29
|
+
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
import { fetchCtaBanner } from '../api/ctaBanner';
|
|
5
|
+
import { PartnerContext } from '../utils/partnerContext';
|
|
6
|
+
|
|
7
|
+
type PartnerDataType = {
|
|
8
|
+
companyToken: string;
|
|
9
|
+
isMockedMode: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useCtaBanner = () => {
|
|
13
|
+
const { companyToken, isMockedMode } = useContext(PartnerContext) as PartnerDataType;
|
|
14
|
+
|
|
15
|
+
return useQuery({
|
|
16
|
+
queryKey: ['cta', companyToken, isMockedMode],
|
|
17
|
+
queryFn: () => fetchCtaBanner(companyToken, isMockedMode),
|
|
18
|
+
staleTime: Infinity,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { StartHostedApplicationRequestType } from '@wf-financing/embedded-types';
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
|
|
4
|
+
import { PartnerContext } from "../utils/partnerContext";
|
|
5
|
+
|
|
6
|
+
type PartnerDataType = {
|
|
7
|
+
companyToken: string;
|
|
8
|
+
isMockedMode: boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
import { useMutation } from '@tanstack/react-query';
|
|
12
|
+
import { startHostedApplication } from '../api/startHostedApplication';
|
|
13
|
+
|
|
14
|
+
export const useStartHostedApplication = () => {
|
|
15
|
+
const { companyToken, isMockedMode } = useContext(PartnerContext) as PartnerDataType;
|
|
16
|
+
|
|
17
|
+
return useMutation({
|
|
18
|
+
mutationFn: ({
|
|
19
|
+
partnerData,
|
|
20
|
+
}: {
|
|
21
|
+
partnerData: StartHostedApplicationRequestType;
|
|
22
|
+
}) => startHostedApplication(companyToken, isMockedMode, partnerData),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"common.cancel": "Cancel",
|
|
3
|
+
"ctaModal.call": "Get funded",
|
|
4
|
+
"ctaModal.title": "We work with Wayflyer to provide you capital",
|
|
5
|
+
"ctaModal.action": "Continue with Wayflyer",
|
|
6
|
+
"ctaModal.consent": "By proceeding, you consent to sharing your BigCommerce information with Wayflyer.",
|
|
7
|
+
"ctaModal.step1.title": "Create your account",
|
|
8
|
+
"ctaModal.step1.subtitle": "Tell us about your business and funding needs",
|
|
9
|
+
"ctaModal.step1.time": "2 mins",
|
|
10
|
+
"ctaModal.step2.title": "Connect your platforms and apply",
|
|
11
|
+
"ctaModal.step2.subtitle": "We’ll analyze your business performance",
|
|
12
|
+
"ctaModal.step2.time": "10 mins",
|
|
13
|
+
"ctaModal.step3.title": "Accept and receive funds",
|
|
14
|
+
"ctaModal.step3.subtitle": "Once approved, funds can be deployed in 24h",
|
|
15
|
+
"ctaModal.step3.time": "24 hrs"
|
|
16
|
+
}
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import ReactDOM from 'react-dom/client';
|
|
2
|
+
import { App } from './App';
|
|
3
|
+
|
|
4
|
+
const roots = new Map<HTMLElement, ReactDOM.Root>();
|
|
5
|
+
|
|
6
|
+
export const mountToTarget = (
|
|
7
|
+
targetId: string,
|
|
8
|
+
partnerDesignId: 'wayflyer' | 'defaultTheme' | 'staff' | 'whiteLabel' | 'bigCommerce',
|
|
9
|
+
companyToken: string,
|
|
10
|
+
isMockedMode?: boolean,
|
|
11
|
+
) => {
|
|
12
|
+
const targetElement = document.getElementById(targetId);
|
|
13
|
+
|
|
14
|
+
if (!targetElement) {
|
|
15
|
+
console.warn(`Target element with id "${targetId}" not found.`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let root = roots.get(targetElement);
|
|
20
|
+
|
|
21
|
+
if (!root) {
|
|
22
|
+
root = ReactDOM.createRoot(targetElement);
|
|
23
|
+
roots.set(targetElement, root);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
root.render(
|
|
27
|
+
<App
|
|
28
|
+
partnerDesignId={partnerDesignId}
|
|
29
|
+
companyToken={companyToken}
|
|
30
|
+
isMockedMode={isMockedMode}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|