@antelopejs/dms 0.4.5 → 0.4.6

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 (158) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/hooks/invite-extensions.js +2 -2
  3. package/dist/hooks/invite-extensions.js.map +1 -1
  4. package/dist/implementations/dms/layout-banners.d.ts +18 -0
  5. package/dist/implementations/dms/layout-banners.js +60 -0
  6. package/dist/implementations/dms/layout-banners.js.map +1 -0
  7. package/dist/implementations/dms/page.d.ts +2 -0
  8. package/dist/implementations/dms/page.js +82 -27
  9. package/dist/implementations/dms/page.js.map +1 -1
  10. package/dist/implementations/dms/quick-actions.d.ts +5 -0
  11. package/dist/implementations/dms/quick-actions.js.map +1 -1
  12. package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
  13. package/dist/implementations/dms/resolver-timeout.js +13 -0
  14. package/dist/implementations/dms/resolver-timeout.js.map +1 -0
  15. package/dist/implementations/dms/saas-mode.d.ts +8 -0
  16. package/dist/implementations/dms/saas-mode.js +20 -0
  17. package/dist/implementations/dms/saas-mode.js.map +1 -0
  18. package/dist/implementations/dms-auth/index.d.ts +2 -1
  19. package/dist/implementations/dms-auth/index.js +19 -8
  20. package/dist/implementations/dms-auth/index.js.map +1 -1
  21. package/dist/implementations/dms-html-render/service-jwt.js +2 -1
  22. package/dist/implementations/dms-html-render/service-jwt.js.map +1 -1
  23. package/dist/index.js +2 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/pages/settings/users/index.d.ts +1 -0
  26. package/dist/pages/settings/users/index.js +1 -0
  27. package/dist/pages/settings/users/index.js.map +1 -1
  28. package/dist/pages/settings/users/invite-extension-routes.d.ts +4 -0
  29. package/dist/pages/settings/users/invite-extension-routes.js +57 -0
  30. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -0
  31. package/dist/pages/settings/users/invites.d.ts +2 -1
  32. package/dist/pages/settings/users/invites.js +17 -5
  33. package/dist/pages/settings/users/invites.js.map +1 -1
  34. package/dist/pages/settings/users/members.d.ts +4 -1
  35. package/dist/pages/settings/users/members.js +10 -6
  36. package/dist/pages/settings/users/members.js.map +1 -1
  37. package/dist/pages/settings/users/notifications.js +1 -1
  38. package/dist/pages/settings/users/notifications.js.map +1 -1
  39. package/dist/pages/settings/users/quick-actions.d.ts +1 -0
  40. package/dist/pages/settings/users/quick-actions.js +22 -0
  41. package/dist/pages/settings/users/quick-actions.js.map +1 -0
  42. package/dist/routes/auth/signup.d.ts +2 -0
  43. package/dist/routes/auth/signup.js +7 -2
  44. package/dist/routes/auth/signup.js.map +1 -1
  45. package/dist/test/integration/invite-extension-edit.test.d.ts +1 -0
  46. package/dist/test/integration/invite-extension-edit.test.js +122 -0
  47. package/dist/test/integration/invite-extension-edit.test.js.map +1 -0
  48. package/dist/test/integration/saas-mode-ownership.test.d.ts +1 -0
  49. package/dist/test/integration/saas-mode-ownership.test.js +81 -0
  50. package/dist/test/integration/saas-mode-ownership.test.js.map +1 -0
  51. package/dist/test/integration/tenant-lifecycle.test.js +38 -0
  52. package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
  53. package/dist/test/unit/implementations/dms/layout-banners.test.d.ts +1 -0
  54. package/dist/test/unit/implementations/dms/layout-banners.test.js +217 -0
  55. package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -0
  56. package/dist/test/unit/implementations/dms/quick-actions-button.test.d.ts +1 -0
  57. package/dist/test/unit/implementations/dms/quick-actions-button.test.js +220 -0
  58. package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -0
  59. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +1 -1
  60. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
  61. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.d.ts +1 -0
  62. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js +37 -0
  63. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js.map +1 -0
  64. package/dist/test/unit/interfaces/dms/invite-extension.test.js +138 -12
  65. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  66. package/dist/test/unit/interfaces/dms/saas-mode.test.d.ts +1 -0
  67. package/dist/test/unit/interfaces/dms/saas-mode.test.js +42 -0
  68. package/dist/test/unit/interfaces/dms/saas-mode.test.js.map +1 -0
  69. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.d.ts +1 -0
  70. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js +53 -0
  71. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js.map +1 -0
  72. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.d.ts +1 -0
  73. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js +80 -0
  74. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js.map +1 -0
  75. package/dist/test/unit/pages/settings/invite-member-quick-action.test.d.ts +1 -0
  76. package/dist/test/unit/pages/settings/invite-member-quick-action.test.js +104 -0
  77. package/dist/test/unit/pages/settings/invite-member-quick-action.test.js.map +1 -0
  78. package/dist/test/unit/pages/settings/notifications-access-gate.test.d.ts +1 -0
  79. package/dist/test/unit/pages/settings/notifications-access-gate.test.js +78 -0
  80. package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -0
  81. package/dist/test/unit/signup-account-guard.test.js +20 -2
  82. package/dist/test/unit/signup-account-guard.test.js.map +1 -1
  83. package/dist/test/unit/utils/admin-invite-email.test.d.ts +1 -0
  84. package/dist/test/unit/utils/admin-invite-email.test.js +69 -0
  85. package/dist/test/unit/utils/admin-invite-email.test.js.map +1 -0
  86. package/dist/utils/admin-invite-email.d.ts +14 -0
  87. package/dist/utils/admin-invite-email.js +51 -0
  88. package/dist/utils/admin-invite-email.js.map +1 -0
  89. package/dist/utils/index.d.ts +0 -1
  90. package/dist/utils/index.js +0 -1
  91. package/dist/utils/index.js.map +1 -1
  92. package/dist/validation/auth.schema.d.ts +3 -3
  93. package/dist/validation/auth.schema.js +1 -1
  94. package/dist/validation/auth.schema.js.map +1 -1
  95. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +37 -8
  96. package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +3 -1
  97. package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +3 -1
  98. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +50 -0
  99. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +16 -13
  100. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +2 -2
  101. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +11 -5
  102. package/frontend-vue/layers/dms-layout/app/build/components/notification/widgetRequest.ts +20 -0
  103. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +59 -0
  104. package/frontend-vue/layers/dms-layout/app/composables/page/useQuickActions.ts +15 -2
  105. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +2 -0
  106. package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +54 -9
  107. package/frontend-vue/layers/dms-layout/app/error.vue +17 -2
  108. package/frontend-vue/layers/dms-layout/app/types/page.ts +22 -0
  109. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +48 -0
  110. package/frontend-vue/layers/dms-layout/app/utils/favorites.ts +94 -0
  111. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +21 -2
  112. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +21 -2
  113. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/index.ts +1 -1
  114. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/{useOnboardingMiddleware.ts → useOnboarding.ts} +0 -12
  115. package/frontend-vue/layers/dms-onboarding/app/custom-pages/onboarding.vue +4 -1
  116. package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +21 -4
  117. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +13 -3
  118. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +13 -3
  119. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +2 -7
  120. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/columnVisibility.ts +13 -0
  121. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  122. package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +1 -1
  123. package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +1 -1
  124. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +2 -2
  125. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +6 -16
  126. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +2 -2
  127. package/frontend-vue/layers/dms-ui/app/components/chart/internal/TrendBadge.vue +1 -1
  128. package/frontend-vue/layers/dms-ui/app/components/copy/CopyButton.vue +1 -1
  129. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +3 -3
  130. package/frontend-vue/layers/dms-ui/app/components/master-detail/MasterDetail.vue +5 -1
  131. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +1 -1
  132. package/frontend-vue/layers/dms-ui/app/components/status/StatusSummary.vue +1 -1
  133. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +33 -10
  134. package/frontend-vue/layers/dms-ui/app/composables/chart/donutRecords.ts +26 -0
  135. package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +4 -2
  136. package/frontend-vue/layers/dms-ui/app/composables/tab/useTab.ts +3 -3
  137. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/column.ts +1 -1
  138. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/custom-button.ts +2 -0
  139. package/frontend-vue/layers/dms-ui/app/types/quick-actions.ts +38 -3
  140. package/frontend-vue/tests/admin-invite-email.test.ts +86 -0
  141. package/frontend-vue/tests/breadcrumb-target.test.ts +50 -0
  142. package/frontend-vue/tests/chart-nested-donut.test.ts +97 -0
  143. package/frontend-vue/tests/error-page-message.test.ts +83 -0
  144. package/frontend-vue/tests/favorite-page.test.ts +104 -0
  145. package/frontend-vue/tests/icon-aria-hidden.test.ts +29 -0
  146. package/frontend-vue/tests/layout-banners.test.ts +224 -0
  147. package/frontend-vue/tests/notification-popover-errors.test.ts +147 -0
  148. package/frontend-vue/tests/onboarding-middleware.test.ts +103 -0
  149. package/frontend-vue/tests/password-strength-meter.test.ts +33 -0
  150. package/frontend-vue/tests/quick-action-intent.test.ts +74 -0
  151. package/frontend-vue/tests/quick-actions.test.ts +23 -0
  152. package/frontend-vue/tests/signup-invitation-link.test.ts +126 -0
  153. package/frontend-vue/tests/skeleton-paragraph.test.ts +30 -0
  154. package/frontend-vue/tests/tab-without-component-id.test.ts +51 -0
  155. package/frontend-vue/tests/table-column-visibility.test.ts +20 -0
  156. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  157. package/package.json +2 -2
  158. package/skills/dms/REFERENCE.md +1 -1
