Shumiland/src/seed.ts
Vadym Samoilenko b33ec01b16
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
feat(home): add FAQ section and seed WhyParents items
- Add FAQ accordion section (FAQ.tsx) after Reviews on home page
- Add faq group field to HomePage global (title + items array)
- Add HomePageFaq types to types/globals.ts
- Seed whyParents.items with 6 items from static fallback texts
- Seed faq with 6 Q&A items about the park
- Migration 0012: create home_page_faq_items table for Payload array field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 14:29:27 +01:00

424 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dotenv/config'
import { getPayload } from 'payload'
import config from '../payload.config.js'
async function seed(): Promise<void> {
const payload = await getPayload({ config })
// FORCE_SEED=true is required to overwrite globals that may have been edited in the admin UI.
// Without it, only empty collections (users, blog posts, tariffs, locations) are seeded.
const force = process.env['FORCE_SEED'] === 'true'
if (!force) {
console.log('Running in safe mode — globals will NOT be overwritten.')
console.log('Set FORCE_SEED=true to re-seed all globals (will overwrite admin changes).')
}
// Admin user
const { totalDocs } = await payload.find({
collection: 'users',
limit: 1,
overrideAccess: true,
})
if (totalDocs === 0) {
await payload.create({
collection: 'users',
data: {
email: 'admin@shumiland.ua',
password: 'changeMe123!',
name: 'Admin',
role: 'admin',
},
overrideAccess: true,
})
console.log('Created admin user: admin@shumiland.ua / changeMe123!')
} else {
console.log('Users already exist, skipping user seed.')
}
if (!force) {
console.log('Skipping globals seed — run with FORCE_SEED=true to overwrite.')
}
// Home page global with real content
if (force) {
await payload.updateGlobal({
slug: 'home-page',
data: {
hero: {
title: 'Започаткуйте традицію:',
subtitle: 'щороку фотографуйтесь біля улюбленого динозавра',
ctaLabel: 'Купити квиток',
ctaHref: '/kvytky',
},
sectionTitles: {
locations: 'ЛАСКАВО ПРОСИМО ДО ШУМІЛЕНДУ',
whyParents: 'ЧОМУ БАТЬКИ ОБИРАЮТЬ ШУМІЛЕНД',
birthday: 'ДЕНЬ НАРОДЖЕННЯ В ШУМІЛЕНДІ',
gallery: 'ФОТОГАЛЕРЕЯ',
reviews: 'ВІДГУКИ',
news: 'НОВИНИ',
},
locations: [
{
name: 'ДиноПарк',
shortDesc: 'Прогуляйтесь серед реалістичних динозаврів у повний зріст',
href: '/lokatsii',
},
{
name: 'Диво Ліс',
shortDesc: 'Чарівний ліс з інтерактивними атракціонами та мотузковими парками',
href: '/lokatsii',
},
{
name: 'Дзеркальний Лабіринт',
shortDesc: 'Захоплюючий лабіринт з дзеркалами та оптичними ілюзіями',
href: '/lokatsii',
},
],
features: [
{
icon: '🦕',
title: 'Безпека',
description: 'Атракціони сертифіковані, майданчик під постійним наглядом',
},
{ icon: '🌲', title: 'Природа', description: 'Парк розташований серед живої природи' },
{
icon: '🎉',
title: 'Свята',
description: 'Дні народження, корпоративи та шкільні екскурсії',
},
{
icon: '🎟️',
title: 'Квитки онлайн',
description: 'Купуйте квитки онлайн без черги на касі',
},
{
icon: '🍕',
title: 'Кафе та їжа',
description: 'Власне кафе з дитячим меню та легкими закусками',
},
{ icon: '🅿️', title: 'Парковка', description: 'Безкоштовна парковка для відвідувачів' },
],
whyParents: {
items: [
{
title: 'Подорож кількома світами за один день',
description:
'ДиноПарк, Диво Ліс, Дзеркальний лабіринт — кожна локація це окремий всесвіт пригод для дітей і батьків.',
},
{
title: 'Свіже повітря та затишок лісу',
description:
"Ми оновлюємо тематику та декорації до кожного сезону, тому тут буде цікаво кожного візиту. А у вас з'являться нові яскраві фото у сімейному альбомі.",
},
{
title: 'Нова казка кожної пори року',
description:
'Зима, весна, літо, осінь — кожен сезон у парку неповторний. Святкові декорації, сезонні активності та тематичні заходи чекають на вас.',
},
{
title: 'Безпека понад усе',
description:
'Всі атракції та зони проходять регулярну перевірку. Охоронці, медичний персонал та чіткі правила безпеки забезпечують спокій для батьків.',
},
{
title: 'Все необхідне — поруч і без пошуків',
description:
'Паркування, вбиральні, зона для годування немовлят, укриття, фудкорт — все на місці, щоб ваш відпочинок був справді комфортним.',
},
{
title: 'Фудкорт — смачно для всієї родини',
description:
'Хот-доги, піца, кава, лимонади та багато іншого. Є дитяче меню та здорові перекуси — ніхто не залишиться голодним.',
},
],
},
faq: {
title: 'ЧАСТІ ЗАПИТАННЯ',
items: [
{
question: 'Скільки коштує вхід до Шуміленду?',
answer:
'Вартість вхідного квитка до ДиноПарку — 300 грн. Є комбо-квитки на кількох відвідувачів зі знижкою. Актуальні ціни та варіанти квитків дивіться у розділі «Квитки».',
},
{
question: 'Чи підходить парк для дітей різного віку?',
answer:
'Шуміленд розроблений для дітей від 3 до 14 років, але й дорослі знаходять тут чимало цікавого. Для найменших є безпечні зони зі спрощеними атракціонами, для старших — більш захоплюючі маршрути.',
},
{
question: 'Який час роботи парку?',
answer:
'Парк відкритий щодня з 11:00 до 20:00. У святкові дні та під час спеціальних заходів графік може змінюватись — слідкуйте за анонсами у соцмережах.',
},
{
question: 'Чи є безкоштовна парковка?',
answer:
'Так, для відвідувачів Шуміленду передбачена безкоштовна парковка безпосередньо біля входу в парк.',
},
{
question: 'Чи можна відсвяткувати день народження в парку?',
answer:
'Так! Ми пропонуємо кілька пакетів для святкування дня народження: від базового з арендою зони та аніматором до преміального з кейтерингом та тематичним декором. Детальніше — у розділі «Дні народження».',
},
{
question: 'Чи є в парку місця для перекусу?',
answer:
'У Шуміленді є власний фудкорт з хот-догами, піцою, кавою, лимонадами та дитячим меню. Також є зони зі столиками, де можна відпочити і поїсти.',
},
],
},
news: { title: 'Новини', limit: 3 },
} as never,
overrideAccess: true,
})
console.log('Seeded home-page global')
}
if (force) {
// Header global
await payload.updateGlobal({
slug: 'header',
data: {
navLinks: [
{ label: 'Головна', href: '/' },
{ label: 'Локації', href: '/lokatsii' },
{ label: 'Блог', href: '/blog' },
{ label: 'Дні народження', href: '/dni-narodzhennia' },
{ label: 'Групові відвідування', href: '/grupovi-vidviduvannia' },
],
ctaLabel: 'Купити квиток',
ctaHref: '/kvytky',
} as never,
overrideAccess: true,
})
console.log('Seeded header global')
// Footer global
await payload.updateGlobal({
slug: 'footer',
data: { copyrightText: `© Шуміленд ${new Date().getFullYear()}` } as never,
overrideAccess: true,
})
console.log('Seeded footer global')
// Site settings
await payload.updateGlobal({
slug: 'site-settings',
data: {
siteName: 'Шуміленд',
siteURL: process.env['NEXT_PUBLIC_SITE_URL'] ?? 'https://shumiland.ua',
} as never,
overrideAccess: true,
})
console.log('Seeded site-settings global')
// Remaining globals — initialize empty
for (const slug of ['checkout-page', 'thank-you-page'] as const) {
try {
await payload.updateGlobal({ slug, data: {} as never, overrideAccess: true })
console.log(`Initialized global: ${slug}`)
} catch (err) {
console.warn(`Could not initialize global ${slug}:`, err)
}
}
}
// Blog posts
const { totalDocs: postCount } = await payload.find({
collection: 'blog-posts',
limit: 1,
overrideAccess: true,
})
if (postCount === 0) {
const posts = [
{
title: 'Сезон динозаврів відкрито!',
slug: 'sezon-dynozavriv-vidkryto',
excerpt: 'Шуміленд вітає нових мешканців ДиноПарку — зустрічайте 12 нових динозаврів!',
status: 'published' as const,
publishedAt: new Date('2025-04-01').toISOString(),
},
{
title: 'Весняні канікули в Шуміленді',
slug: 'vesniani-kanikuly-v-shumilenди',
excerpt:
'Проведіть весняні канікули незабутньо! Спеціальні активності щодня з 28 березня по 6 квітня.',
status: 'published' as const,
publishedAt: new Date('2025-03-20').toISOString(),
},
{
title: 'Нова локація: Тир з призами',
slug: 'nova-lokatsiya-tyr-z-pryzamy',
excerpt:
'Відтепер у Шуміленді є новий Тир з призами — точний постріл приносить реальний виграш!',
status: 'published' as const,
publishedAt: new Date('2025-03-10').toISOString(),
},
]
for (const post of posts) {
await payload.create({ collection: 'blog-posts', data: post as never, overrideAccess: true })
}
console.log('Seeded blog posts')
} else {
console.log('Blog posts already exist, skipping.')
}
// Tariffs — seed only if none exist (never delete admin-managed tariffs)
{
const { totalDocs: tariffCount } = await payload.find({
collection: 'tariffs',
limit: 1,
overrideAccess: true,
})
if (tariffCount > 0) {
console.log('Tariffs already exist, skipping.')
} else {
const tariffs = [
// Individual dino-park tickets (shown on both Dino and DyvoLis pages)
{
ezy_id: 1001,
last_synced_name: 'Вхід до Динопарку',
display_name: 'Вхід до Динопарку',
last_synced_price: 300,
category_tag: 'dyno',
sort: 1,
visible: true,
},
{
ezy_id: 1002,
last_synced_name: 'Звичайна екскурсія',
display_name: 'Звичайна екскурсія',
last_synced_price: 150,
category_tag: 'dyno',
sort: 2,
visible: true,
},
{
ezy_id: 1003,
last_synced_name: 'Палеонтологічна екскурсія',
display_name: 'Палеонтологічна екскурсія',
last_synced_price: 300,
category_tag: 'dyno',
sort: 3,
visible: true,
},
{
ezy_id: 1004,
last_synced_name: 'ДиноРодо',
display_name: 'ДиноРодо',
last_synced_price: 50,
category_tag: 'dyno',
sort: 4,
visible: true,
},
// Combo tickets
{
ezy_id: 3001,
last_synced_name: 'Комбо на 1 людину',
display_name: 'Комбо на 1 людину',
last_synced_price: 600,
category_tag: 'combo',
sort: 1,
visible: true,
},
{
ezy_id: 3002,
last_synced_name: 'Комбо на 3 людини',
display_name: 'Комбо на 3 людини',
last_synced_price: 1500,
category_tag: 'combo',
sort: 2,
visible: true,
},
{
ezy_id: 3003,
last_synced_name: 'Комбо на 4 людини',
display_name: 'Комбо на 4 людини',
last_synced_price: 1800,
category_tag: 'combo',
sort: 3,
visible: true,
},
{
ezy_id: 3004,
last_synced_name: 'Комбо на 5 людин',
display_name: 'Комбо на 5 людин',
last_synced_price: 2000,
category_tag: 'combo',
sort: 4,
visible: true,
},
]
for (const t of tariffs) {
await payload.create({ collection: 'tariffs', data: t as never, overrideAccess: true })
}
console.log('Seeded tariffs')
}
}
// Locations
const { totalDocs: locCount } = await payload.find({
collection: 'locations',
limit: 1,
overrideAccess: true,
})
if (locCount === 0) {
await payload.create({
collection: 'locations',
data: {
name: 'Диво Ліс',
slug: 'dyvolis',
tagline: 'Казковий світ топіарних фігур',
shortDesc: 'Топіарні фігури з живих рослин — 60+ персонажів улюблених казок у живому лісі.',
showInMenu: true,
showOnHome: true,
showDetailPage: true,
sort: 2,
heroStat: '60+',
heroStatLabel: 'експонатів з безпечних для дітей матеріалів',
heroTips: [
{ text: 'Унікальна ландшафтна композиція з місцями для відпочинку' },
{ text: 'Повна свобода переміщення — без заборон' },
],
galleryQuote:
'Це місце — де малеча зустрічає героїв улюблених казок. Простір справжнього дитинства.',
whyVisitTitle: 'Чому варто відвідати ДивоЛіс',
whyVisitItems: [
{
title: 'Простір для спільної фантазії',
description:
'Вигадуйте казки та пригоди разом із дітьми — кожна топіарна фігурка стає новою сторінкою вашої власної чарівної історії.',
},
{
title: 'Казковий ліс у справжньому лісі',
description:
'Ми створили локацію, в якій гармонійно поєднуються казкові фігури та жива природа. Прогулянка лісом ще не була такою захопливою.',
},
{
title: 'Магічні кадри для сімейного альбому',
description:
'Унікальні топіарні декорації та яскраві персонажі — ідеальний фон для незабутніх сімейних фотографій.',
},
],
workingHours: 'щодня з 11:00 до 20:00',
comboDescription:
'Динопарк + Диволіс із казковими топіарними фігурами + Дзеркальний лабіринт',
} as never,
overrideAccess: true,
})
console.log('Created DyvoLis location')
} else {
console.log('Locations already exist, skipping.')
}
process.exit(0)
}
seed().catch((err) => {
console.error('Seed failed:', err)
process.exit(1)
})