@webiny/app-admin 6.5.0-beta.9 → 6.5.1-beta.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.
Files changed (90) hide show
  1. package/base/Base/DefaultFieldRenderers.js +4 -0
  2. package/base/Base/DefaultFieldRenderers.js.map +1 -1
  3. package/base/Base/FieldRenderers/BirthdayInputRenderer.d.ts +21 -0
  4. package/base/Base/FieldRenderers/BirthdayInputRenderer.js +39 -0
  5. package/base/Base/FieldRenderers/BirthdayInputRenderer.js.map +1 -0
  6. package/base/Base/FieldRenderers/BirthdayInputRenderer.test.d.ts +1 -0
  7. package/base/Base/FieldRenderers/BirthdayInputRenderer.test.js +123 -0
  8. package/base/Base/FieldRenderers/BirthdayInputRenderer.test.js.map +1 -0
  9. package/base/Base/FieldRenderers/ObjectRenderer/ObjectFieldComponents.d.ts +2 -0
  10. package/base/Base/FieldRenderers/ObjectRenderer/ObjectFieldComponents.js +13 -5
  11. package/base/Base/FieldRenderers/ObjectRenderer/ObjectFieldComponents.js.map +1 -1
  12. package/base/Base/FieldRenderers/ObjectRenderer/ObjectRenderer.js +8 -26
  13. package/base/Base/FieldRenderers/ObjectRenderer/ObjectRenderer.js.map +1 -1
  14. package/base/Base/FieldRenderers/ObjectRenderer/resolveItemTitle.d.ts +8 -3
  15. package/base/Base/FieldRenderers/ObjectRenderer/resolveItemTitle.js +25 -17
  16. package/base/Base/FieldRenderers/ObjectRenderer/resolveItemTitle.js.map +1 -1
  17. package/base/Base/FieldRenderers/ObjectRenderer/resolveItemTitle.test.d.ts +1 -0
  18. package/base/Base/FieldRenderers/ObjectRenderer/resolveItemTitle.test.js +106 -0
  19. package/base/Base/FieldRenderers/ObjectRenderer/resolveItemTitle.test.js.map +1 -0
  20. package/base/Base/UserMenu/SwitchTenant.d.ts +20 -0
  21. package/base/Base/UserMenu/SwitchTenant.js +45 -0
  22. package/base/Base/UserMenu/SwitchTenant.js.map +1 -0
  23. package/base/Base/UserMenu.js +4 -0
  24. package/base/Base/UserMenu.js.map +1 -1
  25. package/domain/Identity.d.ts +8 -0
  26. package/domain/Identity.js +6 -0
  27. package/domain/Identity.js.map +1 -1
  28. package/exports/admin/security.d.ts +4 -1
  29. package/exports/admin/security.js +4 -1
  30. package/features/formModel/FieldBuilder.test.js +126 -1
  31. package/features/formModel/FieldBuilder.test.js.map +1 -1
  32. package/features/formModel/abstractions.d.ts +16 -0
  33. package/features/formModel/abstractions.js +2 -1
  34. package/features/formModel/abstractions.js.map +1 -1
  35. package/features/formModel/fieldTypes/DateTimeFieldType.d.ts +65 -4
  36. package/features/formModel/fieldTypes/DateTimeFieldType.js +70 -8
  37. package/features/formModel/fieldTypes/DateTimeFieldType.js.map +1 -1
  38. package/features/formModel/fieldTypes/dateLimits.d.ts +37 -0
  39. package/features/formModel/fieldTypes/dateLimits.js +64 -0
  40. package/features/formModel/fieldTypes/dateLimits.js.map +1 -0
  41. package/features/formModel/fieldTypes/dateLimits.test.d.ts +1 -0
  42. package/features/formModel/fieldTypes/dateLimits.test.js +60 -0
  43. package/features/formModel/fieldTypes/dateLimits.test.js.map +1 -0
  44. package/features/formModel/index.d.ts +2 -2
  45. package/features/formModel/index.js +1 -1
  46. package/features/formModel/renderers.d.ts +1 -0
  47. package/features/formModel/renderers.js +1 -0
  48. package/features/security/LogIn/LogInError.d.ts +15 -0
  49. package/features/security/LogIn/LogInError.js +25 -0
  50. package/features/security/LogIn/LogInError.js.map +1 -0
  51. package/features/security/LogIn/LogInGateway.d.ts +2 -2
  52. package/features/security/LogIn/LogInGateway.js +22 -5
  53. package/features/security/LogIn/LogInGateway.js.map +1 -1
  54. package/features/security/LogIn/LogInRepository.d.ts +1 -2
  55. package/features/security/LogIn/LogInRepository.js +6 -2
  56. package/features/security/LogIn/LogInRepository.js.map +1 -1
  57. package/features/security/LogIn/LogInUseCase.d.ts +7 -2
  58. package/features/security/LogIn/LogInUseCase.js +53 -3
  59. package/features/security/LogIn/LogInUseCase.js.map +1 -1
  60. package/features/security/LogIn/LogInUseCase.test.d.ts +1 -0
  61. package/features/security/LogIn/LogInUseCase.test.js +321 -0
  62. package/features/security/LogIn/LogInUseCase.test.js.map +1 -0
  63. package/features/security/LogIn/abstractions.d.ts +48 -3
  64. package/features/security/LogIn/abstractions.js.map +1 -1
  65. package/features/security/LogIn/createLoginMutation.js +40 -27
  66. package/features/security/LogIn/createLoginMutation.js.map +1 -1
  67. package/features/security/LogIn/feature.js +2 -0
  68. package/features/security/LogIn/feature.js.map +1 -1
  69. package/features/security/LogIn/index.d.ts +1 -0
  70. package/features/security/LogIn/index.js +1 -0
  71. package/features/security/LoginTenantResolver/LoginTenantResolver.d.ts +14 -0
  72. package/features/security/LoginTenantResolver/LoginTenantResolver.js +52 -0
  73. package/features/security/LoginTenantResolver/LoginTenantResolver.js.map +1 -0
  74. package/features/security/LoginTenantResolver/abstractions.d.ts +38 -0
  75. package/features/security/LoginTenantResolver/abstractions.js +5 -0
  76. package/features/security/LoginTenantResolver/abstractions.js.map +1 -0
  77. package/features/security/LoginTenantResolver/feature.d.ts +3 -0
  78. package/features/security/LoginTenantResolver/feature.js +17 -0
  79. package/features/security/LoginTenantResolver/feature.js.map +1 -0
  80. package/features/security/LoginTenantResolver/index.d.ts +2 -0
  81. package/features/security/LoginTenantResolver/index.js +2 -0
  82. package/package.json +24 -24
  83. package/presentation/security/components/LoginNoTenantAccess.d.ts +27 -0
  84. package/presentation/security/components/LoginNoTenantAccess.js +22 -0
  85. package/presentation/security/components/LoginNoTenantAccess.js.map +1 -0
  86. package/presentation/security/components/LoginTenantPicker.d.ts +30 -0
  87. package/presentation/security/components/LoginTenantPicker.js +32 -0
  88. package/presentation/security/components/LoginTenantPicker.js.map +1 -0
  89. package/presentation/security/hooks/useAuthentication.d.ts +2 -2
  90. package/presentation/security/hooks/useAuthentication.js.map +1 -1
