|
|
|
|
@ -4,19 +4,21 @@ import React, { useState } from 'react'
|
|
|
|
|
import type { FDContactFormBlock as FDContactFormBlockProps } from '@/payload-types'
|
|
|
|
|
import type { Media } from '@/payload-types'
|
|
|
|
|
import { FDImage } from '@/components/FDImage'
|
|
|
|
|
import { FDButton } from '@/components/FDButton'
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* Theme maps */
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
|
|
|
|
|
const sectionBgMap: Record<string, string> = {
|
|
|
|
|
white: 'bg-white',
|
|
|
|
|
gray: 'bg-[#F0F0F0]',
|
|
|
|
|
white: 'bg-white dark:bg-fd-navy',
|
|
|
|
|
gray: 'bg-fd-gray-light dark:bg-fd-navy',
|
|
|
|
|
navy: 'bg-fd-navy',
|
|
|
|
|
navyGradient: 'bg-gradient-to-br from-fd-navy via-[#153350] to-fd-navy',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isDark = (bg: string) => bg === 'navy' || bg === 'navyGradient'
|
|
|
|
|
// Navy/navyGradient are always dark. White/gray adapt via OS dark: classes.
|
|
|
|
|
const isExplicitDark = (bg: string) => bg === 'navy' || bg === 'navyGradient'
|
|
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
|
/* Component */
|
|
|
|
|
@ -40,11 +42,10 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
|
|
|
|
|
const media = sideImage as Media | undefined
|
|
|
|
|
const hasSideImage = layout === 'withImage' && Boolean(media?.url)
|
|
|
|
|
const dark = isDark(sectionBackground || 'white')
|
|
|
|
|
const dark = isExplicitDark(sectionBackground || 'white')
|
|
|
|
|
const sectionBg = sectionBgMap[sectionBackground || 'white']
|
|
|
|
|
const isCard = layout === 'card'
|
|
|
|
|
|
|
|
|
|
// Extract form object
|
|
|
|
|
const form = formRelation && typeof formRelation === 'object' ? formRelation : null
|
|
|
|
|
const formId = form ? form.id : (formRelation ?? null)
|
|
|
|
|
|
|
|
|
|
@ -60,26 +61,18 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
|
|
|
|
|
setStatus('sending')
|
|
|
|
|
try {
|
|
|
|
|
// 1. Submit to Payload
|
|
|
|
|
const res = await fetch('/api/form-submissions', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
form: formId,
|
|
|
|
|
submissionData: Object.entries(formData).map(([field, value]) => ({
|
|
|
|
|
field,
|
|
|
|
|
value,
|
|
|
|
|
})),
|
|
|
|
|
submissionData: Object.entries(formData).map(([field, value]) => ({ field, value })),
|
|
|
|
|
}),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 2. Submit to external API if configured
|
|
|
|
|
if (externalApi?.enabled && externalApi?.endpoint) {
|
|
|
|
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
|
|
|
|
if (externalApi.authToken) {
|
|
|
|
|
headers['Authorization'] = `Bearer ${externalApi.authToken}`
|
|
|
|
|
}
|
|
|
|
|
// Fire and forget — don't block UX on external API
|
|
|
|
|
if (externalApi.authToken) headers['Authorization'] = `Bearer ${externalApi.authToken}`
|
|
|
|
|
fetch(externalApi.endpoint, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers,
|
|
|
|
|
@ -89,7 +82,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
data: formData,
|
|
|
|
|
submittedAt: new Date().toISOString(),
|
|
|
|
|
}),
|
|
|
|
|
}).catch(() => {}) // silently fail
|
|
|
|
|
}).catch(() => {})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setStatus(res.ok ? 'sent' : 'error')
|
|
|
|
|
@ -98,27 +91,31 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---- Input styles (light vs dark) ---- */
|
|
|
|
|
/* ---- Input styles ---- */
|
|
|
|
|
// Light sections: light inputs + dark: variants for OS dark mode
|
|
|
|
|
// Dark sections: always dark inputs
|
|
|
|
|
|
|
|
|
|
const inputBase = 'w-full rounded-xl px-4 py-3 font-joey text-base outline-none transition-all'
|
|
|
|
|
const inputLight = `${inputBase} bg-gray-100 text-fd-navy placeholder:text-fd-navy/40 focus:ring-2 focus:ring-fd-navy/20 focus:bg-white`
|
|
|
|
|
const inputDark = `${inputBase} bg-white/10 text-white placeholder:text-white/40 border border-white/20 focus:ring-2 focus:ring-fd-yellow/30 focus:bg-white/15 focus:border-fd-yellow/50`
|
|
|
|
|
const inputClass = dark ? inputDark : inputLight
|
|
|
|
|
|
|
|
|
|
const checkboxLight = 'w-5 h-5 rounded border-gray-300 text-fd-navy focus:ring-fd-navy/20'
|
|
|
|
|
const checkboxDark = 'w-5 h-5 rounded border-white/30 text-fd-yellow focus:ring-fd-yellow/30'
|
|
|
|
|
const checkboxClass = dark ? checkboxDark : checkboxLight
|
|
|
|
|
const inputClass = dark
|
|
|
|
|
? `${inputBase} bg-white/10 text-white placeholder:text-white/40 border border-white/20 focus:ring-2 focus:ring-fd-yellow/30 focus:bg-white/15 focus:border-fd-yellow/50`
|
|
|
|
|
: `${inputBase} bg-gray-100 text-fd-navy placeholder:text-fd-navy/40 focus:ring-2 focus:ring-fd-navy/20 focus:bg-white dark:bg-white/10 dark:text-white dark:placeholder:text-white/40 dark:border dark:border-white/20 dark:focus:ring-fd-yellow/30 dark:focus:bg-white/15`
|
|
|
|
|
|
|
|
|
|
const checkboxClass = dark
|
|
|
|
|
? 'w-5 h-5 rounded border-white/30 text-fd-yellow focus:ring-fd-yellow/30'
|
|
|
|
|
: 'w-5 h-5 rounded border-gray-300 text-fd-navy focus:ring-fd-navy/20 dark:border-white/30 dark:text-fd-yellow dark:focus:ring-fd-yellow/30'
|
|
|
|
|
|
|
|
|
|
/* ---- Text color helpers ---- */
|
|
|
|
|
|
|
|
|
|
const headingColor = dark ? 'text-fd-yellow' : 'text-fd-navy'
|
|
|
|
|
const bodyColor = dark ? 'text-white/80' : 'text-fd-navy/80'
|
|
|
|
|
const labelColor = dark ? 'text-white' : 'text-fd-navy'
|
|
|
|
|
const mutedColor = dark ? 'text-white/50' : 'text-fd-navy/60'
|
|
|
|
|
const headingColor = dark ? 'text-fd-yellow' : 'text-fd-navy dark:text-fd-yellow'
|
|
|
|
|
const bodyColor = dark ? 'text-white/80' : 'text-fd-navy/80 dark:text-white/80'
|
|
|
|
|
const labelColor = dark ? 'text-white' : 'text-fd-navy dark:text-white'
|
|
|
|
|
const mutedColor = dark ? 'text-white/50' : 'text-fd-navy/60 dark:text-white/50'
|
|
|
|
|
const errorColor = dark ? 'text-red-300' : 'text-red-600 dark:text-red-300'
|
|
|
|
|
const requiredColor = dark ? 'text-fd-yellow' : 'text-fd-navy dark:text-fd-yellow'
|
|
|
|
|
const linkColor = dark
|
|
|
|
|
? 'underline underline-offset-2 hover:text-fd-yellow transition-colors'
|
|
|
|
|
: 'underline underline-offset-2 hover:text-fd-navy transition-colors'
|
|
|
|
|
const errorColor = dark ? 'text-red-300' : 'text-red-600'
|
|
|
|
|
: 'underline underline-offset-2 hover:text-fd-navy dark:hover:text-fd-yellow transition-colors'
|
|
|
|
|
|
|
|
|
|
/* ---- Field renderer ---- */
|
|
|
|
|
|
|
|
|
|
@ -153,9 +150,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
>
|
|
|
|
|
<option value="">Välj...</option>
|
|
|
|
|
{field.options?.map((opt: any) => (
|
|
|
|
|
<option key={opt.value} value={opt.value}>
|
|
|
|
|
{opt.label}
|
|
|
|
|
</option>
|
|
|
|
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
)
|
|
|
|
|
@ -214,16 +209,14 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
}
|
|
|
|
|
})()
|
|
|
|
|
|
|
|
|
|
if (blockType === 'checkbox') {
|
|
|
|
|
return { element: input, width }
|
|
|
|
|
}
|
|
|
|
|
if (blockType === 'checkbox') return { element: input, width }
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
element: (
|
|
|
|
|
<div className="flex flex-col gap-1.5">
|
|
|
|
|
<label className={`font-joey text-sm md:text-base ${labelColor}`}>
|
|
|
|
|
{label}
|
|
|
|
|
{required && <span className={dark ? 'text-fd-yellow' : 'text-fd-navy'}> *</span>}
|
|
|
|
|
{required && <span className={requiredColor}> *</span>}
|
|
|
|
|
</label>
|
|
|
|
|
{input}
|
|
|
|
|
</div>
|
|
|
|
|
@ -232,7 +225,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---- Fields layout (handles half-width pairing) ---- */
|
|
|
|
|
/* ---- Fields layout ---- */
|
|
|
|
|
|
|
|
|
|
const renderFields = () => {
|
|
|
|
|
if (!form?.fields) return null
|
|
|
|
|
@ -245,9 +238,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
const field = fields[i]
|
|
|
|
|
|
|
|
|
|
if (field.blockType === 'message') {
|
|
|
|
|
elements.push(
|
|
|
|
|
<div key={i} className={`font-joey text-sm ${mutedColor}`} />,
|
|
|
|
|
)
|
|
|
|
|
elements.push(<div key={i} className={`font-joey text-sm ${mutedColor}`} />)
|
|
|
|
|
i++
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
@ -290,8 +281,8 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
return (
|
|
|
|
|
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
|
|
|
|
|
<div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center">
|
|
|
|
|
<div className={`inline-flex items-center justify-center w-16 h-16 rounded-full mb-6 ${dark ? 'bg-fd-yellow/20' : 'bg-fd-mint/20'}`}>
|
|
|
|
|
<svg className={`w-8 h-8 ${dark ? 'text-fd-yellow' : 'text-fd-mint'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
|
|
|
|
<div className={`inline-flex items-center justify-center w-16 h-16 rounded-full mb-6 ${dark ? 'bg-fd-yellow/20' : 'bg-fd-mint/20 dark:bg-fd-yellow/20'}`}>
|
|
|
|
|
<svg className={`w-8 h-8 ${dark ? 'text-fd-yellow' : 'text-fd-mint dark:text-fd-yellow'}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
@ -321,9 +312,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
const formContent = (
|
|
|
|
|
<>
|
|
|
|
|
{heading && (
|
|
|
|
|
<h2
|
|
|
|
|
className={`font-joey-heavy text-3xl md:text-4xl lg:text-5xl leading-tight mb-3 ${headingColor}`}
|
|
|
|
|
>
|
|
|
|
|
<h2 className={`font-joey-heavy text-3xl md:text-4xl lg:text-5xl leading-tight mb-3 ${headingColor}`}>
|
|
|
|
|
{heading}
|
|
|
|
|
</h2>
|
|
|
|
|
)}
|
|
|
|
|
@ -334,17 +323,18 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
|
|
|
|
|
{renderFields()}
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
{/* Submit — uses FDButton, onDark when section is navy or OS is in dark mode */}
|
|
|
|
|
<div className="mt-2">
|
|
|
|
|
<FDButton
|
|
|
|
|
as="button"
|
|
|
|
|
type="submit"
|
|
|
|
|
variant="primary"
|
|
|
|
|
onDark={dark}
|
|
|
|
|
disabled={status === 'sending'}
|
|
|
|
|
className={`self-start inline-flex items-center justify-center px-8 py-3 rounded-full font-joey-bold text-lg transition-colors disabled:opacity-60 mt-2 ${
|
|
|
|
|
dark
|
|
|
|
|
? 'bg-fd-yellow hover:bg-fd-yellow/90 text-fd-navy'
|
|
|
|
|
: 'bg-fd-yellow hover:bg-fd-yellow/90 text-fd-navy'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{status === 'sending' ? 'Skickar...' : submitText}
|
|
|
|
|
</button>
|
|
|
|
|
</FDButton>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{status === 'error' && (
|
|
|
|
|
<p className={`font-joey text-sm ${errorColor}`}>
|
|
|
|
|
@ -380,7 +370,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
className={`max-w-[720px] mx-auto p-8 md:p-12 rounded-[40px] md:rounded-[70px] ${
|
|
|
|
|
dark
|
|
|
|
|
? 'bg-white/5 border border-white/10 backdrop-blur-sm'
|
|
|
|
|
: 'bg-white shadow-lg border border-gray-100'
|
|
|
|
|
: 'bg-white shadow-lg border border-gray-100 dark:bg-white/5 dark:border-white/10 dark:backdrop-blur-sm'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{formContent}
|
|
|
|
|
@ -402,7 +392,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
<FDImage
|
|
|
|
|
media={media!}
|
|
|
|
|
size="medium"
|
|
|
|
|
className="w-[380px] h-auto max-h-[560px] object-cover rounded-[20px]"
|
|
|
|
|
className="w-[380px] h-auto max-h-[560px] object-cover rounded-[70px]"
|
|
|
|
|
sizes="380px"
|
|
|
|
|
fallbackAlt={heading || ''}
|
|
|
|
|
/>
|
|
|
|
|
@ -413,7 +403,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* ---- Layout: Standard (full width) ---- */
|
|
|
|
|
/* ---- Layout: Standard ---- */
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
|
|
|
|
|
|