@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 +6 -3
- package/app/api/client.ts +8 -15
- package/app/components/Brand/BrandDrawer.vue +50 -3
- package/app/components/Brand/Steps/BrandStepContactInfo.vue +87 -0
- package/app/components/Brand/Steps/BrandStepGeneral.vue +61 -8
- package/app/composables/apiErrorMessages.ts +26 -0
- package/i18n/locales/de.ts +26 -0
- package/i18n/locales/en.ts +25 -0
- package/i18n/locales/es.ts +26 -0
- package/i18n/locales/fr.ts +26 -0
- package/i18n/locales/it.ts +25 -0
- package/package.json +2 -2
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<
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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)
|
|
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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/i18n/locales/de.ts
CHANGED
|
@@ -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',
|
package/i18n/locales/en.ts
CHANGED
|
@@ -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',
|
package/i18n/locales/es.ts
CHANGED
|
@@ -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',
|
package/i18n/locales/fr.ts
CHANGED
|
@@ -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',
|
package/i18n/locales/it.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
39
|
+
"@dev.smartpricing/message-composer-utils": "4.2.9-toast.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@nuxt/eslint": "^1.15.2",
|