@antelopejs/dms 0.7.21 → 0.8.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 (157) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/implementations/dms-auth/account-emails.d.ts +4 -0
  3. package/dist/implementations/dms-auth/account-emails.js +62 -0
  4. package/dist/implementations/dms-auth/account-emails.js.map +1 -0
  5. package/dist/implementations/dms-auth/index.d.ts +1 -3
  6. package/dist/implementations/dms-auth/index.js +15 -61
  7. package/dist/implementations/dms-auth/index.js.map +1 -1
  8. package/dist/pages/settings/users/email-change.js +3 -4
  9. package/dist/pages/settings/users/email-change.js.map +1 -1
  10. package/dist/pages/settings/users/invites.js +1 -1
  11. package/dist/pages/settings/users/invites.js.map +1 -1
  12. package/dist/pages/settings/users/members.js +1 -1
  13. package/dist/pages/settings/users/members.js.map +1 -1
  14. package/dist/pages/settings/users/notifications.js +1 -0
  15. package/dist/pages/settings/users/notifications.js.map +1 -1
  16. package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.d.ts +1 -0
  17. package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js +34 -0
  18. package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js.map +1 -0
  19. package/dist/test/unit/interfaces/dms/hook-handler-results.test.d.ts +1 -0
  20. package/dist/test/unit/interfaces/dms/hook-handler-results.test.js +49 -0
  21. package/dist/test/unit/interfaces/dms/hook-handler-results.test.js.map +1 -0
  22. package/dist/test/unit/interfaces/dms/page-topics.test.js +17 -0
  23. package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -1
  24. package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js +2 -2
  25. package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js.map +1 -1
  26. package/dist/test/unit/interfaces/dms-base/query-response-delta.test.d.ts +1 -0
  27. package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js +29 -0
  28. package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js.map +1 -0
  29. package/dist/test/unit/interfaces/dms-base/resource-form.test.js +5 -0
  30. package/dist/test/unit/interfaces/dms-base/resource-form.test.js.map +1 -1
  31. package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js +12 -2
  32. package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js.map +1 -1
  33. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +29 -4
  34. package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
  35. package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +14 -11
  36. package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
  37. package/dist/test/unit/interfaces/dms-base/table-view-views.test.js +8 -5
  38. package/dist/test/unit/interfaces/dms-base/table-view-views.test.js.map +1 -1
  39. package/dist/test/unit/utils/email-subjects.test.d.ts +1 -0
  40. package/dist/test/unit/utils/email-subjects.test.js +27 -0
  41. package/dist/test/unit/utils/email-subjects.test.js.map +1 -0
  42. package/dist/utils/admin-invite-email.js +2 -4
  43. package/dist/utils/admin-invite-email.js.map +1 -1
  44. package/dist/utils/email-change-mail.d.ts +1 -1
  45. package/dist/utils/email-change-mail.js +11 -6
  46. package/dist/utils/email-change-mail.js.map +1 -1
  47. package/dist/utils/email-subjects.d.ts +3 -0
  48. package/dist/utils/email-subjects.js +32 -0
  49. package/dist/utils/email-subjects.js.map +1 -0
  50. package/dist/utils/export-jobs.js +15 -10
  51. package/dist/utils/export-jobs.js.map +1 -1
  52. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +29 -11
  53. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +17 -0
  54. package/frontend-vue/layers/dms-layout/app/app.config.ts +5 -0
  55. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +18 -2
  56. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +16 -1
  57. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +14 -0
  58. package/frontend-vue/layers/dms-layout/app/components/layout/HStack.vue +16 -0
  59. package/frontend-vue/layers/dms-layout/app/components/layout/VStack.vue +2 -0
  60. package/frontend-vue/layers/dms-layout/app/composables/useFooterLinks.ts +11 -1
  61. package/frontend-vue/layers/dms-layout/app/custom-pages/settings/roles.vue +1 -1
  62. package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +1 -1
  63. package/frontend-vue/layers/dms-layout/app/emails/EmailChangeNotice.vue +13 -7
  64. package/frontend-vue/layers/dms-layout/app/emails/EmailChangeVerification.vue +18 -7
  65. package/frontend-vue/layers/dms-layout/app/emails/EmailExportReady.vue +23 -12
  66. package/frontend-vue/layers/dms-layout/app/emails/EmailResetPassword.vue +18 -7
  67. package/frontend-vue/layers/dms-layout/app/emails/EmailTwoFactor.vue +23 -4
  68. package/frontend-vue/layers/dms-layout/app/emails/EmailUserValidation.vue +18 -6
  69. package/frontend-vue/layers/dms-layout/app/emails/components/EmailOTP.vue +8 -2
  70. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +40 -0
  71. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +40 -0
  72. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +2 -1
  73. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +11 -3
  74. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +27 -36
  75. package/frontend-vue/layers/dms-ui/app/build/composables/chart/apexTheme.ts +59 -2
  76. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +27 -4
  77. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +10 -2
  78. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +0 -5
  79. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryOrder.ts +23 -0
  80. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +19 -15
  81. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +26 -2
  82. package/frontend-vue/layers/dms-ui/app/build/composables/page/pageUrlValues.ts +41 -0
  83. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/registerDefaultFunctions.ts +2 -2
  84. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +3 -5
  85. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +7 -12
  86. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +3 -3
  87. package/frontend-vue/layers/dms-ui/app/build/utils/link.ts +7 -3
  88. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +32 -9
  89. package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +8 -1
  90. package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +9 -4
  91. package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +5 -11
  92. package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +2 -1
  93. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +32 -1
  94. package/frontend-vue/layers/dms-ui/app/components/check-list/CheckList.vue +1 -1
  95. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +12 -2
  96. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +7 -3
  97. package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +7 -0
  98. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +4 -1
  99. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +4 -2
  100. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +3 -2
  101. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -1
  102. package/frontend-vue/layers/dms-ui/app/components/grid/columns.ts +15 -3
  103. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +2 -1
  104. package/frontend-vue/layers/dms-ui/app/components/list-row/ListRow.vue +9 -2
  105. package/frontend-vue/layers/dms-ui/app/components/save-bar/InstantSaveBadge.vue +13 -5
  106. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +1 -1
  107. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +3 -2
  108. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +53 -31
  109. package/frontend-vue/layers/dms-ui/app/composables/chart/apexPlotOptions.ts +15 -4
  110. package/frontend-vue/layers/dms-ui/app/composables/chart/apexYRange.ts +123 -0
  111. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +49 -49
  112. package/frontend-vue/layers/dms-ui/app/composables/chart/useChartTheme.ts +20 -1
  113. package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +1 -1
  114. package/frontend-vue/layers/dms-ui/app/composables/form/types/props.ts +2 -0
  115. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +8 -6
  116. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +1 -1
  117. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +3 -5
  118. package/frontend-vue/layers/dms-ui/app/utils/bannerToneIcons.ts +13 -0
  119. package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +26 -0
  120. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -0
  121. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -0
  122. package/frontend-vue/tests/activity-feed-fill-height.test.ts +7 -0
  123. package/frontend-vue/tests/banner-tone-icon.test.ts +55 -0
  124. package/frontend-vue/tests/builtin-emails.test.ts +141 -0
  125. package/frontend-vue/tests/cascader.test.ts +27 -0
  126. package/frontend-vue/tests/chart-auto-y-range.test.ts +25 -9
  127. package/frontend-vue/tests/chart-colors.test.ts +8 -0
  128. package/frontend-vue/tests/chart-resize-after-animation.test.ts +100 -0
  129. package/frontend-vue/tests/chart-type-defaults.test.ts +128 -0
  130. package/frontend-vue/tests/check-list-marker.test.ts +58 -0
  131. package/frontend-vue/tests/confirm-from.test.ts +1 -1
  132. package/frontend-vue/tests/confirm-secondary-action.test.ts +2 -2
  133. package/frontend-vue/tests/context-links.test.ts +2 -2
  134. package/frontend-vue/tests/custom-action-confirm-from.test.ts +2 -2
  135. package/frontend-vue/tests/download-action-target.test.ts +2 -2
  136. package/frontend-vue/tests/file-drop-prompt.test.ts +72 -0
  137. package/frontend-vue/tests/footer-config.test.ts +135 -0
  138. package/frontend-vue/tests/form-actions.test.ts +7 -0
  139. package/frontend-vue/tests/form-entry-order.test.ts +45 -0
  140. package/frontend-vue/tests/form-footer-kind.test.ts +11 -6
  141. package/frontend-vue/tests/form-load-error.test.ts +38 -1
  142. package/frontend-vue/tests/grid-responsive-columns.test.ts +9 -0
  143. package/frontend-vue/tests/header-actions.test.ts +55 -9
  144. package/frontend-vue/tests/instant-save-badge.test.ts +11 -4
  145. package/frontend-vue/tests/list-row-truncate.test.ts +46 -0
  146. package/frontend-vue/tests/roles-name-errors.test.ts +2 -3
  147. package/frontend-vue/tests/save-bar.test.ts +15 -3
  148. package/frontend-vue/tests/save-status-error.test.ts +2 -2
  149. package/frontend-vue/tests/table-row-action-errors.test.ts +5 -5
  150. package/frontend-vue/tests/table-row-hover.test.ts +2 -2
  151. package/frontend-vue/tests/table-view-sources.test.ts +1 -1
  152. package/frontend-vue/tests/template-tokens.test.ts +235 -0
  153. package/frontend-vue/tests/url-interpolation.test.ts +6 -6
  154. package/package.json +2 -2
  155. package/skills/dms/SKILL.md +2 -1
  156. package/skills/dms-pages/REFERENCE.md +2 -2
  157. package/skills/dms-project/SKILL.md +0 -1
