Skip to content

Commit 859db29

Browse files
authored
Fix/various issues: SSR, Styling and UX improvements (#152)
* wrap teleports in clientonly because nuxt doesnt support ssr rendering of teleports if using different selector than #teleports * improve responsive styling of promotion button and modals * improve a11y by removing duplicate sr entry * improve quantity select styling * fix being stuck on certain devices after opening modals using the useModal composable * only load wishlist products on client to not block ssr rendering * improve responsive cookie banner styling * add instant skeleton loaders to certain navigation actions to impove percieved performance * increase version * remove duplicated calls when switching pages or on search page * fix edit and add address in checkout being only rendered once * linting * removed unneeded associations, await cart and wishlist if on their respective pages to prevent wrongfully displaying empty notification
1 parent 7de3ea8 commit 859db29

24 files changed

+151
-140
lines changed

components/Account/AccountAddressContent.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Schemas } from '@shopware/api-client/api-types';
33
import { type ApiClientError } from '@shopware/api-client';
44
import type { BillingAddressForm } from '~/types/form/AddressForm';
55
6-
const modalController = useModal();
6+
const modalController = useModal(false);
77
const { handleError } = useFormErrorStore();
88
99
const { customerAddresses, loadCustomerAddresses, saveAddress, deleteAddress } = useCustomerAddress();

components/Checkout/CheckoutPromotion.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,13 @@ const addPromotion = async (promotionCodeForm: PromotionCodeForm) => {
3838
{{ cartErrors['promotion-not-found'].message }}
3939
</li>
4040
</ul>
41+
4142
<FormKit
4243
type="form"
4344
:submit-label="$t('checkout.promotion.submitLabel')"
45+
:submit-attrs="{
46+
wrapperClass: 'min-w-max',
47+
}"
4448
:classes="{
4549
form: 'w-full flex flex-row gap-4',
4650
}"

components/Checkout/Confirm/CheckoutConfirmAddress.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { refreshContext, signedIn } = useCustomerStore();
88
const { handleError } = useFormErrorStore();
99
const { pushError } = useNotifications();
1010
const { t } = useI18n();
11-
const modalController = useModal();
11+
const modalController = useModal(false);
1212
const { refreshCart } = useCart();
1313
const isLoading = ref(false);
1414

