Introduced `useExactProducts` composable to fetch precise product details for guest cart and wishlist items. Improved cookie-based cart and wishlist fallback handling for unauthenticated users. Updated related components and composables for better synchronization and type safety. - Added `useExactProducts` composable leveraging the `GET_EXACT_PRODUCTS` query. - Enhanced `wishlist.vue` and `cart.vue` for reactive updates on guest state changes. - Improved product synchronization logic in `useOrderSync` and `useWishlistSync`. - Updated translations and fixed minor typos in localization files. Improves user experience by ensuring consistent product details, even for guests. No breaking changes.
181 lines
No EOL
4.3 KiB
Vue
181 lines
No EOL
4.3 KiB
Vue
<template>
|
|
<div class="wishlist">
|
|
<div class="container">
|
|
<div class="wishlist__wrapper">
|
|
<div class="wishlist__top">
|
|
<h1 class="wishlist__top-title">{{ t('wishlist.title') }}</h1>
|
|
<div class="wishlist__top-inner">
|
|
<client-only>
|
|
<p>({{ t('wishlist.items', productsInWishlist.length, { count: productsInWishlist.length }) }})</p>
|
|
</client-only>
|
|
<ui-button
|
|
:type="'button'"
|
|
class="wishlist__top-button"
|
|
@click="onBulkAdd"
|
|
:isLoading="bulkLoading"
|
|
>
|
|
<icon name="material-symbols:add" size="20" />
|
|
{{ t('buttons.addAllToCart') }}
|
|
</ui-button>
|
|
</div>
|
|
</div>
|
|
<client-only>
|
|
<div class="wishlist__list">
|
|
<div class="wishlist__list-inner" v-if="productsInWishlist.length">
|
|
<cards-product
|
|
v-for="product in productsInWishlist"
|
|
:key="product.node.uuid"
|
|
:product="product.node"
|
|
/>
|
|
</div>
|
|
<p class="wishlist__empty" v-else>{{ t('wishlist.empty') }}</p>
|
|
</div>
|
|
</client-only>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {usePageTitle} from '@composables/utils';
|
|
import {useWishlistOverwrite} from '@composables/wishlist';
|
|
import {useOrderOverwrite} from "@composables/orders";
|
|
import {useExactProducts} from "@composables/products/useExactProducts";
|
|
|
|
const {t} = useI18n();
|
|
const wishlistStore = useWishlistStore();
|
|
const userStore = useUserStore();
|
|
const { $appHelpers } = useNuxtApp();
|
|
|
|
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
|
|
|
const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
|
|
default: () => [],
|
|
path: '/',
|
|
});
|
|
|
|
const { overwriteWishlist } = useWishlistOverwrite();
|
|
const { overwriteOrder, bulkLoading } = useOrderOverwrite();
|
|
const { products, getExactProducts } = useExactProducts();
|
|
|
|
const productsInWishlist = computed(() => {
|
|
if (isAuthenticated.value) {
|
|
return wishlistStore.wishlist
|
|
? wishlistStore.wishlist.products.edges
|
|
: [];
|
|
} else {
|
|
return products.value.map(p => ({
|
|
node: p
|
|
}));
|
|
}
|
|
});
|
|
|
|
watchEffect(async () => {
|
|
if (!isAuthenticated.value && cookieWishlist.value?.length) {
|
|
await getExactProducts(cookieWishlist.value, 'uuid');
|
|
}
|
|
});
|
|
|
|
const totalPrice = computed(() => {
|
|
return productsInWishlist.value.reduce((acc, p) => acc + p.node.price, 0);
|
|
});
|
|
|
|
const selectedProducts = ref<{ uuid: string }[]>([]);
|
|
|
|
const productsUuid = computed<{ uuid: string }[]>(() => {
|
|
return productsInWishlist.value.map(p => ({
|
|
uuid: p.node.uuid
|
|
}));
|
|
});
|
|
|
|
function toggleUuid(uuid: string, checked: boolean) {
|
|
if (checked) {
|
|
if (!selectedProducts.value.some(o => o.uuid === uuid)) {
|
|
selectedProducts.value.push({ uuid });
|
|
}
|
|
}
|
|
else {
|
|
selectedProducts.value = selectedProducts.value
|
|
.filter(o => o.uuid !== uuid);
|
|
}
|
|
}
|
|
|
|
function onBulkAdd() {
|
|
overwriteOrder({
|
|
type: 'bulk',
|
|
bulkAction: 'add',
|
|
products: productsUuid.value
|
|
});
|
|
}
|
|
|
|
function onBulkRemove() {
|
|
overwriteWishlist({
|
|
type: 'bulkRemove',
|
|
bulkAction: 'remove',
|
|
products: productsUuid.value
|
|
});
|
|
}
|
|
|
|
const { setPageTitle } = usePageTitle();
|
|
|
|
setPageTitle(t('breadcrumbs.wishlist'));
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.wishlist {
|
|
padding-block: 50px 100px;
|
|
background-color: $main;
|
|
|
|
&__wrapper {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 50px;
|
|
}
|
|
|
|
&__top {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
|
|
&-title {
|
|
color: $primary_dark;
|
|
font-family: "Playfair Display", sans-serif;
|
|
font-size: 36px;
|
|
font-weight: 600;
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
&-inner {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 35px;
|
|
}
|
|
|
|
& p {
|
|
color: $text;
|
|
font-size: 18px;
|
|
font-weight: 400;
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
&-button {
|
|
width: fit-content;
|
|
padding-inline: 25px;
|
|
}
|
|
}
|
|
|
|
&__list {
|
|
&-inner {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
gap: 25px;
|
|
}
|
|
}
|
|
|
|
&__empty {
|
|
font-weight: 500;
|
|
font-size: 18px;
|
|
color: $primary;
|
|
}
|
|
}
|
|
</style> |