@dev.smartpricing/message-composer-layer 4.2.8 → 4.2.9-toast.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
@@ -21,7 +21,7 @@ export const brandsApi = {
21
21
  if (options?.sort_by) params.set('sort_by', options.sort_by)
22
22
  if (options?.direction) params.set('direction', options.direction)
23
23
  const query = params.size ? `?${params.toString()}` : ''
24
- return apiClient<BrandFromDb[]>(`/brands${query}`)
24
+ return apiClient<BrandWithProperties[]>(`/brands${query}`)
25
25
  },
26
26
 
27
27
  getById: (id: string) => apiClient<BrandWithProperties>(`/brands/${id}`),
@@ -30,13 +30,16 @@ export const brandsApi = {
30
30
  apiClient<BrandCreateResponse>('/brands', {
31
31
  method: 'POST',
32
32
  body: data,
33
- }),
33
+
34
+ _skipErrorToast: true,
35
+ } as Record<string, unknown>),
34
36
 
35
37
  update: (id: string, data: BrandUpdateBody) =>
36
38
  apiClient<BrandFromDb>(`/brands/${id}`, {
37
39
  method: 'PUT',
38
40
  body: data,
39
- }),
41
+ _skipErrorToast: true,
42
+ } as Record<string, unknown>),
40
43
 
41
44
  delete: (id: string) => apiClient<BrandDeleteResponse>(`/brands/${id}`, { method: 'DELETE' }),
42
45
 
package/app/api/client.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { AppError } from '@dev.smartpricing/message-composer-utils/types'
2
2
  import { useAppContextStore } from '@/stores/appContext'
3
+ import { resolveErrorToast } from '~/composables/apiErrorMessages'
3
4
 
