@shopbite-de/storefront 1.16.0 → 1.17.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.
Files changed (60) hide show
  1. package/.claude/settings.local.json +21 -0
  2. package/.env.example +0 -4
  3. package/.github/workflows/build.yaml +14 -8
  4. package/.github/workflows/ci.yaml +125 -42
  5. package/.nuxtrc +1 -1
  6. package/CLAUDE.md +106 -0
  7. package/app/app.vue +53 -34
  8. package/app/components/AddToWishlist.vue +0 -3
  9. package/app/components/Address/Form.vue +5 -2
  10. package/app/components/Category/Listing.vue +5 -4
  11. package/app/components/Checkout/DeliveryTimeSelect.vue +4 -2
  12. package/app/components/Contact/Form.vue +3 -2
  13. package/app/components/Hero.vue +1 -1
  14. package/app/components/ImageGallery.vue +1 -1
  15. package/app/components/Navigation/DesktopLeft.vue +2 -1
  16. package/app/components/Order/Detail.vue +2 -2
  17. package/app/components/Product/Card.vue +14 -8
  18. package/app/components/SalesChannelSwitch.vue +5 -3
  19. package/app/components/User/LoginForm.vue +1 -4
  20. package/app/components/User/RegistrationForm.vue +5 -2
  21. package/app/composables/useBusinessHours.ts +11 -4
  22. package/app/composables/useCategory.ts +1 -0
  23. package/app/composables/useTopSellers.ts +2 -2
  24. package/app/layouts/account.vue +0 -1
  25. package/app/pages/anmelden.vue +8 -4
  26. package/app/pages/c/[...all].vue +2 -1
  27. package/app/pages/konto/adressen.vue +4 -1
  28. package/app/pages/konto/bestellung/[id].vue +14 -2
  29. package/app/pages/konto/profil.vue +5 -1
  30. package/app/utils/formatDate.ts +2 -1
  31. package/content.config.ts +1 -2
  32. package/eslint.config.mjs +10 -6
  33. package/node.dockerfile +7 -6
  34. package/nuxt.config.ts +17 -3
  35. package/package.json +38 -32
  36. package/renovate.json +9 -1
  37. package/server/api/address/autocomplete.get.ts +3 -2
  38. package/test/e2e/simple-checkout-as-recurring-customer.test.ts +1 -1
  39. package/test/nuxt/AddressFields.test.ts +12 -3
  40. package/test/nuxt/ContactForm.test.ts +31 -11
  41. package/test/nuxt/HeaderRight.test.ts +15 -6
  42. package/test/nuxt/LoginForm.test.ts +41 -23
  43. package/test/nuxt/PaymentAndDelivery.test.ts +2 -2
  44. package/test/nuxt/RegistrationForm.test.ts +6 -3
  45. package/test/nuxt/registrationSchema.test.ts +8 -8
  46. package/test/nuxt/useAddToCart.test.ts +5 -4
  47. package/test/nuxt/useAddressAutocomplete.test.ts +2 -0
  48. package/test/nuxt/useBusinessHours.test.ts +5 -5
  49. package/test/nuxt/useDeliveryTime.test.ts +17 -9
  50. package/test/nuxt/useProductConfigurator.test.ts +6 -4
  51. package/test/nuxt/useProductVariants.test.ts +16 -10
  52. package/test/nuxt/useProductVariantsZwei.test.ts +6 -2
  53. package/test/nuxt/useScrollAnimation.test.ts +16 -13
  54. package/test/nuxt/useTopSellers.test.ts +2 -2
  55. package/test/nuxt/useWishlistActions.test.ts +4 -3
  56. package/test/unit/useCategorySeo.spec.ts +26 -12
  57. package/tsconfig.json +21 -1
  58. package/app/middleware/trailing-slash.global.ts +0 -19
  59. package/server/utils/shopware/adminApiClient.ts +0 -24
  60. package/test/unit/sales-channels.test.ts +0 -66
