@swan-io/shared-business 1.5.1 → 1.7.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.
- package/package.json +2 -2
- package/src/components/AddressFormPart.d.ts +2 -1
- package/src/components/AddressFormPart.js +3 -3
- package/src/components/BeneficiaryForm.d.ts +1 -0
- package/src/components/BeneficiaryForm.js +35 -14
- package/src/components/{AddressSearchInput.d.ts → GMapAddressSearchInput.d.ts} +3 -9
- package/src/components/GMapAddressSearchInput.js +50 -0
- package/src/components/{LakeCityInput.d.ts → GMapCityInput.d.ts} +4 -7
- package/src/components/GMapCityInput.js +48 -0
- package/src/components/TaxIdentificationNumberInput.d.ts +15 -0
- package/src/components/TaxIdentificationNumberInput.js +15 -0
- package/src/hooks/useGoogleMapSDK.d.ts +10 -2
- package/src/hooks/useGoogleMapSDK.js +40 -1
- package/src/locales/en.json +5 -0
- package/src/utils/templateTranslations.d.ts +6 -0
- package/src/utils/templateTranslations.js +22 -0
- package/src/utils/validation.d.ts +3 -2
- package/src/utils/validation.js +32 -10
- package/src/components/AddressSearchInput.js +0 -119
- package/src/components/LakeCityInput.js +0 -88
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swan-io/shared-business",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"engines": {
|
|
5
5
|
"node": ">=14.0.0",
|
|
6
6
|
"yarn": "^1.20.0"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@formatjs/intl": "^2.6.9",
|
|
33
33
|
"@googlemaps/js-api-loader": "^1.15.1",
|
|
34
|
-
"@swan-io/boxed": "^0.
|
|
34
|
+
"@swan-io/boxed": "^0.13.0",
|
|
35
35
|
"dayjs": "^1.11.7",
|
|
36
36
|
"react": "^18.2.0",
|
|
37
37
|
"react-atomic-state": "^1.2.7",
|
|
@@ -16,6 +16,7 @@ type Props = {
|
|
|
16
16
|
optionalLabel?: string;
|
|
17
17
|
placeholder?: string;
|
|
18
18
|
isLarge: boolean;
|
|
19
|
+
apiKey: string;
|
|
19
20
|
} & FormProps;
|
|
20
|
-
export declare const AddressFormPart: ({ initialAddress, initialCity, initialPostalCode, country, label, optionalLabel, placeholder, Field, setFieldValue, listenFields, isLarge, }: Props) => JSX.Element;
|
|
21
|
+
export declare const AddressFormPart: ({ initialAddress, initialCity, initialPostalCode, country, label, optionalLabel, placeholder, Field, setFieldValue, listenFields, isLarge, apiKey, }: Props) => JSX.Element;
|
|
21
22
|
export {};
|
|
@@ -6,8 +6,8 @@ import { Space } from "@swan-io/lake/src/components/Space";
|
|
|
6
6
|
import { useBoolean } from "@swan-io/lake/src/hooks/useBoolean";
|
|
7
7
|
import { useCallback, useEffect } from "react";
|
|
8
8
|
import { locale, t } from "../utils/i18n";
|
|
9
|
-
import {
|
|
10
|
-
export const AddressFormPart = ({ initialAddress, initialCity, initialPostalCode, country, label, optionalLabel, placeholder, Field, setFieldValue, listenFields, isLarge, }) => {
|
|
9
|
+
import { GMapAddressSearchInput } from "./GMapAddressSearchInput";
|
|
10
|
+
export const AddressFormPart = ({ initialAddress, initialCity, initialPostalCode, country, label, optionalLabel, placeholder, Field, setFieldValue, listenFields, isLarge, apiKey, }) => {
|
|
11
11
|
const [manualModeEnabled, setManualMode] = useBoolean(initialAddress !== "" || initialCity !== "" || initialPostalCode !== "");
|
|
12
12
|
useEffect(() => {
|
|
13
13
|
if (!manualModeEnabled) {
|
|
@@ -22,5 +22,5 @@ export const AddressFormPart = ({ initialAddress, initialCity, initialPostalCode
|
|
|
22
22
|
setFieldValue("postalCode", place.postalCode);
|
|
23
23
|
setManualMode.on();
|
|
24
24
|
}, [setManualMode, setFieldValue]);
|
|
25
|
-
return (_jsxs(_Fragment, { children: [_jsx(Field, { name: "address", children: ({ ref, value, onChange, error }) => (_jsx(LakeLabel, { label: label ?? t("addressFormPart.addressLabel"), optionalLabel: optionalLabel, render: id => (_jsx(
|
|
25
|
+
return (_jsxs(_Fragment, { children: [_jsx(Field, { name: "address", children: ({ ref, value, onChange, error }) => (_jsx(LakeLabel, { label: label ?? t("addressFormPart.addressLabel"), optionalLabel: optionalLabel, render: id => (_jsx(GMapAddressSearchInput, { inputRef: ref, apiKey: apiKey, emptyResultText: t("common.noResult"), placeholder: placeholder ?? t("addressFormPart.placeholder"), language: locale.language, id: id, country: country, value: value, error: error, onValueChange: onChange, onSuggestion: onSuggestion })), actions: !manualModeEnabled && isLarge ? (_jsx(LakeButton, { mode: "secondary", size: "small", onPress: setManualMode.on, children: t("addressFormPart.setManual") })) : null })) }), !manualModeEnabled && !isLarge ? (_jsx(LakeButton, { mode: "secondary", size: "small", onPress: setManualMode.on, children: t("addressFormPart.setManual") })) : null, manualModeEnabled ? (_jsxs(_Fragment, { children: [_jsx(Space, { height: 12 }), _jsx(Field, { name: "city", children: ({ ref, value, valid, error, onChange }) => (_jsx(LakeLabel, { label: t("addressFormPart.cityLabel"), render: id => (_jsx(LakeTextInput, { ref: ref, id: id, value: value, valid: valid, error: error, onChangeText: onChange })) })) }), _jsx(Space, { height: 12 }), _jsx(Field, { name: "postalCode", children: ({ ref, value, valid, error, onChange }) => (_jsx(LakeLabel, { label: t("addressFormPart.postCodeLabel"), render: id => (_jsx(LakeTextInput, { ref: ref, id: id, value: value, valid: valid, error: error, onChangeText: onChange })) })) })] })) : null] }));
|
|
26
26
|
};
|
|
@@ -25,6 +25,7 @@ type Props = {
|
|
|
25
25
|
initialState?: EditorState;
|
|
26
26
|
accountCountry: AccountCountry;
|
|
27
27
|
step: BeneficiaryFormStep;
|
|
28
|
+
googleMapApiKey: string;
|
|
28
29
|
onStepChange: (step: BeneficiaryFormStep) => void;
|
|
29
30
|
onSave: (editorState: EditorState) => void | Promise<void>;
|
|
30
31
|
onClose: () => void;
|
|
@@ -13,7 +13,7 @@ import { noop } from "@swan-io/lake/src/utils/function";
|
|
|
13
13
|
import { isNotNullish, isNotNullishOrEmpty } from "@swan-io/lake/src/utils/nullish";
|
|
14
14
|
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
15
15
|
import { StyleSheet, View } from "react-native";
|
|
16
|
-
import { hasDefinedKeys, useForm } from "react-ux-form";
|
|
16
|
+
import { combineValidators, hasDefinedKeys, useForm } from "react-ux-form";
|
|
17
17
|
import { Rifm } from "rifm";
|
|
18
18
|
import { match } from "ts-pattern";
|
|
19
19
|
import { v4 as uuid } from "uuid";
|
|
@@ -23,7 +23,8 @@ import { locale, rifmDateProps, t } from "../utils/i18n";
|
|
|
23
23
|
import { validateBooleanRequired, validateIndividualTaxNumber, validateNullableRequired, validateRequired, } from "../utils/validation";
|
|
24
24
|
import { AddressFormPart } from "./AddressFormPart";
|
|
25
25
|
import { CountryPicker } from "./CountryPicker";
|
|
26
|
-
import {
|
|
26
|
+
import { GMapCityInput } from "./GMapCityInput";
|
|
27
|
+
import { TaxIdentificationNumberInput } from "./TaxIdentificationNumberInput";
|
|
27
28
|
const styles = StyleSheet.create({
|
|
28
29
|
inputContainer: {
|
|
29
30
|
flex: 1,
|
|
@@ -39,13 +40,24 @@ const beneficiaryTypes = [
|
|
|
39
40
|
];
|
|
40
41
|
export const validateUbo = (editorState, accountCountry) => {
|
|
41
42
|
const isAddressRequired = accountCountry === "DEU";
|
|
43
|
+
const isBirthInfoRequired = accountCountry !== "DEU";
|
|
44
|
+
const isTaxIdentificationNumberRequired = accountCountry === "DEU" && editorState.residencyAddressCountry === "DEU";
|
|
45
|
+
const validateTaxNumber = isTaxIdentificationNumberRequired
|
|
46
|
+
? combineValidators(validateNullableRequired, validateIndividualTaxNumber(accountCountry))
|
|
47
|
+
: validateIndividualTaxNumber(accountCountry);
|
|
42
48
|
return {
|
|
43
49
|
firstName: validateNullableRequired(editorState.firstName),
|
|
44
50
|
lastName: validateNullableRequired(editorState.lastName),
|
|
45
|
-
birthDate:
|
|
51
|
+
birthDate: isBirthInfoRequired
|
|
52
|
+
? validateNullableRequired(editorState.birthDate)
|
|
53
|
+
: undefined,
|
|
46
54
|
birthCountryCode: validateNullableRequired(editorState.birthCountryCode),
|
|
47
|
-
birthCity:
|
|
48
|
-
|
|
55
|
+
birthCity: isBirthInfoRequired
|
|
56
|
+
? validateNullableRequired(editorState.birthCity)
|
|
57
|
+
: undefined,
|
|
58
|
+
birthCityPostalCode: isBirthInfoRequired
|
|
59
|
+
? validateNullableRequired(editorState.birthCityPostalCode)
|
|
60
|
+
: undefined,
|
|
49
61
|
type: validateNullableRequired(editorState.type),
|
|
50
62
|
totalCapitalPercentage: editorState.type === "HasCapital"
|
|
51
63
|
? validateNullableRequired(editorState.totalCapitalPercentage?.toString())
|
|
@@ -62,7 +74,7 @@ export const validateUbo = (editorState, accountCountry) => {
|
|
|
62
74
|
residencyAddressPostalCode: isAddressRequired
|
|
63
75
|
? validateNullableRequired(editorState.residencyAddressPostalCode)
|
|
64
76
|
: undefined,
|
|
65
|
-
taxIdentificationNumber:
|
|
77
|
+
taxIdentificationNumber: validateTaxNumber(editorState.taxIdentificationNumber ?? ""),
|
|
66
78
|
indirect: editorState.type !== "HasCapital" || editorState.direct === true
|
|
67
79
|
? undefined
|
|
68
80
|
: validateBooleanRequired(editorState.indirect),
|
|
@@ -127,9 +139,10 @@ const requiredStepFields = [
|
|
|
127
139
|
"birthCityPostalCode",
|
|
128
140
|
"type",
|
|
129
141
|
];
|
|
130
|
-
export const BeneficiaryForm = forwardRef(({ initialState, accountCountry, step, onStepChange, onClose, onSave, onCityLoadError }, ref) => {
|
|
142
|
+
export const BeneficiaryForm = forwardRef(({ initialState, accountCountry, step, googleMapApiKey, onStepChange, onClose, onSave, onCityLoadError, }, ref) => {
|
|
131
143
|
const [reference] = useState(() => initialState?.reference ?? uuid());
|
|
132
144
|
const isAddressRequired = accountCountry === "DEU";
|
|
145
|
+
const isBirthInfoRequired = accountCountry !== "DEU";
|
|
133
146
|
const commonStepValues = useRef();
|
|
134
147
|
const initialBirthDate = initialState?.birthDate;
|
|
135
148
|
const { Field, FieldsListener, setFieldValue, submitForm, listenFields, validateField } = useForm({
|
|
@@ -147,7 +160,7 @@ export const BeneficiaryForm = forwardRef(({ initialState, accountCountry, step,
|
|
|
147
160
|
initialValue: isNotNullishOrEmpty(initialBirthDate)
|
|
148
161
|
? decodeBirthDate(initialBirthDate)
|
|
149
162
|
: "",
|
|
150
|
-
validate: validateNullableRequired,
|
|
163
|
+
validate: isBirthInfoRequired ? validateNullableRequired : undefined,
|
|
151
164
|
sanitize: value => value?.trim(),
|
|
152
165
|
},
|
|
153
166
|
birthCountryCode: {
|
|
@@ -156,12 +169,12 @@ export const BeneficiaryForm = forwardRef(({ initialState, accountCountry, step,
|
|
|
156
169
|
},
|
|
157
170
|
birthCity: {
|
|
158
171
|
initialValue: initialState?.birthCity ?? "",
|
|
159
|
-
validate: validateNullableRequired,
|
|
172
|
+
validate: isBirthInfoRequired ? validateNullableRequired : undefined,
|
|
160
173
|
sanitize: value => value?.trim(),
|
|
161
174
|
},
|
|
162
175
|
birthCityPostalCode: {
|
|
163
176
|
initialValue: initialState?.birthCityPostalCode ?? "",
|
|
164
|
-
validate: validateNullableRequired,
|
|
177
|
+
validate: isBirthInfoRequired ? validateNullableRequired : undefined,
|
|
165
178
|
sanitize: value => value?.trim(),
|
|
166
179
|
},
|
|
167
180
|
type: {
|
|
@@ -206,7 +219,13 @@ export const BeneficiaryForm = forwardRef(({ initialState, accountCountry, step,
|
|
|
206
219
|
},
|
|
207
220
|
taxIdentificationNumber: {
|
|
208
221
|
initialValue: initialState?.taxIdentificationNumber,
|
|
209
|
-
validate:
|
|
222
|
+
validate: (value, { getFieldState }) => {
|
|
223
|
+
const uboCountry = getFieldState("country").value;
|
|
224
|
+
if (accountCountry === "DEU" && uboCountry === "DEU") {
|
|
225
|
+
return combineValidators(validateNullableRequired, validateIndividualTaxNumber(accountCountry))(value);
|
|
226
|
+
}
|
|
227
|
+
return validateIndividualTaxNumber(accountCountry)(value);
|
|
228
|
+
},
|
|
210
229
|
sanitize: value => value?.trim(),
|
|
211
230
|
},
|
|
212
231
|
});
|
|
@@ -269,14 +288,16 @@ export const BeneficiaryForm = forwardRef(({ initialState, accountCountry, step,
|
|
|
269
288
|
};
|
|
270
289
|
});
|
|
271
290
|
return (_jsx(ResponsiveContainer, { breakpoint: breakpoints.tiny, children: ({ small, large }) => (_jsxs(_Fragment, { children: [match(step)
|
|
272
|
-
.with("common", () => (_jsxs(View, { role: "form", children: [_jsxs(Box, { direction: small ? "column" : "row", children: [_jsx(Field, { name: "firstName", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.firstName"), style: styles.inputContainer, render: id => (_jsx(LakeTextInput, { error: error, placeholder: t("beneficiaryForm.beneficiary.firstNamePlaceholder"), id: id, value: value, onChangeText: onChange })) })) }), _jsx(Space, { width: 12 }), _jsx(Field, { name: "lastName", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.lastName"), style: styles.inputContainer, render: id => (_jsx(LakeTextInput, { error: error, placeholder: t("beneficiaryForm.beneficiary.lastNamePlaceholder"), id: id, value: value, onChangeText: onChange })) })) })] }), _jsxs(Box, { direction: small ? "column" : "row", children: [_jsx(Field, { name: "birthDate", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.birthDate"), style: styles.inputContainer, render: id => (_jsx(Rifm, { value: value ?? "", onChange: onChange, ...rifmDateProps, children: ({ value, onChange }) => (_jsx(LakeTextInput, { error: error, placeholder: locale.datePlaceholder, id: id, value: value, onChange: onChange })) })) })) }), _jsx(Space, { width: 12 }), _jsx(Field, { name: "birthCountryCode", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.birthCountry"), style: styles.inputContainer, render: id => (_jsx(CountryPicker, { id: id, error: error, value: value, placeholder: t("beneficiaryForm.beneficiary.birthCountryPlaceholder"), items: allCountriesItems, onValueChange: onChange })) })) })] }), _jsx(Box, { direction: small ? "column" : "row", children: _jsx(FieldsListener, { names: ["birthCountryCode"], children: ({ birthCountryCode }) => (_jsxs(_Fragment, { children: [_jsx(Field, { name: "birthCity", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.birthCity"), style: styles.inputContainer, render: id => (_jsx(
|
|
291
|
+
.with("common", () => (_jsxs(View, { role: "form", children: [_jsxs(Box, { direction: small ? "column" : "row", children: [_jsx(Field, { name: "firstName", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.firstName"), style: styles.inputContainer, render: id => (_jsx(LakeTextInput, { error: error, placeholder: t("beneficiaryForm.beneficiary.firstNamePlaceholder"), id: id, value: value, onChangeText: onChange })) })) }), _jsx(Space, { width: 12 }), _jsx(Field, { name: "lastName", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.lastName"), style: styles.inputContainer, render: id => (_jsx(LakeTextInput, { error: error, placeholder: t("beneficiaryForm.beneficiary.lastNamePlaceholder"), id: id, value: value, onChangeText: onChange })) })) })] }), _jsxs(Box, { direction: small ? "column" : "row", children: [_jsx(Field, { name: "birthDate", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.birthDate"), style: styles.inputContainer, render: id => (_jsx(Rifm, { value: value ?? "", onChange: onChange, ...rifmDateProps, children: ({ value, onChange }) => (_jsx(LakeTextInput, { error: error, placeholder: locale.datePlaceholder, id: id, value: value, onChange: onChange })) })) })) }), _jsx(Space, { width: 12 }), _jsx(Field, { name: "birthCountryCode", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.birthCountry"), style: styles.inputContainer, render: id => (_jsx(CountryPicker, { id: id, error: error, value: value, placeholder: t("beneficiaryForm.beneficiary.birthCountryPlaceholder"), items: allCountriesItems, onValueChange: onChange })) })) })] }), _jsx(Box, { direction: small ? "column" : "row", children: _jsx(FieldsListener, { names: ["birthCountryCode"], children: ({ birthCountryCode }) => (_jsxs(_Fragment, { children: [_jsx(Field, { name: "birthCity", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.birthCity"), style: styles.inputContainer, render: id => (_jsx(GMapCityInput, { id: id, apiKey: googleMapApiKey, error: error, country: birthCountryCode.value, value: value ?? "", onValueChange: onChange, placeholder: birthCountryCode.value == null
|
|
273
292
|
? t("beneficiaryForm.beneficiary.fillBirthCountry")
|
|
274
293
|
: t("beneficiaryForm.beneficiary.birthCityPlaceholder"), onSuggestion: place => {
|
|
275
294
|
onChange(place.city);
|
|
276
295
|
setFieldValue("birthCityPostalCode", place.postalCode);
|
|
277
296
|
}, onLoadError: onCityLoadError })) })) }), _jsx(Space, { width: 12 }), _jsx(Field, { name: "birthCityPostalCode", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.birthPostalCode"), style: styles.inputContainer, render: id => (_jsx(LakeTextInput, { error: error, placeholder: birthCountryCode.value == null
|
|
278
297
|
? t("beneficiaryForm.beneficiary.fillBirthCountry")
|
|
279
|
-
: t("beneficiaryForm.beneficiary.birthPostalCodePlaceholder"), id: id, disabled: birthCountryCode.value === undefined, value: value, onChangeText: onChange })) })) })] })) }) }), _jsx(Field, { name: "type", children: ({ value, onChange }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.type"), type: "radioGroup", render: () => (_jsx(RadioGroup, { direction: "row", value: value, onValueChange: onChange, items: beneficiaryTypes })) })) }), _jsx(FieldsListener, { names: ["type"], children: ({ type }) => type.value === "HasCapital" ? (_jsxs(_Fragment, { children: [_jsx(Field, { name: "totalCapitalPercentage", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.totalCapitalPercentage"), render: id => (_jsx(LakeTextInput, { error: error, unit: "%", inputMode: "decimal", "aria-valuemin": 0, "aria-valuemax": 100, id: id, value: value, onChangeText: onChange })) })) }), _jsx(Space, { height: 12 }), _jsx(Field, { name: "capitalType", children: ({ value, error, onChange }) => (_jsx(CapitalTypeCheckboxes, { value: value ?? "none", error: error, onChange: onChange })) })] })) : null })] })))
|
|
280
|
-
.with("address", () => (_jsxs(View, { role: "form", children: [_jsx(Field, { name: "country", children: ({ value, onChange }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.country"), render: id => (_jsx(CountryPicker, { id: id, value: value, items: individualCountriesItems, onValueChange: onChange })) })) }), _jsx(Space, { height: 12 }), _jsx(FieldsListener, { names: ["country"], children: ({ country }) => (_jsxs(_Fragment, { children: [_jsx(AddressFormPart, { initialAddress: initialState?.residencyAddressLine1 ?? "", initialCity: initialState?.residencyAddressCity ?? "", initialPostalCode: initialState?.residencyAddressPostalCode ?? "", country: country?.value ?? accountCountry, label: t("beneficiaryForm.beneficiary.address"), Field: Field, setFieldValue: setFieldValue, listenFields: listenFields, isLarge: large }), country?.value === "DEU" && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 12 }), _jsx(Field, { name: "taxIdentificationNumber", children: ({ value, error, valid, onChange }) => (_jsx(
|
|
298
|
+
: t("beneficiaryForm.beneficiary.birthPostalCodePlaceholder"), id: id, disabled: birthCountryCode.value === undefined, value: value, onChangeText: onChange })) })) })] })) }) }), accountCountry === "ESP" && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 12 }), _jsx(Field, { name: "taxIdentificationNumber", children: ({ value, error, valid, onChange }) => (_jsx(TaxIdentificationNumberInput, { value: value ?? "", error: error, valid: valid, onChange: onChange, accountCountry: accountCountry, isCompany: false })) }), _jsx(Space, { height: 12 })] })), _jsx(Field, { name: "type", children: ({ value, onChange }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.type"), type: "radioGroup", render: () => (_jsx(RadioGroup, { direction: "row", value: value, onValueChange: onChange, items: beneficiaryTypes })) })) }), _jsx(FieldsListener, { names: ["type"], children: ({ type }) => type.value === "HasCapital" ? (_jsxs(_Fragment, { children: [_jsx(Field, { name: "totalCapitalPercentage", children: ({ value, onChange, error }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.totalCapitalPercentage"), render: id => (_jsx(LakeTextInput, { error: error, unit: "%", inputMode: "decimal", "aria-valuemin": 0, "aria-valuemax": 100, id: id, value: value, onChangeText: onChange })) })) }), _jsx(Space, { height: 12 }), _jsx(Field, { name: "capitalType", children: ({ value, error, onChange }) => (_jsx(CapitalTypeCheckboxes, { value: value ?? "none", error: error, onChange: onChange })) })] })) : null })] })))
|
|
299
|
+
.with("address", () => (_jsxs(View, { role: "form", children: [_jsx(Field, { name: "country", children: ({ value, onChange }) => (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.country"), render: id => (_jsx(CountryPicker, { id: id, value: value, items: individualCountriesItems, onValueChange: onChange })) })) }), _jsx(Space, { height: 12 }), _jsx(FieldsListener, { names: ["country"], children: ({ country }) => (_jsxs(_Fragment, { children: [_jsx(AddressFormPart, { apiKey: googleMapApiKey, initialAddress: initialState?.residencyAddressLine1 ?? "", initialCity: initialState?.residencyAddressCity ?? "", initialPostalCode: initialState?.residencyAddressPostalCode ?? "", country: country?.value ?? accountCountry, label: t("beneficiaryForm.beneficiary.address"), Field: Field, setFieldValue: setFieldValue, listenFields: listenFields, isLarge: large }), country?.value === "DEU" && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 12 }), _jsx(Field, { name: "taxIdentificationNumber", children: ({ value, error, valid, onChange }) => (_jsx(TaxIdentificationNumberInput, { value: value ?? "", error: error, valid: valid, onChange: onChange, accountCountry: accountCountry, isCompany: false,
|
|
300
|
+
// is mandatory for German accounts and UBO living in Germany
|
|
301
|
+
required: true })) })] }))] })) })] })))
|
|
281
302
|
.exhaustive(), isAddressRequired && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 12 }), _jsx(StepDots, { currentStep: step, steps: formSteps })] }))] })) }));
|
|
282
303
|
});
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import { CountryCCA3 } from "@swan-io/shared-business/src/constants/countries";
|
|
2
2
|
import { MutableRefObject } from "react";
|
|
3
3
|
import { StyleProp, ViewStyle } from "react-native";
|
|
4
|
-
|
|
5
|
-
completeAddress: string;
|
|
6
|
-
streetNumber: string;
|
|
7
|
-
city: string;
|
|
8
|
-
country: string;
|
|
9
|
-
postalCode: string;
|
|
10
|
-
};
|
|
4
|
+
import { PlaceDetail } from "../hooks/useGoogleMapSDK";
|
|
11
5
|
type Props = {
|
|
12
6
|
inputRef?: MutableRefObject<unknown>;
|
|
13
7
|
id?: string;
|
|
@@ -22,7 +16,7 @@ type Props = {
|
|
|
22
16
|
placeholder: string;
|
|
23
17
|
shouldDisplaySuggestions?: boolean;
|
|
24
18
|
emptyResultText: string;
|
|
25
|
-
apiKey
|
|
19
|
+
apiKey: string;
|
|
26
20
|
};
|
|
27
|
-
export declare const
|
|
21
|
+
export declare const GMapAddressSearchInput: ({ inputRef, id, country, disabled, error, value, onValueChange, onSuggestion, language, placeholder, shouldDisplaySuggestions, emptyResultText, apiKey, }: Props) => JSX.Element;
|
|
28
22
|
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Future, Result } from "@swan-io/boxed";
|
|
3
|
+
import { AutoWidthImage } from "@swan-io/lake/src/components/AutoWidthImage";
|
|
4
|
+
import { AutocompleteSearchInput } from "@swan-io/lake/src/components/AutocompleteSearchInput";
|
|
5
|
+
import { Box } from "@swan-io/lake/src/components/Box";
|
|
6
|
+
import { countriesWithMultipleCCA3, } from "@swan-io/shared-business/src/constants/countries";
|
|
7
|
+
import { useCallback, useMemo } from "react";
|
|
8
|
+
import { StyleSheet } from "react-native";
|
|
9
|
+
import poweredByGoogle from "../assets/images/powered_by_google_on_white_hdpi.png";
|
|
10
|
+
import { getPlaceDetails, useGoogleMapSDK } from "../hooks/useGoogleMapSDK";
|
|
11
|
+
const styles = StyleSheet.create({
|
|
12
|
+
poweredByGoogle: {
|
|
13
|
+
paddingVertical: 8,
|
|
14
|
+
paddingHorizontal: 16,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
export const GMapAddressSearchInput = ({ inputRef, id, country, disabled, error, value, onValueChange, onSuggestion, language, placeholder, shouldDisplaySuggestions = true, emptyResultText, apiKey, }) => {
|
|
18
|
+
const sdk = useGoogleMapSDK({
|
|
19
|
+
language,
|
|
20
|
+
apiKey,
|
|
21
|
+
});
|
|
22
|
+
const autocomplete = useMemo(() => sdk.map(google => new google.maps.places.AutocompleteService()), [sdk]);
|
|
23
|
+
const loadSuggestions = useCallback((value) => {
|
|
24
|
+
if (autocomplete.isDone()) {
|
|
25
|
+
return Future.fromPromise(autocomplete.get().getPlacePredictions({
|
|
26
|
+
input: value,
|
|
27
|
+
componentRestrictions: { country: countriesWithMultipleCCA3[country] ?? [country] },
|
|
28
|
+
types: ["address"],
|
|
29
|
+
})).mapOk(({ predictions }) => predictions.map(p => ({
|
|
30
|
+
id: p.place_id,
|
|
31
|
+
title: p.structured_formatting.main_text,
|
|
32
|
+
subtitle: p.structured_formatting.secondary_text,
|
|
33
|
+
value: null,
|
|
34
|
+
})), true);
|
|
35
|
+
}
|
|
36
|
+
return Future.value(Result.Ok([]));
|
|
37
|
+
}, [autocomplete, country]);
|
|
38
|
+
const onSuggestionSelected = useCallback((suggestion) => {
|
|
39
|
+
getPlaceDetails(suggestion.id).onResolve(result => {
|
|
40
|
+
if (result.isOk()) {
|
|
41
|
+
const dependsOnCountriesWithMultipleCCA3 = countriesWithMultipleCCA3[country]?.includes(result.value.country);
|
|
42
|
+
onSuggestion?.({
|
|
43
|
+
...result.value,
|
|
44
|
+
country: dependsOnCountriesWithMultipleCCA3 === true ? country : result.value.country,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}, [country, onSuggestion]);
|
|
49
|
+
return (_jsx(AutocompleteSearchInput, { inputRef: inputRef, value: value, onValueChange: onValueChange, disabled: disabled, id: id, placeholder: placeholder, error: error, emptyResultText: emptyResultText, ListFooterComponent: _jsx(Box, { direction: "row", justifyContent: "end", style: styles.poweredByGoogle, children: _jsx(AutoWidthImage, { height: 14, sourceUri: poweredByGoogle }) }), shouldDisplaySuggestions: shouldDisplaySuggestions, loadSuggestions: loadSuggestions, onSuggestion: onSuggestionSelected }));
|
|
50
|
+
};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { CountryCCA3 } from "../constants/countries";
|
|
3
|
-
|
|
4
|
-
city: string;
|
|
5
|
-
postalCode: string;
|
|
6
|
-
};
|
|
3
|
+
import { PlaceDetail } from "../hooks/useGoogleMapSDK";
|
|
7
4
|
type Props = {
|
|
8
5
|
value: string;
|
|
9
6
|
onValueChange: (value: string) => void;
|
|
@@ -12,9 +9,9 @@ type Props = {
|
|
|
12
9
|
error?: string;
|
|
13
10
|
id?: string;
|
|
14
11
|
placeholder?: string;
|
|
15
|
-
onSuggestion?: (place: PlaceDetail) => void;
|
|
12
|
+
onSuggestion?: (place: Pick<PlaceDetail, "city" | "postalCode">) => void;
|
|
16
13
|
onLoadError: (error: unknown) => void;
|
|
17
|
-
apiKey
|
|
14
|
+
apiKey: string;
|
|
18
15
|
};
|
|
19
|
-
export declare const
|
|
16
|
+
export declare const GMapCityInput: ({ value, onValueChange, country, disabled, placeholder, id, error, onSuggestion, onLoadError, apiKey, }: Props) => JSX.Element;
|
|
20
17
|
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Future, Result } from "@swan-io/boxed";
|
|
3
|
+
import { AutoWidthImage } from "@swan-io/lake/src/components/AutoWidthImage";
|
|
4
|
+
import { AutocompleteSearchInput } from "@swan-io/lake/src/components/AutocompleteSearchInput";
|
|
5
|
+
import { Box } from "@swan-io/lake/src/components/Box";
|
|
6
|
+
import { isNotNullish } from "@swan-io/lake/src/utils/nullish";
|
|
7
|
+
import { useCallback, useMemo } from "react";
|
|
8
|
+
import { StyleSheet } from "react-native";
|
|
9
|
+
import poweredByGoogle from "../assets/images/powered_by_google_on_white_hdpi.png";
|
|
10
|
+
import { countriesWithMultipleCCA3 } from "../constants/countries";
|
|
11
|
+
import { getPlaceDetails, useGoogleMapSDK } from "../hooks/useGoogleMapSDK";
|
|
12
|
+
import { t } from "../utils/i18n";
|
|
13
|
+
const styles = StyleSheet.create({
|
|
14
|
+
poweredByGoogle: {
|
|
15
|
+
paddingVertical: 8,
|
|
16
|
+
paddingHorizontal: 16,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
export const GMapCityInput = ({ value, onValueChange, country, disabled, placeholder, id, error, onSuggestion, onLoadError, apiKey, }) => {
|
|
20
|
+
const sdk = useGoogleMapSDK({ apiKey });
|
|
21
|
+
const autocomplete = useMemo(() => sdk.map(google => new google.maps.places.AutocompleteService()), [sdk]);
|
|
22
|
+
const loadSuggestions = useCallback((value) => {
|
|
23
|
+
if (autocomplete.isDone() && isNotNullish(country)) {
|
|
24
|
+
return Future.fromPromise(autocomplete.get().getPlacePredictions({
|
|
25
|
+
input: value,
|
|
26
|
+
componentRestrictions: {
|
|
27
|
+
country: [...(countriesWithMultipleCCA3[country] ?? "")],
|
|
28
|
+
},
|
|
29
|
+
types: ["(cities)"],
|
|
30
|
+
})).mapOk(({ predictions }) => predictions.map(p => ({
|
|
31
|
+
id: p.place_id,
|
|
32
|
+
title: p.structured_formatting.main_text,
|
|
33
|
+
subtitle: p.structured_formatting.secondary_text,
|
|
34
|
+
value: null,
|
|
35
|
+
})), true);
|
|
36
|
+
}
|
|
37
|
+
return Future.value(Result.Ok([]));
|
|
38
|
+
}, [autocomplete, country]);
|
|
39
|
+
const onSuggestionSelected = useCallback((suggestion) => {
|
|
40
|
+
getPlaceDetails(suggestion.id).onResolve(result => {
|
|
41
|
+
if (result.isOk()) {
|
|
42
|
+
console.log("result.value", result.value);
|
|
43
|
+
onSuggestion?.(result.value);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}, [onSuggestion]);
|
|
47
|
+
return (_jsx(AutocompleteSearchInput, { value: value, onValueChange: onValueChange, disabled: disabled, id: id, placeholder: placeholder, error: error, emptyResultText: t("common.noResult"), ListFooterComponent: _jsx(Box, { direction: "row", justifyContent: "end", style: styles.poweredByGoogle, children: _jsx(AutoWidthImage, { height: 14, sourceUri: poweredByGoogle }) }), loadSuggestions: loadSuggestions, onSuggestion: onSuggestionSelected, onLoadError: onLoadError }));
|
|
48
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { AccountCountry } from "../utils/templateTranslations";
|
|
3
|
+
type Props = {
|
|
4
|
+
value: string;
|
|
5
|
+
error: string | undefined;
|
|
6
|
+
valid: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
onChange: (value: string) => void;
|
|
9
|
+
onBlur?: () => void;
|
|
10
|
+
accountCountry: AccountCountry;
|
|
11
|
+
isCompany: boolean;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare const TaxIdentificationNumberInput: ({ value, error, valid, disabled, onChange, onBlur, accountCountry, isCompany, required, }: Props) => JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from "@swan-io/lake/src/components/Icon";
|
|
3
|
+
import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
|
|
4
|
+
import { LakeTextInput } from "@swan-io/lake/src/components/LakeTextInput";
|
|
5
|
+
import { LakeTooltip } from "@swan-io/lake/src/components/LakeTooltip";
|
|
6
|
+
import { colors } from "@swan-io/lake/src/constants/design";
|
|
7
|
+
import { t } from "../utils/i18n";
|
|
8
|
+
import { getCompanyTaxNumberHelp, getCompanyTaxNumberPlaceholder, getIndividualTaxNumberHelp, getIndividualTaxNumberPlaceholder, getTaxNumberTooltip, } from "../utils/templateTranslations";
|
|
9
|
+
export const TaxIdentificationNumberInput = ({ value, error, valid, disabled, onChange, onBlur, accountCountry, isCompany, required, }) => {
|
|
10
|
+
return (_jsx(LakeLabel, { label: t("beneficiaryForm.beneficiary.taxIdentificationNumber"), optionalLabel: required === true ? undefined : t("common.optional"), help: _jsx(LakeTooltip, { content: getTaxNumberTooltip(accountCountry), placement: "top", width: accountCountry === "DEU" ? 800 : undefined, children: _jsx(Icon, { name: "question-circle-regular", size: 16, color: colors.gray[600] }) }), render: id => (_jsx(LakeTextInput, { id: id, placeholder: isCompany
|
|
11
|
+
? getCompanyTaxNumberPlaceholder(accountCountry)
|
|
12
|
+
: getIndividualTaxNumberPlaceholder(accountCountry), help: isCompany
|
|
13
|
+
? getCompanyTaxNumberHelp(accountCountry)
|
|
14
|
+
: getIndividualTaxNumberHelp(accountCountry), value: value, error: error, valid: valid, disabled: disabled, onChangeText: onChange, onBlur: onBlur })) }));
|
|
15
|
+
};
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/// <reference types="google.maps" />
|
|
2
|
-
import { AsyncData } from "@swan-io/boxed";
|
|
2
|
+
import { AsyncData, Future, Result } from "@swan-io/boxed";
|
|
3
3
|
type Props = {
|
|
4
4
|
language?: string;
|
|
5
|
-
apiKey
|
|
5
|
+
apiKey: string;
|
|
6
6
|
};
|
|
7
7
|
export declare const useGoogleMapSDK: ({ language, apiKey }: Props) => AsyncData<typeof google>;
|
|
8
|
+
export type PlaceDetail = {
|
|
9
|
+
completeAddress: string;
|
|
10
|
+
streetNumber: string;
|
|
11
|
+
city: string;
|
|
12
|
+
country: string;
|
|
13
|
+
postalCode: string;
|
|
14
|
+
};
|
|
15
|
+
export declare const getPlaceDetails: (placeId: string) => Future<Result<PlaceDetail, unknown>>;
|
|
8
16
|
export {};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Loader } from "@googlemaps/js-api-loader";
|
|
2
|
-
import { AsyncData } from "@swan-io/boxed";
|
|
2
|
+
import { AsyncData, Future, Result } from "@swan-io/boxed";
|
|
3
3
|
import { useEffect } from "react";
|
|
4
4
|
import { atom, useAtom } from "react-atomic-state";
|
|
5
|
+
import { match } from "ts-pattern";
|
|
6
|
+
import { getCCA3forCCA2 } from "../constants/countries";
|
|
5
7
|
const sdk = atom(AsyncData.NotAsked());
|
|
6
8
|
export const useGoogleMapSDK = ({ language, apiKey }) => {
|
|
7
9
|
const googleSdk = useAtom(sdk);
|
|
@@ -21,3 +23,40 @@ export const useGoogleMapSDK = ({ language, apiKey }) => {
|
|
|
21
23
|
}, [language, googleSdk, apiKey]);
|
|
22
24
|
return googleSdk;
|
|
23
25
|
};
|
|
26
|
+
export const getPlaceDetails = (placeId) => {
|
|
27
|
+
return Future.make(resolve => {
|
|
28
|
+
const place = new google.maps.places.PlacesService(document.createElement("div"));
|
|
29
|
+
place.getDetails({ placeId }, placeDetail => {
|
|
30
|
+
if (placeDetail?.address_components == null) {
|
|
31
|
+
resolve(Result.Error("No place detail found"));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const result = {
|
|
35
|
+
completeAddress: "",
|
|
36
|
+
streetNumber: "",
|
|
37
|
+
postalCode: "",
|
|
38
|
+
country: "",
|
|
39
|
+
city: "",
|
|
40
|
+
};
|
|
41
|
+
placeDetail.address_components.forEach(({ types, short_name, long_name }) => {
|
|
42
|
+
const type = types[0];
|
|
43
|
+
match(type)
|
|
44
|
+
.with("street_number", () => (result.streetNumber = long_name))
|
|
45
|
+
.with("route", () => (result.completeAddress = long_name))
|
|
46
|
+
.with("postal_code", () => (result.postalCode = short_name))
|
|
47
|
+
.with("country", () => (result.country = getCCA3forCCA2(short_name) ?? short_name))
|
|
48
|
+
.with("locality", () => (result.city = long_name))
|
|
49
|
+
.otherwise(() => { });
|
|
50
|
+
});
|
|
51
|
+
if (result.streetNumber != "") {
|
|
52
|
+
if (placeDetail?.name === `${result.completeAddress} ${result.streetNumber}`) {
|
|
53
|
+
result.completeAddress = `${result.completeAddress} ${result.streetNumber}`;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
result.completeAddress = `${result.streetNumber} ${result.completeAddress}`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
resolve(Result.Ok(result));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
};
|
package/src/locales/en.json
CHANGED
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"businessActivity.scientificActivities": "Scientific activities",
|
|
48
48
|
"businessActivity.transportation": "Transportation",
|
|
49
49
|
"common.form.invalidTaxIdentificationNumber": "This tax identification number is invalid",
|
|
50
|
+
"common.form.taxIdentificationNumber.label": "Tax identification number",
|
|
51
|
+
"common.form.taxIdentificationNumber.placeholder": "Tax ID",
|
|
52
|
+
"common.form.taxIdentificationNumber.tooltip.deu": "Identifikationsnummer (also Steuerliche Identifikationsnummer, Persönliche Identificationsnummer, Identifikationsnummer, Steuer-IdNr., IdNr or Steuer-ID, according to § 139b AO)",
|
|
53
|
+
"common.form.help.nbCharacters": "{nbCharacters} characters",
|
|
54
|
+
"common.form.help.nbDigits": "{nbDigits} digits",
|
|
50
55
|
"common.noResult": "Empty result",
|
|
51
56
|
"common.optional": "Optional",
|
|
52
57
|
"common.skipToContent": "Skip to content",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type AccountCountry = "DEU" | "ESP" | "FRA";
|
|
2
|
+
export declare const getIndividualTaxNumberPlaceholder: (accountCountry: AccountCountry) => string;
|
|
3
|
+
export declare const getCompanyTaxNumberPlaceholder: (accountCountry: AccountCountry) => string;
|
|
4
|
+
export declare const getTaxNumberTooltip: (accountCountry: AccountCountry) => string;
|
|
5
|
+
export declare const getCompanyTaxNumberHelp: (accountCountry: AccountCountry) => string;
|
|
6
|
+
export declare const getIndividualTaxNumberHelp: (accountCountry: AccountCountry) => string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { match } from "ts-pattern";
|
|
2
|
+
import { t } from "./i18n";
|
|
3
|
+
export const getIndividualTaxNumberPlaceholder = (accountCountry) => match(accountCountry)
|
|
4
|
+
.with("DEU", () => `${t('common.form.taxIdentificationNumber.placeholder')} (Steueridentifikationsnummer)`)
|
|
5
|
+
.with("ESP", () => `${t('common.form.taxIdentificationNumber.placeholder')} (Número de Identificación Fiscal)`)
|
|
6
|
+
.otherwise(() => t('common.form.taxIdentificationNumber.placeholder'));
|
|
7
|
+
export const getCompanyTaxNumberPlaceholder = (accountCountry) => match(accountCountry)
|
|
8
|
+
.with("DEU", () => `${t('common.form.taxIdentificationNumber.placeholder')} (Steuer-Nummer)`)
|
|
9
|
+
.with("ESP", () => `${t('common.form.taxIdentificationNumber.placeholder')} (Número de Identificación Fiscal)`)
|
|
10
|
+
.otherwise(() => t('common.form.taxIdentificationNumber.placeholder'));
|
|
11
|
+
export const getTaxNumberTooltip = (accountCountry) => match(accountCountry)
|
|
12
|
+
.with("DEU", () => t("common.form.taxIdentificationNumber.tooltip.deu"))
|
|
13
|
+
.with("ESP", () => "Número de Identificación Fiscal") // no need to translate
|
|
14
|
+
.otherwise(() => '');
|
|
15
|
+
export const getCompanyTaxNumberHelp = (accountCountry) => match(accountCountry)
|
|
16
|
+
.with("DEU", () => t("common.form.help.nbDigits", { nbDigits: '10-11' }))
|
|
17
|
+
.with("ESP", () => t("common.form.help.nbCharacters", { nbCharacters: '9' }))
|
|
18
|
+
.otherwise(() => '');
|
|
19
|
+
export const getIndividualTaxNumberHelp = (accountCountry) => match(accountCountry)
|
|
20
|
+
.with("DEU", () => t("common.form.help.nbDigits", { nbDigits: '11' }))
|
|
21
|
+
.with("ESP", () => t("common.form.help.nbCharacters", { nbCharacters: '9' }))
|
|
22
|
+
.otherwise(() => '');
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Validator } from "react-ux-form";
|
|
2
|
+
import { AccountCountry } from "./templateTranslations";
|
|
2
3
|
export declare const isValidVatNumber: (maybeVat: string) => boolean;
|
|
3
4
|
export declare const validateNullableRequired: Validator<string | undefined>;
|
|
4
5
|
export declare const validateRequired: Validator<string>;
|
|
5
6
|
export declare const validateBooleanRequired: Validator<boolean | undefined>;
|
|
6
|
-
export declare const validateIndividualTaxNumber: Validator<string | undefined>;
|
|
7
|
-
export declare const validateCompanyTaxNumber: Validator<string | undefined>;
|
|
7
|
+
export declare const validateIndividualTaxNumber: (accountCountry: AccountCountry) => Validator<string | undefined>;
|
|
8
|
+
export declare const validateCompanyTaxNumber: (accountCountry: AccountCountry) => Validator<string | undefined>;
|
package/src/utils/validation.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { noop } from "@swan-io/lake/src/utils/function";
|
|
2
|
+
import { match } from "ts-pattern";
|
|
1
3
|
import { t } from "./i18n";
|
|
2
4
|
const VAT_NUMBER_REGEX = /^((AT)?U[0-9]{8}|(BE)?0[0-9]{9}|(BG)?[0-9]{9,10}|(CY)?[0-9]{8}L|(CZ)?[0-9]{8,10}|(DE)?[0-9]{9}|(DK)?[0-9]{8}|(EE)?[0-9]{9}|(EL|GR)?[0-9]{9}|(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]|(FI)?[0-9]{8}|(FR)?[0-9A-Z]{2}[0-9]{9}|(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})|(HU)?[0-9]{8}|(IE)?[0-9]S[0-9]{5}L|(IT)?[0-9]{11}|(LT)?([0-9]{9}|[0-9]{12})|(LU)?[0-9]{8}|(LV)?[0-9]{11}|(MT)?[0-9]{8}|(NL)?[0-9]{9}B[0-9]{2}|(PL)?[0-9]{10}|(PT)?[0-9]{9}|(RO)?[0-9]{2,10}|(SE)?[0-9]{12}|(SI)?[0-9]{8}|(SK)?[0-9]{10})$/;
|
|
3
5
|
// (AT)?U[0-9]{8} | # Austria
|
|
@@ -45,21 +47,41 @@ export const validateBooleanRequired = value => {
|
|
|
45
47
|
return t("error.requiredField");
|
|
46
48
|
}
|
|
47
49
|
};
|
|
48
|
-
export const validateIndividualTaxNumber = value => {
|
|
50
|
+
export const validateIndividualTaxNumber = (accountCountry) => value => {
|
|
49
51
|
if (value == null || !value) {
|
|
50
52
|
return;
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
return match(accountCountry)
|
|
55
|
+
.with("DEU", () => {
|
|
56
|
+
// accept 11 digits
|
|
57
|
+
if (!/^\d{11}$/.test(value)) {
|
|
58
|
+
return t("common.form.invalidTaxIdentificationNumber");
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
.with("ESP", () => {
|
|
62
|
+
// accept 9 characters
|
|
63
|
+
if (!/^[a-zA-Z0-9]{9}$/.test(value)) {
|
|
64
|
+
return t("common.form.invalidTaxIdentificationNumber");
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
.otherwise(noop);
|
|
56
68
|
};
|
|
57
|
-
export const validateCompanyTaxNumber = value => {
|
|
69
|
+
export const validateCompanyTaxNumber = (accountCountry) => value => {
|
|
58
70
|
if (value == null || !value) {
|
|
59
71
|
return;
|
|
60
72
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
return match(accountCountry)
|
|
74
|
+
.with("DEU", () => {
|
|
75
|
+
// accept 10 or 11 digits
|
|
76
|
+
if (!/^\d{10,11}$/.test(value)) {
|
|
77
|
+
return t("common.form.invalidTaxIdentificationNumber");
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
.with("ESP", () => {
|
|
81
|
+
// accept 9 characters
|
|
82
|
+
if (!/^[a-zA-Z0-9]{9}$/.test(value)) {
|
|
83
|
+
return t("common.form.invalidTaxIdentificationNumber");
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
.otherwise(noop);
|
|
65
87
|
};
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { AsyncData, Future } from "@swan-io/boxed";
|
|
3
|
-
import { AutoWidthImage } from "@swan-io/lake/src/components/AutoWidthImage";
|
|
4
|
-
import { Box } from "@swan-io/lake/src/components/Box";
|
|
5
|
-
import { LakeCombobox } from "@swan-io/lake/src/components/LakeCombobox";
|
|
6
|
-
import { LakeTextInput } from "@swan-io/lake/src/components/LakeTextInput";
|
|
7
|
-
import { colors } from "@swan-io/lake/src/constants/colors";
|
|
8
|
-
import { typography } from "@swan-io/lake/src/constants/typography";
|
|
9
|
-
import { useFirstMountState } from "@swan-io/lake/src/hooks/useFirstMountState";
|
|
10
|
-
import { countriesWithMultipleCCA3, getCCA3forCCA2, } from "@swan-io/shared-business/src/constants/countries";
|
|
11
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
12
|
-
import { StyleSheet, Text } from "react-native";
|
|
13
|
-
import { match } from "ts-pattern";
|
|
14
|
-
import poweredByGoogle from "../assets/images/powered_by_google_on_white_hdpi.png";
|
|
15
|
-
import { useGoogleMapSDK } from "../hooks/useGoogleMapSDK";
|
|
16
|
-
const styles = StyleSheet.create({
|
|
17
|
-
itemTitle: {
|
|
18
|
-
...typography.bodyLarge,
|
|
19
|
-
lineHeight: typography.lineHeights.title,
|
|
20
|
-
userSelect: "none",
|
|
21
|
-
},
|
|
22
|
-
itemSubtitle: {
|
|
23
|
-
...typography.bodySmall,
|
|
24
|
-
color: colors.gray[50],
|
|
25
|
-
userSelect: "none",
|
|
26
|
-
},
|
|
27
|
-
poweredByGoogle: {
|
|
28
|
-
paddingVertical: 8,
|
|
29
|
-
paddingHorizontal: 16,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
export const AddressSearchInput = ({ inputRef, id, country, disabled, error, value: initialValue, onValueChange, onSuggestion, language, placeholder, shouldDisplaySuggestions = true, emptyResultText, apiKey, }) => {
|
|
33
|
-
const isFirstMount = useFirstMountState();
|
|
34
|
-
const [state, setState] = useState(AsyncData.NotAsked());
|
|
35
|
-
const [value, setValue] = useState(initialValue ?? "");
|
|
36
|
-
const showTriggerSearchRef = useRef(true);
|
|
37
|
-
const sdk = useGoogleMapSDK({
|
|
38
|
-
language,
|
|
39
|
-
apiKey,
|
|
40
|
-
});
|
|
41
|
-
const autocomplete = useMemo(() => sdk.map(google => new google.maps.places.AutocompleteService()), [sdk]);
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (!isFirstMount && initialValue != null) {
|
|
44
|
-
setValue(initialValue);
|
|
45
|
-
}
|
|
46
|
-
}, [isFirstMount, initialValue]);
|
|
47
|
-
useEffect(() => {
|
|
48
|
-
if (value.length <= 3 || !shouldDisplaySuggestions || showTriggerSearchRef.current === false) {
|
|
49
|
-
return setState(AsyncData.NotAsked());
|
|
50
|
-
}
|
|
51
|
-
setState(AsyncData.Loading());
|
|
52
|
-
const request = Future.make(resolve => {
|
|
53
|
-
const timeoutId = setTimeout(() => {
|
|
54
|
-
if (autocomplete.isDone()) {
|
|
55
|
-
Future.fromPromise(autocomplete.get().getPlacePredictions({
|
|
56
|
-
input: value,
|
|
57
|
-
componentRestrictions: { country: countriesWithMultipleCCA3[country] ?? [country] },
|
|
58
|
-
types: ["address"],
|
|
59
|
-
})).onResolve(resolve);
|
|
60
|
-
}
|
|
61
|
-
}, 250);
|
|
62
|
-
return () => clearTimeout(timeoutId);
|
|
63
|
-
}).mapOk(({ predictions }) => predictions.map(p => ({ value: p.place_id, prediction: p })), true);
|
|
64
|
-
request.onResolve(value => setState(AsyncData.Done(value)));
|
|
65
|
-
return () => request.cancel();
|
|
66
|
-
}, [country, value, autocomplete, shouldDisplaySuggestions]);
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
showTriggerSearchRef.current = true;
|
|
69
|
-
}, [value]);
|
|
70
|
-
return sdk.match({
|
|
71
|
-
NotAsked: () => null,
|
|
72
|
-
Loading: () => null,
|
|
73
|
-
Done: google => {
|
|
74
|
-
if (shouldDisplaySuggestions) {
|
|
75
|
-
const place = new google.maps.places.PlacesService(document.createElement("div"));
|
|
76
|
-
const selectAddress = (suggestion) => {
|
|
77
|
-
place.getDetails({ placeId: suggestion.value }, placeDetail => {
|
|
78
|
-
const result = {
|
|
79
|
-
completeAddress: "",
|
|
80
|
-
streetNumber: "",
|
|
81
|
-
postalCode: "",
|
|
82
|
-
country: "",
|
|
83
|
-
city: "",
|
|
84
|
-
};
|
|
85
|
-
placeDetail?.address_components?.forEach(({ types, short_name, long_name }) => {
|
|
86
|
-
const type = types[0];
|
|
87
|
-
match(type)
|
|
88
|
-
.with("street_number", () => (result.streetNumber = long_name))
|
|
89
|
-
.with("route", () => (result.completeAddress = long_name))
|
|
90
|
-
.with("postal_code", () => (result.postalCode = short_name))
|
|
91
|
-
.with("country", () => (result.country = getCCA3forCCA2(short_name) ?? short_name))
|
|
92
|
-
.with("locality", () => (result.city = long_name))
|
|
93
|
-
.otherwise(() => { });
|
|
94
|
-
});
|
|
95
|
-
if (result.streetNumber != "") {
|
|
96
|
-
if (placeDetail?.name === `${result.completeAddress} ${result.streetNumber}`) {
|
|
97
|
-
result.completeAddress = `${result.completeAddress} ${result.streetNumber}`;
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
result.completeAddress = `${result.streetNumber} ${result.completeAddress}`;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
showTriggerSearchRef.current = false;
|
|
104
|
-
setValue(result.completeAddress);
|
|
105
|
-
onSuggestion?.(result);
|
|
106
|
-
});
|
|
107
|
-
};
|
|
108
|
-
return (_jsx(LakeCombobox, { inputRef: inputRef, id: id, placeholder: placeholder, value: value, items: state, icon: "search-filled", disabled: disabled, error: error, ListFooterComponent: _jsx(Box, { direction: "row", justifyContent: "end", style: styles.poweredByGoogle, children: _jsx(AutoWidthImage, { height: 14, sourceUri: poweredByGoogle }) }), onSelectItem: suggestion => selectAddress(suggestion), onValueChange: value => {
|
|
109
|
-
setValue(value);
|
|
110
|
-
onValueChange(value);
|
|
111
|
-
}, keyExtractor: item => item.value, emptyResultText: emptyResultText, renderItem: item => (_jsxs(_Fragment, { children: [_jsx(Text, { numberOfLines: 1, style: styles.itemTitle, children: item.prediction.structured_formatting.main_text }), _jsx(Text, { numberOfLines: 1, style: styles.itemSubtitle, children: item.prediction.structured_formatting.secondary_text })] })) }));
|
|
112
|
-
}
|
|
113
|
-
return (_jsx(LakeTextInput, { ref: inputRef, id: id, placeholder: placeholder, value: value, icon: "search-filled", disabled: disabled, error: error, onChangeText: value => {
|
|
114
|
-
setValue(value);
|
|
115
|
-
onValueChange(value);
|
|
116
|
-
} }));
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
};
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { AsyncData, Future } from "@swan-io/boxed";
|
|
3
|
-
import { AutoWidthImage } from "@swan-io/lake/src/components/AutoWidthImage";
|
|
4
|
-
import { Box } from "@swan-io/lake/src/components/Box";
|
|
5
|
-
import { LakeCombobox } from "@swan-io/lake/src/components/LakeCombobox";
|
|
6
|
-
import { colors } from "@swan-io/lake/src/constants/design";
|
|
7
|
-
import { typography } from "@swan-io/lake/src/constants/typography";
|
|
8
|
-
import { isNotNullish, isNullish } from "@swan-io/lake/src/utils/nullish";
|
|
9
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
10
|
-
import { StyleSheet, Text } from "react-native";
|
|
11
|
-
import { match } from "ts-pattern";
|
|
12
|
-
import poweredByGoogle from "../assets/images/powered_by_google_on_white_hdpi.png";
|
|
13
|
-
import { countriesWithMultipleCCA3 } from "../constants/countries";
|
|
14
|
-
import { useGoogleMapSDK } from "../hooks/useGoogleMapSDK";
|
|
15
|
-
import { t } from "../utils/i18n";
|
|
16
|
-
const styles = StyleSheet.create({
|
|
17
|
-
itemTitle: {
|
|
18
|
-
...typography.bodyLarge,
|
|
19
|
-
lineHeight: typography.lineHeights.title,
|
|
20
|
-
userSelect: "none",
|
|
21
|
-
},
|
|
22
|
-
itemSubtitle: {
|
|
23
|
-
...typography.bodySmall,
|
|
24
|
-
color: colors.gray[400],
|
|
25
|
-
userSelect: "none",
|
|
26
|
-
},
|
|
27
|
-
poweredByGoogle: {
|
|
28
|
-
paddingVertical: 8,
|
|
29
|
-
paddingHorizontal: 16,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
export const LakeCityInput = ({ value, onValueChange, country, disabled, placeholder, id, error, onSuggestion, onLoadError, apiKey, }) => {
|
|
33
|
-
const [state, setState] = useState(AsyncData.NotAsked());
|
|
34
|
-
const comboboxRef = useRef(null);
|
|
35
|
-
const sdk = useGoogleMapSDK({ apiKey });
|
|
36
|
-
const autocomplete = useMemo(() => sdk.map(google => new google.maps.places.AutocompleteService()), [sdk]);
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
if (value.length <= 3) {
|
|
39
|
-
return setState(AsyncData.NotAsked());
|
|
40
|
-
}
|
|
41
|
-
setState(AsyncData.Loading());
|
|
42
|
-
if (isNotNullish(country)) {
|
|
43
|
-
const request = Future.make(resolve => {
|
|
44
|
-
const timeoutId = setTimeout(() => {
|
|
45
|
-
if (autocomplete.isDone()) {
|
|
46
|
-
Future.fromPromise(autocomplete.get().getPlacePredictions({
|
|
47
|
-
input: value,
|
|
48
|
-
componentRestrictions: {
|
|
49
|
-
country: [...(countriesWithMultipleCCA3[country] ?? "")],
|
|
50
|
-
},
|
|
51
|
-
types: ["(cities)"],
|
|
52
|
-
})).onResolve(resolve);
|
|
53
|
-
}
|
|
54
|
-
}, 250);
|
|
55
|
-
return () => clearTimeout(timeoutId);
|
|
56
|
-
}).mapOk(({ predictions }) => predictions.map(p => ({ value: p.place_id, prediction: p })), true);
|
|
57
|
-
request.tapError(onLoadError).onResolve(value => setState(AsyncData.Done(value)));
|
|
58
|
-
return () => request.cancel();
|
|
59
|
-
}
|
|
60
|
-
}, [country, value, autocomplete, onLoadError]);
|
|
61
|
-
return sdk.match({
|
|
62
|
-
NotAsked: () => null,
|
|
63
|
-
Loading: () => null,
|
|
64
|
-
Done: google => {
|
|
65
|
-
const place = new google.maps.places.PlacesService(document.createElement("div"));
|
|
66
|
-
const selectAddress = (suggestion) => {
|
|
67
|
-
place.getDetails({ placeId: suggestion.value }, placeDetail => {
|
|
68
|
-
const result = {
|
|
69
|
-
postalCode: "",
|
|
70
|
-
city: "",
|
|
71
|
-
};
|
|
72
|
-
placeDetail?.address_components?.forEach(({ types, short_name, long_name }) => {
|
|
73
|
-
const type = types[0];
|
|
74
|
-
match(type)
|
|
75
|
-
.with("postal_code", () => (result.postalCode = short_name))
|
|
76
|
-
.with("locality", () => (result.city = long_name))
|
|
77
|
-
.otherwise(() => { });
|
|
78
|
-
});
|
|
79
|
-
onSuggestion?.(result);
|
|
80
|
-
if (comboboxRef.current) {
|
|
81
|
-
comboboxRef.current.close();
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
};
|
|
85
|
-
return (_jsx(LakeCombobox, { ref: comboboxRef, placeholder: placeholder, value: value, items: state, icon: "search-filled", disabled: isNullish(country) || disabled, error: error, onValueChange: onValueChange, id: id, ListFooterComponent: _jsx(Box, { direction: "row", justifyContent: "end", style: styles.poweredByGoogle, children: _jsx(AutoWidthImage, { height: 14, sourceUri: poweredByGoogle }) }), onSelectItem: selectAddress, emptyResultText: t("common.noResult"), keyExtractor: item => item.value, renderItem: item => (_jsxs(_Fragment, { children: [_jsx(Text, { numberOfLines: 1, style: styles.itemTitle, children: item.prediction.structured_formatting.main_text }), _jsx(Text, { numberOfLines: 1, style: styles.itemSubtitle, children: item.prediction.structured_formatting.secondary_text })] })) }));
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
};
|