components/Checkout/Confirm/CheckoutConfirmChangeAddress.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,16 @@ const onSubmitAddress= (fields: ShippingAddressForm|BillingAddressForm) => {
173173
</ul>
174174

175175
<AddressFormFields
176+
v-if="mode === 'edit-address'"
176177
:address-type="addressType"
177-
:initial-address="mode === 'edit-address' ? selectedAddress : null"
178+
:initial-address="selectedAddress"
179+
:error-name-nested="false"
180+
/>
181+
182+
<AddressFormFields
183+
v-else-if="mode === 'add-address'"
184+
:address-type="addressType"
185+
:initial-address="null"
178186
:error-name-nested="false"
179187
/>
180188

components/Cms/Element/CmsElementCategoryNavigation.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ const navigationStore = useNavigationStore();
1010
const { mainNavigation } = storeToRefs(navigationStore);
1111
1212
const { isActive } = useActivePath();
13+
14+
const route = useRoute();
15+
const listingStore = useListingStore(route.path === '/search' ? 'search' : 'category');
1316
</script>
1417

1518
<template>
@@ -23,6 +26,7 @@ const { isActive } = useActivePath();
2326
:to="getCategoryRoute(item)"
2427
class="text-lg"
2528
:class="{ 'font-bold': isActive(item.seoUrls) }"
29+
@click="listingStore.isLoading = true;"
2630
>
2731
{{ getTranslatedProperty(item, 'name') }}
2832
</LocaleLink>
@@ -36,6 +40,7 @@ const { isActive } = useActivePath();
3640
<LocaleLink
3741
:to="getCategoryRoute(child)"
3842
:class="{ 'font-bold': isActive(child.seoUrls) }"
43+
@click="listingStore.isLoading = true;"
3944
>
4045
{{ getTranslatedProperty(child, 'name') }}
4146
</LocaleLink>

components/Cms/Element/CmsElementImageGallery.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const isZoomEnabled = elementConfig.getConfigValue('zoom');
2222
const thumbnailSlidesPerView = 3;
2323
const spaceBetween = 16;
2424
25-
const lightboxModalController = useModal();
25+
const lightboxModalController = useModal(false);
2626
const lightboxSliderIndex = ref(0);
2727
const staticThumbnails = computed(() => slides.slice(0, thumbnailSlidesPerView));
2828

components/Cms/Element/CmsElementProductListing.vue

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const props = withDefaults(
1313
1414
const route = useRoute();
1515
const { trackSelectItem } = useAnalytics();
16-
const { getElements, search, getCurrentListing } = useCategoryListing();
16+
const { getElements, getCurrentListing } = useCategoryListing();
1717
1818
const listingStore = useListingStore(props.productListingStoreKey);
1919
const { listingState } = storeToRefs(listingStore);
@@ -37,11 +37,7 @@ const { y: windowYPosition } = useScroll(window, { behavior: 'smooth' });
3737
const changePage = async (page: number) => {
3838
windowYPosition.value = 0;
3939
listingStore.setPage(page);
40-
41-
listingStore.displayCardSkeleton = true;
42-
await search(listingState.value.criteria);
43-
listingStore.setSearchResult(getCurrentListing.value, true);
44-
listingStore.displayCardSkeleton = false;
40+
// Removed search call and setting result in listingStore here, since the watcher from CmsElementSidebarFilter already triggers on pagination
4541
};
4642
4743
const cardSkeletons = computed(() => {
@@ -60,6 +56,7 @@ const config = useCmsElementConfig(props.element);
6056
const boxLayout = config.getConfigValue('boxLayout');
6157
6258
listingStore.setSearchResult(props.element.data.listing, true);
59+
listingStore.isLoading = false;
6360
6461
const products = computed(() =>
6562
// If the store is loading, return an empty array

components/Cookie/CookieBannerActions.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ const { isAcceptAllEnabled } = storeToRefs(cookieBannerStore);
44
</script>
55

66
<template>
7-
<div class="flex flex-wrap justify-center gap-2">
7+
<div class="flex flex-wrap justify-center gap-2 [&>button]:w-full [&>button]:sm:w-fit">
88
<FormKit
99
type="button"
1010
:classes="{
11-
input: 'max-w-fit',
11+
outer: 'w-full sm:w-fit',
12+
input: 'max-w-full sm:max-w-fit',
1213
}"
1314
@click="cookieBannerStore.denyAll()"
1415
>
@@ -20,7 +21,7 @@ const { isAcceptAllEnabled } = storeToRefs(cookieBannerStore);
2021
<FormKit
2122
type="button"
2223
:classes="{
23-
input: 'max-w-fit',
24+
input: 'max-w-full sm:max-w-fit',
2425
}"
2526
>
2627
{{ $t('cookie.banner.configureButton') }}
@@ -32,7 +33,8 @@ const { isAcceptAllEnabled } = storeToRefs(cookieBannerStore);
3233
v-if="isAcceptAllEnabled"
3334
type="submit"
3435
:classes="{
35-
input: 'max-w-fit',
36+
outer: 'w-full sm:w-fit',
37+
input: 'max-w-full sm:max-w-fit',
3638
}"
3739
@click="cookieBannerStore.acceptAll()"
3840
>

components/Cookie/CookieBannerMessage.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66

77
<LazySharedModal
88
:with-close-button="true"
9-
size="sm"
9+
size="md"
1010
>
1111
<template #trigger>
12-
<button class="text-status-info">
12+
<button class="text-brand-primary-dark hover:underline">
1313
{{ $t('cookie.banner.additionalInformationTriggerButton') }}
1414
</button>
1515
</template>

components/Cookie/CookieBannerModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const cookieBannerStore = useCookieBannerStore();
33
const { activatedCookies, cookieGroups } = storeToRefs(cookieBannerStore);
44
const configStore = useConfigStore();
55
const isAcceptAllEnabled = configStore.get('core.basicInformation.acceptAllCookies') as boolean | null;
6-
const modalController = useModal();
6+
const modalController = useModal(false);
77
88
const onUpdateCookie = (active: CookieEntry['cookie'][], inactive: CookieEntry['cookie'][]) => {
99
cookieBannerStore.updateCookies(active, inactive);

components/Layout/Header/LayoutHeaderAccount.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
const customerStore = useCustomerStore();
33
const { signedIn } = storeToRefs(customerStore);
4-
const modalController = useModal();
4+
const modalController = useModal(false);
55
const { t } = useI18n();
66
77
const closeModal = () => {

components/Layout/Header/Search/LayoutHeaderSearch.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ if (route.path === '/search') {
2929
/>
3030
</button>
3131

32+
<!-- Nuxt currently has SSR support only for teleports to #teleports, so other targets need to use a <ClientOnly> wrapper. See: https://nuxt.com/docs/api/components/teleports -->
3233
<ClientOnly>
3334
<teleport to="#flyouts">
3435
<LayoutHeaderSearchBar

components/Layout/Header/Search/LayoutHeaderSearchBar.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,11 @@ const performDebouncedSearch = useDebounceFn(async (value: string) => {
4646
search: value,
4747
},
4848
});
49+
} else {
50+
// Only trigger search call and suggestion tracking if not on search page, since there we already fetch the products and hide the suggestion
51+
await search();
52+
trackSearchSuggestions();
4953
}
50-
await search();
51-
trackSearchSuggestions();
5254
}
5355
}, 500);
5456

