fix: hook-based slug with Swedish chars, remove built-in hero from Pages, and more rich text fix
@ -1,2 +0,0 @@
|
||||
# Web Paylod CMS testing
|
||||
|
||||
44
Todo - Updates.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Web Paylod CMS testing
|
||||
|
||||
|
||||
## Comments on what to fix
|
||||
|
||||
Hero should default to none, or we should try to remove it!
|
||||
|
||||
Preview thumbnails for blocks ??
|
||||
|
||||
Can we get filler text in blocks as standard ?
|
||||
|
||||
Why is slug defaulting to one letter?
|
||||
|
||||
Fix corners and embed for video block
|
||||
|
||||
Fix posts formatting to be better
|
||||
|
||||
FAQ is not rendering answer text
|
||||
|
||||
Where does the form submission text come from ???
|
||||
|
||||
Skicka förfrågan forms button needs to be updated
|
||||
|
||||
A CTA block without Image
|
||||
|
||||
Change the cookies banner to full screen width instead of rounded corners floating
|
||||
|
||||
---
|
||||
|
||||
## Funktionalitet
|
||||
|
||||
#### Subscribe to newsletters, connection to API GANL
|
||||
|
||||
#### Få en offert (Lime, GANL, epost?)
|
||||
|
||||
#### Forms generellt ?
|
||||
|
||||
#### Matomo Tracking
|
||||
|
||||
#### Cookies consent
|
||||
|
||||
#### Lime forms integration
|
||||
|
||||
#### Other code embedding
|
||||
243
fix-ts-errors.sh
@ -1,243 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Run from the root of your fdweb2 project
|
||||
set -e
|
||||
cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
|
||||
echo "=== FD TypeScript Error Fix Script ==="
|
||||
echo ""
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# GROUP 1: Remove dead template block imports from RenderBlocks
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
RENDER_BLOCKS="src/app/(frontend)/(pages)/[slug]/RenderBlocks.tsx"
|
||||
# Try alternate path if first doesn't exist
|
||||
if [ ! -f "$RENDER_BLOCKS" ]; then
|
||||
RENDER_BLOCKS=$(find src -name "RenderBlocks.tsx" | head -1)
|
||||
fi
|
||||
echo "→ Fixing RenderBlocks.tsx at: $RENDER_BLOCKS"
|
||||
|
||||
# Remove dead template block imports (leave all FD* blocks intact)
|
||||
sed -i '' \
|
||||
'/^import { ArchiveBlock } from/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
sed -i '' \
|
||||
'/^import { CallToActionBlock } from/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
sed -i '' \
|
||||
'/^import { ContentBlock } from/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
sed -i '' \
|
||||
'/^import { FormBlock } from/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
sed -i '' \
|
||||
'/^import { MediaBlock } from/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
# Remove corresponding entries from blockComponents object
|
||||
sed -i '' \
|
||||
'/^ archive: ArchiveBlock,/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
sed -i '' \
|
||||
'/^ cta: CallToActionBlock,/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
sed -i '' \
|
||||
'/^ content: ContentBlock,/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
sed -i '' \
|
||||
'/^ formBlock: FormBlock,/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
sed -i '' \
|
||||
'/^ mediaBlock: MediaBlock,/d' \
|
||||
"$RENDER_BLOCKS"
|
||||
|
||||
echo " ✓ Removed 5 dead template block imports and blockComponent entries"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# GROUP 2: Fix CallToActionBlock import in RichText
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
RICH_TEXT="src/components/RichText/index.tsx"
|
||||
if [ -f "$RICH_TEXT" ]; then
|
||||
# Remove CallToActionBlock from the payload-types import
|
||||
sed -i '' \
|
||||
's/import type { CallToActionBlock } from '\''@\/payload-types'\''/\/\/ CallToActionBlock removed - not in schema/' \
|
||||
"$RICH_TEXT"
|
||||
# More general: remove just the named import if it's part of a multi-import
|
||||
sed -i '' \
|
||||
's/, CallToActionBlock//' \
|
||||
"$RICH_TEXT"
|
||||
sed -i '' \
|
||||
's/CallToActionBlock, //' \
|
||||
"$RICH_TEXT"
|
||||
echo " ✓ Removed CallToActionBlock from RichText"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# GROUP 3: Fix null safety in FD block components
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "→ Fixing null safety issues in FD blocks..."
|
||||
|
||||
# FDLocationsGridBlock - cards is possibly null
|
||||
LOCATIONS="src/blocks/FDLocationsGridBlock/Component.tsx"
|
||||
if [ -f "$LOCATIONS" ]; then
|
||||
# Change: cards.map( → (cards ?? []).map(
|
||||
sed -i '' \
|
||||
's/cards\.map(/\(cards ?? []\)\.map(/g' \
|
||||
"$LOCATIONS"
|
||||
echo " ✓ FDLocationsGridBlock: cards null check"
|
||||
fi
|
||||
|
||||
# FDNewsletterBlock - bulletPoints is possibly null
|
||||
NEWSLETTER="src/blocks/FDNewsletterBlock/Component.tsx"
|
||||
if [ -f "$NEWSLETTER" ]; then
|
||||
sed -i '' \
|
||||
's/bulletPoints\.map(/\(bulletPoints ?? []\)\.map(/g' \
|
||||
"$NEWSLETTER"
|
||||
sed -i '' \
|
||||
's/bulletPoints\.length/\(bulletPoints ?? []\)\.length/g' \
|
||||
"$NEWSLETTER"
|
||||
# Fix conditional renders: bulletPoints && → bulletPoints?.length &&
|
||||
sed -i '' \
|
||||
's/bulletPoints &&/bulletPoints?.length \&\&/g' \
|
||||
"$NEWSLETTER"
|
||||
echo " ✓ FDNewsletterBlock: bulletPoints null check"
|
||||
fi
|
||||
|
||||
# FDServiceChooserBlock - categories is possibly null
|
||||
SERVICE_CHOOSER="src/blocks/FDServiceChooserBlock/Component.tsx"
|
||||
if [ -f "$SERVICE_CHOOSER" ]; then
|
||||
sed -i '' \
|
||||
's/categories\.map(/\(categories ?? []\)\.map(/g' \
|
||||
"$SERVICE_CHOOSER"
|
||||
sed -i '' \
|
||||
's/categories\.filter(/\(categories ?? []\)\.filter(/g' \
|
||||
"$SERVICE_CHOOSER"
|
||||
sed -i '' \
|
||||
's/categories\.find(/\(categories ?? []\)\.find(/g' \
|
||||
"$SERVICE_CHOOSER"
|
||||
sed -i '' \
|
||||
's/categories\.length/\(categories ?? []\)\.length/g' \
|
||||
"$SERVICE_CHOOSER"
|
||||
echo " ✓ FDServiceChooserBlock: categories null check"
|
||||
fi
|
||||
|
||||
# FDStatisticsBlock - stats is possibly null
|
||||
STATISTICS="src/blocks/FDStatisticsBlock/Component.tsx"
|
||||
if [ -f "$STATISTICS" ]; then
|
||||
sed -i '' \
|
||||
's/stats\.map(/\(stats ?? []\)\.map(/g' \
|
||||
"$STATISTICS"
|
||||
sed -i '' \
|
||||
's/stats\.length/\(stats ?? []\)\.length/g' \
|
||||
"$STATISTICS"
|
||||
echo " ✓ FDStatisticsBlock: stats null check"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# GROUP 4: Fix revalidatePath — Next.js now requires 2nd arg
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "→ Fixing revalidatePath calls (adding 'page' as second argument)..."
|
||||
|
||||
REVALIDATE_FILES=(
|
||||
"src/collections/Pages/hooks/revalidatePage.ts"
|
||||
"src/collections/Posts/hooks/revalidatePost.ts"
|
||||
"src/Footer/hooks/revalidateFooter.ts"
|
||||
"src/Header/hooks/revalidateHeader.ts"
|
||||
"src/globals/PopupAnnouncement/hooks/revalidatePopup.ts"
|
||||
"src/hooks/revalidateRedirects.ts"
|
||||
)
|
||||
|
||||
for FILE in "${REVALIDATE_FILES[@]}"; do
|
||||
if [ -f "$FILE" ]; then
|
||||
# revalidatePath('/some/path') → revalidatePath('/some/path', 'page')
|
||||
# Match revalidatePath with a single string arg (no comma inside the parens)
|
||||
sed -i '' \
|
||||
"s/revalidatePath('\([^']*\)')/revalidatePath('\1', 'page')/g" \
|
||||
"$FILE"
|
||||
sed -i '' \
|
||||
's/revalidatePath("\([^"]*\)")/revalidatePath("\1", "page")/g' \
|
||||
"$FILE"
|
||||
# Handle template literals: revalidatePath(\`...\`) → revalidatePath(\`...\`, 'page')
|
||||
# This one is trickier with backticks, handle separately
|
||||
perl -i '' -pe 's/revalidatePath\(`([^`]*)`\)/revalidatePath(`$1`, '\''page'\'')/g' "$FILE" 2>/dev/null || true
|
||||
echo " ✓ $FILE"
|
||||
fi
|
||||
done
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# GROUP 5: Delete BACKUP file (it's causing compilation errors)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "→ Removing backup files from compilation..."
|
||||
|
||||
BACKUP="src/Header/Nav/index BACKUP.tsx"
|
||||
if [ -f "$BACKUP" ]; then
|
||||
rm "$BACKUP"
|
||||
echo " ✓ Deleted 'src/Header/Nav/index BACKUP.tsx'"
|
||||
else
|
||||
echo " - BACKUP file not found (already removed?)"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# GROUP 6: Fix Footer/Header getCachedGlobal type cast
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "→ Fixing Footer and Header getCachedGlobal type cast..."
|
||||
|
||||
FOOTER_COMPONENT="src/Footer/Component.tsx"
|
||||
if [ -f "$FOOTER_COMPONENT" ]; then
|
||||
# Add 'as Footer' cast to getCachedGlobal call
|
||||
sed -i '' \
|
||||
"s/const \(.*\) = await getCachedGlobal('footer'/const \1 = (await getCachedGlobal('footer'/" \
|
||||
"$FOOTER_COMPONENT"
|
||||
echo " ⚠ Footer/Component.tsx needs manual fix — see note below"
|
||||
fi
|
||||
|
||||
HEADER_COMPONENT="src/Header/Component.tsx"
|
||||
if [ -f "$HEADER_COMPONENT" ]; then
|
||||
echo " ⚠ Header/Component.tsx needs manual fix — see note below"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# DONE
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "=== Script complete ==="
|
||||
echo ""
|
||||
echo "MANUAL FIXES STILL NEEDED:"
|
||||
echo ""
|
||||
echo "1. FDTagsBlock and FDTextBlock missing from payload-types:"
|
||||
echo " → Run: npx payload generate:types"
|
||||
echo " → If still missing, check that FDTagsBlock and FDTextBlock"
|
||||
echo " are imported and added to the Pages collection layout field"
|
||||
echo " in src/collections/Pages/index.ts"
|
||||
echo ""
|
||||
echo "2. Footer/Component.tsx and Header/Component.tsx type mismatch:"
|
||||
echo " → Change the getCachedGlobal line to add a type cast, e.g.:"
|
||||
echo " const footerData = await getCachedGlobal('footer', 1) as Footer"
|
||||
echo " const headerData = await getCachedGlobal('header', 1) as Header"
|
||||
echo ""
|
||||
echo "3. Footer/RowLabel.tsx and Header/RowLabel.tsx — 'link' property:"
|
||||
echo " → Change: data.link?.label"
|
||||
echo " To: data.label (the nav item schema changed, link is now flat)"
|
||||
echo ""
|
||||
echo "4. Media.caption doesn't exist — removed from your schema:"
|
||||
echo " → In MediaBlock/Component.tsx line 33: remove the .caption reference"
|
||||
echo " → In heros/MediumImpact/index.tsx lines 36/38: remove .caption"
|
||||
echo " → In src/endpoints/seed/image-*.ts: remove caption field"
|
||||
echo ""
|
||||
echo "5. Seed files (contact-page.ts, home.ts, index.ts):"
|
||||
echo " → These are dev seeds, not used in production."
|
||||
echo " → Quickest fix: add // @ts-ignore above each flagged line,"
|
||||
echo " or delete the seed files if you no longer use them."
|
||||
echo ""
|
||||
echo "After manual fixes, run: npx tsc --noEmit to verify"
|
||||
213
fix2.py
@ -1,213 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix remaining TypeScript errors in fdweb2 project."""
|
||||
import os, re, sys
|
||||
|
||||
def read(path):
|
||||
with open(path, 'r') as f: return f.read()
|
||||
|
||||
def write(path, content):
|
||||
with open(path, 'w') as f: f.write(content)
|
||||
print(f" ✓ {path}")
|
||||
|
||||
def prepend_nocheck(path):
|
||||
if not os.path.exists(path): return
|
||||
content = read(path)
|
||||
if '// @ts-nocheck' not in content:
|
||||
write(path, '// @ts-nocheck\n' + content)
|
||||
|
||||
# ── 1. Add @ts-nocheck to old template blocks we don't use ───────────────
|
||||
print("\n→ Suppressing old template block errors...")
|
||||
for p in [
|
||||
'src/blocks/ArchiveBlock/Component.tsx',
|
||||
'src/blocks/CallToAction/Component.tsx',
|
||||
'src/blocks/Content/Component.tsx',
|
||||
'src/blocks/MediaBlock/Component.tsx',
|
||||
]:
|
||||
prepend_nocheck(p)
|
||||
|
||||
# ── 2. Add @ts-nocheck to seed files ─────────────────────────────────────
|
||||
print("\n→ Suppressing seed file errors...")
|
||||
for p in [
|
||||
'src/endpoints/seed/contact-page.ts',
|
||||
'src/endpoints/seed/home.ts',
|
||||
'src/endpoints/seed/image-1.ts',
|
||||
'src/endpoints/seed/image-2.ts',
|
||||
'src/endpoints/seed/image-3.ts',
|
||||
'src/endpoints/seed/index.ts',
|
||||
]:
|
||||
prepend_nocheck(p)
|
||||
|
||||
# ── 3. Fix revalidatePath — add 'page' second argument ───────────────────
|
||||
print("\n→ Fixing revalidatePath calls...")
|
||||
revalidate_files = [
|
||||
'src/collections/Pages/hooks/revalidatePage.ts',
|
||||
'src/collections/Posts/hooks/revalidatePost.ts',
|
||||
'src/Footer/hooks/revalidateFooter.ts',
|
||||
'src/Header/hooks/revalidateHeader.ts',
|
||||
'src/globals/PopupAnnouncement/hooks/revalidatePopup.ts',
|
||||
'src/hooks/revalidateRedirects.ts',
|
||||
]
|
||||
# Match revalidatePath(anything) where there's no second arg
|
||||
# i.e. revalidatePath( <stuff> ) with no comma at top level inside parens
|
||||
pattern = re.compile(r'revalidatePath\(([^,)]+)\)')
|
||||
def add_page_arg(m):
|
||||
inner = m.group(1).strip()
|
||||
return f"revalidatePath({inner}, 'page')"
|
||||
|
||||
for path in revalidate_files:
|
||||
if not os.path.exists(path): continue
|
||||
content = read(path)
|
||||
new_content = pattern.sub(add_page_arg, content)
|
||||
if new_content != content:
|
||||
write(path, new_content)
|
||||
else:
|
||||
print(f" - No match in {path} (may already be fixed)")
|
||||
|
||||
# ── 4. Fix Pages/index.ts — add FDTagsBlock + FDTextBlock to layout ──────
|
||||
print("\n→ Fixing Pages/index.ts block registrations...")
|
||||
pages_path = 'src/collections/Pages/index.ts'
|
||||
if os.path.exists(pages_path):
|
||||
content = read(pages_path)
|
||||
|
||||
# Add FDTextBlock import if missing
|
||||
if 'FDTextBlock' not in content:
|
||||
content = content.replace(
|
||||
"import { FDTagsBlock } from '../../blocks/FDTagsBlock/config'",
|
||||
"import { FDTagsBlock } from '../../blocks/FDTagsBlock/config'\nimport { FDTextBlock } from '../../blocks/FDTextBlock/config'"
|
||||
)
|
||||
|
||||
# Add FDTagsBlock and FDTextBlock to blocks array if missing
|
||||
if 'FDTagsBlock' not in content.split('blocks: [')[1].split(']')[0]:
|
||||
content = content.replace(
|
||||
'FDVpsCalculatorBlock]',
|
||||
'FDVpsCalculatorBlock, FDTagsBlock, FDTextBlock]'
|
||||
)
|
||||
elif 'FDTextBlock' not in content.split('blocks: [')[1].split(']')[0]:
|
||||
content = content.replace(
|
||||
'FDVpsCalculatorBlock]',
|
||||
'FDVpsCalculatorBlock, FDTextBlock]'
|
||||
)
|
||||
|
||||
write(pages_path, content)
|
||||
|
||||
# ── 5. Fix Footer/Component.tsx — add type cast ───────────────────────────
|
||||
print("\n→ Fixing Footer/Component.tsx type cast...")
|
||||
footer_path = 'src/Footer/Component.tsx'
|
||||
if os.path.exists(footer_path):
|
||||
content = read(footer_path)
|
||||
# Add Footer import if needed and cast the getCachedGlobal result
|
||||
# Pattern: const X = await getCachedGlobal('footer', ...)
|
||||
new = re.sub(
|
||||
r"(const\s+\w+\s*=\s*await getCachedGlobal\('footer'[^)]*\))",
|
||||
r"\1 as Footer",
|
||||
content
|
||||
)
|
||||
if new == content:
|
||||
# Try without await
|
||||
new = re.sub(
|
||||
r"(getCachedGlobal\('footer'[^)]*\))",
|
||||
r"(\1 as Footer)",
|
||||
content
|
||||
)
|
||||
if new != content:
|
||||
write(footer_path, new)
|
||||
else:
|
||||
print(f" ⚠ Footer/Component.tsx — pattern not matched, needs manual fix")
|
||||
|
||||
# ── 6. Fix Header/Component.tsx — add type cast ───────────────────────────
|
||||
print("\n→ Fixing Header/Component.tsx type cast...")
|
||||
header_path = 'src/Header/Component.tsx'
|
||||
if os.path.exists(header_path):
|
||||
content = read(header_path)
|
||||
new = re.sub(
|
||||
r"(const\s+\w+\s*=\s*await getCachedGlobal\('header'[^)]*\))",
|
||||
r"\1 as Header",
|
||||
content
|
||||
)
|
||||
if new == content:
|
||||
new = re.sub(
|
||||
r"(getCachedGlobal\('header'[^)]*\))",
|
||||
r"(\1 as Header)",
|
||||
content
|
||||
)
|
||||
if new != content:
|
||||
write(header_path, new)
|
||||
else:
|
||||
print(f" ⚠ Header/Component.tsx — pattern not matched, needs manual fix")
|
||||
|
||||
# ── 7. Fix RowLabel files — .link property doesn't exist ─────────────────
|
||||
print("\n→ Fixing RowLabel files...")
|
||||
for path in ['src/Footer/RowLabel.tsx', 'src/Header/RowLabel.tsx']:
|
||||
if not os.path.exists(path): continue
|
||||
content = read(path)
|
||||
# data.link.label → data.label
|
||||
# data.link.url → data.url
|
||||
new = content.replace('.link.label', '.label').replace('.link.url', '.url').replace('.link?.label', '.label').replace('.link?.url', '.url')
|
||||
if new != content:
|
||||
write(path, new)
|
||||
else:
|
||||
print(f" ⚠ {path} — pattern not matched, needs manual fix")
|
||||
|
||||
# ── 8. Fix MediaBlock caption ─────────────────────────────────────────────
|
||||
print("\n→ Fixing MediaBlock/Component.tsx caption property...")
|
||||
media_block = 'src/blocks/MediaBlock/Component.tsx'
|
||||
if os.path.exists(media_block):
|
||||
content = read(media_block)
|
||||
# Cast media to any to access caption, or just remove it
|
||||
new = re.sub(r'\(media as Media\)\.caption', '(media as any).caption', content)
|
||||
new = re.sub(r'media\.caption', '(media as any).caption', new)
|
||||
if new != content:
|
||||
write(media_block, new)
|
||||
|
||||
# ── 9. Fix heros/MediumImpact caption ────────────────────────────────────
|
||||
print("\n→ Fixing heros/MediumImpact/index.tsx caption property...")
|
||||
medium_impact = 'src/heros/MediumImpact/index.tsx'
|
||||
if os.path.exists(medium_impact):
|
||||
content = read(medium_impact)
|
||||
new = re.sub(r'\(media as Media\)\.caption', '(media as any).caption', content)
|
||||
new = re.sub(r'(?<!\(media as any\))media\.caption', '(media as any).caption', new)
|
||||
if new != content:
|
||||
write(medium_impact, new)
|
||||
|
||||
# ── 10. Fix generateMeta.ts — .og doesn't exist ──────────────────────────
|
||||
print("\n→ Fixing generateMeta.ts og property...")
|
||||
meta_path = 'src/utilities/generateMeta.ts'
|
||||
if os.path.exists(meta_path):
|
||||
content = read(meta_path)
|
||||
# Cast the image to any to access og
|
||||
new = re.sub(r'(\w+)\.og\b', r'(\1 as any).og', content)
|
||||
if new != content:
|
||||
write(meta_path, new)
|
||||
|
||||
# ── 11. Fix FDServiceChooserBlock categories null ────────────────────────
|
||||
print("\n→ Fixing FDServiceChooserBlock categories null...")
|
||||
svc_path = 'src/blocks/FDServiceChooserBlock/Component.tsx'
|
||||
if os.path.exists(svc_path):
|
||||
content = read(svc_path)
|
||||
# Any remaining direct categories. access
|
||||
new = re.sub(r'(?<!\?\? \[\]\))categories\.map\(', '(categories ?? []).map(', content)
|
||||
new = re.sub(r'(?<!\?\? \[\]\))categories\.filter\(', '(categories ?? []).filter(', new)
|
||||
new = re.sub(r'(?<!\?\? \[\]\))categories\.find\(', '(categories ?? []).find(', new)
|
||||
new = re.sub(r'(?<!\?\? \[\]\))categories\.length', '(categories ?? []).length', new)
|
||||
# Handle line 31 which may be something like: if (categories && categories[
|
||||
new = re.sub(r'\bcategories\[', '(categories ?? [])[', new)
|
||||
if new != content:
|
||||
write(svc_path, new)
|
||||
|
||||
# ── 12. Fix RichText CallToActionBlock import ─────────────────────────────
|
||||
print("\n→ Fixing RichText CallToActionBlock import...")
|
||||
richtext = 'src/components/RichText/index.tsx'
|
||||
if os.path.exists(richtext):
|
||||
content = read(richtext)
|
||||
# Remove CallToActionBlock from any import line
|
||||
new = re.sub(r',\s*CallToActionBlock', '', content)
|
||||
new = re.sub(r'CallToActionBlock,\s*', '', new)
|
||||
# If it was the only import, remove the whole import line
|
||||
new = re.sub(r"import type \{ \} from '@/payload-types'\n?", '', new)
|
||||
if new != content:
|
||||
write(richtext, new)
|
||||
|
||||
print("\n=== Done! ===")
|
||||
print("\nNow run:")
|
||||
print(" npx payload generate:types")
|
||||
print(" npx tsc --noEmit 2>&1 | grep 'error TS'")
|
||||
109
fix3.py
@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix all remaining TypeScript errors."""
|
||||
import os, re
|
||||
|
||||
def read(path):
|
||||
with open(path, 'r') as f: return f.read()
|
||||
|
||||
def write(path, content):
|
||||
with open(path, 'w') as f: f.write(content)
|
||||
print(f" ✓ {path}")
|
||||
|
||||
# ── 1. revalidateTag needs 2 args in Next.js 16 — suppress with cast ──────
|
||||
print("\n→ Fixing revalidateTag calls...")
|
||||
revalidate_tag_files = [
|
||||
'src/collections/Pages/hooks/revalidatePage.ts',
|
||||
'src/collections/Posts/hooks/revalidatePost.ts',
|
||||
'src/Footer/hooks/revalidateFooter.ts',
|
||||
'src/Header/hooks/revalidateHeader.ts',
|
||||
'src/globals/PopupAnnouncement/hooks/revalidatePopup.ts',
|
||||
'src/hooks/revalidateRedirects.ts',
|
||||
]
|
||||
for path in revalidate_tag_files:
|
||||
if not os.path.exists(path): continue
|
||||
content = read(path)
|
||||
# Replace revalidateTag('anything') with (revalidateTag as any)('anything')
|
||||
new = re.sub(r'\brevalidateTag\(', '(revalidateTag as any)(', content)
|
||||
if new != content:
|
||||
write(path, new)
|
||||
|
||||
# ── 2. Fix Footer/Component.tsx — python script broke the cast ────────────
|
||||
print("\n→ Fixing Footer/Component.tsx getCachedGlobal cast...")
|
||||
footer_path = 'src/Footer/Component.tsx'
|
||||
if os.path.exists(footer_path):
|
||||
content = read(footer_path)
|
||||
# Fix the broken: await (getCachedGlobal('footer', 1) as Footer)()
|
||||
new = re.sub(
|
||||
r'await \(getCachedGlobal\(([^)]+)\) as Footer\)\(\)',
|
||||
r'await getCachedGlobal(\1) as unknown as Footer',
|
||||
content
|
||||
)
|
||||
# Also fix if it got the type annotation wrong
|
||||
new = re.sub(
|
||||
r'const footerData: Footer =',
|
||||
'const footerData =',
|
||||
new
|
||||
)
|
||||
if new != content:
|
||||
write(footer_path, new)
|
||||
|
||||
# ── 3. Fix Header/Component.tsx — same broken cast ────────────────────────
|
||||
print("\n→ Fixing Header/Component.tsx getCachedGlobal cast...")
|
||||
header_path = 'src/Header/Component.tsx'
|
||||
if os.path.exists(header_path):
|
||||
content = read(header_path)
|
||||
new = re.sub(
|
||||
r'await \(getCachedGlobal\(([^)]+)\) as Header\)\(\)',
|
||||
r'await getCachedGlobal(\1) as unknown as Header',
|
||||
content
|
||||
)
|
||||
new = re.sub(
|
||||
r'const headerData: Header =',
|
||||
'const headerData =',
|
||||
new
|
||||
)
|
||||
if new != content:
|
||||
write(header_path, new)
|
||||
|
||||
# ── 4. Fix RichText — CTABlockProps and CallToActionBlock still referenced ─
|
||||
print("\n→ Fixing RichText/index.tsx...")
|
||||
richtext_path = 'src/components/RichText/index.tsx'
|
||||
if os.path.exists(richtext_path):
|
||||
content = read(richtext_path)
|
||||
# Remove CTABlockProps from the SerializedBlockNode union type
|
||||
new = re.sub(r'CTABlockProps \| ', '', content)
|
||||
new = re.sub(r' \| CTABlockProps', '', new)
|
||||
new = re.sub(r'CTABlockProps', '', new)
|
||||
# Replace <CallToActionBlock ... /> usage with a null render
|
||||
new = re.sub(
|
||||
r"cta: \(\{ node \}\) => <CallToActionBlock \{\.\.\.node\.fields\} />",
|
||||
"cta: () => null",
|
||||
new
|
||||
)
|
||||
# Remove the import line if still present
|
||||
new = re.sub(r"import \{ CallToActionBlock \} from '@/blocks/CallToAction/Component'\n", '', new)
|
||||
if new != content:
|
||||
write(richtext_path, new)
|
||||
|
||||
# ── 5. Fix MediumImpact hero — caption doesn't exist on Media ─────────────
|
||||
print("\n→ Fixing heros/MediumImpact/index.tsx caption...")
|
||||
medium_path = 'src/heros/MediumImpact/index.tsx'
|
||||
if os.path.exists(medium_path):
|
||||
content = read(medium_path)
|
||||
# Cast media to any where .caption is accessed
|
||||
new = re.sub(r'media(\??)\.(caption)', r'(media as any)\1.\2', content)
|
||||
if new != content:
|
||||
write(medium_path, new)
|
||||
|
||||
# ── 6. Fix generateMeta.ts — og size doesn't exist ───────────────────────
|
||||
print("\n→ Fixing utilities/generateMeta.ts og size...")
|
||||
meta_path = 'src/utilities/generateMeta.ts'
|
||||
if os.path.exists(meta_path):
|
||||
content = read(meta_path)
|
||||
# Cast image.sizes to any
|
||||
new = re.sub(r'image\.sizes(\??)\.(og)', r'(image as any).sizes\1.\2', content)
|
||||
if new != content:
|
||||
write(meta_path, new)
|
||||
|
||||
print("\n=== Done! ===")
|
||||
print("\nRun: npx tsc --noEmit")
|
||||
38
fix4.py
@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os, re
|
||||
|
||||
def read(path):
|
||||
with open(path, 'r') as f: return f.read()
|
||||
|
||||
def write(path, content):
|
||||
with open(path, 'w') as f: f.write(content)
|
||||
print(f" ✓ {path}")
|
||||
|
||||
# Fix 1: revalidatePath cast to any (Next.js 16 types it as possibly undefined)
|
||||
# Fix 2: logger.info`...`) → logger.info(`...`)
|
||||
files = [
|
||||
'src/collections/Pages/hooks/revalidatePage.ts',
|
||||
'src/collections/Posts/hooks/revalidatePost.ts',
|
||||
'src/Footer/hooks/revalidateFooter.ts',
|
||||
'src/Header/hooks/revalidateHeader.ts',
|
||||
'src/hooks/revalidateRedirects.ts',
|
||||
'src/globals/PopupAnnouncement/hooks/revalidatePopup.ts',
|
||||
]
|
||||
|
||||
for path in files:
|
||||
if not os.path.exists(path): continue
|
||||
content = read(path)
|
||||
|
||||
# Fix revalidatePath(x, 'page') → (revalidatePath as any)(x, 'page')
|
||||
new = re.sub(r'\brevalidatePath\(', '(revalidatePath as any)(', content)
|
||||
|
||||
# Fix broken logger.info`...`) → logger.info(`...`)
|
||||
# Pattern: .info`some text ${var}`) → .info(`some text ${var}`)
|
||||
new = re.sub(r'\.info`([^`]*)`\)', r'.info(`\1`)', new)
|
||||
|
||||
if new != content:
|
||||
write(path, new)
|
||||
else:
|
||||
print(f" - no changes: {path}")
|
||||
|
||||
print("\nDone. Run: npx tsc --noEmit")
|
||||
72
fix_final.py
@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
|
||||
# Fix 1: Clean up debug globals in payload.config.ts
|
||||
path = 'src/payload.config.ts'
|
||||
with open(path) as f: c = f.read()
|
||||
|
||||
old = """ globals: (() => {
|
||||
const g = [Header, Footer, AnnouncementBar, PopupAnnouncement]
|
||||
g.forEach((x, i) => { if (!x || !x.slug) console.error('UNDEFINED GLOBAL at index', i, ':', ['Header','Footer','AnnouncementBar','PopupAnnouncement'][i]) })
|
||||
return g.filter(x => x && x.slug)
|
||||
})(),"""
|
||||
|
||||
new = " globals: [Header, Footer, AnnouncementBar, PopupAnnouncement],"
|
||||
|
||||
if old in c:
|
||||
c = c.replace(old, new)
|
||||
with open(path, 'w') as f: f.write(c)
|
||||
print('✓ Cleaned up globals debug code')
|
||||
else:
|
||||
print('⚠ Pattern not found in payload.config.ts - may already be clean')
|
||||
for line in c.split('\n'):
|
||||
if 'globals' in line:
|
||||
print(' ', repr(line))
|
||||
|
||||
# Fix 2: Restore formBuilderPlugin in plugins/index.ts
|
||||
path = 'src/plugins/index.ts'
|
||||
with open(path) as f: c = f.read()
|
||||
|
||||
if 'formBuilderPlugin' not in c:
|
||||
# Add import
|
||||
c = c.replace(
|
||||
"import { nestedDocsPlugin }",
|
||||
"import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'\nimport { FixedToolbarFeature, HeadingFeature, lexicalEditor } from '@payloadcms/richtext-lexical'\nimport { nestedDocsPlugin }"
|
||||
)
|
||||
# Add plugin before searchPlugin
|
||||
c = c.replace(
|
||||
" searchPlugin({",
|
||||
""" formBuilderPlugin({
|
||||
fields: {
|
||||
payment: false,
|
||||
},
|
||||
formOverrides: {
|
||||
fields: ({ defaultFields }) => {
|
||||
return defaultFields.map((field) => {
|
||||
if ('name' in field && field.name === 'confirmationMessage') {
|
||||
return {
|
||||
...field,
|
||||
editor: lexicalEditor({
|
||||
features: ({ rootFeatures }) => {
|
||||
return [
|
||||
...rootFeatures,
|
||||
FixedToolbarFeature(),
|
||||
HeadingFeature({ enabledHeadingSizes: ['h1', 'h2', 'h3', 'h4'] }),
|
||||
]
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
return field
|
||||
})
|
||||
},
|
||||
},
|
||||
}),
|
||||
searchPlugin({"""
|
||||
)
|
||||
with open(path, 'w') as f: f.write(c)
|
||||
print('✓ Restored formBuilderPlugin')
|
||||
else:
|
||||
print('✓ formBuilderPlugin already present')
|
||||
|
||||
print('\nDone. Restart dev server.')
|
||||
BIN
media/Stockholm-1200x671.webp
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
media/Stockholm-1920x1073.webp
Normal file
|
After Width: | Height: | Size: 318 KiB |
BIN
media/Stockholm-400x224.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
media/Stockholm-800x447.webp
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
media/Stockholm.png
Normal file
|
After Width: | Height: | Size: 4.3 MiB |
5
media/Text-file-data.csv
Normal file
@ -0,0 +1,5 @@
|
||||
Packages;SLA;Telefontime;Månadspris;Response
|
||||
Fiber Basic 10 000;99,90%;kl 8-17; 1 195,00 kr ;Lots of details
|
||||
Fiber Basic 10 000 Plus;99,90%;kl 8-17; 1 995,00 kr ;
|
||||
Fiber Premium;99,90%;kl 8-17; 2 995,00 kr ;Lots of details
|
||||
Fiber Platina;99,90%;kl 8-17; 4 995,00 kr ;
|
||||
|
BIN
media/test-1200x671.webp
Normal file
|
After Width: | Height: | Size: 149 KiB |
BIN
media/test-1920x1073.webp
Normal file
|
After Width: | Height: | Size: 318 KiB |
BIN
media/test-400x224.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
media/test-800x447.webp
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
media/test.png
Normal file
|
After Width: | Height: | Size: 4.3 MiB |
@ -60,7 +60,7 @@ export const FDFaqBlockComponent: React.FC<FDFaqBlockProps> = ({
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<div className={`font-joey text-fd-body pl-7 md:pl-9 fd-prose ${proseColor}`}>
|
||||
<RichText content={item.answer} />
|
||||
<RichText data={item.answer} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
74
src/blocks/FDFaqBlock/FDFaqBlock-config.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import type { Block } from 'payload'
|
||||
import {
|
||||
lexicalEditor,
|
||||
BoldFeature,
|
||||
ItalicFeature,
|
||||
UnderlineFeature,
|
||||
LinkFeature,
|
||||
UnorderedListFeature,
|
||||
OrderedListFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
const fdRichTextEditor = lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BoldFeature(),
|
||||
ItalicFeature(),
|
||||
UnderlineFeature(),
|
||||
LinkFeature({ enabledCollections: ['pages', 'posts'] }),
|
||||
UnorderedListFeature(),
|
||||
OrderedListFeature(),
|
||||
],
|
||||
})
|
||||
|
||||
export const FDFaqBlock: Block = {
|
||||
slug: 'fdFaq',
|
||||
interfaceName: 'FDFaqBlock',
|
||||
labels: {
|
||||
singular: 'FD FAQ',
|
||||
plural: 'FD FAQs',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'heading',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
required: true,
|
||||
label: 'Rubrik',
|
||||
defaultValue: 'Vanliga frågor',
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
label: 'Frågor',
|
||||
minRows: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'question',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
required: true,
|
||||
label: 'Fråga',
|
||||
},
|
||||
{
|
||||
name: 'answer',
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
label: 'Svar',
|
||||
editor: fdRichTextEditor,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'theme',
|
||||
type: 'select',
|
||||
label: 'Tema',
|
||||
defaultValue: 'gray',
|
||||
options: [
|
||||
{ label: 'Grå bakgrund', value: 'gray' },
|
||||
{ label: 'Ljust', value: 'light' },
|
||||
{ label: 'Mörkt', value: 'dark' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -58,7 +58,7 @@ export const FDTextBlockComponent: React.FC<FDTextBlockProps> = ({
|
||||
)}
|
||||
{body && (
|
||||
<div className={`font-joey text-fd-body-lg fd-prose ${colors.body}`}>
|
||||
<RichText content={body} />
|
||||
<RichText data={body} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
102
src/blocks/FDTextBlock/FDTextBlock-config.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import type { Block } from 'payload'
|
||||
import {
|
||||
lexicalEditor,
|
||||
BoldFeature,
|
||||
ItalicFeature,
|
||||
UnderlineFeature,
|
||||
LinkFeature,
|
||||
UnorderedListFeature,
|
||||
OrderedListFeature,
|
||||
HeadingFeature,
|
||||
BlockquoteFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
const fdRichTextEditor = lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BoldFeature(),
|
||||
ItalicFeature(),
|
||||
UnderlineFeature(),
|
||||
LinkFeature({ enabledCollections: ['pages', 'posts'] }),
|
||||
UnorderedListFeature(),
|
||||
OrderedListFeature(),
|
||||
HeadingFeature({ enabledHeadingSizes: ['h2', 'h3', 'h4'] }),
|
||||
BlockquoteFeature(),
|
||||
],
|
||||
})
|
||||
|
||||
export const FDTextBlock: Block = {
|
||||
slug: 'fdText',
|
||||
interfaceName: 'FDTextBlock',
|
||||
labels: {
|
||||
singular: 'FD Textblock',
|
||||
plural: 'FD Textblock',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'heading',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
label: 'Rubrik',
|
||||
},
|
||||
{
|
||||
name: 'subheading',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
label: 'Underrubrik',
|
||||
},
|
||||
{
|
||||
name: 'body',
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
label: 'Brödtext',
|
||||
editor: fdRichTextEditor,
|
||||
},
|
||||
{
|
||||
name: 'alignment',
|
||||
type: 'select',
|
||||
label: 'Textjustering',
|
||||
defaultValue: 'left',
|
||||
options: [
|
||||
{ label: 'Vänster', value: 'left' },
|
||||
{ label: 'Centrerad', value: 'center' },
|
||||
{ label: 'Höger', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'textColor',
|
||||
type: 'select',
|
||||
label: 'Textfärg',
|
||||
defaultValue: 'navy',
|
||||
options: [
|
||||
{ label: 'Navy', value: 'navy' },
|
||||
{ label: 'Vit', value: 'white' },
|
||||
{ label: 'Gul', value: 'yellow' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sectionBackground',
|
||||
type: 'select',
|
||||
label: 'Bakgrund',
|
||||
defaultValue: 'white',
|
||||
options: [
|
||||
{ label: 'Vit', value: 'white' },
|
||||
{ label: 'Navy', value: 'navy' },
|
||||
{ label: 'Grå', value: 'gray' },
|
||||
{ label: 'Gul', value: 'yellow' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'maxWidth',
|
||||
type: 'select',
|
||||
label: 'Maxbredd',
|
||||
defaultValue: 'wide',
|
||||
options: [
|
||||
{ label: 'Smal (600px)', value: 'narrow' },
|
||||
{ label: 'Medium (800px)', value: 'medium' },
|
||||
{ label: 'Bred (1100px)', value: 'wide' },
|
||||
{ label: 'Full', value: 'full' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -65,7 +65,7 @@ export const FDUspTableBlockComponent: React.FC<FDUspTableBlockProps> = ({
|
||||
<span className={`font-joey-bold text-fd-h3 ${txt}`}>{row.title}</span>
|
||||
</div>
|
||||
<div className={`font-joey text-fd-body fd-prose ${txt} ${proseOpacity} md:pl-0 pl-14`}>
|
||||
<RichText content={row.description} />
|
||||
<RichText data={row.description} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
92
src/blocks/FDUspTableBlock/FDUspTableBlock-config.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import type { Block } from 'payload'
|
||||
import {
|
||||
lexicalEditor,
|
||||
BoldFeature,
|
||||
ItalicFeature,
|
||||
UnderlineFeature,
|
||||
LinkFeature,
|
||||
UnorderedListFeature,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
const fdRichTextEditor = lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BoldFeature(),
|
||||
ItalicFeature(),
|
||||
UnderlineFeature(),
|
||||
LinkFeature({ enabledCollections: ['pages', 'posts'] }),
|
||||
UnorderedListFeature(),
|
||||
],
|
||||
})
|
||||
|
||||
export const FDUspTableBlock: Block = {
|
||||
slug: 'fdUspTable',
|
||||
interfaceName: 'FDUspTableBlock',
|
||||
labels: {
|
||||
singular: 'FD USP-tabell',
|
||||
plural: 'FD USP-tabeller',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'heading',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
label: 'Rubrik (valfri)',
|
||||
},
|
||||
{
|
||||
name: 'rows',
|
||||
type: 'array',
|
||||
label: 'Rader',
|
||||
minRows: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
required: true,
|
||||
label: 'Rubrik',
|
||||
admin: { description: 'T.ex. "Högsta säkerhet"' },
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
localized: true,
|
||||
label: 'Beskrivning',
|
||||
editor: fdRichTextEditor,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'checkColor',
|
||||
type: 'select',
|
||||
label: 'Checkikon-färg',
|
||||
defaultValue: 'navy',
|
||||
options: [
|
||||
{ label: 'Navy (vit bock)', value: 'navy' },
|
||||
{ label: 'Gul (navy bock)', value: 'yellow' },
|
||||
{ label: 'Grå (navy bock)', value: 'gray' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sectionBackground',
|
||||
type: 'select',
|
||||
label: 'Sektionsbakgrund',
|
||||
defaultValue: 'white',
|
||||
options: [
|
||||
{ label: 'Vit', value: 'white' },
|
||||
{ label: 'Grå', value: 'gray' },
|
||||
{ label: 'Navy', value: 'navy' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'textColor',
|
||||
type: 'select',
|
||||
label: 'Textfärg',
|
||||
defaultValue: 'navy',
|
||||
options: [
|
||||
{ label: 'Navy', value: 'navy' },
|
||||
{ label: 'Vit', value: 'white' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -2,16 +2,10 @@ import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { authenticated } from '../../access/authenticated'
|
||||
import { authenticatedOrPublished } from '../../access/authenticatedOrPublished'
|
||||
import { Archive } from '../../blocks/ArchiveBlock/config'
|
||||
import { CallToAction } from '../../blocks/CallToAction/config'
|
||||
import { Content } from '../../blocks/Content/config'
|
||||
import { FormBlock } from '../../blocks/Form/config'
|
||||
import { MediaBlock } from '../../blocks/MediaBlock/config'
|
||||
import { hero } from '@/heros/config'
|
||||
import { slugField } from 'payload'
|
||||
import { populatePublishedAt } from '../../hooks/populatePublishedAt'
|
||||
import { generatePreviewPath } from '../../utilities/generatePreviewPath'
|
||||
import { revalidateDelete, revalidatePage } from './hooks/revalidatePage'
|
||||
|
||||
import { FDHeroBlock } from '../../blocks/FDHeroBlock/config'
|
||||
import { FDCtaSideImageBlock } from '../../blocks/FDCtaSideImageBlock/config'
|
||||
import { FDFeatureAnnouncementBlock } from '../../blocks/FDFeatureAnnouncementBlock/config'
|
||||
@ -49,6 +43,16 @@ import {
|
||||
PreviewField,
|
||||
} from '@payloadcms/plugin-seo/fields'
|
||||
|
||||
// ── Slug generator — handles Swedish characters ────────────────────────────
|
||||
const generateSlug = (value: string): string =>
|
||||
value
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[åä]/g, 'a')
|
||||
.replace(/ö/g, 'o')
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
|
||||
export const Pages: CollectionConfig<'pages'> = {
|
||||
slug: 'pages',
|
||||
access: {
|
||||
@ -57,9 +61,6 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||
read: authenticatedOrPublished,
|
||||
update: authenticated,
|
||||
},
|
||||
// This config controls what's populated by default when a page is referenced
|
||||
// https://payloadcms.com/docs/queries/select#defaultpopulate-collection-config-property
|
||||
// Type safe if the collection slug generic is passed to `CollectionConfig` - `CollectionConfig<'pages'>
|
||||
defaultPopulate: {
|
||||
title: true,
|
||||
slug: true,
|
||||
@ -91,23 +92,48 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
fields: [hero],
|
||||
label: 'Hero',
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'blocks',
|
||||
blocks: [FDHeroBlock, FDCtaSideImageBlock, FDFeatureAnnouncementBlock, FDServicesGridBlock, FDContactBlock, FDFaqBlock, FDCardGridBlock, FDPricingCardBlock, FDSpacerBlock, FDIconBarBlock, FDUspChecklistBlock, FDWideCardBlock, FDTechPropertiesBlock, FDUspTableBlock, FDHeaderTextImageBlock, FDContactFormBlock, FDLocationsGridBlock, FDAlternateHeroBlock, FDStatisticsBlock, FDPartnersLogosBlock, FDNewsletterBlock, FDServiceChooserBlock, FDDataTableBlock, FDVpsCalculatorBlock, FDTagsBlock, FDTextBlock, FDCodeEmbedBlock, FDVideoBlock],
|
||||
blocks: [
|
||||
FDHeroBlock,
|
||||
FDCtaSideImageBlock,
|
||||
FDFeatureAnnouncementBlock,
|
||||
FDServicesGridBlock,
|
||||
FDContactBlock,
|
||||
FDFaqBlock,
|
||||
FDCardGridBlock,
|
||||
FDPricingCardBlock,
|
||||
FDSpacerBlock,
|
||||
FDIconBarBlock,
|
||||
FDUspChecklistBlock,
|
||||
FDWideCardBlock,
|
||||
FDTechPropertiesBlock,
|
||||
FDUspTableBlock,
|
||||
FDHeaderTextImageBlock,
|
||||
FDContactFormBlock,
|
||||
FDLocationsGridBlock,
|
||||
FDAlternateHeroBlock,
|
||||
FDStatisticsBlock,
|
||||
FDPartnersLogosBlock,
|
||||
FDNewsletterBlock,
|
||||
FDServiceChooserBlock,
|
||||
FDDataTableBlock,
|
||||
FDVpsCalculatorBlock,
|
||||
FDTagsBlock,
|
||||
FDTextBlock,
|
||||
FDCodeEmbedBlock,
|
||||
FDVideoBlock,
|
||||
],
|
||||
required: true,
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
label: 'Content',
|
||||
label: 'Innehåll',
|
||||
},
|
||||
{
|
||||
name: 'meta',
|
||||
@ -124,13 +150,9 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||
MetaImageField({
|
||||
relationTo: 'media',
|
||||
}),
|
||||
|
||||
MetaDescriptionField({}),
|
||||
PreviewField({
|
||||
// if the `generateUrl` function is configured
|
||||
hasGenerateFn: true,
|
||||
|
||||
// field paths to match the target field for data
|
||||
titlePath: 'meta.title',
|
||||
descriptionPath: 'meta.description',
|
||||
}),
|
||||
@ -145,7 +167,22 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
slugField(),
|
||||
// ── Slug ──────────────────────────────────────────────────────────────
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
index: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Genereras automatiskt från titeln.',
|
||||
},
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => generateSlug(data?.title || ''),
|
||||
],
|
||||
},
|
||||
}, // ← slug object closes here, then straight into ],
|
||||
],
|
||||
hooks: {
|
||||
afterChange: [revalidatePage],
|
||||
@ -155,7 +192,7 @@ export const Pages: CollectionConfig<'pages'> = {
|
||||
versions: {
|
||||
drafts: {
|
||||
autosave: {
|
||||
interval: 100, // We set this interval for optimal live preview
|
||||
interval: 100,
|
||||
},
|
||||
schedulePublish: true,
|
||||
},
|
||||
|
||||
@ -25,7 +25,16 @@ import {
|
||||
OverviewField,
|
||||
PreviewField,
|
||||
} from '@payloadcms/plugin-seo/fields'
|
||||
import { slugField } from 'payload'
|
||||
|
||||
// ── Slug generator — handles Swedish characters ────────────────────────────
|
||||
const generateSlug = (value: string): string =>
|
||||
value
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[åä]/g, 'a')
|
||||
.replace(/ö/g, 'o')
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
|
||||
export const Posts: CollectionConfig<'posts'> = {
|
||||
slug: 'posts',
|
||||
@ -35,9 +44,6 @@ export const Posts: CollectionConfig<'posts'> = {
|
||||
read: authenticatedOrPublished,
|
||||
update: authenticated,
|
||||
},
|
||||
// This config controls what's populated by default when a post is referenced
|
||||
// https://payloadcms.com/docs/queries/select#defaultpopulate-collection-config-property
|
||||
// Type safe if the collection slug generic is passed to `CollectionConfig` - `CollectionConfig<'posts'>
|
||||
defaultPopulate: {
|
||||
title: true,
|
||||
slug: true,
|
||||
@ -100,7 +106,7 @@ export const Posts: CollectionConfig<'posts'> = {
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
label: 'Content',
|
||||
label: 'Innehåll',
|
||||
},
|
||||
{
|
||||
fields: [
|
||||
@ -147,13 +153,9 @@ export const Posts: CollectionConfig<'posts'> = {
|
||||
MetaImageField({
|
||||
relationTo: 'media',
|
||||
}),
|
||||
|
||||
MetaDescriptionField({}),
|
||||
PreviewField({
|
||||
// if the `generateUrl` function is configured
|
||||
hasGenerateFn: true,
|
||||
|
||||
// field paths to match the target field for data
|
||||
titlePath: 'meta.title',
|
||||
descriptionPath: 'meta.description',
|
||||
}),
|
||||
@ -190,9 +192,6 @@ export const Posts: CollectionConfig<'posts'> = {
|
||||
hasMany: true,
|
||||
relationTo: 'users',
|
||||
},
|
||||
// This field is only used to populate the user data via the `populateAuthors` hook
|
||||
// This is because the `user` collection has access control locked to protect user privacy
|
||||
// GraphQL will also not return mutated user data that differs from the underlying schema
|
||||
{
|
||||
name: 'populatedAuthors',
|
||||
type: 'array',
|
||||
@ -214,7 +213,23 @@ export const Posts: CollectionConfig<'posts'> = {
|
||||
},
|
||||
],
|
||||
},
|
||||
slugField(),
|
||||
|
||||
// ── Slug ──────────────────────────────────────────────────────────────
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
index: true,
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Genereras automatiskt från titeln.',
|
||||
},
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => generateSlug(data?.title || ''),
|
||||
],
|
||||
},
|
||||
}, // ← slug object closes here, then straight into ],
|
||||
],
|
||||
hooks: {
|
||||
afterChange: [revalidatePost],
|
||||
@ -224,7 +239,7 @@ export const Posts: CollectionConfig<'posts'> = {
|
||||
versions: {
|
||||
drafts: {
|
||||
autosave: {
|
||||
interval: 100, // We set this interval for optimal live preview
|
||||
interval: 100,
|
||||
},
|
||||
schedulePublish: true,
|
||||
},
|
||||
|
||||
@ -153,49 +153,6 @@ export interface UserAuthOperations {
|
||||
export interface Page {
|
||||
id: number;
|
||||
title: string;
|
||||
hero: {
|
||||
type: 'none' | 'highImpact' | 'mediumImpact' | 'lowImpact';
|
||||
richText?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
links?:
|
||||
| {
|
||||
link: {
|
||||
type?: ('reference' | 'custom') | null;
|
||||
newTab?: boolean | null;
|
||||
reference?:
|
||||
| ({
|
||||
relationTo: 'pages';
|
||||
value: number | Page;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: number | Post;
|
||||
} | null);
|
||||
url?: string | null;
|
||||
label: string;
|
||||
/**
|
||||
* Choose how the link should be rendered.
|
||||
*/
|
||||
appearance?: ('default' | 'outline') | null;
|
||||
};
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
media?: (number | null) | Media;
|
||||
};
|
||||
layout: (
|
||||
| FDHeroBlock
|
||||
| FDCtaSideImageBlock
|
||||
@ -236,9 +193,8 @@ export interface Page {
|
||||
};
|
||||
publishedAt?: string | null;
|
||||
/**
|
||||
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
||||
* Genereras automatiskt från titeln.
|
||||
*/
|
||||
generateSlug?: boolean | null;
|
||||
slug: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@ -246,53 +202,32 @@ export interface Page {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
* via the `definition` "FDHeroBlock".
|
||||
*/
|
||||
export interface Post {
|
||||
id: number;
|
||||
title: string;
|
||||
heroImage?: (number | null) | Media;
|
||||
content: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
relatedPosts?: (number | Post)[] | null;
|
||||
categories?: (number | Category)[] | null;
|
||||
meta?: {
|
||||
title?: string | null;
|
||||
export interface FDHeroBlock {
|
||||
heading: string;
|
||||
subheading?: string | null;
|
||||
body?: string | null;
|
||||
ctaText?: string | null;
|
||||
ctaLink?: string | null;
|
||||
secondaryCtaText?: string | null;
|
||||
secondaryCtaLink?: string | null;
|
||||
/**
|
||||
* Maximum upload file size: 12MB. Recommended file size for images is <500KB.
|
||||
* Fullbreddsbild bakom texten. Lämna tom för enfärgad bakgrund.
|
||||
*/
|
||||
image?: (number | null) | Media;
|
||||
description?: string | null;
|
||||
};
|
||||
publishedAt?: string | null;
|
||||
authors?: (number | User)[] | null;
|
||||
populatedAuthors?:
|
||||
| {
|
||||
backgroundImage?: (number | null) | Media;
|
||||
/**
|
||||
* Hur mörk overlay över bilden (för läsbarhet)
|
||||
*/
|
||||
overlayOpacity?: ('30' | '50' | '70') | null;
|
||||
textColor?: ('auto' | 'white' | 'navy') | null;
|
||||
/**
|
||||
* Ignoreras om bakgrundsbild är vald
|
||||
*/
|
||||
theme?: ('light' | 'dark') | null;
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* When enabled, the slug will auto-generate from the title field on save and autosave.
|
||||
*/
|
||||
generateSlug?: boolean | null;
|
||||
slug: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'fdHero';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@ -347,81 +282,6 @@ export interface Media {
|
||||
};
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "categories".
|
||||
*/
|
||||
export interface Category {
|
||||
id: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
parent?: (number | null) | Category;
|
||||
breadcrumbs?:
|
||||
| {
|
||||
doc?: (number | null) | Category;
|
||||
url?: string | null;
|
||||
label?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
name?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
resetPasswordExpiration?: string | null;
|
||||
salt?: string | null;
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
sessions?:
|
||||
| {
|
||||
id: string;
|
||||
createdAt?: string | null;
|
||||
expiresAt: string;
|
||||
}[]
|
||||
| null;
|
||||
password?: string | null;
|
||||
collection: 'users';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "FDHeroBlock".
|
||||
*/
|
||||
export interface FDHeroBlock {
|
||||
heading: string;
|
||||
subheading?: string | null;
|
||||
body?: string | null;
|
||||
ctaText?: string | null;
|
||||
ctaLink?: string | null;
|
||||
secondaryCtaText?: string | null;
|
||||
secondaryCtaLink?: string | null;
|
||||
/**
|
||||
* Fullbreddsbild bakom texten. Lämna tom för enfärgad bakgrund.
|
||||
*/
|
||||
backgroundImage?: (number | null) | Media;
|
||||
/**
|
||||
* Hur mörk overlay över bilden (för läsbarhet)
|
||||
*/
|
||||
overlayOpacity?: ('30' | '50' | '70') | null;
|
||||
textColor?: ('auto' | 'white' | 'navy') | null;
|
||||
/**
|
||||
* Ignoreras om bakgrundsbild är vald
|
||||
*/
|
||||
theme?: ('light' | 'dark') | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'fdHero';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "FDCtaSideImageBlock".
|
||||
@ -1376,6 +1236,101 @@ export interface FDVideoBlock {
|
||||
blockName?: string | null;
|
||||
blockType: 'fdVideo';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
id: number;
|
||||
title: string;
|
||||
heroImage?: (number | null) | Media;
|
||||
content: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
};
|
||||
relatedPosts?: (number | Post)[] | null;
|
||||
categories?: (number | Category)[] | null;
|
||||
meta?: {
|
||||
title?: string | null;
|
||||
/**
|
||||
* Maximum upload file size: 12MB. Recommended file size for images is <500KB.
|
||||
*/
|
||||
image?: (number | null) | Media;
|
||||
description?: string | null;
|
||||
};
|
||||
publishedAt?: string | null;
|
||||
authors?: (number | User)[] | null;
|
||||
populatedAuthors?:
|
||||
| {
|
||||
id?: string | null;
|
||||
name?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
/**
|
||||
* Genereras automatiskt från titeln.
|
||||
*/
|
||||
slug: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "categories".
|
||||
*/
|
||||
export interface Category {
|
||||
id: number;
|
||||
title: string;
|
||||
slug: string;
|
||||
parent?: (number | null) | Category;
|
||||
breadcrumbs?:
|
||||
| {
|
||||
doc?: (number | null) | Category;
|
||||
url?: string | null;
|
||||
label?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
*/
|
||||
export interface User {
|
||||
id: number;
|
||||
name?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
email: string;
|
||||
resetPasswordToken?: string | null;
|
||||
resetPasswordExpiration?: string | null;
|
||||
salt?: string | null;
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
sessions?:
|
||||
| {
|
||||
id: string;
|
||||
createdAt?: string | null;
|
||||
expiresAt: string;
|
||||
}[]
|
||||
| null;
|
||||
password?: string | null;
|
||||
collection: 'users';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "redirects".
|
||||
@ -1615,28 +1570,6 @@ export interface PayloadMigration {
|
||||
*/
|
||||
export interface PagesSelect<T extends boolean = true> {
|
||||
title?: T;
|
||||
hero?:
|
||||
| T
|
||||
| {
|
||||
type?: T;
|
||||
richText?: T;
|
||||
links?:
|
||||
| T
|
||||
| {
|
||||
link?:
|
||||
| T
|
||||
| {
|
||||
type?: T;
|
||||
newTab?: T;
|
||||
reference?: T;
|
||||
url?: T;
|
||||
label?: T;
|
||||
appearance?: T;
|
||||
};
|
||||
id?: T;
|
||||
};
|
||||
media?: T;
|
||||
};
|
||||
layout?:
|
||||
| T
|
||||
| {
|
||||
@ -1677,7 +1610,6 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
description?: T;
|
||||
};
|
||||
publishedAt?: T;
|
||||
generateSlug?: T;
|
||||
slug?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
@ -2273,7 +2205,6 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
id?: T;
|
||||
name?: T;
|
||||
};
|
||||
generateSlug?: T;
|
||||
slug?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
|
||||