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(webapp): import resource UI #386

Merged
merged 4 commits into from
Mar 21, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class ImportController extends BaseController {
this.asyncMiddleware(this.mapping.bind(this)),
this.catchServiceErrors
);
router.post(
router.get(
'/:import_id/preview',
this.asyncMiddleware(this.preview.bind(this)),
this.catchServiceErrors
Expand Down
13 changes: 7 additions & 6 deletions packages/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",
"@tiptap/core": "2.1.13",
"@tiptap/extension-color": "latest",
"@tiptap/extension-list-item": "2.1.13",
"@tiptap/extension-text-style": "2.1.13",
"@tiptap/core": "2.1.13",
"@tiptap/pm": "2.1.13",
"@tiptap/extension-list-item": "2.1.13",
"@tiptap/react": "2.1.13",
"@tiptap/starter-kit": "2.1.13",
"@types/jest": "^26.0.15",
Expand All @@ -38,9 +38,9 @@
"@types/react-redux": "^7.1.24",
"@types/react-router-dom": "^5.3.3",
"@types/react-transition-group": "^4.4.5",
"@types/socket.io-client": "^3.0.0",
"@types/styled-components": "^5.1.25",
"@types/yup": "^0.29.13",
"@types/socket.io-client": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^2.10.0",
"@typescript-eslint/parser": "^2.10.0",
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
Expand Down Expand Up @@ -69,10 +69,9 @@
"moment": "^2.24.0",
"moment-timezone": "^0.5.33",
"path-browserify": "^1.0.1",
"prop-types": "15.8.1",
"plaid": "^9.3.0",
"plaid-threads": "^11.4.3",
"react-plaid-link": "^3.2.1",
"prop-types": "15.8.1",
"query-string": "^7.1.1",
"ramda": "^0.27.1",
"react": "^18.2.0",
Expand All @@ -82,11 +81,13 @@
"react-dev-utils": "^11.0.4",
"react-dom": "^18.2.0",
"react-dropzone": "^11.0.1",
"react-dropzone-esm": "^15.0.1",
"react-error-boundary": "^3.0.2",
"react-error-overlay": "^6.0.9",
"react-hotkeys-hook": "^3.0.3",
"react-intl-universal": "^2.4.7",
"react-loadable": "^5.5.0",
"react-plaid-link": "^3.2.1",
"react-query": "^3.6.0",
"react-query-devtools": "^2.1.1",
"react-redux": "^7.2.9",
Expand All @@ -112,10 +113,10 @@
"rtl-detect": "^1.0.3",
"sass": "^1.68.0",
"semver": "6.3.0",
"socket.io-client": "^4.7.4",
"style-loader": "0.23.1",
"styled-components": "^5.3.1",
"stylis-rtlcss": "^2.1.1",
"socket.io-client": "^4.7.4",
"typescript": "^4.8.3",
"yup": "^0.28.1"
},
Expand Down
12 changes: 12 additions & 0 deletions packages/webapp/src/components/Dropzone/Dropzone.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@


.root {
padding: 20px;
border: 2px dotted #c5cbd3;
border-radius: 6px;
min-height: 200px;
display: flex;
flex-direction: column;
background: #fff;
position: relative;
}
291 changes: 291 additions & 0 deletions packages/webapp/src/components/Dropzone/Dropzone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
// @ts-nocheck
import React from 'react';
import { Ref, useCallback } from 'react';
import clsx from 'classnames';
import {
Accept,
DropEvent,
FileError,
FileRejection,
FileWithPath,
useDropzone,
} from 'react-dropzone-esm';
import { DropzoneProvider } from './DropzoneProvider';
import { DropzoneAccept, DropzoneIdle, DropzoneReject } from './DropzoneStatus';
import { Box } from '../Layout';
import styles from './Dropzone.module.css';
import { CloudLoadingIndicator } from '../Indicator';

export type DropzoneStylesNames = 'root' | 'inner';
export type DropzoneVariant = 'filled' | 'light';
export type DropzoneCssVariables = {
root:
| '--dropzone-radius'
| '--dropzone-accept-color'
| '--dropzone-accept-bg'
| '--dropzone-reject-color'
| '--dropzone-reject-bg';
};

export interface DropzoneProps {
/** Key of `theme.colors` or any valid CSS color to set colors of `Dropzone.Accept`, `theme.primaryColor` by default */
acceptColor?: MantineColor;

/** Key of `theme.colors` or any valid CSS color to set colors of `Dropzone.Reject`, `'red'` by default */
rejectColor?: MantineColor;

/** Key of `theme.radius` or any valid CSS value to set `border-radius`, numbers are converted to rem, `theme.defaultRadius` by default */
radius?: MantineRadius;

/** Determines whether files capturing should be disabled, `false` by default */
disabled?: boolean;

/** Called when any files are dropped to the dropzone */
onDropAny?: (files: FileWithPath[], fileRejections: FileRejection[]) => void;

/** Called when valid files are dropped to the dropzone */
onDrop: (files: FileWithPath[]) => void;

/** Called when dropped files do not meet file restrictions */
onReject?: (fileRejections: FileRejection[]) => void;

/** Determines whether a loading overlay should be displayed over the dropzone, `false` by default */
loading?: boolean;

/** Mime types of the files that dropzone can accepts. By default, dropzone accepts all file types. */
accept?: Accept | string[];

/** A ref function which when called opens the file system file picker */
openRef?: React.ForwardedRef<() => void | undefined>;

/** Determines whether multiple files can be dropped to the dropzone or selected from file system picker, `true` by default */
multiple?: boolean;

/** Maximum file size in bytes */
maxSize?: number;

/** Name of the form control. Submitted with the form as part of a name/value pair. */
name?: string;

/** Maximum number of files that can be picked at once */
maxFiles?: number;

/** Set to autofocus the root element */
autoFocus?: boolean;

/** If `false`, disables click to open the native file selection dialog */
activateOnClick?: boolean;

/** If `false`, disables drag 'n' drop */
activateOnDrag?: boolean;

/** If `false`, disables Space/Enter to open the native file selection dialog. Note that it also stops tracking the focus state. */
activateOnKeyboard?: boolean;

/** If `false`, stops drag event propagation to parents */
dragEventsBubbling?: boolean;

/** Called when the `dragenter` event occurs */
onDragEnter?: (event: React.DragEvent<HTMLElement>) => void;

/** Called when the `dragleave` event occurs */
onDragLeave?: (event: React.DragEvent<HTMLElement>) => void;

/** Called when the `dragover` event occurs */
onDragOver?: (event: React.DragEvent<HTMLElement>) => void;

/** Called when user closes the file selection dialog with no selection */
onFileDialogCancel?: () => void;

/** Called when user opens the file selection dialog */
onFileDialogOpen?: () => void;

/** If `false`, allow dropped items to take over the current browser window */
preventDropOnDocument?: boolean;

/** Set to true to use the File System Access API to open the file picker instead of using an <input type="file"> click event, defaults to true */
useFsAccessApi?: boolean;

/** Use this to provide a custom file aggregator */
getFilesFromEvent?: (
event: DropEvent,
) => Promise<Array<File | DataTransferItem>>;

/** Custom validation function. It must return null if there's no errors. */
validator?: <T extends File>(file: T) => FileError | FileError[] | null;

/** Determines whether pointer events should be enabled on the inner element, `false` by default */
enablePointerEvents?: boolean;

/** Props passed down to the Loader component */
loaderProps?: LoaderProps;

/** Props passed down to the internal Input component */
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
}

export type DropzoneFactory = Factory<{
props: DropzoneProps;
ref: HTMLDivElement;
stylesNames: DropzoneStylesNames;
vars: DropzoneCssVariables;
staticComponents: {
Accept: typeof DropzoneAccept;
Idle: typeof DropzoneIdle;
Reject: typeof DropzoneReject;
};
}>;

const defaultProps: Partial<DropzoneProps> = {
loading: false,
multiple: true,
maxSize: Infinity,
autoFocus: false,
activateOnClick: true,
activateOnDrag: true,
dragEventsBubbling: true,
activateOnKeyboard: true,
useFsAccessApi: true,
variant: 'light',
rejectColor: 'red',
};

export const Dropzone = (_props: DropzoneProps) => {
const {
// classNames,
// className,
// style,
// styles,
// unstyled,
// vars,
radius,
disabled,
loading,
multiple,
maxSize,
accept,
children,
onDropAny,
onDrop,
onReject,
openRef,
name,
maxFiles,
autoFocus,
activateOnClick,
activateOnDrag,
dragEventsBubbling,
activateOnKeyboard,
onDragEnter,
onDragLeave,
onDragOver,
onFileDialogCancel,
onFileDialogOpen,
preventDropOnDocument,
useFsAccessApi,
getFilesFromEvent,
validator,
rejectColor,
acceptColor,
enablePointerEvents,
loaderProps,
inputProps,
// mod,
classNames,
...others
} = {
...defaultProps,
..._props,
};

const { getRootProps, getInputProps, isDragAccept, isDragReject, open } =
useDropzone({
onDrop: onDropAny,
onDropAccepted: onDrop,
onDropRejected: onReject,
disabled: disabled || loading,
accept: Array.isArray(accept)
? accept.reduce((r, key) => ({ ...r, [key]: [] }), {})
: accept,
multiple,
maxSize,
maxFiles,
autoFocus,
noClick: !activateOnClick,
noDrag: !activateOnDrag,
noDragEventsBubbling: !dragEventsBubbling,
noKeyboard: !activateOnKeyboard,
onDragEnter,
onDragLeave,
onDragOver,
onFileDialogCancel,
onFileDialogOpen,
preventDropOnDocument,
useFsAccessApi,
validator,
...(getFilesFromEvent ? { getFilesFromEvent } : null),
});

const isIdle = !isDragAccept && !isDragReject;
assignRef(openRef, open);

return (
<DropzoneProvider
value={{ accept: isDragAccept, reject: isDragReject, idle: isIdle }}
>
<Box
{...getRootProps({
className: clsx(styles.root, classNames?.root),
})}
// {...getStyles('root', { focusable: true })}
{...others}
mod={[
{
accept: isDragAccept,
reject: isDragReject,
idle: isIdle,
loading,
'activate-on-click': activateOnClick,
},
// mod,
]}
>
<input {...getInputProps(inputProps)} name={name} />
<div
data-enable-pointer-events={enablePointerEvents || undefined}
className={classNames?.content}
>
{children}
</div>
</Box>
</DropzoneProvider>
);
};

Dropzone.displayName = '@mantine/dropzone/Dropzone';
Dropzone.Accept = DropzoneAccept;
Dropzone.Idle = DropzoneIdle;
Dropzone.Reject = DropzoneReject;




type PossibleRef<T> = Ref<T> | undefined;

export function assignRef<T>(ref: PossibleRef<T>, value: T) {
if (typeof ref === 'function') {
ref(value);
} else if (typeof ref === 'object' && ref !== null && 'current' in ref) {
(ref as React.MutableRefObject<T>).current = value;
}
}

export function mergeRefs<T>(...refs: PossibleRef<T>[]) {
return (node: T | null) => {
refs.forEach((ref) => assignRef(ref, node));
};
}

export function useMergedRef<T>(...refs: PossibleRef<T>[]) {
return useCallback(mergeRefs(...refs), refs);
}
12 changes: 12 additions & 0 deletions packages/webapp/src/components/Dropzone/DropzoneProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createSafeContext } from './create-safe-context';

export interface DropzoneContextValue {
idle: boolean;
accept: boolean;
reject: boolean;
}

export const [DropzoneProvider, useDropzoneContext] =
createSafeContext<DropzoneContextValue>(
'Dropzone component was not found in tree',
);
Loading
Loading