fix: cast SEO plugin fields override as Field[] to resolve
This commit is contained in:
parent
a410143ce7
commit
1ab4e41c00
@ -1,6 +1,4 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
import { AnnouncementBarComponent } from '@/components/AnnouncementBar/AnnouncementBar'
|
||||
import { cn } from '@/utilities/ui'
|
||||
import { GeistMono } from 'geist/font/mono'
|
||||
@ -21,27 +19,14 @@ import { PopupAnnouncementComponent } from '@/globals/PopupAnnouncement/Componen
|
||||
import './globals.css'
|
||||
import { getServerSideURL } from '@/utilities/getURL'
|
||||
|
||||
async function AnnouncementBarWrapper() {
|
||||
const payload = await getPayload({ config })
|
||||
const announcement = await payload.findGlobal({ slug: 'announcement-bar' })
|
||||
if (!announcement?.enabled) return null
|
||||
return (
|
||||
<AnnouncementBarComponent
|
||||
text={announcement.text}
|
||||
buttonLabel={announcement.buttonLabel}
|
||||
buttonUrl={announcement.buttonUrl}
|
||||
dismissible={announcement.dismissible}
|
||||
backgroundColor={announcement.backgroundColor as any}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const { isEnabled } = await draftMode()
|
||||
|
||||
const [siteSettings, popupData] = await Promise.all([
|
||||
// All three globals fetched once via the ISR cache — no direct DB hits downstream
|
||||
const [siteSettings, popupData, announcement] = await Promise.all([
|
||||
getCachedGlobal('site-settings', 1)() as any,
|
||||
getCachedGlobal('popup-announcement', 1)() as any,
|
||||
getCachedGlobal('announcement-bar', 1)() as any,
|
||||
])
|
||||
|
||||
const cc = siteSettings?.cookieConsent
|
||||
@ -66,12 +51,20 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
<link href="/favicon.svg" rel="icon" type="image/svg+xml" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<HeadInjection />
|
||||
<HeadInjection siteSettings={siteSettings} />
|
||||
</head>
|
||||
<body>
|
||||
<Providers>
|
||||
<AdminBar adminBarProps={{ preview: isEnabled }} />
|
||||
<AnnouncementBarWrapper />
|
||||
{announcement?.enabled && (
|
||||
<AnnouncementBarComponent
|
||||
text={announcement.text}
|
||||
buttonLabel={announcement.buttonLabel}
|
||||
buttonLink={announcement.buttonLink}
|
||||
dismissible={announcement.dismissible}
|
||||
backgroundColor={announcement.backgroundColor as any}
|
||||
/>
|
||||
)}
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
@ -84,7 +77,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
/>
|
||||
)}
|
||||
{matomoEnabled && <MatomoScript code={matomoCode} />}
|
||||
<FooterInjection />
|
||||
<FooterInjection siteSettings={siteSettings} />
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -2,11 +2,7 @@ import React, { Fragment } from 'react'
|
||||
|
||||
import type { Page } from '@/payload-types'
|
||||
|
||||
import { ArchiveBlock } from '@/blocks/ArchiveBlock/Component'
|
||||
import { CallToActionBlock } from '@/blocks/CallToAction/Component'
|
||||
import { ContentBlock } from '@/blocks/Content/Component'
|
||||
import { FormBlock } from '@/blocks/Form/Component'
|
||||
import { MediaBlock } from '@/blocks/MediaBlock/Component'
|
||||
import { FDHeroBlockComponent } from '@/blocks/FDHeroBlock/Component'
|
||||
import { FDCtaSideImageBlockComponent } from '@/blocks/FDCtaSideImageBlock/Component'
|
||||
import { FDFeatureAnnouncementBlockComponent } from '@/blocks/FDFeatureAnnouncementBlock/Component'
|
||||
@ -40,11 +36,7 @@ import { FDTestimonialBlockComponent } from './FDTestimonialBlock/Component'
|
||||
import { FDTeamBlockComponent } from './FDTeamBlock/Component'
|
||||
|
||||
const blockComponents = {
|
||||
archive: ArchiveBlock,
|
||||
content: ContentBlock,
|
||||
cta: CallToActionBlock,
|
||||
formBlock: FormBlock,
|
||||
mediaBlock: MediaBlock,
|
||||
fdHero: FDHeroBlockComponent,
|
||||
fdCtaSideImage: FDCtaSideImageBlockComponent,
|
||||
fdFeatureAnnouncement: FDFeatureAnnouncementBlockComponent,
|
||||
|
||||
@ -222,14 +222,34 @@ export const Posts: CollectionConfig<'posts'> = {
|
||||
index: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Genereras automatiskt från titeln.',
|
||||
description:
|
||||
'Inläggets URL-slug, t.ex. "mitt-inlagg" → fiberdirekt.se/posts/mitt-inlagg. Kan alltid redigeras manuellt. Bocka i "Generera slug" nedan för att skriva om automatiskt från titeln.',
|
||||
},
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => generateSlug(data?.title || ''),
|
||||
({ data, siblingData, value }) => {
|
||||
// Only auto-generate when the checkbox is explicitly checked
|
||||
if (siblingData?.generateSlug) {
|
||||
return generateSlug(data?.title || siblingData?.title || '')
|
||||
}
|
||||
// Otherwise keep whatever value the editor typed (or the existing value)
|
||||
return value
|
||||
},
|
||||
],
|
||||
},
|
||||
}, // ← slug object closes here, then straight into ],
|
||||
},
|
||||
// ── Auto-generate toggle ───────────────────────────────────────────────
|
||||
{
|
||||
name: 'generateSlug',
|
||||
type: 'checkbox',
|
||||
label: 'Generera slug automatiskt från titeln',
|
||||
defaultValue: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description:
|
||||
'När ikryssad skrivs slugen om från titeln vid varje sparning. Avbocka för att låsa slugen och redigera den manuellt.',
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
afterChange: [revalidatePost],
|
||||
|
||||
@ -1,18 +1,34 @@
|
||||
'use client'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
|
||||
type LinkGroup = {
|
||||
type?: 'reference' | 'custom' | null
|
||||
reference?: { slug?: string | null; relationTo?: string } | null
|
||||
url?: string | null
|
||||
newTab?: boolean | null
|
||||
}
|
||||
|
||||
type Props = {
|
||||
text?: string | null
|
||||
buttonLabel?: string | null
|
||||
buttonUrl?: string | null
|
||||
buttonLink?: LinkGroup | null
|
||||
dismissible?: boolean | null
|
||||
backgroundColor?: 'yellow' | 'navy' | 'mint' | null
|
||||
}
|
||||
|
||||
function resolveUrl(link?: LinkGroup | null): string | null {
|
||||
if (!link) return null
|
||||
if (link.type === 'reference' && link.reference) {
|
||||
const slug = link.reference.slug
|
||||
return slug ? `/${slug}` : null
|
||||
}
|
||||
return link.url ?? null
|
||||
}
|
||||
|
||||
export const AnnouncementBarComponent: React.FC<Props> = ({
|
||||
text,
|
||||
buttonLabel,
|
||||
buttonUrl,
|
||||
buttonLink,
|
||||
dismissible = true,
|
||||
backgroundColor = 'yellow',
|
||||
}) => {
|
||||
@ -38,11 +54,22 @@ export const AnnouncementBarComponent: React.FC<Props> = ({
|
||||
setDismissed(true)
|
||||
}
|
||||
|
||||
const href = resolveUrl(buttonLink)
|
||||
const newTab = buttonLink?.newTab ?? false
|
||||
|
||||
return (
|
||||
<div className={`w-full px-4 py-2 flex items-center justify-center gap-4 text-sm font-joey relative ${bgClass}`} role="status">
|
||||
<div
|
||||
className={`w-full px-4 py-2 flex items-center justify-center gap-4 text-sm font-joey relative ${bgClass}`}
|
||||
role="status"
|
||||
>
|
||||
<span>{text}</span>
|
||||
{buttonLabel && buttonUrl && (
|
||||
<a href={buttonUrl} className="underline font-joey-bold hover:opacity-70 transition-opacity">
|
||||
{buttonLabel && href && (
|
||||
<a
|
||||
href={href}
|
||||
target={newTab ? '_blank' : undefined}
|
||||
rel={newTab ? 'noopener noreferrer' : undefined}
|
||||
className="underline font-joey-bold hover:opacity-70 transition-opacity"
|
||||
>
|
||||
{buttonLabel}
|
||||
</a>
|
||||
)}
|
||||
|
||||
@ -1,97 +1,32 @@
|
||||
import React from 'react'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
|
||||
type SiteSettings = {
|
||||
headerCodeInjection?: { enabled?: boolean; code?: string } | null
|
||||
footerCodeInjection?: { enabled?: boolean; code?: string } | null
|
||||
}
|
||||
|
||||
/**
|
||||
* HeadInjection — Server Component
|
||||
*
|
||||
* Fetches the SiteSettings global and renders the header code injection
|
||||
* as raw HTML inside the <head> of your layout.
|
||||
*
|
||||
* Usage in app/layout.tsx:
|
||||
*
|
||||
* ```tsx
|
||||
* import { HeadInjection } from '@/components/HeadInjection'
|
||||
*
|
||||
* export default async function RootLayout({ children }) {
|
||||
* return (
|
||||
* <html lang="sv">
|
||||
* <head>
|
||||
* <HeadInjection />
|
||||
* </head>
|
||||
* <body>{children}</body>
|
||||
* </html>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
* HeadInjection — accepts already-fetched siteSettings as a prop.
|
||||
* Data is fetched once in layout.tsx via getCachedGlobal and passed down,
|
||||
* eliminating the uncached DB hit that was here previously.
|
||||
*/
|
||||
export async function HeadInjection() {
|
||||
let headerCode = ''
|
||||
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
const settings = await payload.findGlobal({ slug: 'site-settings' })
|
||||
|
||||
if (
|
||||
settings?.headerCodeInjection?.enabled &&
|
||||
settings?.headerCodeInjection?.code
|
||||
) {
|
||||
headerCode = settings.headerCodeInjection.code
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail — don't break the page if settings can't be fetched
|
||||
console.error('HeadInjection: Failed to fetch site settings', error)
|
||||
}
|
||||
export function HeadInjection({ siteSettings }: { siteSettings: SiteSettings }) {
|
||||
const headerCode = siteSettings?.headerCodeInjection?.enabled
|
||||
? (siteSettings.headerCodeInjection.code ?? '')
|
||||
: ''
|
||||
|
||||
if (!headerCode) return null
|
||||
|
||||
return <div dangerouslySetInnerHTML={{ __html: headerCode }} />
|
||||
}
|
||||
|
||||
/**
|
||||
* FooterInjection — Server Component
|
||||
*
|
||||
* Same as HeadInjection but for the footer code injection.
|
||||
* Place just before </body> in your layout.
|
||||
*
|
||||
* Usage in app/layout.tsx:
|
||||
*
|
||||
* ```tsx
|
||||
* import { FooterInjection } from '@/components/HeadInjection'
|
||||
*
|
||||
* export default async function RootLayout({ children }) {
|
||||
* return (
|
||||
* <html lang="sv">
|
||||
* <head>
|
||||
* <HeadInjection />
|
||||
* </head>
|
||||
* <body>
|
||||
* {children}
|
||||
* <FooterInjection />
|
||||
* </body>
|
||||
* </html>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
* FooterInjection — same pattern as HeadInjection.
|
||||
*/
|
||||
export async function FooterInjection() {
|
||||
let footerCode = ''
|
||||
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
const settings = await payload.findGlobal({ slug: 'site-settings' })
|
||||
|
||||
if (
|
||||
settings?.footerCodeInjection?.enabled &&
|
||||
settings?.footerCodeInjection?.code
|
||||
) {
|
||||
footerCode = settings.footerCodeInjection.code
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('FooterInjection: Failed to fetch site settings', error)
|
||||
}
|
||||
export function FooterInjection({ siteSettings }: { siteSettings: SiteSettings }) {
|
||||
const footerCode = siteSettings?.footerCodeInjection?.enabled
|
||||
? (siteSettings.footerCodeInjection.code ?? '')
|
||||
: ''
|
||||
|
||||
if (!footerCode) return null
|
||||
|
||||
return <div dangerouslySetInnerHTML={{ __html: footerCode }} />
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { GlobalConfig } from 'payload'
|
||||
import { adminOnly } from '../access/adminOnly'
|
||||
import { revalidateAnnouncementBar } from './hooks/revalidateAnnouncementBar'
|
||||
|
||||
export const AnnouncementBar: GlobalConfig = {
|
||||
slug: 'announcement-bar',
|
||||
@ -11,6 +12,9 @@ export const AnnouncementBar: GlobalConfig = {
|
||||
read: () => true,
|
||||
update: adminOnly,
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [revalidateAnnouncementBar],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'enabled',
|
||||
@ -35,12 +39,55 @@ export const AnnouncementBar: GlobalConfig = {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'buttonUrl',
|
||||
type: 'text',
|
||||
label: 'Knapp-URL (valfri)',
|
||||
name: 'buttonLink',
|
||||
type: 'group',
|
||||
label: 'Knapp-länk (valfri)',
|
||||
admin: {
|
||||
condition: (_, siblingData) => Boolean(siblingData?.enabled),
|
||||
hideGutter: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'radio',
|
||||
label: 'Länktyp',
|
||||
defaultValue: 'custom',
|
||||
options: [
|
||||
{ label: 'Intern sida', value: 'reference' },
|
||||
{ label: 'Egen URL', value: 'custom' },
|
||||
],
|
||||
admin: { layout: 'horizontal', width: '50%' },
|
||||
},
|
||||
{
|
||||
name: 'newTab',
|
||||
type: 'checkbox',
|
||||
label: 'Öppna i ny flik',
|
||||
admin: { width: '50%', style: { alignSelf: 'flex-end' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'reference',
|
||||
type: 'relationship',
|
||||
label: 'Sida',
|
||||
relationTo: ['pages', 'posts'],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.type === 'reference',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'URL',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.type === 'custom',
|
||||
description: 'T.ex. /bredband eller https://example.com',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'dismissible',
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import type { Media } from '@/payload-types'
|
||||
import type { Media, Page } from '@/payload-types'
|
||||
|
||||
type LinkGroup = {
|
||||
type?: 'reference' | 'custom' | null
|
||||
reference?: Page | string | number | null
|
||||
url?: string | null
|
||||
newTab?: boolean | null
|
||||
}
|
||||
|
||||
// specificPages is now a relationship array — each item is a populated Page or just an id
|
||||
type SpecificPage = Page | string | number
|
||||
|
||||
interface PopupAnnouncementProps {
|
||||
enabled?: boolean
|
||||
@ -9,22 +19,43 @@ interface PopupAnnouncementProps {
|
||||
subheading?: string
|
||||
body?: string
|
||||
ctaText?: string
|
||||
ctaLink?: string
|
||||
image?: Media | string
|
||||
ctaLink?: LinkGroup | null
|
||||
image?: Media | string | null
|
||||
badgeText?: string
|
||||
theme?: 'light' | 'dark'
|
||||
showOnPages?: 'all' | 'home' | 'specific'
|
||||
specificPages?: string
|
||||
specificPages?: SpecificPage[] | null
|
||||
dismissDays?: number
|
||||
}
|
||||
|
||||
function resolveUrl(link?: LinkGroup | null): string | null {
|
||||
if (!link) return null
|
||||
if (link.type === 'reference' && link.reference) {
|
||||
if (typeof link.reference === 'object' && 'slug' in link.reference) {
|
||||
return link.reference.slug ? `/${link.reference.slug}` : null
|
||||
}
|
||||
return null
|
||||
}
|
||||
return link.url ?? null
|
||||
}
|
||||
|
||||
function resolveSpecificPageSlugs(pages?: SpecificPage[] | null): string[] {
|
||||
if (!pages) return []
|
||||
return pages.flatMap((p) => {
|
||||
if (typeof p === 'object' && p !== null && 'slug' in p && typeof p.slug === 'string') {
|
||||
return [`/${p.slug}`]
|
||||
}
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
export const PopupAnnouncementComponent: React.FC<PopupAnnouncementProps> = ({
|
||||
enabled,
|
||||
heading,
|
||||
subheading,
|
||||
body,
|
||||
ctaText = 'Läs mer',
|
||||
ctaLink = '/',
|
||||
ctaLink,
|
||||
image,
|
||||
badgeText,
|
||||
theme = 'light',
|
||||
@ -50,9 +81,9 @@ export const PopupAnnouncementComponent: React.FC<PopupAnnouncementProps> = ({
|
||||
// Check page targeting
|
||||
const path = window.location.pathname
|
||||
if (showOnPages === 'home' && path !== '/') return
|
||||
if (showOnPages === 'specific' && specificPages) {
|
||||
const pages = specificPages.split(',').map(p => p.trim())
|
||||
if (!pages.includes(path)) return
|
||||
if (showOnPages === 'specific') {
|
||||
const slugs = resolveSpecificPageSlugs(specificPages)
|
||||
if (!slugs.includes(path)) return
|
||||
}
|
||||
|
||||
// Show after short delay
|
||||
@ -68,8 +99,10 @@ export const PopupAnnouncementComponent: React.FC<PopupAnnouncementProps> = ({
|
||||
|
||||
if (!isVisible || !enabled) return null
|
||||
|
||||
const media = typeof image === 'object' ? image : null
|
||||
const media = typeof image === 'object' && image !== null ? (image as Media) : null
|
||||
const isDark = theme === 'dark'
|
||||
const href = resolveUrl(ctaLink)
|
||||
const newTab = ctaLink?.newTab ?? false
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -80,7 +113,9 @@ export const PopupAnnouncementComponent: React.FC<PopupAnnouncementProps> = ({
|
||||
/>
|
||||
|
||||
{/* Popup */}
|
||||
<div className={`fixed inset-0 z-[9999] flex items-center justify-center p-4 md:p-6 pointer-events-none transition-all duration-300 ${isClosing ? 'opacity-0 scale-95' : 'opacity-100 scale-100'}`}>
|
||||
<div
|
||||
className={`fixed inset-0 z-[9999] flex items-center justify-center p-4 md:p-6 pointer-events-none transition-all duration-300 ${isClosing ? 'opacity-0 scale-95' : 'opacity-100 scale-100'}`}
|
||||
>
|
||||
<div
|
||||
className={`pointer-events-auto relative w-full max-w-[720px] rounded-2xl overflow-hidden shadow-2xl flex flex-col md:flex-row ${isDark ? 'bg-fd-navy' : 'bg-white'}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
@ -91,13 +126,17 @@ export const PopupAnnouncementComponent: React.FC<PopupAnnouncementProps> = ({
|
||||
className={`absolute top-4 right-4 w-10 h-10 flex items-center justify-center rounded-full transition-all duration-200 z-10 ${isDark ? 'text-white/60 hover:text-white hover:bg-white/10' : 'text-gray-400 hover:text-gray-800 hover:bg-gray-100'}`}
|
||||
aria-label="Stäng"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M18 6L6 18M6 6l12 12" /></svg>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
||||
<path d="M18 6L6 18M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 p-6 md:p-8 flex flex-col justify-center">
|
||||
{badgeText && (
|
||||
<span className={`inline-block self-start px-3 py-1 rounded-full text-fd-xs font-joey-bold uppercase tracking-wider mb-4 ${isDark ? 'bg-fd-yellow/20 text-fd-yellow' : 'bg-fd-navy/10 text-fd-navy'}`}>
|
||||
<span
|
||||
className={`inline-block self-start px-3 py-1 rounded-full text-fd-xs font-joey-bold uppercase tracking-wider mb-4 ${isDark ? 'bg-fd-yellow/20 text-fd-yellow' : 'bg-fd-navy/10 text-fd-navy'}`}
|
||||
>
|
||||
{badgeText}
|
||||
</span>
|
||||
)}
|
||||
@ -116,8 +155,13 @@ export const PopupAnnouncementComponent: React.FC<PopupAnnouncementProps> = ({
|
||||
{body}
|
||||
</p>
|
||||
)}
|
||||
{ctaText && (
|
||||
<a href={ctaLink || '#'} className="fd-btn-primary self-start">
|
||||
{ctaText && href && (
|
||||
<a
|
||||
href={href}
|
||||
target={newTab ? '_blank' : undefined}
|
||||
rel={newTab ? 'noopener noreferrer' : undefined}
|
||||
className="fd-btn-primary self-start"
|
||||
>
|
||||
{ctaText}
|
||||
</a>
|
||||
)}
|
||||
|
||||
@ -1,12 +1,21 @@
|
||||
import type { GlobalConfig } from 'payload'
|
||||
import { adminOnly } from '../../access/adminOnly'
|
||||
import { revalidatePopup } from './hooks/revalidatePopup'
|
||||
|
||||
export const PopupAnnouncement: GlobalConfig = {
|
||||
slug: 'popup-announcement',
|
||||
label: 'Popup-meddelande',
|
||||
admin: {
|
||||
group: 'Globala inställningar',
|
||||
description: 'Ett popup-fönster som visas för besökare. Kan begränsas till specifika sidor.',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
update: adminOnly,
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [revalidatePopup],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'enabled',
|
||||
@ -49,19 +58,62 @@ export const PopupAnnouncement: GlobalConfig = {
|
||||
condition: (data) => Boolean(data?.enabled),
|
||||
},
|
||||
},
|
||||
// ── CTA Link — internal page picker or custom URL ──────────────────────
|
||||
{
|
||||
name: 'ctaLink',
|
||||
type: 'text',
|
||||
type: 'group',
|
||||
label: 'CTA-länk',
|
||||
defaultValue: '/',
|
||||
admin: {
|
||||
condition: (data) => Boolean(data?.enabled),
|
||||
hideGutter: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'radio',
|
||||
label: 'Länktyp',
|
||||
defaultValue: 'custom',
|
||||
options: [
|
||||
{ label: 'Intern sida', value: 'reference' },
|
||||
{ label: 'Egen URL', value: 'custom' },
|
||||
],
|
||||
admin: { layout: 'horizontal', width: '50%' },
|
||||
},
|
||||
{
|
||||
name: 'newTab',
|
||||
type: 'checkbox',
|
||||
label: 'Öppna i ny flik',
|
||||
admin: { width: '50%', style: { alignSelf: 'flex-end' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'reference',
|
||||
type: 'relationship',
|
||||
label: 'Sida',
|
||||
relationTo: ['pages', 'posts'] as const,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.type === 'reference',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'URL',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.type === 'custom',
|
||||
description: 'T.ex. /bredband eller https://example.com',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
relationTo: 'media' as const,
|
||||
label: 'Bild (valfri, visas till höger)',
|
||||
admin: {
|
||||
condition: (data) => Boolean(data?.enabled),
|
||||
@ -104,12 +156,15 @@ export const PopupAnnouncement: GlobalConfig = {
|
||||
condition: (data) => Boolean(data?.enabled),
|
||||
},
|
||||
},
|
||||
// ── Specific pages — relationship instead of fragile comma-separated text ──
|
||||
{
|
||||
name: 'specificPages',
|
||||
type: 'text',
|
||||
label: 'Sökvägar (komma-separerade)',
|
||||
type: 'relationship',
|
||||
label: 'Välj sidor',
|
||||
relationTo: 'pages' as const,
|
||||
hasMany: true,
|
||||
admin: {
|
||||
description: 'T.ex. /bredband, /cloud, /kontakt',
|
||||
description: 'Välj exakt vilka sidor popupen ska visas på.',
|
||||
condition: (data) => data?.showOnPages === 'specific',
|
||||
},
|
||||
},
|
||||
@ -119,7 +174,7 @@ export const PopupAnnouncement: GlobalConfig = {
|
||||
label: 'Dölj i antal dagar efter stängning',
|
||||
defaultValue: 7,
|
||||
admin: {
|
||||
description: 'Hur många dagar popupen ska döljas efter att besökaren stänger den',
|
||||
description: 'Hur många dagar popupen ska döljas efter att besökaren stänger den.',
|
||||
condition: (data) => Boolean(data?.enabled),
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,13 +1,21 @@
|
||||
import type { GlobalConfig } from 'payload'
|
||||
import { adminOnly } from '../access/adminOnly'
|
||||
import { revalidateSiteSettings } from './hooks/revalidateSiteSettings'
|
||||
|
||||
export const SiteSettings: GlobalConfig = {
|
||||
slug: 'site-settings',
|
||||
label: 'Webbplatsinställningar',
|
||||
admin: {
|
||||
group: 'Globala inställningar',
|
||||
description: 'Kodinjektion, cookies, analytics och globala webbplatsinställningar.',
|
||||
},
|
||||
access: {
|
||||
update: adminOnly,
|
||||
read: () => true,
|
||||
},
|
||||
hooks: {
|
||||
afterChange: [revalidateSiteSettings],
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'headerCodeInjection',
|
||||
|
||||
7
src/globals/hooks/revalidateAnnouncementBar.ts
Normal file
7
src/globals/hooks/revalidateAnnouncementBar.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { GlobalAfterChangeHook } from 'payload'
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export const revalidateAnnouncementBar: GlobalAfterChangeHook = ({ req: { payload } }) => {
|
||||
payload.logger.info({ msg: 'Revalidating announcement bar' })
|
||||
;(revalidateTag as any)('global_announcement-bar')
|
||||
}
|
||||
7
src/globals/hooks/revalidateSiteSettings.ts
Normal file
7
src/globals/hooks/revalidateSiteSettings.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import type { GlobalAfterChangeHook } from 'payload'
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export const revalidateSiteSettings: GlobalAfterChangeHook = ({ req: { payload } }) => {
|
||||
payload.logger.info({ msg: 'Revalidating site settings' })
|
||||
;(revalidateTag as any)('global_site-settings')
|
||||
}
|
||||
23103
src/migrations/20260219_175400.json
Normal file
23103
src/migrations/20260219_175400.json
Normal file
File diff suppressed because it is too large
Load Diff
51
src/migrations/20260219_175400.ts
Normal file
51
src/migrations/20260219_175400.ts
Normal file
@ -0,0 +1,51 @@
|
||||
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_header_nav_items_children_type" AS ENUM('reference', 'custom');
|
||||
CREATE TYPE "public"."enum_header_nav_items_type" AS ENUM('reference', 'custom');
|
||||
CREATE TYPE "public"."enum_header_logo_link_type" AS ENUM('reference', 'custom');
|
||||
CREATE TYPE "public"."enum_footer_logo_link_type" AS ENUM('reference', 'custom');
|
||||
CREATE TABLE "header_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"pages_id" integer
|
||||
);
|
||||
|
||||
ALTER TABLE "header_nav_items_children" ALTER COLUMN "url" DROP NOT NULL;
|
||||
ALTER TABLE "pages" ADD COLUMN "generate_slug" boolean DEFAULT true;
|
||||
ALTER TABLE "_pages_v" ADD COLUMN "version_generate_slug" boolean DEFAULT true;
|
||||
ALTER TABLE "header_nav_items_children" ADD COLUMN "type" "enum_header_nav_items_children_type" DEFAULT 'custom';
|
||||
ALTER TABLE "header_nav_items" ADD COLUMN "type" "enum_header_nav_items_type" DEFAULT 'custom';
|
||||
ALTER TABLE "header" ADD COLUMN "logo_link_type" "enum_header_logo_link_type" DEFAULT 'custom';
|
||||
ALTER TABLE "header" ADD COLUMN "logo_link_url" varchar DEFAULT '/';
|
||||
ALTER TABLE "footer" ADD COLUMN "logo_link_type" "enum_footer_logo_link_type" DEFAULT 'custom';
|
||||
ALTER TABLE "footer" ADD COLUMN "logo_link_url" varchar DEFAULT '/';
|
||||
ALTER TABLE "header_rels" ADD CONSTRAINT "header_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."header"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "header_rels" ADD CONSTRAINT "header_rels_pages_fk" FOREIGN KEY ("pages_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX "header_rels_order_idx" ON "header_rels" USING btree ("order");
|
||||
CREATE INDEX "header_rels_parent_idx" ON "header_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "header_rels_path_idx" ON "header_rels" USING btree ("path");
|
||||
CREATE INDEX "header_rels_pages_id_idx" ON "header_rels" USING btree ("pages_id");`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "header_rels" DISABLE ROW LEVEL SECURITY;
|
||||
DROP TABLE "header_rels" CASCADE;
|
||||
ALTER TABLE "header_nav_items_children" ALTER COLUMN "url" SET NOT NULL;
|
||||
ALTER TABLE "pages" DROP COLUMN "generate_slug";
|
||||
ALTER TABLE "_pages_v" DROP COLUMN "version_generate_slug";
|
||||
ALTER TABLE "header_nav_items_children" DROP COLUMN "type";
|
||||
ALTER TABLE "header_nav_items" DROP COLUMN "type";
|
||||
ALTER TABLE "header" DROP COLUMN "logo_link_type";
|
||||
ALTER TABLE "header" DROP COLUMN "logo_link_url";
|
||||
ALTER TABLE "footer" DROP COLUMN "logo_link_type";
|
||||
ALTER TABLE "footer" DROP COLUMN "logo_link_url";
|
||||
DROP TYPE "public"."enum_header_nav_items_children_type";
|
||||
DROP TYPE "public"."enum_header_nav_items_type";
|
||||
DROP TYPE "public"."enum_header_logo_link_type";
|
||||
DROP TYPE "public"."enum_footer_logo_link_type";`)
|
||||
}
|
||||
31255
src/migrations/20260219_184333.json
Normal file
31255
src/migrations/20260219_184333.json
Normal file
File diff suppressed because it is too large
Load Diff
1982
src/migrations/20260219_184333.ts
Normal file
1982
src/migrations/20260219_184333.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,8 @@ import * as migration_20260216_184110 from './20260216_184110';
|
||||
import * as migration_20260216_184342 from './20260216_184342';
|
||||
import * as migration_20260218_130902 from './20260218_130902';
|
||||
import * as migration_20260218_145924 from './20260218_145924';
|
||||
import * as migration_20260219_175400 from './20260219_175400';
|
||||
import * as migration_20260219_184333 from './20260219_184333';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
@ -46,6 +48,16 @@ export const migrations = [
|
||||
{
|
||||
up: migration_20260218_145924.up,
|
||||
down: migration_20260218_145924.down,
|
||||
name: '20260218_145924'
|
||||
name: '20260218_145924',
|
||||
},
|
||||
{
|
||||
up: migration_20260219_175400.up,
|
||||
down: migration_20260219_175400.down,
|
||||
name: '20260219_175400',
|
||||
},
|
||||
{
|
||||
up: migration_20260219_184333.up,
|
||||
down: migration_20260219_184333.down,
|
||||
name: '20260219_184333'
|
||||
},
|
||||
];
|
||||
|
||||
@ -100,7 +100,7 @@ export interface Config {
|
||||
db: {
|
||||
defaultIDType: number;
|
||||
};
|
||||
fallbackLocale: null;
|
||||
fallbackLocale: ('false' | 'none' | 'null') | false | null | ('sv' | 'en') | ('sv' | 'en')[];
|
||||
globals: {
|
||||
header: Header;
|
||||
footer: Footer;
|
||||
@ -115,7 +115,7 @@ export interface Config {
|
||||
'popup-announcement': PopupAnnouncementSelect<false> | PopupAnnouncementSelect<true>;
|
||||
'site-settings': SiteSettingsSelect<false> | SiteSettingsSelect<true>;
|
||||
};
|
||||
locale: null;
|
||||
locale: 'sv' | 'en';
|
||||
user: User;
|
||||
jobs: {
|
||||
tasks: {
|
||||
@ -196,9 +196,13 @@ export interface Page {
|
||||
};
|
||||
publishedAt?: string | null;
|
||||
/**
|
||||
* Genereras automatiskt från titeln.
|
||||
* Sidans URL-slug, t.ex. "om-oss" → fiberdirekt.se/om-oss. Kan alltid redigeras manuellt. Bocka i "Generera slug" nedan för att skriva om automatiskt från titeln.
|
||||
*/
|
||||
slug: string;
|
||||
/**
|
||||
* När ikryssad skrivs slugen om från titeln vid varje sparning. Avbocka för att låsa slugen och redigera den manuellt.
|
||||
*/
|
||||
generateSlug?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
@ -1364,9 +1368,13 @@ export interface Post {
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* Genereras automatiskt från titeln.
|
||||
* Inläggets URL-slug, t.ex. "mitt-inlagg" → fiberdirekt.se/posts/mitt-inlagg. Kan alltid redigeras manuellt. Bocka i "Generera slug" nedan för att skriva om automatiskt från titeln.
|
||||
*/
|
||||
slug: string;
|
||||
/**
|
||||
* När ikryssad skrivs slugen om från titeln vid varje sparning. Avbocka för att låsa slugen och redigera den manuellt.
|
||||
*/
|
||||
generateSlug?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
@ -1704,6 +1712,7 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
};
|
||||
publishedAt?: T;
|
||||
slug?: T;
|
||||
generateSlug?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
@ -2361,6 +2370,7 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
};
|
||||
slug?: T;
|
||||
generateSlug?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
@ -2712,23 +2722,53 @@ export interface PayloadMigrationsSelect<T extends boolean = true> {
|
||||
*/
|
||||
export interface Header {
|
||||
id: number;
|
||||
/**
|
||||
* Vart man hamnar när man klickar på logotypen. Standard är startsidan (/).
|
||||
*/
|
||||
logoLink?: {
|
||||
type?: ('reference' | 'custom') | null;
|
||||
reference?: {
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null;
|
||||
/**
|
||||
* T.ex. / eller /startsida
|
||||
*/
|
||||
url?: string | null;
|
||||
};
|
||||
navItems?:
|
||||
| {
|
||||
label: string;
|
||||
/**
|
||||
* Lämna tomt / ignoreras om detta objekt har en undermeny.
|
||||
*/
|
||||
type?: ('reference' | 'custom') | null;
|
||||
reference?: {
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null;
|
||||
/**
|
||||
* T.ex. /bredband eller https://extern-sida.se
|
||||
*/
|
||||
url?: string | null;
|
||||
/**
|
||||
* When checked, hovering/clicking this item opens a full-width mega menu instead of a small dropdown card.
|
||||
* När ikryssad öppnar hover/klick ett fullbredds mega menu istället för en liten dropdown.
|
||||
*/
|
||||
megaMenu?: boolean | null;
|
||||
/**
|
||||
* Add submenu links. If these exist, the parent URL is ignored. Use "Group" to create columns in the mega menu.
|
||||
* Lägg till undermenylänkar. Om dessa finns ignoreras förälderlänken. Använd "Grupp" för att skapa kolumner i mega menu.
|
||||
*/
|
||||
children?:
|
||||
| {
|
||||
label: string;
|
||||
url: string;
|
||||
type?: ('reference' | 'custom') | null;
|
||||
reference?: {
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null;
|
||||
url?: string | null;
|
||||
/**
|
||||
* Links with the same group name appear together in a separate column in the mega menu. Leave empty for the main column.
|
||||
* Länkar med samma gruppnamn visas tillsammans i en separat kolumn i mega menu. Lämna tomt för huvudkolumnen.
|
||||
*/
|
||||
group?: string | null;
|
||||
id?: string | null;
|
||||
@ -2746,6 +2786,20 @@ export interface Header {
|
||||
*/
|
||||
export interface Footer {
|
||||
id: number;
|
||||
/**
|
||||
* Vart man hamnar när man klickar på logotypen i footern. Standard är startsidan (/).
|
||||
*/
|
||||
logoLink?: {
|
||||
type?: ('reference' | 'custom') | null;
|
||||
reference?: {
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null;
|
||||
/**
|
||||
* T.ex. / eller /startsida
|
||||
*/
|
||||
url?: string | null;
|
||||
};
|
||||
/**
|
||||
* Footer-kolumner med rubriker och länkar (sitemap-stil)
|
||||
*/
|
||||
@ -2818,7 +2872,23 @@ export interface AnnouncementBar {
|
||||
enabled?: boolean | null;
|
||||
text?: string | null;
|
||||
buttonLabel?: string | null;
|
||||
buttonUrl?: string | null;
|
||||
buttonLink?: {
|
||||
type?: ('reference' | 'custom') | null;
|
||||
newTab?: boolean | null;
|
||||
reference?:
|
||||
| ({
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
} | null);
|
||||
/**
|
||||
* T.ex. /bredband eller https://example.com
|
||||
*/
|
||||
url?: string | null;
|
||||
};
|
||||
dismissible?: boolean | null;
|
||||
backgroundColor?: ('yellow' | 'navy' | 'mint') | null;
|
||||
updatedAt?: string | null;
|
||||
@ -2837,7 +2907,23 @@ export interface PopupAnnouncement {
|
||||
subheading?: string | null;
|
||||
body?: string | null;
|
||||
ctaText?: string | null;
|
||||
ctaLink?: string | null;
|
||||
ctaLink?: {
|
||||
type?: ('reference' | 'custom') | null;
|
||||
newTab?: boolean | null;
|
||||
reference?:
|
||||
| ({
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
} | null);
|
||||
/**
|
||||
* T.ex. /bredband eller https://example.com
|
||||
*/
|
||||
url?: string | null;
|
||||
};
|
||||
image?: (number | null) | Media;
|
||||
/**
|
||||
* T.ex. "NYHET", "ERBJUDANDE", "VIKTIG INFO"
|
||||
@ -2846,17 +2932,19 @@ export interface PopupAnnouncement {
|
||||
theme?: ('light' | 'dark') | null;
|
||||
showOnPages?: ('all' | 'home' | 'specific') | null;
|
||||
/**
|
||||
* T.ex. /bredband, /cloud, /kontakt
|
||||
* Välj exakt vilka sidor popupen ska visas på.
|
||||
*/
|
||||
specificPages?: string | null;
|
||||
specificPages?: (number | Page)[] | null;
|
||||
/**
|
||||
* Hur många dagar popupen ska döljas efter att besökaren stänger den
|
||||
* Hur många dagar popupen ska döljas efter att besökaren stänger den.
|
||||
*/
|
||||
dismissDays?: number | null;
|
||||
updatedAt?: string | null;
|
||||
createdAt?: string | null;
|
||||
}
|
||||
/**
|
||||
* Kodinjektion, cookies, analytics och globala webbplatsinställningar.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "site-settings".
|
||||
*/
|
||||
@ -2903,16 +2991,27 @@ export interface SiteSetting {
|
||||
* via the `definition` "header_select".
|
||||
*/
|
||||
export interface HeaderSelect<T extends boolean = true> {
|
||||
logoLink?:
|
||||
| T
|
||||
| {
|
||||
type?: T;
|
||||
reference?: T;
|
||||
url?: T;
|
||||
};
|
||||
navItems?:
|
||||
| T
|
||||
| {
|
||||
label?: T;
|
||||
type?: T;
|
||||
reference?: T;
|
||||
url?: T;
|
||||
megaMenu?: T;
|
||||
children?:
|
||||
| T
|
||||
| {
|
||||
label?: T;
|
||||
type?: T;
|
||||
reference?: T;
|
||||
url?: T;
|
||||
group?: T;
|
||||
id?: T;
|
||||
@ -2928,6 +3027,13 @@ export interface HeaderSelect<T extends boolean = true> {
|
||||
* via the `definition` "footer_select".
|
||||
*/
|
||||
export interface FooterSelect<T extends boolean = true> {
|
||||
logoLink?:
|
||||
| T
|
||||
| {
|
||||
type?: T;
|
||||
reference?: T;
|
||||
url?: T;
|
||||
};
|
||||
columns?:
|
||||
| T
|
||||
| {
|
||||
@ -2976,7 +3082,14 @@ export interface AnnouncementBarSelect<T extends boolean = true> {
|
||||
enabled?: T;
|
||||
text?: T;
|
||||
buttonLabel?: T;
|
||||
buttonUrl?: T;
|
||||
buttonLink?:
|
||||
| T
|
||||
| {
|
||||
type?: T;
|
||||
newTab?: T;
|
||||
reference?: T;
|
||||
url?: T;
|
||||
};
|
||||
dismissible?: T;
|
||||
backgroundColor?: T;
|
||||
updatedAt?: T;
|
||||
@ -2993,7 +3106,14 @@ export interface PopupAnnouncementSelect<T extends boolean = true> {
|
||||
subheading?: T;
|
||||
body?: T;
|
||||
ctaText?: T;
|
||||
ctaLink?: T;
|
||||
ctaLink?:
|
||||
| T
|
||||
| {
|
||||
type?: T;
|
||||
newTab?: T;
|
||||
reference?: T;
|
||||
url?: T;
|
||||
};
|
||||
image?: T;
|
||||
badgeText?: T;
|
||||
theme?: T;
|
||||
|
||||
@ -63,6 +63,14 @@ export default buildConfig({
|
||||
},
|
||||
},
|
||||
editor: defaultLexical,
|
||||
localization: {
|
||||
locales: [
|
||||
{ label: 'Svenska', code: 'sv' },
|
||||
{ label: 'English', code: 'en' },
|
||||
],
|
||||
defaultLocale: 'sv',
|
||||
fallback: true,
|
||||
},
|
||||
db: postgresAdapter({
|
||||
pool: {
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
// @ts-nocheck
|
||||
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'
|
||||
import { FixedToolbarFeature, HeadingFeature, lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||
import { nestedDocsPlugin } from '@payloadcms/plugin-nested-docs'
|
||||
import { redirectsPlugin } from '@payloadcms/plugin-redirects'
|
||||
import { seoPlugin } from '@payloadcms/plugin-seo'
|
||||
import { Plugin } from 'payload'
|
||||
import type { Plugin, Field } from 'payload'
|
||||
import { revalidateRedirects } from '@/hooks/revalidateRedirects'
|
||||
import { GenerateTitle, GenerateURL } from '@payloadcms/plugin-seo/types'
|
||||
import { Page, Post } from '@/payload-types'
|
||||
import type { GenerateTitle, GenerateURL } from '@payloadcms/plugin-seo/types'
|
||||
import type { Page, Post } from '@/payload-types'
|
||||
import { getServerSideURL } from '@/utilities/getURL'
|
||||
|
||||
const generateTitle: GenerateTitle<Post | Page> = ({ doc }) => {
|
||||
@ -23,8 +22,8 @@ export const plugins: Plugin[] = [
|
||||
redirectsPlugin({
|
||||
collections: ['pages', 'posts'],
|
||||
overrides: {
|
||||
// @ts-expect-error - This is a valid override, mapped fields don't resolve to the same type
|
||||
fields: ({ defaultFields }) => {
|
||||
// @ts-expect-error — mapped fields don't resolve to the same type as the plugin expects
|
||||
fields: ({ defaultFields }: { defaultFields: Field[] }) => {
|
||||
return defaultFields.map((field) => {
|
||||
if ('name' in field && field.name === 'from') {
|
||||
return {
|
||||
@ -50,11 +49,13 @@ export const plugins: Plugin[] = [
|
||||
seoPlugin({
|
||||
generateTitle,
|
||||
generateURL,
|
||||
// Keep localized: true as the foundation for future i18n support.
|
||||
// The localization config in payload.config.ts defines sv (default) + en.
|
||||
fields: ({ defaultFields }) => {
|
||||
return defaultFields.map((field) => ({
|
||||
...field,
|
||||
localized: true,
|
||||
}))
|
||||
})) as Field[]
|
||||
},
|
||||
}),
|
||||
formBuilderPlugin({
|
||||
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user