wwwfiberdirekt/src/blocks/FDNewsletterBlock/Component.tsx

242 lines
9.3 KiB
TypeScript

'use client'
import React, { useState, useCallback } from 'react'
import type { FDNewsletterBlock as FDNewsletterBlockProps } from '@/payload-types'
// Navy is always dark. White/gray adapt to OS dark mode.
const bgClasses: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy',
navy: 'bg-fd-navy',
gray: 'bg-fd-surface-alt dark:bg-fd-navy',
yellow: 'bg-fd-yellow',
}
export const FDNewsletterBlockComponent: React.FC<FDNewsletterBlockProps> = ({
heading = 'Håll dig uppdaterad',
description,
submitEndpoint,
buttonText = 'Prenumerera',
successMessage = 'Tack! Du är nu prenumerant.',
consentText,
privacyPolicyLink = '/integritetspolicy',
collectName = false,
collectCompany = false,
layout = 'inline',
sectionBackground = 'navy',
textColor = 'auto',
}) => {
const [email, setEmail] = useState('')
const [name, setName] = useState('')
const [company, setCompany] = useState('')
const [consented, setConsented] = useState(false)
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
const [errorMsg, setErrorMsg] = useState('')
const isDark = sectionBackground === 'navy'
const headingColor =
textColor === 'white' ? 'text-white'
: textColor === 'navy' ? 'text-fd-navy'
: isDark
? 'text-fd-yellow'
: 'text-fd-navy dark:text-fd-yellow'
const bodyColor =
textColor === 'white' ? 'text-white'
: textColor === 'navy' ? 'text-fd-navy'
: isDark
? 'text-white'
: 'text-fd-navy dark:text-white'
const bgClass = bgClasses[sectionBackground ?? 'navy'] || 'bg-fd-navy'
const inputClass =
'w-full px-5 py-3 rounded-full text-fd-navy font-joey text-fd-body focus:outline-none focus:ring-2 focus:ring-fd-yellow bg-white border border-gray-200'
const handleSubmit = useCallback(
async (e: React.FormEvent) => {
e.preventDefault()
if (!email || !consented) return
setStatus('loading')
setErrorMsg('')
try {
const payload: Record<string, string> = { email }
if (collectName && name) payload.name = name
if (collectCompany && company) payload.company = company
const response = await fetch(submitEndpoint || '/api/newsletter', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
setStatus('success')
setEmail('')
setName('')
setCompany('')
setConsented(false)
} catch (err) {
setStatus('error')
setErrorMsg('Något gick fel. Försök igen senare.')
}
},
[email, name, company, consented, submitEndpoint, collectName, collectCompany],
)
if (status === 'success') {
return (
<section className={`relative w-full py-16 md:py-20 lg:py-[99px] ${bgClass} overflow-hidden`}>
<div className="relative max-w-[1200px] mx-auto px-6 md:px-8 text-center">
<div className="inline-flex items-center gap-3 mb-4">
<svg className="w-8 h-8 text-fd-mint" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
<p className={`font-joey-bold text-fd-h2 ${headingColor}`}>
{successMessage}
</p>
</div>
</div>
</section>
)
}
const isCard = layout === 'card'
const isInline = layout === 'inline'
return (
<section className={`relative w-full py-16 md:py-20 lg:py-[99px] ${bgClass} overflow-hidden`}>
<div className="relative max-w-[1200px] mx-auto px-6 md:px-8">
{isCard ? (
<div className="max-w-[700px] mx-auto bg-white/10 backdrop-blur-sm rounded-[70px] p-8 md:p-12 lg:p-16">
<div className="text-center mb-8">
{heading && (
<h2 className={`font-joey-medium text-fd-h1 mb-4 ${headingColor}`}>
{heading}
</h2>
)}
{description && (
<p className={`font-joey text-fd-body-lg ${bodyColor}`}>{description}</p>
)}
</div>
<div>
{collectName && (
<input type="text" placeholder="Namn" value={name} onChange={(e) => setName(e.target.value)} className={`${inputClass} mb-3`} />
)}
{collectCompany && (
<input type="text" placeholder="Företag" value={company} onChange={(e) => setCompany(e.target.value)} className={`${inputClass} mb-3`} />
)}
<input type="email" required placeholder="Din e-postadress" value={email} onChange={(e) => setEmail(e.target.value)} className={`${inputClass} mb-4`} />
<ConsentCheckbox consented={consented} setConsented={setConsented} consentText={consentText} privacyPolicyLink={privacyPolicyLink} isDark={isDark} bodyColor={bodyColor} />
<button
onClick={handleSubmit}
disabled={!email || !consented || status === 'loading'}
className="fd-btn-primary w-full mt-4 disabled:opacity-50 disabled:cursor-not-allowed"
>
{status === 'loading' ? 'Skickar...' : buttonText}
</button>
{status === 'error' && (
<p className="text-red-400 text-fd-small mt-3 text-center font-joey">{errorMsg}</p>
)}
</div>
</div>
) : (
<>
<div className={`${isInline ? 'text-center md:text-left' : 'text-center'} mb-8 md:mb-12`}>
{heading && (
<h2 className={`font-joey-medium text-fd-h1 mb-4 ${headingColor}`}>
{heading}
</h2>
)}
{description && (
<p className={`font-joey text-fd-body-lg max-w-[700px] ${isInline ? '' : 'mx-auto'} ${bodyColor}`}>
{description}
</p>
)}
</div>
<div className={`max-w-[700px] ${isInline ? '' : 'mx-auto'}`}>
{collectName && (
<input type="text" placeholder="Namn" value={name} onChange={(e) => setName(e.target.value)} className={`${inputClass} mb-3`} />
)}
{collectCompany && (
<input type="text" placeholder="Företag" value={company} onChange={(e) => setCompany(e.target.value)} className={`${inputClass} mb-3`} />
)}
{isInline ? (
<div className="flex flex-col sm:flex-row gap-3">
<input type="email" required placeholder="Din e-postadress" value={email} onChange={(e) => setEmail(e.target.value)} className={`${inputClass} flex-1`} />
<button
onClick={handleSubmit}
disabled={!email || !consented || status === 'loading'}
className="fd-btn-primary whitespace-nowrap disabled:opacity-50 disabled:cursor-not-allowed"
>
{status === 'loading' ? 'Skickar...' : buttonText}
</button>
</div>
) : (
<>
<input type="email" required placeholder="Din e-postadress" value={email} onChange={(e) => setEmail(e.target.value)} className={`${inputClass} mb-4`} />
<button
onClick={handleSubmit}
disabled={!email || !consented || status === 'loading'}
className="fd-btn-primary w-full disabled:opacity-50 disabled:cursor-not-allowed"
>
{status === 'loading' ? 'Skickar...' : buttonText}
</button>
</>
)}
<div className="mt-4">
<ConsentCheckbox consented={consented} setConsented={setConsented} consentText={consentText} privacyPolicyLink={privacyPolicyLink} isDark={isDark} bodyColor={bodyColor} />
</div>
{status === 'error' && (
<p className="text-red-400 text-fd-small mt-3 font-joey">{errorMsg}</p>
)}
</div>
</>
)}
</div>
</section>
)
}
interface ConsentCheckboxProps {
consented: boolean
setConsented: (v: boolean) => void
consentText?: string | null
privacyPolicyLink?: string | null
isDark: boolean
bodyColor: string
}
const ConsentCheckbox: React.FC<ConsentCheckboxProps> = ({
consented,
setConsented,
consentText,
privacyPolicyLink,
isDark,
bodyColor,
}) => (
<label className={`flex items-start gap-3 cursor-pointer font-joey text-fd-small ${bodyColor}`}>
<input
type="checkbox"
checked={consented}
onChange={(e) => setConsented(e.target.checked)}
className="mt-1 w-5 h-5 rounded border-2 border-current accent-fd-yellow flex-shrink-0"
/>
<span>
{consentText || 'Jag godkänner att mina uppgifter används enligt vår integritetspolicy.'}{' '}
{privacyPolicyLink && (
<a
href={privacyPolicyLink}
target="_blank"
rel="noopener noreferrer"
className={`underline ${isDark ? 'text-fd-yellow hover:text-fd-yellow/80' : 'text-fd-navy hover:text-fd-navy/70 dark:text-fd-yellow dark:hover:text-fd-yellow/80'}`}
>
Läs mer
</a>
)}
</span>
</label>
)