Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions client-app/config/menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@
"icon": "clipboard-list",
"priority": 50
},
{
"id": "saved-for-later",
"route": {
"name": "SavedForLater"
Copy link

Choose a reason for hiding this comment

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

Bug: Route Naming Inconsistency

The route name "SavedForLater" is hardcoded as a string instead of using the constant ROUTES.SAVED_FOR_LATER.NAME. Based on the PR discussion comment, this should use the constant from routes/constants.ts for consistency.

Fix in Cursor Fix in Web

},
"title": "shared.layout.header.mobile.account_menu.saved_for_later",
"icon": "list-v2",
"priority": 55
},
{
"id": "lists",
"route": {
Expand Down Expand Up @@ -236,6 +245,14 @@
"icon": "clipboard-list",
"priority": 40
},
{
"route": {
"name": "SavedForLater"
},
"title": "shared.layout.header.mobile.account_menu.saved_for_later",
"icon": "list-v2",
"priority": 45
},
{
"route": {
"name": "Lists"
Expand Down
28 changes: 13 additions & 15 deletions client-app/pages/account/list-details.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
<div class="flex flex-col">
<!-- Title block -->
<div class="contents md:flex md:flex-wrap md:items-center md:justify-between md:gap-3">
<VcTypography v-if="list?.name" tag="h1" truncate>
{{ list.name }}
<VcTypography v-if="actualListName" tag="h1" truncate>
{{ actualListName }}
</VcTypography>

<!-- Title skeleton -->
<div v-else class="w-2/3 bg-neutral-200 text-3xl md:w-1/3">&nbsp;</div>
<div v-else class="w-2/3 bg-neutral-200 text-3xl md:w-1/3">{{ props.listName ?? "&nbsp;" }}</div>
Copy link

Choose a reason for hiding this comment

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

Bug: Template Interpolation Renders HTML Entity

When props.listName is not provided, the skeleton title displays the literal string " ". This happens because &nbsp; is now within a template interpolation, causing it to render as text instead of an HTML non-breaking space.

Fix in Cursor Fix in Web


<div class="order-last mt-8 flex flex-wrap gap-3 md:ms-0 md:mt-0 md:shrink-0 lg:my-0">
<VcButton
Expand Down Expand Up @@ -49,6 +49,7 @@
</VcButton>

<VcButton
v-if="!hideSettings"
:disabled="loading || !list"
size="sm"
variant="outline"
Expand All @@ -63,15 +64,7 @@

<div ref="listElement" class="mt-5 w-full">
<!-- Skeletons -->
<template v-if="listLoading">
<div v-if="isMobile" class="grid grid-cols-2 gap-x-4 gap-y-6">
<ProductSkeletonGrid v-for="i in actualPageRowsCount" :key="i" />
</div>

<div v-else class="flex flex-col rounded border bg-additional-50 shadow-sm">
<WishlistProductItemSkeleton v-for="i in actualPageRowsCount" :key="i" class="even:bg-neutral-50" />
</div>
</template>
<WishlistProductsSkeleton v-if="listLoading" :itemsCount="actualPageRowsCount" />

<!-- List details -->
<template v-else-if="!listLoading && !!list?.items?.length">
Expand Down Expand Up @@ -137,7 +130,6 @@ import { prepareLineItem, Logger } from "@/core/utilities";
import { ROUTES } from "@/router/routes/constants";
import { dataChangedEvent, useBroadcast } from "@/shared/broadcast";
import { useShortCart, getItemsForAddBulkItemsToCartResultsModal } from "@/shared/cart";
import { ProductSkeletonGrid } from "@/shared/catalog";
import { SaveChangesModal } from "@/shared/common";
import { BackButtonInHeader } from "@/shared/layout";
import { useModal } from "@/shared/modal";
Expand All @@ -146,7 +138,7 @@ import {
AddOrUpdateWishlistModal,
DeleteWishlistProductModal,
WishlistLineItems,
WishlistProductItemSkeleton,
WishlistProductsSkeleton,
} from "@/shared/wishlists";
import type {
InputUpdateWishlistItemsType,
Expand All @@ -160,9 +152,13 @@ import AddBulkItemsToCartResultsModal from "@/shared/cart/components/add-bulk-it

interface IProps {
listId: string;
hideSettings?: boolean;
listName?: string;
}

const props = defineProps<IProps>();
const props = withDefaults(defineProps<IProps>(), {
listName: undefined,
});

const Error404 = defineAsyncComponent(() => import("@/pages/404.vue"));

Expand Down Expand Up @@ -224,6 +220,8 @@ const wishlistListProperties = computed(() => ({
related_type: "wishlist",
}));

const actualListName = computed(() => props.listName ?? list.value?.name);

const isMobile = breakpoints.smaller("lg");

function openListSettingsModal(): void {
Expand Down
44 changes: 44 additions & 0 deletions client-app/pages/account/saved-for-later-details.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<template v-if="saveForLaterLoading">
<div class="contents md:flex md:flex-wrap md:items-center md:justify-between md:gap-3">
<VcTypography tag="h1">
{{ $t("pages.cart.saved_for_later") }}
</VcTypography>
</div>

<div class="mt-5 w-full">
<WishlistProductsSkeleton :itemsCount="6" />
</div>
</template>

<ListDetails
v-else-if="savedForLaterListId && !savedForLaterListIsEmpty"
:list-id="savedForLaterListId"
:list-name="$t('pages.cart.saved_for_later')"
hide-settings
/>

<div v-else>
<VcTypography tag="h1">
{{ $t("pages.cart.saved_for_later") }}
</VcTypography>

<VcEmptyView :text="$t('pages.cart.saved_for_later_not_found')" icon="list-v2" />
</div>
</template>

<script lang="ts" setup>
import { computed, onMounted } from "vue";
import { useSavedForLater } from "@/shared/cart/composables/useSaveForLater";
import { WishlistProductsSkeleton } from "@/shared/wishlists";
import ListDetails from "./list-details.vue";

const { savedForLaterList, loading: saveForLaterLoading, getSavedForLater } = useSavedForLater();

const savedForLaterListId = computed(() => savedForLaterList.value?.id);
const savedForLaterListIsEmpty = computed(() => !savedForLaterList.value?.items?.length);

onMounted(() => {
void getSavedForLater();
});
</script>
33 changes: 2 additions & 31 deletions client-app/pages/shared-list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,7 @@

<div ref="listElement" class="shared-list__content">
<!-- Skeletons -->
<template v-if="listLoading">
<div v-if="isMobile" class="shared-list__skeleton-mobile">
<ProductSkeletonGrid v-for="i in actualPageRowsCount" :key="i" />
</div>

<div v-else class="shared-list__skeleton-desktop">
<WishlistProductItemSkeleton
v-for="i in actualPageRowsCount"
:key="i"
class="shared-list__skeleton-desktop-item"
/>
</div>
</template>
<WishlistProductsSkeleton v-if="listLoading" :itemsCount="actualPageRowsCount" />

<!-- List details -->
<template v-else-if="!listLoading && !!list?.items?.length">
Expand Down Expand Up @@ -74,7 +62,6 @@
</template>

<script setup lang="ts">
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
import cloneDeep from "lodash/cloneDeep";
import keyBy from "lodash/keyBy";
import { computed, ref, watchEffect, defineAsyncComponent } from "vue";
Expand All @@ -85,11 +72,9 @@ import { PAGE_LIMIT } from "@/core/constants";
import { MODULE_XAPI_KEYS } from "@/core/constants/modules";
import { prepareLineItem } from "@/core/utilities";
import { useShortCart } from "@/shared/cart";
import { ProductSkeletonGrid } from "@/shared/catalog";
import { useWishlists, WishlistLineItems, WishlistProductItemSkeleton } from "@/shared/wishlists";
import { useWishlists, WishlistLineItems, WishlistProductsSkeleton, WishlistSummary } from "@/shared/wishlists";
import type { LineItemType, Product } from "@/core/api/graphql/types";
import type { PreparedLineItemType } from "@/core/types";
import WishlistSummary from "@/shared/wishlists/components/wishlist-summary.vue";

const props = defineProps<IProps>();

Expand All @@ -108,7 +93,6 @@ const { cart } = useShortCart();
const { continue_shopping_link } = getModuleSettings({
[MODULE_XAPI_KEYS.CONTINUE_SHOPPING_LINK]: "continue_shopping_link",
});
const breakpoints = useBreakpoints(breakpointsTailwind);

usePageHead({
title: computed(() => t("pages.account.list_details.meta.title", [list.value?.name])),
Expand All @@ -120,7 +104,6 @@ const wishlistListProperties = computed(() => ({
related_id: list.value?.id,
related_type: "wishlist",
}));
const isMobile = breakpoints.smaller("lg");

const listElement = ref<HTMLElement | undefined>();
const pendingItems = ref<Record<string, boolean>>({});
Expand Down Expand Up @@ -175,18 +158,6 @@ watchEffect(() => {
@apply mt-5 w-full;
}

&__skeleton-mobile {
@apply grid grid-cols-2 gap-x-4 gap-y-6;
}

&__skeleton-desktop {
@apply flex flex-col rounded border bg-additional-50 shadow-sm;
}

&__skeleton-desktop-item {
@apply even:bg-neutral-50;
}

&__items {
@apply flex flex-col gap-6;
}
Expand Down
6 changes: 6 additions & 0 deletions client-app/router/routes/account.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ROUTES } from "@/router/routes/constants";
import { useUser } from "@/shared/account";
import type { RouteRecordRaw } from "vue-router";

Expand All @@ -10,6 +11,7 @@ const OrderDetails = () => import("@/pages/account/order-details.vue");
const OrderPayment = () => import("@/pages/account/order-payment.vue");
const Lists = () => import("@/pages/account/lists.vue");
const ListDetails = () => import("@/pages/account/list-details.vue");
const SavedForLaterDetails = () => import("@/pages/account/saved-for-later-details.vue");
const SavedCreditCards = () => import("@/pages/account/saved-credit-cards.vue");
const Impersonate = () => import("@/pages/account/impersonate.vue");

Expand Down Expand Up @@ -56,6 +58,10 @@ export const accountRoutes: RouteRecordRaw[] = [
},
],
},
{
path: "saved-for-later",
children: [{ path: "", name: ROUTES.SAVED_FOR_LATER.NAME, component: SavedForLaterDetails }],
},
{
path: "lists",
children: [
Expand Down
3 changes: 3 additions & 0 deletions client-app/router/routes/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ export const ROUTES = {
NAME: "ChangePassword",
PATH: "/change-password",
},
SAVED_FOR_LATER: {
NAME: "SavedForLater",
},
} as const;
7 changes: 7 additions & 0 deletions client-app/shared/cart/components/cart-for-later.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<template>
<VcWidget :title="$t('pages.cart.saved_for_later')" prepend-icon="bookmark" size="lg">
<template v-slot:append>
<VcButton variant="outline" :to="{ name: ROUTES.SAVED_FOR_LATER.NAME }" size="sm">
{{ $t("common.buttons.see_all") }}
</VcButton>
</template>

<VcProductsGrid short>
<CartItemForLater
v-for="(item, index) in savedForLaterList?.items"
Expand All @@ -19,6 +25,7 @@
import { computed, toRef } from "vue";
import { useI18n } from "vue-i18n";
import { useAnalytics } from "@/core/composables/useAnalytics";
import { ROUTES } from "@/router/routes/constants";
import type { SavedForLaterListFragment, Product } from "@/core/api/graphql/types";
import CartItemForLater from "@/shared/cart/components/cart-item-for-later.vue";

Expand Down
7 changes: 6 additions & 1 deletion client-app/shared/cart/composables/useSaveForLater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,15 @@ function _useSavedForLater() {
}
}

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

Expand All @@ -99,7 +103,8 @@ function _useSavedForLater() {
_moveToSavedForLaterLoading.value ||
_moveToSavedForLaterBatchedLoading.value ||
_moveFromSavedForLaterLoading.value ||
_moveFromSavedForLaterBatchedLoading.value,
_moveFromSavedForLaterBatchedLoading.value ||
_getSavedForLaterLoading.value,
),
};
}
Expand Down
2 changes: 2 additions & 0 deletions client-app/shared/wishlists/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export { default as WishlistCard } from "./wishlist-card.vue";
export { default as WishlistCardSkeleton } from "./wishlist-card-skeleton.vue";
export { default as WishlistLineItems } from "./wishlist-line-items.vue";
export { default as WishlistProductItemSkeleton } from "./wishlist-product-item-skeleton.vue";
export { default as WishlistProductsSkeleton } from "./wishlist-products-skeleton.vue";
export { default as WishlistSummary } from "./wishlist-summary.vue";
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<template>
<div v-if="isMobile" class="wishlist-products-skeleton wishlist-products-skeleton--mobile">
<ProductSkeletonGrid v-for="i in itemsCount" :key="i" class="wishlist-products-skeleton__item" />
</div>

<div v-else class="wishlist-products-skeleton wishlist-products-skeleton--desktop">
<WishlistProductItemSkeleton v-for="i in itemsCount" :key="i" class="wishlist-products-skeleton__item" />
</div>
</template>

<script lang="ts" setup>
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
import { ProductSkeletonGrid } from "@/shared/catalog";
import { WishlistProductItemSkeleton } from "@/shared/wishlists";

interface IProps {
itemsCount: number;
}

defineProps<IProps>();

const breakpoints = useBreakpoints(breakpointsTailwind);

const isMobile = breakpoints.smaller("lg");
</script>

<style lang="scss">
.wishlist-products-skeleton {
$desktop: "";

&--mobile {
@apply grid grid-cols-2 gap-x-4 gap-y-6;
}

&--desktop {
@apply flex flex-col rounded border bg-additional-50 shadow-sm;

$desktop: &;
}

&__item {
#{$desktop} & {
@apply even:bg-neutral-50;
}
}
}
Copy link

Choose a reason for hiding this comment

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

Bug: CSS Class Naming Violates BEM Convention

The CSS class naming does not follow the BEM naming convention as specifically requested by the reviewer. The classes should be named based on the component name (e.g., "wishlist-products-skeleton wishlist-products-skeleton--mobile") instead of generic names like "skeleton-mobile" and "skeleton-desktop". This violates the project's naming standards as indicated in the PR discussion.

Fix in Cursor Fix in Web

</style>
5 changes: 4 additions & 1 deletion locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@
"show_less": "Weniger anzeigen",
"expand_all": "Alle ausklappen",
"all_products": "Alle Produkte",
"buy_now": "Jetzt kaufen"
"buy_now": "Jetzt kaufen",
"see_all": "Alle anzeigen"
},
"labels": {
"actions": "Aktionen",
Expand Down Expand Up @@ -335,6 +336,7 @@
"change_password": "@:common.links.change_password",
"addresses": "@:common.links.addresses",
"orders": "@:common.links.orders",
"saved_for_later": "Für später gespeichert",
"lists": "@:common.links.lists",
"saved_credit_cards": "@:common.links.saved_credit_cards"
},
Expand Down Expand Up @@ -1318,6 +1320,7 @@
"recently_browsed_products": "Kürzlich angesehen",
"save_for_later": "Für später speichern",
"saved_for_later": "Für später gespeichert",
"saved_for_later_not_found": "Sie haben noch keine Produkte für später gespeichert",
"move_to_cart": "In den Warenkorb"
},
"checkout": {
Expand Down
Loading
Loading