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.
174 lines
No EOL
4.1 KiB
Vue
174 lines
No EOL
4.1 KiB
Vue
<template>
|
|
<div class="cart">
|
|
<div class="container">
|
|
<div class="cart__wrapper">
|
|
<div class="cart__top">
|
|
<h1 class="cart__top-title">{{ t('cart.title') }}</h1>
|
|
<div class="cart__top-inner">
|
|
<client-only>
|
|
<p>({{ t('cart.items', productsInCartQuantity, { count: productsInCartQuantity }) }})</p>
|
|
</client-only>
|
|
<p>{{ totalPrice }}$</p>
|
|
<ui-button
|
|
:type="'button'"
|
|
class="cart__top-button"
|
|
@click="buyOrder"
|
|
>
|
|
<icon name="material-symbols:add" size="20" />
|
|
{{ t('buttons.checkout') }}
|
|
</ui-button>
|
|
</div>
|
|
</div>
|
|
<client-only>
|
|
<div class="cart__list">
|
|
<div class="cart__list-inner" v-if="productsInCart.length">
|
|
<cards-product
|
|
v-for="product in productsInCart"
|
|
:key="product.node.uuid"
|
|
:product="product.node.product"
|
|
/>
|
|
</div>
|
|
<p class="cart__empty" v-else>{{ t('cart.empty') }}</p>
|
|
</div>
|
|
</client-only>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {usePageTitle} from "@composables/utils";
|
|
import {useOrderBuy} from "~/composables/orders";
|
|
import {useExactProducts} from "@composables/products/useExactProducts";
|
|
|
|
const {t} = useI18n();
|
|
const cartStore = useCartStore();
|
|
const userStore = useUserStore();
|
|
const { $appHelpers } = useNuxtApp();
|
|
|
|
const isAuthenticated = computed(() => userStore.isAuthenticated);
|
|
|
|
const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
|
|
default: () => [],
|
|
path: '/',
|
|
});
|
|
|
|
const { buyOrder } = useOrderBuy();
|
|
const { products, getExactProducts } = useExactProducts();
|
|
|
|
const cartUuids = computed<string[]>(() => {
|
|
return cookieCart.value.map(item => item.productUuid);
|
|
});
|
|
|
|
const productsInCart = computed(() => {
|
|
if (isAuthenticated.value) {
|
|
return cartStore.currentOrder
|
|
? cartStore.currentOrder.orderProducts.edges
|
|
: [];
|
|
} else {
|
|
return products.value.map(product => {
|
|
const cartItem = cookieCart.value.find(
|
|
item => item.productUuid === product.uuid
|
|
);
|
|
|
|
return {
|
|
node: {
|
|
product: product,
|
|
quantity: cartItem?.quantity ?? 1
|
|
}
|
|
};
|
|
});
|
|
}
|
|
});
|
|
|
|
watchEffect(async () => {
|
|
if (!isAuthenticated.value && cartUuids.value.length) {
|
|
await getExactProducts(cartUuids.value, 'uuid');
|
|
}
|
|
});
|
|
|
|
const totalPrice = computed(() => {
|
|
if (isAuthenticated.value) {
|
|
return cartStore.currentOrder ? cartStore.currentOrder.totalPrice : 0;
|
|
} else {
|
|
return productsInCart.value.reduce((acc, item) => {
|
|
return acc + (item.node.product.price * item.node.quantity);
|
|
}, 0);
|
|
}
|
|
});
|
|
|
|
const productsInCartQuantity = computed(() => {
|
|
if (isAuthenticated.value) {
|
|
let count = 0;
|
|
cartStore.currentOrder?.orderProducts?.edges.forEach((el) => {
|
|
count = count + el.node.quantity;
|
|
});
|
|
return count;
|
|
} else {
|
|
return cookieCart.value.reduce((acc, item) => acc + item.quantity, 0);
|
|
}
|
|
});
|
|
|
|
const { setPageTitle } = usePageTitle();
|
|
|
|
setPageTitle(t('breadcrumbs.cart'));
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.cart {
|
|
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;
|
|
|
|
&-inner {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 35px;
|
|
}
|
|
|
|
&-title {
|
|
color: $primary_dark;
|
|
font-family: "Playfair Display", sans-serif;
|
|
font-size: 36px;
|
|
font-weight: 600;
|
|
letter-spacing: -0.5px;
|
|
}
|
|
|
|
& 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> |