1
- /*eslint complexity: ["error", 20]*/
2
- // Imports
3
1
import { useState , useEffect } from 'react' ;
4
-
5
- // Utils
6
2
import { filteredVariantPrice , paddedPrice } from '@/utils/functions/functions' ;
7
-
8
- // Components
9
3
import AddToCart , { IProductRootObject } from './AddToCart.component' ;
10
4
import LoadingSpinner from '@/components/LoadingSpinner/LoadingSpinner.component' ;
11
5
12
- const SingleProduct = ( { product } : IProductRootObject ) => {
6
+ interface IImage {
7
+ __typename : string ;
8
+ id : string ;
9
+ uri : string ;
10
+ title : string ;
11
+ srcSet : string ;
12
+ sourceUrl : string ;
13
+ }
14
+
15
+ interface IVariationNode {
16
+ __typename : string ;
17
+ name : string ;
18
+ }
19
+
20
+ interface IAllPaColors {
21
+ __typename : string ;
22
+ nodes : IVariationNode [ ] ;
23
+ }
24
+
25
+ interface IAllPaSizes {
26
+ __typename : string ;
27
+ nodes : IVariationNode [ ] ;
28
+ }
29
+
30
+ interface IVariationNodes {
31
+ __typename : string ;
32
+ id : string ;
33
+ databaseId : number ;
34
+ name : string ;
35
+ stockStatus : string ;
36
+ stockQuantity : number ;
37
+ purchasable : boolean ;
38
+ onSale : boolean ;
39
+ salePrice ?: string ;
40
+ regularPrice : string ;
41
+ }
42
+
43
+ interface IVariations {
44
+ __typename : string ;
45
+ nodes : IVariationNodes [ ] ;
46
+ }
47
+
48
+ interface IProduct {
49
+ __typename : string ;
50
+ id : string ;
51
+ databaseId : number ;
52
+ averageRating : number ;
53
+ slug : string ;
54
+ description : string ;
55
+ onSale : boolean ;
56
+ image : IImage ;
57
+ name : string ;
58
+ salePrice ?: string ;
59
+ regularPrice : string ;
60
+ price : string ;
61
+ stockQuantity : number ;
62
+ allPaColors ?: IAllPaColors ;
63
+ allPaSizes ?: IAllPaSizes ;
64
+ variations ?: IVariations ;
65
+ }
66
+
67
+ const SingleProduct : React . FC < IProductRootObject > = ( { product } ) => {
13
68
const [ isLoading , setIsLoading ] = useState < boolean > ( true ) ;
14
- const [ selectedVariation , setSelectedVariation ] = useState < number > ( ) ;
69
+ const [ selectedVariation , setSelectedVariation ] = useState < number | undefined > ( ) ;
15
70
16
71
const placeholderFallBack = 'https://via.placeholder.com/600' ;
17
72
18
- let DESCRIPTION_WITHOUT_HTML ;
73
+ let DESCRIPTION_WITHOUT_HTML : string | undefined ;
19
74
20
75
useEffect ( ( ) => {
21
76
setIsLoading ( false ) ;
@@ -25,121 +80,94 @@ const SingleProduct = ({ product }: IProductRootObject) => {
25
80
}
26
81
} , [ product . variations ] ) ;
27
82
28
- let { description, image, name, onSale, price, regularPrice, salePrice } =
29
- product ;
83
+ let { description, image, name, onSale, price, regularPrice, salePrice } = product ;
30
84
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
- }
85
+ if ( price ) price = paddedPrice ( price , 'kr' ) ;
86
+ if ( regularPrice ) regularPrice = paddedPrice ( regularPrice , 'kr' ) ;
87
+ if ( salePrice ) salePrice = paddedPrice ( salePrice , 'kr' ) ;
41
88
42
- // Strip out HTML from description
43
89
if ( process . browser ) {
44
- DESCRIPTION_WITHOUT_HTML = new DOMParser ( ) . parseFromString (
45
- description ,
46
- 'text/html' ,
47
- ) . body . textContent ;
90
+ DESCRIPTION_WITHOUT_HTML = new DOMParser ( ) . parseFromString ( description , 'text/html' ) . body . textContent ;
48
91
}
49
92
50
93
return (
51
- < section className = "bg-white mb-12 sm:mb-2" >
52
- { /* Show loading spinner while loading, and hide content while loading */ }
94
+ < section className = "bg-white" >
53
95
{ isLoading ? (
54
96
< div className = "h-56 mt-20" >
55
97
< p className = "text-2xl font-bold text-center" > Laster produkt ...</ p >
56
98
< br />
57
99
< LoadingSpinner />
58
100
</ div >
59
101
) : (
60
- < div className = "container mx-auto px-4 sm:px-6 lg:px-8" >
61
- < div className = "grid grid-cols-1 gap-4 md:grid-cols-2 lg: gap-8" >
62
- { image && (
63
- < div className = "flex justify-center items-center" >
102
+ < div className = "container mx-auto px-4 py-8 sm:px-6 lg:px-8" >
103
+ < div className = "grid grid-cols-1 md:grid-cols-2 gap-8 items-start " >
104
+ < div className = "w-full" >
105
+ { image ? (
64
106
< img
65
107
id = "product-image"
66
108
src = { image . sourceUrl }
67
109
alt = { name }
68
- className = "max- w-full h-auto transition duration-500 ease-in-out transform hover:scale-105 "
110
+ className = "w-full h-auto object-cover rounded-lg shadow-md "
69
111
/>
70
- </ div >
71
- ) }
72
- { ! image && (
73
- < div className = "flex justify-center items-center" >
112
+ ) : (
74
113
< img
75
114
id = "product-image"
76
- src = {
77
- process . env . NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL ??
78
- placeholderFallBack
79
- }
115
+ src = { process . env . NEXT_PUBLIC_PLACEHOLDER_LARGE_IMAGE_URL ?? placeholderFallBack }
80
116
alt = { name }
81
- className = "max- w-full h-auto transition duration-500 ease-in-out transform hover:scale-105 "
117
+ className = "w-full h-auto object-cover rounded-lg shadow-md "
82
118
/>
83
- </ div >
84
- ) }
85
- < div className = "flex flex-col justify-center text-center md:text-left" >
86
- < h1 className = "text-3xl font-bold mb-4" > { name } </ h1 >
87
- { /* Display sale price when on sale */ }
88
- { onSale && (
89
- < div className = "flex flex-col md:flex-row items-center md:items-start justify-center md:justify-start mb-4" >
90
- < p className = "text-3xl font-bold text-red-600 mr-4" >
91
- { product . variations && filteredVariantPrice ( price , '' ) }
92
- { ! product . variations && salePrice }
93
- </ p >
94
- < p className = "text-2xl text-gray-500 line-through" >
95
- { product . variations && filteredVariantPrice ( price , 'right' ) }
96
- { ! product . variations && regularPrice }
97
- </ p >
98
- </ div >
99
119
) }
100
- { /* Display regular price when not on sale */ }
101
- { ! onSale && (
102
- < p className = "text-3xl font-bold mb-4" > { price } </ p >
103
- ) }
104
- < p className = "text-lg mb-4" > { DESCRIPTION_WITHOUT_HTML } </ p >
120
+ </ div >
121
+ < div className = "flex flex-col space-y-4" >
122
+ < h1 className = "text-3xl font-bold text-center md:text-left" > { name } </ h1 >
123
+ < div className = "text-center md:text-left" >
124
+ { onSale ? (
125
+ < div className = "flex flex-col md:flex-row items-center md:items-start space-y-2 md:space-y-0 md:space-x-4" >
126
+ < p className = "text-3xl font-bold text-red-600" >
127
+ { product . variations ? filteredVariantPrice ( price , '' ) : salePrice }
128
+ </ p >
129
+ < p className = "text-xl text-gray-500 line-through" >
130
+ { product . variations ? filteredVariantPrice ( price , 'right' ) : regularPrice }
131
+ </ p >
132
+ </ div >
133
+ ) : (
134
+ < p className = "text-2xl font-bold" > { price } </ p >
135
+ ) }
136
+ </ div >
137
+ < p className = "text-gray-600 text-center md:text-left" > { DESCRIPTION_WITHOUT_HTML } </ p >
105
138
{ Boolean ( product . stockQuantity ) && (
106
- < p className = "text-lg mb-4 " >
139
+ < p className = "text-sm font-semibold text-center md:text-left " >
107
140
{ product . stockQuantity } på lager
108
141
</ p >
109
142
) }
110
143
{ product . variations && (
111
- < div className = "mb-4" >
112
- < label htmlFor = "variant" className = "block text-lg mb-2" > Varianter</ label >
144
+ < div className = "w-full" >
145
+ < label htmlFor = "variant" className = "block text-sm font-medium text-gray-700 mb-2" >
146
+ Varianter
147
+ </ label >
113
148
< select
114
149
id = "variant"
115
150
name = "variant"
116
- className = "w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
117
- onChange = { ( e ) => {
118
- setSelectedVariation ( Number ( e . target . value ) ) ;
119
- } }
151
+ className = "w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
152
+ onChange = { ( e ) => setSelectedVariation ( Number ( e . target . value ) ) }
120
153
>
121
- { product . variations . nodes . map (
122
- ( { id, name, databaseId, stockQuantity } ) => {
123
- // Remove product name from variation name
124
- const filteredName = name . split ( '- ' ) . pop ( ) ;
125
- return (
126
- < option key = { id } value = { databaseId } >
127
- { filteredName } - ({ stockQuantity } på lager)
128
- </ option >
129
- ) ;
130
- } ,
131
- ) }
154
+ { product . variations . nodes . map ( ( { id, name, databaseId, stockQuantity } ) => {
155
+ const filteredName = name . split ( '- ' ) . pop ( ) ;
156
+ return (
157
+ < option key = { id } value = { databaseId } >
158
+ { filteredName } - ({ stockQuantity } på lager)
159
+ </ option >
160
+ ) ;
161
+ } ) }
132
162
</ select >
133
163
</ div >
134
164
) }
135
165
< div className = "flex justify-center md:justify-start" >
136
- { product . variations && (
137
- < AddToCart
138
- product = { product }
139
- variationId = { selectedVariation }
140
- />
166
+ { product . variations ? (
167
+ < AddToCart product = { product } variationId = { selectedVariation } />
168
+ ) : (
169
+ < AddToCart product = { product } />
141
170
) }
142
- { ! product . variations && < AddToCart product = { product } /> }
143
171
</ div >
144
172
</ div >
145
173
</ div >
0 commit comments