@shopbite-de/storefront 1.1.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 (136) hide show
  1. package/.dockerignore +28 -0
  2. package/.env.example +11 -0
  3. package/.github/workflows/build.yaml +48 -0
  4. package/.github/workflows/ci.yaml +102 -0
  5. package/.prettierignore +6 -0
  6. package/.prettierrc +1 -0
  7. package/api-types/storeApiSchema.json +13863 -0
  8. package/api-types/storeApiTypes.d.ts +7010 -0
  9. package/app/app.config.ts +18 -0
  10. package/app/app.vue +99 -0
  11. package/app/assets/css/main.css +60 -0
  12. package/app/assets/fonts/Courier_Prime/CourierPrime-Bold.ttf +0 -0
  13. package/app/assets/fonts/Courier_Prime/CourierPrime-BoldItalic.ttf +0 -0
  14. package/app/assets/fonts/Courier_Prime/CourierPrime-Italic.ttf +0 -0
  15. package/app/assets/fonts/Courier_Prime/CourierPrime-Regular.ttf +0 -0
  16. package/app/assets/fonts/Courier_Prime/OFL.txt +93 -0
  17. package/app/assets/fonts/Kalam/Kalam-Bold.ttf +0 -0
  18. package/app/assets/fonts/Kalam/Kalam-Light.ttf +0 -0
  19. package/app/assets/fonts/Kalam/Kalam-Regular.ttf +0 -0
  20. package/app/assets/fonts/Kalam/OFL.txt +93 -0
  21. package/app/assets/fonts/Marcellus/Marcellus-Regular.ttf +0 -0
  22. package/app/assets/fonts/Marcellus/OFL.txt +93 -0
  23. package/app/assets/fonts/Sora/OFL.txt +93 -0
  24. package/app/assets/fonts/Sora/README.txt +70 -0
  25. package/app/assets/fonts/Sora/Sora-VariableFont_wght.ttf +0 -0
  26. package/app/assets/fonts/Sora/static/Sora-Bold.ttf +0 -0
  27. package/app/assets/fonts/Sora/static/Sora-ExtraBold.ttf +0 -0
  28. package/app/assets/fonts/Sora/static/Sora-ExtraLight.ttf +0 -0
  29. package/app/assets/fonts/Sora/static/Sora-Light.ttf +0 -0
  30. package/app/assets/fonts/Sora/static/Sora-Medium.ttf +0 -0
  31. package/app/assets/fonts/Sora/static/Sora-Regular.ttf +0 -0
  32. package/app/assets/fonts/Sora/static/Sora-SemiBold.ttf +0 -0
  33. package/app/assets/fonts/Sora/static/Sora-Thin.ttf +0 -0
  34. package/app/components/AddToWishlist.vue +55 -0
  35. package/app/components/Address/Card.vue +32 -0
  36. package/app/components/Address/Detail.vue +22 -0
  37. package/app/components/Address/Form.vue +117 -0
  38. package/app/components/AnimatedSection.vue +77 -0
  39. package/app/components/BottomNavi.vue +63 -0
  40. package/app/components/Cart/Item.vue +112 -0
  41. package/app/components/Cart/QuickView.vue +55 -0
  42. package/app/components/Category/Header.vue +53 -0
  43. package/app/components/Category/Listing.vue +295 -0
  44. package/app/components/Checkout/DeliveryTimeSelect.vue +177 -0
  45. package/app/components/Checkout/LoginOrRegister.vue +43 -0
  46. package/app/components/Checkout/PaymentAndDelivery.vue +101 -0
  47. package/app/components/Checkout/PaymentMethod.vue +30 -0
  48. package/app/components/Checkout/ShippingMethod.vue +30 -0
  49. package/app/components/Checkout/Summary.vue +125 -0
  50. package/app/components/Cta.vue +34 -0
  51. package/app/components/Features.vue +36 -0
  52. package/app/components/Food/Marquee.vue +35 -0
  53. package/app/components/Food/MarqueeItem.vue +72 -0
  54. package/app/components/Footer.vue +51 -0
  55. package/app/components/Header.vue +160 -0
  56. package/app/components/Hero.vue +77 -0
  57. package/app/components/ImageGallery.vue +46 -0
  58. package/app/components/Loading.vue +29 -0
  59. package/app/components/Navigation/DesktopLeft.vue +51 -0
  60. package/app/components/Navigation/DesktopLeft2.vue +43 -0
  61. package/app/components/Navigation/MobileTop.vue +59 -0
  62. package/app/components/Navigation/MobileTop2.vue +42 -0
  63. package/app/components/Order/Detail.vue +84 -0
  64. package/app/components/Product/Card.vue +132 -0
  65. package/app/components/Product/Category.vue +153 -0
  66. package/app/components/Product/Configurator.vue +65 -0
  67. package/app/components/Product/CrossSelling.vue +95 -0
  68. package/app/components/Product/DeselectIngredient.vue +46 -0
  69. package/app/components/Product/Detail.vue +187 -0
  70. package/app/components/Product/SearchBar.vue +109 -0
  71. package/app/components/PublicAnnouncement.vue +17 -0
  72. package/app/components/Topseller.vue +43 -0
  73. package/app/components/User/Detail.vue +47 -0
  74. package/app/components/User/LoginForm.vue +105 -0
  75. package/app/components/User/RegistrationForm.vue +340 -0
  76. package/app/components/Wishlist.vue +102 -0
  77. package/app/composables/useDeliveryTime.ts +139 -0
  78. package/app/composables/useInterval.ts +15 -0
  79. package/app/composables/usePizzaToppings.ts +31 -0
  80. package/app/composables/useProductEvents.test.ts +111 -0
  81. package/app/composables/useProductEvents.ts +22 -0
  82. package/app/composables/useProductVariants.ts +61 -0
  83. package/app/composables/useScrollAnimation.ts +39 -0
  84. package/app/composables/useTopSellers.ts +34 -0
  85. package/app/error.vue +30 -0
  86. package/app/layouts/account.vue +74 -0
  87. package/app/layouts/default.vue +6 -0
  88. package/app/layouts/listing.vue +32 -0
  89. package/app/layouts/listing2.vue +8 -0
  90. package/app/middleware/trailing-slash.global.ts +19 -0
  91. package/app/pages/account/recover/password/index.vue +143 -0
  92. package/app/pages/anmelden.vue +32 -0
  93. package/app/pages/bestellung.vue +103 -0
  94. package/app/pages/c/[...all].vue +49 -0
  95. package/app/pages/index.vue +59 -0
  96. package/app/pages/konto/adressen.vue +135 -0
  97. package/app/pages/konto/bestellung/[id].vue +41 -0
  98. package/app/pages/konto/bestellungen.vue +53 -0
  99. package/app/pages/konto/index.vue +74 -0
  100. package/app/pages/konto/profil.vue +160 -0
  101. package/app/pages/merkliste.vue +11 -0
  102. package/app/pages/order/[id].vue +69 -0
  103. package/app/pages/passwort-vergessen.vue +103 -0
  104. package/app/pages/registrierung/bestaetigen.vue +44 -0
  105. package/app/pages/registrierung/index.vue +24 -0
  106. package/app/pages/speisekarte.vue +58 -0
  107. package/app/pages/unternehmen/[slug].vue +66 -0
  108. package/app/types/Association.d.ts +11 -0
  109. package/app/utils/businessHours.ts +119 -0
  110. package/app/utils/formatDate.ts +9 -0
  111. package/app/utils/holidays.ts +43 -0
  112. package/app/utils/storeHours.ts +8 -0
  113. package/app/utils/time.ts +20 -0
  114. package/app/validation/addressSchema.ts +34 -0
  115. package/app/validation/registrationSchema.ts +156 -0
  116. package/bun.dockerfile +60 -0
  117. package/compose.yml +17 -0
  118. package/container +7 -0
  119. package/content/index.yml +91 -0
  120. package/content/navigation.yml +67 -0
  121. package/content/unternehmen/agb.md +1 -0
  122. package/content/unternehmen/datenschutz.md +1 -0
  123. package/content/unternehmen/impressum.md +39 -0
  124. package/content.config.ts +134 -0
  125. package/eslint.config.mjs +8 -0
  126. package/node.dockerfile +33 -0
  127. package/nuxt.config.ts +153 -0
  128. package/package.json +70 -0
  129. package/public/dark/Logo.svg +32 -0
  130. package/public/favicon.ico +0 -0
  131. package/public/light/Logo.svg +32 -0
  132. package/renovate.json +4 -0
  133. package/server/tsconfig.json +3 -0
  134. package/shopware.d.ts +19 -0
  135. package/tsconfig.json +4 -0
  136. package/vitest.config.mts +26 -0
