refactor: codebase audit — remove orphaned blocks, unify design tokens

- Delete 5 orphaned blocks: ArchiveBlock, CallToAction, Content, Form, RelatedPosts
  - Remove Form import from RenderBlocks
  - Delete Media-BACKUP.txt
  - Extract fdCardRadius, fdCardRadiusSm to fdTheme — adopted across 15 blocks
  - Extract fdContainer to fdTheme — adopted across 29 blocks
  - Add fdSepiaOverlay, fd-sepia CSS token — replaces hardcoded #8B7D3C
  - Replace #153350 with via-fd-navy-700 in navyGradient
  - Replace #e2e8f0 with fd-gray-light in calculator blocks
This commit is contained in:
Jeffrey 2026-03-13 13:22:16 +01:00
parent ea230696f7
commit 15c3194eb6
61 changed files with 100 additions and 2232 deletions

View File

@ -341,6 +341,9 @@ html:not([data-theme]) {
--color-fd-mint-100: #D9FCE2; --color-fd-mint-100: #D9FCE2;
--color-fd-mint-50: #ECFDF0; --color-fd-mint-50: #ECFDF0;
/* Accent / overlay tones */
--color-fd-sepia: #8B7D3C; /* warm sepia overlay */
/* Neutral grays */ /* Neutral grays */
--color-fd-gray: #F0F0F0; /* section alt backgrounds */ --color-fd-gray: #F0F0F0; /* section alt backgrounds */
--color-fd-gray-warm: #F8F8F6; --color-fd-gray-warm: #F8F8F6;

View File

@ -1,66 +0,0 @@
// @ts-nocheck
import type { Post, ArchiveBlock as ArchiveBlockProps } from '@/payload-types'
import configPromise from '@payload-config'
import { getPayload } from 'payload'
import React from 'react'
import RichText from '@/components/RichText'
import { CollectionArchive } from '@/components/CollectionArchive'
export const ArchiveBlock: React.FC<
ArchiveBlockProps & {
id?: string
}
> = async (props) => {
const { id, categories, introContent, limit: limitFromProps, populateBy, selectedDocs } = props
const limit = limitFromProps || 3
let posts: Post[] = []
if (populateBy === 'collection') {
const payload = await getPayload({ config: configPromise })
const flattenedCategories = categories?.map((category) => {
if (typeof category === 'object') return category.id
else return category
})
const fetchedPosts = await payload.find({
collection: 'posts',
depth: 1,
limit,
...(flattenedCategories && flattenedCategories.length > 0
? {
where: {
categories: {
in: flattenedCategories,
},
},
}
: {}),
})
posts = fetchedPosts.docs
} else {
if (selectedDocs?.length) {
const filteredSelectedPosts = selectedDocs.map((post) => {
if (typeof post.value === 'object') return post.value
}) as Post[]
posts = filteredSelectedPosts
}
}
return (
<div className="my-16" id={`block-${id}`}>
{introContent && (
<div className="container mb-16">
<RichText className="ms-0 max-w-[48rem]" data={introContent} enableGutter={false} />
</div>
)}
<CollectionArchive posts={posts} />
</div>
)
}

View File

@ -1,94 +0,0 @@
import type { Block } from 'payload'
import {
FixedToolbarFeature,
HeadingFeature,
InlineToolbarFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
export const Archive: Block = {
slug: 'archive',
interfaceName: 'ArchiveBlock',
fields: [
{
name: 'introContent',
type: 'richText',
editor: lexicalEditor({
features: ({ rootFeatures }) => {
return [
...rootFeatures,
HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
FixedToolbarFeature(),
InlineToolbarFeature(),
]
},
}),
label: 'Intro Content',
},
{
name: 'populateBy',
type: 'select',
defaultValue: 'collection',
options: [
{
label: 'Collection',
value: 'collection',
},
{
label: 'Individual Selection',
value: 'selection',
},
],
},
{
name: 'relationTo',
type: 'select',
admin: {
condition: (_, siblingData) => siblingData.populateBy === 'collection',
},
defaultValue: 'posts',
label: 'Collections To Show',
options: [
{
label: 'Posts',
value: 'posts',
},
],
},
{
name: 'categories',
type: 'relationship',
admin: {
condition: (_, siblingData) => siblingData.populateBy === 'collection',
},
hasMany: true,
label: 'Categories To Show',
relationTo: 'categories',
},
{
name: 'limit',
type: 'number',
admin: {
condition: (_, siblingData) => siblingData.populateBy === 'collection',
step: 1,
},
defaultValue: 10,
label: 'Limit',
},
{
name: 'selectedDocs',
type: 'relationship',
admin: {
condition: (_, siblingData) => siblingData.populateBy === 'selection',
},
hasMany: true,
label: 'Selection',
relationTo: ['posts'],
},
],
labels: {
plural: 'Archives',
singular: 'Archive',
},
}

View File

@ -1,24 +0,0 @@
// @ts-nocheck
import React from 'react'
import type { CallToActionBlock as CTABlockProps } from '@/payload-types'
import RichText from '@/components/RichText'
import { CMSLink } from '@/components/Link'
export const CallToActionBlock: React.FC<CTABlockProps> = ({ links, richText }) => {
return (
<div className="container">
<div className="bg-card rounded border-border border p-4 flex flex-col gap-8 md:flex-row md:justify-between md:items-center">
<div className="max-w-[48rem] flex items-center">
{richText && <RichText className="mb-0" data={richText} enableGutter={false} />}
</div>
<div className="flex flex-col gap-8">
{(links || []).map(({ link }, i) => {
return <CMSLink key={i} size="lg" {...link} />
})}
</div>
</div>
</div>
)
}

View File

@ -1,42 +0,0 @@
import type { Block } from 'payload'
import {
FixedToolbarFeature,
HeadingFeature,
InlineToolbarFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
import { linkGroup } from '../../fields/linkGroup'
export const CallToAction: Block = {
slug: 'cta',
interfaceName: 'CallToActionBlock',
fields: [
{
name: 'richText',
type: 'richText',
editor: lexicalEditor({
features: ({ rootFeatures }) => {
return [
...rootFeatures,
HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
FixedToolbarFeature(),
InlineToolbarFeature(),
]
},
}),
label: false,
},
linkGroup({
appearances: ['default', 'outline'],
overrides: {
maxRows: 2,
},
}),
],
labels: {
plural: 'Calls to Action',
singular: 'Call to Action',
},
}

View File

@ -1,44 +0,0 @@
// @ts-nocheck
import { cn } from '@/utilities/ui'
import React from 'react'
import RichText from '@/components/RichText'
import type { ContentBlock as ContentBlockProps } from '@/payload-types'
import { CMSLink } from '../../components/Link'
export const ContentBlock: React.FC<ContentBlockProps> = (props) => {
const { columns } = props
const colsSpanClasses = {
full: '12',
half: '6',
oneThird: '4',
twoThirds: '8',
}
return (
<div className="container my-16">
<div className="grid grid-cols-4 lg:grid-cols-12 gap-y-8 gap-x-16">
{columns &&
columns.length > 0 &&
columns.map((col, index) => {
const { enableLink, link, richText, size } = col
return (
<div
className={cn(`col-span-4 lg:col-span-${colsSpanClasses[size!]}`, {
'md:col-span-2': size !== 'full',
})}
key={index}
>
{richText && <RichText data={richText} enableGutter={false} />}
{enableLink && <CMSLink {...link} />}
</div>
)
})}
</div>
</div>
)
}

View File

@ -1,79 +0,0 @@
import type { Block, Field } from 'payload'
import {
FixedToolbarFeature,
HeadingFeature,
InlineToolbarFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
import { link } from '@/fields/link'
const columnFields: Field[] = [
{
name: 'size',
type: 'select',
defaultValue: 'oneThird',
options: [
{
label: 'One Third',
value: 'oneThird',
},
{
label: 'Half',
value: 'half',
},
{
label: 'Two Thirds',
value: 'twoThirds',
},
{
label: 'Full',
value: 'full',
},
],
},
{
name: 'richText',
type: 'richText',
editor: lexicalEditor({
features: ({ rootFeatures }) => {
return [
...rootFeatures,
HeadingFeature({ enabledHeadingSizes: ['h2', 'h3', 'h4'] }),
FixedToolbarFeature(),
InlineToolbarFeature(),
]
},
}),
label: false,
},
{
name: 'enableLink',
type: 'checkbox',
},
link({
overrides: {
admin: {
condition: (_data, siblingData) => {
return Boolean(siblingData?.enableLink)
},
},
},
}),
]
export const Content: Block = {
slug: 'content',
interfaceName: 'ContentBlock',
fields: [
{
name: 'columns',
type: 'array',
admin: {
initCollapsed: true,
},
fields: columnFields,
},
],
}

View File

@ -2,6 +2,7 @@ import React from 'react'
import type { FDAlternateHeroBlock as Props, Media } from '@/payload-types' import type { FDAlternateHeroBlock as Props, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
import { fdContainer, fdSepiaOverlay} from '@/utilities/fdTheme'
const overlayMap: Record<string, string> = { const overlayMap: Record<string, string> = {
none: '', none: '',
@ -9,7 +10,7 @@ const overlayMap: Record<string, string> = {
navyMedium: 'bg-fd-navy/40', navyMedium: 'bg-fd-navy/40',
yellowLight: 'bg-fd-yellow/20', yellowLight: 'bg-fd-yellow/20',
yellowMedium:'bg-fd-yellow/40', yellowMedium:'bg-fd-yellow/40',
sepia: 'bg-[#8B7D3C]/30', sepia: fdSepiaOverlay,
blackLight: 'bg-black/20', blackLight: 'bg-black/20',
blackMedium: 'bg-black/40', blackMedium: 'bg-black/40',
} }
@ -50,7 +51,7 @@ export const FDAlternateHeroBlockComponent: React.FC<Props> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full pt-16 md:pt-20 lg:pt-[99px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full pt-16 md:pt-20 lg:pt-[99px] ${bgClass}`}>
{/* Centered content */} {/* Centered content */}
<div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center flex flex-col items-center gap-6 pb-12 md:pb-16"> <div className={`${fdContainer} text-center flex flex-col items-center gap-6 pb-12 md:pb-16`}>
<h1 className={`w-full max-w-[820px] font-joey-heavy text-fd-display ${titleClass}`}> <h1 className={`w-full max-w-[820px] font-joey-heavy text-fd-display ${titleClass}`}>
{heading} {heading}
</h1> </h1>

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { FDCardGridBlock as FDCardGridBlockProps } from '@/payload-types' import type { FDCardGridBlock as FDCardGridBlockProps } from '@/payload-types'
import { fdCardRadius as cardRadius, fdContainer} from '@/utilities/fdTheme'
const cardStyleMap: Record< const cardStyleMap: Record<
string, string,
@ -64,7 +65,6 @@ const styleClassMap: Record<string, string> = {
} }
/* Priority #5: Responsive radius constant */ /* Priority #5: Responsive radius constant */
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
export const FDCardGridBlockComponent: React.FC<FDCardGridBlockProps> = ({ export const FDCardGridBlockComponent: React.FC<FDCardGridBlockProps> = ({
layout = '1-1-1', layout = '1-1-1',
@ -79,7 +79,7 @@ export const FDCardGridBlockComponent: React.FC<FDCardGridBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<div className={`grid grid-cols-1 ${gridCols} gap-4 md:gap-6`}> <div className={`grid grid-cols-1 ${gridCols} gap-4 md:gap-6`}>
{cards?.map((card, index) => { {cards?.map((card, index) => {
const mode = card.displayMode || 'content' const mode = card.displayMode || 'content'
@ -117,7 +117,7 @@ export const FDCardGridBlockComponent: React.FC<FDCardGridBlockProps> = ({
> >
{card.heading && ( {card.heading && (
<h3 <h3
className={`font-joey-heavy text-fd-h2 mb-2 ${style.headingText}`} className={`font-joey-heavy text-fd-h2 mb-6 hyphens-auto break-words ${style.headingText}`}
> >
{card.heading} {card.heading}
</h3> </h3>

View File

@ -2,6 +2,7 @@
import React, { useEffect, useRef } from 'react' import React, { useEffect, useRef } from 'react'
import type { FDCodeEmbedBlock as FDCodeEmbedBlockProps } from '@/payload-types' import type { FDCodeEmbedBlock as FDCodeEmbedBlockProps } from '@/payload-types'
import { fdCardRadius as cardRadius } from '@/utilities/fdTheme'
const maxWidthClasses: Record<string, string> = { const maxWidthClasses: Record<string, string> = {
default: 'max-w-[1200px]', default: 'max-w-[1200px]',
@ -20,7 +21,6 @@ const bgClasses: Record<string, string> = {
} }
/* Priority #5: Responsive radius for embed card wrappers */ /* Priority #5: Responsive radius for embed card wrappers */
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
export const FDCodeEmbedBlockComponent: React.FC<FDCodeEmbedBlockProps> = ({ export const FDCodeEmbedBlockComponent: React.FC<FDCodeEmbedBlockProps> = ({
heading, heading,

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDContactBlock as FDContactBlockProps, Media } from '@/payload-types' import type { FDContactBlock as FDContactBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { fdContainer } from '@/utilities/fdTheme'
/* Smaller radius for compact contact method images — the one exception per Jeffrey */ /* Smaller radius for compact contact method images — the one exception per Jeffrey */
const imageRadius = 'rounded-[16px] md:rounded-[24px] lg:rounded-[30px]' const imageRadius = 'rounded-[16px] md:rounded-[24px] lg:rounded-[30px]'
@ -12,7 +13,7 @@ export const FDContactBlockComponent: React.FC<FDContactBlockProps> = ({
}) => { }) => {
return ( return (
<section id={anchorId || undefined} className="relative w-full bg-fd-navy py-16 md:py-20 lg:pt-[100px] lg:pb-[120px]"> <section id={anchorId || undefined} className="relative w-full bg-fd-navy py-16 md:py-20 lg:pt-[100px] lg:pb-[120px]">
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-center gap-8 lg:gap-10"> <div className={`${fdContainer} flex flex-col items-center gap-8 lg:gap-10`}>
<h2 className="w-full font-joey-heavy text-fd-h1 text-fd-yellow text-center"> <h2 className="w-full font-joey-heavy text-fd-h1 text-fd-yellow text-center">
{heading} {heading}
</h2> </h2>

View File

@ -5,6 +5,7 @@ import type { FDContactFormBlock as FDContactFormBlockProps } from '@/payload-ty
import type { Media } from '@/payload-types' import type { Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
import { fdCardRadius as cardRadius, fdContainer} from '@/utilities/fdTheme'
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Theme maps */ /* Theme maps */
@ -14,12 +15,11 @@ const sectionBgMap: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy', white: 'bg-white dark:bg-fd-navy',
gray: 'bg-fd-gray-light dark:bg-fd-navy', gray: 'bg-fd-gray-light dark:bg-fd-navy',
navy: 'bg-fd-navy', navy: 'bg-fd-navy',
navyGradient: 'bg-gradient-to-br from-fd-navy via-[#153350] to-fd-navy', navyGradient: 'bg-gradient-to-br from-fd-navy via-fd-navy-700 to-fd-navy',
} }
const isExplicitDark = (bg: string) => bg === 'navy' || bg === 'navyGradient' const isExplicitDark = (bg: string) => bg === 'navy' || bg === 'navyGradient'
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Component */ /* Component */
@ -280,7 +280,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
if (status === 'sent') { if (status === 'sent') {
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <section id={anchorId || undefined} 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={`${fdContainer} 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 dark:bg-fd-yellow/20'}`}> <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}> <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" /> <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
@ -301,7 +301,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
if (!form) { if (!form) {
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <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={`${fdContainer} text-center`}>
<p className={`font-joey ${mutedColor}`}>Inget formulär valt.</p> <p className={`font-joey ${mutedColor}`}>Inget formulär valt.</p>
</div> </div>
</section> </section>
@ -366,7 +366,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
if (isCard) { if (isCard) {
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <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"> <div className={fdContainer}>
<div <div
className={`max-w-[720px] mx-auto p-8 md:p-12 ${cardRadius} ${ className={`max-w-[720px] mx-auto p-8 md:p-12 ${cardRadius} ${
dark dark
@ -386,14 +386,14 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
if (hasSideImage) { if (hasSideImage) {
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <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"> <div className={fdContainer}>
<div className="flex flex-col lg:flex-row gap-10 lg:gap-16 items-start"> <div className="flex flex-col lg:flex-row gap-10 lg:gap-16 items-start">
<div className="flex-1">{formContent}</div> <div className="flex-1">{formContent}</div>
<div className="hidden lg:block flex-shrink-0"> <div className="hidden lg:block flex-shrink-0">
<FDImage <FDImage
media={media!} media={media!}
size="medium" size="medium"
className="w-[380px] h-auto max-h-[560px] object-cover rounded-[32px] md:rounded-[50px] lg:rounded-[70px]" className={`w-[380px] h-auto max-h-[560px] object-cover ${cardRadius}`}
sizes="380px" sizes="380px"
fallbackAlt={heading || ''} fallbackAlt={heading || ''}
/> />
@ -408,7 +408,7 @@ export const FDContactFormBlockComponent: React.FC<FDContactFormBlockProps> = ({
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <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"> <div className={fdContainer}>
<div className="max-w-[800px]">{formContent}</div> <div className="max-w-[800px]">{formContent}</div>
</div> </div>
</section> </section>

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { FDCtaBannerBlock as FDCtaBannerBlockProps } from '@/payload-types' import type { FDCtaBannerBlock as FDCtaBannerBlockProps } from '@/payload-types'
import { fdContainer } from '@/utilities/fdTheme'
const bgMap: Record<string, { const bgMap: Record<string, {
section: string; heading: string; sub: string section: string; heading: string; sub: string
@ -59,7 +60,7 @@ export const FDCtaBannerBlockComponent: React.FC<FDCtaBannerBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full ${sizing.py} ${theme.section}`}> <section id={anchorId || undefined} className={`w-full ${sizing.py} ${theme.section}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<div className={`flex flex-col gap-6 md:gap-8 items-center text-center min-[820px]:${isCenter ? 'items-center text-center' : 'items-start text-left'} max-w-[800px] ${isCenter ? 'mx-auto' : ''}`}> <div className={`flex flex-col gap-6 md:gap-8 items-center text-center min-[820px]:${isCenter ? 'items-center text-center' : 'items-start text-left'} max-w-[800px] ${isCenter ? 'mx-auto' : ''}`}>
<div className={`flex flex-col gap-3 items-center min-[820px]:${isCenter ? 'items-center' : 'items-start'}`}> <div className={`flex flex-col gap-3 items-center min-[820px]:${isCenter ? 'items-center' : 'items-start'}`}>

View File

@ -4,8 +4,7 @@ import type { Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
/* Priority #5: Responsive radius */ import { fdCardRadius as imageRadius, fdContainer, fdSepiaOverlay} from '@/utilities/fdTheme'
const imageRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
/* Color overlay — same map as FDHeaderTextImageBlock */ /* Color overlay — same map as FDHeaderTextImageBlock */
const overlayMap: Record<string, string> = { const overlayMap: Record<string, string> = {
@ -14,7 +13,7 @@ const overlayMap: Record<string, string> = {
navyMedium: 'bg-fd-navy/40', navyMedium: 'bg-fd-navy/40',
yellowLight: 'bg-fd-yellow/20', yellowLight: 'bg-fd-yellow/20',
yellowMedium:'bg-fd-yellow/40', yellowMedium:'bg-fd-yellow/40',
sepia: 'bg-[#8B7D3C]/30', sepia: fdSepiaOverlay,
blackLight: 'bg-black/20', blackLight: 'bg-black/20',
blackMedium: 'bg-black/40', blackMedium: 'bg-black/40',
} }
@ -56,7 +55,7 @@ export const FDCtaSideImageBlockComponent: React.FC<FDCtaSideImageBlockProps> =
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col min-[820px]:flex-row min-[820px]:items-center gap-6 min-[820px]:gap-16"> <div className={`${fdContainer} flex flex-col min-[820px]:flex-row min-[820px]:items-center gap-6 min-[820px]:gap-16`}>
{/* Heading — always first on mobile */} {/* Heading — always first on mobile */}
<h2 className={`w-full font-joey-heavy text-fd-h1 leading-tight order-1 min-[820px]:hidden ${headingClass}`}> <h2 className={`w-full font-joey-heavy text-fd-h1 leading-tight order-1 min-[820px]:hidden ${headingClass}`}>

View File

@ -1,6 +1,7 @@
'use client' 'use client'
import React, { useState, useEffect, useCallback } from 'react' import React, { useState, useEffect, useCallback } from 'react'
import type { FDDataTableBlock as Props, Media } from '@/payload-types' import type { FDDataTableBlock as Props, Media } from '@/payload-types'
import { fdContainer } from '@/utilities/fdTheme'
type TableData = { type TableData = {
headers: string[] headers: string[]
@ -168,7 +169,7 @@ export const FDDataTableBlockComponent: React.FC<Props> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{(heading || description) && ( {(heading || description) && (
<div className="mb-10 md:mb-12"> <div className="mb-10 md:mb-12">

View File

@ -2,6 +2,7 @@
import React, { useState, useId } from 'react' import React, { useState, useId } from 'react'
import type { FDFaqBlock as FDFaqBlockProps } from '@/payload-types' import type { FDFaqBlock as FDFaqBlockProps } from '@/payload-types'
import RichText from '@/components/RichText' import RichText from '@/components/RichText'
import { fdContainer } from '@/utilities/fdTheme'
export const FDFaqBlockComponent: React.FC<FDFaqBlockProps> = ({ export const FDFaqBlockComponent: React.FC<FDFaqBlockProps> = ({
heading, heading,
@ -39,7 +40,7 @@ export const FDFaqBlockComponent: React.FC<FDFaqBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[130px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[130px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-start gap-6"> <div className={`${fdContainer} flex flex-col items-start gap-6`}>
<h2 className={`w-full max-w-[550px] font-joey-heavy text-fd-h1 ${headingColor}`}> <h2 className={`w-full max-w-[550px] font-joey-heavy text-fd-h1 ${headingColor}`}>
{heading} {heading}
</h2> </h2>

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDFeatureAnnouncementBlock as FDFeatureAnnouncementBlockProps } from '@/payload-types' import type { FDFeatureAnnouncementBlock as FDFeatureAnnouncementBlockProps } from '@/payload-types'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
import { fdContainer } from '@/utilities/fdTheme'
export const FDFeatureAnnouncementBlockComponent: React.FC<FDFeatureAnnouncementBlockProps> = ({ export const FDFeatureAnnouncementBlockComponent: React.FC<FDFeatureAnnouncementBlockProps> = ({
heading, heading,
@ -33,7 +34,7 @@ export const FDFeatureAnnouncementBlockComponent: React.FC<FDFeatureAnnouncement
return ( return (
<section id={anchorId || undefined} className={`w-full py-20 md:py-28 lg:py-[173px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full py-20 md:py-28 lg:py-[173px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col items-center gap-8"> <div className={`${fdContainer} flex flex-col items-center gap-8`}>
<h2 <h2
className={`w-full max-w-[696px] font-joey-bold text-fd-h1 text-center leading-tight ${headingColor}`} className={`w-full max-w-[696px] font-joey-bold text-fd-h1 text-center leading-tight ${headingColor}`}
> >

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDHeaderTextImageBlock as FDHeaderTextImageBlockProps, Media } from '@/payload-types' import type { FDHeaderTextImageBlock as FDHeaderTextImageBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { fdCardRadius, fdCardRadiusSm, fdContainer, fdSepiaOverlay} from '@/utilities/fdTheme'
const bgMap: Record<string, string> = { const bgMap: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy', white: 'bg-white dark:bg-fd-navy',
@ -26,7 +27,7 @@ const overlayMap: Record<string, string> = {
navyMedium: 'bg-fd-navy/40', navyMedium: 'bg-fd-navy/40',
yellowLight: 'bg-fd-yellow/20', yellowLight: 'bg-fd-yellow/20',
yellowMedium:'bg-fd-yellow/40', yellowMedium:'bg-fd-yellow/40',
sepia: 'bg-[#8B7D3C]/30', sepia: fdSepiaOverlay,
blackLight: 'bg-black/20', blackLight: 'bg-black/20',
blackMedium: 'bg-black/40', blackMedium: 'bg-black/40',
} }
@ -34,8 +35,8 @@ const overlayMap: Record<string, string> = {
/* Updated: responsive radius matching the standard system */ /* Updated: responsive radius matching the standard system */
const roundedMap: Record<string, string> = { const roundedMap: Record<string, string> = {
none: '', none: '',
medium: 'rounded-[20px] md:rounded-[32px] lg:rounded-[40px]', medium: fdCardRadiusSm,
large: 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]', large: fdCardRadius,
} }
export const FDHeaderTextImageBlockComponent: React.FC<FDHeaderTextImageBlockProps> = ({ export const FDHeaderTextImageBlockComponent: React.FC<FDHeaderTextImageBlockProps> = ({
@ -59,7 +60,7 @@ export const FDHeaderTextImageBlockComponent: React.FC<FDHeaderTextImageBlockPro
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col gap-8 md:gap-10"> <div className={`${fdContainer} flex flex-col gap-8 md:gap-10`}>
{(heading || body) && ( {(heading || body) && (
<div className={`flex flex-col gap-4 md:gap-6 ${align} ${textAlign === 'center' ? 'max-w-[900px] mx-auto' : ''}`}> <div className={`flex flex-col gap-4 md:gap-6 ${align} ${textAlign === 'center' ? 'max-w-[900px] mx-auto' : ''}`}>
{heading && ( {heading && (

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDIconBarBlock as FDIconBarBlockProps, Media } from '@/payload-types' import type { FDIconBarBlock as FDIconBarBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { fdContainer } from '@/utilities/fdTheme'
const bgMap: Record<string, string> = { const bgMap: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy', white: 'bg-white dark:bg-fd-navy',
@ -42,7 +43,7 @@ export const FDIconBarBlockComponent: React.FC<FDIconBarBlockProps> = ({
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{heading && ( {heading && (
<h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${headClr}`}> <h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${headClr}`}>
{heading} {heading}

View File

@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import type { FDLinkCardsBlock as Props, Media } from '@/payload-types' import type { FDLinkCardsBlock as Props, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { sectionBg, isExplicitDark, headingColor, bodyColor, fdCardRadius } from '@/utilities/fdTheme' import { sectionBg, isExplicitDark, headingColor, bodyColor, fdCardRadius, fdContainer} from '@/utilities/fdTheme'
const cardStyleMap: Record<string, { bg: string; title: string; link: string; border: string }> = { const cardStyleMap: Record<string, { bg: string; title: string; link: string; border: string }> = {
outlined: { outlined: {
@ -72,7 +72,7 @@ export const FDLinkCardsBlockComponent: React.FC<Props> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{/* Centered header */} {/* Centered header */}
<div className="text-center mb-12 md:mb-16 flex flex-col items-center gap-4"> <div className="text-center mb-12 md:mb-16 flex flex-col items-center gap-4">

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { FDLocationsGridBlock as Props, Media } from '@/payload-types' import type { FDLocationsGridBlock as Props, Media } from '@/payload-types'
import { fdCardRadius, fdContainer} from '@/utilities/fdTheme'
export const FDLocationsGridBlockComponent: React.FC<Props> = ({ export const FDLocationsGridBlockComponent: React.FC<Props> = ({
heading, heading,
@ -33,7 +34,7 @@ export const FDLocationsGridBlockComponent: React.FC<Props> = ({
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}> <section className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{(heading || description || ctaText) && ( {(heading || description || ctaText) && (
<div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-6 mb-12 md:mb-16"> <div className="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-6 mb-12 md:mb-16">
@ -59,7 +60,7 @@ export const FDLocationsGridBlockComponent: React.FC<Props> = ({
{(cards ?? []).map((card, i) => { {(cards ?? []).map((card, i) => {
const media = card.image as Media | undefined const media = card.image as Media | undefined
const isLink = Boolean(card.link) const isLink = Boolean(card.link)
const className = `group relative overflow-hidden rounded-[32px] md:rounded-[50px] lg:rounded-[70px] aspect-[16/9] sm:aspect-[4/3] block ${isLink ? 'cursor-pointer' : ''}` const className = `group relative overflow-hidden ${fdCardRadius} aspect-[16/9] sm:aspect-[4/3] block ${isLink ? 'cursor-pointer' : ''}`
const inner = ( const inner = (
<> <>

View File

@ -2,6 +2,7 @@
import React, { useState, useCallback } from 'react' import React, { useState, useCallback } from 'react'
import type { FDNewsletterBlock as FDNewsletterBlockProps } from '@/payload-types' import type { FDNewsletterBlock as FDNewsletterBlockProps } from '@/payload-types'
import { fdCardRadius as cardRadius } from '@/utilities/fdTheme'
// Navy is always dark. White/gray adapt to OS dark mode. // Navy is always dark. White/gray adapt to OS dark mode.
const bgClasses: Record<string, string> = { const bgClasses: Record<string, string> = {
@ -12,7 +13,6 @@ const bgClasses: Record<string, string> = {
} }
/* Priority #5: Responsive card radius */ /* Priority #5: Responsive card radius */
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
export const FDNewsletterBlockComponent: React.FC<FDNewsletterBlockProps> = ({ export const FDNewsletterBlockComponent: React.FC<FDNewsletterBlockProps> = ({
heading = 'Håll dig uppdaterad', heading = 'Håll dig uppdaterad',

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { FDPartnersLogosBlock as FDPartnersLogosBlockProps, Media } from '@/payload-types' import type { FDPartnersLogosBlock as FDPartnersLogosBlockProps, Media } from '@/payload-types'
import { fdContainer } from '@/utilities/fdTheme'
export const FDPartnersLogosBlockComponent: React.FC<FDPartnersLogosBlockProps> = ({ export const FDPartnersLogosBlockComponent: React.FC<FDPartnersLogosBlockProps> = ({
heading, heading,
@ -24,7 +25,7 @@ export const FDPartnersLogosBlockComponent: React.FC<FDPartnersLogosBlockProps>
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}> <section className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{heading && ( {heading && (
<h2 className={`font-joey-heavy text-fd-h2 text-center mb-10 md:mb-14 ${titleClass}`}> <h2 className={`font-joey-heavy text-fd-h2 text-center mb-10 md:mb-14 ${titleClass}`}>
{heading} {heading}

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDPricingCardBlock as FDPricingCardBlockProps } from '@/payload-types' import type { FDPricingCardBlock as FDPricingCardBlockProps } from '@/payload-types'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
import { fdCardRadius as cardRadius, fdContainer} from '@/utilities/fdTheme'
const sectionBgMap: Record<string, string> = { const sectionBgMap: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy', white: 'bg-white dark:bg-fd-navy',
@ -69,7 +70,6 @@ const gridColsMap: Record<number, string> = {
3: 'min-[820px]:grid-cols-3', 3: 'min-[820px]:grid-cols-3',
} }
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
export const FDPricingCardBlockComponent: React.FC<FDPricingCardBlockProps> = ({ export const FDPricingCardBlockComponent: React.FC<FDPricingCardBlockProps> = ({
sectionTitle, sectionTitle,
@ -90,7 +90,7 @@ export const FDPricingCardBlockComponent: React.FC<FDPricingCardBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{sectionTitle && ( {sectionTitle && (
<h2 className={`font-joey-heavy text-fd-h1 text-center mb-10 md:mb-14 ${sectionTitleColor}`}> <h2 className={`font-joey-heavy text-fd-h1 text-center mb-10 md:mb-14 ${sectionTitleColor}`}>
{sectionTitle} {sectionTitle}

View File

@ -2,7 +2,7 @@
import React, { useState, useEffect, useCallback, useRef } from 'react' import React, { useState, useEffect, useCallback, useRef } from 'react'
import type { FDQuizBlock as Props } from '@/payload-types' import type { FDQuizBlock as Props } from '@/payload-types'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
import { sectionBg, isExplicitDark, headingColor, bodyColor, fdCardRadius } from '@/utilities/fdTheme' import { sectionBg, isExplicitDark, headingColor, bodyColor, fdCardRadius, fdContainer} from '@/utilities/fdTheme'
/* ── Types ── */ /* ── Types ── */
type QuizState = 'idle' | 'active' | 'result' type QuizState = 'idle' | 'active' | 'result'
@ -156,7 +156,7 @@ export const FDQuizBlockComponent: React.FC<Props> = ({
if (state === 'idle') { if (state === 'idle') {
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center flex flex-col items-center gap-6"> <div className={`${fdContainer} text-center flex flex-col items-center gap-6`}>
<h2 className={`font-joey-heavy text-fd-h1 max-w-[700px] ${hClr}`}> <h2 className={`font-joey-heavy text-fd-h1 max-w-[700px] ${hClr}`}>
{heading} {heading}
</h2> </h2>
@ -185,7 +185,7 @@ export const FDQuizBlockComponent: React.FC<Props> = ({
const winner = calculateResult() const winner = calculateResult()
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex justify-center"> <div className={`${fdContainer} flex justify-center`}>
<div <div
ref={panelRef} ref={panelRef}
className={`${panelBg} rounded-2xl md:rounded-3xl p-8 md:p-10 w-full max-w-[520px] ${transitionCls}`} className={`${panelBg} rounded-2xl md:rounded-3xl p-8 md:p-10 w-full max-w-[520px] ${transitionCls}`}
@ -253,7 +253,7 @@ export const FDQuizBlockComponent: React.FC<Props> = ({
/* ── Render: Active (question step) ── */ /* ── Render: Active (question step) ── */
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex justify-center"> <div className={`${fdContainer} flex justify-center`}>
<div <div
ref={panelRef} ref={panelRef}
className={`${panelBg} rounded-2xl md:rounded-3xl p-8 md:p-10 w-full max-w-[520px]`} className={`${panelBg} rounded-2xl md:rounded-3xl p-8 md:p-10 w-full max-w-[520px]`}

View File

@ -2,10 +2,10 @@
import React, { useState, useMemo } from 'react' import React, { useState, useMemo } from 'react'
import type { FDServiceCalculatorBlock as Props } from '@/payload-types' import type { FDServiceCalculatorBlock as Props } from '@/payload-types'
import { fdCardRadius as cardRadius } from '@/utilities/fdTheme'
const formatKr = (n: number) => Math.round(n).toLocaleString('sv-SE') + ' kr' const formatKr = (n: number) => Math.round(n).toLocaleString('sv-SE') + ' kr'
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
/* ── Toggle switch ─────────────────────────────────────────────────────── */ /* ── Toggle switch ─────────────────────────────────────────────────────── */
function Toggle({ active, onToggle, label }: { active: boolean; onToggle: () => void; label: string }) { function Toggle({ active, onToggle, label }: { active: boolean; onToggle: () => void; label: string }) {
@ -14,7 +14,7 @@ function Toggle({ active, onToggle, label }: { active: boolean; onToggle: () =>
type="button" type="button"
onClick={onToggle} onClick={onToggle}
style={{ display: 'block', width: '3.5rem', height: '2rem', flexShrink: 0 }} style={{ display: 'block', width: '3.5rem', height: '2rem', flexShrink: 0 }}
className={`relative rounded-full transition-all duration-300 ${active ? 'bg-fd-yellow' : 'bg-[#e2e8f0] dark:bg-white/20'}`} className={`relative rounded-full transition-all duration-300 ${active ? 'bg-fd-yellow' : 'bg-fd-gray-light dark:bg-white/20'}`}
role="switch" role="switch"
aria-checked={active} aria-checked={active}
aria-label={label} aria-label={label}
@ -142,7 +142,7 @@ export const FDServiceCalculatorBlockComponent: React.FC<Props> = ({
const cardClass = isDark const cardClass = isDark
? `bg-white/5 border-[5px] border-white/10 ${cardRadius}` ? `bg-white/5 border-[5px] border-white/10 ${cardRadius}`
: `bg-white border-[5px] border-[#e2e8f0] ${cardRadius} dark:bg-white/5 dark:border-white/10` : `bg-white border-[5px] border-fd-gray-light ${cardRadius} dark:bg-white/5 dark:border-white/10`
const headingColor = isDark ? 'text-white' : 'text-fd-navy dark:text-white' const headingColor = isDark ? 'text-white' : 'text-fd-navy dark:text-white'
const descColor = isDark ? 'text-white/60' : 'text-fd-navy/60 dark:text-white/60' const descColor = isDark ? 'text-white/60' : 'text-fd-navy/60 dark:text-white/60'

View File

@ -1,9 +1,7 @@
'use client' 'use client'
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useEffect } from 'react'
import type { FDServiceChooserBlock as Props } from '@/payload-types' import type { FDServiceChooserBlock as Props } from '@/payload-types'
import { fdCardRadius as cardRadius, fdContainer} from '@/utilities/fdTheme'
/* Consistent radius system — same as CardGrid, PricingCard, etc. */
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
export const FDServiceChooserBlockComponent: React.FC<Props> = ({ export const FDServiceChooserBlockComponent: React.FC<Props> = ({
heading, heading,
@ -53,7 +51,7 @@ export const FDServiceChooserBlockComponent: React.FC<Props> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<div className="text-center mb-10 md:mb-12"> <div className="text-center mb-10 md:mb-12">
{heading && ( {heading && (

View File

@ -9,8 +9,7 @@ const columnClasses: Record<string, string> = {
'4': 'grid-cols-2 min-[820px]:grid-cols-4', '4': 'grid-cols-2 min-[820px]:grid-cols-4',
} }
/* Priority #5: Responsive radius for service images */ import { fdCardRadius as imageRadius, fdContainer} from '@/utilities/fdTheme'
const imageRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
export const FDServicesGridBlockComponent: React.FC<FDServicesGridBlockProps> = ({ export const FDServicesGridBlockComponent: React.FC<FDServicesGridBlockProps> = ({
heading, heading,
@ -20,7 +19,7 @@ export const FDServicesGridBlockComponent: React.FC<FDServicesGridBlockProps> =
}) => { }) => {
return ( return (
<section id={anchorId || undefined} className="relative w-full bg-white dark:bg-fd-navy py-16 md:py-20 lg:py-[99px]"> <section id={anchorId || undefined} className="relative w-full bg-white dark:bg-fd-navy py-16 md:py-20 lg:py-[99px]">
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<h2 className="font-joey-heavy text-fd-h1 text-fd-navy dark:text-fd-yellow mb-8 lg:mb-12"> <h2 className="font-joey-heavy text-fd-h1 text-fd-navy dark:text-fd-yellow mb-8 lg:mb-12">
{heading} {heading}
</h2> </h2>

View File

@ -3,8 +3,7 @@ import type { FDSpecCardsBlock as Props } from '@/payload-types'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
import { import {
sectionBg, isExplicitDark, headingColor, bodyColor, sectionBg, isExplicitDark, headingColor, bodyColor,
bodySubduedColor, fdCardRadius, fdCardRadiusSm, bodySubduedColor, fdCardRadius, fdCardRadiusSm, fdContainer} from '@/utilities/fdTheme'
} from '@/utilities/fdTheme'
const cardStyleMap: Record<string, { const cardStyleMap: Record<string, {
bg: string; border: string; title: string; body: string bg: string; border: string; title: string; body: string
@ -163,7 +162,7 @@ export const FDSpecCardsBlockComponent: React.FC<Props> = ({
/* Full-width layout — heading on top, cards below */ /* Full-width layout — heading on top, cards below */
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<div className="mb-10 md:mb-14"> <div className="mb-10 md:mb-14">
{renderText()} {renderText()}
</div> </div>
@ -176,7 +175,7 @@ export const FDSpecCardsBlockComponent: React.FC<Props> = ({
/* Side-by-side layout */ /* Side-by-side layout */
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<div className={`flex flex-col min-[820px]:flex-row gap-10 min-[820px]:gap-14 items-start ${isReversed ? 'min-[820px]:flex-row-reverse' : ''}`}> <div className={`flex flex-col min-[820px]:flex-row gap-10 min-[820px]:gap-14 items-start ${isReversed ? 'min-[820px]:flex-row-reverse' : ''}`}>
<div className="flex-1 min-w-0 min-[820px]:max-w-[380px]"> <div className="flex-1 min-w-0 min-[820px]:max-w-[380px]">
{renderText()} {renderText()}

View File

@ -1,6 +1,7 @@
'use client' 'use client'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import type { FDStatisticsBlock as Props } from '@/payload-types' import type { FDStatisticsBlock as Props } from '@/payload-types'
import { fdContainer } from '@/utilities/fdTheme'
export const FDStatisticsBlockComponent: React.FC<Props> = ({ export const FDStatisticsBlockComponent: React.FC<Props> = ({
heading, heading,
@ -63,7 +64,7 @@ export const FDStatisticsBlockComponent: React.FC<Props> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`} ref={ref}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bgClass}`} ref={ref}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 text-center"> <div className={`${fdContainer} text-center`}>
{heading && ( {heading && (
<h2 className={`font-joey-heavy text-fd-h1 mb-12 md:mb-16 ${titleClass}`}> <h2 className={`font-joey-heavy text-fd-h1 mb-12 md:mb-16 ${titleClass}`}>
{heading} {heading}

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { FDTagsBlock as FDTagsBlockProps } from '@/payload-types' import type { FDTagsBlock as FDTagsBlockProps } from '@/payload-types'
import { fdContainer } from '@/utilities/fdTheme'
const tagStyleMap: Record<string, { bg: string; text: string; border: string }> = { const tagStyleMap: Record<string, { bg: string; text: string; border: string }> = {
navy: { bg: 'bg-fd-navy', text: 'text-fd-yellow', border: '' }, navy: { bg: 'bg-fd-navy', text: 'text-fd-yellow', border: '' },
@ -42,7 +43,7 @@ export const FDTagsBlockComponent: React.FC<FDTagsBlockProps> = ({
return ( return (
<section className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <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"> <div className={fdContainer}>
{heading && ( {heading && (
<h2 <h2
className={`font-joey-heavy text-fd-h2 mb-6 md:mb-8 ${headingColor} ${ className={`font-joey-heavy text-fd-h2 mb-6 md:mb-8 ${headingColor} ${

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDTeamBlock as FDTeamBlockProps, Media } from '@/payload-types' import type { FDTeamBlock as FDTeamBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { fdCardRadius as cardRadius, fdContainer} from '@/utilities/fdTheme'
const sectionBgMap: Record<string, string> = { const sectionBgMap: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy', white: 'bg-white dark:bg-fd-navy',
@ -39,7 +40,6 @@ const colsMap: Record<string, string> = {
} }
/* Priority #5: Responsive radius for team member cards */ /* Priority #5: Responsive radius for team member cards */
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
export const FDTeamBlockComponent: React.FC<FDTeamBlockProps> = ({ export const FDTeamBlockComponent: React.FC<FDTeamBlockProps> = ({
heading, heading,
@ -57,7 +57,7 @@ export const FDTeamBlockComponent: React.FC<FDTeamBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{(heading || subheading) && ( {(heading || subheading) && (
<div className="flex flex-col gap-3 mb-10 md:mb-14"> <div className="flex flex-col gap-3 mb-10 md:mb-14">

View File

@ -1,5 +1,6 @@
import React from 'react' import React from 'react'
import type { FDTechPropertiesBlock as FDTechPropertiesBlockProps } from '@/payload-types' import type { FDTechPropertiesBlock as FDTechPropertiesBlockProps } from '@/payload-types'
import { fdContainer } from '@/utilities/fdTheme'
const bgMap: Record<string, string> = { const bgMap: Record<string, string> = {
navy: 'bg-fd-navy', navy: 'bg-fd-navy',
@ -37,7 +38,7 @@ export const FDTechPropertiesBlockComponent: React.FC<FDTechPropertiesBlockProps
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<div className={`grid ${gridCols} gap-8 md:gap-12`}> <div className={`grid ${gridCols} gap-8 md:gap-12`}>
{properties?.map((prop, index) => ( {properties?.map((prop, index) => (
<div key={index} className="flex flex-col gap-1 md:gap-2"> <div key={index} className="flex flex-col gap-1 md:gap-2">

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDTestimonialBlock as FDTestimonialBlockProps, Media } from '@/payload-types' import type { FDTestimonialBlock as FDTestimonialBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { fdCardRadius as cardRadius, fdContainer} from '@/utilities/fdTheme'
const bgMap: Record<string, { const bgMap: Record<string, {
section: string; card: string section: string; card: string
@ -32,7 +33,6 @@ const bgMap: Record<string, {
}, },
} }
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
const Avatar: React.FC<{ media: Media | undefined; name: string; size: number }> = ({ media, name, size }) => { const Avatar: React.FC<{ media: Media | undefined; name: string; size: number }> = ({ media, name, size }) => {
if (!media?.url) return null if (!media?.url) return null
@ -64,7 +64,7 @@ export const FDTestimonialBlockComponent: React.FC<FDTestimonialBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${theme.section}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${theme.section}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{heading && ( {heading && (
<h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${theme.accent}`}> <h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${theme.accent}`}>

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDTextBlock as FDTextBlockProps } from '@/payload-types' import type { FDTextBlock as FDTextBlockProps } from '@/payload-types'
import RichText from '@/components/RichText' import RichText from '@/components/RichText'
import { fdContainer } from '@/utilities/fdTheme'
const bgMap: Record<string, string> = { const bgMap: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy', white: 'bg-white dark:bg-fd-navy',
@ -54,7 +55,7 @@ export const FDTextBlockComponent: React.FC<FDTextBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<div className={`${width} ${containerAlign} ${align} flex flex-col gap-4 md:gap-6`}> <div className={`${width} ${containerAlign} ${align} flex flex-col gap-4 md:gap-6`}>
{heading && ( {heading && (
<h1 className={`font-joey-heavy text-fd-display ${colors.h1}`}>{heading}</h1> <h1 className={`font-joey-heavy text-fd-display ${colors.h1}`}>{heading}</h1>

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDUspChecklistBlock as FDUspChecklistBlockProps, Media } from '@/payload-types' import type { FDUspChecklistBlock as FDUspChecklistBlockProps, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { fdContainer, fdSepiaOverlay} from '@/utilities/fdTheme'
const bgMap: Record<string, string> = { const bgMap: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy', white: 'bg-white dark:bg-fd-navy',
@ -24,7 +25,7 @@ const overlayMap: Record<string, string> = {
navyMedium: 'bg-fd-navy/40', navyMedium: 'bg-fd-navy/40',
yellowLight: 'bg-fd-yellow/20', yellowLight: 'bg-fd-yellow/20',
yellowMedium:'bg-fd-yellow/40', yellowMedium:'bg-fd-yellow/40',
sepia: 'bg-[#8B7D3C]/30', sepia: fdSepiaOverlay,
blackLight: 'bg-black/20', blackLight: 'bg-black/20',
blackMedium: 'bg-black/40', blackMedium: 'bg-black/40',
} }
@ -92,7 +93,7 @@ export const FDUspChecklistBlockComponent: React.FC<FDUspChecklistBlockProps> =
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col min-[820px]:flex-row items-center gap-10 min-[820px]:gap-16"> <div className={`${fdContainer} flex flex-col min-[820px]:flex-row items-center gap-10 min-[820px]:gap-16`}>
{imagePosition === 'left' ? <>{imageContent}{textContent}</> : <>{textContent}{imageContent}</>} {imagePosition === 'left' ? <>{imageContent}{textContent}</> : <>{textContent}{imageContent}</>}
</div> </div>
</section> </section>

View File

@ -1,6 +1,7 @@
import React from 'react' import React from 'react'
import type { FDUspTableBlock as FDUspTableBlockProps } from '@/payload-types' import type { FDUspTableBlock as FDUspTableBlockProps } from '@/payload-types'
import RichText from '@/components/RichText' import RichText from '@/components/RichText'
import { fdContainer } from '@/utilities/fdTheme'
const bgMap: Record<string, string> = { const bgMap: Record<string, string> = {
white: 'bg-white dark:bg-fd-navy', white: 'bg-white dark:bg-fd-navy',
@ -62,7 +63,7 @@ export const FDUspTableBlockComponent: React.FC<FDUspTableBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${bg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
{heading && ( {heading && (
<h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${headClr}`}>{heading}</h2> <h2 className={`font-joey-heavy text-fd-h1 mb-10 md:mb-14 ${headClr}`}>{heading}</h2>
)} )}

View File

@ -41,8 +41,7 @@ function extractVimeoId(url: string): string | null {
return match ? match[1] : null return match ? match[1] : null
} }
/* Priority #5: Responsive video radius */ import { fdCardRadius as videoRadius } from '@/utilities/fdTheme'
const videoRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
export const FDVideoBlockComponent: React.FC<FDVideoBlockProps> = ({ export const FDVideoBlockComponent: React.FC<FDVideoBlockProps> = ({
heading, heading,

View File

@ -3,6 +3,7 @@
import React, { useRef, useEffect, useState } from 'react' import React, { useRef, useEffect, useState } from 'react'
import type { FDVideoHeroBlock as FDVideoHeroBlockProps, Media } from '@/payload-types' import type { FDVideoHeroBlock as FDVideoHeroBlockProps, Media } from '@/payload-types'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
import { fdContainer } from '@/utilities/fdTheme'
export const FDVideoHeroBlockComponent: React.FC<FDVideoHeroBlockProps> = (props) => { export const FDVideoHeroBlockComponent: React.FC<FDVideoHeroBlockProps> = (props) => {
const { const {
@ -116,7 +117,7 @@ export const FDVideoHeroBlockComponent: React.FC<FDVideoHeroBlockProps> = (props
{/* Content */} {/* Content */}
{(hasText || ctaText || secondaryCtaText) && ( {(hasText || ctaText || secondaryCtaText) && (
<div className="relative z-10 w-full pb-16 md:pb-20 lg:pb-[99px] pt-32 md:pt-40"> <div className="relative z-10 w-full pb-16 md:pb-20 lg:pb-[99px] pt-32 md:pt-40">
<div className={`max-w-[1200px] mx-auto px-6 md:px-8 flex flex-col ${alignClass}`}> <div className={`${fdContainer} flex flex-col ${alignClass}`}>
{/* Text container — constrain width */} {/* Text container — constrain width */}
<div className={`max-w-[640px] ${textAlign === 'center' ? 'mx-auto' : ''}`}> <div className={`max-w-[640px] ${textAlign === 'center' ? 'mx-auto' : ''}`}>
{heading && ( {heading && (

View File

@ -2,6 +2,7 @@
import React, { useState, useMemo } from 'react' import React, { useState, useMemo } from 'react'
import type { FDVpsCalculatorBlock as FDVpsCalculatorBlockProps } from '@/payload-types' import type { FDVpsCalculatorBlock as FDVpsCalculatorBlockProps } from '@/payload-types'
import { fdCardRadius } from '@/utilities/fdTheme'
const DEFAULT_PRICING = { const DEFAULT_PRICING = {
windows: 250, windows: 250,
@ -20,7 +21,7 @@ function Toggle({ active, onToggle }: { active: boolean; onToggle: () => void })
type="button" type="button"
onClick={onToggle} onClick={onToggle}
style={{ display: 'block', width: '3.5rem', height: '2rem', flexShrink: 0 }} style={{ display: 'block', width: '3.5rem', height: '2rem', flexShrink: 0 }}
className={`relative rounded-full transition-all duration-300 ${active ? 'bg-fd-yellow' : 'bg-[#e2e8f0] dark:bg-white/20'}`} className={`relative rounded-full transition-all duration-300 ${active ? 'bg-fd-yellow' : 'bg-fd-gray-light dark:bg-white/20'}`}
role="switch" role="switch"
aria-checked={active} aria-checked={active}
> >
@ -121,8 +122,8 @@ export const FDVpsCalculatorBlockComponent: React.FC<FDVpsCalculatorBlockProps>
// Use Tailwind classes for card styling instead of inline styles // Use Tailwind classes for card styling instead of inline styles
const cardClass = isDark const cardClass = isDark
? 'bg-white/5 border-[5px] border-white/10 rounded-[32px] md:rounded-[50px] lg:rounded-[70px]' ? `bg-white/5 border-[5px] border-white/10 ${fdCardRadius}`
: 'bg-white border-[5px] border-[#e2e8f0] rounded-[32px] md:rounded-[50px] lg:rounded-[70px] dark:bg-white/5 dark:border-white/10' : `bg-white border-[5px] border-fd-gray-light ${fdCardRadius} dark:bg-white/5 dark:border-white/10`
const headingColor = isDark ? 'text-white' : 'text-fd-navy dark:text-white' const headingColor = isDark ? 'text-white' : 'text-fd-navy dark:text-white'
const descColor = isDark ? 'text-white/60' : 'text-fd-navy/60 dark:text-white/60' const descColor = isDark ? 'text-white/60' : 'text-fd-navy/60 dark:text-white/60'

View File

@ -2,6 +2,7 @@ import React from 'react'
import type { FDWideCardBlock as FDWideCardBlockProps, Media } from '@/payload-types' import type { FDWideCardBlock as FDWideCardBlockProps, Media } from '@/payload-types'
import { FDButton } from '@/components/FDButton' import { FDButton } from '@/components/FDButton'
import { FDImage } from '@/components/FDImage' import { FDImage } from '@/components/FDImage'
import { fdCardRadius as cardRadius, fdContainer, fdSepiaOverlay} from '@/utilities/fdTheme'
const cardBgMap: Record<string, { bg: string; heading: string; body: string; isDark: boolean }> = { const cardBgMap: Record<string, { bg: string; heading: string; body: string; isDark: boolean }> = {
navy: { bg: 'bg-fd-navy dark:bg-white/10', heading: 'text-white', body: 'text-white/80', isDark: true }, navy: { bg: 'bg-fd-navy dark:bg-white/10', heading: 'text-white', body: 'text-white/80', isDark: true },
@ -22,7 +23,6 @@ const btnVariantMap: Record<string, { variant: 'primary' | 'outline' }> = {
white: { variant: 'primary' }, white: { variant: 'primary' },
} }
const cardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
const overlayMap: Record<string, string> = { const overlayMap: Record<string, string> = {
none: '', none: '',
@ -30,7 +30,7 @@ const overlayMap: Record<string, string> = {
navyMedium: 'bg-fd-navy/40', navyMedium: 'bg-fd-navy/40',
yellowLight: 'bg-fd-yellow/20', yellowLight: 'bg-fd-yellow/20',
yellowMedium:'bg-fd-yellow/40', yellowMedium:'bg-fd-yellow/40',
sepia: 'bg-[#8B7D3C]/30', sepia: fdSepiaOverlay,
blackLight: 'bg-black/20', blackLight: 'bg-black/20',
blackMedium: 'bg-black/40', blackMedium: 'bg-black/40',
} }
@ -56,7 +56,7 @@ export const FDWideCardBlockComponent: React.FC<FDWideCardBlockProps> = ({
return ( return (
<section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}> <section id={anchorId || undefined} className={`w-full py-16 md:py-20 lg:py-[99px] ${sectionBg}`}>
<div className="max-w-[1200px] mx-auto px-6 md:px-8"> <div className={fdContainer}>
<div className={`${card.bg} ${cardRadius} flex flex-col min-[820px]:flex-row`}> <div className={`${card.bg} ${cardRadius} flex flex-col min-[820px]:flex-row`}>
<div className="flex-1 flex flex-col justify-center gap-5 md:gap-6 px-8 md:px-14 lg:px-16 py-12 md:py-16"> <div className="flex-1 flex flex-col justify-center gap-5 md:gap-6 px-8 md:px-14 lg:px-16 py-12 md:py-16">
<h2 className={`font-joey-heavy text-fd-h1 leading-tight ${card.heading}`}> <h2 className={`font-joey-heavy text-fd-h1 leading-tight ${card.heading}`}>

View File

@ -1,45 +0,0 @@
import type { CheckboxField } from '@payloadcms/plugin-form-builder/types'
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
import { useFormContext } from 'react-hook-form'
import { Checkbox as CheckboxUi } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
import React from 'react'
import { Error } from '../Error'
import { Width } from '../Width'
export const Checkbox: React.FC<
CheckboxField & {
errors: Partial<FieldErrorsImpl>
register: UseFormRegister<FieldValues>
}
> = ({ name, defaultValue, errors, label, register, required, width }) => {
const props = register(name, { required: required })
const { setValue } = useFormContext()
return (
<Width width={width}>
<div className="flex items-center gap-2">
<CheckboxUi
defaultChecked={defaultValue}
id={name}
{...props}
onCheckedChange={(checked) => {
setValue(props.name, checked)
}}
/>
<Label htmlFor={name}>
{required && (
<span className="required">
* <span className="sr-only">(required)</span>
</span>
)}
{label}
</Label>
</div>
{errors[name] && <Error name={name} />}
</Width>
)
}

View File

@ -1,163 +0,0 @@
'use client'
import type { FormFieldBlock, Form as FormType } from '@payloadcms/plugin-form-builder/types'
import { useRouter } from 'next/navigation'
import React, { useCallback, useState } from 'react'
import { useForm, FormProvider } from 'react-hook-form'
import RichText from '@/components/RichText'
import { Button } from '@/components/ui/button'
import type { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'
import { fields } from './fields'
import { getClientSideURL } from '@/utilities/getURL'
export type FormBlockType = {
blockName?: string
blockType?: 'formBlock'
enableIntro: boolean
form: FormType
introContent?: DefaultTypedEditorState
}
export const FormBlock: React.FC<
{
id?: string
} & FormBlockType
> = (props) => {
const {
enableIntro,
form: formFromProps,
form: { id: formID, confirmationMessage, confirmationType, redirect, submitButtonLabel } = {},
introContent,
} = props
const formMethods = useForm({
defaultValues: formFromProps.fields,
})
const {
control,
formState: { errors },
handleSubmit,
register,
} = formMethods
const [isLoading, setIsLoading] = useState(false)
const [hasSubmitted, setHasSubmitted] = useState<boolean>()
const [error, setError] = useState<{ message: string; status?: string } | undefined>()
const router = useRouter()
const onSubmit = useCallback(
(data: FormFieldBlock[]) => {
let loadingTimerID: ReturnType<typeof setTimeout>
const submitForm = async () => {
setError(undefined)
const dataToSend = Object.entries(data).map(([name, value]) => ({
field: name,
value,
}))
// delay loading indicator by 1s
loadingTimerID = setTimeout(() => {
setIsLoading(true)
}, 1000)
try {
const req = await fetch(`${getClientSideURL()}/api/form-submissions`, {
body: JSON.stringify({
form: formID,
submissionData: dataToSend,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
})
const res = await req.json()
clearTimeout(loadingTimerID)
if (req.status >= 400) {
setIsLoading(false)
setError({
message: res.errors?.[0]?.message || 'Internal Server Error',
status: res.status,
})
return
}
setIsLoading(false)
setHasSubmitted(true)
if (confirmationType === 'redirect' && redirect) {
const { url } = redirect
const redirectUrl = url
if (redirectUrl) router.push(redirectUrl)
}
} catch (err) {
console.warn(err)
setIsLoading(false)
setError({
message: 'Something went wrong.',
})
}
}
void submitForm()
},
[router, formID, redirect, confirmationType],
)
return (
<div className="container lg:max-w-[48rem]">
{enableIntro && introContent && !hasSubmitted && (
<RichText className="mb-8 lg:mb-12" data={introContent} enableGutter={false} />
)}
<div className="p-4 lg:p-6 border border-border rounded-[0.8rem]">
<FormProvider {...formMethods}>
{!isLoading && hasSubmitted && confirmationType === 'message' && (
<RichText data={confirmationMessage} />
)}
{isLoading && !hasSubmitted && <p>Loading, please wait...</p>}
{error && <div>{`${error.status || '500'}: ${error.message || ''}`}</div>}
{!hasSubmitted && (
<form id={formID} onSubmit={handleSubmit(onSubmit)}>
<div className="mb-4 last:mb-0">
{formFromProps &&
formFromProps.fields &&
formFromProps.fields?.map((field, index) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Field: React.FC<any> = fields?.[field.blockType as keyof typeof fields]
if (Field) {
return (
<div className="mb-6 last:mb-0" key={index}>
<Field
form={formFromProps}
{...field}
{...formMethods}
control={control}
errors={errors}
register={register}
/>
</div>
)
}
return null
})}
</div>
<Button form={formID} type="submit" variant="default">
{submitButtonLabel}
</Button>
</form>
)}
</FormProvider>
</div>
</div>
)
}

View File

@ -1,65 +0,0 @@
import type { CountryField } from '@payloadcms/plugin-form-builder/types'
import type { Control, FieldErrorsImpl } from 'react-hook-form'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import React from 'react'
import { Controller } from 'react-hook-form'
import { Error } from '../Error'
import { Width } from '../Width'
import { countryOptions } from './options'
export const Country: React.FC<
CountryField & {
control: Control
errors: Partial<FieldErrorsImpl>
}
> = ({ name, control, errors, label, required, width }) => {
return (
<Width width={width}>
<Label className="" htmlFor={name}>
{label}
{required && (
<span className="required">
* <span className="sr-only">(required)</span>
</span>
)}
</Label>
<Controller
control={control}
defaultValue=""
name={name}
render={({ field: { onChange, value } }) => {
const controlledValue = countryOptions.find((t) => t.value === value)
return (
<Select onValueChange={(val) => onChange(val)} value={controlledValue?.value}>
<SelectTrigger className="w-full" id={name}>
<SelectValue placeholder={label} />
</SelectTrigger>
<SelectContent>
{countryOptions.map(({ label, value }) => {
return (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
)
})}
</SelectContent>
</Select>
)
}}
rules={{ required }}
/>
{errors[name] && <Error name={name} />}
</Width>
)
}

View File

@ -1,982 +0,0 @@
export const countryOptions = [
{
label: 'Afghanistan',
value: 'AF',
},
{
label: 'Åland Islands',
value: 'AX',
},
{
label: 'Albania',
value: 'AL',
},
{
label: 'Algeria',
value: 'DZ',
},
{
label: 'American Samoa',
value: 'AS',
},
{
label: 'Andorra',
value: 'AD',
},
{
label: 'Angola',
value: 'AO',
},
{
label: 'Anguilla',
value: 'AI',
},
{
label: 'Antarctica',
value: 'AQ',
},
{
label: 'Antigua and Barbuda',
value: 'AG',
},
{
label: 'Argentina',
value: 'AR',
},
{
label: 'Armenia',
value: 'AM',
},
{
label: 'Aruba',
value: 'AW',
},
{
label: 'Australia',
value: 'AU',
},
{
label: 'Austria',
value: 'AT',
},
{
label: 'Azerbaijan',
value: 'AZ',
},
{
label: 'Bahamas',
value: 'BS',
},
{
label: 'Bahrain',
value: 'BH',
},
{
label: 'Bangladesh',
value: 'BD',
},
{
label: 'Barbados',
value: 'BB',
},
{
label: 'Belarus',
value: 'BY',
},
{
label: 'Belgium',
value: 'BE',
},
{
label: 'Belize',
value: 'BZ',
},
{
label: 'Benin',
value: 'BJ',
},
{
label: 'Bermuda',
value: 'BM',
},
{
label: 'Bhutan',
value: 'BT',
},
{
label: 'Bolivia',
value: 'BO',
},
{
label: 'Bosnia and Herzegovina',
value: 'BA',
},
{
label: 'Botswana',
value: 'BW',
},
{
label: 'Bouvet Island',
value: 'BV',
},
{
label: 'Brazil',
value: 'BR',
},
{
label: 'British Indian Ocean Territory',
value: 'IO',
},
{
label: 'Brunei Darussalam',
value: 'BN',
},
{
label: 'Bulgaria',
value: 'BG',
},
{
label: 'Burkina Faso',
value: 'BF',
},
{
label: 'Burundi',
value: 'BI',
},
{
label: 'Cambodia',
value: 'KH',
},
{
label: 'Cameroon',
value: 'CM',
},
{
label: 'Canada',
value: 'CA',
},
{
label: 'Cape Verde',
value: 'CV',
},
{
label: 'Cayman Islands',
value: 'KY',
},
{
label: 'Central African Republic',
value: 'CF',
},
{
label: 'Chad',
value: 'TD',
},
{
label: 'Chile',
value: 'CL',
},
{
label: 'China',
value: 'CN',
},
{
label: 'Christmas Island',
value: 'CX',
},
{
label: 'Cocos (Keeling) Islands',
value: 'CC',
},
{
label: 'Colombia',
value: 'CO',
},
{
label: 'Comoros',
value: 'KM',
},
{
label: 'Congo',
value: 'CG',
},
{
label: 'Congo, The Democratic Republic of the',
value: 'CD',
},
{
label: 'Cook Islands',
value: 'CK',
},
{
label: 'Costa Rica',
value: 'CR',
},
{
label: "Cote D'Ivoire",
value: 'CI',
},
{
label: 'Croatia',
value: 'HR',
},
{
label: 'Cuba',
value: 'CU',
},
{
label: 'Cyprus',
value: 'CY',
},
{
label: 'Czech Republic',
value: 'CZ',
},
{
label: 'Denmark',
value: 'DK',
},
{
label: 'Djibouti',
value: 'DJ',
},
{
label: 'Dominica',
value: 'DM',
},
{
label: 'Dominican Republic',
value: 'DO',
},
{
label: 'Ecuador',
value: 'EC',
},
{
label: 'Egypt',
value: 'EG',
},
{
label: 'El Salvador',
value: 'SV',
},
{
label: 'Equatorial Guinea',
value: 'GQ',
},
{
label: 'Eritrea',
value: 'ER',
},
{
label: 'Estonia',
value: 'EE',
},
{
label: 'Ethiopia',
value: 'ET',
},
{
label: 'Falkland Islands (Malvinas)',
value: 'FK',
},
{
label: 'Faroe Islands',
value: 'FO',
},
{
label: 'Fiji',
value: 'FJ',
},
{
label: 'Finland',
value: 'FI',
},
{
label: 'France',
value: 'FR',
},
{
label: 'French Guiana',
value: 'GF',
},
{
label: 'French Polynesia',
value: 'PF',
},
{
label: 'French Southern Territories',
value: 'TF',
},
{
label: 'Gabon',
value: 'GA',
},
{
label: 'Gambia',
value: 'GM',
},
{
label: 'Georgia',
value: 'GE',
},
{
label: 'Germany',
value: 'DE',
},
{
label: 'Ghana',
value: 'GH',
},
{
label: 'Gibraltar',
value: 'GI',
},
{
label: 'Greece',
value: 'GR',
},
{
label: 'Greenland',
value: 'GL',
},
{
label: 'Grenada',
value: 'GD',
},
{
label: 'Guadeloupe',
value: 'GP',
},
{
label: 'Guam',
value: 'GU',
},
{
label: 'Guatemala',
value: 'GT',
},
{
label: 'Guernsey',
value: 'GG',
},
{
label: 'Guinea',
value: 'GN',
},
{
label: 'Guinea-Bissau',
value: 'GW',
},
{
label: 'Guyana',
value: 'GY',
},
{
label: 'Haiti',
value: 'HT',
},
{
label: 'Heard Island and Mcdonald Islands',
value: 'HM',
},
{
label: 'Holy See (Vatican City State)',
value: 'VA',
},
{
label: 'Honduras',
value: 'HN',
},
{
label: 'Hong Kong',
value: 'HK',
},
{
label: 'Hungary',
value: 'HU',
},
{
label: 'Iceland',
value: 'IS',
},
{
label: 'India',
value: 'IN',
},
{
label: 'Indonesia',
value: 'ID',
},
{
label: 'Iran, Islamic Republic Of',
value: 'IR',
},
{
label: 'Iraq',
value: 'IQ',
},
{
label: 'Ireland',
value: 'IE',
},
{
label: 'Isle of Man',
value: 'IM',
},
{
label: 'Israel',
value: 'IL',
},
{
label: 'Italy',
value: 'IT',
},
{
label: 'Jamaica',
value: 'JM',
},
{
label: 'Japan',
value: 'JP',
},
{
label: 'Jersey',
value: 'JE',
},
{
label: 'Jordan',
value: 'JO',
},
{
label: 'Kazakhstan',
value: 'KZ',
},
{
label: 'Kenya',
value: 'KE',
},
{
label: 'Kiribati',
value: 'KI',
},
{
label: "Democratic People's Republic of Korea",
value: 'KP',
},
{
label: 'Korea, Republic of',
value: 'KR',
},
{
label: 'Kosovo',
value: 'XK',
},
{
label: 'Kuwait',
value: 'KW',
},
{
label: 'Kyrgyzstan',
value: 'KG',
},
{
label: "Lao People's Democratic Republic",
value: 'LA',
},
{
label: 'Latvia',
value: 'LV',
},
{
label: 'Lebanon',
value: 'LB',
},
{
label: 'Lesotho',
value: 'LS',
},
{
label: 'Liberia',
value: 'LR',
},
{
label: 'Libyan Arab Jamahiriya',
value: 'LY',
},
{
label: 'Liechtenstein',
value: 'LI',
},
{
label: 'Lithuania',
value: 'LT',
},
{
label: 'Luxembourg',
value: 'LU',
},
{
label: 'Macao',
value: 'MO',
},
{
label: 'Macedonia, The Former Yugoslav Republic of',
value: 'MK',
},
{
label: 'Madagascar',
value: 'MG',
},
{
label: 'Malawi',
value: 'MW',
},
{
label: 'Malaysia',
value: 'MY',
},
{
label: 'Maldives',
value: 'MV',
},
{
label: 'Mali',
value: 'ML',
},
{
label: 'Malta',
value: 'MT',
},
{
label: 'Marshall Islands',
value: 'MH',
},
{
label: 'Martinique',
value: 'MQ',
},
{
label: 'Mauritania',
value: 'MR',
},
{
label: 'Mauritius',
value: 'MU',
},
{
label: 'Mayotte',
value: 'YT',
},
{
label: 'Mexico',
value: 'MX',
},
{
label: 'Micronesia, Federated States of',
value: 'FM',
},
{
label: 'Moldova, Republic of',
value: 'MD',
},
{
label: 'Monaco',
value: 'MC',
},
{
label: 'Mongolia',
value: 'MN',
},
{
label: 'Montenegro',
value: 'ME',
},
{
label: 'Montserrat',
value: 'MS',
},
{
label: 'Morocco',
value: 'MA',
},
{
label: 'Mozambique',
value: 'MZ',
},
{
label: 'Myanmar',
value: 'MM',
},
{
label: 'Namibia',
value: 'NA',
},
{
label: 'Nauru',
value: 'NR',
},
{
label: 'Nepal',
value: 'NP',
},
{
label: 'Netherlands',
value: 'NL',
},
{
label: 'Netherlands Antilles',
value: 'AN',
},
{
label: 'New Caledonia',
value: 'NC',
},
{
label: 'New Zealand',
value: 'NZ',
},
{
label: 'Nicaragua',
value: 'NI',
},
{
label: 'Niger',
value: 'NE',
},
{
label: 'Nigeria',
value: 'NG',
},
{
label: 'Niue',
value: 'NU',
},
{
label: 'Norfolk Island',
value: 'NF',
},
{
label: 'Northern Mariana Islands',
value: 'MP',
},
{
label: 'Norway',
value: 'NO',
},
{
label: 'Oman',
value: 'OM',
},
{
label: 'Pakistan',
value: 'PK',
},
{
label: 'Palau',
value: 'PW',
},
{
label: 'Palestinian Territory, Occupied',
value: 'PS',
},
{
label: 'Panama',
value: 'PA',
},
{
label: 'Papua New Guinea',
value: 'PG',
},
{
label: 'Paraguay',
value: 'PY',
},
{
label: 'Peru',
value: 'PE',
},
{
label: 'Philippines',
value: 'PH',
},
{
label: 'Pitcairn',
value: 'PN',
},
{
label: 'Poland',
value: 'PL',
},
{
label: 'Portugal',
value: 'PT',
},
{
label: 'Puerto Rico',
value: 'PR',
},
{
label: 'Qatar',
value: 'QA',
},
{
label: 'Reunion',
value: 'RE',
},
{
label: 'Romania',
value: 'RO',
},
{
label: 'Russian Federation',
value: 'RU',
},
{
label: 'Rwanda',
value: 'RW',
},
{
label: 'Saint Helena',
value: 'SH',
},
{
label: 'Saint Kitts and Nevis',
value: 'KN',
},
{
label: 'Saint Lucia',
value: 'LC',
},
{
label: 'Saint Pierre and Miquelon',
value: 'PM',
},
{
label: 'Saint Vincent and the Grenadines',
value: 'VC',
},
{
label: 'Samoa',
value: 'WS',
},
{
label: 'San Marino',
value: 'SM',
},
{
label: 'Sao Tome and Principe',
value: 'ST',
},
{
label: 'Saudi Arabia',
value: 'SA',
},
{
label: 'Senegal',
value: 'SN',
},
{
label: 'Serbia',
value: 'RS',
},
{
label: 'Seychelles',
value: 'SC',
},
{
label: 'Sierra Leone',
value: 'SL',
},
{
label: 'Singapore',
value: 'SG',
},
{
label: 'Slovakia',
value: 'SK',
},
{
label: 'Slovenia',
value: 'SI',
},
{
label: 'Solomon Islands',
value: 'SB',
},
{
label: 'Somalia',
value: 'SO',
},
{
label: 'South Africa',
value: 'ZA',
},
{
label: 'South Georgia and the South Sandwich Islands',
value: 'GS',
},
{
label: 'Spain',
value: 'ES',
},
{
label: 'Sri Lanka',
value: 'LK',
},
{
label: 'Sudan',
value: 'SD',
},
{
label: 'Suriname',
value: 'SR',
},
{
label: 'Svalbard and Jan Mayen',
value: 'SJ',
},
{
label: 'Swaziland',
value: 'SZ',
},
{
label: 'Sweden',
value: 'SE',
},
{
label: 'Switzerland',
value: 'CH',
},
{
label: 'Syrian Arab Republic',
value: 'SY',
},
{
label: 'Taiwan',
value: 'TW',
},
{
label: 'Tajikistan',
value: 'TJ',
},
{
label: 'Tanzania, United Republic of',
value: 'TZ',
},
{
label: 'Thailand',
value: 'TH',
},
{
label: 'Timor-Leste',
value: 'TL',
},
{
label: 'Togo',
value: 'TG',
},
{
label: 'Tokelau',
value: 'TK',
},
{
label: 'Tonga',
value: 'TO',
},
{
label: 'Trinidad and Tobago',
value: 'TT',
},
{
label: 'Tunisia',
value: 'TN',
},
{
label: 'Turkey',
value: 'TR',
},
{
label: 'Turkmenistan',
value: 'TM',
},
{
label: 'Turks and Caicos Islands',
value: 'TC',
},
{
label: 'Tuvalu',
value: 'TV',
},
{
label: 'Uganda',
value: 'UG',
},
{
label: 'Ukraine',
value: 'UA',
},
{
label: 'United Arab Emirates',
value: 'AE',
},
{
label: 'United Kingdom',
value: 'GB',
},
{
label: 'United States',
value: 'US',
},
{
label: 'United States Minor Outlying Islands',
value: 'UM',
},
{
label: 'Uruguay',
value: 'UY',
},
{
label: 'Uzbekistan',
value: 'UZ',
},
{
label: 'Vanuatu',
value: 'VU',
},
{
label: 'Venezuela',
value: 'VE',
},
{
label: 'Viet Nam',
value: 'VN',
},
{
label: 'Virgin Islands, British',
value: 'VG',
},
{
label: 'Virgin Islands, U.S.',
value: 'VI',
},
{
label: 'Wallis and Futuna',
value: 'WF',
},
{
label: 'Western Sahara',
value: 'EH',
},
{
label: 'Yemen',
value: 'YE',
},
{
label: 'Zambia',
value: 'ZM',
},
{
label: 'Zimbabwe',
value: 'ZW',
},
]

View File

@ -1,38 +0,0 @@
import type { EmailField } from '@payloadcms/plugin-form-builder/types'
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import React from 'react'
import { Error } from '../Error'
import { Width } from '../Width'
export const Email: React.FC<
EmailField & {
errors: Partial<FieldErrorsImpl>
register: UseFormRegister<FieldValues>
}
> = ({ name, defaultValue, errors, label, register, required, width }) => {
return (
<Width width={width}>
<Label htmlFor={name}>
{label}
{required && (
<span className="required">
* <span className="sr-only">(required)</span>
</span>
)}
</Label>
<Input
defaultValue={defaultValue}
id={name}
type="text"
{...register(name, { pattern: /^\S[^\s@]*@\S+$/, required })}
/>
{errors[name] && <Error name={name} />}
</Width>
)
}

View File

@ -1,15 +0,0 @@
'use client'
import * as React from 'react'
import { useFormContext } from 'react-hook-form'
export const Error = ({ name }: { name: string }) => {
const {
formState: { errors },
} = useFormContext()
return (
<div className="mt-2 text-red-500 text-sm">
{(errors[name]?.message as string) || 'This field is required'}
</div>
)
}

View File

@ -1,13 +0,0 @@
import RichText from '@/components/RichText'
import React from 'react'
import { Width } from '../Width'
import { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'
export const Message: React.FC<{ message: DefaultTypedEditorState }> = ({ message }) => {
return (
<Width className="my-12" width="100">
{message && <RichText data={message} />}
</Width>
)
}

View File

@ -1,36 +0,0 @@
import type { TextField } from '@payloadcms/plugin-form-builder/types'
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import React from 'react'
import { Error } from '../Error'
import { Width } from '../Width'
export const Number: React.FC<
TextField & {
errors: Partial<FieldErrorsImpl>
register: UseFormRegister<FieldValues>
}
> = ({ name, defaultValue, errors, label, register, required, width }) => {
return (
<Width width={width}>
<Label htmlFor={name}>
{label}
{required && (
<span className="required">
* <span className="sr-only">(required)</span>
</span>
)}
</Label>
<Input
defaultValue={defaultValue}
id={name}
type="number"
{...register(name, { required })}
/>
{errors[name] && <Error name={name} />}
</Width>
)
}

View File

@ -1,63 +0,0 @@
import type { SelectField } from '@payloadcms/plugin-form-builder/types'
import type { Control, FieldErrorsImpl } from 'react-hook-form'
import { Label } from '@/components/ui/label'
import {
Select as SelectComponent,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import React from 'react'
import { Controller } from 'react-hook-form'
import { Error } from '../Error'
import { Width } from '../Width'
export const Select: React.FC<
SelectField & {
control: Control
errors: Partial<FieldErrorsImpl>
}
> = ({ name, control, errors, label, options, required, width, defaultValue }) => {
return (
<Width width={width}>
<Label htmlFor={name}>
{label}
{required && (
<span className="required">
* <span className="sr-only">(required)</span>
</span>
)}
</Label>
<Controller
control={control}
defaultValue={defaultValue}
name={name}
render={({ field: { onChange, value } }) => {
const controlledValue = options.find((t) => t.value === value)
return (
<SelectComponent onValueChange={(val) => onChange(val)} value={controlledValue?.value}>
<SelectTrigger className="w-full" id={name}>
<SelectValue placeholder={label} />
</SelectTrigger>
<SelectContent>
{options.map(({ label, value }) => {
return (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
)
})}
</SelectContent>
</SelectComponent>
)
}}
rules={{ required }}
/>
{errors[name] && <Error name={name} />}
</Width>
)
}

View File

@ -1,64 +0,0 @@
import type { StateField } from '@payloadcms/plugin-form-builder/types'
import type { Control, FieldErrorsImpl } from 'react-hook-form'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import React from 'react'
import { Controller } from 'react-hook-form'
import { Error } from '../Error'
import { Width } from '../Width'
import { stateOptions } from './options'
export const State: React.FC<
StateField & {
control: Control
errors: Partial<FieldErrorsImpl>
}
> = ({ name, control, errors, label, required, width }) => {
return (
<Width width={width}>
<Label htmlFor={name}>
{label}
{required && (
<span className="required">
* <span className="sr-only">(required)</span>
</span>
)}
</Label>
<Controller
control={control}
defaultValue=""
name={name}
render={({ field: { onChange, value } }) => {
const controlledValue = stateOptions.find((t) => t.value === value)
return (
<Select onValueChange={(val) => onChange(val)} value={controlledValue?.value}>
<SelectTrigger className="w-full" id={name}>
<SelectValue placeholder={label} />
</SelectTrigger>
<SelectContent>
{stateOptions.map(({ label, value }) => {
return (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
)
})}
</SelectContent>
</Select>
)
}}
rules={{ required }}
/>
{errors[name] && <Error name={name} />}
</Width>
)
}

View File

@ -1,52 +0,0 @@
export const stateOptions = [
{ label: 'Alabama', value: 'AL' },
{ label: 'Alaska', value: 'AK' },
{ label: 'Arizona', value: 'AZ' },
{ label: 'Arkansas', value: 'AR' },
{ label: 'California', value: 'CA' },
{ label: 'Colorado', value: 'CO' },
{ label: 'Connecticut', value: 'CT' },
{ label: 'Delaware', value: 'DE' },
{ label: 'Florida', value: 'FL' },
{ label: 'Georgia', value: 'GA' },
{ label: 'Hawaii', value: 'HI' },
{ label: 'Idaho', value: 'ID' },
{ label: 'Illinois', value: 'IL' },
{ label: 'Indiana', value: 'IN' },
{ label: 'Iowa', value: 'IA' },
{ label: 'Kansas', value: 'KS' },
{ label: 'Kentucky', value: 'KY' },
{ label: 'Louisiana', value: 'LA' },
{ label: 'Maine', value: 'ME' },
{ label: 'Maryland', value: 'MD' },
{ label: 'Massachusetts', value: 'MA' },
{ label: 'Michigan', value: 'MI' },
{ label: 'Minnesota', value: 'MN' },
{ label: 'Mississippi', value: 'MS' },
{ label: 'Missouri', value: 'MO' },
{ label: 'Montana', value: 'MT' },
{ label: 'Nebraska', value: 'NE' },
{ label: 'Nevada', value: 'NV' },
{ label: 'New Hampshire', value: 'NH' },
{ label: 'New Jersey', value: 'NJ' },
{ label: 'New Mexico', value: 'NM' },
{ label: 'New York', value: 'NY' },
{ label: 'North Carolina', value: 'NC' },
{ label: 'North Dakota', value: 'ND' },
{ label: 'Ohio', value: 'OH' },
{ label: 'Oklahoma', value: 'OK' },
{ label: 'Oregon', value: 'OR' },
{ label: 'Pennsylvania', value: 'PA' },
{ label: 'Rhode Island', value: 'RI' },
{ label: 'South Carolina', value: 'SC' },
{ label: 'South Dakota', value: 'SD' },
{ label: 'Tennessee', value: 'TN' },
{ label: 'Texas', value: 'TX' },
{ label: 'Utah', value: 'UT' },
{ label: 'Vermont', value: 'VT' },
{ label: 'Virginia', value: 'VA' },
{ label: 'Washington', value: 'WA' },
{ label: 'West Virginia', value: 'WV' },
{ label: 'Wisconsin', value: 'WI' },
{ label: 'Wyoming', value: 'WY' },
]

View File

@ -1,32 +0,0 @@
import type { TextField } from '@payloadcms/plugin-form-builder/types'
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import React from 'react'
import { Error } from '../Error'
import { Width } from '../Width'
export const Text: React.FC<
TextField & {
errors: Partial<FieldErrorsImpl>
register: UseFormRegister<FieldValues>
}
> = ({ name, defaultValue, errors, label, register, required, width }) => {
return (
<Width width={width}>
<Label htmlFor={name}>
{label}
{required && (
<span className="required">
* <span className="sr-only">(required)</span>
</span>
)}
</Label>
<Input defaultValue={defaultValue} id={name} type="text" {...register(name, { required })} />
{errors[name] && <Error name={name} />}
</Width>
)
}

View File

@ -1,40 +0,0 @@
import type { TextField } from '@payloadcms/plugin-form-builder/types'
import type { FieldErrorsImpl, FieldValues, UseFormRegister } from 'react-hook-form'
import { Label } from '@/components/ui/label'
import { Textarea as TextAreaComponent } from '@/components/ui/textarea'
import React from 'react'
import { Error } from '../Error'
import { Width } from '../Width'
export const Textarea: React.FC<
TextField & {
errors: Partial<FieldErrorsImpl>
register: UseFormRegister<FieldValues>
rows?: number
}
> = ({ name, defaultValue, errors, label, register, required, rows = 3, width }) => {
return (
<Width width={width}>
<Label htmlFor={name}>
{label}
{required && (
<span className="required">
* <span className="sr-only">(required)</span>
</span>
)}
</Label>
<TextAreaComponent
defaultValue={defaultValue}
id={name}
rows={rows}
{...register(name, { required: required })}
/>
{errors[name] && <Error name={name} />}
</Width>
)
}

View File

@ -1,13 +0,0 @@
import * as React from 'react'
export const Width: React.FC<{
children: React.ReactNode
className?: string
width?: number | string
}> = ({ children, className, width }) => {
return (
<div className={className} style={{ maxWidth: width ? `${width}%` : undefined }}>
{children}
</div>
)
}

View File

@ -1,51 +0,0 @@
import type { Block } from 'payload'
import {
FixedToolbarFeature,
HeadingFeature,
InlineToolbarFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
export const FormBlock: Block = {
slug: 'formBlock',
interfaceName: 'FormBlock',
fields: [
{
name: 'form',
type: 'relationship',
relationTo: 'forms',
required: true,
},
{
name: 'enableIntro',
type: 'checkbox',
label: 'Enable Intro Content',
},
{
name: 'introContent',
type: 'richText',
admin: {
condition: (_, { enableIntro }) => Boolean(enableIntro),
},
editor: lexicalEditor({
features: ({ rootFeatures }) => {
return [
...rootFeatures,
HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
FixedToolbarFeature(),
InlineToolbarFeature(),
]
},
}),
label: 'Intro Content',
},
],
graphQL: {
singularName: 'FormBlock',
},
labels: {
plural: 'Form Blocks',
singular: 'Form Block',
},
}

View File

@ -1,21 +0,0 @@
import { Checkbox } from './Checkbox'
import { Country } from './Country'
import { Email } from './Email'
import { Message } from './Message'
import { Number } from './Number'
import { Select } from './Select'
import { State } from './State'
import { Text } from './Text'
import { Textarea } from './Textarea'
export const fields = {
checkbox: Checkbox,
country: Country,
email: Email,
message: Message,
number: Number,
select: Select,
state: State,
text: Text,
textarea: Textarea,
}

View File

@ -1,32 +0,0 @@
import clsx from 'clsx'
import React from 'react'
import RichText from '@/components/RichText'
import type { Post } from '@/payload-types'
import { Card } from '../../components/Card'
import { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'
export type RelatedPostsProps = {
className?: string
docs?: Post[]
introContent?: DefaultTypedEditorState
}
export const RelatedPosts: React.FC<RelatedPostsProps> = (props) => {
const { className, docs, introContent } = props
return (
<div className={clsx('lg:container', className)}>
{introContent && <RichText data={introContent} enableGutter={false} />}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 md:gap-8 items-stretch">
{docs?.map((doc, index) => {
if (typeof doc === 'string') return null
return <Card key={index} doc={doc} relationTo="posts" showCategories />
})}
</div>
</div>
)
}

View File

@ -2,7 +2,6 @@ import React, { Fragment } from 'react'
import type { Page } from '@/payload-types' import type { Page } from '@/payload-types'
import { FormBlock } from '@/blocks/Form/Component'
import { FDHeroBlockComponent } from '@/blocks/FDHeroBlock/Component' import { FDHeroBlockComponent } from '@/blocks/FDHeroBlock/Component'
import { FDCtaSideImageBlockComponent } from '@/blocks/FDCtaSideImageBlock/Component' import { FDCtaSideImageBlockComponent } from '@/blocks/FDCtaSideImageBlock/Component'
import { FDFeatureAnnouncementBlockComponent } from '@/blocks/FDFeatureAnnouncementBlock/Component' import { FDFeatureAnnouncementBlockComponent } from '@/blocks/FDFeatureAnnouncementBlock/Component'
@ -42,7 +41,6 @@ import { FDVideoHeroBlockComponent } from '@/blocks/FDVideoHeroBlock/Component'
const blockComponents: Record<string, React.FC<any>> = { const blockComponents: Record<string, React.FC<any>> = {
formBlock: FormBlock,
fdHero: FDHeroBlockComponent, fdHero: FDHeroBlockComponent,
fdCtaSideImage: FDCtaSideImageBlockComponent, fdCtaSideImage: FDCtaSideImageBlockComponent,
fdFeatureAnnouncement: FDFeatureAnnouncementBlockComponent, fdFeatureAnnouncement: FDFeatureAnnouncementBlockComponent,

View File

@ -1,81 +0,0 @@
import type { CollectionConfig } from 'payload'
import {
FixedToolbarFeature,
InlineToolbarFeature,
lexicalEditor,
} from '@payloadcms/richtext-lexical'
import path from 'path'
import { fileURLToPath } from 'url'
import { anyone } from '../access/anyone'
import { authenticated } from '../access/authenticated'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export const Media: CollectionConfig = {
slug: 'media',
folders: true,
access: {
create: authenticated,
delete: authenticated,
read: anyone,
update: authenticated,
},
fields: [
{
name: 'alt',
type: 'text',
//required: true,
},
{
name: 'caption',
type: 'richText',
editor: lexicalEditor({
features: ({ rootFeatures }) => {
return [...rootFeatures, FixedToolbarFeature(), InlineToolbarFeature()]
},
}),
},
],
upload: {
// Upload to the public/media directory in Next.js making them publicly accessible even outside of Payload
staticDir: path.resolve(dirname, '../../public/media'),
adminThumbnail: 'thumbnail',
focalPoint: true,
imageSizes: [
{
name: 'thumbnail',
width: 300,
},
{
name: 'square',
width: 500,
height: 500,
},
{
name: 'small',
width: 600,
},
{
name: 'medium',
width: 900,
},
{
name: 'large',
width: 1400,
},
{
name: 'xlarge',
width: 1920,
},
{
name: 'og',
width: 1200,
height: 630,
crop: 'center',
},
],
},
}

View File

@ -19,7 +19,7 @@ const sectionBgMap: Record<string, string> = {
gray: 'bg-fd-gray-light dark:bg-fd-navy', gray: 'bg-fd-gray-light dark:bg-fd-navy',
navy: 'bg-fd-navy', navy: 'bg-fd-navy',
yellow: 'bg-fd-yellow', yellow: 'bg-fd-yellow',
navyGradient: 'bg-gradient-to-br from-fd-navy via-[#153350] to-fd-navy', navyGradient: 'bg-gradient-to-br from-fd-navy via-fd-navy-700 to-fd-navy',
transparent: 'bg-transparent', transparent: 'bg-transparent',
} }
@ -74,3 +74,13 @@ export const fdCardRadius = 'rounded-[32px] md:rounded-[50px] lg:rounded-[70px]'
/** Smaller radius for sub-elements (images inside cards, data tables, etc.) */ /** Smaller radius for sub-elements (images inside cards, data tables, etc.) */
export const fdCardRadiusSm = 'rounded-[20px] md:rounded-[30px] lg:rounded-[40px]' export const fdCardRadiusSm = 'rounded-[20px] md:rounded-[30px] lg:rounded-[40px]'
/* ── Layout ───────────────────────────────────────────────────────────── */
/** Standard page container — max width + horizontal padding */
export const fdContainer = 'max-w-[1200px] mx-auto px-6 md:px-8'
/* ── Overlay utilities ────────────────────────────────────────────────── */
/** Sepia colour overlay for images */
export const fdSepiaOverlay = 'bg-fd-sepia/30'