Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4554133
feat: optimistic responses
yuskithedeveloper Sep 19, 2025
a911126
Merge branch 'dev' into feat/VCST-3928-save-for-later-optimistic-resp…
yuskithedeveloper Sep 22, 2025
f233264
Merge branch 'dev' into feat/VCST-3928-save-for-later-optimistic-resp…
yuskithedeveloper Oct 21, 2025
808d9f1
feat: removed batching, getSavedForLater changed to apollo cached query
yuskithedeveloper Oct 23, 2025
e9c68fe
Merge branch 'dev' into feat/VCST-3928-save-for-later-optimistic-resp…
yuskithedeveloper Oct 23, 2025
fa17118
chore: removed not used code
yuskithedeveloper Oct 23, 2025
15eb43a
Merge branch 'feat/VCST-3928-save-for-later-optimistic-response' of h…
yuskithedeveloper Oct 23, 2025
4e9ca92
refactor: undefined checks
yuskithedeveloper Oct 23, 2025
f1fd410
Merge branch 'dev' into feat/VCST-3928-save-for-later-optimistic-resp…
yuskithedeveloper Oct 24, 2025
67c84ea
fix: accidentally removed file
yuskithedeveloper Oct 24, 2025
9ad902b
chore: removed not used code
yuskithedeveloper Oct 24, 2025
f170856
Merge branch 'dev' into feat/VCST-3928-save-for-later-optimistic-resp…
yuskithedeveloper Oct 28, 2025
4a6ed42
Merge branch 'dev' into feat/VCST-3928-save-for-later-optimistic-resp…
Lenajava1 Oct 29, 2025
c5dc43d
Merge branch 'dev' into feat/VCST-3928-save-for-later-optimistic-resp…
yuskithedeveloper Oct 29, 2025
839d335
refactor: duplicated code
yuskithedeveloper Oct 29, 2025
01f9a08
feat: apollo lazy query usage
yuskithedeveloper Oct 29, 2025
99e0b6b
feat: showing saved for later block for empty cart
yuskithedeveloper Oct 29, 2025
02cdd1b
Merge branch 'dev' into feat/VCST-3928-save-for-later-optimistic-resp…
yuskithedeveloper Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

24 changes: 12 additions & 12 deletions client-app/pages/cart.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<template v-if="!cart?.items?.length && !recentlyBrowsedProducts?.length">
<template v-if="!cart?.items?.length && !savedForLaterList?.items?.length && !recentlyBrowsedProducts?.length">
<VcLoaderOverlay v-if="loading" no-bg />

<VcEmptyPage
Expand Down Expand Up @@ -62,15 +62,14 @@
</VcWidget>

<CartForLater
v-if="savedForLaterList?.items?.length && !shouldHide('cart-for-later')"
v-if="!shouldHideSavedForLater && savedForLaterList?.items?.length"
:saved-for-later-list="savedForLaterList"
:loading="moveFromSavedForLaterOverflowed"
class="mt-5"
@add-to-cart="(lineItemId) => handleMoveToCart([lineItemId])"
/>

<RecentlyBrowsedProducts
v-if="recentlyBrowsedProducts.length && !shouldHide('recently-browsed-products')"
v-if="recentlyBrowsedProducts.length && !shouldHideRecentlyBrowsed"
:products="recentlyBrowsedProducts"
class="mt-5"
/>
Expand All @@ -84,7 +83,7 @@
:items-grouped-by-vendor="lineItemsGroupedByVendor"
:selected-item-ids="selectedItemIds"
:validation-errors="cart.validationErrors"
:disabled="changeItemQuantityBatchedOverflowed || moveToSavedForLaterOverflowed || selectionOverflowed"
:disabled="changeItemQuantityBatchedOverflowed || selectionOverflowed"
data-test-id="cart.products-section"
:hide-controls="hideControls"
@change:item-quantity="changeItemQuantityBatched($event.itemId, $event.quantity)"
Expand Down Expand Up @@ -112,15 +111,14 @@
</template>

<CartForLater
v-if="savedForLaterList?.items?.length && !shouldHide('cart-for-later')"
v-if="!shouldHideSavedForLater && savedForLaterList?.items?.length"
:saved-for-later-list="savedForLaterList"
:loading="moveFromSavedForLaterOverflowed"
class="mt-5"
@add-to-cart="(lineItemId) => handleMoveToCart([lineItemId])"
/>

