fix(seed): write Lexical JSON to richText fields instead of plain strings
Some checks are pending
CI / Type Check (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Unit Tests (push) Waiting to run
Deploy / Build & Push Image (push) Waiting to run
Deploy / Deploy to VPS (push) Blocked by required conditions

makeLexical moved to module scope and applied to locations.shortDesc,
home-page subtitle/whyParents/birthdayIntro/news, dinosaur-page
descriptions; seed-legal.mjs wraps legal texts for jsonb columns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-06-12 11:16:18 +01:00
parent 03cd2e39ab
commit 43b8da73aa
2 changed files with 92 additions and 42 deletions

View file

@ -33,15 +33,49 @@ const client = new pg.Client({
})
await client.connect()
// Content columns are jsonb (Lexical) since migration 0021 — wrap plain text.
function textToLexical(text) {
return JSON.stringify({
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
direction: 'ltr',
children: text.split('\n').map((line) => ({
type: 'paragraph',
format: '',
indent: 0,
version: 1,
direction: 'ltr',
children:
line === ''
? []
: [
{
type: 'text',
detail: 0,
format: 0,
mode: 'normal',
style: '',
text: line,
version: 1,
},
],
})),
},
})
}
await client.query(
`UPDATE legal_pages SET
privacy_content = $1,
terms_content = $2,
offer_content = $3,
data_processing_content = $4,
privacy_content = $1::jsonb,
terms_content = $2::jsonb,
offer_content = $3::jsonb,
data_processing_content = $4::jsonb,
updated_at = now()
WHERE id = 1`,
[PRIVACY_DEFAULT, TERMS_DEFAULT, OFFER_DEFAULT, DATA_PROCESSING_DEFAULT]
[PRIVACY_DEFAULT, TERMS_DEFAULT, OFFER_DEFAULT, DATA_PROCESSING_DEFAULT].map(textToLexical)
)
console.log('✓ Legal pages seeded')

View file

