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: reworked documentfield & integrated entityfield to kyb & update… #3040

Merged
merged 1 commit into from
Feb 9, 2025
Merged
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
3 changes: 3 additions & 0 deletions apps/kyb-app/src/domains/collection-flow/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ export interface UISchema {
};
uiOptions?: UIOptions;
version: number;
metadata: {
businessId: string;
}
}

export * from './ui-schema.types';
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,29 @@ export const CollectionFlowV2 = withSessionProtected(() => {
const [isLogoLoaded, setLogoLoaded] = useState(customer?.logoImageUri ? false : true);

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

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

if (getCollectionFlowState(initialContext)?.status === CollectionFlowStatusesEnum.approved)
if (getCollectionFlowState(initialContext)?.status === CollectionFlowStatusesEnum.approved) {
return <Approved />;
}

if (getCollectionFlowState(initialContext)?.status === CollectionFlowStatusesEnum.rejected)
if (getCollectionFlowState(initialContext)?.status === CollectionFlowStatusesEnum.rejected) {
return <Rejected />;
}

if (getCollectionFlowState(initialContext)?.status === CollectionFlowStatusesEnum.completed)
if (getCollectionFlowState(initialContext)?.status === CollectionFlowStatusesEnum.completed) {
return <CompletedScreen />;
}

if (getCollectionFlowState(initialContext)?.status === CollectionFlowStatusesEnum.failed)
if (getCollectionFlowState(initialContext)?.status === CollectionFlowStatusesEnum.failed) {
return <FailedScreen />;
}

return definition && collectionFlowData ? (
<DynamicUI initialState={initialUIState}>
Expand All @@ -119,11 +125,17 @@ export const CollectionFlowV2 = withSessionProtected(() => {
>
{() => {
// Temp state, has to be resolved to success or failure by plugins
if (state === 'done') return <LoadingScreen />;
if (state === 'done') {
return <LoadingScreen />;
}

if (isCompleted(state)) return <CompletedScreen />;
if (isCompleted(state)) {
return <CompletedScreen />;
}

if (isFailed(state)) return <FailedScreen />;
if (isFailed(state)) {
return <FailedScreen />;
}

return (
<DynamicUI.PageResolver
Expand Down Expand Up @@ -251,6 +263,7 @@ export const CollectionFlowV2 = withSessionProtected(() => {
}
context={payload}
isRevision={isRevision}
metadata={schema?.metadata}
/>
</PluginsRunner>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './validator';

import { useDynamicUIContext } from '@/components/organisms/DynamicUI/hooks/useDynamicUIContext';
import { useStateManagerContext } from '@/components/organisms/DynamicUI/StateManager/components/StateProvider/hooks/useStateManagerContext';
import { UISchema } from '@/domains/collection-flow';
import { CollectionFlowContext } from '@/domains/collection-flow/types/flow-context.types';
import { DynamicFormV2, IDynamicFormValidationParams, IFormElement, IFormRef } from '@ballerine/ui';
import { FunctionComponent, useCallback, useMemo, useRef } from 'react';
Expand All @@ -18,6 +19,7 @@ interface ICollectionFlowUIProps<TValues = CollectionFlowContext> {
elements: Array<IFormElement<any, any>>;
context: TValues;
isRevision?: boolean;
metadata: UISchema['metadata'];
}

const validationParams: IDynamicFormValidationParams = {
Expand All @@ -32,6 +34,7 @@ export const CollectionFlowUI: FunctionComponent<ICollectionFlowUIProps> = ({
elements,
context,
isRevision,
metadata: _uiSchemaMetadata,
}) => {
const { stateApi } = useStateManagerContext();
const { helpers } = useDynamicUIContext();
Expand Down Expand Up @@ -60,8 +63,9 @@ export const CollectionFlowUI: FunctionComponent<ICollectionFlowUIProps> = ({
_appState: {
isSyncing,
},
..._uiSchemaMetadata,
}),
[appMetadata, pluginStatuses, isSyncing],
[appMetadata, pluginStatuses, isSyncing, _uiSchemaMetadata],
);

const handleChange = useCallback(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { AnyObject, ctw } from '@/common';
import { IHttpParams } from '@/common/hooks/useHttp';
import { IHttpParams, useHttp } from '@/common/hooks/useHttp';
import { Button } from '@/components/atoms';
import { Input } from '@/components/atoms/Input';
import { createTestId } from '@/components/organisms/Renderer/utils/create-test-id';
import { Upload, XCircle } from 'lucide-react';
import { useCallback, useMemo, useRef } from 'react';
import { useDynamicForm } from '../../context';
import { useElement, useField } from '../../hooks/external';
import { useMountEvent } from '../../hooks/internal/useMountEvent';
import { useUnmountEvent } from '../../hooks/internal/useUnmountEvent';
Expand Down Expand Up @@ -48,8 +49,16 @@ export interface IDocumentFieldParams extends IFileFieldParams {
export const DOCUMENT_FIELD_TYPE = 'documentfield';

export const DocumentField: TDynamicFormField<IDocumentFieldParams> = ({ element }) => {
const { metadata } = useDynamicForm();

useMountEvent(element);
useUnmountEvent(element);

const { run: deleteDocument, isLoading: isDeletingDocument } = useHttp(
(element.params?.httpParams?.deleteDocument || {}) as IHttpParams,
metadata,
);

const { handleChange, isUploading: disabledWhileUploading } = useDocumentUpload(
element as IFormElement<'documentfield', IDocumentFieldParams>,
element.params || ({} as IDocumentFieldParams),
Expand Down Expand Up @@ -95,7 +104,7 @@ export const DocumentField: TDynamicFormField<IDocumentFieldParams> = ({ element
return undefined;
}, [value]);

const clearFileAndInput = useCallback(() => {
const clearFileAndInput = useCallback(async () => {
if (!element.params?.template?.id) {
console.warn('Template id is migging in element', element);

Expand All @@ -107,20 +116,29 @@ export const DocumentField: TDynamicFormField<IDocumentFieldParams> = ({ element
element.params?.template?.id as string,
);

const documentId = value;

if (typeof documentId === 'string') {
await deleteDocument({ ids: [documentId] });
}

onChange(updatedDocuments);
removeTask(id);

if (inputRef.current) {
inputRef.current.value = '';
}
}, [documentsList, element, onChange, id, removeTask]);
}, [documentsList, element, onChange, id, removeTask, value, deleteDocument]);

return (
<FieldLayout element={element}>
<div
className={ctw(
'relative flex h-[56px] flex-row items-center gap-3 rounded-[16px] border bg-white px-4',
{ 'pointer-events-none opacity-50': disabled || disabledWhileUploading },
{
'pointer-events-none opacity-50':
disabled || disabledWhileUploading || isDeletingDocument,
},
)}
onClick={focusInputOnContainerClick}
data-testid={createTestId(element, stack)}
Expand All @@ -138,9 +156,9 @@ export const DocumentField: TDynamicFormField<IDocumentFieldParams> = ({ element
variant="ghost"
size="icon"
className="h-[28px] w-[28px] rounded-full"
onClick={e => {
onClick={async e => {
e.stopPropagation();
clearFileAndInput();
await clearFileAndInput();
}}
>
<div className="rounded-full bg-white">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { AnyObject } from '@/common';
import { IHttpParams, useHttp } from '@/common/hooks/useHttp';
import get from 'lodash/get';
import set from 'lodash/set';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import { useDynamicForm } from '../../../../context';
import { uploadFile } from '../../../../helpers/upload-file';
import { useElement, useField } from '../../../../hooks/external';
import { useTaskRunner } from '../../../../providers/TaskRunner/hooks/useTaskRunner';
import { ITask } from '../../../../providers/TaskRunner/types';
import { IFormElement } from '../../../../types';
import { formatHeaders } from '../../../../utils/format-headers';
import { formatString } from '../../../../utils/format-string';
import { useStack } from '../../../FieldList/providers/StackProvider';
import { IDocumentFieldParams } from '../../DocumentField';
import { buildDocumentFormData } from '../../helpers/build-document-form-data';
import { createOrUpdateFileIdOrFileInDocuments } from './helpers/create-or-update-fileid-or-file-in-documents';

export const useDocumentUpload = (
Expand All @@ -22,8 +21,11 @@ export const useDocumentUpload = (
const { stack } = useStack();
const { id } = useElement(element, stack);
const { addTask, removeTask } = useTaskRunner();
const [isUploading, setIsUploading] = useState(false);
const { metadata, values } = useDynamicForm();
const { run: uploadDocument, isLoading: isUploading } = useHttp(
(element.params?.httpParams?.createDocument || {}) as IHttpParams,
metadata,
);

const { onChange } = useField(element, stack);

Expand All @@ -37,29 +39,29 @@ export const useDocumentUpload = (
async (e: React.ChangeEvent<HTMLInputElement>) => {
removeTask(id);

const { uploadSettings } = params;
const { createDocument } = params?.httpParams || {};

if (!uploadSettings) {
if (!createDocument) {
console.warn('Upload settings are missing on element', element, 'Upload will be skipped.');

return;
}

const uploadParams = {
...uploadSettings,
method: uploadSettings?.method || 'POST',
headers: formatHeaders(uploadSettings?.headers || {}, metadata),
url: formatString(uploadSettings?.url || '', metadata),
};
if (!metadata.entityId) {
console.warn('Entity ID is missing on element', element, 'Upload will be skipped.');

return;
}

const documentUploadPayload = buildDocumentFormData(
element,
{ businessId: metadata.businessId as string },
e.target?.files?.[0] as File,
);

if (uploadOn === 'change') {
try {
setIsUploading(true);

const result = await uploadFile(
e.target?.files?.[0] as File,
uploadParams as IDocumentFieldParams['uploadSettings'],
);
const result = await uploadDocument(documentUploadPayload);

const documents = get(valuesRef.current, element.valueDestination);
const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
Expand All @@ -70,8 +72,6 @@ export const useDocumentUpload = (
onChange(updatedDocuments);
} catch (error) {
console.error('Failed to upload file.', error);
} finally {
setIsUploading(false);
}
}

Expand All @@ -89,11 +89,7 @@ export const useDocumentUpload = (
try {
const documents = get(context, element.valueDestination);

setIsUploading(true);
const result = await uploadFile(
e.target?.files?.[0] as File,
uploadParams as IDocumentFieldParams['uploadSettings'],
);
const result = await uploadDocument(documentUploadPayload);

const updatedDocuments = createOrUpdateFileIdOrFileInDocuments(
documents,
Expand All @@ -108,8 +104,6 @@ export const useDocumentUpload = (
console.error('Failed to upload file.', error, element);

return context;
} finally {
setIsUploading(false);
}
};

Expand All @@ -121,7 +115,18 @@ export const useDocumentUpload = (
addTask(task);
}
},
[uploadOn, params, metadata, addTask, removeTask, onChange, id, element, valuesRef],
[
uploadOn,
params,
metadata,
addTask,
removeTask,
onChange,
uploadDocument,
id,
element,
valuesRef,
],
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const ubosSchema: Array<IFormElement<any, any>> = [
resultPath: 'id',
},
deleteDocument: {
url: '{apiUrl}collection-flow/files/{documentId}',
url: '{apiUrl}collection-flow/files',
method: 'DELETE',
headers: {
Authorization: 'Bearer {token}',
Expand Down Expand Up @@ -490,7 +490,6 @@ const initialUbosContext = {
const metadata = {
apiUrl: 'http://localhost:3000/api/v1/',
token: 'e3a69aa3-c1ad-42f3-87ac-5105cff81a94',
workflowId: 'cm6ufmpme0004tl7sqoxwlah4',
};

export const UbosFieldGroup = () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AnyObject } from '@/common';
import { useHttp } from '@/common/hooks/useHttp';
import { Button } from '@/components/atoms';
import { formatValueDestination, TDeepthLevelStack } from '@/components/organisms/Form/Validator';
Expand Down Expand Up @@ -67,7 +68,16 @@ export const EntityFields: FunctionComponent<IEntityFieldsProps> = ({

const entitiesDestination = formatValueDestination(element.valueDestination, stack);

const createEntityPayload = await buildEntityCreationPayload(element, entity, context);
let createEntityPayload: AnyObject;

try {
createEntityPayload = await buildEntityCreationPayload(element, entity, context);
} catch (error) {
console.error(error);
toast.error('Failed to build entity creation payload.');
setIsCreatingEntity(false);
throw error;
}

let createdEntityId: string;

Expand Down
Loading
Loading