@@ -0,0 +1,21 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(find:*)",
5
+ "Bash(grep:*)",
6
+ "Bash(pnpm add:*)",
7
+ "Bash(pnpm nuxt:*)",
8
+ "Bash(ls /home/lirim/workspace/shopbite/storefront/.nuxt/eslint* 2>&1)",
9
+ "Bash(pnpm typecheck:*)",
10
+ "Bash(git stash:*)",
11
+ "Bash(ls node_modules/.bin/vue-tsc 2>&1 && node_modules/.bin/vue-tsc --version 2>&1)",
12
+ "Bash(cat /home/lirim/workspace/shopbite/storefront/node_modules/.pnpm/nuxt@4.2.2_*/node_modules/nuxt/package.json 2>/dev/null | python3 -c \"import json,sys; p=json.load\\(sys.stdin\\); deps={**p.get\\('dependencies',{}\\),**p.get\\('devDependencies',{}\\),**p.get\\('peerDependencies',{}\\)}; print\\({k:v for k,v in deps.items\\(\\) if 'tsc' in k or 'volar' in k or 'vue-tsc' in k}\\)\" 2>/dev/null | head -5)",
13
+ "Bash(find /home/lirim/workspace/shopbite/storefront/node_modules/.pnpm -path \"*/nuxt@4.2.2*/nuxt/package.json\" 2>/dev/null | head -2 | xargs -I{} python3 -c \"import json,sys; p=json.load\\(open\\('{}',\\)\\); all_deps={**p.get\\('dependencies',{}\\),**p.get\\('devDependencies',{}\\),**p.get\\('optionalDependencies',{}\\)}; [print\\(k,v\\) for k,v in all_deps.items\\(\\) if any\\(x in k for x in ['tsc','volar','typescript']\\)]\" 2>/dev/null)",
14
+ "Bash(pnpm run:*)",
15
+ "Bash(git log:*)",
16
+ "Bash(pnpm test:unit 2>&1 | tail -80)",
17
+ "Bash(pnpm test:unit 2>&1 | grep -E \"\\(FAIL|PASS|TypeError|ContextError|error\\)\" | head -30)",
18
+ "Bash(pnpm test:unit 2>&1 | tail -30)"
19
+ ]
20
+ }
21
+ }
package/.env.example CHANGED
@@ -7,10 +7,6 @@ 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
10
  NUXT_PUBLIC_SHOP_BITE_FEATURE_MULTI_CHANNEL=false
15
11
  NUXT_PUBLIC_SHOP_BITE_FEATURE_SECURE_KEY=
16
12
 
@@ -14,8 +14,11 @@ jobs:
14
14
  - name: Checkout repository
15
15
  uses: actions/checkout@v6
16
16
 
17
+ - name: Set up Docker Buildx
18
+ uses: docker/setup-buildx-action@v4
19
+
17
20
  - name: Log in to the Container registry
18
- uses: docker/login-action@v3
21
+ uses: docker/login-action@v4
19
22
  with:
20
23
  registry: ghcr.io
21
24
  username: ${{ github.actor }}
@@ -23,7 +26,7 @@ jobs:
23
26
 
24
27
  - name: Extract metadata (tags, labels) for app image
25
28
  id: meta-app
26
- uses: docker/metadata-action@v5
29
+ uses: docker/metadata-action@v6
27
30
  with:
28
31
  images: ghcr.io/${{ github.repository }}-app
29
32
  tags: |
@@ -35,7 +38,7 @@ jobs:
35
38
  type=raw,value=latest,enable=${{ github.event_name == 'release' }}
36
39
 
37
40
  - name: Build and push Docker app image
38
- uses: docker/build-push-action@v6
41
+ uses: docker/build-push-action@v7
39
42
  with:
40
43
  context: .
41
44
  file: ./node.dockerfile
@@ -46,6 +49,8 @@ jobs:
46
49
  push: true
47
50
  tags: ${{ steps.meta-app.outputs.tags }}
48
51
  labels: ${{ steps.meta-app.outputs.labels }}
52
+ cache-from: type=gha
53
+ cache-to: type=gha,mode=max
49
54
 
50
55
  release-npm:
51
56
  runs-on: ubuntu-latest