@@ -4,6 +4,5 @@ export * from "./export-jobs-run";
4
4
  export * from "./run-in-batches";
5
5
  export * from "./tenant-export-archive";
6
6
  export * from "@antelopejs/interface-dms/utils/row-instance";
7
- export * from "@antelopejs/interface-dms/utils/saas-mode";
8
7
  export * from "@antelopejs/interface-dms/utils/time";
9
8
  export * from "@antelopejs/interface-dms/utils/value-parser";
@@ -20,7 +20,6 @@ __exportStar(require("./export-jobs-run"), exports);
20
20
  __exportStar(require("./run-in-batches"), exports);
21
21
  __exportStar(require("./tenant-export-archive"), exports);
22
22
  __exportStar(require("@antelopejs/interface-dms/utils/row-instance"), exports);
23
- __exportStar(require("@antelopejs/interface-dms/utils/saas-mode"), exports);
24
23
  __exportStar(require("@antelopejs/interface-dms/utils/time"), exports);
25
24
  __exportStar(require("@antelopejs/interface-dms/utils/value-parser"), exports);
26
25
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,gDAA8B;AAC9B,oDAAkC;AAClC,mDAAiC;AACjC,0DAAwC;AACxC,+EAA6D;AAC7D,4EAA0D;AAC1D,uEAAqD;AACrD,+EAA6D"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,gDAA8B;AAC9B,oDAAkC;AAClC,mDAAiC;AACjC,0DAAwC;AACxC,+EAA6D;AAC7D,uEAAqD;AACrD,+EAA6D"}
@@ -4,20 +4,20 @@ export declare const authSchema: {
4
4
  name: z.ZodString;
5
5
  email: z.ZodString;
6
6
  password: z.ZodString;
7
- lang: z.ZodString;
7
+ lang: z.ZodOptional<z.ZodString>;
8
8
  token: z.ZodString;
9
9
  }, "strip", z.ZodTypeAny, {
10
10
  token: string;
11
11
  name: string;
12
12
  password: string;
13
13
  email: string;
14
- lang: string;
14
+ lang?: string | undefined;
15
15
  }, {
16
16
  token: string;
17
17
  name: string;
18
18
  password: string;
19
19
  email: string;
20
- lang: string;
20
+ lang?: string | undefined;
21
21
  }>;