@@ -0,0 +1,106 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { resolveItemDescription, resolveItemTitle } from "./resolveItemTitle.js";
3
+ const field = (name, type, value, extra = {})=>({
4
+ name,
5
+ type,
6
+ value,
7
+ ...extra
8
+ });
9
+ const classTypes = [
10
+ {
11
+ label: "Fitness",
12
+ value: "fitness"
13
+ },
14
+ {
15
+ label: "Personal Training",
16
+ value: "personal-training"
17
+ }
18
+ ];
19
+ describe("resolveItemTitle", ()=>{
20
+ it("shows the label of a predefined value instead of its value", ()=>{
21
+ const fields = [
22
+ field("classType", "text", "personal-training", {
23
+ options: classTypes
24
+ })
25
+ ];
26
+ expect(resolveItemTitle(fields, 0, "Item #1", "classType", [])).toBe("Personal Training");
27
+ });
28
+ it("joins the labels of a list of predefined values", ()=>{
29
+ const fields = [
30
+ field("classTypes", "text", [
31
+ "fitness",
32
+ "personal-training"
33
+ ], {
34
+ options: classTypes
35
+ })
36
+ ];
37
+ expect(resolveItemTitle(fields, 0, "Item #1", "classTypes", [])).toBe("Fitness, Personal Training");
38
+ });
39
+ it("matches a numeric predefined value", ()=>{
40
+ const fields = [
41
+ field("size", "number", 2, {
42
+ options: [
43
+ {
44
+ label: "Small",
45
+ value: 1
46
+ },
47
+ {
48
+ label: "Large",
49
+ value: 2
50
+ }
51
+ ]
52
+ })
53
+ ];
54
+ expect(resolveItemTitle(fields, 0, "Item #1", "size", [])).toBe("Large");
55
+ });
56
+ it("hands the value to the formatter registered for the field's type", ()=>{
57
+ const formatter = {
58
+ type: "ref",
59
+ format: (f)=>null === f.value ? void 0 : "Baker"
60
+ };
61
+ const fields = [
62
+ field("location", "ref", {
63
+ id: "baker#0001",
64
+ modelId: "location"
65
+ })
66
+ ];
67
+ expect(resolveItemTitle(fields, 0, "Item #1", "location", [
68
+ formatter
69
+ ])).toBe("Baker");
70
+ expect(resolveItemDescription(fields, 0, "location", [
71
+ formatter
72
+ ])).toBe("Baker");
73
+ });
74
+ it("falls back while a value has no readable text", ()=>{
75
+ const loading = {
76
+ type: "ref",
77
+ format: ()=>void 0
78
+ };
79
+ const fields = [
80
+ field("location", "ref", {
81
+ id: "baker#0001",
82
+ modelId: "location"
83
+ }),
84
+ field("details", "object", {
85
+ a: 1
86
+ })
87
+ ];
88
+ expect(resolveItemTitle(fields, 2, "Facility #3", "location", [
89
+ loading
90
+ ])).toBe("Facility #3");
91
+ expect(resolveItemTitle(fields, 2, "Facility #3", "details", [])).toBe("Facility #3");
92
+ expect(resolveItemDescription(fields, 2, "location", [
93
+ loading
94
+ ])).toBeUndefined();
95
+ });
96
+ it("passes raw values to a title function", ()=>{
97
+ const fields = [
98
+ field("classType", "text", "fitness", {
99
+ options: classTypes
100
+ })
101
+ ];
102
+ expect(resolveItemTitle(fields, 1, "Item #2", (data, index)=>`${data.classType} ${index}`, [])).toBe("fitness 1");
103
+ });
104
+ });
105
+
106
+ //# sourceMappingURL=resolveItemTitle.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base/Base/FieldRenderers/ObjectRenderer/resolveItemTitle.test.js","sources":["../../../../../src/base/Base/FieldRenderers/ObjectRenderer/resolveItemTitle.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport type { IFieldValueFormatter, IFieldVM } from \"~/features/formModel/index.js\";\nimport { resolveItemDescription, resolveItemTitle } from \"./resolveItemTitle.js\";\n\nconst field = (name: string, type: string, value: unknown, extra: Partial<IFieldVM> = {}) =>\n ({ name, type, value, ...extra }) as IFieldVM;\n\nconst classTypes = [\n { label: \"Fitness\", value: \"fitness\" },\n { label: \"Personal Training\", value: \"personal-training\" }\n];\n\ndescribe(\"resolveItemTitle\", () => {\n it(\"shows the label of a predefined value instead of its value\", () => {\n const fields = [field(\"classType\", \"text\", \"personal-training\", { options: classTypes })];\n\n expect(resolveItemTitle(fields, 0, \"Item #1\", \"classType\", [])).toBe(\"Personal Training\");\n });\n\n it(\"joins the labels of a list of predefined values\", () => {\n const fields = [\n field(\"classTypes\", \"text\", [\"fitness\", \"personal-training\"], { options: classTypes })\n ];\n\n expect(resolveItemTitle(fields, 0, \"Item #1\", \"classTypes\", [])).toBe(\n \"Fitness, Personal Training\"\n );\n });\n\n it(\"matches a numeric predefined value\", () => {\n const fields = [\n field(\"size\", \"number\", 2, {\n options: [\n { label: \"Small\", value: 1 },\n { label: \"Large\", value: 2 }\n ]\n })\n ];\n\n expect(resolveItemTitle(fields, 0, \"Item #1\", \"size\", [])).toBe(\"Large\");\n });\n\n it(\"hands the value to the formatter registered for the field's type\", () => {\n const formatter: IFieldValueFormatter = {\n type: \"ref\",\n format: f => (f.value === null ? undefined : \"Baker\")\n };\n const fields = [field(\"location\", \"ref\", { id: \"baker#0001\", modelId: \"location\" })];\n\n expect(resolveItemTitle(fields, 0, \"Item #1\", \"location\", [formatter])).toBe(\"Baker\");\n expect(resolveItemDescription(fields, 0, \"location\", [formatter])).toBe(\"Baker\");\n });\n\n it(\"falls back while a value has no readable text\", () => {\n const loading: IFieldValueFormatter = { type: \"ref\", format: () => undefined };\n const fields = [\n field(\"location\", \"ref\", { id: \"baker#0001\", modelId: \"location\" }),\n field(\"details\", \"object\", { a: 1 })\n ];\n\n expect(resolveItemTitle(fields, 2, \"Facility #3\", \"location\", [loading])).toBe(\n \"Facility #3\"\n );\n expect(resolveItemTitle(fields, 2, \"Facility #3\", \"details\", [])).toBe(\"Facility #3\");\n expect(resolveItemDescription(fields, 2, \"location\", [loading])).toBeUndefined();\n });\n\n it(\"passes raw values to a title function\", () => {\n const fields = [field(\"classType\", \"text\", \"fitness\", { options: classTypes })];\n\n expect(\n resolveItemTitle(\n fields,\n 1,\n \"Item #2\",\n (data, index) => `${data.classType} ${index}`,\n []\n )\n ).toBe(\"fitness 1\");\n });\n});\n"],"names":["field","name","type","value","extra","classTypes","describe","it","fields","expect","resolveItemTitle","formatter","f","undefined","resolveItemDescription","loading","data","index"],"mappings":";;AAIA,MAAMA,QAAQ,CAACC,MAAcC,MAAcC,OAAgBC,QAA2B,CAAC,CAAC,GACnF;QAAEH;QAAMC;QAAMC;QAAO,GAAGC,KAAK;IAAC;AAEnC,MAAMC,aAAa;IACf;QAAE,OAAO;QAAW,OAAO;IAAU;IACrC;QAAE,OAAO;QAAqB,OAAO;IAAoB;CAC5D;AAEDC,SAAS,oBAAoB;IACzBC,GAAG,8DAA8D;QAC7D,MAAMC,SAAS;YAACR,MAAM,aAAa,QAAQ,qBAAqB;gBAAE,SAASK;YAAW;SAAG;QAEzFI,OAAOC,iBAAiBF,QAAQ,GAAG,WAAW,aAAa,EAAE,GAAG,IAAI,CAAC;IACzE;IAEAD,GAAG,mDAAmD;QAClD,MAAMC,SAAS;YACXR,MAAM,cAAc,QAAQ;gBAAC;gBAAW;aAAoB,EAAE;gBAAE,SAASK;YAAW;SACvF;QAEDI,OAAOC,iBAAiBF,QAAQ,GAAG,WAAW,cAAc,EAAE,GAAG,IAAI,CACjE;IAER;IAEAD,GAAG,sCAAsC;QACrC,MAAMC,SAAS;YACXR,MAAM,QAAQ,UAAU,GAAG;gBACvB,SAAS;oBACL;wBAAE,OAAO;wBAAS,OAAO;oBAAE;oBAC3B;wBAAE,OAAO;wBAAS,OAAO;oBAAE;iBAC9B;YACL;SACH;QAEDS,OAAOC,iBAAiBF,QAAQ,GAAG,WAAW,QAAQ,EAAE,GAAG,IAAI,CAAC;IACpE;IAEAD,GAAG,oEAAoE;QACnE,MAAMI,YAAkC;YACpC,MAAM;YACN,QAAQC,CAAAA,IAAMA,AAAY,SAAZA,EAAE,KAAK,GAAYC,SAAY;QACjD;QACA,MAAML,SAAS;YAACR,MAAM,YAAY,OAAO;gBAAE,IAAI;gBAAc,SAAS;YAAW;SAAG;QAEpFS,OAAOC,iBAAiBF,QAAQ,GAAG,WAAW,YAAY;YAACG;SAAU,GAAG,IAAI,CAAC;QAC7EF,OAAOK,uBAAuBN,QAAQ,GAAG,YAAY;YAACG;SAAU,GAAG,IAAI,CAAC;IAC5E;IAEAJ,GAAG,iDAAiD;QAChD,MAAMQ,UAAgC;YAAE,MAAM;YAAO,QAAQ,IAAMF;QAAU;QAC7E,MAAML,SAAS;YACXR,MAAM,YAAY,OAAO;gBAAE,IAAI;gBAAc,SAAS;YAAW;YACjEA,MAAM,WAAW,UAAU;gBAAE,GAAG;YAAE;SACrC;QAEDS,OAAOC,iBAAiBF,QAAQ,GAAG,eAAe,YAAY;YAACO;SAAQ,GAAG,IAAI,CAC1E;QAEJN,OAAOC,iBAAiBF,QAAQ,GAAG,eAAe,WAAW,EAAE,GAAG,IAAI,CAAC;QACvEC,OAAOK,uBAAuBN,QAAQ,GAAG,YAAY;YAACO;SAAQ,GAAG,aAAa;IAClF;IAEAR,GAAG,yCAAyC;QACxC,MAAMC,SAAS;YAACR,MAAM,aAAa,QAAQ,WAAW;gBAAE,SAASK;YAAW;SAAG;QAE/EI,OACIC,iBACIF,QACA,GACA,WACA,CAACQ,MAAMC,QAAU,GAAGD,KAAK,SAAS,CAAC,CAAC,EAAEC,OAAO,EAC7C,EAAE,GAER,IAAI,CAAC;IACX;AACJ"}
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ export declare const SwitchTenant: (() => React.JSX.Element | null) & {
3
+ original: () => React.JSX.Element | null;
4
+ originalName: string;
5
+ displayName: string;
6
+ } & {
7
+ original: (() => React.JSX.Element | null) & {
8
+ original: () => React.JSX.Element | null;
9
+ originalName: string;
10
+ displayName: string;
11
+ };
12
+ originalName: string;
13
+ displayName: string;
14
+ } & {
15
+ createDecorator: (decorator: import("@webiny/app").ComponentDecorator<(() => React.JSX.Element | null) & {
16
+ original: () => React.JSX.Element | null;
17
+ originalName: string;
18
+ displayName: string;
19
+ }>) => (props: unknown) => React.JSX.Element;
20
+ };
@@ -0,0 +1,45 @@
1
+ import react from "react";
2
+ import { makeDecoratable } from "@webiny/app";
3
+ import { Button } from "@webiny/admin-ui";
4
+ import { ReactComponent } from "@webiny/icons/swap_horiz.svg";
5
+ import { useAuthentication } from "../../../presentation/security/hooks/useAuthentication.js";
6
+ import { useTenantContext } from "../../../presentation/tenancy/useTenantContext.js";
7
+ import { useDialogs } from "../../../components/Dialogs/useDialogs.js";
8
+ import { Menu } from "../../../config/AdminConfig/Menu.js";
9
+ const SwitchTenant = makeDecoratable("SwitchTenant", ()=>{
10
+ const tenancy = useTenantContext();
11
+ const dialogs = useDialogs();
12
+ const { identity, isAuthenticated } = useAuthentication();
13
+ if (!isAuthenticated || !identity.canSwitchTenant) return null;
14
+ const showTenants = ()=>{
15
+ const currentTenantId = identity.currentTenant.id;
16
+ dialogs.showDialog({
17
+ title: "Switch tenant",
18
+ description: "Your account has access to these tenants. Pick the one to open.",
19
+ acceptLabel: null,
20
+ cancelLabel: "Cancel",
21
+ content: /*#__PURE__*/ react.createElement("div", {
22
+ className: "flex flex-col gap-sm",
23
+ "data-testid": "switch-tenant-list"
24
+ }, identity.tenants.map((tenant)=>/*#__PURE__*/ react.createElement(Button, {
25
+ key: tenant.id,
26
+ variant: "tertiary",
27
+ className: "w-full justify-start",
28
+ text: tenant.id === currentTenantId ? `${tenant.name} (current)` : tenant.name,
29
+ disabled: tenant.id === currentTenantId,
30
+ onClick: ()=>tenancy.setTenant(tenant.id)
31
+ })))
32
+ });
33
+ };
34
+ return /*#__PURE__*/ react.createElement(Menu.User.Item, {
35
+ icon: /*#__PURE__*/ react.createElement(Menu.User.Item.Icon, {
36
+ element: /*#__PURE__*/ react.createElement(ReactComponent, null),
37
+ label: "Switch tenant"
38
+ }),
39
+ text: "Switch tenant",
40
+ onClick: showTenants
41
+ });
42
+ });
43
+ export { SwitchTenant };
44
+
45
+ //# sourceMappingURL=SwitchTenant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base/Base/UserMenu/SwitchTenant.js","sources":["../../../../src/base/Base/UserMenu/SwitchTenant.tsx"],"sourcesContent":["import React from \"react\";\nimport { makeDecoratable } from \"@webiny/app\";\nimport { Button } from \"@webiny/admin-ui\";\nimport { ReactComponent as SwitchIcon } from \"@webiny/icons/swap_horiz.svg\";\nimport { useAuthentication } from \"~/presentation/security/hooks/useAuthentication.js\";\nimport { useTenantContext } from \"~/presentation/tenancy/useTenantContext.js\";\nimport { useDialogs } from \"~/components/Dialogs/useDialogs.js\";\nimport { Menu } from \"~/config/AdminConfig/Menu.js\";\n\nexport const SwitchTenant = makeDecoratable(\"SwitchTenant\", () => {\n const tenancy = useTenantContext();\n const dialogs = useDialogs();\n const { identity, isAuthenticated } = useAuthentication();\n\n if (!isAuthenticated || !identity.canSwitchTenant) {\n return null;\n }\n\n const showTenants = () => {\n const currentTenantId = identity.currentTenant.id;\n\n dialogs.showDialog({\n title: \"Switch tenant\",\n description: \"Your account has access to these tenants. Pick the one to open.\",\n acceptLabel: null,\n cancelLabel: \"Cancel\",\n content: (\n <div className={\"flex flex-col gap-sm\"} data-testid={\"switch-tenant-list\"}>\n {identity.tenants.map(tenant => (\n <Button\n key={tenant.id}\n variant={\"tertiary\"}\n className={\"w-full justify-start\"}\n text={\n tenant.id === currentTenantId\n ? `${tenant.name} (current)`\n : tenant.name\n }\n disabled={tenant.id === currentTenantId}\n onClick={() => tenancy.setTenant(tenant.id)}\n />\n ))}\n </div>\n )\n });\n };\n\n return (\n <Menu.User.Item\n icon={<Menu.User.Item.Icon element={<SwitchIcon />} label={\"Switch tenant\"} />}\n text={\"Switch tenant\"}\n onClick={showTenants}\n />\n );\n});\n"],"names":["SwitchTenant","makeDecoratable","tenancy","useTenantContext","dialogs","useDialogs","identity","isAuthenticated","useAuthentication","showTenants","currentTenantId","tenant","Button","Menu","SwitchIcon"],"mappings":";;;;;;;;AASO,MAAMA,eAAeC,gBAAgB,gBAAgB;IACxD,MAAMC,UAAUC;IAChB,MAAMC,UAAUC;IAChB,MAAM,EAAEC,QAAQ,EAAEC,eAAe,EAAE,GAAGC;IAEtC,IAAI,CAACD,mBAAmB,CAACD,SAAS,eAAe,EAC7C,OAAO;IAGX,MAAMG,cAAc;QAChB,MAAMC,kBAAkBJ,SAAS,aAAa,CAAC,EAAE;QAEjDF,QAAQ,UAAU,CAAC;YACf,OAAO;YACP,aAAa;YACb,aAAa;YACb,aAAa;YACb,SAAS,WAAT,GACI,oBAAC;gBAAI,WAAW;gBAAwB,eAAa;eAChDE,SAAS,OAAO,CAAC,GAAG,CAACK,CAAAA,SAAAA,WAAAA,GAClB,oBAACC,QAAMA;oBACH,KAAKD,OAAO,EAAE;oBACd,SAAS;oBACT,WAAW;oBACX,MACIA,OAAO,EAAE,KAAKD,kBACR,GAAGC,OAAO,IAAI,CAAC,UAAU,CAAC,GAC1BA,OAAO,IAAI;oBAErB,UAAUA,OAAO,EAAE,KAAKD;oBACxB,SAAS,IAAMR,QAAQ,SAAS,CAACS,OAAO,EAAE;;QAK9D;IACJ;IAEA,OAAO,WAAP,GACI,oBAACE,KAAK,IAAI,CAAC,IAAI;QACX,oBAAM,oBAACA,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI;YAAC,uBAAS,oBAACC,gBAAUA;YAAK,OAAO;;QAC3D,MAAM;QACN,SAASL;;AAGrB"}
@@ -3,10 +3,14 @@ import { AdminConfig } from "../../config/AdminConfig.js";
3
3
  import { SignOut } from "./UserMenu/SignOut.js";
