feat(pages): redesign all 6 pages to match Figma designs with full CMS coverage
- Birthday: new pricing structure (component breakdown instead of packages), packageItems with image upload per card, why/accordion CMS fields - GroupVisits: all text/prices/images CMS-editable (heroDescription, amenities, featureImages, workingHours, price, bottomImages, etc.) - DyvoLis: fixed 404 (seed location record), combo tickets now show (600/1500/1800/2000) - ThankYou: DyvoLis topiary background, 'Купити квиток' button text - Tariffs: updated to match Figma (300/150/300/50 dyno + 4 combo variants) - Seed: add DyvoLis location, correct hero text and section titles for home Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
095beb0303
commit
6863f5022d
7 changed files with 958 additions and 392 deletions
|
|
@ -5,6 +5,7 @@ import { BirthdayBookingForm } from '@/components/forms/BirthdayBookingForm'
|
|||
import { FormBlock, type FormData as FormBlockData } from '@/components/forms/FormBlock'
|
||||
import { RefreshRouteOnSave } from '@/components/cms/RefreshRouteOnSave'
|
||||
import { DyvoLisWhyVisit } from '@/components/sections/DyvoLisWhyVisit'
|
||||
import type { Media } from '@/payload-types'
|
||||
|
||||
export const revalidate = 60
|
||||
|
||||
|
|
@ -14,28 +15,15 @@ const FONT_POPPINS = { fontFamily: 'var(--font-poppins, Poppins), sans-serif' }
|
|||
async function getBirthdayPageData() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
return await payload.findGlobal({ slug: 'birthday-page', depth: 1 })
|
||||
return await payload.findGlobal({ slug: 'birthday-page', depth: 2 })
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function getPackages() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
const result = await payload.find({
|
||||
collection: 'birthday-packages',
|
||||
sort: 'sort',
|
||||
limit: 20,
|
||||
})
|
||||
return result.docs
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function formatPrice(price: number): string {
|
||||
return price.toLocaleString('uk-UA').replace(/,/g, ' ')
|
||||
function mediaUrl(m: number | Media | null | undefined): string | null {
|
||||
if (!m || typeof m === 'number') return null
|
||||
return (m as Media).url ?? null
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
|
|
@ -48,45 +36,146 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||
}
|
||||
}
|
||||
|
||||
const PACKAGE_LOCATIONS = [
|
||||
{ name: 'ДинопаркArk', description: 'Справжні динозаври в натуральну величину' },
|
||||
{ name: 'ДивоЛіс', description: 'Казкові топіарні фігури улюблених персонажів' },
|
||||
{ name: 'Дзеркальний Лабіринт', description: 'Весела гра для дітей та дорослих' },
|
||||
{ name: 'Костюмованих ведучих', description: 'Аніматори в яскравих костюмах проведуть свято' },
|
||||
{ name: 'Аніматорів', description: 'Конкурси, ігри та розваги для всіх гостей' },
|
||||
{ name: 'Затишну альтанку', description: 'Власна зона відпочинку для вашої родини' },
|
||||
]
|
||||
export default async function BirthdayPage() {
|
||||
const pageData = await getBirthdayPageData()
|
||||
const d = pageData as any
|
||||
|
||||
const WHY_ITEMS = [
|
||||
{
|
||||
title: 'Свято під ключ',
|
||||
description:
|
||||
'Ми беремо на себе всі деталі: аніматорів, конкурси, прикраси та окрему зону для вашої родини. Вам залишається лише насолоджуватись.',
|
||||
},
|
||||
{
|
||||
title: 'Простір для дітей і дорослих',
|
||||
description:
|
||||
'Шуміленд — це 7 локацій, де кожен знайде щось для себе: від динозаврів до казкових лісів, від лабіринтів до фотозон.',
|
||||
},
|
||||
{
|
||||
title: 'Незабутні фото та спогади',
|
||||
description:
|
||||
'Унікальні декорації, яскраві персонажі та щира радість дітей — ідеальний фон для фотографій, які хочеться переглядати знову і знову.',
|
||||
},
|
||||
]
|
||||
const heroTitle = d?.heroTitle ?? 'ДЕНЬ НАРОДЖЕННЯ У ШУМІЛЕНДІ ПІД КЛЮЧ'
|
||||
const heroSubtitle =
|
||||
d?.heroSubtitle ??
|
||||
'Будьте повноцінними гостями на дні народження вашої дитини. Залиште нам усі турботи про організацію. Ваш єдиний обовʼязок — відпочивати, святкувати, фотографуватися та насолоджуватися моментами.'
|
||||
const heroCta = d?.heroCta ?? 'Забронювати пригоду'
|
||||
|
||||
export default async function BirthdayPage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ package?: string }>
|
||||
}) {
|
||||
const params = await searchParams
|
||||
const defaultPackage = params.package
|
||||
const [pageData, packages] = await Promise.all([getBirthdayPageData(), getPackages()])
|
||||
const packageSectionTitle = d?.packageSectionTitle ?? 'ЩО ВХОДИТЬ У ПАКЕТ СВЯТА'
|
||||
const packageSectionSubtitle =
|
||||
d?.packageSectionSubtitle ?? 'Єдиний квиток для іменинника та 15-ти гостей'
|
||||
const packageItems: {
|
||||
title: string
|
||||
description: string
|
||||
imageUrl: string | null
|
||||
ctaLabel: string
|
||||
ctaHref?: string
|
||||
}[] = (
|
||||
d?.packageItems ?? [
|
||||
{
|
||||
title: 'ДинопаркArk',
|
||||
description: 'Справжні динозаври в натуральну величину',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'ДивоЛіс',
|
||||
description: 'Казкові топіарні фігури улюблених персонажів',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Дзеркальний Лабіринт',
|
||||
description: 'Весела гра для дітей та дорослих',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Костюмованих ведучих',
|
||||
description: 'Аніматори в яскравих костюмах проведуть свято',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Аквагрим',
|
||||
description: 'Конкурси, ігри та розваги для всіх гостей',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Затишну альтанку',
|
||||
description: 'Власна зона відпочинку для вашої родини',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
]
|
||||
).map((item: any) => ({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
imageUrl: mediaUrl(item.image),
|
||||
ctaLabel: item.ctaLabel ?? 'Замовити',
|
||||
ctaHref: item.ctaHref,
|
||||
}))
|
||||
|
||||
const whyTitle = d?.whyTitle ?? 'Чому варто відвідати ДивоЛіс'
|
||||
const whyItems = d?.whyItems ?? [
|
||||
{
|
||||
title: 'Свято під ключ',
|
||||
description:
|
||||
'Ми беремо на себе всі деталі: аніматорів, конкурси, прикраси та окрему зону для вашої родини.',
|
||||
},
|
||||
{
|
||||
title: 'Простір для дітей і дорослих',
|
||||
description: 'Шуміленд — це 7 локацій, де кожен знайде щось для себе.',
|
||||
},
|
||||
{
|
||||
title: 'Незабутні фото та спогади',
|
||||
description: 'Унікальні декорації та щира радість дітей — ідеальний фон для фотографій.',
|
||||
},
|
||||
]
|
||||
const whyVideos = d?.whyVideos ?? []
|
||||
|
||||
const workingHours = d?.workingHours ?? "п'ятниця-субота-неділя з 11:00 до 20:00"
|
||||
|
||||
const pricingSectionTitle = d?.pricingSectionTitle ?? 'ВАРТІСТЬ КВИТКІВ:'
|
||||
const pricingPackages = d?.pricingPackages ?? [
|
||||
{ label: 'Стандарт', price: '1 500 грн', ctaLabel: 'Купити квиток', ctaHref: '/kvytky' },
|
||||
{ label: '+ 4 дитини', price: '1 800 грн', ctaLabel: 'Купити квиток', ctaHref: '/kvytky' },
|
||||
{ label: '+ 4 дорослих', price: '2 000 грн', ctaLabel: 'Купити квиток', ctaHref: '/kvytky' },
|
||||
{
|
||||
label: 'Додатково',
|
||||
price: '400 грн',
|
||||
note: 'особа',
|
||||
ctaLabel: 'Купити квиток',
|
||||
ctaHref: '/kvytky',
|
||||
},
|
||||
]
|
||||
|
||||
const entranceSectionTitle = d?.entranceSectionTitle ?? 'Вхід на локації (для інших дітей):'
|
||||
const entrancePrices = d?.entrancePrices ?? [
|
||||
{
|
||||
label: 'Вхід на локації для інших дітей',
|
||||
price: '600 грн',
|
||||
ctaLabel: 'Забронювати пригоду',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
{
|
||||
label: 'ДиноПарк',
|
||||
price: '300 грн',
|
||||
ctaLabel: 'Забронювати пригоду',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
{ label: 'Диволіс', price: '250 грн', ctaLabel: 'Забронювати пригоду', ctaHref: '#order-form' },
|
||||
{
|
||||
label: 'Дзеркальний Лабіринт',
|
||||
price: '160 грн',
|
||||
ctaLabel: 'Забронювати пригоду',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
]
|
||||
|
||||
const freeInclusions =
|
||||
d?.freeInclusions ??
|
||||
'Діти до 3 років, Діти з іменинником до 18 років, VIP (за наявності запрошення), Діти-сироти'
|
||||
|
||||
const entertainmentSectionTitle = d?.entertainmentSectionTitle ?? 'Розважальна програма:'
|
||||
const entertainmentPackages = d?.entertainmentPackages ?? [
|
||||
{ label: 'Тривалість 1 год', price: '3 000 грн', ctaLabel: 'Замовити', ctaHref: '#order-form' },
|
||||
{
|
||||
label: 'Тривалість 1.5 год',
|
||||
price: '4 500 грн',
|
||||
ctaLabel: 'Замовити',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
{ label: 'Тривалість 2 год', price: '6 000 грн', ctaLabel: 'Замовити', ctaHref: '#order-form' },
|
||||
]
|
||||
|
||||
const formTitle = d?.formTitle ?? 'Замовити святкування'
|
||||
const formSubtitle =
|
||||
d?.formSubtitle ?? "Залиште заявку і наш менеджер зв'яжеться з вами протягом 30 хвилин"
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* ── 1. HERO ────────────────────────────────────────────────────── */}
|
||||
{/* ── 1. HERO ─────────────────────────────────────────────────────── */}
|
||||
<section
|
||||
className="relative flex min-h-[520px] flex-col items-center justify-end overflow-hidden"
|
||||
style={{
|
||||
|
|
@ -95,42 +184,34 @@ export default async function BirthdayPage({
|
|||
backgroundPosition: 'center',
|
||||
}}
|
||||
>
|
||||
{/* dark green overlay */}
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{ background: 'rgba(30, 60, 10, 0.72)' }}
|
||||
style={{ background: 'rgba(30,60,10,0.55)' }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{/* orange banner box */}
|
||||
<div className="relative z-10 mx-auto mt-20 w-full max-w-[900px] px-6">
|
||||
<div
|
||||
className="rounded-[20px] px-8 py-7 text-center shadow-[0_4px_30px_rgba(242,139,74,0.4)]"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)',
|
||||
}}
|
||||
style={{ background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)' }}
|
||||
>
|
||||
<h1
|
||||
className="text-[24px] leading-tight font-black text-[#1a1a1a] uppercase sm:text-[32px] lg:text-[40px]"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pageData?.heroTitle ?? 'ДЕНЬ НАРОДЖЕННЯ У ШУМІЛЕНДІ ПІД КЛЮЧ'}
|
||||
{heroTitle}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* dark green subtitle band */}
|
||||
<div
|
||||
className="relative z-10 mt-6 w-full px-6 py-6"
|
||||
style={{ background: 'rgba(30, 60, 10, 0.85)' }}
|
||||
style={{ background: 'rgba(30,60,10,0.85)' }}
|
||||
>
|
||||
<div className="mx-auto flex max-w-[900px] flex-col items-center gap-4 sm:flex-row sm:justify-between">
|
||||
<p
|
||||
className="text-center text-[16px] leading-relaxed text-white/90 sm:text-left sm:text-[18px]"
|
||||
className="text-center text-[15px] leading-relaxed text-white/90 sm:text-left sm:text-[17px]"
|
||||
style={FONT_POPPINS}
|
||||
>
|
||||
{pageData?.heroSubtitle ??
|
||||
"Зробіть свято незабутнім! Оберіть пакет і наші менеджери зв'яжуться з вами для уточнення деталей."}
|
||||
{heroSubtitle}
|
||||
</p>
|
||||
<a
|
||||
href="#order-form"
|
||||
|
|
@ -141,52 +222,58 @@ export default async function BirthdayPage({
|
|||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
Забронювати пригоду
|
||||
{heroCta}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── 2. ЩО ВХОДИТЬ У ПАКЕТ СВЯТА ──────────────────────────────── */}
|
||||
{/* ── 2. ЩО ВХОДИТЬ У ПАКЕТ СВЯТА ───────────────────────────────── */}
|
||||
<section className="py-16" style={{ background: '#f1fbeb' }}>
|
||||
<div className="mx-auto max-w-[1204px] px-6 lg:px-8">
|
||||
<h2
|
||||
className="mb-2 text-[24px] font-black text-[#272727] uppercase lg:text-[32px]"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
ЩО ВХОДИТЬ У ПАКЕТ СВЯТА
|
||||
{packageSectionTitle}
|
||||
</h2>
|
||||
<p className="mb-10 text-[16px] text-[#396817]" style={FONT_POPPINS}>
|
||||
Єдиний квиток для іменинника та 15-ти гостей
|
||||
{packageSectionSubtitle}
|
||||
</p>
|
||||
|
||||
{/* Desktop: 2 rows × 3 cols. Mobile: horizontal scroll carousel */}
|
||||
<div className="hidden gap-5 sm:grid sm:grid-cols-3">
|
||||
{PACKAGE_LOCATIONS.map((loc) => (
|
||||
{packageItems.map((item) => (
|
||||
<div
|
||||
key={loc.name}
|
||||
key={item.title}
|
||||
className="flex flex-col gap-3 rounded-[20px] p-5 shadow-[0_4px_30px_rgba(242,139,74,0.25)]"
|
||||
style={{ background: '#fff' }}
|
||||
>
|
||||
{/* image placeholder */}
|
||||
<div
|
||||
className="h-[160px] w-full rounded-[14px]"
|
||||
style={{ background: '#e8f5dc' }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{item.imageUrl ? (
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
className="h-[160px] w-full rounded-[14px] object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="h-[160px] w-full rounded-[14px]"
|
||||
style={{ background: '#e8f5dc' }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-[16px] font-bold text-[#272727]" style={FONT_MONT}>
|
||||
{loc.name}
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-1 text-[13px] text-[#555]" style={FONT_POPPINS}>
|
||||
{loc.description}
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
<a
|
||||
href={item.ctaHref ?? '#order-form'}
|
||||
className="flex h-9 w-9 flex-none items-center justify-center rounded-full text-white transition-opacity hover:opacity-80"
|
||||
style={{ background: '#f28b4a' }}
|
||||
aria-label={`Детальніше про ${loc.name}`}
|
||||
aria-label={item.ctaLabel}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path
|
||||
|
|
@ -197,41 +284,49 @@ export default async function BirthdayPage({
|
|||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Mobile: scroll carousel */}
|
||||
{/* Mobile scroll */}
|
||||
<div
|
||||
className="flex gap-4 overflow-x-auto pb-4 sm:hidden"
|
||||
style={{ scrollSnapType: 'x mandatory' }}
|
||||
>
|
||||
{PACKAGE_LOCATIONS.map((loc) => (
|
||||
{packageItems.map((item) => (
|
||||
<div
|
||||
key={loc.name}
|
||||
key={item.title}
|
||||
className="flex w-[260px] flex-none flex-col gap-3 rounded-[20px] p-5 shadow-[0_4px_30px_rgba(242,139,74,0.25)]"
|
||||
style={{ background: '#fff', scrollSnapAlign: 'start' }}
|
||||
>
|
||||
<div
|
||||
className="h-[130px] w-full rounded-[14px]"
|
||||
style={{ background: '#e8f5dc' }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{item.imageUrl ? (
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.title}
|
||||
className="h-[130px] w-full rounded-[14px] object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="h-[130px] w-full rounded-[14px]"
|
||||
style={{ background: '#e8f5dc' }}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-[15px] font-bold text-[#272727]" style={FONT_MONT}>
|
||||
{loc.name}
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="mt-1 text-[12px] text-[#555]" style={FONT_POPPINS}>
|
||||
{loc.description}
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
<a
|
||||
href={item.ctaHref ?? '#order-form'}
|
||||
className="flex h-8 w-8 flex-none items-center justify-center rounded-full text-white"
|
||||
style={{ background: '#f28b4a' }}
|
||||
aria-label={`Детальніше про ${loc.name}`}
|
||||
aria-label={item.ctaLabel}
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path
|
||||
|
|
@ -242,7 +337,7 @@ export default async function BirthdayPage({
|
|||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
@ -250,15 +345,25 @@ export default async function BirthdayPage({
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── 3. ЧОМУ ВАРТО ВІДВІДАТИ ───────────────────────────────────── */}
|
||||
<DyvoLisWhyVisit title="Чому варто обрати Шуміленд" items={WHY_ITEMS} />
|
||||
{/* ── 3. ЧОМУ ВАРТО ─────────────────────────────────────────────── */}
|
||||
<DyvoLisWhyVisit
|
||||
title={whyTitle}
|
||||
items={whyItems.map((i: any) => ({ title: i.title, description: i.description }))}
|
||||
reviewVideos={
|
||||
whyVideos.length > 0
|
||||
? whyVideos.map((v: any) => ({
|
||||
src: v.src,
|
||||
poster: v.poster ?? null,
|
||||
label: v.label ?? null,
|
||||
}))
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
{/* ── 4. WORKING HOURS BANNER ───────────────────────────────────── */}
|
||||
{/* ── 4. WORKING HOURS ──────────────────────────────────────────── */}
|
||||
<section
|
||||
className="py-10"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)',
|
||||
}}
|
||||
style={{ background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)' }}
|
||||
>
|
||||
<div className="mx-auto flex max-w-[1204px] flex-col items-center gap-2 px-6 text-center lg:px-8">
|
||||
<p
|
||||
|
|
@ -271,112 +376,131 @@ export default async function BirthdayPage({
|
|||
className="text-[20px] font-black text-[#1a1a1a] uppercase lg:text-[26px]"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
п’ятниця — субота — неділя з 11:00 до
|
||||
20:00
|
||||
{workingHours}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* ── 5. PRICING SECTION ────────────────────────────────────────── */}
|
||||
{/* ── 5. PRICING ────────────────────────────────────────────────── */}
|
||||
<section className="rounded-t-[40px] py-16" style={{ background: '#396817' }}>
|
||||
<div className="mx-auto max-w-[1204px] px-6 lg:px-8">
|
||||
<h2
|
||||
className="mb-10 text-[24px] font-black text-white uppercase lg:text-[32px]"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
ВАРТІСТЬ КВИТКІВ:
|
||||
{pricingSectionTitle}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{packages.length > 0
|
||||
? packages.map((pkg) => (
|
||||
<div
|
||||
key={pkg.id}
|
||||
className="flex flex-col gap-5 rounded-[20px] p-7 shadow-[0_4px_30px_rgba(242,139,74,0.25)]"
|
||||
style={{ background: '#fdf2e8' }}
|
||||
>
|
||||
{pkg.featured && pkg.badge && (
|
||||
<span
|
||||
className="self-start rounded-full bg-[#f28b4a] px-3 py-1 text-[11px] font-bold text-white uppercase"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pkg.badge}
|
||||
</span>
|
||||
)}
|
||||
<div>
|
||||
<p
|
||||
className="text-[42px] leading-none font-black text-[#272727]"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pkg.priceLabel ?? formatPrice(pkg.price)}{' '}
|
||||
<span className="text-[22px]">{pkg.currency ?? '₴'}</span>
|
||||
</p>
|
||||
<h3 className="mt-1 text-[18px] font-bold text-[#396817]" style={FONT_MONT}>
|
||||
{pkg.name}
|
||||
</h3>
|
||||
</div>
|
||||
{pkg.features && pkg.features.length > 0 && (
|
||||
<ul className="flex flex-col gap-2">
|
||||
{pkg.features.map((f: { id?: string | null; text: string }) => (
|
||||
<li
|
||||
key={f.id}
|
||||
className="flex items-center gap-2 text-[13px] text-[#555]"
|
||||
style={FONT_POPPINS}
|
||||
>
|
||||
<span className="text-[#f28b4a]">✓</span> {f.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<a
|
||||
href="#order-form"
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-3 text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)',
|
||||
}}
|
||||
>
|
||||
Купити квиток
|
||||
</a>
|
||||
</div>
|
||||
))
|
||||
: /* placeholder cards when CMS is empty */
|
||||
[1, 2, 3].map((n) => (
|
||||
<div
|
||||
key={n}
|
||||
className="flex flex-col gap-5 rounded-[20px] p-7 shadow-[0_4px_30px_rgba(242,139,74,0.25)]"
|
||||
style={{ background: '#fdf2e8' }}
|
||||
>
|
||||
<div>
|
||||
<p
|
||||
className="text-[42px] leading-none font-black text-[#272727]"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
— ₴
|
||||
</p>
|
||||
<h3 className="mt-1 text-[18px] font-bold text-[#396817]" style={FONT_MONT}>
|
||||
Пакет {n}
|
||||
</h3>
|
||||
</div>
|
||||
<a
|
||||
href="#order-form"
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-3 text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)',
|
||||
}}
|
||||
>
|
||||
Купити квиток
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
{/* Main packages grid */}
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
{pricingPackages.map((pkg: any) => (
|
||||
<div
|
||||
key={pkg.label}
|
||||
className="flex flex-col gap-4 rounded-[20px] p-7"
|
||||
style={{ background: '#fdf2e8' }}
|
||||
>
|
||||
<p
|
||||
className="text-[13px] font-bold tracking-widest text-[#396817] uppercase"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pkg.label}
|
||||
</p>
|
||||
<p className="text-[48px] leading-none font-black text-[#1a1a1a]" style={FONT_MONT}>
|
||||
{pkg.price}
|
||||
</p>
|
||||
{pkg.note && (
|
||||
<p className="text-[14px] text-[#555]" style={FONT_POPPINS}>
|
||||
{pkg.note}
|
||||
</p>
|
||||
)}
|
||||
<a
|
||||
href={pkg.ctaHref ?? '/kvytky'}
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-3 text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)',
|
||||
}}
|
||||
>
|
||||
{pkg.ctaLabel ?? 'Купити квиток'}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Free inclusions note */}
|
||||
<p className="mt-8 text-[14px] leading-relaxed text-white/80" style={FONT_POPPINS}>
|
||||
<span className="font-bold text-white">Безкоштовно:</span> До 3 дорослих (батьки та інші
|
||||
супроводжуючі), Діти до 3 років, Вхід по запрошеннях для іменинника
|
||||
</p>
|
||||
{/* Entrance prices */}
|
||||
<h3 className="mt-10 mb-6 text-[18px] font-black text-white" style={FONT_MONT}>
|
||||
{entranceSectionTitle}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
||||
{entrancePrices.map((item: any) => (
|
||||
<div
|
||||
key={item.label}
|
||||
className="flex flex-col gap-4 rounded-[20px] p-7"
|
||||
style={{ background: '#fdf2e8' }}
|
||||
>
|
||||
<p
|
||||
className="text-[13px] font-bold tracking-widest text-[#396817] uppercase"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{item.label}
|
||||
</p>
|
||||
<p className="text-[48px] leading-none font-black text-[#1a1a1a]" style={FONT_MONT}>
|
||||
{item.price}
|
||||
</p>
|
||||
<a
|
||||
href={item.ctaHref ?? '#order-form'}
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-3 text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)',
|
||||
}}
|
||||
>
|
||||
{item.ctaLabel ?? 'Забронювати пригоду'}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Free inclusions */}
|
||||
{freeInclusions && (
|
||||
<p className="mt-8 text-[14px] leading-relaxed text-white/80" style={FONT_POPPINS}>
|
||||
<span className="font-bold text-white">Безкоштовно:</span> {freeInclusions}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Entertainment packages */}
|
||||
<h3 className="mt-10 mb-6 text-[18px] font-black text-white" style={FONT_MONT}>
|
||||
{entertainmentSectionTitle}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{entertainmentPackages.map((pkg: any) => (
|
||||
<div
|
||||
key={pkg.label}
|
||||
className="flex flex-col gap-4 rounded-[20px] p-7"
|
||||
style={{ background: '#fdf2e8' }}
|
||||
>
|
||||
<p
|
||||
className="text-[13px] font-bold tracking-widest text-[#396817] uppercase"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pkg.label}
|
||||
</p>
|
||||
<p className="text-[48px] leading-none font-black text-[#1a1a1a]" style={FONT_MONT}>
|
||||
{pkg.price}
|
||||
</p>
|
||||
<a
|
||||
href={pkg.ctaHref ?? '#order-form'}
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-3 text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg, #f28b4a 0%, #fdcf54 55%, #f28b4a 100%)',
|
||||
}}
|
||||
>
|
||||
{pkg.ctaLabel ?? 'Замовити'}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -385,11 +509,10 @@ export default async function BirthdayPage({
|
|||
<div className="mx-auto max-w-[1204px] px-6 lg:px-8">
|
||||
<div className="rounded-[24px] bg-[#2d5414] p-8 lg:p-10">
|
||||
<h2 className="mb-2 text-[28px] font-bold text-white" style={FONT_MONT}>
|
||||
{pageData?.formTitle ?? 'Замовити святкування'}
|
||||
{formTitle}
|
||||
</h2>
|
||||
<p className="mb-8 text-[15px] text-white/70" style={FONT_POPPINS}>
|
||||
{pageData?.formSubtitle ??
|
||||
"Залиште заявку і наш менеджер зв'яжеться з вами протягом 30 хвилин"}
|
||||
{formSubtitle}
|
||||
</p>
|
||||
{pageData?.form && typeof pageData.form === 'object' ? (
|
||||
<FormBlock
|
||||
|
|
@ -397,7 +520,7 @@ export default async function BirthdayPage({
|
|||
submitLabel="Замовити святкування"
|
||||
/>
|
||||
) : (
|
||||
<BirthdayBookingForm defaultPackage={defaultPackage} />
|
||||
<BirthdayBookingForm />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,15 @@ import configPromise from '@payload-config'
|
|||
import { GroupRequestForm } from '@/components/forms/GroupRequestForm'
|
||||
import { FormBlock, type FormData as FormBlockData } from '@/components/forms/FormBlock'
|
||||
import { RefreshRouteOnSave } from '@/components/cms/RefreshRouteOnSave'
|
||||
import { PayloadImage } from '@/components/ui/PayloadImage'
|
||||
import type { Media } from '@/payload-types'
|
||||
|
||||
export const revalidate = 60
|
||||
|
||||
async function getGroupVisitsData() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
return await payload.findGlobal({ slug: 'group-visits-page', depth: 1 })
|
||||
return await payload.findGlobal({ slug: 'group-visits-page', depth: 2 })
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
|
|
@ -26,14 +28,65 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||
}
|
||||
}
|
||||
|
||||
function mediaUrl(m: number | Media | null | undefined): string | null {
|
||||
if (!m || typeof m === 'number') return null
|
||||
return (m as Media).url ?? null
|
||||
}
|
||||
|
||||
export default async function GroupVisitsPage() {
|
||||
const data = await getGroupVisitsData()
|
||||
const d = data as any
|
||||
|
||||
const formTitle = data?.formTitle ?? 'Подати заявку на групове відвідування'
|
||||
const heroTitle = d?.heroTitle ?? 'Групові відвідування'
|
||||
const heroSubtitle =
|
||||
d?.heroSubtitle ??
|
||||
'Спеціальні умови для організованих груп. Мінімум 10 осіб — максимум вражень.'
|
||||
const heroDescription =
|
||||
d?.heroDescription ??
|
||||
'Шукаєте ідеальне місце для групового виїзду класу чи садочка? Або яскраву локацію для фотосесії? Хочете, щоб дитячий випускний альбом був дійсно унікальним? Запрошуємо провести цей захопливий і незабутній день на казковій локації.'
|
||||
const heroCta = d?.heroCta ?? 'Забронювати пригоду'
|
||||
|
||||
const featureText =
|
||||
d?.featureText ??
|
||||
'На дітлах чекає подорож ДинопарКом та ДивоЛісом. Це активне дозвілля на свіжому повітрі та справжні казкові пригоди, де кожен стане героєм власної історії.'
|
||||
const featureImages: string[] = (d?.featureImages ?? [])
|
||||
.map((i: any) => mediaUrl(i.image))
|
||||
.filter(Boolean)
|
||||
|
||||
const amenitiesTitle = d?.amenitiesTitle ?? 'Ми подбали про затишок і комфорт'
|
||||
const amenities: { label: string; imageUrl: string | null }[] = (
|
||||
d?.amenities ?? [
|
||||
{ label: '2 локації без обмежень у часі' },
|
||||
{ label: 'Вбиральні та кафе на території' },
|
||||
{ label: 'Укриття поруч' },
|
||||
{ label: 'Огороджено забором, є охорона' },
|
||||
]
|
||||
).map((a: any) => ({ label: a.label, imageUrl: mediaUrl(a.image) }))
|
||||
|
||||
const workingHours = d?.workingHours ?? "п'ятниця-субота-неділя з 11:00 до 20:00"
|
||||
const price = d?.price ?? '350 грн'
|
||||
const priceLabel = d?.priceLabel ?? 'особа'
|
||||
const priceNote = d?.priceNote ?? 'Вхід для двох дорослих, що супроводжують дітей, безкоштовний.'
|
||||
const priceMinPeople = d?.priceMinPeople ?? 'Пропозиція для груп від 10 людей'
|
||||
const priceCta = d?.priceCta ?? 'Купити квиток'
|
||||
const priceDescription =
|
||||
d?.priceDescription ??
|
||||
'У вартість входить відвідування Динопарку та ДивоЛісу.\nЧас перебування на локаціях необмежений.'
|
||||
|
||||
const bottomText =
|
||||
d?.bottomText ??
|
||||
'Хочете перетворити візит на справжню маленьку експедицію з розповідями або замовити екскурсію з розповідями про динозаврів? Ми залюбки це організуємо! Телефонуйте нам — і ми все підготуємо та розрахуємо індивідуально для вашої групи.'
|
||||
const bottomImages: string[] = (d?.bottomImages ?? [])
|
||||
.map((i: any) => mediaUrl(i.image))
|
||||
.filter(Boolean)
|
||||
|
||||
const formTitle = d?.formTitle ?? 'Подати заявку на групове відвідування'
|
||||
const formSubtitle =
|
||||
data?.formSubtitle ??
|
||||
d?.formSubtitle ??
|
||||
'Вкажіть кількість учасників та бажану дату — менеджер зателефонує і погодить деталі.'
|
||||
|
||||
const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#f1fbeb]">
|
||||
{/* 1. Hero */}
|
||||
|
|
@ -49,21 +102,19 @@ export default async function GroupVisitsPage() {
|
|||
<div className="relative z-10 px-4 text-center">
|
||||
<div
|
||||
className="inline-block rounded-[12px] px-8 py-5"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, #f28b4a, #fdcf54, #f28b4a)',
|
||||
}}
|
||||
style={{ background: 'linear-gradient(90deg, #f28b4a, #fdcf54, #f28b4a)' }}
|
||||
>
|
||||
<h1
|
||||
className="text-[32px] leading-tight font-black tracking-widest text-[#1a1a1a] uppercase md:text-[48px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
ГРУПОВІ ВІЗИТИ
|
||||
{heroTitle}
|
||||
</h1>
|
||||
<p
|
||||
className="mt-2 text-[15px] font-medium text-[#1a1a1a] md:text-[18px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{data?.heroSubtitle ?? 'Спеціальна пропозиція для садочків та шкіл'}
|
||||
{heroSubtitle}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -74,21 +125,19 @@ export default async function GroupVisitsPage() {
|
|||
<div className="mx-auto max-w-[860px] text-center">
|
||||
<p
|
||||
className="mb-8 text-[16px] leading-relaxed text-white md:text-[18px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{}
|
||||
{(data as any)?.heroDescription ??
|
||||
'Шукаєте ідеальне місце для групового виїзду класу чи садочка? Або яскраву локацію для фотосесії? Хочете, щоб дитячий випускний альбом був дійсно унікальним? Запрошуємо провести цей захопливий і незабутній день на казковій локації.'}
|
||||
{heroDescription}
|
||||
</p>
|
||||
<a
|
||||
href="#order-form"
|
||||
className="inline-block w-full rounded-[12px] px-10 py-4 text-[17px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90 md:w-auto"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, #f28b4a, #fdcf54, #f28b4a)',
|
||||
fontFamily: 'var(--font-montserrat, Montserrat), sans-serif',
|
||||
...FONT_MONT,
|
||||
}}
|
||||
>
|
||||
Забронювати пригоду
|
||||
{heroCta}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -98,15 +147,30 @@ export default async function GroupVisitsPage() {
|
|||
<div className="mx-auto grid max-w-[1100px] grid-cols-1 items-center gap-10 md:grid-cols-2">
|
||||
<p
|
||||
className="text-[16px] leading-relaxed text-[#1a1a1a] md:text-[18px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
На дітлах чекає подорож ДинопарКом та ДивоЛісом. Це активне дозвілля на свіжому повітрі
|
||||
та справжні казкові пригоди, де кожен стане героєм власної історії.
|
||||
{featureText}
|
||||
</p>
|
||||
{/* Tilted image placeholders */}
|
||||
<div className="relative flex h-[280px] items-center justify-center">
|
||||
<div className="absolute top-[5%] left-[5%] h-[200px] w-[60%] rotate-[-2deg] rounded-[16px] bg-[#c8e6a0] shadow-md" />
|
||||
<div className="absolute right-[5%] bottom-[5%] h-[200px] w-[60%] rotate-3 rounded-[16px] bg-[#c8e6a0] shadow-md" />
|
||||
{featureImages.length >= 2 ? (
|
||||
<>
|
||||
<img
|
||||
src={featureImages[0]}
|
||||
alt=""
|
||||
className="absolute top-[5%] left-[5%] h-[200px] w-[60%] rotate-[-2deg] rounded-[16px] object-cover shadow-md"
|
||||
/>
|
||||
<img
|
||||
src={featureImages[1]}
|
||||
alt=""
|
||||
className="absolute right-[5%] bottom-[5%] h-[200px] w-[60%] rotate-3 rounded-[16px] object-cover shadow-md"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="absolute top-[5%] left-[5%] h-[200px] w-[60%] rotate-[-2deg] rounded-[16px] bg-[#c8e6a0] shadow-md" />
|
||||
<div className="absolute right-[5%] bottom-[5%] h-[200px] w-[60%] rotate-3 rounded-[16px] bg-[#c8e6a0] shadow-md" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -116,22 +180,25 @@ export default async function GroupVisitsPage() {
|
|||
<div className="mx-auto max-w-[1100px]">
|
||||
<h2
|
||||
className="mb-8 text-center text-[22px] font-black tracking-wide text-[#396817] uppercase md:text-[28px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
МИ ПОДБАЛИ ПРО ЗАТИШОК І КОМФОРТ
|
||||
{amenitiesTitle}
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{[
|
||||
{ label: '2 локації без обмежень у часі', hasPhoto: true },
|
||||
{ label: 'Вбиральні та кафе на території', hasPhoto: true },
|
||||
{ label: 'Укриття поруч', hasPhoto: false },
|
||||
{ label: 'Огороджено забором, є охорона', hasPhoto: true },
|
||||
].map((item) => (
|
||||
<div className="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
{amenities.map((item) => (
|
||||
<div key={item.label} className="overflow-hidden rounded-[16px] bg-white shadow-md">
|
||||
{item.hasPhoto && <div className="h-[120px] bg-[#c8e6a0]" />}
|
||||
{item.imageUrl ? (
|
||||
<img
|
||||
src={item.imageUrl}
|
||||
alt={item.label}
|
||||
className="h-[120px] w-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-[120px] bg-[#c8e6a0]" />
|
||||
)}
|
||||
<p
|
||||
className="p-4 text-[14px] leading-snug font-semibold text-[#1a1a1a]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{item.label}
|
||||
</p>
|
||||
|
|
@ -148,15 +215,12 @@ export default async function GroupVisitsPage() {
|
|||
>
|
||||
<p
|
||||
className="text-[13px] font-bold tracking-widest text-[#1a1a1a] uppercase"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
ЧАС РОБОТИ
|
||||
</p>
|
||||
<p
|
||||
className="mt-1 text-[18px] font-black text-[#1a1a1a] md:text-[22px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
п'ятниця-субота-неділя з 11:00 до 20:00
|
||||
<p className="mt-1 text-[18px] font-black text-[#1a1a1a] md:text-[22px]" style={FONT_MONT}>
|
||||
{workingHours}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
|
|
@ -165,59 +229,53 @@ export default async function GroupVisitsPage() {
|
|||
<div className="mx-auto max-w-[700px]">
|
||||
<h2
|
||||
className="mb-8 text-center text-[22px] font-black tracking-wide text-white uppercase md:text-[28px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
ВАРТІСТЬ ГРУПОВОГО ВІЗИТУ:
|
||||
</h2>
|
||||
<div className="mb-8 rounded-[20px] bg-[#fdf2e8] px-8 py-10 text-center shadow-lg">
|
||||
<p
|
||||
className="mb-3 text-[12px] font-bold tracking-widest text-[#396817] uppercase"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
СПЕЦІАЛЬНА ЦІНА ДЛЯ ГРУП
|
||||
</p>
|
||||
<p
|
||||
className="text-[72px] leading-none font-black text-[#1a1a1a] md:text-[96px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
350 грн
|
||||
{price}
|
||||
</p>
|
||||
<p
|
||||
className="mt-1 text-[16px] font-medium text-[#396817]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
особа
|
||||
<p className="mt-1 text-[16px] font-medium text-[#396817]" style={FONT_MONT}>
|
||||
{priceLabel}
|
||||
</p>
|
||||
<p
|
||||
className="mx-auto mt-4 max-w-[420px] text-[14px] leading-relaxed text-[#1a1a1a]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
Вхід для двох дорослих, що супроводжують дітей, безкоштовний.
|
||||
{priceNote}
|
||||
</p>
|
||||
<p
|
||||
className="mt-3 text-[13px] font-semibold text-[#f28b4a]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
Пропозиція для груп від 10 людей
|
||||
<p className="mt-3 text-[13px] font-semibold text-[#f28b4a]" style={FONT_MONT}>
|
||||
{priceMinPeople}
|
||||
</p>
|
||||
<a
|
||||
href="#order-form"
|
||||
className="mt-6 inline-block rounded-[12px] px-10 py-4 text-[16px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
background: 'linear-gradient(90deg, #f28b4a, #fdcf54, #f28b4a)',
|
||||
fontFamily: 'var(--font-montserrat, Montserrat), sans-serif',
|
||||
...FONT_MONT,
|
||||
}}
|
||||
>
|
||||
Купити квиток
|
||||
{priceCta}
|
||||
</a>
|
||||
</div>
|
||||
<p
|
||||
className="text-center text-[15px] leading-relaxed text-white/80"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
У вартість входить відвідування ДинопаркаTM та ДивоЛісу.
|
||||
<br />
|
||||
Час перебування на локаціях необмежений.
|
||||
<p className="text-center text-[15px] leading-relaxed text-white/80" style={FONT_MONT}>
|
||||
{priceDescription.split('\n').map((line, i) => (
|
||||
<span key={i}>
|
||||
{line}
|
||||
{i < priceDescription.split('\n').length - 1 && <br />}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -227,16 +285,30 @@ export default async function GroupVisitsPage() {
|
|||
<div className="mx-auto grid max-w-[1100px] grid-cols-1 items-center gap-10 md:grid-cols-2">
|
||||
<p
|
||||
className="text-[16px] leading-relaxed text-[#1a1a1a] md:text-[18px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
style={FONT_MONT}
|
||||
>
|
||||
Хочете перетворити візит на справжню маленьку експедицію з розповідями або замовити
|
||||
екскурсію з розповідями про динозаврів? Ми залюбки це організуємо! Телефонуйте нам — і
|
||||
ми все підготуємо та розрахуємо індивідуально для вашої групи.
|
||||
{bottomText}
|
||||
</p>
|
||||
{/* Tilted image placeholders */}
|
||||
<div className="relative flex h-[280px] items-center justify-center">
|
||||
<div className="absolute top-[5%] left-[5%] h-[200px] w-[60%] rotate-[-2deg] rounded-[16px] bg-[#c8e6a0] shadow-md" />
|
||||
<div className="absolute right-[5%] bottom-[5%] h-[200px] w-[60%] rotate-3 rounded-[16px] bg-[#c8e6a0] shadow-md" />
|
||||
{bottomImages.length >= 2 ? (
|
||||
<>
|
||||
<img
|
||||
src={bottomImages[0]}
|
||||
alt=""
|
||||
className="absolute top-[5%] left-[5%] h-[200px] w-[60%] rotate-[-2deg] rounded-[16px] object-cover shadow-md"
|
||||
/>
|
||||
<img
|
||||
src={bottomImages[1]}
|
||||
alt=""
|
||||
className="absolute right-[5%] bottom-[5%] h-[200px] w-[60%] rotate-3 rounded-[16px] object-cover shadow-md"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="absolute top-[5%] left-[5%] h-[200px] w-[60%] rotate-[-2deg] rounded-[16px] bg-[#c8e6a0] shadow-md" />
|
||||
<div className="absolute right-[5%] bottom-[5%] h-[200px] w-[60%] rotate-3 rounded-[16px] bg-[#c8e6a0] shadow-md" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -244,16 +316,10 @@ export default async function GroupVisitsPage() {
|
|||
{/* 8. Order form */}
|
||||
<section id="order-form" className="bg-[#f1fbeb] px-4 pb-16 md:px-8">
|
||||
<div className="mx-auto max-w-[860px] rounded-[24px] bg-[#396817] p-8 md:p-12">
|
||||
<h2
|
||||
className="mb-2 text-[24px] font-bold text-white md:text-[28px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
<h2 className="mb-2 text-[24px] font-bold text-white md:text-[28px]" style={FONT_MONT}>
|
||||
{formTitle}
|
||||
</h2>
|
||||
<p
|
||||
className="mb-8 text-[15px] text-white/70"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
<p className="mb-8 text-[15px] text-white/70" style={FONT_MONT}>
|
||||
{formSubtitle}
|
||||
</p>
|
||||
{data?.form && typeof data.form === 'object' ? (
|
||||
|
|
|
|||
|
|
@ -12,21 +12,19 @@ export default function DyakuiemoPage() {
|
|||
return (
|
||||
<main
|
||||
className="relative flex min-h-screen items-center justify-center overflow-hidden px-4 py-20"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #1e3610 0%, #2d5414 40%, #396817 70%, #1e3610 100%)',
|
||||
}}
|
||||
style={{ background: '#1e3610' }}
|
||||
>
|
||||
{/* Background texture */}
|
||||
{/* Background photo — DyvoLis topiary */}
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 opacity-20"
|
||||
className="pointer-events-none absolute inset-0"
|
||||
aria-hidden="true"
|
||||
style={{
|
||||
backgroundImage: `url('/images/page-hero-default.webp')`,
|
||||
backgroundImage: `url('/images/dyvolis/photo-01.jpg')`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
mixBlendMode: 'luminosity',
|
||||
}}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0 bg-black/30" aria-hidden="true" />
|
||||
|
||||
{/* Ticket card */}
|
||||
<div
|
||||
|
|
@ -57,7 +55,7 @@ export default function DyakuiemoPage() {
|
|||
className="inline-flex w-fit items-center gap-2 rounded-full px-7 py-3.5 text-[15px] font-bold text-white transition-opacity hover:opacity-85"
|
||||
style={{ background: '#3b39b5', ...FONT_MONT }}
|
||||
>
|
||||
Купити ще квиток
|
||||
Купити квиток
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -124,15 +124,19 @@ export function DyvoLisTickets({
|
|||
workingHours = 'щодня з 11:00 до 20:00',
|
||||
comboDescription = 'Динопарк + Диволіс із казковими топіарними фігурами + Дзеркальний лабіринт',
|
||||
}: DyvoLisTicketsProps) {
|
||||
const [tariffs, setTariffs] = useState<Tariff[]>([])
|
||||
const [single, setSingle] = useState<Tariff[]>([])
|
||||
const [combo, setCombo] = useState<Tariff[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/tickets/tariffs')
|
||||
.then((r) => r.json())
|
||||
.then((data: { tariffs?: Tariff[] }) => {
|
||||
const dyvolis = (data.tariffs ?? []).filter((t) => t.categoryTag === 'dyvolis')
|
||||
setTariffs(dyvolis)
|
||||
const all = data.tariffs ?? []
|
||||
const dyno = all.filter((t) => t.categoryTag === 'dyno')
|
||||
const fallback = all.filter((t) => t.categoryTag === 'dyvolis')
|
||||
setSingle(dyno.length > 0 ? dyno : fallback)
|
||||
setCombo(all.filter((t) => t.categoryTag === 'combo'))
|
||||
})
|
||||
.catch(() => {
|
||||
/* show nothing on error — section still renders */
|
||||
|
|
@ -140,13 +144,6 @@ export function DyvoLisTickets({
|
|||
.finally(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
const single = tariffs.filter(
|
||||
(t) => !t.name.toLowerCase().includes('комбо') && !t.name.toLowerCase().includes('combo')
|
||||
)
|
||||
const combo = tariffs.filter(
|
||||
(t) => t.name.toLowerCase().includes('комбо') || t.name.toLowerCase().includes('combo')
|
||||
)
|
||||
|
||||
return (
|
||||
<section className="relative overflow-hidden">
|
||||
{/* Dark green background */}
|
||||
|
|
@ -187,9 +184,7 @@ export function DyvoLisTickets({
|
|||
<div className="mb-10 grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
{loading
|
||||
? Array.from({ length: 4 }).map((_, i) => <SkeletonCard key={i} />)
|
||||
: (single.length > 0 ? single : tariffs).map((t) => (
|
||||
<TicketCard key={t.id} tariff={t} />
|
||||
))}
|
||||
: single.map((t) => <TicketCard key={t.id} tariff={t} />)}
|
||||
</div>
|
||||
|
||||
{/* Combo section — only if we have combo tariffs */}
|
||||
|
|
|
|||
|
|
@ -10,17 +10,263 @@ export const BirthdayPage: GlobalConfig = {
|
|||
hooks: { afterChange: [revalidateGlobalAfterChange] },
|
||||
versions: { max: 20, drafts: { autosave: { interval: 2000 } } },
|
||||
fields: [
|
||||
// Hero
|
||||
{
|
||||
name: 'heroTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Дні народження',
|
||||
defaultValue: 'ДЕНЬ НАРОДЖЕННЯ У ШУМІЛЕНДІ ПІД КЛЮЧ',
|
||||
},
|
||||
{
|
||||
name: 'heroSubtitle',
|
||||
type: 'text',
|
||||
defaultValue:
|
||||
"Зробіть свято незабутнім! Оберіть пакет і наші менеджери зв'яжуться з вами для уточнення деталей.",
|
||||
'Будьте повноцінними гостями на дні народження вашої дитини. Залиште нам усі турботи про організацію. Ваш єдиний обовʼязок — відпочивати, святкувати, фотографуватися та насолоджуватися моментами.',
|
||||
},
|
||||
{
|
||||
name: 'heroCta',
|
||||
type: 'text',
|
||||
defaultValue: 'Забронювати пригоду',
|
||||
},
|
||||
// Package items (ЩО ВХОДИТЬ)
|
||||
{
|
||||
name: 'packageSectionTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'ЩО ВХОДИТЬ У ПАКЕТ СВЯТА',
|
||||
},
|
||||
{
|
||||
name: 'packageSectionSubtitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Єдиний квиток для іменинника та 15-ти гостей',
|
||||
},
|
||||
{
|
||||
name: 'packageItems',
|
||||
type: 'array',
|
||||
label: 'Що входить у пакет (картки з фото)',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea', required: true },
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
admin: { description: 'Фото для картки' },
|
||||
},
|
||||
{ name: 'ctaLabel', type: 'text', defaultValue: 'Замовити' },
|
||||
{ name: 'ctaHref', type: 'text', admin: { description: 'Посилання кнопки (опційно)' } },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
title: 'ДинопаркArk',
|
||||
description: 'Справжні динозаври в натуральну величину',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'ДивоЛіс',
|
||||
description: 'Казкові топіарні фігури улюблених персонажів',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Дзеркальний Лабіринт',
|
||||
description: 'Весела гра для дітей та дорослих',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Костюмованих ведучих',
|
||||
description: 'Аніматори в яскравих костюмах проведуть свято',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Аквагрим',
|
||||
description: 'Конкурси, ігри та розваги для всіх гостей',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Затишну альтанку',
|
||||
description: 'Власна зона відпочинку для вашої родини',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Why section
|
||||
{
|
||||
name: 'whyTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Чому варто відвідати ДивоЛіс',
|
||||
},
|
||||
{
|
||||
name: 'whyItems',
|
||||
type: 'array',
|
||||
label: 'Переваги (акордеон)',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea', required: true },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
title: 'Свято під ключ',
|
||||
description:
|
||||
'Ми беремо на себе всі деталі: аніматорів, конкурси, прикраси та окрему зону для вашої родини. Вам залишається лише насолоджуватись.',
|
||||
},
|
||||
{
|
||||
title: 'Простір для дітей і дорослих',
|
||||
description:
|
||||
'Шуміленд — це 7 локацій, де кожен знайде щось для себе: від динозаврів до казкових лісів, від лабіринтів до фотозон.',
|
||||
},
|
||||
{
|
||||
title: 'Незабутні фото та спогади',
|
||||
description:
|
||||
'Унікальні декорації, яскраві персонажі та щира радість дітей — ідеальний фон для фотографій, які хочеться переглядати знову і знову.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'whyVideos',
|
||||
type: 'array',
|
||||
label: 'Відео-відгуки у секції "Чому"',
|
||||
fields: [
|
||||
{ name: 'src', type: 'text', required: true },
|
||||
{ name: 'poster', type: 'text' },
|
||||
{ name: 'label', type: 'text' },
|
||||
],
|
||||
},
|
||||
// Working hours
|
||||
{
|
||||
name: 'workingHours',
|
||||
type: 'text',
|
||||
defaultValue: "п'ятниця-субота-неділя з 11:00 до 20:00",
|
||||
},
|
||||
// Pricing section
|
||||
{
|
||||
name: 'pricingSectionTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'ВАРТІСТЬ КВИТКІВ:',
|
||||
},
|
||||
{
|
||||
name: 'pricingPackages',
|
||||
type: 'array',
|
||||
label: 'Пакети (2-колонна сітка)',
|
||||
fields: [
|
||||
{ name: 'label', type: 'text', required: true, admin: { description: 'Підпис над ціною' } },
|
||||
{
|
||||
name: 'price',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: { description: 'Напр. "1 500 грн"' },
|
||||
},
|
||||
{ name: 'note', type: 'text', admin: { description: 'Примітка під ціною' } },
|
||||
{ name: 'ctaLabel', type: 'text', defaultValue: 'Купити квиток' },
|
||||
{ name: 'ctaHref', type: 'text', defaultValue: '/kvytky' },
|
||||
],
|
||||
defaultValue: [
|
||||
{ label: 'Стандарт', price: '1 500 грн', ctaLabel: 'Купити квиток', ctaHref: '/kvytky' },
|
||||
{ label: '+ 4 дитини', price: '1 800 грн', ctaLabel: 'Купити квиток', ctaHref: '/kvytky' },
|
||||
{
|
||||
label: '+ 4 дорослих',
|
||||
price: '2 000 грн',
|
||||
ctaLabel: 'Купити квиток',
|
||||
ctaHref: '/kvytky',
|
||||
},
|
||||
{
|
||||
label: 'Додатково',
|
||||
price: '400 грн',
|
||||
note: 'особа',
|
||||
ctaLabel: 'Купити квиток',
|
||||
ctaHref: '/kvytky',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'entranceSectionTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Вхід на локації (для інших дітей):',
|
||||
},
|
||||
{
|
||||
name: 'entrancePrices',
|
||||
type: 'array',
|
||||
label: 'Ціни на вхід (для інших дітей)',
|
||||
fields: [
|
||||
{ name: 'label', type: 'text', required: true },
|
||||
{ name: 'price', type: 'text', required: true },
|
||||
{ name: 'note', type: 'text' },
|
||||
{ name: 'ctaLabel', type: 'text', defaultValue: 'Забронювати пригоду' },
|
||||
{ name: 'ctaHref', type: 'text', defaultValue: '#order-form' },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
label: 'Вхід на локації для інших дітей',
|
||||
price: '600 грн',
|
||||
ctaLabel: 'Забронювати пригоду',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
{
|
||||
label: 'ДиноПарк',
|
||||
price: '300 грн',
|
||||
ctaLabel: 'Забронювати пригоду',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
{
|
||||
label: 'Диволіс',
|
||||
price: '250 грн',
|
||||
ctaLabel: 'Забронювати пригоду',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
{
|
||||
label: 'Дзеркальний Лабіринт',
|
||||
price: '160 грн',
|
||||
ctaLabel: 'Забронювати пригоду',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'freeInclusions',
|
||||
type: 'text',
|
||||
label: '"Безкоштовно" текст',
|
||||
defaultValue:
|
||||
'Діти до 3 років, Діти з іменинником до 18 років, VIP (за наявності запрошення), Діти-сироти',
|
||||
},
|
||||
{
|
||||
name: 'entertainmentSectionTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Розважальна програма:',
|
||||
},
|
||||
{
|
||||
name: 'entertainmentPackages',
|
||||
type: 'array',
|
||||
label: 'Пакети розважальної програми',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: { description: 'Напр. "Тривалість 1 год"' },
|
||||
},
|
||||
{ name: 'price', type: 'text', required: true },
|
||||
{ name: 'ctaLabel', type: 'text', defaultValue: 'Замовити' },
|
||||
{ name: 'ctaHref', type: 'text', defaultValue: '#order-form' },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
label: 'Тривалість 1 год',
|
||||
price: '3 000 грн',
|
||||
ctaLabel: 'Замовити',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
{
|
||||
label: 'Тривалість 1.5 год',
|
||||
price: '4 500 грн',
|
||||
ctaLabel: 'Замовити',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
{
|
||||
label: 'Тривалість 2 год',
|
||||
price: '6 000 грн',
|
||||
ctaLabel: 'Замовити',
|
||||
ctaHref: '#order-form',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Form
|
||||
{
|
||||
name: 'formTitle',
|
||||
type: 'text',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const GroupVisitsPage: GlobalConfig = {
|
|||
hooks: { afterChange: [revalidateGlobalAfterChange] },
|
||||
versions: { max: 20, drafts: { autosave: { interval: 2000 } } },
|
||||
fields: [
|
||||
// Hero
|
||||
{
|
||||
name: 'heroTitle',
|
||||
type: 'text',
|
||||
|
|
@ -20,6 +21,122 @@ export const GroupVisitsPage: GlobalConfig = {
|
|||
type: 'text',
|
||||
defaultValue: 'Спеціальні умови для організованих груп. Мінімум 10 осіб — максимум вражень.',
|
||||
},
|
||||
// Green band
|
||||
{
|
||||
name: 'heroDescription',
|
||||
type: 'textarea',
|
||||
label: 'Текст зеленої смуги під hero',
|
||||
defaultValue:
|
||||
'Шукаєте ідеальне місце для групового виїзду класу чи садочка? Або яскраву локацію для фотосесії? Хочете, щоб дитячий випускний альбом був дійсно унікальним? Запрошуємо провести цей захопливий і незабутній день на казковій локації.',
|
||||
},
|
||||
{
|
||||
name: 'heroCta',
|
||||
type: 'text',
|
||||
label: 'Текст кнопки в hero',
|
||||
defaultValue: 'Забронювати пригоду',
|
||||
},
|
||||
// Feature two-col section
|
||||
{
|
||||
name: 'featureText',
|
||||
type: 'textarea',
|
||||
label: 'Текст у секції з фото (ліворуч)',
|
||||
defaultValue:
|
||||
'На дітлах чекає подорож ДинопарКом та ДивоЛісом. Це активне дозвілля на свіжому повітрі та справжні казкові пригоди, де кожен стане героєм власної історії.',
|
||||
},
|
||||
{
|
||||
name: 'featureImages',
|
||||
type: 'array',
|
||||
label: 'Фото у секції (два перекошені фото)',
|
||||
admin: { description: 'Завантажте 2 фото — відображаються перекошено side by side' },
|
||||
fields: [{ name: 'image', type: 'upload', relationTo: 'media', required: true }],
|
||||
},
|
||||
// Amenity cards
|
||||
{
|
||||
name: 'amenitiesTitle',
|
||||
type: 'text',
|
||||
label: 'Заголовок секції зручностей',
|
||||
defaultValue: 'Ми подбали про затишок і комфорт',
|
||||
},
|
||||
{
|
||||
name: 'amenities',
|
||||
type: 'array',
|
||||
label: 'Картки зручностей',
|
||||
fields: [
|
||||
{ name: 'label', type: 'text', required: true },
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
admin: { description: 'Фото для картки (опційно)' },
|
||||
},
|
||||
],
|
||||
defaultValue: [
|
||||
{ label: '2 локації без обмежень у часі' },
|
||||
{ label: 'Вбиральні та кафе на території' },
|
||||
{ label: 'Укриття поруч' },
|
||||
{ label: 'Огороджено забором, є охорона' },
|
||||
],
|
||||
},
|
||||
// Working hours
|
||||
{
|
||||
name: 'workingHours',
|
||||
type: 'text',
|
||||
label: 'Час роботи',
|
||||
defaultValue: "п'ятниця-субота-неділя з 11:00 до 20:00",
|
||||
},
|
||||
// Pricing
|
||||
{
|
||||
name: 'price',
|
||||
type: 'text',
|
||||
label: 'Ціна (напр. "350 грн")',
|
||||
defaultValue: '350 грн',
|
||||
},
|
||||
{
|
||||
name: 'priceLabel',
|
||||
type: 'text',
|
||||
label: 'Підпис до ціни',
|
||||
defaultValue: 'особа',
|
||||
},
|
||||
{
|
||||
name: 'priceNote',
|
||||
type: 'text',
|
||||
label: 'Примітка до ціни',
|
||||
defaultValue: 'Вхід для двох дорослих, що супроводжують дітей, безкоштовний.',
|
||||
},
|
||||
{
|
||||
name: 'priceMinPeople',
|
||||
type: 'text',
|
||||
label: 'Мінімальна кількість (підпис помаранчевий)',
|
||||
defaultValue: 'Пропозиція для груп від 10 людей',
|
||||
},
|
||||
{
|
||||
name: 'priceCta',
|
||||
type: 'text',
|
||||
label: 'Кнопка в блоці ціни',
|
||||
defaultValue: 'Купити квиток',
|
||||
},
|
||||
{
|
||||
name: 'priceDescription',
|
||||
type: 'textarea',
|
||||
label: 'Текст під ціновим блоком',
|
||||
defaultValue:
|
||||
'У вартість входить відвідування Динопарку та ДивоЛісу.\nЧас перебування на локаціях необмежений.',
|
||||
},
|
||||
// Bottom section
|
||||
{
|
||||
name: 'bottomText',
|
||||
type: 'textarea',
|
||||
label: 'Текст нижньої секції (ліворуч)',
|
||||
defaultValue:
|
||||
'Хочете перетворити візит на справжню маленьку експедицію з розповідями або замовити екскурсію з розповідями про динозаврів? Ми залюбки це організуємо! Телефонуйте нам — і ми все підготуємо та розрахуємо індивідуально для вашої групи.',
|
||||
},
|
||||
{
|
||||
name: 'bottomImages',
|
||||
type: 'array',
|
||||
label: 'Фото нижньої секції (два перекошені фото)',
|
||||
fields: [{ name: 'image', type: 'upload', relationTo: 'media', required: true }],
|
||||
},
|
||||
// Form
|
||||
{
|
||||
name: 'formTitle',
|
||||
type: 'text',
|
||||
|
|
@ -31,48 +148,6 @@ export const GroupVisitsPage: GlobalConfig = {
|
|||
defaultValue:
|
||||
'Вкажіть кількість учасників та бажану дату — менеджер зателефонує і погодить деталі.',
|
||||
},
|
||||
{
|
||||
name: 'groups',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{ name: 'icon', type: 'text', required: true, admin: { description: 'Emoji іконка' } },
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea', required: true },
|
||||
{
|
||||
name: 'minPeople',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: { description: 'Напр. "15 осіб"' },
|
||||
},
|
||||
{ name: 'discount', type: 'text', required: true, admin: { description: 'Напр. "15%"' } },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
icon: '🏫',
|
||||
title: 'Шкільні екскурсії',
|
||||
description:
|
||||
'Пізнавальні екскурсії для учнів початкової та середньої школи. Екскурсовод, адаптована програма, безпечний маршрут.',
|
||||
minPeople: '15 осіб',
|
||||
discount: '15%',
|
||||
},
|
||||
{
|
||||
icon: '🎒',
|
||||
title: 'Дитячі садки',
|
||||
description:
|
||||
'Програма для наймолодших — безпечний формат, розвивальні активності, відповідальний супровід.',
|
||||
minPeople: '10 осіб',
|
||||
discount: '20%',
|
||||
},
|
||||
{
|
||||
icon: '🏢',
|
||||
title: 'Корпоративи',
|
||||
description:
|
||||
'Тімбілдинг та корпоративний відпочинок у форматі парку розваг. Ексклюзивні зони, кейтеринг, програма на замовлення.',
|
||||
minPeople: '20 осіб',
|
||||
discount: '10%',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'form',
|
||||
type: 'relationship',
|
||||
|
|
|
|||
159
src/seed.ts
159
src/seed.ts
|
|
@ -33,12 +33,19 @@ async function seed(): Promise<void> {
|
|||
slug: 'home-page',
|
||||
data: {
|
||||
hero: {
|
||||
title: 'Шуміленд — світ, де казка оживає',
|
||||
subtitle:
|
||||
'Сімейний тематичний парк розваг. ДиноПарк, Диво Ліс, Дзеркальний Лабіринт — незабутні емоції для всієї родини.',
|
||||
title: 'Започаткуйте традицію:',
|
||||
subtitle: 'щороку фотографуйтесь біля улюбленого динозавра',
|
||||
ctaLabel: 'Купити квиток',
|
||||
ctaHref: '/kvytky',
|
||||
},
|
||||
sectionTitles: {
|
||||
locations: 'ЛАСКАВО ПРОСИМО ДО ШУМІЛЕНДУ',
|
||||
whyParents: 'ЧОМУ БАТЬКИ ОБИРАЮТЬ ШУМІЛЕНД',
|
||||
birthday: 'ДЕНЬ НАРОДЖЕННЯ В ШУМІЛЕНДІ',
|
||||
gallery: 'ФОТОГАЛЕРЕЯ',
|
||||
reviews: 'ВІДГУКИ',
|
||||
news: 'НОВИНИ',
|
||||
},
|
||||
locations: [
|
||||
{
|
||||
name: 'ДиноПарк',
|
||||
|
|
@ -173,93 +180,149 @@ async function seed(): Promise<void> {
|
|||
console.log('Blog posts already exist, skipping.')
|
||||
}
|
||||
|
||||
// Tariffs (sample — normally synced from ezy API)
|
||||
const { totalDocs: tariffCount } = await payload.find({
|
||||
collection: 'tariffs',
|
||||
limit: 1,
|
||||
overrideAccess: true,
|
||||
})
|
||||
if (tariffCount === 0) {
|
||||
// Tariffs — update to match Figma designs
|
||||
{
|
||||
// Delete old tariffs and re-seed with correct data
|
||||
const existing = await payload.find({ collection: 'tariffs', limit: 100, overrideAccess: true })
|
||||
for (const t of existing.docs) {
|
||||
await payload.delete({ collection: 'tariffs', id: t.id, overrideAccess: true })
|
||||
}
|
||||
|
||||
const tariffs = [
|
||||
// Individual dino-park tickets (shown on both Dino and DyvoLis pages)
|
||||
{
|
||||
ezy_id: 1001,
|
||||
last_synced_name: 'Дорослий — ДиноПарк',
|
||||
display_name: 'Дорослий',
|
||||
last_synced_price: 350,
|
||||
last_synced_name: 'Вхід до Динопарку',
|
||||
display_name: 'Вхід до Динопарку',
|
||||
last_synced_price: 300,
|
||||
category_tag: 'dyno',
|
||||
sort: 1,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
ezy_id: 1002,
|
||||
last_synced_name: 'Дитячий — ДиноПарк',
|
||||
display_name: 'Дитячий (3–12 років)',
|
||||
last_synced_price: 250,
|
||||
last_synced_name: 'Звичайна екскурсія',
|
||||
display_name: 'Звичайна екскурсія',
|
||||
last_synced_price: 150,
|
||||
category_tag: 'dyno',
|
||||
sort: 2,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
ezy_id: 1003,
|
||||
last_synced_name: 'Дитина до 3 років — ДиноПарк',
|
||||
display_name: 'До 3 років (безкоштовно)',
|
||||
last_synced_price: 0,
|
||||
last_synced_name: 'Палеонтологічна екскурсія',
|
||||
display_name: 'Палеонтологічна екскурсія',
|
||||
last_synced_price: 300,
|
||||
category_tag: 'dyno',
|
||||
sort: 3,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
ezy_id: 2001,
|
||||
last_synced_name: 'Дорослий — Диво Ліс',
|
||||
display_name: 'Дорослий',
|
||||
last_synced_price: 300,
|
||||
category_tag: 'dyvolis',
|
||||
sort: 1,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
ezy_id: 2002,
|
||||
last_synced_name: 'Дитячий — Диво Ліс',
|
||||
display_name: 'Дитячий (3–12 років)',
|
||||
last_synced_price: 200,
|
||||
category_tag: 'dyvolis',
|
||||
sort: 2,
|
||||
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: 'Комбо — ДиноПарк + Диво Ліс',
|
||||
display_name: 'Комбо дорослий',
|
||||
last_synced_price: 550,
|
||||
last_synced_name: 'Комбо на 1 людину',
|
||||
display_name: 'Комбо на 1 людину',
|
||||
last_synced_price: 600,
|
||||
category_tag: 'combo',
|
||||
sort: 1,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
ezy_id: 3002,
|
||||
last_synced_name: 'Комбо дитячий — ДиноПарк + Диво Ліс',
|
||||
display_name: 'Комбо дитячий',
|
||||
last_synced_price: 400,
|
||||
last_synced_name: 'Комбо на 3 людини',
|
||||
display_name: 'Комбо на 3 людини',
|
||||
last_synced_price: 1500,
|
||||
category_tag: 'combo',
|
||||
sort: 2,
|
||||
visible: true,
|
||||
},
|
||||
{
|
||||
ezy_id: 4001,
|
||||
last_synced_name: 'Сімейний (2 дор + 2 діт)',
|
||||
display_name: 'Сімейний квиток',
|
||||
last_synced_price: 1200,
|
||||
category_tag: 'family',
|
||||
sort: 1,
|
||||
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')
|
||||
console.log('Seeded tariffs (updated to match Figma)')
|
||||
}
|
||||
|
||||
// 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('Tariffs already exist, skipping.')
|
||||
console.log('Locations already exist, skipping.')
|
||||
}
|
||||
|
||||
process.exit(0)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue