@dev.smartpricing/message-composer-layer 1.0.13 → 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/pages/brands.vue +7 -176
- package/app/pages/images.vue +1 -126
- package/app/pages/index.vue +6 -138
- package/app/pages/template/email/[id].vue +16 -545
- package/app/pages/template/email/create.vue +13 -514
- package/app/pages/template/whatsapp/[id].vue +21 -509
- package/app/pages/template/whatsapp/create.vue +13 -426
- package/app/stores/emailComposer.ts +188 -0
- package/app/stores/whatsappComposer.ts +173 -0
- package/package.json +2 -2
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import EmailEditor from '@/components/Email/Editor/Index.vue'
|
|
3
|
+
import type { PostEntity, EmailMessage } from '@dev.smartpricing/message-composer-utils/types'
|
|
4
|
+
|
|
5
|
+
const props = defineProps<{
|
|
6
|
+
mode: 'create' | 'edit'
|
|
7
|
+
}>()
|
|
8
|
+
|
|
9
|
+
const emit = defineEmits<{
|
|
10
|
+
'block-added': [payload: { type: string; adding_mode: string }]
|
|
11
|
+
'block-deleted': [payload: { type: string }]
|
|
12
|
+
'block-duplicated': [payload: { type: string }]
|
|
13
|
+
'block-sorted': [payload: { type: string; before_sort_position: number; new_position: number }]
|
|
14
|
+
'widget-tab-changed': [tab: string]
|
|
15
|
+
'test-sent': [payload: { to_recipients: number }, lang?: string]
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const store = useEmailComposerStore()
|
|
19
|
+
const emailEditorStore = useEmailEditorStore()
|
|
20
|
+
const adminStore = useAdminStore()
|
|
21
|
+
|
|
22
|
+
const testIds = computed(() =>
|
|
23
|
+
props.mode === 'create' ? templateEmailCreateTestIds : templateEmailEditTestIds,
|
|
24
|
+
)
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<UCard
|
|
29
|
+
v-if="store.currentMessage"
|
|
30
|
+
:ui="{
|
|
31
|
+
root: 'flex flex-col flex-1 min-h-0',
|
|
32
|
+
body: 'flex-1 overflow-hidden sm:px-0 px-0 py-0 sm:py-0 min-h-0',
|
|
33
|
+
header: 'sm:px-4 px-4 py-4 sm:py-4',
|
|
34
|
+
}"
|
|
35
|
+
:data-testid="testIds.contentCard"
|
|
36
|
+
>
|
|
37
|
+
<template #header>
|
|
38
|
+
<div class="grid grid-cols-3 gap-2 items-center">
|
|
39
|
+
<UFieldGroup>
|
|
40
|
+
<UBadge variant="outline" color="neutral" size="lg">{{
|
|
41
|
+
$t(`pages.email.${mode === 'create' ? 'create' : 'edit'}_message_group.name`)
|
|
42
|
+
}}</UBadge>
|
|
43
|
+
<UInput
|
|
44
|
+
v-model="store.name"
|
|
45
|
+
:placeholder="
|
|
46
|
+
$t(
|
|
47
|
+
`pages.email.${mode === 'create' ? 'create' : 'edit'}_message_group.name_placeholder`,
|
|
48
|
+
)
|
|
49
|
+
"
|
|
50
|
+
:data-testid="testIds.nameInput"
|
|
51
|
+
/>
|
|
52
|
+
</UFieldGroup>
|
|
53
|
+
|
|
54
|
+
<UFieldGroup v-if="store.currentMessage">
|
|
55
|
+
<UBadge variant="outline" color="neutral" size="lg">{{
|
|
56
|
+
$t(`pages.email.${mode === 'create' ? 'create' : 'edit'}_message_group.subject`)
|
|
57
|
+
}}</UBadge>
|
|
58
|
+
<UInput
|
|
59
|
+
v-model="(store.messages[store.language] as PostEntity<EmailMessage>).body.subject"
|
|
60
|
+
:placeholder="
|
|
61
|
+
$t(
|
|
62
|
+
`pages.email.${mode === 'create' ? 'create' : 'edit'}_message_group.subject_placeholder`,
|
|
63
|
+
)
|
|
64
|
+
"
|
|
65
|
+
:data-testid="testIds.subjectInput"
|
|
66
|
+
/>
|
|
67
|
+
</UFieldGroup>
|
|
68
|
+
|
|
69
|
+
<USwitch
|
|
70
|
+
v-if="mode === 'create' && adminStore.isAdminModeActive"
|
|
71
|
+
:label="$t('common.internal')"
|
|
72
|
+
v-model="store.isInternal"
|
|
73
|
+
color="info"
|
|
74
|
+
:data-testid="testIds.internalSwitch"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<div class="flex flex-col h-full overflow-hidden">
|
|
80
|
+
<template v-if="store.currentMessage" :key="store.language">
|
|
81
|
+
<div class="flex-1 overflow-hidden">
|
|
82
|
+
<EmailEditor
|
|
83
|
+
:message="store.currentMessage as PostEntity<EmailMessage>"
|
|
84
|
+
@block-added="emit('block-added', $event)"
|
|
85
|
+
@block-deleted="emit('block-deleted', $event)"
|
|
86
|
+
@block-duplicated="emit('block-duplicated', $event)"
|
|
87
|
+
@block-sorted="emit('block-sorted', $event)"
|
|
88
|
+
@widget-tab-changed="emit('widget-tab-changed', $event)"
|
|
89
|
+
@test-sent="(payload, lang) => emit('test-sent', payload, lang)"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
|
93
|
+
</div>
|
|
94
|
+
</UCard>
|
|
95
|
+
</template>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import TranslationEditor from '@/components/Email/TranslationEditor.vue'
|
|
3
|
+
import LanguageList from '@/components/Common/LanguageList.vue'
|
|
4
|
+
import type { EmailMessage, PostEntity } from '@dev.smartpricing/message-composer-utils/types'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
mode: 'create' | 'edit'
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const emit = defineEmits<{
|
|
11
|
+
'add-language': [lang: string]
|
|
12
|
+
'remove-language': [lang: string]
|
|
13
|
+
'auto-translate-tracked': [lang: string]
|
|
14
|
+
preview: [lang: string]
|
|
15
|
+
'filters-changed': [
|
|
16
|
+
payload: {
|
|
17
|
+
language: string
|
|
18
|
+
search_term: string
|
|
19
|
+
show_only_missing: boolean
|
|
20
|
+
show_only_changes: boolean
|
|
21
|
+
},
|
|
22
|
+
]
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const store = useEmailComposerStore()
|
|
26
|
+
|
|
27
|
+
const testIds = computed(() =>
|
|
28
|
+
props.mode === 'create' ? templateEmailCreateTestIds : templateEmailEditTestIds,
|
|
29
|
+
)
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<UCard
|
|
34
|
+
v-if="store.currentMessage"
|
|
35
|
+
:ui="{
|
|
36
|
+
root: 'flex flex-col h-full',
|
|
37
|
+
body: 'flex-1 overflow-hidden sm:px-0 px-0 py-0 sm:py-0',
|
|
38
|
+
header: 'sm:px-4 px-4 py-4 sm:py-4',
|
|
39
|
+
}"
|
|
40
|
+
:data-testid="testIds.translationsCard"
|
|
41
|
+
>
|
|
42
|
+
<div class="grid grid-cols-[auto_1fr] divide-x divide-muted overflow-auto max-h-full h-full">
|
|
43
|
+
<div class="overflow-y-auto">
|
|
44
|
+
<LanguageList
|
|
45
|
+
v-model:messages="store.messages"
|
|
46
|
+
v-model:language="store.targetLanguage"
|
|
47
|
+
render-type="email_v1"
|
|
48
|
+
:main-language-id="store.language"
|
|
49
|
+
:message-group-id="store.messageGroupId ?? undefined"
|
|
50
|
+
@add-language="emit('add-language', $event)"
|
|
51
|
+
@remove-language="emit('remove-language', $event)"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
<div class="overflow-y-auto" v-if="store.currentMessage && store.targetMessage">
|
|
55
|
+
<TranslationEditor
|
|
56
|
+
:base-message="store.currentMessage as PostEntity<EmailMessage>"
|
|
57
|
+
v-model="store.messages[store.targetLanguage] as PostEntity<EmailMessage>"
|
|
58
|
+
:messages="store.messages as Record<string, PostEntity<EmailMessage>>"
|
|
59
|
+
:current-language="store.targetLanguage"
|
|
60
|
+
:base-language="store.language"
|
|
61
|
+
:message-group-id="store.messageGroupId ?? undefined"
|
|
62
|
+
:key="store.targetLanguage"
|
|
63
|
+
@auto-translate-tracked="emit('auto-translate-tracked', $event)"
|
|
64
|
+
@preview="emit('preview', $event)"
|
|
65
|
+
@filters-changed="emit('filters-changed', $event)"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</UCard>
|
|
70
|
+
</template>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { MediaFromDb } from '@dev.smartpricing/message-composer-utils/types'
|
|
3
|
+
import { useMediaQuery, useUploadToSmartchatMutation, useDeleteMediaMutation } from '~/queries'
|
|
4
|
+
import { formatDate } from '@dev.smartpricing/message-composer-utils/utils'
|
|
5
|
+
|
|
6
|
+
const emit = defineEmits<{
|
|
7
|
+
synced: [item: MediaFromDb]
|
|
8
|
+
deleted: [item: MediaFromDb]
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const { t } = useI18n()
|
|
12
|
+
|
|
13
|
+
const { data: media, isPending, refetch } = useMediaQuery()
|
|
14
|
+
const { mutateAsync: uploadToSmartchat } = useUploadToSmartchatMutation()
|
|
15
|
+
const { mutateAsync: deleteMedia } = useDeleteMediaMutation()
|
|
16
|
+
|
|
17
|
+
const uploadingIds = ref<Set<string>>(new Set())
|
|
18
|
+
const deletingIds = ref<Set<string>>(new Set())
|
|
19
|
+
|
|
20
|
+
async function handleUploadToSmartchat(item: MediaFromDb) {
|
|
21
|
+
uploadingIds.value.add(item.id)
|
|
22
|
+
try {
|
|
23
|
+
await uploadToSmartchat(item.id)
|
|
24
|
+
useToast().add({
|
|
25
|
+
color: 'success',
|
|
26
|
+
title: t('common.success'),
|
|
27
|
+
description: t('pages.images.image_synced'),
|
|
28
|
+
})
|
|
29
|
+
emit('synced', item)
|
|
30
|
+
refetch()
|
|
31
|
+
} finally {
|
|
32
|
+
uploadingIds.value.delete(item.id)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function handleDelete(item: MediaFromDb) {
|
|
37
|
+
if (!confirm(t('pages.images.delete_confirm', { name: item.file_name }))) return
|
|
38
|
+
|
|
39
|
+
deletingIds.value.add(item.id)
|
|
40
|
+
try {
|
|
41
|
+
await deleteMedia(item.id)
|
|
42
|
+
useToast().add({
|
|
43
|
+
color: 'success',
|
|
44
|
+
title: t('common.success'),
|
|
45
|
+
description: t('pages.images.image_deleted'),
|
|
46
|
+
})
|
|
47
|
+
emit('deleted', item)
|
|
48
|
+
refetch()
|
|
49
|
+
} finally {
|
|
50
|
+
deletingIds.value.delete(item.id)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
defineExpose({ refetch })
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<div v-if="isPending" class="flex justify-center py-12">
|
|
59
|
+
<UIcon
|
|
60
|
+
name="i-heroicons-arrow-path"
|
|
61
|
+
class="animate-spin h-8 w-8"
|
|
62
|
+
:data-testid="imagesPageTestIds.loadingIcon"
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div
|
|
67
|
+
v-else-if="!media || media.length === 0"
|
|
68
|
+
class="text-center py-12 text-gray-500"
|
|
69
|
+
:data-testid="imagesPageTestIds.emptyState"
|
|
70
|
+
>
|
|
71
|
+
{{ t('pages.images.no_images_uploaded') }}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<div
|
|
75
|
+
v-else
|
|
76
|
+
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"
|
|
77
|
+
:data-testid="imagesPageTestIds.grid"
|
|
78
|
+
>
|
|
79
|
+
<UCard
|
|
80
|
+
v-for="item in media"
|
|
81
|
+
:key="item.id"
|
|
82
|
+
:data-testid="`${imagesPageTestIds.card}-${item.id}`"
|
|
83
|
+
>
|
|
84
|
+
<div class="space-y-3">
|
|
85
|
+
<img
|
|
86
|
+
:src="item.remote_url"
|
|
87
|
+
:alt="item.file_name"
|
|
88
|
+
class="h-40 w-full object-cover rounded"
|
|
89
|
+
:data-testid="`${imagesPageTestIds.image}-${item.id}`"
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
<div class="space-y-2">
|
|
93
|
+
<div class="flex items-center justify-between">
|
|
94
|
+
<span
|
|
95
|
+
class="text-sm font-medium truncate"
|
|
96
|
+
:title="item.file_name"
|
|
97
|
+
:data-testid="`${imagesPageTestIds.fileName}-${item.id}`"
|
|
98
|
+
>
|
|
99
|
+
{{ item.file_name }}
|
|
100
|
+
</span>
|
|
101
|
+
<UBadge
|
|
102
|
+
:color="item.smartchat_media_id ? 'success' : 'neutral'"
|
|
103
|
+
variant="subtle"
|
|
104
|
+
size="xs"
|
|
105
|
+
:data-testid="`${imagesPageTestIds.syncBadge}-${item.id}`"
|
|
106
|
+
>
|
|
107
|
+
{{
|
|
108
|
+
item.smartchat_media_id ? t('pages.images.synced') : t('pages.images.not_synced')
|
|
109
|
+
}}
|
|
110
|
+
</UBadge>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div class="text-xs text-gray-500 space-y-1">
|
|
114
|
+
<div>{{ t('pages.images.type_label') }} {{ item.file_type }}</div>
|
|
115
|
+
<div>{{ t('pages.images.created_label') }} {{ formatDate(item.created_at) }}</div>
|
|
116
|
+
<div
|
|
117
|
+
v-if="item.smartchat_media_id"
|
|
118
|
+
class="font-mono truncate"
|
|
119
|
+
:title="item.smartchat_media_id"
|
|
120
|
+
>
|
|
121
|
+
{{ t('pages.images.media_id_label') }} {{ item.smartchat_media_id }}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<div v-if="!item.smartchat_media_id" class="flex gap-2 pt-2">
|
|
126
|
+
<UButton
|
|
127
|
+
size="xs"
|
|
128
|
+
color="primary"
|
|
129
|
+
block
|
|
130
|
+
:loading="uploadingIds.has(item.id)"
|
|
131
|
+
:data-testid="`${imagesPageTestIds.syncButton}-${item.id}`"
|
|
132
|
+
@click="handleUploadToSmartchat(item)"
|
|
133
|
+
>
|
|
134
|
+
{{ t('pages.images.sync_to_smartchat') }}
|
|
135
|
+
</UButton>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
</UCard>
|
|
140
|
+
</div>
|
|
141
|
+
</template>
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
useMessageGroupsQuery,
|
|
4
|
+
useDeleteMessageGroupMutation,
|
|
5
|
+
usePublishMessageGroupMutation,
|
|
6
|
+
useUnpublishMessageGroupMutation,
|
|
7
|
+
} from '~/queries'
|
|
8
|
+
import type {
|
|
9
|
+
MessageGroupWithMessageSummary,
|
|
10
|
+
RenderType,
|
|
11
|
+
} from '@dev.smartpricing/message-composer-utils/types'
|
|
12
|
+
import type { MessageActionProperties, MessagesKind } from '@/types/tracking'
|
|
13
|
+
import { computed } from 'vue'
|
|
14
|
+
import BaseTable from '@/components/TemplateList/BaseTable.vue'
|
|
15
|
+
|
|
16
|
+
const renderType = defineModel<RenderType>('renderType', { required: true })
|
|
17
|
+
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
edit: [row: MessageGroupWithMessageSummary]
|
|
20
|
+
deleted: [row: MessageGroupWithMessageSummary]
|
|
21
|
+
duplicated: [result: { id: string | number; sourceId: string | number; name: string }]
|
|
22
|
+
published: [row: MessageGroupWithMessageSummary]
|
|
23
|
+
unpublished: [row: MessageGroupWithMessageSummary]
|
|
24
|
+
}>()
|
|
25
|
+
|
|
26
|
+
const { trackEvent } = useTracking()
|
|
27
|
+
const { t } = useI18n()
|
|
28
|
+
|
|
29
|
+
// Query
|
|
30
|
+
const {
|
|
31
|
+
data: messageGroups,
|
|
32
|
+
isPending: isFetching,
|
|
33
|
+
refetch: execute,
|
|
34
|
+
} = useMessageGroupsQuery(computed(() => ({ renderType: renderType.value })))
|
|
35
|
+
|
|
36
|
+
// Polling
|
|
37
|
+
const interval = setInterval(() => execute(), 15000)
|
|
38
|
+
onBeforeUnmount(() => clearInterval(interval))
|
|
39
|
+
|
|
40
|
+
// Mutations
|
|
41
|
+
const { confirm } = useConfirm()
|
|
42
|
+
const { mutateAsync: deleteGroup } = useDeleteMessageGroupMutation()
|
|
43
|
+
const { mutateAsync: publishGroup } = usePublishMessageGroupMutation()
|
|
44
|
+
const { mutateAsync: unpublishGroup } = useUnpublishMessageGroupMutation()
|
|
45
|
+
|
|
46
|
+
function getMessageTrackingProps(row: MessageGroupWithMessageSummary): MessageActionProperties {
|
|
47
|
+
return {
|
|
48
|
+
message_name: row.name,
|
|
49
|
+
languages: row.messages.map((m) => m.language),
|
|
50
|
+
message_id: row.id,
|
|
51
|
+
message_kind: (row.messages[0]?.render_type === 'email_v1'
|
|
52
|
+
? 'email'
|
|
53
|
+
: 'whatsapp') as MessagesKind,
|
|
54
|
+
message_category: row.category,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function askDeleteGroup(row: MessageGroupWithMessageSummary) {
|
|
59
|
+
confirm({
|
|
60
|
+
title: t('dialogs.delete_message_group.title'),
|
|
61
|
+
message: t('dialogs.delete_message_group.message', { name: row.name }),
|
|
62
|
+
destructive: true,
|
|
63
|
+
confirmLabel: t('common.actions.delete'),
|
|
64
|
+
cancelLabel: t('common.actions.cancel'),
|
|
65
|
+
action: async () => {
|
|
66
|
+
await deleteGroup(row.id)
|
|
67
|
+
trackEvent('message_delete', getMessageTrackingProps(row))
|
|
68
|
+
notifyParent('TEMPLATE_DELETED', { id: row.id, name: row.name })
|
|
69
|
+
useToast().add({
|
|
70
|
+
title: t('common.success'),
|
|
71
|
+
description: t('dialogs.delete_message_group.success', { name: row.name }),
|
|
72
|
+
color: 'success',
|
|
73
|
+
})
|
|
74
|
+
emit('deleted', row)
|
|
75
|
+
execute()
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function askPublishGroup(row: MessageGroupWithMessageSummary) {
|
|
81
|
+
confirm({
|
|
82
|
+
title: t('dialogs.publish_message_group.title'),
|
|
83
|
+
message: t('dialogs.publish_message_group.message', { name: row.name }),
|
|
84
|
+
confirmLabel: t('common.actions.publish'),
|
|
85
|
+
cancelLabel: t('common.actions.cancel'),
|
|
86
|
+
action: async () => {
|
|
87
|
+
await publishGroup(row.id)
|
|
88
|
+
notifyParent('TEMPLATE_PUBLISHED', { id: row.id, name: row.name })
|
|
89
|
+
useToast().add({
|
|
90
|
+
title: t('common.success'),
|
|
91
|
+
description: t('dialogs.publish_message_group.success', { name: row.name }),
|
|
92
|
+
color: 'success',
|
|
93
|
+
})
|
|
94
|
+
emit('published', row)
|
|
95
|
+
execute()
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function askUnpublishGroup(row: MessageGroupWithMessageSummary) {
|
|
101
|
+
confirm({
|
|
102
|
+
title: t('dialogs.unpublish_message_group.title'),
|
|
103
|
+
message: t('dialogs.unpublish_message_group.message', { name: row.name }),
|
|
104
|
+
confirmLabel: t('common.actions.unpublish'),
|
|
105
|
+
cancelLabel: t('common.actions.cancel'),
|
|
106
|
+
action: async () => {
|
|
107
|
+
await unpublishGroup(row.id)
|
|
108
|
+
notifyParent('TEMPLATE_UNPUBLISHED', { id: row.id, name: row.name })
|
|
109
|
+
useToast().add({
|
|
110
|
+
title: t('common.success'),
|
|
111
|
+
description: t('dialogs.unpublish_message_group.success', { name: row.name }),
|
|
112
|
+
color: 'success',
|
|
113
|
+
})
|
|
114
|
+
emit('unpublished', row)
|
|
115
|
+
execute()
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { duplicateTemplate } = useTemplateOperations({
|
|
121
|
+
onSuccess: () => execute(),
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
async function handleDuplicate(row: MessageGroupWithMessageSummary) {
|
|
125
|
+
trackEvent('message_duplicate', getMessageTrackingProps(row))
|
|
126
|
+
const result = await duplicateTemplate(row.id.toString(), t('common.copy_of', { name: row.name }))
|
|
127
|
+
if (result) {
|
|
128
|
+
notifyParent('TEMPLATE_DUPLICATED', { id: result.id, sourceId: row.id, name: result.name })
|
|
129
|
+
emit('duplicated', { id: result.id, sourceId: row.id, name: result.name })
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const router = useRouter()
|
|
134
|
+
function navigate(row: MessageGroupWithMessageSummary) {
|
|
135
|
+
trackEvent('message_edit', getMessageTrackingProps(row))
|
|
136
|
+
emit('edit', row)
|
|
137
|
+
const type = row.messages[0]?.render_type === 'email_v1' ? 'email' : 'whatsapp'
|
|
138
|
+
router.push({ name: `${type}-message-edit`, params: { id: row.id } })
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
defineExpose({ execute })
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<template>
|
|
145
|
+
<BaseTable
|
|
146
|
+
:loading="isFetching"
|
|
147
|
+
:message-groups="messageGroups || []"
|
|
148
|
+
:render-type="renderType"
|
|
149
|
+
@edit="navigate"
|
|
150
|
+
@delete="askDeleteGroup"
|
|
151
|
+
@duplicate="handleDuplicate"
|
|
152
|
+
@publish="askPublishGroup"
|
|
153
|
+
@unpublish="askUnpublishGroup"
|
|
154
|
+
/>
|
|
155
|
+
</template>
|