4
4
  import { UserInfo } from "./UserMenu/UserInfo.js";
5
5
  import { ExitTenant } from "./UserMenu/ExitTenant.js";
6
+ import { SwitchTenant } from "./UserMenu/SwitchTenant.js";
6
7
  import { Menu } from "../../config/AdminConfig/Menu.js";
7
8
  const UserMenu = /*#__PURE__*/ react.memo(()=>/*#__PURE__*/ react.createElement(AdminConfig, null, /*#__PURE__*/ react.createElement(Menu.User, {
8
9
  name: "userInfo",
9
10
  element: /*#__PURE__*/ react.createElement(UserInfo, null)
11
+ }), /*#__PURE__*/ react.createElement(Menu.User, {
12
+ name: "switchTenant",
13
+ element: /*#__PURE__*/ react.createElement(SwitchTenant, null)
10
14
  }), /*#__PURE__*/ react.createElement(Menu.User, {
11
15
  name: "exitTenant",
12
16
  element: /*#__PURE__*/ react.createElement(ExitTenant, null)
@@ -1 +1 @@
1
- {"version":3,"file":"base/Base/UserMenu.js","sources":["../../../src/base/Base/UserMenu.tsx"],"sourcesContent":["import React from \"react\";\nimport { AdminConfig } from \"~/config/AdminConfig.js\";\nimport { SignOut } from \"./UserMenu/SignOut.js\";\nimport { UserInfo } from \"./UserMenu/UserInfo.js\";\nimport { ExitTenant } from \"./UserMenu/ExitTenant.js\";\nimport { Menu } from \"~/config/AdminConfig/Menu.js\";\n\nexport const UserMenu = React.memo(() => {\n return (\n <AdminConfig>\n <Menu.User name={\"userInfo\"} element={<UserInfo />} />\n <Menu.User name={\"exitTenant\"} element={<ExitTenant />} />\n <Menu.User name={\"signOut\"} element={<SignOut />} />\n </AdminConfig>\n );\n});\n\nUserMenu.displayName = \"UserMenu\";\n"],"names":["UserMenu","React","AdminConfig","Menu","UserInfo","ExitTenant","SignOut"],"mappings":";;;;;;AAOO,MAAMA,WAAW,WAAHA,GAAGC,MAAAA,IAAU,CAAC,IACxB,WAAP,GACI,oBAACC,aAAWA,MAAAA,WAAAA,GACR,oBAACC,KAAK,IAAI;QAAC,MAAM;QAAY,uBAAS,oBAACC,UAAQA;sBAC/C,oBAACD,KAAK,IAAI;QAAC,MAAM;QAAc,uBAAS,oBAACE,YAAUA;sBACnD,oBAACF,KAAK,IAAI;QAAC,MAAM;QAAW,uBAAS,oBAACG,SAAOA;;AAKzDN,SAAS,WAAW,GAAG"}
1
+ {"version":3,"file":"base/Base/UserMenu.js","sources":["../../../src/base/Base/UserMenu.tsx"],"sourcesContent":["import React from \"react\";\nimport { AdminConfig } from \"~/config/AdminConfig.js\";\nimport { SignOut } from \"./UserMenu/SignOut.js\";\nimport { UserInfo } from \"./UserMenu/UserInfo.js\";\nimport { ExitTenant } from \"./UserMenu/ExitTenant.js\";\nimport { SwitchTenant } from \"./UserMenu/SwitchTenant.js\";\nimport { Menu } from \"~/config/AdminConfig/Menu.js\";\n\nexport const UserMenu = React.memo(() => {\n return (\n <AdminConfig>\n <Menu.User name={\"userInfo\"} element={<UserInfo />} />\n <Menu.User name={\"switchTenant\"} element={<SwitchTenant />} />\n <Menu.User name={\"exitTenant\"} element={<ExitTenant />} />\n <Menu.User name={\"signOut\"} element={<SignOut />} />\n </AdminConfig>\n );\n});\n\nUserMenu.displayName = \"UserMenu\";\n"],"names":["UserMenu","React","AdminConfig","Menu","UserInfo","SwitchTenant","ExitTenant","SignOut"],"mappings":";;;;;;;AAQO,MAAMA,WAAW,WAAHA,GAAGC,MAAAA,IAAU,CAAC,IACxB,WAAP,GACI,oBAACC,aAAWA,MAAAA,WAAAA,GACR,oBAACC,KAAK,IAAI;QAAC,MAAM;QAAY,uBAAS,oBAACC,UAAQA;sBAC/C,oBAACD,KAAK,IAAI;QAAC,MAAM;QAAgB,uBAAS,oBAACE,cAAYA;sBACvD,oBAACF,KAAK,IAAI;QAAC,MAAM;QAAc,uBAAS,oBAACG,YAAUA;sBACnD,oBAACH,KAAK,IAAI;QAAC,MAAM;QAAW,uBAAS,oBAACI,SAAOA;;AAKzDP,SAAS,WAAW,GAAG"}
@@ -32,6 +32,8 @@ export interface IdentityData {
32
32
  profile: Identity.Profile;
33
33
  currentTenant: Tenant;
34
34
  defaultTenant: Tenant;
35
+ /** The enabled tenants in which the identity has a user record, sorted by name. */
36
+ tenants?: Tenant[];
35
37
  }
36
38
  export declare class Identity {
37
39
  private data;
@@ -48,6 +50,12 @@ export declare class Identity {
48
50
  get profile(): IProfile;
49
51
  get currentTenant(): Tenant;
50
52
  get defaultTenant(): Tenant;
53
+ get tenants(): Tenant[];
54
+ /**
55
+ * Whether the identity can open another tenant than this one.
56
+ * Root users never can: they open tenants through Tenant Manager.
57
+ */
58
+ get canSwitchTenant(): boolean;
51
59
  getPermission<T extends Identity.Permission = Identity.Permission>(name: string, exact?: boolean): T | null;
52
60
  getPermissions<T extends Identity.Permission = Identity.Permission>(name?: string): Array<T>;
53
61
  update(updates: Partial<Pick<Identity.Data, "displayName" | "profile">>): void;
@@ -58,6 +58,12 @@ class Identity {
58
58
  get defaultTenant() {
59
59
  return this.data.defaultTenant;
60
60
  }
61
+ get tenants() {
62
+ return this.data.tenants ?? [];
63
+ }
64
+ get canSwitchTenant() {
65
+ return this.tenants.length > 1 && !this.tenants.some((tenant)=>"root" === tenant.id);
66
+ }
61
67
  getPermission(name, exact) {
62
68
  const perms = this.data.permissions || [];
63
69
  const exactMatch = perms.find((p)=>p.name === name);
@@ -1 +1 @@
1
- {"version":3,"file":"domain/Identity.js","sources":["../../src/domain/Identity.ts"],"sourcesContent":["import { makeAutoObservable, runInAction } from \"mobx\";\nimport { minimatch } from \"minimatch\";\nimport type { Tenant } from \"~/features/tenancy/abstractions.js\";\n\nexport interface IPermission {\n name: string;\n [key: string]: any;\n}\n\nexport interface IRole {\n id: string;\n slug: string;\n name: string;\n}\n\nexport interface ITeam {\n id: string;\n slug: string;\n name: string;\n}\n\nexport interface IProfile {\n external: boolean;\n email?: string;\n firstName?: string;\n lastName?: string;\n avatar?: {\n src?: string;\n };\n}\n\nexport interface IdentityData {\n id: string;\n type: string;\n displayName: string;\n roles: IRole[];\n teams: ITeam[];\n permissions: Identity.Permission[];\n profile: Identity.Profile;\n currentTenant: Tenant;\n defaultTenant: Tenant;\n}\n\nconst anonymousData: IdentityData = {\n id: \"anonymous\",\n displayName: \"Anonymous\",\n type: \"admin\",\n roles: [],\n teams: [],\n permissions: [],\n profile: {\n external: false\n },\n currentTenant: {\n id: \"root\",\n name: \"Root\"\n },\n defaultTenant: {\n id: \"root\",\n name: \"Root\"\n }\n};\n\nexport class Identity {\n private constructor(\n private data: Identity.Data,\n private identityType: \"anonymous\" | \"authenticated\"\n ) {\n makeAutoObservable(this);\n }\n\n static createAuthenticated(data: Identity.Data) {\n return new Identity(data, \"authenticated\");\n }\n\n static createAnonymous() {\n return new Identity(anonymousData, \"anonymous\");\n }\n\n get isAuthenticated(): boolean {\n return this.identityType === \"authenticated\";\n }\n\n get id() {\n return this.data.id;\n }\n\n get type() {\n return this.data.type;\n }\n\n get displayName() {\n return this.data.displayName;\n }\n\n get roles() {\n return this.data.roles ?? [];\n }\n\n get teams() {\n return this.data.teams ?? [];\n }\n\n get profile() {\n return this.data.profile;\n }\n\n get currentTenant() {\n return this.data.currentTenant;\n }\n\n get defaultTenant() {\n return this.data.defaultTenant;\n }\n\n getPermission<T extends Identity.Permission = Identity.Permission>(\n name: string,\n exact?: boolean\n ): T | null {\n const perms = (this.data.permissions || []) as T[];\n const exactMatch = perms.find(p => p.name === name);\n if (exactMatch) {\n return exactMatch as T;\n } else if (exact) {\n return null;\n }\n\n // Try matching using patterns\n return perms.find(p => minimatch(name, p.name)) || null;\n }\n\n getPermissions<T extends Identity.Permission = Identity.Permission>(name?: string): Array<T> {\n const permissions = this.data.permissions || [];\n if (!name) {\n return permissions as T[];\n }\n\n return permissions.filter(current => {\n const exactMatch = current.name === name;\n if (exactMatch) {\n return true;\n }\n\n // Try matching using patterns.\n return minimatch(name, current.name);\n }) as T[];\n }\n\n update(updates: Partial<Pick<Identity.Data, \"displayName\" | \"profile\">>) {\n runInAction(() => {\n this.data.displayName = updates.displayName || this.data.displayName;\n this.data.profile = updates.profile || this.data.profile;\n });\n }\n}\n\nexport namespace Identity {\n export type Data = IdentityData;\n export type Permission = IPermission;\n export type Profile = IProfile;\n}\n"],"names":["anonymousData","Identity","data","identityType","makeAutoObservable","name","exact","perms","exactMatch","p","minimatch","permissions","current","updates","runInAction"],"mappings":";;AA2CA,MAAMA,gBAA8B;IAChC,IAAI;IACJ,aAAa;IACb,MAAM;IACN,OAAO,EAAE;IACT,OAAO,EAAE;IACT,aAAa,EAAE;IACf,SAAS;QACL,UAAU;IACd;IACA,eAAe;QACX,IAAI;QACJ,MAAM;IACV;IACA,eAAe;QACX,IAAI;QACJ,MAAM;IACV;AACJ;AAEO,MAAMC;IACT,YACYC,IAAmB,EACnBC,YAA2C,CACrD;aAFUD,IAAI,GAAJA;aACAC,YAAY,GAAZA;QAERC,mBAAmB,IAAI;IAC3B;IAEA,OAAO,oBAAoBF,IAAmB,EAAE;QAC5C,OAAO,IAAID,SAASC,MAAM;IAC9B;IAEA,OAAO,kBAAkB;QACrB,OAAO,IAAID,SAASD,eAAe;IACvC;IAEA,IAAI,kBAA2B;QAC3B,OAAO,AAAsB,oBAAtB,IAAI,CAAC,YAAY;IAC5B;IAEA,IAAI,KAAK;QACL,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE;IACvB;IAEA,IAAI,OAAO;QACP,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI;IACzB;IAEA,IAAI,cAAc;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW;IAChC;IAEA,IAAI,QAAQ;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;IAChC;IAEA,IAAI,QAAQ;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;IAChC;IAEA,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO;IAC5B;IAEA,IAAI,gBAAgB;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa;IAClC;IAEA,IAAI,gBAAgB;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa;IAClC;IAEA,cACIK,IAAY,EACZC,KAAe,EACP;QACR,MAAMC,QAAS,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE;QAC1C,MAAMC,aAAaD,MAAM,IAAI,CAACE,CAAAA,IAAKA,EAAE,IAAI,KAAKJ;QAC9C,IAAIG,YACA,OAAOA;aACJ,IAAIF,OACP,OAAO;QAIX,OAAOC,MAAM,IAAI,CAACE,CAAAA,IAAKC,UAAUL,MAAMI,EAAE,IAAI,MAAM;IACvD;IAEA,eAAoEJ,IAAa,EAAY;QACzF,MAAMM,cAAc,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE;QAC/C,IAAI,CAACN,MACD,OAAOM;QAGX,OAAOA,YAAY,MAAM,CAACC,CAAAA;YACtB,MAAMJ,aAAaI,QAAQ,IAAI,KAAKP;YACpC,IAAIG,YACA,OAAO;YAIX,OAAOE,UAAUL,MAAMO,QAAQ,IAAI;QACvC;IACJ;IAEA,OAAOC,OAAgE,EAAE;QACrEC,YAAY;YACR,IAAI,CAAC,IAAI,CAAC,WAAW,GAAGD,QAAQ,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW;YACpE,IAAI,CAAC,IAAI,CAAC,OAAO,GAAGA,QAAQ,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO;QAC5D;IACJ;AACJ"}
1
+ {"version":3,"file":"domain/Identity.js","sources":["../../src/domain/Identity.ts"],"sourcesContent":["import { makeAutoObservable, runInAction } from \"mobx\";\nimport { minimatch } from \"minimatch\";\nimport type { Tenant } from \"~/features/tenancy/abstractions.js\";\n\nexport interface IPermission {\n name: string;\n [key: string]: any;\n}\n\nexport interface IRole {\n id: string;\n slug: string;\n name: string;\n}\n\nexport interface ITeam {\n id: string;\n slug: string;\n name: string;\n}\n\nexport interface IProfile {\n external: boolean;\n email?: string;\n firstName?: string;\n lastName?: string;\n avatar?: {\n src?: string;\n };\n}\n\nexport interface IdentityData {\n id: string;\n type: string;\n displayName: string;\n roles: IRole[];\n teams: ITeam[];\n permissions: Identity.Permission[];\n profile: Identity.Profile;\n currentTenant: Tenant;\n defaultTenant: Tenant;\n /** The enabled tenants in which the identity has a user record, sorted by name. */\n tenants?: Tenant[];\n}\n\nconst anonymousData: IdentityData = {\n id: \"anonymous\",\n displayName: \"Anonymous\",\n type: \"admin\",\n roles: [],\n teams: [],\n permissions: [],\n profile: {\n external: false\n },\n currentTenant: {\n id: \"root\",\n name: \"Root\"\n },\n defaultTenant: {\n id: \"root\",\n name: \"Root\"\n }\n};\n\nexport class Identity {\n private constructor(\n private data: Identity.Data,\n private identityType: \"anonymous\" | \"authenticated\"\n ) {\n makeAutoObservable(this);\n }\n\n static createAuthenticated(data: Identity.Data) {\n return new Identity(data, \"authenticated\");\n }\n\n static createAnonymous() {\n return new Identity(anonymousData, \"anonymous\");\n }\n\n get isAuthenticated(): boolean {\n return this.identityType === \"authenticated\";\n }\n\n get id() {\n return this.data.id;\n }\n\n get type() {\n return this.data.type;\n }\n\n get displayName() {\n return this.data.displayName;\n }\n\n get roles() {\n return this.data.roles ?? [];\n }\n\n get teams() {\n return this.data.teams ?? [];\n }\n\n get profile() {\n return this.data.profile;\n }\n\n get currentTenant() {\n return this.data.currentTenant;\n }\n\n get defaultTenant() {\n return this.data.defaultTenant;\n }\n\n get tenants(): Tenant[] {\n return this.data.tenants ?? [];\n }\n\n /**\n * Whether the identity can open another tenant than this one.\n * Root users never can: they open tenants through Tenant Manager.\n */\n get canSwitchTenant(): boolean {\n return this.tenants.length > 1 && !this.tenants.some(tenant => tenant.id === \"root\");\n }\n\n getPermission<T extends Identity.Permission = Identity.Permission>(\n name: string,\n exact?: boolean\n ): T | null {\n const perms = (this.data.permissions || []) as T[];\n const exactMatch = perms.find(p => p.name === name);\n if (exactMatch) {\n return exactMatch as T;\n } else if (exact) {\n return null;\n }\n\n // Try matching using patterns\n return perms.find(p => minimatch(name, p.name)) || null;\n }\n\n getPermissions<T extends Identity.Permission = Identity.Permission>(name?: string): Array<T> {\n const permissions = this.data.permissions || [];\n if (!name) {\n return permissions as T[];\n }\n\n return permissions.filter(current => {\n const exactMatch = current.name === name;\n if (exactMatch) {\n return true;\n }\n\n // Try matching using patterns.\n return minimatch(name, current.name);\n }) as T[];\n }\n\n update(updates: Partial<Pick<Identity.Data, \"displayName\" | \"profile\">>) {\n runInAction(() => {\n this.data.displayName = updates.displayName || this.data.displayName;\n this.data.profile = updates.profile || this.data.profile;\n });\n }\n}\n\nexport namespace Identity {\n export type Data = IdentityData;\n export type Permission = IPermission;\n export type Profile = IProfile;\n}\n"],"names":["anonymousData","Identity","data","identityType","makeAutoObservable","tenant","name","exact","perms","exactMatch","p","minimatch","permissions","current","updates","runInAction"],"mappings":";;AA6CA,MAAMA,gBAA8B;IAChC,IAAI;IACJ,aAAa;IACb,MAAM;IACN,OAAO,EAAE;IACT,OAAO,EAAE;IACT,aAAa,EAAE;IACf,SAAS;QACL,UAAU;IACd;IACA,eAAe;QACX,IAAI;QACJ,MAAM;IACV;IACA,eAAe;QACX,IAAI;QACJ,MAAM;IACV;AACJ;AAEO,MAAMC;IACT,YACYC,IAAmB,EACnBC,YAA2C,CACrD;aAFUD,IAAI,GAAJA;aACAC,YAAY,GAAZA;QAERC,mBAAmB,IAAI;IAC3B;IAEA,OAAO,oBAAoBF,IAAmB,EAAE;QAC5C,OAAO,IAAID,SAASC,MAAM;IAC9B;IAEA,OAAO,kBAAkB;QACrB,OAAO,IAAID,SAASD,eAAe;IACvC;IAEA,IAAI,kBAA2B;QAC3B,OAAO,AAAsB,oBAAtB,IAAI,CAAC,YAAY;IAC5B;IAEA,IAAI,KAAK;QACL,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE;IACvB;IAEA,IAAI,OAAO;QACP,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI;IACzB;IAEA,IAAI,cAAc;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW;IAChC;IAEA,IAAI,QAAQ;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;IAChC;IAEA,IAAI,QAAQ;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;IAChC;IAEA,IAAI,UAAU;QACV,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO;IAC5B;IAEA,IAAI,gBAAgB;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa;IAClC;IAEA,IAAI,gBAAgB;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC,aAAa;IAClC;IAEA,IAAI,UAAoB;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE;IAClC;IAMA,IAAI,kBAA2B;QAC3B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAACK,CAAAA,SAAUA,AAAc,WAAdA,OAAO,EAAE;IAC5E;IAEA,cACIC,IAAY,EACZC,KAAe,EACP;QACR,MAAMC,QAAS,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE;QAC1C,MAAMC,aAAaD,MAAM,IAAI,CAACE,CAAAA,IAAKA,EAAE,IAAI,KAAKJ;QAC9C,IAAIG,YACA,OAAOA;aACJ,IAAIF,OACP,OAAO;QAIX,OAAOC,MAAM,IAAI,CAACE,CAAAA,IAAKC,UAAUL,MAAMI,EAAE,IAAI,MAAM;IACvD;IAEA,eAAoEJ,IAAa,EAAY;QACzF,MAAMM,cAAc,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE;QAC/C,IAAI,CAACN,MACD,OAAOM;QAGX,OAAOA,YAAY,MAAM,CAACC,CAAAA;YACtB,MAAMJ,aAAaI,QAAQ,IAAI,KAAKP;YACpC,IAAIG,YACA,OAAO;YAIX,OAAOE,UAAUL,MAAMO,QAAQ,IAAI;QACvC;IACJ;IAEA,OAAOC,OAAgE,EAAE;QACrEC,YAAY;YACR,IAAI,CAAC,IAAI,CAAC,WAAW,GAAGD,QAAQ,WAAW,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW;YACpE,IAAI,CAAC,IAAI,CAAC,OAAO,GAAGA,QAAQ,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO;QAC5D;IACJ;AACJ"}
@@ -4,7 +4,8 @@ export { createPermissionsFeature } from "../../permissions/index.js";
4
4
  export { createHasPermission } from "../../permissions/index.js";
5
5
  export { createUsePermissions } from "../../permissions/index.js";
6
6
  export type { Permissions } from "../../permissions/types.js";
7
- export { LogInUseCase } from "../../features/security/LogIn/index.js";
7
+ export { LogInUseCase, LogInError } from "../../features/security/LogIn/index.js";
8
+ export { LoginTenantResolver } from "../../features/security/LoginTenantResolver/index.js";
8
9
  export { LogOutUseCase } from "../../features/security/LogOut/index.js";
9
10
  export { AuthenticationContext } from "../../features/security/AuthenticationContext/index.js";
10
11
  export { IdentityContext } from "../../features/security/IdentityContext/index.js";
@@ -12,3 +13,5 @@ export { useAuthentication } from "../../presentation/security/hooks/useAuthenti
12
13
  export { useIdentity } from "../../presentation/security/hooks/useIdentity.js";
13
14
  export { HasPermissionComponent } from "../../presentation/security/components/HasPermission.js";
14
15
  export { LoginScreenComponent } from "../../base/ui/LoginScreen.js";
16
+ export { LoginTenantPicker } from "../../presentation/security/components/LoginTenantPicker.js";
17
+ export { LoginNoTenantAccess } from "../../presentation/security/components/LoginNoTenantAccess.js";
@@ -1,5 +1,6 @@
1
1
  export { createHasPermission, createPermissionSchema, createPermissionsAbstraction, createPermissionsFeature, createUsePermissions } from "../../permissions/index.js";
2
- export { LogInUseCase } from "../../features/security/LogIn/index.js";
2
+ export { LogInError, LogInUseCase } from "../../features/security/LogIn/index.js";
3
+ export { LoginTenantResolver } from "../../features/security/LoginTenantResolver/index.js";
3
4
  export { LogOutUseCase } from "../../features/security/LogOut/index.js";
4
5
  export { AuthenticationContext } from "../../features/security/AuthenticationContext/index.js";
5
6
  export { IdentityContext } from "../../features/security/IdentityContext/index.js";
@@ -7,3 +8,5 @@ export { useAuthentication } from "../../presentation/security/hooks/useAuthenti
7
8
  export { useIdentity } from "../../presentation/security/hooks/useIdentity.js";
8
9
  export { HasPermissionComponent } from "../../presentation/security/components/HasPermission.js";
9
10
  export { LoginScreenComponent } from "../../base/ui/LoginScreen.js";
11
+ export { LoginTenantPicker } from "../../presentation/security/components/LoginTenantPicker.js";
12
+ export { LoginNoTenantAccess } from "../../presentation/security/components/LoginNoTenantAccess.js";
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
2
  import { z } from "zod";
3
3
  import { Container } from "@webiny/di";
4
4
  import { FormModelFeature } from "./feature.js";
@@ -8,6 +8,7 @@ import { TextFieldBuilder } from "./fieldTypes/TextFieldType.js";
8
8
  import { NumberFieldBuilder } from "./fieldTypes/NumberFieldType.js";
9
9
  import { BooleanFieldBuilder } from "./fieldTypes/BooleanFieldType.js";
10
10
  import { DateTimeFieldBuilder } from "./fieldTypes/DateTimeFieldType.js";
11
+ import { DateFormatter } from "../dateFormatter/abstractions.js";
11
12
  function createRegistry() {
12
13
  const container = new Container();
13
14
  IdentityContextFeature.register(container);
@@ -166,6 +167,130 @@ describe("DateTimeFieldBuilder", ()=>{
166
167
  expect(subtype(new DateTimeFieldBuilder()).build("d").renderer).toBe("dateTimeInput");
167
168
  }
168
169
  });
170
+ it("renders a date-only field with the birthday picker", ()=>{
171
+ const config = new DateTimeFieldBuilder().dateOnly().renderer("birthdayInput").build("dob");
172
+ expect(config.renderer).toBe("birthdayInput");
173
+ expect(config.rendererSettings).toBeUndefined();
174
+ });
175
+ it("keeps a birthday picker set before dateOnly() and drops it for other subtypes", ()=>{
176
+ const birthday = ()=>new DateTimeFieldBuilder().renderer("birthdayInput");
177
+ const dateOnly = birthday().dateOnly().build("dob");
178
+ expect(dateOnly.renderer).toBe("birthdayInput");
179
+ expect(dateOnly.rendererSettings).toEqual({
180
+ type: "date"
181
+ });
182
+ expect(birthday().timeOnly().build("t").renderer).toBe("dateTimeInput");
183
+ });
184
+ describe("date limits", ()=>{
185
+ afterEach(()=>{
186
+ vi.useRealTimers();
187
+ });
188
+ const isValid = async (config, value)=>(await config.schema.safeParseAsync(value)).success;
189
+ it("hands fixed and relative limits to the renderer, whatever the call order", ()=>{
190
+ const config = new DateTimeFieldBuilder().dateOnly().dateGte("1900-01-01").dateLte({
191
+ expression: "today-18y"
192
+ }).renderer("birthdayInput").build("dob");
193
+ expect(config.rendererSettings).toEqual({
194
+ dateLimits: {
195
+ gte: [
196
+ "1900-01-01"
197
+ ],
198
+ lte: [
199
+ {
200
+ expression: "today-18y"
201
+ }
202
+ ]
203
+ }
204
+ });
205
+ });
206
+ it("checks a value against fixed limits with their messages", async ()=>{
207
+ const config = new DateTimeFieldBuilder().dateOnly().dateGte("1950-01-01", "Too early.").dateLte("2000-12-31", "Too late.").build("d");
208
+ expect(await isValid(config, "1975-06-15")).toBe(true);
209
+ expect(await isValid(config, "1950-01-01")).toBe(true);
210
+ expect(await isValid(config, "2000-12-31")).toBe(true);
211
+ const late = await config.schema.safeParseAsync("2005-01-01");
212
+ expect(late.success).toBe(false);
213
+ expect(late.error?.issues[0].message).toBe("Too late.");
214
+ const early = await config.schema.safeParseAsync("1949-12-31");
215
+ expect(early.error?.issues[0].message).toBe("Too early.");
216
+ });
217
+ it("works out an expression against the local date at the moment of the check", async ()=>{
218
+ vi.useFakeTimers();
219
+ vi.setSystemTime(new Date(2026, 9, 19, 12));
220
+ const config = new DateTimeFieldBuilder().dateOnly().dateLte({
221
+ expression: "today-18y"
222
+ }).build("dob");
223
+ expect(await isValid(config, "2008-10-19")).toBe(true);
224
+ expect(await isValid(config, "2008-10-20")).toBe(false);
225
+ const message = (await config.schema.safeParseAsync("2008-10-20")).error?.issues[0].message;
226
+ expect(message).toBe("Must be on or before 2008-10-19.");
227
+ vi.setSystemTime(new Date(2026, 9, 20, 12));
228
+ expect(await isValid(config, "2008-10-20")).toBe(true);
229
+ });
230
+ it("puts the limit into a message's {date} in the admin's date format", async ()=>{
231
+ vi.useFakeTimers();
232
+ vi.setSystemTime(new Date(2026, 9, 19, 12));
233
+ const formatter = {
234
+ format: vi.fn((date)=>`formatted ${date}`)
235
+ };
236
+ const message = async (builder, value)=>(await builder.build("d").schema.safeParseAsync(value)).error?.issues[0].message;
237
+ expect(await message(new DateTimeFieldBuilder(formatter).dateOnly().dateLte({
238
+ expression: "today-18y"
239
+ }, "Born on or before {date}, please."), "2010-01-01")).toBe("Born on or before formatted 2008-10-19, please.");
240
+ expect(formatter.format).toHaveBeenLastCalledWith("2008-10-19", {
241
+ hour: void 0,
242
+ minute: void 0,
243
+ timeZone: "UTC"
244
+ });
245
+ expect(await message(new DateTimeFieldBuilder(formatter).dateOnly().dateGte("1950-01-01"), "1949-12-31")).toBe("Must be on or after formatted 1950-01-01.");
246
+ expect(await message(new DateTimeFieldBuilder(formatter).timeOnly().dateLte("12:00:00"), "13:00:00")).toBe("Must be on or before 12:00:00.");
247
+ });
248
+ it("formats {date} with the registered DateFormatter", async ()=>{
249
+ const container = new Container();
250
+ container.registerInstance(DateFormatter, {
251
+ format: (date)=>`formatted ${date}`
252
+ });
253
+ IdentityContextFeature.register(container);
254
+ FormModelFeature.register(container);
255
+ const registry = container.resolve(FieldBuilderRegistry);
256
+ const config = registry.datetime().dateOnly().dateLte("2000-12-31").build("d");
257
+ const result = await config.schema.safeParseAsync("2005-01-01");
258
+ expect(result.error?.issues[0].message).toBe("Must be on or before formatted 2000-12-31.");
259
+ });
260
+ it("keeps the field's own schema", async ()=>{
261
+ const config = new DateTimeFieldBuilder().dateOnly().dateLte("2000-12-31").schema(z.string().regex(/^19/, "Must be in the 1900s.")).build("d");
262
+ expect(await isValid(config, "1990-01-01")).toBe(true);
263
+ const result = await config.schema.safeParseAsync("2000-01-01");
264
+ expect(result.error?.issues[0].message).toBe("Must be in the 1900s.");
265
+ expect(await isValid(config, "2005-01-01")).toBe(false);
266
+ });
267
+ it("checks every value of a list and lets an empty value through", async ()=>{
268
+ const config = new DateTimeFieldBuilder().list().dateOnly().dateGte("2000-01-01").build("d");
269
+ expect(await isValid(config, [
270
+ "2001-01-01",
271
+ "2002-01-01"
272
+ ])).toBe(true);
273
+ expect(await isValid(config, [
274
+ "2001-01-01",
275
+ "1999-01-01"
276
+ ])).toBe(false);
277
+ expect(await isValid(config, "")).toBe(true);
278
+ });
279
+ it("fails a value checked against an expression it can't read", async ()=>{
280
+ const config = new DateTimeFieldBuilder().dateOnly().dateLte({
281
+ expression: "now-18y"
282
+ }).build("d");
283
+ const result = await config.schema.safeParseAsync("2000-01-01");
284
+ expect(result.error?.issues[0].message).toBe('Invalid date limit "now-18y".');
285
+ });
286
+ it("leaves a field without limits as it was", ()=>{
287
+ const config = new DateTimeFieldBuilder().dateOnly().build("d");
288
+ expect(config.schema).toBeUndefined();
289
+ expect(config.rendererSettings).toEqual({
290
+ type: "date"
291
+ });
292
+ });
293
+ });
169
294
  it("normalizes withTimezone values", ()=>{
170
295
  const builder = new DateTimeFieldBuilder().withTimezone();
171
296
  expect(builder.normalizeValue("2026-05-01T14:30:00+02:00")).toBe("2026-05-01T14:30:00+02:00");
@@ -1 +1 @@
1
- {"version":3,"file":"features/formModel/FieldBuilder.test.js","sources":["../../../src/features/formModel/FieldBuilder.test.ts"],"sourcesContent":["import { describe, it, expect } from \"vitest\";\nimport { z } from \"zod\";\nimport { Container } from \"@webiny/di\";\nimport { FormModelFeature } from \"./feature.js\";\nimport { IdentityContextFeature } from \"~/features/security/IdentityContext/feature.js\";\nimport { FieldBuilderRegistry, type IFieldBuilderRegistry } from \"./abstractions.js\";\nimport { TextFieldBuilder } from \"./fieldTypes/TextFieldType.js\";\nimport { NumberFieldBuilder } from \"./fieldTypes/NumberFieldType.js\";\nimport { BooleanFieldBuilder } from \"./fieldTypes/BooleanFieldType.js\";\nimport { DateTimeFieldBuilder } from \"./fieldTypes/DateTimeFieldType.js\";\n\nfunction createRegistry(): IFieldBuilderRegistry {\n const container = new Container();\n IdentityContextFeature.register(container);\n FormModelFeature.register(container);\n return container.resolve(FieldBuilderRegistry);\n}\n\ndescribe(\"TextFieldBuilder\", () => {\n it(\"should build a text field config with all fluent methods\", () => {\n const builder = new TextFieldBuilder()\n .label(\"Title\")\n .placeholder(\"Enter title\")\n .required(\"Title is required\")\n .defaultValue(\"Untitled\")\n .renderer(\"custom-text\")\n .schema(z.string().min(1));\n\n const config = builder.build(\"title\");\n\n expect(config.name).toBe(\"title\");\n expect(config.type).toBe(\"text\");\n expect(config.label).toBe(\"Title\");\n expect(config.placeholder).toBe(\"Enter title\");\n expect(config.required).toBe(true);\n expect(config.requiredMessage).toBe(\"Title is required\");\n expect(config.defaultValue).toBe(\"Untitled\");\n expect(config.renderer).toBe(\"custom-text\");\n expect(config.hidden).toBe(false);\n expect(config.disabled).toBe(false);\n expect(config.schema).toBeDefined();\n });\n\n it(\"should support hidden() method\", () => {\n const config = new TextFieldBuilder().hidden().build(\"secret\");\n expect(config.hidden).toBe(true);\n });\n\n it(\"should support disabled() method\", () => {\n const config = new TextFieldBuilder().disabled().build(\"readonly\");\n expect(config.disabled).toBe(true);\n });\n\n it(\"should default disabled to false\", () => {\n const config = new TextFieldBuilder().build(\"field\");\n expect(config.disabled).toBe(false);\n });\n});\n\ndescribe(\"TextFieldBuilder - beforeChange / afterChange\", () => {\n it(\"should store beforeChange callbacks in config\", () => {\n const cb1 = (value: unknown) => value;\n const cb2 = (value: unknown) => value;\n\n const config = new TextFieldBuilder().beforeChange(cb1).beforeChange(cb2).build(\"field\");\n\n expect(config.beforeChangeCallbacks).toHaveLength(2);\n expect(config.beforeChangeCallbacks![0]).toBe(cb1);\n expect(config.beforeChangeCallbacks![1]).toBe(cb2);\n });\n\n it(\"should store afterChange callbacks in config\", () => {\n const cb = () => {};\n const config = new TextFieldBuilder().afterChange(cb).build(\"field\");\n\n expect(config.afterChangeCallbacks).toHaveLength(1);\n expect(config.afterChangeCallbacks![0]).toBe(cb);\n });\n\n it(\"should not have callback arrays when none are added\", () => {\n const config = new TextFieldBuilder().build(\"field\");\n expect(config.beforeChangeCallbacks).toBeUndefined();\n expect(config.afterChangeCallbacks).toBeUndefined();\n });\n\n it(\"should support chaining callbacks with other builder methods\", () => {\n const config = new TextFieldBuilder()\n .label(\"Title\")\n .beforeChange(value => value)\n .required(\"Required\")\n .afterChange(() => {})\n .build(\"title\");\n\n expect(config.label).toBe(\"Title\");\n expect(config.required).toBe(true);\n expect(config.beforeChangeCallbacks).toHaveLength(1);\n expect(config.afterChangeCallbacks).toHaveLength(1);\n });\n});\n\ndescribe(\"TextFieldBuilder - options\", () => {\n it(\"should build a text field config with static options\", () => {\n const options = [\n { label: \"English\", value: \"en\" },\n { label: \"German\", value: \"de\" }\n ];\n\n const config = new TextFieldBuilder()\n .label(\"Language\")\n .options(options)\n .required()\n .build(\"language\");\n\n expect(config.name).toBe(\"language\");\n expect(config.type).toBe(\"text\");\n expect(config.label).toBe(\"Language\");\n expect(config.required).toBe(true);\n expect(config.options).toEqual(options);\n expect(config.renderer).toBe(\"select\");\n });\n\n it(\"should support reactive options function\", () => {\n const optionsFn = () => [{ label: \"A\", value: \"a\" }];\n const config = new TextFieldBuilder().options(optionsFn).build(\"dynamic\");\n expect(typeof config.options).toBe(\"function\");\n });\n\n it(\"should auto-switch renderer to dropdown when options are set\", () => {\n const config = new TextFieldBuilder().options([{ label: \"A\", value: \"a\" }]).build(\"field\");\n expect(config.renderer).toBe(\"select\");\n });\n\n it(\"should not override an explicitly set renderer\", () => {\n const config = new TextFieldBuilder()\n .renderer(\"radioButtons\")\n .options([{ label: \"A\", value: \"a\" }])\n .build(\"field\");\n expect(config.renderer).toBe(\"radioButtons\");\n });\n});\n\ndescribe(\"NumberFieldBuilder - options\", () => {\n it(\"should build a number field config with options\", () => {\n const options = [\n { label: \"One\", value: \"1\" },\n { label: \"Two\", value: \"2\" }\n ];\n\n const config = new NumberFieldBuilder().label(\"Count\").options(options).build(\"count\");\n\n expect(config.type).toBe(\"number\");\n expect(config.options).toEqual(options);\n expect(config.renderer).toBe(\"select\");\n });\n\n it(\"should not override an explicitly set renderer\", () => {\n const config = new NumberFieldBuilder()\n .renderer(\"radioButtons\")\n .options([{ label: \"1\", value: \"1\" }])\n .build(\"field\");\n expect(config.renderer).toBe(\"radioButtons\");\n });\n});\n\ndescribe(\"DateTimeFieldBuilder\", () => {\n it(\"should build a datetime field with default renderer\", () => {\n const config = new DateTimeFieldBuilder().label(\"Created\").build(\"created\");\n expect(config.type).toBe(\"datetime\");\n expect(config.renderer).toBe(\"dateTimeInput\");\n expect(config.label).toBe(\"Created\");\n });\n\n it(\"keeps the list renderer when a subtype is set after list()\", () => {\n const subtypes = [\n (b: DateTimeFieldBuilder) => b.dateOnly(),\n (b: DateTimeFieldBuilder) => b.timeOnly(),\n (b: DateTimeFieldBuilder) => b.withTimezone(),\n (b: DateTimeFieldBuilder) => b.withoutTimezone()\n ];\n for (const subtype of subtypes) {\n expect(subtype(new DateTimeFieldBuilder().list()).build(\"d\").renderer).toBe(\n \"dateTimeInputs\"\n );\n expect(subtype(new DateTimeFieldBuilder()).build(\"d\").renderer).toBe(\"dateTimeInput\");\n }\n });\n\n it(\"normalizes withTimezone values\", () => {\n const builder = new DateTimeFieldBuilder().withTimezone();\n expect(builder.normalizeValue(\"2026-05-01T14:30:00+02:00\")).toBe(\n \"2026-05-01T14:30:00+02:00\"\n );\n expect(builder.normalizeValue(\"2026-05-01T14:30-05:30\")).toBe(\"2026-05-01T14:30:00-05:30\");\n expect(builder.normalizeValue(\"2026-05-01T09:00:00.000Z\")).toBe(\n \"2026-05-01T09:00:00.000+00:00\"\n );\n expect(builder.normalizeValue(\"2026-05-01T14:30:00\")).toBe(\"2026-05-01T14:30:00\");\n });\n\n it(\"normalizes withoutTimezone values to a stable UTC instant\", () => {\n const builder = new DateTimeFieldBuilder().withoutTimezone();\n const value = \"2026-05-01T12:30:00.000Z\";\n expect(builder.normalizeValue(value)).toBe(value);\n expect(builder.normalizeValue(builder.normalizeValue(value))).toBe(value);\n });\n});\n\ndescribe(\"FieldBuilderRegistry\", () => {\n it(\"should create text builders via registry.text()\", () => {\n const registry = createRegistry();\n const builder = registry.text();\n expect(builder).toBeInstanceOf(TextFieldBuilder);\n });\n\n it(\"should create number builders via registry.number()\", () => {\n const registry = createRegistry();\n const builder = registry.number();\n expect(builder).toBeInstanceOf(NumberFieldBuilder);\n });\n\n it(\"should create boolean builders via registry.boolean()\", () => {\n const registry = createRegistry();\n const builder = registry.boolean();\n expect(builder).toBeInstanceOf(BooleanFieldBuilder);\n });\n\n it(\"should create datetime builders via registry.datetime()\", () => {\n const registry = createRegistry();\n const builder = registry.datetime();\n expect(builder).toBeInstanceOf(DateTimeFieldBuilder);\n });\n\n it(\"should support chaining on registry-created builders\", () => {\n const registry = createRegistry();\n const config = registry.text().label(\"Name\").required().build(\"name\");\n expect(config.label).toBe(\"Name\");\n expect(config.required).toBe(true);\n });\n\n it(\"should support options on text builders from registry\", () => {\n const registry = createRegistry();\n const config = registry\n .text()\n .options([{ label: \"A\", value: \"a\" }])\n .build(\"field\");\n expect(config.options).toHaveLength(1);\n expect(config.renderer).toBe(\"select\");\n });\n});\n\ndescribe(\"normalizeValue\", () => {\n it(\"should be identity for TextFieldBuilder\", () => {\n const builder = new TextFieldBuilder();\n expect(builder.normalizeValue(\"hello\")).toBe(\"hello\");\n expect(builder.normalizeValue(42)).toBe(42);\n expect(builder.normalizeValue(null)).toBe(null);\n });\n\n it(\"should coerce string to number for NumberFieldBuilder\", () => {\n const builder = new NumberFieldBuilder();\n expect(builder.normalizeValue(\"42\")).toBe(42);\n expect(builder.normalizeValue(\"3.14\")).toBe(3.14);\n expect(builder.normalizeValue(\"0\")).toBe(0);\n });\n\n it(\"should return null for non-numeric values in NumberFieldBuilder\", () => {\n const builder = new NumberFieldBuilder();\n expect(builder.normalizeValue(null)).toBe(null);\n expect(builder.normalizeValue(undefined)).toBe(null);\n expect(builder.normalizeValue(\"\")).toBe(null);\n expect(builder.normalizeValue(\"abc\")).toBe(null);\n });\n\n it(\"should coerce to boolean for BooleanFieldBuilder\", () => {\n const builder = new BooleanFieldBuilder();\n expect(builder.normalizeValue(1)).toBe(true);\n expect(builder.normalizeValue(0)).toBe(false);\n expect(builder.normalizeValue(\"\")).toBe(false);\n expect(builder.normalizeValue(\"yes\")).toBe(true);\n expect(builder.normalizeValue(null)).toBe(false);\n });\n\n it(\"should be identity for DateTimeFieldBuilder\", () => {\n const builder = new DateTimeFieldBuilder();\n expect(builder.normalizeValue(\"2024-01-01\")).toBe(\"2024-01-01\");\n expect(builder.normalizeValue(null)).toBe(null);\n });\n\n it(\"should capture normalizeValue into config at build time\", () => {\n const config = new NumberFieldBuilder().label(\"Count\").build(\"count\");\n expect(config.normalizeValue).toBeDefined();\n expect(config.normalizeValue!(\"42\")).toBe(42);\n expect(config.normalizeValue!(null)).toBe(null);\n });\n});\n"],"names":["createRegistry","container","Container","IdentityContextFeature","FormModelFeature","FieldBuilderRegistry","describe","it","builder","TextFieldBuilder","z","config","expect","cb1","value","cb2","cb","options","optionsFn","NumberFieldBuilder","DateTimeFieldBuilder","subtypes","b","subtype","registry","BooleanFieldBuilder","undefined"],"mappings":"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"}
1
+ {"version":3,"file":"features/formModel/FieldBuilder.test.js","sources":["../../../src/features/formModel/FieldBuilder.test.ts"],"sourcesContent":["import { afterEach, describe, it, expect, vi } from \"vitest\";\nimport { z } from \"zod\";\nimport { Container } from \"@webiny/di\";\nimport { FormModelFeature } from \"./feature.js\";\nimport { IdentityContextFeature } from \"~/features/security/IdentityContext/feature.js\";\nimport { FieldBuilderRegistry, type IFieldBuilderRegistry } from \"./abstractions.js\";\nimport { TextFieldBuilder } from \"./fieldTypes/TextFieldType.js\";\nimport { NumberFieldBuilder } from \"./fieldTypes/NumberFieldType.js\";\nimport { BooleanFieldBuilder } from \"./fieldTypes/BooleanFieldType.js\";\nimport { DateTimeFieldBuilder } from \"./fieldTypes/DateTimeFieldType.js\";\nimport { DateFormatter } from \"~/features/dateFormatter/abstractions.js\";\n\nfunction createRegistry(): IFieldBuilderRegistry {\n const container = new Container();\n IdentityContextFeature.register(container);\n FormModelFeature.register(container);\n return container.resolve(FieldBuilderRegistry);\n}\n\ndescribe(\"TextFieldBuilder\", () => {\n it(\"should build a text field config with all fluent methods\", () => {\n const builder = new TextFieldBuilder()\n .label(\"Title\")\n .placeholder(\"Enter title\")\n .required(\"Title is required\")\n .defaultValue(\"Untitled\")\n .renderer(\"custom-text\")\n .schema(z.string().min(1));\n\n const config = builder.build(\"title\");\n\n expect(config.name).toBe(\"title\");\n expect(config.type).toBe(\"text\");\n expect(config.label).toBe(\"Title\");\n expect(config.placeholder).toBe(\"Enter title\");\n expect(config.required).toBe(true);\n expect(config.requiredMessage).toBe(\"Title is required\");\n expect(config.defaultValue).toBe(\"Untitled\");\n expect(config.renderer).toBe(\"custom-text\");\n expect(config.hidden).toBe(false);\n expect(config.disabled).toBe(false);\n expect(config.schema).toBeDefined();\n });\n\n it(\"should support hidden() method\", () => {\n const config = new TextFieldBuilder().hidden().build(\"secret\");\n expect(config.hidden).toBe(true);\n });\n\n it(\"should support disabled() method\", () => {\n const config = new TextFieldBuilder().disabled().build(\"readonly\");\n expect(config.disabled).toBe(true);\n });\n\n it(\"should default disabled to false\", () => {\n const config = new TextFieldBuilder().build(\"field\");\n expect(config.disabled).toBe(false);\n });\n});\n\ndescribe(\"TextFieldBuilder - beforeChange / afterChange\", () => {\n it(\"should store beforeChange callbacks in config\", () => {\n const cb1 = (value: unknown) => value;\n const cb2 = (value: unknown) => value;\n\n const config = new TextFieldBuilder().beforeChange(cb1).beforeChange(cb2).build(\"field\");\n\n expect(config.beforeChangeCallbacks).toHaveLength(2);\n expect(config.beforeChangeCallbacks![0]).toBe(cb1);\n expect(config.beforeChangeCallbacks![1]).toBe(cb2);\n });\n\n it(\"should store afterChange callbacks in config\", () => {\n const cb = () => {};\n const config = new TextFieldBuilder().afterChange(cb).build(\"field\");\n\n expect(config.afterChangeCallbacks).toHaveLength(1);\n expect(config.afterChangeCallbacks![0]).toBe(cb);\n });\n\n it(\"should not have callback arrays when none are added\", () => {\n const config = new TextFieldBuilder().build(\"field\");\n expect(config.beforeChangeCallbacks).toBeUndefined();\n expect(config.afterChangeCallbacks).toBeUndefined();\n });\n\n it(\"should support chaining callbacks with other builder methods\", () => {\n const config = new TextFieldBuilder()\n .label(\"Title\")\n .beforeChange(value => value)\n .required(\"Required\")\n .afterChange(() => {})\n .build(\"title\");\n\n expect(config.label).toBe(\"Title\");\n expect(config.required).toBe(true);\n expect(config.beforeChangeCallbacks).toHaveLength(1);\n expect(config.afterChangeCallbacks).toHaveLength(1);\n });\n});\n\ndescribe(\"TextFieldBuilder - options\", () => {\n it(\"should build a text field config with static options\", () => {\n const options = [\n { label: \"English\", value: \"en\" },\n { label: \"German\", value: \"de\" }\n ];\n\n const config = new TextFieldBuilder()\n .label(\"Language\")\n .options(options)\n .required()\n .build(\"language\");\n\n expect(config.name).toBe(\"language\");\n expect(config.type).toBe(\"text\");\n expect(config.label).toBe(\"Language\");\n expect(config.required).toBe(true);\n expect(config.options).toEqual(options);\n expect(config.renderer).toBe(\"select\");\n });\n\n it(\"should support reactive options function\", () => {\n const optionsFn = () => [{ label: \"A\", value: \"a\" }];\n const config = new TextFieldBuilder().options(optionsFn).build(\"dynamic\");\n expect(typeof config.options).toBe(\"function\");\n });\n\n it(\"should auto-switch renderer to dropdown when options are set\", () => {\n const config = new TextFieldBuilder().options([{ label: \"A\", value: \"a\" }]).build(\"field\");\n expect(config.renderer).toBe(\"select\");\n });\n\n it(\"should not override an explicitly set renderer\", () => {\n const config = new TextFieldBuilder()\n .renderer(\"radioButtons\")\n .options([{ label: \"A\", value: \"a\" }])\n .build(\"field\");\n expect(config.renderer).toBe(\"radioButtons\");\n });\n});\n\ndescribe(\"NumberFieldBuilder - options\", () => {\n it(\"should build a number field config with options\", () => {\n const options = [\n { label: \"One\", value: \"1\" },\n { label: \"Two\", value: \"2\" }\n ];\n\n const config = new NumberFieldBuilder().label(\"Count\").options(options).build(\"count\");\n\n expect(config.type).toBe(\"number\");\n expect(config.options).toEqual(options);\n expect(config.renderer).toBe(\"select\");\n });\n\n it(\"should not override an explicitly set renderer\", () => {\n const config = new NumberFieldBuilder()\n .renderer(\"radioButtons\")\n .options([{ label: \"1\", value: \"1\" }])\n .build(\"field\");\n expect(config.renderer).toBe(\"radioButtons\");\n });\n});\n\ndescribe(\"DateTimeFieldBuilder\", () => {\n it(\"should build a datetime field with default renderer\", () => {\n const config = new DateTimeFieldBuilder().label(\"Created\").build(\"created\");\n expect(config.type).toBe(\"datetime\");\n expect(config.renderer).toBe(\"dateTimeInput\");\n expect(config.label).toBe(\"Created\");\n });\n\n it(\"keeps the list renderer when a subtype is set after list()\", () => {\n const subtypes = [\n (b: DateTimeFieldBuilder) => b.dateOnly(),\n (b: DateTimeFieldBuilder) => b.timeOnly(),\n (b: DateTimeFieldBuilder) => b.withTimezone(),\n (b: DateTimeFieldBuilder) => b.withoutTimezone()\n ];\n for (const subtype of subtypes) {\n expect(subtype(new DateTimeFieldBuilder().list()).build(\"d\").renderer).toBe(\n \"dateTimeInputs\"\n );\n expect(subtype(new DateTimeFieldBuilder()).build(\"d\").renderer).toBe(\"dateTimeInput\");\n }\n });\n\n it(\"renders a date-only field with the birthday picker\", () => {\n const config = new DateTimeFieldBuilder().dateOnly().renderer(\"birthdayInput\").build(\"dob\");\n expect(config.renderer).toBe(\"birthdayInput\");\n expect(config.rendererSettings).toBeUndefined();\n });\n\n it(\"keeps a birthday picker set before dateOnly() and drops it for other subtypes\", () => {\n const birthday = () => new DateTimeFieldBuilder().renderer(\"birthdayInput\");\n const dateOnly = birthday().dateOnly().build(\"dob\");\n expect(dateOnly.renderer).toBe(\"birthdayInput\");\n expect(dateOnly.rendererSettings).toEqual({ type: \"date\" });\n\n expect(birthday().timeOnly().build(\"t\").renderer).toBe(\"dateTimeInput\");\n });\n\n describe(\"date limits\", () => {\n afterEach(() => {\n vi.useRealTimers();\n });\n\n const isValid = async (config: { schema?: z.ZodTypeAny }, value: unknown) =>\n (await config.schema!.safeParseAsync(value)).success;\n\n it(\"hands fixed and relative limits to the renderer, whatever the call order\", () => {\n const config = new DateTimeFieldBuilder()\n .dateOnly()\n .dateGte(\"1900-01-01\")\n .dateLte({ expression: \"today-18y\" })\n .renderer(\"birthdayInput\")\n .build(\"dob\");\n expect(config.rendererSettings).toEqual({\n dateLimits: { gte: [\"1900-01-01\"], lte: [{ expression: \"today-18y\" }] }\n });\n });\n\n it(\"checks a value against fixed limits with their messages\", async () => {\n const config = new DateTimeFieldBuilder()\n .dateOnly()\n .dateGte(\"1950-01-01\", \"Too early.\")\n .dateLte(\"2000-12-31\", \"Too late.\")\n .build(\"d\");\n expect(await isValid(config, \"1975-06-15\")).toBe(true);\n expect(await isValid(config, \"1950-01-01\")).toBe(true);\n expect(await isValid(config, \"2000-12-31\")).toBe(true);\n\n const late = await config.schema!.safeParseAsync(\"2005-01-01\");\n expect(late.success).toBe(false);\n expect(late.error?.issues[0].message).toBe(\"Too late.\");\n\n const early = await config.schema!.safeParseAsync(\"1949-12-31\");\n expect(early.error?.issues[0].message).toBe(\"Too early.\");\n });\n\n it(\"works out an expression against the local date at the moment of the check\", async () => {\n vi.useFakeTimers();\n vi.setSystemTime(new Date(2026, 9, 19, 12));\n const config = new DateTimeFieldBuilder()\n .dateOnly()\n .dateLte({ expression: \"today-18y\" })\n .build(\"dob\");\n expect(await isValid(config, \"2008-10-19\")).toBe(true);\n expect(await isValid(config, \"2008-10-20\")).toBe(false);\n\n const message = (await config.schema!.safeParseAsync(\"2008-10-20\")).error?.issues[0]\n .message;\n expect(message).toBe(\"Must be on or before 2008-10-19.\");\n\n // The next day, the same form allows the next birthday.\n vi.setSystemTime(new Date(2026, 9, 20, 12));\n expect(await isValid(config, \"2008-10-20\")).toBe(true);\n });\n\n it(\"puts the limit into a message's {date} in the admin's date format\", async () => {\n vi.useFakeTimers();\n vi.setSystemTime(new Date(2026, 9, 19, 12));\n const formatter = { format: vi.fn((date: unknown) => `formatted ${date}`) };\n const message = async (builder: DateTimeFieldBuilder, value: string) =>\n (await builder.build(\"d\").schema!.safeParseAsync(value)).error?.issues[0].message;\n\n expect(\n await message(\n new DateTimeFieldBuilder(formatter)\n .dateOnly()\n .dateLte({ expression: \"today-18y\" }, \"Born on or before {date}, please.\"),\n \"2010-01-01\"\n )\n ).toBe(\"Born on or before formatted 2008-10-19, please.\");\n // A date-only limit is shown without a time, and in UTC so it keeps its day.\n expect(formatter.format).toHaveBeenLastCalledWith(\"2008-10-19\", {\n hour: undefined,\n minute: undefined,\n timeZone: \"UTC\"\n });\n\n expect(\n await message(\n new DateTimeFieldBuilder(formatter).dateOnly().dateGte(\"1950-01-01\"),\n \"1949-12-31\"\n )\n ).toBe(\"Must be on or after formatted 1950-01-01.\");\n\n // A time has no date to format.\n expect(\n await message(\n new DateTimeFieldBuilder(formatter).timeOnly().dateLte(\"12:00:00\"),\n \"13:00:00\"\n )\n ).toBe(\"Must be on or before 12:00:00.\");\n });\n\n it(\"formats {date} with the registered DateFormatter\", async () => {\n const container = new Container();\n container.registerInstance(DateFormatter, {\n format: (date: unknown) => `formatted ${date}`\n });\n IdentityContextFeature.register(container);\n FormModelFeature.register(container);\n const registry = container.resolve(FieldBuilderRegistry);\n const config = registry.datetime().dateOnly().dateLte(\"2000-12-31\").build(\"d\");\n const result = await config.schema!.safeParseAsync(\"2005-01-01\");\n expect(result.error?.issues[0].message).toBe(\n \"Must be on or before formatted 2000-12-31.\"\n );\n });\n\n it(\"keeps the field's own schema\", async () => {\n const config = new DateTimeFieldBuilder()\n .dateOnly()\n .dateLte(\"2000-12-31\")\n .schema(z.string().regex(/^19/, \"Must be in the 1900s.\"))\n .build(\"d\");\n expect(await isValid(config, \"1990-01-01\")).toBe(true);\n const result = await config.schema!.safeParseAsync(\"2000-01-01\");\n expect(result.error?.issues[0].message).toBe(\"Must be in the 1900s.\");\n expect(await isValid(config, \"2005-01-01\")).toBe(false);\n });\n\n it(\"checks every value of a list and lets an empty value through\", async () => {\n const config = new DateTimeFieldBuilder()\n .list()\n .dateOnly()\n .dateGte(\"2000-01-01\")\n .build(\"d\");\n expect(await isValid(config, [\"2001-01-01\", \"2002-01-01\"])).toBe(true);\n expect(await isValid(config, [\"2001-01-01\", \"1999-01-01\"])).toBe(false);\n expect(await isValid(config, \"\")).toBe(true);\n });\n\n it(\"fails a value checked against an expression it can't read\", async () => {\n const config = new DateTimeFieldBuilder()\n .dateOnly()\n .dateLte({ expression: \"now-18y\" })\n .build(\"d\");\n const result = await config.schema!.safeParseAsync(\"2000-01-01\");\n expect(result.error?.issues[0].message).toBe('Invalid date limit \"now-18y\".');\n });\n\n it(\"leaves a field without limits as it was\", () => {\n const config = new DateTimeFieldBuilder().dateOnly().build(\"d\");\n expect(config.schema).toBeUndefined();\n expect(config.rendererSettings).toEqual({ type: \"date\" });\n });\n });\n\n it(\"normalizes withTimezone values\", () => {\n const builder = new DateTimeFieldBuilder().withTimezone();\n expect(builder.normalizeValue(\"2026-05-01T14:30:00+02:00\")).toBe(\n \"2026-05-01T14:30:00+02:00\"\n );\n expect(builder.normalizeValue(\"2026-05-01T14:30-05:30\")).toBe(\"2026-05-01T14:30:00-05:30\");\n expect(builder.normalizeValue(\"2026-05-01T09:00:00.000Z\")).toBe(\n \"2026-05-01T09:00:00.000+00:00\"\n );\n expect(builder.normalizeValue(\"2026-05-01T14:30:00\")).toBe(\"2026-05-01T14:30:00\");\n });\n\n it(\"normalizes withoutTimezone values to a stable UTC instant\", () => {\n const builder = new DateTimeFieldBuilder().withoutTimezone();\n const value = \"2026-05-01T12:30:00.000Z\";\n expect(builder.normalizeValue(value)).toBe(value);\n expect(builder.normalizeValue(builder.normalizeValue(value))).toBe(value);\n });\n});\n\ndescribe(\"FieldBuilderRegistry\", () => {\n it(\"should create text builders via registry.text()\", () => {\n const registry = createRegistry();\n const builder = registry.text();\n expect(builder).toBeInstanceOf(TextFieldBuilder);\n });\n\n it(\"should create number builders via registry.number()\", () => {\n const registry = createRegistry();\n const builder = registry.number();\n expect(builder).toBeInstanceOf(NumberFieldBuilder);\n });\n\n it(\"should create boolean builders via registry.boolean()\", () => {\n const registry = createRegistry();\n const builder = registry.boolean();\n expect(builder).toBeInstanceOf(BooleanFieldBuilder);\n });\n\n it(\"should create datetime builders via registry.datetime()\", () => {\n const registry = createRegistry();\n const builder = registry.datetime();\n expect(builder).toBeInstanceOf(DateTimeFieldBuilder);\n });\n\n it(\"should support chaining on registry-created builders\", () => {\n const registry = createRegistry();\n const config = registry.text().label(\"Name\").required().build(\"name\");\n expect(config.label).toBe(\"Name\");\n expect(config.required).toBe(true);\n });\n\n it(\"should support options on text builders from registry\", () => {\n const registry = createRegistry();\n const config = registry\n .text()\n .options([{ label: \"A\", value: \"a\" }])\n .build(\"field\");\n expect(config.options).toHaveLength(1);\n expect(config.renderer).toBe(\"select\");\n });\n});\n\ndescribe(\"normalizeValue\", () => {\n it(\"should be identity for TextFieldBuilder\", () => {\n const builder = new TextFieldBuilder();\n expect(builder.normalizeValue(\"hello\")).toBe(\"hello\");\n expect(builder.normalizeValue(42)).toBe(42);\n expect(builder.normalizeValue(null)).toBe(null);\n });\n\n it(\"should coerce string to number for NumberFieldBuilder\", () => {\n const builder = new NumberFieldBuilder();\n expect(builder.normalizeValue(\"42\")).toBe(42);\n expect(builder.normalizeValue(\"3.14\")).toBe(3.14);\n expect(builder.normalizeValue(\"0\")).toBe(0);\n });\n\n it(\"should return null for non-numeric values in NumberFieldBuilder\", () => {\n const builder = new NumberFieldBuilder();\n expect(builder.normalizeValue(null)).toBe(null);\n expect(builder.normalizeValue(undefined)).toBe(null);\n expect(builder.normalizeValue(\"\")).toBe(null);\n expect(builder.normalizeValue(\"abc\")).toBe(null);\n });\n\n it(\"should coerce to boolean for BooleanFieldBuilder\", () => {\n const builder = new BooleanFieldBuilder();\n expect(builder.normalizeValue(1)).toBe(true);\n expect(builder.normalizeValue(0)).toBe(false);\n expect(builder.normalizeValue(\"\")).toBe(false);\n expect(builder.normalizeValue(\"yes\")).toBe(true);\n expect(builder.normalizeValue(null)).toBe(false);\n });\n\n it(\"should be identity for DateTimeFieldBuilder\", () => {\n const builder = new DateTimeFieldBuilder();\n expect(builder.normalizeValue(\"2024-01-01\")).toBe(\"2024-01-01\");\n expect(builder.normalizeValue(null)).toBe(null);\n });\n\n it(\"should capture normalizeValue into config at build time\", () => {\n const config = new NumberFieldBuilder().label(\"Count\").build(\"count\");\n expect(config.normalizeValue).toBeDefined();\n expect(config.normalizeValue!(\"42\")).toBe(42);\n expect(config.normalizeValue!(null)).toBe(null);\n });\n});\n"],"names":["createRegistry","container","Container","IdentityContextFeature","FormModelFeature","FieldBuilderRegistry","describe","it","builder","TextFieldBuilder","z","config","expect","cb1","value","cb2","cb","options","optionsFn","NumberFieldBuilder","DateTimeFieldBuilder","subtypes","b","subtype","birthday","dateOnly","afterEach","vi","isValid","late","early","Date","message","formatter","date","undefined","DateFormatter","registry","result","BooleanFieldBuilder"],"mappings":"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"}
@@ -787,6 +787,22 @@ export declare namespace FieldType {
787
787
  type FieldBuilder = IFieldBuilder;
788
788
  type FieldBuilderRegistry = IFieldBuilderRegistry;
789
789
  }
790
+ /**
791
+ * Turns a field's value into the text shown where the value is summarised rather than edited,
792
+ * such as an accordion item's title. Register one for a field type whose raw value is not
793
+ * readable, like a reference, which holds an entry ID rather than the entry's title.
794
+ *
795
+ * `format` is read inside an observer, so it may return `undefined` while it loads a value and
796
+ * an observable value once it has it.
797
+ */
798
+ export interface IFieldValueFormatter {
799
+ readonly type: string;
800
+ format(field: IFieldVM): string | undefined;
801
+ }
802
+ export declare const FieldValueFormatter: import("@webiny/di").Abstraction<IFieldValueFormatter>;
803
+ export declare namespace FieldValueFormatter {
804
+ type Interface = IFieldValueFormatter;
805
+ }
790
806
  export declare const FieldBuilderRegistry: import("@webiny/di").Abstraction<IFieldBuilderRegistry>;
791
807
  export declare namespace FieldBuilderRegistry {
792
808
  type Interface = IFieldBuilderRegistry;