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