@shopbite-de/storefront 1.18.0 → 1.18.2
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/app.vue +2 -2
- package/app/components/Cart/Item.vue +5 -5
- package/app/components/Category/Listing.vue +36 -5
- package/app/components/Checkout/DeliveryTimeSelect.vue +8 -6
- package/app/components/Checkout/PaymentAndDelivery.vue +31 -33
- package/app/components/Checkout/PaymentMethod.vue +1 -1
- package/app/components/Checkout/ShippingMethod.vue +1 -1
- package/app/components/Checkout/Summary.vue +37 -48
- package/app/components/Header/Body.vue +66 -8
- package/app/components/Header/Right.vue +1 -1
- package/app/components/Header/Title.vue +7 -14
- package/app/components/Header.vue +3 -5
- package/app/components/Product/Card.vue +13 -11
- package/app/components/Product/CardSkeleton.vue +26 -0
- package/app/components/Product/{Detail2.vue → Detail.vue} +1 -1
- package/app/components/SalesChannelSwitch.vue +1 -3
- package/app/components/User/Detail.vue +2 -2
- package/app/pages/bestellung/bestaetigen.vue +14 -0
- package/app/pages/bestellung/warenkorb.vue +49 -0
- package/app/pages/bestellung/zahlung-versand.vue +23 -0
- package/app/pages/bestellung.vue +36 -84
- package/app/pages/index.vue +1 -1
- package/app/pages/kontakt.vue +1 -1
- package/app/pages/speisekarte/[...all].vue +1 -0
- package/app/stores/checkout.ts +22 -0
- package/nuxt.config.ts +6 -1
- package/package.json +3 -1
- package/test/e2e/simple-checkout-as-recurring-customer.test.ts +8 -8
- package/test/nuxt/HeaderTitle.test.ts +0 -42
- /package/app/components/Product/{Configurator2.vue → Configurator.vue} +0 -0
package/app/app.vue
CHANGED
|
@@ -7,11 +7,11 @@ const TOAST_CONFIG = {
|
|
|
7
7
|
title: "Wir haben geöffnet!",
|
|
8
8
|
color: "primary" as const,
|
|
9
9
|
progress: false,
|
|
10
|
-
duration:
|
|
10
|
+
duration: 5000,
|
|
11
11
|
icon: "i-lucide-party-popper",
|
|
12
12
|
actions: [
|
|
13
13
|
{
|
|
14
|
-
icon: "i-lucide-
|
|
14
|
+
icon: "i-lucide-utensils-crossed",
|
|
15
15
|
label: "Zur Speisekarte",
|
|
16
16
|
color: "neutral" as const,
|
|
17
17
|
variant: "outline" as const,
|
|
@@ -64,7 +64,7 @@ const handleRemoveItem = () => {
|
|
|
64
64
|
? cartItem?.children?.[0]?.payload?.options
|
|
65
65
|
: cartItem?.payload?.options"
|
|
66
66
|
:key="option.group + option.option"
|
|
67
|
-
class="text-sm text-pretty text-
|
|
67
|
+
class="text-sm text-pretty text-muted mt-1"
|
|
68
68
|
>
|
|
69
69
|
{{ option.group }}: {{ option.option }}
|
|
70
70
|
</p>
|
|
@@ -84,14 +84,14 @@ const handleRemoveItem = () => {
|
|
|
84
84
|
:min="1"
|
|
85
85
|
:max="100"
|
|
86
86
|
class="max-w-46"
|
|
87
|
-
aria-label="
|
|
87
|
+
aria-label="Menge"
|
|
88
88
|
/>
|
|
89
89
|
<UButton
|
|
90
90
|
v-if="withDeleteButton"
|
|
91
91
|
icon="i-lucide-trash"
|
|
92
92
|
variant="soft"
|
|
93
93
|
color="neutral"
|
|
94
|
-
aria-label="
|
|
94
|
+
aria-label="Artikel entfernen"
|
|
95
95
|
@click="handleRemoveItem"
|
|
96
96
|
/>
|
|
97
97
|
</div>
|
|
@@ -101,14 +101,14 @@ const handleRemoveItem = () => {
|
|
|
101
101
|
icon="i-lucide-trash"
|
|
102
102
|
variant="outline"
|
|
103
103
|
color="error"
|
|
104
|
-
aria-label="
|
|
104
|
+
aria-label="Artikel entfernen"
|
|
105
105
|
@click="handleRemoveItem"
|
|
106
106
|
/>
|
|
107
107
|
</div>
|
|
108
108
|
|
|
109
109
|
<!-- Empty state -->
|
|
110
110
|
<div v-else class="text-center py-4">
|
|
111
|
-
<p class="text-
|
|
111
|
+
<p class="text-muted">Warenkorb ist leer...</p>
|
|
112
112
|
</div>
|
|
113
113
|
</div>
|
|
114
114
|
</template>
|
|
@@ -46,12 +46,14 @@ const {
|
|
|
46
46
|
loading,
|
|
47
47
|
search,
|
|
48
48
|
getElements,
|
|
49
|
+
getCurrentListing,
|
|
49
50
|
getCurrentSortingOrder,
|
|
50
51
|
getSortingOrders,
|
|
51
52
|
changeCurrentSortingOrder,
|
|
52
53
|
getAvailableFilters,
|
|
53
54
|
getCurrentFilters,
|
|
54
55
|
setCurrentFilters,
|
|
56
|
+
setInitialListing,
|
|
55
57
|
} = useListing({
|
|
56
58
|
listingType: "categoryListing",
|
|
57
59
|
categoryId: props.id,
|
|
@@ -85,9 +87,27 @@ const selectedListingFilters = computed<ShortcutFilterParam[]>(() => {
|
|
|
85
87
|
];
|
|
86
88
|
});
|
|
87
89
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
90
|
+
const nuxtApp = useNuxtApp();
|
|
91
|
+
const { data: listingPayload, pending } = await useAsyncData(
|
|
92
|
+
`listing${categoryId.value}`,
|
|
93
|
+
async () => {
|
|
94
|
+
await search(searchCriteria);
|
|
95
|
+
// Return the result so it gets serialized into the SSR payload.
|
|
96
|
+
// On the client, useAsyncData will restore this without re-running search().
|
|
97
|
+
return getCurrentListing.value;
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// Populate useListing state from the SSR payload on the client.
|
|
102
|
+
// useListing uses plain refs (not useState), so its state is not automatically
|
|
103
|
+
// hydrated — we restore it via setInitialListing.
|
|
104
|
+
if (listingPayload.value) {
|
|
105
|
+
await setInitialListing(listingPayload.value);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// During SSR hydration, pending may briefly be true before the payload cache is applied.
|
|
109
|
+
// Suppress the skeleton in that window to prevent a hydration mismatch.
|
|
110
|
+
const showSkeleton = computed(() => pending.value && !nuxtApp.isHydrating);
|
|
91
111
|
|
|
92
112
|
watch(selectedListingFilters, (newFilters, oldFilters) => {
|
|
93
113
|
if (newFilters[0]?.value === oldFilters?.[0]?.value) {
|
|
@@ -196,9 +216,20 @@ const moreThanOneFilterAndOption = computed<boolean>(
|
|
|
196
216
|
</UDrawer>
|
|
197
217
|
</div>
|
|
198
218
|
|
|
199
|
-
<
|
|
219
|
+
<div
|
|
220
|
+
v-if="showSkeleton"
|
|
221
|
+
class="flex flex-col gap-4"
|
|
222
|
+
aria-busy="true"
|
|
223
|
+
aria-label="Produkte werden geladen"
|
|
224
|
+
>
|
|
225
|
+
<LazyProductCardSkeleton v-for="i in 6" :key="i" />
|
|
226
|
+
</div>
|
|
200
227
|
|
|
201
|
-
<div
|
|
228
|
+
<div
|
|
229
|
+
v-else
|
|
230
|
+
class="flex flex-col gap-4 transition-opacity duration-200"
|
|
231
|
+
:class="{ 'opacity-40 pointer-events-none': loading }"
|
|
232
|
+
>
|
|
202
233
|
<ProductCard
|
|
203
234
|
v-for="product in getElements"
|
|
204
235
|
:key="product.id"
|
|
@@ -136,24 +136,26 @@ function handleTimeInput(event: Event): void {
|
|
|
136
136
|
<template>
|
|
137
137
|
<div v-if="isClosedHoliday(now) === false" class="flex flex-col gap-2 mt-4">
|
|
138
138
|
<div class="flex flex-row items-center justify-between gap-4">
|
|
139
|
-
<
|
|
139
|
+
<label for="delivery-time" class="flex-1">
|
|
140
|
+
Wunschlieferung- oder Abholzeit ab:
|
|
141
|
+
</label>
|
|
140
142
|
<client-only>
|
|
141
|
-
<
|
|
143
|
+
<UInput
|
|
144
|
+
id="delivery-time"
|
|
142
145
|
type="time"
|
|
143
146
|
:min="minTime ?? undefined"
|
|
144
147
|
:max="maxTime ?? undefined"
|
|
145
148
|
:disabled="isClosedToday || isClosedHoliday(now) === true"
|
|
146
|
-
:value="selected"
|
|
149
|
+
:model-value="selected"
|
|
147
150
|
step="300"
|
|
148
|
-
class="border rounded px-2 py-1"
|
|
149
151
|
@input="handleTimeInput"
|
|
150
152
|
/>
|
|
151
153
|
</client-only>
|
|
152
154
|
</div>
|
|
153
|
-
<p v-if="validationError" class="text-sm text-
|
|
155
|
+
<p v-if="validationError" class="text-sm text-error">
|
|
154
156
|
{{ validationError }}
|
|
155
157
|
</p>
|
|
156
|
-
<p v-else class="text-sm text-
|
|
158
|
+
<p v-else class="text-sm text-muted">{{ helperText }}</p>
|
|
157
159
|
<UBadge
|
|
158
160
|
:label="deliveryTimeInfo"
|
|
159
161
|
icon="i-lucide-clock"
|
|
@@ -82,42 +82,40 @@ watch(
|
|
|
82
82
|
</script>
|
|
83
83
|
|
|
84
84
|
<template>
|
|
85
|
-
<
|
|
86
|
-
<div class="flex flex-col
|
|
87
|
-
<div class="
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
icon="i-lucide-circle-question-mark"
|
|
96
|
-
/>
|
|
97
|
-
</div>
|
|
98
|
-
<URadioGroup
|
|
99
|
-
v-model="selectedPaymentMethodId"
|
|
100
|
-
:items="selectablePaymentMethods"
|
|
101
|
-
variant="card"
|
|
85
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
86
|
+
<div class="flex flex-col gap-4">
|
|
87
|
+
<div class="flex items-center gap-2">
|
|
88
|
+
<UIcon name="i-lucide-badge-euro" class="size-5 text-muted" />
|
|
89
|
+
<h2 class="text-lg font-semibold">Zahlungsarten</h2>
|
|
90
|
+
<UButton
|
|
91
|
+
to="/zahlung-und-versand"
|
|
92
|
+
size="sm"
|
|
93
|
+
variant="ghost"
|
|
94
|
+
icon="i-lucide-circle-help"
|
|
102
95
|
/>
|
|
103
96
|
</div>
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
97
|
+
<URadioGroup
|
|
98
|
+
v-model="selectedPaymentMethodId"
|
|
99
|
+
:items="selectablePaymentMethods"
|
|
100
|
+
variant="card"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="flex flex-col gap-4">
|
|
104
|
+
<div class="flex items-center gap-2">
|
|
105
|
+
<UIcon name="i-lucide-car" class="size-5 text-muted" />
|
|
106
|
+
<h2 class="text-lg font-semibold">Versandarten</h2>
|
|
107
|
+
<UButton
|
|
108
|
+
to="/zahlung-und-versand"
|
|
109
|
+
size="sm"
|
|
110
|
+
variant="ghost"
|
|
111
|
+
icon="i-lucide-circle-help"
|
|
119
112
|
/>
|
|
120
113
|
</div>
|
|
114
|
+
<URadioGroup
|
|
115
|
+
v-model="selectedShippingMethodId"
|
|
116
|
+
:items="selectableShippingMethods"
|
|
117
|
+
variant="card"
|
|
118
|
+
/>
|
|
121
119
|
</div>
|
|
122
|
-
</
|
|
120
|
+
</div>
|
|
123
121
|
</template>
|
|
@@ -22,7 +22,7 @@ const { paymentMethod } = toRefs(props);
|
|
|
22
22
|
<h3 class="text-base text-pretty font-semibold text-highlighted">
|
|
23
23
|
Zahlart
|
|
24
24
|
</h3>
|
|
25
|
-
<p class="text-[15px] text-pretty text-
|
|
25
|
+
<p class="text-[15px] text-pretty text-muted mt-1">
|
|
26
26
|
{{ paymentMethod.distinguishableName }}
|
|
27
27
|
</p>
|
|
28
28
|
</div>
|
|
@@ -22,7 +22,7 @@ const { shippingMethod } = toRefs(props);
|
|
|
22
22
|
<h3 class="text-base text-pretty font-semibold text-highlighted">
|
|
23
23
|
Versandart
|
|
24
24
|
</h3>
|
|
25
|
-
<p class="text-[15px] text-pretty text-
|
|
25
|
+
<p class="text-[15px] text-pretty text-muted mt-1">
|
|
26
26
|
{{ shippingMethod.name }}
|
|
27
27
|
</p>
|
|
28
28
|
</div>
|
|
@@ -23,8 +23,6 @@ onMounted(() => {
|
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
watch(isCheckoutEnabled, () => console.log(isCheckoutEnabled.value));
|
|
27
|
-
|
|
28
26
|
useIntervalFn(refresh, 10000);
|
|
29
27
|
|
|
30
28
|
async function handleCreateOrder() {
|
|
@@ -63,10 +61,6 @@ const isValidToProceed = computed(
|
|
|
63
61
|
const selectedDeliveryTime = ref("");
|
|
64
62
|
const isValidTime = ref(true);
|
|
65
63
|
|
|
66
|
-
watch(selectedDeliveryTime, (newValue) => {
|
|
67
|
-
console.log(newValue);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
64
|
const checkoutButtonLabel = computed<string>(() => {
|
|
71
65
|
if (!customerDataAvailable.value) {
|
|
72
66
|
return "Bitte einloggen oder Kundendaten erfassen";
|
|
@@ -85,52 +79,47 @@ const checkoutButtonLabel = computed<string>(() => {
|
|
|
85
79
|
</script>
|
|
86
80
|
|
|
87
81
|
<template>
|
|
88
|
-
<
|
|
89
|
-
<div class="
|
|
82
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 py-8">
|
|
83
|
+
<div class="flex flex-col gap-6">
|
|
90
84
|
<div class="flex flex-col gap-4">
|
|
91
|
-
<h3 class="text-
|
|
85
|
+
<h3 class="text-lg font-semibold">Kundendaten</h3>
|
|
92
86
|
<UserDetail v-if="customerDataAvailable" />
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<CheckoutPaymentMethod :payment-method="selectedPaymentMethod" />
|
|
97
|
-
<CheckoutShippingMethod :shipping-method="selectedShippingMethod" />
|
|
98
|
-
<CheckoutDeliveryTimeSelect
|
|
99
|
-
v-model:valid="isValidTime"
|
|
100
|
-
v-model="selectedDeliveryTime"
|
|
101
|
-
/>
|
|
102
|
-
</div>
|
|
87
|
+
<p v-else class="text-muted">
|
|
88
|
+
Bitte vorher einloggen oder Kundendaten erfassen
|
|
89
|
+
</p>
|
|
103
90
|
</div>
|
|
104
91
|
<div class="flex flex-col gap-4">
|
|
105
|
-
<h3 class="text-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<CheckoutVoucherInput />
|
|
113
|
-
<UButton
|
|
114
|
-
:icon="
|
|
115
|
-
isValidToProceed ? 'i-lucide-shopping-cart' : 'i-lucide-lock'
|
|
116
|
-
"
|
|
117
|
-
:disabled="!isValidToProceed"
|
|
118
|
-
:label="checkoutButtonLabel"
|
|
119
|
-
size="xl"
|
|
120
|
-
block
|
|
121
|
-
@click="handleCreateOrder"
|
|
122
|
-
/>
|
|
123
|
-
<p class="font-light text-muted">
|
|
124
|
-
Mit Klick auf den Button "Jetzt bestellen!" erklärst du dich mit
|
|
125
|
-
unseren
|
|
126
|
-
<ULink to="/agb" class="text-primary font-medium">AGB</ULink> und
|
|
127
|
-
<ULink to="/datenschutz" class="text-primary font-medium"
|
|
128
|
-
>Datenschutzbestimmungen</ULink
|
|
129
|
-
>
|
|
130
|
-
einverstanden.
|
|
131
|
-
</p>
|
|
132
|
-
</div>
|
|
92
|
+
<h3 class="text-lg font-semibold">Versand & Zahlung</h3>
|
|
93
|
+
<CheckoutPaymentMethod :payment-method="selectedPaymentMethod" />
|
|
94
|
+
<CheckoutShippingMethod :shipping-method="selectedShippingMethod" />
|
|
95
|
+
<CheckoutDeliveryTimeSelect
|
|
96
|
+
v-model:valid="isValidTime"
|
|
97
|
+
v-model="selectedDeliveryTime"
|
|
98
|
+
/>
|
|
133
99
|
</div>
|
|
134
100
|
</div>
|
|
135
|
-
|
|
101
|
+
<div class="flex flex-col gap-4">
|
|
102
|
+
<h3 class="text-lg font-semibold">Warenkorb</h3>
|
|
103
|
+
<UCard>
|
|
104
|
+
<QuickView :with-quantity-input="false" :with-delete-button="false" />
|
|
105
|
+
</UCard>
|
|
106
|
+
<CheckoutVoucherInput />
|
|
107
|
+
<UButton
|
|
108
|
+
:icon="isValidToProceed ? 'i-lucide-shopping-cart' : 'i-lucide-lock'"
|
|
109
|
+
:disabled="!isValidToProceed"
|
|
110
|
+
:label="checkoutButtonLabel"
|
|
111
|
+
size="xl"
|
|
112
|
+
block
|
|
113
|
+
@click="handleCreateOrder"
|
|
114
|
+
/>
|
|
115
|
+
<p class="text-sm text-muted">
|
|
116
|
+
Mit Klick auf "Jetzt bestellen!" erklärst du dich mit unseren
|
|
117
|
+
<ULink to="/agb" class="text-primary font-medium">AGB</ULink> und
|
|
118
|
+
<ULink to="/datenschutz" class="text-primary font-medium"
|
|
119
|
+
>Datenschutzbestimmungen</ULink
|
|
120
|
+
>
|
|
121
|
+
einverstanden.
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
136
125
|
</template>
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { useUser } from "@shopware/composables";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const { mainMenu } = useNavigation(false);
|
|
5
|
+
const multiChannelEnabled =
|
|
6
|
+
useRuntimeConfig().public.shopBite.feature.multiChannel;
|
|
5
7
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
);
|
|
8
|
+
const { isLoggedIn, isGuestSession, logout } = useUser();
|
|
9
|
+
const toast = useToast();
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
const logoutHandler = () => {
|
|
12
|
+
logout();
|
|
13
|
+
toast.add({
|
|
14
|
+
title: "Tschüss!",
|
|
15
|
+
description: "Erfolgreich abgemeldet.",
|
|
16
|
+
color: "success",
|
|
17
|
+
});
|
|
18
|
+
};
|
|
12
19
|
</script>
|
|
13
20
|
|
|
14
21
|
<template>
|
|
@@ -18,7 +25,58 @@ const { mainMenu } = useNavigation(false);
|
|
|
18
25
|
orientation="vertical"
|
|
19
26
|
class="-mx-2.5"
|
|
20
27
|
/>
|
|
21
|
-
|
|
28
|
+
|
|
29
|
+
<USeparator class="my-4" />
|
|
30
|
+
|
|
31
|
+
<div class="flex flex-col gap-1">
|
|
32
|
+
<template v-if="isLoggedIn || isGuestSession">
|
|
33
|
+
<UButton
|
|
34
|
+
color="neutral"
|
|
35
|
+
variant="ghost"
|
|
36
|
+
to="/konto"
|
|
37
|
+
icon="i-lucide-user"
|
|
38
|
+
label="Mein Konto"
|
|
39
|
+
class="justify-start"
|
|
40
|
+
/>
|
|
41
|
+
<UButton
|
|
42
|
+
color="neutral"
|
|
43
|
+
variant="ghost"
|
|
44
|
+
to="/konto/bestellungen"
|
|
45
|
+
icon="i-lucide-pizza"
|
|
46
|
+
label="Bestellungen"
|
|
47
|
+
class="justify-start"
|
|
48
|
+
/>
|
|
49
|
+
<UButton
|
|
50
|
+
color="neutral"
|
|
51
|
+
variant="ghost"
|
|
52
|
+
icon="i-lucide-log-out"
|
|
53
|
+
label="Abmelden"
|
|
54
|
+
class="justify-start"
|
|
55
|
+
@click="logoutHandler"
|
|
56
|
+
/>
|
|
57
|
+
</template>
|
|
58
|
+
<template v-else>
|
|
59
|
+
<UButton
|
|
60
|
+
color="neutral"
|
|
61
|
+
variant="ghost"
|
|
62
|
+
to="/anmelden"
|
|
63
|
+
icon="i-lucide-user"
|
|
64
|
+
label="Anmelden"
|
|
65
|
+
class="justify-start"
|
|
66
|
+
/>
|
|
67
|
+
<UButton
|
|
68
|
+
color="neutral"
|
|
69
|
+
variant="ghost"
|
|
70
|
+
to="/registrierung"
|
|
71
|
+
icon="i-lucide-user-plus"
|
|
72
|
+
label="Registrieren"
|
|
73
|
+
class="justify-start"
|
|
74
|
+
/>
|
|
75
|
+
</template>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div v-if="multiChannelEnabled" class="mt-4">
|
|
79
|
+
<USeparator class="mb-4" />
|
|
22
80
|
<SalesChannelSwitch />
|
|
23
81
|
</div>
|
|
24
82
|
</template>
|
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
const config = useRuntimeConfig();
|
|
3
|
-
const siteName = computed(() => config.public.site.name);
|
|
4
|
-
</script>
|
|
5
|
-
|
|
1
|
+
<script setup lang="ts"></script>
|
|
6
2
|
<template>
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class="h-12 w-auto"
|
|
14
|
-
/>
|
|
15
|
-
</NuxtLink>
|
|
3
|
+
<UColorModeImage
|
|
4
|
+
alt="Logo"
|
|
5
|
+
light="/light/Logo.png"
|
|
6
|
+
dark="/dark/Logo.png"
|
|
7
|
+
width="150"
|
|
8
|
+
/>
|
|
16
9
|
</template>
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useNavigation } from "~/composables/useNavigation";
|
|
3
|
-
|
|
4
2
|
const { mainMenu } = useNavigation(false);
|
|
5
|
-
|
|
6
3
|
const loginSlide = ref(false);
|
|
7
4
|
</script>
|
|
8
5
|
|
|
@@ -13,13 +10,14 @@ const loginSlide = ref(false);
|
|
|
13
10
|
</template>
|
|
14
11
|
|
|
15
12
|
<UNavigationMenu color="primary" variant="pill" :items="mainMenu" />
|
|
13
|
+
<SalesChannelSwitch />
|
|
16
14
|
|
|
17
15
|
<template #right>
|
|
18
16
|
<HeaderRight />
|
|
19
17
|
</template>
|
|
20
18
|
|
|
21
19
|
<template #body>
|
|
22
|
-
<
|
|
20
|
+
<LazyHeaderBody />
|
|
23
21
|
</template>
|
|
24
22
|
</UHeader>
|
|
25
23
|
|
|
@@ -30,7 +28,7 @@ const loginSlide = ref(false);
|
|
|
30
28
|
>
|
|
31
29
|
<template #body>
|
|
32
30
|
<div class="h-full m-4">
|
|
33
|
-
<
|
|
31
|
+
<LazyUserLoginForm />
|
|
34
32
|
</div>
|
|
35
33
|
</template>
|
|
36
34
|
</USlideover>
|
|
@@ -81,7 +81,7 @@ function onVariantSelected(variant: Schemas["Product"]) {
|
|
|
81
81
|
:ui="{ footer: 'w-full', root: 'shadow-lg' }"
|
|
82
82
|
>
|
|
83
83
|
<template #header>
|
|
84
|
-
<
|
|
84
|
+
<LazyUBadge
|
|
85
85
|
v-if="isVegi"
|
|
86
86
|
icon="i-lucide-leaf"
|
|
87
87
|
color="success"
|
|
@@ -92,7 +92,7 @@ function onVariantSelected(variant: Schemas["Product"]) {
|
|
|
92
92
|
</template>
|
|
93
93
|
|
|
94
94
|
<div v-if="product.cover?.media?.url">
|
|
95
|
-
<
|
|
95
|
+
<LazyNuxtImg
|
|
96
96
|
:src="product.cover.media.url"
|
|
97
97
|
class="rounded-md h-auto max-w-full object-contain ransition-opacity duration-700"
|
|
98
98
|
sizes="(min-width: 1024px) 50vw, 100vw"
|
|
@@ -138,15 +138,17 @@ function onVariantSelected(variant: Schemas["Product"]) {
|
|
|
138
138
|
/>
|
|
139
139
|
</div>
|
|
140
140
|
</div>
|
|
141
|
-
<
|
|
142
|
-
<
|
|
143
|
-
<
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
141
|
+
<ClientOnly>
|
|
142
|
+
<UCollapsible v-model:open="openDetails" class="flex flex-col gap-2">
|
|
143
|
+
<template #content>
|
|
144
|
+
<LazyProductDetail
|
|
145
|
+
:product-id="product.id"
|
|
146
|
+
@product-added="toggleDetails"
|
|
147
|
+
@variant-selected="onVariantSelected"
|
|
148
|
+
/>
|
|
149
|
+
</template>
|
|
150
|
+
</UCollapsible>
|
|
151
|
+
</ClientOnly>
|
|
150
152
|
</template>
|
|
151
153
|
</UPageCard>
|
|
152
154
|
</AnimatedSection>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="ring-1 ring-accented rounded-xl shadow-lg p-4 flex flex-row gap-4"
|
|
4
|
+
>
|
|
5
|
+
<div class="flex flex-col gap-3 flex-1 min-w-0">
|
|
6
|
+
<USkeleton class="h-5 w-20 rounded-full" />
|
|
7
|
+
<div class="flex items-baseline gap-2">
|
|
8
|
+
<USkeleton class="h-3.5 w-7" />
|
|
9
|
+
<USkeleton class="h-3.5 w-36" />
|
|
10
|
+
</div>
|
|
11
|
+
<div class="flex flex-col gap-2">
|
|
12
|
+
<USkeleton class="h-3 w-full" />
|
|
13
|
+
<USkeleton class="h-3 w-5/6" />
|
|
14
|
+
<USkeleton class="h-3 w-1/2" />
|
|
15
|
+
</div>
|
|
16
|
+
<div class="flex justify-between items-center mt-auto pt-2">
|
|
17
|
+
<USkeleton class="h-4 w-14" />
|
|
18
|
+
<div class="flex gap-2">
|
|
19
|
+
<USkeleton class="h-8 w-8 rounded-md" />
|
|
20
|
+
<USkeleton class="h-8 w-8 rounded-md" />
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<USkeleton class="size-28 rounded-md shrink-0" />
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
@@ -142,7 +142,7 @@ watch(productDetails, () => {
|
|
|
142
142
|
<template>
|
|
143
143
|
<div v-if="!pending">
|
|
144
144
|
<div v-if="productDetails?.configurator">
|
|
145
|
-
<
|
|
145
|
+
<ProductConfigurator
|
|
146
146
|
v-if="productDetails?.configurator"
|
|
147
147
|
:p="productDetails.product"
|
|
148
148
|
:c="productDetails.configurator"
|
|
@@ -8,9 +8,7 @@ const { apiClient } = useShopwareContext();
|
|
|
8
8
|
|
|
9
9
|
const config = useRuntimeConfig();
|
|
10
10
|
|
|
11
|
-
const isMultiChannel =
|
|
12
|
-
() => config.public.shopBite.feature.multiChannel === "true",
|
|
13
|
-
);
|
|
11
|
+
const isMultiChannel = useRuntimeConfig().public.shopBite.feature.multiChannel;
|
|
14
12
|
|
|
15
13
|
const storeUrl = computed(() => config.public.storeUrl);
|
|
16
14
|
|
|
@@ -47,7 +47,7 @@ onMounted(() => {
|
|
|
47
47
|
</script>
|
|
48
48
|
|
|
49
49
|
<template>
|
|
50
|
-
<
|
|
50
|
+
<UCard class="mb-4">
|
|
51
51
|
<div>{{ fullName }}</div>
|
|
52
52
|
<div>{{ user?.email }}</div>
|
|
53
53
|
<USeparator
|
|
@@ -76,5 +76,5 @@ onMounted(() => {
|
|
|
76
76
|
:with-edit-button="withEditButton"
|
|
77
77
|
@update:address="handleAddressUpdate"
|
|
78
78
|
/>
|
|
79
|
-
</
|
|
79
|
+
</UCard>
|
|
80
80
|
</template>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { isLoggedIn, isGuestSession } = useUser();
|
|
3
|
+
const { setStep } = useCheckoutStore();
|
|
4
|
+
|
|
5
|
+
if (!isLoggedIn.value && !isGuestSession.value) {
|
|
6
|
+
await navigateTo("/bestellung/warenkorb");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
setStep(2);
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<CheckoutSummary />
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import LoginOrRegister from "~/components/Checkout/LoginOrRegister.vue";
|
|
3
|
+
|
|
4
|
+
const { isLoggedIn, isGuestSession } = useUser();
|
|
5
|
+
const { isEmpty } = useCart();
|
|
6
|
+
const { setStep } = useCheckoutStore();
|
|
7
|
+
|
|
8
|
+
setStep(0);
|
|
9
|
+
|
|
10
|
+
const isCustomerAvailable = computed<boolean>(() => {
|
|
11
|
+
return isLoggedIn.value || isGuestSession.value;
|
|
12
|
+
});
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 py-8">
|
|
17
|
+
<div class="flex flex-col gap-4">
|
|
18
|
+
<h2 class="text-lg font-semibold">
|
|
19
|
+
{{ isCustomerAvailable ? "Deine Daten" : "Anmelden oder registrieren" }}
|
|
20
|
+
</h2>
|
|
21
|
+
<LoginOrRegister v-if="!isLoggedIn && !isGuestSession" />
|
|
22
|
+
<UserDetail v-else :with-edit-button="true" />
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="flex flex-col gap-4">
|
|
26
|
+
<h2 class="text-lg font-semibold">Warenkorb</h2>
|
|
27
|
+
<UCard>
|
|
28
|
+
<CartQuickView />
|
|
29
|
+
</UCard>
|
|
30
|
+
<UButton
|
|
31
|
+
v-if="!isEmpty && isCustomerAvailable"
|
|
32
|
+
label="Zahlungs- und Versandart auswählen"
|
|
33
|
+
size="xl"
|
|
34
|
+
block
|
|
35
|
+
trailing-icon="i-lucide-arrow-right"
|
|
36
|
+
to="/bestellung/zahlung-versand"
|
|
37
|
+
/>
|
|
38
|
+
<UButton
|
|
39
|
+
v-if="isEmpty"
|
|
40
|
+
label="Zur Speisekarte"
|
|
41
|
+
size="xl"
|
|
42
|
+
block
|
|
43
|
+
variant="outline"
|
|
44
|
+
icon="i-lucide-arrow-left"
|
|
45
|
+
to="/speisekarte"
|
|
46
|
+
/>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const { isLoggedIn, isGuestSession } = useUser();
|
|
3
|
+
const { setStep } = useCheckoutStore();
|
|
4
|
+
|
|
5
|
+
if (!isLoggedIn.value && !isGuestSession.value) {
|
|
6
|
+
await navigateTo("/bestellung/warenkorb");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
setStep(1);
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<div class="flex flex-col gap-8 py-8">
|
|
14
|
+
<CheckoutPaymentAndDelivery />
|
|
15
|
+
<UButton
|
|
16
|
+
label="Weiter zu Prüfen & Bestellen"
|
|
17
|
+
trailing-icon="i-lucide-arrow-right"
|
|
18
|
+
size="xl"
|
|
19
|
+
block
|
|
20
|
+
to="/bestellung/bestaetigen"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
package/app/pages/bestellung.vue
CHANGED
|
@@ -1,95 +1,47 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import LoginOrRegister from "~/components/Checkout/LoginOrRegister.vue";
|
|
3
2
|
import type { StepperItem } from "@nuxt/ui";
|
|
4
3
|
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
4
|
+
const checkoutStore = useCheckoutStore();
|
|
5
|
+
const { step } = storeToRefs(checkoutStore);
|
|
7
6
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
slot: "shipping" as const,
|
|
16
|
-
title: "Zahlung & Versand",
|
|
17
|
-
icon: "i-lucide-truck",
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
slot: "checkout" as const,
|
|
21
|
-
title: "Prüfen & Bestellen",
|
|
22
|
-
icon: "i-lucide-check",
|
|
23
|
-
},
|
|
24
|
-
] satisfies StepperItem[];
|
|
7
|
+
const stepRoutes = [
|
|
8
|
+
"/bestellung/warenkorb",
|
|
9
|
+
"/bestellung/zahlung-versand",
|
|
10
|
+
"/bestellung/bestaetigen",
|
|
11
|
+
] as const;
|
|
25
12
|
|
|
26
|
-
const
|
|
13
|
+
const items = computed(
|
|
14
|
+
() =>
|
|
15
|
+
[
|
|
16
|
+
{
|
|
17
|
+
title: "Warenkorb",
|
|
18
|
+
icon: "i-lucide-shopping-cart",
|
|
19
|
+
disabled: false,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
title: "Zahlung & Versand",
|
|
23
|
+
icon: "i-lucide-truck",
|
|
24
|
+
disabled: step.value < 1,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
title: "Prüfen & Bestellen",
|
|
28
|
+
icon: "i-lucide-check",
|
|
29
|
+
disabled: step.value < 2,
|
|
30
|
+
},
|
|
31
|
+
] satisfies StepperItem[],
|
|
32
|
+
);
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
watch(step, (newStep) => {
|
|
35
|
+
navigateTo(stepRoutes[newStep]);
|
|
30
36
|
});
|
|
31
37
|
</script>
|
|
32
38
|
|
|
33
39
|
<template>
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
v-if="!isLoggedIn && !isGuestSession"
|
|
42
|
-
class="basis-1/2"
|
|
43
|
-
/>
|
|
44
|
-
<div v-else class="basis-1/2">
|
|
45
|
-
<UserDetail :with-edit-button="true" />
|
|
46
|
-
</div>
|
|
47
|
-
<div class="basis-1/2">
|
|
48
|
-
<h3 class="text-2xl mb-6 font-semibold">Warenkorb</h3>
|
|
49
|
-
<CartQuickView />
|
|
50
|
-
<UButton
|
|
51
|
-
v-if="!isEmpty && isCustomerAvailable"
|
|
52
|
-
:disabled="!isLoggedIn && !isGuestSession"
|
|
53
|
-
label="Zahlungs- und Versandart auswählen"
|
|
54
|
-
size="xl"
|
|
55
|
-
block
|
|
56
|
-
trailing-icon="i-lucide-arrow-right"
|
|
57
|
-
class="my-4"
|
|
58
|
-
@click="activeStep = 1"
|
|
59
|
-
/>
|
|
60
|
-
<UButton
|
|
61
|
-
v-if="isEmpty"
|
|
62
|
-
label="Zur Speisekarte"
|
|
63
|
-
size="xl"
|
|
64
|
-
block
|
|
65
|
-
icon="i-lucide-arrow-left"
|
|
66
|
-
class="my-4"
|
|
67
|
-
to="/speisekarte"
|
|
68
|
-
/>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
</template>
|
|
72
|
-
|
|
73
|
-
<template #shipping>
|
|
74
|
-
<div class="my-14">
|
|
75
|
-
<CheckoutPaymentAndDelivery />
|
|
76
|
-
<div class="w-full flex justify-end">
|
|
77
|
-
<UButton
|
|
78
|
-
label="Weiter zu Prüfen & Bestellen"
|
|
79
|
-
trailing-icon="i-lucide-arrow-right"
|
|
80
|
-
class="m-8 md:max-w-96"
|
|
81
|
-
size="xl"
|
|
82
|
-
block
|
|
83
|
-
@click="activeStep = 2"
|
|
84
|
-
/>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
</template>
|
|
88
|
-
|
|
89
|
-
<template #checkout>
|
|
90
|
-
<CheckoutSummary />
|
|
91
|
-
</template>
|
|
92
|
-
</UStepper>
|
|
93
|
-
</UPageBody>
|
|
94
|
-
</UContainer>
|
|
40
|
+
<UPageSection>
|
|
41
|
+
<UStepper ref="stepper" v-model="step" :items="items" size="lg">
|
|
42
|
+
<template #content>
|
|
43
|
+
<NuxtPage />
|
|
44
|
+
</template>
|
|
45
|
+
</UStepper>
|
|
46
|
+
</UPageSection>
|
|
95
47
|
</template>
|
package/app/pages/index.vue
CHANGED
package/app/pages/kontakt.vue
CHANGED
|
@@ -13,7 +13,7 @@ if (config.public.shopBite.feature.contactForm !== true) {
|
|
|
13
13
|
|
|
14
14
|
<template>
|
|
15
15
|
<UPageSection
|
|
16
|
-
title="Sie haben eine Frage oder Anregung?Wir freuen uns auf Ihre Nachricht."
|
|
16
|
+
title="Sie haben eine Frage oder Anregung? Wir freuen uns auf Ihre Nachricht."
|
|
17
17
|
description="Bitte beachten Sie, dass wir Tischreservierung oder Tischstornierung nur telefonisch entgegennehmen können."
|
|
18
18
|
icon="i-lucide-mail"
|
|
19
19
|
orientation="horizontal"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const stepRoutes = [
|
|
2
|
+
"/bestellung/warenkorb",
|
|
3
|
+
"/bestellung/zahlung-versand",
|
|
4
|
+
"/bestellung/bestaetigen",
|
|
5
|
+
] as const;
|
|
6
|
+
|
|
7
|
+
export const useCheckoutStore = defineStore("checkout", () => {
|
|
8
|
+
const step = ref(0);
|
|
9
|
+
|
|
10
|
+
function setStep(index: number) {
|
|
11
|
+
step.value = index;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function navigateToStep(index: number) {
|
|
15
|
+
if (index < 0 || index >= stepRoutes.length) return;
|
|
16
|
+
if (index <= step.value) {
|
|
17
|
+
await navigateTo(stepRoutes[index]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { step, setStep, navigateToStep };
|
|
22
|
+
});
|
package/nuxt.config.ts
CHANGED
|
@@ -57,7 +57,7 @@ export default defineNuxtConfig({
|
|
|
57
57
|
shopBite: {
|
|
58
58
|
menuCategoryId: "",
|
|
59
59
|
feature: {
|
|
60
|
-
multiChannel:
|
|
60
|
+
multiChannel: false,
|
|
61
61
|
secureKey: "",
|
|
62
62
|
contactForm: false,
|
|
63
63
|
},
|
|
@@ -78,6 +78,9 @@ export default defineNuxtConfig({
|
|
|
78
78
|
"/registrierung/bestaetigen": {
|
|
79
79
|
ssr: false,
|
|
80
80
|
},
|
|
81
|
+
"/bestellung": {
|
|
82
|
+
redirect: "/bestellung/warenkorb",
|
|
83
|
+
},
|
|
81
84
|
},
|
|
82
85
|
|
|
83
86
|
css: ["~/assets/css/main.css"],
|
|
@@ -100,6 +103,8 @@ export default defineNuxtConfig({
|
|
|
100
103
|
"@nuxt/scripts",
|
|
101
104
|
"nuxt-vitalizer",
|
|
102
105
|
"@nuxt/eslint",
|
|
106
|
+
"@pinia/nuxt",
|
|
107
|
+
"@nuxt/hints",
|
|
103
108
|
],
|
|
104
109
|
|
|
105
110
|
content: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopbite-de/storefront",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.2",
|
|
4
4
|
"main": "nuxt.config.ts",
|
|
5
5
|
"description": "Shopware storefront for food delivery shops",
|
|
6
6
|
"keywords": [
|
|
@@ -23,10 +23,12 @@
|
|
|
23
23
|
"@iconify-json/lucide": "^1.2.97",
|
|
24
24
|
"@iconify-json/simple-icons": "^1.2.73",
|
|
25
25
|
"@nuxt/content": "3.12.0",
|
|
26
|
+
"@nuxt/hints": "1.0.2",
|
|
26
27
|
"@nuxt/image": "^2.0.0",
|
|
27
28
|
"@nuxt/scripts": "0.13.2",
|
|
28
29
|
"@nuxt/ui": "^4.5.1",
|
|
29
30
|
"@nuxtjs/robots": "^6.0.0",
|
|
31
|
+
"@pinia/nuxt": "0.11.3",
|
|
30
32
|
"@sentry/nuxt": "^10.43.0",
|
|
31
33
|
"@shopware/api-client": "^1.4.0",
|
|
32
34
|
"@shopware/api-gen": "^1.4.0",
|
|
@@ -39,7 +39,7 @@ async function clearCart(page: Page) {
|
|
|
39
39
|
await expect(page.getByRole("heading", { name: /warenkorb/i })).toBeVisible();
|
|
40
40
|
|
|
41
41
|
const deleteButtons = page.getByRole("button", {
|
|
42
|
-
name: "
|
|
42
|
+
name: "Artikel entfernen",
|
|
43
43
|
});
|
|
44
44
|
const itemCount = await deleteButtons.count();
|
|
45
45
|
|
|
@@ -92,7 +92,7 @@ async function selectProductAndAddToCart(
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
async function proceedToCheckoutAndLogin(page: Page) {
|
|
95
|
-
await page.goto("/bestellung", { waitUntil: "load" });
|
|
95
|
+
await page.goto("/bestellung/warenkorb", { waitUntil: "load" });
|
|
96
96
|
|
|
97
97
|
const loginTab = page.getByRole("tab", { name: "Einloggen" });
|
|
98
98
|
await expect(loginTab).toBeVisible();
|
|
@@ -129,7 +129,7 @@ async function proceedToCheckoutAndLogin(page: Page) {
|
|
|
129
129
|
|
|
130
130
|
// Wait for login to complete and navigate back to checkout
|
|
131
131
|
await page.waitForTimeout(2000);
|
|
132
|
-
await page.goto("/bestellung", { waitUntil: "load" });
|
|
132
|
+
await page.goto("/bestellung/zahlung-versand", { waitUntil: "load" });
|
|
133
133
|
await page.waitForTimeout(1000);
|
|
134
134
|
|
|
135
135
|
// Skip the rest of this function since we already logged in
|
|
@@ -159,15 +159,15 @@ async function proceedToCheckoutAndLogin(page: Page) {
|
|
|
159
159
|
|
|
160
160
|
async function verifyCheckoutQuantity(page: Page) {
|
|
161
161
|
// Ensure we're on the checkout page
|
|
162
|
-
if (!page.url().includes("/bestellung")) {
|
|
163
|
-
await page.goto("/bestellung", { waitUntil: "load" });
|
|
162
|
+
if (!page.url().includes("/bestellung/warenkorb")) {
|
|
163
|
+
await page.goto("/bestellung/warenkorb", { waitUntil: "load" });
|
|
164
164
|
await page.waitForTimeout(1000);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
// Try to find the quantity input with multiple strategies
|
|
168
168
|
const checkoutQuantityInput = page
|
|
169
169
|
.getByRole("spinbutton", {
|
|
170
|
-
name: /
|
|
170
|
+
name: /menge/i,
|
|
171
171
|
})
|
|
172
172
|
.or(page.getByRole("spinbutton"))
|
|
173
173
|
.or(page.locator("input[type='number']"));
|
|
@@ -178,7 +178,7 @@ async function verifyCheckoutQuantity(page: Page) {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
async function selectPaymentAndShipping(page: Page) {
|
|
181
|
-
const nextStepButton = page.getByRole("
|
|
181
|
+
const nextStepButton = page.getByRole("link", {
|
|
182
182
|
name: "Zahlungs- und Versandart auswählen",
|
|
183
183
|
});
|
|
184
184
|
await expect(nextStepButton).toBeVisible({ timeout: 10000 });
|
|
@@ -197,7 +197,7 @@ async function selectPaymentAndShipping(page: Page) {
|
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
async function proceedToOrderReview(page: Page) {
|
|
200
|
-
const lastStepButton = page.getByRole("
|
|
200
|
+
const lastStepButton = page.getByRole("link", {
|
|
201
201
|
name: "Weiter zu Prüfen & Bestellen",
|
|
202
202
|
});
|
|
203
203
|
await expect(lastStepButton).toBeVisible({ timeout: 10000 });
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { mountSuspended, mockNuxtImport } from "@nuxt/test-utils/runtime";
|
|
3
|
-
import HeaderTitle from "~/components/Header/Title.vue";
|
|
4
|
-
|
|
5
|
-
const { mockRuntimeConfig } = vi.hoisted(() => ({
|
|
6
|
-
mockRuntimeConfig: {
|
|
7
|
-
app: {
|
|
8
|
-
baseURL: "/",
|
|
9
|
-
},
|
|
10
|
-
public: {
|
|
11
|
-
site: {
|
|
12
|
-
name: "ShopBite Test Store",
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
|
-
mockNuxtImport("useRuntimeConfig", () => () => mockRuntimeConfig);
|
|
19
|
-
|
|
20
|
-
describe("HeaderTitle", () => {
|
|
21
|
-
it("renders the site name for screen readers", async () => {
|
|
22
|
-
const component = await mountSuspended(HeaderTitle);
|
|
23
|
-
const srOnly = component.find(".sr-only");
|
|
24
|
-
expect(srOnly.exists()).toBe(true);
|
|
25
|
-
expect(srOnly.text()).toBe("ShopBite Test Store");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("renders a link to the home page", async () => {
|
|
29
|
-
const component = await mountSuspended(HeaderTitle);
|
|
30
|
-
const link = component.findComponent({ name: "NuxtLink" });
|
|
31
|
-
expect(link.exists()).toBe(true);
|
|
32
|
-
expect(link.props("to")).toBe("/");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("renders the logo image", async () => {
|
|
36
|
-
const component = await mountSuspended(HeaderTitle);
|
|
37
|
-
const image = component.findComponent({ name: "UColorModeImage" });
|
|
38
|
-
expect(image.exists()).toBe(true);
|
|
39
|
-
expect(image.props("light")).toBe("/light/Logo.png");
|
|
40
|
-
expect(image.props("dark")).toBe("/dark/Logo.png");
|
|
41
|
-
});
|
|
42
|
-
});
|
|
File without changes
|