@@ -51,7 +51,7 @@ const invitation = computed(() => {
51
51
  </script>
52
52
 
53
53
  <template>
54
- <EmailLayout :title="title" :lang="locale">
54
+ <EmailLayout :title="title" :lang="locale" public-logo-only>
55
55
  <EText :style="emailStyles.text">
56
56
  {{ t("emails.admin_invite.greeting") }}
57
57
  </EText>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "vue-i18n";
2
3
  import EmailLayout from "./components/EmailLayout.vue";
3
4
  import { emailStyles } from "./components/index";
4
5
 
@@ -6,22 +7,27 @@ defineProps<{
6
7
  userName: string;
7
8
  newEmail: string;
8
9
  }>();
10
+
11
+ const { t, locale } = useI18n();
9
12
  </script>
10
13
 
11
14
  <template>
12
- <EmailLayout title="Your sign-in email is being changed">
13
- <EText :style="emailStyles.text">Hello {{ userName }},</EText>
15
+ <EmailLayout
16
+ :title="t('emails.email_change_notice.title')"
17
+ :lang="locale"
18
+ public-logo-only
19
+ >
20
+ <EText :style="emailStyles.text">
21
+ {{ t("emails.common.greeting", { name: userName }) }}
22
+ </EText>
14
23
 
15
24
  <EText :style="emailStyles.text">
16
- Someone signed in to your account asked to change its sign-in email to
17
- {{ newEmail }}. The change takes effect once that address is confirmed.
25
+ {{ t("emails.email_change_notice.body", { email: newEmail }) }}
18
26
  </EText>
19
27
 
20
28
  <template #footer>
21
29
  <EText :style="emailStyles.footer">
22
- If this wasn't you, change your password now and sign out the other
23
- sessions in Security: your account keeps this address until the new
24
- one is confirmed.
30
+ {{ t("emails.email_change_notice.footer") }}
25
31
  </EText>
26
32
  </template>
27
33
  </EmailLayout>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "vue-i18n";
2
3
  import EmailLayout from "./components/EmailLayout.vue";
3
4
  import EmailOTP from "./components/EmailOTP.vue";
4
5
  import { emailStyles } from "./components/index";
@@ -8,24 +9,34 @@ defineProps<{
8
9
  newEmail: string;
9
10
  code: string;
10
11
  expiresIn: string;
12
+ expiresInHours: number;
11
13
  }>();
14
+
15
+ const { t, locale } = useI18n();
12
16
  </script>
13
17
 
14
18
  <template>
15
- <EmailLayout title="Confirm your new sign-in email">
16
- <EText :style="emailStyles.text">Hello {{ userName }},</EText>
19
+ <EmailLayout
20
+ :title="t('emails.email_change_verification.title')"
21
+ :lang="locale"
22
+ public-logo-only
23
+ >
24
+ <EText :style="emailStyles.text">
25
+ {{ t("emails.common.greeting", { name: userName }) }}
26
+ </EText>
17
27
 
18
28
  <EText :style="emailStyles.text">
19
- Enter the code below to make {{ newEmail }} the sign-in email of your
20
- account:
29
+ {{ t("emails.email_change_verification.body", { email: newEmail }) }}
21
30
  </EText>
22
31
 
23
- <EmailOTP :code="code" :expires-in="expiresIn" />
32
+ <EmailOTP
33
+ :code="code"
34
+ :expires-in="t('emails.common.hours', expiresInHours)"
35
+ />
24
36
 
25
37
  <template #footer>
26
38
  <EText :style="emailStyles.footer">
27
- If you didn't ask for this change, you can safely ignore this email:
28
- your account keeps its current address.
39
+ {{ t("emails.email_change_verification.footer") }}
29
40
  </EText>
30
41
  </template>
31
42
  </EmailLayout>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { I18nT, useI18n } from "vue-i18n";
2
3
  import EmailLayout from "./components/EmailLayout.vue";
3
4
  import EmailButton from "./components/EmailButton.vue";
4
5
  import { emailStyles } from "./components/index";
@@ -7,38 +8,48 @@ defineProps<{
7
8
  userName: string;
8
9
  downloadLink: string;
9
10
  expiresIn: string;
11
+ expiresInHours: number;
10
12
  partial: boolean;
11
13
  failedSources: string[];
12
14
  }>();
15
+
16
+ const { t, locale } = useI18n();
13
17
  </script>
14
18
 
15
19
  <template>
16
- <EmailLayout title="Your data export is ready">
17
- <EText :style="emailStyles.text">Hello {{ userName }},</EText>
20
+ <EmailLayout
21
+ :title="t('emails.export_ready.title')"
22
+ :lang="locale"
23
+ public-logo-only
24
+ >
25
+ <EText :style="emailStyles.text">
26
+ {{ t("emails.common.greeting", { name: userName }) }}
27
+ </EText>
18
28
 
19
29
  <EText :style="emailStyles.text">
20
- Your data export has been prepared. Use the button below to download the
21
- archive:
30
+ {{ t("emails.export_ready.body") }}
22
31
  </EText>
23
32
 
24
33
  <EmailButton
25
34
  :href="downloadLink"
26
- :expires-in="expiresIn"
27
- expires-label="This download link expires in"
35
+ :expires-in="t('emails.common.hours', expiresInHours)"
36
+ :expires-label="t('emails.export_ready.expires_label')"
28
37
  >
29
- Download export
38
+ {{ t("emails.export_ready.button") }}
30
39
  </EmailButton>
31
40
 
32
41
  <EText v-if="partial" :style="emailStyles.text">
33
- This export is
34
- <strong>incomplete</strong>
35
- : the following parts could not be included —
36
- {{ failedSources.join(", ") }}. Request a new export if you need them.
42
+ <I18nT keypath="emails.export_ready.partial" scope="global">
43
+ <template #incomplete>
44
+ <strong>{{ t("emails.export_ready.incomplete") }}</strong>
45
+ </template>
46
+ <template #sources>{{ failedSources.join(", ") }}</template>
47
+ </I18nT>
37
48
  </EText>
38
49
 
39
50
  <template #footer>
40
51
  <EText :style="emailStyles.footer">
41
- If you didn't request this export, you can safely ignore this email.
52
+ {{ t("emails.export_ready.footer") }}
42
53
  </EText>
43
54
  </template>
44
55
  </EmailLayout>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "vue-i18n";
2
3
  import EmailLayout from "./components/EmailLayout.vue";
3
4
  import EmailOTP from "./components/EmailOTP.vue";
4
5
  import { emailStyles } from "./components/index";
@@ -7,24 +8,34 @@ defineProps<{
7
8
  userName: string;
8
9
  resetCode: string;
9
10
  expiresIn: string;
11
+ expiresInHours: number;
10
12
  }>();
13
+
14
+ const { t, locale } = useI18n();
11
15
  </script>
12
16
 
13
17
  <template>
14
- <EmailLayout title="Reset Your Password">
15
- <EText :style="emailStyles.text">Hello {{ userName }},</EText>
18
+ <EmailLayout
19
+ :title="t('emails.reset_password.title')"
20
+ :lang="locale"
21
+ public-logo-only
22
+ >
23
+ <EText :style="emailStyles.text">
24
+ {{ t("emails.common.greeting", { name: userName }) }}
25
+ </EText>
16
26
 
17
27
  <EText :style="emailStyles.text">
18
- We received a request to reset your password. Enter the verification code
19
- below to reset your password:
28
+ {{ t("emails.reset_password.body") }}
20
29
  </EText>
21
30
 
22
- <EmailOTP :code="resetCode" :expires-in="expiresIn" />
31
+ <EmailOTP
32
+ :code="resetCode"
33
+ :expires-in="t('emails.common.hours', expiresInHours)"
34
+ />
23
35
 
24
36
  <template #footer>
25
37
  <EText :style="emailStyles.footer">
26
- If you didn't request a password reset, you can safely ignore this
27
- email. Your password will remain unchanged.
38
+ {{ t("emails.reset_password.footer") }}
28
39
  </EText>
29
40
  </template>
30
41
  </EmailLayout>
@@ -1,18 +1,37 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "vue-i18n";
2
3
  import EmailLayout from "./components/EmailLayout.vue";
3
4
  import EmailOTP from "./components/EmailOTP.vue";
5
+ import { emailStyles } from "./components/index";
4
6
 
5
7
  defineProps<{
6
8
  userName: string;
7
9
  verificationCode: string;
8
10
  }>();
11
+
12
+ const { t, locale } = useI18n();
9
13
  </script>
10
14
 
11
15
  <template>
12
- <EmailLayout title="Your verification code">
13
- <p>Hello {{ userName }},</p>
14
- <p>Use this verification code to finish signing in:</p>
16
+ <EmailLayout
17
+ :title="t('emails.two_factor.title')"
18
+ :lang="locale"
19
+ public-logo-only
20
+ >
21
+ <EText :style="emailStyles.text">
22
+ {{ t("emails.common.greeting", { name: userName }) }}
23
+ </EText>
24
+
25
+ <EText :style="emailStyles.text">
26
+ {{ t("emails.two_factor.body") }}
27
+ </EText>
28
+
15
29
  <EmailOTP :code="verificationCode" />
16
- <p>If you did not attempt to sign in, you can safely ignore this email.</p>
30
+
31
+ <template #footer>
32
+ <EText :style="emailStyles.footer">
33
+ {{ t("emails.two_factor.footer") }}
34
+ </EText>
35
+ </template>
17
36
  </EmailLayout>
18
37
  </template>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "vue-i18n";
2
3
  import EmailLayout from "./components/EmailLayout.vue";
3
4
  import EmailOTP from "./components/EmailOTP.vue";
4
5
  import { emailStyles } from "./components/index";
@@ -7,23 +8,34 @@ defineProps<{
7
8
  userName: string;
8
9
  validationCode: string;
9
10
  expiresIn: string;
11
+ expiresInHours: number;
10
12
  }>();
13
+
14
+ const { t, locale } = useI18n();
11
15
  </script>
12
16
 
13
17
  <template>
14
- <EmailLayout title="Verify Your Email">
15
- <EText :style="emailStyles.text">Hello {{ userName }},</EText>
18
+ <EmailLayout
19
+ :title="t('emails.user_validation.title')"
20
+ :lang="locale"
21
+ public-logo-only
22
+ >
23
+ <EText :style="emailStyles.text">
24
+ {{ t("emails.common.greeting", { name: userName }) }}
25
+ </EText>
16
26
 
17
27
  <EText :style="emailStyles.text">
18
- Thank you for signing up! Enter the verification code below to verify your
19
- email address:
28
+ {{ t("emails.user_validation.body") }}
20
29
  </EText>
21
30
 
22
- <EmailOTP :code="validationCode" :expires-in="expiresIn" />
31
+ <EmailOTP
32
+ :code="validationCode"
33
+ :expires-in="t('emails.common.hours', expiresInHours)"
34
+ />
23
35
 
24
36
  <template #footer>
25
37
  <EText :style="emailStyles.footer">
26
- If you didn't create an account, you can safely ignore this email.
38
+ {{ t("emails.user_validation.footer") }}
27
39
  </EText>
28
40
  </template>
29
41
  </EmailLayout>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useI18n } from "vue-i18n";
2
3
  import { emailStyles } from "./index";
3
4
 
4
5
  defineProps<{
@@ -6,17 +7,22 @@ defineProps<{
6
7
  expiresIn?: string;
7
8
  expiresLabel?: string;
8
9
  }>();
10
+
11
+ const { t } = useI18n();
9
12
  </script>
10
13
 
11
14
  <template>
12
15
  <ESection :style="emailStyles.otp.box">
13
- <EText :style="emailStyles.otp.label">Your verification code is:</EText>
16
+ <EText :style="emailStyles.otp.label">
17
+ {{ t("emails.common.code_label") }}
18
+ </EText>
14
19
  <EText :style="emailStyles.otp.code">
15
20
  {{ code }}
16
21
  </EText>
17
22
  </ESection>
18
23
 
19
24
  <EText v-if="expiresIn" :style="emailStyles.expires">
20
- {{ expiresLabel ?? "This code expires in" }} {{ expiresIn }}.
25
+ {{ expiresLabel ?? t("emails.common.code_expires_label") }}
26
+ {{ expiresIn }}.
21
27
  </EText>
22
28
  </template>
@@ -867,6 +867,46 @@
867
867
  "expires_label": "This invitation expires in",
868
868
  "expires_in": "{days} days",
869
869
  "footer": "If you didn't expect this invitation, you can safely ignore this email."
870
+ },
871
+ "common": {
872
+ "greeting": "Hello {name},",
873
+ "code_label": "Your verification code is:",
874
+ "code_expires_label": "This code expires in",
875
+ "hours": "{count} hour | {count} hours"
876
+ },
877
+ "user_validation": {
878
+ "title": "Verify Your Email",
879
+ "body": "Thank you for signing up! Enter the verification code below to verify your email address:",
880
+ "footer": "If you didn't create an account, you can safely ignore this email."
881
+ },
882
+ "reset_password": {
883
+ "title": "Reset Your Password",
884
+ "body": "We received a request to reset your password. Enter the verification code below to reset your password:",
885
+ "footer": "If you didn't request a password reset, you can safely ignore this email. Your password will remain unchanged."
886
+ },
887
+ "two_factor": {
888
+ "title": "Your verification code",
889
+ "body": "Use this verification code to finish signing in:",
890
+ "footer": "If you did not attempt to sign in, you can safely ignore this email."
891
+ },
892
+ "email_change_verification": {
893
+ "title": "Confirm your new sign-in email",
894
+ "body": "Enter the code below to make {email} the sign-in email of your account:",
895
+ "footer": "If you didn't ask for this change, you can safely ignore this email: your account keeps its current address."
896
+ },
897
+ "email_change_notice": {
898
+ "title": "Your sign-in email is being changed",
899
+ "body": "Someone signed in to your account asked to change its sign-in email to {email}. The change takes effect once that address is confirmed.",
900
+ "footer": "If this wasn't you, change your password now and sign out the other sessions in Security: your account keeps this address until the new one is confirmed."
901
+ },
902
+ "export_ready": {
903
+ "title": "Your data export is ready",
904
+ "body": "Your data export has been prepared. Use the button below to download the archive:",
905
+ "button": "Download export",
906
+ "expires_label": "This download link expires in",
907
+ "partial": "This export is {incomplete}: the following parts could not be included — {sources}. Request a new export if you need them.",
908
+ "incomplete": "incomplete",
909
+ "footer": "If you didn't request this export, you can safely ignore this email."
870
910
  }
871
911
  },
