@shopbite-de/storefront 1.6.2 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -7,6 +7,12 @@ NUXT_PUBLIC_SHOPWARE_ENDPOINT="https://shopware.shopbite.de/store-api"
7
7
  NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN="TOKEN"
8
8
  NUXT_PUBLIC_SHOPWARE_COUNTRY_ID=019a17a0f67b706a8ec9ead3059e12ba
9
9
 
10
+ NUXT_SHOPWARE_ADMIN_ENDPOINT=https://your-shopware-instance.com
11
+ NUXT_SHOPWARE_ADMIN_CLIENT_ID=your_client_id
12
+ NUXT_SHOPWARE_ADMIN_CLIENT_SECRET=your_client_secret
13
+
14
+ NUXT_PUBLIC_SHOP_BITE_FEATURE_MULTI_CHANNEL=false
15
+
10
16
  OPENAPI_ACCESS_KEY=key
11
17
  OPENAPI_JSON_URL="https://shopware.shopbite.net"
12
18
 
@@ -0,0 +1,15 @@
1
+ <script setup lang="ts">
2
+ const { navi } = useHeaderNavigation();
3
+ </script>
4
+
5
+ <template>
6
+ <UNavigationMenu
7
+ color="primary"
8
+ :items="navi"
9
+ orientation="vertical"
10
+ class="-mx-2.5"
11
+ />
12
+ <div class="my-4">
13
+ <SalesChannelSwitch />
14
+ </div>
15
+ </template>
@@ -0,0 +1,126 @@
1
+ <script setup lang="ts">
2
+ import { useUser } from "@shopware/composables";
3
+ import type { DropdownMenuItem } from "@nuxt/ui";
4
+
5
+ const cartQuickViewOpen = ref(false);
6
+ const { count } = useCart();
7
+ const { isCheckoutEnabled } = useShopBiteConfig();
8
+ const { isLoggedIn, isGuestSession, user, logout } = useUser();
9
+ const toast = useToast();
10
+
11
+ const logoutHandler = () => {
12
+ logout();
13
+ toast.add({
14
+ title: "Tschüss!",
15
+ description: "Erfolreich abgemeldet.",
16
+ color: "success",
17
+ });
18
+ };
19
+
20
+ const { data: navigationData } = await useAsyncData("header-navigation", () =>
21
+ queryCollection("navigation").first(),
22
+ );
23
+
24
+ const accountHoverText = computed(() => {
25
+ return isLoggedIn.value || isGuestSession.value
26
+ ? `${user.value?.firstName} ${user.value?.lastName}`
27
+ : "Hallo";
28
+ });
29
+
30
+ const loggedInDropDown = computed<DropdownMenuItem[][]>(() => {
31
+ if (!navigationData.value?.account.loggedIn) return [];
32
+
33
+ return navigationData.value.account.loggedIn
34
+ .map((group) =>
35
+ group
36
+ .filter((item) => {
37
+ if (isGuestSession.value) {
38
+ return item.type === "label" || item.action === "logout";
39
+ }
40
+ return true;
41
+ })
42
+ .map((item) => ({
43
+ label: item.type === "label" ? accountHoverText.value : item.label,
44
+ type: item.type,
45
+ icon: item.icon,
46
+ to: item.to,
47
+ onSelect: item.action === "logout" ? logoutHandler : undefined,
48
+ })),
49
+ )
50
+ .filter((group) => group.length > 0);
51
+ });
52
+
53
+ const loggedOutDropDown = computed<DropdownMenuItem[][]>(() => {
54
+ if (!navigationData.value?.account.loggedOut) return [];
55
+
56
+ return navigationData.value.account.loggedOut.map((group) =>
57
+ group.map((item) => ({
58
+ label: item.label,
59
+ type: item.type,
60
+ icon: item.icon,
61
+ to: item.to,
62
+ })),
63
+ );
64
+ });
65
+ </script>
66
+
67
+ <template>
68
+ <UButton
69
+ color="neutral"
70
+ variant="ghost"
71
+ to="tel:+49610471427"
72
+ target="_blank"
73
+ icon="i-lucide-phone"
74
+ aria-label="Anrufen"
75
+ />
76
+ <UDropdownMenu
77
+ :items="isLoggedIn || isGuestSession ? loggedInDropDown : loggedOutDropDown"
78
+ >
79
+ <UChip v-if="isLoggedIn || isGuestSession" size="3xl" text="✓">
80
+ <UButton
81
+ aria-label="Konto Dropdown öffnen"
82
+ icon="i-lucide-user"
83
+ color="neutral"
84
+ variant="outline"
85
+ />
86
+ </UChip>
87
+ <UButton
88
+ v-else
89
+ aria-label="Konto Dropdown öffnen"
90
+ icon="i-lucide-user"
91
+ color="neutral"
92
+ variant="outline"
93
+ />
94
+ </UDropdownMenu>
95
+ <UDrawer
96
+ v-if="isCheckoutEnabled"
97
+ v-model:open="cartQuickViewOpen"
98
+ title="Warenkorb"
99
+ direction="right"
100
+ >
101
+ <UChip :text="count" size="3xl">
102
+ <UButton
103
+ aria-label="Zum Warenkorb"
104
+ color="neutral"
105
+ variant="outline"
106
+ icon="i-lucide-shopping-cart"
107
+ />
108
+ </UChip>
109
+
110
+ <template #header>
111
+ <h2 class="text-3xl md:text-4xl mt-8 mb-3 pb-2">
112
+ <UIcon name="i-lucide-shopping-cart" class="size-8" color="primary" />
113
+ Warenkorb
114
+ </h2>
115
+ </template>
116
+ <template #body>
117
+ <CartQuickView
118
+ :with-to-cart-button="true"
119
+ class="md:min-w-90"
120
+ @go-to-cart="cartQuickViewOpen = false"
121
+ />
122
+ </template>
123
+ </UDrawer>
124
+ </template>
125
+
126
+ <style scoped></style>
@@ -0,0 +1,16 @@
1
+ <script setup lang="ts">
2
+ const config = useRuntimeConfig();
3
+ const siteName = computed(() => config.public.site.name);
4
+ </script>
5
+
6
+ <template>
7
+ <NuxtLink to="/" class="-m-1.5 p-1.5">
8
+ <span class="sr-only">{{ siteName }}</span>
9
+ <UColorModeImage
10
+ alt="Logo"
11
+ light="/light/Logo.png"
12
+ dark="/dark/Logo.png"
13
+ class="h-12 w-auto"
14
+ />
15
+ </NuxtLink>
16
+ </template>
@@ -1,175 +1,25 @@
1
1
  <script setup lang="ts">