22
22
  login: z.ZodObject<{
23
23
  email: z.ZodString;
@@ -44,7 +44,7 @@ exports.authSchema = {
44
44
  name: z.string(),
45
45
  email: z.string().email(),
46
46
  password: password_1.passwordSchema,
47
- lang: z.string(),
47
+ lang: z.string().optional(),
48
48
  token: z.string(),
49
49
  }),
50
50
  login: z.object({
@@ -1 +1 @@
1
- {"version":3,"file":"auth.schema.js","sourceRoot":"","sources":["../../src/validation/auth.schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.schema.js","sourceRoot":"","sources":["../../src/validation/auth.schema.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AAEzB,yCAA4C;AAE5C,MAAM,+BAA+B,GAAG,IAAI,CAAC;AAC7C,MAAM,uBAAuB,GAAG,EAAE,CAAC;AACnC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAEvB,QAAA,UAAU,GAAG;IACxB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,yBAAc;QACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC3B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;KAClB,CAAC;IACF,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KACnC,CAAC;IACF,aAAa,EAAE,CAAC,CAAC,MAAM,CAAC;QACtB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,+BAA+B,CAAC;QAC5D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,+BAA+B,CAAC;QAC7D,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,+BAA+B,CAAC;QACpE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,QAAQ,EAAE;QAC5D,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC,QAAQ,EAAE;KAC3D,CAAC;IACF,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC;QAChB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;KAClB,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;KAClB,CAAC;IACF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;KAClB,CAAC;IACF,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;KAC1B,CAAC;IACF,2BAA2B,EAAE,CAAC,CAAC,MAAM,CAAC;QACpC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;KAC1B,CAAC;IACF,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,yBAAc;KACzB,CAAC;IACF,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;QAClB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;KAC5C,CAAC;IACF,eAAe,EAAE,CAAC,CAAC,MAAM,CAAC;QACxB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;KAClB,CAAC;IACF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;KACjB,CAAC;IACF,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC;QACzB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;KACjB,CAAC;IACF,gBAAgB,EAAE,CAAC,CAAC,MAAM,CAAC;QACzB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;KACjB,CAAC;CACH,CAAC"}
@@ -4,7 +4,7 @@ import type { FormSubmitEvent } from "@nuxt/ui";
4
4
 
5
5
  const MIN_VALID_PASSWORD_SCORE = 4;
6
6
 
7
- const { locale } = useI18n();
7
+ const { locale, locales, setLocale } = useI18n();
8
8
  const config = useDmsRuntimeConfig();
9
9
  const homepage = useHomepage();
10
10
 
@@ -12,13 +12,20 @@ const route = useDmsRoute();
12
12
  const queryToken = computed(() => route.query.token as string);
13
13
  const queryEmail = computed(() => route.query.email as string);
14
14
  const queryName = computed(() => route.query.name as string | undefined);
15
+ // Only an invitation email links here, so a link without its token was cut
16
+ // short on the way: say so, rather than answering with a server error.
17
+ const hasInvitationToken = computed(
18
+ () => typeof queryToken.value === "string" && queryToken.value !== "",
19
+ );
15
20
 
16
- if (!queryToken.value) {
17
- throw createError({
18
- statusCode: HTTP_BAD_REQUEST,
19
- statusMessage: INVALID_REQUEST,
20
- message: "Missing required token parameter",
21
- });
21
+ // The invitation link carries the language the invitee was invited in: the
22
+ // page opens in it, and the footer switcher still lets them pick another one
23
+ // before the account is created with whichever is current.
24
+ const invitationLanguage = locales.value.find(
25
+ (available) => available.code === route.query.lang,
26
+ );
27
+ if (invitationLanguage && invitationLanguage.code !== locale.value) {
28
+ void setLocale(invitationLanguage.code);
22
29
  }
23
30
 
24
31
  const isLoading = ref(false);
@@ -66,7 +73,29 @@ async function onSubmit(payload: FormSubmitEvent<Schema>) {
66
73
  </script>
67
74
 
68
75
  <template>
69
- <div class="mx-auto max-w-xl">
76
+ <div v-if="!hasInvitationToken" class="mx-auto max-w-md">
77
+ <DmsCard
78
+ variant="elevated"
79
+ :padded="false"
80
+ class="p-6 sm:p-12"
81
+ data-testid="signup-invalid-invitation"
82
+ >
83
+ <UIcon
84
+ name="i-ph-link-break"
85
+ class="text-warning mb-4 size-10"
86
+ :aria-hidden="true"
87
+ />
88
+ <h1 class="pb-5 text-2xl font-bold">
89
+ {{ $t("page.signup.invalid_invitation_title") }}
90
+ </h1>
91
+ <p class="text-muted pb-7 text-sm font-normal">
92
+ {{ $t("page.signup.invalid_invitation_description") }}
93
+ </p>
94
+ <UButton :label="$t('button.login')" to="/auth" block />
95
+ </DmsCard>
96
+ </div>
97
+
98
+ <div v-else class="mx-auto max-w-xl">
70
99
  <DmsCard variant="elevated" :padded="false" class="p-6 sm:p-12">
71
100
  <h1 class="pb-11 text-xl font-bold sm:text-2xl">
72
101
  {{ $t("page.signup.create_account_title") }}
@@ -15,7 +15,9 @@
15
15
  },
16
16
  "signup": {
17
17
  "create_account_title": "Create your account",
18
- "error_title": "Account creation error"
18
+ "error_title": "Account creation error",
19
+ "invalid_invitation_title": "This invitation link is invalid or incomplete",
20
+ "invalid_invitation_description": "Open the link from your invitation email again, or ask the workspace owner to send you a new invitation. Already have an account? Log in."
19
21
  },
20
22
  "2fa": {
21
23
  "title": "Two-factor verification",
@@ -15,7 +15,9 @@
15
15
  },
16
16
  "signup": {
17
17
  "create_account_title": "Créer votre compte",
18
- "error_title": "Erreur de création de compte"
18
+ "error_title": "Erreur de création de compte",
19
+ "invalid_invitation_title": "Ce lien d'invitation est invalide ou incomplet",
20
+ "invalid_invitation_description": "Rouvrez le lien de votre e-mail d'invitation, ou demandez au propriétaire de l'espace de travail de vous envoyer une nouvelle invitation. Vous avez déjà un compte ? Connectez-vous."
19
21
  },
20
22
  "2fa": {
21
23
  "title": "Vérification à deux facteurs",
@@ -0,0 +1,50 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Stacks the global layout banners under the dashboard header, on every page
4
+ * of the dashboard layout. Each banner carries its own live-region role, so a
5
+ * banner appearing after a navigation is announced as well.
6
+ */
7
+ const { banners, dismiss } = useLayoutBanners();
8
+ const { processI18n } = useTranslation();
9
+
10
+ const entries = computed(() =>
11
+ banners.value.map((banner) => ({
12
+ banner,
13
+ presentation: resolveLayoutBannerPresentation(banner),
14
+ })),
15
+ );
16
+ </script>
17
+
18
+ <template>
19
+ <div
20
+ v-if="entries.length"
21
+ data-dms-layout-banners
22
+ class="border-default flex flex-col border-b"
23
+ >
24
+ <div
25
+ v-for="{ banner, presentation } in entries"
26
+ :key="banner.key"
27
+ :role="presentation.role"
28
+ :data-dms-layout-banner="banner.key"
29
+ >
30
+ <UAlert
31
+ variant="subtle"
32
+ orientation="horizontal"
33
+ :color="presentation.color"
34
+ :icon="presentation.icon"
35
+ :close="banner.dismissible"
36
+ class="rounded-none px-4 py-2.5 sm:px-6"
37
+ @update:open="dismiss(banner.key)"
38
+ >
39
+ <template #description>
40
+ <component
41
+ :is="banner.component"
42
+ v-if="banner.component"
43
+ v-bind="banner.props"
44
+ />
45
+ <template v-else>{{ processI18n(banner.text ?? "") }}</template>
46
+ </template>
47
+ </UAlert>
48
+ </div>
49
+ </div>
50
+ </template>
@@ -58,22 +58,21 @@ const { processI18n } = useTranslation();
58
58
  const favoritePages = useFavoritePages();
59
59
 
60
60
  const currentPageInfo = computed((): FavoritePage | null => {
61
- const currentPath = route.path;
62
- const matchedRoute = siteLayout.findMatchingRoute(currentPath);
61
+ const matchedRoute = siteLayout.findMatchingRoute(route.path);
63
62
 
64
63
  if (!matchedRoute) {
65
64
  return null;
66
65
  }
67
66
 
68
- return {
69
- id: matchedRoute.metadata.id || currentPath,
70
- path: currentPath,
71
- title:
72
- matchedRoute.metadata.displayName ||
73
- route.name?.toString() ||
74
- currentPath,
75
- icon: matchedRoute.metadata.icon,
76
- };
67
+ return buildFavoritePage({
68
+ path: route.path,
69
+ metadata: {
70
+ ...matchedRoute.metadata,
71
+ displayName: matchedRoute.metadata.displayName || route.name?.toString(),
72
+ },
73
+ query: route.query,
74
+ translate: processI18n,
75
+ });
77
76
  });
78
77
 
79
78
  const isCurrentPageFavorite = computed(() => {
@@ -112,11 +111,15 @@ const breadcrumb = computed((): BreadcrumbItem[] => {
112
111
  return null;
113
112
  }
114
113
 
115
- const isPage = "layoutUrl" in matchingRoute.metadata;
114
+ const target = buildBreadcrumbTarget(
115
+ to,
116
+ matchingRoute.metadata,
117
+ route.query,
118
+ );
116
119
 
117
120
  return {
118
121
  label: processI18n(matchingRoute.metadata?.displayName || path),
119
- ...(isPage ? { to } : {}),
122
+ ...(target ? { to: target } : {}),
120
123
  };
121
124
  })
122
125
  .filter((item) => item !== null),
@@ -42,8 +42,8 @@ if (!siteLayout.siteLayout.value) {
42
42
 
43
43
  const favoritePages = useFavoritePages();
44
44
 
45
- favoritePages.cleanupInvalidFavorites(
46
- (path) => siteLayout.findMatchingRoute(path) !== null,
45
+ favoritePages.cleanupInvalidFavorites((path) =>
46
+ isFavoritePathValid(path, siteLayout.findMatchingRoute),
47
47
  );
48
48
 
49
49
  const isOwner = useIsOwner();
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { formatRelativeTime } from "#dms-core/app/utils/formatter";
3
3
  import NotificationCard from "./NotificationCard.vue";
4
+ import { settleWidgetRequest } from "./widgetRequest";
4
5
 
5
6
  const MAX_DISPLAYED_COUNT = 99;
6
7
 
@@ -25,9 +26,14 @@ const { isLoadingMore, setupObserver, disconnectObserver } = useInfiniteScroll(
25
26
  hasMore,
26
27
  );
27
28
 
29
+ const refreshUnreadCount = () =>
30
+ settleWidgetRequest(fetchUnreadCount, () => {
31
+ unreadCount.value = 0;
32
+ });
33
+
28
34
  const handleNotificationEvent = async () => {
29
35
  if (!isOpen.value) {
30
- await fetchUnreadCount();
36
+ await refreshUnreadCount();
31
37
  }
32
38
  };
33
39
 
@@ -37,14 +43,14 @@ const handleFormSubmitSuccess = async (event: Event) => {
37
43
 
38
44
  if (submitUrl && submitUrl.includes("/api/notification/")) {
39
45
  if (!isOpen.value) {
40
- await fetchUnreadCount();
46
+ await refreshUnreadCount();
41
47
  }
42
48
  }
43
49
  };
44
50
 
45
51
  const markNotificationsAsRead = async () => {
46
52
  if (hasBeenOpened.value) {
47
- await markAllAsRead();
53
+ await settleWidgetRequest(markAllAsRead);
48
54
  hasBeenOpened.value = false;
49
55
  }
50
56
  };
@@ -57,7 +63,7 @@ const handleBeforeUnload = () => {
57
63
 
58
64
  onMounted(async () => {
59
65
  if (!loggedIn.value) return;
60
- await fetchUnreadCount();
66
+ await refreshUnreadCount();
61
67
 
62
68
  window.addEventListener(
63
69
  NotificationEvents.NOTIFICATION_RECEIVED,
@@ -87,7 +93,7 @@ onUnmounted(() => {
87
93
  watch(isOpen, async (isNowOpen) => {
88
94
  if (isNowOpen) {
89
95
  hasBeenOpened.value = true;
90
- await fetchNotifications(true);
96
+ await settleWidgetRequest(() => fetchNotifications(true));
91
97
  await nextTick();
92
98
  setupObserver();
93
99
  } else {
@@ -0,0 +1,20 @@
1
+ const WIDGET_REQUEST_FAILED_MESSAGE = "[dms] Header widget request failed";
2
+
3
+ /**
4
+ * Runs a request a layout widget issues on its own (on mount, on a realtime
5
+ * event, on open). A failure -- a tenant access gate answering 403, a network
6
+ * error -- is logged and degrades the widget through `onFailure` instead of
7
+ * reaching the app error handler, which would replace the whole page with an
8
+ * error screen.
9
+ */
10
+ export async function settleWidgetRequest(
11
+ request: () => Promise<unknown>,
12
+ onFailure?: () => void,
13
+ ): Promise<void> {
14
+ try {
15
+ await request();
16
+ } catch (error) {
17
+ onFailure?.();
18
+ console.warn(WIDGET_REQUEST_FAILED_MESSAGE, error);
19
+ }
20
+ }
@@ -0,0 +1,59 @@
1
+ const DISMISSED_BANNERS_COOKIE = "dms-dismissed-banners";
2
+
3
+ type LayoutBannerRole = "status" | "alert";
4
+
5
+ interface LayoutBannerPresentation {
6
+ role: LayoutBannerRole;
7
+ color: LayoutBannerVariant;
8
+ icon: string;
9
+ }
10
+
11
+ // An error interrupts the screen reader; info and warning wait their turn.
12
+ const PRESENTATIONS: Record<LayoutBannerVariant, LayoutBannerPresentation> = {
13
+ info: { role: "status", color: "info", icon: "i-ph-info-light" },
14
+ warning: { role: "status", color: "warning", icon: "i-ph-warning-light" },
15
+ error: { role: "alert", color: "error", icon: "i-ph-warning-octagon-light" },
16
+ };
17
+
18
+ /**
19
+ * Role, color and icon a banner renders with. An unknown variant — from a
20
+ * newer backend, or an untyped registration — renders as `info` rather than
21
+ * as an unstyled strip.
22
+ */
23
+ export const resolveLayoutBannerPresentation = (
24
+ banner: LayoutBanner,
25
+ ): LayoutBannerPresentation => {
26
+ const presentation = PRESENTATIONS[banner.variant] ?? PRESENTATIONS.info;
27
+ return { ...presentation, icon: banner.icon ?? presentation.icon };
28
+ };
29
+
30
+ /**
31
+ * The global banners rendered under the dashboard header, as resolved by the
32
+ * server for this request (see `RegisterLayoutBanner` in
33
+ * `@antelopejs/interface-dms/layout-banners`), minus the ones the user
34
+ * dismissed.
35
+ *
36
+ * Dismissals are kept in a session cookie rather than in local storage so the
37
+ * server-rendered page already leaves a dismissed banner out, instead of
38
+ * painting it and removing it on hydration.
39
+ */
40
+ export const useLayoutBanners = () => {
41
+ const { siteLayout } = useSiteLayout();
42
+ const dismissed = useDmsCookie<string[]>(DISMISSED_BANNERS_COOKIE, {
43
+ default: () => [],
44
+ sameSite: "lax",
45
+ });
46
+
47
+ const banners = computed<LayoutBanner[]>(() =>
48
+ (siteLayout.value?.banners ?? []).filter(
49
+ (banner) => !banner.dismissible || !dismissed.value.includes(banner.key),
50
+ ),
51
+ );
52
+
53
+ function dismiss(key: string): void {
54
+ if (dismissed.value.includes(key)) return;
55
+ dismissed.value = [...dismissed.value, key];
56
+ }
57
+
58
+ return { banners, dismiss };
59
+ };
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  QUICK_ACTION_ADD,
3
+ QUICK_ACTION_BUTTON,
4
+ QUICK_ACTION_BUTTON_KEY,
3
5
  QUICK_ACTION_COMPONENT_KEY,
4
6
  QUICK_ACTION_QUERY_KEY,
5
7
  } from "#dms-ui/app/types/quick-actions";
@@ -32,6 +34,17 @@ const targetHandlers: Record<
32
34
  },
33
35
  });
34
36
  },
37
+ button: (target) => {
38
+ if (target.type !== "button") return;
39
+ return navigateDms({
40
+ path: target.to,
41
+ query: {
42
+ [QUICK_ACTION_QUERY_KEY]: QUICK_ACTION_BUTTON,
43
+ [QUICK_ACTION_COMPONENT_KEY]: target.component,
44
+ [QUICK_ACTION_BUTTON_KEY]: target.button,
45
+ },
46
+ });
47
+ },
35
48
  event: (target) => {
36
49
  if (target.type !== "event") return;
37
50
  if (typeof window === "undefined") return;
@@ -43,8 +56,8 @@ const targetHandlers: Record<
43
56
 
44
57
  /**
45
58
  * Runs the client-side behavior a quick action declares through its
46
- * discriminated `target`: `navigate` and `openForm` route to the action's
47
- * page, `event` dispatches a window `CustomEvent` in place for whoever
59
+ * discriminated `target`: `navigate`, `openForm` and `button` route to the
60
+ * action's page, `event` dispatches a window `CustomEvent` in place for whoever
48
61
  * listens.
49
62
  */
50
63
  export function dispatchQuickActionTarget(target: QuickActionTarget): unknown {
@@ -3,6 +3,7 @@ import Container from "../build/components/layout/Container.vue";
3
3
  import PageHeader from "../build/components/layout/PageHeader.vue";
4
4
  import DashboardSidebar from "../build/components/layout/DashboardSidebar.vue";
5
5
  import DashboardHeader from "../build/components/layout/DashboardHeader.vue";
6
+ import DashboardBanners from "../build/components/layout/DashboardBanners.vue";
6
7
 
7
8
  interface Props {
8
9
  fullWidth?: boolean;
@@ -28,6 +29,7 @@ const props = withDefaults(defineProps<Props>(), {
28
29
  <UDashboardPanel>
29
30
  <template #header>
30
31
  <DashboardHeader />
32
+ <DashboardBanners />
31
33
  </template>
32
34
 
33
35
  <template #body>
@@ -1,31 +1,76 @@
1
1
  <script setup lang="ts">
2
+ import { computed } from "vue";
3
+ import { useI18n } from "vue-i18n";
2
4
  import EmailLayout from "./components/EmailLayout.vue";
3
5
  import EmailButton from "./components/EmailButton.vue";
4
6
  import { emailStyles } from "./components/index";
5
7
 
6
- defineProps<{
8
+ const props = defineProps<{
7
9
  email: string;
8
10
  signupLink: string;
9
- expiresIn: string;
11
+ expiresInDays: number;
12
+ workspaceName?: string;
13
+ inviterName?: string;
14
+ platformName?: string;
10
15
  }>();
16
+
17
+ const { t, locale } = useI18n();
18
+
19
+ const joinTarget = computed(() => {
20
+ const { workspaceName, platformName } = props;
21
+ if (workspaceName && platformName) {
22
+ return t("emails.admin_invite.workspace_on_platform", {
23
+ workspace: workspaceName,
24
+ platform: platformName,
25
+ });
26
+ }
27
+ return workspaceName || platformName;
28
+ });
29
+
30
+ const title = computed(() =>
31
+ props.workspaceName
32
+ ? t("emails.admin_invite.title_workspace", {
33
+ workspace: props.workspaceName,
34
+ })
35
+ : t("emails.admin_invite.title"),
36
+ );
37
+
38
+ const invitation = computed(() => {
39
+ if (!joinTarget.value) {
40
+ return t("emails.admin_invite.invitation");
41
+ }
42
+ return props.inviterName
43
+ ? t("emails.admin_invite.invitation_from_inviter", {
44
+ inviter: props.inviterName,
45
+ target: joinTarget.value,
46
+ })
47
+ : t("emails.admin_invite.invitation_to_target", {
48
+ target: joinTarget.value,
49
+ });
50
+ });
11
51
  </script>
12
52
 
13
53
  <template>
14
- <EmailLayout title="You're Invited!">
15
- <EText :style="emailStyles.text">Hello,</EText>
54
+ <EmailLayout :title="title" :lang="locale">
55
+ <EText :style="emailStyles.text">
56
+ {{ t("emails.admin_invite.greeting") }}
57
+ </EText>
16
58
 
17
59
  <EText :style="emailStyles.text">
18
- You've been invited to join our platform. Click the button below to create
19
- your account:
60
+ {{ invitation }} {{ t("emails.admin_invite.call_to_action") }}
20
61
  </EText>
21
62
 
22
- <EmailButton :href="signupLink" :expires-in="expiresIn">
23
- Create Account
63
+ <EmailButton
64
+ :href="signupLink"
65
+ :expires-in="t('emails.admin_invite.expires_in', { days: expiresInDays })"
66
+ :expires-label="t('emails.admin_invite.expires_label')"
67
+ >
68
+ {{ t("emails.admin_invite.button") }}
24
69
  </EmailButton>
25
70
 
26
71
  <template #footer>
27
72
  <EText :style="emailStyles.footer">
28
- If you didn't expect this invitation, you can safely ignore this email.
73
+ {{ t("emails.admin_invite.footer") }}
29
74
  </EText>
30
75
  </template>
31
76
  </EmailLayout>
@@ -34,6 +34,13 @@ const DEFAULT_ERROR_CONFIG = {
34
34
 
35
35
  const DEFAULT_STATUS_CODE = 500;
36
36
 
37
+ // Their cards already say what happened; the message behind them is the
38
+ // technical cause ("DMS backend request failed"), which the server has logged.
39
+ const STATUSES_WITHOUT_ERROR_MESSAGE = new Set([
40
+ HTTP_NOT_FOUND,
41
+ HTTP_FORBIDDEN,
42
+ ]);
43
+
37
44
  const props = defineProps<{
38
45
  error: DmsErrorData;
39
46
  }>();
@@ -45,6 +52,14 @@ const { loggedIn, reconcileSession, redirectToAuth } = useSessionRecovery();
45
52
 
46
53
  const isRecovering = ref(false);
47
54
 
55
+ const visibleErrorMessage = computed(() =>
56
+ STATUSES_WITHOUT_ERROR_MESSAGE.has(
57
+ props.error.statusCode || DEFAULT_STATUS_CODE,
58
+ )
59
+ ? undefined
60
+ : props.error.message,
61
+ );
62
+
48
63
  const errorConfig = computed(() => {
49
64
  const statusCode = props.error.statusCode || DEFAULT_STATUS_CODE;
50
65
  const config = ERROR_CONFIGS[statusCode] || DEFAULT_ERROR_CONFIG;
@@ -121,10 +136,10 @@ onMounted(() => {
121
136
  </p>
122
137
 
123
138
  <p
124
- v-if="!isRecovering && error.message"
139
+ v-if="!isRecovering && visibleErrorMessage"
125
140
  class="text-dimmed mb-6 text-sm"
126
141
  >
127
- {{ error.message }}
142
+ {{ visibleErrorMessage }}
128
143
  </p>
129
144
 
130
145
  <div v-if="!isRecovering" class="flex gap-3">
@@ -65,9 +65,30 @@ export type SiteLayoutTree = Omit<PageInfo, "layoutUrl"> & {
65
65
  layoutUrl?: string;
66
66
  hasAccess?: boolean;
67
67
  };
68
+ // Mirror of LayoutBannerVariant from @antelopejs/interface-dms/layout-banners.
69
+ export type LayoutBannerVariant = "info" | "warning" | "error";
70
+
71
+ /**
72
+ * Mirror of the backend's serialized layout banner. The payload only carries
73
+ * the banners whose visibility resolver passed for this request, so there is
74
+ * nothing left to decide here but the user's own dismissals.
75
+ */
76
+ export interface LayoutBanner {
77
+ key: string;
78
+ variant: LayoutBannerVariant;
79
+ order: number;
80
+ dismissible: boolean;
81
+ icon?: string;
82
+ text?: string;
83
+ component?: string;
84
+ props?: Record<string, unknown>;
85
+ }
86
+
68
87
  export type SiteLayout = {
69
88
  pages: Record<string, PageInfo>;
70
89
  categories: Record<string, CategoryInfo>;
90
+ /** Absent when the backend predates layout banners. */
91
+ banners?: LayoutBanner[];
71
92
  };
72
93
 
73
94
  export interface QuickActionCategoryInfo {
@@ -80,6 +101,7 @@ export interface QuickActionCategoryInfo {
80
101
  export type QuickActionTarget =
81
102
  | { type: "navigate"; to: string; query?: Record<string, string> }
82
103
  | { type: "openForm"; to: string; component: string }
104
+ | { type: "button"; to: string; component: string; button: string }
83
105
  | { type: "event"; name: string; payload?: unknown };
84
106
 
85
107
  /**