diff --git a/storefront/app/composables/auth/useLogin.ts b/storefront/app/composables/auth/useLogin.ts
index 087c48e7..c4724ba1 100644
--- a/storefront/app/composables/auth/useLogin.ts
+++ b/storefront/app/composables/auth/useLogin.ts
@@ -43,6 +43,8 @@ export function useLogin() {
userStore.setUser(authData.user);
cookieAccess.value = authData.accessToken;
+ await nextTick();
+
navigateTo(localePath('/'));
useNotification({
diff --git a/storefront/app/composables/breadcrumbs/useBreadcrumbs.ts b/storefront/app/composables/breadcrumbs/useBreadcrumbs.ts
index 8b9a702a..1df13a88 100644
--- a/storefront/app/composables/breadcrumbs/useBreadcrumbs.ts
+++ b/storefront/app/composables/breadcrumbs/useBreadcrumbs.ts
@@ -82,8 +82,6 @@ export function useBreadcrumbs() {
return crumbs;
});
- console.log(breadcrumbs.value)
-
return {
breadcrumbs,
};
diff --git a/storefront/app/composables/orders/index.ts b/storefront/app/composables/orders/index.ts
index c0f5c51c..332c179f 100644
--- a/storefront/app/composables/orders/index.ts
+++ b/storefront/app/composables/orders/index.ts
@@ -1,3 +1,4 @@
export * from './useOrderBuy';
export * from './useOrderOverwrite';
+export * from './useOrderSync';
export * from './useOrders';
diff --git a/storefront/app/composables/orders/useOrderOverwrite.ts b/storefront/app/composables/orders/useOrderOverwrite.ts
index 91b20a5e..2be8f5fd 100644
--- a/storefront/app/composables/orders/useOrderOverwrite.ts
+++ b/storefront/app/composables/orders/useOrderOverwrite.ts
@@ -8,7 +8,7 @@ import {
} from '@graphql/mutations/cart';
import type {
IAddToOrderResponse,
- IBulkOrderResponse,
+ IBulkOrderResponse, IProduct,
IRemoveAllFromOrderResponse,
IRemoveFromOrderResponse,
IRemoveKindFromOrderResponse,
@@ -16,9 +16,9 @@ import type {
interface IOverwriteOrderArguments {
type: string;
- productUuid?: string;
- productName?: string;
+ product: IProduct;
bulkAction?: string;
+ isBulkSync?: boolean;
products?: {
uuid: string;
}[];
@@ -32,6 +32,11 @@ export function useOrderOverwrite() {
const isAuthenticated = computed(() => userStore.isAuthenticated);
const orderUuid = computed(() => cartStore.currentOrder?.uuid);
+ const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
+ default: () => [],
+ path: '/',
+ });
+
const {
mutate: addMutate,
loading: addLoading,
@@ -64,7 +69,7 @@ export function useOrderOverwrite() {
case 'add': {
const addResult = await addMutate({
orderUuid: orderUuid.value,
- productUuid: args.productUuid,
+ productUuid: args.product.uuid,
});
if (addResult?.data?.addOrderProduct?.order) {
@@ -72,7 +77,7 @@ export function useOrderOverwrite() {
useNotification({
message: t('popup.success.addToCart', {
- product: args.productName,
+ product: args.product.name,
}),
type: 'success',
});
@@ -84,7 +89,7 @@ export function useOrderOverwrite() {
case 'remove': {
const removeResult = await removeMutate({
orderUuid: orderUuid.value,
- productUuid: args.productUuid,
+ productUuid: args.product.uuid,
});
if (removeResult?.data?.removeOrderProduct?.order) {
@@ -92,7 +97,7 @@ export function useOrderOverwrite() {
useNotification({
message: t('popup.success.removeFromCart', {
- product: args.productName,
+ product: args.product.name,
}),
type: 'success',
});
@@ -104,7 +109,7 @@ export function useOrderOverwrite() {
case 'removeKind': {
const removeKindResult = await removeKindMutate({
orderUuid: orderUuid.value,
- productUuid: args.productUuid,
+ productUuid: args.product.uuid,
});
if (removeKindResult?.data?.removeOrderProductsOfAKind?.order) {
@@ -112,7 +117,7 @@ export function useOrderOverwrite() {
useNotification({
message: t('popup.success.removeFromCart', {
- product: args.productName,
+ product: args.product.name,
}),
type: 'success',
});
@@ -148,7 +153,7 @@ export function useOrderOverwrite() {
if (bulkResult?.data?.bulkOrderAction?.order) {
cartStore.setCurrentOrders(bulkResult.data.bulkOrderAction.order);
useNotification({
- message: t('popup.success.bulkRemoveWishlist'),
+ message: t('popup.success.bulkRemoveOrder'),
type: 'success',
});
}
@@ -160,10 +165,119 @@ export function useOrderOverwrite() {
console.error('No type provided for overwriteOrder');
}
} else {
- useNotification({
- message: t('popup.errors.loginFirst'),
- type: 'error',
- });
+ switch (args.type) {
+ case 'add': {
+ const currentCart = cookieCart.value || [];
+ const existingItem = currentCart.find(
+ (item) => item.product.uuid === args.product.uuid
+ );
+
+ if (existingItem) {
+ existingItem.quantity += 1;
+ cookieCart.value = [...currentCart];
+ } else {
+ cookieCart.value = [
+ ...currentCart,
+ { product: args.product, quantity: 1 }
+ ];
+ }
+
+ useNotification({
+ message: t('popup.success.addToCart', {
+ product: args.product.name,
+ }),
+ type: 'success',
+ });
+
+ break;
+ }
+
+ case 'remove': {
+ const currentCart = cookieCart.value || [];
+ const existingItem = currentCart.find(
+ (item) => item.product.uuid === args.product.uuid
+ );
+
+ if (existingItem) {
+ if (existingItem.quantity > 1) {
+ existingItem.quantity -= 1;
+ cookieCart.value = [...currentCart];
+ } else {
+ cookieCart.value = currentCart.filter(
+ (item) => item.product.uuid !== args.product.uuid
+ );
+ }
+
+ useNotification({
+ message: t('popup.success.removeFromCart', {
+ product: args.product.name,
+ }),
+ type: 'success',
+ });
+ }
+
+ break;
+ }
+
+ case 'removeKind': {
+ cookieCart.value = cookieCart.value.filter(
+ (item) => item.product.uuid !== args.product.uuid
+ );
+
+ useNotification({
+ message: t('popup.success.removeFromCart', {
+ product: args.product.name,
+ }),
+ type: 'success',
+ });
+
+ break;
+ }
+
+ case 'removeAll': {
+ cookieCart.value = [];
+
+ useNotification({
+ message: t('popup.success.removeAllFromCart'),
+ type: 'success',
+ });
+
+ break;
+ }
+
+ case 'bulk': {
+ if (args.bulkAction === 'remove' && args.products) {
+ const uuidsToRemove = args.products.map(p => p.uuid);
+ cookieCart.value = cookieCart.value.filter(
+ (item) => !uuidsToRemove.includes(item.product.uuid)
+ );
+
+ useNotification({
+ message: t('popup.success.bulkRemoveOrder'),
+ type: 'success',
+ });
+ } else if (args.bulkAction === 'add' && args.products) {
+ const currentCart = cookieCart.value || [];
+
+ for (const productRef of args.products) {
+ const existingItem = currentCart.find(
+ (item) => item.product.uuid === productRef.uuid
+ );
+
+ if (existingItem) {
+ existingItem.quantity += 1;
+ }
+ }
+
+ cookieCart.value = [...currentCart];
+ }
+
+ break;
+ }
+
+ default:
+ console.error('No type provided for overwriteOrder');
+ }
}
}
diff --git a/storefront/app/composables/orders/useOrderSync.ts b/storefront/app/composables/orders/useOrderSync.ts
new file mode 100644
index 00000000..202450a6
--- /dev/null
+++ b/storefront/app/composables/orders/useOrderSync.ts
@@ -0,0 +1,68 @@
+import { useOrderOverwrite } from '@composables/orders/useOrderOverwrite';
+
+export function useOrderSync() {
+ const cartStore = useCartStore();
+ const userStore = useUserStore();
+ const { $appHelpers } = useNuxtApp();
+
+ const { overwriteOrder } = useOrderOverwrite();
+
+ const isAuthenticated = computed(() => userStore.isAuthenticated);
+ const orderUuid = computed(() => cartStore.currentOrder?.uuid);
+
+ const cookieCart = useCookie($appHelpers.COOKIES_CART_KEY, {
+ default: () => [],
+ path: '/',
+ });
+
+ async function syncOrder() {
+ if (!isAuthenticated.value || !orderUuid.value) {
+ return;
+ }
+
+ const cookieCartItems = cookieCart.value || [];
+
+ if (cookieCartItems.length === 0) {
+ return;
+ }
+
+ const apiCartProducts = cartStore.currentOrder?.orderProducts?.edges || [];
+
+ const apiProductMap = new Map(
+ apiCartProducts.map(e => [e.node.product.uuid, e.node.quantity])
+ );
+
+ const productsToSync = [];
+
+ for (const cartItem of cookieCartItems) {
+ const apiQuantity = apiProductMap.get(cartItem.product.uuid) || 0;
+ const quantityDifference = cartItem.quantity - apiQuantity;
+
+ if (quantityDifference > 0) {
+ for (let i = 0; i < quantityDifference; i++) {
+ productsToSync.push({ uuid: cartItem.product.uuid });
+ }
+ }
+ }
+
+ if (productsToSync.length === 0) {
+ cookieCart.value = [];
+ return;
+ }
+
+ try {
+ await overwriteOrder({
+ type: 'bulk',
+ bulkAction: 'add',
+ isBulkSync: true,
+ products: productsToSync
+ });
+ } catch (err) {
+ console.error('Failed to sync cart:', err);
+ }
+ }
+
+ return {
+ syncOrder,
+ };
+}
\ No newline at end of file
diff --git a/storefront/app/composables/user/useUserBaseData.ts b/storefront/app/composables/user/useUserBaseData.ts
index 30497cc2..d23d866d 100644
--- a/storefront/app/composables/user/useUserBaseData.ts
+++ b/storefront/app/composables/user/useUserBaseData.ts
@@ -1,4 +1,6 @@
import { orderStatuses } from '@appConstants';
+import { useOrderSync } from '@composables/orders';
+import {useWishlistSync} from "@composables/wishlist";
import { getUserBaseData } from '@graphql/queries/combined/userBaseData';
import type { IUserBaseDataResponse } from '@types';
@@ -7,6 +9,9 @@ export async function useUserBaseData(userEmail: string) {
const cartStore = useCartStore();
const promocodeStore = usePromocodeStore();
+ const { syncWishlist } = useWishlistSync();
+ const { syncOrder } = useOrderSync();
+
const { document, variables } = getUserBaseData({
userEmail,
status: orderStatuses.PENDING,
@@ -19,9 +24,13 @@ export async function useUserBaseData(userEmail: string) {
if (data?.wishlists.edges) {
wishlistStore.setWishlist(data.wishlists.edges[0].node);
+
+ await syncWishlist();
}
if (data?.orders.edges) {
cartStore.setCurrentOrders(data.orders.edges[0].node);
+
+ await syncOrder();
}
if (data?.promocodes.edges) {
promocodeStore.setPromocodes(data.promocodes.edges);
diff --git a/storefront/app/composables/wishlist/index.ts b/storefront/app/composables/wishlist/index.ts
index ecc23d95..3f65a7cd 100644
--- a/storefront/app/composables/wishlist/index.ts
+++ b/storefront/app/composables/wishlist/index.ts
@@ -1,2 +1,3 @@
export * from './useWishlist';
export * from './useWishlistOverwrite';
+export * from './useWishlistSync';
diff --git a/storefront/app/composables/wishlist/useWishlistOverwrite.ts b/storefront/app/composables/wishlist/useWishlistOverwrite.ts
index f32924fe..5c2c7bad 100644
--- a/storefront/app/composables/wishlist/useWishlistOverwrite.ts
+++ b/storefront/app/composables/wishlist/useWishlistOverwrite.ts
@@ -7,16 +7,16 @@ import {
} from '@graphql/mutations/wishlist';
import type {
IAddToWishlistResponse,
- IBulkWishlistResponse,
+ IBulkWishlistResponse, IProduct,
IRemoveAllFromWishlistResponse,
IRemoveFromWishlistResponse,
} from '@types';
interface IOverwriteWishlistArguments {
type: string;
- productUuid?: string;
- productName?: string;
+ product: IProduct;
bulkAction?: string;
+ isBulkSync?: boolean;
products?: {
uuid: string;
}[];
@@ -26,10 +26,16 @@ export function useWishlistOverwrite() {
const { t } = useI18n();
const wishlistStore = useWishlistStore();
const userStore = useUserStore();
+ const { $appHelpers } = useNuxtApp();
const isAuthenticated = computed(() => userStore.isAuthenticated);
const wishlistUuid = computed(() => wishlistStore.wishlist?.uuid);
+ const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
+ default: () => [],
+ path: '/',
+ });
+
const {
mutate: addMutate,
loading: addLoading,
@@ -57,7 +63,7 @@ export function useWishlistOverwrite() {
case 'add': {
const addResult = await addMutate({
wishlistUuid: wishlistUuid.value,
- productUuid: args.productUuid,
+ productUuid: args.product.uuid,
});
if (addResult?.data?.addWishlistProduct?.wishlist) {
@@ -65,7 +71,7 @@ export function useWishlistOverwrite() {
useNotification({
message: t('popup.success.addToWishlist', {
- product: args.productName,
+ product: args.product.name,
}),
type: 'success',
});
@@ -77,7 +83,7 @@ export function useWishlistOverwrite() {
case 'remove': {
const removeResult = await removeMutate({
wishlistUuid: wishlistUuid.value,
- productUuid: args.productUuid,
+ productUuid: args.product.uuid,
});
if (removeResult?.data?.removeWishlistProduct?.wishlist) {
@@ -85,7 +91,7 @@ export function useWishlistOverwrite() {
useNotification({
message: t('popup.success.removeFromWishlist', {
- product: args.productName,
+ product: args.product.name,
}),
type: 'success',
});
@@ -97,7 +103,7 @@ export function useWishlistOverwrite() {
case 'removeAll': {
const removeAllResult = await removeAllMutate({
wishlistUuid: wishlistUuid.value,
- productUuid: args.productUuid,
+ productUuid: args.product.uuid,
});
if (removeAllResult?.data?.removeAllWishlistProducts?.wishlist) {
@@ -121,10 +127,15 @@ export function useWishlistOverwrite() {
if (bulkResult?.data?.bulkWishlistAction?.wishlist) {
wishlistStore.setWishlist(bulkResult.data.bulkWishlistAction.wishlist);
- useNotification({
- message: t('popup.success.bulkRemoveWishlist'),
- type: 'success',
- });
+
+ if (args.isBulkSync) {
+ cookieWishlist.value = [];
+ } else {
+ useNotification({
+ message: t('popup.success.bulkRemoveWishlist'),
+ type: 'success',
+ });
+ }
}
break;
@@ -134,10 +145,85 @@ export function useWishlistOverwrite() {
console.error('No type provided for overwriteWishlist');
}
} else {
- useNotification({
- message: t('popup.errors.loginFirst'),
- type: 'error',
- });
+ switch (args.type) {
+ case 'add': {
+ const isAlreadyInWishlist = cookieWishlist.value.some(
+ (item) => item.uuid === args.product.uuid
+ );
+
+ if (isAlreadyInWishlist) {
+ useNotification({
+ message: t('popup.errors.alreadyInWishlist', {
+ product: args.product.name,
+ }),
+ type: 'warning',
+ });
+ } else {
+ cookieWishlist.value = [...cookieWishlist.value, args.product];
+
+ useNotification({
+ message: t('popup.success.addToWishlist', {
+ product: args.product.name,
+ }),
+ type: 'success',
+ });
+ }
+
+ break;
+ }
+
+ case 'remove': {
+ cookieWishlist.value = cookieWishlist.value.filter(
+ (item) => item.uuid !== args.product.uuid
+ );
+
+ useNotification({
+ message: t('popup.success.removeFromWishlist', {
+ product: args.product.name,
+ }),
+ type: 'success',
+ });
+
+ break;
+ }
+
+ case 'removeAll': {
+ cookieWishlist.value = [];
+
+ useNotification({
+ message: t('popup.success.removeAllFromWishlist'),
+ type: 'success',
+ });
+
+ break;
+ }
+
+ case 'bulk': {
+ const bulkResult = await bulkMutate({
+ wishlistUuid: wishlistUuid.value,
+ action: args.bulkAction,
+ products: args.products,
+ });
+
+ if (bulkResult?.data?.bulkWishlistAction?.wishlist) {
+ wishlistStore.setWishlist(bulkResult.data.bulkWishlistAction.wishlist);
+
+ if (args.isBulkSync) {
+ cookieWishlist.value = [];
+ } else {
+ useNotification({
+ message: t('popup.success.bulkRemoveWishlist'),
+ type: 'success',
+ });
+ }
+ }
+
+ break;
+ }
+
+ default:
+ console.error('No type provided for overwriteWishlist');
+ }
}
}
diff --git a/storefront/app/composables/wishlist/useWishlistSync.ts b/storefront/app/composables/wishlist/useWishlistSync.ts
new file mode 100644
index 00000000..d149a9a1
--- /dev/null
+++ b/storefront/app/composables/wishlist/useWishlistSync.ts
@@ -0,0 +1,66 @@
+import {useWishlistOverwrite} from "@composables/wishlist/useWishlistOverwrite";
+
+export function useWishlistSync() {
+ const wishlistStore = useWishlistStore();
+ const userStore = useUserStore();
+ const { $appHelpers } = useNuxtApp();
+
+ const { overwriteWishlist } = useWishlistOverwrite();
+
+ const isAuthenticated = computed(() => userStore.isAuthenticated);
+ const wishlistUuid = computed(() => wishlistStore.wishlist?.uuid);
+
+ const cookieWishlist = useCookie($appHelpers.COOKIES_WISHLIST_KEY, {
+ default: () => [],
+ path: '/',
+ });
+
+ async function syncWishlist() {
+ if (!isAuthenticated.value || !wishlistUuid.value) {
+ return;
+ }
+
+ const cookieProducts = cookieWishlist.value || [];
+
+ if (cookieProducts.length === 0) {
+ return;
+ }
+
+ const apiProductUuids = wishlistStore.wishlist?.products?.edges.map(e => e.node.uuid) || [];
+
+ const productsToAdd = cookieProducts.filter(
+ (product) => !apiProductUuids.includes(product.uuid)
+ );
+
+ if (productsToAdd.length === 0) {
+ cookieWishlist.value = [];
+ return;
+ }
+
+ try {
+ await overwriteWishlist({
+ type: 'bulk',
+ bulkAction: 'add',
+ isBulkSync: true,
+ products: productsToAdd.map(p => ({ uuid: p.uuid }))
+ })
+
+ if (bulkResult?.data?.bulkWishlistAction?.wishlist) {
+ wishlistStore.setWishlist(bulkResult.data.bulkWishlistAction.wishlist);
+
+ cookieWishlist.value = [];
+ }
+ } catch (err) {
+ console.error('Failed to sync wishlist:', err);
+ }
+ }
+
+ watch(syncError, (err) => {
+ if (!err) return;
+ console.error('useWishlistSync error:', err);
+ });
+
+ return {
+ syncWishlist,
+ };
+}
\ No newline at end of file
diff --git a/storefront/app/constants/cookies.ts b/storefront/app/constants/cookies.ts
index 64ce7b36..23e27ffa 100644
--- a/storefront/app/constants/cookies.ts
+++ b/storefront/app/constants/cookies.ts
@@ -2,6 +2,8 @@ export const COOKIE_KEY_TEMPLATES = {
LOCALE: (appNameKey: string) => `${appNameKey}-locale`,
REFRESH_TOKEN: (appNameKey: string) => `${appNameKey}-refresh`,
ACCESS_TOKEN: (appNameKey: string) => `${appNameKey}-access`,
+ WISHLIST_TOKEN: (appNameKey: string) => `${appNameKey}-wishlist`,
+ CART_TOKEN: (appNameKey: string) => `${appNameKey}-cart`,
PRODUCT_VIEW: (appNameKey: string) => `${appNameKey}-product-view`,
THEME: (appNameKey: string) => `${appNameKey}-theme`,
} as const;
diff --git a/storefront/app/pages/cart.vue b/storefront/app/pages/cart.vue
index de4e37e2..04798ed8 100644
--- a/storefront/app/pages/cart.vue
+++ b/storefront/app/pages/cart.vue
@@ -4,7 +4,27 @@
{{ t('cart.title') }}
-
({{ t('cart.items', productsInCartQuantity, { count: productsInCartQuantity }) }})
+
+
({{ t('cart.items', productsInCartQuantity, { count: productsInCartQuantity }) }})
+
+
+ {{ t('buttons.checkout') }}
+
+
+
+
+
+
+
+
{{ t('cart.empty') }}
@@ -13,23 +33,55 @@
+
+
\ No newline at end of file
diff --git a/storefront/app/plugins/00.app-constants.ts b/storefront/app/plugins/00.app-constants.ts
index acf1f406..529ca08d 100644
--- a/storefront/app/plugins/00.app-constants.ts
+++ b/storefront/app/plugins/00.app-constants.ts
@@ -14,6 +14,8 @@ export default defineNuxtPlugin(() => {
COOKIES_LOCALE_KEY: COOKIE_KEY_TEMPLATES.LOCALE(APP_NAME_KEY),
COOKIES_REFRESH_TOKEN_KEY: COOKIE_KEY_TEMPLATES.REFRESH_TOKEN(APP_NAME_KEY),
COOKIES_ACCESS_TOKEN_KEY: COOKIE_KEY_TEMPLATES.ACCESS_TOKEN(APP_NAME_KEY),
+ COOKIES_WISHLIST_KEY: COOKIE_KEY_TEMPLATES.WISHLIST_TOKEN(APP_NAME_KEY),
+ COOKIES_CART_KEY: COOKIE_KEY_TEMPLATES.CART_TOKEN(APP_NAME_KEY),
COOKIES_PRODUCT_VIEW_KEY: COOKIE_KEY_TEMPLATES.PRODUCT_VIEW(APP_NAME_KEY),
COOKIES_THEME_KEY: COOKIE_KEY_TEMPLATES.THEME(APP_NAME_KEY),
},
diff --git a/storefront/app/plugins/01.apollo.ts b/storefront/app/plugins/01.apollo.ts
index fa8f59a7..7cb08c80 100644
--- a/storefront/app/plugins/01.apollo.ts
+++ b/storefront/app/plugins/01.apollo.ts
@@ -1,3 +1,4 @@
+
import { ApolloLink, from } from '@apollo/client/core';
import { setContext } from '@apollo/client/link/context';
import { onError } from '@apollo/client/link/error';
@@ -12,25 +13,47 @@ export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.callHook('apollo:error', err);
});
- const localeCookie = useCookie($appHelpers.COOKIES_LOCALE_KEY);
- const accessTokenCookie = useCookie($appHelpers.COOKIES_ACCESS_TOKEN_KEY);
-
nuxtApp.hook('apollo:error', (error) => {
console.error('[Apollo Error]:', error);
});
const authLink = setContext((_, { headers }) => {
- const acceptLanguage = localeCookie.value || 'en-gb';
+ let accessToken = '';
+ let locale = 'en-gb';
- return {
- headers: {
- ...headers,
- 'Accept-Language': acceptLanguage,
- ...(accessTokenCookie.value && {
- 'X-SCHON-AUTH': `Bearer ${accessTokenCookie.value}`,
- }),
- },
+ if (import.meta.client) {
+ const clientCookies = document.cookie.split(';').reduce((acc, cookie) => {
+ const [key, value] = cookie.trim().split('=');
+ acc[key] = decodeURIComponent(value);
+ return acc;
+ }, {} as Record