Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial implementation of cf preview (draft) #2535

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion apps/kyb-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,31 @@
"private": true,
"version": "0.3.22",
"type": "module",
"main": "./dist/collection-flow-portable/index.js",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build-portable": "vite build --config vite.lib.config.ts",
"lint": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"preview": "vite preview",
"test": "vitest run",
"test:dev": "vitest"
"test:dev": "vitest",
"postinstall": "vite build --config vite.lib.config.ts"
},
"dependencies": {
<<<<<<< Updated upstream
"@ballerine/common": "^0.9.14",
"@ballerine/blocks": "0.2.8",
"@ballerine/ui": "0.5.8",
"@ballerine/workflow-browser-sdk": "0.6.22",
=======
"@ballerine/blocks": "0.2.6",
"@ballerine/common": "^0.9.12",
"@ballerine/ui": "0.5.6",
"@ballerine/workflow-browser-sdk": "0.6.20",
>>>>>>> Stashed changes
"@lukemorales/query-key-factory": "^1.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@rjsf/core": "^5.9.0",
Expand Down Expand Up @@ -59,7 +69,11 @@
},
"devDependencies": {
"@ballerine/config": "^1.1.7",
<<<<<<< Updated upstream
"@ballerine/eslint-config-react": "^2.0.7",
=======
"@ballerine/eslint-config-react": "^2.0.5",
>>>>>>> Stashed changes
"@jest/globals": "^29.7.0",
"@sentry/vite-plugin": "^2.9.0",
"@testing-library/jest-dom": "^6.1.4",
Expand Down Expand Up @@ -90,6 +104,7 @@
"typescript": "^5.0.2",
"vite": "^4.5.3",
"vite-plugin-checker": "^0.6.1",
"vite-plugin-dts": "^1.7.3",
"vite-tsconfig-paths": "^4.0.7",
"vitest": "^0.34.6"
}
Expand Down
2 changes: 1 addition & 1 deletion apps/kyb-app/src/common/components/atoms/Chip/Chip.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ctw } from '@ballerine/ui';
import { cva, VariantProps } from 'class-variance-authority';

type BaseChipVariantProps = VariantProps<typeof baseChipVariants>;
export type BaseChipVariantProps = VariantProps<typeof baseChipVariants>;

const baseChipVariants = cva('flex transition-all gap-2 px-4 py-2 rounded-2xl items-center', {
variants: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useMemo } from 'react';

import { AnyChildren } from '@ballerine/ui';
import { useCustomerQuery } from '@/hooks/useCustomerQuery';
import { CustomerContext } from '@/components/providers/CustomerProvider/types';
import { customerContext } from '@/components/providers/CustomerProvider/customer.context';
import { CustomerContext } from '@/components/providers/CustomerProvider/types';
import { useCustomerQuery } from '@/hooks/useCustomerQuery';
import { AnyChildren } from '@ballerine/ui';

const { Provider } = customerContext;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useMemo } from 'react';

import { customerContext } from '@/components/providers/CustomerProvider/customer.context';
import { CustomerContext } from '@/components/providers/CustomerProvider/types';
import { TCustomer } from '@/domains/collection-flow';
import { AnyChildren } from '@ballerine/ui';

const { Provider } = customerContext;

interface Props {
children: AnyChildren;
defaultCustomer: TCustomer;
}

export const CustomerProviderPortable = ({ children, defaultCustomer }: Props) => {
const context = useMemo(() => {
const ctx: CustomerContext = {
customer: defaultCustomer,
};

return ctx;
}, [defaultCustomer]);

return <Provider value={context}>{children}</Provider>;
};
4 changes: 2 additions & 2 deletions apps/kyb-app/src/hooks/useCustomerQuery/useCustomerQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { collectionFlowQuerykeys } from '@/domains/collection-flow';
import { useQuery } from '@tanstack/react-query';
import { HTTPError } from 'ky';