2
- import type { DropdownMenuItem, NavigationMenuItem } from "@nuxt/ui";
3
- import { useRoute } from "vue-router";
4
- import { useUser } from "@shopware/composables";
5
-
6
- const route = useRoute();
7
- const toast = useToast();
8
-
9
- const { isLoggedIn, isGuestSession, user, logout } = useUser();
10
- const { isCheckoutEnabled } = useShopBiteConfig();
11
- const { count } = useCart();
12
- const runtimeConfig = useRuntimeConfig();
13
-
14
- // Fetch navigation from content
15
- const { data: navigationData } = await useAsyncData("header-navigation", () =>
16
- queryCollection("navigation").first(),
17
- );
18
-
19
- const siteName = computed(() => runtimeConfig.public.site?.name ?? "ShopBite");
20
-
21
- const navi = computed<NavigationMenuItem[]>(() => {
22
- if (!navigationData.value?.main) return [];
23
-
24
- return navigationData.value.main.map((item) => ({
25
- label: item.label,
26
- icon: item.icon,
27
- to: item.to,
28
- target: item.target,
29
- active:
30
- item.to === "/"
31
- ? route.path.length === 1
32
- : route.path.startsWith(item.to),
33
- }));
34
- });
35
-
36
- const accountHoverText = computed(() => {
37
- return isLoggedIn.value || isGuestSession.value
38
- ? `${user.value?.firstName} ${user.value?.lastName}`
39
- : "Hallo";
40
- });
41
-
42
- const logoutHandler = () => {
43
- logout();
44
- toast.add({
45
- title: "Tschüss!",
46
- description: "Erfolreich abgemeldet.",
47
- color: "success",
48
- });
49
- };
50
-
51
- const loggedInDropDown = computed<DropdownMenuItem[][]>(() => {
52
- if (!navigationData.value?.account.loggedIn) return [];
53
-
54
- return navigationData.value.account.loggedIn
55
- .map((group) =>
56
- group
57
- .filter((item) => {
58
- if (isGuestSession.value) {
59
- return item.type === "label" || item.action === "logout";
60
- }
61
- return true;
62
- })
63
- .map((item) => ({
64
- label: item.type === "label" ? accountHoverText.value : item.label,
65
- type: item.type,
66
- icon: item.icon,
67
- to: item.to,
68
- onSelect: item.action === "logout" ? logoutHandler : undefined,
69
- })),
70
- )
71
- .filter((group) => group.length > 0);
72
- });
73
-
74
- const loggedOutDropDown = computed<DropdownMenuItem[][]>(() => {
75
- if (!navigationData.value?.account.loggedOut) return [];
76
-
77
- return navigationData.value.account.loggedOut.map((group) =>
78
- group.map((item) => ({
79
- label: item.label,
80
- type: item.type,
81
- icon: item.icon,
82
- to: item.to,
83
- })),
84
- );
85
- });
86
-
2
+ const { navi } = useHeaderNavigation();
87
3
  const loginSlide = ref(false);
