@swan-io/shared-business 13.12.0 → 15.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/shared-business",
3
- "version": "13.12.0",
3
+ "version": "15.0.0",
4
4
  "engines": {
5
5
  "node": ">22.12.0"
6
6
  },
@@ -25,7 +25,10 @@
25
25
  ],
26
26
  "license": "MIT",
27
27
  "peerDependencies": {
28
- "@swan-io/lake": "13.12.0"
28
+ "react": ">=19.0.0",
29
+ "react-dom": ">=19.0.0",
30
+ "react-native-web": ">=0.21.2",
31
+ "@swan-io/lake": "15.0.0"
29
32
  },
30
33
  "dependencies": {
31
34
  "@formatjs/intl": "^4.1.11",
@@ -35,11 +38,8 @@
35
38
  "@swan-io/use-form": "^3.1.0",
36
39
  "dayjs": "^1.11.20",
37
40
  "iban": "^0.0.14",
38
- "react": "^19.2.6",
39
41
  "react-atomic-state": "^2.1.0",
40
- "react-dom": "^19.2.6",
41
42
  "react-dropzone": "^15.0.0",
42
- "react-native-web": "^0.21.2",
43
43
  "rifm": "^0.12.1",
44
44
  "ts-pattern": "^5.9.0",
45
45
  "uuid": "^14.0.0"
@@ -49,7 +49,10 @@
49
49
  "@types/react": "^19.2.14",
50
50
  "@types/react-dom": "^19.2.3",
51
51
  "@types/react-native": "^0.72.8",
52
+ "react": "^19.2.6",
53
+ "react-dom": "^19.2.6",
54
+ "react-native-web": "^0.21.2",
52
55
  "type-fest": "^5.6.0",
53
- "@swan-io/lake": "13.12.0"
56
+ "@swan-io/lake": "15.0.0"
54
57
  }
55
58
  }
@@ -20,9 +20,7 @@ type SupportingDocumentPurposeInput<Purpose extends string> = {
20
20
  description: string;
21
21
  purposeDetails?: string | null;
22
22
  };