export const useCustomerQuery = () => {
export const useCustomerQuery = (enabled = true) => {
const { data, isLoading, error } = useQuery(
// @ts-ignore
collectionFlowQuerykeys.getCustomer(),
{ ...collectionFlowQuerykeys.getCustomer(), enabled },
Comment on lines +5 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enhance the type definition for the enabled parameter.

The enabled parameter is implicitly typed as boolean by default. Consider explicitly typing it for better clarity and maintainability.

- export const useCustomerQuery = (enabled = true) => {
+ export const useCustomerQuery = (enabled: boolean = true) => {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const useCustomerQuery = (enabled = true) => {
const { data, isLoading, error } = useQuery(
// @ts-ignore
collectionFlowQuerykeys.getCustomer(),
{ ...collectionFlowQuerykeys.getCustomer(), enabled },
export const useCustomerQuery = (enabled: boolean = true) => {
const { data, isLoading, error } = useQuery(
// @ts-ignore
{ ...collectionFlowQuerykeys.getCustomer(), enabled },

);

return {
Expand Down
12 changes: 12 additions & 0 deletions apps/kyb-app/src/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import './i18next';
import './index.css';
export * from './lib/collection-flow-portable';

(window as any).toggleDevmode = () => {
const key = 'devmode';
const isDebug = localStorage.getItem(key);

isDebug ? localStorage.removeItem(key) : localStorage.setItem(key, 'true');

location.reload();
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import { StepperProgress } from '@/common/components/atoms/StepperProgress';
import { ProgressBar } from '@/common/components/molecules/ProgressBar';
import { AppShell } from '@/components/layouts/AppShell';
import { DynamicUI, State } from '@/components/organisms/DynamicUI';
import { usePageErrors } from '@/components/organisms/DynamicUI/Page/hooks/usePageErrors';
import { UIRenderer } from '@/components/organisms/UIRenderer';
import { StepperUI } from '@/components/organisms/UIRenderer/elements/StepperUI';
import { CustomerProviderPortable } from '@/components/providers/CustomerProvider/CustomerProviderPortable';
import { TCustomer, UISchema } from '@/domains/collection-flow';
import { prepareInitialUIState } from '@/helpers/prepareInitialUIState';
import { collectionFlowElements } from '@/pages/CollectionFlow';
import { Approved } from '@/pages/CollectionFlow/components/pages/Approved';
import { Rejected } from '@/pages/CollectionFlow/components/pages/Rejected';
import { Success } from '@/pages/CollectionFlow/components/pages/Success';
import { AnyObject } from '@ballerine/ui';
import set from 'lodash/set';
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';

export interface ICollectionFlowPortableProps {
language: string;
schema: UISchema;
context: AnyObject;
customer: TCustomer;
}

export const CollectionFlowPortable: FunctionComponent<ICollectionFlowPortableProps> = ({
language,
schema,
context,
customer,
}) => {
const { t } = useTranslation();

const elements = schema?.uiSchema?.elements;
const definition = schema?.definition.definition;

const pageErrors = usePageErrors(context ?? {}, elements || []);
const isRevision = useMemo(
() => pageErrors.some(error => error.errors?.some(error => error.type === 'warning')),
[pageErrors],
);

const filteredNonEmptyErrors = pageErrors?.filter(pageError => !!pageError.errors.length);

// @ts-ignore
const initialContext: CollectionFlowContext | null = useMemo(() => {
const appState =
filteredNonEmptyErrors?.[0]?.stateName ||
context?.flowConfig?.appState ||
elements?.at(0)?.stateName;
if (!appState) return null;

return {
...context,
flowConfig: {
...context?.flowConfig,
appState,
},
state: appState,
};
}, [context, elements, filteredNonEmptyErrors]);

const initialUIState = useMemo(() => {
return prepareInitialUIState(elements || [], context || {}, isRevision);
}, [elements, context, isRevision]);

// Breadcrumbs now using scrollIntoView method to make sure that breadcrumb is always in viewport.
// Due to dynamic dimensions of logo it doesnt work well if scroll happens before logo is loaded.
// This workaround is needed to wait for logo to be loaded so scrollIntoView will work with correct dimensions of page.
const [isLogoLoaded, setLogoLoaded] = useState(customer?.logoImageUri ? false : true);

useEffect(() => {
if (!customer?.logoImageUri) return;

// Resseting loaded state in case of logo change
setLogoLoaded(false);
}, [customer?.logoImageUri]);

if (initialContext?.flowConfig?.appState === 'approved') return <Approved />;
if (initialContext?.flowConfig?.appState == 'rejected') return <Rejected />;

return definition && context ? (
<CustomerProviderPortable defaultCustomer={customer}>
<DynamicUI initialState={initialUIState}>
<DynamicUI.StateManager
initialContext={initialContext}
workflowId="1"
definitionType={schema?.definition.definitionType}
extensions={schema?.definition.extensions}
definition={definition as State}
>
{({ state, stateApi }) =>
state === 'finish' ? (
<Success />
) : (
<DynamicUI.PageResolver state={state} pages={elements ?? []}>
{({ currentPage }) => {
return currentPage ? (
<DynamicUI.Page page={currentPage}>
<DynamicUI.TransitionListener
onNext={async (tools, prevState) => {
tools.setElementCompleted(prevState, true);

set(
stateApi.getContext(),
`flowConfig.stepsProgress.${prevState}.isCompleted`,
true,
);
await stateApi.invokePlugin('sync_workflow_runtime');
}}
>
<DynamicUI.ActionsHandler actions={currentPage.actions} stateApi={stateApi}>
<AppShell>
<AppShell.Sidebar>
<div className="flex h-full flex-col">
<div className="flex h-full flex-1 flex-col">
<div className="flex flex-row justify-between gap-2 whitespace-nowrap pb-10">
<AppShell.Navigation />
<div>
<AppShell.LanguagePicker />
</div>
</div>
<div className="pb-10">
{customer?.logoImageUri && (
<AppShell.Logo
// @ts-ignore
logoSrc={customer?.logoImageUri}
// @ts-ignore
appName={customer?.displayName}
onLoad={() => setLogoLoaded(true)}
/>
)}
</div>
<div className="min-h-0 flex-1 pb-10">
{isLogoLoaded ? <StepperUI /> : null}
</div>
<div>
{customer?.displayName && (
<div className="border-b pb-12">
{
t('contact', {
companyName: customer.displayName,
}) as string
}
</div>
)}
<img src={'/poweredby.svg'} className="mt-6" />
</div>
</div>
</div>
</AppShell.Sidebar>
<AppShell.Content>
<AppShell.FormContainer>
{localStorage.getItem('devmode') ? (
<div className="flex flex-col gap-4">
DEBUG
<div>
{currentPage
? currentPage.stateName
: 'Page not found and state ' + state}
</div>
<div className="flex gap-4">
<button onClick={() => stateApi.sendEvent('PREVIOUS')}>
prev
</button>
<button onClick={() => stateApi.sendEvent('NEXT')}>
next
</button>
</div>
</div>
) : null}
<div className="flex flex-col">
<div className="flex items-center gap-3 pb-3">
<StepperProgress
currentStep={
(elements?.findIndex(page => page?.stateName === state) ??
0) + 1
}
totalSteps={elements?.length ?? 0}
/>
<ProgressBar />
</div>
<div>
<UIRenderer
elements={collectionFlowElements}
schema={currentPage.elements}
/>
</div>
</div>
</AppShell.FormContainer>
</AppShell.Content>
</AppShell>
</DynamicUI.ActionsHandler>
</DynamicUI.TransitionListener>
</DynamicUI.Page>
) : null;
}}
</DynamicUI.PageResolver>
)
}
</DynamicUI.StateManager>
</DynamicUI>
</CustomerProviderPortable>
) : null;
Comment on lines +27 to +205
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! The CollectionFlowPortable component is well-implemented.

The component is well-implemented with various hooks and utilities to manage its state and functionality.

Refactor suggestion: Simplify the conditional expression.

Based on the hint from Biome, the use of boolean literals in the conditional expression can be simplified.

-  const [isLogoLoaded, setLogoLoaded] = useState(customer?.logoImageUri ? false : true);
+  const [isLogoLoaded, setLogoLoaded] = useState(!customer?.logoImageUri);
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const CollectionFlowPortable: FunctionComponent<ICollectionFlowPortableProps> = ({
language,
schema,
context,
customer,
}) => {
const { t } = useTranslation();
const elements = schema?.uiSchema?.elements;
const definition = schema?.definition.definition;
const pageErrors = usePageErrors(context ?? {}, elements || []);
const isRevision = useMemo(
() => pageErrors.some(error => error.errors?.some(error => error.type === 'warning')),
[pageErrors],
);
const filteredNonEmptyErrors = pageErrors?.filter(pageError => !!pageError.errors.length);
// @ts-ignore
const initialContext: CollectionFlowContext | null = useMemo(() => {
const appState =
filteredNonEmptyErrors?.[0]?.stateName ||
context?.flowConfig?.appState ||
elements?.at(0)?.stateName;
if (!appState) return null;
return {
...context,
flowConfig: {
...context?.flowConfig,
appState,
},
state: appState,
};
}, [context, elements, filteredNonEmptyErrors]);
const initialUIState = useMemo(() => {
return prepareInitialUIState(elements || [], context || {}, isRevision);
}, [elements, context, isRevision]);
// Breadcrumbs now using scrollIntoView method to make sure that breadcrumb is always in viewport.
// Due to dynamic dimensions of logo it doesnt work well if scroll happens before logo is loaded.
// This workaround is needed to wait for logo to be loaded so scrollIntoView will work with correct dimensions of page.
const [isLogoLoaded, setLogoLoaded] = useState(customer?.logoImageUri ? false : true);
useEffect(() => {
if (!customer?.logoImageUri) return;
// Resseting loaded state in case of logo change
setLogoLoaded(false);
}, [customer?.logoImageUri]);
if (initialContext?.flowConfig?.appState === 'approved') return <Approved />;
if (initialContext?.flowConfig?.appState == 'rejected') return <Rejected />;
return definition && context ? (
<CustomerProviderPortable defaultCustomer={customer}>
<DynamicUI initialState={initialUIState}>
<DynamicUI.StateManager
initialContext={initialContext}
workflowId="1"
definitionType={schema?.definition.definitionType}
extensions={schema?.definition.extensions}
definition={definition as State}
>
{({ state, stateApi }) =>
state === 'finish' ? (
<Success />
) : (
<DynamicUI.PageResolver state={state} pages={elements ?? []}>
{({ currentPage }) => {
return currentPage ? (
<DynamicUI.Page page={currentPage}>
<DynamicUI.TransitionListener
onNext={async (tools, prevState) => {
tools.setElementCompleted(prevState, true);
set(
stateApi.getContext(),
`flowConfig.stepsProgress.${prevState}.isCompleted`,
true,
);
await stateApi.invokePlugin('sync_workflow_runtime');
}}
>
<DynamicUI.ActionsHandler actions={currentPage.actions} stateApi={stateApi}>
<AppShell>
<AppShell.Sidebar>
<div className="flex h-full flex-col">
<div className="flex h-full flex-1 flex-col">
<div className="flex flex-row justify-between gap-2 whitespace-nowrap pb-10">
<AppShell.Navigation />
<div>
<AppShell.LanguagePicker />
</div>
</div>
<div className="pb-10">
{customer?.logoImageUri && (
<AppShell.Logo
// @ts-ignore
logoSrc={customer?.logoImageUri}
// @ts-ignore
appName={customer?.displayName}
onLoad={() => setLogoLoaded(true)}
/>
)}
</div>
<div className="min-h-0 flex-1 pb-10">
{isLogoLoaded ? <StepperUI /> : null}
</div>
<div>
{customer?.displayName && (
<div className="border-b pb-12">
{
t('contact', {
companyName: customer.displayName,
}) as string
}
</div>
)}
<img src={'/poweredby.svg'} className="mt-6" />
</div>
</div>
</div>
</AppShell.Sidebar>
<AppShell.Content>
<AppShell.FormContainer>
{localStorage.getItem('devmode') ? (
<div className="flex flex-col gap-4">
DEBUG
<div>
{currentPage
? currentPage.stateName
: 'Page not found and state ' + state}
</div>
<div className="flex gap-4">
<button onClick={() => stateApi.sendEvent('PREVIOUS')}>
prev
</button>
<button onClick={() => stateApi.sendEvent('NEXT')}>
next
</button>
</div>
</div>
) : null}
<div className="flex flex-col">
<div className="flex items-center gap-3 pb-3">
<StepperProgress
currentStep={
(elements?.findIndex(page => page?.stateName === state) ??
0) + 1
}
totalSteps={elements?.length ?? 0}
/>
<ProgressBar />
</div>
<div>
<UIRenderer
elements={collectionFlowElements}
schema={currentPage.elements}
/>
</div>
</div>
</AppShell.FormContainer>
</AppShell.Content>
</AppShell>
</DynamicUI.ActionsHandler>
</DynamicUI.TransitionListener>
</DynamicUI.Page>
) : null;
}}
</DynamicUI.PageResolver>
)
}
</DynamicUI.StateManager>
</DynamicUI>
</CustomerProviderPortable>
) : null;
export const CollectionFlowPortable: FunctionComponent<ICollectionFlowPortableProps> = ({
language,
schema,
context,
customer,
}) => {
const { t } = useTranslation();
const elements = schema?.uiSchema?.elements;
const definition = schema?.definition.definition;
const pageErrors = usePageErrors(context ?? {}, elements || []);
const isRevision = useMemo(
() => pageErrors.some(error => error.errors?.some(error => error.type === 'warning')),
[pageErrors],
);
const filteredNonEmptyErrors = pageErrors?.filter(pageError => !!pageError.errors.length);
// @ts-ignore
const initialContext: CollectionFlowContext | null = useMemo(() => {
const appState =
filteredNonEmptyErrors?.[0]?.stateName ||
context?.flowConfig?.appState ||
elements?.at(0)?.stateName;
if (!appState) return null;
return {
...context,
flowConfig: {
...context?.flowConfig,
appState,
},
state: appState,
};
}, [context, elements, filteredNonEmptyErrors]);
const initialUIState = useMemo(() => {
return prepareInitialUIState(elements || [], context || {}, isRevision);
}, [elements, context, isRevision]);
// Breadcrumbs now using scrollIntoView method to make sure that breadcrumb is always in viewport.
// Due to dynamic dimensions of logo it doesnt work well if scroll happens before logo is loaded.
// This workaround is needed to wait for logo to be loaded so scrollIntoView will work with correct dimensions of page.
const [isLogoLoaded, setLogoLoaded] = useState(!customer?.logoImageUri);
useEffect(() => {
if (!customer?.logoImageUri) return;
// Resseting loaded state in case of logo change
setLogoLoaded(false);
}, [customer?.logoImageUri]);
if (initialContext?.flowConfig?.appState === 'approved') return <Approved />;
if (initialContext?.flowConfig?.appState == 'rejected') return <Rejected />;
return definition && context ? (
<CustomerProviderPortable defaultCustomer={customer}>
<DynamicUI initialState={initialUIState}>
<DynamicUI.StateManager
initialContext={initialContext}
workflowId="1"
definitionType={schema?.definition.definitionType}
extensions={schema?.definition.extensions}
definition={definition as State}
>
{({ state, stateApi }) =>
state === 'finish' ? (
<Success />
) : (
<DynamicUI.PageResolver state={state} pages={elements ?? []}>
{({ currentPage }) => {
return currentPage ? (
<DynamicUI.Page page={currentPage}>
<DynamicUI.TransitionListener
onNext={async (tools, prevState) => {
tools.setElementCompleted(prevState, true);
set(
stateApi.getContext(),
`flowConfig.stepsProgress.${prevState}.isCompleted`,
true,
);
await stateApi.invokePlugin('sync_workflow_runtime');
}}
>
<DynamicUI.ActionsHandler actions={currentPage.actions} stateApi={stateApi}>
<AppShell>
<AppShell.Sidebar>
<div className="flex h-full flex-col">
<div className="flex h-full flex-1 flex-col">
<div className="flex flex-row justify-between gap-2 whitespace-nowrap pb-10">
<AppShell.Navigation />
<div>
<AppShell.LanguagePicker />
</div>
</div>
<div className="pb-10">
{customer?.logoImageUri && (
<AppShell.Logo
// @ts-ignore
logoSrc={customer?.logoImageUri}
// @ts-ignore
appName={customer?.displayName}
onLoad={() => setLogoLoaded(true)}
/>
)}
</div>
<div className="min-h-0 flex-1 pb-10">
{isLogoLoaded ? <StepperUI /> : null}
</div>
<div>
{customer?.displayName && (
<div className="border-b pb-12">
{
t('contact', {
companyName: customer.displayName,
}) as string
}
</div>
)}
<img src={'/poweredby.svg'} className="mt-6" />
</div>
</div>
</div>
</AppShell.Sidebar>
<AppShell.Content>
<AppShell.FormContainer>
{localStorage.getItem('devmode') ? (
<div className="flex flex-col gap-4">
DEBUG
<div>
{currentPage
? currentPage.stateName
: 'Page not found and state ' + state}
</div>
<div className="flex gap-4">
<button onClick={() => stateApi.sendEvent('PREVIOUS')}>
prev
</button>
<button onClick={() => stateApi.sendEvent('NEXT')}>
next
</button>
</div>
</div>
) : null}
<div className="flex flex-col">
<div className="flex items-center gap-3 pb-3">
<StepperProgress
currentStep={
(elements?.findIndex(page => page?.stateName === state) ??
0) + 1
}
totalSteps={elements?.length ?? 0}
/>
<ProgressBar />
</div>
<div>
<UIRenderer
elements={collectionFlowElements}
schema={currentPage.elements}
/>
</div>
</div>
</AppShell.FormContainer>
</AppShell.Content>
</AppShell>
</DynamicUI.ActionsHandler>
</DynamicUI.TransitionListener>
</DynamicUI.Page>
) : null;
}}
</DynamicUI.PageResolver>
)
}
</DynamicUI.StateManager>
</DynamicUI>
</CustomerProviderPortable>
) : null;
Tools
Biome

[error] 71-71: Unnecessary use of boolean literals in conditional expression.

Simplify your code by directly assigning the result without using a ternary operator.
If your goal is negation, you may use the logical NOT (!) or double NOT (!!) operator for clearer and concise code.
Check for more details about NOT operator.
Unsafe fix: Remove the conditional expression with

(lint/complexity/noUselessTernary)

};
27 changes: 27 additions & 0 deletions apps/kyb-app/src/lib/collection-flow-portable/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SettingsProvider } from '@/common/providers/SettingsProvider/SettingsProvider';
import { ThemeProvider } from '@/common/providers/ThemeProvider';
import { queryClient } from '@/common/utils/query-client';
import { Head } from '@/Head';
import {
CollectionFlowPortable,
ICollectionFlowPortableProps,
} from '@/lib/collection-flow-portable/CollectionFlowPortable';
import { QueryClientProvider } from '@tanstack/react-query';
import { FunctionComponent } from 'react';
import { HelmetProvider } from 'react-helmet-async';
import settingsJson from '../../../settings.json';

export const Main: FunctionComponent<ICollectionFlowPortableProps> = props => {
return (
<HelmetProvider>
<QueryClientProvider client={queryClient}>
<Head />
<SettingsProvider settings={settingsJson}>
<ThemeProvider theme={settingsJson.theme}>
<CollectionFlowPortable {...props} />
</ThemeProvider>
</SettingsProvider>
</QueryClientProvider>
</HelmetProvider>
);
};
Loading
Loading