@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,106 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import Image from "next/image"
|
|
4
|
+
import { useRef, useState } from "react"
|
|
5
|
+
import { HttpTypes } from "@medusajs/types"
|
|
6
|
+
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
|
7
|
+
import PlaceholderImage from "@modules/common/icons/placeholder-image"
|
|
8
|
+
import { getCollectionImage } from "@lib/category-collection-images"
|
|
9
|
+
|
|
10
|
+
type NavCollectionsDropdownProps = {
|
|
11
|
+
collections: HttpTypes.StoreCollection[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function NavCollectionsDropdown({
|
|
15
|
+
collections,
|
|
16
|
+
}: NavCollectionsDropdownProps) {
|
|
17
|
+
const [open, setOpen] = useState(false)
|
|
18
|
+
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
19
|
+
|
|
20
|
+
const handleOpen = () => {
|
|
21
|
+
if (closeTimerRef.current) {
|
|
22
|
+
clearTimeout(closeTimerRef.current)
|
|
23
|
+
closeTimerRef.current = null
|
|
24
|
+
}
|
|
25
|
+
setOpen(true)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const handleClose = () => {
|
|
29
|
+
closeTimerRef.current = setTimeout(() => setOpen(false), 200)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!collections.length) {
|
|
33
|
+
return (
|
|
34
|
+
<LocalizedClientLink href="/collections" className="nav-luxury-link group">
|
|
35
|
+
<span className="nav-luxury-link-label">Collections</span>
|
|
36
|
+
<span className="nav-luxury-link-line" aria-hidden />
|
|
37
|
+
</LocalizedClientLink>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
className={`nav-collections${open ? " nav-collections--open" : ""}`}
|
|
44
|
+
onMouseEnter={handleOpen}
|
|
45
|
+
onMouseLeave={handleClose}
|
|
46
|
+
onFocus={handleOpen}
|
|
47
|
+
onBlur={handleClose}
|
|
48
|
+
>
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
className="nav-luxury-link nav-collections__trigger group"
|
|
52
|
+
aria-expanded={open}
|
|
53
|
+
aria-haspopup="true"
|
|
54
|
+
>
|
|
55
|
+
<span className="nav-luxury-link-label">Collections</span>
|
|
56
|
+
<span className="nav-luxury-link-line" aria-hidden />
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
<div className="nav-collections__panel" aria-hidden={!open}>
|
|
60
|
+
<div className="nav-collections__grid">
|
|
61
|
+
{collections.map((collection) => {
|
|
62
|
+
const imageUrl = getCollectionImage(collection)
|
|
63
|
+
const title = collection.title || "Collection"
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<LocalizedClientLink
|
|
67
|
+
key={collection.id}
|
|
68
|
+
href={`/store?collection=${collection.id}`}
|
|
69
|
+
className="nav-collections__card"
|
|
70
|
+
onClick={() => setOpen(false)}
|
|
71
|
+
>
|
|
72
|
+
<div className="nav-collections__card-media">
|
|
73
|
+
{imageUrl ? (
|
|
74
|
+
<Image
|
|
75
|
+
src={imageUrl}
|
|
76
|
+
alt={title}
|
|
77
|
+
fill
|
|
78
|
+
unoptimized
|
|
79
|
+
sizes="160px"
|
|
80
|
+
className="nav-collections__card-image"
|
|
81
|
+
/>
|
|
82
|
+
) : (
|
|
83
|
+
<div className="nav-collections__card-placeholder">
|
|
84
|
+
<PlaceholderImage size={28} />
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
<span className="nav-collections__card-title">{title}</span>
|
|
89
|
+
</LocalizedClientLink>
|
|
90
|
+
)
|
|
91
|
+
})}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<div className="nav-collections__footer">
|
|
95
|
+
<LocalizedClientLink
|
|
96
|
+
href="/collections"
|
|
97
|
+
className="nav-collections__view-all"
|
|
98
|
+
onClick={() => setOpen(false)}
|
|
99
|
+
>
|
|
100
|
+
View all collections
|
|
101
|
+
</LocalizedClientLink>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { type ReactNode } from "react"
|
|
4
|
+
import { HttpTypes } from "@medusajs/types"
|
|
5
|
+
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
|
6
|
+
import MobileMenu from "@modules/layout/components/mobile-menu"
|
|
7
|
+
import {
|
|
8
|
+
DesktopSearchIcon,
|
|
9
|
+
useSearchContext,
|
|
10
|
+
} from "@modules/layout/components/desktop-search"
|
|
11
|
+
import AccountDropdown from "@modules/layout/components/account-dropdown"
|
|
12
|
+
import WishlistCounter from "@modules/layout/components/wishlist-counter"
|
|
13
|
+
import SmartSearchResults from "@modules/layout/components/smart-search/smart-search-results"
|
|
14
|
+
import SmartSearchInline from "@modules/layout/components/smart-search/smart-search-inline"
|
|
15
|
+
import NavLinkLuxury from "./nav-link-luxury"
|
|
16
|
+
import NavCategoriesDropdown from "./nav-categories-dropdown"
|
|
17
|
+
import NavCollectionsDropdown from "./nav-collections-dropdown"
|
|
18
|
+
|
|
19
|
+
type NavHeaderContentProps = {
|
|
20
|
+
currentLocale: string
|
|
21
|
+
customer: HttpTypes.StoreCustomer | null
|
|
22
|
+
categories: HttpTypes.StoreProductCategory[]
|
|
23
|
+
collections: HttpTypes.StoreCollection[]
|
|
24
|
+
logo: ReactNode
|
|
25
|
+
cartButton: ReactNode
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function NavHeaderContent({
|
|
29
|
+
currentLocale,
|
|
30
|
+
customer,
|
|
31
|
+
categories,
|
|
32
|
+
collections,
|
|
33
|
+
logo,
|
|
34
|
+
cartButton,
|
|
35
|
+
}: NavHeaderContentProps) {
|
|
36
|
+
const { isSearchOpen } = useSearchContext()
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
className="nav-header-search-wrap relative"
|
|
41
|
+
data-search-open={isSearchOpen ? "true" : "false"}
|
|
42
|
+
>
|
|
43
|
+
<header className="nav-header">
|
|
44
|
+
<div className="nav-header-inner">
|
|
45
|
+
<div className="nav-header-mobile relative flex h-full w-full items-center lg:hidden">
|
|
46
|
+
{isSearchOpen ? (
|
|
47
|
+
<div className="nav-header-search-inline">
|
|
48
|
+
<SmartSearchInline />
|
|
49
|
+
</div>
|
|
50
|
+
) : (
|
|
51
|
+
<>
|
|
52
|
+
<div className="nav-header-mobile__start flex flex-1 items-center justify-start min-w-0">
|
|
53
|
+
<MobileMenu
|
|
54
|
+
customer={customer}
|
|
55
|
+
countryCode={currentLocale || "in"}
|
|
56
|
+
categories={categories}
|
|
57
|
+
collections={collections}
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div className="nav-header-mobile__logo absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none">
|
|
62
|
+
<div className="nav-header-logo-wrap pointer-events-auto flex justify-center">
|
|
63
|
+
{logo}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div className="nav-header-mobile__end flex flex-1 items-center justify-end min-w-0">
|
|
68
|
+
<div className="nav-header-icons flex items-center justify-end gap-2 min-w-0">
|
|
69
|
+
<DesktopSearchIcon />
|
|
70
|
+
|
|
71
|
+
{!customer ? (
|
|
72
|
+
<LocalizedClientLink
|
|
73
|
+
href="/account"
|
|
74
|
+
className="nav-header-profile-link"
|
|
75
|
+
data-testid="nav-login-link"
|
|
76
|
+
>
|
|
77
|
+
<span className="sr-only">Login</span>
|
|
78
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
79
|
+
<img
|
|
80
|
+
src="/Account.svg"
|
|
81
|
+
alt="Login"
|
|
82
|
+
width={22}
|
|
83
|
+
height={22}
|
|
84
|
+
className="nav-header-icon w-[22px] h-[22px]"
|
|
85
|
+
/>
|
|
86
|
+
</LocalizedClientLink>
|
|
87
|
+
) : (
|
|
88
|
+
<AccountDropdown customer={customer} />
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
<div className="nav-header-cart flex items-center">{cartButton}</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
{isSearchOpen ? (
|
|
99
|
+
<div className="nav-header-search-inline nav-header-search-inline--desktop">
|
|
100
|
+
<SmartSearchInline />
|
|
101
|
+
</div>
|
|
102
|
+
) : (
|
|
103
|
+
<div className="hidden lg:grid h-full grid-cols-[1fr_auto_1fr] items-center gap-4">
|
|
104
|
+
<div className="flex items-center justify-start min-w-0">
|
|
105
|
+
<nav
|
|
106
|
+
className="nav-header-menu flex items-center gap-8 xl:gap-10"
|
|
107
|
+
aria-label="Main"
|
|
108
|
+
>
|
|
109
|
+
<NavLinkLuxury href="/" label="Home" />
|
|
110
|
+
<NavLinkLuxury href="/store" label="Shop" />
|
|
111
|
+
<NavCategoriesDropdown categories={categories} />
|
|
112
|
+
<NavCollectionsDropdown collections={collections} />
|
|
113
|
+
<NavLinkLuxury href="/help" label="Help" />
|
|
114
|
+
</nav>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<div className="nav-header-logo-wrap flex justify-center px-2">
|
|
118
|
+
{logo}
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div className="nav-header-actions flex items-center justify-end h-full w-full min-w-0">
|
|
122
|
+
<div className="nav-header-icons flex items-center justify-end gap-5 min-w-0">
|
|
123
|
+
<DesktopSearchIcon />
|
|
124
|
+
|
|
125
|
+
{!customer ? (
|
|
126
|
+
<LocalizedClientLink
|
|
127
|
+
href="/account"
|
|
128
|
+
className="nav-header-login-btn"
|
|
129
|
+
data-testid="nav-login-link"
|
|
130
|
+
>
|
|
131
|
+
Login
|
|
132
|
+
</LocalizedClientLink>
|
|
133
|
+
) : (
|
|
134
|
+
<AccountDropdown customer={customer} />
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
<LocalizedClientLink
|
|
138
|
+
href="/wishlist"
|
|
139
|
+
className="nav-header-icon-link inline-flex items-center relative"
|
|
140
|
+
data-testid="nav-wishlist-link"
|
|
141
|
+
>
|
|
142
|
+
<span className="sr-only">Wishlist</span>
|
|
143
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
144
|
+
<img
|
|
145
|
+
src="/Wishlist.svg"
|
|
146
|
+
alt=""
|
|
147
|
+
width={22}
|
|
148
|
+
height={22}
|
|
149
|
+
className="nav-header-icon w-[22px] h-[22px]"
|
|
150
|
+
/>
|
|
151
|
+
<WishlistCounter />
|
|
152
|
+
</LocalizedClientLink>
|
|
153
|
+
|
|
154
|
+
<div className="nav-header-cart flex items-center">{cartButton}</div>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
</div>
|
|
160
|
+
</header>
|
|
161
|
+
|
|
162
|
+
<SmartSearchResults countryCode={currentLocale || "in"} />
|
|
163
|
+
</div>
|
|
164
|
+
)
|
|
165
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useSearchContext } from "@modules/layout/components/desktop-search"
|
|
4
|
+
import { usePathname } from "next/navigation"
|
|
5
|
+
import { useEffect, useState, type ReactNode } from "react"
|
|
6
|
+
|
|
7
|
+
function isHomePath(pathname: string) {
|
|
8
|
+
const segments = pathname.split("/").filter(Boolean)
|
|
9
|
+
return segments.length <= 1
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function NavHeaderShell({ children }: { children: ReactNode }) {
|
|
13
|
+
const pathname = usePathname() || ""
|
|
14
|
+
const isHome = isHomePath(pathname)
|
|
15
|
+
const { isSearchOpen } = useSearchContext()
|
|
16
|
+
const [scrolled, setScrolled] = useState(false)
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!isHome) {
|
|
20
|
+
setScrolled(true)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const onScroll = () => {
|
|
25
|
+
setScrolled(window.scrollY > 24)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
onScroll()
|
|
29
|
+
window.addEventListener("scroll", onScroll, { passive: true })
|
|
30
|
+
return () => window.removeEventListener("scroll", onScroll)
|
|
31
|
+
}, [isHome])
|
|
32
|
+
|
|
33
|
+
const isTransparent = isHome && !scrolled && !isSearchOpen
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<>
|
|
37
|
+
<div
|
|
38
|
+
className="nav-header-fixed"
|
|
39
|
+
data-nav-transparent={isTransparent ? "true" : "false"}
|
|
40
|
+
data-search-open={isSearchOpen ? "true" : "false"}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</div>
|
|
44
|
+
<div className="nav-header-spacer" aria-hidden />
|
|
45
|
+
</>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
|
2
|
+
|
|
3
|
+
type NavLinkLuxuryProps = {
|
|
4
|
+
href: string
|
|
5
|
+
label: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function NavLinkLuxury({ href, label }: NavLinkLuxuryProps) {
|
|
9
|
+
return (
|
|
10
|
+
<LocalizedClientLink href={href} className="nav-luxury-link group">
|
|
11
|
+
<span className="nav-luxury-link-label">{label}</span>
|
|
12
|
+
<span className="nav-luxury-link-line" aria-hidden />
|
|
13
|
+
</LocalizedClientLink>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from "react"
|
|
4
|
+
import {
|
|
5
|
+
Truck,
|
|
6
|
+
Sparkles,
|
|
7
|
+
Gem,
|
|
8
|
+
Tag,
|
|
9
|
+
ShieldCheck,
|
|
10
|
+
type LucideIcon,
|
|
11
|
+
} from "lucide-react"
|
|
12
|
+
|
|
13
|
+
export type PromoMessage = {
|
|
14
|
+
icon: LucideIcon
|
|
15
|
+
text: string
|
|
16
|
+
highlight?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const PROMO_MESSAGES: PromoMessage[] = [
|
|
20
|
+
{
|
|
21
|
+
icon: Truck,
|
|
22
|
+
text: "Free Shipping on Orders Above ₹999",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
icon: Sparkles,
|
|
26
|
+
text: "New Arrivals Are Live • Shop The Latest Saree Collection",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
icon: Gem,
|
|
30
|
+
text: "Handpicked Sarees Crafted For Every Celebration",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
icon: Tag,
|
|
34
|
+
text: "Flat 10% OFF On Your First Order • Use Code: WELCOME10",
|
|
35
|
+
highlight: "WELCOME10",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
icon: ShieldCheck,
|
|
39
|
+
text: "Free Shipping • Easy Returns • Secure Payments",
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
const HOLD_MS = 1000
|
|
44
|
+
const ANIMATION_MS = 280
|
|
45
|
+
|
|
46
|
+
function PromoMessageItem({ text, highlight }: PromoMessage) {
|
|
47
|
+
if (!highlight || !text.includes(highlight)) {
|
|
48
|
+
return <span>{text}</span>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const [before, after] = text.split(highlight)
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<span>
|
|
55
|
+
{before}
|
|
56
|
+
<span className="promo-bar__highlight">{highlight}</span>
|
|
57
|
+
{after}
|
|
58
|
+
</span>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default function PromoBarContent() {
|
|
63
|
+
const [activeIndex, setActiveIndex] = useState(0)
|
|
64
|
+
const [phase, setPhase] = useState<"idle" | "exit" | "enter">("enter")
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
let holdTimer: ReturnType<typeof setTimeout>
|
|
68
|
+
let exitTimer: ReturnType<typeof setTimeout>
|
|
69
|
+
let enterTimer: ReturnType<typeof setTimeout>
|
|
70
|
+
|
|
71
|
+
const scheduleNext = () => {
|
|
72
|
+
holdTimer = setTimeout(() => {
|
|
73
|
+
setPhase("exit")
|
|
74
|
+
|
|
75
|
+
exitTimer = setTimeout(() => {
|
|
76
|
+
setActiveIndex((i) => (i + 1) % PROMO_MESSAGES.length)
|
|
77
|
+
setPhase("enter")
|
|
78
|
+
|
|
79
|
+
enterTimer = setTimeout(() => {
|
|
80
|
+
setPhase("idle")
|
|
81
|
+
scheduleNext()
|
|
82
|
+
}, ANIMATION_MS)
|
|
83
|
+
}, ANIMATION_MS)
|
|
84
|
+
}, HOLD_MS)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const startTimer = setTimeout(() => {
|
|
88
|
+
setPhase("idle")
|
|
89
|
+
scheduleNext()
|
|
90
|
+
}, ANIMATION_MS)
|
|
91
|
+
|
|
92
|
+
return () => {
|
|
93
|
+
clearTimeout(startTimer)
|
|
94
|
+
clearTimeout(holdTimer)
|
|
95
|
+
clearTimeout(exitTimer)
|
|
96
|
+
clearTimeout(enterTimer)
|
|
97
|
+
}
|
|
98
|
+
}, [])
|
|
99
|
+
|
|
100
|
+
const message = PROMO_MESSAGES[activeIndex]
|
|
101
|
+
const Icon = message.icon
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="promo-bar__viewport" aria-live="polite">
|
|
105
|
+
<div
|
|
106
|
+
key={activeIndex}
|
|
107
|
+
className={`promo-bar__slide promo-bar__slide--${phase}`}
|
|
108
|
+
>
|
|
109
|
+
<span className="promo-bar__icon" aria-hidden>
|
|
110
|
+
<Icon size={14} strokeWidth={1.65} />
|
|
111
|
+
</span>
|
|
112
|
+
<span className="promo-bar__text">
|
|
113
|
+
<PromoMessageItem {...message} />
|
|
114
|
+
</span>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import OrderDetailsTemplate from "@modules/order/templates/order-details-template"
|
|
4
|
+
import type { HttpTypes } from "@medusajs/types"
|
|
5
|
+
|
|
6
|
+
type OrderDetailsProps = {
|
|
7
|
+
order: HttpTypes.StoreOrder
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function OrderDetails({ order }: OrderDetailsProps) {
|
|
11
|
+
return <OrderDetailsTemplate order={order} />
|
|
12
|
+
}
|