@dev.smartpricing/message-composer-layer 4.0.2 → 4.0.4

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.
@@ -2,6 +2,7 @@ import type {
2
2
  TestEmailRequest,
3
3
  CompileAndSendResponse,
4
4
  GetMessageParametersResponse,
5
+ GetResolvedParametersResponse,
5
6
  CompileMessageRequest,
6
7
  CompileEmailResponse,
7
8
  PreviewMessageRequest,
@@ -20,6 +21,11 @@ export const compilationApi = {
20
21
  `/message-groups/${messageGroupId}/parameters?language=${language}`,
21
22
  ),
22
23
 
24
+ getResolvedParameters: (messageGroupId: string, language: string) =>
25
+ apiClient<GetResolvedParametersResponse>(
26
+ `/message-groups/${messageGroupId}/parameters/resolved?language=${language}`,
27
+ ),
28
+
23
29
  compileEmail: (messageGroupId: string, data: CompileMessageRequest) =>
24
30
  apiClient<CompileEmailResponse>(`/message-groups/${messageGroupId}/compile`, {
25
31
  method: 'POST',
@@ -6,6 +6,7 @@ import type {
6
6
  PostEntity,
7
7
  MessageGroup,
8
8
  RenderType,
9
+ DuplicateMessageGroupBody,
9
10
  } from '@dev.smartpricing/message-composer-utils/types'
10
11
  import { apiClient } from './client'
11
12
 
@@ -46,4 +47,10 @@ export const messageGroupsApi = {
46
47
 
47
48
  validate: (id: string) =>
48
49
  apiClient<MessageGroupValidationResponse>(`/message-groups/${id}/validate`),
50
+
51
+ duplicate: (id: string, data?: DuplicateMessageGroupBody) =>
52
+ apiClient<MessageGroupFromDb & { tags: string[] }>(`/message-groups/${id}/duplicate`, {
53
+ method: 'POST',
54
+ body: data,
55
+ }),
49
56
  }
@@ -13,12 +13,10 @@ import { useEmailEditorStore } from '~/stores/emailEditor'
13
13
 
14
14
  interface Props {
15
15
  message: PostEntity<Message> | MessageFromDb | null | undefined
16
- dynamicData?: Record<string, any>
16
+ messageGroupId?: string
17
17
  }
18
18
 
19
- const props = withDefaults(defineProps<Props>(), {
20
- dynamicData: () => ({}),
21
- })
19
+ const props = defineProps<Props>()
22
20
 
23
21
  const emit = defineEmits<{
24
22
  opened: []
@@ -71,9 +69,9 @@ const isEmail = computed(() => renderType.value === 'email_v1')
71
69
  <template #body>
72
70
  <!-- WhatsApp Preview -->
73
71
  <WhatsappPreview
74
- v-if="isWhatsapp"
72
+ v-if="isWhatsapp && messageGroupId"
75
73
  :message="message as WhatsappMessage"
76
- :dynamic-data="dynamicData"
74
+ :message-group-id="messageGroupId"
77
75
  />
78
76
 
79
77
  <!-- Email Inline Preview (client-driven, reflects unsaved changes) -->
@@ -9,7 +9,6 @@ const props = withDefaults(
9
9
  id: string
10
10
  showSubject?: boolean
11
11
  view?: 'desktop' | 'mobile'
12
- dynamicData?: Record<string, string>
13
12
  brandId?: string
14
13
  }>(),
15
14
  { showSubject: false, view: 'desktop' },
@@ -46,14 +45,13 @@ const isEmail = computed(() => selectedMessage.value?.render_type === 'email_v1'
46
45
  :message-group-id="id"
47
46
  :show-subject="showSubject"
48
47
  :view="view"
49
- :dynamic-data="dynamicData"
50
48
  :brand-id="brandId"
51
49
  />
52
50
 
53
51
  <WhatsappPreview
54
52
  v-else-if="isWhatsapp && selectedMessage"
55
53
  :message="selectedMessage as unknown as WhatsappMessage"
56
- :dynamic-data="dynamicData"
54
+ :message-group-id="id"
57
55
  />
58
56
  </div>
59
57
  </template>
@@ -133,6 +133,7 @@ const handleAutoTranslate = () => {
133
133
  <MessagePreviewSlideover
134
134
  v-if="messages[currentLanguage]"
135
135
  :message="messages[currentLanguage]"
136
+ :message-group-id="String(messageGroupId ?? '')"
136
137
  @opened="emit('preview')"
137
138
  />
138
139
  </div>
@@ -81,7 +81,10 @@ const testIds = computed(() =>
81
81
  />
82
82
  </div>
83
83
  <div class="col-span-1 overflow-y-auto" v-if="store.currentMessage">
84
- <WhatsappPreview :message="store.currentMessage as WhatsappMessage" />
84
+ <WhatsappPreview
85
+ :message="store.currentMessage as WhatsappMessage"
86
+ :message-group-id="store.messageGroupId ?? ''"
87
+ />
85
88
  </div>
86
89
  </div>
87
90
  </UCard>
@@ -1,10 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import type { EmailMessage, PostEntity } from '@dev.smartpricing/message-composer-utils/types'
3
- import { extractEmailParameters } from '@dev.smartpricing/message-composer-utils/utils'
4
- import { ref, computed, watch } from 'vue'
3
+ import { ref, watch } from 'vue'
5
4
  import { useDebounceFn } from '@vueuse/core'
6
5
  import HtmlPreviewIframe from '@/components/Common/HtmlPreviewIframe.vue'
7
- import { useCompileEmailMutation } from '~/queries/compilation'
6
+ import { useCompileEmailMutation, useResolvedParametersQuery } from '~/queries/compilation'
8
7
 
9
8
  defineOptions({ name: 'EmailPreview' })
10
9
 
@@ -12,7 +11,6 @@ const props = withDefaults(
12
11
  defineProps<{
13
12
  message: PostEntity<EmailMessage> | EmailMessage
14
13
  messageGroupId: string
15
- dynamicData?: Record<string, string>
16
14
  view?: 'desktop' | 'mobile'
17
15
  showSubject?: boolean
18
16
  brandId?: string
@@ -24,38 +22,28 @@ const iframeWidth = computed(() => {
24
22
  return props.view === 'mobile' ? '400px' : '800px'
25
23
  })
26
24
 
27
- // Extract parameters directly from message
28
- const parameters = computed(() => extractEmailParameters(props.message))
25
+ const { data: resolvedParams } = useResolvedParametersQuery(
26
+ () => props.messageGroupId,
27
+ () => props.message.language_id,
28
+ )
29
29
 
30
- // Initialize parameter values
31
- const parameterValues = ref<Record<string, string>>({})
32
30
  const { mutate: compile, data: compiled, isLoading: isCompiling } = useCompileEmailMutation()
33
31
 
34
32
  const compileEmail = useDebounceFn(() => {
33
+ if (!resolvedParams.value) return
35
34
  compile({
36
35
  messageGroupId: props.messageGroupId,
37
36
  data: {
38
37
  language: props.message.language_id,
39
- parameters: parameterValues.value,
38
+ parameters: resolvedParams.value.parameters,
40
39
  brand_id: props.brandId,
41
40
  },
42
41
  })
43
42
  }, 0)
44
43
 
45
44
  watch(
46
- parameters,
47
- (params) => {
48
- const defaults = props.dynamicData || {}
49
- parameterValues.value = params.reduce(
50
- (acc, param) => {
51
- acc[param] = defaults[param] || `Sample ${param}`
52
- return acc
53
- },
54
- {} as Record<string, string>,
55
- )
56
-
57
- compileEmail()
58
- },
45
+ () => resolvedParams.value,
46
+ () => compileEmail(),
59
47
  { immediate: true },
60
48
  )
61
49
 
@@ -10,7 +10,6 @@ defineOptions({ name: 'EmailPreviewPanel' })
10
10
  defineProps<{
11
11
  message: PostEntity<EmailMessage> | EmailMessage
12
12
  messageGroupId: string
13
- dynamicData?: Record<string, string>
14
13
  }>()
15
14
 
16
15
  const emit = defineEmits<{
@@ -47,11 +46,6 @@ const currentView = ref<'desktop' | 'mobile'>('desktop')
47
46
  </div>
48
47
  </div>
49
48
 
50
- <EmailPreview
51
- :message="message"
52
- :message-group-id="messageGroupId"
53
- :dynamic-data="dynamicData"
54
- :view="currentView"
55
- />
49
+ <EmailPreview :message="message" :message-group-id="messageGroupId" :view="currentView" />
56
50
  </div>
57
51
  </template>
@@ -7,6 +7,7 @@ import type {
7
7
  import { generateHTML } from '@tiptap/vue-3'
8
8
  import { computed } from 'vue'
9
9
  import { getTipTapExtensions } from '@dev.smartpricing/message-composer-utils/utils'
10
+ import { useResolvedParametersQuery } from '~/queries/compilation'
10
11
 
11
12
  defineOptions({
12
13
  name: 'WhatsappPreview',
@@ -14,14 +15,20 @@ defineOptions({
14
15
 
15
16
  const props = defineProps<{
16
17
  message: PostEntity<WhatsappMessage>
17
- dynamicData?: Record<string, any>
18
+ messageGroupId: string
18
19
  }>()
19
20
 
21
+ const { data: resolvedParams } = useResolvedParametersQuery(
22
+ () => props.messageGroupId,
23
+ () => props.message.language_id,
24
+ )
25
+
20
26
  function replaceMentionsWithValues(html: string): string {
21
- if (!props.dynamicData) return html
27
+ const dynamicData = resolvedParams.value?.parameters
28
+ if (!dynamicData) return html
22
29
 
23
30
  let replacedHtml = html
24
- for (const [key, value] of Object.entries(props.dynamicData)) {
31
+ for (const [key, value] of Object.entries(dynamicData)) {
25
32
  const mentionRegex = new RegExp(`{{${key}}}`, 'g')
26
33
  replacedHtml = replacedHtml.replace(mentionRegex, value || `{{${key}}}`)
27
34
  }
@@ -8,7 +8,7 @@ defineOptions({
8
8
 
9
9
  defineProps<{
10
10
  message: PostEntity<WhatsappMessage>
11
- dynamicData?: Record<string, any>
11
+ messageGroupId: string
12
12
  }>()
13
13
  </script>
14
14
 
@@ -19,6 +19,6 @@ defineProps<{
19
19
  <h3 class="label-lg">{{ $t('common.preview') }}</h3>
20
20
  </div>
21
21
 
22
- <WhatsappPreview :message="message" :dynamic-data="dynamicData" />
22
+ <WhatsappPreview :message="message" :message-group-id="messageGroupId" />
23
23
  </div>
24
24
  </template>
@@ -4,10 +4,9 @@ import {
4
4
  useUpdateMessageGroupMutation,
5
5
  useCreateMessageMutation,
6
6
  useUpdateMessageMutation,
7
+ useDuplicateMessageGroupMutation,
7
8
  } from '~/queries'
8
- import { messageGroupsApi } from '~/api'
9
9
  import type {
10
- MessageFromDb,
11
10
  MessageGroupCategory,
12
11
  Message,
13
12
  PostEntity,
@@ -25,24 +24,7 @@ interface UseTemplateOperationsOptions {
25
24
  onError?: (error: Error) => void
26
25
  }
27
26
 
28
- function transformMessagesForCreation(
29
- messages: MessageFromDb[],
30
- ): Record<string, PostEntity<Message>> {
31
- return messages.reduce(
32
- (acc, message) => {
33
- // Remove DB-specific fields, keep message group content
34
-
35
- const { id, message_group_id, created_at, updated_at, ...messageGroupMessage } = message
36
- acc[message.language_id] = { ...messageGroupMessage, message_group_id: '', metadata: {} }
37
-
38
- return acc
39
- },
40
- {} as Record<string, PostEntity<Message>>,
41
- )
42
- }
43
-
44
27
  export function useTemplateOperations(options: UseTemplateOperationsOptions = {}) {
45
- const { t } = useI18n()
46
28
  const saving = ref(false)
47
29
  const saved = ref(false)
48
30
 
@@ -51,6 +33,7 @@ export function useTemplateOperations(options: UseTemplateOperationsOptions = {}
51
33
  const { mutateAsync: createMessage } = useCreateMessageMutation()
52
34
  const { mutateAsync: updateMessageGroup } = useUpdateMessageGroupMutation()
53
35
  const { mutateAsync: updateMessage } = useUpdateMessageMutation()
36
+ const { mutateAsync: duplicateMessageGroup } = useDuplicateMessageGroupMutation()
54
37
 
55
38
  // Create new message group(used by Create + Duplicate)
56
39
  async function createTemplate(data: TemplateData, status: MessageStatus = 'draft') {
@@ -117,26 +100,19 @@ export function useTemplateOperations(options: UseTemplateOperationsOptions = {}
117
100
  async function duplicateTemplate(sourceId: string, newName?: string) {
118
101
  saving.value = true
119
102
  try {
120
- const sourceTemplate = await messageGroupsApi.getById(sourceId)
121
-
122
- if (!sourceTemplate) {
123
- throw new Error('Source message group not found')
124
- }
103
+ const result = await duplicateMessageGroup({
104
+ id: sourceId,
105
+ data: newName ? { name: newName } : undefined,
106
+ })
125
107
 
126
- const templateData: TemplateData = {
127
- messageGroup: {
128
- name: newName || t('common.copy_of', { name: sourceTemplate.name }),
129
- category: sourceTemplate.category,
130
- brand_id: sourceTemplate.brand_id,
131
- },
132
- messages: transformMessagesForCreation(sourceTemplate.messages),
133
- }
134
-
135
- const result = await createTemplate(templateData, 'draft')
108
+ saved.value = true
109
+ options.onSuccess?.()
136
110
  return result
137
111
  } catch (error) {
138
112
  options.onError?.(error as Error)
139
113
  throw error
114
+ } finally {
115
+ saving.value = false
140
116
  }
141
117
  }
142
118
 
@@ -39,7 +39,11 @@ const isEmail = computed(() => selectedMessage.value?.render_type === 'email_v1'
39
39
 
40
40
  <template>
41
41
  <div :data-testid="previewPageTestIds.page" class="w-full overflow-y-auto">
42
- <WhatsappPreview v-if="isWhatsapp && selectedMessage" :message="selectedMessage as any" />
42
+ <WhatsappPreview
43
+ v-if="isWhatsapp && selectedMessage"
44
+ :message="selectedMessage as any"
45
+ :message-group-id="route.params.id as string"
46
+ />
43
47
 
44
48
  <EmailPreview
45
49
  v-else-if="isEmail && selectedMessage"
@@ -23,6 +23,17 @@ export function useEmailParametersQuery(
23
23
  })
24
24
  }
25
25
 
26
+ export function useResolvedParametersQuery(
27
+ messageGroupId: MaybeRefOrGetter<string>,
28
+ language: MaybeRefOrGetter<string>,
29
+ ) {
30
+ return useQuery({
31
+ key: () => ['resolved-parameters', toValue(messageGroupId), toValue(language)],
32
+ query: () => compilationApi.getResolvedParameters(toValue(messageGroupId), toValue(language)),
33
+ enabled: () => !!toValue(messageGroupId) && !!toValue(language),
34
+ })
35
+ }
36
+
26
37
  export function useCompileEmailMutation() {
27
38
  return useMutation({
28
39
  mutation: ({ messageGroupId, data }: { messageGroupId: string; data: CompileMessageRequest }) =>
@@ -1,5 +1,6 @@
1
1
  import { useQuery, useMutation, useQueryCache } from '@pinia/colada'
2
2
  import type {
3
+ DuplicateMessageGroupBody,
3
4
  MessageGroup,
4
5
  MessageGroupWithMessageSummary,
5
6
  PostEntity,
@@ -82,6 +83,18 @@ export function useUnpublishMessageGroupMutation() {
82
83
  })
83
84
  }
84
85
 
86
+ export function useDuplicateMessageGroupMutation() {
87
+ const queryCache = useQueryCache()
88
+
89
+ return useMutation({
90
+ mutation: ({ id, data }: { id: string; data?: DuplicateMessageGroupBody }) =>
91
+ messageGroupsApi.duplicate(id, data),
92
+ onSuccess: () => {
93
+ queryCache.invalidateQueries({ key: ['message-groups'] })
94
+ },
95
+ })
96
+ }
97
+
85
98
  export function useDeleteMessageGroupMutation() {
86
99
  const queryCache = useQueryCache()
87
100
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.smartpricing/message-composer-layer",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -38,7 +38,7 @@
38
38
  "vue-router": "^5.0.7",
39
39
  "vue3-emoji-picker": "^1.1.8",
40
40
  "zod": "^4.4.3",
41
- "@dev.smartpricing/message-composer-utils": "4.0.3"
41
+ "@dev.smartpricing/message-composer-utils": "4.0.5"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@nuxt/eslint": "^1.15.2",