@pradip1995/theme-valero 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/package.json +59 -0
- package/src/blocks/home/Features/index.tsx +61 -0
- package/src/blocks/home/Hero/index.tsx +102 -0
- package/src/blocks/home/LovedByMoms/index.tsx +407 -0
- package/src/blocks/home/NewArrivals/index.tsx +48 -0
- package/src/blocks/home/ShopByAge/index.tsx +128 -0
- package/src/blocks/home/ShopByCategory/index.tsx +409 -0
- package/src/blocks/home/Testimonials/index.tsx +697 -0
- package/src/blocks/home/WhyChooseUs/index.tsx +62 -0
- package/src/layouts/MainLayoutShell.tsx +14 -0
- package/src/primitives/Button.tsx +30 -0
- package/src/primitives/Card.tsx +32 -0
- package/src/primitives/index.ts +2 -0
- package/src/slots/account/ForgotPassword/index.tsx +1 -0
- package/src/slots/account/Login/index.tsx +1 -0
- package/src/slots/account/LoginTemplate/index.tsx +44 -0
- package/src/slots/account/Register/index.tsx +1 -0
- package/src/slots/cart/CartItem/index.tsx +11 -0
- package/src/slots/cart/CartSummary/index.tsx +13 -0
- package/src/slots/checkout/CheckoutForm/index.tsx +1 -0
- package/src/slots/checkout/CheckoutSummary/index.tsx +1 -0
- package/src/slots/layout/Footer/index.tsx +104 -0
- package/src/slots/layout/Nav/index.tsx +97 -0
- package/src/slots/layout/PromoBar/index.tsx +19 -0
- package/src/slots/layout/PromoBar/promo-bar-content.tsx +174 -0
- package/src/slots/order/OrderDetails/index.tsx +12 -0
- package/src/slots/product/ProductActions/ProductCTASection.tsx +191 -0
- package/src/slots/product/ProductActions/ProductDetailsSection.tsx +137 -0
- package/src/slots/product/ProductActions/ProductFeaturePanel.tsx +245 -0
- package/src/slots/product/ProductActions/ProductHighlightsSection.tsx +99 -0
- package/src/slots/product/ProductActions/ProductOptionsSection.tsx +234 -0
- package/src/slots/product/ProductActions/ProductPriceSection.tsx +53 -0
- package/src/slots/product/ProductActions/ProductTrustSection.tsx +84 -0
- package/src/slots/product/ProductActions/index.tsx +161 -0
- package/src/slots/product/ProductCard/index.tsx +132 -0
- package/src/slots/product/ProductInfo/index.tsx +40 -0
- package/src/templates/StorePage/index.tsx +154 -0
- package/src/tokens/colors.js +16 -0
- package/src/tokens/colors.ts +21 -0
- package/src/tokens/fonts.ts +13 -0
- package/src/tokens/index.ts +3 -0
- package/src/tokens/spacing.ts +9 -0
- package/src/tokens/theme.css +91 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import Image from "next/image"
|
|
2
|
+
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
|
3
|
+
import { HttpTypes } from "@medusajs/types"
|
|
4
|
+
import PlaceholderImage from "@modules/common/icons/placeholder-image"
|
|
5
|
+
|
|
6
|
+
type ShopByAgeProps = {
|
|
7
|
+
collections: HttpTypes.StoreCollection[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const getIconForCollection = (collection: HttpTypes.StoreCollection): string | null => {
|
|
11
|
+
// First try to get icon from metadata
|
|
12
|
+
const metadata = (collection as any)?.metadata || {}
|
|
13
|
+
const collectionIcon = metadata.collection_icon || null
|
|
14
|
+
|
|
15
|
+
if (collectionIcon) {
|
|
16
|
+
return collectionIcon
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Fallback to hardcoded logic if metadata doesn't have icon
|
|
20
|
+
const handle = collection.handle?.toLowerCase() || ""
|
|
21
|
+
const title = collection.title?.toLowerCase() || ""
|
|
22
|
+
const searchText = `${handle} ${title}`
|
|
23
|
+
|
|
24
|
+
if (searchText.includes("newborn") || searchText.includes("new-born") || searchText.includes("0-12")) {
|
|
25
|
+
return "/Newborn.svg"
|
|
26
|
+
}
|
|
27
|
+
if (searchText.includes("toddler") || searchText.includes("1-3")) {
|
|
28
|
+
return "/Toddler.svg"
|
|
29
|
+
}
|
|
30
|
+
if (searchText.includes("kids") || searchText.includes("kid") || searchText.includes("4-7")) {
|
|
31
|
+
return "/Kids.svg"
|
|
32
|
+
}
|
|
33
|
+
if (searchText.includes("juniors") || searchText.includes("junior") || searchText.includes("8-12")) {
|
|
34
|
+
return "/Juniors.svg"
|
|
35
|
+
}
|
|
36
|
+
if (searchText.includes("teens") || searchText.includes("teen") || searchText.includes("13-15")) {
|
|
37
|
+
return "/Teens.svg"
|
|
38
|
+
}
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ShopByAge = ({ collections }: ShopByAgeProps) => {
|
|
43
|
+
if (!collections || collections.length === 0) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Reverse collections so that last added appears last (API returns newest first)
|
|
48
|
+
const reversedCollections = [...collections].reverse()
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="w-full py-8 sm:py-10 md:py-12 lg:py-16 px-4 sm:px-5 md:px-6">
|
|
52
|
+
<div className="max-w-[1440px] mx-auto">
|
|
53
|
+
<h2 className="text-xl sm:text-2xl md:text-3xl font-bold text-center mb-6 sm:mb-8 md:mb-10 lg:mb-12 text-gray-900">
|
|
54
|
+
Shop By Age
|
|
55
|
+
</h2>
|
|
56
|
+
<div className="grid grid-cols-2 min-[500px]:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 sm:gap-5 md:gap-6 lg:gap-8 xl:gap-12 justify-items-center">
|
|
57
|
+
{reversedCollections.map((collection) => {
|
|
58
|
+
const metadata = (collection as any)?.metadata || {}
|
|
59
|
+
const collectionImage = metadata.image || (collection as any)?.thumbnail || null
|
|
60
|
+
const iconPath = getIconForCollection(collection)
|
|
61
|
+
const collectionAge = metadata.collection_age || null
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<LocalizedClientLink
|
|
65
|
+
key={collection.id}
|
|
66
|
+
href={`/store?collection=${collection.id}`}
|
|
67
|
+
data-ga-event="shop_by_age_click"
|
|
68
|
+
data-ga-label={collection.title || "Collection"}
|
|
69
|
+
data-ga-collection-name={collection.title || ""}
|
|
70
|
+
data-ga-collection-handle={collection.handle || ""}
|
|
71
|
+
data-ga-collection-id={collection.id || ""}
|
|
72
|
+
data-ga-collection-age={collectionAge || ""}
|
|
73
|
+
data-ga-section="Shop by Age"
|
|
74
|
+
className="flex flex-col items-center group transition-transform duration-200 hover:scale-105 w-full"
|
|
75
|
+
>
|
|
76
|
+
{/* Wrap all content in a div with pointer-events-none to force GTM to target the Link */}
|
|
77
|
+
<div className="flex flex-col items-center pointer-events-none">
|
|
78
|
+
<div className="relative w-24 h-24 sm:w-28 sm:h-28 md:w-32 md:h-32 lg:w-40 lg:h-40 xl:w-[180px] xl:h-[180px] mb-2 sm:mb-3 md:mb-4 rounded-full overflow-hidden bg-gray-100 flex items-center justify-center">
|
|
79
|
+
{collectionImage ? (
|
|
80
|
+
<Image
|
|
81
|
+
src={collectionImage}
|
|
82
|
+
alt={collection.title || "Collection"}
|
|
83
|
+
fill
|
|
84
|
+
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 25vw, 20vw"
|
|
85
|
+
className="w-full h-full object-cover"
|
|
86
|
+
/>
|
|
87
|
+
) : iconPath ? (
|
|
88
|
+
<Image
|
|
89
|
+
src={iconPath}
|
|
90
|
+
alt={collection.title || "Collection"}
|
|
91
|
+
fill
|
|
92
|
+
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 25vw, 20vw"
|
|
93
|
+
className="w-full h-full object-contain"
|
|
94
|
+
/>
|
|
95
|
+
) : (
|
|
96
|
+
<div className="w-full h-full flex items-center justify-center">
|
|
97
|
+
<PlaceholderImage size={80} />
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
<h3 className="text-xs sm:text-sm md:text-base lg:text-lg font-semibold text-gray-900 mb-0.5 sm:mb-1 group-hover:text-pink-500 transition-colors text-center px-1">
|
|
102
|
+
{collection.title}
|
|
103
|
+
</h3>
|
|
104
|
+
{collectionAge && (
|
|
105
|
+
<p className="text-[10px] sm:text-xs md:text-sm text-gray-600 text-center mb-0.5 sm:mb-1 px-1">
|
|
106
|
+
{collectionAge}
|
|
107
|
+
</p>
|
|
108
|
+
)}
|
|
109
|
+
{collection.handle && !collectionAge && (
|
|
110
|
+
<p className="text-[10px] sm:text-xs md:text-sm text-gray-600 text-center px-1">
|
|
111
|
+
{collection.handle
|
|
112
|
+
.split(" ")
|
|
113
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
114
|
+
.join(" ")}
|
|
115
|
+
</p>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
</LocalizedClientLink>
|
|
119
|
+
)
|
|
120
|
+
})}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default ShopByAge
|
|
128
|
+
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import Image from "next/image"
|
|
2
|
+
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
|
3
|
+
import { HttpTypes } from "@medusajs/types"
|
|
4
|
+
import PlaceholderImage from "@modules/common/icons/placeholder-image"
|
|
5
|
+
|
|
6
|
+
type ShopByCategoryProps = {
|
|
7
|
+
categories: HttpTypes.StoreProductCategory[]
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ShopByCategory = ({ categories }: ShopByCategoryProps) => {
|
|
11
|
+
if (!categories || categories.length === 0) {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Filter for subcategories (categories with parent_category_id)
|
|
16
|
+
const subcategories = categories.filter(
|
|
17
|
+
(category) => category.parent_category_id && category.parent_category
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// Find Traditional and Dresses categories
|
|
21
|
+
const traditionalCategory = subcategories.find(c => {
|
|
22
|
+
const name = (c.name || "").toLowerCase()
|
|
23
|
+
return name.includes("traditional")
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const dressesCategory = subcategories.find(c => {
|
|
27
|
+
const name = (c.name || "").toLowerCase()
|
|
28
|
+
return name.includes("dress")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Get other categories (excluding Traditional and Dresses)
|
|
32
|
+
const otherCategories = subcategories.filter(c => {
|
|
33
|
+
const name = (c.name || "").toLowerCase()
|
|
34
|
+
return !name.includes("traditional") && !name.includes("dress")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Build display order: first 3 other categories, then Traditional (index 3), Dresses (index 4), then 6th category
|
|
38
|
+
const displayCategories: typeof subcategories = []
|
|
39
|
+
let otherIndex = 0
|
|
40
|
+
|
|
41
|
+
// 1. Fill up to first 3 slots with other categories
|
|
42
|
+
while (displayCategories.length < 3 && otherIndex < otherCategories.length) {
|
|
43
|
+
displayCategories.push(otherCategories[otherIndex++])
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Add Traditional or next other category
|
|
47
|
+
if (traditionalCategory) {
|
|
48
|
+
displayCategories.push(traditionalCategory)
|
|
49
|
+
} else if (otherIndex < otherCategories.length) {
|
|
50
|
+
displayCategories.push(otherCategories[otherIndex++])
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Add Dresses or next other category
|
|
54
|
+
if (dressesCategory) {
|
|
55
|
+
displayCategories.push(dressesCategory)
|
|
56
|
+
} else if (otherIndex < otherCategories.length) {
|
|
57
|
+
displayCategories.push(otherCategories[otherIndex++])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 4. Fill remaining slots with any remaining other categories
|
|
61
|
+
while (otherIndex < otherCategories.length) {
|
|
62
|
+
displayCategories.push(otherCategories[otherIndex++])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// No longer slice to 6 - allow all categories
|
|
66
|
+
const finalDisplayCategories = displayCategories
|
|
67
|
+
|
|
68
|
+
if (finalDisplayCategories.length === 0) {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className="w-full py-8 lg:py-16 bg-page-bg">
|
|
74
|
+
<div className="w-full px-4 sm:px-6 md:px-8 lg:px-12 xl:px-16">
|
|
75
|
+
<div className="w-full max-w-[1360px] mx-auto">
|
|
76
|
+
<h2 className="font-display text-2xl sm:text-3xl text-heading text-center mb-8 lg:mb-12">
|
|
77
|
+
Shop by category
|
|
78
|
+
</h2>
|
|
79
|
+
{/* Tablet Grid Layout - 768px to 1023px */}
|
|
80
|
+
<div
|
|
81
|
+
className="hidden md:grid lg:hidden gap-3 md:gap-4"
|
|
82
|
+
style={{
|
|
83
|
+
gridTemplateColumns: 'repeat(4, 1fr)',
|
|
84
|
+
gridAutoRows: 'minmax(280px, 350px)',
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
{finalDisplayCategories.map((category, index) => {
|
|
88
|
+
// Calculate grid positioning based on 6-item repeating pattern
|
|
89
|
+
// Row A (Indices 0,1,2): Large (2 col), Small (1 col), Small (1 col)
|
|
90
|
+
// Row B (Indices 3,4,5): Small (1 col), Small (1 col), Large (2 col)
|
|
91
|
+
|
|
92
|
+
const positionInPattern = index % 6
|
|
93
|
+
const rowGroup = Math.floor(index / 6)
|
|
94
|
+
const rowBase = (rowGroup * 2) + 1
|
|
95
|
+
|
|
96
|
+
let gridColumn = ""
|
|
97
|
+
let gridRow = ""
|
|
98
|
+
|
|
99
|
+
if (positionInPattern === 0) {
|
|
100
|
+
gridColumn = "1 / 3" // Span 2 cols
|
|
101
|
+
gridRow = `${rowBase}`
|
|
102
|
+
} else if (positionInPattern === 1) {
|
|
103
|
+
gridColumn = "3"
|
|
104
|
+
gridRow = `${rowBase}`
|
|
105
|
+
} else if (positionInPattern === 2) {
|
|
106
|
+
gridColumn = "4"
|
|
107
|
+
gridRow = `${rowBase}`
|
|
108
|
+
} else if (positionInPattern === 3) {
|
|
109
|
+
gridColumn = "1"
|
|
110
|
+
gridRow = `${rowBase + 1}`
|
|
111
|
+
} else if (positionInPattern === 4) {
|
|
112
|
+
gridColumn = "2"
|
|
113
|
+
gridRow = `${rowBase + 1}`
|
|
114
|
+
} else if (positionInPattern === 5) {
|
|
115
|
+
gridColumn = "3 / 5" // Span 2 cols
|
|
116
|
+
gridRow = `${rowBase + 1}`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Skip if position not set (shouldn't happen, but safety check)
|
|
120
|
+
if (!gridColumn || !gridRow) {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Get metadata
|
|
125
|
+
const metadata = (category as any)?.metadata || {}
|
|
126
|
+
const categoryImage = metadata.category_image || null
|
|
127
|
+
const backgroundColor = metadata.category_background_color || "#F3F4F6"
|
|
128
|
+
const showShopNow = metadata.show_shop_now === "true" || metadata.show_shop_now === true
|
|
129
|
+
|
|
130
|
+
// Get parent category name as subtitle
|
|
131
|
+
const parentCategory = (category as any)?.parent_category
|
|
132
|
+
const subtitle = parentCategory?.name || ""
|
|
133
|
+
|
|
134
|
+
// Get subcategory name as main title (in uppercase)
|
|
135
|
+
const mainTitle = (category.name || "").toUpperCase()
|
|
136
|
+
|
|
137
|
+
// Build category URL - use store page with category filter
|
|
138
|
+
const categoryUrl = category.id
|
|
139
|
+
? `/store?category=${category.id}`
|
|
140
|
+
: "#"
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<LocalizedClientLink
|
|
144
|
+
key={`tablet-${category.id}`}
|
|
145
|
+
href={categoryUrl}
|
|
146
|
+
data-ga-event="shop_by_category_click"
|
|
147
|
+
data-ga-label={category.name || "Category"}
|
|
148
|
+
data-ga-category-name={category.name || ""}
|
|
149
|
+
data-ga-category-handle={category.handle || ""}
|
|
150
|
+
data-ga-category-id={category.id || ""}
|
|
151
|
+
data-ga-parent-category={subtitle || ""}
|
|
152
|
+
className="relative rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow duration-200 flex flex-col group w-full"
|
|
153
|
+
style={{
|
|
154
|
+
backgroundColor: backgroundColor,
|
|
155
|
+
gridColumn: gridColumn,
|
|
156
|
+
gridRow: gridRow,
|
|
157
|
+
minHeight: "280px",
|
|
158
|
+
}}
|
|
159
|
+
>
|
|
160
|
+
<div className="flex flex-col h-full p-3 md:p-4 pb-0 md:pb-0 pointer-events-none">
|
|
161
|
+
{/* Category Title */}
|
|
162
|
+
<div className="mb-2 md:mb-3">
|
|
163
|
+
{subtitle && (
|
|
164
|
+
<p className="text-xs text-gray-700 mb-1">{subtitle}</p>
|
|
165
|
+
)}
|
|
166
|
+
<h3 className="text-sm md:text-base lg:text-lg font-bold text-gray-900">
|
|
167
|
+
{mainTitle}
|
|
168
|
+
</h3>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
{/* Category Image */}
|
|
172
|
+
<div className="flex-1 flex items-end justify-center relative" style={{ minHeight: 0 }}>
|
|
173
|
+
{categoryImage ? (
|
|
174
|
+
<div className="w-full h-full flex items-end justify-center">
|
|
175
|
+
<Image
|
|
176
|
+
src={categoryImage}
|
|
177
|
+
alt={category.name || "Category"}
|
|
178
|
+
fill
|
|
179
|
+
sizes="(max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
|
|
180
|
+
className="object-contain object-bottom"
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
) : (
|
|
184
|
+
<div className="w-full h-full flex items-end justify-center">
|
|
185
|
+
<PlaceholderImage size={90} />
|
|
186
|
+
</div>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</LocalizedClientLink>
|
|
191
|
+
)
|
|
192
|
+
})}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{/* Desktop Grid Layout - 1024px and above */}
|
|
196
|
+
<div
|
|
197
|
+
className="hidden lg:grid gap-4 xl:gap-6"
|
|
198
|
+
style={{
|
|
199
|
+
gridTemplateColumns: 'repeat(4, 1fr)',
|
|
200
|
+
gridAutoRows: 'minmax(300px, 390px)',
|
|
201
|
+
}}
|
|
202
|
+
>
|
|
203
|
+
{finalDisplayCategories.map((category, index) => {
|
|
204
|
+
// Same 4-column logic for Desktop
|
|
205
|
+
const positionInPattern = index % 6
|
|
206
|
+
const rowGroup = Math.floor(index / 6)
|
|
207
|
+
const rowBase = (rowGroup * 2) + 1
|
|
208
|
+
|
|
209
|
+
let gridColumn = ""
|
|
210
|
+
let gridRow = ""
|
|
211
|
+
|
|
212
|
+
if (positionInPattern === 0) {
|
|
213
|
+
gridColumn = "1 / 3"
|
|
214
|
+
gridRow = `${rowBase}`
|
|
215
|
+
} else if (positionInPattern === 1) {
|
|
216
|
+
gridColumn = "3"
|
|
217
|
+
gridRow = `${rowBase}`
|
|
218
|
+
} else if (positionInPattern === 2) {
|
|
219
|
+
gridColumn = "4"
|
|
220
|
+
gridRow = `${rowBase}`
|
|
221
|
+
} else if (positionInPattern === 3) {
|
|
222
|
+
gridColumn = "1"
|
|
223
|
+
gridRow = `${rowBase + 1}`
|
|
224
|
+
} else if (positionInPattern === 4) {
|
|
225
|
+
gridColumn = "2"
|
|
226
|
+
gridRow = `${rowBase + 1}`
|
|
227
|
+
} else if (positionInPattern === 5) {
|
|
228
|
+
gridColumn = "3 / 5"
|
|
229
|
+
gridRow = `${rowBase + 1}`
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Skip if position not set (shouldn't happen, but safety check)
|
|
233
|
+
if (!gridColumn || !gridRow) {
|
|
234
|
+
return null
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Get metadata
|
|
238
|
+
const metadata = (category as any)?.metadata || {}
|
|
239
|
+
const categoryImage = metadata.category_image || null
|
|
240
|
+
const backgroundColor = metadata.category_background_color || "#F3F4F6"
|
|
241
|
+
const showShopNow = metadata.show_shop_now === "true" || metadata.show_shop_now === true
|
|
242
|
+
|
|
243
|
+
// Get parent category name as subtitle
|
|
244
|
+
const parentCategory = (category as any)?.parent_category
|
|
245
|
+
const subtitle = parentCategory?.name || ""
|
|
246
|
+
|
|
247
|
+
// Get subcategory name as main title (in uppercase)
|
|
248
|
+
const mainTitle = (category.name || "").toUpperCase()
|
|
249
|
+
|
|
250
|
+
// Build category URL - use store page with category filter
|
|
251
|
+
const categoryUrl = category.id
|
|
252
|
+
? `/store?category=${category.id}`
|
|
253
|
+
: "#"
|
|
254
|
+
|
|
255
|
+
return (
|
|
256
|
+
<LocalizedClientLink
|
|
257
|
+
key={category.id}
|
|
258
|
+
href={categoryUrl}
|
|
259
|
+
data-ga-event="shop_by_category_click"
|
|
260
|
+
data-ga-label={category.name || "Category"}
|
|
261
|
+
data-ga-category-name={category.name || ""}
|
|
262
|
+
data-ga-category-handle={category.handle || ""}
|
|
263
|
+
data-ga-category-id={category.id || ""}
|
|
264
|
+
data-ga-parent-category={subtitle || ""}
|
|
265
|
+
className="relative rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow duration-200 flex flex-col group w-full"
|
|
266
|
+
style={{
|
|
267
|
+
backgroundColor: backgroundColor,
|
|
268
|
+
gridColumn: gridColumn,
|
|
269
|
+
gridRow: gridRow,
|
|
270
|
+
minHeight: "300px",
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
<div className="flex flex-col h-full p-4 sm:p-5 lg:p-6 pb-0 sm:pb-0 lg:pb-0 pointer-events-none">
|
|
274
|
+
{/* Category Title */}
|
|
275
|
+
<div className="mb-3 lg:mb-4">
|
|
276
|
+
{subtitle && (
|
|
277
|
+
<p className="text-xs sm:text-sm text-gray-700 mb-1">{subtitle}</p>
|
|
278
|
+
)}
|
|
279
|
+
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900">
|
|
280
|
+
{mainTitle}
|
|
281
|
+
</h3>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
{/* Category Image */}
|
|
285
|
+
<div className="flex-1 flex items-end justify-center relative" style={{ minHeight: 0 }}>
|
|
286
|
+
{categoryImage ? (
|
|
287
|
+
<div className="w-full h-full flex items-end justify-center">
|
|
288
|
+
<Image
|
|
289
|
+
src={categoryImage}
|
|
290
|
+
alt={category.name || "Category"}
|
|
291
|
+
fill
|
|
292
|
+
sizes="(max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
|
|
293
|
+
className="object-contain object-bottom"
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
) : (
|
|
297
|
+
<div className="w-full h-full flex items-end justify-center">
|
|
298
|
+
<PlaceholderImage size={110} />
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
</LocalizedClientLink>
|
|
304
|
+
)
|
|
305
|
+
})}
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{/* Mobile Layout - Below 768px */}
|
|
309
|
+
<style dangerouslySetInnerHTML={{
|
|
310
|
+
__html: `
|
|
311
|
+
@media (min-width: 350px) and (max-width: 767px) {
|
|
312
|
+
.shop-category-mobile-container {
|
|
313
|
+
grid-template-columns: repeat(2, 1fr) !important;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
@media (min-width: 350px) and (max-width: 450px) {
|
|
317
|
+
.shop-category-mobile-card {
|
|
318
|
+
aspect-ratio: 0.55 !important;
|
|
319
|
+
min-height: 210px !important;
|
|
320
|
+
height: 210px !important;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
@media (min-width: 450px) and (max-width: 550px) {
|
|
324
|
+
.shop-category-mobile-card {
|
|
325
|
+
aspect-ratio: 0.75 !important;
|
|
326
|
+
min-height: 250px !important;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
@media (min-width: 550px) and (max-width: 767px) {
|
|
330
|
+
.shop-category-mobile-card {
|
|
331
|
+
aspect-ratio: 0.80 !important;
|
|
332
|
+
min-height: 280px !important;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
`
|
|
336
|
+
}} />
|
|
337
|
+
<div
|
|
338
|
+
className="grid grid-cols-1 md:hidden gap-3 sm:gap-4 shop-category-mobile-container"
|
|
339
|
+
>
|
|
340
|
+
{finalDisplayCategories.map((category) => {
|
|
341
|
+
const metadata = (category as any)?.metadata || {}
|
|
342
|
+
const categoryImage = metadata.category_image || null
|
|
343
|
+
const backgroundColor = metadata.category_background_color || "#F3F4F6"
|
|
344
|
+
const showShopNow = metadata.show_shop_now === "true" || metadata.show_shop_now === true
|
|
345
|
+
|
|
346
|
+
const parentCategory = (category as any)?.parent_category
|
|
347
|
+
const subtitle = parentCategory?.name || ""
|
|
348
|
+
const mainTitle = (category.name || "").toUpperCase()
|
|
349
|
+
const categoryUrl = category.id
|
|
350
|
+
? `/store?category=${category.id}`
|
|
351
|
+
: "#"
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<LocalizedClientLink
|
|
355
|
+
key={category.id}
|
|
356
|
+
href={categoryUrl}
|
|
357
|
+
data-ga-event="shop_by_category_click"
|
|
358
|
+
data-ga-label={category.name || "Category"}
|
|
359
|
+
data-ga-category-name={category.name || ""}
|
|
360
|
+
data-ga-category-handle={category.handle || ""}
|
|
361
|
+
data-ga-category-id={category.id || ""}
|
|
362
|
+
data-ga-parent-category={subtitle || ""}
|
|
363
|
+
className="relative rounded-lg overflow-hidden shadow-sm hover:shadow-md transition-shadow duration-200 flex flex-col group w-full shop-category-mobile-card"
|
|
364
|
+
style={{
|
|
365
|
+
backgroundColor: backgroundColor,
|
|
366
|
+
width: '100%',
|
|
367
|
+
aspectRatio: '0.85', // Default aspect ratio
|
|
368
|
+
minHeight: '300px',
|
|
369
|
+
}}
|
|
370
|
+
>
|
|
371
|
+
<div className="flex flex-col h-full p-4 sm:p-5 pb-0 sm:pb-0 pointer-events-none">
|
|
372
|
+
<div className="mb-3">
|
|
373
|
+
{subtitle && (
|
|
374
|
+
<p className="text-xs sm:text-sm text-gray-700 mb-1">{subtitle}</p>
|
|
375
|
+
)}
|
|
376
|
+
<h3 className="text-lg sm:text-xl font-bold text-gray-900">
|
|
377
|
+
{mainTitle}
|
|
378
|
+
</h3>
|
|
379
|
+
</div>
|
|
380
|
+
<div className="flex-1 flex items-end justify-center relative" style={{ minHeight: 0 }}>
|
|
381
|
+
{categoryImage ? (
|
|
382
|
+
<div className="w-full h-full flex items-end justify-center">
|
|
383
|
+
<Image
|
|
384
|
+
src={categoryImage}
|
|
385
|
+
alt={category.name || "Category"}
|
|
386
|
+
fill
|
|
387
|
+
sizes="(max-width: 768px) 50vw, (max-width: 1024px) 33vw, 25vw"
|
|
388
|
+
className="object-contain object-bottom"
|
|
389
|
+
/>
|
|
390
|
+
</div>
|
|
391
|
+
) : (
|
|
392
|
+
<div className="w-full h-full flex items-end justify-center">
|
|
393
|
+
<PlaceholderImage size={120} />
|
|
394
|
+
</div>
|
|
395
|
+
)}
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
</LocalizedClientLink>
|
|
399
|
+
)
|
|
400
|
+
})}
|
|
401
|
+
</div>
|
|
402
|
+
</div>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export default ShopByCategory
|
|
409
|
+
|