Skip to content

Commit 9420883

Browse files
committed
fix: update minor style improvements, fetching data, add update fields
1 parent 3bb564d commit 9420883

File tree

7 files changed

+134
-67
lines changed

7 files changed

+134
-67
lines changed

src/api/productService.js

Lines changed: 107 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,65 @@ import axiosInstance from './axiosInstance.js';
55
* @param {Object} apiProduct - The product object from the API.
66
* @returns {Object} Transformed product object.
77
*/
8-
const transformProduct = (apiProduct) => {
9-
const originalPrice = apiProduct.sale > 0
10-
? Number((apiProduct.price / (1 - apiProduct.sale / 100)).toFixed(2))
11-
: null;
8+
const transformProduct = (apiProduct = {}) => {
9+
if (!apiProduct || typeof apiProduct !== 'object') return null;
10+
11+
const price = Number(apiProduct.price ?? 0);
12+
const sale = Number(apiProduct.sale ?? 0);
13+
const originalPrice = sale > 0 ? Number((price / (1 - sale / 100)).toFixed(2)) : null;
1214

1315
return {
14-
id: apiProduct._id,
15-
name: apiProduct.name,
16-
description: apiProduct.description || apiProduct.shortDescription,
17-
price: apiProduct.price,
18-
originalPrice: originalPrice,
19-
category: apiProduct.category,
20-
imageUrl: apiProduct.image,
21-
rating: apiProduct.rating,
22-
reviewCount: apiProduct.reviewsCount,
23-
stock: 15, // Consider fetching stock from the API if available
24-
isNew: apiProduct.new,
25-
onSale: apiProduct.sale > 0,
26-
flavors: apiProduct.flavors || [],
27-
features: apiProduct.quality || [],
28-
goals: apiProduct.goals || [],
29-
sizes: apiProduct.sizes || [],
30-
salePercentage: apiProduct.sale,
31-
longDescription: apiProduct.longDescription,
32-
usageTips: apiProduct.usageTips || []
16+
id: apiProduct._id ?? apiProduct.id ?? null,
17+
name: apiProduct.name ?? apiProduct.title ?? 'Unnamed Product',
18+
description: apiProduct.description ?? apiProduct.shortDescription ?? '',
19+
shortDescription: apiProduct.shortDescription ?? apiProduct.description ?? '',
20+
longDescription: apiProduct.longDescription ?? '',
21+
price,
22+
originalPrice,
23+
category: apiProduct.category ?? null,
24+
imageUrl: apiProduct.image ?? apiProduct.imageUrl ?? '/images/product-default-image.jpg',
25+
imageGallery: Array.isArray(apiProduct.images) ? apiProduct.images : Array.isArray(apiProduct.gallery) ? apiProduct.gallery : [],
26+
rating: Number(apiProduct.rating ?? 0),
27+
reviewCount: Number(apiProduct.reviewsCount ?? apiProduct.reviewCount ?? 0),
28+
stock: Number(apiProduct.stock ?? apiProduct.inventory ?? 0),
29+
isNew: Boolean(apiProduct.new),
30+
onSale: sale > 0,
31+
salePercentage: sale,
32+
flavors: Array.isArray(apiProduct.flavors) ? apiProduct.flavors : [],
33+
sizes: Array.isArray(apiProduct.sizes) ? apiProduct.sizes : [],
34+
quality: Array.isArray(apiProduct.quality) ? apiProduct.quality : [],
35+
goals: Array.isArray(apiProduct.goals) ? apiProduct.goals : [],
36+
collections: Array.isArray(apiProduct.collections) ? apiProduct.collections : [],
37+
usageTips: typeof apiProduct.usageTips === 'object' ? apiProduct.usageTips : {},
38+
createdAt: apiProduct.createdAt ? new Date(apiProduct.createdAt).toISOString() : null,
39+
updatedAt: apiProduct.updatedAt ? new Date(apiProduct.updatedAt).toISOString() : null,
40+
raw: apiProduct, // keep original object for debugging if needed
3341
};
3442
};
3543

3644
/**
3745
* Transforms the API response containing multiple products.
46+
* Accepts several shapes returned by different backends.
3847
* @param {Object} apiResponse - The response object from the API.
3948
* @returns {Object} Transformed response object.
4049
*/
41-
const transformApiResponse = (apiResponse) => {
50+
const transformApiResponse = (apiResponse = {}) => {
51+
// apiResponse may be { products: [...], total, page, pages } or { data: { products: [...] } }
52+
const payload = apiResponse.products ?? apiResponse.data?.products ?? apiResponse.data ?? apiResponse;
53+
const productsArray = Array.isArray(payload) ? payload : Array.isArray(payload.products) ? payload.products : [];
54+
55+
const mapped = productsArray.map(transformProduct).filter(Boolean);
56+
57+
const total = Number(apiResponse.total ?? apiResponse.data?.total ?? payload.total ?? mapped.length);
58+
const page = Number(apiResponse.page ?? apiResponse.data?.page ?? payload.page ?? 1);
59+
const pages = Number(apiResponse.pages ?? apiResponse.data?.pages ?? payload.pages ?? 1);
60+
4261
return {
43-
products: apiResponse.products.map(transformProduct),
44-
totalCount: apiResponse.total,
45-
currentPage: apiResponse.page,
46-
hasNextPage: apiResponse.page < apiResponse.pages,
47-
totalPages: apiResponse.pages
62+
products: mapped,
63+
totalCount: total,
64+
currentPage: page,
65+
hasNextPage: page < pages,
66+
totalPages: pages,
4867
};
4968
};
5069

@@ -73,20 +92,39 @@ export const getProducts = async (params = {}) => {
7392

7493
const data = response.data;
7594

95+
// support multiple payload shapes
96+
if (Array.isArray(data)) {
97+
return {
98+
success: true,
99+
data: {
100+
products: data.map(transformProduct).filter(Boolean),
101+
total: data.length,
102+
page: 1,
103+
pages: 1,
104+
},
105+
status: response.status,
106+
};
107+
}
108+
109+
// If API returns wrapper like { products: [...], total, page, pages }
110+
if (data.products || data.data?.products) {
111+
return {
112+
success: true,
113+
data: transformApiResponse(data),
114+
status: response.status,
115+
};
116+
}
117+
118+
// If API returns nested data or other shapes, attempt to transform generically
76119
return {
77120
success: true,
78-
data: Array.isArray(data) ? {
79-
products: data,
80-
total: data.length,
81-
page: 1,
82-
pages: 1,
83-
} : transformApiResponse(data),
121+
data: transformApiResponse(data),
84122
status: response.status,
85123
};
86124
} catch (error) {
87125
return {
88126
success: false,
89-
error: error.response?.data?.message || 'Failed to fetch products',
127+
error: error.response?.data?.message || error.message || 'Failed to fetch products',
90128
status: error.response?.status || 500,
91129
};
92130
}
@@ -104,16 +142,20 @@ export const getProductById = async (id) => {
104142
}
105143

106144
const response = await axiosInstance.get(`/products/${id}`);
145+
const data = response.data;
146+
147+
// Accept shapes: { ...product }, { product: {...} }, { data: { product: {...} } }
148+
const productPayload = data.product ?? data.data?.product ?? data.data ?? data;
107149

108150
return {
109151
success: true,
110-
data: transformProduct(response.data),
152+
data: transformProduct(productPayload),
111153
status: response.status,
112154
};
113155
} catch (error) {
114156
return {
115157
success: false,
116-
error: error.response?.data?.message || 'Failed to fetch product',
158+
error: error.response?.data?.message || error.message || 'Failed to fetch product',
117159
status: error.response?.status || 500,
118160
};
119161
}
@@ -134,15 +176,30 @@ export const searchProducts = async (query, params = {}) => {
134176
},
135177
});
136178