@@ -57,17 +62,18 @@ jobs:
57
62
  - name: Checkout repository
58
63
  uses: actions/checkout@v6
59
64
 
65
+ - name: Install pnpm
66
+ uses: pnpm/action-setup@v4
67
+ with:
68
+ version: 10.32.1
69
+
60
70
  - name: Install Node.js
61
71
  uses: actions/setup-node@v6
62
72
  with:
63
73
  node-version: '24'
74
+ cache: 'pnpm'
64
75
  registry-url: 'https://registry.npmjs.org'
65
76
 
66
- - name: Install pnpm
67
- uses: pnpm/action-setup@v4
68
- with:
69
- version: 10.30.1
70
-
71
77
  - name: Install dependencies
72
78
  run: pnpm install --frozen-lockfile
73
79
 
@@ -4,56 +4,39 @@ on:
4
4
  branches: ["main", "dev"]
5
5
  pull_request:
6
6
  types: [opened, synchronize]
7
+
8
+ env:
9
+ NUXT_PUBLIC_APP_ENV: 'test'
10
+ NUXT_PUBLIC_STORE_URL: 'http://localhost:3005'
11
+ NUXT_PUBLIC_SHOPWARE_DEV_STORE_FRONT_URL: 'http://localhost:3005'
12
+ PORT: '3005'
13
+ NUXT_PUBLIC_SHOPWARE_ENDPOINT: ${{ secrets.NUXT_PUBLIC_SHOPWARE_ENDPOINT }}
14
+ NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN: ${{ secrets.NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN }}
15
+ NUXT_PUBLIC_SITE_COUNTRY_ID: ${{ secrets.NUXT_PUBLIC_SITE_COUNTRY_ID }}
16
+ GEOPIFY: ${{ secrets.GEOPIFY }}
17
+
7
18
  jobs:
8
- test:
9
- name: Run unit and e2e tests
10
- environment: test
11
- env:
12
- NUXT_PUBLIC_APP_ENV: 'test'
13
- NUXT_PUBLIC_STORE_URL: 'http://localhost:3005'
14
- NUXT_PUBLIC_SHOPWARE_DEV_STORE_FRONT_URL: 'http://localhost:3005'
15
- PORT: '3005'
16
- NUXT_PUBLIC_SHOPWARE_ENDPOINT: ${{ secrets.NUXT_PUBLIC_SHOPWARE_ENDPOINT }}
17
- NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN: ${{ secrets.NUXT_PUBLIC_SHOPWARE_ACCESS_TOKEN }}
18
- NUXT_PUBLIC_SITE_COUNTRY_ID: ${{ secrets.NUXT_PUBLIC_SITE_COUNTRY_ID }}
19
- GEOPIFY: ${{ secrets.GEOPIFY }}
20
- TEST_USER: ${{ secrets.TEST_USER }}
21
- TEST_USER_PASS: ${{ secrets.TEST_USER_PASS }}
19
+ setup:
20
+ name: Install & Build
22
21
  if: github.event.pull_request.draft == false
23
- timeout-minutes: 15
24
22
  runs-on: ubuntu-latest
25
23
  steps:
26
- - name: Check out code
27
- uses: actions/checkout@v6
24
+ - uses: actions/checkout@v6
28
25
 
29
- - name: Fetch main
30
- run: git fetch origin main
26
+ - uses: pnpm/action-setup@v4
27
+ with:
28
+ version: 10.32.1
31
29
 
32
- - name: Install Node.js
33
- uses: actions/setup-node@v6
30
+ - uses: actions/setup-node@v6
34
31
  with:
35
32
  node-version: '24'
36
-
37
- - name: Enable corepack and pnpm
38
- run: |
39
- corepack enable
40
- corepack prepare pnpm@10.26.2 --activate
33
+ cache: 'pnpm'
41
34
 
42
35
  - name: Install build dependencies
43
36
  run: |
44
37
  sudo apt-get update
45
38
  sudo apt-get install -y build-essential python3
46
39
 