@ -4,6 +4,30 @@ import config from '@payload-config'
import path from 'path'
import fs from 'fs'
// Wraps plain paragraphs into a Lexical editor state — required for all
// richText fields (shortDesc, descriptions, subtitles, blog body).
function makeLexical(paragraphs: string[]) {
return {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
direction: 'ltr',
children: paragraphs.map((text) => ({
type: 'paragraph',
format: '',
indent: 0,
version: 1,
direction: 'ltr',
children: [
{ type: 'text', detail: 0, format: 0, mode: 'normal', style: '', text, version: 1 },
],
})),
},
}
}
function getMimeType(filename: string): string {
if (filename.endsWith('.jpg') || filename.endsWith('.jpeg')) return 'image/jpeg'
if (filename.endsWith('.png')) return 'image/png'
@ -174,7 +198,7 @@ export async function POST(req: NextRequest) {
name: loc.name,
slug: loc.slug,
tagline: loc.tagline,
shortDesc: loc.shortDesc,
shortDesc: makeLexical([loc.shortDesc]),
image: imageId ?? undefined,
showInMenu: true,
showOnHome: true,
@ -362,8 +386,9 @@ export async function POST(req: NextRequest) {
data: {
hero: {
title: 'ШУМІЛЕНД \nСВІТ, ДЕ КАЗКА\nОЖИВАЄ',
subtitle:
subtitle: makeLexical([
'Сімейний тематичний парк, де гра допомагає пізнавати світ, а кожна прогулянка перетворюється на незабутню пригоду.',
]),
ctaLabel: 'Купити квиток',
ctaHref: '/payments',
foregroundOverlay: heroBg1Media ?? undefined,
@ -384,33 +409,39 @@ export async function POST(req: NextRequest) {
items: [
{
title: 'Подорож кількома світами за один день',
description:
description: makeLexical([
'ДиноПарк, Диво Ліс, Дзеркальний лабіринт — кожна локація це окремий всесвіт пригод для дітей і батьків.',
]),
},
{
title: 'Свіже повітря та затишок лісу',
description:
description: makeLexical([
'Ми оновлюємо тематику та декорації до кожного сезону, тому тут буде цікаво кожного візиту.',
]),
},
{
title: 'Нова казка кожної пори року',
description:
description: makeLexical([
'Зима, весна, літо, осінь — кожен сезон у парку неповторний. Святкові декорації та тематичні заходи чекають на вас.',
]),
},
{
title: 'Безпека понад усе',
description:
description: makeLexical([
'Всі атракції та зони проходять регулярну перевірку. Охоронці, медичний персонал та чіткі правила безпеки.',
]),
},
{
title: 'Все необхідне — поруч і без пошуків',
description:
description: makeLexical([
'Паркування, вбиральні, зона для годування немовлят, укриття, фудкорт — все на місці.',
]),
},
{
title: 'Фудкорт — смачно для всієї родини',
description:
description: makeLexical([
'Хот-доги, піца, кава, лимонади та багато іншого. Є дитяче меню та здорові перекуси.',
]),
},
],
sideGallery: wpMediaIds.filter(Boolean).map((id) => ({ image: id })),
@ -425,11 +456,13 @@ export async function POST(req: NextRequest) {
src: null,
},
birthdayIntro: {
text: 'Незабутнє свято для вашої дитини. Ми подбаємо про все: від декорацій до аніматорів!',
text: makeLexical([
'Незабутнє свято для вашої дитини. Ми подбаємо про все: від декорацій до аніматорів!',
]),
},
news: {
title: 'Новини',
subtitle: 'Свіжі події, акції та оновлення парку.',
subtitle: makeLexical(['Свіжі події, акції та оновлення парку.']),
limit: 3,
},
map: {
@ -485,28 +518,6 @@ export async function POST(req: NextRequest) {
results.push('Seeded site-settings global')
// === BLOG POSTS ===
function makeLexical(paragraphs: string[]) {
return {
root: {
type: 'root',
format: '',
indent: 0,
version: 1,
direction: 'ltr',
children: paragraphs.map((text) => ({
type: 'paragraph',
format: '',
indent: 0,
version: 1,
direction: 'ltr',
children: [
{ type: 'text', detail: 0, format: 0, mode: 'normal', style: '', text, version: 1 },
],
})),
},
}
}
const { totalDocs: postCount } = await payload.find({
collection: 'blog-posts',
limit: 1,
@ -937,8 +948,9 @@ export async function POST(req: NextRequest) {
slug: 'dinosaur-page' as never,
data: {
heroTitle: 'Динопарк — портал у світ динозаврів',
heroDescription:
heroDescription: makeLexical([
'Великі динозаври, що рухаються та гарчать, справжнє роздоволлє, цікаві екскурсії та динородео — тут є все, щоб ваша дитина не нудьгувала.',
]),
heroStat: '26',
heroStatLabel: 'унікальних експонатів',
heroFeatures: [
@ -956,8 +968,9 @@ export async function POST(req: NextRequest) {
{ name: 'Анкілозавр', epoch: 'Крейдяний', length: '8 м', weight: '7 т' },
],
activitiesTitle: 'Додаткові розваги у динопарку',
activitiesDescription:
activitiesDescription: makeLexical([
'Хочете дізнатись ще більше про динозаврів? Замовте екскурсію з гідом, поринь у світ палеонтологічних розкопок або підкорюй справжнього динозавра!',
]),
activities: [
{ name: 'Звичайна екскурсія', price: '150 грн', href: '#tickets' },
{ name: 'Палеонтологічна екскурсія', price: '300 грн', href: '#tickets' },
@ -967,18 +980,21 @@ export async function POST(req: NextRequest) {
whyVisitItems: [
{
title: 'Навчання через гру',
description:
description: makeLexical([
'Дітки дізнаються про стародавніх тварин через захопливі ігри та інтерактивні вправи з гідом.',
]),
},
{
title: 'Дитячі очі, що палають захватом',
description:
description: makeLexical([
'Реалістичні рухи та звуки динозаврів створюють ефект повного занурення — дитина точно не забуде цього дня.',
]),
},
{
title: 'Неймовірні фотографії',
description:
description: makeLexical([
'Сфотографуйтесь поруч із улюбленим динозавром або зробіть фото з екскурсоводом — тепла згадка для всієї родини.',
]),
},
],
workingHours: "п'ятниця-субота-неділя з 11:00 до 20:00",