@pradip1995/theme-sahsha 3.1.0
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 +29 -0
- package/assets/hero-desktop.svg +10 -0
- package/assets/hero-mobile.svg +9 -0
- package/assets/logo.svg +3 -0
- package/package.json +60 -0
- package/src/blocks/home/Features/index.tsx +87 -0
- package/src/blocks/home/Hero/index.tsx +98 -0
- package/src/blocks/home/LovedByMoms/bestsellers-carousel.tsx +1 -0
- package/src/blocks/home/LovedByMoms/index.tsx +43 -0
- package/src/blocks/home/LovedByMoms/loved-by-moms-section.tsx +46 -0
- package/src/blocks/home/NewArrivals/index.tsx +91 -0
- package/src/blocks/home/PromotionalBanners/index.tsx +81 -0
- package/src/blocks/home/ShopByAge/collections-showcase-client.tsx +131 -0
- package/src/blocks/home/ShopByAge/collections-showcase-types.ts +4 -0
- package/src/blocks/home/ShopByAge/index.tsx +168 -0
- package/src/blocks/home/ShopByCategory/index.tsx +111 -0
- package/src/blocks/home/Testimonials/index.tsx +25 -0
- package/src/blocks/home/Testimonials/reviews-scroll.tsx +122 -0
- package/src/blocks/home/Testimonials/testimonials-client.tsx +127 -0
- package/src/blocks/home/WhyChooseUs/index.tsx +39 -0
- package/src/components/product-carousel.tsx +79 -0
- package/src/layouts/MainLayoutShell.tsx +14 -0
- package/src/primitives/Button.tsx +31 -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/GoogleLogin/index.tsx +28 -0
- package/src/slots/account/Login/index.tsx +1 -0
- package/src/slots/account/LoginTemplate/index.tsx +12 -0
- package/src/slots/account/LoginTemplate/login-template-client.tsx +83 -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 +8 -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 +95 -0
- package/src/slots/layout/Nav/index.tsx +50 -0
- package/src/slots/layout/Nav/nav-categories-dropdown.tsx +74 -0
- package/src/slots/layout/Nav/nav-collections-dropdown.tsx +106 -0
- package/src/slots/layout/Nav/nav-header-content.tsx +165 -0
- package/src/slots/layout/Nav/nav-header-shell.tsx +47 -0
- package/src/slots/layout/Nav/nav-link-luxury.tsx +15 -0
- package/src/slots/layout/PromoBar/index.tsx +9 -0
- package/src/slots/layout/PromoBar/promo-bar-content.tsx +118 -0
- package/src/slots/order/OrderDetails/index.tsx +12 -0
- package/src/slots/product/ProductActions/ProductCTASection.tsx +232 -0
- package/src/slots/product/ProductActions/ProductDetailsSection.tsx +200 -0
- package/src/slots/product/ProductActions/ProductFeaturePanel.tsx +150 -0
- package/src/slots/product/ProductActions/ProductHighlightsSection.tsx +112 -0
- package/src/slots/product/ProductActions/ProductOptionsSection.tsx +215 -0
- package/src/slots/product/ProductActions/ProductPriceSection.tsx +53 -0
- package/src/slots/product/ProductActions/ProductTrustSection.tsx +84 -0
- package/src/slots/product/ProductActions/SizeChartPanel.tsx +93 -0
- package/src/slots/product/ProductActions/index.tsx +156 -0
- package/src/slots/product/ProductActions/product-metadata-fields.ts +503 -0
- package/src/slots/product/ProductActions/size-chart-data.ts +108 -0
- package/src/slots/product/ProductCard/index.tsx +258 -0
- package/src/slots/product/ProductInfo/index.tsx +35 -0
- package/src/templates/CollectionsPage/index.tsx +72 -0
- package/src/templates/StorePage/index.tsx +134 -0
- package/src/tokens/colors.ts +21 -0
- package/src/tokens/fonts.ts +16 -0
- package/src/tokens/index.ts +3 -0
- package/src/tokens/spacing.ts +9 -0
- package/src/tokens/theme.css +12754 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useRouter } from "next/navigation"
|
|
4
|
+
import { HttpTypes } from "@medusajs/types"
|
|
5
|
+
import type { ProductOptions } from "@core/domain/product/variant-selection"
|
|
6
|
+
|
|
7
|
+
export type ProductCTASectionProps = {
|
|
8
|
+
product: HttpTypes.StoreProduct
|
|
9
|
+
selectedVariant?: HttpTypes.StoreProductVariant
|
|
10
|
+
options: ProductOptions
|
|
11
|
+
isValidVariant: boolean
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
inStock: boolean
|
|
14
|
+
inventoryLimit: number | null
|
|
15
|
+
quantity: number
|
|
16
|
+
setQuantity: (value: number) => void
|
|
17
|
+
quantityInCart: number
|
|
18
|
+
variantInCart: HttpTypes.StoreCartLineItem | undefined
|
|
19
|
+
isAdding: boolean
|
|
20
|
+
isBuyingNow: boolean
|
|
21
|
+
showNotifyMessage: boolean
|
|
22
|
+
setShowNotifyMessage: (v: boolean) => void
|
|
23
|
+
handleAddToCart: () => void
|
|
24
|
+
handleBuyNow: () => void
|
|
25
|
+
handleIncreaseQuantity: () => void
|
|
26
|
+
handleDecreaseQuantity: () => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const btnBase =
|
|
30
|
+
"product-cta-btn h-12 flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
31
|
+
|
|
32
|
+
export function ProductCTASection(props: ProductCTASectionProps) {
|
|
33
|
+
const router = useRouter()
|
|
34
|
+
const {
|
|
35
|
+
product,
|
|
36
|
+
selectedVariant,
|
|
37
|
+
options,
|
|
38
|
+
isValidVariant,
|
|
39
|
+
disabled,
|
|
40
|
+
inStock,
|
|
41
|
+
inventoryLimit,
|
|
42
|
+
quantity,
|
|
43
|
+
setQuantity,
|
|
44
|
+
quantityInCart,
|
|
45
|
+
variantInCart,
|
|
46
|
+
isAdding,
|
|
47
|
+
isBuyingNow,
|
|
48
|
+
showNotifyMessage,
|
|
49
|
+
setShowNotifyMessage,
|
|
50
|
+
handleAddToCart,
|
|
51
|
+
handleBuyNow,
|
|
52
|
+
handleIncreaseQuantity,
|
|
53
|
+
handleDecreaseQuantity,
|
|
54
|
+
} = props
|
|
55
|
+
|
|
56
|
+
if (!inStock && selectedVariant) {
|
|
57
|
+
return (
|
|
58
|
+
<div className="flex flex-col gap-4 w-full">
|
|
59
|
+
<p className="text-sm font-medium text-brand-sale uppercase tracking-[var(--letter-spacing-nav)]">
|
|
60
|
+
Sold out
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
{showNotifyMessage ? (
|
|
64
|
+
<div className="border border-[var(--color-header-border)] p-5 flex flex-col gap-3">
|
|
65
|
+
<p className="text-sm text-body">
|
|
66
|
+
We'll notify you when this item is back in stock.
|
|
67
|
+
</p>
|
|
68
|
+
<button
|
|
69
|
+
onClick={() => {
|
|
70
|
+
const relatedSection = document.getElementById("related-products")
|
|
71
|
+
if (relatedSection) {
|
|
72
|
+
relatedSection.scrollIntoView({ behavior: "smooth" })
|
|
73
|
+
} else {
|
|
74
|
+
router.push("/store")
|
|
75
|
+
}
|
|
76
|
+
}}
|
|
77
|
+
className="text-sm font-medium underline underline-offset-4 text-heading hover:opacity-70 w-fit"
|
|
78
|
+
>
|
|
79
|
+
Browse similar products
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
) : (
|
|
83
|
+
<button
|
|
84
|
+
onClick={() => setShowNotifyMessage(true)}
|
|
85
|
+
className={`${btnBase} w-full bg-brand-accent text-inverse hover:bg-brand-accent-hover`}
|
|
86
|
+
>
|
|
87
|
+
Notify me when available
|
|
88
|
+
</button>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="flex flex-col gap-3 w-full">
|
|
96
|
+
{inventoryLimit !== null && inventoryLimit > 0 && inventoryLimit <= 5 && (
|
|
97
|
+
<p className="text-[10px] sm:text-xs font-bold uppercase tracking-wider text-orange-600 text-right">
|
|
98
|
+
Hurry! Only {inventoryLimit} left
|
|
99
|
+
</p>
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
<div className="flex items-stretch gap-3 w-full">
|
|
103
|
+
{variantInCart ? (
|
|
104
|
+
<div className="flex h-12 shrink-0 w-[120px] border border-brand-accent rounded-full overflow-hidden">
|
|
105
|
+
<button
|
|
106
|
+
onClick={handleDecreaseQuantity}
|
|
107
|
+
disabled={isAdding}
|
|
108
|
+
className="w-10 flex items-center justify-center hover:bg-surface-muted transition-colors disabled:opacity-50"
|
|
109
|
+
aria-label="Decrease quantity"
|
|
110
|
+
>
|
|
111
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
112
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
113
|
+
</svg>
|
|
114
|
+
</button>
|
|
115
|
+
<div className="flex-1 flex items-center justify-center text-sm font-semibold text-heading">
|
|
116
|
+
{variantInCart.quantity}
|
|
117
|
+
</div>
|
|
118
|
+
<button
|
|
119
|
+
onClick={handleIncreaseQuantity}
|
|
120
|
+
disabled={isAdding || !inStock}
|
|
121
|
+
className="w-10 flex items-center justify-center hover:bg-surface-muted transition-colors disabled:opacity-50"
|
|
122
|
+
aria-label="Increase quantity"
|
|
123
|
+
>
|
|
124
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
125
|
+
<line x1="12" y1="5" x2="12" y2="19" />
|
|
126
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
127
|
+
</svg>
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
) : (
|
|
131
|
+
<div className="flex h-12 shrink-0 w-[120px] items-center border border-[var(--color-header-border)] rounded-full overflow-hidden bg-surface">
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
onClick={() => setQuantity(Math.max(1, quantity - 1))}
|
|
135
|
+
disabled={quantity <= 1}
|
|
136
|
+
className="w-10 h-full flex items-center justify-center hover:bg-gray-50 disabled:opacity-50"
|
|
137
|
+
>
|
|
138
|
+
−
|
|
139
|
+
</button>
|
|
140
|
+
<span className="flex-1 text-center font-semibold text-gray-900">
|
|
141
|
+
{quantity}
|
|
142
|
+
</span>
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
onClick={() => {
|
|
146
|
+
if (inventoryLimit === null || quantity < inventoryLimit) {
|
|
147
|
+
setQuantity(quantity + 1)
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
disabled={inventoryLimit !== null && quantity >= inventoryLimit}
|
|
151
|
+
className="w-10 h-full flex items-center justify-center hover:bg-gray-50 disabled:opacity-50"
|
|
152
|
+
>
|
|
153
|
+
+
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
{!variantInCart && (
|
|
159
|
+
<button
|
|
160
|
+
onClick={handleAddToCart}
|
|
161
|
+
disabled={
|
|
162
|
+
!inStock ||
|
|
163
|
+
!!disabled ||
|
|
164
|
+
isAdding ||
|
|
165
|
+
isBuyingNow ||
|
|
166
|
+
(!isValidVariant && product.options?.every((opt) => options[opt.id]))
|
|
167
|
+
}
|
|
168
|
+
className={`${btnBase} flex-1 min-w-0 product-cta-btn--secondary`}
|
|
169
|
+
data-testid="add-product-button"
|
|
170
|
+
data-ga-event="add_to_bag_click"
|
|
171
|
+
data-ga-label={product.title || "Product"}
|
|
172
|
+
>
|
|
173
|
+
{isAdding ? (
|
|
174
|
+
<span className="product-cta-btn__label">
|
|
175
|
+
<Spinner />
|
|
176
|
+
</span>
|
|
177
|
+
) : (
|
|
178
|
+
<span className="product-cta-btn__label">
|
|
179
|
+
{!selectedVariant || !isValidVariant
|
|
180
|
+
? product.options?.every((opt) => options[opt.id])
|
|
181
|
+
? "Unavailable"
|
|
182
|
+
: "Select options"
|
|
183
|
+
: !inStock
|
|
184
|
+
? inventoryLimit !== null &&
|
|
185
|
+
inventoryLimit + quantityInCart > 0 &&
|
|
186
|
+
quantity > inventoryLimit
|
|
187
|
+
? inventoryLimit <= 0
|
|
188
|
+
? "Sold out"
|
|
189
|
+
: "Max quantity reached"
|
|
190
|
+
: "Sold out"
|
|
191
|
+
: `Add to Cart${quantity > 1 ? ` (${quantity})` : ""}`}
|
|
192
|
+
</span>
|
|
193
|
+
)}
|
|
194
|
+
</button>
|
|
195
|
+
)}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<button
|
|
199
|
+
onClick={handleBuyNow}
|
|
200
|
+
disabled={
|
|
201
|
+
!inStock ||
|
|
202
|
+
!!disabled ||
|
|
203
|
+
isAdding ||
|
|
204
|
+
isBuyingNow ||
|
|
205
|
+
(!isValidVariant && product.options?.every((opt) => options[opt.id]))
|
|
206
|
+
}
|
|
207
|
+
className={`${btnBase} w-full product-cta-btn--primary`}
|
|
208
|
+
>
|
|
209
|
+
{isBuyingNow ? (
|
|
210
|
+
<span className="product-cta-btn__label">
|
|
211
|
+
<Spinner />
|
|
212
|
+
</span>
|
|
213
|
+
) : (
|
|
214
|
+
<span className="product-cta-btn__label">Buy It Now</span>
|
|
215
|
+
)}
|
|
216
|
+
</button>
|
|
217
|
+
</div>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function Spinner() {
|
|
222
|
+
return (
|
|
223
|
+
<svg className="animate-spin h-5 w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
224
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
|
225
|
+
<path
|
|
226
|
+
className="opacity-75"
|
|
227
|
+
fill="currentColor"
|
|
228
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
229
|
+
/>
|
|
230
|
+
</svg>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
import { HttpTypes } from "@medusajs/types"
|
|
5
|
+
import RatingsTab from "@modules/products/components/product-tabs/ratings-tab"
|
|
6
|
+
import {
|
|
7
|
+
getProductDetailsNarratives,
|
|
8
|
+
getProductMetadataEntries,
|
|
9
|
+
hasProductDetailsContent,
|
|
10
|
+
} from "./product-metadata-fields"
|
|
11
|
+
|
|
12
|
+
function AccordionItem({
|
|
13
|
+
title,
|
|
14
|
+
isOpen,
|
|
15
|
+
onToggle,
|
|
16
|
+
children,
|
|
17
|
+
}: {
|
|
18
|
+
title: string
|
|
19
|
+
isOpen: boolean
|
|
20
|
+
onToggle: () => void
|
|
21
|
+
children: React.ReactNode
|
|
22
|
+
}) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="product-accordion__item border-t border-[var(--color-header-border)]">
|
|
25
|
+
<button
|
|
26
|
+
onClick={onToggle}
|
|
27
|
+
className="product-accordion__trigger flex items-center justify-between w-full py-4 text-left"
|
|
28
|
+
>
|
|
29
|
+
<h3 className="product-accordion__title">{title}</h3>
|
|
30
|
+
<svg
|
|
31
|
+
width="14"
|
|
32
|
+
height="14"
|
|
33
|
+
viewBox="0 0 24 24"
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="currentColor"
|
|
36
|
+
strokeWidth="2"
|
|
37
|
+
className={`transition-transform text-heading ${isOpen ? "rotate-180" : ""}`}
|
|
38
|
+
>
|
|
39
|
+
<polyline points="6 9 12 15 18 9" />
|
|
40
|
+
</svg>
|
|
41
|
+
</button>
|
|
42
|
+
{isOpen && (
|
|
43
|
+
<div className="pb-5 text-sm text-body leading-relaxed">{children}</div>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function NarrativeBlock({
|
|
50
|
+
title,
|
|
51
|
+
items,
|
|
52
|
+
}: {
|
|
53
|
+
title: string
|
|
54
|
+
items: string[]
|
|
55
|
+
}) {
|
|
56
|
+
return (
|
|
57
|
+
<div className="product-details__block">
|
|
58
|
+
<h4 className="product-details__block-title">{title}</h4>
|
|
59
|
+
{items.length === 1 ? (
|
|
60
|
+
<p className="product-details__block-text">{items[0]}</p>
|
|
61
|
+
) : (
|
|
62
|
+
<ul className="product-details__list">
|
|
63
|
+
{items.map((item, index) => (
|
|
64
|
+
<li key={index}>{item}</li>
|
|
65
|
+
))}
|
|
66
|
+
</ul>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function ProductDetailsSection({ product }: { product: HttpTypes.StoreProduct }) {
|
|
73
|
+
const metadataEntries = getProductMetadataEntries(product.metadata)
|
|
74
|
+
const narrativeSections = getProductDetailsNarratives(product.metadata)
|
|
75
|
+
const careGuide = product.metadata?.care_guide
|
|
76
|
+
const hasCareGuide =
|
|
77
|
+
careGuide !== null &&
|
|
78
|
+
careGuide !== undefined &&
|
|
79
|
+
String(careGuide).trim() !== ""
|
|
80
|
+
const showProductDetails = hasProductDetailsContent(product)
|
|
81
|
+
const [isDetailsOpen, setIsDetailsOpen] = useState(false)
|
|
82
|
+
const [isDescriptionOpen, setIsDescriptionOpen] = useState(false)
|
|
83
|
+
const [isCareGuideOpen, setIsCareGuideOpen] = useState(false)
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="product-details-section mt-2">
|
|
87
|
+
{showProductDetails && (
|
|
88
|
+
<AccordionItem
|
|
89
|
+
title="Product details"
|
|
90
|
+
isOpen={isDetailsOpen}
|
|
91
|
+
onToggle={() => setIsDetailsOpen(!isDetailsOpen)}
|
|
92
|
+
>
|
|
93
|
+
{product.title && (
|
|
94
|
+
<p className="product-details__product-title">{product.title}</p>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{narrativeSections.map((section) => (
|
|
98
|
+
<NarrativeBlock
|
|
99
|
+
key={section.key}
|
|
100
|
+
title={section.title}
|
|
101
|
+
items={section.items}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
|
|
105
|
+
{metadataEntries.length > 0 && (
|
|
106
|
+
<>
|
|
107
|
+
<p className="product-details__spec-heading">Specifications</p>
|
|
108
|
+
<div className="product-details__grid">
|
|
109
|
+
{metadataEntries.map((entry) => (
|
|
110
|
+
<div key={entry.key} className="product-details__item">
|
|
111
|
+
<span className="product-details__label">{entry.label}</span>
|
|
112
|
+
<span className="product-details__value">{entry.value}</span>
|
|
113
|
+
</div>
|
|
114
|
+
))}
|
|
115
|
+
</div>
|
|
116
|
+
</>
|
|
117
|
+
)}
|
|
118
|
+
</AccordionItem>
|
|
119
|
+
)}
|
|
120
|
+
|
|
121
|
+
{product.description && (
|
|
122
|
+
<AccordionItem
|
|
123
|
+
title="Description"
|
|
124
|
+
isOpen={isDescriptionOpen}
|
|
125
|
+
onToggle={() => setIsDescriptionOpen(!isDescriptionOpen)}
|
|
126
|
+
>
|
|
127
|
+
<div
|
|
128
|
+
className="mb-3 whitespace-pre-line"
|
|
129
|
+
dangerouslySetInnerHTML={{
|
|
130
|
+
__html: product.description
|
|
131
|
+
? product.description.replace(/\n/g, "<br />")
|
|
132
|
+
: "",
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
{(() => {
|
|
136
|
+
const features = product.metadata?.features
|
|
137
|
+
if (!features) return null
|
|
138
|
+
|
|
139
|
+
if (typeof features === "string") {
|
|
140
|
+
return (
|
|
141
|
+
<ul className="list-disc list-inside space-y-1">
|
|
142
|
+
{features.split(",").map((feature: string, index: number) => (
|
|
143
|
+
<li key={index}>{feature.trim()}</li>
|
|
144
|
+
))}
|
|
145
|
+
</ul>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (Array.isArray(features)) {
|
|
150
|
+
return (
|
|
151
|
+
<ul className="list-disc list-inside space-y-1">
|
|
152
|
+
{(features as string[]).map((feature: string, index: number) => (
|
|
153
|
+
<li key={index}>{feature}</li>
|
|
154
|
+
))}
|
|
155
|
+
</ul>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return null
|
|
160
|
+
})()}
|
|
161
|
+
</AccordionItem>
|
|
162
|
+
)}
|
|
163
|
+
|
|
164
|
+
{hasCareGuide && (
|
|
165
|
+
<AccordionItem
|
|
166
|
+
title="Care guide"
|
|
167
|
+
isOpen={isCareGuideOpen}
|
|
168
|
+
onToggle={() => setIsCareGuideOpen(!isCareGuideOpen)}
|
|
169
|
+
>
|
|
170
|
+
{(() => {
|
|
171
|
+
const careGuideStr =
|
|
172
|
+
typeof careGuide === "string" ? careGuide : String(careGuide)
|
|
173
|
+
|
|
174
|
+
const items = careGuideStr.includes(",")
|
|
175
|
+
? careGuideStr.split(",").map((item) => item.trim()).filter(Boolean)
|
|
176
|
+
: careGuideStr.includes("\n")
|
|
177
|
+
? careGuideStr.split("\n").map((item) => item.trim()).filter(Boolean)
|
|
178
|
+
: [careGuideStr]
|
|
179
|
+
|
|
180
|
+
if (items.length > 1) {
|
|
181
|
+
return (
|
|
182
|
+
<ul className="list-disc list-inside space-y-1">
|
|
183
|
+
{items.map((item: string, index: number) => (
|
|
184
|
+
<li key={index}>{item}</li>
|
|
185
|
+
))}
|
|
186
|
+
</ul>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return <p>{careGuideStr}</p>
|
|
191
|
+
})()}
|
|
192
|
+
</AccordionItem>
|
|
193
|
+
)}
|
|
194
|
+
|
|
195
|
+
<div className="product-reviews-section border-t border-[var(--color-header-border)] pt-4 pb-2">
|
|
196
|
+
<RatingsTab product={product} />
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
)
|
|
200
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import SidePanel from "@modules/common/components/side-panel"
|
|
4
|
+
import { SizeChartPanel } from "./SizeChartPanel"
|
|
5
|
+
|
|
6
|
+
type ProductFeaturePanelProps = {
|
|
7
|
+
activeFeature: string | null
|
|
8
|
+
onClose: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function ProductFeaturePanel({ activeFeature, onClose }: ProductFeaturePanelProps) {
|
|
12
|
+
return (
|
|
13
|
+
<SidePanel
|
|
14
|
+
isOpen={!!activeFeature}
|
|
15
|
+
onClose={() => onClose()}
|
|
16
|
+
title={
|
|
17
|
+
activeFeature === 'return' ? '7 Days Easy Return' :
|
|
18
|
+
activeFeature === 'delivery' ? 'Fast Delivery' :
|
|
19
|
+
activeFeature === 'cod' ? 'Cash On Delivery' :
|
|
20
|
+
activeFeature === 'size_chart' ? 'Size Guide' : ''
|
|
21
|
+
}
|
|
22
|
+
>
|
|
23
|
+
{activeFeature === 'return' && (
|
|
24
|
+
<div className="space-y-6">
|
|
25
|
+
<div className="bg-purple-50 p-4 rounded-xl border border-purple-100">
|
|
26
|
+
<p className="text-sm text-purple-900 leading-relaxed font-medium">
|
|
27
|
+
We offer a hassle-free 7-day return policy for all our customers. If you're not satisfied with your purchase, we're here to help!
|
|
28
|
+
</p>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="space-y-4">
|
|
31
|
+
<div className="flex gap-4">
|
|
32
|
+
<div className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0">
|
|
33
|
+
<span className="font-bold text-gray-700">1</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div>
|
|
36
|
+
<h4 className="font-bold text-gray-900 mb-1">Unused Conditions</h4>
|
|
37
|
+
<p className="text-sm text-gray-600">Items must be returned in their original condition, unused, and with all tags intact.</p>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="flex gap-4">
|
|
41
|
+
<div className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0">
|
|
42
|
+
<span className="font-bold text-gray-700">2</span>
|
|
43
|
+
</div>
|
|
44
|
+
<div>
|
|
45
|
+
<h4 className="font-bold text-gray-900 mb-1">Easy Process</h4>
|
|
46
|
+
<p className="text-sm text-gray-600">Raise a return request from your account or contact our support team directly.</p>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
<div className="flex gap-4">
|
|
50
|
+
<div className="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center flex-shrink-0">
|
|
51
|
+
<span className="font-bold text-gray-700">3</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div>
|
|
54
|
+
<h4 className="font-bold text-gray-900 mb-1">Quick Refund</h4>
|
|
55
|
+
<p className="text-sm text-gray-600">Once the quality check is completed, your refund will be processed within 24–48 hours. The amount may take a few business days to reflect in your account depending on your bank.</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
|
|
62
|
+
{activeFeature === 'delivery' && (
|
|
63
|
+
<div className="space-y-6">
|
|
64
|
+
<div className="bg-blue-50 p-4 rounded-xl border border-blue-100">
|
|
65
|
+
<p className="text-sm text-blue-900 leading-relaxed font-medium">
|
|
66
|
+
We prioritize your excitement! Our fast delivery network ensures your order reaches you as soon as possible.
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
<div className="space-y-6">
|
|
70
|
+
<div>
|
|
71
|
+
<h4 className="font-bold text-gray-900 mb-2 flex items-center gap-2">
|
|
72
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-blue-500">
|
|
73
|
+
<path d="M12 2v20m10-10H2"></path>
|
|
74
|
+
</svg>
|
|
75
|
+
Order Processing
|
|
76
|
+
</h4>
|
|
77
|
+
<p className="text-sm text-gray-600 pl-7">Most orders are processed and dispatched within 24 hours of placement.</p>
|
|
78
|
+
</div>
|
|
79
|
+
<div>
|
|
80
|
+
<h4 className="font-bold text-gray-900 mb-2 flex items-center gap-2">
|
|
81
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-blue-500">
|
|
82
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
83
|
+
<polyline points="12 6 12 12 16 14"></polyline>
|
|
84
|
+
</svg>
|
|
85
|
+
Average Timelines
|
|
86
|
+
</h4>
|
|
87
|
+
<ul className="text-sm text-gray-600 pl-7 space-y-2 list-disc">
|
|
88
|
+
<li>Metro Cities: 2-3 business days</li>
|
|
89
|
+
<li>Tier 2 Cities: 4-5 business days</li>
|
|
90
|
+
<li>Rest of India: 5-7 business days</li>
|
|
91
|
+
</ul>
|
|
92
|
+
</div>
|
|
93
|
+
<div>
|
|
94
|
+
<h4 className="font-bold text-gray-900 mb-2 flex items-center gap-2">
|
|
95
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-blue-500">
|
|
96
|
+
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
|
|
97
|
+
<circle cx="12" cy="10" r="3"></circle>
|
|
98
|
+
</svg>
|
|
99
|
+
Live Tracking
|
|
100
|
+
</h4>
|
|
101
|
+
<p className="text-sm text-gray-600 pl-7">You will receive a real-time tracking link via Email & WhatsApp once your order is shipped.</p>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{activeFeature === 'cod' && (
|
|
108
|
+
<div className="space-y-6">
|
|
109
|
+
<div className="bg-green-50 p-4 rounded-xl border border-green-100">
|
|
110
|
+
<p className="text-sm text-green-900 leading-relaxed font-medium">
|
|
111
|
+
Shop with confidence! Pay only when you receive your package at your doorstep.
|
|
112
|
+
</p>
|
|
113
|
+
</div>
|
|
114
|
+
<div className="space-y-5">
|
|
115
|
+
<div className="flex gap-4 p-4 bg-gray-50 rounded-lg">
|
|
116
|
+
<div className="w-10 h-10 flex items-center justify-center bg-page-bg rounded-lg shadow-sm flex-shrink-0">
|
|
117
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2">
|
|
118
|
+
<rect x="2" y="5" width="20" height="14" rx="2"></rect>
|
|
119
|
+
<line x1="2" y1="10" x2="22" y2="10"></line>
|
|
120
|
+
</svg>
|
|
121
|
+
</div>
|
|
122
|
+
<div>
|
|
123
|
+
<h4 className="font-bold text-gray-900">Zero Upfront Cost</h4>
|
|
124
|
+
<p className="text-xs text-gray-500">No need for credit cards or online payments to place your order.</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div className="flex gap-4 p-4 bg-gray-50 rounded-lg">
|
|
128
|
+
<div className="w-10 h-10 flex items-center justify-center bg-page-bg rounded-lg shadow-sm flex-shrink-0">
|
|
129
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2">
|
|
130
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>
|
|
131
|
+
</svg>
|
|
132
|
+
</div>
|
|
133
|
+
<div>
|
|
134
|
+
<h4 className="font-bold text-gray-900">Safe & Secure</h4>
|
|
135
|
+
<p className="text-xs text-gray-500">Verify your package and then pay our delivery partner directly.</p>
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
<ul className="text-xs text-gray-500 italic space-y-1 pl-4 list-disc">
|
|
139
|
+
<li>Available for orders up to ₹10,000.</li>
|
|
140
|
+
<li>Please keep the exact change ready for a smoother experience.</li>
|
|
141
|
+
<li>Only cash payments are accepted for COD.</li>
|
|
142
|
+
</ul>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{activeFeature === 'size_chart' && <SizeChartPanel />}
|
|
148
|
+
</SidePanel>
|
|
149
|
+
)
|
|
150
|
+
}
|