Skip to content

Commit c38f7b2

Browse files
committed
Reusable price component
1 parent f6e13e2 commit c38f7b2

File tree

8 files changed

+368
-257
lines changed

8 files changed

+368
-257
lines changed
Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
11
<template>
2-
<img id="product-image"
3-
class="h-auto p-8 transition duration-500 ease-in-out transform md:p-0 hover:shadow-lg hover:scale-95" :alt="alt"
4-
:src="displayedImage" :width="width" :height="height" />
2+
<img
3+
id="product-image"
4+
class="h-auto p-8 transition duration-500 ease-in-out transform md:p-0 hover:shadow-lg hover:scale-95"
5+
:alt="alt"
6+
:src="displayedImage"
7+
:width="width"
8+
:height="height"
9+
/>
510
</template>
611

712
<script setup>
813
import { computed } from "vue";
914
1015
const props = defineProps({
11-
alt: { type: String, required: true },
12-
src: { type: String, required: true },
13-
width: { type: String, required: false },
14-
height: { type: String, required: false },
16+
alt: { type: String, required: true },
17+
src: { type: String, required: true },
18+
width: { type: String, required: false },
19+
height: { type: String, required: false },
1520
});
1621
1722
const config = useRuntimeConfig();
1823
1924
const displayedImage = computed(() => {
20-
return props.src || config.public.placeholderImage;
25+
return props.src || config.public.placeholderImage;
2126
});
2227
</script>
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<template>
2+
<div>
3+
<div
4+
v-if="onSale"
5+
class="flex"
6+
:class="shouldCenterPrice ? 'justify-center' : ''"
7+
>
8+
<p class="pt-1 mt-4 text-gray-900" :class="getFontSizeClass">
9+
<span v-if="hasVariations"> {{ formatPrice(variantPrice) }}</span>
10+
<span v-else>{{ formatPrice(salePrice) }}</span>
11+
</p>
12+
<p
13+
v-if="hasVariations"
14+
class="pt-1 pl-8 mt-4 text-gray-900 line-through"
15+
:class="getSaleFontSizeClass"
16+
>
17+
>
18+
{{ formatPrice(variantPrice) }}
19+
</p>
20+
<p
21+
v-else
22+
class="pt-1 pl-8 mt-4 text-gray-900 line-through"
23+
:class="getSaleFontSizeClass"
24+
>
25+
{{ formatPrice(regularPrice) }}
26+
</p>
27+
</div>
28+
<p
29+
v-else
30+
class="flex justify-center pt-1 mt-4 text-gray-900"
31+
:class="getSaleFontSizeClass"
32+
>
33+
{{ formatPrice(nonSalePrice) }}
34+
</p>
35+
</div>
36+
</template>
37+
38+
<script setup>
39+
/**
40+
* Vue.js component that renders a product's price.
41+
*
42+
* @typedef {Object} PriceComponentProps
43+
* @property {string} [variantPrice] - The price of a product's variant.
44+
* @property {boolean} onSale - Whether or not the product is on sale.
45+
* @property {boolean} hasVariations - Whether or not the product has variations.
46+
* @property {string} [salePrice] - The sale price of a product.
47+
* @property {string} regularPrice - The regular price of a product.
48+
* @property {string} nonSalePrice - The price of a product that is not on sale.
49+
* @property {string} [priceFontSize] - The font size of the price.
50+
* @property {boolean} [shouldCenterPrice] - Whether or not the price should be centered.
51+
*
52+
* @typedef {Object} PriceComponentComputedProperties
53+
* @property {string} getFontSizeClass - The font size CSS class for the regular price.
54+
* @property {string} getSaleFontSizeClass - The font size CSS class for the sale price.
55+
*
56+
* @typedef {Object} PriceComponentExports
57+
* @property {PriceComponentProps} props - The component's props.
58+
* @property {PriceComponentComputedProperties} computed - The component's computed properties.
59+
*
60+
* @type {PriceComponentExports}
61+
*/
62+
63+
import { formatPrice } from "@/utils/functions";
64+
65+
const props = defineProps({
66+
variantPrice: { type: String, required: false },
67+
onSale: { type: Boolean, required: true },
68+
hasVariations: { type: Boolean, required: true },
69+
salePrice: { type: String, required: false },
70+
regularPrice: { type: String, required: true },
71+
nonSalePrice: { type: String, required: true },
72+
priceFontSize: { type: String, required: false },
73+
shouldCenterPrice: { type: Boolean, required: false },
74+
});
75+
76+
const getFontSizeClass = computed(() => {
77+
switch (props.priceFontSize) {
78+
case "small":
79+
return "text-lg";
80+
case "normal":
81+
return "text-xl";
82+
case "big":
83+
return "text-2xl";
84+
default:
85+
return "text-xl";
86+
}
87+
});
88+
89+
const getSaleFontSizeClass = computed(() => {
90+
switch (props.priceFontSize) {
91+
case "small":
92+
return "text-lg";
93+
case "normal":
94+
return "text-lg";
95+
case "big":
96+
return "text-xl";
97+
default:
98+
return "text-xl";
99+
}
100+
});
101+
</script>

