322 lines
13 KiB
TypeScript
322 lines
13 KiB
TypeScript
import type {FetcherWithComponents} from 'react-router';
|
|
import {Link} from 'react-router';
|
|
import {CartForm, Image} from '@shopify/hydrogen';
|
|
import {AbiirEyebrow} from '~/components/AbiirEyebrow';
|
|
import {ArrowRight, Check, Sparkles, Star} from 'lucide-react';
|
|
import type {SpringGlowBannerCms} from '~/lib/springGlowBanner';
|
|
import {
|
|
ABIIR_WIDE_CONTENT_SHELL_CLASS,
|
|
hasSpringGlowBannerRating,
|
|
MOCK_SPRING_GLOW_BANNER_CMS,
|
|
SPRING_GLOW_BANNER_GROQ,
|
|
} from '~/lib/springGlowBanner';
|
|
/** Local fallback when Sanity has no hero image for this band. */
|
|
import bundleFallbackSrc from '~/assets/spring-glow-bundle_new.png';
|
|
|
|
/**
|
|
* Sanity GROQ for this module (loader + future `useQuery`):
|
|
* @see {@link SPRING_GLOW_BANNER_GROQ}
|
|
*/
|
|
export {MOCK_SPRING_GLOW_BANNER_CMS, SPRING_GLOW_BANNER_GROQ};
|
|
export type {SpringGlowBannerCms} from '~/lib/springGlowBanner';
|
|
|
|
const IMAGE_OVERLAY =
|
|
'linear-gradient(131.54deg, rgba(184, 160, 66, 0.12) 0%, rgba(122, 35, 64, 0.35) 100%)';
|
|
|
|
function isExternalHref(href: string) {
|
|
return /^https?:\/\//i.test(href);
|
|
}
|
|
|
|
function SpringGlowBannerImage({cms}: {cms: SpringGlowBannerCms}) {
|
|
const alt =
|
|
cms.image?.alt ??
|
|
'Spring Glow Bundle — Hydra Face Serum, Hydrating Moisturizer & Copper Gua Sha';
|
|
|
|
if (cms.image?.url) {
|
|
const w = cms.image.width ?? 1200;
|
|
const h = cms.image.height ?? 1500;
|
|
return (
|
|
<Image
|
|
alt={alt}
|
|
className="absolute inset-0 size-full min-h-full object-cover"
|
|
data={{
|
|
url: cms.image.url,
|
|
altText: alt,
|
|
width: w,
|
|
height: h,
|
|
}}
|
|
loader={({src}) => src ?? ''}
|
|
sizes="(min-width: 1024px) 50vw, 100vw"
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Image
|
|
alt={alt}
|
|
className="absolute inset-0 size-full min-h-full object-cover"
|
|
height={791}
|
|
sizes="(min-width: 1024px) 50vw, 100vw"
|
|
src={bundleFallbackSrc}
|
|
width={700}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export type SpringGlowBannerProps = {
|
|
cms: SpringGlowBannerCms;
|
|
bundleVariantId: string | null;
|
|
};
|
|
|
|
export function SpringGlowBanner({cms, bundleVariantId}: SpringGlowBannerProps) {
|
|
const burgundy = cms.sectionBackground;
|
|
|
|
return (
|
|
<section
|
|
className="w-full overflow-hidden !py-0"
|
|
style={{background: burgundy}}
|
|
>
|
|
<div className={ABIIR_WIDE_CONTENT_SHELL_CLASS}>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 lg:min-h-[min(790px,92vh)]">
|
|
{/* Image column — SpringGlowBanner reference */}
|
|
<div className="relative min-h-[min(72vw,420px)] overflow-hidden lg:min-h-[min(790px,92vh)]">
|
|
<SpringGlowBannerImage cms={cms} />
|
|
<div
|
|
aria-hidden
|
|
className="pointer-events-none absolute inset-0"
|
|
style={{background: IMAGE_OVERLAY}}
|
|
/>
|
|
<div
|
|
className="absolute right-5 top-5 z-10 flex flex-col items-center justify-center rounded-full sm:right-6 sm:top-[25px]"
|
|
style={{
|
|
background: cms.badgeBackground,
|
|
width: 'clamp(4.5rem, 16vw, 5.75rem)',
|
|
height: 'clamp(4.5rem, 16vw, 5.75rem)',
|
|
boxShadow: '0px 3px 10px 0px rgba(0,0,0,0.3)',
|
|
padding: 0,
|
|
boxSizing: 'border-box',
|
|
}}
|
|
>
|
|
<p
|
|
className="m-0 font-['Raleway',sans-serif] font-semibold uppercase leading-none text-[#1a1a1a]"
|
|
style={{
|
|
fontSize: 'clamp(7px, 1.6vw, 13px)',
|
|
letterSpacing: '0.12em',
|
|
marginBottom: 'clamp(2px, 0.8vw, 4px)',
|
|
}}
|
|
>
|
|
{cms.badgeLabel}
|
|
</p>
|
|
<p
|
|
className="m-0 font-['Cormorant_Garamond',serif] font-medium leading-[0.95]"
|
|
style={{
|
|
fontSize: 'clamp(1.512rem, 3.6vw, 1.9rem)',
|
|
color: burgundy,
|
|
}}
|
|
>
|
|
{cms.badgeAmount}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content column */}
|
|
<div className="relative flex flex-col justify-center py-12 sm:py-14 lg:py-10 lg:pl-[clamp(2rem,4vw,60px)] lg:pr-4">
|
|
<div className="flex w-full max-w-[581px] flex-col lg:max-w-none">
|
|
<div className="mb-2 flex items-center gap-[12.75px] lg:mb-0 lg:mt-4">
|
|
<Sparkles
|
|
aria-hidden
|
|
className="shrink-0"
|
|
size={13}
|
|
strokeWidth={2}
|
|
style={{color: cms.gold}}
|
|
/>
|
|
<AbiirEyebrow as="span" className="!text-left" surface="dark">
|
|
{cms.eyebrow}
|
|
</AbiirEyebrow>
|
|
</div>
|
|
|
|
<h2
|
|
className="mb-0 mt-5 font-['Cormorant_Garamond',serif] font-light text-[#faf8f3] tracking-[-0.969px]"
|
|
style={{
|
|
fontSize: 'clamp(2.5rem, 5.5vw, 64.6px)',
|
|
lineHeight: 1.12,
|
|
fontWeight: 300,
|
|
}}
|
|
>
|
|
<span className="block whitespace-normal">
|
|
{cms.titleLine1Before}
|
|
<span
|
|
className="font-['Cormorant_Garamond',serif] font-light italic"
|
|
style={{color: cms.gold}}
|
|
>
|
|
{cms.titleLine1Italic}
|
|
</span>
|
|
</span>
|
|
<span className="block">{cms.titleLine2}</span>
|
|
</h2>
|
|
|
|
<p
|
|
className="mb-0 mt-6 max-w-[580px] font-['Raleway',sans-serif] text-[16px] font-medium leading-[26.18px]"
|
|
style={{color: cms.creamText}}
|
|
>
|
|
{cms.body}
|
|
</p>
|
|
|
|
<div className="mt-8 flex flex-col gap-[12.75px]">
|
|
<p
|
|
className="m-0 font-['Raleway',sans-serif] text-[12px] font-semibold uppercase leading-[19.89px] tracking-[2.21px]"
|
|
style={{color: cms.creamText}}
|
|
>
|
|
{cms.whatsIncludedLabel}
|
|
</p>
|
|
<ul className="m-0 flex list-none flex-col gap-2 p-0">
|
|
{cms.includedItems.map((item) => (
|
|
<li
|
|
key={item}
|
|
className="flex items-center gap-[10.625px]"
|
|
>
|
|
<span
|
|
aria-hidden
|
|
className="flex size-[15px] shrink-0 items-center justify-center rounded-full"
|
|
style={{background: cms.gold}}
|
|
>
|
|
<Check
|
|
color={burgundy}
|
|
size={9}
|
|
strokeWidth={3}
|
|
/>
|
|
</span>
|
|
<span
|
|
className="font-['Raleway',sans-serif] text-[16px] font-light leading-[22.44px]"
|
|
style={{color: cms.creamText}}
|
|
>
|
|
{item}
|
|
</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
|
|
{hasSpringGlowBannerRating(cms) ? (
|
|
<div className="mt-8 flex items-center gap-[8.5px]">
|
|
<div className="flex items-center gap-[2px]">
|
|
{Array.from({length: 5}).map((_, i) => (
|
|
<Star
|
|
key={i}
|
|
aria-hidden
|
|
className="shrink-0"
|
|
fill={cms.gold}
|
|
size={12}
|
|
strokeWidth={0}
|
|
/>
|
|
))}
|
|
</div>
|
|
<p
|
|
className="m-0 font-['Raleway',sans-serif] text-[12px] font-light leading-[19.125px]"
|
|
style={{color: cms.creamText}}
|
|
>
|
|
{cms.ratingScoreText}
|
|
{cms.ratingReviewsText
|
|
? ` · ${cms.ratingReviewsText}`
|
|
: null}
|
|
</p>
|
|
</div>
|
|
) : null}
|
|
|
|
<div
|
|
className="mt-8 flex flex-wrap items-start gap-x-6 gap-y-2 border-b border-solid pb-6"
|
|
style={{borderColor: 'rgba(248, 244, 239, 0.08)'}}
|
|
>
|
|
<p
|
|
className="m-0 font-['Cormorant_Garamond',serif] text-[48px] font-normal leading-[45.2px]"
|
|
style={{color: cms.gold,fontSize:'2.4rem'}}
|
|
>
|
|
{cms.priceMain}
|
|
</p>
|
|
<div className="flex flex-col pt-1">
|
|
<p
|
|
className="m-0 font-['Raleway',sans-serif] text-[14px] font-light leading-[24.225px] line-through"
|
|
style={{color: cms.creamText}}
|
|
>
|
|
{cms.compareAtLine}
|
|
</p>
|
|
<p
|
|
className="m-0 mt-[10px] font-['Raleway',sans-serif] text-[20px] font-medium uppercase leading-[17.85px] tracking-[1.19px]"
|
|
style={{color: cms.saveGreen}}
|
|
>
|
|
{cms.saveLine}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-8 flex w-full max-w-[581px] flex-col gap-[12.75px] sm:flex-row">
|
|
{bundleVariantId ? (
|
|
<CartForm
|
|
action={CartForm.ACTIONS.LinesAdd}
|
|
className="w-full flex-1"
|
|
inputs={{
|
|
lines: [{merchandiseId: bundleVariantId, quantity: 1}],
|
|
}}
|
|
route="/cart"
|
|
>
|
|
{(fetcher: FetcherWithComponents<unknown>) => (
|
|
<button
|
|
className="group flex min-h-[55px] w-full flex-1 cursor-pointer items-center justify-center gap-3 border-0 px-4 font-['Raleway',sans-serif] text-[14px] font-semibold uppercase leading-[19.125px] tracking-[2.295px] text-[#1a1a1a] transition-opacity duration-300 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50"
|
|
disabled={fetcher.state !== 'idle'}
|
|
style={{background: cms.gold}}
|
|
type="submit"
|
|
>
|
|
{cms.primaryCtaLabel}
|
|
<ArrowRight
|
|
className="shrink-0 transition-transform group-hover:translate-x-1"
|
|
size={14}
|
|
strokeWidth={2}
|
|
/>
|
|
</button>
|
|
)}
|
|
</CartForm>
|
|
) : (
|
|
<button
|
|
className="flex min-h-[55px] w-full flex-1 cursor-not-allowed items-center justify-center gap-3 border-0 px-4 font-['Raleway',sans-serif] text-[14px] font-semibold uppercase leading-[19.125px] tracking-[2.295px] text-[#1a1a1a] opacity-50"
|
|
disabled
|
|
style={{background: cms.gold}}
|
|
type="button"
|
|
>
|
|
{cms.primaryCtaLabel}
|
|
<ArrowRight className="shrink-0" size={14} strokeWidth={2} />
|
|
</button>
|
|
)}
|
|
{isExternalHref(cms.secondaryCtaHref) ? (
|
|
<a
|
|
className="flex min-h-[55px] w-full flex-1 items-center justify-center border border-solid px-4 font-['Raleway',sans-serif] text-[14px] font-medium uppercase leading-[19.125px] tracking-[2.295px] no-underline transition-opacity duration-300 hover:opacity-85"
|
|
href={cms.secondaryCtaHref}
|
|
rel="noreferrer"
|
|
style={{
|
|
borderColor: 'rgba(248, 244, 239, 0.8)',
|
|
color: 'rgba(248, 244, 239, 0.75)',
|
|
}}
|
|
>
|
|
{cms.secondaryCtaLabel}
|
|
</a>
|
|
) : (
|
|
<Link
|
|
className="flex min-h-[55px] w-full flex-1 items-center justify-center border border-solid px-4 font-['Raleway',sans-serif] text-[14px] font-medium uppercase leading-[19.125px] tracking-[2.295px] no-underline transition-opacity duration-300 hover:opacity-85"
|
|
prefetch="intent"
|
|
style={{
|
|
borderColor: 'rgba(248, 244, 239, 0.8)',
|
|
color: 'rgba(248, 244, 239, 0.75)',
|
|
}}
|
|
to={cms.secondaryCtaHref}
|
|
>
|
|
{cms.secondaryCtaLabel}
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|