47
- - name: Cache pnpm dependencies
48
- uses: actions/cache@v5
49
- with:
50
- path: |
51
- /home/runner/.local/share/pnpm/store/v3
52
- ./node_modules
53
- key: ${{ runner.os }}-pnpm-${{ inputs.workdir }}-${{ hashFiles('**/pnpm-lock.yaml') }}
54
- restore-keys: |
55
- ${{ runner.os }}-pnpm-
56
-
57
40
  - name: Install dependencies
58
41
  run: pnpm install --frozen-lockfile --prefer-offline
59
42
 
@@ -65,24 +48,124 @@ jobs:
65
48
  NODE_OPTIONS: "--max_old_space_size=4096"
66
49
  run: pnpm run build
67
50
 
68
- - name: Install Playwright Browsers
69
- run: pnpm exec playwright install --with-deps chromium
51
+ - name: Save workspace to cache
52
+ uses: actions/cache/save@v5
53
+ with:
54
+ path: |
55
+ ~/.local/share/pnpm/store
56
+ node_modules
57
+ .nuxt
58
+ .output
59
+ key: workspace-${{ runner.os }}-${{ github.sha }}
60
+
61
+ lint:
62
+ name: Lint & format
63
+ environment: test
64
+ needs: [setup]
65
+ timeout-minutes: 5
66
+ runs-on: ubuntu-latest
67
+ steps:
68
+ - uses: actions/checkout@v6
69
+
70
+ - uses: pnpm/action-setup@v4
71
+ with:
72
+ version: 10.32.1
73
+
74
+ - uses: actions/setup-node@v6
75
+ with:
76
+ node-version: '24'
77
+
78
+ - name: Restore workspace from cache
79
+ uses: actions/cache/restore@v5
80
+ with:
81
+ path: |
82
+ ~/.local/share/pnpm/store
83
+ node_modules
84
+ .nuxt
85
+ .output
86
+ key: workspace-${{ runner.os }}-${{ github.sha }}
87
+
88
+ - name: Prettier
89
+ run: pnpm prettier
90
+
91
+ - name: ESLint
92
+ run: pnpm eslint
93
+
94
+ unit-test:
95
+ name: Unit tests
96
+ environment: test
97
+ needs: [setup]
98
+ timeout-minutes: 10
99
+ runs-on: ubuntu-latest
100
+ steps:
101
+ - uses: actions/checkout@v6
102
+
103
+ - uses: pnpm/action-setup@v4
104
+ with:
105
+ version: 10.32.1
106
+
107
+ - uses: actions/setup-node@v6
108
+ with:
109
+ node-version: '24'
110
+
111
+ - name: Restore workspace from cache
112
+ uses: actions/cache/restore@v5
113
+ with:
114
+ path: |
115
+ ~/.local/share/pnpm/store
116
+ node_modules
117
+ .nuxt
118
+ .output
119
+ key: workspace-${{ runner.os }}-${{ github.sha }}
70
120
 
71
121
  - name: Unit tests
72
122
  run: pnpm test:unit
73
123
 
124
+ e2e-test:
125
+ name: E2E tests
126
+ environment: test
127
+ needs: [setup]
128
+ timeout-minutes: 15
129
+ runs-on: ubuntu-latest
130
+ steps:
131
+ - uses: actions/checkout@v6
132
+
133
+ - uses: pnpm/action-setup@v4
134
+ with:
135
+ version: 10.32.1
136
+
137
+ - uses: actions/setup-node@v6
138
+ with:
139
+ node-version: '24'
140
+
141
+ - name: Restore workspace from cache
142
+ uses: actions/cache/restore@v5
143
+ with:
144
+ path: |
145
+ ~/.local/share/pnpm/store
146
+ node_modules
147
+ .nuxt
148
+ .output
149
+ key: workspace-${{ runner.os }}-${{ github.sha }}
150
+
151
+ - name: Cache Playwright browsers
152
+ uses: actions/cache@v5
153
+ with:
154
+ path: ~/.cache/ms-playwright
155
+ key: playwright-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
156
+
157
+ - name: Install Playwright browsers
158
+ run: pnpm exec playwright install --with-deps chromium
159
+
74
160
  - name: E2E tests
