242 lines
9.3 KiB
TypeScript
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>
|
|
)
|