@bosonprotocol/react-kit 0.32.0-alpha.9 → 0.33.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/cjs/components/error/SimpleError.d.ts +7 -5
  2. package/dist/cjs/components/error/SimpleError.d.ts.map +1 -1
  3. package/dist/cjs/components/error/SimpleError.js +4 -4
  4. package/dist/cjs/components/error/SimpleError.js.map +1 -1
  5. package/dist/cjs/components/form/BaseInput.js +1 -1
  6. package/dist/cjs/components/form/BaseInput.js.map +1 -1
  7. package/dist/cjs/components/form/BaseTagsInput.d.ts.map +1 -1
  8. package/dist/cjs/components/form/BaseTagsInput.js +1 -1
  9. package/dist/cjs/components/form/BaseTagsInput.js.map +1 -1
  10. package/dist/cjs/components/form/BaseTextArea.d.ts.map +1 -1
  11. package/dist/cjs/components/form/BaseTextArea.js +1 -1
  12. package/dist/cjs/components/form/BaseTextArea.js.map +1 -1
  13. package/dist/cjs/components/form/Checkbox.d.ts.map +1 -1
  14. package/dist/cjs/components/form/Checkbox.js +1 -1
  15. package/dist/cjs/components/form/Checkbox.js.map +1 -1
  16. package/dist/cjs/components/form/CountrySelect.d.ts +16 -3
  17. package/dist/cjs/components/form/CountrySelect.d.ts.map +1 -1
  18. package/dist/cjs/components/form/CountrySelect.js +26 -18
  19. package/dist/cjs/components/form/CountrySelect.js.map +1 -1
  20. package/dist/cjs/components/form/Datepicker.d.ts.map +1 -1
  21. package/dist/cjs/components/form/Datepicker.js +1 -1
  22. package/dist/cjs/components/form/Datepicker.js.map +1 -1
  23. package/dist/cjs/components/form/Field.styles.d.ts +0 -1
  24. package/dist/cjs/components/form/Field.styles.d.ts.map +1 -1
  25. package/dist/cjs/components/form/Field.styles.js.map +1 -1
  26. package/dist/cjs/components/form/FormField.d.ts +1 -1
  27. package/dist/cjs/components/form/FormField.d.ts.map +1 -1
  28. package/dist/cjs/components/form/FormField.js +5 -3
  29. package/dist/cjs/components/form/FormField.js.map +1 -1
  30. package/dist/cjs/components/form/InputColor.d.ts.map +1 -1
  31. package/dist/cjs/components/form/InputColor.js +1 -1
  32. package/dist/cjs/components/form/InputColor.js.map +1 -1
  33. package/dist/cjs/components/form/Phone.d.ts.map +1 -1
  34. package/dist/cjs/components/form/Phone.js +1 -1
  35. package/dist/cjs/components/form/Phone.js.map +1 -1
  36. package/dist/cjs/components/form/Select.d.ts.map +1 -1
  37. package/dist/cjs/components/form/Select.js +1 -2
  38. package/dist/cjs/components/form/Select.js.map +1 -1
  39. package/dist/cjs/components/form/Upload/ImageEditorModal/ImageEditor.d.ts +9 -0
  40. package/dist/cjs/components/form/Upload/ImageEditorModal/ImageEditor.d.ts.map +1 -0
  41. package/dist/cjs/components/form/Upload/ImageEditorModal/ImageEditor.js +65 -0
  42. package/dist/cjs/components/form/Upload/ImageEditorModal/ImageEditor.js.map +1 -0
  43. package/dist/cjs/components/form/Upload/ImageEditorModal/ImageEditorModal.d.ts +10 -0
  44. package/dist/cjs/components/form/Upload/ImageEditorModal/ImageEditorModal.d.ts.map +1 -0
  45. package/dist/cjs/components/form/Upload/ImageEditorModal/ImageEditorModal.js +100 -0
  46. package/dist/cjs/components/form/Upload/ImageEditorModal/ImageEditorModal.js.map +1 -0
  47. package/dist/cjs/components/form/Upload/Upload.d.ts +39 -2
  48. package/dist/cjs/components/form/Upload/Upload.d.ts.map +1 -1
  49. package/dist/cjs/components/form/Upload/Upload.js +80 -19
  50. package/dist/cjs/components/form/Upload/Upload.js.map +1 -1
  51. package/dist/cjs/components/form/Upload/WithUploadToIpfs.d.ts +1 -1
  52. package/dist/cjs/components/form/Upload/WithUploadToIpfs.d.ts.map +1 -1
  53. package/dist/cjs/components/form/Upload/WithUploadToIpfs.js +9 -7
  54. package/dist/cjs/components/form/Upload/WithUploadToIpfs.js.map +1 -1
  55. package/dist/cjs/components/form/index.d.ts +2 -1
  56. package/dist/cjs/components/form/index.d.ts.map +1 -1
  57. package/dist/cjs/components/form/index.js +2 -1
  58. package/dist/cjs/components/form/index.js.map +1 -1
  59. package/dist/cjs/components/form/types.d.ts +17 -4
  60. package/dist/cjs/components/form/types.d.ts.map +1 -1
  61. package/dist/cjs/components/modal/ModalComponents.d.ts +2 -0
  62. package/dist/cjs/components/modal/ModalComponents.d.ts.map +1 -1
  63. package/dist/cjs/components/modal/ModalComponents.js +3 -1
  64. package/dist/cjs/components/modal/ModalComponents.js.map +1 -1
  65. package/dist/cjs/components/modal/ModalContext.d.ts +2 -1
  66. package/dist/cjs/components/modal/ModalContext.d.ts.map +1 -1
  67. package/dist/cjs/components/modal/ModalContext.js.map +1 -1
  68. package/dist/cjs/components/modal/ModalTypes.d.ts +1 -0
  69. package/dist/cjs/components/modal/ModalTypes.d.ts.map +1 -1
  70. package/dist/cjs/components/modal/ModalTypes.js +2 -1
  71. package/dist/cjs/components/modal/ModalTypes.js.map +1 -1
  72. package/dist/cjs/components/modal/useModal.d.ts +3 -2
  73. package/dist/cjs/components/modal/useModal.d.ts.map +1 -1
  74. package/dist/cjs/hooks/images/useFileImage.d.ts +8 -0
  75. package/dist/cjs/hooks/images/useFileImage.d.ts.map +1 -0
  76. package/dist/cjs/hooks/images/useFileImage.js +26 -0
  77. package/dist/cjs/hooks/images/useFileImage.js.map +1 -0
  78. package/dist/cjs/hooks/images/useIpfsImage.d.ts +13 -0
  79. package/dist/cjs/hooks/images/useIpfsImage.d.ts.map +1 -0
  80. package/dist/cjs/hooks/images/useIpfsImage.js +26 -0
  81. package/dist/cjs/hooks/images/useIpfsImage.js.map +1 -0
  82. package/dist/cjs/index.d.ts +1 -0
  83. package/dist/cjs/index.d.ts.map +1 -1
  84. package/dist/cjs/index.js +1 -0
  85. package/dist/cjs/index.js.map +1 -1
  86. package/dist/cjs/lib/base64/base64.d.ts +4 -1
  87. package/dist/cjs/lib/base64/base64.d.ts.map +1 -1
  88. package/dist/cjs/lib/base64/base64.js +1 -1
  89. package/dist/cjs/lib/base64/base64.js.map +1 -1
  90. package/dist/esm/components/error/SimpleError.d.ts +7 -5
  91. package/dist/esm/components/error/SimpleError.d.ts.map +1 -1
  92. package/dist/esm/components/error/SimpleError.js +4 -4
  93. package/dist/esm/components/error/SimpleError.js.map +1 -1
  94. package/dist/esm/components/form/BaseInput.js +1 -1
  95. package/dist/esm/components/form/BaseInput.js.map +1 -1
  96. package/dist/esm/components/form/BaseTagsInput.d.ts.map +1 -1
  97. package/dist/esm/components/form/BaseTagsInput.js +1 -1
  98. package/dist/esm/components/form/BaseTagsInput.js.map +1 -1
  99. package/dist/esm/components/form/BaseTextArea.d.ts.map +1 -1
  100. package/dist/esm/components/form/BaseTextArea.js +1 -1
  101. package/dist/esm/components/form/BaseTextArea.js.map +1 -1
  102. package/dist/esm/components/form/Checkbox.d.ts.map +1 -1
  103. package/dist/esm/components/form/Checkbox.js +1 -1
  104. package/dist/esm/components/form/Checkbox.js.map +1 -1
  105. package/dist/esm/components/form/CountrySelect.d.ts +16 -3
  106. package/dist/esm/components/form/CountrySelect.d.ts.map +1 -1
  107. package/dist/esm/components/form/CountrySelect.js +58 -30
  108. package/dist/esm/components/form/CountrySelect.js.map +1 -1
  109. package/dist/esm/components/form/Datepicker.d.ts.map +1 -1
  110. package/dist/esm/components/form/Datepicker.js +1 -1
  111. package/dist/esm/components/form/Datepicker.js.map +1 -1
  112. package/dist/esm/components/form/Field.styles.d.ts +0 -1
  113. package/dist/esm/components/form/Field.styles.d.ts.map +1 -1
  114. package/dist/esm/components/form/Field.styles.js.map +1 -1
  115. package/dist/esm/components/form/FormField.d.ts +1 -1
  116. package/dist/esm/components/form/FormField.d.ts.map +1 -1
  117. package/dist/esm/components/form/FormField.js +3 -2
  118. package/dist/esm/components/form/FormField.js.map +1 -1
  119. package/dist/esm/components/form/InputColor.d.ts.map +1 -1
  120. package/dist/esm/components/form/InputColor.js +1 -1
  121. package/dist/esm/components/form/InputColor.js.map +1 -1
  122. package/dist/esm/components/form/Phone.d.ts.map +1 -1
  123. package/dist/esm/components/form/Phone.js +1 -1
  124. package/dist/esm/components/form/Phone.js.map +1 -1
  125. package/dist/esm/components/form/Select.d.ts.map +1 -1
  126. package/dist/esm/components/form/Select.js +1 -2
  127. package/dist/esm/components/form/Select.js.map +1 -1
  128. package/dist/esm/components/form/Upload/ImageEditorModal/ImageEditor.d.ts +9 -0
  129. package/dist/esm/components/form/Upload/ImageEditorModal/ImageEditor.d.ts.map +1 -0
  130. package/dist/esm/components/form/Upload/ImageEditorModal/ImageEditor.js +36 -0
  131. package/dist/esm/components/form/Upload/ImageEditorModal/ImageEditor.js.map +1 -0
  132. package/dist/esm/components/form/Upload/ImageEditorModal/ImageEditorModal.d.ts +10 -0
  133. package/dist/esm/components/form/Upload/ImageEditorModal/ImageEditorModal.d.ts.map +1 -0
  134. package/dist/esm/components/form/Upload/ImageEditorModal/ImageEditorModal.js +48 -0
  135. package/dist/esm/components/form/Upload/ImageEditorModal/ImageEditorModal.js.map +1 -0
  136. package/dist/esm/components/form/Upload/Upload.d.ts +39 -2
  137. package/dist/esm/components/form/Upload/Upload.d.ts.map +1 -1
  138. package/dist/esm/components/form/Upload/Upload.js +92 -19
  139. package/dist/esm/components/form/Upload/Upload.js.map +1 -1
  140. package/dist/esm/components/form/Upload/WithUploadToIpfs.d.ts +1 -1
  141. package/dist/esm/components/form/Upload/WithUploadToIpfs.d.ts.map +1 -1
  142. package/dist/esm/components/form/Upload/WithUploadToIpfs.js +9 -7
  143. package/dist/esm/components/form/Upload/WithUploadToIpfs.js.map +1 -1
  144. package/dist/esm/components/form/index.d.ts +2 -1
  145. package/dist/esm/components/form/index.d.ts.map +1 -1
  146. package/dist/esm/components/form/index.js +2 -1
  147. package/dist/esm/components/form/index.js.map +1 -1
  148. package/dist/esm/components/form/types.d.ts +17 -4
  149. package/dist/esm/components/form/types.d.ts.map +1 -1
  150. package/dist/esm/components/modal/ModalComponents.d.ts +2 -0
  151. package/dist/esm/components/modal/ModalComponents.d.ts.map +1 -1
  152. package/dist/esm/components/modal/ModalComponents.js +3 -1
  153. package/dist/esm/components/modal/ModalComponents.js.map +1 -1
  154. package/dist/esm/components/modal/ModalContext.d.ts +2 -1
  155. package/dist/esm/components/modal/ModalContext.d.ts.map +1 -1
  156. package/dist/esm/components/modal/ModalContext.js.map +1 -1
  157. package/dist/esm/components/modal/ModalTypes.d.ts +1 -0
  158. package/dist/esm/components/modal/ModalTypes.d.ts.map +1 -1
  159. package/dist/esm/components/modal/ModalTypes.js +2 -1
  160. package/dist/esm/components/modal/ModalTypes.js.map +1 -1
  161. package/dist/esm/components/modal/useModal.d.ts +3 -2
  162. package/dist/esm/components/modal/useModal.d.ts.map +1 -1
  163. package/dist/esm/hooks/images/useFileImage.d.ts +8 -0
  164. package/dist/esm/hooks/images/useFileImage.d.ts.map +1 -0
  165. package/dist/esm/hooks/images/useFileImage.js +13 -0
  166. package/dist/esm/hooks/images/useFileImage.js.map +1 -0
  167. package/dist/esm/hooks/images/useIpfsImage.d.ts +13 -0
  168. package/dist/esm/hooks/images/useIpfsImage.d.ts.map +1 -0
  169. package/dist/esm/hooks/images/useIpfsImage.js +16 -0
  170. package/dist/esm/hooks/images/useIpfsImage.js.map +1 -0
  171. package/dist/esm/index.d.ts +1 -0
  172. package/dist/esm/index.d.ts.map +1 -1
  173. package/dist/esm/index.js +1 -0
  174. package/dist/esm/index.js.map +1 -1
  175. package/dist/esm/lib/base64/base64.d.ts +4 -1
  176. package/dist/esm/lib/base64/base64.d.ts.map +1 -1
  177. package/dist/esm/lib/base64/base64.js +1 -1
  178. package/dist/esm/lib/base64/base64.js.map +1 -1
  179. package/package.json +7 -4
  180. package/src/components/error/SimpleError.tsx +19 -10
  181. package/src/components/form/BaseInput.tsx +1 -1
  182. package/src/components/form/BaseTagsInput.tsx +1 -2
  183. package/src/components/form/BaseTextArea.tsx +1 -2
  184. package/src/components/form/Checkbox.tsx +1 -2
  185. package/src/components/form/CountrySelect.tsx +96 -43
  186. package/src/components/form/Datepicker.tsx +1 -2
  187. package/src/components/form/Field.styles.ts +1 -1
  188. package/src/components/form/FormField.tsx +6 -4
  189. package/src/components/form/InputColor.tsx +1 -2
  190. package/src/components/form/Phone.tsx +1 -2
  191. package/src/components/form/Select.tsx +1 -2
  192. package/src/components/form/Upload/ImageEditorModal/ImageEditor.tsx +74 -0
  193. package/src/components/form/Upload/ImageEditorModal/ImageEditorModal.tsx +77 -0
  194. package/src/components/form/Upload/Upload.tsx +136 -40
  195. package/src/components/form/Upload/WithUploadToIpfs.tsx +13 -11
  196. package/src/components/form/index.ts +2 -1
  197. package/src/components/form/types.ts +18 -3
  198. package/src/components/modal/ModalComponents.tsx +3 -1
  199. package/src/components/modal/ModalContext.tsx +4 -3
  200. package/src/components/modal/ModalTypes.ts +2 -1
  201. package/src/hooks/images/useFileImage.ts +24 -0
  202. package/src/hooks/images/useIpfsImage.ts +27 -0
  203. package/src/index.tsx +1 -0
  204. package/src/lib/base64/base64.ts +1 -1
  205. package/src/stories/buttons/Upload.stories.tsx +82 -0
  206. package/src/stories/selects/CountrySelect.stories.tsx +118 -0
