@dev.smartpricing/message-composer-layer 1.0.12 → 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.
@@ -0,0 +1,424 @@
1
+ <script setup lang="ts">
2
+ import IncompleteTranslationsDialog from '@/components/Common/IncompleteTranslationsDialog.vue'
3
+ import { computed, ref, watch, onUnmounted } from 'vue'
4
+ import type { WhatsappMessage, PostEntity } from '@dev.smartpricing/message-composer-utils/types'
5
+ import { useAppContextStore } from '@/stores/appContext'
6
+ import { useMetaSubmissionStore } from '@/stores/metaSubmission'
7
+ import { useMessageGroupQuery, useLanguagesQuery } from '~/queries'
8
+ import type { WaHeaderType } from '~/types/tracking'
9
+
10
+ const props = defineProps<{
11
+ mode: 'create' | 'edit'
12
+ messageGroupId?: string
13
+ }>()
14
+
15
+ const emit = defineEmits<{
16
+ saved: [data: { id: string | number; name: string; status?: string }]
17
+ cancelled: [data?: { id?: string | number }]
18
+ }>()
19
+
20
+ const store = useWhatsappComposerStore()
21
+ const { mainLanguage } = storeToRefs(useAppContextStore())
22
+ const { trackEvent } = useTracking()
23
+ const { goBackOrFallback } = useGoBack()
24
+ const { submitToMeta } = useMetaSubmissionStore()
25
+
26
+ // ── Init ──────────────────────────────────────────────────────────
27
+ if (props.mode === 'create') {
28
+ store.initCreate(mainLanguage.value)
29
+ }
30
+
31
+ // ── Edit mode: fetch message group ────────────────────────────────
32
+ const { data: messageGroupData, isPending: isFetching } =
33
+ props.mode === 'edit' && props.messageGroupId
34
+ ? useMessageGroupQuery(props.messageGroupId)
35
+ : { data: ref(null), isPending: ref(false) }
36
+
37
+ // ── Change tracking ───────────────────────────────────────────────
38
+ const changeTracking = useChangeTracking(computed(() => store.changeTrackingId))
39
+
40
+ // Record initial state for create mode
41
+ if (props.mode === 'create' && store.currentMessage) {
42
+ changeTracking.recordOriginalState(mainLanguage.value, store.currentMessage as WhatsappMessage)
43
+ }
44
+
45
+ // Watch messageGroupData to init edit mode
46
+ if (props.mode === 'edit') {
47
+ watch(
48
+ messageGroupData,
49
+ (data) => {
50
+ if (!data?.messages) return
51
+
52
+ store.initEdit(data, mainLanguage.value)
53
+
54
+ // Record original states for change tracking
55
+ for (const message of data.messages) {
56
+ if (message.render_type === 'whatsapp_v1') {
57
+ changeTracking.recordOriginalState(message.language_id, message as WhatsappMessage)
58
+ }
59
+ }
60
+ },
61
+ { immediate: true },
62
+ )
63
+ }
64
+
65
+ // ── WhatsApp template behaviors ───────────────────────────────────
66
+ const { ensureUnsubscribeButton } = useWhatsappTemplateBehaviors({
67
+ category: computed(() => store.category),
68
+ messages: computed(() => store.messages),
69
+ })
70
+
71
+ if (props.mode === 'create') {
72
+ ensureUnsubscribeButton()
73
+ }
74
+
75
+ // ── Validation ────────────────────────────────────────────────────
76
+ const messagesRef = computed(() => store.messages)
77
+ const { formState, draftSchema, validationErrors, validateDraft, validateFull, clearErrors } =
78
+ useTemplateFormValidation({
79
+ type: 'whatsapp',
80
+ name: computed(() => store.name),
81
+ category: computed(() => store.category),
82
+ messages: messagesRef,
83
+ })
84
+
85
+ // ── Template operations ───────────────────────────────────────────
86
+ const successFlag = ref(false)
87
+
88
+ const { saving, createTemplate, updateTemplate } = useTemplateOperations({
89
+ onSuccess: () => {
90
+ successFlag.value = true
91
+ changeTracking.clearAllChanges()
92
+ if (props.mode === 'create') {
93
+ goBackOrFallback()
94
+ }
95
+ },
96
+ })
97
+
98
+ // ── Change detection ──────────────────────────────────────────────
99
+ const hasAnyChanges = computed(() => {
100
+ if (store.name !== store.originalName) return true
101
+ if (store.category !== store.originalCategory) return true
102
+
103
+ for (const languageId in store.messages) {
104
+ const message = store.messages[languageId]
105
+ if (message && changeTracking.hasChanges(languageId, message as WhatsappMessage)) {
106
+ return true
107
+ }
108
+ }
109
+ return false
110
+ })
111
+
112
+ useExitConfirmation(computed(() => hasAnyChanges.value && !successFlag.value))
113
+
114
+ // ── Incomplete translations ───────────────────────────────────────
115
+ const { data: languages } = useLanguagesQuery()
116
+
117
+ const { incompleteLanguages, hasIncompleteTranslations } = useIncompleteTranslations({
118
+ messages: computed(() => store.messages as Record<string, WhatsappMessage>),
119
+ languages,
120
+ })
121
+
122
+ const showIncompleteDialog = ref(false)
123
+ const pendingAction = ref<'save_draft' | 'save_as_new' | 'request_approval' | null>(null)
124
+
125
+ // ── Auto-translate ────────────────────────────────────────────────
126
+ const { translateMessage, isTranslating } = useAutoTranslate()
127
+
128
+ // ── Cleanup ───────────────────────────────────────────────────────
129
+ onUnmounted(() => {
130
+ store.$reset()
131
+ })
132
+
133
+ // ── Tracking helpers ──────────────────────────────────────────────
134
+ const trackPrefix = computed(() => (props.mode === 'create' ? 'wa_create' : 'wa_edit'))
135
+
136
+ function getWaBaseProps() {
137
+ return { name: store.name, category: store.category }
138
+ }
139
+
140
+ function getWaDraftProps() {
141
+ const resolved = resolveDefaultLanguage(store.messages, mainLanguage.value)
142
+ const msg = resolved ? (store.messages[resolved] as WhatsappMessage) : undefined
143
+ return {
144
+ ...getWaBaseProps(),
145
+ languages: Object.keys(store.messages),
146
+ header: (msg?.body?.header?.type ?? 'none') as WaHeaderType,
147
+ footer: !!msg?.body?.footer?.enabled,
148
+ buttons: msg?.body?.buttons?.buttons?.length ?? 0,
149
+ is_internal: store.isInternal,
150
+ }
151
+ }
152
+
153
+ watch(
154
+ () => store.activeTab,
155
+ (tab) => {
156
+ trackEvent(`${trackPrefix.value}.change_tab`, { ...getWaBaseProps(), tab })
157
+ },
158
+ )
159
+
160
+ // Edit mode: watch for mainLanguage resolution
161
+ if (props.mode === 'edit') {
162
+ watch(
163
+ () => store.messages[mainLanguage.value],
164
+ () => {
165
+ const resolved = resolveDefaultLanguage(store.messages, mainLanguage.value)
166
+ if (resolved && resolved !== mainLanguage.value) {
167
+ store.language = resolved
168
+ store.targetLanguage = resolved
169
+ }
170
+ },
171
+ )
172
+ }
173
+
174
+ // ── Save logic ────────────────────────────────────────────────────
175
+ async function saveDraft() {
176
+ trackEvent(`${trackPrefix.value}.save_draft`, getWaDraftProps())
177
+
178
+ if (props.mode === 'create') {
179
+ const result = await createTemplate(
180
+ { messageGroup: store.messageGroupData, messages: store.messages },
181
+ 'draft',
182
+ )
183
+ if (result) {
184
+ emit('saved', { id: result.id, name: result.name })
185
+ notifyParent('TEMPLATE_SAVED_DRAFT', { id: result.id, name: result.name })
186
+ }
187
+ } else {
188
+ if (!store.messageGroupId) return
189
+ await updateTemplate(store.messageGroupId, {
190
+ messageGroup: store.messageGroupUpdateData,
191
+ messages: store.messages,
192
+ })
193
+ emit('saved', { id: store.messageGroupId, name: store.name })
194
+ notifyParent('TEMPLATE_SAVED_DRAFT', { id: store.messageGroupId, name: store.name })
195
+ goBackOrFallback()
196
+ }
197
+ }
198
+
199
+ async function saveAsNew() {
200
+ if (!store.messageGroupId) return
201
+ trackEvent(`${trackPrefix.value}.save_as_new`, getWaDraftProps())
202
+
203
+ const cleanedMessages: Record<string, PostEntity<WhatsappMessage>> = {}
204
+ for (const [languageId, message] of Object.entries(store.messages)) {
205
+ const { id, message_group_id, created_at, updated_at, ...templateMessage } =
206
+ message as WhatsappMessage
207
+ cleanedMessages[languageId] = {
208
+ ...templateMessage,
209
+ message_group_id: '',
210
+ metadata: {},
211
+ } as PostEntity<WhatsappMessage>
212
+ }
213
+
214
+ const result = await createTemplate(
215
+ { messageGroup: store.messageGroupUpdateData, messages: cleanedMessages },
216
+ 'draft',
217
+ )
218
+ if (result) {
219
+ emit('saved', { id: result.id, name: result.name })
220
+ notifyParent('TEMPLATE_SAVED_AS_NEW', { id: result.id, name: result.name })
221
+ navigateTo({ name: 'whatsapp-message-edit', params: { id: result.id } }, { replace: true })
222
+ }
223
+ }
224
+
225
+ async function requestApproval() {
226
+ trackEvent(`${trackPrefix.value}.request_approval`, getWaDraftProps())
227
+
228
+ if (props.mode === 'create') {
229
+ const result = await createTemplate(
230
+ { messageGroup: store.messageGroupData, messages: store.messages },
231
+ 'draft',
232
+ )
233
+ if (result) {
234
+ await submitToMeta(result.id.toString())
235
+ emit('saved', { id: result.id, name: result.name })
236
+ notifyParent('TEMPLATE_APPROVAL_REQUESTED', { id: result.id, name: result.name })
237
+ }
238
+ } else {
239
+ if (!store.messageGroupId) return
240
+ await updateTemplate(store.messageGroupId, {
241
+ messageGroup: store.messageGroupUpdateData,
242
+ messages: store.messages,
243
+ })
244
+ await submitToMeta(store.messageGroupId)
245
+ emit('saved', { id: store.messageGroupId, name: store.name })
246
+ notifyParent('TEMPLATE_APPROVAL_REQUESTED', { id: store.messageGroupId, name: store.name })
247
+ goBackOrFallback()
248
+ }
249
+ }
250
+
251
+ // ── Public action handlers ────────────────────────────────────────
252
+ async function handleSaveDraft() {
253
+ if (!(await validateDraft())) return
254
+ if (hasIncompleteTranslations.value) {
255
+ pendingAction.value = 'save_draft'
256
+ showIncompleteDialog.value = true
257
+ } else {
258
+ saveDraft()
259
+ }
260
+ }
261
+
262
+ async function handleSaveAsNew() {
263
+ if (!(await validateDraft())) return
264
+ if (hasIncompleteTranslations.value) {
265
+ pendingAction.value = 'save_as_new'
266
+ showIncompleteDialog.value = true
267
+ } else {
268
+ saveAsNew()
269
+ }
270
+ }
271
+
272
+ async function handleRequestApproval() {
273
+ if (!(await validateFull())) return
274
+ if (hasIncompleteTranslations.value) {
275
+ pendingAction.value = 'request_approval'
276
+ showIncompleteDialog.value = true
277
+ } else {
278
+ requestApproval()
279
+ }
280
+ }
281
+
282
+ function handleCancel() {
283
+ const data =
284
+ props.mode === 'edit' && store.messageGroupId ? { id: store.messageGroupId } : undefined
285
+ emit('cancelled', data)
286
+ notifyParent('TEMPLATE_CANCELLED', data ? { id: data.id } : undefined)
287
+ goBackOrFallback()
288
+ }
289
+
290
+ function proceedWithSave() {
291
+ if (pendingAction.value === 'save_draft') {
292
+ saveDraft()
293
+ } else if (pendingAction.value === 'save_as_new') {
294
+ saveAsNew()
295
+ } else if (pendingAction.value === 'request_approval') {
296
+ requestApproval()
297
+ }
298
+ pendingAction.value = null
299
+ }
300
+
301
+ function goToTranslations() {
302
+ store.activeTab = 'translations'
303
+ pendingAction.value = null
304
+ }
305
+
306
+ async function translateAndSave() {
307
+ if (!incompleteLanguages.value.length) {
308
+ proceedWithSave()
309
+ return
310
+ }
311
+
312
+ const baseMessage = store.messages[store.language]
313
+ if (!baseMessage) {
314
+ proceedWithSave()
315
+ return
316
+ }
317
+
318
+ for (const { language: lang } of incompleteLanguages.value) {
319
+ const targetMsg = store.messages[lang.id]
320
+ if (!targetMsg) continue
321
+
322
+ await translateMessage(
323
+ baseMessage as WhatsappMessage,
324
+ targetMsg as WhatsappMessage,
325
+ store.language,
326
+ lang.id,
327
+ )
328
+ }
329
+
330
+ proceedWithSave()
331
+ }
332
+
333
+ // ── Tracking event forwarders ─────────────────────────────────────
334
+ function onAddLanguage(lang: string) {
335
+ trackEvent(`${trackPrefix.value}.add_language`, { ...getWaBaseProps(), language: lang })
336
+ }
337
+
338
+ function onRemoveLanguage(lang: string) {
339
+ trackEvent(`${trackPrefix.value}.remove_language`, { ...getWaBaseProps(), language: lang })
340
+ }
341
+
342
+ function onAutoTranslateTracked(lang: string) {
343
+ trackEvent(`${trackPrefix.value}.auto_translate`, { ...getWaBaseProps(), language: lang })
344
+ }
345
+
346
+ function onPreview(lang: string) {
347
+ trackEvent(`${trackPrefix.value}.preview`, { ...getWaBaseProps(), language: lang })
348
+ }
349
+
350
+ function onFiltersChanged(payload: {
351
+ language: string
352
+ search_term: string
353
+ show_only_missing: boolean
354
+ show_only_changes: boolean
355
+ }) {
356
+ trackEvent(`${trackPrefix.value}.translations_filters`, { ...getWaBaseProps(), ...payload })
357
+ }
358
+
359
+ // ── Expose ────────────────────────────────────────────────────────
360
+ defineExpose({
361
+ handleSaveDraft,
362
+ handleSaveAsNew,
363
+ handleRequestApproval,
364
+ handleCancel,
365
+ saving,
366
+ isTranslating,
367
+ validationErrors,
368
+ isPending: store.isPending,
369
+ isApproved: store.isApproved,
370
+ })
371
+ </script>
372
+
373
+ <template>
374
+ <UForm
375
+ ref="templateForm"
376
+ :schema="draftSchema"
377
+ :state="formState"
378
+ :validate-on="[]"
379
+ class="flex-1 relative min-h-0 flex flex-col"
380
+ >
381
+ <div v-if="validationErrors.length">
382
+ <UAlert
383
+ color="error"
384
+ variant="subtle"
385
+ icon="i-lucide-alert-triangle"
386
+ :title="$t('editor.validation.error.title')"
387
+ :close="true"
388
+ @update:open="clearErrors()"
389
+ >
390
+ <template #description>
391
+ <ul class="list-disc pl-4 space-y-1">
392
+ <li v-for="err in validationErrors" :key="err">
393
+ {{ err }}
394
+ </li>
395
+ </ul>
396
+ </template>
397
+ </UAlert>
398
+ </div>
399
+
400
+ <ComposerWhatsappContent
401
+ v-if="store.activeTab === 'content'"
402
+ :mode="mode"
403
+ @category-changed="ensureUnsubscribeButton"
404
+ />
405
+
406
+ <ComposerWhatsappTranslations
407
+ v-if="store.activeTab === 'translations'"
408
+ :mode="mode"
409
+ @add-language="onAddLanguage"
410
+ @remove-language="onRemoveLanguage"
411
+ @auto-translate-tracked="onAutoTranslateTracked"
412
+ @preview="onPreview"
413
+ @filters-changed="onFiltersChanged"
414
+ />
415
+ </UForm>
416
+
417
+ <IncompleteTranslationsDialog
418
+ v-model="showIncompleteDialog"
419
+ :incomplete-languages="incompleteLanguages"
420
+ @save-without-translating="proceedWithSave"
421
+ @translate-and-save="translateAndSave"
422
+ @go-to-translations="goToTranslations"
423
+ />
424
+ </template>
@@ -0,0 +1,88 @@
1
+ <script setup lang="ts">
2
+ import WhatsappEditorSyncWrapper from '@/components/Whatsapp/Editor/SyncWrapper.vue'
3
+ import WhatsappPreview from '@/components/Whatsapp/PreviewPanel.vue'
4
+ import CategoryChanger from '@/components/Whatsapp/CategoryChanger.vue'
5
+ import type { WhatsappMessage, PostEntity } from '@dev.smartpricing/message-composer-utils/types'
6
+
7
+ const props = defineProps<{
8
+ mode: 'create' | 'edit'
9
+ }>()
10
+
11
+ const emit = defineEmits<{
12
+ 'category-changed': []
13
+ }>()
14
+
15
+ const store = useWhatsappComposerStore()
16
+ const adminStore = useAdminStore()
17
+
18
+ const testIds = computed(() =>
19
+ props.mode === 'create' ? templateWhatsappCreateTestIds : templateWhatsappEditTestIds,
20
+ )
21
+ </script>
22
+
23
+ <template>
24
+ <UCard
25
+ v-if="store.currentMessage"
26
+ :ui="{
27
+ root: 'flex flex-col',
28
+ body: 'flex-1 overflow-hidden sm:px-0 px-0 py-0 sm:py-0',
29
+ header: 'sm:px-4 px-4 py-4 sm:py-4',
30
+ }"
31
+ :data-testid="testIds.contentCard"
32
+ >
33
+ <template #header>
34
+ <div class="grid grid-cols-3 gap-2">
35
+ <UFieldGroup>
36
+ <UBadge variant="outline" color="neutral" size="lg">{{
37
+ $t(`pages.whatsapp.${mode === 'create' ? 'create' : 'edit'}_message_group.name`)
38
+ }}</UBadge>
39
+ <UInput
40
+ v-model="store.name"
41
+ :placeholder="
42
+ $t(
43
+ `pages.whatsapp.${mode === 'create' ? 'create' : 'edit'}_message_group.name_placeholder`,
44
+ )
45
+ "
46
+ :data-testid="testIds.nameInput"
47
+ />
48
+ </UFieldGroup>
49
+
50
+ <UFieldGroup>
51
+ <UBadge variant="outline" color="neutral" size="lg">{{
52
+ $t(`pages.whatsapp.${mode === 'create' ? 'create' : 'edit'}_message_group.category`)
53
+ }}</UBadge>
54
+ <CategoryChanger v-model:category="store.category" @changed="emit('category-changed')" />
55
+ </UFieldGroup>
56
+
57
+ <USwitch
58
+ v-if="mode === 'create' && adminStore.isAdminModeActive"
59
+ v-model="store.isInternal"
60
+ :label="$t('common.internal')"
61
+ color="info"
62
+ :data-testid="testIds.internalSwitch"
63
+ />
64
+ </div>
65
+ </template>
66
+
67
+ <div class="grid grid-cols-3 divide-x divide-muted overflow-auto max-h-full h-full">
68
+ <div class="col-span-2 overflow-y-auto pt-4 pb-4" v-if="store.currentMessage">
69
+ <h2 class="px-4 pb-4 label-lg">
70
+ {{
71
+ $t(
72
+ `pages.whatsapp.${mode === 'create' ? 'create' : 'edit'}_message_group.message_content`,
73
+ )
74
+ }}
75
+ </h2>
76
+ <WhatsappEditorSyncWrapper
77
+ v-model="store.messages[store.language]"
78
+ :messages="store.messages"
79
+ :main-language-id="store.language"
80
+ :key="store.language"
81
+ />
82
+ </div>
83
+ <div class="col-span-1 overflow-y-auto" v-if="store.currentMessage">
84
+ <WhatsappPreview :message="store.currentMessage" />
85
+ </div>
86
+ </div>
87
+ </UCard>
88
+ </template>
@@ -0,0 +1,68 @@
1
+ <script setup lang="ts">
2
+ import TranslationEditor from '@/components/Whatsapp/TranslationEditor.vue'
3
+ import LanguageList from '@/components/Common/LanguageList.vue'
4
+
5
+ const props = defineProps<{
6
+ mode: 'create' | 'edit'
7
+ }>()
8
+
9
+ const emit = defineEmits<{
10
+ 'add-language': [lang: string]
11
+ 'remove-language': [lang: string]
12
+ 'auto-translate-tracked': [lang: string]
13
+ preview: [lang: string]
14
+ 'filters-changed': [
15
+ payload: {
16
+ language: string
17
+ search_term: string
18
+ show_only_missing: boolean
19
+ show_only_changes: boolean
20
+ },
21
+ ]
22
+ }>()
23
+
24
+ const store = useWhatsappComposerStore()
25
+
26
+ const testIds = computed(() =>
27
+ props.mode === 'create' ? templateWhatsappCreateTestIds : templateWhatsappEditTestIds,
28
+ )
29
+ </script>
30
+
31
+ <template>
32
+ <UCard
33
+ v-if="store.currentMessage"
34
+ :ui="{
35
+ root: 'flex flex-col h-full',
36
+ body: 'flex-1 overflow-hidden sm:px-0 px-0 py-0 sm:py-0',
37
+ header: 'sm:px-4 px-4 py-4 sm:py-4',
38
+ }"
39
+ :data-testid="testIds.translationsCard"
40
+ >
41
+ <div class="grid grid-cols-[auto_1fr] divide-x divide-muted overflow-auto max-h-full h-full">
42
+ <div class="overflow-y-auto">
43
+ <LanguageList
44
+ v-model:messages="store.messages"
45
+ v-model:language="store.targetLanguage"
46
+ render-type="whatsapp_v1"
47
+ :message-group-id="store.changeTrackingId"
48
+ :main-language-id="store.language"
49
+ @add-language="emit('add-language', $event)"
50
+ @remove-language="emit('remove-language', $event)"
51
+ />
52
+ </div>
53
+ <div class="overflow-y-auto" v-if="store.currentMessage && store.targetMessage">
54
+ <TranslationEditor
55
+ :base-message="store.messages[store.language] || store.messages['en']"
56
+ v-model:target-message="store.messages[store.targetLanguage]"
57
+ :messages="store.messages"
58
+ :current-language="store.targetLanguage"
59
+ :base-language="store.language"
60
+ :message-group-id="store.changeTrackingId"
61
+ @auto-translate-tracked="emit('auto-translate-tracked', $event)"
62
+ @preview="emit('preview', $event)"
63
+ @filters-changed="emit('filters-changed', $event)"
64
+ />
65
+ </div>
66
+ </div>
67
+ </UCard>
68
+ </template>
@@ -9,7 +9,7 @@ const { trackEvent } = useTracking()
9
9
 