179+
const data = response.data;
180+
181+
if (Array.isArray(data)) {
182+
return {
183+
success: true,
184+
data: {
185+
products: data.map(transformProduct).filter(Boolean),
186+
total: data.length,
187+
page: 1,
188+
pages: 1,
189+
},
190+
status: response.status,
191+
};
192+
}
193+
137194
return {
138195
success: true,
139-
data: response.data,
196+
data: transformApiResponse(data),
140197
status: response.status,
141198
};
142199
} catch (error) {
143200
return {
144201
success: false,
145-
error: error.response?.data?.message || 'Failed to search products',
202+
error: error.response?.data?.message || error.message || 'Failed to search products',
146203
status: error.response?.status || 500,
147204
};
148205
}
@@ -164,7 +221,7 @@ export const getProductCategories = async () => {
164221
} catch (error) {
165222
return {
166223
success: false,
167-
error: error.response?.data?.message || 'Failed to fetch categories',
224+
error: error.response?.data?.message || error.message || 'Failed to fetch categories',
168225
status: error.response?.status || 500,
169226
};
170227
}
@@ -182,19 +239,25 @@ export const getRecommendedProducts = async (id, limit = 3) => {
182239
throw new Error('Product ID is required');
183240
}
184241

185-
// const response = await axiosInstance.get(`/products/recommended/${id}?limit=${limit}`);
186242
const response = await axiosInstance.get(`/products/${id}/recommended?limit=${limit}`);
243+
const data = response.data;
187244

