@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,39 @@
|
|
|
1
|
+
import Image from "next/image"
|
|
2
|
+
import type { WhyChooseUsBlockData } from "@core/types/home"
|
|
3
|
+
|
|
4
|
+
const WhyChooseUs = ({ title, features }: WhyChooseUsBlockData) => {
|
|
5
|
+
if (features.length === 0) {
|
|
6
|
+
return null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<section className="why-choose w-full bg-white py-8 sm:py-14 md:py-16">
|
|
11
|
+
<div className="why-choose__inner">
|
|
12
|
+
<h2 className="why-choose__title">{title}</h2>
|
|
13
|
+
|
|
14
|
+
<div className="why-choose__grid">
|
|
15
|
+
{features.map((feature, index) => (
|
|
16
|
+
<div
|
|
17
|
+
key={`${feature.name}-${index}`}
|
|
18
|
+
className="why-choose__item"
|
|
19
|
+
>
|
|
20
|
+
<div className="why-choose__icon-wrap">
|
|
21
|
+
<Image
|
|
22
|
+
src={feature.icon}
|
|
23
|
+
alt={feature.name}
|
|
24
|
+
width={120}
|
|
25
|
+
height={120}
|
|
26
|
+
className="why-choose__icon"
|
|
27
|
+
unoptimized
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
<h3 className="why-choose__label">{feature.name}</h3>
|
|
31
|
+
</div>
|
|
32
|
+
))}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</section>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default WhyChooseUs
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useRef } from "react"
|
|
4
|
+
import { ChevronLeft, ChevronRight } from "lucide-react"
|
|
5
|
+
import { HttpTypes } from "@medusajs/types"
|
|
6
|
+
import type { ProductCardRating } from "@core/types/product-card"
|
|
7
|
+
import ProductCard from "@theme/slots/product/ProductCard"
|
|
8
|
+
|
|
9
|
+
type ProductCarouselProps = {
|
|
10
|
+
products: HttpTypes.StoreProduct[]
|
|
11
|
+
region: HttpTypes.StoreRegion
|
|
12
|
+
ratings?: ProductCardRating[]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ProductCarousel = ({ products, region, ratings }: ProductCarouselProps) => {
|
|
16
|
+
const viewportRef = useRef<HTMLDivElement>(null)
|
|
17
|
+
const trackRef = useRef<HTMLDivElement>(null)
|
|
18
|
+
|
|
19
|
+
const scrollBySlide = (direction: "prev" | "next") => {
|
|
20
|
+
const viewport = viewportRef.current
|
|
21
|
+
const track = trackRef.current
|
|
22
|
+
if (!viewport || !track) return
|
|
23
|
+
|
|
24
|
+
const firstSlide = track.querySelector<HTMLElement>(".bestsellers-carousel__slide")
|
|
25
|
+
if (!firstSlide) return
|
|
26
|
+
|
|
27
|
+
const trackStyles = getComputedStyle(track)
|
|
28
|
+
const gap =
|
|
29
|
+
Number.parseFloat(trackStyles.columnGap || trackStyles.gap || "0") || 0
|
|
30
|
+
const slideStep = firstSlide.offsetWidth + gap
|
|
31
|
+
|
|
32
|
+
viewport.scrollBy({
|
|
33
|
+
left: direction === "next" ? slideStep : -slideStep,
|
|
34
|
+
behavior: "smooth",
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="bestsellers-carousel">
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
className="bestsellers-carousel__nav bestsellers-carousel__nav--prev"
|
|
43
|
+
onClick={() => scrollBySlide("prev")}
|
|
44
|
+
aria-label="Previous products"
|
|
45
|
+
>
|
|
46
|
+
<ChevronLeft size={20} strokeWidth={1.75} />
|
|
47
|
+
</button>
|
|
48
|
+
|
|
49
|
+
<div ref={viewportRef} className="bestsellers-carousel__viewport">
|
|
50
|
+
<div ref={trackRef} className="bestsellers-carousel__track">
|
|
51
|
+
{products.map((product) => (
|
|
52
|
+
<div key={product.id} className="bestsellers-carousel__slide">
|
|
53
|
+
<ProductCard
|
|
54
|
+
product={product}
|
|
55
|
+
region={region}
|
|
56
|
+
rating={ratings?.find((r) => r.product_id === product.id)}
|
|
57
|
+
className="h-full"
|
|
58
|
+
imageClassName="shop-grid__card-image"
|
|
59
|
+
quickAddClassName="product-card__quick-add--hero"
|
|
60
|
+
showWishlistIcon
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
className="bestsellers-carousel__nav bestsellers-carousel__nav--next"
|
|
70
|
+
onClick={() => scrollBySlide("next")}
|
|
71
|
+
aria-label="Next products"
|
|
72
|
+
>
|
|
73
|
+
<ChevronRight size={20} strokeWidth={1.75} />
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default ProductCarousel
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { colorClasses } from "@theme/tokens"
|
|
3
|
+
|
|
4
|
+
type MainLayoutShellProps = {
|
|
5
|
+
children: ReactNode
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function MainLayoutShell({ children }: MainLayoutShellProps) {
|
|
9
|
+
return (
|
|
10
|
+
<main className={`${colorClasses.pageBg} min-h-screen w-full mobile-layout-shell`}>
|
|
11
|
+
{children}
|
|
12
|
+
</main>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Button as MedusaButton } from "@medusajs/ui"
|
|
2
|
+
import { clx } from "@medusajs/ui"
|
|
3
|
+
import type { ComponentProps } from "react"
|
|
4
|
+
|
|
5
|
+
type ButtonVariant = "primary" | "secondary" | "ghost"
|
|
6
|
+
|
|
7
|
+
type ButtonProps = Omit<ComponentProps<typeof MedusaButton>, "variant"> & {
|
|
8
|
+
variant?: ButtonVariant
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const variantClasses: Record<ButtonVariant, string> = {
|
|
12
|
+
primary:
|
|
13
|
+
"bg-brand-accent hover:bg-brand-accent-hover text-white rounded-none font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-xs",
|
|
14
|
+
secondary:
|
|
15
|
+
"bg-transparent border border-brand-accent text-brand-accent rounded-none font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-xs hover:bg-brand-accent hover:text-white",
|
|
16
|
+
ghost:
|
|
17
|
+
"bg-transparent text-heading hover:opacity-70 rounded-none font-semibold uppercase tracking-[var(--letter-spacing-nav)] text-xs",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Button({
|
|
21
|
+
variant = "primary",
|
|
22
|
+
className,
|
|
23
|
+
children,
|
|
24
|
+
...props
|
|
25
|
+
}: ButtonProps) {
|
|
26
|
+
return (
|
|
27
|
+
<MedusaButton className={clx(variantClasses[variant], className)} {...props}>
|
|
28
|
+
{children}
|
|
29
|
+
</MedusaButton>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { clx } from "@medusajs/ui"
|
|
2
|
+
import type { HTMLAttributes } from "react"
|
|
3
|
+
|
|
4
|
+
type CardProps = HTMLAttributes<HTMLDivElement> & {
|
|
5
|
+
padding?: "sm" | "md" | "lg"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const paddingClasses = {
|
|
9
|
+
sm: "p-4",
|
|
10
|
+
md: "p-6",
|
|
11
|
+
lg: "p-8",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Card({
|
|
15
|
+
padding = "md",
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
...props
|
|
19
|
+
}: CardProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
className={clx(
|
|
23
|
+
"rounded-xl bg-white border border-gray-100 shadow-sm",
|
|
24
|
+
paddingClasses[padding],
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</div>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@pradip1995/commerce-auth/components/forgot-password"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { initiateGoogleAuth } from "@core/data/customer"
|
|
4
|
+
import { GoogleLoginButton } from "@pradip1995/commerce-auth/components/google-login"
|
|
5
|
+
import { useParams } from "next/navigation"
|
|
6
|
+
|
|
7
|
+
const BUTTON_CLASS =
|
|
8
|
+
"w-full flex items-center justify-center gap-2.5 py-3 px-4 border border-gray-200 bg-surface hover:bg-gray-50 transition-all shadow-sm hover:shadow-md active:scale-[0.98]"
|
|
9
|
+
|
|
10
|
+
type GoogleLoginProps = {
|
|
11
|
+
countryCode?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function GoogleLogin({
|
|
15
|
+
countryCode: countryCodeProp,
|
|
16
|
+
}: GoogleLoginProps = {}) {
|
|
17
|
+
const params = useParams()
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<GoogleLoginButton
|
|
21
|
+
countryCode={
|
|
22
|
+
countryCodeProp ?? (params?.countryCode as string | undefined)
|
|
23
|
+
}
|
|
24
|
+
initiateAuth={initiateGoogleAuth}
|
|
25
|
+
className={BUTTON_CLASS}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@modules/account/components/login"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AccountPageData } from "@core/types/account"
|
|
2
|
+
import { getLoginPagePanelFromConfig } from "@lib/data/login-panel"
|
|
3
|
+
|
|
4
|
+
import LoginTemplateClient from "./login-template-client"
|
|
5
|
+
|
|
6
|
+
type LoginTemplateProps = Pick<AccountPageData, "countryCode">
|
|
7
|
+
|
|
8
|
+
export default async function LoginTemplate(_props: LoginTemplateProps) {
|
|
9
|
+
const panelImage = await getLoginPagePanelFromConfig()
|
|
10
|
+
|
|
11
|
+
return <LoginTemplateClient panelImage={panelImage} />
|
|
12
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Suspense, useState } from "react"
|
|
4
|
+
|
|
5
|
+
import { LOGIN_VIEW } from "@core/types/account"
|
|
6
|
+
import Login from "@theme/slots/account/Login"
|
|
7
|
+
import Register from "@theme/slots/account/Register"
|
|
8
|
+
import ForgotPassword from "@theme/slots/account/ForgotPassword"
|
|
9
|
+
import Spinner from "@modules/common/icons/spinner"
|
|
10
|
+
|
|
11
|
+
type LoginTemplateClientProps = {
|
|
12
|
+
panelImage: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const VIEW_COPY = {
|
|
16
|
+
[LOGIN_VIEW.SIGN_IN]: {
|
|
17
|
+
title: "Sign in",
|
|
18
|
+
subtitle: "Access your exclusive collection",
|
|
19
|
+
},
|
|
20
|
+
[LOGIN_VIEW.REGISTER]: {
|
|
21
|
+
title: "Create account",
|
|
22
|
+
subtitle: "Join Sahsha for a curated experience",
|
|
23
|
+
},
|
|
24
|
+
[LOGIN_VIEW.FORGOT_PASSWORD]: {
|
|
25
|
+
title: "Forgot password",
|
|
26
|
+
subtitle: "We'll help you get back in",
|
|
27
|
+
},
|
|
28
|
+
} as const
|
|
29
|
+
|
|
30
|
+
export default function LoginTemplateClient({
|
|
31
|
+
panelImage,
|
|
32
|
+
}: LoginTemplateClientProps) {
|
|
33
|
+
const [currentView, setCurrentView] = useState(LOGIN_VIEW.SIGN_IN)
|
|
34
|
+
const copy = VIEW_COPY[currentView]
|
|
35
|
+
|
|
36
|
+
const renderView = () => {
|
|
37
|
+
switch (currentView) {
|
|
38
|
+
case LOGIN_VIEW.REGISTER:
|
|
39
|
+
return <Register setCurrentView={setCurrentView} />
|
|
40
|
+
case LOGIN_VIEW.FORGOT_PASSWORD:
|
|
41
|
+
return <ForgotPassword setCurrentView={setCurrentView} />
|
|
42
|
+
default:
|
|
43
|
+
return <Login setCurrentView={setCurrentView} />
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="account-login">
|
|
49
|
+
<div className="account-login__hero" aria-hidden>
|
|
50
|
+
<img
|
|
51
|
+
src={panelImage}
|
|
52
|
+
alt=""
|
|
53
|
+
className="account-login__hero-image"
|
|
54
|
+
loading="eager"
|
|
55
|
+
decoding="async"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div className="account-login__panel">
|
|
60
|
+
<div className="account-login__panel-inner">
|
|
61
|
+
<header className="account-login__header">
|
|
62
|
+
<h1 className="account-login__title">{copy.title}</h1>
|
|
63
|
+
<p className="account-login__subtitle">{copy.subtitle}</p>
|
|
64
|
+
</header>
|
|
65
|
+
|
|
66
|
+
<div
|
|
67
|
+
className={`account-login__card account-auth account-auth--${currentView}`}
|
|
68
|
+
>
|
|
69
|
+
<Suspense
|
|
70
|
+
fallback={
|
|
71
|
+
<div className="flex justify-center py-12">
|
|
72
|
+
<Spinner />
|
|
73
|
+
</div>
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
{renderView()}
|
|
77
|
+
</Suspense>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@modules/account/components/register"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { HttpTypes } from "@medusajs/types"
|
|
2
|
+
import CartItemCard from "@modules/cart/components/cart-item-card"
|
|
3
|
+
|
|
4
|
+
export type CartItemSlotProps = {
|
|
5
|
+
item: HttpTypes.StoreCartLineItem
|
|
6
|
+
currencyCode: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function CartItem({ item, currencyCode }: CartItemSlotProps) {
|
|
10
|
+
return <CartItemCard item={item} currencyCode={currencyCode} />
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CartSummaryProps } from "@core/types/cart"
|
|
2
|
+
import Summary from "@modules/cart/templates/summary"
|
|
3
|
+
|
|
4
|
+
export type CartSummarySlotProps = CartSummaryProps
|
|
5
|
+
|
|
6
|
+
export default function CartSummary({ cart, customer }: CartSummarySlotProps) {
|
|
7
|
+
return <Summary cart={cart} customer={customer} />
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@modules/checkout/templates/checkout-form"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "@modules/checkout/templates/checkout-summary"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { FooterSlotData } from "@core/types/layout"
|
|
2
|
+
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
|
3
|
+
import FooterLogo from "@modules/layout/components/footer-logo"
|
|
4
|
+
import FooterNewsletter from "@modules/layout/components/footer-newsletter"
|
|
5
|
+
import FooterSocial from "@modules/layout/components/footer-social"
|
|
6
|
+
|
|
7
|
+
const COMPANY_LINKS = [
|
|
8
|
+
{ href: "/", label: "Home" },
|
|
9
|
+
{ href: "/store", label: "Shop" },
|
|
10
|
+
{ href: "/about", label: "About" },
|
|
11
|
+
{ href: "/contact", label: "Contact" },
|
|
12
|
+
{ href: "/help", label: "Help" },
|
|
13
|
+
{ href: "/privacy-policy", label: "Privacy Policy" },
|
|
14
|
+
{ href: "/terms-of-use", label: "Terms of Use" },
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
export default function Footer({ categories, socialLinks }: FooterSlotData) {
|
|
18
|
+
return (
|
|
19
|
+
<footer className="site-footer">
|
|
20
|
+
<div
|
|
21
|
+
className="site-footer__inner mx-auto px-4 sm:px-6 lg:px-8 py-12 lg:py-16"
|
|
22
|
+
style={{ maxWidth: "var(--container-max)" }}
|
|
23
|
+
>
|
|
24
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-10 lg:gap-12">
|
|
25
|
+
<div className="sm:col-span-2 lg:col-span-1">
|
|
26
|
+
<FooterLogo />
|
|
27
|
+
<p className="site-footer__text max-w-xs mt-4">
|
|
28
|
+
Kurtis and ethnic wear curated for everyday elegance. Handpicked
|
|
29
|
+
styles, crafted with comfort and grace.
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div>
|
|
34
|
+
<h3 className="site-footer__label">Shop</h3>
|
|
35
|
+
<ul className="space-y-2.5">
|
|
36
|
+
{(categories?.slice(0, 6) ?? []).map((cat) => (
|
|
37
|
+
<li key={cat.id}>
|
|
38
|
+
<LocalizedClientLink
|
|
39
|
+
href={`/store?category=${cat.id}`}
|
|
40
|
+
className="site-footer__link"
|
|
41
|
+
>
|
|
42
|
+
{cat.name}
|
|
43
|
+
</LocalizedClientLink>
|
|
44
|
+
</li>
|
|
45
|
+
))}
|
|
46
|
+
{!categories?.length && (
|
|
47
|
+
<li>
|
|
48
|
+
<LocalizedClientLink href="/store" className="site-footer__link">
|
|
49
|
+
All products
|
|
50
|
+
</LocalizedClientLink>
|
|
51
|
+
</li>
|
|
52
|
+
)}
|
|
53
|
+
</ul>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div>
|
|
57
|
+
<h3 className="site-footer__label">Company</h3>
|
|
58
|
+
<ul className="space-y-2.5">
|
|
59
|
+
{COMPANY_LINKS.map((link) => (
|
|
60
|
+
<li key={link.href}>
|
|
61
|
+
<LocalizedClientLink href={link.href} className="site-footer__link">
|
|
62
|
+
{link.label}
|
|
63
|
+
</LocalizedClientLink>
|
|
64
|
+
</li>
|
|
65
|
+
))}
|
|
66
|
+
</ul>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div>
|
|
70
|
+
<h3 className="site-footer__label">Newsletter</h3>
|
|
71
|
+
<p className="site-footer__text mb-4">
|
|
72
|
+
Subscribe for exclusive offers and new arrivals.
|
|
73
|
+
</p>
|
|
74
|
+
<FooterNewsletter />
|
|
75
|
+
<FooterSocial links={socialLinks ?? []} />
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div className="site-footer__divider mt-12 pt-8 flex flex-col sm:flex-row items-center justify-between gap-4">
|
|
80
|
+
<p className="site-footer__copyright">
|
|
81
|
+
© {new Date().getFullYear()} Sahsha. All rights reserved.
|
|
82
|
+
</p>
|
|
83
|
+
<div className="flex gap-6">
|
|
84
|
+
<LocalizedClientLink href="/privacy-policy" className="site-footer__link text-xs">
|
|
85
|
+
Privacy
|
|
86
|
+
</LocalizedClientLink>
|
|
87
|
+
<LocalizedClientLink href="/terms-of-use" className="site-footer__link text-xs">
|
|
88
|
+
Terms
|
|
89
|
+
</LocalizedClientLink>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</footer>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Suspense } from "react"
|
|
2
|
+
import type { NavLayoutData } from "@controllers/layout/load-layout-data"
|
|
3
|
+
import DesktopSearch from "@modules/layout/components/desktop-search"
|
|
4
|
+
import DynamicLogo from "@modules/layout/components/dynamic-logo"
|
|
5
|
+
import CartButton from "@modules/layout/components/cart-button"
|
|
6
|
+
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
|
7
|
+
import NavHeaderShell from "./nav-header-shell"
|
|
8
|
+
import NavHeaderContent from "./nav-header-content"
|
|
9
|
+
|
|
10
|
+
export default function Nav({ currentLocale, customer, categories, collections }: NavLayoutData) {
|
|
11
|
+
const cartButton = (
|
|
12
|
+
<Suspense
|
|
13
|
+
fallback={
|
|
14
|
+
<LocalizedClientLink
|
|
15
|
+
href="/cart"
|
|
16
|
+
className="nav-header-icon-link flex items-center"
|
|
17
|
+
data-testid="nav-cart-link"
|
|
18
|
+
>
|
|
19
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
20
|
+
<img
|
|
21
|
+
src="/shopping-bag (3).svg"
|
|
22
|
+
alt="Cart"
|
|
23
|
+
width={22}
|
|
24
|
+
height={22}
|
|
25
|
+
className="nav-header-icon w-[22px] h-[22px]"
|
|
26
|
+
/>
|
|
27
|
+
</LocalizedClientLink>
|
|
28
|
+
}
|
|
29
|
+
>
|
|
30
|
+
<CartButton />
|
|
31
|
+
</Suspense>
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Suspense fallback={null}>
|
|
36
|
+
<DesktopSearch countryCode={currentLocale || "in"} hideNavLinks>
|
|
37
|
+
<NavHeaderShell>
|
|
38
|
+
<NavHeaderContent
|
|
39
|
+
currentLocale={currentLocale || "in"}
|
|
40
|
+
customer={customer}
|
|
41
|
+
categories={categories}
|
|
42
|
+
collections={collections}
|
|
43
|
+
logo={<DynamicLogo />}
|
|
44
|
+
cartButton={cartButton}
|
|
45
|
+
/>
|
|
46
|
+
</NavHeaderShell>
|
|
47
|
+
</DesktopSearch>
|
|
48
|
+
</Suspense>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useRef, useState } from "react"
|
|
4
|
+
import { HttpTypes } from "@medusajs/types"
|
|
5
|
+
import LocalizedClientLink from "@modules/common/components/localized-client-link"
|
|
6
|
+
|
|
7
|
+
type NavCategoriesDropdownProps = {
|
|
8
|
+
categories: HttpTypes.StoreProductCategory[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function NavCategoriesDropdown({
|
|
12
|
+
categories,
|
|
13
|
+
}: NavCategoriesDropdownProps) {
|
|
14
|
+
const [open, setOpen] = useState(false)
|
|
15
|
+
const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
16
|
+
|
|
17
|
+
const handleOpen = () => {
|
|
18
|
+
if (closeTimerRef.current) {
|
|
19
|
+
clearTimeout(closeTimerRef.current)
|
|
20
|
+
closeTimerRef.current = null
|
|
21
|
+
}
|
|
22
|
+
setOpen(true)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const handleClose = () => {
|
|
26
|
+
closeTimerRef.current = setTimeout(() => setOpen(false), 180)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!categories.length) {
|
|
30
|
+
return (
|
|
31
|
+
<LocalizedClientLink href="/store" className="nav-luxury-link group">
|
|
32
|
+
<span className="nav-luxury-link-label">Categories</span>
|
|
33
|
+
<span className="nav-luxury-link-line" aria-hidden />
|
|
34
|
+
</LocalizedClientLink>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
className={`nav-categories${open ? " nav-categories--open" : ""}`}
|
|
41
|
+
onMouseEnter={handleOpen}
|
|
42
|
+
onMouseLeave={handleClose}
|
|
43
|
+
onFocus={handleOpen}
|
|
44
|
+
onBlur={handleClose}
|
|
45
|
+
>
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
className="nav-luxury-link nav-categories__trigger group"
|
|
49
|
+
aria-expanded={open}
|
|
50
|
+
aria-haspopup="true"
|
|
51
|
+
>
|
|
52
|
+
<span className="nav-luxury-link-label">Categories</span>
|
|
53
|
+
<span className="nav-luxury-link-line" aria-hidden />
|
|
54
|
+
</button>
|
|
55
|
+
|
|
56
|
+
<div className="nav-categories__panel" role="menu" aria-hidden={!open}>
|
|
57
|
+
<ul className="nav-categories__list">
|
|
58
|
+
{categories.map((category) => (
|
|
59
|
+
<li key={category.id} role="none">
|
|
60
|
+
<LocalizedClientLink
|
|
61
|
+
href={`/store?category=${category.id}`}
|
|
62
|
+
className="nav-categories__link"
|
|
63
|
+
role="menuitem"
|
|
64
|
+
onClick={() => setOpen(false)}
|
|
65
|
+
>
|
|
66
|
+
<span className="nav-categories__link-text">{category.name}</span>
|
|
67
|
+
</LocalizedClientLink>
|
|
68
|
+
</li>
|
|
69
|
+
))}
|
|
70
|
+
</ul>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|