@wf-financing/ui 0.1.1 → 1.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/README.md +3 -0
- package/dist/index.es.js +72962 -68930
- package/global.d.ts +8 -0
- package/index.ts +5 -18
- package/package.json +24 -7
- package/sdk/index.ts +36 -0
- package/src/App.tsx +7 -6
- package/src/CtaWidget.tsx +13 -8
- package/src/api/ctaBanner.ts +5 -3
- package/src/api/fetchCtaBanner.test.ts +36 -0
- package/src/api/getHeadlessSdkInstance.test.ts +54 -0
- package/src/api/getHeadlessSdkInstance.ts +32 -7
- package/src/api/startHostedApplication.test.ts +53 -0
- package/src/api/startHostedApplication.ts +4 -4
- package/src/components/banner/BannerActionsDesktop.tsx +13 -0
- package/src/components/banner/BannerContent.stories.tsx +31 -0
- package/src/components/banner/BulletList.stories.tsx +15 -0
- package/src/components/banner/BulletList.tsx +1 -1
- package/src/components/banner/CloseButton.tsx +16 -0
- package/src/components/banner/CtaBanner.stories.tsx +51 -0
- package/src/components/banner/CtaBanner.tsx +24 -22
- package/src/components/banner/CtaBannerContent.tsx +14 -10
- package/src/components/banner/FooterActions.tsx +8 -0
- package/src/components/banner/HeaderActions.tsx +9 -0
- package/src/components/banner/ProceedFundingButton.tsx +17 -6
- package/src/components/modal/ConsentModal.stories.tsx +42 -0
- package/src/components/modal/ConsentModal.tsx +2 -2
- package/src/components/modal/FundingSteps.tsx +2 -4
- package/src/components/modal/Modal.tsx +4 -9
- package/src/components/modal/ModalFooter.tsx +21 -13
- package/src/components/modal/modalEnhancements.ts +3 -4
- package/src/config/index.ts +2 -0
- package/src/config/scriptId.ts +1 -0
- package/src/config/url.ts +1 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useCtaBanner.ts +5 -11
- package/src/hooks/usePartnerContext.ts +7 -0
- package/src/hooks/useStartHostedApplication.ts +4 -13
- package/src/locales/en.json +3 -1
- package/src/main.tsx +12 -14
- package/src/utils/index.ts +3 -0
- package/src/utils/initializeHeadlessSdk.ts +21 -0
- package/src/utils/loadScriptAndInitializeSdk.ts +19 -0
- package/src/utils/partnerContext.ts +10 -6
- package/tsconfig.json +2 -2
- package/vite.config.ts +2 -1
- package/vitest.shims.d.ts +1 -0
- package/src/components/banner/CtaBannerActions.tsx +0 -19
package/global.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { IWayflyerUiCtaSdkConstructor, IHeadlessWayflyerCtaSdkConstructor } from '@wf-financing/embedded-types';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
WayflyerUiCtaSdk: IWayflyerUiCtaSdkConstructor;
|
|
6
|
+
WayflyerHeadlessSdk: IHeadlessWayflyerCtaSdkConstructor;
|
|
7
|
+
}
|
|
8
|
+
}
|
package/index.ts
CHANGED
|
@@ -1,20 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { PartnerCallbackType } from '@wf-financing/embedded-types';
|
|
1
|
+
import { WayflyerUiCtaSdk } from './sdk';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const mount = (
|
|
7
|
-
targetId: string,
|
|
8
|
-
partnerDesignId: Themes,
|
|
9
|
-
partnerCallback: PartnerCallbackType,
|
|
10
|
-
companyToken: string,
|
|
11
|
-
isMockedMode?: boolean,
|
|
12
|
-
) => {
|
|
13
|
-
if (document.readyState === 'loading') {
|
|
14
|
-
document.addEventListener('DOMContentLoaded', () =>
|
|
15
|
-
mountToTarget(targetId, partnerDesignId, partnerCallback, companyToken, isMockedMode),
|
|
16
|
-
);
|
|
17
|
-
} else {
|
|
18
|
-
mountToTarget(targetId, partnerDesignId, partnerCallback, companyToken, isMockedMode);
|
|
19
|
-
}
|
|
3
|
+
const addHeadlessSdkToWindow = () => {
|
|
4
|
+
window.WayflyerUiCtaSdk = WayflyerUiCtaSdk;
|
|
20
5
|
};
|
|
6
|
+
|
|
7
|
+
addHeadlessSdkToWindow();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wf-financing/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": {
|
|
6
6
|
"import": "./dist/index.es.js",
|
|
@@ -11,19 +11,34 @@
|
|
|
11
11
|
"module": "dist/index.es.js",
|
|
12
12
|
"types": "dist/index.d.ts",
|
|
13
13
|
"devDependencies": {
|
|
14
|
+
"@chromatic-com/storybook": "^4.0.1",
|
|
15
|
+
"@rollup/plugin-inject": "^5.0.5",
|
|
16
|
+
"@storybook/addon-a11y": "^9.0.15",
|
|
17
|
+
"@storybook/addon-vitest": "^9.0.15",
|
|
18
|
+
"@storybook/builder-vite": "^9.0.15",
|
|
19
|
+
"@storybook/jest": "^0.2.3",
|
|
20
|
+
"@storybook/react": "^9.0.15",
|
|
21
|
+
"@storybook/react-vite": "^9.0.15",
|
|
22
|
+
"@storybook/testing-library": "^0.2.2",
|
|
23
|
+
"@vitest/browser": "^3.2.4",
|
|
24
|
+
"chromatic": "^13.1.2",
|
|
25
|
+
"eslint-plugin-storybook": "^9.0.15",
|
|
26
|
+
"playwright": "^1.53.2",
|
|
27
|
+
"storybook": "^9.0.15",
|
|
14
28
|
"typescript": "^5.0.0",
|
|
15
|
-
"vite": "^
|
|
16
|
-
"vite-plugin-dts": "^3.4.0"
|
|
29
|
+
"vite": "^6.3.5",
|
|
30
|
+
"vite-plugin-dts": "^3.4.0",
|
|
31
|
+
"vitest": "^3.2.4"
|
|
17
32
|
},
|
|
18
33
|
"dependencies": {
|
|
19
|
-
"@wayflyer/flyui": "188.3.0",
|
|
20
|
-
"@wayflyer/flyui-icons": "1.3.2",
|
|
21
34
|
"@tanstack/react-query": "5.81.5",
|
|
35
|
+
"@wayflyer/flyui": "196.0.0",
|
|
36
|
+
"@wayflyer/flyui-icons": "1.3.2",
|
|
22
37
|
"framer-motion": "^12.23.0",
|
|
23
38
|
"react-aria": "^3.41.1",
|
|
24
39
|
"react-intl": "^6.2.5",
|
|
25
40
|
"styled-components": "^6.1.19",
|
|
26
|
-
"@wf-financing/embedded-types": "
|
|
41
|
+
"@wf-financing/embedded-types": "0.2.4"
|
|
27
42
|
},
|
|
28
43
|
"publishConfig": {
|
|
29
44
|
"access": "public"
|
|
@@ -33,6 +48,8 @@
|
|
|
33
48
|
"dev": "vite",
|
|
34
49
|
"build": "vite build",
|
|
35
50
|
"preview": "vite preview",
|
|
36
|
-
"publish-sdk-cta-ui": "pnpm clean && pnpm build && pnpm publish --access public --no-git-checks"
|
|
51
|
+
"publish-sdk-cta-ui": "pnpm clean && pnpm build && pnpm publish --access public --no-git-checks",
|
|
52
|
+
"test": "vitest",
|
|
53
|
+
"test:coverage": "vitest --coverage"
|
|
37
54
|
}
|
|
38
55
|
}
|
package/sdk/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Themes } from '@wayflyer/flyui';
|
|
2
|
+
import { PartnerCallbackType, MockedModeType } from '@wf-financing/embedded-types';
|
|
3
|
+
|
|
4
|
+
import { mountToTarget } from '../src/main';
|
|
5
|
+
|
|
6
|
+
export class WayflyerUiCtaSdk {
|
|
7
|
+
private targetId: string;
|
|
8
|
+
private partnerDesignId: Themes;
|
|
9
|
+
private partnerCallback: PartnerCallbackType;
|
|
10
|
+
private companyToken: string;
|
|
11
|
+
private mockedMode?: MockedModeType;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
targetId: string,
|
|
15
|
+
partnerDesignId: Themes,
|
|
16
|
+
partnerCallback: PartnerCallbackType,
|
|
17
|
+
companyToken: string,
|
|
18
|
+
mockedMode?: MockedModeType,
|
|
19
|
+
) {
|
|
20
|
+
this.targetId = targetId;
|
|
21
|
+
this.partnerDesignId = partnerDesignId;
|
|
22
|
+
this.partnerCallback = partnerCallback;
|
|
23
|
+
this.companyToken = companyToken;
|
|
24
|
+
this.mockedMode = mockedMode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
mountCta() {
|
|
28
|
+
if (document.readyState === 'loading') {
|
|
29
|
+
document.addEventListener('DOMContentLoaded', () =>
|
|
30
|
+
mountToTarget(this.targetId, this.partnerDesignId, this.partnerCallback, this.companyToken, this.mockedMode),
|
|
31
|
+
);
|
|
32
|
+
} else {
|
|
33
|
+
mountToTarget(this.targetId, this.partnerDesignId, this.partnerCallback, this.companyToken, this.mockedMode);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/App.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
2
2
|
import { FlyUIProvider, Themes } from '@wayflyer/flyui';
|
|
3
|
-
import { PartnerCallbackType } from '@wf-financing/embedded-types';
|
|
3
|
+
import { PartnerCallbackType, MockedModeType } from '@wf-financing/embedded-types';
|
|
4
4
|
import { createIntl, createIntlCache, IntlShape } from 'react-intl';
|
|
5
5
|
|
|
6
6
|
import { CtaWidget } from './CtaWidget';
|
|
@@ -10,8 +10,9 @@ import { PartnerContext } from './utils/partnerContext';
|
|
|
10
10
|
type AppPropsType = {
|
|
11
11
|
partnerDesignId: Themes;
|
|
12
12
|
companyToken: string;
|
|
13
|
-
|
|
14
|
-
partnerCallback: PartnerCallbackType
|
|
13
|
+
mockedMode?: MockedModeType;
|
|
14
|
+
partnerCallback: PartnerCallbackType;
|
|
15
|
+
onWidgetClose: () => void;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const queryClient = new QueryClient();
|
|
@@ -19,7 +20,7 @@ const messages = {
|
|
|
19
20
|
en: enMessages,
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
export const App = ({ partnerDesignId, companyToken,
|
|
23
|
+
export const App = ({ partnerDesignId, companyToken, mockedMode, partnerCallback, onWidgetClose }: AppPropsType) => {
|
|
23
24
|
const locale = 'en';
|
|
24
25
|
|
|
25
26
|
const intl: IntlShape = createIntl(
|
|
@@ -27,12 +28,12 @@ export const App = ({ partnerDesignId, companyToken, isMockedMode, partnerCallba
|
|
|
27
28
|
locale,
|
|
28
29
|
messages: messages[locale],
|
|
29
30
|
},
|
|
30
|
-
createIntlCache()
|
|
31
|
+
createIntlCache(),
|
|
31
32
|
);
|
|
32
33
|
|
|
33
34
|
return (
|
|
34
35
|
<QueryClientProvider client={queryClient}>
|
|
35
|
-
<PartnerContext.Provider value={{ companyToken,
|
|
36
|
+
<PartnerContext.Provider value={{ companyToken, mockedMode, partnerCallback, onWidgetClose }}>
|
|
36
37
|
<FlyUIProvider theme={partnerDesignId} intl={intl}>
|
|
37
38
|
<CtaWidget />
|
|
38
39
|
</FlyUIProvider>
|
package/src/CtaWidget.tsx
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
1
3
|
import { CtaBanner } from './components/banner/CtaBanner';
|
|
2
|
-
import { useCtaBanner } from './hooks
|
|
4
|
+
import { useCtaBanner, usePartnerContext } from './hooks';
|
|
3
5
|
|
|
4
6
|
export const CtaWidget = () => {
|
|
5
7
|
const banner = useCtaBanner();
|
|
8
|
+
const partnerContext = usePartnerContext();
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!banner.isLoading && !banner.data) {
|
|
12
|
+
requestAnimationFrame(() => {
|
|
13
|
+
partnerContext.onWidgetClose();
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}, [banner.data]);
|
|
6
17
|
|
|
7
|
-
return
|
|
8
|
-
<>
|
|
9
|
-
{!banner.isLoading && (
|
|
10
|
-
<CtaBanner />
|
|
11
|
-
)}
|
|
12
|
-
</>
|
|
13
|
-
);
|
|
18
|
+
return <>{!banner.isLoading && banner.data && <CtaBanner />}</>;
|
|
14
19
|
};
|
package/src/api/ctaBanner.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { MockedModeType } from '@wf-financing/embedded-types';
|
|
2
|
+
|
|
1
3
|
import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
|
|
2
4
|
|
|
3
|
-
export const fetchCtaBanner = async (companyToken: string,
|
|
4
|
-
const sdk = await getHeadlessSdkInstance(companyToken,
|
|
5
|
+
export const fetchCtaBanner = async (companyToken: string, mockedMode?: MockedModeType) => {
|
|
6
|
+
const sdk = await getHeadlessSdkInstance(companyToken, mockedMode);
|
|
5
7
|
const cta = await sdk.getCta();
|
|
6
8
|
|
|
7
9
|
return cta;
|
|
8
|
-
}
|
|
10
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SdkScenarios, MockedModeType } from '@wf-financing/embedded-types';
|
|
2
|
+
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { fetchCtaBanner } from './ctaBanner';
|
|
5
|
+
import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
|
|
6
|
+
|
|
7
|
+
vi.mock('./getHeadlessSdkInstance', () => ({
|
|
8
|
+
getHeadlessSdkInstance: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const fakeSdk = {
|
|
12
|
+
getCta: vi.fn(),
|
|
13
|
+
startHostedApplication: vi.fn(),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('fetchCtaBanner', () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('calls sdk.getCta and returns data', async () => {
|
|
22
|
+
const mockCta = { banner: 'hello' };
|
|
23
|
+
(getHeadlessSdkInstance as any).mockResolvedValueOnce(fakeSdk);
|
|
24
|
+
fakeSdk.getCta.mockResolvedValueOnce(mockCta);
|
|
25
|
+
const mockedMode: MockedModeType = {
|
|
26
|
+
isMockedMode: true,
|
|
27
|
+
sdkScenario: SdkScenarios.NEW_APPLICATION,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const result = await fetchCtaBanner('TOKEN', mockedMode);
|
|
31
|
+
|
|
32
|
+
expect(getHeadlessSdkInstance).toHaveBeenCalledWith('TOKEN', mockedMode);
|
|
33
|
+
expect(fakeSdk.getCta).toHaveBeenCalled();
|
|
34
|
+
expect(result).toBe(mockCta);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MockedModeType, SdkScenarios } from '@wf-financing/embedded-types';
|
|
2
|
+
import { afterEach, describe, expect, Mock, test, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
vi.mock('./getHeadlessSdkInstance', () => ({
|
|
5
|
+
getHeadlessSdkInstance: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
|
|
9
|
+
import { startHostedApplication } from './startHostedApplication';
|
|
10
|
+
|
|
11
|
+
const fakeSdk = {
|
|
12
|
+
getCta: vi.fn(),
|
|
13
|
+
startHostedApplication: vi.fn(),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const mockedMode: MockedModeType = {
|
|
17
|
+
isMockedMode: true,
|
|
18
|
+
sdkScenario: SdkScenarios.NEW_APPLICATION,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('startHostedApplication', () => {
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('calls sdk.startHostedApplication with partnerData and returns nextUrl', async () => {
|
|
27
|
+
const fakePartnerData = { foo: 1 };
|
|
28
|
+
const fakeNextUrl = '/next-url';
|
|
29
|
+
const partnerCallback = vi.fn().mockResolvedValue(fakePartnerData);
|
|
30
|
+
|
|
31
|
+
(getHeadlessSdkInstance as unknown as Mock).mockResolvedValueOnce(fakeSdk);
|
|
32
|
+
fakeSdk.startHostedApplication.mockResolvedValueOnce(fakeNextUrl);
|
|
33
|
+
|
|
34
|
+
const result = await startHostedApplication('TOKEN', partnerCallback, mockedMode);
|
|
35
|
+
|
|
36
|
+
expect(getHeadlessSdkInstance).toHaveBeenCalledWith('TOKEN', mockedMode);
|
|
37
|
+
expect(partnerCallback).toHaveBeenCalled();
|
|
38
|
+
expect(fakeSdk.startHostedApplication).toHaveBeenCalledWith(fakePartnerData);
|
|
39
|
+
expect(result).toBe(fakeNextUrl);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('calls sdk.startHostedApplication with empty object if partnerCallback returns falsy', async () => {
|
|
43
|
+
const partnerCallback = vi.fn().mockResolvedValue(undefined);
|
|
44
|
+
const fakeNextUrl = '/next-url';
|
|
45
|
+
|
|
46
|
+
(getHeadlessSdkInstance as unknown as Mock).mockResolvedValueOnce(fakeSdk);
|
|
47
|
+
fakeSdk.startHostedApplication.mockResolvedValueOnce(fakeNextUrl);
|
|
48
|
+
|
|
49
|
+
const result = await startHostedApplication('TOKEN', partnerCallback, mockedMode);
|
|
50
|
+
|
|
51
|
+
expect(fakeSdk.startHostedApplication).toHaveBeenCalledWith({});
|
|
52
|
+
expect(result).toBe(fakeNextUrl);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,13 +1,38 @@
|
|
|
1
|
-
import { IHeadlessWayflyerCtaSdk } from '@wf-financing/embedded-types';
|
|
1
|
+
import { IHeadlessWayflyerCtaSdk, MockedModeType } from '@wf-financing/embedded-types';
|
|
2
|
+
|
|
3
|
+
import { HEADLESS_SDK_URL, WAYFLYER_HEADLESS_SDK_ID } from '../config';
|
|
4
|
+
import { initializeHeadlessSdk, loadScriptAndInitializeSdk } from '../utils';
|
|
2
5
|
|
|
3
6
|
let cachedSdkInstance: IHeadlessWayflyerCtaSdk | null = null;
|
|
4
7
|
|
|
5
|
-
export const getHeadlessSdkInstance = async (companyToken: string,
|
|
6
|
-
|
|
8
|
+
export const getHeadlessSdkInstance = async (companyToken: string, mockedMode?: MockedModeType) => {
|
|
9
|
+
try {
|
|
10
|
+
if (cachedSdkInstance) return cachedSdkInstance;
|
|
11
|
+
|
|
12
|
+
const existingScript = document.getElementById(WAYFLYER_HEADLESS_SDK_ID) as HTMLScriptElement;
|
|
13
|
+
|
|
14
|
+
if (window.WayflyerHeadlessSdk) {
|
|
15
|
+
return initializeHeadlessSdk(companyToken, mockedMode);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (existingScript) {
|
|
19
|
+
return loadScriptAndInitializeSdk(existingScript, companyToken, mockedMode);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const script = document.createElement('script');
|
|
23
|
+
script.src = HEADLESS_SDK_URL;
|
|
24
|
+
script.type = 'module';
|
|
25
|
+
script.id = WAYFLYER_HEADLESS_SDK_ID;
|
|
26
|
+
script.async = true;
|
|
27
|
+
|
|
28
|
+
document.head.appendChild(script);
|
|
7
29
|
|
|
8
|
-
|
|
9
|
-
|
|
30
|
+
const headlessSdk: IHeadlessWayflyerCtaSdk = await loadScriptAndInitializeSdk(script, companyToken, mockedMode);
|
|
31
|
+
cachedSdkInstance = headlessSdk;
|
|
10
32
|
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
return headlessSdk;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Error in loading headless SDK:', error);
|
|
36
|
+
throw new Error('Failed to load script');
|
|
37
|
+
}
|
|
13
38
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { MockedModeType, SdkScenarios } from '@wf-financing/embedded-types';
|
|
2
|
+
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
vi.mock('./getHeadlessSdkInstance', () => ({
|
|
5
|
+
getHeadlessSdkInstance: vi.fn(),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
|
|
9
|
+
import { startHostedApplication } from './startHostedApplication';
|
|
10
|
+
|
|
11
|
+
const fakeSdk = {
|
|
12
|
+
getCta: vi.fn(),
|
|
13
|
+
startHostedApplication: vi.fn(),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe('startHostedApplication', () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('calls sdk.startHostedApplication with partnerData and returns nextUrl', async () => {
|
|
22
|
+
const fakePartnerData = { foo: 1 };
|
|
23
|
+
const fakeNextUrl = '/next-url';
|
|
24
|
+
const partnerCallback = vi.fn().mockResolvedValue(fakePartnerData);
|
|
25
|
+
|
|
26
|
+
(getHeadlessSdkInstance as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce(fakeSdk);
|
|
27
|
+
fakeSdk.startHostedApplication.mockResolvedValueOnce(fakeNextUrl);
|
|
28
|
+
const mockedMode: MockedModeType = {
|
|
29
|
+
isMockedMode: true,
|
|
30
|
+
sdkScenario: SdkScenarios.NEW_APPLICATION,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const result = await startHostedApplication('TOKEN', partnerCallback, mockedMode);
|
|
34
|
+
|
|
35
|
+
expect(getHeadlessSdkInstance).toHaveBeenCalledWith('TOKEN', mockedMode);
|
|
36
|
+
expect(partnerCallback).toHaveBeenCalled();
|
|
37
|
+
expect(fakeSdk.startHostedApplication).toHaveBeenCalledWith(fakePartnerData);
|
|
38
|
+
expect(result).toBe(fakeNextUrl);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('calls sdk.startHostedApplication with empty object if partnerCallback returns falsy', async () => {
|
|
42
|
+
const partnerCallback = vi.fn().mockResolvedValue(undefined);
|
|
43
|
+
const fakeNextUrl = '/next-url';
|
|
44
|
+
|
|
45
|
+
(getHeadlessSdkInstance as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce(fakeSdk);
|
|
46
|
+
fakeSdk.startHostedApplication.mockResolvedValueOnce(fakeNextUrl);
|
|
47
|
+
|
|
48
|
+
const result = await startHostedApplication('TOKEN', partnerCallback);
|
|
49
|
+
|
|
50
|
+
expect(fakeSdk.startHostedApplication).toHaveBeenCalledWith({});
|
|
51
|
+
expect(result).toBe(fakeNextUrl);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { PartnerCallbackType } from '@wf-financing/embedded-types';
|
|
1
|
+
import { PartnerCallbackType, MockedModeType } from '@wf-financing/embedded-types';
|
|
2
2
|
|
|
3
3
|
import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
|
|
4
4
|
|
|
5
5
|
export const startHostedApplication = async (
|
|
6
6
|
companyToken: string,
|
|
7
|
-
isMockedMode: boolean,
|
|
8
7
|
partnerCallback: PartnerCallbackType,
|
|
8
|
+
mockedMode?: MockedModeType,
|
|
9
9
|
) => {
|
|
10
10
|
let partnerData = await partnerCallback();
|
|
11
|
-
const sdk = await getHeadlessSdkInstance(companyToken,
|
|
11
|
+
const sdk = await getHeadlessSdkInstance(companyToken, mockedMode);
|
|
12
12
|
|
|
13
13
|
if (!partnerData) {
|
|
14
14
|
partnerData = {};
|
|
@@ -17,4 +17,4 @@ export const startHostedApplication = async (
|
|
|
17
17
|
const nextUrl = await sdk.startHostedApplication(partnerData);
|
|
18
18
|
|
|
19
19
|
return nextUrl;
|
|
20
|
-
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Flex } from '@wayflyer/flyui';
|
|
2
|
+
|
|
3
|
+
import { CloseButton } from './CloseButton';
|
|
4
|
+
import { ProceedFundingButton } from './ProceedFundingButton';
|
|
5
|
+
|
|
6
|
+
export const BannerActionsDesktop = () => {
|
|
7
|
+
return (
|
|
8
|
+
<Flex gap="4">
|
|
9
|
+
<ProceedFundingButton />
|
|
10
|
+
<CloseButton />
|
|
11
|
+
</Flex>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { CtaBannerContent } from './CtaBannerContent';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof CtaBannerContent> = {
|
|
5
|
+
title: 'BannerContent',
|
|
6
|
+
component: CtaBannerContent,
|
|
7
|
+
argTypes: {
|
|
8
|
+
isMobile: {
|
|
9
|
+
control: 'boolean',
|
|
10
|
+
description: 'Render mobile version',
|
|
11
|
+
defaultValue: false,
|
|
12
|
+
},
|
|
13
|
+
isTablet: {
|
|
14
|
+
control: 'boolean',
|
|
15
|
+
description: 'Render tablet version',
|
|
16
|
+
defaultValue: false,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
|
|
23
|
+
type Story = StoryObj<typeof CtaBannerContent>;
|
|
24
|
+
|
|
25
|
+
export const Default: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
isMobile: false,
|
|
28
|
+
isTablet: false,
|
|
29
|
+
},
|
|
30
|
+
render: (args) => <CtaBannerContent {...args} />,
|
|
31
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { BulletList } from './BulletList';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof BulletList> = {
|
|
5
|
+
title: 'BulletList',
|
|
6
|
+
component: BulletList,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
|
|
11
|
+
type Story = StoryObj<typeof BulletList>;
|
|
12
|
+
|
|
13
|
+
export const Default: Story = {
|
|
14
|
+
render: () => <BulletList items={['Item 1', 'Item 2', 'Item 3']} />,
|
|
15
|
+
};
|
|
@@ -7,7 +7,7 @@ export const BulletList = ({ items }: { items: string[] }) => {
|
|
|
7
7
|
{items.map((bullet_point: string) => (
|
|
8
8
|
<Flex key={bullet_point} gap="1" align="center">
|
|
9
9
|
<IconCheck16Line />
|
|
10
|
-
<Text size="sm" fontWeight="
|
|
10
|
+
<Text size="sm" fontWeight="medium" fontStyle="regular" lineHeight="tight">
|
|
11
11
|
{bullet_point}
|
|
12
12
|
</Text>
|
|
13
13
|
</Flex>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Button, Flex } from '@wayflyer/flyui';
|
|
2
|
+
import { IconX16Line } from '@wayflyer/flyui-icons/16/line';
|
|
3
|
+
|
|
4
|
+
import { usePartnerContext } from '../../hooks';
|
|
5
|
+
|
|
6
|
+
export const CloseButton = () => {
|
|
7
|
+
const { onWidgetClose: closeWidget } = usePartnerContext();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Button onClick={closeWidget} variant="Tertiary">
|
|
11
|
+
<Flex padding="2">
|
|
12
|
+
<IconX16Line />
|
|
13
|
+
</Flex>
|
|
14
|
+
</Button>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Themes } from '@wayflyer/flyui';
|
|
3
|
+
import { CtaResponseTypes, MockedModeType } from '@wf-financing/embedded-types';
|
|
4
|
+
import { App } from '../../App';
|
|
5
|
+
import { CtaBanner } from './CtaBanner';
|
|
6
|
+
|
|
7
|
+
const defaultArgs = {
|
|
8
|
+
partnerDesignId: 'whiteLabel' as Themes,
|
|
9
|
+
companyToken: 'demo-token',
|
|
10
|
+
partnerCallback: () => {},
|
|
11
|
+
onWidgetClose: () => {},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type CtaBannerStoryArgs = typeof defaultArgs & { mockedMode: MockedModeType };
|
|
15
|
+
|
|
16
|
+
const Template = (args: CtaBannerStoryArgs) => (
|
|
17
|
+
<App {...args}>
|
|
18
|
+
<CtaBanner />
|
|
19
|
+
</App>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const meta: Meta<typeof CtaBanner> = {
|
|
23
|
+
title: 'CtaBanner',
|
|
24
|
+
component: CtaBanner,
|
|
25
|
+
argTypes: {
|
|
26
|
+
mockedMode: {
|
|
27
|
+
control: { type: 'object' },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
args: {
|
|
31
|
+
...defaultArgs,
|
|
32
|
+
mockedMode: {
|
|
33
|
+
isMockedMode: true,
|
|
34
|
+
sdkScenario: CtaResponseTypes.GENERIC_OFFER,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
decorators: [(Story, context) => <Template {...(context.args as CtaBannerStoryArgs)} />],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof CtaBanner>;
|
|
42
|
+
|
|
43
|
+
export const GenericOffer: Story = {
|
|
44
|
+
name: 'Generic Offer',
|
|
45
|
+
args: {
|
|
46
|
+
mockedMode: {
|
|
47
|
+
isMockedMode: true,
|
|
48
|
+
sdkScenario: CtaResponseTypes.GENERIC_OFFER,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -1,34 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Flex,
|
|
3
|
-
useDetectDeviceSize,
|
|
4
|
-
useTheme,
|
|
5
|
-
} from '@wayflyer/flyui';
|
|
1
|
+
import { Flex, Theme, useDetectDeviceSize, useTheme } from '@wayflyer/flyui';
|
|
6
2
|
import { styled } from 'styled-components';
|
|
7
3
|
|
|
8
|
-
import { CtaBannerActions } from './CtaBannerActions';
|
|
9
4
|
import { CtaBannerContent } from './CtaBannerContent';
|
|
10
|
-
import {
|
|
5
|
+
import { FooterActions } from './FooterActions';
|
|
6
|
+
import { HeaderActions } from './HeaderActions';
|
|
7
|
+
|
|
8
|
+
type BannerContainerPropTypes = {
|
|
9
|
+
theme: Theme;
|
|
10
|
+
isMobile: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const BannerContainer = styled.aside<BannerContainerPropTypes>`
|
|
14
|
+
padding: ${({ theme, isMobile }) => theme.spacing(isMobile ? ['3', '3', '3', '4'] : ['4', '4', '4', '6'])};
|
|
15
|
+
box-shadow: ${({ theme }) => theme.effects.shadow};
|
|
16
|
+
border-radius: ${({ theme }) => theme.borderRadius(['md'])};
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: ${({ isMobile }) => (isMobile ? 'column' : 'row')};
|
|
19
|
+
gap: ${({ isMobile, theme }) => (isMobile ? theme.spacing(['4']) : '0')};
|
|
20
|
+
background-color: ${({ theme }) => theme.palette.utility.primaryBrand};
|
|
21
|
+
`;
|
|
11
22
|
|
|
12
23
|
export const CtaBanner = () => {
|
|
13
24
|
const { isMobile, isTablet } = useDetectDeviceSize();
|
|
14
25
|
const theme = useTheme();
|
|
15
26
|
|
|
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
27
|
return (
|
|
26
|
-
<BannerContainer>
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
<BannerContainer isMobile={isMobile} theme={theme}>
|
|
29
|
+
<Flex gap="4" align="center" justify="space-between" width="100%">
|
|
30
|
+
<CtaBannerContent isMobile={isMobile} isTablet={isTablet} />
|
|
31
|
+
<HeaderActions />
|
|
32
|
+
</Flex>
|
|
33
|
+
<FooterActions />
|
|
32
34
|
</BannerContainer>
|
|
33
35
|
);
|
|
34
36
|
};
|