diff --git a/scripts/seed-legal.mjs b/scripts/seed-legal.mjs index f80b69e..5c48751 100644 --- a/scripts/seed-legal.mjs +++ b/scripts/seed-legal.mjs @@ -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') diff --git a/src/app/api/admin/seed/route.ts b/src/app/api/admin/seed/route.ts index 51ff9bf..0841eeb 100644 --- a/src/app/api/admin/seed/route.ts +++ b/src/app/api/admin/seed/route.ts @@ -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",