@wf-financing/ui 0.1.0 → 1.0.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 +72528 -68575
- package/global.d.ts +8 -0
- package/index.ts +5 -16
- package/package.json +24 -7
- package/sdk/index.ts +36 -0
- package/src/App.tsx +6 -4
- package/src/CtaWidget.tsx +1 -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 +14 -4
- package/src/components/banner/BannerActions.stories.tsx +22 -0
- package/src/components/banner/{CtaBannerActions.tsx → BannerActionsDesktop.tsx} +2 -8
- package/src/components/banner/BannerActionsMobile.tsx +12 -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/CtaBanner.stories.tsx +14 -0
- package/src/components/banner/CtaBanner.tsx +24 -22
- package/src/components/banner/CtaBannerContent.tsx +14 -10
- 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 +3 -8
- package/src/components/modal/ModalFooter.tsx +17 -14
- package/src/components/modal/modalEnhancements.ts +1 -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/useCtaBanner.ts +6 -5
- package/src/hooks/useStartHostedApplication.ts +8 -11
- package/src/locales/en.json +3 -1
- package/src/main.tsx +9 -4
- 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 +4 -1
- package/tsconfig.json +3 -3
- package/vite.config.ts +2 -1
- package/vitest.shims.d.ts +1 -0
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,18 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { WayflyerUiCtaSdk } from './sdk';
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
}
|
|
3
|
+
const addHeadlessSdkToWindow = () => {
|
|
4
|
+
window.WayflyerUiCtaSdk = WayflyerUiCtaSdk;
|
|
18
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.0.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,5 +1,6 @@
|
|
|
1
1
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
2
2
|
import { FlyUIProvider, Themes } from '@wayflyer/flyui';
|
|
3
|
+
import { PartnerCallbackType, MockedModeType } from '@wf-financing/embedded-types';
|
|
3
4
|
import { createIntl, createIntlCache, IntlShape } from 'react-intl';
|
|
4
5
|
|
|
5
6
|
import { CtaWidget } from './CtaWidget';
|
|
@@ -9,7 +10,8 @@ import { PartnerContext } from './utils/partnerContext';
|
|
|
9
10
|
type AppPropsType = {
|
|
10
11
|
partnerDesignId: Themes;
|
|
11
12
|
companyToken: string;
|
|
12
|
-
|
|
13
|
+
mockedMode?: MockedModeType;
|
|
14
|
+
partnerCallback: PartnerCallbackType;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
const queryClient = new QueryClient();
|
|
@@ -17,7 +19,7 @@ const messages = {
|
|
|
17
19
|
en: enMessages,
|
|
18
20
|
};
|
|
19
21
|
|
|
20
|
-
export const App = ({ partnerDesignId, companyToken,
|
|
22
|
+
export const App = ({ partnerDesignId, companyToken, mockedMode, partnerCallback }: AppPropsType) => {
|
|
21
23
|
const locale = 'en';
|
|
22
24
|
|
|
23
25
|
const intl: IntlShape = createIntl(
|
|
@@ -25,12 +27,12 @@ export const App = ({ partnerDesignId, companyToken, isMockedMode }: AppPropsTyp
|
|
|
25
27
|
locale,
|
|
26
28
|
messages: messages[locale],
|
|
27
29
|
},
|
|
28
|
-
createIntlCache()
|
|
30
|
+
createIntlCache(),
|
|
29
31
|
);
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<QueryClientProvider client={queryClient}>
|
|
33
|
-
<PartnerContext.Provider value={{ companyToken,
|
|
35
|
+
<PartnerContext.Provider value={{ companyToken, mockedMode, partnerCallback }}>
|
|
34
36
|
<FlyUIProvider theme={partnerDesignId} intl={intl}>
|
|
35
37
|
<CtaWidget />
|
|
36
38
|
</FlyUIProvider>
|
package/src/CtaWidget.tsx
CHANGED
|
@@ -3,13 +3,6 @@ import { useCtaBanner } from './hooks/useCtaBanner';
|
|
|
3
3
|
|
|
4
4
|
export const CtaWidget = () => {
|
|
5
5
|
const banner = useCtaBanner();
|
|
6
|
-
const handleCloseWidget = () => console.log('close widget');
|
|
7
6
|
|
|
8
|
-
return
|
|
9
|
-
<>
|
|
10
|
-
{!banner.isLoading && (
|
|
11
|
-
<CtaBanner closeWidget={handleCloseWidget} />
|
|
12
|
-
)}
|
|
13
|
-
</>
|
|
14
|
-
);
|
|
7
|
+
return <>{!banner.isLoading && <CtaBanner />}</>;
|
|
15
8
|
};
|
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,10 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PartnerCallbackType, MockedModeType } from '@wf-financing/embedded-types';
|
|
2
2
|
|
|
3
3
|
import { getHeadlessSdkInstance } from './getHeadlessSdkInstance';
|
|
4
4
|
|
|
5
|
-
export const startHostedApplication = async (
|
|
6
|
-
|
|
5
|
+
export const startHostedApplication = async (
|
|
6
|
+
companyToken: string,
|
|
7
|
+
partnerCallback: PartnerCallbackType,
|
|
8
|
+
mockedMode?: MockedModeType,
|
|
9
|
+
) => {
|
|
10
|
+
let partnerData = await partnerCallback();
|
|
11
|
+
const sdk = await getHeadlessSdkInstance(companyToken, mockedMode);
|
|
12
|
+
|
|
13
|
+
if (!partnerData) {
|
|
14
|
+
partnerData = {};
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
const nextUrl = await sdk.startHostedApplication(partnerData);
|
|
8
18
|
|
|
9
19
|
return nextUrl;
|
|
10
|
-
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BannerActionsDesktop } from './BannerActionsDesktop';
|
|
2
|
+
import { BannerActionsMobile } from './BannerActionsMobile';
|
|
3
|
+
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: 'Banner/BannerActions',
|
|
8
|
+
component: BannerActionsDesktop,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const Desktop: StoryObj = {
|
|
15
|
+
render: () => <BannerActionsDesktop />,
|
|
16
|
+
name: 'Desktop Actions',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const Mobile: StoryObj = {
|
|
20
|
+
render: () => <BannerActionsMobile />,
|
|
21
|
+
name: 'Mobile Actions',
|
|
22
|
+
};
|
|
@@ -3,15 +3,9 @@ import { IconX16Line } from '@wayflyer/flyui-icons/16/line';
|
|
|
3
3
|
|
|
4
4
|
import { ProceedFundingButton } from './ProceedFundingButton';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
isMobile: boolean;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export const CtaBannerActions = ({ isMobile }: CtaBannerActionsType) => (
|
|
6
|
+
export const BannerActionsDesktop = () => (
|
|
11
7
|
<Flex gap="4">
|
|
12
|
-
|
|
13
|
-
<ProceedFundingButton />
|
|
14
|
-
)}
|
|
8
|
+
<ProceedFundingButton />
|
|
15
9
|
<Button variant="Tertiary">
|
|
16
10
|
<IconX16Line />
|
|
17
11
|
</Button>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Button, Flex } from '@wayflyer/flyui';
|
|
2
|
+
|
|
3
|
+
import { ProceedFundingButton } from './ProceedFundingButton';
|
|
4
|
+
|
|
5
|
+
export const BannerActionsMobile = () => (
|
|
6
|
+
<Flex gap="2" align="center" width="100%" justify="space-around">
|
|
7
|
+
<ProceedFundingButton />
|
|
8
|
+
<Button fullWidth title="Dismiss" variant="Secondary">
|
|
9
|
+
Dismiss
|
|
10
|
+
</Button>
|
|
11
|
+
</Flex>
|
|
12
|
+
);
|
|
@@ -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,14 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { CtaBanner } from './CtaBanner';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof CtaBanner> = {
|
|
5
|
+
title: 'CtaBanner',
|
|
6
|
+
component: CtaBanner,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default meta;
|
|
10
|
+
type Story = StoryObj<typeof CtaBanner>;
|
|
11
|
+
|
|
12
|
+
export const Default: Story = {
|
|
13
|
+
render: () => <CtaBanner />,
|
|
14
|
+
};
|
|
@@ -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 {
|
|
4
|
+
import { BannerActionsDesktop } from './BannerActionsDesktop';
|
|
5
|
+
import { BannerActionsMobile } from './BannerActionsMobile';
|
|
9
6
|
import { CtaBannerContent } from './CtaBannerContent';
|
|
10
|
-
|
|
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
|
+
{!isMobile && <BannerActionsDesktop />}
|
|
32
|
+
</Flex>
|
|
33
|
+
{isMobile && <BannerActionsMobile />}
|
|
32
34
|
</BannerContainer>
|
|
33
35
|
);
|
|
34
36
|
};
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
import { Flex,
|
|
1
|
+
import { Flex, Text } from '@wayflyer/flyui';
|
|
2
2
|
import type { CtaResponseType } from '@wf-financing/embedded-types';
|
|
3
3
|
|
|
4
4
|
import { useCtaBanner } from '../../hooks/useCtaBanner';
|
|
5
5
|
import { BulletList } from './BulletList';
|
|
6
6
|
|
|
7
|
-
type
|
|
7
|
+
type CtaBannerContentProps = {
|
|
8
8
|
isMobile: boolean;
|
|
9
9
|
isTablet: boolean;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
export const CtaBannerContent = ({ isMobile, isTablet }:
|
|
12
|
+
export const CtaBannerContent = ({ isMobile, isTablet }: CtaBannerContentProps) => {
|
|
13
13
|
const sdk = useCtaBanner();
|
|
14
14
|
const ctaData = sdk.data as CtaResponseType;
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
<>
|
|
18
|
+
{!sdk.isLoading && (
|
|
19
|
+
<Flex direction="column" gap="1">
|
|
20
|
+
<Text size={isMobile ? 'lg' : 'xl'} fontStyle="regular" fontWeight="medium" lineHeight="normal">
|
|
21
|
+
{ctaData?.data?.config?.text}
|
|
22
|
+
</Text>
|
|
23
|
+
{!(isTablet || isMobile) && ctaData?.data?.config?.bullet_points && (
|
|
24
|
+
<BulletList items={ctaData?.data?.config?.bullet_points} />
|
|
25
|
+
)}
|
|
26
|
+
</Flex>
|
|
23
27
|
)}
|
|
24
|
-
|
|
28
|
+
</>
|
|
25
29
|
);
|
|
26
30
|
};
|