@@ -0,0 +1,77 @@
1
+ import * as Sentry from "@sentry/browser";
2
+ import React, { useRef, useState } from "react";
3
+ import AvatarEditor from "react-avatar-editor";
4
+ import { dataURItoBlob } from "../../../../lib/base64/base64";
5
+ import Modal from "../../../modal/Modal";
6
+ import { Grid } from "../../../ui/Grid";
7
+ import { Spinner } from "../../../ui/loading/Spinner";
8
+ import { BaseButton, BaseButtonTheme } from "../../../buttons/BaseButton";
9
+ import { ImageEditor, ImageEditorProps } from "./ImageEditor";
10
+ import { useFileImage } from "../../../../hooks/images/useFileImage";
11
+
12
+ export type ImageEditorModalProps = Omit<ImageEditorProps, "url"> & {
13
+ files: File[] | null;
14
+ hideModal: (files?: File[]) => Promise<void>;
15
+ saveButtonTheme: BaseButtonTheme;
16
+ };
17
+
18
+ export const ImageEditorModal: React.FC<ImageEditorModalProps> = ({
19
+ hideModal,
20
+ files,
21
+ saveButtonTheme,
22
+ ...rest
23
+ }) => {
24
+ const originalFile = files?.[0];
25
+ const { data: url } = useFileImage(
26
+ { file: originalFile },
27
+ { enabled: !!originalFile }
28
+ );
29
+ const editorRef = useRef<AvatarEditor>(null);
30
+ const [isSaving, setSaving] = useState<boolean>(false);
31
+ const onClickSave = async () => {
32
+ setSaving(true);
33
+ try {
34
+ const img = editorRef.current?.getImageScaledToCanvas().toDataURL();
35
+ if (!img) {
36
+ return;
37
+ }
38
+ const blob = dataURItoBlob(img);
39
+ const file = new File([blob], originalFile?.name ?? "edited", {
40
+ type: originalFile?.type,
41
+ lastModified: originalFile?.lastModified
42
+ });
43
+ if (!file) {
44
+ return;
45
+ }
46
+ await hideModal([file]);
47
+ } catch (error) {
48
+ Sentry.captureException(error);
49
+ } finally {
50
+ setSaving(false);
51
+ }
52
+ };
53
+ return (
54
+ <Modal
55
+ modalType="IMAGE_EDITOR"
56
+ hideModal={() => hideModal()}
57
+ size="auto"
58
+ maxWidths={{
59
+ s: "50rem"
60
+ }}
61
+ theme="light"
62
+ >
63
+ <Grid flexDirection="column">
64
+ <ImageEditor url={url} {...rest} ref={editorRef} />
65
+ <BaseButton
66
+ type="button"
67
+ theme={saveButtonTheme}
68
+ onClick={() => onClickSave()}
69
+ disabled={isSaving}
70
+ >
71
+ {isSaving ? "Saving" : "Save"}
72
+ {isSaving && <Spinner />}
73
+ </BaseButton>
74
+ </Grid>
75
+ </Modal>
76
+ );
77
+ };
@@ -1,3 +1,4 @@
1
+ import * as Sentry from "@sentry/browser";
1
2
  import { useField } from "formik";