components/Layout/LayoutLogo.vue

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ const shopName = configStore.get('core.basicInformation.shopName') as string|nul
2222
:title="shopName"
2323
:to="withLink ? '/' : ''"
2424
>
25-
<span class="sr-only">
26-
{{ shopName }}
27-
</span>
28-
2925
<img
3026
v-if="!smallLogo"
3127
src="/logo.svg"

components/Layout/LayoutSidebar.vue

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,41 +20,47 @@ onClickOutside(sidebarContentElement, () => (props.isClosable ? close() : ''));
2020
</script>
2121

2222
<template>
23-
<Teleport to="body">
24-
<div
25-
ref="sidebarContentElement"
26-
class="fixed z-50 overflow-y-auto bg-white transition-transform duration-500"
27-
tabindex="-1"
28-
aria-labelledby="sidebar"
29-
:class="{
30-
'!translate-x-0': isOpen && (side === 'left' || side === 'right'),
31-
'top-0 h-dvh w-10/12 sm:w-96': side === 'left' || side === 'right',
32-
'!translate-y-0': isOpen && side === 'bottom',
33-
'bottom-0 h-[90dvh] w-full': side === 'bottom',
34-
'left-0 -translate-x-full': side === 'left',
35-
'right-0 translate-x-full': side === 'right',
36-
'bottom-0 translate-y-full': side === 'bottom',
37-
}"
38-
>
39-
<div class="flex items-center justify-between bg-gray-light p-3.5">
40-
<slot name="header" />
41-
<FormKitIcon
42-
icon="xmark"
43-
:title="t('icon.close')"
44-
class="mb-4 ml-auto block size-4 cursor-pointer"
45-
@click="controller.close()"
46-
/>
47-
</div>
48-
<div class="overflow-y-scroll p-4">
49-
<slot name="content" />
23+
<!-- Nuxt currently has SSR support only for teleports to #teleports, so other targets need to use a <ClientOnly> wrapper. See: https://nuxt.com/docs/api/components/teleports -->
24+
<ClientOnly>
25+
<Teleport to="body">
26+
<div
27+
ref="sidebarContentElement"
28+
class="fixed z-50 overflow-y-auto bg-white transition-transform duration-500"
29+
tabindex="-1"
30+
aria-labelledby="sidebar"
31+
:class="{
32+
'!translate-x-0': isOpen && (side === 'left' || side === 'right'),
33+
'top-0 h-dvh w-10/12 sm:w-96': side === 'left' || side === 'right',
34+
'!translate-y-0': isOpen && side === 'bottom',
35+
'bottom-0 h-[90dvh] w-full': side === 'bottom',
36+
'left-0 -translate-x-full': side === 'left',
37+
'right-0 translate-x-full': side === 'right',
38+
'bottom-0 translate-y-full': side === 'bottom',
39+
}"
40+
>
41+
<div class="flex items-center justify-between bg-gray-light p-3.5">
42+
<slot name="header" />
43+
44+
<FormKitIcon
45+
icon="xmark"
46+
:title="t('icon.close')"
47+
class="mb-4 ml-auto block size-4 cursor-pointer"
48+
@click="controller.close()"
49+
/>
50+
</div>
51+
52+
<div class="overflow-y-scroll p-4">
53+
<slot name="content" />
54+
</div>
55+
56+
<slot name="footer" />
5057
</div>
51-
<slot name="footer" />
52-
</div>
5358