88
- const cartQuickViewOpen = ref(false);
89
4
  </script>
90
5
 
91
6
  <template>
92
7
  <UHeader>
93
8
  <template #title>
94
- <NuxtLink to="/" class="-m-1.5 p-1.5">
95
- <span class="sr-only">{{ siteName }}</span>
96
- <UColorModeImage
97
- light="/light/Logo.png"
98
- dark="/dark/Logo.png"
99
- class="h-12 w-auto"
100
- />
101
- </NuxtLink>
9
+ <HeaderTitle />
102
10
  </template>
103
11
 
104
12
  <UNavigationMenu color="primary" variant="pill" :items="navi" />
105
13
 
106
14
  <template #right>
107
- <UButton
108
- color="neutral"
109
- variant="ghost"
110
- to="tel:+49610471427"
111
- target="_blank"
112
- icon="i-lucide-phone"
113
- aria-label="Anrufen"
114
- />
115
- <UDropdownMenu
116
- :items="
117
- isLoggedIn || isGuestSession ? loggedInDropDown : loggedOutDropDown
118
- "
119
- >
120
- <UChip v-if="isLoggedIn || isGuestSession" size="3xl" text="✓">
121
- <UButton icon="i-lucide-user" color="neutral" variant="outline" />
122
- </UChip>
123
- <UButton
124
- v-else
125
- icon="i-lucide-user"
126
- color="neutral"
127
- variant="outline"
128
- />
129
- </UDropdownMenu>
130
- <UDrawer
131
- v-if="isCheckoutEnabled"
132
- v-model:open="cartQuickViewOpen"
133
- title="Warenkorb"
134
- direction="right"
135
- >
136
- <UChip :text="count" size="3xl">
137
- <UButton
138
- color="neutral"
139
- variant="outline"
140
- icon="i-lucide-shopping-cart"
141
- />
142
- </UChip>
143
-
144
- <template #header>
145
- <h2 class="text-3xl md:text-4xl mt-8 mb-3 pb-2">
146
- <UIcon
147
- name="i-lucide-shopping-cart"
148
- class="size-8"
149
- color="primary"
150
- />
151
- Warenkorb
152
- </h2>
153
- </template>
154
- <template #body>
155
- <CartQuickView
156
- :with-to-cart-button="true"
157
- class="md:min-w-90"
158
- @go-to-cart="cartQuickViewOpen = false"
159
- />
160
- </template>
161
- </UDrawer>
15
+ <HeaderRight />
162
16
  </template>
163
17
 
164
18
  <template #body>
165
- <UNavigationMenu
166
- color="primary"
167
- :items="navi"
168
- orientation="vertical"
169
- class="-mx-2.5"
170
- />
19
+ <HeaderBody />
171
20
  </template>
172
21
  </UHeader>
22
+
173
23
  <USlideover
174
24
  v-model:open="loginSlide"
175
25
  title="Konto"
@@ -22,18 +22,30 @@ withDefaults(
22
22
  usps: () => [],
23
23
  },
24
24
  );
25
+
26
+ const videoRef = ref<HTMLVideoElement>();
27
+
28
+ onMounted(() => {
29
+ // Handle video playback on bfcache restore
30
+ window.addEventListener("pageshow", (event) => {
31
+ if (event.persisted && videoRef.value) {
32
+ videoRef.value.play();
33
+ }
34
+ });
35
+ });
25
36
  </script>
