@antelopejs/dms 0.4.4 → 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 (205) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/config.d.ts +1 -0
  3. package/dist/config.js.map +1 -1
  4. package/dist/hooks/invite-extensions.js +2 -2
  5. package/dist/hooks/invite-extensions.js.map +1 -1
  6. package/dist/implementations/dms/layout-banners.d.ts +18 -0
  7. package/dist/implementations/dms/layout-banners.js +60 -0
  8. package/dist/implementations/dms/layout-banners.js.map +1 -0
  9. package/dist/implementations/dms/page.d.ts +3 -0
  10. package/dist/implementations/dms/page.js +90 -28
  11. package/dist/implementations/dms/page.js.map +1 -1
  12. package/dist/implementations/dms/quick-actions.d.ts +5 -0
  13. package/dist/implementations/dms/quick-actions.js.map +1 -1
  14. package/dist/implementations/dms/resolver-timeout.d.ts +1 -0
  15. package/dist/implementations/dms/resolver-timeout.js +13 -0
  16. package/dist/implementations/dms/resolver-timeout.js.map +1 -0
  17. package/dist/implementations/dms/saas-mode.d.ts +8 -0
  18. package/dist/implementations/dms/saas-mode.js +20 -0
  19. package/dist/implementations/dms/saas-mode.js.map +1 -0
  20. package/dist/implementations/dms/stale-session.d.ts +2 -0
  21. package/dist/implementations/dms/stale-session.js +16 -0
  22. package/dist/implementations/dms/stale-session.js.map +1 -0
  23. package/dist/implementations/dms-auth/index.d.ts +3 -1
  24. package/dist/implementations/dms-auth/index.js +38 -8
  25. package/dist/implementations/dms-auth/index.js.map +1 -1
  26. package/dist/implementations/dms-html-render/service-jwt.js +2 -1
  27. package/dist/implementations/dms-html-render/service-jwt.js.map +1 -1
  28. package/dist/index.js +2 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/pages/settings/users/index.d.ts +1 -0
  31. package/dist/pages/settings/users/index.js +1 -0
  32. package/dist/pages/settings/users/index.js.map +1 -1
  33. package/dist/pages/settings/users/invite-extension-routes.d.ts +4 -0
  34. package/dist/pages/settings/users/invite-extension-routes.js +57 -0
  35. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -0
  36. package/dist/pages/settings/users/invites.d.ts +2 -1
  37. package/dist/pages/settings/users/invites.js +17 -5
  38. package/dist/pages/settings/users/invites.js.map +1 -1
  39. package/dist/pages/settings/users/members.d.ts +4 -1
  40. package/dist/pages/settings/users/members.js +10 -6
  41. package/dist/pages/settings/users/members.js.map +1 -1
  42. package/dist/pages/settings/users/notifications.js +1 -1
  43. package/dist/pages/settings/users/notifications.js.map +1 -1
  44. package/dist/pages/settings/users/quick-actions.d.ts +1 -0
  45. package/dist/pages/settings/users/quick-actions.js +22 -0
  46. package/dist/pages/settings/users/quick-actions.js.map +1 -0
  47. package/dist/routes/auth/client-ip.d.ts +7 -0
  48. package/dist/routes/auth/client-ip.js +42 -0
  49. package/dist/routes/auth/client-ip.js.map +1 -0
  50. package/dist/routes/auth/index.d.ts +1 -0
  51. package/dist/routes/auth/index.js +1 -0
  52. package/dist/routes/auth/index.js.map +1 -1
  53. package/dist/routes/auth/signup.d.ts +2 -0
  54. package/dist/routes/auth/signup.js +7 -2
  55. package/dist/routes/auth/signup.js.map +1 -1
  56. package/dist/routes/auth.d.ts +3 -0
  57. package/dist/routes/auth.js +15 -4
  58. package/dist/routes/auth.js.map +1 -1
  59. package/dist/test/integration/auth/client-ip.test.d.ts +1 -0
  60. package/dist/test/integration/auth/client-ip.test.js +14 -0
  61. package/dist/test/integration/auth/client-ip.test.js.map +1 -0
  62. package/dist/test/integration/invite-extension-edit.test.d.ts +1 -0
  63. package/dist/test/integration/invite-extension-edit.test.js +122 -0
  64. package/dist/test/integration/invite-extension-edit.test.js.map +1 -0
  65. package/dist/test/integration/page-session.test.d.ts +1 -0
  66. package/dist/test/integration/page-session.test.js +52 -0
  67. package/dist/test/integration/page-session.test.js.map +1 -0
  68. package/dist/test/integration/saas-mode-ownership.test.d.ts +1 -0
  69. package/dist/test/integration/saas-mode-ownership.test.js +81 -0
  70. package/dist/test/integration/saas-mode-ownership.test.js.map +1 -0
  71. package/dist/test/integration/tenant-lifecycle.test.js +38 -0
  72. package/dist/test/integration/tenant-lifecycle.test.js.map +1 -1
  73. package/dist/test/unit/client-ip.test.d.ts +1 -0
  74. package/dist/test/unit/client-ip.test.js +77 -0
  75. package/dist/test/unit/client-ip.test.js.map +1 -0
  76. package/dist/test/unit/implementations/dms/layout-banners.test.d.ts +1 -0
  77. package/dist/test/unit/implementations/dms/layout-banners.test.js +217 -0
  78. package/dist/test/unit/implementations/dms/layout-banners.test.js.map +1 -0
  79. package/dist/test/unit/implementations/dms/quick-actions-button.test.d.ts +1 -0
  80. package/dist/test/unit/implementations/dms/quick-actions-button.test.js +220 -0
  81. package/dist/test/unit/implementations/dms/quick-actions-button.test.js.map +1 -0
  82. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js +1 -1
  83. package/dist/test/unit/implementations/dms-html-render/generate-html.test.js.map +1 -1
  84. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.d.ts +1 -0
  85. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js +37 -0
  86. package/dist/test/unit/implementations/dms-html-render/service-jwt.test.js.map +1 -0
  87. package/dist/test/unit/interfaces/dms/invite-extension.test.js +138 -12
  88. package/dist/test/unit/interfaces/dms/invite-extension.test.js.map +1 -1
  89. package/dist/test/unit/interfaces/dms/saas-mode.test.d.ts +1 -0
  90. package/dist/test/unit/interfaces/dms/saas-mode.test.js +42 -0
  91. package/dist/test/unit/interfaces/dms/saas-mode.test.js.map +1 -0
  92. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.d.ts +1 -0
  93. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js +53 -0
  94. package/dist/test/unit/interfaces/dms/tenant-invite-email.test.js.map +1 -0
  95. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.d.ts +1 -0
  96. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js +80 -0
  97. package/dist/test/unit/interfaces/dms-base/table-view-form-slots.test.js.map +1 -0
  98. package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.d.ts +1 -0
  99. package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.js +96 -0
  100. package/dist/test/unit/interfaces/dms-base/table-view-tab-count-warning.test.js.map +1 -0
  101. package/dist/test/unit/pages/settings/invite-member-quick-action.test.d.ts +1 -0
  102. package/dist/test/unit/pages/settings/invite-member-quick-action.test.js +104 -0
  103. package/dist/test/unit/pages/settings/invite-member-quick-action.test.js.map +1 -0
  104. package/dist/test/unit/pages/settings/notifications-access-gate.test.d.ts +1 -0
  105. package/dist/test/unit/pages/settings/notifications-access-gate.test.js +78 -0
  106. package/dist/test/unit/pages/settings/notifications-access-gate.test.js.map +1 -0
  107. package/dist/test/unit/signup-account-guard.test.js +20 -2
  108. package/dist/test/unit/signup-account-guard.test.js.map +1 -1
  109. package/dist/test/unit/utils/admin-invite-email.test.d.ts +1 -0
  110. package/dist/test/unit/utils/admin-invite-email.test.js +69 -0
  111. package/dist/test/unit/utils/admin-invite-email.test.js.map +1 -0
  112. package/dist/utils/admin-invite-email.d.ts +14 -0
  113. package/dist/utils/admin-invite-email.js +51 -0
  114. package/dist/utils/admin-invite-email.js.map +1 -0
  115. package/dist/utils/index.d.ts +0 -1
  116. package/dist/utils/index.js +0 -1
  117. package/dist/utils/index.js.map +1 -1
  118. package/dist/validation/auth.schema.d.ts +3 -3
  119. package/dist/validation/auth.schema.js +1 -1
  120. package/dist/validation/auth.schema.js.map +1 -1
  121. package/frontend-vue/layers/dms-auth/app/custom-pages/auth/signup.vue +37 -8
  122. package/frontend-vue/layers/dms-auth/app/middleware/auth.ts +5 -2
  123. package/frontend-vue/layers/dms-auth/i18n/locales/auth-en-GB.json +3 -1
  124. package/frontend-vue/layers/dms-auth/i18n/locales/auth-fr-FR.json +3 -1
  125. package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +27 -2
  126. package/frontend-vue/layers/dms-core/app/utils/menu.ts +26 -10
  127. package/frontend-vue/layers/dms-core/app/utils/routePath.ts +23 -0
  128. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +3 -0
  129. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +3 -0
  130. package/frontend-vue/layers/dms-core/shared/types/runtime-config.d.ts +4 -1
  131. package/frontend-vue/layers/dms-layout/app/app.config.ts +9 -1
  132. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +50 -0
  133. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +16 -13
  134. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +2 -2
  135. package/frontend-vue/layers/dms-layout/app/build/components/notification/NotificationPopover.vue +11 -5
  136. package/frontend-vue/layers/dms-layout/app/build/components/notification/widgetRequest.ts +20 -0
  137. package/frontend-vue/layers/dms-layout/app/components/navigation/CollapsedMenuItem.vue +0 -5
  138. package/frontend-vue/layers/dms-layout/app/components/profile/ProfileLanguage.vue +3 -23
  139. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +59 -0
  140. package/frontend-vue/layers/dms-layout/app/composables/page/useQuickActions.ts +15 -2
  141. package/frontend-vue/layers/dms-layout/app/composables/profile/useUserLanguage.ts +33 -0
  142. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +2 -0
  143. package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +54 -9
  144. package/frontend-vue/layers/dms-layout/app/error.vue +17 -2
  145. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +2 -1
  146. package/frontend-vue/layers/dms-layout/app/types/page.ts +22 -0
  147. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +48 -0
  148. package/frontend-vue/layers/dms-layout/app/utils/favorites.ts +94 -0
  149. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +21 -2
  150. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +21 -2
  151. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/index.ts +1 -1
  152. package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/{useOnboardingMiddleware.ts → useOnboarding.ts} +0 -12
  153. package/frontend-vue/layers/dms-onboarding/app/custom-pages/onboarding.vue +4 -1
  154. package/frontend-vue/layers/dms-onboarding/app/middleware/onboarding.global.ts +21 -4
  155. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +13 -3
  156. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +13 -3
  157. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +31 -3
  158. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationBadges.ts +36 -0
  159. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +2 -7
  160. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/columnVisibility.ts +13 -0
  161. package/frontend-vue/layers/dms-ui/app/build/types/tree.ts +5 -10
  162. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  163. package/frontend-vue/layers/dms-ui/app/components/activity/ActivityItem.vue +1 -1
  164. package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +1 -1
  165. package/frontend-vue/layers/dms-ui/app/components/card/NavCard.vue +2 -2
  166. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +6 -16
  167. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +2 -2
  168. package/frontend-vue/layers/dms-ui/app/components/chart/internal/TrendBadge.vue +1 -1
  169. package/frontend-vue/layers/dms-ui/app/components/copy/CopyButton.vue +1 -1
  170. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +3 -3
  171. package/frontend-vue/layers/dms-ui/app/components/master-detail/MasterDetail.vue +5 -1
  172. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +1 -1
  173. package/frontend-vue/layers/dms-ui/app/components/status/StatusSummary.vue +1 -1
  174. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +33 -10
  175. package/frontend-vue/layers/dms-ui/app/composables/chart/donutRecords.ts +26 -0
  176. package/frontend-vue/layers/dms-ui/app/composables/tab/types/props.ts +4 -2
  177. package/frontend-vue/layers/dms-ui/app/composables/tab/useTab.ts +3 -3
  178. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/column.ts +1 -1
  179. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/custom-button.ts +2 -0
  180. package/frontend-vue/layers/dms-ui/app/types/quick-actions.ts +38 -3
  181. package/frontend-vue/tests/admin-invite-email.test.ts +86 -0
  182. package/frontend-vue/tests/api-error-title.test.ts +24 -0
  183. package/frontend-vue/tests/auth-middleware-error.test.ts +58 -0
  184. package/frontend-vue/tests/breadcrumb-target.test.ts +50 -0
  185. package/frontend-vue/tests/button-disabled-theme.test.ts +21 -0
  186. package/frontend-vue/tests/chart-nested-donut.test.ts +97 -0
  187. package/frontend-vue/tests/error-page-message.test.ts +83 -0
  188. package/frontend-vue/tests/favorite-page.test.ts +104 -0
  189. package/frontend-vue/tests/icon-aria-hidden.test.ts +29 -0
  190. package/frontend-vue/tests/layout-banners.test.ts +224 -0
  191. package/frontend-vue/tests/menu-navigation.test.ts +39 -10
  192. package/frontend-vue/tests/notification-popover-errors.test.ts +147 -0
  193. package/frontend-vue/tests/onboarding-middleware.test.ts +103 -0
  194. package/frontend-vue/tests/password-strength-meter.test.ts +33 -0
  195. package/frontend-vue/tests/quick-action-intent.test.ts +74 -0
  196. package/frontend-vue/tests/quick-actions.test.ts +23 -0
  197. package/frontend-vue/tests/relation-badges.test.ts +32 -0
  198. package/frontend-vue/tests/signup-invitation-link.test.ts +126 -0
  199. package/frontend-vue/tests/skeleton-paragraph.test.ts +30 -0
  200. package/frontend-vue/tests/tab-without-component-id.test.ts +51 -0
  201. package/frontend-vue/tests/table-column-visibility.test.ts +20 -0
  202. package/frontend-vue/tests/user-language.test.ts +58 -0
  203. package/frontend-vue/tests/vue-inventory.test.ts +1 -1
  204. package/package.json +2 -2
  205. package/skills/dms/REFERENCE.md +1 -1
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const chai_1 = require("chai");
4
+ const admin_invite_email_1 = require("../../../utils/admin-invite-email");
5
+ describe("[unit] utils/admin-invite-email", () => {
6
+ describe("resolveAdminInviteLanguage", () => {
7
+ it("keeps a supported language, regional variants included", () => {
8
+ (0, chai_1.expect)((0, admin_invite_email_1.resolveAdminInviteLanguage)("fr")).to.equal("fr");
9
+ (0, chai_1.expect)((0, admin_invite_email_1.resolveAdminInviteLanguage)("fr-FR")).to.equal("fr");
10
+ (0, chai_1.expect)((0, admin_invite_email_1.resolveAdminInviteLanguage)("en-GB")).to.equal("en");
11
+ });
12
+ it("falls back to English for a missing or unsupported language", () => {
13
+ (0, chai_1.expect)((0, admin_invite_email_1.resolveAdminInviteLanguage)()).to.equal("en");
14
+ (0, chai_1.expect)((0, admin_invite_email_1.resolveAdminInviteLanguage)("de")).to.equal("en");
15
+ (0, chai_1.expect)((0, admin_invite_email_1.resolveAdminInviteLanguage)("constructor")).to.equal("en");
16
+ });
17
+ });
18
+ describe("buildAdminInviteSubject", () => {
19
+ it("keeps the generic subject without any name", () => {
20
+ (0, chai_1.expect)((0, admin_invite_email_1.buildAdminInviteSubject)({})).to.equal("You're Invited to Join");
21
+ (0, chai_1.expect)((0, admin_invite_email_1.buildAdminInviteSubject)({ inviterName: "Ada" })).to.equal("You're Invited to Join");
22
+ });
23
+ it("names the platform alone", () => {
24
+ (0, chai_1.expect)((0, admin_invite_email_1.buildAdminInviteSubject)({ platformName: "Acme" })).to.equal("You're invited to join Acme");
25
+ });
26
+ it("names the workspace, the platform and the inviter", () => {
27
+ (0, chai_1.expect)((0, admin_invite_email_1.buildAdminInviteSubject)({
28
+ workspaceName: "Storefront",
29
+ platformName: "Acme",
30
+ inviterName: "Ada",
31
+ })).to.equal("Ada invited you to join Storefront on Acme");
32
+ (0, chai_1.expect)((0, admin_invite_email_1.buildAdminInviteSubject)({ workspaceName: "Storefront" })).to.equal("You're invited to join Storefront");
33
+ });
34
+ it("writes the subject in French", () => {
35
+ (0, chai_1.expect)((0, admin_invite_email_1.buildAdminInviteSubject)({}, "fr")).to.equal("Vous êtes invité à nous rejoindre");
36
+ (0, chai_1.expect)((0, admin_invite_email_1.buildAdminInviteSubject)({
37
+ workspaceName: "Storefront",
38
+ platformName: "Acme",
39
+ inviterName: "Ada",
40
+ }, "fr-FR")).to.equal("Ada vous invite à rejoindre Storefront sur Acme");
41
+ });
42
+ });
43
+ describe("buildAdminInviteSignupLink", () => {
44
+ const BASE_URL = "https://app.local";
45
+ it("carries the token, the address and the invitation's language", () => {
46
+ const link = new URL((0, admin_invite_email_1.buildAdminInviteSignupLink)(BASE_URL, {
47
+ email: "ada+team@acme.dev",
48
+ token: "t0ken",
49
+ inviteeName: "Ada Lovelace",
50
+ language: "fr",
51
+ }));
52
+ (0, chai_1.expect)(link.origin + link.pathname).to.equal(`${BASE_URL}/auth/signup`);
53
+ (0, chai_1.expect)(Object.fromEntries(link.searchParams)).to.deep.equal({
54
+ token: "t0ken",
55
+ email: "ada+team@acme.dev",
56
+ name: "Ada Lovelace",
57
+ lang: "fr",
58
+ });
59
+ });
60
+ it("leaves out the name and the language when unknown", () => {
61
+ const link = new URL((0, admin_invite_email_1.buildAdminInviteSignupLink)(BASE_URL, {
62
+ email: "ada@acme.dev",
63
+ token: "t0ken",
64
+ }));
65
+ (0, chai_1.expect)([...link.searchParams.keys()]).to.deep.equal(["token", "email"]);
66
+ });
67
+ });
68
+ });
69
+ //# sourceMappingURL=admin-invite-email.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-invite-email.test.js","sourceRoot":"","sources":["../../../../src/test/unit/utils/admin-invite-email.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface AdminInviteEmailNames {
2
+ workspaceName?: string;
3
+ inviterName?: string;
4
+ platformName?: string;
5
+ }
6
+ export declare function resolveAdminInviteLanguage(language?: string): string;
7
+ export declare function buildAdminInviteSubject(names: AdminInviteEmailNames, language?: string): string;
8
+ export interface AdminInviteSignupTarget {
9
+ email: string;
10
+ token: string;
11
+ inviteeName?: string;
12
+ language?: string;
13
+ }
14
+ export declare function buildAdminInviteSignupLink(clientBaseUrl: string, { email, token, inviteeName, language }: AdminInviteSignupTarget): string;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAdminInviteLanguage = resolveAdminInviteLanguage;
4
+ exports.buildAdminInviteSubject = buildAdminInviteSubject;
5
+ exports.buildAdminInviteSignupLink = buildAdminInviteSignupLink;
6
+ const DEFAULT_LANGUAGE = "en";
7
+ const LANGUAGE_CODE_LENGTH = 2;
8
+ const SUBJECT_COPY = {
9
+ en: {
10
+ generic: "You're Invited to Join",
11
+ join: (target) => `You're invited to join ${target}`,
12
+ joinFromInviter: (inviter, target) => `${inviter} invited you to join ${target}`,
13
+ workspaceOnPlatform: (workspace, platform) => `${workspace} on ${platform}`,
14
+ },
15
+ fr: {
16
+ generic: "Vous êtes invité à nous rejoindre",
17
+ join: (target) => `Vous êtes invité à rejoindre ${target}`,
18
+ joinFromInviter: (inviter, target) => `${inviter} vous invite à rejoindre ${target}`,
19
+ workspaceOnPlatform: (workspace, platform) => `${workspace} sur ${platform}`,
20
+ },
21
+ };
22
+ function resolveAdminInviteLanguage(language) {
23
+ const code = language?.slice(0, LANGUAGE_CODE_LENGTH).toLowerCase();
24
+ return code && Object.hasOwn(SUBJECT_COPY, code) ? code : DEFAULT_LANGUAGE;
25
+ }
26
+ function joinTarget(copy, names) {
27
+ const { workspaceName, platformName } = names;
28
+ if (workspaceName && platformName) {
29
+ return copy.workspaceOnPlatform(workspaceName, platformName);
30
+ }
31
+ return workspaceName || platformName;
32
+ }
33
+ function buildAdminInviteSubject(names, language) {
34
+ const copy = SUBJECT_COPY[resolveAdminInviteLanguage(language)];
35
+ const target = joinTarget(copy, names);
36
+ if (!target) {
37
+ return copy.generic;
38
+ }
39
+ return names.inviterName
40
+ ? copy.joinFromInviter(names.inviterName, target)
41
+ : copy.join(target);
42
+ }
43
+ function buildAdminInviteSignupLink(clientBaseUrl, { email, token, inviteeName, language }) {
44
+ const query = new URLSearchParams({ token, email });
45
+ if (inviteeName)
46
+ query.set("name", inviteeName);
47
+ if (language)
48
+ query.set("lang", language);
49
+ return `${clientBaseUrl}/auth/signup?${query.toString()}`;
50
+ }
51
+ //# sourceMappingURL=admin-invite-email.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-invite-email.js","sourceRoot":"","sources":["../../src/utils/admin-invite-email.ts"],"names":[],"mappings":";;AAyCA,gEAGC;AAiBD,0DAYC;AAcD,gEAQC;AA/FD,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAkB/B,MAAM,YAAY,GAA2C;IAC3D,EAAE,EAAE;QACF,OAAO,EAAE,wBAAwB;QACjC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,0BAA0B,MAAM,EAAE;QACpD,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CACnC,GAAG,OAAO,wBAAwB,MAAM,EAAE;QAC5C,mBAAmB,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,GAAG,SAAS,OAAO,QAAQ,EAAE;KAC5E;IACD,EAAE,EAAE;QACF,OAAO,EAAE,mCAAmC;QAC5C,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,gCAAgC,MAAM,EAAE;QAC1D,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CACnC,GAAG,OAAO,4BAA4B,MAAM,EAAE;QAChD,mBAAmB,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,EAAE,CAC3C,GAAG,SAAS,QAAQ,QAAQ,EAAE;KACjC;CACF,CAAC;AAMF,SAAgB,0BAA0B,CAAC,QAAiB;IAC1D,MAAM,IAAI,GAAG,QAAQ,EAAE,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,CAAC,WAAW,EAAE,CAAC;IACpE,OAAO,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC;AAC7E,CAAC;AAED,SAAS,UAAU,CACjB,IAA4B,EAC5B,KAA4B;IAE5B,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;IAC9C,IAAI,aAAa,IAAI,YAAY,EAAE,CAAC;QAClC,OAAO,IAAI,CAAC,mBAAmB,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC/D,CAAC;IACD,OAAO,aAAa,IAAI,YAAY,CAAC;AACvC,CAAC;AAMD,SAAgB,uBAAuB,CACrC,KAA4B,EAC5B,QAAiB;IAEjB,MAAM,IAAI,GAAG,YAAY,CAAC,0BAA0B,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IACD,OAAO,KAAK,CAAC,WAAW;QACtB,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,EAAE,MAAM,CAAC;QACjD,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC;AAcD,SAAgB,0BAA0B,CACxC,aAAqB,EACrB,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAA2B;IAEhE,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,IAAI,WAAW;QAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAChD,IAAI,QAAQ;QAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,OAAO,GAAG,aAAa,gBAAgB,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;AAC5D,CAAC"}
@@ -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":"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"}
@@ -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") }}
@@ -72,8 +72,11 @@ async function isPublicAccessRoute(to: ReturnType<typeof useDmsRoute>) {
72
72
  export default defineDmsMiddleware(async (to) => {
73
73
  if (to.matched.length === 0) return;
74
74
 
75
- const error = useError();
76
- if (error.value) throw showError(error.value);
75
+ // An error the page already raised (a failed `validation`, a missing page)
76
+ // is committed and rendered with its own status. Re-throwing it here would
77
+ // reject the pending navigation the SSR render awaits, turning that 404 into
78
+ // a 500 fallback render.
79
+ if (useError().value) return abortNavigation();
77
80
 
78
81
  const options = to.meta.auth as MiddlewareMeta | undefined;
79
82
  if (!options) return;
@@ -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",
@@ -6,12 +6,16 @@ interface Options {
6
6
 
7
7
  interface ApiError {
8
8
  statusCode?: number;
9
+ status?: number;
9
10
  message?: string;
10
11
  data?: unknown;
11
12
  }
12
13
 
13
- const DEFAULT_TITLE_KEY = "error.500.title";
14
+ const SERVER_ERROR_TITLE_KEY = "error.500.title";
15
+ const CLIENT_ERROR_TITLE_KEY = "error.request_refused.title";
14
16
  const FALLBACK_ERROR_KEY = "error.500.description";
17
+ const CLIENT_ERROR_STATUS_MIN = 400;
18
+ const CLIENT_ERROR_STATUS_MAX = 499;
15
19
 
16
20
  function isApiError(error: unknown): error is ApiError {
17
21
  return typeof error === "object" && error !== null;
@@ -32,6 +36,27 @@ function resolveDescription(error: ApiError): string {
32
36
  return FALLBACK_ERROR_KEY;
33
37
  }
34
38
 
39
+ function isClientErrorStatus(status: number | undefined): boolean {
40
+ return (
41
+ status !== undefined &&
42
+ status >= CLIENT_ERROR_STATUS_MIN &&
43
+ status <= CLIENT_ERROR_STATUS_MAX
44
+ );
45
+ }
46
+
47
+ /**
48
+ * The i18n title key matching a fetch error's HTTP status: a 4xx is a request
49
+ * the backend refused, anything else (5xx, network failure) a server error.
50
+ */
51
+ export function resolveApiErrorTitle(error: unknown): string {
52
+ const status = isApiError(error)
53
+ ? (error.statusCode ?? error.status)
54
+ : undefined;
55
+ return isClientErrorStatus(status)
56
+ ? CLIENT_ERROR_TITLE_KEY
57
+ : SERVER_ERROR_TITLE_KEY;
58
+ }
59
+
35
60
  /**
36
61
  * Extract the backend's error body from a fetch error — an i18n message key
37
62
  * per the API error contract — falling back to the generic 500 key.
@@ -46,7 +71,7 @@ export function useApiError(error: unknown, options?: Options) {
46
71
  const { t } = dmsApp.$i18n;
47
72
  const translate = (message: string) => resolveApiMessage(t, message);
48
73
 
49
- const title = options?.title || DEFAULT_TITLE_KEY;
74
+ const title = options?.title || resolveApiErrorTitle(error);
50
75
  const description = options?.description || resolveApiErrorMessage(error);
51
76
 
52
77
  toast.add({
@@ -1,4 +1,5 @@
1
1
  import type { NavigationMenuItem } from "@nuxt/ui";
2
+ import { parseLinkQuery, stripQueryAndHash } from "./routePath";
2
3
 
3
4
  // Menu items carry the registered page/category's dot-separated `fullId`
4
5
  // (e.g. `pages.erp.orders`) plus the DMS semantic fields; Nuxt UI's
@@ -88,7 +89,9 @@ const matchesRoutePrefix = (currentPath: string, to: string): boolean => {
88
89
  const resolveItemTarget = (
89
90
  to: NavigationMenuItem["to"],
90
91
  ): MenuItemTarget | null => {
91
- if (typeof to === "string") return { path: to };
92
+ if (typeof to === "string") {
93
+ return { path: stripQueryAndHash(to), query: parseLinkQuery(to) };
94
+ }
92
95
  const target = to as MenuItemTarget | undefined;
93
96
  if (target && typeof target.path === "string") {
94
97
  return { path: target.path, query: target.query };
@@ -96,9 +99,11 @@ const resolveItemTarget = (
96
99
  return null;
97
100
  };
98
101
 
99
- // Subset match, mirroring the `exactQuery: "partial"` the entries are built
100
- // with: the entry's own parameters must be the current ones, while unrelated
101
- // parameters the page adds (tabs, filters) are ignored.
102
+ const hasQuery = (target: MenuItemTarget | null): boolean =>
103
+ Object.keys(target?.query ?? {}).length > 0;
104
+
105
+ // Subset match: the entry's own parameters must be the current ones, while
106
+ // unrelated parameters the page adds (tabs, filters) are ignored.
102
107
  const queryMatches = (
103
108
  declared: MenuItemTarget["query"],
104
109
  currentQuery: Record<string, unknown>,
@@ -133,18 +138,29 @@ export const isMenuItemActive = (
133
138
  return matchedFullId !== null && fullId === matchedFullId;
134
139
  };
135
140
 
136
- // Only the boolean `true` is set: Nuxt UI forwards an explicit `active` to the
137
- // underlying link, and a `false` there would suppress the built-in route match.
138
- // Leaving it undefined preserves that native highlighting for every other item.
141
+ // Only the boolean `true` is set on plain entries: Nuxt UI forwards an explicit
142
+ // `active` to the underlying link, and a `false` there would suppress the
143
+ // built-in route match. Entries carrying a query always get an explicit value,
144
+ // because that built-in match is a URL prefix test that would light up
145
+ // `?project=a` on `?project=ab`.
146
+ const resolveActiveState = (
147
+ item: NavigationMenuItem,
148
+ matchedFullId: string | null,
149
+ currentRoute: MenuRouteState,
150
+ ): Partial<Pick<NavigationMenuItem, "active">> => {
151
+ if (isMenuItemActive(item, matchedFullId, currentRoute)) {
152
+ return { active: true };
153
+ }
154
+ return hasQuery(resolveItemTarget(item.to)) ? { active: false } : {};
155
+ };
156
+
139
157
  export const addActiveStateToMenuItem = (
140
158
  item: NavigationMenuItem,
141
159
  matchedFullId: string | null,
142
160
  currentRoute: MenuRouteState,
143
161
  ): NavigationMenuItem => ({
144
162
  ...item,
145
- ...(isMenuItemActive(item, matchedFullId, currentRoute)
146
- ? { active: true }
147
- : {}),
163
+ ...resolveActiveState(item, matchedFullId, currentRoute),
148
164
  children: item.children?.map((child) =>
149
165
  addActiveStateToMenuItem(child, matchedFullId, currentRoute),
150
166
  ),
@@ -9,3 +9,26 @@ export function stripQueryAndHash(link: string): string {
9
9
  const [path] = link.split(QUERY_OR_HASH);
10
10
  return path ?? link;
11
11
  }
12
+
13
+ /**
14
+ * A link to `path` narrowed by `query`, as the single string every link
15
+ * renderer accepts: the Inertia-backed `ULink` only takes a string `to`, and
16
+ * stringifies an object into `[object Object]`.
17
+ */
18
+ export function buildLinkWithQuery(
19
+ path: string,
20
+ query: Record<string, string> | undefined,
21
+ ): string {
22
+ const search = new URLSearchParams(query ?? {}).toString();
23
+ return search === "" ? path : `${path}?${search}`;
24
+ }
25
+
26
+ /** The query parameters a link carries, the inverse of `buildLinkWithQuery`. */
27
+ export function parseLinkQuery(link: string): Record<string, string> {
28
+ const [withoutHash = ""] = link.split("#");
29
+ const queryStart = withoutHash.indexOf("?");
30
+ if (queryStart === -1) return {};
31
+ return Object.fromEntries(
32
+ new URLSearchParams(withoutHash.slice(queryStart + 1)),
33
+ );
34
+ }
@@ -37,6 +37,9 @@
37
37
  "title": "Server error",
38
38
  "description": "An unexpected error occurred. Please try again later."
39
39
  },
40
+ "request_refused": {
41
+ "title": "Request refused"
42
+ },
40
43
  "recovering": {
41
44
  "title": "Restoring your session...",
42
45
  "description": "Your session has expired. Refreshing your access and reloading the page."
@@ -37,6 +37,9 @@
37
37
  "title": "Erreur serveur",
38
38
  "description": "Une erreur inattendue s'est produite. Veuillez réessayer plus tard."
39
39
  },
40
+ "request_refused": {
41
+ "title": "Requête refusée"
42
+ },
40
43
  "recovering": {
41
44
  "title": "Restauration de votre session...",
42
45
  "description": "Votre session a expiré. Renouvellement de l'accès et rechargement de la page."
@@ -43,7 +43,10 @@ interface DmsPrivateConfig {
43
43
  relaySecret: string;
44
44
  /**
45
45
  * Whether a trusted reverse proxy sits immediately in front and appends
46
- * to `x-forwarded-for`; drives the OAuth rate-limit client key.
46
+ * to `x-forwarded-for`.
47
+ *
48
+ * @deprecated Mirrors the deprecated `auth.oauth.trustProxy`. The frontend
49
+ * server keys its OAuth rate limit on its own `DMS_TRUSTED_PROXY_HOPS`.
47
50
  */
48
51
  trustProxy?: boolean;
49
52
  };
@@ -134,7 +134,15 @@ export default {
134
134
  button: {
135
135
  slots: {
136
136
  // Tactile press + full transition (design .btn active:scale(.97)).
137
- base: "transition active:scale-[0.98]",
137
+ // Nuxt UI only fades a disabled button to 75%, which still reads as
138
+ // enabled on a filled primary: fade it further and drop the press and
139
+ // the inset highlight so every variant looks inert.
140
+ base: [
141
+ "transition active:scale-[0.98]",
142
+ "disabled:opacity-50 aria-disabled:opacity-50",
143
+ "disabled:active:scale-100 aria-disabled:active:scale-100",
144
+ "disabled:shadow-none aria-disabled:shadow-none",
145
+ ].join(" "),
138
146
  },
139
147
  compoundVariants: [
140
148
  // Inset top highlight on the solid primary fill (design .btn-primary).
@@ -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();