@swan-io/shared-business 8.13.0 → 8.14.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/shared-business",
3
- "version": "8.13.0",
3
+ "version": "8.14.0",
4
4
  "engines": {
5
5
  "node": ">=20.9.0",
6
6
  "yarn": "^1.22.0"
@@ -112,11 +112,14 @@ export const ChoicePicker = ({ tile = true, items, getId = identity, large = fal
112
112
  return;
113
113
  }
114
114
  // auto scroll to selected value on mobile
115
- const scrollContainer = containerRef.current;
115
+ const container = containerRef.current;
116
+ if (container == null) {
117
+ return;
118
+ }
116
119
  const index = items.findIndex(item => value === item);
117
- if (index !== -1 && scrollContainer instanceof HTMLDivElement) {
118
- const width = scrollContainer.offsetWidth;
119
- scrollContainer.scrollTo({ x: index * width, animated: false });
120
+ if (index !== -1 && container.element != null) {
121
+ const width = container.element.offsetWidth;
122
+ container.scrollTo({ x: index * width, animated: false });
120
123
  }
121
124
  // if no value is selected, select first item
122
125
  if (value == null && items[0] != null) {
@@ -129,10 +132,13 @@ export const ChoicePicker = ({ tile = true, items, getId = identity, large = fal
129
132
  if (desktop) {
130
133
  return;
131
134
  }
132
- const scrollContainer = containerRef.current;
133
- if (scrollContainer instanceof HTMLDivElement) {
134
- const scrollLeft = scrollContainer.scrollLeft;
135
- const width = scrollContainer.offsetWidth;
135
+ const container = containerRef.current;
136
+ if (container == null) {
137
+ return;
138
+ }
139
+ if (container.element != null) {
140
+ const scrollLeft = container.element.scrollLeft;
141
+ const width = container.element.offsetWidth;
136
142
  const index = clampValue(0, items.length - 1)(Math.round(scrollLeft / width));
137
143
  const item = items[index];
138
144
  if (item != null) {
@@ -146,37 +152,43 @@ export const ChoicePicker = ({ tile = true, items, getId = identity, large = fal
146
152
  };
147
153
  const onPressPrevious = () => {
148
154
  var _a;
149
- const scrollContainer = containerRef.current;
150
- if (scrollContainer instanceof HTMLDivElement) {
151
- const scrollLeft = scrollContainer.scrollLeft;
152
- const width = scrollContainer.offsetWidth;
155
+ const container = containerRef.current;
156
+ if (container == null) {
157
+ return;
158
+ }
159
+ if (container.element != null) {
160
+ const scrollLeft = container.element.scrollLeft;
161
+ const width = container.element.offsetWidth;
153
162
  const index = Math.round(scrollLeft / width);
154
163
  const previousIndex = Math.max(0, index - 1);
155
164
  // remove scroll snap during scroll animation to avoid weird behavior on older browsers
156
- scrollContainer.style.scrollSnapType = "none";
165
+ container.element.style.scrollSnapType = "none";
157
166
  (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo({ x: previousIndex * width, animated: true });
158
- detectScrollAnimationEnd(scrollContainer).onResolve(() => {
167
+ detectScrollAnimationEnd(container.element).onResolve(() => {
159
168
  // set back scroll snap
160
169
  // @ts-expect-error
161
- scrollContainer.style.scrollSnapType = null;
170
+ container.element.style.scrollSnapType = null;
162
171
  });
163
172
  }
164
173
  };
165
174
  const onPressNext = () => {
166
175
  var _a;
167
- const scrollContainer = containerRef.current;
168
- if (scrollContainer instanceof HTMLDivElement) {
169
- const scrollLeft = scrollContainer.scrollLeft;
170
- const width = scrollContainer.offsetWidth;
176
+ const container = containerRef.current;
177
+ if (container == null) {
178
+ return;
179
+ }
180
+ if (container.element != null) {
181
+ const scrollLeft = container.element.scrollLeft;
182
+ const width = container.element.offsetWidth;
171
183
  const index = Math.round(scrollLeft / width);
172
184
  const nextIndex = Math.min(items.length - 1, index + 1);
173
185
  // remove scroll snap during scroll animation to avoid weird behavior on older browsers
174
- scrollContainer.style.scrollSnapType = "none";
186
+ container.element.style.scrollSnapType = "none";
175
187
  (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo({ x: nextIndex * width, animated: true });
176
- detectScrollAnimationEnd(scrollContainer).onResolve(() => {
188
+ detectScrollAnimationEnd(container.element).onResolve(() => {
177
189
  // set back scroll snap
178
190
  // @ts-expect-error
179
- scrollContainer.style.scrollSnapType = null;
191
+ container.element.style.scrollSnapType = null;
180
192
  });
181
193
  }
182
194
  };
@@ -0,0 +1,10 @@
1
+ import { ExtractedDate } from "../utils/date";
2
+ export type InlineDatePickerProps = {
3
+ label: string;
4
+ value: ExtractedDate | undefined;
5
+ onValueChange: (value: ExtractedDate) => void;
6
+ error?: string;
7
+ onBlur?: () => void;
8
+ order: "day-month-year" | "month-day-year";
9
+ };
10
+ export declare const InlineDatePicker: ({ value, label, onValueChange, error, onBlur, order, }: InlineDatePickerProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box } from "@swan-io/lake/src/components/Box";
3
+ import { InputError } from "@swan-io/lake/src/components/InputError";
4
+ import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
5
+ import { LakeSelect } from "@swan-io/lake/src/components/LakeSelect";
6
+ import { LakeTextInput } from "@swan-io/lake/src/components/LakeTextInput";
7
+ import { Stack } from "@swan-io/lake/src/components/Stack";
8
+ import { colors } from "@swan-io/lake/src/constants/design";
9
+ import { isNotNullish } from "@swan-io/lake/src/utils/nullish";
10
+ import { StyleSheet, View } from "react-native";
11
+ import { t } from "../utils/i18n";
12
+ const months = [
13
+ { value: "01", name: t("datePicker.month.january") },
14
+ { value: "02", name: t("datePicker.month.february") },
15
+ { value: "03", name: t("datePicker.month.march") },
16
+ { value: "04", name: t("datePicker.month.april") },
17
+ { value: "05", name: t("datePicker.month.may") },
18
+ { value: "06", name: t("datePicker.month.june") },
19
+ { value: "07", name: t("datePicker.month.july") },
20
+ { value: "08", name: t("datePicker.month.august") },
21
+ { value: "09", name: t("datePicker.month.september") },
22
+ { value: "10", name: t("datePicker.month.october") },
23
+ { value: "11", name: t("datePicker.month.november") },
24
+ { value: "12", name: t("datePicker.month.december") },
25
+ ];
26
+ const styles = StyleSheet.create({
27
+ day: {
28
+ maxWidth: 90,
29
+ flexGrow: 0,
30
+ },
31
+ year: {
32
+ maxWidth: 120,
33
+ flexGrow: 0,
34
+ },
35
+ error: {
36
+ borderColor: colors.negative[400],
37
+ },
38
+ });
39
+ export const InlineDatePicker = ({ value = { day: "", month: "", year: "" }, label, onValueChange, error, onBlur, order, }) => {
40
+ return (_jsx(LakeLabel, { label: label, render: id => {
41
+ const day = (_jsx(View, { style: styles.day, children: _jsx(LakeTextInput, { id: id, style: isNotNullish(error) && styles.error, placeholder: t("datePicker.day"), value: value.day, onBlur: onBlur, hideErrors: true, onChangeText: day => {
42
+ onValueChange({
43
+ day,
44
+ month: value.month,
45
+ year: value.year,
46
+ });
47
+ }, pattern: "[0-9]", maxLength: 2, autoComplete: "bday-day" }) }));
48
+ const month = (_jsx(LakeSelect, { value: value.month === "" ? undefined : value.month, style: isNotNullish(error) && styles.error, placeholder: t("datePicker.month"), hideErrors: true, items: months, onValueChange: month => {
49
+ onValueChange({
50
+ day: value.day,
51
+ month,
52
+ year: value.year,
53
+ });
54
+ } }));
55
+ const year = (_jsx(View, { style: styles.year, children: _jsx(LakeTextInput, { value: value.year, style: isNotNullish(error) && styles.error, placeholder: t("datePicker.year"), onBlur: onBlur, hideErrors: true, onChangeText: year => onValueChange({
56
+ day: value.day,
57
+ month: value.month,
58
+ year,
59
+ }), pattern: "[0-9]", maxLength: 4, autoComplete: "bday-year" }) }));
60
+ return (_jsxs(Box, { children: [order === "day-month-year" ? (_jsxs(Stack, { direction: "row", space: 12, children: [day, " ", month, " ", year] })) : (_jsxs(Stack, { direction: "row", space: 12, children: [month, " ", day, " ", year] })), _jsx(InputError, { message: error })] }));
61
+ } }));
62
+ };
@@ -40,6 +40,7 @@
40
40
  "common.skipToContent": "Skip to content",
41
41
  "copyButton.copiedTooltip": "Copied to clipboard",
42
42
  "copyButton.copyTooltip": "Click to copy",
43
+ "datePicker.day": "Day",
43
44
  "datePicker.day.friday": "Friday",
44
45
  "datePicker.day.monday": "Monday",
45
46
  "datePicker.day.saturday": "Saturday",
@@ -47,6 +48,7 @@
47
48
  "datePicker.day.thursday": "Thursday",
48
49
  "datePicker.day.tuesday": "Tuesday",
49
50
  "datePicker.day.wednesday": "Wednesday",
51
+ "datePicker.month": "Month",
50
52
  "datePicker.month.april": "April",
51
53
  "datePicker.month.august": "August",
52
54
  "datePicker.month.december": "December",
@@ -61,6 +63,7 @@
61
63
  "datePicker.month.october": "October",
62
64
  "datePicker.month.previous": "Previous month",
63
65
  "datePicker.month.september": "September",
66
+ "datePicker.year": "Year",
64
67
  "error.generic": "An error occurred",
65
68
  "error.iban.invalid": "This IBAN doesn't look right. Trying entering it again.",
66
69
  "error.network.500": "Internal server error",
@@ -293,5 +296,7 @@
293
296
  "transactionStatement.title.creditor": "Creditor information",
294
297
  "transactionStatement.title.debtor": "Debtor information",
295
298
  "transactionStatement.title.document": "Transaction confirmation",
296
- "transactionStatement.title.information": "Information"
299
+ "transactionStatement.title.information": "Information",
300
+ "validation.invalidBirthDate": "Invalid birthdate",
301
+ "validation.birthdateCannotBeFuture": "Birthdate cannot be in the future"
297
302
  }
@@ -1,2 +1,9 @@
1
1
  export declare const decodeBirthDate: (value: string) => string;
2
2
  export declare const encodeBirthDate: (value: string) => string;
3
+ export type ExtractedDate = {
4
+ day: string;
5
+ month: string;
6
+ year: string;
7
+ };
8
+ export declare const extractDate: (value: string) => ExtractedDate | undefined;
9
+ export declare const formatExtractedDate: (date: ExtractedDate) => string;
package/src/utils/date.js CHANGED
@@ -1,9 +1,25 @@
1
1
  import dayjs from "dayjs";
2
2
  export const decodeBirthDate = (value) => {
3
- const date = dayjs.utc(value, "YYYY-MM-DD");
3
+ const date = dayjs.utc(value, "YYYY-MM-DD", true);
4
4
  return date.isValid() ? date.format("DD/MM/YYYY") : "";
5
5
  };
6
6
  export const encodeBirthDate = (value) => {
7
- const date = dayjs.utc(value, "DD/MM/YYYY");
7
+ const date = dayjs.utc(value, "DD/MM/YYYY", true);
8
8
  return date.isValid() ? date.format("YYYY-MM-DD") : "";
9
9
  };
10
+ export const extractDate = (value) => {
11
+ const date = dayjs.utc(value, "YYYY-MM-DD", true);
12
+ if (date.isValid()) {
13
+ return {
14
+ day: date.format("DD"),
15
+ month: date.format("MM"),
16
+ year: date.format("YYYY"),
17
+ };
18
+ }
19
+ };
20
+ export const formatExtractedDate = (date) => {
21
+ const day = date.day.trim().padStart(2, "0");
22
+ const month = date.month.trim().padStart(2, "0");
23
+ const year = date.year.trim().padStart(4, "0");
24
+ return `${year}-${month}-${day}`;
25
+ };
@@ -1,4 +1,5 @@
1
1
  import { Validator } from "@swan-io/use-form";
2
+ import { ExtractedDate } from "./date";
2
3
  import { AccountCountry } from "./templateTranslations";
3
4
  export declare const isValidVatNumber: (maybeVat: string) => boolean;
4
5
  export declare const isValidEmail: (maybeEmail: string) => boolean;
@@ -9,3 +10,4 @@ export declare const validateIndividualTaxNumber: (accountCountry: AccountCountr
9
10
  export declare const validateCompanyTaxNumber: (accountCountry: AccountCountry) => Validator<string | undefined>;
10
11
  export { printFormat as printIbanFormat } from "iban";
11
12
  export declare const validateIban: (iban: string) => string | undefined;
13
+ export declare const validateBirthdate: (value: ExtractedDate) => string | undefined;
@@ -1,6 +1,8 @@
1
1
  import { noop } from "@swan-io/lake/src/utils/function";
2
+ import dayjs from "dayjs";
2
3
  import { isValid as isValidIban } from "iban";
3
4
  import { match } from "ts-pattern";
5
+ import { formatExtractedDate } from "./date";
4
6
  import { t } from "./i18n";
5
7
  const EMAIL_REGEX = /^[A-Z0-9_+.-]*[A-Z0-9_+-]@([A-Z0-9][A-Z0-9-]*\.)+[A-Z]{2,}$/i;
6
8
  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})$/;
@@ -102,3 +104,14 @@ export const validateIban = (iban) => {
102
104
  return t("error.iban.invalid");
103
105
  }
104
106
  };
107
+ export const validateBirthdate = (value) => {
108
+ const date = dayjs.utc(formatExtractedDate(value), "YYYY-MM-DD", true);
109
+ const isBirthdateOver150years = date.isBefore(dayjs.utc().subtract(150, "years"));
110
+ const isBirthdateWithin4years = date.isAfter(dayjs.utc().subtract(4, "years"));
111
+ if (!date.isValid() || isBirthdateOver150years || isBirthdateWithin4years) {
112
+ return t("validation.invalidBirthDate");
113
+ }
114
+ if (date.isAfter(dayjs.utc().add(1, "day"))) {
115
+ return t("validation.birthdateCannotBeFuture");
116
+ }
117
+ };