@shopbite-de/storefront 1.7.9 → 1.8.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.
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import type { Schemas } from "#shopware";
3
- import { useTrackEvent } from "#imports";
4
3
 
5
4
  const props = defineProps<{
6
5
  product: Schemas["Product"];
@@ -10,25 +9,17 @@ const { addToWishlist, isInWishlist, removeFromWishlist } = useProductWishlist(
10
9
  props.product.id,
11
10
  );
12
11
  const { getWishlistProducts } = useWishlist();
13
-
14
- const trackWishlistEvent = (
15
- action: "add_to_wishlist" | "remove_from_wishlist",
16
- ) => {
17
- useTrackEvent(action, {
18
- props: { product_number: props.product.productNumber },
19
- });
20
- };
12
+ const { trackAddToWishlist } = useTrackEvent();
21
13
 
22
14
  const toggleWishlistProduct = async () => {
23
15
  try {
24
16
  if (isInWishlist.value) {
25
17
  await removeFromWishlist();
26
- trackWishlistEvent("remove_from_wishlist");
27
18
  await getWishlistProducts();
28
19
  } else {
29
20
  await addToWishlist();
30
- trackWishlistEvent("add_to_wishlist");
31
21
  await getWishlistProducts();
22
+ trackAddToWishlist(props.product);
32
23
  }
33
24
  } catch (error) {
34
25
  console.error("[wishlist][handleWishlistError]", error);
@@ -1,13 +1,13 @@
1
1
  <script setup lang="ts">
2
2
  import QuickView from "~/components/Cart/QuickView.vue";
3
3
  import { useIntervalFn } from "@vueuse/core";
4
- import { useTrackEvent } from "#imports";
5
4
 
6
5
  const { selectedPaymentMethod, selectedShippingMethod } = useCheckout();
7
6
  const { createOrder } = useCheckout();
8
7
  const { refreshCart } = useCart();
9
8
  const { isLoggedIn, isGuestSession } = useUser();
10
9
  const { isCheckoutEnabled, refresh } = useShopBiteConfig();
10
+ const { trackOrder } = useTrackEvent();
11
11
 
12
12
  const toast = useToast();
13
13
 
@@ -24,12 +24,7 @@ async function handleCreateOrder() {
24
24
  customerComment: "Wunschlieferzeit: " + selectedDeliveryTime.value,
25
25
  });
26
26
 
27
- useTrackEvent("checkout", {
28
- revenue: {
29
- currency: "EUR",
30
- amount: order.amountTotal as number,
31
- },
32
- });
27
+ trackOrder(order);
33
28
 
34
29
  await refreshCart();
35
30
  toast.add({
@@ -1,5 +1,4 @@
1
1
  <script setup lang="ts">
2
- import { useTrackEvent } from "#imports";
3
2
  import { useProductSearch } from "@shopware/composables";
4
3
  import type { Schemas } from "#shopware";
5
4
 
@@ -42,12 +41,6 @@ async function addToCart(productId: string) {
42
41
  });
43
42
  await refreshCart(newCart);
44
43
  await showSuccessToast();
45
- useTrackEvent("add_to_cart", {
46
- props: {
47
- product_number: product.productNumber,
48
- quantity: 1,
49
- },
50
- });
51
44
  }
52
45
  </script>
53
46
 
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { NavigationMenuItem } from "@nuxt/ui";
3
3
  import type { Schemas } from "#shopware";
4
- import { useTrackEvent } from "#imports";
5
4
 
6
5
  const { loadNavigationElements, navigationElements } = useNavigation();
7
6
 
@@ -17,9 +16,6 @@ const scrollToElement = (elementId: string, margin = 0) => {
17
16
  top: offsetPosition,
18
17
  behavior: "smooth",
19
18
  });
20
- useTrackEvent("scroll_to_category", {
21
- props: { category_name: elementId },
22
- });
23
19
  }
24
20
  };
25
21
 
@@ -1,7 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { NavigationMenuItem } from "@nuxt/ui";
3
3
  import type { Schemas } from "#shopware";
4
- import { useTrackEvent } from "#imports";
5
4
 
6
5
  const { loadNavigationElements, navigationElements } = useNavigation();
7
6
 
@@ -17,9 +16,6 @@ const scrollToElement = (elementId: string, margin = 0) => {
17
16
  top: offsetPosition,
18
17
  behavior: "smooth",
19
18
  });
20
- useTrackEvent("scroll_to_category", {
21
- props: { category_name: elementId },
22
- });
23
19
  }