54-
<!-- body overlay -->
55-
<div
56-
v-if="isOpen"
57-
class="fixed inset-0 z-40 h-screen w-screen bg-black/50"
58-
/>
59-
</Teleport>
59+
<!-- body overlay -->
60+
<div
61+
v-if="isOpen"
62+
class="fixed inset-0 z-40 h-screen w-screen bg-black/50"
63+
/>
64+
</Teleport>
65+
</ClientOnly>
6066
</template>

components/Navigation/NavigationFlyout.vue

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,44 @@ const selfNotHovered = refDebounced(isOutsideFlyout, 200);
1212
</script>
1313

1414
<template>
15-
<Teleport to="#flyouts">
16-
<div
17-
v-show="parentHovered || !selfNotHovered"
18-
ref="flyout"
19-
class="absolute left-0 top-full w-full bg-white py-4 shadow-md"
20-
:data-flyout-category="navigationElement.id"
21-
>
22-
<div class="container flex">
23-
<div
24-
v-for="child in navigationElement.children"
25-
:key="`nav-item-${child.id}`"
26-
class="flex w-1/4 flex-col gap-2"
27-
>
28-
<NavigationLink
29-
:navigation-element="child"
30-
classes="text-lg font-bold py-2"
31-
active-classes="text-brand-primary"
32-
:as-link="child.type !== 'folder'"
33-
/>
34-
15+
<!-- Nuxt currently has SSR support only for teleports to #teleports, so other targets need to use a <ClientOnly> wrapper. See: https://nuxt.com/docs/api/components/teleports -->
16+
<ClientOnly>
17+
<Teleport to="#flyouts">
18+
<div
19+
v-show="parentHovered || !selfNotHovered"
20+
ref="flyout"
21+
class="absolute left-0 top-full w-full bg-white py-4 shadow-md"
22+
:data-flyout-category="navigationElement.id"
23+
>
24+
<div class="container flex">
3525
<div
36-
v-if="child.childCount > 0"
37-
class="flex flex-col gap-2"
26+
v-for="child in navigationElement.children"
27+
:key="`nav-item-${child.id}`"
28+
class="flex w-1/4 flex-col gap-2"
3829
>
3930
<NavigationLink
40-
v-for="subChild in child.children"
41-
:key="`nav-item-${subChild.id}`"
42-
:navigation-element="subChild"
43-
classes="py-2"
31+
:navigation-element="child"
32+
classes="text-lg font-bold py-2"
4433
active-classes="text-brand-primary"
45-
:as-link="subChild.type !== 'folder'"
34+
:as-link="child.type !== 'folder'"
4635
/>
36+
37+
<div
38+
v-if="child.childCount > 0"
39+
class="flex flex-col gap-2"
40+
>
41+
<NavigationLink
42+
v-for="subChild in child.children"
43+
:key="`nav-item-${subChild.id}`"
44+
:navigation-element="subChild"
45+
classes="py-2"
46+
active-classes="text-brand-primary"
47+
:as-link="subChild.type !== 'folder'"
48+
/>
49+
</div>
4750
</div>
4851
</div>
4952
</div>
50-
</div>
51-
</Teleport>
53+
</Teleport>
54+
</ClientOnly>
5255
</template>

components/Navigation/NavigationLink.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ const categoryLink = computed(() => {
4444
const { isActive } = useActivePath();
4545
const { trackNavigation } = useAnalytics();
4646
const { t } = useI18n();
47+
48+
const route = useRoute();
49+
const listingStore = useListingStore(route.path === '/search' ? 'search' : 'category');
4750
</script>
4851

4952
<template>
@@ -56,7 +59,7 @@ const { t } = useI18n();
5659
:format="!isExternalLink"
5760
class="block transition-all hover:text-brand-primary"
5861
:class="[classes, isActive(navigationElement.seoUrls, activeWithExactMatch) ? activeClasses : '']"
59-
@click="trackNavigation(navigationElement.level ? navigationElement.level - 1 : 0, getTranslatedProperty(navigationElement, 'name'))"
62+
@click="trackNavigation(navigationElement.level ? navigationElement.level - 1 : 0, getTranslatedProperty(navigationElement, 'name')); listingStore.isLoading = true;"
6063
>
6164
<template v-if="asAllItemsLink">
6265
{{ $t('navigation.sidebar.allItems') }}

0 commit comments

Comments
 (0)