@antelopejs/dms-mailing 0.7.1 → 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.
- package/README.md +21 -4
- package/dist/crons/jobs.d.ts +1 -1
- package/dist/data/body.js +2 -1
- package/dist/data/body.js.map +1 -1
- package/dist/data/templates-table.js +4 -3
- package/dist/data/templates-table.js.map +1 -1
- package/dist/db/models/template-versions.model.js.map +1 -1
- package/dist/db/models/templates.model.js +2 -1
- package/dist/db/models/templates.model.js.map +1 -1
- package/dist/engine/changes.js +2 -1
- package/dist/engine/changes.js.map +1 -1
- package/dist/engine/conditions.js +2 -3
- package/dist/engine/conditions.js.map +1 -1
- package/dist/engine/fallback-html.js +8 -8
- package/dist/engine/fallback-html.js.map +1 -1
- package/dist/engine/formats.js +6 -6
- package/dist/engine/formats.js.map +1 -1
- package/dist/engine/paths.js +2 -3
- package/dist/engine/paths.js.map +1 -1
- package/dist/engine/system-variables.js +1 -1
- package/dist/engine/system-variables.js.map +1 -1
- package/dist/engine/tokens.js.map +1 -1
- package/dist/pages/sends.js.map +1 -1
- package/dist/pages/toolbar.js.map +1 -1
- package/dist/routes/events.js +2 -2
- package/dist/routes/events.js.map +1 -1
- package/dist/routes/metrics.js +6 -5
- package/dist/routes/metrics.js.map +1 -1
- package/dist/routes/sends.js +1 -1
- package/dist/routes/sends.js.map +1 -1
- package/dist/routes/settings.js +6 -6
- package/dist/routes/settings.js.map +1 -1
- package/dist/routes/templates.js +4 -4
- package/dist/routes/templates.js.map +1 -1
- package/dist/services/attention.js +2 -1
- package/dist/services/attention.js.map +1 -1
- package/dist/services/categories.js +6 -6
- package/dist/services/categories.js.map +1 -1
- package/dist/services/checked-send.js +16 -9
- package/dist/services/checked-send.js.map +1 -1
- package/dist/services/declarations.js.map +1 -1
- package/dist/services/events.js +11 -7
- package/dist/services/events.js.map +1 -1
- package/dist/services/localized-text.d.ts +2 -1
- package/dist/services/localized-text.js +2 -1
- package/dist/services/localized-text.js.map +1 -1
- package/dist/services/metrics.js.map +1 -1
- package/dist/services/render.js.map +1 -1
- package/dist/services/send-import.js +7 -3
- package/dist/services/send-import.js.map +1 -1
- package/dist/services/send.js +4 -3
- package/dist/services/send.js.map +1 -1
- package/dist/services/starters.js +5 -5
- package/dist/services/starters.js.map +1 -1
- package/dist/services/template-stats.js +5 -2
- package/dist/services/template-stats.js.map +1 -1
- package/dist/services/templates.js +6 -2
- package/dist/services/templates.js.map +1 -1
- package/dist/validation/declarations.schema.d.ts +16 -16
- package/dist/validation/declarations.schema.js +2 -2
- package/dist/validation/declarations.schema.js.map +1 -1
- package/dist/validation/guards.d.ts +4 -0
- package/dist/validation/guards.js +19 -0
- package/dist/validation/guards.js.map +1 -0
- package/dist/validation/send-event-type.schema.d.ts +2 -0
- package/dist/validation/send-event-type.schema.js +11 -0
- package/dist/validation/send-event-type.schema.js.map +1 -0
- package/dist/validation/settings.schema.js +1 -1
- package/dist/validation/settings.schema.js.map +1 -1
- package/dist/validation/templates.schema.d.ts +19 -18
- package/dist/validation/templates.schema.js.map +1 -1
- package/frontend-vue/app/build/components/TemplateDrawerFooter.vue +45 -0
- package/frontend-vue/app/build/components/TemplateDrawerPerformance.vue +102 -0
- package/frontend-vue/app/build/components/TemplateDrawerVariables.vue +110 -0
- package/frontend-vue/app/build/composables/useDrawerStats.ts +78 -0
- package/frontend-vue/app/components/DuplicateModal.vue +3 -11
- package/frontend-vue/app/components/FixAddressModal.vue +2 -1
- package/frontend-vue/app/components/NewTemplateModal.vue +3 -11
- package/frontend-vue/app/components/RealSendModal.vue +6 -17
- package/frontend-vue/app/components/RenderingModal.vue +2 -1
- package/frontend-vue/app/components/SendDrawer.vue +5 -4
- package/frontend-vue/app/components/StarterGallery.vue +3 -11
- package/frontend-vue/app/components/TemplateDrawer.vue +40 -269
- package/frontend-vue/app/components/TestSendModal.vue +7 -18
- package/frontend-vue/app/components/VersionHistory.vue +2 -7
- package/frontend-vue/app/components/WebhookStatus.vue +2 -1
- package/frontend-vue/app/components/editor/EditorWorkspace.vue +2 -7
- package/frontend-vue/build/backend-boundary.ts +122 -0
- package/frontend-vue/dms.frontend.build.ts +7 -0
- package/frontend-vue/eslint.config.mjs +13 -0
- package/frontend-vue/package.json +4 -2
- package/frontend-vue/pnpm-lock.yaml +210 -61
- package/frontend-vue/tests/backend-boundary.test.ts +203 -0
- package/frontend-vue/tests/dnd.test.ts +1 -2
- package/frontend-vue/tests/editor-block-list.test.ts +136 -53
- package/frontend-vue/tests/helpers/auth-fetch.ts +123 -0
- package/frontend-vue/tests/helpers/nuxt-globals.ts +17 -1
- package/frontend-vue/tests/new-template-modal.test.ts +41 -6
- package/frontend-vue/tests/page-reads.test.ts +26 -6
- package/frontend-vue/tests/send-actions.test.ts +0 -1
- package/frontend-vue/tests/send-drawer.test.ts +2 -6
- package/frontend-vue/tests/send-modals.test.ts +8 -5
- package/frontend-vue/tests/sortable-list.test.ts +54 -62
- package/frontend-vue/tests/suppression.test.ts +1 -2
- package/frontend-vue/tests/template-drawer.test.ts +27 -18
- package/frontend-vue/tests/template-gallery.test.ts +7 -5
- package/frontend-vue/tests/template-preview-frame.test.ts +2 -5
- package/package.json +3 -8
- package/skills/dms-mailing/SKILL.md +3 -3
- package/skills/dms-mailing/cases.json +49 -0
- package/src/crons/jobs.ts +1 -1
- package/src/data/body.ts +2 -1
- package/src/data/templates-table.ts +12 -7
- package/src/db/models/template-versions.model.ts +5 -1
- package/src/db/models/templates.model.ts +6 -3
- package/src/engine/changes.ts +3 -2
- package/src/engine/conditions.ts +2 -4
- package/src/engine/fallback-html.ts +13 -13
- package/src/engine/formats.ts +6 -6
- package/src/engine/paths.ts +4 -3
- package/src/engine/system-variables.ts +4 -3
- package/src/engine/tokens.ts +1 -2
- package/src/pages/sends.ts +5 -3
- package/src/pages/templates.ts +1 -1
- package/src/pages/toolbar.ts +4 -2
- package/src/routes/events.ts +3 -6
- package/src/routes/metrics.ts +7 -5
- package/src/routes/sends.ts +10 -18
- package/src/routes/settings.ts +8 -9
- package/src/routes/suppressions.ts +1 -1
- package/src/routes/templates.ts +5 -5
- package/src/services/attention.ts +4 -3
- package/src/services/categories.ts +6 -6
- package/src/services/checked-send.ts +17 -19
- package/src/services/declarations.ts +3 -5
- package/src/services/events.ts +16 -13
- package/src/services/localized-text.ts +4 -2
- package/src/services/metrics.ts +2 -2
- package/src/services/notify.ts +1 -1
- package/src/services/render.ts +5 -1
- package/src/services/send-import.ts +16 -11
- package/src/services/send.ts +7 -7
- package/src/services/starters.ts +6 -6
- package/src/services/template-stats.ts +11 -4
- package/src/services/templates.ts +10 -8
- package/src/validation/declarations.schema.ts +2 -2
- package/src/validation/guards.ts +25 -0
- package/src/validation/send-event-type.schema.ts +10 -0
- package/src/validation/settings.schema.ts +7 -2
- package/src/validation/templates.schema.ts +2 -1
- package/frontend-vue/tests/i18n-editor.test.ts +0 -50
- package/frontend-vue/tests/i18n.test.ts +0 -138
|
@@ -7,7 +7,6 @@ import { useMailingApi } from '../composables/useMailingApi'
|
|
|
7
7
|
import { useTemplateContentRead } from '../composables/useMailingReads'
|
|
8
8
|
import type { SendResult } from '../composables/useMailingApi'
|
|
9
9
|
import { useTemplateDrawer } from '../composables/useTemplateDrawer'
|
|
10
|
-
import { apiErrorMessage } from '../utils/api-error'
|
|
11
10
|
import { firstName, formatCardDate } from '../utils/gallery'
|
|
12
11
|
import { localeChipClass, missingLocales } from '../utils/locales'
|
|
13
12
|
import {
|
|
@@ -57,10 +56,10 @@ const emit = defineEmits<{ success: [sent?: boolean] }>()
|
|
|
57
56
|
|
|
58
57
|
const api = useMailingApi()
|
|
59
58
|
const toast = useToast()
|
|
59
|
+
const { presentError } = useApiErrorPresenter()
|
|
60
60
|
const { t, locale: uiLocale } = useI18n()
|
|
61
61
|
const onDate = (date: string): string =>
|
|
62
62
|
t('dms_mailing.common.on_date', { date })
|
|
63
|
-
const { processApiMessage } = useTranslation()
|
|
64
63
|
const { uniqueLocales } = useUniqueLocales()
|
|
65
64
|
const overlays = useTemplateDrawer()
|
|
66
65
|
|
|
@@ -110,10 +109,7 @@ onMounted(async () => {
|
|
|
110
109
|
values.value = initialSendValues(fields.value, detail.value.testData)
|
|
111
110
|
locale.value = detail.value.fallbackLocale
|
|
112
111
|
} catch (error) {
|
|
113
|
-
|
|
114
|
-
color: 'error',
|
|
115
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
116
|
-
})
|
|
112
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
117
113
|
} finally {
|
|
118
114
|
loading.value = false
|
|
119
115
|
}
|
|
@@ -296,15 +292,11 @@ function openSettings(): void {
|
|
|
296
292
|
void navigateDms({ path: SETTINGS_PATH })
|
|
297
293
|
}
|
|
298
294
|
|
|
299
|
-
function report(results: SendResult[]
|
|
295
|
+
function report(results: SendResult[]): void {
|
|
300
296
|
results
|
|
301
297
|
.filter((result) => result.error)
|
|
302
298
|
.forEach((result) =>
|
|
303
|
-
|
|
304
|
-
color: 'error',
|
|
305
|
-
title: to[results.indexOf(result)] ?? result.sendId,
|
|
306
|
-
description: processApiMessage(result.error),
|
|
307
|
-
}),
|
|
299
|
+
presentError(result.error, { fallbackKey: 'error.500.description' }),
|
|
308
300
|
)
|
|
309
301
|
const sent = results.filter((result) => !result.error).length
|
|
310
302
|
if (!sent) return
|
|
@@ -331,13 +323,10 @@ async function submit(): Promise<void> {
|
|
|
331
323
|
locale: locale.value || undefined,
|
|
332
324
|
data: sendData.value,
|
|
333
325
|
})
|
|
334
|
-
report(response.results
|
|
326
|
+
report(response.results)
|
|
335
327
|
close(true)
|
|
336
328
|
} catch (error) {
|
|
337
|
-
|
|
338
|
-
color: 'error',
|
|
339
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
340
|
-
})
|
|
329
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
341
330
|
} finally {
|
|
342
331
|
sending.value = false
|
|
343
332
|
}
|
|
@@ -45,6 +45,7 @@ const FRAME_WIDTHS: Record<RenderingView, string> = {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const { t, locale } = useI18n()
|
|
48
|
+
const { presentError } = useApiErrorPresenter()
|
|
48
49
|
|
|
49
50
|
const view = ref<RenderingView>('desktop')
|
|
50
51
|
const rendering = ref<SendRendering | null>(null)
|
|
@@ -99,7 +100,7 @@ async function load(): Promise<void> {
|
|
|
99
100
|
rendering.value = html
|
|
100
101
|
} catch (error) {
|
|
101
102
|
failed.value = true
|
|
102
|
-
|
|
103
|
+
presentError(error, { fallbackKey: `${KEY_PREFIX}error_description` })
|
|
103
104
|
} finally {
|
|
104
105
|
loading.value = false
|
|
105
106
|
}
|
|
@@ -82,6 +82,7 @@ const props = defineProps<SendDrawerProps>()
|
|
|
82
82
|
|
|
83
83
|
const api = useMailingApi()
|
|
84
84
|
const toast = useToast()
|
|
85
|
+
const { presentError } = useApiErrorPresenter()
|
|
85
86
|
const reportReplay = useReplayToast()
|
|
86
87
|
const { confirm } = useConfirm()
|
|
87
88
|
const { open: openModal } = useModal()
|
|
@@ -236,7 +237,7 @@ async function load(): Promise<void> {
|
|
|
236
237
|
try {
|
|
237
238
|
detail.value = await readSend()
|
|
238
239
|
} catch (error) {
|
|
239
|
-
|
|
240
|
+
presentError(error, { fallbackKey: `${KEY_PREFIX}send_unavailable` })
|
|
240
241
|
} finally {
|
|
241
242
|
loading.value = false
|
|
242
243
|
}
|
|
@@ -264,8 +265,8 @@ async function loadTracking(): Promise<void> {
|
|
|
264
265
|
async function showSend(id: string): Promise<void> {
|
|
265
266
|
try {
|
|
266
267
|
await props.showRow?.(id)
|
|
267
|
-
} catch {
|
|
268
|
-
|
|
268
|
+
} catch (error) {
|
|
269
|
+
presentError(error, { fallbackKey: `${KEY_PREFIX}send_unavailable` })
|
|
269
270
|
}
|
|
270
271
|
}
|
|
271
272
|
|
|
@@ -363,7 +364,7 @@ async function replay(): Promise<void> {
|
|
|
363
364
|
)
|
|
364
365
|
settle()
|
|
365
366
|
} catch (error) {
|
|
366
|
-
|
|
367
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
367
368
|
} finally {
|
|
368
369
|
sending.value = false
|
|
369
370
|
}
|
|
@@ -4,11 +4,7 @@ import { useLocalizedDefaults } from '../composables/useLocalizedDefaults'
|
|
|
4
4
|
import { useMailingApi } from '../composables/useMailingApi'
|
|
5
5
|
import { useStarters } from '../composables/useMailingReads'
|
|
6
6
|
import { useTemplateDrawer } from '../composables/useTemplateDrawer'
|
|
7
|
-
import {
|
|
8
|
-
HTTP_CONFLICT,
|
|
9
|
-
apiErrorMessage,
|
|
10
|
-
apiErrorStatus,
|
|
11
|
-
} from '../utils/api-error'
|
|
7
|
+
import { HTTP_CONFLICT, apiErrorStatus } from '../utils/api-error'
|
|
12
8
|
import { editorRoute } from '../utils/editor-route'
|
|
13
9
|
import type { StarterSummary } from '../types/mailing'
|
|
14
10
|
|
|
@@ -19,9 +15,8 @@ const SKELETON_STARTERS = 4
|
|
|
19
15
|
const emit = defineEmits<{ created: [] }>()
|
|
20
16
|
|
|
21
17
|
const api = useMailingApi()
|
|
22
|
-
const
|
|
18
|
+
const { presentError } = useApiErrorPresenter()
|
|
23
19
|
const { t } = useI18n()
|
|
24
|
-
const { processApiMessage } = useTranslation()
|
|
25
20
|
const overlays = useTemplateDrawer()
|
|
26
21
|
|
|
27
22
|
const localized = useLocalizedDefaults()
|
|
@@ -69,10 +64,7 @@ async function createFrom(starter: StarterSummary): Promise<void> {
|
|
|
69
64
|
openNewTemplate(starter)
|
|
70
65
|
return
|
|
71
66
|
}
|
|
72
|
-
|
|
73
|
-
color: 'error',
|
|
74
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
75
|
-
})
|
|
67
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
76
68
|
} finally {
|
|
77
69
|
creatingId.value = null
|
|
78
70
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { useDrawerStats } from '../build/composables/useDrawerStats'
|
|
3
|
+
import TemplateDrawerFooter from '../build/components/TemplateDrawerFooter.vue'
|
|
4
|
+
import TemplateDrawerPerformance from '../build/components/TemplateDrawerPerformance.vue'
|
|
5
|
+
import TemplateDrawerVariables from '../build/components/TemplateDrawerVariables.vue'
|
|
3
6
|
import { useElementSize } from '@vueuse/core'
|
|
4
7
|
import {
|
|
5
8
|
useMailingApi,
|
|
@@ -10,11 +13,9 @@ import {
|
|
|
10
13
|
useTemplatePerformanceRead,
|
|
11
14
|
} from '../composables/useMailingReads'
|
|
12
15
|
import { useTemplateDrawer } from '../composables/useTemplateDrawer'
|
|
13
|
-
import { apiErrorMessage } from '../utils/api-error'
|
|
14
16
|
import { formatCardDate } from '../utils/gallery'
|
|
15
17
|
import { localeCoverage } from '../utils/locales'
|
|
16
18
|
import { sendSnippet } from '../utils/send-snippet'
|
|
17
|
-
import { variableTypeLabelKey } from '../utils/variable-types'
|
|
18
19
|
import { validateName } from '../utils/slug'
|
|
19
20
|
import {
|
|
20
21
|
validTransitions,
|
|
@@ -66,14 +67,8 @@ interface LifecycleSpec {
|
|
|
66
67
|
confirmColor?: ConfirmTone
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
/** The send log reads these: `?template=` narrows it, `?view=` opens a tab. */
|
|
70
|
-
const SENDS_TEMPLATE_KEY = 'template'
|
|
71
|
-
const SENDS_VIEW_KEY = 'view'
|
|
72
|
-
const PROBLEMS_VIEW = 'problems'
|
|
73
70
|
const PREVIEW_WIDTH = 600
|
|
74
71
|
const PREVIEW_FALLBACK_SCALE = 0.72
|
|
75
|
-
const OPEN_RATE_DIGITS = 1
|
|
76
|
-
const PERCENT = 100
|
|
77
72
|
const DEFAULT_VARIABLE_TYPE: VariableType = 'string'
|
|
78
73
|
|
|
79
74
|
const STATUS_TONES: Record<TemplateStatus, string> = {
|
|
@@ -88,9 +83,9 @@ defineEmits<{ success: [changed?: boolean] }>()
|
|
|
88
83
|
|
|
89
84
|
const api = useMailingApi()
|
|
90
85
|
const toast = useToast()
|
|
86
|
+
const { presentError } = useApiErrorPresenter()
|
|
91
87
|
const { confirm } = useConfirm()
|
|
92
88
|
const { t, locale: uiLocale } = useI18n()
|
|
93
|
-
const { processApiMessage } = useTranslation()
|
|
94
89
|
const { uniqueLocales } = useUniqueLocales()
|
|
95
90
|
const overlays = useTemplateDrawer()
|
|
96
91
|
|
|
@@ -142,10 +137,7 @@ async function load(): Promise<void> {
|
|
|
142
137
|
performance.value = stats
|
|
143
138
|
locale.value ||= content.fallbackLocale
|
|
144
139
|
} catch (error) {
|
|
145
|
-
|
|
146
|
-
color: 'error',
|
|
147
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
148
|
-
})
|
|
140
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
149
141
|
} finally {
|
|
150
142
|
loading.value = false
|
|
151
143
|
}
|
|
@@ -261,60 +253,12 @@ const versionLabel = computed(() =>
|
|
|
261
253
|
: t('dms_mailing.templates.drawer.never_published'),
|
|
262
254
|
)
|
|
263
255
|
|
|
264
|
-
const formatNumber = (
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const formatPercent = (value: number) =>
|
|
271
|
-
new Intl.NumberFormat(uiLocale.value, {
|
|
272
|
-
style: 'percent',
|
|
273
|
-
maximumFractionDigits: OPEN_RATE_DIGITS,
|
|
274
|
-
}).format(value / PERCENT)
|
|
275
|
-
|
|
276
|
-
/** The send log narrowed to this template, on one of its tabs if given. */
|
|
277
|
-
function sendLogRoute(view?: string) {
|
|
278
|
-
return {
|
|
279
|
-
path: SENDS_PATH,
|
|
280
|
-
query: {
|
|
281
|
-
[SENDS_TEMPLATE_KEY]: template.value?.slug ?? '',
|
|
282
|
-
...(view ? { [SENDS_VIEW_KEY]: view } : {}),
|
|
283
|
-
},
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function sendLogUrl(view?: string): string {
|
|
288
|
-
const { path, query } = sendLogRoute(view)
|
|
289
|
-
return `${path}?${new URLSearchParams(query).toString()}`
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const hasSends = computed(() => Boolean(performance.value?.sends))
|
|
293
|
-
|
|
294
|
-
const stats = computed(() => [
|
|
295
|
-
{
|
|
296
|
-
id: 'sends',
|
|
297
|
-
eyebrow: t('dms_mailing.templates.drawer.stats_sends'),
|
|
298
|
-
value: formatNumber(performance.value?.sends ?? 0),
|
|
299
|
-
},
|
|
300
|
-
{
|
|
301
|
-
id: 'open',
|
|
302
|
-
eyebrow: t('dms_mailing.templates.drawer.stats_open'),
|
|
303
|
-
value: performance.value?.sends
|
|
304
|
-
? formatPercent(performance.value.openRate)
|
|
305
|
-
: '—',
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
id: 'problems',
|
|
309
|
-
eyebrow: t('dms_mailing.templates.drawer.stats_problems'),
|
|
310
|
-
value: formatNumber(performance.value?.problems ?? 0),
|
|
311
|
-
detail: performance.value?.problems
|
|
312
|
-
? t('dms_mailing.templates.drawer.problems_detail')
|
|
313
|
-
: undefined,
|
|
314
|
-
detailTone: 'error' as const,
|
|
315
|
-
to: performance.value?.problems ? sendLogUrl(PROBLEMS_VIEW) : undefined,
|
|
316
|
-
},
|
|
317
|
-
])
|
|
256
|
+
const { formatNumber, sendLogRoute, hasSends, stats } = useDrawerStats(
|
|
257
|
+
template,
|
|
258
|
+
performance,
|
|
259
|
+
t,
|
|
260
|
+
uiLocale,
|
|
261
|
+
)
|
|
318
262
|
|
|
319
263
|
const snippet = computed(() =>
|
|
320
264
|
sendSnippet({
|
|
@@ -363,10 +307,7 @@ async function saveDetails(changes: Partial<TemplateDetails>): Promise<void> {
|
|
|
363
307
|
})
|
|
364
308
|
reloadInPlace()
|
|
365
309
|
} catch (error) {
|
|
366
|
-
|
|
367
|
-
color: 'error',
|
|
368
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
369
|
-
})
|
|
310
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
370
311
|
}
|
|
371
312
|
}
|
|
372
313
|
|
|
@@ -424,10 +365,7 @@ async function runLifecycle(action: LifecycleAction): Promise<void> {
|
|
|
424
365
|
})
|
|
425
366
|
settle()
|
|
426
367
|
} catch (error) {
|
|
427
|
-
|
|
428
|
-
color: 'error',
|
|
429
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
430
|
-
})
|
|
368
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
431
369
|
}
|
|
432
370
|
}
|
|
433
371
|
|
|
@@ -472,10 +410,7 @@ async function declareVariable(path: string): Promise<void> {
|
|
|
472
410
|
detail.value.variables = response.variables
|
|
473
411
|
detail.value.revision = response.revision
|
|
474
412
|
} catch (error) {
|
|
475
|
-
|
|
476
|
-
color: 'error',
|
|
477
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
478
|
-
})
|
|
413
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
479
414
|
reloadInPlace()
|
|
480
415
|
}
|
|
481
416
|
}
|
|
@@ -499,10 +434,7 @@ async function restoreVersion(version: number): Promise<void> {
|
|
|
499
434
|
],
|
|
500
435
|
})
|
|
501
436
|
} catch (error) {
|
|
502
|
-
|
|
503
|
-
color: 'error',
|
|
504
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
505
|
-
})
|
|
437
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
506
438
|
}
|
|
507
439
|
reloadInPlace()
|
|
508
440
|
}
|
|
@@ -701,172 +633,26 @@ function onResolved(preview: PreviewResponse): void {
|
|
|
701
633
|
</div>
|
|
702
634
|
|
|
703
635
|
<div v-show="tab === 'variables'" class="flex flex-col gap-4 py-4">
|
|
704
|
-
<
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
:key="variable.path"
|
|
714
|
-
class="border-default flex min-h-9 items-center gap-2 border-b last:border-b-0"
|
|
715
|
-
>
|
|
716
|
-
<code
|
|
717
|
-
class="truncate font-mono text-[12px]"
|
|
718
|
-
:class="
|
|
719
|
-
variable.usage === 'unused' ? 'text-dimmed' : 'text-toned'
|
|
720
|
-
"
|
|
721
|
-
>
|
|
722
|
-
{{ variable.path }}
|
|
723
|
-
</code>
|
|
724
|
-
<span
|
|
725
|
-
v-if="variable.required && variable.usage !== 'detected'"
|
|
726
|
-
class="text-error"
|
|
727
|
-
:title="t('dms_mailing.templates.drawer.required')"
|
|
728
|
-
>
|
|
729
|
-
*
|
|
730
|
-
</span>
|
|
731
|
-
<DmsStatusPill
|
|
732
|
-
v-if="variable.usage === 'detected'"
|
|
733
|
-
tone="warning"
|
|
734
|
-
size="sm"
|
|
735
|
-
dot="none"
|
|
736
|
-
:label="t('dms_mailing.templates.drawer.not_declared')"
|
|
737
|
-
/>
|
|
738
|
-
<DmsStatusPill
|
|
739
|
-
v-if="variable.usage === 'unused'"
|
|
740
|
-
tone="neutral"
|
|
741
|
-
size="sm"
|
|
742
|
-
dot="none"
|
|
743
|
-
:label="t('dms_mailing.templates.drawer.unused')"
|
|
744
|
-
/>
|
|
745
|
-
<UButton
|
|
746
|
-
v-if="variable.usage === 'detected' && !isLive"
|
|
747
|
-
class="ml-auto"
|
|
748
|
-
color="neutral"
|
|
749
|
-
variant="ghost"
|
|
750
|
-
size="xs"
|
|
751
|
-
:label="t('dms_mailing.templates.drawer.declare')"
|
|
752
|
-
@click="declareVariable(variable.path)"
|
|
753
|
-
/>
|
|
754
|
-
<span
|
|
755
|
-
v-else-if="variable.usage !== 'detected'"
|
|
756
|
-
class="text-dimmed ml-auto font-mono text-[11px]"
|
|
757
|
-
>
|
|
758
|
-
{{ t(variableTypeLabelKey(variable.type)) }}
|
|
759
|
-
</span>
|
|
760
|
-
</div>
|
|
761
|
-
</div>
|
|
762
|
-
<DmsBanner
|
|
763
|
-
v-if="hasDraftDeclarations"
|
|
764
|
-
tone="info"
|
|
765
|
-
size="sm"
|
|
766
|
-
icon="i-ph-brackets-curly"
|
|
767
|
-
:title="t('dms_mailing.templates.drawer.draft_declarations')"
|
|
768
|
-
:description="
|
|
769
|
-
t('dms_mailing.templates.drawer.draft_declarations_description')
|
|
770
|
-
"
|
|
771
|
-
>
|
|
772
|
-
<template #actions>
|
|
773
|
-
<UButton
|
|
774
|
-
color="neutral"
|
|
775
|
-
variant="outline"
|
|
776
|
-
size="xs"
|
|
777
|
-
:label="t('dms_mailing.templates.actions.open_editor')"
|
|
778
|
-
@click="openEditor()"
|
|
779
|
-
/>
|
|
780
|
-
</template>
|
|
781
|
-
</DmsBanner>
|
|
782
|
-
<MailingSystemVariables :variables="systemVars" />
|
|
783
|
-
<div class="flex flex-col gap-1.5">
|
|
784
|
-
<div class="flex items-center justify-between">
|
|
785
|
-
<DmsEyebrow
|
|
786
|
-
:label="t('dms_mailing.templates.drawer.call_from_code')"
|
|
787
|
-
/>
|
|
788
|
-
<DmsCopyButton :value="snippet" />
|
|
789
|
-
</div>
|
|
790
|
-
<pre
|
|
791
|
-
class="bg-elevated text-toned overflow-x-auto rounded-md p-3 font-mono text-[11.5px] leading-relaxed"
|
|
792
|
-
>{{ snippet }}</pre>
|
|
793
|
-
</div>
|
|
636
|
+
<TemplateDrawerVariables
|
|
637
|
+
:variables="variables"
|
|
638
|
+
:is-live="isLive"
|
|
639
|
+
:has-draft-declarations="hasDraftDeclarations"
|
|
640
|
+
:system-vars="systemVars"
|
|
641
|
+
:snippet="snippet"
|
|
642
|
+
:declare-variable="declareVariable"
|
|
643
|
+
:open-editor="openEditor"
|
|
644
|
+
/>
|
|
794
645
|
</div>
|
|
795
646
|
|
|
796
647
|
<div v-show="tab === 'performance'" class="flex flex-col gap-4 py-4">
|
|
797
|
-
<
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
class="p-0"
|
|
806
|
-
:label="t('dms_mailing.templates.drawer.open_send_log')"
|
|
807
|
-
@click="openSendLog"
|
|
808
|
-
/>
|
|
809
|
-
</div>
|
|
810
|
-
<DmsStatGroup
|
|
811
|
-
layout="joined"
|
|
812
|
-
:columns="3"
|
|
813
|
-
:items="stats"
|
|
814
|
-
:loading="loading"
|
|
815
|
-
/>
|
|
816
|
-
</div>
|
|
817
|
-
<div class="flex flex-col gap-1">
|
|
818
|
-
<DmsEyebrow :label="t('dms_mailing.templates.drawer.called_from')" />
|
|
819
|
-
<p
|
|
820
|
-
v-if="!hasSends || !performance?.sources.length"
|
|
821
|
-
class="text-muted text-[12.5px]"
|
|
822
|
-
>
|
|
823
|
-
{{ t('dms_mailing.templates.drawer.no_sources') }}
|
|
824
|
-
</p>
|
|
825
|
-
<DmsListRow
|
|
826
|
-
v-for="source in performance?.sources ?? []"
|
|
827
|
-
:key="source.source"
|
|
828
|
-
icon="i-ph-code"
|
|
829
|
-
size="sm"
|
|
830
|
-
mono
|
|
831
|
-
:title="
|
|
832
|
-
source.source || t('dms_mailing.templates.drawer.unknown_source')
|
|
833
|
-
"
|
|
834
|
-
:trailing="
|
|
835
|
-
t(
|
|
836
|
-
'dms_mailing.templates.drawer.sends_count',
|
|
837
|
-
{ count: formatNumber(source.count) },
|
|
838
|
-
source.count,
|
|
839
|
-
)
|
|
840
|
-
"
|
|
841
|
-
/>
|
|
842
|
-
</div>
|
|
843
|
-
<div class="flex flex-col gap-1">
|
|
844
|
-
<DmsEyebrow :label="t('dms_mailing.templates.drawer.fallbacks')" />
|
|
845
|
-
<p v-if="!hasSends" class="text-muted text-[12.5px]">
|
|
846
|
-
{{ t('dms_mailing.templates.drawer.no_sources') }}
|
|
847
|
-
</p>
|
|
848
|
-
<p
|
|
849
|
-
v-else-if="!performance?.fallbacks.length"
|
|
850
|
-
class="text-muted text-[12.5px]"
|
|
851
|
-
>
|
|
852
|
-
{{ t('dms_mailing.templates.drawer.no_fallbacks') }}
|
|
853
|
-
</p>
|
|
854
|
-
<DmsListRow
|
|
855
|
-
v-for="fallback in performance?.fallbacks ?? []"
|
|
856
|
-
:key="`${fallback.requested}-${fallback.used}`"
|
|
857
|
-
icon="i-ph-translate"
|
|
858
|
-
tone="warning"
|
|
859
|
-
size="sm"
|
|
860
|
-
:title="`${fallback.requested.toUpperCase()} → ${fallback.used.toUpperCase()}`"
|
|
861
|
-
:trailing="
|
|
862
|
-
t(
|
|
863
|
-
'dms_mailing.templates.drawer.sends_count',
|
|
864
|
-
{ count: formatNumber(fallback.count) },
|
|
865
|
-
fallback.count,
|
|
866
|
-
)
|
|
867
|
-
"
|
|
868
|
-
/>
|
|
869
|
-
</div>
|
|
648
|
+
<TemplateDrawerPerformance
|
|
649
|
+
:stats="stats"
|
|
650
|
+
:loading="loading"
|
|
651
|
+
:has-sends="hasSends"
|
|
652
|
+
:performance="performance"
|
|
653
|
+
:open-send-log="openSendLog"
|
|
654
|
+
:format-number="formatNumber"
|
|
655
|
+
/>
|
|
870
656
|
</div>
|
|
871
657
|
|
|
872
658
|
<div v-if="tab === 'history'" class="flex flex-col gap-2 py-4">
|
|
@@ -882,28 +668,13 @@ function onResolved(preview: PreviewResponse): void {
|
|
|
882
668
|
/>
|
|
883
669
|
</div>
|
|
884
670
|
|
|
885
|
-
<
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
/>
|
|
893
|
-
<UButton
|
|
894
|
-
class="ml-auto"
|
|
895
|
-
icon="i-ph-copy"
|
|
896
|
-
color="neutral"
|
|
897
|
-
variant="outline"
|
|
898
|
-
:label="t('dms_mailing.templates.actions.duplicate')"
|
|
899
|
-
@click="overlays.openDuplicate(templateId, { rowData: template })"
|
|
900
|
-
/>
|
|
901
|
-
<UButton
|
|
902
|
-
icon="i-ph-pencil-simple"
|
|
903
|
-
:label="t('dms_mailing.templates.actions.open_editor')"
|
|
904
|
-
@click="openEditor(locale)"
|
|
905
|
-
/>
|
|
906
|
-
</DmsContainerFooter>
|
|
671
|
+
<TemplateDrawerFooter
|
|
672
|
+
:template-id="templateId"
|
|
673
|
+
:locale="locale"
|
|
674
|
+
:template="template"
|
|
675
|
+
:overlays="overlays"
|
|
676
|
+
:open-editor="openEditor"
|
|
677
|
+
/>
|
|
907
678
|
</template>
|
|
908
679
|
</div>
|
|
909
680
|
</template>
|
|
@@ -4,7 +4,6 @@ import FormRows from '../build/components/FormRows.vue'
|
|
|
4
4
|
import { useMailingApi } from '../composables/useMailingApi'
|
|
5
5
|
import { useTemplateContentRead } from '../composables/useMailingReads'
|
|
6
6
|
import type { SendResult, TestSendInput } from '../composables/useMailingApi'
|
|
7
|
-
import { apiErrorMessage } from '../utils/api-error'
|
|
8
7
|
import { sendLogRoute } from '../utils/send-link'
|
|
9
8
|
import { reasonKey, type SuppressionSummary } from '../utils/suppression'
|
|
10
9
|
import {
|
|
@@ -49,8 +48,8 @@ const emit = defineEmits<{ success: [sent?: boolean] }>()
|
|
|
49
48
|
|
|
50
49
|
const api = useMailingApi()
|
|
51
50
|
const toast = useToast()
|
|
51
|
+
const { presentError } = useApiErrorPresenter()
|
|
52
52
|
const { t, locale: uiLocale } = useI18n()
|
|
53
|
-
const { processApiMessage } = useTranslation()
|
|
54
53
|
const { user } = useCurrentUser()
|
|
55
54
|
const { uniqueLocales } = useUniqueLocales()
|
|
56
55
|
|
|
@@ -70,10 +69,7 @@ onMounted(async () => {
|
|
|
70
69
|
detail.value = await readContent()
|
|
71
70
|
locale.value ||= detail.value.fallbackLocale
|
|
72
71
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
color: 'error',
|
|
75
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
76
|
-
})
|
|
72
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
77
73
|
}
|
|
78
74
|
})
|
|
79
75
|
|
|
@@ -154,14 +150,10 @@ function contentOverride(): Pick<TestSendInput, 'content' | 'version'> {
|
|
|
154
150
|
return {}
|
|
155
151
|
}
|
|
156
152
|
|
|
157
|
-
function reportFailures(results: SendResult[]
|
|
158
|
-
results.forEach((result
|
|
153
|
+
function reportFailures(results: SendResult[]): void {
|
|
154
|
+
results.forEach((result) => {
|
|
159
155
|
if (!result.error) return
|
|
160
|
-
|
|
161
|
-
color: 'error',
|
|
162
|
-
title: to[index] ?? result.sendId,
|
|
163
|
-
description: processApiMessage(result.error),
|
|
164
|
-
})
|
|
156
|
+
presentError(result.error, { fallbackKey: 'error.500.description' })
|
|
165
157
|
})
|
|
166
158
|
const sent = results.filter((result) => !result.error).length
|
|
167
159
|
if (sent) {
|
|
@@ -213,14 +205,11 @@ async function submit(): Promise<void> {
|
|
|
213
205
|
...contentOverride(),
|
|
214
206
|
data: DATA_SETS[dataChoice.value](),
|
|
215
207
|
})
|
|
216
|
-
reportFailures(response.results
|
|
208
|
+
reportFailures(response.results)
|
|
217
209
|
warnOwnSuppression(response.ownAddressSuppressed)
|
|
218
210
|
close(true)
|
|
219
211
|
} catch (error) {
|
|
220
|
-
|
|
221
|
-
color: 'error',
|
|
222
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
223
|
-
})
|
|
212
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
224
213
|
} finally {
|
|
225
214
|
sending.value = false
|
|
226
215
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import type { Component } from 'vue'
|
|
3
3
|
import { useTemplateVersionsRead } from '../composables/useMailingReads'
|
|
4
4
|
import type { TemplateStatus, TemplateVersionSummary } from '../types/mailing'
|
|
5
|
-
import { apiErrorMessage } from '../utils/api-error'
|
|
6
5
|
import {
|
|
7
6
|
changeSummary,
|
|
8
7
|
formatVersionDate,
|
|
@@ -32,10 +31,9 @@ const props = withDefaults(defineProps<VersionHistoryProps>(), {
|
|
|
32
31
|
})
|
|
33
32
|
|
|
34
33
|
const modal = useModal()
|
|
35
|
-
const
|
|
34
|
+
const { presentError } = useApiErrorPresenter()
|
|
36
35
|
const { confirm } = useConfirm()
|
|
37
36
|
const { t, locale: uiLocale } = useI18n()
|
|
38
|
-
const { processApiMessage } = useTranslation()
|
|
39
37
|
const previewComponent = resolveComponent(PREVIEW_COMPONENT) as Component
|
|
40
38
|
|
|
41
39
|
const readVersions = useTemplateVersionsRead(() => props.templateId)
|
|
@@ -47,10 +45,7 @@ async function load(): Promise<void> {
|
|
|
47
45
|
try {
|
|
48
46
|
versions.value = (await readVersions()).versions
|
|
49
47
|
} catch (error) {
|
|
50
|
-
|
|
51
|
-
color: 'error',
|
|
52
|
-
title: processApiMessage(apiErrorMessage(error)),
|
|
53
|
-
})
|
|
48
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
54
49
|
} finally {
|
|
55
50
|
loading.value = false
|
|
56
51
|
}
|
|
@@ -35,6 +35,7 @@ const api = useMailingApi()
|
|
|
35
35
|
const toast = useToast()
|
|
36
36
|
const { t, locale } = useI18n()
|
|
37
37
|
const { dateTimeFormat } = useRegionalFormat()
|
|
38
|
+
const { presentError } = useApiErrorPresenter()
|
|
38
39
|
|
|
39
40
|
const read = useWebhookStatus()
|
|
40
41
|
/** The status read, `null` when the read failed. */
|
|
@@ -84,7 +85,7 @@ async function sendTest(): Promise<void> {
|
|
|
84
85
|
: undefined,
|
|
85
86
|
})
|
|
86
87
|
} catch (error) {
|
|
87
|
-
|
|
88
|
+
presentError(error, { fallbackKey: 'error.500.description' })
|
|
88
89
|
} finally {
|
|
89
90
|
isTesting.value = false
|
|
90
91
|
}
|