24
20
  };
25
21
 
@@ -1,12 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import type { operations, Schemas } from "#shopware";
3
3
  import type { AssociationItemProduct } from "~/types/Association";
4
+ import { useTrackEvent } from "~/composables/useTrackEvent";
4
5
 
5
6
  const props = defineProps<{
6
7
  productId: string;
7
8
  }>();
8
9
 
9
10
  const { apiClient } = useShopwareContext();
11
+ const { trackProductView } = useTrackEvent();
10
12
 
11
13
  const productId = toRef(props.productId);
12
14
 
@@ -23,6 +25,7 @@ const searchCriteria = {
23
25
  "propertyIds",
24
26
  "options",
25
27
  "optionIds",
28
+ "seoCategory",
26
29
  "configuratorSettings",
27
30
  "children",
28
31
  "parentId",
@@ -33,6 +36,7 @@ const searchCriteria = {
33
36
  property_group_option: ["id", "name", "translated", "group"],
34
37
  product_configurator_setting: ["id", "optionId", "option", "productId"],
35
38
  product_option: ["id", "groupId", "name", "translated", "group"],
39
+ category: ["id", "name", "translated"],
36
40
  },
37
41
  associations: {
38
42
  cover: {
@@ -128,6 +132,11 @@ const onIngredientsDeselected = (deselected: string[]) => {
128
132
  const onAddToCart = () => emit("product-added");
129
133
 
130
134
  const emit = defineEmits(["product-added", "variant-selected"]);
135
+
136
+ watch(productDetails, () => {
137
+ if (!productDetails.value) return;
138
+ trackProductView(productDetails.value.product);
139
+ });
131
140
  </script>
132
141
 
133
142
  <template>
@@ -1,8 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { ref } from "vue";
3
3
  import { onClickOutside, useDebounceFn, useFocus } from "@vueuse/core";
4
- import type { Schemas } from "#shopware";
5
- import { useTrackEvent } from "#imports";
6
4
 
7
5
  withDefaults(
8
6
  defineProps<{
@@ -53,18 +51,6 @@ watch(typingQuery, (value) => {
53
51
  const performSuggestSearch = useDebounceFn((value) => {
54
52
  searchTerm.value = value;
55
53
  search();
56
- trackEvent(value);
57
- }, 500);
58
-
59
- const trackEvent = useDebounceFn((searchTerm: string) => {
60
- const searchResult = getProducts.value.map(function (
61
- product: Schemas["Product"],
62
- ) {
63
- return product.productNumber;
64
- });
65
- useTrackEvent("search", {
66
- props: { search_term: searchTerm, search_result: searchResult.join(",") },
67
- });
68
54
  }, 500);
69
55
 
70
56
  if (import.meta.client) {
@@ -12,6 +12,7 @@ export function useAddToCart() {
12
12
  const { addProducts, refreshCart } = useCart();
13
13
  const toast = useToast();
14
14
  const { triggerProductAdded } = useProductEvents();
15
+ const { trackAddToCart: trackAddToCartEvent } = useTrackEvent();
15
16
 
16
17
  const selectedExtras = ref<AssociationItemProduct[]>([]);
17
18
  const deselectedIngredients = ref<string[]>([]);
@@ -140,13 +141,7 @@ export function useAddToCart() {
140
141
  await showSuccessToast();
141
142
 
142
143
  triggerProductAdded();
143
-
144
- useTrackEvent("add_to_cart", {
145
- props: {
146
- product_number: selectedProduct.value.productNumber,
147
- quantity: selectedQuantity.value,
148
- },
149
- });
144
+ trackAddToCartEvent(selectedProduct.value, selectedQuantity.value);
150
145
 
151
146
  isLoading.value = false;
152
147
  if (onSuccess) {
@@ -0,0 +1,62 @@
1
+ import type { Schemas } from "#shopware";
2
+
3
+ export function useTrackEvent() {
4
+ const { proxy } = useScriptMatomoAnalytics();
5
+
6
+ function trackProductView(product: Schemas["Product"]) {
7
+ proxy._paq.push([
8
+ "setEcommerceView",
9
+ product.productNumber,
10
+ product.translated.name ?? product.name,
11
+ product.seoCategory.name,
12
+ product.calculatedPrice.unitPrice,
13
+ ]);
14
+ proxy._paq.push(["trackPageView", product.productNumber]);
15
+ }
16
+
17
+ function trackOrder(order: Schemas["Order"]) {
18
+ order.lineItems?.forEach((item) => {
19
+ if (item.type === "container") return;
20
+ proxy._paq.push([
21
+ "addEcommerceItem",
22
+ item.product?.productNumber ?? item.id,
23
+ item.label,
24
+ item.product?.seoCategory.name ?? "Default",
25
+ item.unitPrice,
26
+ item.quantity,
27
+ ]);
28
+ });
29
+
30
+ proxy._paq.push([
31
+ "trackEcommerceOrder",
32
+ order.orderNumber,
33
+ order.price.totalPrice,
34
+ ]);
35
+ }
36
+
37
+ function trackAddToWishlist(product: Schemas["Product"]) {
38
+ proxy._paq.push([
39
+ "trackEvent",
40
+ "Product",
41
+ "AddToWishlist",
42
+ product.productNumber,
43
+ ]);
44
+ }
45
+
46
+ function trackAddToCart(product: Schemas["Product"], quantity: number) {
47
+ proxy._paq.push([
48
+ "trackEvent",
49
+ "Cart",
50
+ "AddToCart",
51
+ product.productNumber,
52
+ quantity,
53
+ ]);
54
+ }
55
+
56
+ return {
57
+ trackProductView,
58
+ trackOrder,
59
+ trackAddToWishlist,
60
+ trackAddToCart,
61
+ };
62
+ }
@@ -5,6 +5,7 @@ export function useWishlistActions() {
5
5
  const toast = useToast();
6
6
  const { triggerProductAdded } = useProductEvents();
7
7
  const { clearWishlist } = useWishlist();
8
+ const { trackAddToCart: trackAddToCartEvent } = useTrackEvent();
8
9
 
9
10
  const isAddingToCart = ref(false);
10
11
  const addingItemId = ref<string | null>(null);
@@ -53,6 +54,7 @@ export function useWishlistActions() {
53
54
  await refreshCart(newCart);
54
55
 
55
56
  triggerProductAdded();
57
+ trackAddToCartEvent(product, 1);
56
58
 
57
59
  toast.add({
58
60
  title: "In den Warenkorb gelegt",
@@ -60,13 +62,6 @@ export function useWishlistActions() {
60
62
  icon: "i-lucide-shopping-cart",
61
63
  color: "primary",
62
64
  });
63
-
64
- useTrackEvent("add_to_cart", {
65
- props: {
66
- product_number: product.productNumber,
67
- quantity: 1,
68
- },
69
- });
70
65
  } catch (error) {
71
66
  console.error("[wishlist][addSingleItemToCart] Error details:", error);
72
67
  toast.add({
@@ -128,14 +123,6 @@ export function useWishlistActions() {
128
123
  icon: "i-lucide-shopping-cart",
129
124
  color: "primary",
130
125
  });
131
-
132
- useTrackEvent("add_to_cart", {
133
- props: {
134
- product_count: addableProducts.length,
135
- skipped_count: products.length - addableProducts.length,
136
- source: "wishlist_bulk",
137
- },
138
- });
139
126
  } catch (error) {
140
127
  console.error("[wishlist][addAllItemsToCart] Error:", error);
141
128
  toast.add({
package/content/index.yml CHANGED
@@ -23,7 +23,7 @@ hero:
23
23
  icon: i-lucide-arrow-right
24
24
  trailing: true
25
25
  color: primary
26
- to: /speisekarte
26
+ to: /menu/
27
27
  size: xl
28
28
  - label: Tisch reservieren
29
29
  icon: i-lucide-phone
@@ -87,7 +87,7 @@ cta:
87
87
  backgroundImage: https://shopware.shopbite.de/media/f5/19/4a/1762546880/category-pizza-header.webp
88
88
  links:
89
89
  - label: Zur Speisekarte
90
- to: /speisekarte
90
+ to: /menu/
91
91
  color: primary
92
92
  - label: Tisch reservieren
93
93
  to: tel:+49610471427
@@ -48,12 +48,8 @@ footer:
48
48
  to: /agb
49
49
  - label: Top Kategorien
50
50
  children:
51
- - label: Pizza
52
- to: /c/Pizza/
53
- - label: Nudeln
54
- to: /c/Nudeln/
55
- - label: Fleischgerichte
56
- to: /c/Fleischgerichte/
51
+ - label: Burger
52
+ to: /menu/burger/
57
53
  - label: Unternehmen
58
54
  children:
59
55
  - label: 'Tel: 06104 71427'
package/nuxt.config.ts CHANGED
@@ -97,14 +97,9 @@ export default defineNuxtConfig({
97
97
  "@sentry/nuxt/module",
98
98
  "@nuxt/ui",
99
99
  "@nuxt/scripts",
100
- "@nuxtjs/plausible",
101
100
  "nuxt-vitalizer",
102
101
  ],
103
102
 
104
- plausible: {
105
- ignoredHostnames: ["localhost"],
106
- },
107
-
108
103
  content: {
109
104
  experimental: { sqliteConnector: "native" },
110
105
  },
@@ -184,7 +179,6 @@ export default defineNuxtConfig({
184
179
  "@sentry/nuxt/module",
185
180
  "@nuxt/ui",
186
181
  "@nuxt/scripts",
187
- "@nuxtjs/plausible",
188
182
  "@nuxt/test-utils/module",
189
183
  "@nuxt/eslint",
190
184
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopbite-de/storefront",
3
- "version": "1.7.9",
3
+ "version": "1.8.0",
4
4
  "main": "nuxt.config.ts",
5
5
  "description": "Shopware storefront for food delivery shops",
6
6
  "keywords": [
@@ -18,11 +18,10 @@
18
18
  "@heroicons/vue": "^2.2.0",
19
19
  "@iconify-json/lucide": "^1.2.73",
20
20
  "@iconify-json/simple-icons": "^1.2.59",
21
- "@nuxt/content": "^3.8.2",
21
+ "@nuxt/content": "3.8.2",
22
22
  "@nuxt/image": "^2.0.0",
23
23
  "@nuxt/scripts": "0.13.2",
24
24
  "@nuxt/ui": "^4.1.0",
25
- "@nuxtjs/plausible": "2.0.1",
26
25
  "@nuxtjs/robots": "^5.5.6",
27
26
  "@sentry/nuxt": "^10.25.0",
28
27
  "@shopware/api-client": "^1.3.0",
@@ -39,7 +39,9 @@ mockNuxtImport("useProductEvents", () => {
39
39
  });
40
40
 
41
41
  mockNuxtImport("useTrackEvent", () => {
42
- return mockTrackEvent;
42
+ return () => ({
43
+ trackAddToCart: mockTrackEvent,
44
+ });
43
45
  });
44
46
 
45
47
  // Provide the mocks globally or in a way that they are picked up
@@ -53,7 +55,9 @@ vi.stubGlobal("useToast", () => ({
53
55
  vi.stubGlobal("useProductEvents", () => ({
54
56
  triggerProductAdded: mockTriggerProductAdded,
55
57
  }));
56
- vi.stubGlobal("useTrackEvent", mockTrackEvent);
58
+ vi.stubGlobal("useTrackEvent", () => ({
59
+ trackAddToCart: mockTrackEvent,
60
+ }));
57
61
 
58
62
  describe("useAddToCart", () => {
59
63
  const mockProduct = {
@@ -131,10 +135,7 @@ describe("useAddToCart", () => {
131
135
  expect(mockRefreshCart).toHaveBeenCalled();
132
136
  expect(mockToastAdd).toHaveBeenCalled();
133
137
  expect(mockTriggerProductAdded).toHaveBeenCalled();
134
- expect(mockTrackEvent).toHaveBeenCalledWith(
135
- "add_to_cart",
136
- expect.any(Object),
137
- );
138
+ expect(mockTrackEvent).toHaveBeenCalledWith(mockProduct, 1);
138
139
  });
139
140
 
140
141
  it("should add product with extras to cart as container", async () => {
@@ -35,7 +35,9 @@ mockNuxtImport("useWishlist", () => () => ({
35
35
  clearWishlist: mockClearWishlist,
36
36
  }));
37
37
 
38
- mockNuxtImport("useTrackEvent", () => mockTrackEvent);
38
+ mockNuxtImport("useTrackEvent", () => () => ({
39
+ trackAddToCart: mockTrackEvent,
40
+ }));
39
41
 
40
42
  describe("useWishlistActions", () => {
41
43
  const mockProduct = {
@@ -72,10 +74,7 @@ describe("useWishlistActions", () => {
72
74
  expect(mockToastAdd).toHaveBeenCalledWith(
73
75
  expect.objectContaining({ title: "In den Warenkorb gelegt" }),
74
76
  );
75
- expect(mockTrackEvent).toHaveBeenCalledWith(
76
- "add_to_cart",
77
- expect.any(Object),
78
- );
77
+ expect(mockTrackEvent).toHaveBeenCalledWith(mockProduct, 1);
79
78
  });
80
79
 
81
80
  it("should warn when adding a base product with variants", async () => {
@@ -1,65 +0,0 @@
1
- <script setup lang="ts">
2
- import type { Schemas } from "#shopware";
3
- const props = defineProps<{
4
- parentProduct: Schemas["Product"];
5
- }>();
6
- const { parentProduct } = toRefs(props);
7
- const { product, changeVariant, configurator } = useProduct(
8
- parentProduct.value?.children?.length > 0
9
- ? parentProduct.value.children[0]
10
- : parentProduct.value,
11
- parentProduct.value?.configuratorSettings || [],
12
- );
13
- const { findVariantForSelectedOptions } = useProductConfigurator();
14
- const { variants: selectableOptions } = useProductVariants(configurator);
15
- const selectedOptions = ref<Record<string, string>>({});
16
- function initialOptions(variant: Ref<Schemas["Product"]>) {
17
- const options = variant.value.options as Schemas["PropertyGroupOption"][];
18
- for (const option of options) {
19
- if (option.group && option.id) {
20
- selectedOptions.value[option.group.id] = option.id;
21
- }
22
- }
23
- }
24
- onMounted(() => {
25
- initialOptions(product);
26
- });
27
- watch(
28
- selectedOptions.value,
29
- async () => {
30
- const foundVariant = await findVariantForSelectedOptions(
31
- selectedOptions.value,
32
- );
33
- const variant = parentProduct.value.children?.find(
34
- (child: Schemas["Product"]) => child.id === foundVariant?.id,
35
- );
36
-
37
- if (variant) {
38
- changeVariant(variant);
39
- emit("variant-switched", variant);
40
- }
41
- },
42
- { deep: true },
43
- );
44
- const emit = defineEmits<{
45
- "variant-switched": [variant: Schemas["Product"]];
46
- }>();
47
- </script>
48
- <template>
49
- <div
50
- v-for="(variantGroup, propertyGroupId) in selectableOptions"
51
- :key="propertyGroupId"
52
- class="my-6"
53
- >
54
- <div class="flex flex-row gap-2 items-center">
55
- <div class="basis-1/3">{{ variantGroup.name }}:</div>
56
- <USelect
57
- v-model="selectedOptions[propertyGroupId]"
58
- value-key="productId"
59
- :items="variantGroup.options"
60
- class="w-full"
61
- icon="i-lucide-square-stack"
62
- />
63
- </div>
64
- </div>
65
- </template>
@@ -1,187 +0,0 @@
1
- <script setup lang="ts">
2
- import type { Schemas, operations } from "#shopware";
3
- import type { AssociationItemProduct } from "~/types/Association";
4
- import { v5 as uuidv5 } from "uuid";
5
- import { computed, ref, toRefs } from "vue";
6
- import { useTrackEvent } from "#imports";
7
-
8
- const UUID_NAMESPACE = "b098ef7e-0fa2-4073-b002-7ceec4360fbf";
9
- const CART_SUCCESS_TITLE = "Gute Wahl!";
10
- const LINE_ITEM_PRODUCT = "product";
11
- const LINE_ITEM_CONTAINER = "container";
12
-
13
- const props = defineProps<{
14
- product: Schemas["Product"];
15
- }>();
16
- const { product } = toRefs(props);
17
- const { addProducts, refreshCart } = useCart();
18
- const toast = useToast();
19
- const { triggerProductAdded } = useProductEvents();
20
-
21
- const selectedExtras = ref<AssociationItemProduct[]>([]);
22
- const deselectedIngredients = ref<string[]>([]);
23
- const selectedQuantity = ref(1);
24
- const selectedProduct = ref<Schemas["Product"]>(product.value);
25
-
26
- const cartItemLabel = computed(() => {
27
- const formatIngredientModifications = (
28
- items: Array<{ label: string } | string>,
29
- prefix: string,
30
- ): string => {
31
- if (!items.length) return "";
32
-
33
- const labels = items.map((item) =>
34
- typeof item === "string" ? item : item.label,
35
- );
36
- const separator = " " + prefix;
37
- return " " + prefix + labels.join(separator);
38
- };
39
-
40
- const extrasFormatted = formatIngredientModifications(
41
- selectedExtras.value,
42
- "+",
43
- );
44
- const removedFormatted = formatIngredientModifications(
45
- deselectedIngredients.value,
46
- "-",
47
- );
48
-
49
- return `${selectedProduct.value.translated.name}${extrasFormatted}${removedFormatted}`;
50
- });
51
-
52
- const createExtras = () =>
53
- selectedExtras.value.map((extra) => ({
54
- id: extra.value,
55
- type: LINE_ITEM_PRODUCT,
56
- quantity: selectedQuantity.value,
57
- }));
58
-
59
- const generateSortedExtrasString = (extras: AssociationItemProduct[]) =>
60
- extras
61
- .map((extra) => extra.value)
62
- .sort()
63
- .join("");
64
-
65
- const generateProductId = (baseId: string, extras: AssociationItemProduct[]) =>
66
- extras.length ? baseId + generateSortedExtrasString(extras) : baseId;
67
-
68
- function createCartItems(): operations["addLineItem post /checkout/cart/line-item"]["body"]["items"] {
69
- const extras = createExtras();
70
-
71
- // Simple product when no extras
72
- if (extras.length === 0 && deselectedIngredients.value.length === 0) {
73
- return [
74
- {
75
- id: selectedProduct.value.id,
76
- quantity: selectedQuantity.value,
77
- type: LINE_ITEM_PRODUCT,
78
- label: cartItemLabel.value,
79
- },
80
- ];
81
- }
82
-
83
- // Container product when extras are selected
84
- const generatedUuid = uuidv5(
85
- generateProductId(selectedProduct.value.id, selectedExtras.value),
86
- UUID_NAMESPACE,
87
- );
88
-
89
- return [
90
- {
91
- id: generatedUuid,
92
- quantity: selectedQuantity.value,
93
- type: LINE_ITEM_CONTAINER,
94
- label: cartItemLabel.value,
95
- payload: {
96
- productNumber: selectedProduct.value.productNumber,
97
- },
98
- children: [
99
- {
100
- id: selectedProduct.value.id,
101
- quantity: selectedQuantity.value,
102
- type: LINE_ITEM_PRODUCT,
103
- },
104
- ...extras,
105
- ],
106
- },
107
- ];
108
- }
109
-
110
- // Toast Notification
111
- async function showSuccessToast() {
112
- toast.add({
113
- title: CART_SUCCESS_TITLE,
114
- description: `${selectedProduct.value.translated.name} wurde in den Warenkorb gelegt.`,
115
- icon: "i-lucide-shopping-cart",
116
- color: "success",
117
- progress: true,
118
- color: "primary",
119
- duration: 2000,
120
- });
121
- }
122
-
123
- // Add to Cart
124
- async function addToCart() {
125
- const cartItems = createCartItems();
126
- const newCart = await addProducts(cartItems);
127
- await refreshCart(newCart);
128
- await showSuccessToast();
129
- emit("product-added");
130
- triggerProductAdded(); // Clear search globally
131
- useTrackEvent("add_to_cart", {
132
- props: {
133
- product_number: selectedProduct.value.productNumber,
134
- quantity: selectedQuantity.value,
135
- },
136
- });
137
- }
138
-
139
- // Event Handlers
140
- const onVariantSwitched = (variant: Schemas["Product"]) => {
141
- selectedProduct.value = variant;
142
- };
143
-
144
- const onExtrasSelected = (extras: AssociationItemProduct[]) => {
145
- selectedExtras.value = extras;
146
- };
147
-
148
- const onIngredientsDeselected = (deselected: string[]) => {
149
- deselectedIngredients.value = deselected;
150
- };
151
- const emit = defineEmits(["product-added"]);
152
- </script>
153
-
154
- <template>
155
- <div class="flex flex-col justify-between w-full">
156
- <div>
157
- <ProductConfigurator
158
- :parent-product="product"
159
- @variant-switched="onVariantSwitched"
160
- />
161
- <ProductCrossSelling
162
- :product="selectedProduct"
163
- @extras-selected="onExtrasSelected"
164
- />
165
- <ProductDeselectIngredient
166
- :product="selectedProduct"
167
- @ingredients-deselected="onIngredientsDeselected"
168
- />
169
- </div>
170
- <div class="flex flex-row gap-4 mt-8">
171
- <UInputNumber
172
- v-model="selectedQuantity"
173
- size="xl"
174
- placeholder="Anzahl"
175
- :min="1"
176
- :max="100"
177
- />
178
- <UButton
179
- size="xl"
180
- label="In den Warenkorb"
181
- icon="i-lucide-shopping-cart"
182
- block
183
- @click="addToCart"
184
- />
185
- </div>
186
- </div>
187
- </template>