@swan-io/shared-business 1.8.0 → 1.9.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/shared-business",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "engines": {
5
5
  "node": ">=14.0.0",
6
6
  "yarn": "^1.20.0"
@@ -26,10 +26,10 @@
26
26
  ],
27
27
  "license": "MIT",
28
28
  "peerDependencies": {
29
- "@swan-io/lake": "^1.3.2"
29
+ "@swan-io/lake": "*"
30
30
  },
31
31
  "dependencies": {
32
- "@formatjs/intl": "^2.7.0",
32
+ "@formatjs/intl": "^2.7.1",
33
33
  "@googlemaps/js-api-loader": "^1.15.1",
34
34
  "@swan-io/boxed": "^0.13.0",
35
35
  "dayjs": "^1.11.7",
@@ -44,13 +44,13 @@
44
44
  "uuid": "^9.0.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@swan-io/lake": "^1.7.0",
47
+ "@swan-io/lake": "*",
48
48
  "@testing-library/react": "^14.0.0",
49
49
  "@testing-library/user-event": "^14.4.3",
50
- "@types/google.maps": "^3.52.5",
51
- "@types/react": "^18.0.35",
50
+ "@types/google.maps": "^3.52.6",
51
+ "@types/react": "^18.0.37",
52
52
  "@types/react-dom": "^18.0.11",
53
- "@types/react-native": "^0.71.5",
53
+ "@types/react-native": "^0.71.6",
54
54
  "@types/uuid": "^9.0.1",
55
55
  "jsdom": "^21.1.1",
56
56
  "vitest": "^0.30.1"
@@ -52,10 +52,10 @@ export const SupportingDocument = forwardRef(({ documents, getAwsUrl, onChange,
52
52
  [doc.purpose]: [
53
53
  ...(acc[doc.purpose] ?? []),
54
54
  {
55
+ status: "finished",
55
56
  id: doc.id,
56
57
  name: doc.name,
57
58
  fileUrl: doc.downloadUrl,
58
- finished: true,
59
59
  },
60
60
  ],
61
61
  };
@@ -133,9 +133,9 @@ export const SupportingDocument = forwardRef(({ documents, getAwsUrl, onChange,
133
133
  const state = getFieldState(fieldName).value;
134
134
  setFieldValue(fieldName, state.map(doc => doc.id === NO_ID_YET
135
135
  ? {
136
+ status: "uploading",
136
137
  progress: 0,
137
138
  name: file.name,
138
- finished: false,
139
139
  id,
140
140
  }
141
141
  : doc));
@@ -144,15 +144,33 @@ export const SupportingDocument = forwardRef(({ documents, getAwsUrl, onChange,
144
144
  const state = getFieldState(fieldName).value;
145
145
  setFieldValue(fieldName, state.map(uploadState => uploadState.id === id ? { ...uploadState, progress } : uploadState));
146
146
  };
147
+ xhr.onerror = () => {
148
+ const state = getFieldState(fieldName).value;
149
+ setFieldValue(fieldName, state.map(uploadState => uploadState.id === id
150
+ ? {
151
+ status: "failed",
152
+ id: uploadState.id,
153
+ name: uploadState.name,
154
+ fileUrl: uploadState.fileUrl,
155
+ error: t("supportingDoc.errorUpload"),
156
+ }
157
+ : uploadState));
158
+ };
147
159
  xhr.onload = () => {
148
160
  const state = getFieldState(fieldName).value;
149
161
  if (xhr.status !== 200 && xhr.status !== 204) {
150
- setFieldValue(fieldName, state.filter(uploadState => (uploadState.id === id ? false : true)));
162
+ setFieldValue(fieldName, state.map(uploadState => uploadState.id === id
163
+ ? {
164
+ status: "failed",
165
+ id: uploadState.id,
166
+ name: uploadState.name,
167
+ fileUrl: uploadState.fileUrl,
168
+ error: t("supportingDoc.errorUpload"),
169
+ }
170
+ : uploadState));
151
171
  return;
152
172
  }
153
- setFieldValue(fieldName, state.map(uploadState => uploadState.id === id
154
- ? { ...uploadState, progress: undefined, finished: true }
155
- : uploadState));
173
+ setFieldValue(fieldName, state.map(uploadState => uploadState.id === id ? { ...uploadState, status: "finished" } : uploadState));
156
174
  };
157
175
  const formData = new FormData();
158
176
  fields.forEach(({ key, value }) => {
@@ -161,28 +179,39 @@ export const SupportingDocument = forwardRef(({ documents, getAwsUrl, onChange,
161
179
  formData.append("file", file);
162
180
  xhr.send(formData);
163
181
  })
164
- .catch(console.error);
182
+ .catch(() => {
183
+ const state = getFieldState(fieldName).value;
184
+ setFieldValue(fieldName, state.map(uploadState => uploadState.id === NO_ID_YET
185
+ ? {
186
+ status: "failed",
187
+ id: uploadState.id,
188
+ name: uploadState.name,
189
+ fileUrl: uploadState.fileUrl,
190
+ error: t("supportingDoc.errorUpload"),
191
+ }
192
+ : uploadState));
193
+ });
165
194
  };
166
195
  return (_jsxs(Form, { children: [requiredDocumentTypes.includes("CompanyRegistration") && (_jsxs(_Fragment, { children: [_jsx(LakeLabel, { label: t("supportingDoc.companyRegistration"), help: _jsx(Help, { type: "tooltip", text: "supportingDoc.companyRegistration.description" }), render: () => (_jsx(Field, { name: "CompanyRegistration", children: ({ value, onChange, error }) => (_jsx(UploadArea, { layout: "horizontal", error: error, onDropAccepted: files => {
167
- onChange([...value, { id: NO_ID_YET }]);
196
+ onChange([...value, { id: NO_ID_YET, status: "uploading", progress: 0 }]);
168
197
  handleUpload(files, "CompanyRegistration");
169
198
  }, documents: value, accept: ACCEPTED_FORMATS, icon: "document-regular", description: t("supportingDoc.documentTypes"), maxSize: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE })) })) }), _jsx(Space, { height: 24 })] })), requiredDocumentTypes.includes("AssociationRegistration") && (_jsxs(_Fragment, { children: [_jsx(LakeLabel, { label: t("supportingDoc.associationRegistration"), help: _jsx(Help, { type: "tooltip", text: "supportingDoc.associationRegistration.description" }), render: () => (_jsx(Field, { name: "AssociationRegistration", children: ({ value, onChange, error }) => (_jsx(UploadArea, { layout: "horizontal", onDropAccepted: files => {
170
- onChange([...value, { id: NO_ID_YET }]);
199
+ onChange([...value, { id: NO_ID_YET, status: "uploading", progress: 0 }]);
171
200
  handleUpload(files, "AssociationRegistration");
172
201
  }, error: error, documents: value, accept: ACCEPTED_FORMATS, icon: "document-regular", description: t("supportingDoc.documentTypes"), maxSize: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE })) })) }), _jsx(Space, { height: 24 })] })), requiredDocumentTypes.includes("SignedStatus") && (_jsxs(_Fragment, { children: [_jsx(LakeLabel, { label: t("supportingDoc.signedStatus"), help: _jsx(Help, { type: "tooltip", text: "supportingDoc.signedStatus.description" }), render: () => (_jsx(Field, { name: "SignedStatus", children: ({ value, onChange, error }) => (_jsx(UploadArea, { layout: "horizontal", onDropAccepted: files => {
173
- onChange([...value, { id: NO_ID_YET }]);
202
+ onChange([...value, { id: NO_ID_YET, status: "uploading", progress: 0 }]);
174
203
  handleUpload(files, "SignedStatus");
175
204
  }, error: error, documents: value, accept: ACCEPTED_FORMATS, icon: "document-regular", description: t("supportingDoc.documentTypes"), maxSize: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE })) })) }), _jsx(Space, { height: 24 })] })), requiredDocumentTypes.includes("UBODeclaration") && (_jsxs(_Fragment, { children: [_jsx(LakeLabel, { label: t("supportingDoc.UBODeclaration"), help: _jsx(Help, { type: "tooltip", text: "supportingDoc.UBODeclaration.description" }), render: () => (_jsx(Field, { name: "UBODeclaration", children: ({ value, onChange, error }) => (_jsx(UploadArea, { layout: "horizontal", onDropAccepted: files => {
176
- onChange([...value, { id: NO_ID_YET }]);
205
+ onChange([...value, { id: NO_ID_YET, status: "uploading", progress: 0 }]);
177
206
  handleUpload(files, "UBODeclaration");
178
207
  }, error: error, documents: value, accept: ACCEPTED_FORMATS, icon: "document-regular", description: t("supportingDoc.documentTypes"), maxSize: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE })) })) }), _jsx(Space, { height: 24 })] })), requiredDocumentTypes.includes("ProofOfIdentity") && (_jsxs(_Fragment, { children: [_jsx(LakeLabel, { label: t("supportingDoc.proofOfIdentity"), help: _jsx(Help, { type: "tooltip", width: 600, text: "supportingDoc.proofOfIdentity.description" }), render: () => (_jsx(Field, { name: "ProofOfIdentity", children: ({ value, onChange, error }) => (_jsx(UploadArea, { layout: "horizontal", onDropAccepted: files => {
179
- onChange([...value, { id: NO_ID_YET }]);
208
+ onChange([...value, { id: NO_ID_YET, status: "uploading", progress: 0 }]);
180
209
  handleUpload(files, "ProofOfIdentity");
181
210
  }, error: error, documents: value, accept: ACCEPTED_FORMATS, icon: "document-regular", description: t("supportingDoc.documentTypes"), maxSize: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE })) })) }), _jsx(Space, { height: 24 })] })), requiredDocumentTypes.includes("PowerOfAttorney") && (_jsxs(_Fragment, { children: [_jsx(LakeLabel, { label: t("supportingDoc.powerAttornySigned"), help: _jsx(Help, { type: "button", label: t("supportingDoc.whatIsThis"), onPress: () => setShowPowerOfAttorneyModal(true) }), render: () => (_jsx(Field, { name: "PowerOfAttorney", children: ({ value, onChange, error }) => (_jsx(UploadArea, { layout: "horizontal", onDropAccepted: files => {
182
- onChange([...value, { id: NO_ID_YET }]);
211
+ onChange([...value, { id: NO_ID_YET, status: "uploading", progress: 0 }]);
183
212
  handleUpload(files, "PowerOfAttorney");
184
213
  }, error: error, documents: value, accept: ACCEPTED_FORMATS, icon: "document-regular", description: t("supportingDoc.documentTypes"), maxSize: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE })) })) }), _jsx(Space, { height: 24 })] })), requiredDocumentTypes.includes("SwornStatement") && (_jsxs(_Fragment, { children: [_jsx(LakeLabel, { label: t("supportingDoc.swornStatementDeclaration"), help: _jsx(Help, { type: "button", label: t("supportingDoc.whatIsThis"), onPress: () => setShowSwornStatementModal(true) }), render: () => (_jsx(Field, { name: "SwornStatement", children: ({ value, onChange, error }) => (_jsx(UploadArea, { layout: "horizontal", onDropAccepted: files => {
185
- onChange([...value, { id: NO_ID_YET }]);
214
+ onChange([...value, { id: NO_ID_YET, status: "uploading", progress: 0 }]);
186
215
  handleUpload(files, "SwornStatement");
187
216
  }, error: error, documents: value, accept: ACCEPTED_FORMATS, icon: "document-regular", description: t("supportingDoc.documentTypes"), maxSize: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE })) })) }), _jsx(Space, { height: 24 })] })), requiredDocumentTypes.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Space, { height: 24 }), _jsx(LakeText, { children: t("supportingDoc.noRequiredDocuments") }), _jsx(Space, { height: 24 })] })) : null, _jsxs(LakeModal, { visible: showPowerOfAttorneyModal, title: t("supportingDoc.powerAttorney"), icon: "document-regular", onPressClose: () => setShowPowerOfAttorneyModal(false), children: [_jsx(LakeText, { children: t("supportingDoc.powerAttornySigned.description") }), _jsx(Space, { height: 16 }), _jsx(LakeButtonGroup, { paddingBottom: 0, children: _jsx(LakeButton, { grow: true, color: "current", onPress: () => window.open(`/power-of-attorney-template/${match(onboardingLanguage)
188
217
  .with("fr", () => "fr")
@@ -1,12 +1,18 @@
1
1
  /// <reference types="react" />
2
2
  import { IconName } from "@swan-io/lake/src/components/Icon";
3
3
  import { DropzoneOptions } from "react-dropzone";
4
- export type UploadFileStatus = {
4
+ export type UploadFileStatus = ({
5
+ status: "uploading";
6
+ progress: number;
7
+ } | {
8
+ status: "finished";
9
+ } | {
10
+ status: "failed";
11
+ error: string;
12
+ }) & {
13
+ fileUrl?: string;
5
14
  id: string;
6
15
  name?: string;
7
- fileUrl?: string;
8
- progress?: number;
9
- finished?: boolean;
10
16
  };
11
17
  type Props = {
12
18
  icon: IconName;
@@ -9,10 +9,11 @@ import { commonStyles } from "@swan-io/lake/src/constants/commonStyles";
9
9
  import { backgroundColor, colors, gray75, radii, shadows, spacings, } from "@swan-io/lake/src/constants/design";
10
10
  import { useBoolean } from "@swan-io/lake/src/hooks/useBoolean";
11
11
  import { getIconNameFromFilename } from "@swan-io/lake/src/utils/file";
12
- import { isNotNullish, isNullish } from "@swan-io/lake/src/utils/nullish";
12
+ import { isNotNullish } from "@swan-io/lake/src/utils/nullish";
13
13
  import { Fragment, useLayoutEffect, useRef } from "react";
14
14
  import { useDropzone } from "react-dropzone";
15
15
  import { StyleSheet, Text, View } from "react-native";
16
+ import { match } from "ts-pattern";
16
17
  import { formatNestedMessage, t } from "../utils/i18n";
17
18
  const styles = StyleSheet.create({
18
19
  container: {
@@ -154,8 +155,11 @@ export const UploadArea = ({ icon, accept, value, documents = [], disabled = fal
154
155
  : formatNestedMessage("uploadArea.dropFile", {
155
156
  browse: _jsx(Text, { style: styles.browse, children: t("uploadArea.browse") }),
156
157
  }) }), isNotNullish(error) ? (_jsxs(_Fragment, { children: [_jsx(Space, { height: 4 }), _jsx(LakeText, { color: colors.negative[400], align: layout === "horizontal" ? "left" : "center", children: error })] })) : (!disabled &&
157
- isNotNullish(description) && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 4 }), _jsx(LakeText, { align: layout === "horizontal" ? "left" : "center", children: description })] })))] })] }))] }) }), documents.map(({ finished = false, ...document }, index) => {
158
- const progress = Math.max(document.progress ?? 0, 3);
159
- return (_jsxs(Fragment, { children: [_jsx(Space, { height: index === 0 ? 8 : 4 }), finished ? (_jsx(FileTile, { name: document.name ?? t("uploadArea.unknownFileName"), url: document.fileUrl, onRemove: isNullish(onRemoveFile) ? undefined : () => onRemoveFile(document.id) })) : (_jsxs(Box, { direction: "row", alignItems: "center", style: styles.item, children: [_jsx(LakeText, { numberOfLines: 1, color: colors.gray[700], children: t("uploadArea.uploading") }), _jsx(Space, { width: 20 }), _jsx(View, { role: "progressbar", style: styles.progressBar, children: _jsx(View, { style: [styles.progress, { width: `${progress}%` }] }) })] }))] }, document.id));
158
+ isNotNullish(description) && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 4 }), _jsx(LakeText, { align: layout === "horizontal" ? "left" : "center", children: description })] })))] })] }))] }) }), documents.map(({ ...document }, index) => {
159
+ return (_jsxs(Fragment, { children: [_jsx(Space, { height: index === 0 ? 8 : 4 }), match(document)
160
+ .with({ status: "finished" }, () => (_jsx(FileTile, { name: document.name ?? t("uploadArea.unknownFileName"), url: document.fileUrl, onRemove: () => onRemoveFile?.(document.id) })))
161
+ .with({ status: "failed" }, ({ error }) => (_jsx(FileTile, { name: document.name ?? t("uploadArea.unknownFileName"), url: document.fileUrl, onRemove: () => onRemoveFile?.(document.id), variant: "refused", title: error })))
162
+ .with({ status: "uploading" }, ({ progress }) => (_jsxs(Box, { direction: "row", alignItems: "center", style: styles.item, children: [_jsx(LakeText, { numberOfLines: 1, color: colors.gray[700], children: t("uploadArea.uploading") }), _jsx(Space, { width: 20 }), _jsx(View, { role: "progressbar", style: styles.progressBar, children: _jsx(View, { style: [styles.progress, { width: `${progress}%` }] }) })] })))
163
+ .exhaustive()] }, document.id));
160
164
  })] }));
161
165
  };
@@ -102,6 +102,7 @@
102
102
  "supportingDoc.companyRegistration.description": "Official document proving the legal existence of a company and providing info about its legal personality.\n(< 3 months old)",
103
103
  "supportingDoc.documentTypes": "Supported documents: .pdf, .png, .jpg",
104
104
  "supportingDoc.downloadTemplate": "Download a template",
105
+ "supportingDoc.errorUpload": "Failed to upload file",
105
106
  "supportingDoc.legalRepresentative": "Legal representative",
106
107
  "supportingDoc.noRequiredDocuments": "We don't need any documents from you. You can continue.",
107
108
  "supportingDoc.other": "Other",