@@ -0,0 +1,103 @@
1
+ <script setup lang="ts">
2
+ import LoginOrRegister from "~/components/Checkout/LoginOrRegister.vue";
3
+ import type { StepperItem } from "@nuxt/ui";
4
+
5
+ const { isLoggedIn, isGuestSession } = useUser();
6
+ const { isEmpty } = useCart();
7
+
8
+ const items = [
9
+ {
10
+ slot: "user" as const,
11
+ title: "Deine Daten",
12
+ icon: "i-lucide-user",
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[];
25
+
26
+ const activeStep = ref(0);
27
+
28
+ const isCustomerAvailable = computed<boolean>(() => {
29
+ return isLoggedIn.value || isGuestSession.value;
30
+ });
31
+ </script>
32
+
33
+ <template>
34
+ <UContainer>
35
+ <UPageHeader title="Bestellung aufgeben" />
36
+ <UPageBody>
37
+ <UStepper v-model="activeStep" :items="items">
38
+ <template #user>
39
+ <div class="flex flex-col md:flex-row w-full gap-18 my-16">
40
+ <LoginOrRegister
41
+ v-if="!isLoggedIn && !isGuestSession"
42
+ class="basis-1/2"
43
+ />
44
+ <div v-else class="basis-1/2">
45
+ <UserDetail />
46
+ <UButton
47
+ label="Daten überarbeiten"
48
+ variant="subtle"
49
+ block
50
+ icon="i-lucide-pen"
51
+ class="my-4"
52
+ to="/konto"
53
+ />
54
+ </div>
55
+ <div class="basis-1/2">
56
+ <h3 class="text-2xl mb-6 font-semibold">Warenkorb</h3>
57
+ <CartQuickView />
58
+ <UButton
59
+ v-if="!isEmpty && isCustomerAvailable"
60
+ :disabled="!isLoggedIn && !isGuestSession"
61
+ label="Zahlungs- und Versandart auswählen"
62
+ size="xl"
63
+ block
64
+ trailing-icon="i-lucide-arrow-right"
65
+ class="my-4"
66
+ @click="activeStep = 1"
67
+ />
68
+ <UButton
69
+ v-if="isEmpty"
70
+ label="Zur Speisekarte"
71
+ size="xl"
72
+ block
73
+ icon="i-lucide-arrow-left"
74
+ class="my-4"
75
+ to="/speisekarte"
76
+ />
77
+ </div>
78
+ </div>
79
+ </template>
80
+
81
+ <template #shipping>
82
+ <div class="my-14">
83
+ <CheckoutPaymentAndDelivery />
84
+ <div class="w-full flex justify-end">
85
+ <UButton
86
+ label="Weiter zu Prüfen & Bestellen"
87
+ trailing-icon="i-lucide-arrow-right"
88
+ class="m-8 md:max-w-96"
89
+ size="xl"
90
+ block
91
+ @click="activeStep = 2"
92
+ />
93
+ </div>
94
+ </div>
95
+ </template>
96
+
97
+ <template #checkout>
98
+ <CheckoutSummary />
99
+ </template>
100
+ </UStepper>
101
+ </UPageBody>
102
+ </UContainer>
103
+ </template>
@@ -0,0 +1,49 @@
1
+ <script setup lang="ts">
2
+ import type { Schemas } from "#shopware";
3
+
4
+ definePageMeta({
5
+ layout: "listing2",
6
+ });
7
+ const { clearBreadcrumbs } = useBreadcrumbs();
8
+ const { resolvePath } = useNavigationSearch();
9
+ const route = useRoute();
10
+ const routePath = route.path;
11
+
12
+ const { data: seoResult } = await useAsyncData(
13
+ `cmsResponse${routePath}`,
14
+ async () => {
15
+ // For client links if the history state contains seo url information we can omit the api call
16
+ if (import.meta.client) {
17
+ if (history.state?.routeName) {
18
+ return {
19
+ routeName: history.state?.routeName,
20
+ foreignKey: history.state?.foreignKey,
21
+ };
22
+ }
23
+ }
24
+ const seoUrl = await resolvePath(routePath);
25
+ return seoUrl;
26
+ },
27
+ );
28
+
29
+ if (!seoResult.value?.foreignKey) {
30
+ console.error("c/[...all].vue:", `No data found in API for ${routePath}`);
31
+
32
+ throw createError({
33
+ statusCode: 404,
34
+ statusMessage: `No data fetched from API for ${routePath}`,
35
+ });
36
+ }
37
+
38
+ const { foreignKey } = useNavigationContext(
39
+ seoResult as Ref<Schemas["SeoUrl"]>,
40
+ );
41
+
42
+ onBeforeRouteLeave(() => {
43
+ clearBreadcrumbs();
44
+ });
45
+ </script>
46
+
47
+ <template>
48
+ <CategoryListing :id="foreignKey" />
49
+ </template>
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ const { data: page } = await useAsyncData("index", () =>
3
+ queryCollection("home").first(),
4
+ );
5
+ if (!page.value) {
6
+ throw createError({
7
+ statusCode: 404,
8
+ statusMessage: "Page not found",
9
+ fatal: true,
10
+ });
11
+ }
12
+
13
+ useSeoMeta({
14
+ title: page.value.seo?.title || page.value.title,
15
+ ogTitle: page.value.seo?.title || page.value.title,
16
+ description: page.value.seo?.description || page.value.description,
17
+ ogDescription: page.value.seo?.description || page.value.description,
18
+ });
19
+ </script>
20
+ <template>
21
+ <div v-if="page" class="relative">
22
+ <Hero
23
+ :title="page.title"
24
+ :background-video="page.hero.backgroundVideo"
25
+ :description="page.description"
26
+ :headline="page.hero.headline"
27
+ :links="page.hero.links"
28
+ :usps="page.hero.usps"
29
+ />
30
+
31
+ <USeparator :ui="{ border: 'border-primary/30' }" />
32
+
33
+ <Features
34
+ :title="page.features.title"
35
+ :description="page.features.description"
36
+ :headline="page.features.headline"
37
+ :features="page.features.features"
38
+ />
39
+
40
+ <USeparator :ui="{ border: 'border-primary/30' }" />
41
+
42
+ <FoodMarquee
43
+ v-if="page.marquee.items?.length > 0"
44
+ :title="page.marquee.title"
45
+ :description="page.marquee.description"
46
+ :headline="page.marquee.headline"
47
+ :items="page.marquee.items"
48
+ />
49
+
50
+ <ImageGallery
51
+ :title="page.gallery.title"
52
+ :description="page.gallery.description"
53
+ :headline="page.gallery.headline"
54
+ :images="page.gallery.images"
55
+ :links="page.gallery.links"
56
+ />
57
+ <Cta />
58
+ </div>
59
+ </template>
@@ -0,0 +1,135 @@
1
+ <script setup lang="ts">
2
+ import { useAddress, useUser } from "@shopware/composables";
3
+
4
+ definePageMeta({
5
+ layout: "account",
6
+ });
7
+
8
+ const toast = useToast();
9
+ const { userDefaultShippingAddress, userDefaultBillingAddress, refreshUser } =
10
+ useUser();
11
+ const {
12
+ customerAddresses,
13
+ loadCustomerAddresses,
14
+ setDefaultCustomerBillingAddress,
15
+ setDefaultCustomerShippingAddress,
16
+ } = useAddress();
17
+
18
+ onMounted(async () => {
19
+ await loadCustomerAddresses();
20
+ });
21
+
22
+ const reloadCustomerData = async () => {
23
+ openEditModal.value = false;
24
+ openNewModal.value = false;
25
+ await loadCustomerAddresses();
26
+ await refreshUser();
27
+ };
28
+
29
+ async function makeDefaultShippingAddress(addressId: string) {
30
+ await setDefaultCustomerShippingAddress(addressId);
31
+ await reloadCustomerData();
32
+ toast.add({
33
+ title: "Standard Lieferadresse wurde geändert!",
34
+ color: "success",
35
+ });
36
+ }
37
+
38
+ async function makeDefaultBillingAddress(addressId: string) {
39
+ await setDefaultCustomerBillingAddress(addressId);
40
+ await reloadCustomerData();
41
+ toast.add({
42
+ title: "Standard Rechnungsadresse wurde geändert!",
43
+ color: "success",
44
+ });
45
+ }
46
+
47
+ const openEditModal = ref(false);
48
+ const openNewModal = ref(false);
49
+ </script>
50
+
51
+ <template>
52
+ <UContainer>
53
+ <UPageHeader
54
+ headline="KONTO"
55
+ title="Adressen"
56
+ description="Verwalte deine Adressen."
57
+ />
58
+ <UPageBody>
59
+ <div class="relative grid grid-cols-1 sm:grid-cols-2 gap-8">
60
+ <AddressCard
61
+ v-if="userDefaultShippingAddress"
62
+ :address="userDefaultShippingAddress"
63
+ title="Standard Lieferadresse"
64
+ icon="i-lucide-house"
65
+ />
66
+ <AddressCard
67
+ v-if="userDefaultBillingAddress"
68
+ :address="userDefaultBillingAddress"
69
+ title="Standard Rechnungsadresse"
70
+ icon="i-lucide-receipt-text"
71
+ />
72
+ </div>
73
+ <USeparator label="Alle Adressen" color="primary" />
74
+ <UPageGrid>
75
+ <div
76
+ v-for="address in customerAddresses ?? []"
77
+ :key="address.id"
78
+ class="space-y-4"
79
+ >
80
+ <AddressCard :address="address" />
81
+ <div class="flex justify-between">
82
+ <UModal v-model:open="openEditModal" title="Adresse bearbeiten">
83
+ <UButton
84
+ label="Bearbeiten"
85
+ variant="outline"
86
+ icon="i-lucide-pen"
87
+ />
88
+ <template #body>
89
+ <div class="p-8">
90
+ <AddressForm
91
+ :address="address"
92
+ @submit-success="reloadCustomerData"
93
+ />
94
+ </div>
95
+ </template>
96
+ </UModal>
97
+ <div class="flex gap-2">
98
+ <UTooltip
99
+ v-if="address.id !== userDefaultShippingAddress?.id"
100
+ text="Als Standard Lieferaddresse setzten"
101
+ >
102
+ <UButton
103
+ color="secondary"
104
+ variant="subtle"
105
+ icon="i-lucide-house"
106
+ @click="makeDefaultShippingAddress(address.id)"
107
+ />
108
+ </UTooltip>
109
+ <UTooltip
110
+ v-if="address.id !== userDefaultBillingAddress?.id"
111
+ text="Als Standard Rechnungsaddresse setzten"
112
+ >
113
+ <UButton
114
+ color="secondary"
115
+ variant="subtle"
116
+ icon="i-lucide-receipt-text"
117
+ @click="makeDefaultBillingAddress(address.id)"
118
+ />
119
+ </UTooltip>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </UPageGrid>
124
+ <UModal v-model:open="openNewModal" title="Neue Adresse erstellen">
125
+ <UButton label="Neue Adresse hinzufügen" icon="i-lucide-plus" />
126
+
127
+ <template #body>
128
+ <div class="p-8">
129
+ <AddressForm :address="{}" @submit-success="reloadCustomerData" />
130
+ </div>
131
+ </template>
132
+ </UModal>
133
+ </UPageBody>
134
+ </UContainer>
135
+ </template>
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import { useOrderDetails } from "@shopware/composables";
3
+ import { formatDate } from "~/utils/formatDate";
4
+
5
+ import type { RouteParams } from "vue-router";
6
+
7
+ definePageMeta({
8
+ layout: "account",
9
+ });
10
+
11
+ interface OrderRouteParams extends RouteParams {
12
+ id: string;
13
+ }
14
+
15
+ const route = useRoute();
16
+ const { id } = route.params as OrderRouteParams;
17
+ const { order, loadOrderDetails, status } = useOrderDetails(id);
18
+
19
+ const isLoadingData = ref(true);
20
+
21
+ onMounted(async () => {
22
+ isLoadingData.value = true;
23
+
24
+ await loadOrderDetails();
25
+
26
+ isLoadingData.value = false;
27
+ });
28
+ </script>
29
+
30
+ <template>
31
+ <UContainer>
32
+ <UPageHeader
33
+ headline="BESTELLUNG"
34
+ :title="order?.orderNumber"
35
+ :description="formatDate(order?.createdAt)"
36
+ />
37
+ <UPageBody>
38
+ <OrderDetail :order="order" :status="status ?? 'laden...'" />
39
+ </UPageBody>
40
+ </UContainer>
41
+ </template>
@@ -0,0 +1,53 @@
1
+ <script setup lang="ts">
2
+ import { formatDate } from "~/utils/formatDate";
3
+
4
+ definePageMeta({
5
+ layout: "account",
6
+ });
7
+
8
+ const { orders, loadOrders } = useCustomerOrders();
9
+
10
+ onMounted(() => {
11
+ loadOrders();
12
+ });
13
+
14
+ const { getFormattedPrice } = usePrice({
15
+ currencyCode: "EUR",
16
+ localeCode: "de-DE",
17
+ });
18
+ </script>
19
+
20
+ <template>
21
+ <UContainer>
22
+ <UPageHeader
23
+ headline="KONTO"
24
+ title="Meine Bestellungen"
25
+ description="Historie deiner Bestellungen."
26
+ />
27
+ <UPageBody>
28
+ <UPageList divide>
29
+ <UPageCard
30
+ v-for="order in orders"
31
+ :key="order.id"
32
+ variant="ghost"
33
+ :to="'/konto/bestellung/' + order.id"
34
+ :ui="{
35
+ root: 'shadow-md rounded-md',
36
+ footer: 'w-full',
37
+ }"
38
+ >
39
+ <template #default>
40
+ <div class="flex flex-row justify-between">
41
+ <div>Bestellung: {{ order.orderNumber }}</div>
42
+ <div>vom: {{ formatDate(order.createdAt) }}</div>
43
+ <div>
44
+ Gesamtbetrag: {{ getFormattedPrice(order.amountTotal) }}
45
+ </div>
46
+ <UIcon name="i-lucide-eye" class="size-6" />
47
+ </div>
48
+ </template>
49
+ </UPageCard>
50
+ </UPageList>
51
+ </UPageBody>
52
+ </UContainer>
53
+ </template>
@@ -0,0 +1,74 @@
1
+ <script setup lang="ts">
2
+ definePageMeta({
3
+ layout: "account",
4
+ });
5
+
6
+ const { user, userDefaultShippingAddress, userDefaultBillingAddress } =
7
+ useUser();
8
+ </script>
9
+
10
+ <template>
11
+ <UContainer>
12
+ <UPageHeader
13
+ headline="KONTO"
14
+ title="Übersicht"
15
+ description="Verwalte deine Nutzerdaten und Bestellungen."
16
+ />
17
+ <UPageBody>
18
+ <UPageGrid>
19
+ <div class="flex flex-col gap-2 w-full">
20
+ <UPageCard
21
+ class="h-full"
22
+ icon="i-lucide-user"
23
+ title="Persönliches Profil"
24
+ :ui="{
25
+ root: 'shadow-md rounded-md',
26
+ footer: 'w-full',
27
+ }"
28
+ >
29
+ <div class="h-full">
30
+ <p>{{ user?.firstName }} {{ user?.lastName }}</p>
31
+ <p>{{ user?.email }}</p>
32
+ </div>
33
+ </UPageCard>
34
+ <UButton
35
+ variant="outline"
36
+ label="Bearbeiten"
37
+ icon="i-lucide-pen"
38
+ block
39
+ to="/konto/profil"
40
+ />
41
+ </div>
42
+ <div class="flex flex-col gap-2 w-full">
43
+ <AddressCard
44
+ :address="userDefaultShippingAddress"
45
+ title="Standard Lieferadresse"
46
+ icon="i-lucide-house"
47
+ />
48
+ <UButton
49
+ variant="outline"
50
+ label="Bearbeiten"
51
+ icon="i-lucide-pen"
52
+ block
53
+ to="/konto/adressen"
54
+ />
55
+ </div>
56
+ <div class="flex flex-col gap-2 w-full">
57
+ <AddressCard
58
+ :address="userDefaultBillingAddress"
59
+ title="Standard Rechnungsadresse"
60
+ icon="i-lucide-receipt-text"
61
+ :with-edit-button="false"
62
+ />
63
+ <UButton
64
+ variant="outline"
65
+ label="Bearbeiten"
66
+ icon="i-lucide-pen"
67
+ block
68
+ to="/konto/adressen"
69
+ />
70
+ </div>
71
+ </UPageGrid>
72
+ </UPageBody>
73
+ </UContainer>
74
+ </template>
@@ -0,0 +1,160 @@
1
+ <script setup lang="ts">
2
+ import { useUser } from "@shopware/composables";
3
+ import type { FormSubmitEvent } from "@nuxt/ui";
4
+ import * as z from "zod";
5
+
6
+ definePageMeta({
7
+ layout: "account",
8
+ });
9
+
10
+ const { apiClient } = useShopwareContext();
11
+ const loading = ref(true);
12
+ const { user, refreshUser, updatePersonalInfo, updateEmail, logout } =
13
+ useUser();
14
+ const toast = useToast();
15
+ const open = ref(false);
16
+
17
+ const schema = z.object({
18
+ email: z.string().email("Keine gültige Emailadresse"),
19
+ firstName: z.string().min(1),
20
+ lastName: z.string().min(1),
21
+ });
22
+ type Schema = z.output<typeof schema>;
23
+
24
+ function initializeFormState() {
25
+ return reactive({
26
+ email: user.value?.email,
27
+ firstName: user.value?.firstName,
28
+ lastName: user.value?.lastName,
29
+ });
30
+ }
31
+
32
+ const state = initializeFormState();
33
+
34
+ async function handleEmailUpdate(newEmail: string) {
35
+ if (newEmail !== user.value?.email) {
36
+ await updateEmail(newEmail);
37
+ }
38
+ }
39
+
40
+ async function handlePersonalInfoUpdate(firstName: string, lastName: string) {
41
+ await updatePersonalInfo({
42
+ firstName,
43
+ lastName,
44
+ });
45
+ }
46
+
47
+ function showSuccessMessage(message: string) {
48
+ toast.add({
49
+ title: message,
50
+ color: "success",
51
+ });
52
+ }
53
+
54
+ function showErrorMessage(title: string, description: string) {
55
+ toast.add({
56
+ title,
57
+ description,
58
+ icon: "i-lucide-x",
59
+ color: "error" as const,
60
+ });
61
+ }
62
+
63
+ async function onSubmit(event: FormSubmitEvent<Schema>) {
64
+ try {
65
+ loading.value = true;
66
+ const eventData = event.data;
67
+
68
+ await handleEmailUpdate(eventData.email);
69
+ await handlePersonalInfoUpdate(eventData.firstName, eventData.lastName);
70
+ await refreshUser();
71
+
72
+ showSuccessMessage("Erfolgreich gespeichert");
73
+ } catch (error) {
74
+ console.error("Profile update error:", error);
75
+ showErrorMessage("Fehler!", "Bitte versuchen Sie es später erneut.");
76
+ } finally {
77
+ loading.value = false;
78
+ }
79
+ }
80
+
81
+ async function deleteCustomerAccount() {
82
+ await apiClient.invoke("deleteCustomer delete /account/customer");
83
+ toast.add({
84
+ title: "Tschüss!",
85
+ icon: "i-lucide-check",
86
+ color: "success" as const,
87
+ });
88
+ await logout();
89
+ navigateTo("/");
90
+ }
91
+
92
+ async function onDeleteProfile() {
93
+ try {
94
+ await deleteCustomerAccount();
95
+ } catch (error) {
96
+ console.error("Customer delete error:", error);
97
+ showErrorMessage("Fehler!", "Bitte versuchen Sie es später erneut.");
98
+ }
99
+ }
100
+
101
+ onMounted(async () => {
102
+ await refreshUser();
103
+ loading.value = false;
104
+ });
105
+ </script>
106
+
107
+ <template>
108
+ <UContainer>
109
+ <UPageHeader
110
+ headline="KONTO"
111
+ title="Mein Profil"
112
+ description="Ändere hier deine prerönlichen Daten."
113
+ />
114
+ <UPageBody v-if="!loading">
115
+ <UForm
116
+ :schema="schema"
117
+ :state="state"
118
+ class="space-y-4"
119
+ @submit="onSubmit"
120
+ @error="(error) => console.log('Form validation error:', error)"
121
+ >
122
+ <UFormField label="Vorname" name="firstName">
123
+ <UInput v-model="state.firstName" type="text" class="w-full" />
124
+ </UFormField>
125
+ <UFormField label="Nachname" name="lastName">
126
+ <UInput v-model="state.lastName" type="text" class="w-full" />
127
+ </UFormField>
128
+ <UFormField label="Emailadresse" name="email">
129
+ <UInput v-model="state.email" type="email" class="w-full" />
130
+ </UFormField>
131
+ <div class="flex flex-row justify-between">
132
+ <UButton label="Speichern" type="submit" />
133
+ <UButton label="Konto löschen" color="error" @click="open = !open" />
134
+ </div>
135
+ </UForm>
136
+ <UModal
137
+ v-model:open="open"
138
+ title="Konto löschen"
139
+ description="Ihre Daten werden unwiederruflich gelöscht."
140
+ :ui="{ footer: 'justify-end' }"
141
+ >
142
+ <template #body>
143
+ <p>
144
+ Löscht unwiederuflich ihr Kundenkonto zusammen mit Ihren Adressen,
145
+ Merklisten und verknüpften Daten.
146
+ </p>
147
+ </template>
148
+ <template #footer="{ close }">
149
+ <UButton
150
+ label="Abbrechen"
151
+ color="neutral"
152
+ variant="outline"
153
+ @click="close"
154
+ />
155
+ <UButton label="Löschen" color="error" @click="onDeleteProfile" />
156
+ </template>
157
+ </UModal>
158
+ </UPageBody>
159
+ </UContainer>
160
+ </template>
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ useSeoMeta({
3
+ title: "Merkliste | Pizzeria La Fattoria",
4
+ });
5
+ </script>
6
+
7
+ <template>
8
+ <UContainer>
9
+ <Wishlist />
10
+ </UContainer>
11
+ </template>