|
1 |
| -import React, { useState, useEffect } from 'react'; |
| 1 | +/*eslint complexity: ["error", 20]*/ |
| 2 | +// Imports |
| 3 | +import { useState, useEffect } from 'react'; |
| 4 | + |
| 5 | +// Utils |
2 | 6 | import { filteredVariantPrice, paddedPrice } from '@/utils/functions/functions';
|
| 7 | + |
| 8 | +// Components |
3 | 9 | import AddToCart, { IProductRootObject } from './AddToCart.component';
|
4 | 10 | import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner.component';
|
5 | 11 |
|
6 |
| -const SingleProduct: React.FC<IProductRootObject> = ({ |
7 |
| - product, |
8 |
| - variationId: initialVariationId, |
9 |
| -}) => { |
| 12 | +const SingleProduct = ({ product }: IProductRootObject) => { |
10 | 13 | const [isLoading, setIsLoading] = useState<boolean>(true);
|
11 |
| - const [selectedVariation, setSelectedVariation] = useState< |
12 |
| - number | undefined |
13 |
| - >(initialVariationId); |
| 14 | + const [selectedVariation, setSelectedVariation] = useState<number>(); |
14 | 15 |
|
15 | 16 | const placeholderFallBack = 'https://via.placeholder.com/600';
|
16 | 17 |
|
17 |
| - let DESCRIPTION_WITHOUT_HTML: string | null = null; |
| 18 | + let DESCRIPTION_WITHOUT_HTML; |
18 | 19 |
|
19 | 20 | useEffect(() => {
|
20 | 21 | setIsLoading(false);
|
21 |
| - if (product.variations && !selectedVariation) { |
| 22 | + if (product.variations) { |
22 | 23 | const firstVariant = product.variations.nodes[0].databaseId;
|
23 | 24 | setSelectedVariation(firstVariant);
|
24 | 25 | }
|
25 |
| - }, [product.variations, selectedVariation]); |
| 26 | + }, [product.variations]); |
26 | 27 |
|
27 | 28 | let { description, image, name, onSale, price, regularPrice, salePrice } =
|
28 | 29 | product;
|
29 | 30 |
|
30 |
| - if (price) price = paddedPrice(price, 'kr'); |
31 |
| - if (regularPrice) regularPrice = paddedPrice(regularPrice, 'kr'); |
32 |
| - if (salePrice) salePrice = paddedPrice(salePrice, 'kr'); |
| 31 | + // Add padding/empty character after currency symbol here |
| 32 | + if (price) { |
| 33 | + price = paddedPrice(price, 'kr'); |
| 34 | + } |
| 35 | + if (regularPrice) { |
| 36 | + regularPrice = paddedPrice(regularPrice, 'kr'); |
| 37 | + } |
| 38 | + if (salePrice) { |
| 39 | + salePrice = paddedPrice(salePrice, 'kr'); |
| 40 | + } |
33 | 41 |
|
34 |
| - if (typeof window !== 'undefined' && description) { |
35 |
| - DESCRIPTION_WITHOUT_HTML = |
36 |
| - new DOMParser().parseFromString(description, 'text/html').body |
37 |
| - .textContent || ''; |
| 42 | + // Strip out HTML from description |
| 43 | + if (process.browser) { |
| 44 | + DESCRIPTION_WITHOUT_HTML = new DOMParser().parseFromString( |
| 45 | + description, |
| 46 | + 'text/html', |
| 47 | + ).body.textContent; |
38 | 48 | }
|
39 | 49 |
|
40 | 50 | return (
|
41 |
| - <section className="bg-white mb-16 sm:mb-24"> |
| 51 | + <section className="bg-white mb-[10rem] md:mb-12"> |
| 52 | + {/* Show loading spinner while loading, and hide content while loading */} |
42 | 53 | {isLoading ? (
|
43 | 54 | <div className="h-56 mt-20">
|
44 | 55 | <p className="text-2xl font-bold text-center">Laster produkt ...</p>
|
45 | 56 | <br />
|
46 | 57 | <LoadingSpinner />
|
47 | 58 | </div>
|
48 | 59 | ) : (
|
49 |
| - <div className="container mx-auto px-4 py-8 sm:px-6 lg:px-8"> |
50 |
| - <div className="flex flex-col space-y-8"> |
51 |
| - {/* First row: Product info, price, and purchase options */} |
52 |
| - <div className="flex flex-col md:flex-row gap-8"> |
53 |
| - <div className="w-full md:w-1/2"> |
54 |
| - {image ? ( |
55 |
| - <img |
56 |
| - id="product-image" |
57 |
| - src={image.sourceUrl} |
58 |
| - alt={name} |
59 |
| - className="w-full h-auto object-cover rounded-lg shadow-md" |
60 |
| - /> |
61 |
| - ) : ( |
62 |
| - <img |
63 |
| - id="product-image" |
64 |
| - src={ |
65 |
| - process.env.NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL ?? |
66 |
| - placeholderFallBack |
67 |
| - } |
68 |
| - alt={name} |
69 |
| - className="w-full h-auto object-cover rounded-lg shadow-md" |
70 |
| - /> |
71 |
| - )} |
72 |
| - </div> |
73 |
| - <div className="w-full md:w-1/2 flex flex-col space-y-6"> |
74 |
| - <h1 className="text-3xl font-bold">{name}</h1> |
75 |
| - <div className="flex flex-col space-y-2"> |
76 |
| - {onSale && <p className="text-sm">Før {regularPrice}</p>} |
77 |
| - <p className="text-4xl font-bold"> |
78 |
| - {product.variations |
79 |
| - ? filteredVariantPrice(price, '') |
80 |
| - : onSale |
81 |
| - ? salePrice |
82 |
| - : price} |
| 60 | + <div className="container flex flex-wrap items-center pt-4 pb-12 mx-auto "> |
| 61 | + <div className="grid grid-cols-1 gap-4 mt-16 lg:grid-cols-2 xl:grid-cols-2 md:grid-cols-2 sm:grid-cols-2"> |
| 62 | + {image && ( |
| 63 | + <img |
| 64 | + id="product-image" |
| 65 | + src={image.sourceUrl} |
| 66 | + alt={name} |
| 67 | + className="h-auto p-8 transition duration-500 ease-in-out transform xl:p-2 md:p-2 lg:p-2 md:hover:grow md:hover:scale-105" |
| 68 | + /> |
| 69 | + )} |
| 70 | + {!image && ( |
| 71 | + <img |
| 72 | + id="product-image" |
| 73 | + src={ |
| 74 | + process.env.NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL ?? |
| 75 | + placeholderFallBack |
| 76 | + } |
| 77 | + alt={name} |
| 78 | + className="h-auto p-8 transition duration-500 ease-in-out transform xl:p-2 md:p-2 lg:p-2 md:hover:grow md:hover:shadow-lg md:hover:scale-105" |
| 79 | + /> |
| 80 | + )} |
| 81 | + <div className="ml-8"> |
| 82 | + <p className="text-3xl font-bold text-left">{name}</p> |
| 83 | + <br /> |
| 84 | + {/* Display sale price when on sale */} |
| 85 | + {onSale && ( |
| 86 | + <div className="flex"> |
| 87 | + <p className="pt-1 mt-4 text-3xl text-gray-900"> |
| 88 | + {product.variations && filteredVariantPrice(price, '')} |
| 89 | + {!product.variations && salePrice} |
83 | 90 | </p>
|
84 |
| - <p className="text-sm"> |
85 |
| - Tilbudet gjelder til 19/09 eller så lenge lageret rekker. |
| 91 | + <p className="pt-1 pl-8 mt-4 text-2xl text-gray-900 line-through"> |
| 92 | + {product.variations && filteredVariantPrice(price, 'right')} |
| 93 | + {!product.variations && regularPrice} |
86 | 94 | </p>
|
87 | 95 | </div>
|
88 |
| - <div className="space-y-4"> |
| 96 | + )} |
| 97 | + {/* Display regular price when not on sale */} |
| 98 | + {!onSale && ( |
| 99 | + <p className="pt-1 mt-4 text-2xl text-gray-900"> {price}</p> |
| 100 | + )} |
| 101 | + <br /> |
| 102 | + <p className="pt-1 mt-4 text-2xl text-gray-900"> |
| 103 | + {DESCRIPTION_WITHOUT_HTML} |
| 104 | + </p> |
| 105 | + {Boolean(product.stockQuantity) && ( |
| 106 | + <p |
| 107 | + v-if="data.product.stockQuantity" |
| 108 | + className="pt-1 mt-4 mb-4 text-2xl text-gray-900" |
| 109 | + > |
| 110 | + {product.stockQuantity} på lager |
| 111 | + </p> |
| 112 | + )} |
| 113 | + {product.variations && ( |
| 114 | + <p className="pt-1 mt-4 text-xl text-gray-900"> |
| 115 | + <span className="py-2">Varianter</span> |
| 116 | + <select |
| 117 | + id="variant" |
| 118 | + name="variant" |
| 119 | + className="block w-80 px-6 py-2 bg-white border border-gray-500 rounded-lg focus:outline-none focus:shadow-outline" |
| 120 | + onChange={(e) => { |
| 121 | + setSelectedVariation(Number(e.target.value)); |
| 122 | + }} |
| 123 | + > |
| 124 | + {product.variations.nodes.map( |
| 125 | + ({ id, name, databaseId, stockQuantity }) => { |
| 126 | + // Remove product name from variation name |
| 127 | + const filteredName = name.split('- ').pop(); |
| 128 | + return ( |
| 129 | + <option key={id} value={databaseId}> |
| 130 | + {filteredName} - ({stockQuantity} på lager) |
| 131 | + </option> |
| 132 | + ); |
| 133 | + }, |
| 134 | + )} |
| 135 | + </select> |
| 136 | + </p> |
| 137 | + )} |
| 138 | + <div className="pt-1 mt-2"> |
| 139 | + { |
| 140 | + // Display default AddToCart button if we do not have variations. |
| 141 | + // If we do, send the variationId to AddToCart button |
| 142 | + } |
| 143 | + {product.variations && ( |
89 | 144 | <AddToCart
|
90 | 145 | product={product}
|
91 | 146 | variationId={selectedVariation}
|
92 | 147 | />
|
93 |
| - |
94 |
| - {product.variations && ( |
95 |
| - <div className="w-full"> |
96 |
| - <label |
97 |
| - htmlFor="variant" |
98 |
| - className="block text-sm font-medium mb-2" |
99 |
| - > |
100 |
| - Varianter |
101 |
| - </label> |
102 |
| - <select |
103 |
| - id="variant" |
104 |
| - name="variant" |
105 |
| - className="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500" |
106 |
| - onChange={(e) => |
107 |
| - setSelectedVariation(Number(e.target.value)) |
108 |
| - } |
109 |
| - value={selectedVariation} |
110 |
| - > |
111 |
| - {product.variations.nodes.map( |
112 |
| - ({ id, name, databaseId, stockQuantity }) => { |
113 |
| - const filteredName = name.split('- ').pop(); |
114 |
| - return ( |
115 |
| - <option key={id} value={databaseId}> |
116 |
| - {filteredName} - ({stockQuantity} på lager) |
117 |
| - </option> |
118 |
| - ); |
119 |
| - }, |
120 |
| - )} |
121 |
| - </select> |
122 |
| - </div> |
123 |
| - )} |
124 |
| - </div> |
125 |
| - {Boolean(product.stockQuantity) && ( |
126 |
| - <p className="text-sm font-semibold"> |
127 |
| - <span className="inline-block w-3 h-3 bg-green-500 rounded-full mr-2"></span> |
128 |
| - {product.stockQuantity}+ stk. på lager |
129 |
| - </p> |
130 | 148 | )}
|
| 149 | + {!product.variations && <AddToCart product={product} />} |
131 | 150 | </div>
|
132 | 151 | </div>
|
133 |
| - |
134 |
| - {/* Second row: Product description */} |
135 |
| - <div className="mt-8"> |
136 |
| - <h2 className="text-2xl font-semibold mb-4"> |
137 |
| - Produktbeskrivelse |
138 |
| - </h2> |
139 |
| - <p className="text-gray-600">{DESCRIPTION_WITHOUT_HTML}</p> |
140 |
| - </div> |
141 | 152 | </div>
|
142 | 153 | </div>
|
143 | 154 | )}
|
|
0 commit comments