diff --git a/client/.flowconfig b/client/.flowconfig
new file mode 100644
index 000000000..1fed44533
--- /dev/null
+++ b/client/.flowconfig
@@ -0,0 +1,11 @@
+[ignore]
+
+[include]
+
+[libs]
+
+[lints]
+
+[options]
+
+[strict]
diff --git a/client/package.json b/client/package.json
index f5dd2ccb1..9de51b5ae 100644
--- a/client/package.json
+++ b/client/package.json
@@ -40,6 +40,7 @@
"eslint-plugin-react": "7.18.0",
"eslint-plugin-react-hooks": "^1.6.1",
"file-loader": "4.3.0",
+ "flow-bin": "^0.123.0",
"formik": "^2.1.4",
"fs-extra": "^8.1.0",
"html-webpack-plugin": "4.0.0-beta.11",
@@ -54,6 +55,7 @@
"moment": "^2.24.0",
"node-sass": "^4.13.1",
"optimize-css-assets-webpack-plugin": "5.0.3",
+ "p-progress": "^0.4.2",
"pnp-webpack-plugin": "1.6.0",
"postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0",
@@ -65,6 +67,7 @@
"react-body-classname": "^1.3.1",
"react-dev-utils": "^10.2.0",
"react-dom": "^16.12.0",
+ "react-dropzone": "^11.0.1",
"react-grid-system": "^6.2.3",
"react-hook-form": "^4.9.4",
"react-intl": "^3.12.0",
@@ -97,7 +100,8 @@
"scripts": {
"start": "PORT=8000 node scripts/start.js",
"build": "node scripts/build.js",
- "test": "node scripts/test.js"
+ "test": "node scripts/test.js",
+ "flow": "flow"
},
"eslintConfig": {
"extends": "react-app"
@@ -115,6 +119,7 @@
]
},
"devDependencies": {
+ "@babel/preset-flow": "^7.9.0",
"http-proxy-middleware": "^1.0.0",
"redux-devtools": "^3.5.0"
},
diff --git a/client/src/components/Dragzone.js b/client/src/components/Dragzone.js
new file mode 100644
index 000000000..d5331ef5f
--- /dev/null
+++ b/client/src/components/Dragzone.js
@@ -0,0 +1,77 @@
+import React, { useState, useMemo, useCallback, useEffect } from 'react'
+import { useDropzone } from 'react-dropzone'
+import { pullAt } from 'lodash';
+import classNames from 'classnames';
+import Icon from 'components/Icon';
+
+// const initialFile: {
+// file: ?File,
+// preview: string,
+// metadata: ?object,
+// uploaded: boolean,
+// };
+
+export default function Dropzone({
+ text = 'Drag/Drop files here or click here',
+ onDrop,
+ initialFiles = [],
+ onDeleteFile,
+ hint,
+ className,
+}) {
+ const [files, setFiles] = useState([]);
+
+ useEffect(() => {
+ setFiles([ ...initialFiles ]);
+ }, [initialFiles]);
+
+ const {getRootProps, getInputProps} = useDropzone({
+ accept: 'image/*',
+ onDrop: (acceptedFiles) => {
+ const _files = acceptedFiles.map((file) => ({
+ file,
+ preview: URL.createObjectURL(file),
+ uploaded: false,
+ }));
+ setFiles(_files);
+ }
+ });
+
+ const handleRemove = useCallback((index) => {
+ const deletedFile = files.splice(index, 1);
+ setFiles([...files]);
+ onDeleteFile && onDeleteFile(deletedFile);
+ }, [files, onDeleteFile]);
+
+ const thumbs = files.map((file, index) => (
+
+
+
+
+ ));
+
+ useEffect(() => () => {
+ files.forEach(file => URL.revokeObjectURL(file.preview));
+ }, [files, onDrop]);
+
+ useEffect(() => {
+ onDrop && onDrop(files);
+ }, [files, onDrop]);
+
+ return (
+
+ {(hint) && { hint }
}
+
+
+
+
+ { thumbs }
+
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/components/Items/ItemForm.js b/client/src/components/Items/ItemForm.js
index f9fa1ec46..38b109450 100644
--- a/client/src/components/Items/ItemForm.js
+++ b/client/src/components/Items/ItemForm.js
@@ -23,15 +23,34 @@ import Icon from 'components/Icon';
import ItemCategoryConnect from 'connectors/ItemsCategory.connect';
import MoneyInputGroup from 'components/MoneyInputGroup';
import {useHistory} from 'react-router-dom';
+import Dragzone from 'components/Dragzone';
+import MediaConnect from 'connectors/Media.connect';
+import useMedia from 'hooks/useMedia';
const ItemForm = ({
requestSubmitItem,
+
accounts,
categories,
+
+ requestSubmitMedia,
+ requestDeleteMedia,
}) => {
const [selectedAccounts, setSelectedAccounts] = useState({});
const history = useHistory();
+ const {
+ files,
+ setFiles,
+ saveMedia,
+ deletedFiles,
+ setDeletedFiles,
+ deleteMedia,
+ } = useMedia({
+ saveCallback: requestSubmitMedia,
+ deleteCallback: requestDeleteMedia,
+ })
+
const ItemTypeDisplay = useMemo(() => ([
{ value: null, label: 'Select Item Type' },
{ value: 'service', label: 'Service' },
@@ -85,15 +104,27 @@ const ItemForm = ({
...initialValues,
},
onSubmit: (values, { setSubmitting }) => {
- requestSubmitItem(values).then((response) => {
- AppToaster.show({
- message: 'The_Items_has_been_Submit'
+ const saveItem = (mediaIds) => {
+ const formValues = { ...values, media_ids: mediaIds };
+
+ return requestSubmitItem(formValues).then((response) => {
+ AppToaster.show({
+ message: 'The_Items_has_been_submit'
+ });
+ setSubmitting(false);
+ history.push('/dashboard/items');
+ })
+ .catch((error) => {
+ setSubmitting(false);
});
- setSubmitting(false);
- history.push('/dashboard/items');
- })
- .catch((error) => {
- setSubmitting(false);
+ };
+
+ Promise.all([
+ saveMedia(),
+ deleteMedia(),
+ ]).then(([savedMediaResponses]) => {
+ const mediaIds = savedMediaResponses.map(res => res.data.media.id);
+ return saveItem(mediaIds);
});
}
});
@@ -139,95 +170,126 @@ const ItemForm = ({
setFieldValue(fieldKey, value);
};
+ const initialAttachmentFiles = useMemo(() => {
+ return [];
+ }, []);
+
+ const handleDropFiles = useCallback((_files) => {
+ setFiles(_files.filter((file) => file.uploaded === false));
+ }, []);
+
+ const handleDeleteFile = useCallback((_deletedFiles) => {
+ _deletedFiles.forEach((deletedFile) => {
+ if (deletedFile.uploaded && deletedFile.metadata.id) {
+ setDeletedFiles([
+ ...deletedFiles, deletedFile.metadata.id,
+ ]);
+ }
+ });
+ }, [setDeletedFiles, deletedFiles]);
+
return (