import {useEffect, useState, type FormEvent} from 'react'; import {useFetcher} from 'react-router'; import {motion} from 'motion/react'; import {ArrowRight, Sparkles} from 'lucide-react'; import {PAGE_TITLE_TYPOGRAPHY_CLASS} from '~/lib/pageTitle'; import type {NewsletterSectionCms} from '~/lib/newsletter'; import {MOCK_NEWSLETTER_SECTION_CMS} from '~/lib/newsletter'; /** * GROQ: `home` singleton → Glow Club newsletter. * Use with `context.sanity.loadQuery(NEWSLETTER_SECTION_GROQ)` or `loadNewsletterFromSanity`. * * Reference has no ``; decorative orbs are CSS-only (no Shopify `Image` needed). */ export const NEWSLETTER_SECTION_GROQ = `*[_type == "home" && _id == "home"][0].newsletterSection{ eyebrow, headlinePlain, headlineItalic, body, emailPlaceholder, submitButtonLabel, successTitle, successSubtitle, perks }` as const; export {MOCK_NEWSLETTER_SECTION_CMS}; export type {NewsletterSectionCms}; /** * Design tokens match `React/src/app/components/Newsletter.tsx` (pixel-aligned Tailwind). */ const BURGUNDY = '#5d0b24'; const CREAM = '#faf8f3'; const CREAM_SOFT = '#f8f4ef'; const GOLD = '#b39763'; const CHARCOAL = '#1a1a1a'; const NEWSLETTER_SUBSCRIBE_ACTION = '/api/newsletter-subscribe'; type SubscribeResponse = | {ok: true; alreadyExists?: boolean} | {ok: false; error: string}; function subscribeErrorMessage(code: string): string { switch (code) { case 'invalid_email': return 'Please enter a valid email address.'; case 'not_configured': return 'Newsletter signup is temporarily unavailable.'; case 'service_unavailable': case 'shopify_error': default: return 'Something went wrong. Please try again in a moment.'; } } export type NewsletterProps = { cms: NewsletterSectionCms; }; export function Newsletter({cms}: NewsletterProps) { const fetcher = useFetcher(); const [email, setEmail] = useState(''); const [submitted, setSubmitted] = useState(false); useEffect(() => { if (fetcher.state !== 'idle' || !fetcher.data) return; if (fetcher.data.ok) { setSubmitted(true); } }, [fetcher.state, fetcher.data]); function handleSubmit(e: FormEvent) { e.preventDefault(); const trimmed = email.trim(); if (!trimmed) return; fetcher.submit({email: trimmed}, {method: 'POST', action: NEWSLETTER_SUBSCRIBE_ACTION}); } const submitError = fetcher.state === 'idle' && fetcher.data && !fetcher.data.ok && 'error' in fetcher.data ? subscribeErrorMessage(fetcher.data.error) : null; const isSubmitting = fetcher.state !== 'idle'; return (

{cms.eyebrow}

{/* Headline + body + form share one grid column. The form uses min-width in vw so the column can grow up to 649px even when the headline is narrower (100% alone would stay headline-wide). */}

{cms.headlinePlain}{' '} {cms.headlineItalic}

{cms.body}

{!submitted ? (
setEmail(e.target.value)} placeholder={cms.emailPlaceholder} style={{ padding: '17px 21.25px', background: 'rgba(248, 244, 239, 0.07)', color: CREAM_SOFT, fontFamily: "'Raleway', sans-serif", fontSize: '15.64px', fontWeight: 300, height: 61, minHeight: 61, boxSizing: 'border-box', }} type="email" value={email} /> {submitError ?

{submitError}

: null}
) : (

{cms.successTitle} ✨

{cms.successSubtitle}

)}
{cms.perks.map((perk, i) => ( · {perk} ))}
); }