@motor-cms/ui-admin 1.16.2 → 2.0.0
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/client/FooterSlotCard.vue +261 -0
- package/app/components/client/FrontendConfigSection.vue +115 -0
- package/app/components/client/GlobalComponentsSection.vue +50 -0
- package/app/components/dashboard/DashboardOnboarding.vue +6 -8
- package/app/composables/useClientFrontendConfig.ts +145 -0
- package/app/composables/useClientLanguages.ts +81 -0
- package/app/composables/useOnboardingState.ts +20 -0
- package/app/data/footerTemplate.ts +283 -0
- package/app/lang/de/motor-admin/clients.json +34 -1
- package/app/lang/en/motor-admin/clients.json +34 -1
- package/app/pages/index.vue +0 -11
- package/app/pages/login.vue +6 -1
- package/app/pages/motor-admin/clients/[id]/edit.vue +158 -3
- package/app/pages/profile.vue +1 -0
- package/app/types/frontend-config.ts +252 -0
- package/nuxt.config.ts +6 -0
- package/package.json +2 -2
|
@@ -129,3 +129,23 @@ export function useOnboardingDone() {
|
|
|
129
129
|
|
|
130
130
|
return { commitDone, isDone }
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Central guard: should the onboarding tour run for this user?
|
|
135
|
+
*
|
|
136
|
+
* Returns true only when BOTH conditions are met:
|
|
137
|
+
* 1. Backend says show_onboarding=true (fresh user or "restart tour" from profile)
|
|
138
|
+
* 2. User hasn't already completed/skipped the tour in this browser (isDone=false)
|
|
139
|
+
*
|
|
140
|
+
* Use this everywhere instead of hand-rolling the check.
|
|
141
|
+
*/
|
|
142
|
+
export function useOnboardingEnabled() {
|
|
143
|
+
const { user } = useSanctumAuth<User>()
|
|
144
|
+
const { isDone } = useOnboardingDone()
|
|
145
|
+
|
|
146
|
+
const isEnabled = computed(() => {
|
|
147
|
+
return !!user.value?.data?.show_onboarding && !isDone()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
return { isEnabled }
|
|
151
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { generateUuid } from '@motor-cms/ui-core/app/utils/uuid'
|
|
2
|
+
|
|
3
|
+
// Types mirror @zrmdev/ui-builder/app/types/builder/page-definition. Names
|
|
4
|
+
// (HeadlineParagraphComponent, ImageComponent, HeadlineAtom, ParagraphAtom,
|
|
5
|
+
// ButtonAtom, ImageAtom) match the renderer registry in
|
|
6
|
+
// motor-ui-components/app/builder-registry/bootstrap.ts.
|
|
7
|
+
|
|
8
|
+
// ============================================
|
|
9
|
+
// Local type definitions (mirror page-definition)
|
|
10
|
+
// ============================================
|
|
11
|
+
|
|
12
|
+
interface CssProp {
|
|
13
|
+
key: string
|
|
14
|
+
value: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface PageAtom {
|
|
18
|
+
uuid: string
|
|
19
|
+
display_name: string
|
|
20
|
+
component_name: string
|
|
21
|
+
classes: string
|
|
22
|
+
attributes: Record<string, unknown>
|
|
23
|
+
locked_attributes: string[]
|
|
24
|
+
disabled: boolean
|
|
25
|
+
visible: boolean
|
|
26
|
+
cssProps?: CssProp[]
|
|
27
|
+
admin_scss?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ComponentSlot {
|
|
31
|
+
uuid: string
|
|
32
|
+
name: string
|
|
33
|
+
display_name: string
|
|
34
|
+
allowedAtoms: string[]
|
|
35
|
+
atoms: PageAtom[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface PageComponent {
|
|
39
|
+
uuid: string
|
|
40
|
+
name: string
|
|
41
|
+
display_name: string
|
|
42
|
+
icon: string
|
|
43
|
+
classes: string
|
|
44
|
+
cssClassName: string
|
|
45
|
+
visible: boolean
|
|
46
|
+
disabled: boolean
|
|
47
|
+
attributes: Record<string, unknown>
|
|
48
|
+
component_slot_name: string | null
|
|
49
|
+
component_slot_prefix: string | null
|
|
50
|
+
is_removable: number
|
|
51
|
+
is_duplicatable: number
|
|
52
|
+
min_amount_in_another_component: number
|
|
53
|
+
display_viewports: string
|
|
54
|
+
slots: ComponentSlot[]
|
|
55
|
+
components: PageComponent[]
|
|
56
|
+
scorings: unknown[]
|
|
57
|
+
scoring_component_configuration: { score: number; topic_id: number; comparison_operator: string }
|
|
58
|
+
anchors: unknown[]
|
|
59
|
+
admin_scss?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface PageColumn {
|
|
63
|
+
uuid: string
|
|
64
|
+
classes: string
|
|
65
|
+
display_name: string
|
|
66
|
+
value_as_grid_column: number
|
|
67
|
+
rows: PageRow[]
|
|
68
|
+
components: PageComponent[]
|
|
69
|
+
admin_scss?: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface PageRow {
|
|
73
|
+
uuid: string
|
|
74
|
+
display_name: string
|
|
75
|
+
classes: string
|
|
76
|
+
global_css: string
|
|
77
|
+
cols: PageColumn[]
|
|
78
|
+
admin_scss?: string
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
type PageDefinition = PageRow[]
|
|
82
|
+
|
|
83
|
+
// ============================================
|
|
84
|
+
// Builder helpers
|
|
85
|
+
// ============================================
|
|
86
|
+
|
|
87
|
+
function makeAtom(
|
|
88
|
+
display_name: string,
|
|
89
|
+
component_name: string,
|
|
90
|
+
attributes: Record<string, unknown> = {}
|
|
91
|
+
): PageAtom {
|
|
92
|
+
return {
|
|
93
|
+
uuid: generateUuid(),
|
|
94
|
+
display_name,
|
|
95
|
+
component_name,
|
|
96
|
+
classes: '',
|
|
97
|
+
attributes,
|
|
98
|
+
locked_attributes: [],
|
|
99
|
+
disabled: false,
|
|
100
|
+
visible: true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function makeSlot(
|
|
105
|
+
name: string,
|
|
106
|
+
display_name: string,
|
|
107
|
+
allowedAtoms: string[],
|
|
108
|
+
atoms: PageAtom[]
|
|
109
|
+
): ComponentSlot {
|
|
110
|
+
return {
|
|
111
|
+
uuid: generateUuid(),
|
|
112
|
+
name,
|
|
113
|
+
display_name,
|
|
114
|
+
allowedAtoms,
|
|
115
|
+
atoms
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function makeComponent(
|
|
120
|
+
name: string,
|
|
121
|
+
display_name: string,
|
|
122
|
+
cssClassName: string,
|
|
123
|
+
slots: ComponentSlot[],
|
|
124
|
+
attributes: Record<string, unknown> = {},
|
|
125
|
+
components: PageComponent[] = []
|
|
126
|
+
): PageComponent {
|
|
127
|
+
return {
|
|
128
|
+
uuid: generateUuid(),
|
|
129
|
+
name,
|
|
130
|
+
display_name,
|
|
131
|
+
icon: '',
|
|
132
|
+
classes: '',
|
|
133
|
+
cssClassName,
|
|
134
|
+
visible: true,
|
|
135
|
+
disabled: false,
|
|
136
|
+
attributes,
|
|
137
|
+
component_slot_name: null,
|
|
138
|
+
component_slot_prefix: null,
|
|
139
|
+
is_removable: 1,
|
|
140
|
+
is_duplicatable: 1,
|
|
141
|
+
min_amount_in_another_component: 0,
|
|
142
|
+
display_viewports: 's,t,m,l,xl',
|
|
143
|
+
slots,
|
|
144
|
+
components,
|
|
145
|
+
scorings: [],
|
|
146
|
+
scoring_component_configuration: { score: 0, topic_id: 0, comparison_operator: '' },
|
|
147
|
+
anchors: []
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function makeColumn(
|
|
152
|
+
display_name: string,
|
|
153
|
+
value_as_grid_column: number,
|
|
154
|
+
components: PageComponent[]
|
|
155
|
+
): PageColumn {
|
|
156
|
+
return {
|
|
157
|
+
uuid: generateUuid(),
|
|
158
|
+
classes: '',
|
|
159
|
+
display_name,
|
|
160
|
+
value_as_grid_column,
|
|
161
|
+
rows: [],
|
|
162
|
+
components
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function makeRow(display_name: string, cols: PageColumn[], classes = ''): PageRow {
|
|
167
|
+
return {
|
|
168
|
+
uuid: generateUuid(),
|
|
169
|
+
display_name,
|
|
170
|
+
classes,
|
|
171
|
+
global_css: '',
|
|
172
|
+
cols
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ============================================
|
|
177
|
+
// Footer template factory
|
|
178
|
+
// ============================================
|
|
179
|
+
|
|
180
|
+
const PLACEHOLDER_IMAGE = '/images/general/frau_mit_kind.jpeg'
|
|
181
|
+
|
|
182
|
+
const TEXT_SLOT_ALLOWED = ['OverlineAtom', 'HeadlineAtom', 'ParagraphAtom', 'ButtonAtom', 'ImageAtom', 'VideoAtom']
|
|
183
|
+
|
|
184
|
+
function textContentComponent(
|
|
185
|
+
display_name: string,
|
|
186
|
+
atoms: PageAtom[],
|
|
187
|
+
attributes: Record<string, unknown> = { has_background: false }
|
|
188
|
+
): PageComponent {
|
|
189
|
+
return makeComponent(
|
|
190
|
+
'HeadlineParagraphComponent',
|
|
191
|
+
display_name,
|
|
192
|
+
'headline-paragraph',
|
|
193
|
+
[makeSlot('content', 'Content', TEXT_SLOT_ALLOWED, atoms)],
|
|
194
|
+
attributes
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function headlineAtom(text: string, level: 'h2' | 'h3' | 'h4' = 'h3'): PageAtom {
|
|
199
|
+
return makeAtom('Headline', 'HeadlineAtom', {
|
|
200
|
+
text,
|
|
201
|
+
type: level,
|
|
202
|
+
displayedLevel: level,
|
|
203
|
+
weight: 'bold'
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function paragraphAtom(text: string): PageAtom {
|
|
208
|
+
return makeAtom('Paragraph', 'ParagraphAtom', {
|
|
209
|
+
text,
|
|
210
|
+
bullet_type: 'check__default',
|
|
211
|
+
orderedlist_type: 'decimal'
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function buttonAtom(title: string): PageAtom {
|
|
216
|
+
return makeAtom('Button', 'ButtonAtom', {
|
|
217
|
+
title,
|
|
218
|
+
link: '',
|
|
219
|
+
variant: 'dark',
|
|
220
|
+
has_arrow: false
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function imageAtom(display_name: string): PageAtom {
|
|
225
|
+
return makeAtom(display_name, 'ImageAtom', {
|
|
226
|
+
src: PLACEHOLDER_IMAGE,
|
|
227
|
+
alt: display_name
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function imageComponent(display_name: string, extraClasses = ''): PageComponent {
|
|
232
|
+
const c = makeComponent(
|
|
233
|
+
'ImageComponent',
|
|
234
|
+
display_name,
|
|
235
|
+
'image',
|
|
236
|
+
[makeSlot('content', 'Content', ['ImageAtom'], [imageAtom(display_name)])]
|
|
237
|
+
)
|
|
238
|
+
if (extraClasses) c.classes = extraClasses
|
|
239
|
+
return c
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function createFooterTemplate(): PageDefinition {
|
|
243
|
+
// ---- Row 1: 6/6 split ----
|
|
244
|
+
// Col 1: intro text (Headline + Paragraph)
|
|
245
|
+
// Col 2: three award badges, each its own ImageComponent
|
|
246
|
+
const row1 = makeRow('Row 1', [
|
|
247
|
+
makeColumn('Column 1', 6, [
|
|
248
|
+
textContentComponent('Intro', [
|
|
249
|
+
headlineAtom('Headline', 'h2'),
|
|
250
|
+
paragraphAtom('Paragraph')
|
|
251
|
+
])
|
|
252
|
+
]),
|
|
253
|
+
makeColumn('Column 2', 6, [
|
|
254
|
+
imageComponent('Badge 1', 'footer-badge'),
|
|
255
|
+
imageComponent('Badge 2', 'footer-badge'),
|
|
256
|
+
imageComponent('Badge 3', 'footer-badge')
|
|
257
|
+
])
|
|
258
|
+
])
|
|
259
|
+
|
|
260
|
+
// ---- Row 2: four 3/12 columns ----
|
|
261
|
+
// Col 1: Kontakt (pre-filled headline)
|
|
262
|
+
// Col 2-4: nav-style headline + paragraph + CTA button
|
|
263
|
+
// Row gets the white inset card chrome inside the peach FooterFrame.
|
|
264
|
+
const row2 = makeRow('Row 2', [
|
|
265
|
+
makeColumn('Column 1', 3, [
|
|
266
|
+
textContentComponent('Kontakt', [
|
|
267
|
+
headlineAtom('Kontakt', 'h3'),
|
|
268
|
+
paragraphAtom('Paragraph')
|
|
269
|
+
])
|
|
270
|
+
]),
|
|
271
|
+
...['Column 2', 'Column 3', 'Column 4'].map((label) =>
|
|
272
|
+
makeColumn(label, 3, [
|
|
273
|
+
textContentComponent(label, [
|
|
274
|
+
headlineAtom('Headline', 'h3'),
|
|
275
|
+
paragraphAtom('Paragraph'),
|
|
276
|
+
buttonAtom('CTA Button')
|
|
277
|
+
])
|
|
278
|
+
])
|
|
279
|
+
)
|
|
280
|
+
], 'footer-content-card')
|
|
281
|
+
|
|
282
|
+
return [row1, row2]
|
|
283
|
+
}
|
|
@@ -23,5 +23,38 @@
|
|
|
23
23
|
"contact_phone": "Telefon",
|
|
24
24
|
"group_address": "Adresse",
|
|
25
25
|
"group_contact": "Kontakt",
|
|
26
|
-
"group_other": "Sonstiges"
|
|
26
|
+
"group_other": "Sonstiges",
|
|
27
|
+
"frontend_config": {
|
|
28
|
+
"group_brand": "Marke",
|
|
29
|
+
"group_contact": "Kontakt (Frontend)",
|
|
30
|
+
"group_features": "Funktionen",
|
|
31
|
+
"group_social": "Social Media",
|
|
32
|
+
"group_seo": "SEO",
|
|
33
|
+
"brand_name": "Markenname",
|
|
34
|
+
"brand_logo_alt": "Logo Alt-Text",
|
|
35
|
+
"color_scheme": "Farbschema",
|
|
36
|
+
"logo_slug": "Logo",
|
|
37
|
+
"contact_url": "Kontaktseiten-URL",
|
|
38
|
+
"contact_email": "Kontakt-E-Mail",
|
|
39
|
+
"contact_whatsapp_url": "WhatsApp-URL",
|
|
40
|
+
"features_order_line": "Bestellstrecke",
|
|
41
|
+
"features_appointments": "Terminbuchung",
|
|
42
|
+
"features_clickpath": "Clickpath",
|
|
43
|
+
"features_footer_menu": "Footer-Menü",
|
|
44
|
+
"social_instagram": "Instagram",
|
|
45
|
+
"social_facebook": "Facebook",
|
|
46
|
+
"seo_site_name": "Seitenname"
|
|
47
|
+
},
|
|
48
|
+
"global_components": {
|
|
49
|
+
"title": "Globale Komponenten",
|
|
50
|
+
"footer": "Footer",
|
|
51
|
+
"no_footer": "Kein Footer konfiguriert",
|
|
52
|
+
"create_footer": "Footer erstellen",
|
|
53
|
+
"edit_footer": "Footer bearbeiten",
|
|
54
|
+
"unlink_footer": "Verknüpfung lösen",
|
|
55
|
+
"footer_created": "Footer erfolgreich erstellt.",
|
|
56
|
+
"published": "Veröffentlicht",
|
|
57
|
+
"draft": "Entwurf",
|
|
58
|
+
"no_languages": "Keine Sprachen für diesen Mandanten konfiguriert. Erstellen Sie zuerst Navigationsbäume."
|
|
59
|
+
}
|
|
27
60
|
}
|
|
@@ -23,5 +23,38 @@
|
|
|
23
23
|
"contact_phone": "Phone",
|
|
24
24
|
"group_address": "Address",
|
|
25
25
|
"group_contact": "Contact",
|
|
26
|
-
"group_other": "Other"
|
|
26
|
+
"group_other": "Other",
|
|
27
|
+
"frontend_config": {
|
|
28
|
+
"group_brand": "Brand",
|
|
29
|
+
"group_contact": "Contact (Frontend)",
|
|
30
|
+
"group_features": "Features",
|
|
31
|
+
"group_social": "Social Media",
|
|
32
|
+
"group_seo": "SEO",
|
|
33
|
+
"brand_name": "Brand Name",
|
|
34
|
+
"brand_logo_alt": "Logo Alt Text",
|
|
35
|
+
"color_scheme": "Color Scheme",
|
|
36
|
+
"logo_slug": "Logo",
|
|
37
|
+
"contact_url": "Contact Page URL",
|
|
38
|
+
"contact_email": "Contact Email",
|
|
39
|
+
"contact_whatsapp_url": "WhatsApp URL",
|
|
40
|
+
"features_order_line": "Order Line",
|
|
41
|
+
"features_appointments": "Appointments",
|
|
42
|
+
"features_clickpath": "Clickpath",
|
|
43
|
+
"features_footer_menu": "Footer Menu",
|
|
44
|
+
"social_instagram": "Instagram",
|
|
45
|
+
"social_facebook": "Facebook",
|
|
46
|
+
"seo_site_name": "Site Name"
|
|
47
|
+
},
|
|
48
|
+
"global_components": {
|
|
49
|
+
"title": "Global Components",
|
|
50
|
+
"footer": "Footer",
|
|
51
|
+
"no_footer": "No footer configured",
|
|
52
|
+
"create_footer": "Create Footer",
|
|
53
|
+
"edit_footer": "Edit Footer",
|
|
54
|
+
"unlink_footer": "Unlink",
|
|
55
|
+
"footer_created": "Footer created successfully.",
|
|
56
|
+
"published": "Published",
|
|
57
|
+
"draft": "Draft",
|
|
58
|
+
"no_languages": "No languages configured for this client. Add navigation trees first."
|
|
59
|
+
}
|
|
27
60
|
}
|
package/app/pages/index.vue
CHANGED
|
@@ -47,17 +47,6 @@ function onAnnouncementCreated() {
|
|
|
47
47
|
|
|
48
48
|
const toast = useToast()
|
|
49
49
|
|
|
50
|
-
onMounted(() => {
|
|
51
|
-
if (sessionStorage.getItem('motor:login-success')) {
|
|
52
|
-
sessionStorage.removeItem('motor:login-success')
|
|
53
|
-
toast.add({
|
|
54
|
-
title: t('motor-core.login.login_success'),
|
|
55
|
-
color: 'success',
|
|
56
|
-
icon: 'i-lucide-check-circle'
|
|
57
|
-
})
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
|
|
61
50
|
async function onDismiss(id: number) {
|
|
62
51
|
await dismissAnnouncement(id)
|
|
63
52
|
toast.add({ title: t('motor-admin.dashboard.announcement_dismissed'), icon: 'i-lucide-check', color: 'success' })
|
package/app/pages/login.vue
CHANGED
|
@@ -4,6 +4,7 @@ import type { FormSubmitEvent } from '@nuxt/ui'
|
|
|
4
4
|
|
|
5
5
|
const { t } = useI18n()
|
|
6
6
|
const { login } = useSanctumAuth()
|
|
7
|
+
const toast = useToast()
|
|
7
8
|
|
|
8
9
|
definePageMeta({
|
|
9
10
|
layout: 'auth',
|
|
@@ -53,7 +54,11 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|
|
53
54
|
email: event.data.email,
|
|
54
55
|
password: event.data.password
|
|
55
56
|
})
|
|
56
|
-
|
|
57
|
+
toast.add({
|
|
58
|
+
title: t('motor-core.login.login_success'),
|
|
59
|
+
color: 'success',
|
|
60
|
+
icon: 'i-lucide-check-circle'
|
|
61
|
+
})
|
|
57
62
|
} catch {
|
|
58
63
|
error.value = t('motor-core.login.login_failed')
|
|
59
64
|
} finally {
|
|
@@ -2,19 +2,148 @@
|
|
|
2
2
|
<script setup lang="ts">
|
|
3
3
|
import { clientFormMeta } from '../../../../types/generated/form-meta'
|
|
4
4
|
import { clientFormConfig } from '@motor-cms/ui-core/app/types/config/client'
|
|
5
|
+
import { useClientFrontendConfig } from '../../../../composables/useClientFrontendConfig'
|
|
6
|
+
import { useClientLanguages } from '../../../../composables/useClientLanguages'
|
|
5
7
|
|
|
6
8
|
definePageMeta({ layout: 'default', permission: 'clients.read' })
|
|
7
9
|
|
|
10
|
+
const { t } = useI18n()
|
|
11
|
+
const { error: notifyError } = useNotify()
|
|
8
12
|
const route = useRoute()
|
|
9
|
-
const
|
|
13
|
+
const clientId = route.params.id as string
|
|
14
|
+
|
|
15
|
+
const isFrontendConfigEnabled
|
|
16
|
+
= useRuntimeConfig().public.featureClientFrontendConfig === true
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
fields,
|
|
20
|
+
schema,
|
|
21
|
+
groups,
|
|
22
|
+
state,
|
|
23
|
+
loading,
|
|
24
|
+
fetching,
|
|
25
|
+
fetchError,
|
|
26
|
+
canWrite,
|
|
27
|
+
pageTitle,
|
|
28
|
+
formRef,
|
|
29
|
+
onSubmit,
|
|
30
|
+
onSaveAndContinue,
|
|
31
|
+
onSaveAndNew,
|
|
32
|
+
deleteRecord,
|
|
33
|
+
deleting
|
|
34
|
+
} = await useEntityForm({
|
|
10
35
|
apiEndpoint: '/api/v2/clients',
|
|
11
36
|
routePrefix: '/motor-admin/clients',
|
|
12
37
|
translationPrefix: 'motor-admin.clients',
|
|
13
38
|
formMeta: clientFormMeta,
|
|
14
39
|
formConfig: clientFormConfig,
|
|
15
40
|
mode: 'edit',
|
|
16
|
-
id:
|
|
41
|
+
id: clientId,
|
|
42
|
+
beforeSubmit: (data) => {
|
|
43
|
+
if (!isFrontendConfigEnabled) return
|
|
44
|
+
if (!validateFrontendConfig()) {
|
|
45
|
+
throw new Error(t('motor-core.global.validation_failed'))
|
|
46
|
+
}
|
|
47
|
+
data.frontend_config = getFrontendConfigSubmitData()
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const { data: clientRecord } = useNuxtData<{ data: Record<string, unknown> }>(
|
|
52
|
+
`entity-form-/api/v2/clients-${clientId}`
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const {
|
|
56
|
+
state: frontendConfigState,
|
|
57
|
+
errors: frontendConfigErrors,
|
|
58
|
+
fields: frontendConfigFields,
|
|
59
|
+
groups: frontendConfigGroups,
|
|
60
|
+
validate: validateFrontendConfig,
|
|
61
|
+
getSubmitData: getFrontendConfigSubmitData
|
|
62
|
+
} = useClientFrontendConfig({ clientRecord, fetching })
|
|
63
|
+
|
|
64
|
+
const colorSchemeOptions = [
|
|
65
|
+
{ label: 'energis', value: 'energis' },
|
|
66
|
+
{ label: 'highspeed', value: 'highspeed' },
|
|
67
|
+
{ label: 'jaeckel', value: 'jaeckel' }
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
const logoSlugOptions = [
|
|
71
|
+
{ label: 'energis', value: 'energis' },
|
|
72
|
+
{ label: 'highspeed', value: 'highspeed' },
|
|
73
|
+
{ label: 'jaeckel', value: 'jaeckel' }
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
const clientIdRef = computed(() =>
|
|
77
|
+
isFrontendConfigEnabled ? (route.params.id as string) : ''
|
|
78
|
+
)
|
|
79
|
+
const { languages, isMultiLanguage, loading: languagesLoading } = useClientLanguages(clientIdRef)
|
|
80
|
+
|
|
81
|
+
const footerMap = computed(() => {
|
|
82
|
+
const fc = clientRecord.value?.data?.frontend_config as Record<string, unknown> | undefined
|
|
83
|
+
const gc = fc?.globalComponents as Record<string, unknown> | undefined
|
|
84
|
+
return gc?.footer as Record<string, string> | undefined
|
|
17
85
|
})
|
|
86
|
+
|
|
87
|
+
const sanctumClient = useSanctumClient()
|
|
88
|
+
|
|
89
|
+
async function onFooterLinked(languageId: number, uuid: string, _pageId: number) {
|
|
90
|
+
try {
|
|
91
|
+
const freshClient = await sanctumClient<{ data: Record<string, unknown> }>(
|
|
92
|
+
`/api/v2/clients/${clientId}`
|
|
93
|
+
)
|
|
94
|
+
const freshConfig = (freshClient.data.frontend_config as Record<string, unknown>) ?? {}
|
|
95
|
+
const freshGc = (freshConfig.globalComponents as Record<string, unknown>) ?? {}
|
|
96
|
+
const freshFooter = { ...(freshGc.footer as Record<string, string>) ?? {} }
|
|
97
|
+
freshFooter[String(languageId)] = uuid
|
|
98
|
+
|
|
99
|
+
await sanctumClient(`/api/v2/clients/${clientId}`, {
|
|
100
|
+
method: 'PATCH',
|
|
101
|
+
body: {
|
|
102
|
+
name: freshClient.data.name,
|
|
103
|
+
slug: freshClient.data.slug,
|
|
104
|
+
frontend_config: {
|
|
105
|
+
...freshConfig,
|
|
106
|
+
globalComponents: { ...freshGc, footer: freshFooter }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
await refreshNuxtData(`entity-form-/api/v2/clients-${clientId}`)
|
|
112
|
+
} catch (err: unknown) {
|
|
113
|
+
const message = err instanceof Error ? err.message : t('motor-core.errors.update_failed')
|
|
114
|
+
notifyError(t('motor-admin.clients.edit_title'), message)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function onFooterUnlinked(languageId: number) {
|
|
119
|
+
try {
|
|
120
|
+
const freshClient = await sanctumClient<{ data: Record<string, unknown> }>(
|
|
121
|
+
`/api/v2/clients/${clientId}`
|
|
122
|
+
)
|
|
123
|
+
const freshConfig = (freshClient.data.frontend_config as Record<string, unknown>) ?? {}
|
|
124
|
+
const freshGc = (freshConfig.globalComponents as Record<string, unknown>) ?? {}
|
|
125
|
+
const freshFooter = { ...(freshGc.footer as Record<string, string>) ?? {} }
|
|
126
|
+
delete freshFooter[String(languageId)]
|
|
127
|
+
|
|
128
|
+
await sanctumClient(`/api/v2/clients/${clientId}`, {
|
|
129
|
+
method: 'PATCH',
|
|
130
|
+
body: {
|
|
131
|
+
name: freshClient.data.name,
|
|
132
|
+
slug: freshClient.data.slug,
|
|
133
|
+
frontend_config: {
|
|
134
|
+
...freshConfig,
|
|
135
|
+
globalComponents: { ...freshGc, footer: freshFooter }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
await refreshNuxtData(`entity-form-/api/v2/clients-${clientId}`)
|
|
141
|
+
} catch (err: unknown) {
|
|
142
|
+
const message = err instanceof Error ? err.message : t('motor-core.errors.update_failed')
|
|
143
|
+
notifyError(t('motor-admin.clients.edit_title'), message)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
18
147
|
</script>
|
|
19
148
|
|
|
20
149
|
<template>
|
|
@@ -40,6 +169,32 @@ const { fields, schema, groups, state, loading, fetching, fetchError, canWrite,
|
|
|
40
169
|
@submit="onSubmit"
|
|
41
170
|
@save-and-continue="onSaveAndContinue"
|
|
42
171
|
@save-and-new="onSaveAndNew"
|
|
43
|
-
|
|
172
|
+
>
|
|
173
|
+
<template
|
|
174
|
+
v-if="isFrontendConfigEnabled"
|
|
175
|
+
#after-fields
|
|
176
|
+
>
|
|
177
|
+
<ClientFrontendConfigSection
|
|
178
|
+
:state="frontendConfigState"
|
|
179
|
+
:fields="frontendConfigFields"
|
|
180
|
+
:groups="frontendConfigGroups"
|
|
181
|
+
:errors="frontendConfigErrors"
|
|
182
|
+
:disabled="!canWrite"
|
|
183
|
+
:color-scheme-options="colorSchemeOptions"
|
|
184
|
+
:logo-slug-options="logoSlugOptions"
|
|
185
|
+
/>
|
|
186
|
+
<ClientGlobalComponentsSection
|
|
187
|
+
:client-id="route.params.id"
|
|
188
|
+
:client-name="(state.name as string) ?? ''"
|
|
189
|
+
:footer-map="footerMap"
|
|
190
|
+
:languages="languages"
|
|
191
|
+
:is-multi-language="isMultiLanguage"
|
|
192
|
+
:languages-loading="languagesLoading"
|
|
193
|
+
:disabled="!canWrite"
|
|
194
|
+
@footer-linked="onFooterLinked"
|
|
195
|
+
@footer-unlinked="onFooterUnlinked"
|
|
196
|
+
/>
|
|
197
|
+
</template>
|
|
198
|
+
</FormBase>
|
|
44
199
|
</FormPage>
|
|
45
200
|
</template>
|
package/app/pages/profile.vue
CHANGED
|
@@ -185,6 +185,7 @@ async function onRestartTour() {
|
|
|
185
185
|
try {
|
|
186
186
|
await resetOnboarding()
|
|
187
187
|
resetOnboardingState()
|
|
188
|
+
await refreshIdentity()
|
|
188
189
|
success(t('motor-core.profile.toast_tour_reset_title'), t('motor-core.profile.toast_tour_reset_message'))
|
|
189
190
|
await router.push('/')
|
|
190
191
|
}
|