feat: add FDLinkCards, FDSpecCards, FDQuiz blocks

This commit is contained in:
Jeffrey 2026-03-04 20:48:34 +01:00
parent 805ec291df
commit 38ecfce2eb
12 changed files with 39546 additions and 1 deletions

View File

@ -0,0 +1,140 @@
import React from 'react'
import type { FDLinkCardsBlock as Props, Media } from '@/payload-types'
import { FDImage } from '@/components/FDImage'
import { sectionBg, isExplicitDark, headingColor, bodyColor, fdCardRadius } from '@/utilities/fdTheme'
const cardStyleMap: Record<string, { bg: string; title: string; link: string; border: string }> = {
outlined: {
bg: 'bg-white/5',
title: 'text-white dark:text-white',
link: 'text-white/60 hover:text-fd-yellow dark:text-white/60 dark:hover:text-fd-yellow',
border: 'border border-white/10 dark:border-white/10',
},
navy: {
bg: 'bg-fd-navy dark:bg-white/10',
title: 'text-white',
link: 'text-fd-yellow hover:text-fd-yellow/80',
border: '',
},
gray: {
bg: 'bg-fd-gray-light dark:bg-white/10',
title: 'text-fd-navy dark:text-white',
link: 'text-fd-navy/60 hover:text-fd-navy dark:text-white/60 dark:hover:text-fd-yellow',
border: '',
},
yellow: {
bg: 'bg-fd-yellow',
title: 'text-fd-navy',
link: 'text-fd-navy/60 hover:text-fd-navy',
border: '',
},
}
/* Light-section overrides for outlined cards (needs dark text, not white) */
const cardStyleMapLight: Record<string, { bg: string; title: string; link: string; border: string }> = {
outlined: {
bg: 'bg-white dark:bg-white/5',
title: 'text-fd-navy dark:text-white',
link: 'text-fd-navy/60 hover:text-fd-navy dark:text-white/60 dark:hover:text-fd-yellow',
border: 'border border-gray-200 dark:border-white/10',
},
}
const colsMap: Record<string, string> = {
'2': 'md:grid-cols-2',
'3': 'md:grid-cols-3',
'4': 'md:grid-cols-2 lg:grid-cols-4',
}
const ArrowIcon: React.FC<{ className?: string }> = ({ className }) => (
<svg className={className || 'w-4 h-4'} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 8h10M9 4l4 4-4 4" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
export const FDLinkCardsBlockComponent: React.FC<Props> = ({
icon,
heading,
description,
cards,
columns = '3',
cardStyle = 'outlined',
sectionBackground = 'navy',
anchorId,
}) => {
const dark = isExplicitDark(sectionBackground)
const bg = sectionBg(sectionBackground)
const hClr = headingColor(dark)
const bClr = bodyColor(dark)
const gridCols = colsMap[columns || '3'] || colsMap['3']
/* Pick card style — outlined adapts based on section darkness */
const style = (!dark && cardStyle === 'outlined')
? cardStyleMapLight.outlined
: (cardStyleMap[cardStyle || 'outlined'] || cardStyleMap.outlined)
const iconMedia = icon as Media | undefined
const hasIcon = iconMedia && typeof iconMedia === 'object' && iconMedia.url
return (
<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">
{/* Centered header */}
<div className="text-center mb-12 md:mb-16 flex flex-col items-center gap-4">
{hasIcon && (
<FDImage
media={iconMedia}
size="thumbnail"
className="w-12 h-12 md:w-16 md:h-16 object-contain"
sizes="64px"
fallbackAlt=""
/>
)}
<h2 className={`font-joey-heavy text-fd-h1 max-w-[700px] ${hClr}`}>
{heading}
</h2>
{description && (
<p className={`font-joey text-fd-body-lg max-w-[600px] ${bClr} opacity-70`}>
{description}
</p>
)}
</div>
{/* Card grid */}
<div className={`grid grid-cols-1 ${gridCols} gap-4 md:gap-6`}>
{cards?.map((card, i) => {
const linkIconMedia = card.linkIcon as Media | undefined
const hasLinkIcon = linkIconMedia && typeof linkIconMedia === 'object' && linkIconMedia.url
return (
<a
key={i}
href={card.linkUrl || '#'}
className={`group ${style.bg} ${style.border} ${fdCardRadius} px-8 py-8 md:px-10 md:py-10 flex flex-col justify-between gap-8 min-h-[220px] md:min-h-[260px] hover:-translate-y-1 transition-all duration-200`}
>
<h3 className={`font-joey-bold text-fd-h3 leading-snug ${style.title}`}>
{card.title}
</h3>
<div className={`flex items-center gap-2 font-joey text-fd-body transition-colors ${style.link}`}>
{hasLinkIcon && (
<FDImage
media={linkIconMedia}
size="thumbnail"
className="w-5 h-5 object-contain opacity-60"
sizes="20px"
fallbackAlt=""
/>
)}
<span>{card.linkLabel}</span>
<ArrowIcon className="w-4 h-4 transition-transform group-hover:translate-x-1" />
</div>
</a>
)
})}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,108 @@
import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDLinkCardsBlock: Block = {
slug: 'fdLinkCards',
imageURL: '/block-thumbnails/fd-link-cards.png',
imageAltText: 'FD Länkkort',
interfaceName: 'FDLinkCardsBlock',
labels: {
singular: 'FD Länkkort',
plural: 'FD Länkkort',
},
fields: [
{
name: 'icon',
type: 'upload',
relationTo: 'media',
label: 'Ikon ovanför rubrik (valfri)',
},
{
name: 'heading',
type: 'text',
localized: true,
label: 'Rubrik',
required: true,
},
{
name: 'description',
type: 'textarea',
localized: true,
label: 'Beskrivning (valfri)',
},
{
name: 'cards',
type: 'array',
label: 'Kort',
minRows: 1,
maxRows: 6,
labels: {
singular: 'Kort',
plural: 'Kort',
},
fields: [
{
name: 'title',
type: 'textarea',
localized: true,
label: 'Korttitel',
required: true,
},
{
name: 'linkLabel',
type: 'text',
localized: true,
label: 'Länktext (visas i botten)',
required: true,
},
{
name: 'linkUrl',
type: 'text',
label: 'Länk-URL',
required: true,
},
{
name: 'linkIcon',
type: 'upload',
relationTo: 'media',
label: 'Länkikon (valfri, visas bredvid länktexten)',
},
],
},
{
name: 'columns',
type: 'select',
label: 'Kolumner (desktop)',
defaultValue: '3',
options: [
{ label: '2 kolumner', value: '2' },
{ label: '3 kolumner', value: '3' },
{ label: '4 kolumner', value: '4' },
],
},
{
name: 'cardStyle',
type: 'select',
label: 'Kortstil',
defaultValue: 'outlined',
options: [
{ label: 'Kantlinje', value: 'outlined' },
{ label: 'Navy', value: 'navy' },
{ label: 'Grå', value: 'gray' },
{ label: 'Gul', value: 'yellow' },
],
},
{
name: 'sectionBackground',
type: 'select',
label: 'Sektionsbakgrund',
defaultValue: 'navy',
options: [
{ label: 'Vit', value: 'white' },
{ label: 'Grå', value: 'gray' },
{ label: 'Navy', value: 'navy' },
],
},
anchorField,
],
}

View File

@ -0,0 +1,370 @@
'use client'
import React, { useState, useEffect, useCallback, useRef } from 'react'
import type { FDQuizBlock as Props } from '@/payload-types'
import { FDButton } from '@/components/FDButton'
import { sectionBg, isExplicitDark, headingColor, bodyColor, fdCardRadius } from '@/utilities/fdTheme'
/* ── Types ── */
type QuizState = 'idle' | 'active' | 'result'
/* ── Icons ── */
const QuizIcon: React.FC<{ className?: string }> = ({ className }) => (
<svg className={className || 'w-5 h-5'} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
<circle cx="12" cy="12" r="10" />
<path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" strokeLinecap="round" strokeLinejoin="round" />
<circle cx="12" cy="17" r=".5" fill="currentColor" />
</svg>
)
const ArrowRight: React.FC<{ className?: string }> = ({ className }) => (
<svg className={className || 'w-4 h-4'} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M3 8h10M9 4l4 4-4 4" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
const ArrowLeft: React.FC<{ className?: string }> = ({ className }) => (
<svg className={className || 'w-4 h-4'} viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M13 8H3M7 4L3 8l4 4" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)
/* ── Component ── */
export const FDQuizBlockComponent: React.FC<Props> = ({
heading,
description,
triggerLabel = 'Starta quiz',
outcomes = [],
questions = [],
nextLabel = 'Nästa',
backLabel = 'Tillbaka',
resultHeading = 'Rekommenderat:',
restartLabel = 'Börja om',
sectionBackground = 'navy',
anchorId,
}) => {
const [state, setState] = useState<QuizState>('idle')
const [step, setStep] = useState(0)
const [answers, setAnswers] = useState<Record<number, number>>({}) // step → option index
const [animating, setAnimating] = useState(false)
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false)
const panelRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const mql = window.matchMedia('(prefers-reduced-motion: reduce)')
setPrefersReducedMotion(mql.matches)
const handler = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches)
mql.addEventListener('change', handler)
return () => mql.removeEventListener('change', handler)
}, [])
const dark = isExplicitDark(sectionBackground)
const bg = sectionBg(sectionBackground)
const hClr = headingColor(dark)
const bClr = bodyColor(dark)
/* Panel colors — always dark-ish card on any section */
const panelBg = dark
? 'bg-white/5 border border-white/10'
: 'bg-white border border-gray-200 dark:bg-white/5 dark:border-white/10'
const panelText = dark
? 'text-white'
: 'text-fd-navy dark:text-white'
const panelMuted = dark
? 'text-white/50'
: 'text-fd-navy/50 dark:text-white/50'
const optionBase = dark
? 'border border-white/15 hover:border-white/30'
: 'border border-gray-200 hover:border-fd-navy/30 dark:border-white/15 dark:hover:border-white/30'
const optionSelected = dark
? 'border-fd-yellow bg-fd-yellow/10'
: 'border-fd-yellow bg-fd-yellow/10 dark:border-fd-yellow dark:bg-fd-yellow/10'
const radioActive = 'border-fd-yellow bg-fd-yellow'
const radioInactive = dark
? 'border-white/30'
: 'border-gray-300 dark:border-white/30'
const totalQuestions = (questions ?? []).length
const currentQuestion = (questions ?? [])[step]
const hasAnswer = answers[step] !== undefined
/* ── Scoring logic ── */
const calculateResult = useCallback(() => {
const scores: Record<string, number> = {}
;(outcomes ?? []).forEach((o) => { scores[o.key || ''] = 0 })
Object.entries(answers).forEach(([stepStr, optionIndex]) => {
const q = (questions ?? [])[Number(stepStr)]
const option = q?.options?.[optionIndex]
if (option?.outcomeKeys) {
option.outcomeKeys.split(',').forEach((key) => {
const k = key.trim()
if (k in scores) scores[k] += 1
})
}
})
/* Find highest scoring outcome */
let maxKey = ''
let maxScore = -1
Object.entries(scores).forEach(([key, score]) => {
if (score > maxScore) { maxScore = score; maxKey = key }
})
return (outcomes ?? []).find((o) => o.key === maxKey) || (outcomes ?? [])[0]
}, [answers, outcomes, questions])
/* ── Navigation ── */
const animateTransition = (callback: () => void) => {
if (prefersReducedMotion) { callback(); return }
setAnimating(true)
setTimeout(() => { callback(); setAnimating(false) }, 150)
}
const handleStart = () => {
setState('active')
setStep(0)
setAnswers({})
}
const handleSelect = (optionIndex: number) => {
setAnswers((prev) => ({ ...prev, [step]: optionIndex }))
}
const handleNext = () => {
if (!hasAnswer) return
if (step < totalQuestions - 1) {
animateTransition(() => setStep((s) => s + 1))
} else {
animateTransition(() => setState('result'))
}
}
const handleBack = () => {
if (step > 0) {
animateTransition(() => setStep((s) => s - 1))
}
}
const handleRestart = () => {
animateTransition(() => {
setState('idle')
setStep(0)
setAnswers({})
})
}
const handleClose = () => {
setState('idle')
setStep(0)
setAnswers({})
}
/* ── Render: Idle state ── */
if (state === 'idle') {
return (
<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">
<h2 className={`font-joey-heavy text-fd-h1 max-w-[700px] ${hClr}`}>
{heading}
</h2>
{description && (
<p className={`font-joey text-fd-body-lg max-w-[600px] ${bClr} opacity-70`}>
{description}
</p>
)}
<div className="flex items-center gap-3 mt-2">
<QuizIcon className={`w-5 h-5 ${dark ? 'text-white/50' : 'text-fd-navy/50 dark:text-white/50'}`} />
<button
onClick={handleStart}
className={`font-joey-medium text-fd-body underline underline-offset-4 transition-colors ${
dark
? 'text-fd-yellow hover:text-fd-yellow/80'
: 'text-fd-navy hover:text-fd-navy/70 dark:text-fd-yellow dark:hover:text-fd-yellow/80'
}`}
>
{triggerLabel}
</button>
</div>
</div>
</section>
)
}
/* ── Transition classes ── */
const transitionCls = prefersReducedMotion
? ''
: `transition-all duration-150 ${animating ? 'opacity-0 translate-y-2' : 'opacity-100 translate-y-0'}`
/* ── Render: Result state ── */
if (state === 'result') {
const winner = calculateResult()
return (
<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
ref={panelRef}
className={`${panelBg} rounded-2xl md:rounded-3xl p-8 md:p-10 w-full max-w-[520px] ${transitionCls}`}
>
{/* Close */}
<div className="flex justify-between items-center mb-6">
<span className={`font-joey-medium text-fd-small uppercase tracking-wider ${panelMuted}`}>
Resultat
</span>
<button
onClick={handleClose}
className={`w-8 h-8 flex items-center justify-center rounded-full transition-colors ${
dark ? 'hover:bg-white/10 text-white/60' : 'hover:bg-fd-navy/5 text-fd-navy/40 dark:hover:bg-white/10 dark:text-white/60'
}`}
aria-label="Stäng"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path d="M18 6L6 18M6 6l12 12" strokeLinecap="round" />
</svg>
</button>
</div>
{/* Recommendation */}
<div className={`flex flex-col gap-4 ${transitionCls}`}>
<p className={`font-joey-medium text-fd-body ${
dark ? 'text-fd-yellow' : 'text-fd-navy dark:text-fd-yellow'
}`}>
{resultHeading}
</p>
<h3 className={`font-joey-heavy text-fd-h2 leading-tight ${panelText}`}>
{winner?.title}
</h3>
{winner?.description && (
<p className={`font-joey text-fd-body leading-relaxed ${
dark ? 'text-white/70' : 'text-fd-navy/70 dark:text-white/70'
}`}>
{winner.description}
</p>
)}
<div className="flex flex-wrap gap-3 mt-4">
<button
onClick={handleRestart}
className={`px-6 py-3 rounded-full font-joey-medium text-fd-body border transition-colors ${
dark
? 'border-white/20 text-white hover:bg-white/10'
: 'border-gray-300 text-fd-navy hover:bg-fd-navy/5 dark:border-white/20 dark:text-white dark:hover:bg-white/10'
}`}
>
{restartLabel}
</button>
{winner?.ctaText && (
<FDButton href={winner.ctaLink || '/kontakt'} onDark={dark}>
{winner.ctaText}
</FDButton>
)}
</div>
</div>
</div>
</div>
</section>
)
}
/* ── Render: Active (question step) ── */
return (
<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
ref={panelRef}
className={`${panelBg} rounded-2xl md:rounded-3xl p-8 md:p-10 w-full max-w-[520px]`}
>
{/* Header: counter + close */}
<div className="flex justify-between items-center mb-6">
<span className={`font-joey-medium text-fd-small ${panelMuted}`}>
Fråga {String(step + 1).padStart(2, '0')} / {String(totalQuestions).padStart(2, '0')}
</span>
<button
onClick={handleClose}
className={`w-8 h-8 flex items-center justify-center rounded-full transition-colors ${
dark ? 'hover:bg-white/10 text-white/60' : 'hover:bg-fd-navy/5 text-fd-navy/40 dark:hover:bg-white/10 dark:text-white/60'
}`}
aria-label="Stäng"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
<path d="M18 6L6 18M6 6l12 12" strokeLinecap="round" />
</svg>
</button>
</div>
{/* Question */}
<div className={transitionCls}>
<h3 className={`font-joey-bold text-fd-h3 mb-6 leading-snug ${panelText}`}>
{currentQuestion?.question}
</h3>
{/* Options */}
<div className="flex flex-col gap-3">
{currentQuestion?.options?.map((option, i) => {
const isSelected = answers[step] === i
return (
<button
key={i}
onClick={() => handleSelect(i)}
className={`w-full text-left px-5 py-4 rounded-xl flex items-start gap-4 transition-all duration-150 ${
isSelected ? optionSelected : optionBase
}`}
>
{/* Radio circle */}
<span className={`mt-0.5 w-5 h-5 rounded-full border-2 flex-shrink-0 flex items-center justify-center transition-colors ${
isSelected ? radioActive : radioInactive
}`}>
{isSelected && (
<span className="w-2 h-2 rounded-full bg-fd-navy" />
)}
</span>
<div className="flex flex-col gap-0.5">
<span className={`font-joey-medium text-fd-body ${panelText}`}>
{option.label}
</span>
{option.sublabel && (
<span className={`font-joey text-fd-small ${panelMuted}`}>
{option.sublabel}
</span>
)}
</div>
</button>
)
})}
</div>
</div>
{/* Navigation */}
<div className={`flex justify-between items-center mt-8 pt-6 border-t ${
dark ? 'border-white/10' : 'border-gray-200 dark:border-white/10'
}`}>
{step > 0 ? (
<button
onClick={handleBack}
className={`flex items-center gap-2 font-joey-medium text-fd-body transition-colors ${
dark ? 'text-white/60 hover:text-white' : 'text-fd-navy/60 hover:text-fd-navy dark:text-white/60 dark:hover:text-white'
}`}
>
<ArrowLeft className="w-4 h-4" />
{backLabel}
</button>
) : <div />}
<button
onClick={handleNext}
disabled={!hasAnswer}
className={`flex items-center gap-2 px-6 py-2.5 rounded-full font-joey-medium text-fd-body transition-all ${
hasAnswer
? 'bg-fd-yellow text-fd-navy hover:-translate-y-0.5'
: dark
? 'bg-white/10 text-white/30 cursor-not-allowed'
: 'bg-gray-100 text-fd-navy/30 cursor-not-allowed dark:bg-white/10 dark:text-white/30'
}`}
>
{step < totalQuestions - 1 ? nextLabel : 'Visa resultat'}
<ArrowRight className="w-4 h-4" />
</button>
</div>
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,199 @@
import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDQuizBlock: Block = {
slug: 'fdQuiz',
imageURL: '/block-thumbnails/fd-quiz.png',
imageAltText: 'FD Quiz / Behovsanalys',
interfaceName: 'FDQuizBlock',
labels: {
singular: 'FD Quiz / Behovsanalys',
plural: 'FD Quiz / Behovsanalyser',
},
fields: [
{
name: 'heading',
type: 'text',
localized: true,
label: 'Rubrik',
required: true,
defaultValue: 'Vilken tjänst passar dig?',
},
{
name: 'description',
type: 'textarea',
localized: true,
label: 'Beskrivning (valfri)',
},
{
name: 'triggerLabel',
type: 'text',
localized: true,
label: 'Startknapp-text',
defaultValue: 'Starta quiz',
},
/* ── Outcomes — define these FIRST so editors know the keys ── */
{
type: 'collapsible',
label: 'Resultat / rekommendationer',
admin: { initCollapsed: false },
fields: [
{
name: 'outcomes',
type: 'array',
label: 'Möjliga resultat',
minRows: 2,
maxRows: 6,
labels: { singular: 'Resultat', plural: 'Resultat' },
fields: [
{
name: 'key',
type: 'text',
label: 'Nyckel (kort, unik, t.ex. "vdc", "colo", "fiber")',
required: true,
admin: {
description: 'Används i frågornas alternativ. Bara små bokstäver, inga mellanslag.',
},
},
{
name: 'title',
type: 'text',
localized: true,
label: 'Resultatrubrik (t.ex. "Virtuellt Datacenter")',
required: true,
},
{
name: 'description',
type: 'textarea',
localized: true,
label: 'Resultatbeskrivning',
},
{
name: 'ctaText',
type: 'text',
localized: true,
label: 'CTA-knapp text',
defaultValue: 'Läs mer',
},
{
name: 'ctaLink',
type: 'text',
label: 'CTA-knapp länk',
defaultValue: '/kontakt',
},
],
},
],
},
/* ── Questions ── */
{
type: 'collapsible',
label: 'Frågor',
admin: { initCollapsed: false },
fields: [
{
name: 'questions',
type: 'array',
label: 'Frågor',
minRows: 2,
maxRows: 10,
labels: { singular: 'Fråga', plural: 'Frågor' },
fields: [
{
name: 'question',
type: 'text',
localized: true,
label: 'Frågetext',
required: true,
},
{
name: 'options',
type: 'array',
label: 'Svarsalternativ',
minRows: 2,
maxRows: 5,
labels: { singular: 'Alternativ', plural: 'Alternativ' },
fields: [
{
name: 'label',
type: 'text',
localized: true,
label: 'Alternativtext',
required: true,
},
{
name: 'sublabel',
type: 'text',
localized: true,
label: 'Undertext (valfri, visas under alternativet)',
},
{
name: 'outcomeKeys',
type: 'text',
label: 'Resultatnycklar (kommaseparerade, t.ex. "vdc,fiber")',
required: true,
admin: {
description: 'Varje valt alternativ ger +1 poäng till angivna resultatnycklar.',
},
},
],
},
],
},
],
},
/* ── UI labels ── */
{
type: 'collapsible',
label: 'Knappar & etiketter',
admin: { initCollapsed: true },
fields: [
{
name: 'nextLabel',
type: 'text',
localized: true,
label: '"Nästa"-knapptext',
defaultValue: 'Nästa',
},
{
name: 'backLabel',
type: 'text',
localized: true,
label: '"Tillbaka"-knapptext',
defaultValue: 'Tillbaka',
},
{
name: 'resultHeading',
type: 'text',
localized: true,
label: 'Resultatrubrik',
defaultValue: 'Rekommenderat:',
},
{
name: 'restartLabel',
type: 'text',
localized: true,
label: '"Börja om"-knapptext',
defaultValue: 'Börja om',
},
],
},
/* ── Styling ── */
{
name: 'sectionBackground',
type: 'select',
label: 'Sektionsbakgrund',
defaultValue: 'navy',
options: [
{ label: 'Vit', value: 'white' },
{ label: 'Grå', value: 'gray' },
{ label: 'Navy', value: 'navy' },
],
},
anchorField,
],
}

View File

@ -0,0 +1,191 @@
import React from 'react'
import type { FDSpecCardsBlock as Props } from '@/payload-types'
import { FDButton } from '@/components/FDButton'
import {
sectionBg, isExplicitDark, headingColor, bodyColor,
bodySubduedColor, fdCardRadius, fdCardRadiusSm,
} from '@/utilities/fdTheme'
const cardStyleMap: Record<string, {
bg: string; border: string; title: string; body: string
specLabel: string; specValue: string; divider: string; isDark: boolean
}> = {
outlined: {
bg: 'bg-transparent',
border: 'border border-white/15 dark:border-white/15',
title: 'text-white dark:text-white',
body: 'text-white/70 dark:text-white/70',
specLabel: 'text-white/50 dark:text-white/50',
specValue: 'text-white dark:text-white',
divider: 'border-white/10 dark:border-white/10',
isDark: true,
},
navy: {
bg: 'bg-fd-navy dark:bg-white/10',
border: '',
title: 'text-white',
body: 'text-white/70',
specLabel: 'text-white/50',
specValue: 'text-fd-yellow',
divider: 'border-white/10',
isDark: true,
},
gray: {
bg: 'bg-fd-gray-light dark:bg-white/10',
border: '',
title: 'text-fd-navy dark:text-white',
body: 'text-fd-navy/70 dark:text-white/70',
specLabel: 'text-fd-navy/50 dark:text-white/50',
specValue: 'text-fd-navy dark:text-white',
divider: 'border-fd-navy/10 dark:border-white/10',
isDark: false,
},
white: {
bg: 'bg-white dark:bg-white/10 shadow-fd-card dark:shadow-none',
border: '',
title: 'text-fd-navy dark:text-white',
body: 'text-fd-navy/70 dark:text-white/70',
specLabel: 'text-fd-navy/50 dark:text-white/50',
specValue: 'text-fd-navy dark:text-white',
divider: 'border-fd-navy/10 dark:border-white/10',
isDark: false,
},
}
/* Light-section overrides for outlined cards */
const cardStyleMapLight: Record<string, typeof cardStyleMap.outlined> = {
outlined: {
bg: 'bg-transparent',
border: 'border border-gray-200 dark:border-white/15',
title: 'text-fd-navy dark:text-white',
body: 'text-fd-navy/70 dark:text-white/70',
specLabel: 'text-fd-navy/50 dark:text-white/50',
specValue: 'text-fd-navy dark:text-white',
divider: 'border-fd-navy/10 dark:border-white/10',
isDark: false,
},
}
export const FDSpecCardsBlockComponent: React.FC<Props> = ({
heading,
description,
ctaText,
ctaLink,
secondaryCtaText,
secondaryCtaLink,
cards,
layout = 'sideBySide',
cardStyle = 'outlined',
sectionBackground = 'navy',
anchorId,
}) => {
const dark = isExplicitDark(sectionBackground)
const bg = sectionBg(sectionBackground)
const hClr = headingColor(dark)
const bClr = bodySubduedColor(dark)
const style = (!dark && cardStyle === 'outlined')
? cardStyleMapLight.outlined
: (cardStyleMap[cardStyle || 'outlined'] || cardStyleMap.outlined)
const isSideBySide = layout === 'sideBySide' || layout === 'sideBySideReverse'
const isReversed = layout === 'sideBySideReverse'
const cardCount = cards?.length || 1
/* Cards grid: 1 card = 1 col, 2 = 2-col, 3+ = 2-col wrapping */
const cardGridCols = cardCount === 1 ? '' : 'sm:grid-cols-2'
const renderCards = () => (
<div className={`grid grid-cols-1 ${cardGridCols} gap-4 md:gap-5`}>
{cards?.map((card, i) => (
<div
key={i}
className={`${style.bg} ${style.border} ${fdCardRadiusSm} px-7 py-7 md:px-9 md:py-9 flex flex-col gap-4 justify-between`}
>
<div className="flex flex-col gap-3">
<h3 className={`font-joey-bold text-fd-h3 leading-snug ${style.title}`}>
{card.title}
</h3>
{card.description && (
<p className={`font-joey text-fd-body leading-relaxed ${style.body}`}>
{card.description}
</p>
)}
</div>
{(card.specLabel || card.specValue) && (
<div className={`pt-4 mt-auto border-t ${style.divider}`}>
{card.specLabel && (
<span className={`font-joey text-fd-small block mb-1 ${style.specLabel}`}>
{card.specLabel}
</span>
)}
{card.specValue && (
<span className={`font-joey-bold text-fd-h4 ${style.specValue}`}>
{card.specValue}
</span>
)}
</div>
)}
</div>
))}
</div>
)
const renderText = () => (
<div className="flex flex-col gap-5 md:gap-6 justify-center">
<h2 className={`font-joey-heavy text-fd-h1 leading-tight ${hClr}`}>
{heading}
</h2>
{description && (
<p className={`font-joey text-fd-body-lg leading-relaxed ${bClr}`}>
{description}
</p>
)}
{(ctaText || secondaryCtaText) && (
<div className="flex flex-wrap gap-4 pt-2">
{ctaText && (
<FDButton href={ctaLink || '#'} variant="outline" onDark={dark}>
{ctaText}
</FDButton>
)}
{secondaryCtaText && (
<FDButton href={secondaryCtaLink || '#'} variant="primary" onDark={dark}>
{secondaryCtaText}
</FDButton>
)}
</div>
)}
</div>
)
if (!isSideBySide) {
/* Full-width layout — heading on top, cards below */
return (
<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="mb-10 md:mb-14">
{renderText()}
</div>
{renderCards()}
</div>
</section>
)
}
/* Side-by-side layout */
return (
<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={`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]">
{renderText()}
</div>
<div className="flex-[1.5] min-w-0">
{renderCards()}
</div>
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,125 @@
import type { Block } from 'payload'
import { anchorField } from '@/fields/anchorField'
export const FDSpecCardsBlock: Block = {
slug: 'fdSpecCards',
imageURL: '/block-thumbnails/fd-spec-cards.png',
imageAltText: 'FD Specifikationskort',
interfaceName: 'FDSpecCardsBlock',
labels: {
singular: 'FD Specifikationskort',
plural: 'FD Specifikationskort',
},
fields: [
{
name: 'heading',
type: 'text',
localized: true,
label: 'Rubrik',
required: true,
},
{
name: 'description',
type: 'textarea',
localized: true,
label: 'Beskrivning (valfri)',
},
{
name: 'ctaText',
type: 'text',
localized: true,
label: 'CTA-knapp text (valfri)',
},
{
name: 'ctaLink',
type: 'text',
label: 'CTA-knapp länk',
defaultValue: '/kontakt',
},
{
name: 'secondaryCtaText',
type: 'text',
localized: true,
label: 'Sekundär CTA-text (valfri)',
},
{
name: 'secondaryCtaLink',
type: 'text',
label: 'Sekundär CTA-länk',
},
{
name: 'cards',
type: 'array',
label: 'Kort',
minRows: 1,
maxRows: 4,
labels: {
singular: 'Kort',
plural: 'Kort',
},
fields: [
{
name: 'title',
type: 'text',
localized: true,
label: 'Korttitel',
required: true,
},
{
name: 'description',
type: 'textarea',
localized: true,
label: 'Kortbeskrivning',
},
{
name: 'specLabel',
type: 'text',
localized: true,
label: 'Spec-etikett (t.ex. "Kostnad", "Hastighet")',
},
{
name: 'specValue',
type: 'text',
localized: true,
label: 'Spec-värde (t.ex. "10 Gbit/s", "$12.50 / 1K")',
},
],
},
{
name: 'layout',
type: 'select',
label: 'Layout',
defaultValue: 'sideBySide',
options: [
{ label: 'Text vänster, kort höger', value: 'sideBySide' },
{ label: 'Text höger, kort vänster', value: 'sideBySideReverse' },
{ label: 'Kort i helbredd (utan sidotext)', value: 'fullWidth' },
],
},
{
name: 'cardStyle',
type: 'select',
label: 'Kortstil',
defaultValue: 'outlined',
options: [
{ label: 'Kantlinje', value: 'outlined' },
{ label: 'Navy', value: 'navy' },
{ label: 'Grå', value: 'gray' },
{ label: 'Vit (med skugga)', value: 'white' },
],
},
{
name: 'sectionBackground',
type: 'select',
label: 'Sektionsbakgrund',
defaultValue: 'navy',
options: [
{ label: 'Vit', value: 'white' },
{ label: 'Grå', value: 'gray' },
{ label: 'Navy', value: 'navy' },
{ label: 'Navy gradient', value: 'navyGradient' },
],
},
anchorField,
],
}

View File

@ -35,6 +35,9 @@ import { FDCtaBannerBlockComponent } from './FDCtaBannerBlock/Component'
import { FDTestimonialBlockComponent } from './FDTestimonialBlock/Component'
import { FDTeamBlockComponent } from './FDTeamBlock/Component'
import { FDServiceCalculatorBlockComponent } from '@/blocks/FDServiceCalculatorBlock/Component'
import { FDLinkCardsBlockComponent } from '@/blocks/FDLinkCardsBlock/Component'
import { FDSpecCardsBlockComponent } from '@/blocks/FDSpecCardsBlock/Component'
import { FDQuizBlockComponent } from '@/blocks/FDQuizBlock/Component'
const blockComponents: Record<string, React.FC<any>> = {
formBlock: FormBlock,
@ -70,6 +73,9 @@ const blockComponents: Record<string, React.FC<any>> = {
fdTestimonial: FDTestimonialBlockComponent,
fdTeam: FDTeamBlockComponent,
fdServiceCalc: FDServiceCalculatorBlockComponent,
fdLinkCards: FDLinkCardsBlockComponent,
fdSpecCards: FDSpecCardsBlockComponent,
fdQuiz: FDQuizBlockComponent,
}
/**

View File

@ -38,6 +38,9 @@ import { FDCtaBannerBlock } from '../../blocks/FDCtaBannerBlock/config'
import { FDTestimonialBlock } from '../../blocks/FDTestimonialBlock/config'
import { FDTeamBlock } from '../../blocks/FDTeamBlock/config'
import { FDServiceCalculatorBlock } from '../../blocks/FDServiceCalculatorBlock/config'
import { FDLinkCardsBlock } from '../../blocks/FDLinkCardsBlock/config'
import { FDSpecCardsBlock } from '../../blocks/FDSpecCardsBlock/config'
import { FDQuizBlock } from '../../blocks/FDQuizBlock/config'
import {
MetaDescriptionField,
@ -134,6 +137,9 @@ export const Pages: CollectionConfig<'pages'> = {
FDCtaBannerBlock,
FDTestimonialBlock,
FDTeamBlock,
FDLinkCardsBlock,
FDSpecCardsBlock,
FDQuizBlock,
],
required: true,
admin: {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,461 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TYPE "public"."enum_pages_blocks_fd_link_cards_columns" AS ENUM('2', '3', '4');
CREATE TYPE "public"."enum_pages_blocks_fd_link_cards_card_style" AS ENUM('outlined', 'navy', 'gray', 'yellow');
CREATE TYPE "public"."enum_pages_blocks_fd_link_cards_section_background" AS ENUM('white', 'gray', 'navy');
CREATE TYPE "public"."enum_pages_blocks_fd_spec_cards_layout" AS ENUM('sideBySide', 'sideBySideReverse', 'fullWidth');
CREATE TYPE "public"."enum_pages_blocks_fd_spec_cards_card_style" AS ENUM('outlined', 'navy', 'gray', 'white');
CREATE TYPE "public"."enum_pages_blocks_fd_spec_cards_section_background" AS ENUM('white', 'gray', 'navy', 'navyGradient');
CREATE TYPE "public"."enum_pages_blocks_fd_quiz_section_background" AS ENUM('white', 'gray', 'navy');
CREATE TYPE "public"."enum__pages_v_blocks_fd_link_cards_columns" AS ENUM('2', '3', '4');
CREATE TYPE "public"."enum__pages_v_blocks_fd_link_cards_card_style" AS ENUM('outlined', 'navy', 'gray', 'yellow');
CREATE TYPE "public"."enum__pages_v_blocks_fd_link_cards_section_background" AS ENUM('white', 'gray', 'navy');
CREATE TYPE "public"."enum__pages_v_blocks_fd_spec_cards_layout" AS ENUM('sideBySide', 'sideBySideReverse', 'fullWidth');
CREATE TYPE "public"."enum__pages_v_blocks_fd_spec_cards_card_style" AS ENUM('outlined', 'navy', 'gray', 'white');
CREATE TYPE "public"."enum__pages_v_blocks_fd_spec_cards_section_background" AS ENUM('white', 'gray', 'navy', 'navyGradient');
CREATE TYPE "public"."enum__pages_v_blocks_fd_quiz_section_background" AS ENUM('white', 'gray', 'navy');
CREATE TABLE "pages_blocks_fd_link_cards_cards" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"link_url" varchar,
"link_icon_id" integer
);
CREATE TABLE "pages_blocks_fd_link_cards_cards_locales" (
"title" varchar,
"link_label" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "pages_blocks_fd_link_cards" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"_path" text NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"icon_id" integer,
"columns" "enum_pages_blocks_fd_link_cards_columns" DEFAULT '3',
"card_style" "enum_pages_blocks_fd_link_cards_card_style" DEFAULT 'outlined',
"section_background" "enum_pages_blocks_fd_link_cards_section_background" DEFAULT 'navy',
"anchor_id" varchar,
"block_name" varchar
);
CREATE TABLE "pages_blocks_fd_link_cards_locales" (
"heading" varchar,
"description" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "pages_blocks_fd_spec_cards_cards" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL
);
CREATE TABLE "pages_blocks_fd_spec_cards_cards_locales" (
"title" varchar,
"description" varchar,
"spec_label" varchar,
"spec_value" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "pages_blocks_fd_spec_cards" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"_path" text NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"cta_link" varchar DEFAULT '/kontakt',
"secondary_cta_link" varchar,
"layout" "enum_pages_blocks_fd_spec_cards_layout" DEFAULT 'sideBySide',
"card_style" "enum_pages_blocks_fd_spec_cards_card_style" DEFAULT 'outlined',
"section_background" "enum_pages_blocks_fd_spec_cards_section_background" DEFAULT 'navy',
"anchor_id" varchar,
"block_name" varchar
);
CREATE TABLE "pages_blocks_fd_spec_cards_locales" (
"heading" varchar,
"description" varchar,
"cta_text" varchar,
"secondary_cta_text" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "pages_blocks_fd_quiz_outcomes" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"key" varchar,
"cta_link" varchar DEFAULT '/kontakt'
);
CREATE TABLE "pages_blocks_fd_quiz_outcomes_locales" (
"title" varchar,
"description" varchar,
"cta_text" varchar DEFAULT 'Läs mer',
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "pages_blocks_fd_quiz_questions_options" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"outcome_keys" varchar
);
CREATE TABLE "pages_blocks_fd_quiz_questions_options_locales" (
"label" varchar,
"sublabel" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "pages_blocks_fd_quiz_questions" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL
);
CREATE TABLE "pages_blocks_fd_quiz_questions_locales" (
"question" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "pages_blocks_fd_quiz" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"_path" text NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"section_background" "enum_pages_blocks_fd_quiz_section_background" DEFAULT 'navy',
"anchor_id" varchar,
"block_name" varchar
);
CREATE TABLE "pages_blocks_fd_quiz_locales" (
"heading" varchar DEFAULT 'Vilken tjänst passar dig?',
"description" varchar,
"trigger_label" varchar DEFAULT 'Starta quiz',
"next_label" varchar DEFAULT 'Nästa',
"back_label" varchar DEFAULT 'Tillbaka',
"result_heading" varchar DEFAULT 'Rekommenderat:',
"restart_label" varchar DEFAULT 'Börja om',
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" varchar NOT NULL
);
CREATE TABLE "_pages_v_blocks_fd_link_cards_cards" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"link_url" varchar,
"link_icon_id" integer,
"_uuid" varchar
);
CREATE TABLE "_pages_v_blocks_fd_link_cards_cards_locales" (
"title" varchar,
"link_label" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
CREATE TABLE "_pages_v_blocks_fd_link_cards" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"_path" text NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"icon_id" integer,
"columns" "enum__pages_v_blocks_fd_link_cards_columns" DEFAULT '3',
"card_style" "enum__pages_v_blocks_fd_link_cards_card_style" DEFAULT 'outlined',
"section_background" "enum__pages_v_blocks_fd_link_cards_section_background" DEFAULT 'navy',
"anchor_id" varchar,
"_uuid" varchar,
"block_name" varchar
);
CREATE TABLE "_pages_v_blocks_fd_link_cards_locales" (
"heading" varchar,
"description" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
CREATE TABLE "_pages_v_blocks_fd_spec_cards_cards" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"_uuid" varchar
);
CREATE TABLE "_pages_v_blocks_fd_spec_cards_cards_locales" (
"title" varchar,
"description" varchar,
"spec_label" varchar,
"spec_value" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
CREATE TABLE "_pages_v_blocks_fd_spec_cards" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"_path" text NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"cta_link" varchar DEFAULT '/kontakt',
"secondary_cta_link" varchar,
"layout" "enum__pages_v_blocks_fd_spec_cards_layout" DEFAULT 'sideBySide',
"card_style" "enum__pages_v_blocks_fd_spec_cards_card_style" DEFAULT 'outlined',
"section_background" "enum__pages_v_blocks_fd_spec_cards_section_background" DEFAULT 'navy',
"anchor_id" varchar,
"_uuid" varchar,
"block_name" varchar
);
CREATE TABLE "_pages_v_blocks_fd_spec_cards_locales" (
"heading" varchar,
"description" varchar,
"cta_text" varchar,
"secondary_cta_text" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
CREATE TABLE "_pages_v_blocks_fd_quiz_outcomes" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"key" varchar,
"cta_link" varchar DEFAULT '/kontakt',
"_uuid" varchar
);
CREATE TABLE "_pages_v_blocks_fd_quiz_outcomes_locales" (
"title" varchar,
"description" varchar,
"cta_text" varchar DEFAULT 'Läs mer',
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
CREATE TABLE "_pages_v_blocks_fd_quiz_questions_options" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"outcome_keys" varchar,
"_uuid" varchar
);
CREATE TABLE "_pages_v_blocks_fd_quiz_questions_options_locales" (
"label" varchar,
"sublabel" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
CREATE TABLE "_pages_v_blocks_fd_quiz_questions" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"_uuid" varchar
);
CREATE TABLE "_pages_v_blocks_fd_quiz_questions_locales" (
"question" varchar,
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
CREATE TABLE "_pages_v_blocks_fd_quiz" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"_path" text NOT NULL,
"id" serial PRIMARY KEY NOT NULL,
"section_background" "enum__pages_v_blocks_fd_quiz_section_background" DEFAULT 'navy',
"anchor_id" varchar,
"_uuid" varchar,
"block_name" varchar
);
CREATE TABLE "_pages_v_blocks_fd_quiz_locales" (
"heading" varchar DEFAULT 'Vilken tjänst passar dig?',
"description" varchar,
"trigger_label" varchar DEFAULT 'Starta quiz',
"next_label" varchar DEFAULT 'Nästa',
"back_label" varchar DEFAULT 'Tillbaka',
"result_heading" varchar DEFAULT 'Rekommenderat:',
"restart_label" varchar DEFAULT 'Börja om',
"id" serial PRIMARY KEY NOT NULL,
"_locale" "_locales" NOT NULL,
"_parent_id" integer NOT NULL
);
ALTER TABLE "pages_blocks_fd_link_cards_cards" ADD CONSTRAINT "pages_blocks_fd_link_cards_cards_link_icon_id_media_id_fk" FOREIGN KEY ("link_icon_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_link_cards_cards" ADD CONSTRAINT "pages_blocks_fd_link_cards_cards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_link_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_link_cards_cards_locales" ADD CONSTRAINT "pages_blocks_fd_link_cards_cards_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_link_cards_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_link_cards" ADD CONSTRAINT "pages_blocks_fd_link_cards_icon_id_media_id_fk" FOREIGN KEY ("icon_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_link_cards" ADD CONSTRAINT "pages_blocks_fd_link_cards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_link_cards_locales" ADD CONSTRAINT "pages_blocks_fd_link_cards_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_link_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_spec_cards_cards" ADD CONSTRAINT "pages_blocks_fd_spec_cards_cards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_spec_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_spec_cards_cards_locales" ADD CONSTRAINT "pages_blocks_fd_spec_cards_cards_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_spec_cards_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_spec_cards" ADD CONSTRAINT "pages_blocks_fd_spec_cards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_spec_cards_locales" ADD CONSTRAINT "pages_blocks_fd_spec_cards_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_spec_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_quiz_outcomes" ADD CONSTRAINT "pages_blocks_fd_quiz_outcomes_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_quiz"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_quiz_outcomes_locales" ADD CONSTRAINT "pages_blocks_fd_quiz_outcomes_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_quiz_outcomes"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_quiz_questions_options" ADD CONSTRAINT "pages_blocks_fd_quiz_questions_options_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_quiz_questions"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_quiz_questions_options_locales" ADD CONSTRAINT "pages_blocks_fd_quiz_questions_options_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_quiz_questions_options"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_quiz_questions" ADD CONSTRAINT "pages_blocks_fd_quiz_questions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_quiz"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_quiz_questions_locales" ADD CONSTRAINT "pages_blocks_fd_quiz_questions_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_quiz_questions"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_quiz" ADD CONSTRAINT "pages_blocks_fd_quiz_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "pages_blocks_fd_quiz_locales" ADD CONSTRAINT "pages_blocks_fd_quiz_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_fd_quiz"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_link_cards_cards" ADD CONSTRAINT "_pages_v_blocks_fd_link_cards_cards_link_icon_id_media_id_fk" FOREIGN KEY ("link_icon_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_link_cards_cards" ADD CONSTRAINT "_pages_v_blocks_fd_link_cards_cards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_link_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_link_cards_cards_locales" ADD CONSTRAINT "_pages_v_blocks_fd_link_cards_cards_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_link_cards_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_link_cards" ADD CONSTRAINT "_pages_v_blocks_fd_link_cards_icon_id_media_id_fk" FOREIGN KEY ("icon_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_link_cards" ADD CONSTRAINT "_pages_v_blocks_fd_link_cards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_link_cards_locales" ADD CONSTRAINT "_pages_v_blocks_fd_link_cards_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_link_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_spec_cards_cards" ADD CONSTRAINT "_pages_v_blocks_fd_spec_cards_cards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_spec_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_spec_cards_cards_locales" ADD CONSTRAINT "_pages_v_blocks_fd_spec_cards_cards_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_spec_cards_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_spec_cards" ADD CONSTRAINT "_pages_v_blocks_fd_spec_cards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_spec_cards_locales" ADD CONSTRAINT "_pages_v_blocks_fd_spec_cards_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_spec_cards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_quiz_outcomes" ADD CONSTRAINT "_pages_v_blocks_fd_quiz_outcomes_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_quiz"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_quiz_outcomes_locales" ADD CONSTRAINT "_pages_v_blocks_fd_quiz_outcomes_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_quiz_outcomes"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_quiz_questions_options" ADD CONSTRAINT "_pages_v_blocks_fd_quiz_questions_options_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_quiz_questions"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_quiz_questions_options_locales" ADD CONSTRAINT "_pages_v_blocks_fd_quiz_questions_options_locales_parent__fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_quiz_questions_options"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_quiz_questions" ADD CONSTRAINT "_pages_v_blocks_fd_quiz_questions_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_quiz"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_quiz_questions_locales" ADD CONSTRAINT "_pages_v_blocks_fd_quiz_questions_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_quiz_questions"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_quiz" ADD CONSTRAINT "_pages_v_blocks_fd_quiz_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "_pages_v_blocks_fd_quiz_locales" ADD CONSTRAINT "_pages_v_blocks_fd_quiz_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."_pages_v_blocks_fd_quiz"("id") ON DELETE cascade ON UPDATE no action;
CREATE INDEX "pages_blocks_fd_link_cards_cards_order_idx" ON "pages_blocks_fd_link_cards_cards" USING btree ("_order");
CREATE INDEX "pages_blocks_fd_link_cards_cards_parent_id_idx" ON "pages_blocks_fd_link_cards_cards" USING btree ("_parent_id");
CREATE INDEX "pages_blocks_fd_link_cards_cards_link_icon_idx" ON "pages_blocks_fd_link_cards_cards" USING btree ("link_icon_id");
CREATE UNIQUE INDEX "pages_blocks_fd_link_cards_cards_locales_locale_parent_id_un" ON "pages_blocks_fd_link_cards_cards_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "pages_blocks_fd_link_cards_order_idx" ON "pages_blocks_fd_link_cards" USING btree ("_order");
CREATE INDEX "pages_blocks_fd_link_cards_parent_id_idx" ON "pages_blocks_fd_link_cards" USING btree ("_parent_id");
CREATE INDEX "pages_blocks_fd_link_cards_path_idx" ON "pages_blocks_fd_link_cards" USING btree ("_path");
CREATE INDEX "pages_blocks_fd_link_cards_icon_idx" ON "pages_blocks_fd_link_cards" USING btree ("icon_id");
CREATE UNIQUE INDEX "pages_blocks_fd_link_cards_locales_locale_parent_id_unique" ON "pages_blocks_fd_link_cards_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "pages_blocks_fd_spec_cards_cards_order_idx" ON "pages_blocks_fd_spec_cards_cards" USING btree ("_order");
CREATE INDEX "pages_blocks_fd_spec_cards_cards_parent_id_idx" ON "pages_blocks_fd_spec_cards_cards" USING btree ("_parent_id");
CREATE UNIQUE INDEX "pages_blocks_fd_spec_cards_cards_locales_locale_parent_id_un" ON "pages_blocks_fd_spec_cards_cards_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "pages_blocks_fd_spec_cards_order_idx" ON "pages_blocks_fd_spec_cards" USING btree ("_order");
CREATE INDEX "pages_blocks_fd_spec_cards_parent_id_idx" ON "pages_blocks_fd_spec_cards" USING btree ("_parent_id");
CREATE INDEX "pages_blocks_fd_spec_cards_path_idx" ON "pages_blocks_fd_spec_cards" USING btree ("_path");
CREATE UNIQUE INDEX "pages_blocks_fd_spec_cards_locales_locale_parent_id_unique" ON "pages_blocks_fd_spec_cards_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "pages_blocks_fd_quiz_outcomes_order_idx" ON "pages_blocks_fd_quiz_outcomes" USING btree ("_order");
CREATE INDEX "pages_blocks_fd_quiz_outcomes_parent_id_idx" ON "pages_blocks_fd_quiz_outcomes" USING btree ("_parent_id");
CREATE UNIQUE INDEX "pages_blocks_fd_quiz_outcomes_locales_locale_parent_id_uniqu" ON "pages_blocks_fd_quiz_outcomes_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "pages_blocks_fd_quiz_questions_options_order_idx" ON "pages_blocks_fd_quiz_questions_options" USING btree ("_order");
CREATE INDEX "pages_blocks_fd_quiz_questions_options_parent_id_idx" ON "pages_blocks_fd_quiz_questions_options" USING btree ("_parent_id");
CREATE UNIQUE INDEX "pages_blocks_fd_quiz_questions_options_locales_locale_parent" ON "pages_blocks_fd_quiz_questions_options_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "pages_blocks_fd_quiz_questions_order_idx" ON "pages_blocks_fd_quiz_questions" USING btree ("_order");
CREATE INDEX "pages_blocks_fd_quiz_questions_parent_id_idx" ON "pages_blocks_fd_quiz_questions" USING btree ("_parent_id");
CREATE UNIQUE INDEX "pages_blocks_fd_quiz_questions_locales_locale_parent_id_uniq" ON "pages_blocks_fd_quiz_questions_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "pages_blocks_fd_quiz_order_idx" ON "pages_blocks_fd_quiz" USING btree ("_order");
CREATE INDEX "pages_blocks_fd_quiz_parent_id_idx" ON "pages_blocks_fd_quiz" USING btree ("_parent_id");
CREATE INDEX "pages_blocks_fd_quiz_path_idx" ON "pages_blocks_fd_quiz" USING btree ("_path");
CREATE UNIQUE INDEX "pages_blocks_fd_quiz_locales_locale_parent_id_unique" ON "pages_blocks_fd_quiz_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "_pages_v_blocks_fd_link_cards_cards_order_idx" ON "_pages_v_blocks_fd_link_cards_cards" USING btree ("_order");
CREATE INDEX "_pages_v_blocks_fd_link_cards_cards_parent_id_idx" ON "_pages_v_blocks_fd_link_cards_cards" USING btree ("_parent_id");
CREATE INDEX "_pages_v_blocks_fd_link_cards_cards_link_icon_idx" ON "_pages_v_blocks_fd_link_cards_cards" USING btree ("link_icon_id");
CREATE UNIQUE INDEX "_pages_v_blocks_fd_link_cards_cards_locales_locale_parent_id" ON "_pages_v_blocks_fd_link_cards_cards_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "_pages_v_blocks_fd_link_cards_order_idx" ON "_pages_v_blocks_fd_link_cards" USING btree ("_order");
CREATE INDEX "_pages_v_blocks_fd_link_cards_parent_id_idx" ON "_pages_v_blocks_fd_link_cards" USING btree ("_parent_id");
CREATE INDEX "_pages_v_blocks_fd_link_cards_path_idx" ON "_pages_v_blocks_fd_link_cards" USING btree ("_path");
CREATE INDEX "_pages_v_blocks_fd_link_cards_icon_idx" ON "_pages_v_blocks_fd_link_cards" USING btree ("icon_id");
CREATE UNIQUE INDEX "_pages_v_blocks_fd_link_cards_locales_locale_parent_id_uniqu" ON "_pages_v_blocks_fd_link_cards_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "_pages_v_blocks_fd_spec_cards_cards_order_idx" ON "_pages_v_blocks_fd_spec_cards_cards" USING btree ("_order");
CREATE INDEX "_pages_v_blocks_fd_spec_cards_cards_parent_id_idx" ON "_pages_v_blocks_fd_spec_cards_cards" USING btree ("_parent_id");
CREATE UNIQUE INDEX "_pages_v_blocks_fd_spec_cards_cards_locales_locale_parent_id" ON "_pages_v_blocks_fd_spec_cards_cards_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "_pages_v_blocks_fd_spec_cards_order_idx" ON "_pages_v_blocks_fd_spec_cards" USING btree ("_order");
CREATE INDEX "_pages_v_blocks_fd_spec_cards_parent_id_idx" ON "_pages_v_blocks_fd_spec_cards" USING btree ("_parent_id");
CREATE INDEX "_pages_v_blocks_fd_spec_cards_path_idx" ON "_pages_v_blocks_fd_spec_cards" USING btree ("_path");
CREATE UNIQUE INDEX "_pages_v_blocks_fd_spec_cards_locales_locale_parent_id_uniqu" ON "_pages_v_blocks_fd_spec_cards_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "_pages_v_blocks_fd_quiz_outcomes_order_idx" ON "_pages_v_blocks_fd_quiz_outcomes" USING btree ("_order");
CREATE INDEX "_pages_v_blocks_fd_quiz_outcomes_parent_id_idx" ON "_pages_v_blocks_fd_quiz_outcomes" USING btree ("_parent_id");
CREATE UNIQUE INDEX "_pages_v_blocks_fd_quiz_outcomes_locales_locale_parent_id_un" ON "_pages_v_blocks_fd_quiz_outcomes_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "_pages_v_blocks_fd_quiz_questions_options_order_idx" ON "_pages_v_blocks_fd_quiz_questions_options" USING btree ("_order");
CREATE INDEX "_pages_v_blocks_fd_quiz_questions_options_parent_id_idx" ON "_pages_v_blocks_fd_quiz_questions_options" USING btree ("_parent_id");
CREATE UNIQUE INDEX "_pages_v_blocks_fd_quiz_questions_options_locales_locale_par" ON "_pages_v_blocks_fd_quiz_questions_options_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "_pages_v_blocks_fd_quiz_questions_order_idx" ON "_pages_v_blocks_fd_quiz_questions" USING btree ("_order");
CREATE INDEX "_pages_v_blocks_fd_quiz_questions_parent_id_idx" ON "_pages_v_blocks_fd_quiz_questions" USING btree ("_parent_id");
CREATE UNIQUE INDEX "_pages_v_blocks_fd_quiz_questions_locales_locale_parent_id_u" ON "_pages_v_blocks_fd_quiz_questions_locales" USING btree ("_locale","_parent_id");
CREATE INDEX "_pages_v_blocks_fd_quiz_order_idx" ON "_pages_v_blocks_fd_quiz" USING btree ("_order");
CREATE INDEX "_pages_v_blocks_fd_quiz_parent_id_idx" ON "_pages_v_blocks_fd_quiz" USING btree ("_parent_id");
CREATE INDEX "_pages_v_blocks_fd_quiz_path_idx" ON "_pages_v_blocks_fd_quiz" USING btree ("_path");
CREATE UNIQUE INDEX "_pages_v_blocks_fd_quiz_locales_locale_parent_id_unique" ON "_pages_v_blocks_fd_quiz_locales" USING btree ("_locale","_parent_id");`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
DROP TABLE "pages_blocks_fd_link_cards_cards" CASCADE;
DROP TABLE "pages_blocks_fd_link_cards_cards_locales" CASCADE;
DROP TABLE "pages_blocks_fd_link_cards" CASCADE;
DROP TABLE "pages_blocks_fd_link_cards_locales" CASCADE;
DROP TABLE "pages_blocks_fd_spec_cards_cards" CASCADE;
DROP TABLE "pages_blocks_fd_spec_cards_cards_locales" CASCADE;
DROP TABLE "pages_blocks_fd_spec_cards" CASCADE;
DROP TABLE "pages_blocks_fd_spec_cards_locales" CASCADE;
DROP TABLE "pages_blocks_fd_quiz_outcomes" CASCADE;
DROP TABLE "pages_blocks_fd_quiz_outcomes_locales" CASCADE;
DROP TABLE "pages_blocks_fd_quiz_questions_options" CASCADE;
DROP TABLE "pages_blocks_fd_quiz_questions_options_locales" CASCADE;
DROP TABLE "pages_blocks_fd_quiz_questions" CASCADE;
DROP TABLE "pages_blocks_fd_quiz_questions_locales" CASCADE;
DROP TABLE "pages_blocks_fd_quiz" CASCADE;
DROP TABLE "pages_blocks_fd_quiz_locales" CASCADE;
DROP TABLE "_pages_v_blocks_fd_link_cards_cards" CASCADE;
DROP TABLE "_pages_v_blocks_fd_link_cards_cards_locales" CASCADE;
DROP TABLE "_pages_v_blocks_fd_link_cards" CASCADE;
DROP TABLE "_pages_v_blocks_fd_link_cards_locales" CASCADE;
DROP TABLE "_pages_v_blocks_fd_spec_cards_cards" CASCADE;
DROP TABLE "_pages_v_blocks_fd_spec_cards_cards_locales" CASCADE;
DROP TABLE "_pages_v_blocks_fd_spec_cards" CASCADE;
DROP TABLE "_pages_v_blocks_fd_spec_cards_locales" CASCADE;
DROP TABLE "_pages_v_blocks_fd_quiz_outcomes" CASCADE;
DROP TABLE "_pages_v_blocks_fd_quiz_outcomes_locales" CASCADE;
DROP TABLE "_pages_v_blocks_fd_quiz_questions_options" CASCADE;
DROP TABLE "_pages_v_blocks_fd_quiz_questions_options_locales" CASCADE;
DROP TABLE "_pages_v_blocks_fd_quiz_questions" CASCADE;
DROP TABLE "_pages_v_blocks_fd_quiz_questions_locales" CASCADE;
DROP TABLE "_pages_v_blocks_fd_quiz" CASCADE;
DROP TABLE "_pages_v_blocks_fd_quiz_locales" CASCADE;
DROP TYPE "public"."enum_pages_blocks_fd_link_cards_columns";
DROP TYPE "public"."enum_pages_blocks_fd_link_cards_card_style";
DROP TYPE "public"."enum_pages_blocks_fd_link_cards_section_background";
DROP TYPE "public"."enum_pages_blocks_fd_spec_cards_layout";
DROP TYPE "public"."enum_pages_blocks_fd_spec_cards_card_style";
DROP TYPE "public"."enum_pages_blocks_fd_spec_cards_section_background";
DROP TYPE "public"."enum_pages_blocks_fd_quiz_section_background";
DROP TYPE "public"."enum__pages_v_blocks_fd_link_cards_columns";
DROP TYPE "public"."enum__pages_v_blocks_fd_link_cards_card_style";
DROP TYPE "public"."enum__pages_v_blocks_fd_link_cards_section_background";
DROP TYPE "public"."enum__pages_v_blocks_fd_spec_cards_layout";
DROP TYPE "public"."enum__pages_v_blocks_fd_spec_cards_card_style";
DROP TYPE "public"."enum__pages_v_blocks_fd_spec_cards_section_background";
DROP TYPE "public"."enum__pages_v_blocks_fd_quiz_section_background";`)
}

View File

@ -2,6 +2,7 @@ import * as migration_20260224_091812_add_anchor_links from './20260224_091812_a
import * as migration_20260224_133833 from './20260224_133833';
import * as migration_20260226_095439 from './20260226_095439';
import * as migration_20260302_145030 from './20260302_145030';
import * as migration_20260304_194657 from './20260304_194657';
export const migrations = [
{
@ -22,6 +23,11 @@ export const migrations = [
{
up: migration_20260302_145030.up,
down: migration_20260302_145030.down,
name: '20260302_145030'
name: '20260302_145030',
},
{
up: migration_20260304_194657.up,
down: migration_20260304_194657.down,
name: '20260304_194657'
},
];

View File

@ -188,6 +188,9 @@ export interface Page {
| FDCtaBannerBlock
| FDTestimonialBlock
| FDTeamBlock
| FDLinkCardsBlock
| FDSpecCardsBlock
| FDQuizBlock
)[];
meta?: {
title?: string | null;
@ -1506,6 +1509,116 @@ export interface FDTeamBlock {
blockName?: string | null;
blockType: 'fdTeam';
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "FDLinkCardsBlock".
*/
export interface FDLinkCardsBlock {
icon?: (number | null) | Media;
heading: string;
description?: string | null;
cards?:
| {
title: string;
linkLabel: string;
linkUrl: string;
linkIcon?: (number | null) | Media;
id?: string | null;
}[]
| null;
columns?: ('2' | '3' | '4') | null;
cardStyle?: ('outlined' | 'navy' | 'gray' | 'yellow') | null;
sectionBackground?: ('white' | 'gray' | 'navy') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'fdLinkCards';
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "FDSpecCardsBlock".
*/
export interface FDSpecCardsBlock {
heading: string;
description?: string | null;
ctaText?: string | null;
ctaLink?: string | null;
secondaryCtaText?: string | null;
secondaryCtaLink?: string | null;
cards?:
| {
title: string;
description?: string | null;
specLabel?: string | null;
specValue?: string | null;
id?: string | null;
}[]
| null;
layout?: ('sideBySide' | 'sideBySideReverse' | 'fullWidth') | null;
cardStyle?: ('outlined' | 'navy' | 'gray' | 'white') | null;
sectionBackground?: ('white' | 'gray' | 'navy' | 'navyGradient') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'fdSpecCards';
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "FDQuizBlock".
*/
export interface FDQuizBlock {
heading: string;
description?: string | null;
triggerLabel?: string | null;
outcomes?:
| {
/**
* Används i frågornas alternativ. Bara små bokstäver, inga mellanslag.
*/
key: string;
title: string;
description?: string | null;
ctaText?: string | null;
ctaLink?: string | null;
id?: string | null;
}[]
| null;
questions?:
| {
question: string;
options?:
| {
label: string;
sublabel?: string | null;
/**
* Varje valt alternativ ger +1 poäng till angivna resultatnycklar.
*/
outcomeKeys: string;
id?: string | null;
}[]
| null;
id?: string | null;
}[]
| null;
nextLabel?: string | null;
backLabel?: string | null;
resultHeading?: string | null;
restartLabel?: string | null;
sectionBackground?: ('white' | 'gray' | 'navy') | null;
/**
* Valfritt. Används för att länka direkt till denna sektion, t.ex. "priser" ger /sida#priser. Använd bara små bokstäver, siffror och bindestreck.
*/
anchorId?: string | null;
id?: string | null;
blockName?: string | null;
blockType: 'fdQuiz';
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts".
@ -1886,6 +1999,9 @@ export interface PagesSelect<T extends boolean = true> {
fdCtaBanner?: T | FDCtaBannerBlockSelect<T>;
fdTestimonial?: T | FDTestimonialBlockSelect<T>;
fdTeam?: T | FDTeamBlockSelect<T>;
fdLinkCards?: T | FDLinkCardsBlockSelect<T>;
fdSpecCards?: T | FDSpecCardsBlockSelect<T>;
fdQuiz?: T | FDQuizBlockSelect<T>;
};
meta?:
| T
@ -2612,6 +2728,98 @@ export interface FDTeamBlockSelect<T extends boolean = true> {
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "FDLinkCardsBlock_select".
*/
export interface FDLinkCardsBlockSelect<T extends boolean = true> {
icon?: T;
heading?: T;
description?: T;
cards?:
| T
| {
title?: T;
linkLabel?: T;
linkUrl?: T;
linkIcon?: T;
id?: T;
};
columns?: T;
cardStyle?: T;
sectionBackground?: T;
anchorId?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "FDSpecCardsBlock_select".
*/
export interface FDSpecCardsBlockSelect<T extends boolean = true> {
heading?: T;
description?: T;
ctaText?: T;
ctaLink?: T;
secondaryCtaText?: T;
secondaryCtaLink?: T;
cards?:
| T
| {
title?: T;
description?: T;
specLabel?: T;
specValue?: T;
id?: T;
};
layout?: T;
cardStyle?: T;
sectionBackground?: T;
anchorId?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "FDQuizBlock_select".
*/
export interface FDQuizBlockSelect<T extends boolean = true> {
heading?: T;
description?: T;
triggerLabel?: T;
outcomes?:
| T
| {
key?: T;
title?: T;
description?: T;
ctaText?: T;
ctaLink?: T;
id?: T;
};
questions?:
| T
| {
question?: T;
options?:
| T
| {
label?: T;
sublabel?: T;
outcomeKeys?: T;
id?: T;
};
id?: T;
};
nextLabel?: T;
backLabel?: T;
resultHeading?: T;
restartLabel?: T;
sectionBackground?: T;
anchorId?: T;
id?: T;
blockName?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "posts_select".