@dev.smartpricing/message-composer-layer 1.0.12 → 1.0.14
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/app/components/Composer/BrandList.vue +190 -0
- package/app/components/Composer/EmailComposer.vue +501 -0
- package/app/components/Composer/EmailContent.vue +95 -0
- package/app/components/Composer/EmailTranslations.vue +70 -0
- package/app/components/Composer/ImageGallery.vue +141 -0
- package/app/components/Composer/MessageLibrary.vue +155 -0
- package/app/components/Composer/WhatsappComposer.vue +424 -0
- package/app/components/Composer/WhatsappContent.vue +88 -0
- package/app/components/Composer/WhatsappTranslations.vue +68 -0
- package/app/components/CreateTemplateButton.vue +1 -1
- package/app/components/Layout/BasePage.vue +34 -34
- package/app/composables/useGoBack.ts +1 -1
- package/app/layouts/default.vue +3 -3
- package/app/middleware/channel-guard.ts +3 -6
- package/app/pages/brands.vue +7 -176
- package/app/pages/images.vue +5 -129
- package/app/pages/index.vue +6 -138
- package/app/pages/preview/[id].vue +1 -0
- package/app/pages/template/email/[id].vue +17 -545
- package/app/pages/template/email/create.vue +15 -514
- package/app/pages/template/whatsapp/[id].vue +22 -509
- package/app/pages/template/whatsapp/create.vue +15 -426
- package/app/stores/emailComposer.ts +188 -0
- package/app/stores/whatsappComposer.ts +173 -0
- package/app/types/page-meta.d.ts +7 -0
- package/package.json +2 -2
|
@@ -2,413 +2,12 @@
|
|
|
2
2
|
definePageMeta({
|
|
3
3
|
name: 'email-message-edit',
|
|
4
4
|
middleware: ['channel-guard'],
|
|
5
|
+
channel: 'email',
|
|
5
6
|
})
|
|
6
7
|
|
|
7
|
-
import EmailEditor from '@/components/Email/Editor/Index.vue'
|
|
8
|
-
import TranslationEditor from '@/components/Email/TranslationEditor.vue'
|
|
9
|
-
import IncompleteTranslationsDialog from '@/components/Common/IncompleteTranslationsDialog.vue'
|
|
10
|
-
import LanguageList from '@/components/Common/LanguageList.vue'
|
|
11
|
-
import { computed, ref, watch, onUnmounted } from 'vue'
|
|
12
|
-
import type {
|
|
13
|
-
MessageGroupCategory,
|
|
14
|
-
EmailMessage,
|
|
15
|
-
PostEntity,
|
|
16
|
-
} from '@dev.smartpricing/message-composer-utils/types'
|
|
17
|
-
import { syncLayoutFromBase } from '@dev.smartpricing/message-composer-utils/utils'
|
|
18
|
-
import { useRoute } from 'vue-router'
|
|
19
|
-
import { useMessageGroupQuery, useLanguagesQuery } from '~/queries'
|
|
20
|
-
import { useAppContextStore } from '@/stores/appContext'
|
|
21
|
-
const { trackEvent } = useTracking()
|
|
22
8
|
const route = useRoute()
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const language = ref(mainLanguage.value)
|
|
27
|
-
const targetLanguage = ref(mainLanguage.value)
|
|
28
|
-
const activeTab = ref<'content' | 'translations'>('content')
|
|
29
|
-
const { goBackOrFallback } = useGoBack()
|
|
30
|
-
const emailEditorStore = useEmailEditorStore()
|
|
31
|
-
|
|
32
|
-
const messages = ref<Record<string, EmailMessage>>({})
|
|
33
|
-
const { data: messageGroup, isPending: isFetching } = useMessageGroupQuery(
|
|
34
|
-
route.params.id as string,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
// Store original messageGroup values
|
|
38
|
-
const originalMessageGroupName = ref<string>('')
|
|
39
|
-
const originalMessageGroupCategory = ref<MessageGroupCategory>('marketing')
|
|
40
|
-
|
|
41
|
-
// Initialize change tracking
|
|
42
|
-
const messageGroupIdRef = computed(() => (messageGroup.value?.id || route.params.id) as string)
|
|
43
|
-
const changeTracking = useChangeTrackingEmail(messageGroupIdRef)
|
|
44
|
-
|
|
45
|
-
// Watch for data changes to process messages
|
|
46
|
-
watch(
|
|
47
|
-
messageGroup,
|
|
48
|
-
(newValue) => {
|
|
49
|
-
if (!newValue?.messages) return
|
|
50
|
-
|
|
51
|
-
// Store original messageGroup metadata
|
|
52
|
-
originalMessageGroupName.value = newValue.name
|
|
53
|
-
originalMessageGroupCategory.value = newValue.category
|
|
54
|
-
|
|
55
|
-
for (const message of newValue.messages) {
|
|
56
|
-
if (message.render_type === 'email_v1') {
|
|
57
|
-
// Convert MessageFromDb to EmailMessage (remove DB timestamps)
|
|
58
|
-
const { created_at, updated_at, ...emailMessage } = message
|
|
59
|
-
messages.value[message.language_id] = emailMessage as EmailMessage
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Record original states for change tracking
|
|
64
|
-
for (const [langId, message] of Object.entries(messages.value)) {
|
|
65
|
-
changeTracking.recordOriginalState(langId, message)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const resolved = resolveDefaultLanguage(messages.value, mainLanguage.value)
|
|
69
|
-
if (resolved && resolved !== mainLanguage.value) {
|
|
70
|
-
language.value = resolved
|
|
71
|
-
targetLanguage.value = resolved
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Load current language into store
|
|
75
|
-
const currentMessage = messages.value[language.value]
|
|
76
|
-
if (currentMessage) {
|
|
77
|
-
emailEditorStore.loadFromData(currentMessage)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Restore brand (null if no brand was saved)
|
|
81
|
-
emailEditorStore.initBrand(newValue.brand_id ?? null)
|
|
82
|
-
|
|
83
|
-
// Set initial target language for translations tab (first non-base language)
|
|
84
|
-
const languages = Object.keys(messages.value)
|
|
85
|
-
if (languages.length > 1) {
|
|
86
|
-
targetLanguage.value = languages.find((l) => l !== language.value) || language.value
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
{ immediate: true },
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
// Watch language changes to load the correct message
|
|
93
|
-
watch(language, (newLang) => {
|
|
94
|
-
const message = messages.value[newLang]
|
|
95
|
-
if (message) {
|
|
96
|
-
emailEditorStore.loadFromData(message)
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
// Sync all languages when switching to translations tab
|
|
101
|
-
watch(activeTab, (newTab) => {
|
|
102
|
-
if (newTab === 'translations') {
|
|
103
|
-
const baseMessage = messages.value[language.value]
|
|
104
|
-
if (!baseMessage || baseMessage.render_type !== 'email_v1') return
|
|
105
|
-
|
|
106
|
-
for (const [langId, message] of Object.entries(messages.value)) {
|
|
107
|
-
if (langId !== language.value && message.render_type === 'email_v1') {
|
|
108
|
-
message.body.blocks = syncLayoutFromBase(baseMessage.body.blocks, message.body.blocks)
|
|
109
|
-
message.body.settings = { ...baseMessage.body.settings }
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
// Sync store changes back to messages
|
|
116
|
-
watch(
|
|
117
|
-
() => emailEditorStore.blocks,
|
|
118
|
-
() => {
|
|
119
|
-
const currentMessage = messages.value[language.value]
|
|
120
|
-
if (currentMessage) {
|
|
121
|
-
const editorData = emailEditorStore.toJSON()
|
|
122
|
-
currentMessage.body.blocks = editorData.blocks
|
|
123
|
-
currentMessage.body.settings = editorData.settings
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
{ deep: true },
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
// Cleanup on unmount
|
|
130
|
-
onUnmounted(() => {
|
|
131
|
-
emailEditorStore.resetEditor()
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
const { formState, draftSchema, validationErrors, validateDraft, validateFull, clearErrors } =
|
|
135
|
-
useTemplateFormValidation({
|
|
136
|
-
type: 'email',
|
|
137
|
-
name: computed(() => messageGroup.value?.name ?? ''),
|
|
138
|
-
category: computed(() => messageGroup.value?.category ?? 'marketing'),
|
|
139
|
-
messages,
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
const { saving, updateTemplate } = useTemplateOperations({
|
|
143
|
-
onSuccess: () => {
|
|
144
|
-
saved.value = true
|
|
145
|
-
goBackOrFallback()
|
|
146
|
-
},
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
const saved = ref(false)
|
|
150
|
-
|
|
151
|
-
const hasAnyChanges = computed(() => {
|
|
152
|
-
// Check if message group name or category changed
|
|
153
|
-
if (messageGroup.value) {
|
|
154
|
-
const nameChanged = messageGroup.value.name !== originalMessageGroupName.value
|
|
155
|
-
const categoryChanged = messageGroup.value.category !== originalMessageGroupCategory.value
|
|
156
|
-
if (nameChanged || categoryChanged) return true
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// For now, always return true if messages exist (simplified change tracking)
|
|
160
|
-
// TODO: Implement proper change tracking for email messages
|
|
161
|
-
return Object.keys(messages.value).length > 0
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
useExitConfirmation(computed(() => hasAnyChanges.value && !saved.value))
|
|
165
|
-
|
|
166
|
-
// Get languages for incomplete translations check
|
|
167
|
-
const { data: languages } = useLanguagesQuery()
|
|
168
|
-
|
|
169
|
-
// Check for incomplete translations
|
|
170
|
-
const { incompleteLanguages, hasIncompleteTranslations } = useIncompleteTranslationsEmail({
|
|
171
|
-
messages,
|
|
172
|
-
languages,
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
// Dialog state and pending action
|
|
176
|
-
const showIncompleteDialog = ref(false)
|
|
177
|
-
const pendingAction = ref<'save_draft' | 'save_message' | null>(null)
|
|
178
|
-
|
|
179
|
-
// Auto-translate composable
|
|
180
|
-
const { translateMessage, isTranslating } = useAutoTranslateEmail()
|
|
181
|
-
|
|
182
|
-
// ── Tracking helpers ──
|
|
183
|
-
function getEmailBaseProps() {
|
|
184
|
-
return {
|
|
185
|
-
name: messageGroup.value?.name ?? '',
|
|
186
|
-
subject: messages.value[language.value]?.body?.subject ?? '',
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function getEmailDraftProps() {
|
|
191
|
-
return {
|
|
192
|
-
...getEmailBaseProps(),
|
|
193
|
-
languages: Object.keys(messages.value),
|
|
194
|
-
widgets: emailEditorStore.blocks.map((b) => b.type),
|
|
195
|
-
is_internal: messageGroup.value?.visibility === 'internal',
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
watch(activeTab, (tab) => {
|
|
200
|
-
trackEvent('email_edit.change_tab', { ...getEmailBaseProps(), tab })
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
function onAddLanguage(lang: string) {
|
|
204
|
-
trackEvent('email_edit.add_language', { ...getEmailBaseProps(), language: lang })
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function onRemoveLanguage(lang: string) {
|
|
208
|
-
trackEvent('email_edit.remove_language', {
|
|
209
|
-
...getEmailBaseProps(),
|
|
210
|
-
language: lang,
|
|
211
|
-
})
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function onAutoTranslateTracked(lang: string) {
|
|
215
|
-
trackEvent('email_edit.auto_translate', {
|
|
216
|
-
...getEmailBaseProps(),
|
|
217
|
-
language: lang,
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function onPreview(lang: string) {
|
|
222
|
-
trackEvent('email_edit.preview', { ...getEmailBaseProps(), language: lang })
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function onFiltersChanged(payload: {
|
|
226
|
-
language: string
|
|
227
|
-
search_term: string
|
|
228
|
-
show_only_missing: boolean
|
|
229
|
-
show_only_changes: boolean
|
|
230
|
-
}) {
|
|
231
|
-
trackEvent('email_edit.translations_filters', { ...getEmailBaseProps(), ...payload })
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function onBlockAdded(payload: { type: string; adding_mode: string }) {
|
|
235
|
-
trackEvent('email_edit.add_widget', {
|
|
236
|
-
...getEmailBaseProps(),
|
|
237
|
-
widget_type: payload.type,
|
|
238
|
-
adding_mode: payload.adding_mode,
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function onBlockDuplicated(payload: { type: string }) {
|
|
243
|
-
trackEvent('email_edit.duplicate_widget', {
|
|
244
|
-
...getEmailBaseProps(),
|
|
245
|
-
widget_type: payload.type,
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function onBlockDeleted(payload: { type: string }) {
|
|
250
|
-
trackEvent('email_edit.remove_widget', {
|
|
251
|
-
...getEmailBaseProps(),
|
|
252
|
-
widget_type: payload.type,
|
|
253
|
-
})
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function onBlockSorted(payload: {
|
|
257
|
-
type: string
|
|
258
|
-
before_sort_position: number
|
|
259
|
-
new_position: number
|
|
260
|
-
}) {
|
|
261
|
-
trackEvent('email_edit.sort_widget', {
|
|
262
|
-
...getEmailBaseProps(),
|
|
263
|
-
widget_type: payload.type,
|
|
264
|
-
before_sort_position: payload.before_sort_position,
|
|
265
|
-
new_position: payload.new_position,
|
|
266
|
-
})
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function onWidgetTabChanged(tab: string) {
|
|
270
|
-
trackEvent('email_edit.change_widget_tab', { ...getEmailBaseProps(), tab })
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function onTestSent(payload: { to_recipients: number }, lang?: string) {
|
|
274
|
-
trackEvent('email_edit.send_test', {
|
|
275
|
-
...getEmailBaseProps(),
|
|
276
|
-
widgets: emailEditorStore.blocks.map((b) => b.type),
|
|
277
|
-
to_recipients: payload.to_recipients,
|
|
278
|
-
language: lang ?? language.value,
|
|
279
|
-
})
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Sync all languages from base before save
|
|
283
|
-
function syncAllLanguagesBeforeSave() {
|
|
284
|
-
const baseMessage = messages.value[language.value]
|
|
285
|
-
if (!baseMessage || baseMessage.render_type !== 'email_v1') return
|
|
286
|
-
|
|
287
|
-
for (const [langId, message] of Object.entries(messages.value)) {
|
|
288
|
-
if (langId !== language.value && message.render_type === 'email_v1') {
|
|
289
|
-
message.body.blocks = syncLayoutFromBase(baseMessage.body.blocks, message.body.blocks)
|
|
290
|
-
message.body.settings = { ...baseMessage.body.settings }
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async function saveAsDraft() {
|
|
296
|
-
if (!messageGroup.value) return
|
|
297
|
-
trackEvent('email_edit.save_draft', getEmailDraftProps())
|
|
298
|
-
|
|
299
|
-
syncAllLanguagesBeforeSave()
|
|
300
|
-
|
|
301
|
-
const templateData = {
|
|
302
|
-
messageGroup: {
|
|
303
|
-
name: messageGroup.value.name,
|
|
304
|
-
category: messageGroup.value.category,
|
|
305
|
-
brand_id: emailEditorStore.brandId,
|
|
306
|
-
},
|
|
307
|
-
messages: messages.value,
|
|
308
|
-
}
|
|
309
|
-
await updateTemplate(messageGroup.value.id.toString(), templateData, 'draft')
|
|
310
|
-
notifyParent('TEMPLATE_SAVED_DRAFT', { id: messageGroup.value.id, name: messageGroup.value.name })
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async function saveTemplate() {
|
|
314
|
-
if (!messageGroup.value) return
|
|
315
|
-
trackEvent('email_edit.save', getEmailDraftProps())
|
|
316
|
-
|
|
317
|
-
syncAllLanguagesBeforeSave()
|
|
318
|
-
|
|
319
|
-
const templateData = {
|
|
320
|
-
messageGroup: {
|
|
321
|
-
name: messageGroup.value.name,
|
|
322
|
-
category: messageGroup.value.category,
|
|
323
|
-
brand_id: emailEditorStore.brandId,
|
|
324
|
-
},
|
|
325
|
-
messages: messages.value,
|
|
326
|
-
}
|
|
327
|
-
await updateTemplate(messageGroup.value.id.toString(), templateData, 'approved')
|
|
328
|
-
notifyParent('TEMPLATE_SAVED', {
|
|
329
|
-
id: messageGroup.value.id,
|
|
330
|
-
name: messageGroup.value.name,
|
|
331
|
-
status: 'approved',
|
|
332
|
-
})
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Wrapper functions to validate then check for incomplete translations
|
|
336
|
-
async function handleSaveAsDraft() {
|
|
337
|
-
if (!(await validateDraft())) return
|
|
338
|
-
if (hasIncompleteTranslations.value) {
|
|
339
|
-
pendingAction.value = 'save_draft'
|
|
340
|
-
showIncompleteDialog.value = true
|
|
341
|
-
} else {
|
|
342
|
-
saveAsDraft()
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async function handleSaveTemplate() {
|
|
347
|
-
if (!(await validateFull())) return
|
|
348
|
-
if (hasIncompleteTranslations.value) {
|
|
349
|
-
pendingAction.value = 'save_message'
|
|
350
|
-
showIncompleteDialog.value = true
|
|
351
|
-
} else {
|
|
352
|
-
saveTemplate()
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Proceed with save without translating
|
|
357
|
-
function proceedWithSave() {
|
|
358
|
-
if (pendingAction.value === 'save_draft') {
|
|
359
|
-
saveAsDraft()
|
|
360
|
-
} else if (pendingAction.value === 'save_message') {
|
|
361
|
-
saveTemplate()
|
|
362
|
-
}
|
|
363
|
-
pendingAction.value = null
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Go to translations tab
|
|
367
|
-
function goToTranslations() {
|
|
368
|
-
activeTab.value = 'translations'
|
|
369
|
-
pendingAction.value = null
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Translate automatically and save
|
|
373
|
-
async function translateAndSave() {
|
|
374
|
-
if (!incompleteLanguages.value.length) {
|
|
375
|
-
proceedWithSave()
|
|
376
|
-
return
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const baseMessage = messages.value[language.value]
|
|
380
|
-
if (!baseMessage) {
|
|
381
|
-
proceedWithSave()
|
|
382
|
-
return
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Translate all incomplete languages
|
|
386
|
-
for (const { language: lang } of incompleteLanguages.value) {
|
|
387
|
-
const targetMessage = messages.value[lang.id]
|
|
388
|
-
if (!targetMessage) continue
|
|
389
|
-
|
|
390
|
-
await translateMessage(
|
|
391
|
-
baseMessage as EmailMessage,
|
|
392
|
-
targetMessage as EmailMessage,
|
|
393
|
-
language.value,
|
|
394
|
-
lang.id,
|
|
395
|
-
)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// After translation, proceed with save
|
|
399
|
-
proceedWithSave()
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
watch(
|
|
403
|
-
() => messages.value[mainLanguage.value],
|
|
404
|
-
() => {
|
|
405
|
-
const resolved = resolveDefaultLanguage(messages.value, mainLanguage.value)
|
|
406
|
-
if (resolved && resolved !== mainLanguage.value) {
|
|
407
|
-
language.value = resolved
|
|
408
|
-
targetLanguage.value = resolved
|
|
409
|
-
}
|
|
410
|
-
},
|
|
411
|
-
)
|
|
9
|
+
const composerRef = ref<InstanceType<typeof ComposerEmailComposer>>()
|
|
10
|
+
const store = useEmailComposerStore()
|
|
412
11
|
</script>
|
|
413
12
|
|
|
414
13
|
<template>
|
|
@@ -424,169 +23,42 @@ watch(
|
|
|
424
23
|
value: 'translations',
|
|
425
24
|
},
|
|
426
25
|
]"
|
|
427
|
-
:active-tab="activeTab"
|
|
26
|
+
:active-tab="store.activeTab"
|
|
428
27
|
:test-id="templateEmailEditTestIds.navbar"
|
|
429
|
-
@tab-change="activeTab = $event"
|
|
28
|
+
@tab-change="store.activeTab = $event as 'content' | 'translations'"
|
|
430
29
|
>
|
|
431
30
|
<template #actions>
|
|
432
31
|
<UButton
|
|
433
|
-
@click="
|
|
434
|
-
() => {
|
|
435
|
-
notifyParent('TEMPLATE_CANCELLED', { id: messageGroup?.id })
|
|
436
|
-
goBackOrFallback()
|
|
437
|
-
}
|
|
438
|
-
"
|
|
32
|
+
@click="composerRef?.handleCancel()"
|
|
439
33
|
:label="$t('common.actions.cancel')"
|
|
440
34
|
color="primary"
|
|
441
35
|
variant="ghost"
|
|
442
|
-
:loading="saving || isTranslating"
|
|
36
|
+
:loading="composerRef?.saving || composerRef?.isTranslating"
|
|
443
37
|
:data-testid="templateEmailEditTestIds.cancelButton"
|
|
444
38
|
/>
|
|
445
39
|
<UButton
|
|
446
|
-
@click="handleSaveAsDraft"
|
|
40
|
+
@click="composerRef?.handleSaveAsDraft()"
|
|
447
41
|
:label="$t('pages.email.edit_message_group.save_as_draft')"
|
|
448
42
|
color="primary"
|
|
449
43
|
variant="outline"
|
|
450
|
-
:loading="saving || isTranslating"
|
|
44
|
+
:loading="composerRef?.saving || composerRef?.isTranslating"
|
|
451
45
|
:data-testid="templateEmailEditTestIds.saveDraftButton"
|
|
452
46
|
/>
|
|
453
47
|
<UButton
|
|
454
|
-
@click="handleSaveTemplate"
|
|
48
|
+
@click="composerRef?.handleSaveTemplate()"
|
|
455
49
|
:label="$t('pages.email.edit_message_group.save_message')"
|
|
456
50
|
color="primary"
|
|
457
|
-
:loading="saving || isTranslating"
|
|
51
|
+
:loading="composerRef?.saving || composerRef?.isTranslating"
|
|
458
52
|
:data-testid="templateEmailEditTestIds.saveTemplateButton"
|
|
459
53
|
/>
|
|
460
54
|
</template>
|
|
461
55
|
|
|
462
|
-
<
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
icon="i-lucide-alert-triangle"
|
|
467
|
-
:title="$t('editor.validation.error.title')"
|
|
468
|
-
:close="true"
|
|
469
|
-
@update:open="clearErrors()"
|
|
470
|
-
:data-testid="templateEmailEditTestIds.validationAlert"
|
|
471
|
-
>
|
|
472
|
-
<template #description>
|
|
473
|
-
<ul class="list-disc pl-4 space-y-1">
|
|
474
|
-
<li v-for="err in validationErrors" :key="err">
|
|
475
|
-
{{ err }}
|
|
476
|
-
</li>
|
|
477
|
-
</ul>
|
|
478
|
-
</template>
|
|
479
|
-
</UAlert>
|
|
480
|
-
</div>
|
|
481
|
-
|
|
482
|
-
<UForm
|
|
483
|
-
ref="templateForm"
|
|
484
|
-
:schema="draftSchema"
|
|
485
|
-
:state="formState"
|
|
486
|
-
:validate-on="[]"
|
|
487
|
-
class="flex-1 relative min-h-0 flex flex-col"
|
|
56
|
+
<ComposerEmailComposer
|
|
57
|
+
ref="composerRef"
|
|
58
|
+
mode="edit"
|
|
59
|
+
:message-group-id="route.params.id as string"
|
|
488
60
|
>
|
|
489
|
-
<
|
|
490
|
-
|
|
491
|
-
:ui="{
|
|
492
|
-
root: 'flex flex-col flex-1 min-h-0',
|
|
493
|
-
body: 'flex-1 overflow-hidden sm:px-0 px-0 py-0 sm:py-0 min-h-0',
|
|
494
|
-
header: 'sm:px-4 px-4 py-4 sm:py-4',
|
|
495
|
-
}"
|
|
496
|
-
:data-testid="templateEmailEditTestIds.contentCard"
|
|
497
|
-
>
|
|
498
|
-
<template #header>
|
|
499
|
-
<div class="grid grid-cols-3 gap-2">
|
|
500
|
-
<UFieldGroup>
|
|
501
|
-
<UBadge variant="outline" color="neutral" size="lg">{{
|
|
502
|
-
$t('pages.email.edit_message_group.name')
|
|
503
|
-
}}</UBadge>
|
|
504
|
-
<UInput
|
|
505
|
-
v-model="messageGroup.name"
|
|
506
|
-
:placeholder="$t('pages.email.edit_message_group.name_placeholder')"
|
|
507
|
-
:data-testid="templateEmailEditTestIds.nameInput"
|
|
508
|
-
/>
|
|
509
|
-
</UFieldGroup>
|
|
510
|
-
|
|
511
|
-
<UFieldGroup v-if="messages[language]">
|
|
512
|
-
<UBadge variant="outline" color="neutral" size="lg">{{
|
|
513
|
-
$t('pages.email.edit_message_group.subject')
|
|
514
|
-
}}</UBadge>
|
|
515
|
-
<UInput
|
|
516
|
-
v-model="messages[language]!.body.subject"
|
|
517
|
-
:placeholder="$t('pages.email.edit_message_group.subject_placeholder')"
|
|
518
|
-
:data-testid="templateEmailEditTestIds.subjectInput"
|
|
519
|
-
/>
|
|
520
|
-
</UFieldGroup>
|
|
521
|
-
</div>
|
|
522
|
-
</template>
|
|
523
|
-
|
|
524
|
-
<template v-if="messages[language]" :key="language">
|
|
525
|
-
<div class="flex flex-col h-full overflow-hidden">
|
|
526
|
-
<div class="flex-1 overflow-hidden">
|
|
527
|
-
<EmailEditor
|
|
528
|
-
v-if="messages[language]"
|
|
529
|
-
:key="language"
|
|
530
|
-
:message="messages[language] as PostEntity<EmailMessage>"
|
|
531
|
-
@block-added="onBlockAdded"
|
|
532
|
-
@block-deleted="onBlockDeleted"
|
|
533
|
-
@block-duplicated="onBlockDuplicated"
|
|
534
|
-
@block-sorted="onBlockSorted"
|
|
535
|
-
@widget-tab-changed="onWidgetTabChanged"
|
|
536
|
-
@test-sent="onTestSent"
|
|
537
|
-
/>
|
|
538
|
-
</div>
|
|
539
|
-
</div>
|
|
540
|
-
</template>
|
|
541
|
-
</UCard>
|
|
542
|
-
|
|
543
|
-
<UCard
|
|
544
|
-
v-if="activeTab === 'translations' && messageGroup && messages[language]"
|
|
545
|
-
:ui="{
|
|
546
|
-
root: 'flex flex-col h-full',
|
|
547
|
-
body: 'flex-1 overflow-hidden sm:px-0 px-0 py-0 sm:py-0',
|
|
548
|
-
header: 'sm:px-4 px-4 py-4 sm:py-4',
|
|
549
|
-
}"
|
|
550
|
-
:data-testid="templateEmailEditTestIds.translationsCard"
|
|
551
|
-
>
|
|
552
|
-
<div
|
|
553
|
-
class="grid grid-cols-[auto_1fr] divide-x divide-muted overflow-auto max-h-full h-full"
|
|
554
|
-
>
|
|
555
|
-
<div class="overflow-y-auto">
|
|
556
|
-
<LanguageList
|
|
557
|
-
v-model:messages="messages"
|
|
558
|
-
v-model:language="targetLanguage"
|
|
559
|
-
render-type="email_v1"
|
|
560
|
-
:main-language-id="language"
|
|
561
|
-
:message-group-id="messageGroup.id"
|
|
562
|
-
@add-language="onAddLanguage"
|
|
563
|
-
@remove-language="onRemoveLanguage"
|
|
564
|
-
/>
|
|
565
|
-
</div>
|
|
566
|
-
<div class="overflow-y-auto" v-if="messages[language] && messages[targetLanguage]">
|
|
567
|
-
<TranslationEditor
|
|
568
|
-
:base-message="messages[language]!"
|
|
569
|
-
v-model="messages[targetLanguage]!"
|
|
570
|
-
:messages="messages"
|
|
571
|
-
:current-language="targetLanguage"
|
|
572
|
-
:base-language="language"
|
|
573
|
-
:message-group-id="messageGroup.id"
|
|
574
|
-
:key="targetLanguage"
|
|
575
|
-
@auto-translate-tracked="onAutoTranslateTracked"
|
|
576
|
-
@preview="onPreview"
|
|
577
|
-
@filters-changed="onFiltersChanged"
|
|
578
|
-
/>
|
|
579
|
-
</div>
|
|
580
|
-
</div>
|
|
581
|
-
</UCard>
|
|
582
|
-
</UForm>
|
|
583
|
-
|
|
584
|
-
<IncompleteTranslationsDialog
|
|
585
|
-
v-model="showIncompleteDialog"
|
|
586
|
-
:incomplete-languages="incompleteLanguages"
|
|
587
|
-
@save-without-translating="proceedWithSave"
|
|
588
|
-
@translate-and-save="translateAndSave"
|
|
589
|
-
@go-to-translations="goToTranslations"
|
|
590
|
-
/>
|
|
61
|
+
<template #actions></template>
|
|
62
|
+
</ComposerEmailComposer>
|
|
591
63
|
</LayoutBasePage>
|
|
592
64
|
</template>
|