@swan-io/shared-business 5.2.0 → 7.0.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.
@@ -1,204 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Dict } from "@swan-io/boxed";
3
- import { Form } from "@swan-io/lake/src/components/Form";
4
- import { LakeButton, LakeButtonGroup } from "@swan-io/lake/src/components/LakeButton";
5
- import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
6
- import { LakeText } from "@swan-io/lake/src/components/LakeText";
7
- import { LakeTooltip } from "@swan-io/lake/src/components/LakeTooltip";
8
- import { Space } from "@swan-io/lake/src/components/Space";
9
- import { isNotNullishOrEmpty, isNullish } from "@swan-io/lake/src/utils/nullish";
10
- import { useForm } from "@swan-io/use-form";
11
- import { Fragment, forwardRef, useEffect, useImperativeHandle, useMemo, useState, } from "react";
12
- import { StyleSheet } from "react-native";
13
- import { match } from "ts-pattern";
14
- import { MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE, MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE_MB, } from "../constants/uploads";
15
- import { isTranslationKey, locale, t } from "../utils/i18n";
16
- import { LakeModal } from "./LakeModal";
17
- import { UploadArea } from "./UploadArea";
18
- const ACCEPTED_FORMATS = ["application/pdf", "image/png", "image/jpeg"];
19
- const NO_ID_YET = "NO_ID_YET";
20
- const validateNotEmpty = value => {
21
- if (value.length === 0) {
22
- return t("error.requiredField");
23
- }
24
- };
25
- const styles = StyleSheet.create({
26
- button: {
27
- opacity: 1,
28
- },
29
- buttonWithDefaultCursor: {
30
- opacity: 1,
31
- cursor: "default",
32
- },
33
- });
34
- const Help = (props) => {
35
- return match(props)
36
- .with({ type: "tooltip" }, ({ text, width }) => (_jsx(LakeTooltip, { content: text, width: width, togglableOnFocus: true, placement: "top", children: _jsx(LakeButton, { mode: "tertiary", size: "small", color: "gray", icon: "question-circle-regular", disabled: true, style: [styles.button, styles.buttonWithDefaultCursor], ariaLabel: t("supportingDocuments.help.whatIsThis") }) })))
37
- .with({ type: "button" }, ({ label, onPress }) => (_jsx(LakeButton, { mode: "tertiary", size: "small", color: "gray", icon: "question-circle-regular", onPress: onPress, style: styles.button, ariaLabel: t("supportingDocuments.help.whatIsThis"), children: label })))
38
- .exhaustive();
39
- };
40
- export const getSupportingDocumentPurposeLabel = (purpose) => {
41
- const key = `supportingDocuments.purpose.${purpose}`;
42
- if (isTranslationKey(key)) {
43
- return t(key);
44
- }
45
- else {
46
- return purpose;
47
- }
48
- };
49
- export const getSupportingDocumentPurposeDescriptionLabel = (purpose) => {
50
- const key = `supportingDocuments.purpose.${purpose}.description`;
51
- if (isTranslationKey(key)) {
52
- return t(key);
53
- }
54
- else {
55
- return "";
56
- }
57
- };
58
- const SupportingDocumentWithRef = ({ documents, getAwsUrl, onChange, requiredDocumentPurposes, onboardingLanguage = locale.language, }, externalRef) => {
59
- const initialValues = useMemo(() => documents.reduce((acc, doc) => {
60
- var _a;
61
- return {
62
- ...acc,
63
- [doc.purpose]: [
64
- ...((_a = acc[doc.purpose]) !== null && _a !== void 0 ? _a : []),
65
- {
66
- status: "finished",
67
- id: doc.id,
68
- name: doc.name,
69
- fileUrl: doc.downloadUrl,
70
- },
71
- ],
72
- };
73
- }, {}), [documents]);
74
- const [showPowerOfAttorneyModal, setShowPowerOfAttorneyModal] = useState(false);
75
- const [showSwornStatementModal, setShowSwornStatementModal] = useState(false);
76
- const { Field, setFieldValue, getFieldValue, listenFields, submitForm } = useForm(requiredDocumentPurposes.reduce((acc, purpose) => {
77
- var _a;
78
- return ({
79
- ...acc,
80
- [purpose]: {
81
- initialValue: (_a = initialValues[purpose]) !== null && _a !== void 0 ? _a : [],
82
- validate: validateNotEmpty,
83
- },
84
- });
85
- }, {}));
86
- useImperativeHandle(externalRef, () => {
87
- return {
88
- submit: (callback) => {
89
- submitForm({
90
- onSuccess: () => callback(true),
91
- onFailure: () => callback(false),
92
- });
93
- },
94
- };
95
- });
96
- useEffect(() => {
97
- const removeListener = listenFields(requiredDocumentPurposes, state => {
98
- let documents = [];
99
- Dict.entries(state).forEach(([key, { value: values = [] }]) => {
100
- documents = [
101
- ...documents,
102
- ...values.map(v => ({
103
- id: v.id,
104
- name: v.name,
105
- downloadUrl: v.fileUrl,
106
- purpose: key,
107
- })),
108
- ];
109
- });
110
- onChange === null || onChange === void 0 ? void 0 : onChange(documents);
111
- });
112
- return () => removeListener();
113
- }, [listenFields, onChange, requiredDocumentPurposes]);
114
- const handleUpload = (files, fieldName) => {
115
- const file = files[0];
116
- if (isNullish(file)) {
117
- return;
118
- }
119
- getAwsUrl(file, fieldName)
120
- .then(({ upload: { url, fields }, id }) => {
121
- const xhr = new XMLHttpRequest();
122
- xhr.open("POST", url, true);
123
- const state = getFieldValue(fieldName);
124
- setFieldValue(fieldName, state.map(doc => doc.id === NO_ID_YET
125
- ? {
126
- status: "uploading",
127
- progress: 0,
128
- name: file.name,
129
- id,
130
- }
131
- : doc));
132
- xhr.upload.onprogress = event => {
133
- const progress = (event.loaded / event.total) * 100;
134
- const state = getFieldValue(fieldName);
135
- setFieldValue(fieldName, state.map(uploadState => uploadState.id === id ? { ...uploadState, progress } : uploadState));
136
- };
137
- xhr.onerror = () => {
138
- const state = getFieldValue(fieldName);
139
- setFieldValue(fieldName, state.map(uploadState => uploadState.id === id
140
- ? {
141
- status: "failed",
142
- id: uploadState.id,
143
- name: uploadState.name,
144
- fileUrl: uploadState.fileUrl,
145
- error: t("supportingDocuments.errorUpload"),
146
- }
147
- : uploadState));
148
- };
149
- xhr.onload = () => {
150
- const state = getFieldValue(fieldName);
151
- if (xhr.status !== 200 && xhr.status !== 204) {
152
- setFieldValue(fieldName, state.map(uploadState => uploadState.id === id
153
- ? {
154
- status: "failed",
155
- id: uploadState.id,
156
- name: uploadState.name,
157
- fileUrl: uploadState.fileUrl,
158
- error: t("supportingDocuments.errorUpload"),
159
- }
160
- : uploadState));
161
- return;
162
- }
163
- setFieldValue(fieldName, state.map(uploadState => uploadState.id === id ? { ...uploadState, status: "finished" } : uploadState));
164
- };
165
- const formData = new FormData();
166
- fields.forEach(({ key, value }) => {
167
- formData.append(key, value);
168
- });
169
- formData.append("file", file);
170
- xhr.send(formData);
171
- })
172
- .catch(() => {
173
- const state = getFieldValue(fieldName);
174
- setFieldValue(fieldName, state.map(uploadState => uploadState.id === NO_ID_YET
175
- ? {
176
- status: "failed",
177
- id: uploadState.id,
178
- name: uploadState.name,
179
- fileUrl: uploadState.fileUrl,
180
- error: t("supportingDocuments.errorUpload"),
181
- }
182
- : uploadState));
183
- });
184
- };
185
- return (_jsxs(Form, { children: [requiredDocumentPurposes.map(purpose => {
186
- return (_jsxs(Fragment, { children: [_jsx(LakeLabel, { label: getSupportingDocumentPurposeLabel(purpose), help: match(purpose)
187
- .with("PowerOfAttorney", () => (_jsx(Help, { type: "button", label: t("supportingDocuments.help.whatIsThis"), onPress: () => setShowPowerOfAttorneyModal(true) })))
188
- .with("SwornStatement", () => (_jsx(Help, { type: "button", label: t("supportingDocuments.help.whatIsThis"), onPress: () => setShowSwornStatementModal(true) })))
189
- .otherwise(() => {
190
- const label = getSupportingDocumentPurposeDescriptionLabel(purpose);
191
- return isNotNullishOrEmpty(label) ? _jsx(Help, { type: "tooltip", text: label }) : null;
192
- }), render: () => (_jsx(Field, { name: purpose, children: ({ value, onChange, error }) => (_jsx(UploadArea, { layout: "horizontal", error: error, onDropAccepted: files => {
193
- onChange([...value, { id: NO_ID_YET, status: "uploading", progress: 0 }]);
194
- handleUpload(files, purpose);
195
- }, documents: value, accept: ACCEPTED_FORMATS, icon: "document-regular", description: t("supportingDocuments.documentTypes", {
196
- maxSizeMB: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE_MB,
197
- }), maxSize: MAX_SUPPORTING_DOCUMENT_UPLOAD_SIZE })) })) }), _jsx(Space, { height: 24 })] }, purpose));
198
- }), requiredDocumentPurposes.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Space, { height: 24 }), _jsx(LakeText, { children: t("supportingDocuments.noRequiredDocuments") }), _jsx(Space, { height: 24 })] })) : null, _jsxs(LakeModal, { visible: showPowerOfAttorneyModal, title: t("supportingDocuments.powerOfAttorneyModal.title"), icon: "document-regular", onPressClose: () => setShowPowerOfAttorneyModal(false), children: [_jsx(LakeText, { children: t("supportingDocuments.powerOfAttorneyModal.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)
199
- .with("fr", () => "fr")
200
- .with("de", () => "de")
201
- .with("es", () => "es")
202
- .otherwise(() => "en")}.pdf`), children: t("supportingDocuments.downloadTemplate") }) })] }), _jsxs(LakeModal, { visible: showSwornStatementModal, title: t("supportingDocuments.purpose.SwornStatement"), icon: "document-regular", onPressClose: () => setShowSwornStatementModal(false), children: [_jsx(LakeText, { children: t("supportingDocuments.purpose.SwornStatement.description") }), _jsx(Space, { height: 16 }), _jsx(LakeButtonGroup, { paddingBottom: 0, children: _jsx(LakeButton, { grow: true, color: "current", onPress: () => window.open("/sworn-statement-template/es.pdf"), children: t("supportingDocuments.downloadTemplate") }) })] })] }));
203
- };
204
- export const SupportingDocument = forwardRef(SupportingDocumentWithRef);
@@ -1,33 +0,0 @@
1
- import { AsyncData } from "@swan-io/boxed";
2
- import { IconName } from "@swan-io/lake/src/components/Icon";
3
- import { DropzoneOptions } from "react-dropzone";
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;
14
- id: string;
15
- name?: string;
16
- };
17
- type Props = {
18
- icon: IconName;
19
- documents?: UploadFileStatus[];
20
- onRemoveDocument?: (fileId: string) => void;
21
- onRemoveFile?: () => void;
22
- accept: string[];
23
- value?: AsyncData<File>;
24
- disabled?: boolean;
25
- onDropAccepted?: DropzoneOptions["onDropAccepted"];
26
- onDropRejected?: DropzoneOptions["onDropRejected"];
27
- layout?: "vertical" | "horizontal";
28
- description?: string;
29
- error?: string;
30
- maxSize?: number;
31
- };
32
- export declare const UploadArea: ({ icon, accept, value, documents, disabled, onRemoveFile, onRemoveDocument, onDropAccepted, onDropRejected, layout, description, error, maxSize, }: Props) => import("react/jsx-runtime").JSX.Element;
33
- export {};
@@ -1,172 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Box } from "@swan-io/lake/src/components/Box";
3
- import { Icon } from "@swan-io/lake/src/components/Icon";
4
- import { LakeButton } from "@swan-io/lake/src/components/LakeButton";
5
- import { LakeHeading } from "@swan-io/lake/src/components/LakeHeading";
6
- import { LakeText } from "@swan-io/lake/src/components/LakeText";
7
- import { LoadingView } from "@swan-io/lake/src/components/LoadingView";
8
- import { Space } from "@swan-io/lake/src/components/Space";
9
- import { commonStyles } from "@swan-io/lake/src/constants/commonStyles";
10
- import { backgroundColor, colors, gray75, radii, shadows, spacings, } from "@swan-io/lake/src/constants/design";
11
- import { useBoolean } from "@swan-io/lake/src/hooks/useBoolean";
12
- import { getIconNameFromFilename } from "@swan-io/lake/src/utils/file";
13
- import { isNotNullish } from "@swan-io/lake/src/utils/nullish";
14
- import { FileTile } from "@swan-io/shared-business/src/components/FileTile";
15
- import { Fragment, useMemo } from "react";
16
- import { useDropzone } from "react-dropzone";
17
- import { StyleSheet, Text, View } from "react-native";
18
- import { match } from "ts-pattern";
19
- import { formatNestedMessage, t } from "../utils/i18n";
20
- const styles = StyleSheet.create({
21
- container: {
22
- backgroundColor: backgroundColor.accented,
23
- borderWidth: 1,
24
- borderColor: colors.gray[200],
25
- borderStyle: "dashed",
26
- borderRadius: radii[8],
27
- padding: spacings[32],
28
- cursor: "pointer",
29
- alignItems: "center",
30
- },
31
- disabled: {
32
- cursor: "default",
33
- backgroundColor: backgroundColor.default,
34
- },
35
- horizontalContainer: {
36
- flexDirection: "row",
37
- },
38
- icon: {
39
- alignItems: "center",
40
- justifyContent: "center",
41
- },
42
- hoveredContainer: {
43
- boxShadow: shadows.tile,
44
- },
45
- activeContainer: {
46
- borderColor: colors.current[500],
47
- boxShadow: shadows.tile,
48
- },
49
- browse: {
50
- color: colors.current[500],
51
- },
52
- browseBlock: {
53
- flex: 1,
54
- },
55
- decorativeIconLeft: {
56
- position: "absolute",
57
- bottom: 0,
58
- left: "50%",
59
- opacity: 0,
60
- transform: "translateX(-50%) scale(0.6)",
61
- transitionProperty: "opacity, transform",
62
- transitionDuration: "300ms",
63
- transitionTimingFunction: "ease-in-out",
64
- },
65
- decorativeIconLeftHovered: {
66
- opacity: 0.3,
67
- transform: "translateX(-50%) translateX(-24px) scale(0.6) rotate(-5deg)",
68
- },
69
- decorativeIconRight: {
70
- position: "absolute",
71
- bottom: 0,
72
- left: "50%",
73
- opacity: 0,
74
- transform: "translateX(-50%) scale(0.6)",
75
- transitionProperty: "opacity, transform",
76
- transitionDuration: "300ms",
77
- transitionTimingFunction: "ease-in-out",
78
- },
79
- decorativeIconRightHovered: {
80
- opacity: 0.3,
81
- transform: "translateX(-50%) translateX(24px) scale(0.6) rotate(5deg)",
82
- },
83
- progressBar: {
84
- flex: 1,
85
- height: 4,
86
- borderRadius: 2,
87
- backgroundColor: gray75,
88
- },
89
- progress: {
90
- height: 4,
91
- transitionProperty: "width",
92
- transitionDuration: "300ms",
93
- transitionTimingFunction: "ease-in-out",
94
- borderRadius: 2,
95
- backgroundColor: colors.current[500],
96
- },
97
- item: {
98
- backgroundColor: backgroundColor.accented,
99
- borderRadius: 8,
100
- boxShadow: shadows.tile,
101
- height: 56,
102
- paddingHorizontal: spacings[20],
103
- },
104
- errorContainer: {
105
- borderColor: colors.negative[500],
106
- },
107
- preview: {
108
- aspectRatio: 16 / 5,
109
- width: "50%",
110
- backgroundPosition: "center",
111
- backgroundSize: "contain",
112
- backgroundRepeat: "no-repeat",
113
- },
114
- deleteButton: {
115
- position: "absolute",
116
- right: spacings[20],
117
- top: spacings[20],
118
- },
119
- });
120
- const UploadAreaPreview = ({ file }) => {
121
- const url = useMemo(() => URL.createObjectURL(file), [file]);
122
- return _jsx(View, { style: [styles.preview, { backgroundImage: `url("${url}")` }] });
123
- };
124
- export const UploadArea = ({ icon, accept, value, documents = [], disabled = false, onRemoveFile, onRemoveDocument, onDropAccepted, onDropRejected, layout = "vertical", description, error, maxSize, }) => {
125
- var _a;
126
- const [isHovered, setIsHovered] = useBoolean(false);
127
- const { getRootProps, getInputProps, isDragActive, fileRejections } = useDropzone({
128
- accept: accept.reduce((acc, item) => ({ ...acc, [item]: [] }), {}),
129
- onDropAccepted,
130
- onDropRejected,
131
- disabled,
132
- maxSize,
133
- });
134
- const browseElement = (_jsxs(_Fragment, { children: [_jsxs(View, { style: styles.icon, children: [_jsx(Icon, { name: icon, size: 48, color: disabled ? colors.gray[200] : colors.current[500], style: [
135
- styles.decorativeIconLeft,
136
- (isDragActive || isHovered) && styles.decorativeIconLeftHovered,
137
- ] }), _jsx(Icon, { name: icon, size: 48, color: disabled ? colors.gray[200] : colors.current[500], style: [
138
- styles.decorativeIconRight,
139
- (isDragActive || isHovered) && styles.decorativeIconRightHovered,
140
- ] }), _jsx(Icon, { name: icon, size: 48, color: disabled ? colors.gray[200] : colors.current[500] })] }), _jsx(Space, { width: layout === "horizontal" ? 48 : 16, height: layout === "horizontal" ? 48 : 16 }), _jsxs(View, { style: styles.browseBlock, children: [_jsx(LakeHeading, { level: 5, variant: "h5", align: layout === "horizontal" ? "left" : "center", color: disabled ? colors.gray[200] : undefined, children: disabled
141
- ? t("uploadArea.noValue")
142
- : formatNestedMessage("uploadArea.dropFile", {
143
- browse: _jsx(Text, { style: styles.browse, children: t("uploadArea.browse") }),
144
- }) }), isNotNullish(error) ? (_jsxs(_Fragment, { children: [_jsx(Space, { height: 4 }), _jsx(LakeText, { color: colors.negative[400], align: layout === "horizontal" ? "left" : "center", children: error })] })) : (!disabled &&
145
- isNotNullish(description) && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 4 }), _jsx(LakeText, { align: layout === "horizontal" ? "left" : "center", children: description })] })))] })] }));
146
- return (_jsxs(View, { style: commonStyles.fill, children: [_jsx("div", { ...getRootProps(), onMouseEnter: setIsHovered.on, onMouseLeave: setIsHovered.off, children: _jsxs(View, { "aria-errormessage": error !== null && error !== void 0 ? error : (_a = fileRejections[0]) === null || _a === void 0 ? void 0 : _a.errors.join(", "), style: [
147
- styles.container,
148
- disabled && styles.disabled,
149
- layout === "horizontal" && styles.horizontalContainer,
150
- !disabled && isHovered && styles.hoveredContainer,
151
- isDragActive && styles.activeContainer,
152
- (error != null || fileRejections.length > 0) && styles.errorContainer,
153
- ], children: [_jsx("input", { ...getInputProps() }), isNotNullish(value)
154
- ? value.match({
155
- NotAsked: () => browseElement,
156
- Loading: () => _jsx(LoadingView, {}),
157
- Done: value => value.type.startsWith("image/") ? (_jsxs(_Fragment, { children: [_jsx(UploadAreaPreview, { file: value }), onRemoveFile != null ? (_jsx(LakeButton, { mode: "tertiary", size: "small", icon: "delete-regular", color: "negative", onPress: onRemoveFile, style: styles.deleteButton, ariaLabel: t("common.remove") })) : null] })) : (_jsxs(_Fragment, { children: [_jsx(Icon, { name: getIconNameFromFilename(value.name), size: 48, color: disabled ? colors.gray[200] : colors.current[500] }), _jsx(Space, { width: layout === "horizontal" ? 48 : 16, height: layout === "horizontal" ? 48 : 16 }), _jsxs(View, { style: styles.browseBlock, children: [_jsx(LakeHeading, { level: 5, variant: "h5", align: layout === "horizontal" ? "left" : "center", children: t("uploadArea.droppedFile") }), _jsx(Space, { height: 4 }), _jsx(LakeText, { variant: "smallRegular", color: colors.gray[200], children: value.name })] }), onRemoveFile != null ? (_jsx(LakeButton, { mode: "tertiary", size: "small", icon: "delete-regular", color: "negative", style: styles.deleteButton, onPress: onRemoveFile, ariaLabel: t("common.remove") })) : null] })),
158
- })
159
- : browseElement] }) }), documents.map(({ ...document }, index) => {
160
- return (_jsxs(Fragment, { children: [_jsx(Space, { height: index === 0 ? 8 : 4 }), match(document)
161
- .with({ status: "finished" }, () => {
162
- var _a;
163
- return (_jsx(FileTile, { name: (_a = document.name) !== null && _a !== void 0 ? _a : t("uploadArea.unknownFileName"), url: document.fileUrl, onRemove: onRemoveDocument ? () => onRemoveDocument(document.id) : undefined }));
164
- })
165
- .with({ status: "failed" }, ({ error }) => {
166
- var _a;
167
- return (_jsx(FileTile, { name: (_a = document.name) !== null && _a !== void 0 ? _a : t("uploadArea.unknownFileName"), url: document.fileUrl, onRemove: onRemoveDocument ? () => onRemoveDocument(document.id) : undefined, variant: "refused", title: error }));
168
- })
169
- .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}%` }] }) })] })))
170
- .exhaustive()] }, document.id));
171
- })] }));
172
- };