@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.
- package/app/components/Composer/BrandList.vue +191 -0
- package/app/components/Composer/EmailComposer.vue +505 -0
- package/app/components/Composer/EmailContent.vue +98 -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 +436 -0
- package/app/components/Composer/WhatsappContent.vue +88 -0
- package/app/components/Composer/WhatsappTranslations.vue +71 -0
- package/app/composables/useTemplateFormValidation.ts +1 -1
- 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/i18n/locales/de.ts +1 -1
- package/i18n/locales/en.ts +1 -1
- package/i18n/locales/es.ts +1 -1
- package/i18n/locales/fr.ts +1 -1
- package/i18n/locales/it.ts +1 -1
- package/package.json +4 -3
|
@@ -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
|
+
})
|
package/i18n/locales/de.ts
CHANGED
|
@@ -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: '
|
|
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',
|
package/i18n/locales/en.ts
CHANGED
|
@@ -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: '
|
|
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',
|
package/i18n/locales/es.ts
CHANGED
|
@@ -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
|
|
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',
|
package/i18n/locales/fr.ts
CHANGED
|
@@ -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: '
|
|
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',
|
package/i18n/locales/it.ts
CHANGED
|
@@ -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: '
|
|
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.
|
|
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.
|
|
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"
|