4
5
  function assertAppError(data: unknown): data is AppError {
5
6
  if (typeof data !== 'object' || data === null) {
@@ -41,20 +42,12 @@ export const apiClient = createApiClient({
41
42
  options.headers.set('sm-default-tags', store.default_tags.join(','))
42
43
  }
43
44
  },
44
- onResponseError({ response }) {
45
- const error = response._data
46
- if (assertAppError(error)) {
47
- useToast().add({
48
- color: 'error',
49
- title: error.code,
50
- description: error.message,
51
- })
52
- } else {
53
- useToast().add({
54
- color: 'error',
55
- title: 'Error',
56
- description: 'An error occurred',
57
- })
58
- }
45
+ onResponseError({ response, options }) {
46
+ if ((options as { _skipErrorToast?: boolean })._skipErrorToast) return
47
+
48
+ const t = useNuxtApp().$i18n.t
49
+ const code = assertAppError(response._data) ? response._data.code : undefined
50
+ const toast = resolveErrorToast({ status: response.status, code }, t)
51
+ useToast().add({ ...toast, color: 'error' })
59
52
  },
60
53
  })
@@ -6,13 +6,15 @@ import type {
6
6
  BrandDefaultButtonSettings,
7
7
  BrandCreateBody,
8
8
  BrandSocials,
9
+ BrandData,
9
10
  } from '@dev.smartpricing/message-composer-utils/types'
10
- import { isValidUrl } from '@dev.smartpricing/message-composer-utils/utils'
11
+ import { isValidUrl, isEmail } from '@dev.smartpricing/message-composer-utils/utils'
11
12
  import type { BrandCascadeData } from '@dev.smartpricing/message-composer-utils/utils'
12
13
  import type { BrandWithProperties } from '~/api/brands'
13
14
  import BrandPreview from '~/components/Brand/BrandPreview.vue'
14
15
  import BrandStepGeneral from '~/components/Brand/Steps/BrandStepGeneral.vue'
15
16
  import BrandStepVisual from '~/components/Brand/Steps/BrandStepVisual.vue'
17
+ import BrandStepContactInfo from '~/components/Brand/Steps/BrandStepContactInfo.vue'
16
18
  import BrandStepSocials from '~/components/Brand/Steps/BrandStepSocials.vue'
17
19
  import { useBrandQuery, useCreateBrandMutation, useUpdateBrandMutation } from '~/queries'
18
20
  import type { BrandCreateResponse } from '~/api/brands'
@@ -48,7 +50,7 @@ const isSaving = computed(() => isCreating.value || isUpdating.value)
48
50
  // ============================================================================
49
51
  // NAVIGATION
50
52
  // ============================================================================
51
- type Step = 'general' | 'visual' | 'socials'
53
+ type Step = 'general' | 'visual' | 'contact_info' | 'socials'
52
54
  const activeStep = ref<Step>('general')
53
55
 
54
56
  const navItems = computed<NavigationMenuItem[]>(() => [
@@ -68,6 +70,14 @@ const navItems = computed<NavigationMenuItem[]>(() => [
68
70
  activeStep.value = 'visual'
69
71
  },
70
72
  },
73
+ {
74
+ label: t('pages.brands.steps.contact_info'),
75
+ icon: 'ph:address-book',
76
+ active: activeStep.value === 'contact_info',
77
+ onSelect: () => {
78
+ activeStep.value = 'contact_info'
79
+ },
80
+ },
71
81
  {
72
82
  label: t('pages.brands.steps.socials'),
73
83
  icon: 'ph:share-network',
@@ -99,6 +109,7 @@ const defaultButtonSettings = ref<BrandDefaultButtonSettings>({
99
109
  backgroundColor: '#000000',
100
110
  })
101
111
 
112
+ const data = ref<BrandData>({})
102
113
  const socials = ref<BrandSocials>({})
103
114
 
104
115
  // ============================================================================
@@ -124,6 +135,7 @@ function populateForm(brand: BrandWithProperties) {
124
135
  colors.value = [...brand.colors]
125
136
  defaultEmailSettings.value = { ...brand.default_email_settings }
126
137
  defaultButtonSettings.value = { ...brand.default_button_settings }
138
+ data.value = brand.data ? { ...brand.data } : {}
127
139
  socials.value = brand.default_socials ? { ...brand.default_socials } : {}
128
140
  }
129
141
 
@@ -143,12 +155,25 @@ function resetForm() {
143
155
  textColor: '#FFFFFF',
144
156
  backgroundColor: '#000000',
145
157
  }
158
+ data.value = {}
146
159
  socials.value = {}
147
160
  activeStep.value = 'general'
148
161
  }
149
162
 
150
163
  watch(isOpen, (val) => {
151
- if (!val) resetForm()
164
+ if (!val) {
165
+ resetForm()
166
+ } else if (props.brandId && existingBrand.value) {
167
+ // Re-populate when reopening with the same brand (watcher won't fire since values didn't change)
168
+ populateForm(existingBrand.value)
169
+ }
170
+ })
171
+
172
+ // Pre-fill data.name with brand display name on create only
173
+ watch(activeStep, (step) => {
174
+ if (step === 'contact_info' && !isEditMode.value && !data.value.name && name.value.trim()) {
175
+ data.value = { ...data.value, name: name.value.trim() }
176
+ }
152
177
  })
153
178
 
154
179
  // ============================================================================
@@ -159,6 +184,7 @@ const brandCascadeData = computed<BrandCascadeData>(() => ({
159
184
  default_button_settings: defaultButtonSettings.value,
160
185
  default_socials: socials.value,
161
186
  logo_url: logoUrl.value || null,
187
+ data: data.value,
162
188
  }))
163
189
 
164
190
  // ============================================================================
@@ -168,6 +194,13 @@ function hasInvalidSocialUrls(): boolean {
168
194
  return Object.values(socials.value).some((url) => !!url && !isValidUrl(url))
169
195
  }
170
196
 
197
+ function hasInvalidContactInfo(): boolean {
198
+ const d = data.value
199
+ if (d.website && !isValidUrl(d.website)) return true
200
+ if (d.email && !isEmail(d.email)) return true
201
+ return false
202
+ }
203
+
171
204
  async function handleSave() {
172
205
  if (!name.value.trim()) {
173
206
  useToast().add({
@@ -178,6 +211,15 @@ async function handleSave() {
178
211
  return
179
212
  }
180
213
 
214
+ if (hasInvalidContactInfo()) {
215
+ useToast().add({
216
+ color: 'error',
217
+ title: t('pages.brands.validation.invalid_contact_info'),
218
+ })
219
+ activeStep.value = 'contact_info'
220
+ return
221
+ }
222
+
181
223
  if (hasInvalidSocialUrls()) {
182
224
  useToast().add({
183
225
  color: 'error',
@@ -194,6 +236,7 @@ async function handleSave() {
194
236
  default_email_settings: defaultEmailSettings.value,
195
237
  default_button_settings: defaultButtonSettings.value,
196
238
  default_socials: socials.value,
239
+ data: data.value,
197
240
  property_ids: selectedPropertyIds.value,
198
241
  }
199
242
 
@@ -221,6 +264,7 @@ async function handleSave() {
221
264
  changedFields.push('button_settings')
222
265
  if (JSON.stringify(payload.default_socials) !== JSON.stringify(brand.default_socials))
223
266
  changedFields.push('socials')
267
+ if (JSON.stringify(payload.data) !== JSON.stringify(brand.data)) changedFields.push('data')
224
268
  if (
225
269
  JSON.stringify(payload.property_ids) !==
226
270
  JSON.stringify(brand.properties?.map((bp) => bp.property_id))
@@ -291,6 +335,7 @@ async function handleSave() {
291
335
  v-if="activeStep === 'general'"
292
336
  v-model:name="name"
293
337
  v-model:selected-property-ids="selectedPropertyIds"
338
+ :brand-id="brandId"
294
339
  />
295
340
 
296
341
  <BrandStepVisual
@@ -302,6 +347,8 @@ async function handleSave() {
302
347
  v-model:default-button-settings="defaultButtonSettings"
303
348
  />
304
349
 
350
+ <BrandStepContactInfo v-if="activeStep === 'contact_info'" v-model="data" />
351
+
305
352
  <BrandStepSocials v-if="activeStep === 'socials'" v-model="socials" />
306
353
  </div>
307
354
 
@@ -0,0 +1,87 @@
1
+ <script setup lang="ts">
2
+ import type { BrandData } from '@dev.smartpricing/message-composer-utils/types'
3
+ import { isValidUrl, isEmail } from '@dev.smartpricing/message-composer-utils/utils'
4
+
5
+ const data = defineModel<BrandData>({ required: true })
6
+
7
+ const { t, locale } = useI18n()
8
+
9
+ const touched = ref<Partial<Record<string, boolean>>>({})
10
+
11
+ function update(field: keyof BrandData, value: string) {
12
+ const trimmed = value.trim()
13
+ data.value = {
14
+ ...data.value,
15
+ [field]: trimmed || undefined,
16
+ }
17
+ }
18
+
19
+ function onBlur(field: string) {
20
+ const value = data.value[field as keyof BrandData]
21
+ if (value) {
22
+ touched.value = { ...touched.value, [field]: true }
23
+ }
24
+ }
25
+
26
+ function fieldError(field: string): string | undefined {
27
+ const value = data.value[field as keyof BrandData]
28
+ if (!value || !touched.value[field]) return undefined
29
+
30
+ if (field === 'website') {
31
+ return isValidUrl(value) ? undefined : t('pages.brands.validation.invalid_url_format')
32
+ }
33
+ if (field === 'email') {
34
+ return isEmail(value) ? undefined : t('pages.brands.validation.invalid_email_format')
35
+ }
36
+ return undefined
37
+ }
38
+ </script>
39
+
40
+ <template>
41
+ <div class="space-y-4">
42
+ <UFormField :label="t('pages.brands.fields.contact_name')">
43
+ <UInput
44
+ :model-value="data.name ?? ''"
45
+ :placeholder="t('pages.brands.fields.contact_name_placeholder')"
46
+ @update:model-value="update('name', $event as string)"
47
+ />
48
+ </UFormField>
49
+
50
+ <UFormField :label="t('pages.brands.fields.contact_address')">
51
+ <UInput
52
+ :model-value="data.address ?? ''"
53
+ :placeholder="t('pages.brands.fields.contact_address_placeholder')"
54
+ @update:model-value="update('address', $event as string)"
55
+ />
56
+ </UFormField>
57
+
58
+ <UFormField :label="t('pages.brands.fields.contact_telephone')">
59
+ <SPhoneInput
60
+ class="w-full"
61
+ :model-value="data.telephone ?? ''"
62
+ :locale="locale"
63
+ @update:model-value="update('telephone', $event as string)"
64
+ />
65
+ </UFormField>
66
+
67
+ <UFormField :label="t('pages.brands.fields.contact_email')" :error="fieldError('email')">
68
+ <UInput
69
+ :model-value="data.email ?? ''"
70
+ :placeholder="t('pages.brands.fields.contact_email_placeholder')"
71
+ :color="fieldError('email') ? 'error' : undefined"
72
+ @update:model-value="update('email', $event as string)"
73
+ @blur="onBlur('email')"
74
+ />
75
+ </UFormField>
76
+
77
+ <UFormField :label="t('pages.brands.fields.contact_website')" :error="fieldError('website')">
78
+ <UInput
79
+ :model-value="data.website ?? ''"
80
+ :placeholder="t('pages.brands.fields.contact_website_placeholder')"
81
+ :color="fieldError('website') ? 'error' : undefined"
82
+ @update:model-value="update('website', $event as string)"
83
+ @blur="onBlur('website')"
84
+ />
85
+ </UFormField>
86
+ </div>
87
+ </template>
@@ -1,17 +1,46 @@
1
1
  <script setup lang="ts">
2
2
  import type { Property } from '@dev.smartpricing/message-composer-utils/types'
3
- import { usePropertiesQuery } from '~/queries'
3
+ import { useBrandsQuery, usePropertiesQuery } from '~/queries'
4
+
5
+ const props = defineProps<{
6
+ brandId?: string
7
+ }>()
4
8
 
5
9
  const name = defineModel<string>('name', { required: true })
6
10
  const selectedPropertyIds = defineModel<string[]>('selectedPropertyIds', { required: true })
7
11
 
12
+ const { t } = useI18n()
13
+
8
14
  const { data: properties } = usePropertiesQuery()
15
+ const { data: brands } = useBrandsQuery()
16
+
17
+ /** Map of property_id → brand name, excluding the brand currently being edited */
18
+ const takenPropertyMap = computed(() => {
19
+ const map = new Map<string, string>()
20
+ for (const brand of brands.value ?? []) {
21
+ if (brand.id === props.brandId) continue
22
+ for (const bp of brand.properties ?? []) {
23
+ map.set(bp.property_id, brand.name)
24
+ }
25
+ }
26
+ return map
27
+ })
28
+
29
+ const hasRestrictedProperties = computed(() =>
30
+ (properties.value ?? []).some((p: Property) => !p.hasAccess),
31
+ )
9
32
 
10
33
  const propertyItems = computed(() =>
11
- (properties.value ?? []).map((p: Property) => ({
12
- label: p.name,
13
- value: p.id,
14
- })),
34
+ (properties.value ?? []).map((p: Property) => {
35
+ const takenBy = takenPropertyMap.value.get(p.id)
36
+ return {
37
+ label: p.name,
38
+ value: p.id,
39
+ disabled: !p.hasAccess || !!takenBy,
40
+ takenByBrand: takenBy,
41
+ noAccess: !p.hasAccess,
42
+ }
43
+ }),
15
44
  )
16
45
  </script>
17
46
 
@@ -24,7 +53,14 @@ const propertyItems = computed(() =>
24
53
  />
25
54
  </UFormField>
26
55
 
27
- <!-- <UFormField :label="$t('pages.brands.fields.properties')">
56
+ <UFormField :label="$t('pages.brands.fields.properties')">
57
+ <UAlert
58
+ v-if="hasRestrictedProperties"
59
+ :description="$t('pages.brands.fields.properties_access_hint')"
60
+ color="neutral"
61
+ variant="subtle"
62
+ class="mb-2"
63
+ />
28
64
  <USelectMenu
29
65
  v-model="selectedPropertyIds"
30
66
  :items="propertyItems"
@@ -32,6 +68,23 @@ const propertyItems = computed(() =>
32
68
  value-key="value"
33
69
  :placeholder="$t('pages.brands.fields.properties_placeholder')"
34
70
  class="w-full"
35
- />
36
- </UFormField> -->
71
+ >
72
+ <template #item-label="{ item }">
73
+ <UTooltip v-if="item.noAccess" :text="t('pages.brands.fields.property_no_access_tooltip')">
74
+ <span class="text-muted">
75
+ {{ item.label }}
76
+ </span>
77
+ </UTooltip>
78
+ <UTooltip
79
+ v-else-if="item.disabled"
80
+ :text="t('pages.brands.fields.property_taken_tooltip', { brand: item.takenByBrand })"
81
+ >
82
+ <span class="text-muted">
83
+ {{ item.label }}
84
+ </span>
85
+ </UTooltip>
86
+ <span v-else>{{ item.label }}</span>
87
+ </template>
88
+ </USelectMenu>
89
+ </UFormField>
37
90
  </template>
@@ -0,0 +1,26 @@
1
+ const CODE_TO_KEY: Record<string, string> = {
2
+ 'session.missing': 'common.errors.session_expired',
3
+ 'session.expired': 'common.errors.session_expired',
4
+ }
5
+
6
+ function descriptionKeyByStatus(status: number): string {
7
+ if (status === 403) return 'common.errors.forbidden'
8
+ if (status === 404) return 'common.errors.not_found'
9
+ if (status === 503) return 'common.errors.service_unavailable'
10
+ if (status >= 500) return 'common.errors.server_error'
11
+ return 'common.errors.generic'
12
+ }
13
+
14
+ export function resolveErrorToast(
15
+ error: { status: number; code?: string },
16
+ t: (key: string) => string,
17
+ ): { title: string; description: string; id: string } {
18
+ const descriptionKey =
19
+ (error.code && CODE_TO_KEY[error.code]) || descriptionKeyByStatus(error.status)
20
+
21
+ return {
22
+ title: t('common.error'),
23
+ description: t(descriptionKey),
24
+ id: `api-error-${error.status}-${error.code || 'unknown'}`,
25
+ }
26
+ }
@@ -21,6 +21,15 @@ export default {
21
21
  desktop: 'Desktop',
22
22
  error: 'Fehler',
23
23
  errore: 'Fehler',
24
+ errors: {
25
+ forbidden: 'Sie haben keine Berechtigung, diese Aktion auszuführen.',
26
+ generic: 'Ein unerwarteter Fehler ist aufgetreten. Bitte versuchen Sie es erneut.',
27
+ not_found: 'Die angeforderte Ressource wurde nicht gefunden.',
28
+ server_error: 'Ein Serverfehler ist aufgetreten. Bitte versuchen Sie es später erneut.',
29
+ service_unavailable:
30
+ 'Der Dienst ist vorübergehend nicht verfügbar. Bitte versuchen Sie es später erneut.',
31
+ session_expired: 'Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an.',
32
+ },
24
33
  imageUpload: {
25
34
  imageJpgPngOnly: 'Es werden nur JPG- und PNG-Bilddateien unterstützt.',
26
35
  mediaFileTooLarge: 'Die Datei ist zu groß. Maximale Größe: {maxSize}MB.',
@@ -654,6 +663,16 @@ export default {
654
663
  button_bg_color: 'Hintergrund',
655
664
  button_text_color: 'Text',
656
665
  change_logo: 'Logo ändern',
666
+ contact_address: 'Adresse',
667
+ contact_address_placeholder: 'Z.B. Via Roma 1, 39100 Bolzano',
668
+ contact_email: 'E-Mail',
669
+ contact_email_placeholder: "Z.B. info{'@'}hotel.com",
670
+ contact_name: 'Name',
671
+ contact_name_placeholder: 'Z.B. Grand Hotel',
672
+ contact_telephone: 'Telefon',
673
+ contact_telephone_placeholder: 'Z.B. +39 0471 123456',
674
+ contact_website: 'Webseite',
675
+ contact_website_placeholder: 'Z.B. https://www.hotel.com',
657
676
  colors: 'Farben',
658
677
  default_button_colors: 'Standard-Schaltflächen-Farben',
659
678
  default_email_colors: 'Standard-E-Mail-Farben',
@@ -666,7 +685,11 @@ export default {
666
685
  name: 'Name',
667
686
  name_placeholder: 'Markennamen eingeben',
668
687
  properties: 'Unterkünfte',
688
+ properties_access_hint:
689
+ 'Nur Unterkünfte, auf die Sie Zugriff haben, können zugewiesen werden. Jede Unterkunft kann nur einer Marke zugeordnet sein.',
669
690
  properties_placeholder: 'Unterkünfte auswählen',
691
+ property_no_access_tooltip: 'Sie haben keinen Zugriff auf diese Unterkunft',
692
+ property_taken_tooltip: 'Bereits mit „{brand}" verknüpft',
670
693
  upload_logo: 'Logo hochladen',
671
694
  },
672
695
  new_brand: 'Neue Marke',
@@ -678,12 +701,15 @@ export default {
678
701
  save_error: 'Fehler beim Speichern der Marke',
679
702
  search_placeholder: 'Marken suchen...',
680
703
  steps: {
704
+ contact_info: 'Kontaktdaten',
681
705
  general: 'Allgemein',
682
706
  socials: 'Soziale Medien',
683
707
  visual_style: 'Visueller Stil',
684
708
  },
685
709
  title: 'Marken',
686
710
  validation: {
711
+ invalid_contact_info: 'Kontaktdaten enthalten ungültige E-Mail oder Webseite',
712
+ invalid_email_format: 'Ungültiges E-Mail-Format',
687
713
  invalid_social_url: 'Eine oder mehrere Social-URLs sind ungültig',
688
714
  invalid_url_format: 'Ungültiges URL-Format',
689
715
  name_required: 'Name ist erforderlich',
@@ -21,6 +21,14 @@ export default {
21
21
  desktop: 'Desktop',
22
22
  error: 'Error',
23
23
  errore: 'Error',
24
+ errors: {
25
+ forbidden: "You don't have permission to perform this action.",
26
+ generic: 'An unexpected error occurred. Please try again.',
27
+ not_found: 'The requested resource was not found.',
28
+ server_error: 'A server error occurred. Please try again later.',
29
+ service_unavailable: 'The service is temporarily unavailable. Please try again later.',
30
+ session_expired: 'Your session has expired. Please log in again.',
31
+ },
24
32
  imageUpload: {
25
33
  imageJpgPngOnly: 'Only JPG and PNG image files are supported.',
26
34
  mediaFileTooLarge: 'File is too large. Maximum size: {maxSize}MB.',
@@ -645,6 +653,16 @@ export default {
645
653
  button_bg_color: 'Background',
646
654
  button_text_color: 'Text',
647
655
  change_logo: 'Change logo',
656
+ contact_address: 'Address',
657
+ contact_address_placeholder: 'E.g. Via Roma 1, 39100 Bolzano',
658
+ contact_email: 'Email',
659
+ contact_email_placeholder: "E.g. info{'@'}hotel.com",
660
+ contact_name: 'Name',
661
+ contact_name_placeholder: 'E.g. Grand Hotel',
662
+ contact_telephone: 'Telephone',
663
+ contact_telephone_placeholder: 'E.g. +39 0471 123456',
664
+ contact_website: 'Website',
665
+ contact_website_placeholder: 'E.g. https://www.hotel.com',
648
666
  colors: 'Brand colors',
649
667
  default_button_colors: 'Default button colors',
650
668
  default_email_colors: 'Default email colors',
@@ -657,7 +675,11 @@ export default {
657
675
  name: 'Brand name',
658
676
  name_placeholder: 'E.g. Smartness',
659
677
  properties: 'Properties',
678
+ properties_access_hint:
679
+ 'Only properties you have access to can be assigned. Each property can only belong to one brand.',
660
680
  properties_placeholder: 'Select properties',
681
+ property_no_access_tooltip: "You don't have access to this property",
682
+ property_taken_tooltip: 'Already linked to "{brand}"',
661
683
  upload_logo: 'Upload logo',
662
684
  },
663
685
  new_brand: 'New brand',
@@ -669,12 +691,15 @@ export default {
669
691
  save_error: 'Failed to save brand. Please try again.',
670
692
  search_placeholder: 'Search brands...',
671
693
  steps: {
694
+ contact_info: 'Contact info',
672
695
  general: 'General',
673
696
  socials: 'Socials',
674
697
  visual_style: 'Visual style',
675
698
  },
676
699
  title: 'Brands',
677
700
  validation: {
701
+ invalid_contact_info: 'Contact info contains invalid email or website',
702
+ invalid_email_format: 'Invalid email format',
678
703
  invalid_social_url: 'One or more social URLs are invalid',
679
704
  invalid_url_format: 'Invalid URL format',
680
705
  name_required: 'Brand name is required',
@@ -21,6 +21,15 @@ export default {
21
21
  desktop: 'Escritorio',
22
22
  error: 'Error',
23
23
  errore: 'Error',
24
+ errors: {
25
+ forbidden: 'No tienes permiso para realizar esta acción.',
26
+ generic: 'Se ha producido un error inesperado. Inténtalo de nuevo.',
27
+ not_found: 'El recurso solicitado no fue encontrado.',
28
+ server_error: 'Se ha producido un error en el servidor. Inténtalo de nuevo más tarde.',
29
+ service_unavailable:
30
+ 'El servicio no está disponible temporalmente. Inténtalo de nuevo más tarde.',
31
+ session_expired: 'Tu sesión ha expirado. Inicia sesión de nuevo.',
32
+ },
24
33
  imageUpload: {
25
34
  imageJpgPngOnly: 'Solo se admiten archivos de imagen JPG y PNG.',
26
35
  mediaFileTooLarge: 'El archivo es demasiado grande. Tamaño máximo: {maxSize}MB.',
@@ -653,6 +662,16 @@ export default {
653
662
  button_bg_color: 'Fondo',
654
663
  button_text_color: 'Texto',
655
664
  change_logo: 'Cambiar logo',
665
+ contact_address: 'Dirección',
666
+ contact_address_placeholder: 'Ej. Via Roma 1, 39100 Bolzano',
667
+ contact_email: 'Email',
668
+ contact_email_placeholder: "Ej. info{'@'}hotel.com",
669
+ contact_name: 'Nombre',
670
+ contact_name_placeholder: 'Ej. Grand Hotel',
671
+ contact_telephone: 'Teléfono',
672
+ contact_telephone_placeholder: 'Ej. +39 0471 123456',
673
+ contact_website: 'Sitio web',
674
+ contact_website_placeholder: 'Ej. https://www.hotel.com',
656
675
  colors: 'Colores',
657
676
  default_button_colors: 'Colores de botón predeterminados',
658
677
  default_email_colors: 'Colores de correo predeterminados',
@@ -665,7 +684,11 @@ export default {
665
684
  name: 'Nombre',
666
685
  name_placeholder: 'Introduce el nombre de la marca',
667
686
  properties: 'Propiedades',
687
+ properties_access_hint:
688
+ 'Solo puedes asignar propiedades a las que tengas acceso. Cada propiedad solo puede pertenecer a una marca.',
668
689
  properties_placeholder: 'Seleccionar propiedades',
690
+ property_no_access_tooltip: 'No tienes acceso a esta propiedad',
691
+ property_taken_tooltip: 'Ya vinculada a "{brand}"',
669
692
  upload_logo: 'Subir logo',
670
693
  },
671
694
  new_brand: 'Nueva marca',
@@ -677,12 +700,15 @@ export default {
677
700
  save_error: 'Error al guardar la marca',
678
701
  search_placeholder: 'Buscar marcas...',
679
702
  steps: {
703
+ contact_info: 'Info de contacto',
680
704
  general: 'General',
681
705
  socials: 'Redes sociales',
682
706
  visual_style: 'Estilo visual',
683
707
  },
684
708
  title: 'Marcas',
685
709
  validation: {
710
+ invalid_contact_info: 'Los datos de contacto contienen email o sitio web no válidos',
711
+ invalid_email_format: 'Formato de email no válido',
686
712
  invalid_social_url: 'Una o más URLs sociales no son válidas',
687
713
  invalid_url_format: 'Formato de URL no válido',
688
714
  name_required: 'El nombre es obligatorio',
@@ -21,6 +21,15 @@ export default {
21
21
  desktop: 'Bureau',
22
22
  error: 'Erreur',
23
23
  errore: 'Erreur',
24
+ errors: {
25
+ forbidden: "Vous n'avez pas la permission d'effectuer cette action.",
26
+ generic: 'Une erreur inattendue est survenue. Veuillez réessayer.',
27
+ not_found: "La ressource demandée n'a pas été trouvée.",
28
+ server_error: 'Une erreur serveur est survenue. Veuillez réessayer plus tard.',
29
+ service_unavailable:
30
+ 'Le service est temporairement indisponible. Veuillez réessayer plus tard.',
31
+ session_expired: 'Votre session a expiré. Veuillez vous reconnecter.',
32
+ },
24
33
  imageUpload: {
25
34
  imageJpgPngOnly: 'Seuls les fichiers image JPG et PNG sont pris en charge.',
26
35
  mediaFileTooLarge: 'Le fichier est trop volumineux. Taille maximale : {maxSize} Mo.',
@@ -657,6 +666,16 @@ export default {
657
666
  button_bg_color: 'Fond',
658
667
  button_text_color: 'Texte',
659
668
  change_logo: 'Changer le logo',
669
+ contact_address: 'Adresse',
670
+ contact_address_placeholder: 'Ex. Via Roma 1, 39100 Bolzano',
671
+ contact_email: 'Email',
672
+ contact_email_placeholder: "Ex. info{'@'}hotel.com",
673
+ contact_name: 'Nom',
674
+ contact_name_placeholder: 'Ex. Grand Hotel',
675
+ contact_telephone: 'Téléphone',
676
+ contact_telephone_placeholder: 'Ex. +39 0471 123456',
677
+ contact_website: 'Site web',
678
+ contact_website_placeholder: 'Ex. https://www.hotel.com',
660
679
  colors: 'Couleurs',
661
680
  default_button_colors: 'Couleurs de bouton par défaut',
662
681
  default_email_colors: "Couleurs d'e-mail par défaut",
@@ -669,7 +688,11 @@ export default {
669
688
  name: 'Nom',
670
689
  name_placeholder: 'Saisir le nom de la marque',
671
690
  properties: 'Établissements',
691
+ properties_access_hint:
692
+ "Seuls les établissements auxquels vous avez accès peuvent être attribués. Chaque établissement ne peut appartenir qu'à une seule marque.",
672
693
  properties_placeholder: 'Sélectionner les établissements',
694
+ property_no_access_tooltip: "Vous n'avez pas accès à cet établissement",
695
+ property_taken_tooltip: 'Déjà associé à « {brand} »',
673
696
  upload_logo: 'Importer le logo',
674
697
  },
675
698
  new_brand: 'Nouvelle marque',
@@ -681,12 +704,15 @@ export default {
681
704
  save_error: "Erreur lors de l'enregistrement de la marque",
682
705
  search_placeholder: 'Rechercher des marques...',
683
706
  steps: {
707
+ contact_info: 'Coordonnées',
684
708
  general: 'Général',
685
709
  socials: 'Réseaux sociaux',
686
710
  visual_style: 'Style visuel',
687
711
  },
688
712
  title: 'Marques',
689
713
  validation: {
714
+ invalid_contact_info: 'Les coordonnées contiennent un email ou un site web invalide',
715
+ invalid_email_format: "Format d'email invalide",
690
716
  invalid_social_url: 'Une ou plusieurs URL de réseaux sociaux sont invalides',
691
717
  invalid_url_format: "Format d'URL invalide",
692
718
  name_required: 'Le nom est requis',
@@ -21,6 +21,14 @@ export default {
21
21
  desktop: 'Desktop',
22
22
  error: 'Errore',
23
23
  errore: 'Errore',
24
+ errors: {
25
+ forbidden: 'Non hai i permessi per eseguire questa azione.',
26
+ generic: 'Si è verificato un errore imprevisto. Riprova.',
27
+ not_found: 'La risorsa richiesta non è stata trovata.',
28
+ server_error: 'Si è verificato un errore del server. Riprova più tardi.',
29
+ service_unavailable: 'Il servizio è temporaneamente non disponibile. Riprova più tardi.',
30
+ session_expired: "La sessione è scaduta. Effettua nuovamente l'accesso.",
31
+ },
24
32
  imageUpload: {
25
33
  imageJpgPngOnly: 'Sono supportati solo file immagine JPG e PNG.',
26
34
  mediaFileTooLarge: 'Il file è troppo grande. Dimensione massima: {maxSize}MB.',
@@ -651,6 +659,16 @@ export default {
651
659
  button_bg_color: 'Sfondo',
652
660
  button_text_color: 'Testo',
653
661
  change_logo: 'Cambia logo',
662
+ contact_address: 'Indirizzo',
663
+ contact_address_placeholder: 'Es. Via Roma 1, 39100 Bolzano',
664
+ contact_email: 'Email',
665
+ contact_email_placeholder: "Es. info{'@'}hotel.com",
666
+ contact_name: 'Nome',
667
+ contact_name_placeholder: 'Es. Grand Hotel',
668
+ contact_telephone: 'Telefono',
669
+ contact_telephone_placeholder: 'Es. +39 0471 123456',
670
+ contact_website: 'Sito web',
671
+ contact_website_placeholder: 'Es. https://www.hotel.com',
654
672
  colors: 'Colori',
655
673
  default_button_colors: 'Colori pulsante predefiniti',
656
674
  default_email_colors: 'Colori email predefiniti',
@@ -663,7 +681,11 @@ export default {
663
681
  name: 'Nome',
664
682
  name_placeholder: 'Inserisci il nome del brand',
665
683
  properties: 'Strutture',
684
+ properties_access_hint:
685
+ 'Puoi assegnare solo le strutture a cui hai accesso. Ogni struttura può appartenere a un solo brand.',
666
686
  properties_placeholder: 'Seleziona le strutture',
687
+ property_no_access_tooltip: 'Non hai accesso a questa struttura',
688
+ property_taken_tooltip: 'Già collegata a "{brand}"',
667
689
  upload_logo: 'Carica logo',
668
690
  },
669
691
  new_brand: 'Nuovo brand',
@@ -675,12 +697,15 @@ export default {
675
697
  save_error: 'Errore durante il salvataggio del brand',
676
698
  search_placeholder: 'Cerca brand...',
677
699
  steps: {
700
+ contact_info: 'Informazioni contatto',
678
701
  general: 'Generale',
679
702
  socials: 'Social',
680
703
  visual_style: 'Stile visivo',
681
704
  },
682
705
  title: 'Brand',
683
706
  validation: {
707
+ invalid_contact_info: 'Le informazioni di contatto contengono email o sito web non validi',
708
+ invalid_email_format: 'Formato email non valido',
684
709
  invalid_social_url: 'Uno o più URL social non sono validi',
685
710
  invalid_url_format: 'Formato URL non valido',
686
711
  name_required: 'Il nome è obbligatorio',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.smartpricing/message-composer-layer",
3
- "version": "4.2.8",
3
+ "version": "4.2.9-toast.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./nuxt.config.ts",
@@ -36,7 +36,7 @@
36
36
  "vue-router": "^5.0.7",
37
37
  "vue3-emoji-picker": "^1.1.8",
38
38
  "zod": "^4.4.3",
39
- "@dev.smartpricing/message-composer-utils": "4.2.8"
39
+ "@dev.smartpricing/message-composer-utils": "4.2.9-toast.1"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@nuxt/eslint": "^1.15.2",