872
912
  "empty_layout": {
@@ -871,6 +871,46 @@
871
871
  "expires_label": "Cette invitation expire dans",
872
872
  "expires_in": "{days} jours",
873
873
  "footer": "Si vous n'attendiez pas cette invitation, vous pouvez ignorer cet e-mail."
874
+ },
875
+ "common": {
876
+ "greeting": "Bonjour {name},",
877
+ "code_label": "Votre code de vérification :",
878
+ "code_expires_label": "Ce code expire dans",
879
+ "hours": "{count} heure | {count} heures"
880
+ },
881
+ "user_validation": {
882
+ "title": "Vérifiez votre adresse e-mail",
883
+ "body": "Merci pour votre inscription ! Saisissez le code ci-dessous pour vérifier votre adresse e-mail :",
884
+ "footer": "Si vous n'avez pas créé de compte, vous pouvez ignorer cet e-mail."
885
+ },
886
+ "reset_password": {
887
+ "title": "Réinitialisez votre mot de passe",
888
+ "body": "Nous avons reçu une demande de réinitialisation de votre mot de passe. Saisissez le code ci-dessous pour le réinitialiser :",
889
+ "footer": "Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail. Votre mot de passe reste inchangé."
890
+ },
891
+ "two_factor": {
892
+ "title": "Votre code de vérification",
893
+ "body": "Saisissez ce code de vérification pour terminer votre connexion :",
894
+ "footer": "Si vous n'avez pas essayé de vous connecter, vous pouvez ignorer cet e-mail."
895
+ },
896
+ "email_change_verification": {
897
+ "title": "Confirmez votre nouvelle adresse de connexion",
898
+ "body": "Saisissez le code ci-dessous pour faire de {email} l'adresse de connexion de votre compte :",
899
+ "footer": "Si vous n'avez pas demandé ce changement, vous pouvez ignorer cet e-mail : votre compte garde son adresse actuelle."
900
+ },
901
+ "email_change_notice": {
902
+ "title": "Votre adresse de connexion va changer",
903
+ "body": "Une personne connectée à votre compte a demandé à remplacer son adresse de connexion par {email}. Le changement prendra effet une fois cette adresse confirmée.",
904
+ "footer": "Si ce n'est pas vous, changez votre mot de passe maintenant et déconnectez les autres sessions dans Sécurité : votre compte garde cette adresse tant que la nouvelle n'est pas confirmée."
905
+ },
906
+ "export_ready": {
907
+ "title": "Votre export de données est prêt",
908
+ "body": "Votre export de données a été préparé. Utilisez le bouton ci-dessous pour télécharger l'archive :",
909
+ "button": "Télécharger l'export",
910
+ "expires_label": "Ce lien de téléchargement expire dans",
911
+ "partial": "Cet export est {incomplete} : les parties suivantes n'ont pas pu être incluses — {sources}. Demandez un nouvel export si vous en avez besoin.",
912
+ "incomplete": "incomplet",
913
+ "footer": "Si vous n'avez pas demandé cet export, vous pouvez ignorer cet e-mail."
874
914
  }
