AbiirSkincare/Hydrogen/web/app/components/SpringGlowBanner.tsx

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>
);
}