@dev.smartpricing/message-composer-layer 1.0.13 → 1.0.15

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.
@@ -0,0 +1,173 @@
1
+ import { ref, computed } from 'vue'
2
+ import type {
3
+ MessageGroupCategory,
4
+ WhatsappMessage,
5
+ PostEntity,
6
+ MessageFromDb,
7
+ MessageGroupWithMessages,
8
+ } from '@dev.smartpricing/message-composer-utils/types'
9
+ import { getWhatsappEmpty } from '@dev.smartpricing/message-composer-utils/utils'
10
+
11
+ type ComposerMode = 'create' | 'edit'
12
+
13
+ export const useWhatsappComposerStore = defineStore('whatsappComposer', () => {
14
+ // ── Core state ────────────────────────────────────────────────────
15
+ const mode = ref<ComposerMode>('create')
16
+ const messageGroupId = ref<string | null>(null)
17
+ const name = ref('')
18
+ const category = ref<MessageGroupCategory>('marketing')
19
+ const isInternal = ref(false)
20
+ const language = ref('en') // base language
21
+ const targetLanguage = ref('en')
22
+ const activeTab = ref<'content' | 'translations'>('content')
23
+ const messages = ref<Record<string, PostEntity<WhatsappMessage> | WhatsappMessage>>({})
24
+
25
+ // Edit-mode original values for change detection
26
+ const originalName = ref('')
27
+ const originalCategory = ref<MessageGroupCategory>('marketing')
28
+
29
+ // Loaded messageGroup data (edit mode)
30
+ const messageGroup = ref<MessageGroupWithMessages | null>(null)
31
+
32
+ // Create mode: draft ID for change tracking
33
+ const draftId = ref('')
34
+
35
+ // ── Computed ──────────────────────────────────────────────────────
36
+ const messageGroupData = computed(() => ({
37
+ name: name.value,
38
+ category: category.value,
39
+ visibility: isInternal.value ? 'internal' : 'public',
40
+ }))
41
+
42
+ const messageGroupUpdateData = computed(() => ({
43
+ name: name.value,
44
+ category: category.value,
45
+ }))
46
+
47
+ const currentMessage = computed(
48
+ () =>
49
+ messages.value[language.value] as (PostEntity<WhatsappMessage> | WhatsappMessage) | undefined,
50
+ )
51
+
52
+ const targetMessage = computed(
53
+ () =>
54
+ messages.value[targetLanguage.value] as
55
+ | (PostEntity<WhatsappMessage> | WhatsappMessage)
56
+ | undefined,
57
+ )
58
+
59
+ // Edit mode: status checks for conditional buttons
60
+ const isPending = computed(() =>
61
+ messageGroup.value?.messages?.some((message: MessageFromDb) => message.status === 'pending'),
62
+ )
63
+ const isApproved = computed(() =>
64
+ messageGroup.value?.messages?.some((message: MessageFromDb) => message.status === 'approved'),
65
+ )
66
+
67
+ // Change tracking ID: draftId for create, messageGroupId for edit
68
+ const changeTrackingId = computed(() =>
69
+ mode.value === 'create' ? draftId.value : (messageGroupId.value ?? ''),
70
+ )
71
+
72
+ // ── Init ──────────────────────────────────────────────────────────
73
+ function initCreate(mainLanguage: string) {
74
+ mode.value = 'create'
75
+ messageGroupId.value = null
76
+ name.value = ''
77
+ category.value = 'marketing'
78
+ isInternal.value = false
79
+ language.value = mainLanguage
80
+ targetLanguage.value = mainLanguage
81
+ activeTab.value = 'content'
82
+ messages.value = { [mainLanguage]: getWhatsappEmpty(mainLanguage) }
83
+ messageGroup.value = null
84
+ originalName.value = ''
85
+ originalCategory.value = 'marketing'
86
+ draftId.value = `draft-${Date.now()}`
87
+ }
88
+
89
+ function initEdit(data: MessageGroupWithMessages, mainLanguage: string) {
90
+ mode.value = 'edit'
91
+ messageGroupId.value = data.id?.toString() ?? null
92
+ name.value = data.name
93
+ category.value = data.category
94
+ isInternal.value = data.visibility === 'internal'
95
+ activeTab.value = 'content'
96
+ draftId.value = ''
97
+
98
+ // Deep clone to avoid mutating cache
99
+ messageGroup.value = structuredClone(data)
100
+
101
+ // Store originals for change detection
102
+ originalName.value = data.name
103
+ originalCategory.value = data.category
104
+
105
+ // Populate messages
106
+ const msgs: Record<string, WhatsappMessage> = {}
107
+ for (const message of data.messages) {
108
+ if (message.render_type === 'whatsapp_v1') {
109
+ msgs[message.language_id] = structuredClone(message) as unknown as WhatsappMessage
110
+ }
111
+ }
112
+ messages.value = msgs
113
+
114
+ // Resolve language
115
+ const resolved = resolveDefaultLanguage(msgs, mainLanguage)
116
+ language.value = resolved ?? mainLanguage
117
+ targetLanguage.value = language.value
118
+
119
+ // Set initial target language for translations tab
120
+ const langs = Object.keys(msgs)
121
+ if (langs.length > 1) {
122
+ targetLanguage.value = langs.find((l) => l !== language.value) ?? language.value
123
+ }
124
+ }
125
+
126
+ // ── Reset ─────────────────────────────────────────────────────────
127
+ function $reset() {
128
+ mode.value = 'create'
129
+ messageGroupId.value = null
130
+ name.value = ''
131
+ category.value = 'marketing'
132
+ isInternal.value = false
133
+ language.value = 'en'
134
+ targetLanguage.value = 'en'
135
+ activeTab.value = 'content'
136
+ messages.value = {}
137
+ messageGroup.value = null
138
+ originalName.value = ''
139
+ originalCategory.value = 'marketing'
140
+ draftId.value = ''
141
+ }
142
+
143
+ return {
144
+ // State
145
+ mode,
146
+ messageGroupId,
147
+ name,
148
+ category,
149
+ isInternal,
150
+ language,
151
+ targetLanguage,
152
+ activeTab,
153
+ messages,
154
+ messageGroup,
155
+ originalName,
156
+ originalCategory,
157
+ draftId,
158
+
159
+ // Computed
160
+ messageGroupData,
161
+ messageGroupUpdateData,
162
+ currentMessage,
163
+ targetMessage,
164
+ isPending,
165
+ isApproved,
166
+ changeTrackingId,
167
+
168
+ // Actions
169
+ initCreate,
170
+ initEdit,
171
+ $reset,
172
+ }
173
+ })
@@ -397,7 +397,7 @@ Diese Nachrichten müssen funktional sein, nicht werblich.
397
397
  emailRequired: 'Bitte geben Sie eine gültige E-Mail-Adresse ein',
