@antelopejs/dms-mailing 0.4.0 → 0.5.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 +1 -1
- package/dist/pages/sends.js +2 -2
- package/dist/pages/sends.js.map +1 -1
- package/dist/pages/suppressions.js +2 -2
- package/dist/pages/suppressions.js.map +1 -1
- package/dist/pages/templates.js +3 -3
- package/dist/pages/templates.js.map +1 -1
- package/frontend-vue/app/components/CategoriesInput.vue +7 -14
- package/frontend-vue/app/components/DuplicateModal.vue +9 -3
- package/frontend-vue/app/components/Editor.vue +3 -3
- package/frontend-vue/app/components/NewTemplateModal.vue +9 -3
- package/frontend-vue/app/components/OverviewGate.vue +20 -29
- package/frontend-vue/app/components/PageHeader.vue +8 -12
- package/frontend-vue/app/components/RealSendModal.vue +3 -1
- package/frontend-vue/app/components/RenderingModal.vue +5 -7
- package/frontend-vue/app/components/RetentionInput.vue +13 -23
- package/frontend-vue/app/components/SendDrawer.vue +5 -2
- package/frontend-vue/app/components/StarterGallery.vue +11 -9
- package/frontend-vue/app/components/TemplateCard.vue +19 -4
- package/frontend-vue/app/components/TemplateDrawer.vue +8 -2
- package/frontend-vue/app/components/TemplateGallery.vue +25 -20
- package/frontend-vue/app/components/TemplatePreviewFrame.vue +8 -6
- package/frontend-vue/app/components/TestSendModal.vue +3 -1
- package/frontend-vue/app/components/VersionHistory.vue +3 -3
- package/frontend-vue/app/components/WebhookStatus.vue +20 -17
- package/frontend-vue/app/components/editor/EditorWorkspace.vue +5 -0
- package/frontend-vue/app/composables/useEditorActions.ts +3 -1
- package/frontend-vue/app/composables/useEditorSession.ts +6 -4
- package/frontend-vue/app/composables/useMailingApi.ts +5 -58
- package/frontend-vue/app/composables/useMailingReads.ts +229 -0
- package/frontend-vue/app/composables/useOverviewSetup.ts +15 -7
- package/frontend-vue/app/composables/useTemplateCategories.ts +8 -27
- package/frontend-vue/app/utils/gallery.ts +19 -7
- package/frontend-vue/app/utils/period.ts +0 -5
- package/frontend-vue/package.json +1 -1
- package/frontend-vue/tests/block-components.test.ts +0 -3
- package/frontend-vue/tests/categories-input.test.ts +44 -13
- package/frontend-vue/tests/conditions.test.ts +0 -2
- package/frontend-vue/tests/editor-block-list.test.ts +1 -0
- package/frontend-vue/tests/editor-session.test.ts +5 -0
- package/frontend-vue/tests/footer-block.test.ts +1 -0
- package/frontend-vue/tests/funnel-card.test.ts +1 -0
- package/frontend-vue/tests/gallery.test.ts +16 -0
- package/frontend-vue/tests/helpers/api-data.ts +173 -0
- package/frontend-vue/tests/i18n-editor.test.ts +0 -9
- package/frontend-vue/tests/new-template-modal.test.ts +18 -5
- package/frontend-vue/tests/on-demand-reads.test.ts +81 -0
- package/frontend-vue/tests/overview-gate.test.ts +60 -21
- package/frontend-vue/tests/page-reads.test.ts +157 -0
- package/frontend-vue/tests/period.test.ts +0 -9
- package/frontend-vue/tests/retention-input.test.ts +31 -9
- package/frontend-vue/tests/send-modals.test.ts +9 -1
- package/frontend-vue/tests/shortcuts.test.ts +1 -0
- package/frontend-vue/tests/template-drawer.test.ts +31 -14
- package/frontend-vue/tests/template-gallery.test.ts +46 -24
- package/frontend-vue/tests/variable-path.test.ts +1 -0
- package/frontend-vue/vitest.config.ts +4 -1
- package/package.json +5 -5
- package/src/pages/sends.ts +2 -2
- package/src/pages/suppressions.ts +2 -2
- package/src/pages/templates.ts +3 -3
- package/frontend-vue/tests/smoke.test.ts +0 -8
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useTemplateCategories } from '../composables/useTemplateCategories'
|
|
3
3
|
import { useLocalizedDefaults } from '../composables/useLocalizedDefaults'
|
|
4
4
|
import { useMailingApi } from '../composables/useMailingApi'
|
|
5
|
+
import { useStarters } from '../composables/useMailingReads'
|
|
5
6
|
import { useTemplateDrawer } from '../composables/useTemplateDrawer'
|
|
6
7
|
import {
|
|
7
8
|
HTTP_CONFLICT,
|
|
@@ -24,18 +25,19 @@ const { processApiMessage } = useTranslation()
|
|
|
24
25
|
const overlays = useTemplateDrawer()
|
|
25
26
|
|
|
26
27
|
const localized = useLocalizedDefaults()
|
|
27
|
-
const
|
|
28
|
-
|
|
28
|
+
const starterList = useStarters()
|
|
29
|
+
/** The starters, none when the read failed. */
|
|
30
|
+
const starters = computed(() =>
|
|
31
|
+
localized.starters(
|
|
32
|
+
starterList.error.value ? [] : (starterList.data.value?.starters ?? []),
|
|
33
|
+
),
|
|
34
|
+
)
|
|
29
35
|
const { categories } = useTemplateCategories()
|
|
30
|
-
const loading =
|
|
36
|
+
const loading = computed(
|
|
37
|
+
() => !starterList.data.value && !starterList.error.value,
|
|
38
|
+
)
|
|
31
39
|
const creatingId = ref<string | null>(null)
|
|
32
40
|
|
|
33
|
-
onMounted(async () => {
|
|
34
|
-
const starterList = await api.starters().catch(() => ({ starters: [] }))
|
|
35
|
-
storedStarters.value = starterList.starters
|
|
36
|
-
loading.value = false
|
|
37
|
-
})
|
|
38
|
-
|
|
39
41
|
function knownCategory(id: string): string | undefined {
|
|
40
42
|
return categories.value.some((category) => category.id === id)
|
|
41
43
|
? id
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CARD_DATE_FORMAT,
|
|
4
|
+
formatCardDate,
|
|
5
|
+
formatCardSince,
|
|
6
|
+
firstName,
|
|
7
|
+
} from '../utils/gallery'
|
|
3
8
|
import { localeChipClass, localeChips } from '../utils/locales'
|
|
4
9
|
import {
|
|
5
10
|
cardFooter,
|
|
@@ -47,6 +52,7 @@ const emit = defineEmits<{
|
|
|
47
52
|
}>()
|
|
48
53
|
|
|
49
54
|
const { t, locale } = useI18n()
|
|
55
|
+
const { dateTimeFormat } = useRegionalFormat()
|
|
50
56
|
const { uniqueLocales } = useUniqueLocales()
|
|
51
57
|
|
|
52
58
|
const isLive = computed(() => props.template.status === 'live')
|
|
@@ -85,8 +91,12 @@ const isNeverSent = computed(() => props.attention.includes('never_sent'))
|
|
|
85
91
|
const footer = computed(() => cardFooter(props.template, props.stats))
|
|
86
92
|
|
|
87
93
|
const footerDate = computed(() =>
|
|
88
|
-
formatCardDate(
|
|
89
|
-
|
|
94
|
+
formatCardDate(
|
|
95
|
+
footer.value.at,
|
|
96
|
+
new Date(),
|
|
97
|
+
locale.value,
|
|
98
|
+
(date) => t('dms_mailing.common.on_date', { date }),
|
|
99
|
+
dateTimeFormat(CARD_DATE_FORMAT),
|
|
90
100
|
),
|
|
91
101
|
)
|
|
92
102
|
|
|
@@ -107,7 +117,12 @@ const openRateLabel = computed(() =>
|
|
|
107
117
|
|
|
108
118
|
/** "live for 1 hr" within a day, "live since Sep 27" after. */
|
|
109
119
|
const liveSince = computed(() =>
|
|
110
|
-
formatCardSince(
|
|
120
|
+
formatCardSince(
|
|
121
|
+
footer.value.at,
|
|
122
|
+
new Date(),
|
|
123
|
+
locale.value,
|
|
124
|
+
dateTimeFormat(CARD_DATE_FORMAT),
|
|
125
|
+
),
|
|
111
126
|
)
|
|
112
127
|
|
|
113
128
|
const FOOTER_TEXTS: Partial<Record<CardFooterKind, () => string>> = {
|
|
@@ -5,6 +5,10 @@ import {
|
|
|
5
5
|
useMailingApi,
|
|
6
6
|
type TemplateDetails,
|
|
7
7
|
} from '../composables/useMailingApi'
|
|
8
|
+
import {
|
|
9
|
+
useTemplateContentRead,
|
|
10
|
+
useTemplatePerformanceRead,
|
|
11
|
+
} from '../composables/useMailingReads'
|
|
8
12
|
import { useTemplateDrawer } from '../composables/useTemplateDrawer'
|
|
9
13
|
import { apiErrorMessage } from '../utils/api-error'
|
|
10
14
|
import { formatCardDate } from '../utils/gallery'
|
|
@@ -106,6 +110,8 @@ const LIFECYCLE: Record<LifecycleAction, LifecycleSpec> = {
|
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
const templateId = computed(() => props.rowData?._id ?? props.templateId ?? '')
|
|
113
|
+
const readContent = useTemplateContentRead(templateId)
|
|
114
|
+
const readPerformance = useTemplatePerformanceRead(templateId)
|
|
109
115
|
const detail = ref<TemplateContentResponse | null>(null)
|
|
110
116
|
const performance = ref<TemplatePerformance | null>(null)
|
|
111
117
|
const loading = ref(true)
|
|
@@ -129,8 +135,8 @@ async function load(): Promise<void> {
|
|
|
129
135
|
loading.value = true
|
|
130
136
|
try {
|
|
131
137
|
const [content, stats] = await Promise.all([
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
readContent(),
|
|
139
|
+
readPerformance().catch(() => null),
|
|
134
140
|
])
|
|
135
141
|
detail.value = content
|
|
136
142
|
performance.value = stats
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useTemplateCategories } from '../composables/useTemplateCategories'
|
|
3
3
|
import { useEventListener } from '@vueuse/core'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
useTemplatesOverview,
|
|
6
|
+
useTemplateTotal,
|
|
7
|
+
} from '../composables/useMailingReads'
|
|
5
8
|
import { useTemplateDrawer } from '../composables/useTemplateDrawer'
|
|
6
9
|
import {
|
|
7
10
|
groupByCategory,
|
|
@@ -44,29 +47,33 @@ const ALL_TAB = 'all'
|
|
|
44
47
|
|
|
45
48
|
const props = defineProps<TemplateGalleryProps>()
|
|
46
49
|
|
|
47
|
-
const api = useMailingApi()
|
|
48
50
|
const { t } = useI18n()
|
|
49
51
|
const overlays = useTemplateDrawer()
|
|
50
52
|
|
|
51
53
|
const { categories } = useTemplateCategories()
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
+
const overview = useTemplatesOverview()
|
|
55
|
+
const total = useTemplateTotal()
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
/** Each template's figures; none for a failed read. */
|
|
58
|
+
const statsById = computed(
|
|
59
|
+
() =>
|
|
60
|
+
new Map<string, TemplateStats>(
|
|
61
|
+
(overview.error.value ? [] : (overview.data.value?.items ?? [])).map(
|
|
62
|
+
(entry) => [entry.id, entry],
|
|
63
|
+
),
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
/** How many templates the tenant has; unknown while read or once failed. */
|
|
67
|
+
const templateCount = computed<number | null>(() =>
|
|
68
|
+
total.error.value ? null : (total.data.value?.total ?? null),
|
|
69
|
+
)
|
|
59
70
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
/** The template count and the figures, read again with the rows. */
|
|
72
|
+
function refreshFigures(): void {
|
|
73
|
+
void overview.refresh()
|
|
74
|
+
void total.refresh()
|
|
63
75
|
}
|
|
64
76
|
|
|
65
|
-
onMounted(() => {
|
|
66
|
-
void loadStats()
|
|
67
|
-
void loadTemplates()
|
|
68
|
-
})
|
|
69
|
-
|
|
70
77
|
/** The page of the table on screen: its query and its page index. */
|
|
71
78
|
const viewKeyOf = (): string =>
|
|
72
79
|
JSON.stringify([props.context.query, props.context.pagination.pageIndex])
|
|
@@ -86,8 +93,7 @@ watch(
|
|
|
86
93
|
const isRefresh = viewKey === shownViewKey
|
|
87
94
|
shownViewKey = viewKey
|
|
88
95
|
if (!isRefresh) return
|
|
89
|
-
|
|
90
|
-
void loadTemplates()
|
|
96
|
+
refreshFigures()
|
|
91
97
|
},
|
|
92
98
|
)
|
|
93
99
|
|
|
@@ -157,10 +163,9 @@ const emptyDescription = computed(() =>
|
|
|
157
163
|
: t('dms_mailing.templates.empty.view_description'),
|
|
158
164
|
)
|
|
159
165
|
|
|
166
|
+
/** The rows, and with them the template count and the figures. */
|
|
160
167
|
function refreshAll(): void {
|
|
161
168
|
void props.context.refresh()
|
|
162
|
-
void loadStats()
|
|
163
|
-
void loadTemplates()
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
function openRow(row: TemplateRow): void {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useDebounceFn, useIntersectionObserver } from '@vueuse/core'
|
|
3
3
|
import { useMailingApi } from '../composables/useMailingApi'
|
|
4
|
+
import { useStarterPreviewRead } from '../composables/useMailingReads'
|
|
4
5
|
import type {
|
|
5
6
|
PreviewResponse,
|
|
6
7
|
PreviewVersion,
|
|
@@ -62,6 +63,10 @@ const emit = defineEmits<{ resolved: [PreviewResponse] }>()
|
|
|
62
63
|
const api = useMailingApi()
|
|
63
64
|
const { locale: uiLocale } = useI18n()
|
|
64
65
|
|
|
66
|
+
const readStarterPreview = useStarterPreviewRead(
|
|
67
|
+
() => props.starterId,
|
|
68
|
+
() => props.locale ?? uiLocale.value,
|
|
69
|
+
)
|
|
65
70
|
const fetchedHtml = ref('')
|
|
66
71
|
const loading = ref(false)
|
|
67
72
|
const height = ref(FALLBACK_HEIGHT)
|
|
@@ -86,11 +91,8 @@ useIntersectionObserver(box, ([entry]) => {
|
|
|
86
91
|
if (entry?.isIntersecting) isVisible.value = true
|
|
87
92
|
})
|
|
88
93
|
|
|
89
|
-
async function fetchStarter(
|
|
90
|
-
const preview = await
|
|
91
|
-
starterId,
|
|
92
|
-
props.locale ?? uiLocale.value,
|
|
93
|
-
)
|
|
94
|
+
async function fetchStarter(): Promise<void> {
|
|
95
|
+
const preview = await readStarterPreview()
|
|
94
96
|
fetchedHtml.value = preview.html
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -111,7 +113,7 @@ async function load(): Promise<void> {
|
|
|
111
113
|
loading.value = true
|
|
112
114
|
try {
|
|
113
115
|
await (props.starterId
|
|
114
|
-
? fetchStarter(
|
|
116
|
+
? fetchStarter()
|
|
115
117
|
: fetchTemplate(props.templateId as string))
|
|
116
118
|
} catch {
|
|
117
119
|
fetchedHtml.value = ''
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import FormFieldRow from '../build/components/FormFieldRow.vue'
|
|
3
3
|
import FormRows from '../build/components/FormRows.vue'
|
|
4
4
|
import { useMailingApi } from '../composables/useMailingApi'
|
|
5
|
+
import { useTemplateContentRead } from '../composables/useMailingReads'
|
|
5
6
|
import type { SendResult, TestSendInput } from '../composables/useMailingApi'
|
|
6
7
|
import { apiErrorMessage } from '../utils/api-error'
|
|
7
8
|
import { sendLogRoute } from '../utils/send-link'
|
|
@@ -54,6 +55,7 @@ const { user } = useCurrentUser()
|
|
|
54
55
|
const { uniqueLocales } = useUniqueLocales()
|
|
55
56
|
|
|
56
57
|
const templateId = computed(() => props.rowData?._id ?? props.templateId ?? '')
|
|
58
|
+
const readContent = useTemplateContentRead(templateId)
|
|
57
59
|
const detail = ref<TemplateContentResponse | null>(null)
|
|
58
60
|
const ownEmail = (user.value as SignedInUser | null)?.email
|
|
59
61
|
const recipients = ref<string[]>(ownEmail ? [ownEmail] : [])
|
|
@@ -65,7 +67,7 @@ const sending = ref(false)
|
|
|
65
67
|
onMounted(async () => {
|
|
66
68
|
if (!templateId.value) return
|
|
67
69
|
try {
|
|
68
|
-
detail.value = await
|
|
70
|
+
detail.value = await readContent()
|
|
69
71
|
locale.value ||= detail.value.fallbackLocale
|
|
70
72
|
} catch (error) {
|
|
71
73
|
toast.add({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { Component } from 'vue'
|
|
3
|
-
import {
|
|
3
|
+
import { useTemplateVersionsRead } from '../composables/useMailingReads'
|
|
4
4
|
import type { TemplateStatus, TemplateVersionSummary } from '../types/mailing'
|
|
5
5
|
import { apiErrorMessage } from '../utils/api-error'
|
|
6
6
|
import {
|
|
@@ -31,7 +31,6 @@ const props = withDefaults(defineProps<VersionHistoryProps>(), {
|
|
|
31
31
|
locale: undefined,
|
|
32
32
|
})
|
|
33
33
|
|
|
34
|
-
const api = useMailingApi()
|
|
35
34
|
const modal = useModal()
|
|
36
35
|
const toast = useToast()
|
|
37
36
|
const { confirm } = useConfirm()
|
|
@@ -39,13 +38,14 @@ const { t, locale: uiLocale } = useI18n()
|
|
|
39
38
|
const { processApiMessage } = useTranslation()
|
|
40
39
|
const previewComponent = resolveComponent(PREVIEW_COMPONENT) as Component
|
|
41
40
|
|
|
41
|
+
const readVersions = useTemplateVersionsRead(() => props.templateId)
|
|
42
42
|
const versions = ref<TemplateVersionSummary[]>([])
|
|
43
43
|
const loading = ref(true)
|
|
44
44
|
|
|
45
45
|
async function load(): Promise<void> {
|
|
46
46
|
loading.value = true
|
|
47
47
|
try {
|
|
48
|
-
versions.value = (await
|
|
48
|
+
versions.value = (await readVersions()).versions
|
|
49
49
|
} catch (error) {
|
|
50
50
|
toast.add({
|
|
51
51
|
color: 'error',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useMailingApi } from '../composables/useMailingApi'
|
|
3
|
-
import {
|
|
3
|
+
import { useWebhookStatus } from '../composables/useMailingReads'
|
|
4
|
+
import { CARD_DATE_FORMAT, formatCardDate } from '../utils/gallery'
|
|
4
5
|
import {
|
|
5
6
|
type WebhookLineTone,
|
|
6
7
|
type WebhookStatus,
|
|
@@ -33,9 +34,14 @@ const TONE_CLASSES: Record<WebhookLineTone, string> = {
|
|
|
33
34
|
const api = useMailingApi()
|
|
34
35
|
const toast = useToast()
|
|
35
36
|
const { t, locale } = useI18n()
|
|
37
|
+
const { dateTimeFormat } = useRegionalFormat()
|
|
36
38
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
+
const read = useWebhookStatus()
|
|
40
|
+
/** The status read, `null` when the read failed. */
|
|
41
|
+
const status = computed<WebhookStatus | null>(() =>
|
|
42
|
+
read.error.value ? null : read.data.value,
|
|
43
|
+
)
|
|
44
|
+
const isLoading = computed(() => !read.data.value && !read.error.value)
|
|
39
45
|
const isTesting = ref(false)
|
|
40
46
|
|
|
41
47
|
const lines = computed(() =>
|
|
@@ -51,17 +57,15 @@ function lineText(line: WebhookStatusLine): string {
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
function lineTime(line: WebhookStatusLine): string {
|
|
54
|
-
return line.at
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
isLoading.value = false
|
|
64
|
-
}
|
|
60
|
+
return line.at
|
|
61
|
+
? formatCardDate(
|
|
62
|
+
line.at,
|
|
63
|
+
new Date(),
|
|
64
|
+
locale.value,
|
|
65
|
+
undefined,
|
|
66
|
+
dateTimeFormat(CARD_DATE_FORMAT),
|
|
67
|
+
)
|
|
68
|
+
: ''
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
async function sendTest(): Promise<void> {
|
|
@@ -69,7 +73,8 @@ async function sendTest(): Promise<void> {
|
|
|
69
73
|
isTesting.value = true
|
|
70
74
|
try {
|
|
71
75
|
const result = await api.webhookTest()
|
|
72
|
-
|
|
76
|
+
read.data.value = result.status
|
|
77
|
+
read.error.value = null
|
|
73
78
|
const message = webhookTestMessage(result)
|
|
74
79
|
toast.add({
|
|
75
80
|
color: message.color,
|
|
@@ -84,8 +89,6 @@ async function sendTest(): Promise<void> {
|
|
|
84
89
|
isTesting.value = false
|
|
85
90
|
}
|
|
86
91
|
}
|
|
87
|
-
|
|
88
|
-
onMounted(() => void load())
|
|
89
92
|
</script>
|
|
90
93
|
|
|
91
94
|
<template>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { onMounted, watch } from 'vue'
|
|
3
3
|
import type { TemplateContentResponse } from '../../types/mailing'
|
|
4
4
|
import { useMailingApi } from '../../composables/useMailingApi'
|
|
5
|
+
import { useEditorReads } from '../../composables/useMailingReads'
|
|
5
6
|
import { provideEditorContext } from '../../composables/useEditorContext'
|
|
6
7
|
import { useEditorActions } from '../../composables/useEditorActions'
|
|
7
8
|
import { useEditorSession } from '../../composables/useEditorSession'
|
|
@@ -49,9 +50,12 @@ function reportFailure(error: unknown): void {
|
|
|
49
50
|
})
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
const reads = useEditorReads(props.templateId)
|
|
54
|
+
|
|
52
55
|
const session = useEditorSession({
|
|
53
56
|
editor,
|
|
54
57
|
api,
|
|
58
|
+
reads,
|
|
55
59
|
templateId: props.templateId,
|
|
56
60
|
loaded: props.loaded,
|
|
57
61
|
onError: reportFailure,
|
|
@@ -61,6 +65,7 @@ const actions = useEditorActions({
|
|
|
61
65
|
editor,
|
|
62
66
|
session,
|
|
63
67
|
api,
|
|
68
|
+
reads,
|
|
64
69
|
templateId: props.templateId,
|
|
65
70
|
reload: props.reload,
|
|
66
71
|
onError: reportFailure,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Component } from 'vue'
|
|
2
2
|
import type { MailingApi } from './useMailingApi'
|
|
3
|
+
import type { EditorReads } from './useMailingReads'
|
|
3
4
|
import type { EditorSession } from './useEditorSession'
|
|
4
5
|
import { isDraftConflict } from './useEditorSession'
|
|
5
6
|
import type { EditorState } from './useTemplateEditor'
|
|
@@ -34,6 +35,7 @@ export interface ActionDeps {
|
|
|
34
35
|
editor: EditorState
|
|
35
36
|
session: EditorSession
|
|
36
37
|
api: MailingApi
|
|
38
|
+
reads: EditorReads
|
|
37
39
|
templateId: string
|
|
38
40
|
reload: () => Promise<void>
|
|
39
41
|
onError: (error: unknown) => void
|
|
@@ -163,7 +165,7 @@ async function runPublish(deps: ActionDeps, tools: ActionTools): Promise<void> {
|
|
|
163
165
|
* editor's last write is a conflict: the review would not show what ships.
|
|
164
166
|
*/
|
|
165
167
|
async function refreshChanges(deps: ActionDeps): Promise<boolean> {
|
|
166
|
-
const response = await deps.
|
|
168
|
+
const response = await deps.reads.changes().catch(() => null)
|
|
167
169
|
if (!response) return true
|
|
168
170
|
const { state } = deps.session
|
|
169
171
|
if (response.revision !== state.revision) {
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
TemplateRow,
|
|
9
9
|
} from '../types/mailing'
|
|
10
10
|
import type { MailingApi, TemplateDetails } from './useMailingApi'
|
|
11
|
+
import type { EditorReads } from './useMailingReads'
|
|
11
12
|
import type { EditorState } from './useTemplateEditor'
|
|
12
13
|
import type { TokenFieldHandle } from './useEditorContext'
|
|
13
14
|
import { deepClone } from '../utils/clone'
|
|
@@ -64,6 +65,7 @@ export interface SessionState {
|
|
|
64
65
|
export interface SessionDeps {
|
|
65
66
|
editor: EditorState
|
|
66
67
|
api: MailingApi
|
|
68
|
+
reads: EditorReads
|
|
67
69
|
templateId: string
|
|
68
70
|
loaded: TemplateContentResponse
|
|
69
71
|
onError: (error: unknown) => void
|
|
@@ -142,7 +144,7 @@ export function retryDelay(failures: number): number {
|
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
function createSaver(deps: SessionDeps, state: SessionState) {
|
|
145
|
-
const { editor, api, templateId, onError } = deps
|
|
147
|
+
const { editor, api, reads, templateId, onError } = deps
|
|
146
148
|
let timer: ReturnType<typeof setTimeout> | null = null
|
|
147
149
|
let inFlight: Promise<boolean> | null = null
|
|
148
150
|
let failures = 0
|
|
@@ -207,7 +209,7 @@ function createSaver(deps: SessionDeps, state: SessionState) {
|
|
|
207
209
|
async function overwrite(): Promise<boolean> {
|
|
208
210
|
cancel()
|
|
209
211
|
if (inFlight) await inFlight
|
|
210
|
-
const latest = await
|
|
212
|
+
const latest = await reads.changes().catch(() => null)
|
|
211
213
|
if (!latest) return false
|
|
212
214
|
state.revision = latest.revision
|
|
213
215
|
return run()
|
|
@@ -249,7 +251,7 @@ function createSaver(deps: SessionDeps, state: SessionState) {
|
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
function createRecordActions(
|
|
252
|
-
{ api, templateId }: SessionDeps,
|
|
254
|
+
{ api, reads, templateId }: SessionDeps,
|
|
253
255
|
state: SessionState,
|
|
254
256
|
) {
|
|
255
257
|
return {
|
|
@@ -274,7 +276,7 @@ function createRecordActions(
|
|
|
274
276
|
state.testDataSource = state.savedTestDataSource
|
|
275
277
|
},
|
|
276
278
|
async loadPerformance(): Promise<void> {
|
|
277
|
-
state.performance = await
|
|
279
|
+
state.performance = await reads.performance().catch(() => null)
|
|
278
280
|
},
|
|
279
281
|
}
|
|
280
282
|
}
|
|
@@ -2,29 +2,22 @@ import type {
|
|
|
2
2
|
FunnelStep,
|
|
3
3
|
PreviewResponse,
|
|
4
4
|
PreviewVersion,
|
|
5
|
-
ProviderInfo,
|
|
6
|
-
RetentionPreview,
|
|
7
|
-
SendDetailResponse,
|
|
8
|
-
SendHtmlResponse,
|
|
9
5
|
StarterSummary,
|
|
10
6
|
SuppressionSummary,
|
|
11
7
|
TemplateChange,
|
|
12
8
|
TemplateContent,
|
|
13
9
|
TemplateCategory,
|
|
14
|
-
TemplateContentResponse,
|
|
15
|
-
TemplatePerformance,
|
|
16
10
|
TemplateRow,
|
|
17
11
|
TemplateStats,
|
|
18
12
|
TemplateVersionSummary,
|
|
19
13
|
VariableDefinition,
|
|
20
14
|
} from '../types/mailing'
|
|
21
|
-
import type {
|
|
22
|
-
import type { WebhookStatus, WebhookTestResult } from '../utils/webhook-status'
|
|
15
|
+
import type { WebhookTestResult } from '../utils/webhook-status'
|
|
23
16
|
|
|
24
17
|
const BASE = '/api/mailing'
|
|
25
18
|
|
|
26
19
|
/** Far above any real tenant's template count; the data-api caps at 100. */
|
|
27
|
-
const TEMPLATE_LIST_LIMIT = '100'
|
|
20
|
+
export const TEMPLATE_LIST_LIMIT = '100'
|
|
28
21
|
|
|
29
22
|
/**
|
|
30
23
|
* Joins the module's API segments. Exported on its own so the path shape stays
|
|
@@ -187,8 +180,9 @@ export interface StarterPreviewResponse {
|
|
|
187
180
|
}
|
|
188
181
|
|
|
189
182
|
/**
|
|
190
|
-
* The
|
|
191
|
-
*
|
|
183
|
+
* The mailing endpoints a component writes to, and the previews it renders
|
|
184
|
+
* from a draft it sends. Every read goes through `useMailingReads`, with the
|
|
185
|
+
* same paths.
|
|
192
186
|
*/
|
|
193
187
|
export function useMailingApi() {
|
|
194
188
|
const { $authFetch } = useAuthFetch()
|
|
@@ -196,29 +190,14 @@ export function useMailingApi() {
|
|
|
196
190
|
$authFetch<T>(path, { method: 'POST', body })
|
|
197
191
|
|
|
198
192
|
return {
|
|
199
|
-
provider: () => $authFetch<ProviderInfo>(mailingPath('provider')),
|
|
200
|
-
setupFacts: () => $authFetch<SetupFacts>(mailingPath('metrics', 'setup')),
|
|
201
|
-
webhookStatus: () =>
|
|
202
|
-
$authFetch<WebhookStatus>(mailingPath('settings', 'webhook-status')),
|
|
203
193
|
webhookTest: () =>
|
|
204
194
|
post<WebhookTestResult>(mailingPath('settings', 'webhook-test'), {}),
|
|
205
|
-
templateContent: (id: string) =>
|
|
206
|
-
$authFetch<TemplateContentResponse>(
|
|
207
|
-
mailingPath('templates', id, 'content'),
|
|
208
|
-
),
|
|
209
195
|
saveTemplateDetails: (id: string, details: TemplateDetails) =>
|
|
210
196
|
$authFetch<unknown>(mailingPath('tables', 'templates', 'edit'), {
|
|
211
197
|
method: 'PUT',
|
|
212
198
|
query: { id },
|
|
213
199
|
body: details,
|
|
214
200
|
}),
|
|
215
|
-
listCategories: () =>
|
|
216
|
-
$authFetch<CategoriesResponse>(mailingPath('templates', 'categories')),
|
|
217
|
-
listTemplates: () =>
|
|
218
|
-
$authFetch<TemplateListResponse>(
|
|
219
|
-
mailingPath('tables', 'templates', 'list'),
|
|
220
|
-
{ query: { limit: TEMPLATE_LIST_LIMIT, sortKey: 'name' } },
|
|
221
|
-
),
|
|
222
201
|
createTemplate: (input: CreateTemplateInput) =>
|
|
223
202
|
post<string[]>(mailingPath('tables', 'templates', 'new'), input),
|
|
224
203
|
saveContent: (id: string, input: SaveContentInput) =>
|
|
@@ -231,30 +210,11 @@ export function useMailingApi() {
|
|
|
231
210
|
},
|
|
232
211
|
keepalive: input.keepalive,
|
|
233
212
|
}),
|
|
234
|
-
changes: (id: string) =>
|
|
235
|
-
$authFetch<ChangesResponse>(mailingPath('templates', id, 'changes')),
|
|
236
|
-
versions: (id: string) =>
|
|
237
|
-
$authFetch<VersionsResponse>(mailingPath('templates', id, 'versions')),
|
|
238
213
|
restoreVersion: (id: string, version: number, revision?: number) =>
|
|
239
214
|
post<RestoreVersionResponse>(
|
|
240
215
|
mailingPath('templates', id, 'versions', String(version), 'restore'),
|
|
241
216
|
{ revision },
|
|
242
217
|
),
|
|
243
|
-
performance: (id: string) =>
|
|
244
|
-
$authFetch<TemplatePerformance>(
|
|
245
|
-
mailingPath('templates', id, 'performance'),
|
|
246
|
-
),
|
|
247
|
-
templatesOverview: () =>
|
|
248
|
-
$authFetch<TemplateOverviewResponse>(
|
|
249
|
-
mailingPath('templates', 'overview'),
|
|
250
|
-
),
|
|
251
|
-
starters: () =>
|
|
252
|
-
$authFetch<StartersResponse>(mailingPath('templates', 'starters')),
|
|
253
|
-
starterPreview: (starterId: string, locale?: string) =>
|
|
254
|
-
$authFetch<StarterPreviewResponse>(
|
|
255
|
-
mailingPath('templates', 'starters', starterId, 'preview'),
|
|
256
|
-
{ query: locale ? { locale } : {} },
|
|
257
|
-
),
|
|
258
218
|
publish: (id: string, revision?: number) =>
|
|
259
219
|
post<PublishResponse>(mailingPath('templates', id, 'publish'), {
|
|
260
220
|
revision,
|
|
@@ -291,21 +251,8 @@ export function useMailingApi() {
|
|
|
291
251
|
post<TestSendResponse>(mailingPath('templates', id, 'send'), input),
|
|
292
252
|
testSend: (id: string, input: TestSendInput) =>
|
|
293
253
|
post<TestSendResponse>(mailingPath('templates', id, 'test-send'), input),
|
|
294
|
-
send: (id: string) =>
|
|
295
|
-
$authFetch<SendDetailResponse>(mailingPath('sends', id)),
|
|
296
|
-
sendHtml: (id: string) =>
|
|
297
|
-
$authFetch<SendHtmlResponse>(mailingPath('sends', id, 'html')),
|
|
298
254
|
replay: (id: string, to?: string) =>
|
|
299
255
|
post<SendResult>(mailingPath('sends', id, 'replay'), to ? { to } : {}),
|
|
300
|
-
retentionPreview: (days: number) =>
|
|
301
|
-
$authFetch<RetentionPreview>(
|
|
302
|
-
mailingPath('settings', 'retention-preview'),
|
|
303
|
-
{ query: { days: String(days) } },
|
|
304
|
-
),
|
|
305
|
-
categoryUsage: () =>
|
|
306
|
-
$authFetch<CategoryUsageResponse>(
|
|
307
|
-
mailingPath('settings', 'category-usage'),
|
|
308
|
-
),
|
|
309
256
|
}
|
|
310
257
|
}
|
|
311
258
|
|