@shopware/cms-base-layer 1.4.0 → 1.5.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/README.md +16 -8
- package/components/SwContactForm.vue +8 -23
- package/components/SwListingProductPrice.vue +2 -1
- package/components/SwNewsletterForm.vue +5 -23
- package/components/SwProductAddToCart.vue +2 -1
- package/components/SwProductCard.vue +4 -3
- package/components/SwProductReviews.vue +20 -5
- package/components/SwSlider.vue +5 -3
- package/components/SwVariantConfigurator.vue +12 -8
- package/components/public/cms/element/CmsBlockHtml.md +1 -0
- package/components/public/cms/element/CmsBlockHtml.vue +17 -0
- package/components/public/cms/element/CmsElementCrossSelling.vue +1 -1
- package/components/public/cms/element/CmsElementHtml.vue +24 -0
- package/components/public/cms/element/CmsElementProductDescriptionReviews.vue +20 -6
- package/nuxt.config.ts +3 -1
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# shopware/frontends - cms-base
|
|
2
2
|
|
|
3
3
|
[](https://npmjs.com/package/@shopware/cms-base-layer)
|
|
4
|
-
[](https://github.com/shopware/frontends/tree/main/packages/cms-base)
|
|
4
|
+
[](https://github.com/shopware/frontends/tree/main/packages/cms-base-layer)
|
|
5
5
|
[](https://github.com/shopware/frontends/issues?q=is%3Aopen+is%3Aissue+label%3Acms-base)
|
|
6
6
|
[](#)
|
|
7
7
|
|
|
@@ -36,6 +36,9 @@ pnpm install -D @shopware/cms-base-layer
|
|
|
36
36
|
|
|
37
37
|
# bun
|
|
38
38
|
bun install -D @shopware/cms-base-layer
|
|
39
|
+
|
|
40
|
+
# deno
|
|
41
|
+
deno install --dev @shopware/cms-base-layer
|
|
39
42
|
```
|
|
40
43
|
|
|
41
44
|
<!-- /automd -->
|
|
@@ -129,16 +132,21 @@ No additional packages needed to be installed.
|
|
|
129
132
|
|
|
130
133
|
Full changelog for stable version is available [here](https://github.com/shopware/frontends/blob/main/packages/cms-base-layer/CHANGELOG.md)
|
|
131
134
|
|
|
132
|
-
### Latest changes: 1.
|
|
135
|
+
### Latest changes: 1.5.0
|
|
133
136
|
|
|
134
137
|
### Minor Changes
|
|
135
138
|
|
|
136
|
-
- [#
|
|
137
|
-
|
|
138
|
-
- [#1602](https://github.com/shopware/frontends/pull/1602) [`bb7d1cb`](https://github.com/shopware/frontends/commit/bb7d1cbc4204ff1d48f77416f94f550bc235e5ed) Thanks [@patzick](https://github.com/patzick)! - Switch from `@shopware-pwa/cms-base` to `@shopware/cms-base-layer` package.
|
|
139
|
+
- [#1727](https://github.com/shopware/frontends/pull/1727) [`94872b7`](https://github.com/shopware/frontends/commit/94872b7c6f6337ff8ce0bcfd9320e4178a4927f0) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Added CmsBlockHtml to render html blocks.
|
|
140
|
+
Added CmsElementHtml to render html element.
|
|
139
141
|
|
|
140
142
|
### Patch Changes
|
|
141
143
|
|
|
142
|
-
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
- [#1843](https://github.com/shopware/frontends/pull/1843) [`1859893`](https://github.com/shopware/frontends/commit/1859893ec3fe1cdead4b1c39e34943d9a70deaa4) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Enhance form error handling and fix TS errors
|
|
145
|
+
|
|
146
|
+
- [#1812](https://github.com/shopware/frontends/pull/1812) [`c28810d`](https://github.com/shopware/frontends/commit/c28810d0ca503b97c232438e200bbf5ba5dab403) Thanks [@patzick](https://github.com/patzick)! - Fix date formatting based on locale, previously always returned en-US format
|
|
147
|
+
|
|
148
|
+
- [#1857](https://github.com/shopware/frontends/pull/1857) [`e78d51e`](https://github.com/shopware/frontends/commit/e78d51e6af0726b12620d9f7e8f2c3150aa80cfa) Thanks [@mdanilowicz](https://github.com/mdanilowicz)! - Fix wrong `SharedPrice` component in `SwListingProductPrice.vue` file
|
|
149
|
+
|
|
150
|
+
- Updated dependencies [[`d016d6b`](https://github.com/shopware/frontends/commit/d016d6b845bff9a148405a74dae88d7fc81ec99c), [`c28810d`](https://github.com/shopware/frontends/commit/c28810d0ca503b97c232438e200bbf5ba5dab403), [`a7ff606`](https://github.com/shopware/frontends/commit/a7ff60681d1a164d5c9f2020c506262e96fad5dc), [`d016d6b`](https://github.com/shopware/frontends/commit/d016d6b845bff9a148405a74dae88d7fc81ec99c), [`bd70905`](https://github.com/shopware/frontends/commit/bd70905b8443fd57d8d8cb3cfc6501a9117dea49)]:
|
|
151
|
+
- @shopware/api-client@1.3.0
|
|
152
|
+
- @shopware/composables@1.9.0
|
|
@@ -115,9 +115,6 @@ const rules = computed(() => ({
|
|
|
115
115
|
required,
|
|
116
116
|
minLength: minLength(3),
|
|
117
117
|
},
|
|
118
|
-
salutationId: {
|
|
119
|
-
required,
|
|
120
|
-
},
|
|
121
118
|
phone: {
|
|
122
119
|
required,
|
|
123
120
|
minLength: minLength(3),
|
|
@@ -176,18 +173,12 @@ const invokeSubmit = async () => {
|
|
|
176
173
|
<template v-if="!formSent">
|
|
177
174
|
<div class="grid grid-cols-12 gap-5">
|
|
178
175
|
<div class="col-span-4">
|
|
179
|
-
<label for="salutation">{{ translations.form.salutation }}
|
|
176
|
+
<label for="salutation">{{ translations.form.salutation }}</label>
|
|
180
177
|
<select
|
|
181
178
|
id="salutation"
|
|
182
179
|
v-model="state.salutationId"
|
|
183
180
|
name="salutation"
|
|
184
|
-
class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
|
|
185
|
-
:class="[
|
|
186
|
-
$v.salutationId.$error
|
|
187
|
-
? 'border-red-600 focus:border-red-600'
|
|
188
|
-
: 'border-gray-300 focus:border-indigo-500',
|
|
189
|
-
]"
|
|
190
|
-
@blur="$v.salutationId.$touch()"
|
|
181
|
+
class="border-gray-300 focus:border-indigo-500 appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
|
|
191
182
|
>
|
|
192
183
|
<option disabled selected value="">
|
|
193
184
|
{{ translations.form.salutationPlaceholder }}
|
|
@@ -200,12 +191,6 @@ const invokeSubmit = async () => {
|
|
|
200
191
|
{{ salutation.displayName }}
|
|
201
192
|
</option>
|
|
202
193
|
</select>
|
|
203
|
-
<span
|
|
204
|
-
v-if="$v.salutationId.$error"
|
|
205
|
-
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
206
|
-
>
|
|
207
|
-
{{ $v.salutationId.$errors[0].$message }}
|
|
208
|
-
</span>
|
|
209
194
|
</div>
|
|
210
195
|
<div class="col-span-4">
|
|
211
196
|
<label for="first-name">{{ translations.form.firstName }} *</label>
|
|
@@ -228,7 +213,7 @@ const invokeSubmit = async () => {
|
|
|
228
213
|
v-if="$v.firstName.$error"
|
|
229
214
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
230
215
|
>
|
|
231
|
-
{{ $v.firstName.$errors[0]
|
|
216
|
+
{{ $v.firstName.$errors[0]?.$message ?? '' }}
|
|
232
217
|
</span>
|
|
233
218
|
</div>
|
|
234
219
|
<div class="col-span-4">
|
|
@@ -252,7 +237,7 @@ const invokeSubmit = async () => {
|
|
|
252
237
|
v-if="$v.lastName.$error"
|
|
253
238
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
254
239
|
>
|
|
255
|
-
{{ $v.lastName.$errors[0]
|
|
240
|
+
{{ $v.lastName.$errors[0]?.$message ?? '' }}
|
|
256
241
|
</span>
|
|
257
242
|
</div>
|
|
258
243
|
<div class="col-span-6">
|
|
@@ -276,7 +261,7 @@ const invokeSubmit = async () => {
|
|
|
276
261
|
v-if="$v.email.$error"
|
|
277
262
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
278
263
|
>
|
|
279
|
-
{{ $v.email.$errors[0]
|
|
264
|
+
{{ $v.email.$errors[0]?.$message ?? '' }}
|
|
280
265
|
</span>
|
|
281
266
|
</div>
|
|
282
267
|
<div class="col-span-6">
|
|
@@ -300,7 +285,7 @@ const invokeSubmit = async () => {
|
|
|
300
285
|
v-if="$v.phone.$error"
|
|
301
286
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
302
287
|
>
|
|
303
|
-
{{ $v.phone.$errors[0]
|
|
288
|
+
{{ $v.phone.$errors[0]?.$message ?? '' }}
|
|
304
289
|
</span>
|
|
305
290
|
</div>
|
|
306
291
|
<div class="col-span-12">
|
|
@@ -324,7 +309,7 @@ const invokeSubmit = async () => {
|
|
|
324
309
|
v-if="$v.subject.$error"
|
|
325
310
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
326
311
|
>
|
|
327
|
-
{{ $v.subject.$errors[0]
|
|
312
|
+
{{ $v.subject.$errors[0]?.$message ?? '' }}
|
|
328
313
|
</span>
|
|
329
314
|
</div>
|
|
330
315
|
<div class="col-span-12">
|
|
@@ -349,7 +334,7 @@ const invokeSubmit = async () => {
|
|
|
349
334
|
v-if="$v.comment.$error"
|
|
350
335
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
351
336
|
>
|
|
352
|
-
{{ $v.comment.$errors[0]
|
|
337
|
+
{{ $v.comment.$errors[0]?.$message || '' }}
|
|
353
338
|
</span>
|
|
354
339
|
</div>
|
|
355
340
|
<div class="col-span-12">
|
|
@@ -12,6 +12,7 @@ type Translations = {
|
|
|
12
12
|
listing: {
|
|
13
13
|
variantsFrom: string;
|
|
14
14
|
previously: string;
|
|
15
|
+
from: string;
|
|
15
16
|
to: string;
|
|
16
17
|
};
|
|
17
18
|
};
|
|
@@ -78,7 +79,7 @@ const {
|
|
|
78
79
|
<template v-if="regulationPrice">
|
|
79
80
|
<div class="flex gap-2 justify-end text-gray-500 text-3.5 mb-2">
|
|
80
81
|
{{ translations.listing.previously }}
|
|
81
|
-
<
|
|
82
|
+
<SwSharedPrice :value="regulationPrice" />
|
|
82
83
|
</div>
|
|
83
84
|
</template>
|
|
84
85
|
<template v-if="!regulationPrice">
|
|
@@ -79,7 +79,7 @@ const { newsletterSubscribe, newsletterUnsubscribe } = useNewsletter();
|
|
|
79
79
|
|
|
80
80
|
const getFormTitle = computed(() => getConfigValue("title"));
|
|
81
81
|
const state = reactive({
|
|
82
|
-
option: subscriptionOptions[0]
|
|
82
|
+
option: subscriptionOptions[0]?.value ?? "",
|
|
83
83
|
salutationId: "",
|
|
84
84
|
firstName: "",
|
|
85
85
|
lastName: "",
|
|
@@ -104,9 +104,6 @@ type Rules = {
|
|
|
104
104
|
required: ValidationRuleWithoutParams;
|
|
105
105
|
minLength: number;
|
|
106
106
|
};
|
|
107
|
-
salutationId: {
|
|
108
|
-
required: ValidationRuleWithoutParams;
|
|
109
|
-
};
|
|
110
107
|
};
|
|
111
108
|
const rules = computed(() => {
|
|
112
109
|
let temp: Partial<Rules> = {
|
|
@@ -130,9 +127,6 @@ const rules = computed(() => {
|
|
|
130
127
|
required,
|
|
131
128
|
minLength: 3,
|
|
132
129
|
},
|
|
133
|
-
salutationId: {
|
|
134
|
-
required,
|
|
135
|
-
},
|
|
136
130
|
};
|
|
137
131
|
}
|
|
138
132
|
return temp;
|
|
@@ -222,7 +216,7 @@ const invokeSubmit = async () => {
|
|
|
222
216
|
v-if="$v.email?.$error"
|
|
223
217
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
224
218
|
>
|
|
225
|
-
{{ $v.email?.$errors[0]
|
|
219
|
+
{{ $v.email?.$errors[0]?.$message || '' }}
|
|
226
220
|
</span>
|
|
227
221
|
</div>
|
|
228
222
|
<div v-if="state.option === 'subscribe'" class="col-span-4">
|
|
@@ -231,13 +225,7 @@ const invokeSubmit = async () => {
|
|
|
231
225
|
id="salutation"
|
|
232
226
|
v-model="state.salutationId"
|
|
233
227
|
name="salutation"
|
|
234
|
-
class="
|
|
235
|
-
:class="[
|
|
236
|
-
$v.salutationId?.$error
|
|
237
|
-
? 'border-red-600 focus:border-red-600'
|
|
238
|
-
: 'border-gray-300 focus:border-indigo-500',
|
|
239
|
-
]"
|
|
240
|
-
@blur="$v.salutationId?.$touch()"
|
|
228
|
+
class=" border-gray-300 focus:border-indigo-500appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
|
|
241
229
|
>
|
|
242
230
|
<option disabled selected value="">
|
|
243
231
|
{{ translations.form.salutationPlaceholder }}
|
|
@@ -250,12 +238,6 @@ const invokeSubmit = async () => {
|
|
|
250
238
|
{{ salutation.displayName }}
|
|
251
239
|
</option>
|
|
252
240
|
</select>
|
|
253
|
-
<span
|
|
254
|
-
v-if="$v.salutationId?.$error"
|
|
255
|
-
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
256
|
-
>
|
|
257
|
-
{{ $v.salutationId?.$errors[0].$message }}
|
|
258
|
-
</span>
|
|
259
241
|
</div>
|
|
260
242
|
<div v-if="state.option === 'subscribe'" class="col-span-4">
|
|
261
243
|
<label for="first-name">{{ translations.form.firstName }} *</label>
|
|
@@ -278,7 +260,7 @@ const invokeSubmit = async () => {
|
|
|
278
260
|
v-if="$v.firstName?.$error"
|
|
279
261
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
280
262
|
>
|
|
281
|
-
{{ $v.firstName?.$errors[0]
|
|
263
|
+
{{ $v.firstName?.$errors[0]?.$message || '' }}
|
|
282
264
|
</span>
|
|
283
265
|
</div>
|
|
284
266
|
<div v-if="state.option === 'subscribe'" class="col-span-4">
|
|
@@ -302,7 +284,7 @@ const invokeSubmit = async () => {
|
|
|
302
284
|
v-if="$v.lastName?.$error"
|
|
303
285
|
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
|
|
304
286
|
>
|
|
305
|
-
{{ $v.lastName?.$errors[0]
|
|
287
|
+
{{ $v.lastName?.$errors[0]?.$message || '' }}
|
|
306
288
|
</span>
|
|
307
289
|
</div>
|
|
308
290
|
<div class="col-span-12">
|
|
@@ -51,7 +51,8 @@ const addToCartProxy = async () => {
|
|
|
51
51
|
const errors = getErrorsCodes();
|
|
52
52
|
for (const element of errors) {
|
|
53
53
|
const { messageKey, params } = resolveCartError(element);
|
|
54
|
-
|
|
54
|
+
if (translations.errors[messageKey])
|
|
55
|
+
pushError(getCmsTranslate(translations.errors[messageKey], params));
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
if (!errors.length)
|
|
@@ -127,7 +127,8 @@ const addToCartProxy = async () => {
|
|
|
127
127
|
const errors = getErrorsCodes();
|
|
128
128
|
for (const element of errors) {
|
|
129
129
|
const { messageKey, params } = resolveCartError(element);
|
|
130
|
-
|
|
130
|
+
if (translations.errors[messageKey])
|
|
131
|
+
pushError(getCmsTranslate(translations.errors[messageKey], params));
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
if (!errors.length)
|
|
@@ -235,11 +236,11 @@ const srcPath = computed(() => {
|
|
|
235
236
|
:to="buildUrlPrefix(getProductRoute(product), getUrlPrefix())"
|
|
236
237
|
data-testid="product-box-product-name-link"
|
|
237
238
|
>
|
|
238
|
-
<
|
|
239
|
+
<div
|
|
239
240
|
class="text-xl font-semibold tracking-tight text-gray-900 dark:text-white min-h-60px"
|
|
240
241
|
>
|
|
241
242
|
{{ getProductName({ product }) }}
|
|
242
|
-
</
|
|
243
|
+
</div>
|
|
243
244
|
</RouterLink>
|
|
244
245
|
|
|
245
246
|
<SwListingProductPrice
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { defu } from "defu";
|
|
3
3
|
import { computed, onMounted, ref, toRefs } from "vue";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
useCmsTranslations,
|
|
6
|
+
useProductReviews,
|
|
7
|
+
useShopwareContext,
|
|
8
|
+
} from "#imports";
|
|
5
9
|
import type { Schemas } from "#shopware";
|
|
6
10
|
|
|
7
11
|
const props = defineProps<{
|
|
@@ -12,12 +16,14 @@ const props = defineProps<{
|
|
|
12
16
|
type Translations = {
|
|
13
17
|
product: {
|
|
14
18
|
noReviews: string;
|
|
19
|
+
reviewNotAccepted: string;
|
|
15
20
|
};
|
|
16
21
|
};
|
|
17
22
|
|
|
18
23
|
let translations: Translations = {
|
|
19
24
|
product: {
|
|
20
25
|
noReviews: "No reviews yet.",
|
|
26
|
+
reviewNotAccepted: "Your review has not been approved yet",
|
|
21
27
|
},
|
|
22
28
|
};
|
|
23
29
|
|
|
@@ -42,15 +48,17 @@ const reviewsList = computed<Schemas["ProductReview"][]>(
|
|
|
42
48
|
|
|
43
49
|
const format: Intl.DateTimeFormatOptions = {
|
|
44
50
|
year: "numeric",
|
|
45
|
-
month: "
|
|
51
|
+
month: "numeric",
|
|
46
52
|
day: "numeric",
|
|
47
53
|
hour: "numeric",
|
|
48
54
|
minute: "numeric",
|
|
49
|
-
hour12: true,
|
|
50
55
|
};
|
|
51
56
|
|
|
52
|
-
const
|
|
53
|
-
|
|
57
|
+
const { browserLocale } = useShopwareContext();
|
|
58
|
+
|
|
59
|
+
const formatDate = (date: string) => {
|
|
60
|
+
return new Intl.DateTimeFormat(browserLocale, format).format(new Date(date));
|
|
61
|
+
};
|
|
54
62
|
</script>
|
|
55
63
|
|
|
56
64
|
<template>
|
|
@@ -70,6 +78,13 @@ const formatDate = (date: string) =>
|
|
|
70
78
|
>
|
|
71
79
|
<span>{{ formatDate(review.createdAt) }}</span>
|
|
72
80
|
</div>
|
|
81
|
+
<div
|
|
82
|
+
v-if="!review.status"
|
|
83
|
+
class="mt-2 text-3 p-2 bg-[#d4f0f5] flex gap-2 items-center"
|
|
84
|
+
>
|
|
85
|
+
<div class="w-6 h-6 i-carbon-warning" />
|
|
86
|
+
{{ translations.product.reviewNotAccepted }}
|
|
87
|
+
</div>
|
|
73
88
|
<div
|
|
74
89
|
class="cms-block-product-description-reviews__reviews-rating inline-flex items-center mt-2"
|
|
75
90
|
>
|
package/components/SwSlider.vue
CHANGED
|
@@ -38,9 +38,11 @@ const { getConfigValue } = useCmsElementConfig({
|
|
|
38
38
|
config: SliderElementConfig;
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
const slots = useSlots()
|
|
41
|
+
const slots = useSlots() as {
|
|
42
|
+
default?: () => { children: VNodeArrayChildren }[];
|
|
43
|
+
};
|
|
42
44
|
const childrenRaw = computed(
|
|
43
|
-
() => (slots?.default?.()[0]
|
|
45
|
+
() => (slots?.default?.()[0]?.children as VNodeArrayChildren) ?? [],
|
|
44
46
|
);
|
|
45
47
|
const slidesToScroll = computed(() =>
|
|
46
48
|
props.slidesToScroll >= props.slidesToShow
|
|
@@ -184,7 +186,7 @@ function buildImageSliderTrackStyle(
|
|
|
184
186
|
const childComponent =
|
|
185
187
|
imageSliderTrack.value?.children[transformIndex + 1];
|
|
186
188
|
// If image exist
|
|
187
|
-
height = childComponent?.children[0]
|
|
189
|
+
height = childComponent?.children[0]?.children[0]?.clientHeight
|
|
188
190
|
? `${childComponent.clientHeight}px`
|
|
189
191
|
: "auto";
|
|
190
192
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useCmsTranslations } from "@shopware/composables";
|
|
3
|
-
import { getProductRoute } from "@shopware/helpers";
|
|
3
|
+
import { buildUrlPrefix, getProductRoute } from "@shopware/helpers";
|
|
4
4
|
import { defu } from "defu";
|
|
5
5
|
import { computed, ref, unref } from "vue";
|
|
6
6
|
import type { ComputedRef } from "vue";
|
|
7
7
|
import { useRouter } from "vue-router";
|
|
8
|
-
import { useProductConfigurator } from "#imports";
|
|
8
|
+
import { useProductConfigurator, useUrlResolver } from "#imports";
|
|
9
9
|
import type { Schemas } from "#shopware";
|
|
10
10
|
|
|
11
|
+
const { getUrlPrefix } = useUrlResolver();
|
|
12
|
+
|
|
13
|
+
const prefix = getUrlPrefix();
|
|
14
|
+
|
|
11
15
|
const props = withDefaults(
|
|
12
16
|
defineProps<{
|
|
13
17
|
allowRedirect?: boolean;
|
|
@@ -52,11 +56,11 @@ const isOptionSelected = (optionId: string) =>
|
|
|
52
56
|
|
|
53
57
|
const onHandleChange = async () => {
|
|
54
58
|
isLoading.value = true;
|
|
55
|
-
const variantFound = await findVariantForSelectedOptions(
|
|
56
|
-
|
|
59
|
+
const variantFound = await findVariantForSelectedOptions();
|
|
60
|
+
const selectedOptionsVariantPath = buildUrlPrefix(
|
|
61
|
+
getProductRoute(variantFound),
|
|
62
|
+
prefix,
|
|
57
63
|
);
|
|
58
|
-
|
|
59
|
-
const selectedOptionsVariantPath = getProductRoute(variantFound);
|
|
60
64
|
if (props.allowRedirect && selectedOptionsVariantPath) {
|
|
61
65
|
try {
|
|
62
66
|
router.push(selectedOptionsVariantPath);
|
|
@@ -100,13 +104,13 @@ const onHandleChange = async () => {
|
|
|
100
104
|
:class="{
|
|
101
105
|
'border-3 border-indigo-600': isOptionSelected(option.id),
|
|
102
106
|
}"
|
|
103
|
-
@click="handleChange(optionGroup.name, option.id, onHandleChange)"
|
|
107
|
+
@click="handleChange(optionGroup.translated.name, option.id, onHandleChange)"
|
|
104
108
|
>
|
|
105
109
|
<p
|
|
106
110
|
:id="`${option.id}-choice-label`"
|
|
107
111
|
data-testid="product-variant-text"
|
|
108
112
|
>
|
|
109
|
-
{{ option.name }}
|
|
113
|
+
{{ option.translated.name }}
|
|
110
114
|
</p>
|
|
111
115
|
</label>
|
|
112
116
|
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Render a HTML section
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CmsBlockHtml } from "@shopware/composables";
|
|
3
|
+
|
|
4
|
+
defineProps<{
|
|
5
|
+
content: CmsBlockHtml;
|
|
6
|
+
}>();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<div>
|
|
11
|
+
<CmsGenericElement
|
|
12
|
+
v-for="slot in content.slots"
|
|
13
|
+
:key="slot.versionId"
|
|
14
|
+
:content="slot"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
@@ -82,7 +82,7 @@ const toggleTab = (index: number) => {
|
|
|
82
82
|
:autoplay="false"
|
|
83
83
|
>
|
|
84
84
|
<SwProductCard
|
|
85
|
-
v-for="product of crossSellCollections[currentTabIndex]
|
|
85
|
+
v-for="product of crossSellCollections[currentTabIndex]?.products"
|
|
86
86
|
:key="product.id"
|
|
87
87
|
class="w-[300px]"
|
|
88
88
|
:product="product"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CmsElementHtml } from "@shopware/composables";
|
|
3
|
+
|
|
4
|
+
import { getCmsLayoutConfiguration } from "@shopware/helpers";
|
|
5
|
+
import { h } from "vue";
|
|
6
|
+
|
|
7
|
+
const props = defineProps<{
|
|
8
|
+
content: CmsElementHtml;
|
|
9
|
+
}>();
|
|
10
|
+
|
|
11
|
+
const { cssClasses, layoutStyles } = getCmsLayoutConfiguration(props.content);
|
|
12
|
+
|
|
13
|
+
const HtmlComponent = () => {
|
|
14
|
+
return h("div", {
|
|
15
|
+
class: cssClasses,
|
|
16
|
+
style: layoutStyles,
|
|
17
|
+
innerHTML: props.content.data.content || "",
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<HtmlComponent />
|
|
24
|
+
</template>
|
|
@@ -3,25 +3,32 @@ import type { CmsElementProductDescriptionReviews } from "@shopware/composables"
|
|
|
3
3
|
import { useCmsTranslations } from "@shopware/composables";
|
|
4
4
|
import { getProductName, getTranslatedProperty } from "@shopware/helpers";
|
|
5
5
|
import { defu } from "defu";
|
|
6
|
-
import { computed, ref } from "vue";
|
|
6
|
+
import { type Ref, computed, onMounted, ref } from "vue";
|
|
7
7
|
import xss from "xss";
|
|
8
8
|
import { useProduct } from "#imports";
|
|
9
|
+
import type { Schemas } from "#shopware";
|
|
9
10
|
|
|
10
11
|
const props = defineProps<{
|
|
11
12
|
content: CmsElementProductDescriptionReviews;
|
|
12
13
|
}>();
|
|
13
14
|
|
|
14
15
|
type Translations = {
|
|
15
|
-
|
|
16
|
+
product: {
|
|
16
17
|
description: string;
|
|
17
18
|
reviews: string;
|
|
19
|
+
messages: {
|
|
20
|
+
reviewAdded: string;
|
|
21
|
+
};
|
|
18
22
|
};
|
|
19
23
|
};
|
|
20
24
|
|
|
21
25
|
let translations: Translations = {
|
|
22
|
-
|
|
26
|
+
product: {
|
|
23
27
|
description: "Description",
|
|
24
28
|
reviews: "Reviews",
|
|
29
|
+
messages: {
|
|
30
|
+
reviewAdded: "Thank you for submitting your review",
|
|
31
|
+
},
|
|
25
32
|
},
|
|
26
33
|
};
|
|
27
34
|
translations = defu(useCmsTranslations(), translations) as Translations;
|
|
@@ -37,7 +44,13 @@ const toggleTabs = (tabNumber: number) => {
|
|
|
37
44
|
currentTab.value = tabNumber;
|
|
38
45
|
};
|
|
39
46
|
|
|
40
|
-
const reviews =
|
|
47
|
+
const reviews: Ref<Schemas["ProductReview"][]> = ref([]);
|
|
48
|
+
|
|
49
|
+
onMounted(async () => {
|
|
50
|
+
if (props.content.data?.reviews?.elements) {
|
|
51
|
+
reviews.value = props.content.data.reviews.elements;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
41
54
|
</script>
|
|
42
55
|
|
|
43
56
|
<template>
|
|
@@ -60,7 +73,7 @@ const reviews = computed(() => props.content.data.reviews.elements);
|
|
|
60
73
|
@click="() => toggleTabs(1)"
|
|
61
74
|
>
|
|
62
75
|
<i class="fas fa-space-shuttle text-base mr-1" />
|
|
63
|
-
{{ translations.
|
|
76
|
+
{{ translations.product.description }}
|
|
64
77
|
</a>
|
|
65
78
|
</li>
|
|
66
79
|
<li class="mr-2 text-center">
|
|
@@ -71,10 +84,11 @@ const reviews = computed(() => props.content.data.reviews.elements);
|
|
|
71
84
|
? 'text-secondary-500 bg-white'
|
|
72
85
|
: 'text-white bg-secondary-500',
|
|
73
86
|
]"
|
|
87
|
+
data-testid="product-reviews-tab"
|
|
74
88
|
@click="() => toggleTabs(2)"
|
|
75
89
|
>
|
|
76
90
|
<i class="fas fa-cog text-base mr-1" />
|
|
77
|
-
{{ translations.
|
|
91
|
+
{{ translations.product.reviews }} ({{ reviews.length }})
|
|
78
92
|
</a>
|
|
79
93
|
</li>
|
|
80
94
|
</ul>
|
package/nuxt.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopware/cms-base-layer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Vue CMS Nuxt Layer for Shopware",
|
|
5
5
|
"author": "Shopware",
|
|
6
6
|
"repository": {
|
|
@@ -29,32 +29,32 @@
|
|
|
29
29
|
"nuxt.config.ts"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@nuxt/kit": "3.
|
|
33
|
-
"@tresjs/cientos": "4.0
|
|
34
|
-
"@tresjs/core": "4.3.
|
|
32
|
+
"@nuxt/kit": "3.16.2",
|
|
33
|
+
"@tresjs/cientos": "4.3.0",
|
|
34
|
+
"@tresjs/core": "4.3.3",
|
|
35
35
|
"@vuelidate/core": "2.0.3",
|
|
36
36
|
"@vuelidate/validators": "2.0.4",
|
|
37
|
-
"@vueuse/core": "
|
|
38
|
-
"entities": "
|
|
37
|
+
"@vueuse/core": "13.1.0",
|
|
38
|
+
"entities": "6.0.0",
|
|
39
39
|
"html-to-ast": "0.0.6",
|
|
40
|
-
"three": "0.
|
|
40
|
+
"three": "0.173.0",
|
|
41
41
|
"vue": "3.5.13",
|
|
42
42
|
"xss": "1.0.15",
|
|
43
|
-
"@shopware/composables": "1.
|
|
43
|
+
"@shopware/composables": "1.9.0",
|
|
44
44
|
"@shopware/helpers": "1.4.0",
|
|
45
|
-
"@shopware/api-client": "1.
|
|
45
|
+
"@shopware/api-client": "1.3.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@biomejs/biome": "1.8.3",
|
|
49
|
-
"@nuxt/schema": "3.
|
|
50
|
-
"@types/three": "0.
|
|
51
|
-
"@vitest/coverage-v8": "
|
|
52
|
-
"nuxt": "3.
|
|
53
|
-
"typescript": "5.
|
|
49
|
+
"@nuxt/schema": "3.16.2",
|
|
50
|
+
"@types/three": "0.173.0",
|
|
51
|
+
"@vitest/coverage-v8": "3.1.1",
|
|
52
|
+
"nuxt": "3.16.2",
|
|
53
|
+
"typescript": "5.8.3",
|
|
54
54
|
"unbuild": "2.0.0",
|
|
55
|
-
"vitest": "
|
|
55
|
+
"vitest": "3.1.1",
|
|
56
56
|
"vue-router": "4.5.0",
|
|
57
|
-
"vue-tsc": "2.
|
|
57
|
+
"vue-tsc": "2.2.8",
|
|
58
58
|
"tsconfig": "0.0.0"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|