26
37
 
27
38
  <template>
28
39
  <div class="relative">
29
40
  <video
41
+ ref="videoRef"
30
42
  autoplay
31
43
  loop
32
44
  muted
33
45
  playsinline
34
46
  class="absolute inset-0 w-full h-full object-cover -z-10"
35
47
  >
36
- <source :src="backgroundVideo" type="video/mp4" >
48
+ <source fetchpriority="high" :src="backgroundVideo" type="video/mp4" >
37
49
  </video>
38
50
  <div class="bg-black/50 backdrop-blur-sm">
39
51
  <UPageHero
@@ -53,7 +65,7 @@ withDefaults(
53
65
  :key="index"
54
66
  as="button"
55
67
  :to="usp.link ?? ''"
56
- class="flex items-center text-left gap-2"
68
+ class="flex flex-col md:flex-row items-center text-center md:text-left gap-2"
57
69
  target="_blank"
58
70
  >
59
71
  <UIcon
@@ -27,7 +27,7 @@ defineProps<{
27
27
  >
28
28
  <template #body>
29
29
  <UCarousel
30
- v-slot="{ item }"
30
+ v-slot="{ item, index }"
31
31
  :items="images"
32
32
  class="mx-auto w-full"
33
33
  auto-height
@@ -35,6 +35,9 @@ defineProps<{
35
35
  loop
36
36
  >
37
37
  <img
38
+ v-if="item"
39
+ :loading="index === 0 ? 'eager' : 'lazy'"
40
+ :fetchpriority="index === 0 ? 'high' : 'low'"
38
41
  :src="item.image"
39
42
  :alt="item.alt"
40
43
  class="rounded-lg w-full max-h-screen object-contain"
@@ -0,0 +1,63 @@
1
+ <script setup lang="ts">
2
+ import type { SelectMenuItem } from "@nuxt/ui";
3
+ import type { Schemas } from "#shopware";
4
+
5
+ const config = useRuntimeConfig();
6
+ const isMultiChannel = computed(
7
+ () => !!config.public.shopBite.feature.multiChannel,
8
+ );
9
+ const storeUrl = computed(() => config.public.storeUrl);
10
+
11
+ const { data: salesChannels, status } = await useFetch(
12
+ "/api/shopware/sales-channels",
13
+ {
14
+ key: "sales-channels",
15
+ transform: (data: Schemas["SalesChannel"][]) => {
16
+ const currentUrl = storeUrl.value;
17
+ return data?.map((channel) => {
18
+ const domains = channel.domains || [];
19
+ const matchingDomain =
20
+ currentUrl != null
21
+ ? domains.find((domain) => domain?.url === currentUrl)
22
+ : undefined;
23
+ const fallbackDomain = domains.find((domain) => !!domain?.url);
24
+ const domainUrl = matchingDomain?.url ?? fallbackDomain?.url ?? "";
25
+ return {
26
+ label: channel.translated.name ?? channel.name,
27
+ value: domainUrl,
28
+ };
29
+ });
30
+ },
31
+ lazy: true,
32
+ },
33
+ );
34
+
35
+ const selectedStore = ref<SelectMenuItem>();
36
+
37
+ watchEffect(() => {
38
+ if (salesChannels.value && storeUrl.value && !selectedStore.value) {
39
+ const matchingChannel = salesChannels.value.find(
40
+ (channel) => channel.value === storeUrl.value,
41
+ );
42
+ if (matchingChannel) {
43
+ selectedStore.value = matchingChannel;
44
+ }
45
+ }
46
+ });
47
+
48
+ watch(selectedStore, (newStore, oldStore) => {
49
+ if (newStore && oldStore && newStore.value !== oldStore.value) {
50
+ window.location.href = newStore.value;
51
+ }
52
+ });
53
+ </script>
54
+
55
+ <template>
56
+ <USelectMenu
57
+ v-if="isMultiChannel"
58
+ v-model="selectedStore"
59
+ :items="salesChannels"
60
+ :loading="status === 'pending'"
61
+ icon="i-lucide-store"
62
+ />
63
+ </template>
@@ -0,0 +1,29 @@
1
+ import type { NavigationMenuItem } from "@nuxt/ui";
2
+
3
+ export const useHeaderNavigation = () => {
4
+ const route = useRoute();
5
+
6
+ const { data: navigationData } = useAsyncData("header-navigation", () =>
7
+ queryCollection("navigation").first(),
8
+ );
9
+
10
+ const navi = computed<NavigationMenuItem[]>(() => {
11
+ if (!navigationData.value?.main) return [];
12
+
13
+ return navigationData.value.main.map((item) => ({
14
+ label: item.label,
15
+ icon: item.icon,
16
+ to: item.to,
17
+ target: item.target,
18
+ active:
19
+ item.to === "/"
20
+ ? route.path.length === 1
21
+ : route.path.startsWith(item.to),
22
+ }));
23
+ });
24
+
25
+ return {
26
+ navigationData,
27
+ navi,
28
+ };
29
+ };
package/content/index.yml CHANGED
@@ -5,7 +5,7 @@ seo:
5
5
  title: ShopBite
6
6
  description: Reduziere Kosten und steigere deinen Umsatz
7
7
  hero:
8
- backgroundVideo: https://shopware.shopbite.de/media/10/59/96/1762465181/background.mp4
8
+ backgroundVideo: https://shopware.shopbite.de/media/10/59/96/1762465181/background.mp4?ts=1762465194
9
9
  headline: SHOPBITE
10
10
  usps:
11
11
  - title: 4.5 ⭐
@@ -68,20 +68,19 @@ gallery:
68
68
  variant: subtle
69
69
  trailingIcon: i-lucide-phone
70
70
  images:
71
- - image: https://shopware.shopbite.de/media/71/2a/1a/1762465670/restaurant1.webp
71
+ - image: https://shopware.shopbite.de/media/71/2a/1a/1762465670/restaurant1.webp?ts=1762465670
72
72
  alt: La Fattoria Restaurant Innenbereich 1
73
- - image: https://shopware.shopbite.de/media/61/ea/77/1762465670/restaurant2.webp
73
+ - image: https://shopware.shopbite.de/media/61/ea/77/1762465670/restaurant2.webp?ts=1762465670
74
74
  alt: La Fattoria Restaurant Innenbereich 2
75
- - image: https://shopware.shopbite.de/media/6d/ac/ca/1762465670/restaurant3.webp
75
+ - image: https://shopware.shopbite.de/media/6d/ac/ca/1762465670/restaurant3.webp?ts=1762465670
76
76
  alt: La Fattoria Restaurant Innenbereich 3
77
- - image: https://shopware.shopbite.de/media/af/d6/da/1762465670/restaurant4.webp
77
+ - image: https://shopware.shopbite.de/media/af/d6/da/1762465670/restaurant4.webp?ts=1762465670
78
78
  alt: La Fattoria Restaurant Innenbereich 4
79
- - image: https://shopware.shopbite.de/media/6a/ff/e2/1762465670/restaurant5.webp
79
+ - image: https://shopware.shopbite.de/media/6a/ff/e2/1762465670/restaurant5.webp?ts=1762465670
80
80
  alt: La Fattoria Restaurant Innenbereich 5
81
- - image: https://shopware.shopbite.de/media/80/76/d0/1762465670/restaurant6.webp
81
+ - image: https://shopware.shopbite.de/media/80/76/d0/1762465670/restaurant6.webp?ts=1762465670
82
82
  alt: La Fattoria Restaurant Innenbereich 6
83
- - image: https://nbg1.your-objectstorage.com/lafattoria-public/media/30/f7/76/1763383122/restaurant10.webp
84
- alt: La Fattoria Restaurant Innenbereich 10
83
+
85
84
  cta:
86
85
  title: Jetzt bestellen!
87
86
  description: Genieße die italienische Küche, frisch zubereitet und direkt zu dir geliefert oder vor Ort genießen.
@@ -1,11 +1,5 @@
1
1
  main:
2
- - label: Start
3
- icon: i-lucide-home
4
- to: /
5
2
  - label: Speisekarte
6
- icon: i-lucide-utensils
7
- to: /speisekarte
8
- - label: Speisekarte 2
9
3
  icon: i-lucide-utensils
10
4
  to: /c/Pizza/
11
5
  - label: Routenplaner
package/nuxt.config.ts CHANGED
@@ -39,10 +39,19 @@ export default defineNuxtConfig({
39
39
  },
40
40
 
41
41
  runtimeConfig: {
42
- shopware: {},
42
+ shopware: {
43
+ adminClientId: "",
44
+ adminClientSecret: "",
45
+ adminEndpoint: "",
46
+ },
43
47
  apiClientConfig: {},
44
48
  geoapifyApiKey: "",
45
49
  public: {
50
+ shopBite: {
51
+ feature: {
52
+ multiChannel: "",
53
+ },
54
+ },
46
55
  site: {
47
56
  name: "ShopBite",
48
57
  description: "Reduziere deine Kosten und steigere deinen Umsatz",
@@ -82,6 +91,7 @@ export default defineNuxtConfig({
82
91
  "@nuxt/ui",
83
92
  "@nuxt/scripts",
84
93
  "@nuxtjs/plausible",
94
+ "nuxt-vitalizer",
85
95
  ],
86
96
 
87
97
  plausible: {
@@ -92,6 +102,10 @@ export default defineNuxtConfig({
92
102
  experimental: { sqliteConnector: "native" },
93
103
  },
94
104
 
105
+ vitalizer: {
106
+ disablePrefetchLinks: true,
107
+ },
108
+
95
109
  pwa: {
96
110
  manifest: {
97
111
  name: "ShopBite",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopbite-de/storefront",
3
- "version": "1.6.2",
3
+ "version": "1.7.1",
4
4
  "main": "nuxt.config.ts",
5
5
  "description": "Shopware storefront for food delivery shops",
6
6
  "keywords": [
@@ -35,6 +35,7 @@
35
35
  "@vueuse/core": "^14.0.0",
36
36
  "dotenv": "^17.2.3",
37
37
  "nuxt": "^4.2.1",
38
+ "nuxt-vitalizer": "2.0.0",
38
39
  "uuid": "^13.0.0"
39
40
  },
40
41
  "devDependencies": {
@@ -0,0 +1,79 @@
1
+ import type { operations } from "@shopware/api-client/admin-api-types";
2
+ import { createAdminAPIClient } from "@shopware/api-client";
3
+
4
+ export default defineEventHandler(async () => {
5
+ const config = useRuntimeConfig();
6
+
7
+ // Get admin API credentials from runtime config
8
+ const adminEndpoint = config.shopware.adminEndpoint;
9
+ const adminClientId = config.shopware.adminClientId;
10
+ const adminClientSecret = config.shopware.adminClientSecret;
11
+
12
+ if (!adminEndpoint || !adminClientId || !adminClientSecret) {
13
+ throw createError({
14
+ statusCode: 500,
15
+ statusMessage: "Shopware admin API credentials not configured",
16
+ });
17
+ }
18
+
19
+ try {
20
+ const adminApiClient = createAdminAPIClient<operations>({
21
+ baseURL: adminEndpoint,
22
+ credentials: {
23
+ grant_type: "client_credentials",
24
+ client_id: adminClientId,
25
+ client_secret: adminClientSecret,
26
+ },
27
+ });
28
+
29
+ const data = await adminApiClient.invoke(
30
+ "searchSalesChannel post /search/sales-channel",
31
+ {
32
+ body: {
33
+ includes: {
34
+ sales_channel: ["id", "translated", "name", "domains", "active"],
35
+ sales_channel_domain: ["url"],
36
+ },
37
+ associations: {
38
+ domains: {},
39
+ },
40
+ filter: [
41
+ {
42
+ field: "active",
43
+ type: "equals",
44
+ value: true,
45
+ },
46
+ ],
47
+ },
48
+ },
49
+ );
50
+
51
+ // Validate response data
52
+ if (!data || !data.data) {
53
+ throw createError({
54
+ statusCode: 500,
55
+ statusMessage: "Invalid response from Shopware API",
56
+ });
57
+ }
58
+
59
+ return data.data.data;
60
+ } catch (error) {
61
+ // Handle specific error cases
62
+ if (error && typeof error === "object" && "statusCode" in error) {
63
+ // Re-throw createError instances
64
+ throw error;
65
+ }
66
+
67
+ // Log the error for debugging
68
+ console.error("Failed to fetch sales channels:", error);
69
+
70
+ // Return a user-friendly error
71
+ throw createError({
72
+ statusCode: 503,
73
+ statusMessage: "Failed to fetch sales channels from Shopware",
74
+ data: {
75
+ originalError: error instanceof Error ? error.message : String(error),
76
+ },
77
+ });
78
+ }
79
+ });