75
161
  env:
76
162
  TEST_USER: ${{ secrets.TEST_USER }}
77
163
  TEST_USER_PASS: ${{ secrets.TEST_USER_PASS }}
78
-
79
164
  run: pnpm test:e2e
80
165
 
81
- - uses: actions/upload-artifact@v6
166
+ - uses: actions/upload-artifact@v7
82
167
  if: ${{ !cancelled() }}
83
168
  with:
84
169
  name: playwright-report
85
170
  path: playwright-report/
86
171
  retention-days: 30
87
-
88
-
package/.nuxtrc CHANGED
@@ -1 +1 @@
1
- setups.@nuxt/test-utils="3.23.0"
1
+ setups.@nuxt/test-utils="4.0.0"
package/CLAUDE.md ADDED
@@ -0,0 +1,106 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ # Development
9
+ pnpm dev # Start dev server
10
+ pnpm build # Production build
11
+ pnpm generate # Static generation
12
+ pnpm preview # Preview production build
13
+
14
+ # Type checking
15
+ pnpm typecheck # nuxt typecheck (uses project-local vue-tsc, not npx)
16
+
17
+ # Linting & formatting
18
+ pnpm lint:fix # Run prettier:fix + eslint:fix
19
+ pnpm eslint # Check ESLint
20
+ pnpm prettier # Check Prettier
21
+
22
+ # Testing
23
+ pnpm test:unit # Vitest (unit + nuxt environments, with coverage)
24
+ pnpm test:e2E # Playwright e2e (Chromium, requires .env.test)
25
+
26
+ # Shopware API types
27
+ pnpm generate-types # Regenerate api-types/storeApiTypes.d.ts from schema
28
+ pnpm load-schema # Reload Shopware API schema
29
+ ```
30
+
31
+ ### Running a single test
32
+
33
+ ```bash
34
+ # Vitest – filter by test name or file path
35
+ pnpm vitest run test/nuxt/useBusinessHours.test.ts
36
+ pnpm vitest run --reporter=verbose -t "should return true"
37
+
38
+ # Playwright – filter by file
39
+ pnpm playwright test test/e2e/checkout.spec.ts
40
+ ```
41
+
42
+ ### Typecheck note
43
+
44
+ Always run `nuxt typecheck` via the `pnpm typecheck` script, not via `npx vue-tsc`. The global npx-cached `vue-tsc` version may not be compatible with the project's installed `vue-router`. If `.nuxt/tsconfig.json` has stale pnpm paths after a dependency update, run `pnpm nuxt prepare` to regenerate it.
45
+
46
+ ## Architecture
47
+
48
+ ### Stack
49
+
50
+ - **Nuxt 4** with `app/` directory convention (not `src/`)
51
+ - **Shopware 6** headless commerce backend via `@shopware/nuxt-module`, `@shopware/composables` (extended as a Nuxt layer), and `@shopware/api-client`
52
+ - **Tailwind CSS v4** + **Nuxt UI** component library
53
+ - **Zod** for form validation schemas (`app/validation/`)
54
+ - **PWA** via `@vite-pwa/nuxt` (strategy configurable via `SW` env var)
55
+ - German-language URL routes (`/konto/`, `/warenkorb/`, etc.)
56
+
57
+ ### Shopware type system
58
+
59
+ Custom Shopware Store API types are auto-generated into `api-types/storeApiTypes.d.ts`. The module augmentation in `shopware.d.ts` wires them into the `#shopware` virtual module:
60
+
61
+ ```ts
62
+ declare module "#shopware" {
63
+ export type operations = import("./api-types/storeApiTypes").operations;
64
+ export type Schemas =
65
+ import("./api-types/storeApiTypes").components["schemas"];
66
+ }
67
+ ```
68
+
69
+ Custom ShopBite plugin endpoints are prefixed `shopbite.*` in the operations type (e.g. `shopbite.business-hour.get`, `shopbite.config.get`). Run `pnpm generate-types` after Shopware plugin changes.
70
+
71
+ ### Composables layer
72
+
73
+ `@shopware/composables` is consumed as a **Nuxt layer** (via `extends` in `nuxt.config.ts`). This means its composables, components, and pages are auto-imported alongside the project's own `app/composables/`. Project-level composables override the layer.
74
+
75
+ Key custom composables:
76
+
77
+ - **`useBusinessHours`** – opening hours with multi-interval support; Sunday is day 7 (not 0)
78
+ - **`useDeliveryTime`** – validates slots in 5-minute increments, accounts for `deliveryMinutes` (default 30), handles holidays
79
+ - **`useAddToCart`** – products with extras/toppings use UUID v5 (product ID + sorted extras) as container item reference; simple products bypass the container
80
+ - **`useAddressAutocomplete`** – proxies Geoapify through `/api/address/autocomplete` to avoid exposing the API key client-side
81
+ - **`useShopBiteConfig`** – fetches delivery config and checkout state from the custom Shopware plugin
82
+
83
+ ### Server routes
84
+
85
+ `server/api/address/autocomplete.get.ts` — Geoapify proxy (keeps API key server-side).
86
+ `server/utils/shopware/adminApiClient.ts` — Admin API client for server-side Shopware operations requiring elevated credentials.
87
+
88
+ ### Testing setup
89
+
90
+ Two Vitest projects in `vitest.config.ts`:
91
+
92
+ - **`unit`** – `test/unit/`, Node environment, pure function tests
93
+ - **`nuxt`** – `test/nuxt/`, Nuxt environment via `@nuxt/test-utils`, for composables and components
94
+
95
+ E2E tests in `test/e2e/` use Playwright (Chromium only) and require `TEST_USER` / `TEST_USER_PASS` env vars. A local dev server must be running before the suite executes.
96
+
97
+ ### Route rendering strategy
98
+
99
+ Most pages are SSR. Exceptions defined in `nuxt.config.ts` route rules:
100
+
101
+ - `/wishlist` – client-side rendered (wishlist fetching deferred to client)
102
+ - `/registrierung/bestaetigen/**` – client-side rendered
103
+
104
+ ### CI
105
+
106
+ `.github/workflows/ci.yaml` runs four jobs in sequence: **setup** (install + build, cached) → **lint** → **unit tests** → **e2e tests**. The `build.yaml` workflow handles Docker image builds and NPM publishing on releases.
package/app/app.vue CHANGED
@@ -1,37 +1,6 @@
1
1
  <script setup lang="ts">