components/Products/ProductsShowAll.vue

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,32 +66,22 @@
6666
}"
6767
>
6868
<ProductImage :alt="product.name" :src="productImage(product)" />
69-
7069
<div class="flex justify-center pt-3">
7170
<p class="text-2xl font-bold text-center cursor-pointer">
7271
{{ product.name }}
7372
</p>
7473
</div>
7574
</NuxtLink>
76-
<div v-if="product.onSale" class="flex justify-center mt-2">
77-
<div class="text-lg text-gray-900 line-through">
78-
<span v-if="product.variations">
79-
{{ filteredVariantPrice(product.price, "right") }}</span
80-
>
81-
<span v-else>{{ product.regularPrice }}</span>
82-
</div>
83-
<div class="ml-4 text-xl text-gray-900">
84-
<span v-if="product.variations">
85-
{{ filteredVariantPrice(product.price) }}</span
86-
>
87-
<span v-else>{{ product.salePrice }}</span>
88-
</div>
89-
</div>
90-
<div v-else>
91-
<p class="mt-2 text-xl text-center text-gray-900">
92-
{{ product.price }}
93-
</p>
94-
</div>
75+
<ProductPrice
76+
:variantPrice="filteredVariantPrice(product.price)"
77+
:onSale="product.onSale"
78+
:hasVariations="hasVariations(product)"
79+
:salePrice="product.salePrice"
80+
:regularPrice="product.regularPrice"
81+
:nonSalePrice="product.price"
82+
priceFontSize="normal"
83+
shouldCenterPrice="true"
84+
/>
9585
</div>
9686
</template>
9787
</div>
@@ -104,8 +94,9 @@ import FETCH_ALL_PRODUCTS_QUERY from "@/apollo/queries/FETCH_ALL_PRODUCTS_QUERY.
10494
import GET_PRODUCTS_FROM_CATEGORY_QUERY from "@/apollo/queries/GET_PRODUCTS_FROM_CATEGORY_QUERY.gql";
10595
10696
import ProductImage from "@/components/Products/ProductImage.vue";
97+
import ProductPrice from "@/components/Products/ProductPrice.vue";
10798
108-
import { filteredVariantPrice } from "@/utils/functions";
99+
import { filteredVariantPrice, hasVariations } from "@/utils/functions";
109100
110101
const props = defineProps({
111102
categoryId: { type: String, required: false },
@@ -115,7 +106,7 @@ const props = defineProps({
115106
const config = useRuntimeConfig();
116107
117108
const productImage = (product) =>
118-
product.image ? product.image.sourceUrl : config.placeholderImage;
109+
product.image ? product.image.sourceUrl : config.public.placeholderImage;
119110
120111
const productVariables = { limit: 99 };
121112
const { data: allProducts } = await useAsyncQuery(

components/Products/ProductsSingleProduct.vue

Lines changed: 31 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,35 @@
22
<div v-if="data?.product">
33
<section>
44
<div class="container flex flex-wrap items-center pt-4 pb-12 mx-auto">
5-
<div
6-
class="grid grid-cols-1 gap-4 mt-8 lg:grid-cols-2 xl:grid-cols-2 md:grid-cols-2 sm:grid-cols-2"
7-
>
8-
<ProductImage
9-
:alt="data.product.name"
10-
:src="data.product.image.sourceUrl"
11-
/>
5+
<div class="grid grid-cols-1 gap-4 mt-8 lg:grid-cols-2 xl:grid-cols-2 md:grid-cols-2 sm:grid-cols-2">
6+
<ProductImage :alt="data.product.name" :src="data.product.image.sourceUrl" />
127
<div class="ml-8">
138
<p class="text-3xl font-bold text-left">
149
{{ data.product.name }}
1510
</p>
16-
<div v-if="data.product.onSale" class="flex">
17-
<p class="pt-1 mt-4 text-3xl text-gray-900">
18-
<span v-if="data.productvariations">
19-
{{ filteredVariantPrice(data.product.price) }}</span
20-
>
21-
<span v-else>{{ data.product.salePrice }}</span>
22-
</p>
23-
<p class="pt-1 pl-8 mt-4 text-2xl text-gray-900 line-through">
24-
<span v-if="data.productvariations">
25-
{{ filteredVariantPrice(data.product.price, "right") }}</span
26-
>
27-
<span v-else>{{ data.product.regularPrice }}</span>
28-
</p>
29-
</div>
30-
<p v-else class="pt-1 mt-4 text-2xl text-gray-900">
31-
{{ data.product.price }}
32-
</p>
33-
<br />
34-
<p class="pt-1 mt-4 text-2xl text-gray-900">
11+
<ProductPrice :variantPrice="filteredVariantPrice(data.product.price)" :onSale="data.product.onSale"
12+
:hasVariations="hasVariations(data)" :salePrice="data.product.salePrice"
13+
:regularPrice="data.product.regularPrice" :nonSalePrice="data.product.price" priceFontSize="big" />
14+
<p class="pt-1 mt-6 text-2xl text-gray-900">
3515
{{ stripHTML(data.product.description) }}
3616
</p>
37-
<p
38-
v-if="data.product.stockQuantity"
39-
class="pt-1 mt-4 text-2xl text-gray-900"
40-
>
17+
<p v-if="data.product.stockQuantity" class="pt-1 mt-4 text-2xl text-gray-900">
4118
{{ data.product.stockQuantity }} in stock
4219
</p>
43-
<p
44-
v-if="data.product.variations"
45-
class="pt-1 mt-4 text-xl text-gray-900"
46-
>
20+
<p v-if="data.product.variations" class="pt-1 mt-4 text-xl text-gray-900">
4721
<span class="py-2">Varianter</span>
48-
<select
49-
id="variant"
50-
name="variant"
51-
class="block w-64 px-6 py-2 bg-white border border-gray-500 rounded-lg focus:outline-none focus:shadow-outline"
52-
>
53-
<option
54-
v-for="(variation, index) in data.product.variations.nodes"
55-
:key="variation.databaseId"
56-
:value="variation.databaseId"
57-
:selected="index === 0"
58-
>
22+
<select id="variant" name="variant"
23+
class="block w-64 px-6 py-2 bg-white border border-gray-500 rounded-lg focus:outline-none focus:shadow-outline">
24+
<option v-for="(variation, index) in data.product.variations.nodes" :key="variation.databaseId"
25+
:value="variation.databaseId" :selected="index === 0">
5926
{{ filteredVariantName(data.product.name, variation.name) }}
6027
({{ variation.stockQuantity }} in stock)
6128
</option>
6229
</select>
6330
</p>
6431
<div class="pt-1 mt-2">
65-
<CommonButton
66-
@common-button-click="addProductToCart(data.product)"
67-
:is-loading="isLoading"
68-
>
69-
ADD TO CART</CommonButton
70-
>
32+
<CommonButton @common-button-click="addProductToCart(data.product)" :is-loading="isLoading">
33+
ADD TO CART</CommonButton>
7134
</div>
7235
</div>
7336
</div>
@@ -77,15 +40,26 @@
7740
</template>
7841

7942
<script setup>
43+
/**
44+
* Vue.js component that displays a single product.
45+
*
46+
* @component
47+
* @example
48+
* <single-product id="1" slug="example-product"></single-product>
49+
* @prop {string} id - The ID of the product to display.
50+
* @prop {string} slug - The slug of the product to display.
51+
*/
8052
import GET_SINGLE_PRODUCT_QUERY from "@/apollo/queries/GET_SINGLE_PRODUCT_QUERY.gql";
8153
import ADD_TO_CART_MUTATION from "@/apollo/mutations/ADD_TO_CART_MUTATION.gql";
8254
8355
import ProductImage from "@/components/Products/ProductImage.vue";
56+
import ProductPrice from "@/components/Products/ProductPrice.vue";
8457
8558
import {
8659
stripHTML,
8760
filteredVariantPrice,
8861
filteredVariantName,
62+
hasVariations,
8963
} from "@/utils/functions";
9064
9165
import { useCart } from "@/store/useCart";
@@ -102,6 +76,12 @@ const props = defineProps({
10276
const variables = { id: props.id, slug: props.slug };
10377
const { data } = await useAsyncQuery(GET_SINGLE_PRODUCT_QUERY, variables);
10478
79+
/**
80+
* Adds a product to the cart by calling the addToCart mutation with the given product.
81+
*
82+
* @param {object} product - The product to add to the cart.
83+
* @return {Promise<void>} A Promise that resolves when the product has been successfully added to the cart.
84+
*/
10585
const addProductToCart = async (product) => {
10686
const productId = product.databaseId ? product.databaseId : product;
10787
const productQueryInput = {

nuxt.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export default defineNuxtConfig({
2121
graphqlURL: process.env.PUBLIC_GRAPHQL_URL,
2222
indexName: process.env.PUBLIC_ALGOLIA_INDEX_NAME,
2323
placeholderImage: process.env.PUBLIC_PLACEHOLDER_SMALL_IMAGE_URL,
24-
currencySymbol: process.env.PUBLIC_CURRENCY_SYMBOL,
24+
currencyLocale: process.env.PUBLIC_CURRENCY_LOCALE,
25+
currency: process.env.PUBLIC_CURRENCY,
2526
},
2627
},
2728
postcss: {

0 commit comments

Comments
 (0)