Skip to content

Commit c18b22a

Browse files
committed
fix: Introduce ProductPrice component for consistent price display across views
1 parent 28c2cfe commit c18b22a

File tree

8 files changed

+127
-47
lines changed

8 files changed

+127
-47
lines changed

app/components/AppHeader.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,13 @@ const totalQuantity = computed(() => cart.value.reduce((s, i) => s + (i.quantity
180180
class="absolute h-full w-full dark:bg-neutral-800 bg-neutral-200 object-cover transition-opacity duration-300 group-hover:opacity-0" />
181181
</div>
182182
<div class="grid gap-0.5 pt-3 pb-4 px-1.5 text-sm font-semibold">
183-
<div class="flex gap-1">
184-
<div v-html="product.salePrice"></div>
185-
<div class="text-[#5f5f5f] dark:text-[#a3a3a3] line-through" v-html="product.regularPrice"></div>
186-
</div>
183+
<ProductPrice
184+
:sale-price="product.salePrice"
185+
:regular-price="product.regularPrice"
186+
variant="card" />
187187
<div>{{ product.name }}</div>
188188
<div class="font-normal text-[#5f5f5f] dark:text-[#a3a3a3]">
189-
{{ product.allPaStyle.nodes[0].name }}
189+
{{ product.allPaStyle?.nodes[0]?.name }}
190190
</div>
191191
</div>
192192
</div>

app/components/Cart.vue

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,11 @@ const { order } = useCheckout();
1919
<NuxtImg :src="product.variation.node.image.sourceUrl" class="w-24 h-28 object-cover shadow-md rounded-2xl" />
2020
<div class="flex-1 gap-1 flex flex-col">
2121
<div class="font-medium text-sm line-clamp-2 overflow-hidden text-ellipsis">{{ product.product.node.name }}</div>
22-
<div class="font-bold">${{ (Number(product.variation.node.salePrice) * product.quantity).toFixed(2) }}</div>
23-
<div class="flex-wrap text-neutral-600 dark:text-neutral-300 items-baseline text-xs gap-1 flex-row flex">
24-
<p>{{ $t('product.originally') }}:</p>
25-
<p class="line-through">${{ (Number(product.variation.node.regularPrice) * product.quantity).toFixed(2) }}</p>
26-
<p class="text-alizarin-crimson-700">-{{ ((1 - product.variation.node.salePrice / product.variation.node.regularPrice) * 100).toFixed(0) }}%</p>
27-
</div>
22+
<ProductPrice
23+
:sale-price="product.variation.node.salePrice"
24+
:regular-price="product.variation.node.regularPrice"
25+
:quantity="product.quantity"
26+
variant="cart" />
2827
<div class="text-xs flex gap-2 font-medium text-neutral-600 dark:text-neutral-300">
2928
<div>
3029
{{ $t('product.size') }}: {{ product.variation.attributes.map(attr => attr.value.toUpperCase()).join(', ') }} • {{ $t('product.quantity') }}:

app/components/Checkout.vue

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ const { userDetails, checkoutStatus, handleCheckout } = useCheckout();
44
const { cart } = useCart();
55
66
const totalQuantity = computed(() => cart.value.reduce((s, i) => s + (i.quantity || 0), 0));
7+
8+
const cartTotal = computed(() => {
9+
const total = cart.value.reduce((accumulator, item) => {
10+
const node = item.variation.node;
11+
const regularPrice = parseFloat(node.regularPrice) || 0;
12+
const salePrice = parseFloat(node.salePrice) || 0;
13+
14+
const priceToUse = salePrice > 0 && salePrice < regularPrice ? salePrice : regularPrice;
15+
16+
return accumulator + priceToUse * (item.quantity ?? 1);
17+
}, 0);
18+
19+
return total.toFixed(2);
20+
});
721
</script>
822
923
<template>
@@ -33,7 +47,7 @@ const totalQuantity = computed(() => cart.value.reduce((s, i) => s + (i.quantity
3347
<div class="text-sm font-semibold p-4 text-neutral-600 dark:text-neutral-400">
3448
{{
3549
$t('checkout.pay.description', {
36-
total: cart.reduce((total, item) => total + parseFloat(item.variation.node.salePrice) * (item.quantity ?? 1), 0).toFixed(2),
50+
total: cartTotal,
3751
items: totalQuantity,
3852
})
3953
}}
@@ -46,7 +60,7 @@ const totalQuantity = computed(() => cart.value.reduce((s, i) => s + (i.quantity
4660
<div v-if="checkoutStatus === 'order'" class="absolute">
4761
{{
4862
$t('checkout.pay.btn', {
49-
total: cart.reduce((total, item) => total + parseFloat(item.variation.node.salePrice) * (item.quantity ?? 1), 0).toFixed(2),
63+
total: cartTotal,
5064
})
5165
}}
5266
</div>

app/components/ProductCard.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ defineProps({
2626
class="absolute h-full w-full dark:bg-neutral-800 bg-neutral-200 object-cover transition-opacity duration-300 group-hover:opacity-0" />
2727
</div>
2828
<div class="grid gap-0.5 pt-3 pb-4 px-1.5 text-sm font-semibold">
29-
<div class="flex gap-1">
30-
<div v-html="product.salePrice"></div>
31-
<div class="text-[#5f5f5f] dark:text-[#a3a3a3] line-through" v-html="product.regularPrice"></div>
32-
</div>
29+
<ProductPrice
30+
:sale-price="product.salePrice"
31+
:regular-price="product.regularPrice"
32+
variant="card" />
3333
<div>{{ product.name }}</div>
3434
<div class="font-normal text-[#5f5f5f] dark:text-[#a3a3a3]">
35-
{{ product.allPaStyle.nodes[0].name }}
35+
{{ product.allPaStyle?.nodes[0]?.name }}
3636
</div>
3737
</div>
3838
</div>

app/components/ProductPrice.vue

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<script setup lang="ts">
2+
import {computed} from 'vue';
3+
4+
type PriceVariant = 'default' | 'card' | 'cart';
5+
6+
interface Props {
7+
salePrice: string;
8+
regularPrice: string;
9+
variant?: PriceVariant;
10+
quantity?: number; // Add the optional quantity prop
11+
}
12+
13+
const props = withDefaults(defineProps<Props>(), {
14+
variant: 'default',
15+
quantity: 1,
16+
});
17+
18+
const parsePrice = (priceString: string | undefined): number => {
19+
if (!priceString) return 0;
20+
return parseFloat(String(priceString).replace(/[^0-9.]/g, ''));
21+
};
22+
23+
const totalSalePrice = computed(() => parsePrice(props.salePrice) * props.quantity);
24+
const totalRegularPrice = computed(() => parsePrice(props.regularPrice) * props.quantity);
25+
26+
const isSale = computed(() => {
27+
const sale = parsePrice(props.salePrice);
28+
const regular = parsePrice(props.regularPrice);
29+
return sale > 0 && regular > 0 && sale < regular;
30+
});
31+
32+
const discountPercentage = computed(() => {
33+
if (!isSale.value) return 0;
34+
const sale = parsePrice(props.salePrice);
35+
const regular = parsePrice(props.regularPrice);
36+
return Math.round(((regular - sale) / regular) * 100);
37+
});
38+
</script>
39+
40+
<template>
41+
<div>
42+
<div v-if="variant === 'default'">
43+
<div v-if="isSale">
44+
<div class="flex items-baseline">
45+
<p class="text-xl font-bold text-alizarin-crimson-700" v-html="salePrice"></p>
46+
<p class="text-sm ml-2">{{ $t('product.vat_included') }}</p>
47+
</div>
48+
<div class="flex items-baseline">
49+
<p class="text-sm">{{ $t('product.originally') }}:</p>
50+
<p class="text-sm ml-1 line-through" v-html="regularPrice"></p>
51+
<p class="text-sm ml-1 text-alizarin-crimson-700">-{{ discountPercentage }}%</p>
52+
</div>
53+
</div>
54+
<div v-else>
55+
<div class="flex items-baseline">
56+
<p class="text-xl font-bold" v-html="regularPrice"></p>
57+
<p class="text-sm ml-2">{{ $t('product.vat_included') }}</p>
58+
</div>
59+
</div>
60+
</div>
61+
62+
<div v-else-if="variant === 'card'" class="flex gap-2 text-sm">
63+
<template v-if="isSale">
64+
<span v-html="salePrice"></span>
65+
<span class="line-through text-white/50" v-html="regularPrice"></span>
66+
</template>
67+
<template v-else>
68+
<span v-html="regularPrice"></span>
69+
</template>
70+
</div>
71+
72+
<div v-else-if="variant === 'cart'">
73+
<div v-if="isSale">
74+
<div class="font-bold">${{ totalSalePrice.toFixed(2) }}</div>
75+
<div class="flex-wrap text-neutral-600 dark:text-neutral-300 items-baseline text-xs gap-1 flex-row flex">
76+
<p>{{ $t('product.originally') }}:</p>
77+
<p class="line-through">${{ totalRegularPrice.toFixed(2) }}</p>
78+
<p class="text-alizarin-crimson-700">-{{ discountPercentage }}%</p>
79+
</div>
80+
</div>
81+
<div v-else>
82+
<div class="font-bold">${{ totalRegularPrice.toFixed(2) }}</div>
83+
</div>
84+
</div>
85+
</div>
86+
</template>

app/gql/queries/getProduct.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ export const getProductQuery = gql`
2020
sourceUrl(size: LARGE)
2121
}
2222
}
23-
allPaColor {
23+
allPaStyle {
2424
nodes {
2525
name
2626
}
2727
}
28-
allPaStyle {
28+
allPaColor {
2929
nodes {
3030
name
3131
}
@@ -67,7 +67,7 @@ export const getProductQuery = gql`
6767
name
6868
regularPrice
6969
salePrice
70-
allPaStyle {
70+
allPaColor {
7171
nodes {
7272
name
7373
}

app/pages/favorites.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,9 @@ useSeoMeta({
3939
class="absolute inset-0 bg-gradient-to-t from-black/50 hover:from-black/60 flex items-end p-5"
4040
:to="localePath(`/product/${product.slug}-${product.sku.split('-')[0]}`)">
4141
<div class="grid gap-0.5 text-white">
42-
<div class="flex gap-2 text-sm">
43-
<span v-html="product.salePrice"></span>
44-
<span class="line-through text-white/50" v-html="product.regularPrice"></span>
45-
</div>
42+
<ProductPrice :sale-price="product.salePrice" :regular-price="product.regularPrice" variant="card" />
4643
<div class="font-bold">{{ product.name }}</div>
47-
<div class="text-sm font-medium">{{ product.allPaStyle.nodes[0].name }}</div>
44+
<div class="text-sm font-medium">{{ product.allPaStyle?.nodes[0]?.name }}</div>
4845
</div>
4946
</NuxtLink>
5047
</div>

app/pages/product/[id].vue

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const localePath = useLocalePath();
88
import 'swiper/css';
99
import 'swiper/css/navigation';
1010
import 'swiper/css/pagination';
11+
import ProductPrice from "~/components/ProductPrice.vue";
1112
1213
const thumbsSwiper = ref(null);
1314
const setThumbsSwiper = swiper => {
@@ -51,13 +52,6 @@ watchEffect(() => {
5152
}
5253
});
5354
54-
const calculateDiscountPercentage = computed(() => {
55-
if (!product.value.salePrice || !product.value.regularPrice) return 0;
56-
const salePriceValue = parseFloat(product.value.salePrice.replace(/[^0-9]/g, ''));
57-
const regularPriceValue = parseFloat(product.value.regularPrice.replace(/[^0-9]/g, ''));
58-
return Math.round(((salePriceValue - regularPriceValue) / regularPriceValue) * 100);
59-
});
60-
6155
const { name } = useAppConfig();
6256
const url = useRequestURL();
6357
const canonical = url.origin + url.pathname;
@@ -159,17 +153,7 @@ const { handleAddToCart, addToCartButtonStatus } = useCart();
159153
<div class="flex-col flex gap-4 lg:max-h-[530px] xl:max-h-[600px] overflow-hidden">
160154
<div class="p-3 lg:pb-4 lg:p-0 border-b border-[#efefef] dark:border-[#262626]">
161155
<h1 class="text-2xl font-semibold mb-1">{{ product.name }}</h1>
162-
<div class="flex justify-between flex-row items-baseline">
163-
<div class="flex flex-row items-baseline">
164-
<p class="text-xl font-bold text-alizarin-crimson-700" v-html="product.salePrice"></p>
165-
<p class="text-sm ml-2">{{ $t('product.vat_included') }}</p>
166-
</div>
167-
</div>
168-
<div class="flex-wrap items-baseline flex-row flex">
169-
<p class="text-sm">{{ $t('product.originally') }}:</p>
170-
<p class="text-sm ml-1 line-through" v-html="product.regularPrice"></p>
171-
<p class="text-sm ml-1 text-alizarin-crimson-700">{{ calculateDiscountPercentage }}%</p>
172-
</div>
156+
<ProductPrice :sale-price="product.salePrice" :regular-price="product.regularPrice" />
173157
</div>
174158
175159
<div class="flex gap-2 px-3 lg:px-0" v-for="(variation, i) in product.productTypes?.nodes" :key="i">
@@ -178,12 +162,12 @@ const { handleAddToCart, addToCartButtonStatus } = useCart();
178162
:to="localePath(`/product/${vars.slug}-${product.sku.split('-')[0]}`)"
179163
:class="[
180164
'flex w-12 rounded-lg border-2 select-varitaion transition-all duration-200 bg-neutral-200 dark:bg-neutral-800',
181-
vars.allPaColor.nodes[0].name === product.allPaColor.nodes[0].name ? 'selected-varitaion' : 'border-[#9b9b9b] dark:border-[#8c8c8c]',
165+
vars.allPaStyle?.nodes[0]?.name === product.allPaStyle.nodes[0].name ? 'selected-varitaion' : 'border-[#9b9b9b] dark:border-[#8c8c8c]',
182166
]">
183167
<NuxtImg
184-
:alt="vars.allPaColor.nodes[0].name"
168+
:alt="vars.allPaColor?.nodes[0]?.name"
185169
:src="vars.image.sourceUrl"
186-
:title="vars.allPaColor.nodes[0].name"
170+
:title="vars.allPaColor?.nodes[0]?.name"
187171
class="rounded-md border-2 border-white dark:border-black" />
188172
</NuxtLink>
189173
</div>

0 commit comments

Comments
 (0)