feat(cms): convert all textarea fields to Lexical richText across all globals and collections
- BirthdayPage: packageItems[].description, whyItems[].description → richText - DinosaurPage: heroDescription, activitiesDescription, activities[].description, whyVisitItems[].description → richText - DyvoLisPage: heroDescription, whyVisitItems[].description → richText - TicketsPage: comboCards[].description, benefitsFootnote → richText - HomePage: heroSlides[].subtitle, hero.subtitle, whyParents.items[].description, birthdayIntro.text, news.subtitle, faq.items[].answer → richText - LegalPages: all 4 content fields → richText - Locations: shortDesc, whyVisitItems[].description → richText - Frontend pages: use <RichText> component from @payloadcms/richtext-lexical/react for all converted fields Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8439941afa
commit
2d6e5f2fe0
26 changed files with 798 additions and 180 deletions
503
migrations/20260611_160000.ts
Normal file
503
migrations/20260611_160000.ts
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
// ── group_visits_page: price_description ──────────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'group_visits_page'
|
||||
AND column_name = 'price_description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "group_visits_page"
|
||||
ALTER COLUMN "price_description" DROP DEFAULT,
|
||||
ALTER COLUMN "price_description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_group_visits_page_v'
|
||||
AND column_name = 'version_price_description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_group_visits_page_v"
|
||||
ALTER COLUMN "version_price_description" DROP DEFAULT,
|
||||
ALTER COLUMN "version_price_description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── dinosaur_page: hero_description, activities_description ───────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'dinosaur_page'
|
||||
AND column_name = 'hero_description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "dinosaur_page"
|
||||
ALTER COLUMN "hero_description" DROP DEFAULT,
|
||||
ALTER COLUMN "hero_description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'dinosaur_page'
|
||||
AND column_name = 'activities_description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "dinosaur_page"
|
||||
ALTER COLUMN "activities_description" DROP DEFAULT,
|
||||
ALTER COLUMN "activities_description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── dyvolis_page: hero_description ────────────────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'dyvolis_page'
|
||||
AND column_name = 'hero_description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "dyvolis_page"
|
||||
ALTER COLUMN "hero_description" DROP DEFAULT,
|
||||
ALTER COLUMN "hero_description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── tickets_page: benefits_footnote ───────────────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'tickets_page'
|
||||
AND column_name = 'benefits_footnote'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "tickets_page"
|
||||
ALTER COLUMN "benefits_footnote" DROP DEFAULT,
|
||||
ALTER COLUMN "benefits_footnote" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_tickets_page_v'
|
||||
AND column_name = 'version_benefits_footnote'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_tickets_page_v"
|
||||
ALTER COLUMN "version_benefits_footnote" DROP DEFAULT,
|
||||
ALTER COLUMN "version_benefits_footnote" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── home_page: hero_subtitle, birthday_intro_text, news_subtitle ──────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'home_page'
|
||||
AND column_name = 'hero_subtitle'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "home_page"
|
||||
ALTER COLUMN "hero_subtitle" DROP DEFAULT,
|
||||
ALTER COLUMN "hero_subtitle" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'home_page'
|
||||
AND column_name = 'birthday_intro_text'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "home_page"
|
||||
ALTER COLUMN "birthday_intro_text" DROP DEFAULT,
|
||||
ALTER COLUMN "birthday_intro_text" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'home_page'
|
||||
AND column_name = 'news_subtitle'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "home_page"
|
||||
ALTER COLUMN "news_subtitle" DROP DEFAULT,
|
||||
ALTER COLUMN "news_subtitle" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_home_page_v'
|
||||
AND column_name = 'version_hero_subtitle'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_home_page_v"
|
||||
ALTER COLUMN "version_hero_subtitle" DROP DEFAULT,
|
||||
ALTER COLUMN "version_hero_subtitle" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_home_page_v'
|
||||
AND column_name = 'version_birthday_intro_text'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_home_page_v"
|
||||
ALTER COLUMN "version_birthday_intro_text" DROP DEFAULT,
|
||||
ALTER COLUMN "version_birthday_intro_text" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_home_page_v'
|
||||
AND column_name = 'version_news_subtitle'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_home_page_v"
|
||||
ALTER COLUMN "version_news_subtitle" DROP DEFAULT,
|
||||
ALTER COLUMN "version_news_subtitle" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── home_page_why_parents_items: description ──────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'home_page_why_parents_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "home_page_why_parents_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_home_page_v_version_why_parents_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_home_page_v_version_why_parents_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── dinosaur_page_activities: description ─────────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'dinosaur_page_activities'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "dinosaur_page_activities"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── dinosaur_page_why_visit_items: description ────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'dinosaur_page_why_visit_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "dinosaur_page_why_visit_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── birthday_page_package_items: description ──────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'birthday_page_package_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "birthday_page_package_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_birthday_page_v_version_package_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_birthday_page_v_version_package_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── birthday_page_why_items: description ──────────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'birthday_page_why_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "birthday_page_why_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_birthday_page_v_version_why_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_birthday_page_v_version_why_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── dyvolis_page_why_visit_items: description ─────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'dyvolis_page_why_visit_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "dyvolis_page_why_visit_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── tickets_page_combo_cards: description ─────────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'tickets_page_combo_cards'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "tickets_page_combo_cards"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = '_tickets_page_v_version_combo_cards'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "_tickets_page_v_version_combo_cards"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── locations: short_desc ─────────────────────────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'locations'
|
||||
AND column_name = 'short_desc'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "locations"
|
||||
ALTER COLUMN "short_desc" DROP DEFAULT,
|
||||
ALTER COLUMN "short_desc" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── locations_why_visit_items: description ────────────────────────────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'locations_why_visit_items'
|
||||
AND column_name = 'description'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "locations_why_visit_items"
|
||||
ALTER COLUMN "description" DROP DEFAULT,
|
||||
ALTER COLUMN "description" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── home_page_hero_slides: subtitle (created after next drizzle push) ─────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables WHERE table_name = 'home_page_hero_slides'
|
||||
) AND EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'home_page_hero_slides'
|
||||
AND column_name = 'subtitle'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "home_page_hero_slides"
|
||||
ALTER COLUMN "subtitle" DROP DEFAULT,
|
||||
ALTER COLUMN "subtitle" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// ── home_page_faq_items: answer (created after next drizzle push) ─────────
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables WHERE table_name = 'home_page_faq_items'
|
||||
) AND EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'home_page_faq_items'
|
||||
AND column_name = 'answer'
|
||||
AND data_type = 'character varying'
|
||||
) THEN
|
||||
ALTER TABLE "home_page_faq_items"
|
||||
ALTER COLUMN "answer" DROP DEFAULT,
|
||||
ALTER COLUMN "answer" TYPE jsonb USING NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
// Revert all jsonb columns back to varchar (data will be lost/NULL)
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "group_visits_page" ALTER COLUMN "price_description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_group_visits_page_v" ALTER COLUMN "version_price_description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "dinosaur_page" ALTER COLUMN "hero_description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "dinosaur_page" ALTER COLUMN "activities_description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "dyvolis_page" ALTER COLUMN "hero_description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "tickets_page" ALTER COLUMN "benefits_footnote" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_tickets_page_v" ALTER COLUMN "version_benefits_footnote" TYPE varchar USING NULL;
|
||||
ALTER TABLE "home_page" ALTER COLUMN "hero_subtitle" TYPE varchar USING NULL;
|
||||
ALTER TABLE "home_page" ALTER COLUMN "birthday_intro_text" TYPE varchar USING NULL;
|
||||
ALTER TABLE "home_page" ALTER COLUMN "news_subtitle" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_home_page_v" ALTER COLUMN "version_hero_subtitle" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_home_page_v" ALTER COLUMN "version_birthday_intro_text" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_home_page_v" ALTER COLUMN "version_news_subtitle" TYPE varchar USING NULL;
|
||||
ALTER TABLE "home_page_why_parents_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_home_page_v_version_why_parents_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "dinosaur_page_activities" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "dinosaur_page_why_visit_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "birthday_page_package_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_birthday_page_v_version_package_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "birthday_page_why_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_birthday_page_v_version_why_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "dyvolis_page_why_visit_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "tickets_page_combo_cards" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "_tickets_page_v_version_combo_cards" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
ALTER TABLE "locations" ALTER COLUMN "short_desc" TYPE varchar USING NULL;
|
||||
ALTER TABLE "locations_why_visit_items" ALTER COLUMN "description" TYPE varchar USING NULL;
|
||||
`)
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import * as migration_20260518_104929 from './20260518_104929'
|
|||
import * as migration_20260518_115657 from './20260518_115657'
|
||||
import * as migration_20260610_140000 from './20260610_140000'
|
||||
import * as migration_20260611_140000 from './20260611_140000'
|
||||
import * as migration_20260611_160000 from './20260611_160000'
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
|
|
@ -36,4 +37,9 @@ export const migrations = [
|
|||
down: migration_20260611_140000.down,
|
||||
name: '20260611_140000',
|
||||
},
|
||||
{
|
||||
up: migration_20260611_160000.up,
|
||||
down: migration_20260611_160000.down,
|
||||
name: '20260611_160000',
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import type { Metadata } from 'next'
|
||||
import React from 'react'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import { BirthdayBookingForm } from '@/components/forms/BirthdayBookingForm'
|
||||
import { FormBlock, type FormData as FormBlockData } from '@/components/forms/FormBlock'
|
||||
import { RefreshRouteOnSave } from '@/components/cms/RefreshRouteOnSave'
|
||||
|
|
@ -9,6 +11,8 @@ import { JsonLd } from '@/components/seo/JsonLd'
|
|||
import { birthdayPageJsonLd } from '@/lib/structuredData'
|
||||
import type { Media } from '@/payload-types'
|
||||
|
||||
type RichTextData = Parameters<typeof RichText>[0]['data']
|
||||
|
||||
export const revalidate = 60
|
||||
|
||||
const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }
|
||||
|
|
@ -99,7 +103,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||
|
||||
interface PackageCardData {
|
||||
title: string
|
||||
description: string
|
||||
description: React.ReactNode
|
||||
imageUrl: string | null
|
||||
ctaLabel: string
|
||||
ctaHref?: string
|
||||
|
|
@ -154,9 +158,12 @@ function PackageCard({ item }: { item: PackageCardData }) {
|
|||
>
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="text-[15px] leading-[1.5] text-[#272727] lg:text-[16px]" style={FONT_MONT}>
|
||||
<div
|
||||
className="text-[15px] leading-[1.5] text-[#272727] lg:text-[16px] [&_p]:mb-1 [&_strong]:font-bold"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href={item.ctaHref ?? '#order-form'}
|
||||
|
|
@ -207,7 +214,15 @@ export default async function BirthdayPage() {
|
|||
)
|
||||
const toCard = (item: Record<string, unknown>): PackageCardData => ({
|
||||
title: item.title as string,
|
||||
description: item.description as string,
|
||||
description: item.description ? (
|
||||
typeof item.description === 'string' ? (
|
||||
item.description
|
||||
) : (
|
||||
<RichText data={item.description as RichTextData} />
|
||||
)
|
||||
) : (
|
||||
''
|
||||
),
|
||||
imageUrl:
|
||||
typeof item.image === 'string' ? item.image : mediaUrl(item.image as Media | undefined),
|
||||
ctaLabel: (item.ctaLabel as string) ?? 'Замовити',
|
||||
|
|
@ -223,8 +238,21 @@ export default async function BirthdayPage() {
|
|||
: FALLBACK_EXTRA_ITEMS.map(toCard)
|
||||
|
||||
const whyTitle = (d?.whyTitle as string) ?? 'Чому варто святкувати у Шуміленді'
|
||||
const whyItems = Array.isArray(d?.whyItems)
|
||||
? (d?.whyItems as typeof FALLBACK_WHY_ITEMS)
|
||||
type WhyRawItem = { title: string; description?: RichTextData | string | null }
|
||||
const rawWhyItems = Array.isArray(d?.whyItems) ? (d?.whyItems as WhyRawItem[]) : null
|
||||
const whyItems = rawWhyItems
|
||||
? rawWhyItems.map((i) => ({
|
||||
title: i.title,
|
||||
description: i.description ? (
|
||||
typeof i.description === 'string' ? (
|
||||
i.description
|
||||
) : (
|
||||
<RichText data={i.description as RichTextData} />
|
||||
)
|
||||
) : (
|
||||
''
|
||||
),
|
||||
}))
|
||||
: FALLBACK_WHY_ITEMS
|
||||
const whyVideos = Array.isArray(d?.whyVideos)
|
||||
? (d?.whyVideos as Array<Record<string, unknown>>)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { Metadata } from 'next'
|
||||
import React from 'react'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
||||
|
|
@ -139,9 +140,16 @@ export default async function GroupVisitsPage() {
|
|||
(d?.priceNote as string) ?? 'Вхід для двох дорослих, що супроводжують дітей, безкоштовний.'
|
||||
const priceMinPeople = (d?.priceMinPeople as string) ?? 'Пропозиція діє для груп від 10 людей'
|
||||
const priceCta = (d?.priceCta as string) ?? 'Купити квиток'
|
||||
const priceDescription =
|
||||
(d?.priceDescription as string) ??
|
||||
'У вартість входить відвідування ДиноПарку та ДивоЛісу.\nЧас перебування на локаціях необмежений.'
|
||||
const priceDescriptionRaw = d?.priceDescription ?? null
|
||||
const priceDescription = priceDescriptionRaw ? (
|
||||
<RichText data={priceDescriptionRaw as RichTextData} />
|
||||
) : (
|
||||
<span className="whitespace-pre-line">
|
||||
{
|
||||
'У вартість входить відвідування ДиноПарку та ДивоЛісу.\nЧас перебування на локаціях необмежений.'
|
||||
}
|
||||
</span>
|
||||
)
|
||||
|
||||
const bottomText = (d?.bottomText ?? null) as RichTextData | null
|
||||
const bottomImages: string[] = ((d?.bottomImages as { image: unknown }[]) ?? [])
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
||||
|
||||
const FONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }
|
||||
|
||||
type RichTextData = Parameters<typeof RichText>[0]['data']
|
||||
|
||||
interface Props {
|
||||
title: string
|
||||
content: string
|
||||
content: string | RichTextData | null | undefined
|
||||
}
|
||||
|
||||
export function LegalPageLayout({ title, content }: Props) {
|
||||
const isRichText = content !== null && content !== undefined && typeof content === 'object'
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-[#f1fbeb]">
|
||||
<div className="bg-[#396817] px-8 py-12">
|
||||
|
|
@ -17,10 +23,14 @@ export function LegalPageLayout({ title, content }: Props) {
|
|||
</div>
|
||||
<div className="mx-auto max-w-[900px] px-8 py-12">
|
||||
<div
|
||||
className="prose prose-sm max-w-none text-[15px] leading-[1.8] whitespace-pre-line text-[#272727]/80"
|
||||
className="prose prose-sm max-w-none text-[15px] leading-[1.8] text-[#272727]/80 [&_ol]:list-decimal [&_ol]:pl-5 [&_p]:mb-3 [&_strong]:font-bold [&_ul]:list-disc [&_ul]:pl-5"
|
||||
style={FONT}
|
||||
>
|
||||
{content}
|
||||
{isRichText ? (
|
||||
<RichText data={content as RichTextData} />
|
||||
) : (
|
||||
<span className="whitespace-pre-line">{content as string}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { Metadata } from 'next'
|
|||
import { notFound } from 'next/navigation'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import { DyvoLisHero } from '@/components/sections/DyvoLisHero'
|
||||
import { DyvoLisGallery } from '@/components/sections/DyvoLisGallery'
|
||||
import { DyvoLisWhyVisit } from '@/components/sections/DyvoLisWhyVisit'
|
||||
|
|
@ -9,6 +10,21 @@ import { DyvoLisTickets } from '@/components/sections/DyvoLisTickets'
|
|||
import { RefreshRouteOnSave } from '@/components/cms/RefreshRouteOnSave'
|
||||
import type { Location, Media } from '@/payload-types'
|
||||
|
||||
type RichTextData = Parameters<typeof RichText>[0]['data']
|
||||
|
||||
function richTextToPlain(rt: RichTextData | null | undefined): string {
|
||||
if (!rt) return ''
|
||||
type LexNode = { text?: string; children?: LexNode[] }
|
||||
const walk = (n: LexNode): string => {
|
||||
if (typeof n.text === 'string') return n.text
|
||||
if (Array.isArray(n.children)) return n.children.map(walk).join('')
|
||||
return ''
|
||||
}
|
||||
const root = (rt as { root?: { children?: LexNode[] } }).root
|
||||
if (!root?.children) return ''
|
||||
return root.children.map(walk).join(' ').trim()
|
||||
}
|
||||
|
||||
interface Props {
|
||||
params: Promise<{ slug: string }>
|
||||
}
|
||||
|
|
@ -48,7 +64,10 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|||
if (!location || !location.showDetailPage) return { title: 'Не знайдено — Шуміленд' }
|
||||
return {
|
||||
title: location.meta?.title ?? `${location.name} — Шуміленд`,
|
||||
description: location.meta?.description ?? location.shortDesc ?? '',
|
||||
description:
|
||||
location.meta?.description ??
|
||||
richTextToPlain(location.shortDesc as RichTextData | null) ??
|
||||
'',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +95,11 @@ export default async function LocationDetailPage({ params }: Props) {
|
|||
|
||||
const whyVisitItems = location.whyVisitItems?.map((item) => ({
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
description: item.description ? (
|
||||
<RichText data={item.description as unknown as RichTextData} />
|
||||
) : (
|
||||
''
|
||||
),
|
||||
}))
|
||||
|
||||
const reviewVideos = location.reviewVideos?.map((v) => ({
|
||||
|
|
@ -89,7 +112,11 @@ export default async function LocationDetailPage({ params }: Props) {
|
|||
<div className="bg-[#f1fbeb]">
|
||||
<DyvoLisHero
|
||||
title={location.shortDesc ? `${location.name}` : undefined}
|
||||
description={location.shortDesc ?? undefined}
|
||||
description={
|
||||
location.shortDesc ? (
|
||||
<RichText data={location.shortDesc as unknown as RichTextData} />
|
||||
) : undefined
|
||||
}
|
||||
stat={location.heroStat ?? undefined}
|
||||
statLabel={location.heroStatLabel ?? undefined}
|
||||
tips={heroTips}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,31 @@
|
|||
import type { Metadata } from 'next'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import { DinoPageContent } from '@/components/sections/DinoPageContent'
|
||||
import { RefreshRouteOnSave } from '@/components/cms/RefreshRouteOnSave'
|
||||
import type { Media, Location } from '@/payload-types'
|
||||
|
||||
type RichTextData = Parameters<typeof RichText>[0]['data']
|
||||
|
||||
function richTextToPlain(rt: RichTextData | null | undefined): string {
|
||||
if (!rt) return ''
|
||||
type LexNode = { text?: string; children?: LexNode[] }
|
||||
const walk = (n: LexNode): string => {
|
||||
if (typeof n.text === 'string') return n.text
|
||||
if (Array.isArray(n.children)) return n.children.map(walk).join('')
|
||||
return ''
|
||||
}
|
||||
const root = (rt as { root?: { children?: LexNode[] } }).root
|
||||
if (!root?.children) return ''
|
||||
return root.children.map(walk).join(' ').trim()
|
||||
}
|
||||
|
||||
export const revalidate = 60
|
||||
|
||||
interface DinoPageData {
|
||||
heroTitle?: string
|
||||
heroDescription?: string
|
||||
heroDescription?: RichTextData | null
|
||||
heroStat?: string
|
||||
heroStatLabel?: string
|
||||
heroImage?: number | Media | null
|
||||
|
|
@ -25,17 +41,17 @@ interface DinoPageData {
|
|||
}[]
|
||||
galleryImages?: { image?: number | Media | null; id?: string }[]
|
||||
activitiesTitle?: string
|
||||
activitiesDescription?: string
|
||||
activitiesDescription?: RichTextData | null
|
||||
activities?: {
|
||||
name: string
|
||||
price?: string | null
|
||||
description?: string | null
|
||||
description?: RichTextData | null
|
||||
image?: number | Media | null
|
||||
href?: string | null
|
||||
id?: string
|
||||
}[]
|
||||
whyVisitTitle?: string
|
||||
whyVisitItems?: { title: string; description: string; id?: string }[]
|
||||
whyVisitItems?: { title: string; description: RichTextData | null; id?: string }[]
|
||||
reviewVideos?: { src: string; poster?: string | null; label?: string | null; id?: string }[]
|
||||
workingHours?: string
|
||||
comboDescription?: string
|
||||
|
|
@ -54,7 +70,8 @@ export async function generateMetadata(): Promise<Metadata> {
|
|||
const data = raw as unknown as DinoPageData
|
||||
return {
|
||||
title: data.meta?.title ?? 'Динопарк — Шуміленд',
|
||||
description: data.meta?.description ?? data.heroDescription ?? 'Динопарк у Шуміленді',
|
||||
description:
|
||||
data.meta?.description ?? (richTextToPlain(data.heroDescription) || 'Динопарк у Шуміленді'),
|
||||
}
|
||||
} catch {
|
||||
return { title: 'Динопарк — Шуміленд' }
|
||||
|
|
@ -104,14 +121,14 @@ export default async function DinosaurPage() {
|
|||
const activities = data.activities?.map((a) => ({
|
||||
name: a.name,
|
||||
price: a.price ?? null,
|
||||
description: a.description ?? null,
|
||||
description: a.description ? <RichText data={a.description} /> : null,
|
||||
imageUrl: mediaUrl(a.image),
|
||||
href: a.href ?? '#tickets',
|
||||
}))
|
||||
|
||||
const whyVisitItems = data.whyVisitItems?.map((w) => ({
|
||||
title: w.title,
|
||||
description: w.description,
|
||||
description: w.description ? <RichText data={w.description} /> : '',
|
||||
}))
|
||||
|
||||
const reviewVideos = data.reviewVideos?.map((v) => ({
|
||||
|
|
@ -123,7 +140,9 @@ export default async function DinosaurPage() {
|
|||
<>
|
||||
<DinoPageContent
|
||||
heroTitle={data.heroTitle}
|
||||
heroDescription={data.heroDescription}
|
||||
heroDescription={
|
||||
data.heroDescription ? <RichText data={data.heroDescription} /> : undefined
|
||||
}
|
||||
stat={data.heroStat}
|
||||
statLabel={data.heroStatLabel}
|
||||
features={features && features.length > 0 ? features : undefined}
|
||||
|
|
@ -132,7 +151,9 @@ export default async function DinosaurPage() {
|
|||
dinos={dinos && dinos.length >= 12 ? dinos : undefined}
|
||||
galleryImages={galleryImages && galleryImages.length >= 4 ? galleryImages : undefined}
|
||||
activitiesTitle={data.activitiesTitle}
|
||||
activitiesDescription={data.activitiesDescription}
|
||||
activitiesDescription={
|
||||
data.activitiesDescription ? <RichText data={data.activitiesDescription} /> : undefined
|
||||
}
|
||||
activities={activities && activities.length > 0 ? activities : undefined}
|
||||
whyVisitTitle={data.whyVisitTitle}
|
||||
whyVisitItems={whyVisitItems && whyVisitItems.length > 0 ? whyVisitItems : undefined}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import React from 'react'
|
||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import { Hero } from '@/components/sections/Hero'
|
||||
import { Locations } from '@/components/sections/Locations'
|
||||
import { WhyParents } from '@/components/sections/WhyParents'
|
||||
|
|
@ -14,6 +16,8 @@ import { JsonLd } from '@/components/seo/JsonLd'
|
|||
import { homePageJsonLd } from '@/lib/structuredData'
|
||||
import type { HomePageHero, HomePageSectionType } from '@/types/globals'
|
||||
|
||||
type RichTextData = Parameters<typeof RichText>[0]['data']
|
||||
|
||||
const DEFAULT_SECTION_ORDER: HomePageSectionType[] = [
|
||||
'locations',
|
||||
'whyParents',
|
||||
|
|
@ -56,15 +60,28 @@ export default async function HomePage() {
|
|||
title={home?.sectionTitles?.locations ?? undefined}
|
||||
/>
|
||||
)
|
||||
case 'whyParents':
|
||||
case 'whyParents': {
|
||||
const rawWhyParentsItems = home?.whyParents?.items
|
||||
const whyParentsItems = rawWhyParentsItems
|
||||
? rawWhyParentsItems.map((item) => ({
|
||||
...item,
|
||||
description:
|
||||
item.description && typeof item.description !== 'string' ? (
|
||||
<RichText data={item.description as unknown as RichTextData} />
|
||||
) : (
|
||||
item.description
|
||||
),
|
||||
}))
|
||||
: undefined
|
||||
return (
|
||||
<WhyParents
|
||||
key={key}
|
||||
items={home?.whyParents?.items ?? undefined}
|
||||
items={whyParentsItems}
|
||||
sideGallery={home?.whyParents?.sideGallery ?? undefined}
|
||||
title={home?.sectionTitles?.whyParents ?? undefined}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'video':
|
||||
return (
|
||||
<VideoSection
|
||||
|
|
@ -76,12 +93,19 @@ export default async function HomePage() {
|
|||
case 'birthday': {
|
||||
const pg = home?.birthdayIntro?.patternGreen
|
||||
const po = home?.birthdayIntro?.patternOrange
|
||||
const birthdayIntroText = home?.birthdayIntro?.text
|
||||
const birthdayIntro =
|
||||
birthdayIntroText && typeof birthdayIntroText !== 'string' ? (
|
||||
<RichText data={birthdayIntroText as unknown as RichTextData} />
|
||||
) : (
|
||||
((birthdayIntroText as string | undefined) ?? undefined)
|
||||
)
|
||||
return (
|
||||
<BirthdayPricing
|
||||
key={key}
|
||||
packages={birthdayPackages.length > 0 ? birthdayPackages : undefined}
|
||||
title={home?.sectionTitles?.birthday ?? undefined}
|
||||
intro={home?.birthdayIntro?.text ?? undefined}
|
||||
intro={birthdayIntro}
|
||||
patternGreen={pg && typeof pg === 'object' ? (pg.url ?? undefined) : (pg ?? undefined)}
|
||||
patternOrange={po && typeof po === 'object' ? (po.url ?? undefined) : (po ?? undefined)}
|
||||
/>
|
||||
|
|
@ -104,23 +128,38 @@ export default async function HomePage() {
|
|||
googleReviewUrl={home?.sectionTitles?.googleReviewUrl}
|
||||
/>
|
||||
)
|
||||
case 'faq':
|
||||
return (
|
||||
<FAQ
|
||||
key={key}
|
||||
items={home?.faq?.items ?? undefined}
|
||||
title={home?.faq?.title ?? undefined}
|
||||
/>
|
||||
)
|
||||
case 'news':
|
||||
case 'faq': {
|
||||
const rawFaqItems = home?.faq?.items
|
||||
const faqItems = rawFaqItems
|
||||
? rawFaqItems.map((item) => ({
|
||||
...item,
|
||||
answer:
|
||||
item.answer && typeof item.answer !== 'string' ? (
|
||||
<RichText data={item.answer as unknown as RichTextData} />
|
||||
) : (
|
||||
item.answer
|
||||
),
|
||||
}))
|
||||
: undefined
|
||||
return <FAQ key={key} items={faqItems} title={home?.faq?.title ?? undefined} />
|
||||
}
|
||||
case 'news': {
|
||||
const newsSubtitleRaw = home?.news?.subtitle
|
||||
const newsSubtitle =
|
||||
newsSubtitleRaw && typeof newsSubtitleRaw !== 'string' ? (
|
||||
<RichText data={newsSubtitleRaw as unknown as RichTextData} />
|
||||
) : (
|
||||
((newsSubtitleRaw as string | undefined) ?? undefined)
|
||||
)
|
||||
return (
|
||||
<News
|
||||
key={key}
|
||||
title={home?.sectionTitles?.news ?? undefined}
|
||||
subtitle={home?.news?.subtitle ?? undefined}
|
||||
subtitle={newsSubtitle}
|
||||
limit={home?.news?.limit ?? undefined}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'map':
|
||||
return (
|
||||
<MapSection
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@
|
|||
import type { Metadata } from 'next'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import { RefreshRouteOnSave } from '@/components/cms/RefreshRouteOnSave'
|
||||
import { KvytkyTicketsClient, type Tariff } from '@/components/sections/KvytkyTicketsClient'
|
||||
import { ComboTickets, type ComboCardData } from '@/components/sections/ComboTickets'
|
||||
import type { Media, TicketsPage } from '@/payload-types'
|
||||
|
||||
type RichTextData = Parameters<typeof RichText>[0]['data']
|
||||
|
||||
export const revalidate = 60
|
||||
|
||||
const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }
|
||||
|
|
@ -209,6 +212,19 @@ const COMBO_CARDS_STATIC: ComboCardData[] = [
|
|||
|
||||
const parsePrice = (s: string): number => Number(String(s).replace(/[^\d]/g, '')) || 0
|
||||
|
||||
type LexicalNode = { text?: string; children?: LexicalNode[] }
|
||||
function richTextToPlain(rt: RichTextData | null | undefined): string {
|
||||
if (!rt) return ''
|
||||
const walk = (n: LexicalNode): string => {
|
||||
if (typeof n.text === 'string') return n.text
|
||||
if (Array.isArray(n.children)) return n.children.map(walk).join('')
|
||||
return ''
|
||||
}
|
||||
const root = (rt as { root?: { children?: LexicalNode[] } }).root
|
||||
if (!root?.children) return ''
|
||||
return root.children.map(walk).join(' ').trim()
|
||||
}
|
||||
|
||||
function buildComboCards(pageData: TicketsPage | null, tariffs: Tariff[]): ComboCardData[] {
|
||||
const apiCombos = tariffs
|
||||
.filter((t) => t.categoryTag === 'combo')
|
||||
|
|
@ -248,7 +264,7 @@ function buildComboCards(pageData: TicketsPage | null, tariffs: Tariff[]): Combo
|
|||
name: c.name,
|
||||
subtitle: c.subtitle ?? null,
|
||||
price: c.price,
|
||||
description: c.description ?? '',
|
||||
description: richTextToPlain(c.description as RichTextData | null | undefined),
|
||||
featured: !!c.featured,
|
||||
badge: c.badge ?? null,
|
||||
locations: (c.locations ?? []).map((l) => l.text).filter(Boolean) as string[],
|
||||
|
|
@ -385,13 +401,16 @@ function BenefitsSection({ pageData }: { pageData: TicketsPage | null }) {
|
|||
|
||||
{/* Footnote */}
|
||||
<div className="rounded-b-[20px] bg-[#d6f2c0] px-8 py-[30px]">
|
||||
<p
|
||||
className="text-center text-[16px] leading-[1.5] font-normal text-[#272727] lg:text-[20px]"
|
||||
<div
|
||||
className="text-center text-[16px] leading-[1.5] font-normal text-[#272727] lg:text-[20px] [&_p]:mb-0"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pageData?.benefitsFootnote ??
|
||||
'*Знижки та пільги поширюються виключно на індивідуальне відвідування 3 основних локацій (Динопарк, Зона топіарних фігур, Дзеркальний лабіринт) та не сумуються з тарифами категорії «КОМБО».'}
|
||||
</p>
|
||||
{pageData?.benefitsFootnote ? (
|
||||
<RichText data={pageData.benefitsFootnote as unknown as RichTextData} />
|
||||
) : (
|
||||
'*Знижки та пільги поширюються виключно на індивідуальне відвідування 3 основних локацій (Динопарк, Зона топіарних фігур, Дзеркальний лабіринт) та не сумуються з тарифами категорії «КОМБО».'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const Locations: CollectionConfig = {
|
|||
{ name: 'name', type: 'text', required: true },
|
||||
{ name: 'slug', type: 'text', required: true, unique: true },
|
||||
{ name: 'tagline', type: 'text' },
|
||||
{ name: 'shortDesc', type: 'textarea' },
|
||||
{ name: 'shortDesc', type: 'richText', editor: standardEditor },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor },
|
||||
{ name: 'image', type: 'upload', relationTo: 'media' },
|
||||
{
|
||||
|
|
@ -91,7 +91,7 @@ export const Locations: CollectionConfig = {
|
|||
label: 'Чому варто відвідати',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea', required: true },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor, required: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import type { BirthdayPackageCMS } from '@/types/globals'
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ const DEFAULT_PATTERN_ORANGE = '/images/figma/card-pattern-dark.webp'
|
|||
interface BirthdayPricingProps {
|
||||
packages?: BirthdayPackageCMS[]
|
||||
title?: string
|
||||
intro?: string
|
||||
intro?: React.ReactNode
|
||||
patternGreen?: string
|
||||
patternOrange?: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client'
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useCart } from '@/context/CartContext'
|
||||
import { ImageLightbox } from '@/components/ui/ImageLightbox'
|
||||
|
||||
|
|
@ -18,14 +18,14 @@ interface DinoSpec {
|
|||
interface ActivityItem {
|
||||
name: string
|
||||
price?: string | null
|
||||
description?: string | null
|
||||
description?: React.ReactNode
|
||||
imageUrl?: string | null
|
||||
href?: string | null
|
||||
}
|
||||
|
||||
interface WhyVisitItem {
|
||||
title: string
|
||||
description: string
|
||||
description: React.ReactNode
|
||||
}
|
||||
|
||||
interface VideoItem {
|
||||
|
|
@ -45,7 +45,7 @@ interface Tariff {
|
|||
|
||||
export interface DinoPageContentProps {
|
||||
heroTitle?: string
|
||||
heroDescription?: string
|
||||
heroDescription?: React.ReactNode
|
||||
stat?: string
|
||||
statLabel?: string
|
||||
features?: string[]
|
||||
|
|
@ -54,7 +54,7 @@ export interface DinoPageContentProps {
|
|||
dinos?: DinoSpec[]
|
||||
galleryImages?: string[]
|
||||
activitiesTitle?: string
|
||||
activitiesDescription?: string
|
||||
activitiesDescription?: React.ReactNode
|
||||
activities?: ActivityItem[]
|
||||
whyVisitTitle?: string
|
||||
whyVisitItems?: WhyVisitItem[]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
import React from 'react'
|
||||
import { BtnPrimary } from '@/components/ui/BtnPrimary'
|
||||
|
||||
const HERO_IMG = '/images/dyvolis/hero-cat.webp'
|
||||
|
|
@ -11,7 +12,7 @@ const DEFAULT_TIPS = [
|
|||
|
||||
interface DyvoLisHeroProps {
|
||||
title?: string
|
||||
description?: string
|
||||
description?: React.ReactNode
|
||||
stat?: string
|
||||
statLabel?: string
|
||||
tips?: string[]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import { useState, useRef, useEffect } from 'react'
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
|
||||
const FONT_MONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }
|
||||
const IMG_WAVE_TILE = '/images/figma/wave-tile.svg'
|
||||
|
|
@ -41,7 +41,7 @@ interface ReviewVideo {
|
|||
|
||||
interface DyvoLisWhyVisitProps {
|
||||
title?: string
|
||||
items?: Array<{ title: string; description: string }>
|
||||
items?: Array<{ title: string; description: React.ReactNode }>
|
||||
reviewVideos?: ReviewVideo[]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import type { HomePageFaqItem } from '@/types/globals'
|
||||
|
||||
const STATIC_ITEMS: HomePageFaqItem[] = [
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
||||
import { HeroSlider } from './HeroSlider'
|
||||
import type { SlideData } from './HeroSlider'
|
||||
|
||||
type RichTextData = Parameters<typeof RichText>[0]['data']
|
||||
|
||||
interface HeroProps {
|
||||
hero?: unknown
|
||||
}
|
||||
|
|
@ -17,7 +20,7 @@ async function getHeroSlides(): Promise<SlideData[] | null> {
|
|||
backgroundImageUrl?: string | null
|
||||
type?: string | null
|
||||
title?: string | null
|
||||
subtitle?: string | null
|
||||
subtitle?: unknown
|
||||
ctaLabel?: string | null
|
||||
ctaHref?: string | null
|
||||
}>
|
||||
|
|
@ -30,7 +33,12 @@ async function getHeroSlides(): Promise<SlideData[] | null> {
|
|||
img: s.backgroundImage?.url ?? s.backgroundImageUrl ?? '',
|
||||
type: (s.type === 'brand' ? 'brand' : 'location') as 'brand' | 'location',
|
||||
title: s.title ?? '',
|
||||
subtitle: s.subtitle ?? '',
|
||||
subtitle:
|
||||
s.subtitle && typeof s.subtitle !== 'string' ? (
|
||||
<RichText data={s.subtitle as RichTextData} />
|
||||
) : (
|
||||
((s.subtitle as string) ?? '')
|
||||
),
|
||||
ctaLabel: s.ctaLabel ?? 'Купити квиток',
|
||||
ctaHref: s.ctaHref ?? '/payments',
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client'
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
||||
import { BtnPrimary } from '@/components/ui/BtnPrimary'
|
||||
|
||||
type SlideType = 'brand' | 'location'
|
||||
|
|
@ -9,7 +9,7 @@ export interface SlideData {
|
|||
img: string
|
||||
type: SlideType
|
||||
title: string
|
||||
subtitle: string
|
||||
subtitle: React.ReactNode
|
||||
ctaLabel: string
|
||||
ctaHref: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
import React from 'react'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { BtnDetails } from '@/components/ui/BtnDetails'
|
||||
|
|
@ -65,7 +66,7 @@ async function getLatestPosts(limit = 3): Promise<Article[]> {
|
|||
|
||||
interface NewsProps {
|
||||
title?: string | null
|
||||
subtitle?: string | null
|
||||
subtitle?: React.ReactNode
|
||||
limit?: number | null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { standardEditor } from '@/fields/richText'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
|
|
@ -40,7 +41,7 @@ export const BirthdayPage: GlobalConfig = {
|
|||
label: 'Що входить у пакет (картки з фото)',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea', required: true },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor, required: true },
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
|
|
@ -51,36 +52,12 @@ export const BirthdayPage: GlobalConfig = {
|
|||
{ name: 'ctaHref', type: 'text', admin: { description: 'Посилання кнопки (опційно)' } },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
title: 'ДиноПарк',
|
||||
description: 'Справжні динозаври в натуральну величину',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'ДивоЛіс',
|
||||
description: 'Казкові топіарні фігури улюблених персонажів',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Дзеркальний Лабіринт',
|
||||
description: 'Весела гра для дітей та дорослих',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Костюмованих ведучих',
|
||||
description: 'Аніматори в яскравих костюмах проведуть свято',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Аквагрим',
|
||||
description: 'Конкурси, ігри та розваги для всіх гостей',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{
|
||||
title: 'Затишну альтанку',
|
||||
description: 'Власна зона відпочинку для вашої родини',
|
||||
ctaLabel: 'Замовити',
|
||||
},
|
||||
{ title: 'ДиноПарк', ctaLabel: 'Замовити' },
|
||||
{ title: 'ДивоЛіс', ctaLabel: 'Замовити' },
|
||||
{ title: 'Дзеркальний Лабіринт', ctaLabel: 'Замовити' },
|
||||
{ title: 'Костюмованих ведучих', ctaLabel: 'Замовити' },
|
||||
{ title: 'Аквагрим', ctaLabel: 'Замовити' },
|
||||
{ title: 'Затишну альтанку', ctaLabel: 'Замовити' },
|
||||
],
|
||||
},
|
||||
// Why section
|
||||
|
|
@ -95,24 +72,12 @@ export const BirthdayPage: GlobalConfig = {
|
|||
label: 'Переваги (акордеон)',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea', required: true },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor, required: true },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
title: 'Свято під ключ',
|
||||
description:
|
||||
'Ми беремо на себе всі деталі: аніматорів, конкурси, прикраси та окрему зону для вашої родини. Вам залишається лише насолоджуватись.',
|
||||
},
|
||||
{
|
||||
title: 'Простір для дітей і дорослих',
|
||||
description:
|
||||
'Шуміленд — це 7 локацій, де кожен знайде щось для себе: від динозаврів до казкових лісів, від лабіринтів до фотозон.',
|
||||
},
|
||||
{
|
||||
title: 'Незабутні фото та спогади',
|
||||
description:
|
||||
'Унікальні декорації, яскраві персонажі та щира радість дітей — ідеальний фон для фотографій, які хочеться переглядати знову і знову.',
|
||||
},
|
||||
{ title: 'Свято під ключ' },
|
||||
{ title: 'Простір для дітей і дорослих' },
|
||||
{ title: 'Незабутні фото та спогади' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { standardEditor } from '@/fields/richText'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
|
|
@ -17,9 +18,8 @@ export const DinosaurPage: GlobalConfig = {
|
|||
},
|
||||
{
|
||||
name: 'heroDescription',
|
||||
type: 'textarea',
|
||||
defaultValue:
|
||||
'Великі динозаври, що рухаються та гарчать, справжнє роздоволлє, цікаві екскурсії та динородео — тут є все, щоб ваша дитина не нудьгувала.',
|
||||
type: 'richText',
|
||||
editor: standardEditor,
|
||||
},
|
||||
{
|
||||
name: 'heroStat',
|
||||
|
|
@ -99,9 +99,8 @@ export const DinosaurPage: GlobalConfig = {
|
|||
},
|
||||
{
|
||||
name: 'activitiesDescription',
|
||||
type: 'textarea',
|
||||
defaultValue:
|
||||
'Хочете дізнатись ще більше про динозаврів? Замовте екскурсію з гідом, поринь у світ палеонтологічних розкопок або підкорюй справжнього динозавра!',
|
||||
type: 'richText',
|
||||
editor: standardEditor,
|
||||
},
|
||||
{
|
||||
name: 'activities',
|
||||
|
|
@ -110,7 +109,7 @@ export const DinosaurPage: GlobalConfig = {
|
|||
fields: [
|
||||
{ name: 'name', type: 'text', required: true },
|
||||
{ name: 'price', type: 'text', admin: { description: 'Наприклад: 150 грн' } },
|
||||
{ name: 'description', type: 'textarea' },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor },
|
||||
{ name: 'image', type: 'upload', relationTo: 'media' },
|
||||
{ name: 'href', type: 'text', defaultValue: '#tickets' },
|
||||
],
|
||||
|
|
@ -131,24 +130,12 @@ export const DinosaurPage: GlobalConfig = {
|
|||
type: 'array',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea', required: true },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor, required: true },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
title: 'Навчання через гру',
|
||||
description:
|
||||
'Дітки дізнаються про стародавніх тварин через захопливі ігри та інтерактивні вправи з гідом.',
|
||||
},
|
||||
{
|
||||
title: 'Дитячі очі, що палають захватом',
|
||||
description:
|
||||
'Реалістичні рухи та звуки динозаврів створюють ефект повного занурення — дитина точно не забуде цього дня.',
|
||||
},
|
||||
{
|
||||
title: 'Неймовірні фотографії',
|
||||
description:
|
||||
'Сфотографуйтесь поруч із улюбленим динозавром або зробіть фото з екскурсоводом — тепла згадка для всієї родини.',
|
||||
},
|
||||
{ title: 'Навчання через гру' },
|
||||
{ title: 'Дитячі очі, що палають захватом' },
|
||||
{ title: 'Неймовірні фотографії' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { standardEditor } from '@/fields/richText'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
|
|
@ -23,9 +24,8 @@ export const DyvoLisPage: GlobalConfig = {
|
|||
},
|
||||
{
|
||||
name: 'heroDescription',
|
||||
type: 'textarea',
|
||||
defaultValue:
|
||||
'Топіарні фігури зроблені з урахуванням важливих деталей, тому ви одразу впізнаєте в них улюблених казкових героїв. Тут можна бігати, стрибати, лазити по фігурках і ставати героями власної казки.',
|
||||
type: 'richText',
|
||||
editor: standardEditor,
|
||||
},
|
||||
{
|
||||
name: 'heroStat',
|
||||
|
|
@ -79,24 +79,12 @@ export const DyvoLisPage: GlobalConfig = {
|
|||
type: 'array',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'description', type: 'textarea', required: true },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor, required: true },
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
title: 'Простір для спільної фантазії',
|
||||
description:
|
||||
'Вигадуйте казки та пригоди разом із дітьми — кожна топіарна фігурка стає новою сторінкою вашої власної чарівної історії.',
|
||||
},
|
||||
{
|
||||
title: 'Казковий ліс у справжньому лісі',
|
||||
description:
|
||||
'Ми створили локацію, в якій гармонійно поєднуються казкові фігури та жива природа. Прогулянка лісом ще не була такою захопливою.',
|
||||
},
|
||||
{
|
||||
title: 'Магічні кадри для сімейного альбому',
|
||||
description:
|
||||
'Унікальні топіарні декорації та яскраві персонажі — ідеальний фон для незабутніх сімейних фотографій, які захочеться переглядати знову і знову.',
|
||||
},
|
||||
{ title: 'Простір для спільної фантазії' },
|
||||
{ title: 'Казковий ліс у справжньому лісі' },
|
||||
{ title: 'Магічні кадри для сімейного альбому' },
|
||||
],
|
||||
},
|
||||
// Video reviews carousel (right column of "Why visit" section)
|
||||
|
|
|
|||
|
|
@ -171,10 +171,9 @@ export const GroupVisitsPage: GlobalConfig = {
|
|||
},
|
||||
{
|
||||
name: 'priceDescription',
|
||||
type: 'textarea',
|
||||
type: 'richText',
|
||||
editor: standardEditor,
|
||||
label: 'Текст під ціновим блоком',
|
||||
defaultValue:
|
||||
'У вартість входить відвідування Динопарку та ДивоЛісу.\nЧас перебування на локаціях необмежений.',
|
||||
},
|
||||
// Bottom section
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { standardEditor } from '@/fields/richText'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
|
|
@ -45,7 +46,12 @@ export const HomePage: GlobalConfig = {
|
|||
],
|
||||
},
|
||||
{ name: 'title', type: 'text', required: true, label: 'Заголовок' },
|
||||
{ name: 'subtitle', type: 'textarea', label: 'Підзаголовок / опис' },
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'richText',
|
||||
editor: standardEditor,
|
||||
label: 'Підзаголовок / опис',
|
||||
},
|
||||
{ name: 'ctaLabel', type: 'text', defaultValue: 'Купити квиток', label: 'Кнопка' },
|
||||
{ name: 'ctaHref', type: 'text', defaultValue: '/payments', label: 'Посилання кнопки' },
|
||||
],
|
||||
|
|
@ -55,7 +61,7 @@ export const HomePage: GlobalConfig = {
|
|||
type: 'group',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'subtitle', type: 'textarea' },
|
||||
{ name: 'subtitle', type: 'richText', editor: standardEditor },
|
||||
{ name: 'ctaLabel', type: 'text' },
|
||||
{ name: 'ctaHref', type: 'text' },
|
||||
{ name: 'backgroundVideo', type: 'text' },
|
||||
|
|
@ -154,7 +160,7 @@ export const HomePage: GlobalConfig = {
|
|||
type: 'array',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'description', type: 'textarea' },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -193,7 +199,7 @@ export const HomePage: GlobalConfig = {
|
|||
name: 'birthdayIntro',
|
||||
type: 'group',
|
||||
fields: [
|
||||
{ name: 'text', type: 'textarea' },
|
||||
{ name: 'text', type: 'richText', editor: standardEditor },
|
||||
{
|
||||
name: 'patternGreen',
|
||||
type: 'upload',
|
||||
|
|
@ -217,7 +223,8 @@ export const HomePage: GlobalConfig = {
|
|||
{ name: 'title', type: 'text' },
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
type: 'richText',
|
||||
editor: standardEditor,
|
||||
admin: { description: 'Підзаголовок секції новин на головній.' },
|
||||
},
|
||||
{ name: 'limit', type: 'number', defaultValue: 3, min: 1, max: 12 },
|
||||
|
|
@ -240,7 +247,13 @@ export const HomePage: GlobalConfig = {
|
|||
label: 'Запитання та відповіді',
|
||||
fields: [
|
||||
{ name: 'question', type: 'text', required: true, label: 'Запитання' },
|
||||
{ name: 'answer', type: 'textarea', required: true, label: 'Відповідь' },
|
||||
{
|
||||
name: 'answer',
|
||||
type: 'richText',
|
||||
editor: standardEditor,
|
||||
required: true,
|
||||
label: 'Відповідь',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { standardEditor } from '@/fields/richText'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ export const LegalPages: GlobalConfig = {
|
|||
label: 'Політика конфіденційності',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', defaultValue: 'Політика конфіденційності' },
|
||||
{ name: 'content', type: 'textarea', label: 'Текст сторінки', admin: { rows: 20 } },
|
||||
{ name: 'content', type: 'richText', editor: standardEditor, label: 'Текст сторінки' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -24,7 +25,7 @@ export const LegalPages: GlobalConfig = {
|
|||
label: 'Умови використання',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', defaultValue: 'Умови використання' },
|
||||
{ name: 'content', type: 'textarea', label: 'Текст сторінки', admin: { rows: 20 } },
|
||||
{ name: 'content', type: 'richText', editor: standardEditor, label: 'Текст сторінки' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -33,7 +34,7 @@ export const LegalPages: GlobalConfig = {
|
|||
label: 'Публічна оферта',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', defaultValue: 'Публічна оферта' },
|
||||
{ name: 'content', type: 'textarea', label: 'Текст сторінки', admin: { rows: 20 } },
|
||||
{ name: 'content', type: 'richText', editor: standardEditor, label: 'Текст сторінки' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -42,7 +43,7 @@ export const LegalPages: GlobalConfig = {
|
|||
label: 'Обробка персональних даних',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', defaultValue: 'Обробка персональних даних' },
|
||||
{ name: 'content', type: 'textarea', label: 'Текст сторінки', admin: { rows: 20 } },
|
||||
{ name: 'content', type: 'richText', editor: standardEditor, label: 'Текст сторінки' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { standardEditor } from '@/fields/richText'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
|
|
@ -56,7 +57,7 @@ export const TicketsPage: GlobalConfig = {
|
|||
{ name: 'name', type: 'text', required: true },
|
||||
{ name: 'subtitle', type: 'text' },
|
||||
{ name: 'price', type: 'text', required: true, admin: { description: 'Напр. «600 ₴»' } },
|
||||
{ name: 'description', type: 'textarea' },
|
||||
{ name: 'description', type: 'richText', editor: standardEditor },
|
||||
{ name: 'featured', type: 'checkbox', label: 'Виділена (помаранчева)' },
|
||||
{ name: 'badge', type: 'text', admin: { description: 'Напр. «Найпопулярніший»' } },
|
||||
{
|
||||
|
|
@ -66,18 +67,11 @@ export const TicketsPage: GlobalConfig = {
|
|||
},
|
||||
],
|
||||
defaultValue: [
|
||||
{
|
||||
name: 'Комбо 1',
|
||||
price: '600 ₴',
|
||||
description: 'Індивідуальний квиток для повного занурення в атмосферу парку.',
|
||||
featured: false,
|
||||
locations: COMBO_LOCATIONS,
|
||||
},
|
||||
{ name: 'Комбо 1', price: '600 ₴', featured: false, locations: COMBO_LOCATIONS },
|
||||
{
|
||||
name: 'Комбо Сімейний',
|
||||
subtitle: '(3 ос.)',
|
||||
price: '1500 ₴',
|
||||
description: 'Оптимальний вибір для батьків та дитини (віком до 14 років).',
|
||||
featured: true,
|
||||
badge: 'Найпопулярніший',
|
||||
locations: COMBO_LOCATIONS,
|
||||
|
|
@ -86,7 +80,6 @@ export const TicketsPage: GlobalConfig = {
|
|||
name: 'Комбо Сімейний',
|
||||
subtitle: '(4 ос.)',
|
||||
price: '1800 ₴',
|
||||
description: 'Універсальний сімейний пакет розваг для компанії з 4 людей.',
|
||||
featured: false,
|
||||
locations: COMBO_LOCATIONS,
|
||||
},
|
||||
|
|
@ -94,7 +87,6 @@ export const TicketsPage: GlobalConfig = {
|
|||
name: 'Комбо Сімейний',
|
||||
subtitle: '(5 ос.)',
|
||||
price: '2000 ₴',
|
||||
description: 'Максимальний та найвигідніший пакет для великої родини.',
|
||||
featured: false,
|
||||
locations: COMBO_LOCATIONS,
|
||||
},
|
||||
|
|
@ -152,9 +144,8 @@ export const TicketsPage: GlobalConfig = {
|
|||
},
|
||||
{
|
||||
name: 'benefitsFootnote',
|
||||
type: 'textarea',
|
||||
defaultValue:
|
||||
'*Знижки та пільги поширюються виключно на індивідуальне відвідування 3 основних локацій (Динопарк, Зона топіарних фігур, Дзеркальний лабіринт) та не сумуються з тарифами категорії «КОМБО».',
|
||||
type: 'richText',
|
||||
editor: standardEditor,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import type React from 'react'
|
||||
|
||||
export interface Media {
|
||||
id?: string
|
||||
url?: string | null
|
||||
|
|
@ -93,7 +95,7 @@ export interface HomePageSectionTitles {
|
|||
|
||||
export interface HomePageWhyParentsItem {
|
||||
title?: string | null
|
||||
description?: string | null
|
||||
description?: React.ReactNode
|
||||
}
|
||||
|
||||
export interface HomePageWhyParents {
|
||||
|
|
@ -116,14 +118,14 @@ export interface HomePageVideo {
|
|||
}
|
||||
|
||||
export interface HomePageBirthdayIntro {
|
||||
text?: string | null
|
||||
text?: React.ReactNode
|
||||
patternGreen?: Media | string | null
|
||||
patternOrange?: Media | string | null
|
||||
}
|
||||
|
||||
export interface HomePageNews {
|
||||
title?: string | null
|
||||
subtitle?: string | null
|
||||
subtitle?: React.ReactNode
|
||||
limit?: number | null
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +138,7 @@ export interface HomePageMap {
|
|||
|
||||
export interface HomePageFaqItem {
|
||||
question?: string | null
|
||||
answer?: string | null
|
||||
answer?: React.ReactNode
|
||||
}
|
||||
|
||||
export interface HomePageFaq {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue