@@ -9,12 +9,12 @@ import {
99 Minus ,
1010} from 'lucide-react'
1111import { useState } from 'react'
12- import { Link } from 'react-router'
12+ import { Link , useSearchParams } from 'react-router'
1313import { ProductCard } from '#app/components/product-card.js'
1414import {
1515 getProductById ,
1616 getRelatedProducts ,
17- } from '#app/domain/products.server.ts '
17+ } from '#app/domain/products.server.js '
1818import {
1919 getMetaFromMatches ,
2020 getMetaTitle ,
@@ -32,30 +32,61 @@ export const meta: Route.MetaFunction = ({ matches }) => {
3232 ]
3333}
3434
35- export const loader = async ( { params } : Route . LoaderArgs ) => {
35+ export const loader = async ( { params, request } : Route . LoaderArgs ) => {
3636 const { productId } = params
3737 const product = await getProductById ( productId )
3838 const relatedProducts = await getRelatedProducts (
3939 productId ,
4040 product ?. category . id ,
4141 product ?. brand . id ,
4242 )
43-
43+ const uniqueSizes = Array . from (
44+ new Set ( product ?. variations . map ( ( v ) => v . size ) || [ ] ) ,
45+ )
46+ const uniqueColors = Array . from (
47+ new Set ( product ?. variations . map ( ( v ) => v . color ) || [ ] ) ,
48+ )
49+ const url = new URL ( request . url )
50+ const searchParams = url . searchParams
51+ // 💰 We need to find the selectColor and selectedSize from the url search params
52+ const selectedSize = undefined
53+ const selectedColor = undefined
54+ // 💰 Here's the code to determine available sizes and colors!
55+ // const availableSizes = selectedColor
56+ // ? product?.variations
57+ // .filter((v) => v.color === selectedColor && v.quantity > 0)
58+ // .map((v) => v.size) || []
59+ // : uniqueSizes
60+ // const availableColors = selectedSize
61+ // ? product?.variations
62+ // .filter((v) => v.size === selectedSize && v.quantity > 0)
63+ // .map((v) => v.color) || []
64+ // : uniqueColors
65+ // 🐨 Let's determine the selected variation based on the selectedSize and selectedColor
66+ // 💰 You can use the find method on the product.variations array
67+ const selectedVariation = undefined
4468 return {
4569 product,
4670 relatedProducts,
71+ uniqueSizes,
72+ uniqueColors,
73+ // 💰 We need to return availableSizes, availableColors and selectedVariation here
4774 }
4875}
4976
5077export default function ProductDetailPage ( {
5178 loaderData,
5279} : Route . ComponentProps ) {
53- const { product, relatedProducts } = loaderData
54- const [ selectedSize ] = useState ( '' )
55- const [ selectedColor ] = useState ( '' )
80+ // 💣 remove this in favor of loaderData
81+ const selectedVariation = undefined
82+ const { product, relatedProducts, uniqueColors, uniqueSizes } = loaderData
83+ const [ searchParams , setSearchParams ] = useSearchParams ( )
84+
5685 const [ quantity , setQuantity ] = useState ( 1 )
5786 const [ activeImage , setActiveImage ] = useState ( 0 )
58-
87+ // 💰 We need to get size and color from the URL search params
88+ const selectedSize = undefined
89+ const selectedColor = undefined
5990 if ( ! product ) {
6091 return (
6192 < div className = "flex min-h-screen items-center justify-center bg-stone-50 dark:bg-gray-900" >
@@ -83,6 +114,50 @@ export default function ProductDetailPage({
83114 alert ( 'Added to cart!' )
84115 }
85116
117+ const handleSizeChange = ( size : string ) => ( ) => {
118+ setSearchParams ( ( prev ) => {
119+ const newParams = new URLSearchParams ( prev )
120+ // 🐨 Let's either remove or set the size depending if this size was selected or not!
121+ // 💰 You can use size and the selectedSize for the comparison
122+
123+ // 🐨 After that, let's get the color from the url
124+ const color = undefined
125+ // 💰 If the selected color is not available for the new size, remove it
126+ // const noQuantityForColor =
127+ // color &&
128+ // product.variations.some(
129+ // (v) => v.size === size && v.color === color && v.quantity === 0,
130+ // )
131+ //if (noQuantityForColor) {
132+ // newParams.delete('color')
133+ //}
134+ return newParams
135+ } )
136+ setQuantity ( 1 )
137+ }
138+
139+ const handleColorChange = ( color : string ) => ( ) => {
140+ setSearchParams ( ( prev ) => {
141+ const newParams = new URLSearchParams ( prev )
142+ // 🐨 Let's either remove or set the color depending if this color was selected or not!
143+ // 💰 You can use color and the selectedColor for the comparison
144+
145+ // 🐨 After that, let's get the size from the url
146+ const size = undefined
147+ // 💰 If the selected size is not available for the new size, remove it
148+ // const noQuantityForSize =
149+ // size &&
150+ // product.variations.some(
151+ // (v) => v.size === size && v.size === size && v.quantity === 0,
152+ // )
153+ //if (noQuantityForSize) {
154+ // newParams.delete('size')
155+ //}
156+ return newParams
157+ } )
158+ setQuantity ( 1 )
159+ }
160+
86161 // Mock additional images for gallery
87162 const productImages = [
88163 product . imageUrl ,
@@ -179,19 +254,23 @@ export default function ProductDetailPage({
179254 Size
180255 </ h3 >
181256 < div className = "grid grid-cols-6 gap-3" >
182- { /* {product.sizes .map((size) => (
257+ { uniqueSizes . map ( ( size ) => (
183258 < button
184259 key = { size }
185- onClick={() => setSelectedSize(size)}
186- className={`rounded-lg border px-4 py-3 text-center transition-colors duration-200 ${
260+ // 🐨 Let's add a title to the button when it's out of stock and disable it!
261+ // 💰 check the availableSizes and if it includes the size
262+ disabled = { false }
263+ title = { false ? 'Out of stock' : '' }
264+ onClick = { handleSizeChange ( size ) }
265+ className = { `rounded-lg border px-4 py-3 text-center transition-colors duration-200 disabled:opacity-20 ${
187266 selectedSize === size
188267 ? 'border-amber-500 bg-amber-50 text-amber-800 dark:bg-amber-900/30 dark:text-amber-200'
189268 : 'border-gray-300 text-gray-700 hover:border-amber-300 dark:border-gray-600 dark:text-gray-300 dark:hover:border-amber-700'
190269 } `}
191270 >
192271 { size }
193272 </ button >
194- ))} */ }
273+ ) ) }
195274 </ div >
196275 </ div >
197276
@@ -200,53 +279,65 @@ export default function ProductDetailPage({
200279 < h3 className = "mb-4 text-lg font-medium text-gray-900 dark:text-white" >
201280 Color
202281 </ h3 >
203- < div className = "flex space-x-3" >
204- { /* {product.colors .map((color) => (
282+ < div className = "flex flex-wrap space-x-3" >
283+ { uniqueColors . map ( ( color ) => (
205284 < button
206285 key = { color }
207- onClick={() => setSelectedColor(color)}
208- className={`rounded-lg border px-4 py-2 transition-colors duration-200 ${
286+ // 🐨 Let's add a title to the button when it's out of stock and disable it!
287+ // 💰 check the availableColors and if it includes the color
288+ disabled = { false }
289+ title = { false ? 'Out of stock' : '' }
290+ onClick = { handleColorChange ( color ) }
291+ className = { `rounded-lg border px-4 py-2 transition-colors duration-200 disabled:opacity-20 ${
209292 selectedColor === color
210293 ? 'border-amber-500 bg-amber-50 text-amber-800 dark:bg-amber-900/30 dark:text-amber-200'
211294 : 'border-gray-300 text-gray-700 hover:border-amber-300 dark:border-gray-600 dark:text-gray-300 dark:hover:border-amber-700'
212295 } `}
213296 >
214297 { color }
215298 </ button >
216- ))} */ }
299+ ) ) }
217300 </ div >
218301 </ div >
219302
220303 { /* Quantity */ }
221- < div >
222- < h3 className = "mb-4 text-lg font-medium text-gray-900 dark:text-white" >
223- Quantity
224- </ h3 >
225- < div className = "flex items-center space-x-4" >
226- < div className = "flex items-center rounded-lg border border-gray-300 dark:border-gray-600" >
227- < button
228- onClick = { ( ) => setQuantity ( Math . max ( 1 , quantity - 1 ) ) }
229- className = "p-2 transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-700"
230- >
231- < Minus className = "h-4 w-4" />
232- </ button >
233- < span className = "px-4 py-2 font-medium text-gray-900 dark:text-white" >
234- { quantity }
235- </ span >
236- < button
237- onClick = { ( ) => setQuantity ( quantity + 1 ) }
238- className = "p-2 transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-700"
239- >
240- < Plus className = "h-4 w-4" />
241- </ button >
304+ { selectedVariation ? (
305+ < div >
306+ < h3 className = "mb-4 text-lg font-medium text-gray-900 dark:text-white" >
307+ Quantity
308+ </ h3 >
309+ < div className = "flex items-center space-x-4" >
310+ < div className = "flex items-center rounded-lg border border-gray-300 dark:border-gray-600" >
311+ < button
312+ onClick = { ( ) => setQuantity ( Math . max ( 1 , quantity - 1 ) ) }
313+ // 🐨 Let's disable this button if the quantity is equal to 1
314+ disabled = { false }
315+ className = "p-2 transition-colors duration-200 hover:bg-gray-100 disabled:opacity-20 disabled:hover:bg-transparent dark:hover:bg-gray-700"
316+ >
317+ < Minus className = "h-4 w-4" />
318+ </ button >
319+ < span className = "px-4 py-2 font-medium text-gray-900 dark:text-white" >
320+ { quantity }
321+ </ span >
322+ < button
323+ onClick = { ( ) => setQuantity ( quantity + 1 ) }
324+ // 🐨 Let's disable this button if the quantity is equal to the selected variation quantity
325+ disabled = { false }
326+ className = "p-2 transition-colors duration-200 hover:bg-gray-100 disabled:opacity-20 disabled:hover:bg-transparent dark:hover:bg-gray-700"
327+ >
328+ < Plus className = "h-4 w-4" />
329+ </ button >
330+ </ div >
242331 </ div >
243332 </ div >
244- </ div >
333+ ) : null }
245334
246335 { /* Add to Cart */ }
247336 < div className = "flex space-x-4" >
248337 < button
249338 onClick = { handleAddToCart }
339+ // 🐨 Let's disable this button if there is no selectedVariation or it's quantity is equal to 0
340+ disabled = { false }
250341 className = "flex-1 rounded-lg bg-amber-600 px-6 py-4 font-medium text-white transition-colors duration-300 hover:bg-amber-700 hover:shadow-lg"
251342 >
252343 Add to Cart
0 commit comments