<RecentlyBrowsedProducts
v-if="recentlyBrowsedProducts.length && !shouldHide('recently-browsed-products')"
v-if="recentlyBrowsedProducts.length && !shouldHideRecentlyBrowsed"
:products="recentlyBrowsedProducts"
class="mt-5"
/>
Expand Down Expand Up @@ -292,9 +290,7 @@ const { couponCode, couponIsApplied, couponValidationError, applyCoupon, removeC
const {
savedForLaterList,
moveToSavedForLater,
moveToSavedForLaterOverflowed,
moveFromSavedForLater,
moveFromSavedForLaterOverflowed,
getSavedForLater,
loading: saveForLaterLoading,
} = useSavedForLater();
Expand Down Expand Up @@ -370,6 +366,10 @@ function selectItemEvent(item: LineItemType | undefined): void {
});
}

const shouldHideSavedForLater = computed(() => !isAuthenticated.value || shouldHide("cart-for-later"));

const shouldHideRecentlyBrowsed = computed(() => shouldHide("recently-browsed-products"));

function shouldHide(id: string) {
return props.blocksToHide?.includes(id);
}
Expand Down Expand Up @@ -431,10 +431,10 @@ void (async () => {
}

const isXRecommendModuleEnabled = isEnabledXRecommend(XRECOMMEND_ENABLED_KEY);
if (isAuthenticated.value && isXRecommendModuleEnabled && !shouldHide("recently-browsed-products")) {
if (isAuthenticated.value && isXRecommendModuleEnabled && !shouldHideRecentlyBrowsed.value) {
recentlyBrowsedProducts.value = (await recentlyBrowsed())?.products || [];
}
if (isAuthenticated.value && !shouldHide("cart-for-later")) {
if (!shouldHideSavedForLater.value) {
await getSavedForLater();
}
})();
Expand Down
114 changes: 73 additions & 41 deletions client-app/shared/cart/composables/useSaveForLater.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,68 @@
import { ApolloError } from "@apollo/client/core";
import { useMutation } from "@vue/apollo-composable";
import { useMutation, useLazyQuery } from "@vue/apollo-composable";
import { createSharedComposable } from "@vueuse/core";
import { ref, computed } from "vue";
import { computed } from "vue";
import { AbortReason } from "@/core/api/common/enums";
import { getSavedForLater as getSavedForLaterQuery } from "@/core/api/graphql/cart/queries/getSavedForLater";
import { MoveToSavedForLaterDocument, MoveFromSavedForLaterDocument } from "@/core/api/graphql/types";
import { useMutationBatcher } from "@/core/composables/useMutationBatcher";
import {
MoveToSavedForLaterDocument,
MoveFromSavedForLaterDocument,
GetSavedForLaterDocument,
} from "@/core/api/graphql/types";
import { globals } from "@/core/globals";
import { Logger } from "@/core/utilities";
import type { SavedForLaterListFragment } from "@/core/api/graphql/types";
import { useFullCart } from "@/shared/cart";

function _useSavedForLater() {
const { storeId, currencyCode, cultureName, userId } = globals;

const savedForLaterList = ref<SavedForLaterListFragment>();
const { cart } = useFullCart();

const { mutate: _moveToSavedForLater, loading: _moveToSavedForLaterLoading } =
useMutation(MoveToSavedForLaterDocument);
const savedForLaterList = computed(() => _savedForLaterQueryResult.value?.getSavedForLater);

const {
add: _moveToSavedForLaterBatched,
overflowed: moveToSavedForLaterOverflowed,
loading: _moveToSavedForLaterBatchedLoading,
} = useMutationBatcher(_moveToSavedForLater);
load: getSavedForLater,
loading: _getSavedForLaterLoading,
result: _savedForLaterQueryResult,
} = useLazyQuery(
GetSavedForLaterDocument,
{
storeId,
userId,
cultureName,
currencyCode,
},
{
notifyOnNetworkStatusChange: true,
fetchPolicy: "cache-first",
},
);

const { mutate: _moveToSavedForLater, loading: _moveToSavedForLaterLoading } = useMutation(
MoveToSavedForLaterDocument,
{
optimisticResponse(vars, { IGNORE }) {
const movedItemIds = vars.command?.lineItemIds;

if (!movedItemIds?.length || !cart.value || !savedForLaterList.value) {
return IGNORE;
}

return {
moveToSavedForLater: {
cart: {
...cart.value,
items: cart.value.items.filter((item) => !movedItemIds.includes(item.id)),
},
list: savedForLaterList.value,
},
};
},
},
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Items Temporarily Disappear During Move

The optimistic responses for moveToSavedForLater and moveFromSavedForLater are incomplete. Items are correctly removed from their source list (cart or saved for later) but aren't added to their destination. This makes items temporarily disappear from the UI, creating a confusing experience until the server response updates the state.

Additional Locations (1)

Fix in Cursor Fix in Web


async function moveToSavedForLater(cartId: string, itemIds: string[]) {
try {
const moveResult = await _moveToSavedForLaterBatched({
await _moveToSavedForLater({
command: {
storeId,
userId,
Expand All @@ -36,8 +72,6 @@ function _useSavedForLater() {
lineItemIds: itemIds,
},
});

savedForLaterList.value = moveResult?.data?.moveToSavedForLater?.list;
} catch (err) {
if (err instanceof ApolloError && err.networkError?.toString() === (AbortReason.Explicit as string)) {
return;
Expand All @@ -46,18 +80,32 @@ function _useSavedForLater() {
}
}

const { mutate: _moveFromSavedForLater, loading: _moveFromSavedForLaterLoading } =
useMutation(MoveFromSavedForLaterDocument);
const { mutate: _moveFromSavedForLater, loading: _moveFromSavedForLaterLoading } = useMutation(
MoveFromSavedForLaterDocument,
{
optimisticResponse(vars, { IGNORE }) {
const movedItemIds = vars.command?.lineItemIds;

const {
add: _moveFromSavedForLaterBatched,
overflowed: moveFromSavedForLaterOverflowed,
loading: _moveFromSavedForLaterBatchedLoading,
} = useMutationBatcher(_moveFromSavedForLater);
if (!movedItemIds?.length || !cart.value || !savedForLaterList.value) {
return IGNORE;
}

return {
moveFromSavedForLater: {
cart: cart.value,
list: {
...savedForLaterList.value,
items: savedForLaterList.value.items.filter((item) => !movedItemIds.includes(item.id)),
},
},
};
},
},
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Optimistic Response Fails to Update Cart

The moveFromSavedForLater optimistic response removes items from the saved-for-later list but doesn't add them to the cart. This causes items to temporarily vanish from the UI, impacting user experience.

Fix in Cursor Fix in Web


async function moveFromSavedForLater(cartId: string, itemIds: string[]) {
try {
const moveResult = await _moveFromSavedForLaterBatched({
await _moveFromSavedForLater({
command: {
storeId,
userId,
Expand All @@ -67,8 +115,6 @@ function _useSavedForLater() {
lineItemIds: itemIds,
},
});

savedForLaterList.value = moveResult?.data?.moveFromSavedForLater?.list;
} catch (err) {
if (err instanceof ApolloError && err.networkError?.toString() === (AbortReason.Explicit as string)) {
return;
Expand All @@ -77,29 +123,15 @@ function _useSavedForLater() {
}
}

async function getSavedForLater() {
try {
savedForLaterList.value = await getSavedForLaterQuery();
} catch (err) {
Logger.error(`useSavedForLater.${getSavedForLater.name}`, err);
}
}

return {
savedForLaterList,

moveToSavedForLater,
moveToSavedForLaterOverflowed,
moveFromSavedForLater,
moveFromSavedForLaterOverflowed,
getSavedForLater,

loading: computed(
() =>
_moveToSavedForLaterLoading.value ||
_moveToSavedForLaterBatchedLoading.value ||
_moveFromSavedForLaterLoading.value ||
_moveFromSavedForLaterBatchedLoading.value,
() => _getSavedForLaterLoading.value || _moveToSavedForLaterLoading.value || _moveFromSavedForLaterLoading.value,
),
};
}
Expand Down
Loading