@dev.smartpricing/message-composer-layer 1.0.26 → 4.0.1

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/api/brands.ts CHANGED
@@ -12,6 +12,8 @@ import type { BrandCascadeData } from '@dev.smartpricing/message-composer-utils/
12
12
  import { apiClient } from './client'
13
13
 
14
14
  export type BrandWithProperties = BrandFromDb & { properties: BrandProperty[] }
15
+ export type BrandCreateResponse = BrandFromDb & { brand_count: number }
16
+ export type BrandDeleteResponse = { brand_count: number }
15
17
 
16
18
  export const brandsApi = {
17
19
  getAll: (options?: { sort_by?: 'created_at' | 'name'; direction?: 'asc' | 'desc' }) => {
@@ -25,7 +27,7 @@ export const brandsApi = {
25
27
  getById: (id: string) => apiClient<BrandWithProperties>(`/brands/${id}`),
26
28
 
27
29
  create: (data: BrandCreateBody) =>
28
- apiClient<BrandFromDb>('/brands', {
30
+ apiClient<BrandCreateResponse>('/brands', {
29
31
  method: 'POST',
30
32
  body: data,
31
33
  }),
@@ -36,7 +38,7 @@ export const brandsApi = {
36
38
  body: data,
37
39
  }),
38
40
 
39
- delete: (id: string) => apiClient(`/brands/${id}`, { method: 'DELETE' }),
41
+ delete: (id: string) => apiClient<BrandDeleteResponse>(`/brands/${id}`, { method: 'DELETE' }),
40
42
 
41
43
  preview: (data: {
42
44
  message: PostEntity<EmailMessage>
@@ -15,6 +15,7 @@ import BrandStepGeneral from '~/components/Brand/Steps/BrandStepGeneral.vue'
15
15
  import BrandStepVisual from '~/components/Brand/Steps/BrandStepVisual.vue'
16
16
  import BrandStepSocials from '~/components/Brand/Steps/BrandStepSocials.vue'
17
17
  import { useBrandQuery, useCreateBrandMutation, useUpdateBrandMutation } from '~/queries'
18
+ import type { BrandCreateResponse } from '~/api/brands'
18
19
 
19
20
  defineOptions({ name: 'BrandDrawer' })
20
21
 
@@ -29,6 +30,7 @@ const emit = defineEmits<{
29
30
  }>()
30
31
 
31
32
  const { t } = useI18n()
33
+ const { trackEvent } = useTracking()
32
34
 
33
35
  const isEditMode = computed(() => !!props.brandId)
34
36
 
@@ -199,8 +201,50 @@ async function handleSave() {
199
201
  let result: BrandFromDb
200
202
  if (isEditMode.value && props.brandId) {
201
203
  result = await updateBrand({ id: props.brandId, data: payload })
204
+
205
+ const changedFields: string[] = []
206
+ const brand = existingBrand.value
207
+ if (brand) {
208
+ if (payload.name !== brand.name) changedFields.push('name')
209
+ if (payload.logo_media_id !== brand.logo_media_id) changedFields.push('logo')
210
+ if (JSON.stringify(payload.colors) !== JSON.stringify(brand.colors))
211
+ changedFields.push('colors')
212
+ if (
213
+ JSON.stringify(payload.default_email_settings) !==
214
+ JSON.stringify(brand.default_email_settings)
215
+ )
216
+ changedFields.push('email_settings')
217
+ if (
218
+ JSON.stringify(payload.default_button_settings) !==
219
+ JSON.stringify(brand.default_button_settings)
220
+ )
221
+ changedFields.push('button_settings')
222
+ if (JSON.stringify(payload.default_socials) !== JSON.stringify(brand.default_socials))
223
+ changedFields.push('socials')
224
+ if (
225
+ JSON.stringify(payload.property_ids) !==
226
+ JSON.stringify(brand.properties?.map((bp) => bp.property_id))
227
+ )
228
+ changedFields.push('properties')
229
+ }
230
+
231
+ trackEvent('brand_update', {
232
+ brand_id: result.id,
233
+ brand_name: result.name,
234
+ changed_fields: changedFields,
235
+ })
202
236
  } else {
203
- result = await createBrand(payload)
237
+ const createResult = (await createBrand(payload)) as BrandCreateResponse
238
+ result = createResult
239
+
240
+ trackEvent('brand_create', {
241
+ brand_id: result.id,
242
+ brand_name: result.name,
243
+ has_logo: !!payload.logo_media_id,
244
+ })
245
+ trackEvent('brand_count_changed', {
246
+ brand_count: createResult.brand_count,
247
+ })
204
248
  }
205
249
 
206
250
  useToast().add({
@@ -7,6 +7,7 @@ const { brand, brands } = storeToRefs(emailEditorStore)
7
7
 
8
8
  const { t } = useI18n()
9
9
  const { confirm } = useConfirm()
10
+ const { trackEvent } = useTracking()
10
11
 
11
12
  // Local selection tracks USelectMenu value; reverted on cancel
12
13
  const selectedBrand = shallowRef<BrandFromDb | undefined>(brand.value)
@@ -24,7 +25,13 @@ function handleBrandChange(newBrand: BrandFromDb) {
24
25
  label: t('dialogs.email_change_brand.cancel'),
25
26
  },
26
27
  action: () => {
28
+ const previousBrandId = brand.value?.id ?? null
27
29
  emailEditorStore.resetToBrand(newBrand)
30
+ trackEvent('brand_select', {
31
+ brand_id: newBrand.id,
32
+ brand_name: newBrand.name,
33
+ previous_brand_id: previousBrandId,
34
+ })
28
35
  },
29
36
  }).then((confirmed) => {
30
37
  if (!confirmed) {
@@ -13,6 +13,7 @@ const emit = defineEmits<{
13
13
 
14
14
  const { t } = useI18n()
15
15
  const { confirm } = useConfirm()
16
+ const { trackEvent } = useTracking()
16
17
 
17
18
  const { data: brands, isPending } = useBrandsQuery()
18
19
  const { mutateAsync: deleteBrand } = useDeleteBrandMutation()
@@ -115,7 +116,16 @@ function askDelete(brand: BrandFromDb) {
115
116
  },
116
117
  action: async () => {
117
118
  editingBrandId.value = undefined
118
- await deleteBrand(brand.id)
119
+ const result = await deleteBrand(brand.id)
120
+
121
+ trackEvent('brand_delete', {
122
+ brand_id: brand.id,
123
+ brand_name: brand.name,
124
+ })
125
+ trackEvent('brand_count_changed', {
126
+ brand_count: result.brand_count,
127
+ })
128
+
119
129
  emit('deleted', brand)
120
130
  useToast().add({
121
131
  color: 'success',
@@ -17,7 +17,7 @@ const emit = defineEmits<{
17
17
 
18
18
  const store = useEmailComposerStore()
19
19
  const emailEditorStore = useEmailEditorStore()
20
- const adminStore = useAdminStore()
20
+ const appContext = useAppContextStore()
21
21
 
22
22
  const testIds = computed(() =>
23
23
  props.mode === 'create' ? templateEmailCreateTestIds : templateEmailEditTestIds,
@@ -67,7 +67,7 @@ const testIds = computed(() =>
67
67
  </UFieldGroup>
68
68
 
69
69
  <USwitch
70
- v-if="mode === 'create' && adminStore.isAdminModeActive"
70
+ v-if="mode === 'create' && appContext.isAdmin"
71
71
  :label="$t('common.internal')"
72
72
  v-model="store.isInternal"
73
73
  color="info"
@@ -13,7 +13,7 @@ const emit = defineEmits<{
13
13
  }>()
14
14
 
15
15
  const store = useWhatsappComposerStore()
16
- const adminStore = useAdminStore()
16
+ const appContext = useAppContextStore()
17
17
 
18
18
  const testIds = computed(() =>
19
19
  props.mode === 'create' ? templateWhatsappCreateTestIds : templateWhatsappEditTestIds,
@@ -55,7 +55,7 @@ const testIds = computed(() =>
55
55
  </UFieldGroup>
56
56
 
57
57
  <USwitch
58
- v-if="mode === 'create' && adminStore.isAdminModeActive"
58
+ v-if="mode === 'create' && appContext.isAdmin"
59
59
  v-model="store.isInternal"
60
60
  :label="$t('common.internal')"
61
61
  color="info"
@@ -21,7 +21,7 @@ const props = withDefaults(
21
21
  )
22
22
 
23
23
  const iframeWidth = computed(() => {
24
- return props.view === 'mobile' ? '400px' : '600px'
24
+ return props.view === 'mobile' ? '400px' : '800px'
25
25
  })
26
26
 
27
27
  // Extract parameters directly from message
@@ -108,7 +108,6 @@ watch(
108
108
  <HtmlPreviewIframe
109
109
  :html="iframeContent"
110
110
  title="Email preview"
111
- :min-height="600"
112
111
  class="border-0 bg-white w-full"
113
112
  :data-testid="emailPreviewTestIds.iframe"
114
113
  />
@@ -20,7 +20,7 @@ const emit = defineEmits<{
20
20
  unpublish: [MessageGroupWithMessageSummary]
21
21
  }>()
22
22
 
23
- const adminStore = useAdminStore()
23
+ const appContext = useAppContextStore()
24
24
  const { trackEvent } = useTracking()
25
25
 
26
26
  const { t, locale } = useI18n()
@@ -44,7 +44,7 @@ function getLocalizedMessage(row: MessageGroupWithMessageSummary) {
44
44
 
45
45
  function rowActions(row: MessageGroupWithMessageSummary) {
46
46
  const publishAction =
47
- adminStore.isAdminModeActive && row.visibility === 'internal'
47
+ appContext.isAdmin && row.visibility === 'internal'
48
48
  ? {
49
49
  label: t('common.actions.publish'),
50
50
  icon: 'ph:globe',
@@ -52,7 +52,7 @@ function rowActions(row: MessageGroupWithMessageSummary) {
52
52
  onSelect: () => emit('publish', row),
53
53
  'data-testid': `${templateListTableTestIds.rowPublishAction}-${row.id}`,
54
54
  }
55
- : adminStore.isAdminModeActive && row.visibility === 'public'
55
+ : appContext.isAdmin && row.visibility === 'public'
56
56
  ? {
57
57
  label: t('common.actions.unpublish'),
58
58
  icon: 'ph:globe-x',
@@ -158,7 +158,7 @@ const props = defineProps<{
158
158
  }>()
159
159
 
160
160
  const filteredMessageGroups = computed(() => {
161
- if (adminStore.isAdminModeActive) return props.messageGroups
161
+ if (appContext.isAdmin) return props.messageGroups
162
162
  return props.messageGroups.filter((mg) => mg.visibility !== 'internal')
163
163
  })
164
164
 
@@ -13,6 +13,7 @@ export interface ComposerContext {
13
13
  default_tags?: Ref<string[]>
14
14
  metaWabaId?: Ref<string>
15
15
  metaAccessToken?: Ref<string>
16
+ isAdmin?: Ref<boolean>
16
17
  }
17
18
 
18
19
  export const COMPOSER_CONTEXT_KEY: InjectionKey<ComposerContext> = Symbol.for('composer-context')
@@ -23,7 +24,13 @@ export function defineComposerContext(context: ComposerContext): void {
23
24
  useNuxtApp().vueApp.provide(COMPOSER_CONTEXT_KEY, context)
24
25
  }
25
26
 
27
+ const SERVER_FALLBACK: ComposerContext = {
28
+ authenticationToken: ref(''),
29
+ }
30
+
26
31
  export function useComposerContext(): ComposerContext {
32
+ if (import.meta.server) return SERVER_FALLBACK
33
+
27
34
  // runWithContext ensures inject() can reach app-level provides
28
35
  // even outside component setup (e.g. Pinia stores, middleware).
29
36
  const context = useNuxtApp().vueApp.runWithContext(() => inject(COMPOSER_CONTEXT_KEY))
@@ -1,15 +1,14 @@
1
1
  import type { TrackingEventMap } from '~/types/tracking'
2
2
 
3
3
  export function useTracking() {
4
- const { product, mainLanguage } = storeToRefs(useAppContextStore())
5
- const { isAdminModeActive } = storeToRefs(useAdminStore())
4
+ const { product, mainLanguage, isAdmin } = storeToRefs(useAppContextStore())
6
5
 
7
6
  function getDefaultProperties(): Record<string, unknown> {
8
7
  return {
9
8
  event_origin: 'message-composer',
10
9
  product: product.value,
11
10
  main_language: mainLanguage.value,
12
- is_admin: isAdminModeActive.value,
11
+ is_admin: isAdmin.value,
13
12
  }
14
13
  }
15
14
 
@@ -27,6 +27,7 @@ export const useAppContextStore = defineStore('appContext', () => {
27
27
  () => ctx.dinamicValues?.value ?? [],
28
28
  )
29
29
  const default_tags = computed<string[]>(() => ctx.default_tags?.value ?? [])
30
+ const isAdmin = computed(() => ctx.isAdmin?.value ?? false)
30
31
  const metaWabaId = computed(() => ctx.metaWabaId?.value ?? '')
31
32
  const metaAccessToken = computed(() => ctx.metaAccessToken?.value ?? '')
32
33
 
@@ -88,6 +89,7 @@ export const useAppContextStore = defineStore('appContext', () => {
88
89
  metaAccessToken,
89
90
  owner_id,
90
91
  default_tags,
92
+ isAdmin,
91
93
 
92
94
  isInitialized: computed(() => isInitialized.value),
93
95
 
@@ -58,12 +58,26 @@ export interface EmailTranslationsFiltersProperties extends EmailLanguagePropert
58
58
  show_only_changes: boolean
59
59
  }
60
60
 
61
+ // ── Brand shared property interfaces ──
62
+
63
+ export interface BrandBaseProperties {
64
+ brand_id: string
65
+ brand_name: string
66
+ }
67
+
61
68
  // ── Event map ──
62
69
 
63
70
  export interface TrackingEventMap {
64
71
  // === Global ===
65
72
  'Page View': { page_name: string }
66
73
 
74
+ // === Brand ===
75
+ brand_create: BrandBaseProperties & { has_logo: boolean }
76
+ brand_update: BrandBaseProperties & { changed_fields: string[] }
77
+ brand_delete: BrandBaseProperties
78
+ brand_select: BrandBaseProperties & { previous_brand_id: string | null }
79
+ brand_count_changed: { brand_count: number }
80
+
67
81
  // === Table_List ===
68
82
  change_filters: {
69
83
  search_term: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.smartpricing/message-composer-layer",
3
- "version": "1.0.26",
3
+ "version": "4.0.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -30,7 +30,6 @@
30
30
  "@vueuse/core": "^14.3.0",
31
31
  "@vueuse/nuxt": "^14.3.0",
32
32
  "colorthief": "^3.3.1",
33
- "jwt-decode": "^4.0.0",
34
33
  "monaco-editor": "^0.55.1",
35
34
  "nuxt": "^4.4.5",
36
35
  "ofetch": "^1.5.1",
@@ -39,7 +38,7 @@
39
38
  "vue-router": "^5.0.7",
40
39
  "vue3-emoji-picker": "^1.1.8",
41
40
  "zod": "^4.4.3",
42
- "@dev.smartpricing/message-composer-utils": "3.1.26"
41
+ "@dev.smartpricing/message-composer-utils": "4.0.1"
43
42
  },
44
43
  "devDependencies": {
45
44
  "@nuxt/eslint": "^1.15.2",
@@ -1,7 +0,0 @@
1
- export default defineNuxtPlugin(() => {
2
- onNuxtReady(() => {
3
- const adminStore = useAdminStore()
4
- const { shift_space } = useMagicKeys()
5
- whenever(shift_space!, () => adminStore.toggleAdminMode())
6
- })
7
- })
@@ -1,50 +0,0 @@
1
- import { jwtDecode } from 'jwt-decode'
2
-
3
- interface TokenData {
4
- id: number
5
- role: string
6
- accounting: boolean
7
- email: string
8
- }
9
-
10
- export const useAdminStore = defineStore('admin', () => {
11
- const appContext = useAppContextStore()
12
- const adminModeActive = ref(false)
13
-
14
- const isAccounting = computed(() => {
15
- const token = appContext.authenticationToken
16
- if (!token) return false
17
- try {
18
- const payload = jwtDecode<TokenData>(token)
19
- return payload.accounting === true
20
- } catch {
21
- return false
22
- }
23
- })
24
-
25
- function activateAdminMode() {
26
- if (!isAccounting.value) return
27
- adminModeActive.value = true
28
- }
29
-
30
- function deactivateAdminMode() {
31
- adminModeActive.value = false
32
- }
33
-
34
- function toggleAdminMode() {
35
- if (!isAccounting.value) {
36
- adminModeActive.value = false
37
- return
38
- }
39
- adminModeActive.value = !adminModeActive.value
40
- }
41
-
42
- const isAdminModeActive = computed(() => adminModeActive.value)
43
-
44
- return {
45
- activateAdminMode,
46
- deactivateAdminMode,
47
- isAdminModeActive,
48
- toggleAdminMode,
49
- }
50
- })