245+
// data may be { products: [...] } or an array
246+
const productsArray = Array.isArray(data)
247+
? data
248+
: Array.isArray(data.products)
249+
? data.products
250+
: data.data?.products ?? [];
188251

189252
return {
190253
success: true,
191-
data: response.data.products.map(transformProduct),
254+
data: productsArray.map(transformProduct).filter(Boolean),
192255
status: response.status,
193256
};
194257
} catch (error) {
195258
return {
196259
success: false,
197-
error: error.response?.data?.message || 'Failed to fetch recommended products',
260+
error: error.response?.data?.message || error.message || 'Failed to fetch recommended products',
198261
status: error.response?.status || 500,
199262
};
200263
}

src/components/ProductDetails.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export default function ProductDetails({ product }) {
3333
// Defensive normalization
3434
const qualities = Array.isArray(product?.quality) ? product.quality : [];
3535

36+
console.log(qualities);
37+
3638
const usage = product?.usageTips || {
3739
when: 'Best used post-workout for muscle recovery or anytime during the day to support lean muscle growth.',
3840
blend:

src/components/RecentlyViewed.jsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,9 @@ export default function RecentlyViewed({ saleOnly = false }) {
7171
<section className="max-w-6xl mx-auto px-3 sm:px-6 lg:px-8 py-8">
7272
{/* Header section with title and navigation arrows */}
7373
<div className="flex items-center justify-between mb-6 flex-col sm:flex-row gap-4 sm:gap-0">
74-
<h2 className="section-title text-center w-full sm:w-auto mb-0">
74+
<h2 className="section-title text-[32px] lg:text-[48px]">
7575
<span className="text-[#000]">RECENTLY </span>
76-
<span className="stroke-title" style={{ color: '#f7faff' }}>
77-
VIEWED
78-
</span>
76+
<span className="text-[#f7faff] stroke-title">VIEWED</span>
7977
</h2>
8078

8179
{/* Navigation Buttons */}

src/index.css

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
COMPONENTS
4545
========================== */
4646
@layer components {
47+
4748
/* Section Title */
4849
.section-title {
4950
@apply text-4xl lg:text-heading-xxl font-montserrat font-bold leading-none uppercase text-center tracking-tight;
@@ -247,6 +248,7 @@
247248
ACCESSIBILITY
248249
========================== */
249250
@media (prefers-reduced-motion: reduce) {
251+
250252
.link-underline::after,
251253
.link-underline-inverse::after,
252254
.animate-double-pop,
@@ -280,4 +282,5 @@ body {
280282
height: 100%;
281283
background-color: var(--color-white-back);
282284
color: var(--color-black);
283-
}
285+
overflow-x: hidden;
286+
}

src/pages/Products/HighlyRecommended/HighlyRecommended.jsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,18 @@ function HighlyRecommended() {
9090
return (
9191
<>
9292
{recommendedProducts?.length > 0 && (
93-
<section className="max-w-6xl mx-auto px-3 sm:px-6 lg:px-8 py-8">
93+
<section className="max-w-6xl mx-auto px-3 sm:px-6 lg:px-8 py-8 flex flex-col gap-6">
9494
{/* Header section with title and navigation arrows */}
95-
<div className="flex items-center justify-between mb-6 flex-col sm:flex-row gap-4 sm:gap-0">
96-
<h2 className="section-title text-center w-full sm:w-auto mb-0">
95+
<div className="flex items-center justify-between flex-col sm:flex-row gap-4 sm:gap-0">
96+
<h2 className="section-title text-left md:text-center w-full sm:w-auto mb-0">
9797
<span className="text-[#000]">HIGHLY </span>
9898
<span className="stroke-title" style={{ color: '#f7faff' }}>
9999
RECOMMENDED
100100
</span>
101101
</h2>
102102

103103
{/* Navigation Buttons */}
104-
<div className="flex items-center gap-3">
104+
<div className="hidden sm:flex items-center gap-3">
105105
<button
106106
onClick={prevSlide}
107107
disabled={!canGoPrev}

src/pages/Products/ProductPage.jsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export default function ProductPage() {
3030
};
3131
}, [dispatch, id]);
3232

33+
// console.log(currentProduct);
34+
3335
// Normalize possible payload wrapper
3436
const product = currentProduct?.product || currentProduct;
3537

@@ -64,7 +66,7 @@ export default function ProductPage() {
6466
if (product)
6567
return (
6668
<main className="max-w-6xl mx-auto p-6">
67-
<section className="flex flex-row w-full mt-4 max-w-[70vw] gap-8">
69+
<section className="flex flex-col md:flex-row w-full md:align-center mt-4 md:max-w-[70vw] gap-8">
6870
{/* Left side section */}
6971
<div className="Left-side flex-4 images-section">
7072
{/* Hero Image section */}
@@ -106,7 +108,7 @@ export default function ProductPage() {
106108
</button>
107109
</div>
108110
{/* Additional image section */}
109-
<section className="additional-images-grid grid grid-cols-4 gap-4 px-4 mt-4">
111+
<div className="additional-images-grid grid grid-cols-4 gap-4 px-4 mt-4">
110112
<img
111113
className="w-full h-auto object-cover rounded-lg cursor-pointer"
112114
src="/images/products/nova-whey-vanilla-protein.jpg"
@@ -127,7 +129,7 @@ export default function ProductPage() {
127129
src="/images/products/nova-whey-vanilla-protein.jpg"
128130
alt={product.name}
129131
/>
130-
</section>
132+
</div>
131133
</div>
132134
{/* Right Side Section */}
133135
<div className="right-side flex-5 product-detail-section flex-col flex gap-3">
@@ -141,15 +143,15 @@ export default function ProductPage() {
141143
<h1 className="text-4xl font-bold mb-2">{product.name}</h1>
142144
<p className="text-gray-700">{product.longDescription}</p>
143145
{/* Flavour and size selection */}
144-
<section className="flavours-size-section flex flex-col gap-4 ">
146+
<div className="flavours-size-section flex flex-col gap-4 ">
145147
{/* flavour selection */}
146-
<div className="flavours-selection items-center justify-start flex flex-row gap-4 ">
147-
<h3 className="text-gray-800 font-semibold ">Flavors :</h3>
148-
<div className="flavours-selection flex flex-row gap-4 ">
148+
<div className="flavours-selection md:items-center justify-start flex flex-row gap-4 ">
149+
<h3 className="text-gray-800 font-semibold ">Flavors:</h3>
150+
<div className="flavours-selection flex flex-row gap-4 flex-wrap md:flex-nowrap">
149151
{product.flavors.map((flavor) => (
150152
<div
151153
key={flavor}
152-
className="flavor-option border border-gray-300 px-4 py-2 rounded-md cursor-pointer hover:bg-gray-100"
154+
className="flavor-option border border-gray-300 px-4 py-2 rounded-md cursor-pointer hover:bg-gray-100 "
153155
>
154156
{flavor}
155157
</div>
@@ -168,8 +170,8 @@ export default function ProductPage() {
168170
</div>
169171
))}
170172
</div>
171-
</section>
172-
<section className="actions-section flex flex-col gap-4 mt-4">
173+
</div>
174+
<div className="actions-section flex flex-col gap-4 mt-4">
173175
<div className="product-count flex flex-row items-center gap-2">
174176
{/* <button className="cursor-pointer text-gray-600 px-3 py-3 rounded-md hover:bg-gray-50 transition">
175177
-
@@ -200,8 +202,8 @@ export default function ProductPage() {
200202
>
201203
{isInWishlist ? 'Remove from Wishlist' : 'Add to Wishlist'}
202204
</button>
203-
</section>
204-
<section className="shipping-info flex-col gap-2 text-sm text-gray-600 mt-4">
205+
</div>
206+
<div className="shipping-info flex-col gap-2 text-sm text-gray-600 mt-4">
205207
<h4 className="font-semibold text-xs text-gray-800">
206208
Secure checkout. Satisfaction guaranteed.
207209
</h4>
@@ -215,7 +217,7 @@ export default function ProductPage() {
215217
Delivery in 1-3 Days on average.
216218
</div>
217219
</div>
218-
</section>
220+
</div>
219221
</div>
220222
</section>
221223
{/* Additional Product Information */}

0 commit comments

Comments
 (0)