2
3
  import { Image, Trash, VideoCamera } from "phosphor-react";
3
4
  import React, { useCallback, useEffect, useRef, useState } from "react";
@@ -19,6 +20,8 @@ import {
19
20
  import type { UploadFileType, UploadProps, FileProps } from "../types";
20
21
  import UploadedFiles from "./UploadedFiles";
21
22
  import { WithUploadToIpfs, WithUploadToIpfsProps } from "./WithUploadToIpfs";
23
+ import { useModal } from "../../modal/useModal";
24
+ import { ImageEditorModal } from "./ImageEditorModal/ImageEditorModal";
22
25
  const colors = theme.colors.light;
23
26
 
24
27
  function Upload({
@@ -33,11 +36,21 @@ function Upload({
33
36
  wrapperProps,
34
37
  onLoadSinglePreviewImage,
35
38
  withUpload,
39
+ withEditor,
36
40
  saveToIpfs,
37
41
  loadMedia,
38
42
  onLoading,
43
+ width,
44
+ height,
45
+ borderRadius,
46
+ imgPreviewStyle,
47
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
48
+ removeFile,
49
+ saveButtonTheme,
39
50
  ...props
40
51
  }: UploadProps & WithUploadToIpfsProps) {
52
+ const { updateProps, store } = useModal();
53
+ const [showEditor, setShowEditor] = useState<boolean>(false);
41
54
  const [isLoading, setIsLoading] = useState<boolean>(false);
42
55
  const [preview, setPreview] = useState<string | null>();
43
56
  const [field, meta, helpers] = useField(name);
@@ -51,10 +64,10 @@ function Upload({
51
64
  );
52
65
 
53
66
  const errorMessage = meta.error && meta.touched ? meta.error : "";
54
- const displayError =
55
- typeof errorMessage === typeof "string" && errorMessage !== "";
67
+ const displayError = typeof errorMessage === "string" && errorMessage !== "";
56
68
 
57
69
  const inputRef = useRef<HTMLInputElement | null>(null);
70
+ const [nativeFiles, setNativeFiles] = useState<File[] | null>(null);
58
71
  const setFiles = useCallback(
59
72
  (value: unknown) => {
60
73
  helpers.setValue(value);
@@ -104,10 +117,17 @@ function Upload({
104
117
  }
105
118
  handleLoading(true);
106
119
  try {
107
- const imagePreview: string = await loadMedia(fileSrc || "");
108
- setPreview(imagePreview);
120
+ const imagePreview = await loadMedia(fileSrc || "");
121
+ if (imagePreview) {
122
+ setPreview(imagePreview);
123
+ } else {
124
+ console.warn(
125
+ `imagePreview ${imagePreview} is falsy in loadIpfsImagePreview`
126
+ );
127
+ }
109
128
  } catch (error) {
110
129
  console.error(error);
130
+ Sentry.captureException(error);
111
131
  } finally {
112
132
  handleLoading(false);
113
133
  }
@@ -120,11 +140,18 @@ function Upload({
120
140
  }
121
141
  try {
122
142
  handleLoading(true);
123
- const imagePreview: string = await loadMedia(fileSrc || "");
124
- setPreview(imagePreview);
125
- onLoadSinglePreviewImage?.(imagePreview);
143
+ const imagePreview = await loadMedia(fileSrc || "");
144
+ if (imagePreview) {
145
+ setPreview(imagePreview);
146
+ onLoadSinglePreviewImage?.(imagePreview);
147
+ } else {
148
+ console.warn(
149
+ `imagePreview ${imagePreview} is falsy in loadIpfsImagePreview`
150
+ );
151
+ }
126
152
  } catch (error) {
127
153
  console.error(error);
154
+ Sentry.captureException(error);
128
155
  } finally {
129
156
  handleLoading(false);
130
157
  }
@@ -152,41 +179,83 @@ function Upload({
152
179
  setFiles(newArray);
153
180
  };
154
181
 
155
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
156
- if (!meta.touched) {
157
- helpers.setTouched(true);
158
- }
182
+ const handleChange = useCallback(
183
+ async (filesArray: File[] | null) => {
184
+ if (!meta.touched) {
185
+ helpers.setTouched(true);
186
+ }
159
187
 
160
- if (!e.target.files) {
161
- return;
162
- }
163
- const { files } = e.target;
164
- const filesArray = Object.values(files);
165
- for (const file of filesArray) {
166
- if (maxSize) {
167
- if (file.size > maxSize) {
168
- const error = `File size cannot exceed more than ${bytesToSize(
169
- maxSize
170
- )}`;
171
- // TODO: change to notification
172
- console.error(error);
188
+ if (!filesArray) {
189
+ return;
190
+ }
191
+ for (const file of filesArray) {
192
+ if (maxSize) {
193
+ if (file.size > maxSize) {
194
+ const error = `File size cannot exceed more than ${bytesToSize(
195
+ maxSize
196
+ )}`;
197
+ // TODO: change to notification
198
+ console.error(error);
199
+ }
173
200
  }
174
201
  }
175
- }
176
- setFiles(filesArray);
177
- };
202
+ setFiles(filesArray);
203
+ },
204
+ [helpers, maxSize, meta.touched, setFiles]
205
+ );
178
206
 
179
207
  const handleSave = useCallback(
180
- async (e: React.ChangeEvent<HTMLInputElement>) => {
208
+ async (efiles: File[] | null) => {
209
+ if (!meta.touched) {
210
+ helpers.setTouched(true);
211
+ }
181
212
  handleLoading(true);
182
- const files: FileProps[] = await saveToIpfs(e);
183
- setFiles(files);
213
+ const files = await saveToIpfs(efiles);
214
+ if (files) {
215
+ setFiles(files);
216
+ } else {
217
+ setFiles([]);
218
+ console.warn(
219
+ `There has been an error because 'files' ${files} is falsy in handleSave`
220
+ );
221
+ }
222
+ handleLoading(false);
184
223
  },
185
- [saveToIpfs, setFiles, handleLoading]
224
+ [meta.touched, handleLoading, saveToIpfs, helpers, setFiles]
186
225
  );
187
-
226
+ const saveFn = withUpload ? handleSave : handleChange;
227
+ const style = {
228
+ borderRadius: borderRadius ? `${borderRadius}%` : "",
229
+ width: width ? `100%` : ""
230
+ };
188
231
  return (
189
232
  <>
233
+ {withEditor && showEditor && (
234
+ <ImageEditorModal
235
+ saveButtonTheme={saveButtonTheme}
236
+ files={nativeFiles}
237
+ borderRadius={borderRadius}
238
+ width={width}
239
+ height={height}
240
+ hideModal={async (fileList) => {
241
+ if (fileList) {
242
+ await saveFn(fileList);
243
+ }
244
+
245
+ setShowEditor(false);
246
+ updateProps({
247
+ ...store,
248
+ modalType: store.modalType,
249
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
250
+ // @ts-ignore
251
+ modalProps: {
252
+ ...store.modalProps,
253
+ hidden: false
254
+ }
255
+ });
256
+ }}
257
+ />
258
+ )}
190
259
  <FieldFileUploadWrapper {...wrapperProps} $disabled={!!disabled}>
191
260
  <FieldInput
192
261
  {...props}
@@ -194,7 +263,29 @@ function Upload({
194
263
  type="file"
195
264
  accept={accept}
196
265
  multiple={multiple}
197
- onChange={withUpload ? handleSave : handleChange}
266
+ onChange={async (e) => {
267
+ const files = e.target.files
268
+ ? Object.values(e.target.files)
269
+ : e.target.files;
270
+
271
+ if (files && withEditor) {
272
+ setNativeFiles(files);
273
+ setShowEditor(true);
274
+ updateProps({
275
+ ...store,
276
+ modalType: store.modalType,
277
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
278
+ // @ts-ignore
279
+ modalProps: {
280
+ ...store.modalProps,
281
+ hidden: true
282
+ }
283
+ });
284
+ } else {
285
+ await saveFn(files);
286
+ }
287
+ e.target.value = ""; // allow user to select the same file again
288
+ }}
198
289
  ref={(ref) => {
199
290
  inputRef.current = ref;
200
291
  }}
@@ -206,10 +297,10 @@ function Upload({
206
297
  </ThemedButton>
207
298
  ) : (
208
299
  <FileUploadWrapper
209
- choosen={files !== null}
210
300
  data-disabled={disabled}
211
301
  onClick={handleChooseFile}
212
302
  error={errorMessage}
303
+ style={style}
213
304
  >
214
305
  {isLoading ? (
215
306
  <Loading size={2} />
@@ -220,17 +311,22 @@ function Upload({
220
311
  {isVideoOnly ? (
221
312
  <VideoPreview
222
313
  src={
223
- "data:video/mp4;base64," +
224
- preview?.substring(
225
- "data:application/octet-stream;base64,".length
226
- )
314
+ preview?.startsWith("http")
315
+ ? preview
316
+ : "data:video/mp4;base64," +
317
+ preview?.substring(
318
+ "data:application/octet-stream;base64,".length
319
+ )
227
320
  }
228
321
  autoPlay
229
322
  muted
230
323
  loop
231
324
  />
232
325
  ) : (
233
- <ImagePreview src={preview} />
326
+ <ImagePreview
327
+ style={{ ...imgPreviewStyle }}
328
+ src={preview}
329
+ />
234
330
  )}
235
331
  </>
236
332
  ) : isVideoOnly ? (
@@ -248,7 +344,7 @@ function Upload({
248
344
  </FileUploadWrapper>
249
345
  )}
250
346
  {!disabled && field.value && field.value?.length !== 0 && preview && (
251
- <div onClick={handleRemoveAllFiles} data-remove>
347
+ <div onClick={handleRemoveAllFiles} data-remove style={style}>
252
348
  <Trash size={24} color={colors.white} />
253
349
  </div>
254
350
  )}
@@ -17,11 +17,13 @@ export const SUPPORTED_FORMATS = [
17
17
  "image/jpg",
18
18
  "image/jpeg",
19
19
  "image/gif",
20
- "image/png"
20
+ "image/png",
21
+ "image/webp"
21
22
  ];
22
-
23
23
  export interface WithUploadToIpfsProps {
24
- saveToIpfs: (e: React.ChangeEvent<HTMLInputElement>) => FileProps[];
24
+ saveToIpfs: (
25
+ files: File[] | null
26
+ ) => Promise<false | FileProps[] | undefined>;
25
27
  loadMedia: (src: string) => string;
26
28
  removeFile: (src: string) => void;
27
29
  }
@@ -32,16 +34,16 @@ export function WithUploadToIpfs<P extends WithUploadToIpfsProps>(
32
34
  props: Omit<P & UploadProps, keyof WithUploadToIpfsProps>
33
35
  ) => {
34
36
  const withUpload = props?.withUpload || false;
35
-
37
+ const accept: string = props.accept
38
+ ? props.accept
39
+ : SUPPORTED_FORMATS.join(",");
36
40
  const { saveFile, loadMedia, removeFile } = useSaveImageToIpfs();
37
41
 
38
- const saveToIpfs = useCallback(
39
- async (e: React.ChangeEvent<HTMLInputElement>) => {
40
- if (!e.target.files) {
42
+ const saveToIpfs: WithUploadToIpfsProps["saveToIpfs"] = useCallback(
43
+ async (filesArray: File[] | null) => {
44
+ if (!filesArray) {
41
45
  return;
42
46
  }
43
- const { files } = e.target;
44
- const filesArray = Object.values(files);
45
47
  const filesErrors: string[] = [];
46
48
 
47
49
  for (const file of filesArray) {
@@ -105,12 +107,12 @@ export function WithUploadToIpfs<P extends WithUploadToIpfsProps>(
105
107
  const newProps = useMemo(
106
108
  () => ({
107
109
  maxSize: MAX_FILE_SIZE,
108
- supportFormats: SUPPORTED_FORMATS,
110
+ accept,
109
111
  saveToIpfs,
110
112
  loadMedia,
111
113
  removeFile
112
114
  }),
113
- [saveToIpfs, loadMedia, removeFile]
115
+ [saveToIpfs, loadMedia, removeFile, accept]
114
116
  );
115
117
 
116
118
  if (withUpload) {
@@ -1,11 +1,12 @@
1
1
  export { default as Checkbox } from "./Checkbox";
2
2
  export { default as Datepicker } from "./Datepicker";
3
3
  export { default as Error } from "./Error";
4
- export { default as FormField } from "./FormField";
4
+ export { FormField } from "./FormField";
5
5
  export * from "./BaseInput";
6
6
  export { default as Input, InputProps } from "./Input";
7
7
  export { default as Phone } from "./Phone";
8
8
  export { default as Select } from "./Select";
9
+ export * from "./CountrySelect";
9
10
  export * from "./BaseTagsInput";
10
11
  export * from "./BaseTextArea";
11
12
  export { default as Upload } from "./Upload/Upload";
@@ -1,4 +1,7 @@
1
+ import { ReactNode } from "react";
1
2
  import { SingleValue } from "react-select";
3
+ import { CSSProperties } from "styled-components";
4
+ import { ImageEditorModalProps } from "./Upload/ImageEditorModal/ImageEditorModal";
2
5
  import type { TextAreaTheme } from "./Field.styles";
3
6
 
4
7
  export interface BaseProps {
@@ -29,12 +32,13 @@ export interface ErrorProps {
29
32
 
30
33
  export interface FormFieldProps {
31
34
  title: string;
35
+ titleIcon?: ReactNode;
32
36
  subTitle?: string | false;
33
37
  required?: boolean;
34
38
  tooltip?: string;
35
39
  children: React.ReactNode | string;
36
40
  style?: React.CSSProperties;
37
- theme?: string;
41
+ copyIconColor?: CSSProperties["backgroundColor"];
38
42
  valueToCopy?:
39
43
  | string
40
44
  | {
@@ -87,7 +91,7 @@ export interface SelectProps extends BaseProps {
87
91
  label?: string;
88
92
  }
89
93
 
90
- export interface UploadProps extends BaseProps {
94
+ export type UploadProps = BaseProps & {
91
95
  accept?: string;
92
96
  multiple?: boolean;
93
97
  trigger?: React.ReactNode | JSX.Element;
@@ -98,7 +102,18 @@ export interface UploadProps extends BaseProps {
98
102
  onLoadSinglePreviewImage?: (base64Uri: string) => void;
99
103
  withUpload?: boolean;
100
104
  onLoading?: (loading: boolean) => void;
101
- }
105
+ borderRadius?: number;
106
+ width?: number;
107
+ height?: number;
108
+ imgPreviewStyle?: Pick<CSSProperties, "objectFit">;
109
+ } & (
110
+ | {
111
+ withEditor: true;
112
+ saveButtonTheme: ImageEditorModalProps["saveButtonTheme"];
113
+ }
114
+ | { withEditor: false; saveButtonTheme: undefined }
115
+ );
116
+
102
117
  export interface FileProps {
103
118
  src: string;
104
119
  name?: string; // for example: "redeemeum.png"
@@ -1,3 +1,4 @@
1
+ import { ImageEditorModal } from "../form/Upload/ImageEditorModal/ImageEditorModal";
1
2
  import { MODAL_TYPES } from "./ModalTypes";
2
3
  import FinanceDeposit from "./components/SellerFinance/FinanceDeposit";
3
4
  import FinanceWithdraw from "./components/SellerFinance/FinanceWithdraw";
@@ -10,5 +11,6 @@ export const MODAL_COMPONENTS = {
10
11
  [MODAL_TYPES.FINANCE_WITHDRAW_MODAL]: FinanceWithdraw,
11
12
  [MODAL_TYPES.TRANSACTION_FAILED]: TransactionFailedModal,
12
13
  [MODAL_TYPES.TRANSACTION_SUBMITTED]: TransactionSubmittedModal,
13
- [MODAL_TYPES.WAITING_FOR_CONFIRMATION]: WaitingForConfirmationModal
14
+ [MODAL_TYPES.WAITING_FOR_CONFIRMATION]: WaitingForConfirmationModal,
15
+ [MODAL_TYPES.IMAGE_EDITOR]: ImageEditorModal
14
16
  } as const;
@@ -12,6 +12,7 @@ export type ModalProps = {
12
12
  contentStyle?: CSSProperties;
13
13
  closable?: boolean;
14
14
  onClose?: (data: unknown | undefined | null) => void;
15
+ hidden?: boolean;
15
16
  };
16
17
  export type ModalType = keyof typeof MODAL_TYPES | null;
17
18
  type ModalSize = "xxs" | "xs" | "s" | "m" | "l" | "xl";
@@ -25,9 +26,9 @@ export type Store = {
25
26
  theme?: "light" | "dark";
26
27
  };
27
28
  export type GenericModalProps<T extends keyof typeof MODAL_TYPES> =
28
- Parameters<(typeof MODAL_COMPONENTS)[T]>[0] extends Record<string, never>
29
- ? ModalProps & Record<string, never>
30
- : ModalProps & Parameters<(typeof MODAL_COMPONENTS)[T]>[0];
29
+ Parameters<(typeof MODAL_COMPONENTS)[T]> extends [infer P, ...any[]]
30
+ ? ModalProps & P
31
+ : ModalProps;
31
32
 
32
33
  export interface ModalContextType {
33
34
  showModal: <T extends keyof typeof MODAL_TYPES>(
@@ -3,5 +3,6 @@ export const MODAL_TYPES = {
3
3
  FINANCE_WITHDRAW_MODAL: "FINANCE_WITHDRAW_MODAL",
4
4
  TRANSACTION_FAILED: "TRANSACTION_FAILED",
5
5
  TRANSACTION_SUBMITTED: "TRANSACTION_SUBMITTED",
6
- WAITING_FOR_CONFIRMATION: "WAITING_FOR_CONFIRMATION"
6
+ WAITING_FOR_CONFIRMATION: "WAITING_FOR_CONFIRMATION",
7
+ IMAGE_EDITOR: "IMAGE_EDITOR"
7
8
  } as const;
@@ -0,0 +1,24 @@
1
+ import { useQuery } from "react-query";
2
+ import { loadAndSetImagePromise } from "../../lib/base64/base64";
3
+
4
+ type UseFileImageProps = {
5
+ file: File | undefined;
6
+ };
7
+
8
+ export function useFileImage(
9
+ { file }: UseFileImageProps,
10
+ options: { enabled: boolean }
11
+ ) {
12
+ return useQuery(
13
+ ["useFileImage", file],
14
+ async () => {
15
+ if (!file) {
16
+ return;
17
+ }
18
+ return await loadAndSetImagePromise(file);
19
+ },
20
+ {
21
+ enabled: !!file && options.enabled
22
+ }
23
+ );
24
+ }
@@ -0,0 +1,27 @@
1
+ import { useQuery } from "react-query";
2
+ import { fetchImageAsBase64 } from "../../lib/base64/base64";
3
+ import { getImageMetadata } from "../../lib/images/images";
4
+
5
+ type UseIpfsImageProps = {
6
+ url: string;
7
+ };
8
+
9
+ export function useIpfsImage(
10
+ { url }: UseIpfsImageProps,
11
+ options: { enabled: boolean }
12
+ ) {
13
+ return useQuery(
14
+ ["useIpfsImage", url],
15
+ async () => {
16
+ const result = await fetchImageAsBase64(url);
17
+ const metadata = await getImageMetadata(result.base64);
18
+ return {
19
+ ...result,
20
+ ...metadata
21
+ };
22
+ },
23
+ {
24
+ enabled: options.enabled
25
+ }
26
+ );
27
+ }
package/src/index.tsx CHANGED
@@ -85,3 +85,4 @@ export * from "./components/error/ErrorMessage";
85
85
  export * from "./components/error/SimpleError";
86
86
  export * from "./components/step/MultiSteps";
87
87
  export * from "./components/ipfs/IpfsProvider";
88
+ export * from "./types/helpers";
@@ -3,7 +3,7 @@ import { IpfsMetadataStorage } from "@bosonprotocol/ipfs-storage";
3
3
  export async function fetchImageAsBase64(imageUrl: string) {
4
4
  const response = await fetch(imageUrl);
5
5
  const blob = await response.blob();
6
- return blobToBase64(blob);
6
+ return { base64: await blobToBase64(blob), blob };
7
7
  }
8
8
 
9
9
  export function fromBase64ToBinary(base64: string): Buffer {
@@ -0,0 +1,82 @@
1
+ import { fn } from "@storybook/test";
2
+ import React from "react";
3
+ import { Meta } from "@storybook/react";
4
+ import { Upload } from "../..";
5
+ import { EnvironmentProvider } from "../../components/environment/EnvironmentProvider";
6
+ import { IpfsProvider } from "../../components/ipfs/IpfsProvider";
7
+ import { Formik } from "formik";
8
+ import { QueryClientProviderCustom } from "../../components/queryClient/withQueryClientProvider";
9
+ import {
10
+ bosonButtonThemeKeys,
11
+ bosonButtonThemes
12
+ } from "../../components/ui/ThemedButton";
13
+ const name = "upload";
14
+
15
+ // More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
16
+ export default {
17
+ title: "Visual Components/Buttons/Upload",
18
+ component: Upload,
19
+ parameters: {
20
+ // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
21
+ layout: "centered"
22
+ },
23
+ // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
24
+ tags: ["autodocs"],
25
+ // args: { onClick: fn() },
26
+ argTypes: {
27
+ name: {
28
+ table: {
29
+ disabled: true
30
+ }
31
+ },
32
+ accept: {
33
+ control: "select",
34
+ options: ["image/*", "video/*", "image/*,video/*"]
35
+ },
36
+ disabled: { control: "boolean" },
37
+ withEditor: { control: "boolean" },
38
+ // @ts-expect-error saveButtonThemeKey is not a valid prop name of Upload but saveButtonTheme is so we get the theme from the key
39
+ saveButtonThemeKey: {
40
+ control: "select",
41
+ options: bosonButtonThemeKeys
42
+ },
43
+ placeholder: { control: "text" },
44
+ width: { control: "number" },
45
+ height: { control: "number" },
46
+ borderRadius: { control: "number" }
47
+ },
48
+ decorators: [
49
+ (Story, { args }) => {
50
+ return (
51
+ <QueryClientProviderCustom>
52
+ <EnvironmentProvider configId="testing-80002-0" envName="testing">
53
+ <IpfsProvider>
54
+ <Formik initialValues={{ [name]: [] }} onSubmit={console.log}>
55
+ <Story
56
+ args={{
57
+ ...args,
58
+ saveButtonTheme: bosonButtonThemes({
59
+ withBosonStyle: false
60
+ // @ts-expect-error saveButtonThemeKey is not a valid prop name of Upload but saveButtonTheme is so we get the theme from the key
61
+ })[args.saveButtonThemeKey]
62
+ }}
63
+ />
64
+ </Formik>
65
+ </IpfsProvider>
66
+ </EnvironmentProvider>
67
+ </QueryClientProviderCustom>
68
+ );
69
+ }
70
+ ]
71
+ } satisfies Meta<typeof Upload>;
72
+
73
+ const BASE_ARGS = {
74
+ name
75
+ } as const;
76
+
77
+ // More on args: https://storybook.js.org/docs/react/writing-stories/args
78
+ export const Base = {
79
+ args: {
80
+ ...BASE_ARGS
81
+ }
82
+ };