23
- export declare const toDocumentPurposes: <Purpose extends string>(requiredPurposes: SupportingDocumentPurposeInput<Purpose>[], documents: {
24
- purpose: SupportingDocumentPurposeInput<Purpose>;
25
- }[]) => Record<Purpose, DocumentPurposeInfo>;
23
+ export declare const toDocumentPurposes: <Purpose extends string>(requiredPurposes: SupportingDocumentPurposeInput<Purpose>[]) => Record<Purpose, DocumentPurposeInfo>;
26
24
  export type UploadOutput = {
27
25
  url: string;
28
26
  fields: {
@@ -19,7 +19,7 @@ import { isTranslationKey, locale, t } from "../utils/i18n";
19
19
  import { FilesUploader } from "./FilesUploader";
20
20
  import { LakeModal } from "./LakeModal";
21
21
  const ACCEPTED_FORMATS = ["application/pdf", "image/png", "image/jpeg", "image/heic"];
22
- export const toDocumentPurposes = (requiredPurposes, documents) => {
22
+ export const toDocumentPurposes = (requiredPurposes) => {
23
23
  const entries = new Map();
24
24
  requiredPurposes.forEach(({ name, label, description, purposeDetails }) => {
25
25
  entries.set(name, {
@@ -29,18 +29,6 @@ export const toDocumentPurposes = (requiredPurposes, documents) => {
29
29
  required: true,
30
30
  });
31
31
  });
32
- documents.forEach(({ purpose }) => {
33
- var _a;
34
- if (entries.has(purpose.name)) {
35
- return;
36
- }
37
- entries.set(purpose.name, {
38
- label: purpose.label,
39
- description: purpose.description,
40
- purposeDetails: (_a = purpose.purposeDetails) !== null && _a !== void 0 ? _a : undefined,
41
- required: false,
42
- });
43
- });
44
32
  return Object.fromEntries(entries);
45
33
  };
46
34
  const styles = StyleSheet.create({
@@ -83,10 +71,8 @@ export const SupportingDocumentCollection = ({ ref, documents, generateUpload, u
83
71
  const [currentMetadata, setCurrentMetadata] = useState(undefined);
84
72
  const [addedDocuments, setAddedDocuments] = useState([]);
85
73
  const orderedDocumentPurposes = useMemo(() => {
86
- // Get all purposes to display: the ones present in documentPurposes and the ones that have at least a document
87
74
  const allPurposes = new Set(Object.keys(documentPurposes));
88
75
  const allDocuments = [...addedDocuments, ...documents];
89
- allDocuments.forEach(document => allPurposes.add(document.purpose));
90
76
  // Compute, for each purpose, everything needed for rendering and sorting in one pass.
91
77
  // Priority drives the display order (lower comes first):
92
78
  // 0 = has documents and all are validated
@@ -315,7 +315,7 @@
315
315
  "supportingDocuments.downloadTemplate": "Vorlage herunterladen",
316
316
  "supportingDocuments.errorUpload": "Datei konnte nicht hochgeladen werden",
317
317
  "supportingDocuments.help.downloadTemplate": "Vorlage herunterladen",
318
- "supportingDocuments.help.howToSendAGoodDocument": "Wie versendet man ein korrektes Dokument?",
318
+ "supportingDocuments.help.howToSendAGoodDocument": "Wie lädt man ein korrektes Dokument hoch?",
319
319
  "supportingDocuments.help.whatIsThis": "Was ist das?",
320
320
  "supportingDocuments.informations": "Details zum Dokument",
321
321
  "supportingDocuments.noDocuments": "Keine Dokumente.",
@@ -315,7 +315,7 @@
315
315
  "supportingDocuments.downloadTemplate": "Download template",
316
316
  "supportingDocuments.errorUpload": "Failed to upload file",
317
317
  "supportingDocuments.help.downloadTemplate": "Download a template",
318
- "supportingDocuments.help.howToSendAGoodDocument": "How to send a good document?",
318
+ "supportingDocuments.help.howToSendAGoodDocument": "How to upload a good document?",
319
319
  "supportingDocuments.help.whatIsThis": "What is this?",
320
320
  "supportingDocuments.informations": "Document details",
321
321
  "supportingDocuments.noDocuments": "No documents.",
@@ -315,7 +315,7 @@
315
315
  "supportingDocuments.downloadTemplate": "Descargar una plantilla",
316
316
  "supportingDocuments.errorUpload": "No se ha podido cargar el archivo",
317
317
  "supportingDocuments.help.downloadTemplate": "Descargar una plantilla",
318
- "supportingDocuments.help.howToSendAGoodDocument": "Cómo enviar un buen documento",
318
+ "supportingDocuments.help.howToSendAGoodDocument": "Cómo cargar un buen documento",
319
319
  "supportingDocuments.help.whatIsThis": "¿Qué es esto?",
320
320
  "supportingDocuments.informations": "Detalles del documento",
321
321
  "supportingDocuments.noDocuments": "Sin documentos.",
@@ -315,7 +315,7 @@
315
315
  "supportingDocuments.downloadTemplate": "Lataa malli",
316
316
  "supportingDocuments.errorUpload": "Tiedoston lataaminen epäonnistui",
317
317
  "supportingDocuments.help.downloadTemplate": "Lataa malli",
318
- "supportingDocuments.help.howToSendAGoodDocument": "Kuinka voin lähettää hyvän asiakirjan?",
318
+ "supportingDocuments.help.howToSendAGoodDocument": "Kuinka voin ladata hyvän asiakirjan?",
319
319
  "supportingDocuments.help.whatIsThis": "Mikä tämä on?",
320
320
  "supportingDocuments.informations": "Asiakirjan tiedot",
321
321
  "supportingDocuments.noDocuments": "Ei asiakirjoja.",
@@ -315,7 +315,7 @@
315
315
  "supportingDocuments.downloadTemplate": "Télécharger un modèle",
316
316
  "supportingDocuments.errorUpload": "Le téléchargement du fichier a échoué",
317
317
  "supportingDocuments.help.downloadTemplate": "Télécharger un modèle",
318
- "supportingDocuments.help.howToSendAGoodDocument": "Comment envoyer un bon document ?",
318
+ "supportingDocuments.help.howToSendAGoodDocument": "Comment télécharger un bon document ?",
319
319
  "supportingDocuments.help.whatIsThis": "Qu'est-ce que c'est ?",
320
320
  "supportingDocuments.informations": "Détails du document",
321
321
  "supportingDocuments.noDocuments": "Aucun document.",
@@ -315,7 +315,7 @@
315
315
  "supportingDocuments.downloadTemplate": "Scarica modello",
316
316
  "supportingDocuments.errorUpload": "Impossibile caricare file",
317
317
  "supportingDocuments.help.downloadTemplate": "Scarica un modello",
318
- "supportingDocuments.help.howToSendAGoodDocument": "Come inviare un documento valido?",
318
+ "supportingDocuments.help.howToSendAGoodDocument": "Come caricare un documento valido?",
319
319
  "supportingDocuments.help.whatIsThis": "Che cos'è?",
320
320
  "supportingDocuments.informations": "Dettagli documento",
321
321
  "supportingDocuments.noDocuments": "Nessun documento.",
@@ -315,7 +315,7 @@
315
315
  "supportingDocuments.downloadTemplate": "Een sjabloon downloaden",
316
316
  "supportingDocuments.errorUpload": "Bestand uploaden mislukt",
317
317
  "supportingDocuments.help.downloadTemplate": "Een sjabloon downloaden",
318
- "supportingDocuments.help.howToSendAGoodDocument": "Hoe stuur ik een goed document?",
318
+ "supportingDocuments.help.howToSendAGoodDocument": "Hoe upload ik een goed document?",
319
319
  "supportingDocuments.help.whatIsThis": "Wat is dit?",
320
320
  "supportingDocuments.informations": "Documentgegevens",
321
321
  "supportingDocuments.noDocuments": "Geen documenten.",
@@ -315,7 +315,7 @@
315
315
  "supportingDocuments.downloadTemplate": "Descarregar um modelo",
316
316
  "supportingDocuments.errorUpload": "Falha ao carregar ficheiro",
317
317
  "supportingDocuments.help.downloadTemplate": "Descarregar um modelo",
318
- "supportingDocuments.help.howToSendAGoodDocument": "Como entregar um bom documento?",
318
+ "supportingDocuments.help.howToSendAGoodDocument": "Como carregar um bom documento?",
319
319
  "supportingDocuments.help.whatIsThis": "O que é isto?",
320
320
  "supportingDocuments.informations": "Detalhes do documento",
321
321
  "supportingDocuments.noDocuments": "Sem documentos.",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { decodeBirthDate, encodeBirthDate, extractDate, formatExtractedDate, } from "../date";
3
+ describe("decodeBirthDate converts a YYYY-MM-DD ISO date to a DD/MM/YYYY display string", () => {
4
+ it("formats a valid ISO date", () => {
5
+ expect(decodeBirthDate("1990-05-15")).toBe("15/05/1990");
6
+ });
7
+ it("handles leap days", () => {
8
+ expect(decodeBirthDate("2000-02-29")).toBe("29/02/2000");
9
+ });
10
+ it("returns an empty string for a date in DD/MM/YYYY format", () => {
11
+ expect(decodeBirthDate("15/05/1990")).toBe("");
12
+ });
13
+ it("returns an empty string for an invalid calendar date", () => {
14
+ expect(decodeBirthDate("1990-13-01")).toBe("");
15
+ });
16
+ it("returns an empty string for a non-leap-year Feb 29", () => {
17
+ expect(decodeBirthDate("1999-02-29")).toBe("");
18
+ });
19
+ it("returns an empty string for an empty input", () => {
20
+ expect(decodeBirthDate("")).toBe("");
21
+ });
22
+ it("returns an empty string for an arbitrary string", () => {
23
+ expect(decodeBirthDate("not a date")).toBe("");
24
+ });
25
+ });
26
+ describe("encodeBirthDate converts a DD/MM/YYYY display string to a YYYY-MM-DD ISO date", () => {
27
+ it("formats a valid display date", () => {
28
+ expect(encodeBirthDate("15/05/1990")).toBe("1990-05-15");
29
+ });
30
+ it("handles leap days", () => {
31
+ expect(encodeBirthDate("29/02/2000")).toBe("2000-02-29");
32
+ });
33
+ it("returns an empty string for a date in YYYY-MM-DD format", () => {
34
+ expect(encodeBirthDate("1990-05-15")).toBe("");
35
+ });
36
+ it("returns an empty string for an invalid calendar date", () => {
37
+ expect(encodeBirthDate("32/01/1990")).toBe("");
38
+ });
39
+ it("returns an empty string for an empty input", () => {
40
+ expect(encodeBirthDate("")).toBe("");
41
+ });
42
+ });
43
+ describe("extractDate splits a YYYY-MM-DD ISO date into its day, month and year parts", () => {
44
+ it("returns the date parts for a valid ISO date", () => {
45
+ expect(extractDate("1990-05-15")).toEqual({ day: "15", month: "05", year: "1990" });
46
+ });
47
+ it("returns undefined for an invalid calendar date", () => {
48
+ expect(extractDate("1990-13-01")).toBeUndefined();
49
+ });
50
+ it("returns undefined for a date in the wrong format", () => {
51
+ expect(extractDate("15/05/1990")).toBeUndefined();
52
+ });
53
+ it("returns undefined for an empty input", () => {
54
+ expect(extractDate("")).toBeUndefined();
55
+ });
56
+ });
57
+ describe("formatExtractedDate joins day, month and year parts into a YYYY-MM-DD ISO date", () => {
58
+ it("joins three already-padded parts", () => {
59
+ expect(formatExtractedDate({ day: "15", month: "05", year: "1990" })).toBe("1990-05-15");
60
+ });
61
+ it("pads single-digit day and month to two characters", () => {
62
+ expect(formatExtractedDate({ day: "5", month: "3", year: "1990" })).toBe("1990-03-05");
63
+ });
64
+ it("pads year to four characters", () => {
65
+ expect(formatExtractedDate({ day: "15", month: "05", year: "90" })).toBe("0090-05-15");
66
+ });
67
+ it("trims whitespace before padding", () => {
68
+ expect(formatExtractedDate({ day: " 5 ", month: " 3 ", year: " 90 " })).toBe("0090-03-05");
69
+ });
70
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ import { BadStatusError } from "@swan-io/request";
2
+ import { describe, expect, it } from "vitest";
3
+ import { formatCurrencyIso, formatCurrencySymbol, isTranslationKey, translateError, } from "../i18n";
4
+ // The i18n module reads `navigator.languages` at import time to pick its locale.
5
+ // In the Vitest jsdom environment, `navigator.languages` defaults to ["en-US"],
6
+ // so the module locks to English. Assertions below assume that locale.
7
+ describe("isTranslationKey checks whether a value is a known translation key", () => {
8
+ it("returns true for a known translation key", () => {
9
+ expect(isTranslationKey("error.generic")).toBe(true);
10
+ });
11
+ it("returns false for an unknown string", () => {
12
+ expect(isTranslationKey("this.key.does.not.exist")).toBe(false);
13
+ });
14
+ it("returns false for an empty string", () => {
15
+ expect(isTranslationKey("")).toBe(false);
16
+ });
17
+ it("returns false for non-string values", () => {
18
+ expect(isTranslationKey(42)).toBe(false);
19
+ expect(isTranslationKey(null)).toBe(false);
20
+ expect(isTranslationKey(undefined)).toBe(false);
21
+ expect(isTranslationKey({})).toBe(false);
22
+ });
23
+ });
24
+ describe("translateError maps an arbitrary error value to a translated message", () => {
25
+ it("translates a GraphQL rejection with a known __typename", () => {
26
+ expect(translateError({ __typename: "AccountHolderNotFoundRejection" })).toBe("Can't find account holder");
27
+ });
28
+ it("translates a known rejection name passed as a string", () => {
29
+ expect(translateError("AccountHolderNotFoundRejection")).toBe("Can't find account holder");
30
+ });
31
+ it("translates a BadStatusError using its status code", () => {
32
+ expect(translateError(new BadStatusError("https://example.com", 500))).toBe("Internal server error");
33
+ });
34
+ it("falls back to the generic error message for an unknown rejection __typename", () => {
35
+ expect(translateError({ __typename: "SomeUnknownRejection" })).toBe("An error occurred");
36
+ });
37
+ it("falls back to the generic error message for a plain object", () => {
38
+ expect(translateError({})).toBe("An error occurred");
39
+ });
40
+ it("falls back to the generic error message for null", () => {
41
+ expect(translateError(null)).toBe("An error occurred");
42
+ });
43
+ it("falls back to the generic error message for an Error with an unmapped message", () => {
44
+ expect(translateError(new Error("boom"))).toBe("An error occurred");
45
+ });
46
+ });
47
+ describe("formatCurrencyIso formats an amount as a decimal followed by the currency code", () => {
48
+ it("formats a positive amount with two fraction digits", () => {
49
+ expect(formatCurrencyIso(1234.5, "EUR")).toBe("1,234.50 EUR");
50
+ });
51
+ it("formats zero with two fraction digits", () => {
52
+ expect(formatCurrencyIso(0, "EUR")).toBe("0.00 EUR");
53
+ });
54
+ it("formats a negative amount", () => {
55
+ expect(formatCurrencyIso(-1234.5, "EUR")).toBe("-1,234.50 EUR");
56
+ });
57
+ it("uses the provided currency code verbatim", () => {
58
+ expect(formatCurrencyIso(10, "USD")).toBe("10.00 USD");
59
+ });
60
+ it("preserves additional decimal precision", () => {
61
+ expect(formatCurrencyIso(1.234, "EUR")).toBe("1.234 EUR");
62
+ });
63
+ });
64
+ describe("formatCurrencySymbol formats an amount with the currency's locale-aware symbol", () => {
65
+ it("formats EUR amounts with the € symbol", () => {
66
+ expect(formatCurrencySymbol(1234.5, "EUR")).toBe("€1,234.50");
67
+ });
68
+ it("formats USD amounts with the $ symbol", () => {
69
+ expect(formatCurrencySymbol(1234.5, "USD")).toBe("$1,234.50");
70
+ });
71
+ it("rounds beyond two fraction digits", () => {
72
+ expect(formatCurrencySymbol(1.239, "EUR")).toBe("€1.24");
73
+ });
74
+ it("formats zero", () => {
75
+ expect(formatCurrencySymbol(0, "EUR")).toBe("€0.00");
76
+ });
77
+ });