2
- import type { Schemas } from "#shopware";
3
2
  import type { Toast } from "#ui/composables/useToast";
4
3
 
5
- // Composables
6
- const toast = useToast();
7
- const appConfig = useAppConfig();
8
- const { apiClient } = useShopwareContext();
9
- const { refresh: refreshToppings } = useShopBiteConfig();
10
- const { refreshCart } = useCart();
11
- const { getWishlistProducts } = useWishlist();
12
-
13
- const {
14
- getNextOpeningTime,
15
- isStoreOpen,
16
- refresh: refreshBusinessHours,
17
- } = useBusinessHours();
18
- const { isClosedHoliday, refresh: refreshHolidays } = useHolidays();
19
-
20
- await Promise.all([refreshBusinessHours(), refreshHolidays()]);
21
-
22
- const sessionContextData = ref<Schemas["SalesChannelContext"]>();
23
-
24
- const contextResponse = await apiClient
25
- .invoke("readContext get /context")
26
- .catch((error) => {
27
- console.error("Error fetching session context data:", error);
28
- return { data: undefined };
29
- });
30
-
31
- sessionContextData.value = contextResponse.data;
32
- useSessionContext(sessionContextData.value);
33
-
34
- // Toast configuration
35
4
  const TOAST_CONFIG = {
36
5
  open: {
37
6
  id: "currently-open",
@@ -59,6 +28,41 @@ const TOAST_CONFIG = {
59
28
  } as Partial<Toast>,
60
29
  } as const;
61
30
 
31
+ const { apiClient } = useShopwareContext();
32
+ const appConfig = useAppConfig();
33
+ const router = useRouter();
34
+ const toast = useToast();
35
+
36
+ const { data: sessionContextData } = await useAsyncData(
37
+ "sessionContext",
38
+ async () => {
39
+ try {
40
+ const { data } = await apiClient.invoke("readContext get /context");
41
+ return data;
42
+ } catch (error) {
43
+ console.error("Failed to load session context", error);
44
+ return null;
45
+ }
46
+ },
47
+ {
48
+ default: () => null,
49
+ },
50
+ );
51
+
52
+ if (sessionContextData.value) {
53
+ usePrice({
54
+ currencyCode: sessionContextData.value.currency?.isoCode || "",
55
+ });
56
+ useSessionContext(sessionContextData.value);
57
+ }
58
+
59
+ const {
60
+ getNextOpeningTime,
61
+ isStoreOpen,
62
+ refresh: refreshBusinessHours,
63
+ } = useBusinessHours();
64
+ const { isClosedHoliday, refresh: refreshHolidays } = useHolidays();
65
+
62
66
  function displayStoreStatus() {
63
67
  const isOpen = isStoreOpen(undefined, isClosedHoliday);
64
68
 
@@ -75,10 +79,25 @@ function displayStoreStatus() {
75
79
  }
76
80
  }
77
81
 
78
- // Lifecycle
82
+ const { refreshCart } = useCart();
83
+ const { getWishlistProducts } = useWishlist();
84
+
85
+ if (import.meta.client) {
86
+ // getting the wishlist products should not block SSR
87
+ if (!(router.currentRoute.value.name as string).includes("wishlist")) {
88
+ getWishlistProducts(); // initial page loading
89
+ }
90
+ }
91
+
92
+ const { refresh: refreshToppings } = useShopBiteConfig();
93
+
79
94
  onMounted(async () => {
80
- await refreshToppings();
81
- await Promise.all([refreshCart(), getWishlistProducts()]);
95
+ await Promise.all([
96
+ refreshHolidays(),
97
+ refreshBusinessHours(),
98
+ refreshToppings(),
99
+ ]);
100
+ refreshCart();
82
101
  displayStoreStatus();
83
102
  });
84
103
 
@@ -8,17 +8,14 @@ const props = defineProps<{
8
8
  const { addToWishlist, isInWishlist, removeFromWishlist } = useProductWishlist(
9
9
  props.product.id,
10
10
  );
11
- const { getWishlistProducts } = useWishlist();
12
11
  const { trackAddToWishlist } = useTrackEvent();
13
12
 
14
13
  const toggleWishlistProduct = async () => {
15
14
  try {
16
15
  if (isInWishlist.value) {
17
16
  await removeFromWishlist();
18
- await getWishlistProducts();
19
17
  } else {
20
18
  await addToWishlist();
21
- await getWishlistProducts();
22
19
  trackAddToWishlist(props.product);
23
20
  }
24
21
  } catch (error) {
@@ -34,10 +34,13 @@ const toast = useToast();
34
34
  async function onSubmit(event: FormSubmitEvent<AddressSchema>) {
35
35
  try {
36
36
  let response: undefined | Schemas["CustomerAddress"] = undefined;
37
+ const addressData = event.data as unknown as Parameters<
38
+ typeof updateCustomerAddress
39
+ >[0];
37
40
  if (event.data.id) {
38
- response = await updateCustomerAddress(event.data);
41
+ response = await updateCustomerAddress(addressData);
39
42
  } else {
40
- response = await createCustomerAddress(event.data);
43
+ response = await createCustomerAddress(addressData);
41
44
  }
42
45
 
43
46
  toast.add({
@@ -64,10 +64,11 @@ useCategorySeo(category);
64
64
 
65
65
  const currentSorting = ref(getCurrentSortingOrder.value ?? "Sortieren");
66
66
 
67
- const propertyFilters = computed<Schemas["PropertyGroup"][]>(() =>
68
- getAvailableFilters.value?.filter(
69
- (availableFilter) => availableFilter.code === "properties",
70
- ),
67
+ const propertyFilters = computed<Schemas["PropertyGroup"][]>(
68
+ () =>
69
+ (getAvailableFilters.value?.filter(
70
+ (availableFilter) => availableFilter.code === "properties",
71
+ ) ?? []) as unknown as Schemas["PropertyGroup"][],
71
72
  );
72
73
 
73
74
  const selectedPropertyFilters = ref(getCurrentFilters.value?.properties ?? []);
@@ -79,7 +79,9 @@ watch(
79
79
 
80
80
  function roundToNext5MinInterval(timeString: string | null): string {
81
81
  if (!timeString) return "";
82
- const [hours, minutes] = timeString.split(":").map(Number);
82
+ const parts = timeString.split(":").map(Number);
83
+ const hours = parts[0] ?? 0;
84
+ const minutes = parts[1] ?? 0;
83
85
  const totalMinutes = hours * 60 + minutes;
84
86
  const roundedMinutes = Math.ceil(totalMinutes / 5) * 5;
85
87
  const newHours = Math.floor(roundedMinutes / 60) % 24;
@@ -145,7 +147,7 @@ function handleTimeInput(event: Event): void {
145
147
  step="300"
146
148
  class="border rounded px-2 py-1"
147
149
  @input="handleTimeInput"
148
- >
150
+ />
149
151
  </client-only>
150
152
  </div>
151
153
  <p v-if="validationError" class="text-sm text-red-600">
@@ -69,9 +69,10 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
69
69
  },
70
70
  );
71
71
 
72
+ const resultData = result?.data as Record<string, unknown> | undefined;
72
73
  const msg =
73
- typeof result?.data?.individualSuccessMessage === "string"
74
- ? result.data.individualSuccessMessage.trim()
74
+ typeof resultData?.individualSuccessMessage === "string"
75
+ ? (resultData.individualSuccessMessage as string).trim()
75
76
  : "";
76
77
  successMessage.value =
77
78
  msg && msg.length > 0
@@ -46,7 +46,7 @@ onMounted(() => {
46
46
  fetchpriority="high"
47
47
  class="absolute inset-0 w-full h-full object-cover -z-10"
48
48
  >
49
- <source :src="backgroundVideo" type="video/mp4" >
49
+ <source :src="backgroundVideo" type="video/mp4" />
50
50
  </video>
51
51
  <div class="bg-black/50 backdrop-blur-sm">
52
52
  <UPageHero
@@ -37,7 +37,7 @@ defineProps<{
37
37
  :alt="item.alt"
38
38
  :fetchpriority="index === 0 ? 'high' : 'auto'"
39
39
  class="rounded-lg"
40
- >
40
+ />
41
41
  </UCarousel>
42
42
  </template>
43
43
  </UPageSection>
@@ -15,7 +15,8 @@ const mapCategoryToNavItem = (
15
15
  description: `${label} Kategorie`,
16
16
  to: category.seoUrl,
17
17
  defaultOpen: true,
18
- icon: category.customFields?.shopbite_category_icon,
18
+ icon: (category.customFields as Record<string, unknown> | undefined)
19
+ ?.shopbite_category_icon as string | undefined,
19
20
  children: (category.children ?? []).map(mapCategoryToNavItem),
20
21
  };
21
22
  };
@@ -52,7 +52,7 @@ onMounted(() => {
52
52
  });
53
53
 
54
54
  const columnRows = computed(() => {
55
- return order.value?.lineItems.filter(
55
+ return order.value?.lineItems?.filter(
56
56
  (lineItem: Schemas["OrderLineItem"]) => lineItem.parentId === null,
57
57
  );
58
58
  });
@@ -65,7 +65,7 @@ const columnRows = computed(() => {
65
65
  >Status: {{ status }}</UBadge
66
66
  >
67
67
  <UBadge variant="outline" color="neutral" size="xl"
68
- >Versandart: {{ order?.deliveries[0].shippingMethod.name }}</UBadge
68
+ >Versandart: {{ order?.deliveries?.[0]?.shippingMethod?.name }}</UBadge
69
69
  >
70
70
  </div>
71
71
  <UTable