From 00f87428142cbdeca05be22b451e94bfa651920a Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Thu, 11 Jun 2026 13:57:52 +0100 Subject: [PATCH] =?UTF-8?q?feat(cms):=20rich=20text=20editor=20for=20group?= =?UTF-8?q?=20visits=20page,=20fix=20/kvytky=20=E2=86=92=20/payments=20lin?= =?UTF-8?q?ks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change heroDescription, featureText, bottomText fields from textarea to richText (Lexical) in GroupVisitsPage global so admins can format paragraphs and bold text in admin UI - Render rich text fields with component on the frontend - Migration: ALTER varchar → jsonb for the three fields; UPDATE header cta_href and birthday_page_pricing_packages cta_href from /kvytky to /payments Co-Authored-By: Claude Sonnet 4.6 --- migrations/20260611_140000.ts | 55 +++++++++++++++++++ migrations/index.ts | 6 ++ .../(frontend)/grupovi-vidviduvannia/page.tsx | 30 +++++----- src/globals/GroupVisitsPage.ts | 16 +++--- src/payload-generated-schema.ts | 28 +++------- 5 files changed, 90 insertions(+), 45 deletions(-) create mode 100644 migrations/20260611_140000.ts diff --git a/migrations/20260611_140000.ts b/migrations/20260611_140000.ts new file mode 100644 index 0000000..e5c3c23 --- /dev/null +++ b/migrations/20260611_140000.ts @@ -0,0 +1,55 @@ +import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres' + +export async function up({ db }: MigrateUpArgs): Promise { + // Fix header CTA link: /kvytky → /payments + await db.execute(sql` + UPDATE "header" + SET "cta_href" = '/payments' + WHERE "cta_href" = '/kvytky'; + `) + + // Fix birthday page pricing package CTA links + await db.execute(sql` + UPDATE "birthday_page_pricing_packages" + SET "cta_href" = '/payments' + WHERE "cta_href" = '/kvytky'; + `) + + // Change hero_description, feature_text, bottom_text from varchar to jsonb + // (existing text content cannot be auto-converted; fields reset to NULL — re-enter in admin) + await db.execute(sql` + ALTER TABLE "group_visits_page" + ALTER COLUMN "hero_description" TYPE jsonb USING NULL, + ALTER COLUMN "feature_text" TYPE jsonb USING NULL, + ALTER COLUMN "bottom_text" TYPE jsonb USING NULL; + `) + + await db.execute(sql` + ALTER TABLE "_group_visits_page_v" + ALTER COLUMN "version_hero_description" TYPE jsonb USING NULL, + ALTER COLUMN "version_feature_text" TYPE jsonb USING NULL, + ALTER COLUMN "version_bottom_text" TYPE jsonb USING NULL; + `) +} + +export async function down({ db }: MigrateDownArgs): Promise { + await db.execute(sql` + UPDATE "header" + SET "cta_href" = '/kvytky' + WHERE "cta_href" = '/payments'; + `) + + await db.execute(sql` + ALTER TABLE "group_visits_page" + ALTER COLUMN "hero_description" TYPE varchar USING NULL, + ALTER COLUMN "feature_text" TYPE varchar USING NULL, + ALTER COLUMN "bottom_text" TYPE varchar USING NULL; + `) + + await db.execute(sql` + ALTER TABLE "_group_visits_page_v" + ALTER COLUMN "version_hero_description" TYPE varchar USING NULL, + ALTER COLUMN "version_feature_text" TYPE varchar USING NULL, + ALTER COLUMN "version_bottom_text" TYPE varchar USING NULL; + `) +} diff --git a/migrations/index.ts b/migrations/index.ts index b620851..2b61c1a 100644 --- a/migrations/index.ts +++ b/migrations/index.ts @@ -3,6 +3,7 @@ import * as migration_20260515_162527 from './20260515_162527' 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' export const migrations = [ { @@ -30,4 +31,9 @@ export const migrations = [ down: migration_20260610_140000.down, name: '20260610_140000', }, + { + up: migration_20260611_140000.up, + down: migration_20260611_140000.down, + name: '20260611_140000', + }, ] diff --git a/src/app/(frontend)/grupovi-vidviduvannia/page.tsx b/src/app/(frontend)/grupovi-vidviduvannia/page.tsx index 3079de2..198cc63 100644 --- a/src/app/(frontend)/grupovi-vidviduvannia/page.tsx +++ b/src/app/(frontend)/grupovi-vidviduvannia/page.tsx @@ -1,6 +1,7 @@ import type { Metadata } from 'next' import { getPayload } from 'payload' import configPromise from '@payload-config' +import { RichText } from '@payloadcms/richtext-lexical/react' import { GroupRequestForm } from '@/components/forms/GroupRequestForm' import { FormBlock, type FormData as FormBlockData } from '@/components/forms/FormBlock' import { RefreshRouteOnSave } from '@/components/cms/RefreshRouteOnSave' @@ -70,14 +71,11 @@ export default async function GroupVisitsPage() { const heroTitleSize = (d?.heroTitleSize as number | undefined) ?? null const heroSubtitleSize = (d?.heroSubtitleSize as number | undefined) ?? null const heroFont = (d?.heroFont as string | undefined) ?? null - const heroDescription = - (d?.heroDescription as string) ?? - 'Шукаєте ідеальне місце для групового виїзду класу чи садочка? Або яскраву локацію для фотосесії? Хочете, щоб дитячий випускний альбом був дійсно унікальним? Запрошуємо провести цей захопливий і незабутній день на казковій локації.' + type RichTextData = Parameters[0]['data'] + const heroDescription = (d?.heroDescription ?? null) as RichTextData | null const heroCta = (d?.heroCta as string) ?? 'Забронювати пригоду' - const featureText = - (d?.featureText as string) ?? - 'На дітлахів чекає подорож ДиноПарком та ДивоЛісом. Це активне дозвілля на свіжому повітрі та справжні казкові пригоди, де кожен стане героєм власної історії.' + const featureText = (d?.featureText ?? null) as RichTextData | null const featureImages: string[] = ((d?.featureImages as { image: unknown }[]) ?? []) .map((i) => mediaUrl(i.image as never)) .filter((u): u is string => Boolean(u)) @@ -145,9 +143,7 @@ export default async function GroupVisitsPage() { (d?.priceDescription as string) ?? 'У вартість входить відвідування ДиноПарку та ДивоЛісу.\nЧас перебування на локаціях необмежений.' - const bottomText = - (d?.bottomText as string) ?? - 'Хочете перетворити візит на справжню маленьку експедицію з розкопками або замовити екскурсію з розповідями про динозаврів? Ми залюбки це організуємо! Телефонуйте нам — і ми все підготуємо та розрахуємо індивідуально для вашої групи.' + const bottomText = (d?.bottomText ?? null) as RichTextData | null const bottomImages: string[] = ((d?.bottomImages as { image: unknown }[]) ?? []) .map((i) => mediaUrl(i.image as never)) .filter((u): u is string => Boolean(u)) @@ -202,7 +198,9 @@ export default async function GroupVisitsPage() { {/* 2. Green description band */}