Skip to content

Commit

Permalink
feat: reworked documentfield & integrated entityfield to kyb & update…
Browse files Browse the repository at this point in the history
…d enpoints (#3040)
  • Loading branch information
chesterkmr authored Feb 9, 2025
1 parent 4b381ba commit 55817e3
Show file tree
Hide file tree
Showing 18 changed files with 161 additions and 378 deletions.
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}',

Check failure

Code scanning / CodeQL

Hard-coded credentials Critical

The hard-coded value "Bearer {token}" is used as
authorization header
.
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

0 comments on commit 55817e3

Please sign in to comment.