10
10
  function createTemplate(type: 'whatsapp' | 'email') {
11
11
  trackEvent('message_create', { message_kind: type })
12
- router.push(`/template/${type}/create`)
12
+ router.push({ name: `${type}-message-create` })
13
13
  }
14
14
 
15
15
  const templateOptions = computed(() => {
@@ -1,43 +1,43 @@
1
1
  <script setup lang="ts">
2
- import type { ButtonProps, TabsItem } from "#ui/types";
2
+ import type { ButtonProps, TabsItem } from '#ui/types'
3
3
 
4
- const props = defineProps<{
5
- title: string
6
- tabs?: TabsItem[]
7
- activeTab?: string | number
8
- back?: ButtonProps
9
- testId?: string
10
- }>();
4
+ const props = defineProps<{
5
+ title: string
6
+ tabs?: TabsItem[]
7
+ activeTab?: string | number
8
+ back?: ButtonProps
9
+ testId?: string
10
+ }>()
11
11
 
12
- const emit = defineEmits<{
13
- tabChange: [value: string | number]
14
- }>();
12
+ const emit = defineEmits<{
13
+ tabChange: [value: string | number]
14
+ }>()
15
15
 
16
- watchEffect(() => {
17
- useHead({ title: props.title });
18
- });
16
+ watchEffect(() => {
17
+ useHead({ title: props.title })
18
+ })
19
19
  </script>
20
20
 
21
21
  <template>
22
- <div class="flex flex-col min-h-0">
23
- <SNavigationBarHeader
24
- :title="title"
25
- :back="back"
26
- :tabs="tabs"
27
- :active-tab="activeTab"
28
- :data-testid="testId"
29
- class="bg-white sticky top-0 z-10"
30
- @tab-change="emit('tabChange', $event)"
31
- >
32
- <template #actions>
33
- <div class="flex items-center gap-1.5">
34
- <slot name="actions" />
35
- </div>
36
- </template>
37
- </SNavigationBarHeader>
22
+ <div class="flex flex-col min-h-0">
23
+ <SNavigationBarHeader
24
+ :title="title"
25
+ :back="back"
26
+ :tabs="tabs"
27
+ :active-tab="activeTab"
28
+ :data-testid="testId"
29
+ class="bg-white sticky top-0 z-10"
30
+ @tab-change="emit('tabChange', $event)"
31
+ >
32
+ <template #actions>
33
+ <div class="flex items-center gap-1.5">
34
+ <slot name="actions" />
35
+ </div>
36
+ </template>
37
+ </SNavigationBarHeader>
38
38
 
39
- <div class="flex flex-col gap-4 sm:gap-6 flex-1 overflow-y-auto sm:p-4 p-4">
40
- <slot />
41
- </div>
42
- </div>
39
+ <div class="flex flex-col gap-4 sm:gap-6 flex-1 overflow-y-auto sm:p-4 p-4">
40
+ <slot />
41
+ </div>
42
+ </div>
43
43
  </template>
@@ -3,7 +3,7 @@ import type { RouteLocationRaw } from 'vue-router'
3
3
  export function useGoBack() {
4
4
  const router = useRouter()
5
5
 
6
- function goBackOrFallback(fallback: RouteLocationRaw = '/') {
6
+ function goBackOrFallback(fallback: RouteLocationRaw = { name: 'message-library' }) {
7
7
  if (router.options.history.state.back) {
8
8
  router.back()
9
9
  } else {
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <SNavigationPage :panel-props="{ ui: { body: 'p-0 sm:p-0 gap-0 sm:gap-0 overflow-hidden' } }">
3
- <slot />
4
- </SNavigationPage>
2
+ <SNavigationPage :panel-props="{ ui: { body: 'p-0 sm:p-0 gap-0 sm:gap-0 overflow-hidden' } }">
3
+ <slot />
4
+ </SNavigationPage>
5
5
  </template>