@wf-financing/ui 1.0.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wf-financing/ui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "exports": {
5
5
  ".": {
6
6
  "import": "./dist/index.es.js",
package/src/App.tsx CHANGED
@@ -12,6 +12,7 @@ type AppPropsType = {
12
12
  companyToken: string;
13
13
  mockedMode?: MockedModeType;
14
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, mockedMode, partnerCallback }: AppPropsType) => {
23
+ export const App = ({ partnerDesignId, companyToken, mockedMode, partnerCallback, onWidgetClose }: AppPropsType) => {
23
24
  const locale = 'en';
24
25
 
25
26
  const intl: IntlShape = createIntl(
@@ -32,7 +33,7 @@ export const App = ({ partnerDesignId, companyToken, mockedMode, partnerCallback
32
33
 
33
34
  return (
34
35
  <QueryClientProvider client={queryClient}>
35
- <PartnerContext.Provider value={{ companyToken, mockedMode, partnerCallback }}>
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,8 +1,19 @@
1
+ import { useEffect } from 'react';
2
+
1
3
  import { CtaBanner } from './components/banner/CtaBanner';
2
- import { useCtaBanner } from './hooks/useCtaBanner';
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 <>{!banner.isLoading && <CtaBanner />}</>;
18
+ return <>{!banner.isLoading && banner.data && <CtaBanner />}</>;
8
19
  };
@@ -1,13 +1,13 @@
1
- import { Button, Flex } from '@wayflyer/flyui';
2
- import { IconX16Line } from '@wayflyer/flyui-icons/16/line';
1
+ import { Flex } from '@wayflyer/flyui';
3
2
 
3
+ import { CloseButton } from './CloseButton';
4
4
  import { ProceedFundingButton } from './ProceedFundingButton';
5
5
 
6
- export const BannerActionsDesktop = () => (
7
- <Flex gap="4">
8
- <ProceedFundingButton />
9
- <Button variant="Tertiary">
10
- <IconX16Line />
11
- </Button>
12
- </Flex>
13
- );
6
+ export const BannerActionsDesktop = () => {
7
+ return (
8
+ <Flex gap="4">
9
+ <ProceedFundingButton />
10
+ <CloseButton />
11
+ </Flex>
12
+ );
13
+ };
@@ -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
+ };
@@ -1,14 +1,51 @@
1
- import type { Meta, StoryObj } from '@storybook/react';
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';
2
5
  import { CtaBanner } from './CtaBanner';
3
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
+
4
22
  const meta: Meta<typeof CtaBanner> = {
5
23
  title: 'CtaBanner',
6
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)} />],
7
38
  };
8
39
 
9
40
  export default meta;
10
41
  type Story = StoryObj<typeof CtaBanner>;
11
42
 
12
- export const Default: Story = {
13
- render: () => <CtaBanner />,
43
+ export const GenericOffer: Story = {
44
+ name: 'Generic Offer',
45
+ args: {
46
+ mockedMode: {
47
+ isMockedMode: true,
48
+ sdkScenario: CtaResponseTypes.GENERIC_OFFER,
49
+ },
50
+ },
14
51
  };
@@ -1,9 +1,9 @@
1
1
  import { Flex, Theme, useDetectDeviceSize, useTheme } from '@wayflyer/flyui';
2
2
  import { styled } from 'styled-components';
3
3
 
4
- import { BannerActionsDesktop } from './BannerActionsDesktop';
5
- import { BannerActionsMobile } from './BannerActionsMobile';
6
4
  import { CtaBannerContent } from './CtaBannerContent';
5
+ import { FooterActions } from './FooterActions';
6
+ import { HeaderActions } from './HeaderActions';
7
7
 
8
8
  type BannerContainerPropTypes = {
9
9
  theme: Theme;
@@ -28,9 +28,9 @@ export const CtaBanner = () => {
28
28
  <BannerContainer isMobile={isMobile} theme={theme}>
29
29
  <Flex gap="4" align="center" justify="space-between" width="100%">
30
30
  <CtaBannerContent isMobile={isMobile} isTablet={isTablet} />
31
- {!isMobile && <BannerActionsDesktop />}
31
+ <HeaderActions />
32
32
  </Flex>
33
- {isMobile && <BannerActionsMobile />}
33
+ <FooterActions />
34
34
  </BannerContainer>
35
35
  );
36
36
  };
@@ -0,0 +1,8 @@
1
+ import { useDetectDeviceSize } from '@wayflyer/flyui';
2
+ import { ProceedFundingButton } from './ProceedFundingButton';
3
+
4
+ export const FooterActions = () => {
5
+ const { isMobile } = useDetectDeviceSize();
6
+
7
+ return isMobile ? <ProceedFundingButton /> : null;
8
+ };
@@ -0,0 +1,9 @@
1
+ import { useDetectDeviceSize } from '@wayflyer/flyui';
2
+ import { BannerActionsDesktop } from './BannerActionsDesktop';
3
+ import { CloseButton } from './CloseButton';
4
+
5
+ export const HeaderActions = () => {
6
+ const { isMobile } = useDetectDeviceSize();
7
+
8
+ return isMobile ? <CloseButton /> : <BannerActionsDesktop />;
9
+ };
@@ -31,7 +31,7 @@ export const Modal = ({ isModalOpen, setIsModalOpen, children }: ModalProps) =>
31
31
  animate={fullSize}
32
32
  exit={small}
33
33
  transition={{ duration: 0.3 }}
34
- style={{ width: '438px', border: 'none' }}
34
+ style={{ width: '438px', border: 'none', maxWidth: '100vw' }}
35
35
  >
36
36
  <MotionModalOverlay
37
37
  data-id="modal-mask"
@@ -21,6 +21,8 @@ export const DialogBody = styled.div`
21
21
  overflow-y: auto;
22
22
  background-color: var(--palette-modal-surface);
23
23
  border-radius: var(--sizes-radius-md);
24
+ max-height: 80vh;
25
+ padding-bottom: 10px;
24
26
 
25
27
  &::-webkit-scrollbar {
26
28
  width: 12px;
@@ -0,0 +1,3 @@
1
+ export { usePartnerContext } from './usePartnerContext';
2
+ export { useCtaBanner } from './useCtaBanner';
3
+ export { useStartHostedApplication } from './useStartHostedApplication';
@@ -1,17 +1,10 @@
1
1
  import { useQuery } from '@tanstack/react-query';
2
- import { useContext } from 'react';
3
- import { MockedModeType } from '@wf-financing/embedded-types';
4
2
 
5
3
  import { fetchCtaBanner } from '../api/ctaBanner';
6
- import { PartnerContext } from '../utils/partnerContext';
7
-
8
- type PartnerDataType = {
9
- companyToken: string;
10
- mockedMode?: MockedModeType;
11
- };
4
+ import { usePartnerContext } from './usePartnerContext';
12
5
 
13
6
  export const useCtaBanner = () => {
14
- const { companyToken, mockedMode } = useContext(PartnerContext) as PartnerDataType;
7
+ const { companyToken, mockedMode } = usePartnerContext();
15
8
 
16
9
  return useQuery({
17
10
  queryKey: ['cta', companyToken, mockedMode],
@@ -0,0 +1,7 @@
1
+ import { useContext } from 'react';
2
+
3
+ import { PartnerContext, type PartnerContextType } from '../utils';
4
+
5
+ export const usePartnerContext = () => {
6
+ return useContext(PartnerContext) as PartnerContextType;
7
+ };
@@ -1,19 +1,10 @@
1
- import { PartnerCallbackType, MockedModeType } 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
- mockedMode?: MockedModeType;
9
- partnerCallback: PartnerCallbackType;
10
- };
11
-
12
1
  import { useMutation } from '@tanstack/react-query';
2
+
13
3
  import { startHostedApplication } from '../api/startHostedApplication';
4
+ import { usePartnerContext } from './usePartnerContext';
14
5
 
15
6
  export const useStartHostedApplication = () => {
16
- const { companyToken, mockedMode, partnerCallback } = useContext(PartnerContext) as PartnerDataType;
7
+ const { companyToken, mockedMode, partnerCallback } = usePartnerContext();
17
8
 
18
9
  return useMutation({
19
10
  mutationFn: () => startHostedApplication(companyToken, partnerCallback, mockedMode),
package/src/main.tsx CHANGED
@@ -4,7 +4,7 @@ import ReactDOM from 'react-dom/client';
4
4
 
5
5
  import { App } from './App';
6
6
 
7
- const roots = new Map<HTMLElement, ReactDOM.Root>();
7
+ let root: ReactDOM.Root | undefined = undefined;
8
8
 
9
9
  export const mountToTarget = (
10
10
  targetId: string,
@@ -14,17 +14,14 @@ export const mountToTarget = (
14
14
  mockedMode?: MockedModeType,
15
15
  ) => {
16
16
  const targetElement = document.getElementById(targetId);
17
+ if (!targetElement) throw new Error(`Target element with id "${targetId}" not found.`);
17
18
 
18
- if (!targetElement) {
19
- console.warn(`Target element with id "${targetId}" not found.`);
20
- return;
21
- }
22
-
23
- let root = roots.get(targetElement);
19
+ if (!root) root = ReactDOM.createRoot(targetElement);
24
20
 
25
- if (!root) {
26
- root = ReactDOM.createRoot(targetElement);
27
- roots.set(targetElement, root);
21
+ function handleCloseWidget() {
22
+ if (!root) throw new Error('Root is not found');
23
+ root.unmount();
24
+ root = undefined;
28
25
  }
29
26
 
30
27
  root.render(
@@ -33,6 +30,7 @@ export const mountToTarget = (
33
30
  companyToken={companyToken}
34
31
  mockedMode={mockedMode}
35
32
  partnerCallback={partnerCallback}
33
+ onWidgetClose={handleCloseWidget}
36
34
  />,
37
35
  );
38
36
  };
@@ -1,3 +1,3 @@
1
1
  export { initializeHeadlessSdk } from './initializeHeadlessSdk';
2
2
  export { loadScriptAndInitializeSdk } from './loadScriptAndInitializeSdk';
3
- export { PartnerContext } from './partnerContext';
3
+ export { PartnerContext, type PartnerContextType } from './partnerContext';
@@ -1,11 +1,14 @@
1
1
  import { PartnerCallbackType, MockedModeType } from '@wf-financing/embedded-types';
2
2
  import { createContext } from 'react';
3
3
 
4
- type PartnerContextType = {
4
+ export type PartnerContextType = {
5
5
  companyToken: string;
6
6
  isMockedMode?: boolean;
7
7
  partnerCallback: PartnerCallbackType;
8
8
  mockedMode?: MockedModeType;
9
- } | null;
9
+ onWidgetClose: () => void;
10
+ };
10
11
 
11
- export const PartnerContext = createContext<PartnerContextType>(null);
12
+ type PartnerContextWithDefaultType = PartnerContextType | null;
13
+
14
+ export const PartnerContext = createContext<PartnerContextWithDefaultType>(null);
@@ -1,22 +0,0 @@
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
- };
@@ -1,12 +0,0 @@
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
- );