875
915
  },
876
916
  "empty_layout": {
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { pageLink } from "../../composables/page/pageUrlValues";
2
3
  import type {
3
4
  FormField,
4
5
  FormFieldReadonly,
@@ -79,7 +80,7 @@ const readonly = computed<FormFieldReadonly>(() =>
79
80
  />
80
81
  <DmsAutoLink
81
82
  v-if="readonly.link"
82
- :to="readonly.link.to"
83
+ :to="pageLink(readonly.link.to)"
83
84
  class="text-primary ms-auto text-[12.5px] font-medium"
84
85
  >
85
86
  {{ processI18n(readonly.link.label) }}
@@ -1,5 +1,7 @@
1
1
  <script setup lang="ts">
2
- import DmsEmptyState from "../../../components/empty-state/EmptyState.vue";
2
+ import DmsEmptyState, {
3
+ type EmptyStateVariant,
4
+ } from "../../../components/empty-state/EmptyState.vue";
3
5
  import type { FormLoadFailure } from "../../composables/form/formRecordLoad";
4
6
 
5
7
  // What a form shows instead of its fields when the record it edits did not
@@ -21,16 +23,22 @@ const props = withDefaults(defineProps<FormLoadErrorProps>(), {
21
23
  const emit = defineEmits<FormLoadErrorEmits>();
22
24
 
23
25
  const { t } = useI18n();
26
+
27
+ const VARIANTS: Record<FormLoadFailure, EmptyStateVariant> = {
28
+ forbidden: "no-access",
29
+ missing: "no-data",
30
+ error: "error",
31
+ };
24
32
  </script>
25
33
 
26
34
  <template>
27
35
  <DmsEmptyState
28
36
  size="md"
29
- :variant="props.failure === 'forbidden' ? 'no-access' : 'error'"
37
+ :variant="VARIANTS[props.failure]"
30
38
  :title="t(`dms.form.load_error.${props.failure}_title`)"
31
39
  :description="t(`dms.form.load_error.${props.failure}_description`)"
32
40
  >
33
- <template #actions>
41
+ <template v-if="props.failure !== 'missing'" #actions>
34
42
  <UButton
35
43
  :label="t('dms.form.load_error.retry')"
36
44
  icon="i-ph-arrows-clockwise"
@@ -52,52 +52,47 @@ import {
52
52
  findServedQuickAction,
53
53
  } from "../../utils/dispatchQuickActionTarget";
54
54
  import {
55
- hasUrlVariables,
55
+ namesPageValue,
56
56
  resolveLinkVariables,
57
57
  resolveTextVariables,
58
58
  } from "../../utils/urlVariables";
59
- import { interpolateLiteral } from "../../utils/literalText";
59
+ import { currentPageUrlValues } from "../page/pageUrlValues";
60
60
  import { type DownloadFetch, saveDownload } from "../../utils/fileDownload";
61
+ import { interpolateRowText } from "#dms-core/app/utils/url-interpolation";
61
62
 
62
63
  /** Runs a confirmed action from inside its dialog (see `onConfirm`). */
63
64
  type ConfirmedRun = NonNullable<ConfirmOptions["onConfirm"]>;
64
65
 
65
66
  /**
66
- * A link target's URL with the page's `{{params.X}}` / `{{query.X}}` filled
67
- * (a query pair without a value left out), then the row's `{field}`: a row
67
+ * A target's URL with the page's `{{params.X}}` / `{{query.X}}` filled (a
68
+ * query pair without a value left out), then the row's `{{row.field}}`: an
68
69
  * action keeps the page's context (`?website={{query.website}}`) as a page
69
- * header button does. Read when the link is followed, so it carries the URL
70
- * as it is then.
70
+ * header button does, whatever its type and wherever it sits. Read when the
71
+ * action runs, so it carries the URL as it is then.
71
72
  */
72
- function linkTargetUrl(url: string, rowData?: Data): string {
73
- const withPageValues = hasUrlVariables(url)
73
+ function targetUrl(url: string, rowData?: Data): string {
74
+ const withPageValues = namesPageValue(url)
74
75
  ? resolveLinkVariables(url, currentPageUrlValues())
75
76
  : url;
76
77
  return rowData ? interpolateUrl(withPageValues, rowData) : withPageValues;
77
78
  }
78
79
 
79
80
  /**
80
- * A download target's file name with the page's tokens filled as they are
81
- * (a name, not a URL: nothing is encoded), then the row's `{field}`. A token
82
- * the page has no value for leaves no name: the server's one is used.
81
+ * A download target's file name with the row's `{{row.field}}` and the
82
+ * page's tokens filled as they are (a name, not a URL: nothing is encoded). A
83
+ * token without a value leaves no name: the server's one is used.
83
84
  */
84
85
  function downloadTargetFilename(
85
86
  filename: string | undefined,
86
87
  rowData?: Data,
87
88
  ): string | undefined {
88
89
  if (!filename) return undefined;
89
- const withPageValues = resolveTextVariables(filename, currentPageUrlValues());
90
- if (!withPageValues || !rowData) return withPageValues;
91
- return interpolateLiteral(withPageValues, rowData);
90
+ const withRow = rowData ? interpolateRowText(filename, rowData) : filename;
91
+ return resolveTextVariables(withRow, currentPageUrlValues());
92
92
  }
93
93
 
94
- function currentPageUrlValues() {
95
- const route = useDmsRoute();
96
- return {
97
- routeParams: useSiteLayout().findMatchingRoute(route.path)?.params,
98
- routeQuery: route.query as Record<string, unknown>,
99
- };
100
- }
94
+ // The id an export job's start answered, in its `statusUrl` / `downloadUrl`.
95
+ const JOB_ID_TOKEN = /\{\{\s*job\.id\s*\}\}/g;
101
96
 
102
97
  /** Where the buttons run: the requests they send, what they refresh. */
103
98
  export interface ActionTargetsConfig {
@@ -119,7 +114,7 @@ export interface ActionTargetsConfig {
119
114
  */
120
115
  onRecordShown?: (id?: string) => void;
121
116
  /**
122
- * The field a row's id is read from (`_id` by default): it fills `{id}` in
117
+ * The field a row's id is read from (`_id` by default): it fills `{{row.id}}` in
123
118
  * the `from` URL of a row action's confirmation, as on a built-in action.
124
119
  */
125
120
  rowIdKey?: string;
@@ -275,7 +270,10 @@ export function useActionTargets(config: ActionTargetsConfig) {
275
270
  const statusUrlTemplate = target.statusUrl;
276
271
  const downloadUrlTemplate = target.downloadUrl;
277
272
  const interpolate = (template: string, ticket: ExportJobTicket): string =>
278
- interpolateUrl(template, { ...(rowData ?? {}), jobId: ticket.jobId });
273
+ targetUrl(
274
+ template.replace(JOB_ID_TOKEN, encodeURIComponent(ticket.jobId)),
275
+ rowData,
276
+ );
279
277
  await runExportJob({
280
278
  startUrl: url,
281
279
  startMethod: target.method ?? "POST",
@@ -299,7 +297,7 @@ export function useActionTargets(config: ActionTargetsConfig) {
299
297
  try {
300
298
  await saveDownload(
301
299
  {
302
- url: linkTargetUrl(target.url, rowData),
300
+ url: targetUrl(target.url, rowData),
303
301
  filename: downloadTargetFilename(target.filename, rowData),
304
302
  },
305
303
  fetchRaw,
@@ -538,11 +536,11 @@ export function useActionTargets(config: ActionTargetsConfig) {
538
536
  const targetHandlers: Record<string, TargetHandler> = {
539
537
  page: (target, _label, rowData) => {
540
538
  const pageTarget = target as ActionTarget & { type: "page" };
541
- navigateDms(linkTargetUrl(pageTarget.url, rowData));
539
+ navigateDms(targetUrl(pageTarget.url, rowData));
542
540
  },
543
541
  external: (target, _label, rowData) => {
544
542
  const externalTarget = target as ActionTarget & { type: "external" };
545
- const url = linkTargetUrl(externalTarget.url, rowData);
543
+ const url = targetUrl(externalTarget.url, rowData);
546
544
  if (typeof window === "undefined" || !url) return;
547
545
  if (externalTarget.newTab) {
548
546
  window.open(url, "_blank", "noopener,noreferrer");
@@ -552,16 +550,12 @@ export function useActionTargets(config: ActionTargetsConfig) {
552
550
  },
553
551
  api: async (target, label, rowData) => {
554
552
  const apiTarget = target as ActionTarget & { type: "api" };
555
- const url = rowData
556
- ? interpolateUrl(apiTarget.url, rowData)
557
- : apiTarget.url;
553
+ const url = targetUrl(apiTarget.url, rowData);
558
554
  await handleApiTarget(apiTarget, url, label, rowData);
559
555
  },
560
556
  exportJob: async (target, _label, rowData) => {
561
557
  const jobTarget = target as ActionTarget & { type: "exportJob" };
562
- const url = rowData
563
- ? interpolateUrl(jobTarget.url, rowData)
564
- : jobTarget.url;
558
+ const url = targetUrl(jobTarget.url, rowData);
565
559
  await handleExportJobTarget(jobTarget, url, rowData);
566
560
  },
567
561
  download: (target, label, rowData) =>
@@ -581,9 +575,6 @@ export function useActionTargets(config: ActionTargetsConfig) {
581
575
  sidePanel: sidePanelHandler,
582
576
  };
583
577
 
584
- const targetUrl = (url: string, rowData?: Data): string =>
585
- rowData ? interpolateUrl(url, rowData) : url;
586
-
587
578
  // A target making a request runs it from inside its confirmation, which
588
579
  // shows a refusal (under its field when it names one) and stays open; any
589
580
  // other runs once confirmed.
@@ -604,7 +595,7 @@ export function useActionTargets(config: ActionTargetsConfig) {
604
595
  },
605
596
  };
606
597
 
607
- // `{id}` of a confirmation's `from` URL, when the row carries an id.
598
+ // `{{row.id}}` of a confirmation's `from` URL, when the row carries an id.
608
599
  const rowUrlParams = (rowData?: Data) => {
609
600
  const id = rowData
610
601
  ? get(rowData, config.rowIdKey ?? DEFAULT_ROW_ID_KEY)