398
398
  nameRequired: 'Name ist erforderlich',
399
399
  headerMediaRequired: 'Header-Mediendatei ist erforderlich',
400
- messageRequired: 'Englische Nachricht ist erforderlich',
400
+ messageRequired: 'Mindestens eine Sprache ist erforderlich',
401
401
  headerTooLong: 'Header-Text überschreitet 60 Zeichen',
402
402
  bodyTooLong: 'Text überschreitet 1024 Zeichen',
403
403
  footerTooLong: 'Footer-Text überschreitet 60 Zeichen',
@@ -390,7 +390,7 @@ These messages must be functional, not promotional.
390
390
  emailInvalid: "Invalid email format. Use email format (e.g., example{'@'}example.com)",
391
391
  emailRequired: 'Please enter a valid email address',
392
392
  nameRequired: 'Name is required',
393
- messageRequired: 'English message is required',
393
+ messageRequired: 'At least one language is required',
394
394
  headerTooLong: 'Header text exceeds 60 characters',
395
395
  headerMediaRequired: 'Header media file is required',
396
396
  bodyTooLong: 'Body text exceeds 1024 characters',
@@ -394,7 +394,7 @@ Estos mensajes deben ser funcionales, no promocionales.
394
394
  "Formato de correo inválido. Usa el formato de correo (p. ej., example{'@'}example.com)",
395
395
  emailRequired: 'Introduce una dirección de correo válida',
396
396
  nameRequired: 'Se requiere el nombre',
397
- messageRequired: 'Se requiere el mensaje en inglés',
397
+ messageRequired: 'Se requiere al menos un idioma',
398
398
  headerTooLong: 'El texto del encabezado supera los 60 caracteres',
399
399
  headerMediaRequired: 'El archivo multimedia del encabezado es obligatorio',
400
400
  bodyTooLong: 'El texto supera los 1024 caracteres',
@@ -394,7 +394,7 @@ Ces messages doivent être fonctionnels, et non promotionnels.
394
394
  "Format d’e-mail invalide. Utilisez le format e-mail (ex. example{'@'}example.com)",
395
395
  emailRequired: 'Veuillez saisir une adresse e-mail valide',
396
396
  nameRequired: 'Le nom est requis',
397
- messageRequired: 'Le message en anglais est requis',
397
+ messageRequired: 'Au moins une langue est requise',
398
398
  headerMediaRequired: "Le fichier média de l'en-tête est requis",
399
399
  headerTooLong: 'Le texte d’en-tête dépasse 60 caractères',
400
400
  bodyTooLong: 'Le texte dépasse 1024 caractères',
@@ -393,7 +393,7 @@ Questi messaggi devono essere funzionali, non promozionali.
393
393
  "Formato email non valido. Usa il formato email (es. example{'@'}example.com)",
394
394
  emailRequired: 'Inserisci un indirizzo email valido',
395
395
  nameRequired: 'Il nome è obbligatorio',
396
- messageRequired: 'Il messaggio in inglese è obbligatorio',
396
+ messageRequired: 'È richiesta almeno una lingua',
397
397
  headerMediaRequired: "Il file multimediale dell'intestazione è obbligatorio",
398
398
  headerTooLong: 'Il testo dell’header supera i 60 caratteri',
399
399
  bodyTooLong: 'Il testo supera i 1024 caratteri',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.smartpricing/message-composer-layer",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -39,14 +39,15 @@
39
39
  "vue-router": "^5.0.6",
40
40
  "vue3-emoji-picker": "^1.1.8",
41
41
  "zod": "^4.4.1",
42
- "@dev.smartpricing/message-composer-utils": "3.1.13"
42
+ "@dev.smartpricing/message-composer-utils": "3.1.15"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@nuxt/eslint": "^1.15.2",
46
46
  "@nuxt/test-utils": "^4.0.3",
47
47
  "@playwright/test": "^1.59.1",
48
48
  "dotenv": "^17.4.2",
49
- "nuxt-ui-layer": "git+ssh://git@github.com:smartpricing/smartness-nuxt-ui#v1.5.4"
49
+ "nuxt-ui-layer": "git+ssh://git@github.com:smartpricing/smartness-nuxt-ui#v1.5.4",
50
+ "vue-tsc": "^3.2.8"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "nuxt-ui-layer": ">=1.3.0"