685 lines
25 KiB
TypeScript
685 lines
25 KiB
TypeScript
import {useState} from 'react';
|
|
import {useFetcher, Link} from 'react-router';
|
|
import {
|
|
ChevronDown,
|
|
Clock,
|
|
Instagram,
|
|
Mail,
|
|
MessageCircle,
|
|
Package,
|
|
RotateCcw,
|
|
Truck,
|
|
Youtube,
|
|
X,
|
|
} from 'lucide-react';
|
|
import type {
|
|
AbiirContactPageCms,
|
|
AbiirContactReachRowCms,
|
|
AbiirLegalPageCms,
|
|
AbiirFaqPageCms,
|
|
AbiirInfoPageCms,
|
|
AbiirPressItemCms,
|
|
AbiirPressPageCms,
|
|
AbiirShippingPageCms,
|
|
} from '~/lib/abiirStaticPages';
|
|
import {
|
|
abiirFormErrorTextClass,
|
|
abiirFormPrimaryButtonClass,
|
|
abiirFormPrimaryButtonStyle,
|
|
abiirFormStackClass,
|
|
contactInputClass,
|
|
contactLabelClass,
|
|
} from '~/lib/abiirFormTheme';
|
|
|
|
const accent = '#c17d5c';
|
|
const burgundy = '#5d0b24';
|
|
|
|
/**
|
|
* Static pages: center-aligned hero band + full-width main column (body copy stays left in children).
|
|
*/
|
|
export function Shell({
|
|
title,
|
|
eyebrow = 'Help',
|
|
subtitle,
|
|
eyebrowTone = 'default',
|
|
children,
|
|
}: {
|
|
title: string;
|
|
/** Upper label above the title — matches collection “eyebrow” (e.g. Our Collection) */
|
|
eyebrow?: string;
|
|
/** Figma: Raleway 16 / #5a5550 (FAQ band) */
|
|
subtitle?: string;
|
|
/** `accent` = Support label (#c17d5c, Jost); `default` = Help band (burgundy Raleway) */
|
|
eyebrowTone?: 'default' | 'accent';
|
|
children: React.ReactNode;
|
|
}) {
|
|
const eyebrowClass =
|
|
eyebrowTone === 'accent'
|
|
? "m-0 font-['Jost',sans-serif] text-[11px] font-medium uppercase not-italic leading-[1.1] tracking-[2.76px] text-[#c17d5c]"
|
|
: "m-0 font-['Raleway',sans-serif] text-[0.75rem] font-medium uppercase not-italic leading-[1.1] tracking-[0.32em] text-[#5d0b24]";
|
|
return (
|
|
<div className="w-full min-h-screen bg-[#f8f4ef]">
|
|
<header className="w-full border-b border-[rgba(26,26,24,0.08)] bg-[#ede8e0] pt-[40px] pb-10 sm:pt-[48px] sm:pb-12">
|
|
<div className="mx-auto flex w-full max-w-[1710px] flex-col items-center px-6 text-center sm:px-10 lg:px-16 2xl:px-[206px]">
|
|
<p className={eyebrowClass}>{eyebrow}</p>
|
|
<h1 className="m-0 mt-3 w-full text-center font-['Cormorant_Garamond',serif] text-[2.5rem] font-light not-italic leading-[1.08] tracking-[-0.02em] text-[#1a1a18] sm:text-[3rem] lg:text-[4.25rem] lg:leading-[1.02]">
|
|
{title}
|
|
</h1>
|
|
{subtitle ? (
|
|
<p className="m-0 mt-4 w-full max-w-[800px] self-center text-balance text-center font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.5] text-[#5a5550]">
|
|
{subtitle}
|
|
</p>
|
|
) : null}
|
|
</div>
|
|
</header>
|
|
|
|
<div className="mx-auto w-full max-w-[1710px] px-6 pb-20 pt-10 sm:px-10 sm:pt-12 lg:px-16 lg:pt-[42.5px] 2xl:px-[206px]">
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function AbiirFaqPageView({cms}: {cms: AbiirFaqPageCms}) {
|
|
const [openId, setOpenId] = useState<string | null>(null);
|
|
return (
|
|
<Shell
|
|
eyebrow="Support"
|
|
eyebrowTone="accent"
|
|
subtitle={cms.subtitle}
|
|
title={cms.title}
|
|
>
|
|
<div className="mx-auto flex w-full max-w-[800px] flex-col text-left">
|
|
{cms.sections.map((section, sIdx) => (
|
|
<section className={sIdx > 0 ? 'mt-[51px]' : undefined} key={sIdx}>
|
|
<h3 className="m-0 font-['Cormorant_Garamond',serif] text-[1.75rem] not-italic leading-tight text-[#1a1a18] [font-weight:350]">
|
|
{section.sectionTitle}
|
|
</h3>
|
|
<div className="mt-[25.5px] space-y-0 border-t border-[rgba(26,26,24,0.08)]">
|
|
{section.items.map((item, iIdx) => {
|
|
const id = `${sIdx}-${iIdx}`;
|
|
const isOpen = openId === id;
|
|
return (
|
|
<div
|
|
className="border-b border-[rgba(26,26,24,0.08)]"
|
|
key={id}
|
|
>
|
|
<button
|
|
aria-expanded={isOpen}
|
|
className="flex min-h-[64px] w-full cursor-pointer items-center justify-between gap-4 border-0 bg-transparent py-0 text-left"
|
|
onClick={() =>
|
|
setOpenId((p) => (p === id ? null : id))
|
|
}
|
|
type="button"
|
|
>
|
|
<span className="flex-1 py-5 pr-2 font-['Jost',sans-serif] text-[1.125rem] font-normal not-italic leading-[1.35] text-[#1a1a18]">
|
|
{item.question}
|
|
</span>
|
|
<ChevronDown
|
|
aria-hidden
|
|
className={`h-[15px] w-[15px] shrink-0 text-[#1a1a18] transition-transform duration-200 ${isOpen ? 'rotate-180' : ''}`}
|
|
strokeWidth={1.75}
|
|
/>
|
|
</button>
|
|
{isOpen ? (
|
|
<div className="pb-6 pt-0">
|
|
<p className="m-0 w-full max-w-full whitespace-pre-wrap font-['Raleway',sans-serif] text-[1rem] font-medium not-italic leading-[1.7] text-[#5a5550]">
|
|
{item.answer}
|
|
</p>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</section>
|
|
))}
|
|
</div>
|
|
</Shell>
|
|
);
|
|
}
|
|
|
|
const returnPolicyIcons = [RotateCcw, Package, Clock] as const;
|
|
const ordersShippingIcons = [Truck, Package, Clock] as const;
|
|
|
|
export function AbiirShippingPageView({cms}: {cms: AbiirShippingPageCms}) {
|
|
return (
|
|
<Shell
|
|
eyebrow="Help"
|
|
subtitle={cms.intro || undefined}
|
|
title={cms.title}
|
|
>
|
|
<div className="mx-auto w-full max-w-[800px] text-left">
|
|
<div className="flex flex-col gap-[25.5px] pt-0.5 sm:pt-2">
|
|
<h3 className="m-0 font-['Cormorant_Garamond',serif] text-[1.9rem] font-light not-italic leading-tight tracking-[-0.02em] text-[#1a1a18] sm:text-[1.9125rem]">
|
|
Orders & shipping
|
|
</h3>
|
|
<div className="flex flex-col gap-[25.5px]">
|
|
{cms.shippingOptions.map((row, i) => {
|
|
const Icon = ordersShippingIcons[i % ordersShippingIcons.length];
|
|
return (
|
|
<div
|
|
className="flex gap-4 bg-[#ede8e0] p-[25.5px] sm:gap-5"
|
|
key={`${row.title}-${i}`}
|
|
>
|
|
<div className="shrink-0 pt-0.5">
|
|
<Icon
|
|
aria-hidden
|
|
className="h-5 w-5 text-[#c17d5c]"
|
|
strokeWidth={1.5}
|
|
/>
|
|
</div>
|
|
<div className="abiir-shipping-qa-stack min-w-0 flex-1">
|
|
<h3 className="abiir-shipping-qa-title font-['Jost',sans-serif] text-[0.83rem] font-medium uppercase not-italic leading-tight tracking-[0.1em] text-[#1a1a18]">
|
|
{row.title}
|
|
</h3>
|
|
<div className="abiir-shipping-qa-subtitles">
|
|
<p className="font-['Jost',sans-serif] text-[0.765rem] font-medium not-italic leading-[1.4] text-[#c17d5c]">
|
|
{row.leadTime}
|
|
</p>
|
|
<p className="font-['Jost',sans-serif] text-[0.765rem] font-normal not-italic leading-[1.4] text-[#1a1a18]">
|
|
{row.priceLine}
|
|
</p>
|
|
</div>
|
|
<p className="abiir-shipping-qa-body whitespace-pre-line font-['Jost',sans-serif] text-[0.88rem] font-light not-italic leading-[1.75] text-[#7a7068]">
|
|
<LegalMailAndShippingLinks text={row.detail} />
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-[68px] flex flex-col gap-[25.5px]">
|
|
<h3 className="m-0 font-['Cormorant_Garamond',serif] text-[1.9rem] font-light not-italic leading-tight tracking-[-0.02em] text-[#1a1a18] sm:text-[1.9125rem]">
|
|
Returns & refunds
|
|
</h3>
|
|
<div className="flex flex-col gap-[25.5px]">
|
|
{cms.returnsPolicy.map((row, i) => {
|
|
const Icon = returnPolicyIcons[i % returnPolicyIcons.length];
|
|
return (
|
|
<div
|
|
className="flex gap-4 bg-[#ede8e0] p-[25.5px] sm:gap-5"
|
|
key={`${row.title}-${i}`}
|
|
>
|
|
<div className="shrink-0 pt-0.5">
|
|
<Icon
|
|
aria-hidden
|
|
className="h-5 w-5 text-[#c17d5c]"
|
|
strokeWidth={1.5}
|
|
/>
|
|
</div>
|
|
<div className="abiir-shipping-qa-stack min-w-0 flex-1">
|
|
<h3 className="abiir-shipping-qa-title font-['Jost',sans-serif] text-[0.83rem] font-medium uppercase not-italic leading-tight tracking-[0.1em] text-[#1a1a18]">
|
|
{row.title}
|
|
</h3>
|
|
<p className="abiir-shipping-qa-body whitespace-pre-line font-['Jost',sans-serif] text-[0.88rem] font-light not-italic leading-[1.75] text-[#7a7068]">
|
|
<LegalMailAndShippingLinks text={row.body} />
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="abiir-shipping-support-banner mt-[68px] -mx-6 bg-[#1a1a18] px-6 py-8 text-center sm:-mx-10 sm:px-10 sm:py-[34px] lg:-mx-16 lg:px-16 2xl:-mx-[206px] 2xl:px-[206px]">
|
|
<p className="m-0 font-['Cormorant_Garamond',serif] text-[1.17rem] font-light italic leading-snug text-[#f8f4ef]">
|
|
{cms.bannerHeadline}
|
|
</p>
|
|
<p className="m-0 mt-2 font-['Jost',sans-serif] text-[0.8rem] font-light not-italic leading-[1.8] text-[rgba(248,244,239,0.6)]">
|
|
<LegalMailAndShippingLinks
|
|
emailLinkClassName="abiir-shipping-support-banner-email transition-opacity hover:opacity-85"
|
|
text={`${cms.bannerTextBeforeEmail}${cms.supportEmail}${cms.bannerTextAfterEmail}`}
|
|
/>
|
|
</p>
|
|
</div>
|
|
</Shell>
|
|
);
|
|
}
|
|
|
|
export function AbiirInfoTextPageView({cms}: {cms: AbiirInfoPageCms}) {
|
|
return (
|
|
<Shell eyebrow="Help" title={cms.title}>
|
|
{cms.intro ? (
|
|
<p className="m-0 mb-8 w-full text-left font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.4] text-[#5a5550]">
|
|
{cms.intro}
|
|
</p>
|
|
) : null}
|
|
<div className="w-full space-y-6 text-left">
|
|
{cms.paragraphs.map((p, i) => (
|
|
<p
|
|
className="m-0 w-full max-w-full font-['Raleway',sans-serif] text-[1rem] font-medium leading-[1.8] text-[#5a5550]"
|
|
key={i}
|
|
>
|
|
{p}
|
|
</p>
|
|
))}
|
|
</div>
|
|
</Shell>
|
|
);
|
|
}
|
|
|
|
const EMAIL_IN_TEXT = /([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})/gi;
|
|
const SHIPPING_LINK_PHRASE = 'Shipping & Returns page';
|
|
|
|
function LegalMailAndShippingLinks({
|
|
text,
|
|
emailLinkClassName = 'text-[#c17d5c] no-underline transition-opacity hover:opacity-90',
|
|
}: {
|
|
text: string;
|
|
emailLinkClassName?: string;
|
|
}) {
|
|
if (text.includes(SHIPPING_LINK_PHRASE)) {
|
|
const [a, b] = text.split(SHIPPING_LINK_PHRASE);
|
|
return (
|
|
<>
|
|
{LegalMailAndShippingLinks({text: a || '', emailLinkClassName})}
|
|
<Link
|
|
className="text-[#c17d5c] no-underline transition-opacity hover:opacity-90"
|
|
to="/shipping"
|
|
>
|
|
{SHIPPING_LINK_PHRASE}
|
|
</Link>
|
|
{LegalMailAndShippingLinks({text: b || '', emailLinkClassName})}
|
|
</>
|
|
);
|
|
}
|
|
const parts = text.split(EMAIL_IN_TEXT);
|
|
return (
|
|
<>
|
|
{parts.map((part, i) =>
|
|
i % 2 === 1 ? (
|
|
<a
|
|
className={emailLinkClassName}
|
|
href={`mailto:${part}`}
|
|
key={`e-${i}-${part}`}
|
|
>
|
|
{part}
|
|
</a>
|
|
) : (
|
|
<span key={`t-${i}`}>{part}</span>
|
|
),
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function LegalParagraphBlock({text}: {text: string}) {
|
|
if (text.includes('\n')) {
|
|
return (
|
|
<p className="m-0 whitespace-pre-line">
|
|
<LegalMailAndShippingLinks text={text} />
|
|
</p>
|
|
);
|
|
}
|
|
return (
|
|
<p className="m-0">
|
|
<LegalMailAndShippingLinks text={text} />
|
|
</p>
|
|
);
|
|
}
|
|
|
|
export function AbiirLegalPageView({cms}: {cms: AbiirLegalPageCms}) {
|
|
return (
|
|
<Shell
|
|
eyebrow={cms.heroEyebrow}
|
|
eyebrowTone="accent"
|
|
subtitle={cms.lastUpdatedLine}
|
|
title={cms.title}
|
|
>
|
|
<div className="mx-auto flex w-full max-w-[800px] flex-col gap-[42.5px] text-left">
|
|
<p className="m-0 font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.48] text-[#7a7068]">
|
|
<LegalMailAndShippingLinks text={cms.lead} />
|
|
</p>
|
|
<div className="flex w-full flex-col gap-10 sm:gap-[40px]">
|
|
{cms.sections.map((section, sIdx) => (
|
|
<section className="w-full" key={`${sIdx}-${section.title}`}>
|
|
<h3 className="m-0 [font-weight:350] font-['Cormorant_Garamond',serif] text-[1.75rem] not-italic leading-tight tracking-[-0.02em] text-[#1a1a18]">
|
|
{section.title}
|
|
</h3>
|
|
<div className="mt-4 space-y-3.5 sm:mt-[17px] sm:space-y-[12.75px]">
|
|
{section.paragraphs.map((p, pIdx) => (
|
|
<div
|
|
className="font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.48] text-[#7a7068]"
|
|
key={`${sIdx}-p-${pIdx}`}
|
|
>
|
|
<LegalParagraphBlock text={p} />
|
|
</div>
|
|
))}
|
|
{section.listItems?.length ? (
|
|
<ul className="m-0 list-outside list-disc space-y-2.5 pl-5 font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.48] text-[#7a7068] marker:text-[#7a7068]">
|
|
{section.listItems.map((item) => (
|
|
<li className="pl-1" key={item}>
|
|
<LegalMailAndShippingLinks text={item} />
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : null}
|
|
{section.trailingParagraphs?.map((p, tpIdx) => (
|
|
<div
|
|
className="font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.48] text-[#7a7068]"
|
|
key={`${sIdx}-t-${tpIdx}`}
|
|
>
|
|
<LegalParagraphBlock text={p} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</section>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</Shell>
|
|
);
|
|
}
|
|
|
|
type ContactViewProps = {cms: AbiirContactPageCms};
|
|
|
|
function contactReachRowIcon(
|
|
row: AbiirContactReachRowCms,
|
|
) {
|
|
const c = 'h-4 w-4 text-[#c17d5c]';
|
|
if (row.icon === 'message') {
|
|
return <MessageCircle aria-hidden className={c} strokeWidth={1.5} />;
|
|
}
|
|
if (row.icon === 'clock') {
|
|
return <Clock aria-hidden className={c} strokeWidth={1.5} />;
|
|
}
|
|
return <Mail aria-hidden className={c} strokeWidth={1.5} />;
|
|
}
|
|
|
|
function socialIcon(network: 'instagram' | 'youtube') {
|
|
const c = 'h-[15px] w-[15px] shrink-0 text-[#1a1a18]';
|
|
return network === 'youtube' ? (
|
|
<Youtube aria-hidden className={c} strokeWidth={1.4} />
|
|
) : (
|
|
<Instagram aria-hidden className={c} strokeWidth={1.4} />
|
|
);
|
|
}
|
|
|
|
function ContactFormBlock({cms}: ContactViewProps) {
|
|
const fetcher = useFetcher<{ok?: boolean; error?: string}>();
|
|
const err = fetcher.data && !fetcher.data.ok;
|
|
const p = cms.formPlaceholders;
|
|
return (
|
|
<div className="flex w-full min-w-0 flex-col text-left">
|
|
{fetcher.data?.ok ? (
|
|
<p className="m-0 text-center font-['Raleway',sans-serif] text-[0.95rem] leading-[1.6] text-[#5a5550] sm:text-left">
|
|
{cms.formSuccessText}
|
|
</p>
|
|
) : (
|
|
<fetcher.Form
|
|
className={abiirFormStackClass}
|
|
method="post"
|
|
action="/api/contact"
|
|
>
|
|
{cms.formHeadline ? (
|
|
<h3 className="m-0 font-['Raleway',sans-serif] text-[0.8rem] font-semibold uppercase tracking-[0.1em] text-[#1a1a18]">
|
|
{cms.formHeadline}
|
|
</h3>
|
|
) : null}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="flex flex-col gap-2" htmlFor="contact-name">
|
|
<span className={contactLabelClass}>Your name</span>
|
|
<input
|
|
className={contactInputClass}
|
|
id="contact-name"
|
|
name="name"
|
|
placeholder={p.name}
|
|
required
|
|
type="text"
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<label className="flex flex-col gap-2" htmlFor="contact-email">
|
|
<span className={contactLabelClass}>Email address</span>
|
|
<input
|
|
className={contactInputClass}
|
|
id="contact-email"
|
|
name="email"
|
|
placeholder={p.email}
|
|
required
|
|
type="email"
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<label className="flex flex-col gap-2" htmlFor="contact-subject">
|
|
<span className={contactLabelClass}>Subject</span>
|
|
<input
|
|
className={contactInputClass}
|
|
id="contact-subject"
|
|
name="subject"
|
|
placeholder={p.subject}
|
|
type="text"
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<label className="flex flex-col gap-2" htmlFor="contact-message">
|
|
<span className={contactLabelClass}>Message</span>
|
|
<textarea
|
|
className={`${contactInputClass} min-h-[166px] resize-y leading-[1.6]`}
|
|
id="contact-message"
|
|
name="message"
|
|
placeholder={p.message}
|
|
required
|
|
/>
|
|
</label>
|
|
</div>
|
|
{err ?
|
|
<p className={abiirFormErrorTextClass}>
|
|
Something went wrong. Please try again.
|
|
</p>
|
|
: null}
|
|
<button
|
|
className={abiirFormPrimaryButtonClass}
|
|
disabled={fetcher.state !== 'idle'}
|
|
style={abiirFormPrimaryButtonStyle}
|
|
type="submit"
|
|
>
|
|
{fetcher.state !== 'idle' ? 'Sending…' : cms.formSubmitLabel}
|
|
</button>
|
|
</fetcher.Form>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function AbiirContactPageView({cms}: ContactViewProps) {
|
|
return (
|
|
<Shell
|
|
eyebrow={cms.heroEyebrow}
|
|
eyebrowTone="accent"
|
|
subtitle={cms.intro}
|
|
title={cms.title}
|
|
>
|
|
<div className="mx-auto grid w-full max-w-[998px] grid-cols-1 items-start gap-[48px] text-left sm:gap-[68px] lg:grid-cols-2">
|
|
<div className="flex min-w-0 flex-col">
|
|
<h3 className="m-0 font-['Cormorant_Garamond',serif] text-[1.7rem] font-light not-italic leading-tight tracking-[-0.02em] text-[#1a1a18] sm:text-[1.9125rem]">
|
|
{cms.howToReachHeading}
|
|
</h3>
|
|
<div className="mt-8 flex flex-col gap-[25.5px] sm:mt-[4.25rem]">
|
|
{cms.reachRows.map((row) => {
|
|
const isEmail = row.icon === 'mail' || row.label.toLowerCase() === 'email';
|
|
return (
|
|
<div className="flex gap-[17px]" key={row.label}>
|
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center bg-[#ede8e0]">
|
|
{contactReachRowIcon(row)}
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="m-0 font-['Jost',sans-serif] text-[0.69rem] font-normal uppercase not-italic leading-tight tracking-[0.1em] text-[#7a7068]">
|
|
{row.label}
|
|
</p>
|
|
{isEmail ? (
|
|
<a
|
|
className="mt-1.5 block font-['Cormorant_Garamond',serif] text-[1.0625rem] font-medium not-italic text-[#1a1a18] no-underline transition-opacity hover:opacity-80"
|
|
href={`mailto:${cms.email}`}
|
|
>
|
|
{row.primary}
|
|
</a>
|
|
) : (
|
|
<p className="m-0 mt-1.5 font-['Cormorant_Garamond',serif] text-[1.0625rem] font-medium not-italic text-[#1a1a18]">
|
|
{row.primary}
|
|
</p>
|
|
)}
|
|
<p className="m-0 mt-0.5 font-['Jost',sans-serif] text-[0.765rem] font-light not-italic leading-[1.45] text-[#7a7068]">
|
|
{row.note}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className="mt-10 border-t border-[rgba(26,26,24,0.08)] pt-10 sm:mt-11 sm:pt-11">
|
|
<p className="m-0 font-['Jost',sans-serif] text-[10.2px] font-medium uppercase not-italic leading-tight tracking-[0.2em] text-[#7a7068]">
|
|
{cms.followUsLabel}
|
|
</p>
|
|
<div className="mt-4 flex flex-col gap-4 sm:flex-row sm:items-center sm:gap-[17px]">
|
|
{cms.socialLinks.map((s) => (
|
|
<a
|
|
className="inline-flex items-center gap-2.5 text-[#1a1a18] no-underline transition-opacity hover:opacity-75"
|
|
href={s.href}
|
|
key={s.label + s.network}
|
|
rel="noreferrer"
|
|
target="_blank"
|
|
>
|
|
{socialIcon(s.network)}
|
|
<span className="font-['Jost',sans-serif] text-[0.765rem] font-normal not-italic">
|
|
{s.label}
|
|
</span>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{cms.showContactForm ? <ContactFormBlock cms={cms} /> : null}
|
|
</div>
|
|
</Shell>
|
|
);
|
|
}
|
|
|
|
function PressCard({
|
|
item,
|
|
onOpen,
|
|
}: {
|
|
item: AbiirPressItemCms;
|
|
onOpen: () => void;
|
|
}) {
|
|
return (
|
|
<button
|
|
className="group flex w-full cursor-pointer flex-col overflow-hidden border border-[rgba(26,26,24,0.08)] bg-white/50 text-left transition-opacity hover:opacity-92"
|
|
onClick={onOpen}
|
|
type="button"
|
|
>
|
|
<div
|
|
className="relative w-full"
|
|
style={{aspectRatio: '4/3', background: 'rgba(26,26,24,0.04)'}}
|
|
>
|
|
{item.image?.url ? (
|
|
<img
|
|
alt={item.image.alt || item.title}
|
|
className="h-full w-full object-cover"
|
|
height={item.image.height}
|
|
src={item.image.url}
|
|
width={item.image.width}
|
|
/>
|
|
) : (
|
|
<div
|
|
className="flex h-full w-full items-center justify-center font-['Cormorant_Garamond',serif] text-[2rem] font-light"
|
|
style={{color: accent}}
|
|
>
|
|
{item.title.charAt(0)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-4">
|
|
<p
|
|
className="m-0 mb-1 font-['Jost',sans-serif] text-[0.7rem] uppercase tracking-wide"
|
|
style={{color: accent}}
|
|
>
|
|
{item.listCaption}
|
|
</p>
|
|
<p className="m-0 font-['Raleway',sans-serif] text-[0.95rem] font-semibold text-[#1a1a18] group-hover:underline">
|
|
{item.title}
|
|
</p>
|
|
</div>
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export function AbiirPressPageView({cms}: {cms: AbiirPressPageCms}) {
|
|
const [open, setOpen] = useState<AbiirPressItemCms | null>(null);
|
|
|
|
return (
|
|
<Shell eyebrow="Press" title={cms.title}>
|
|
{cms.intro ? (
|
|
<p className="m-0 mb-10 w-full text-left font-['Raleway',sans-serif] text-base font-medium not-italic leading-[1.4] text-[#5a5550]">
|
|
{cms.intro}
|
|
</p>
|
|
) : null}
|
|
<div className="grid w-full grid-cols-1 gap-6 text-left sm:grid-cols-2 lg:grid-cols-3">
|
|
{cms.items.map((item, i) => (
|
|
<PressCard
|
|
item={item}
|
|
key={i}
|
|
onOpen={() => setOpen(item)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{open ? (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style={{background: 'rgba(26,26,24,0.55)'}}
|
|
onClick={() => setOpen(null)}
|
|
onKeyDown={(e) => e.key === 'Escape' && setOpen(null)}
|
|
role="dialog"
|
|
aria-modal
|
|
tabIndex={-1}
|
|
>
|
|
<div
|
|
className="relative max-h-[90vh] w-full max-w-[640px] overflow-y-auto rounded-sm bg-white p-6 shadow-lg"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<button
|
|
aria-label="Close"
|
|
className="absolute right-3 top-3 border-none bg-transparent p-1 text-[#7a7068] hover:opacity-70"
|
|
onClick={() => setOpen(null)}
|
|
type="button"
|
|
>
|
|
<X size={22} />
|
|
</button>
|
|
{open.image?.url ? (
|
|
<div
|
|
className="relative -mx-2 mb-4"
|
|
style={{aspectRatio: '16/9'}}
|
|
>
|
|
<img
|
|
alt={open.image.alt || open.title}
|
|
className="h-full w-full object-cover"
|
|
src={open.image.url}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
<h2 className="m-0 mb-2 pr-8 font-['Cormorant_Garamond',serif] text-[1.6rem] font-light text-[#1a1a18]">
|
|
{open.title}
|
|
</h2>
|
|
{open.listCaption ? (
|
|
<p className="m-0 mb-3 font-['Jost',sans-serif] text-[0.75rem] uppercase tracking-wide" style={{color: accent}}>
|
|
{open.listCaption}
|
|
</p>
|
|
) : null}
|
|
<p className="m-0 whitespace-pre-wrap font-['Raleway',sans-serif] text-[0.95rem] font-medium leading-[1.75] text-[#5a5550]">
|
|
{open.modalDescription}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</Shell>
|
|
);
|
|
}
|