feat(legal): 4 Ukrainian legal pages + footer links + CMS global
Some checks are pending
CI / Type Check (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Unit Tests (push) Waiting to run
Deploy / Build & Push Image (push) Waiting to run
Deploy / Deploy to VPS (push) Blocked by required conditions

- LegalPages global in Payload CMS (Юридичні сторінки section)
- /privacy-policy, /terms-of-use, /oferta, /data-processing pages
- Legal text pre-filled with contract data (ТОВ ТЕХНОСМАРТ УКРАЇНА,
  ЄДРПОУ 40166430, contacts from contract №20260422)
- Footer: legal links row above copyright
- All pages editable from CMS admin, fallback to hardcoded defaults

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-06-04 13:01:38 +01:00
parent 028d14ea17
commit 2cf8ec9715
10 changed files with 538 additions and 0 deletions

View file

@ -35,6 +35,7 @@ import { TicketsPage } from './src/globals/TicketsPage'
import { LocationsPage } from './src/globals/LocationsPage'
import { BlogIndexPage } from './src/globals/BlogIndexPage'
import { DinosaurPage } from './src/globals/DinosaurPage'
import { LegalPages } from './src/globals/LegalPages'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
@ -90,6 +91,7 @@ export default buildConfig({
LocationsPage,
BlogIndexPage,
DinosaurPage,
LegalPages,
],
plugins: [

View file

@ -0,0 +1,32 @@
import type { Metadata } from 'next'
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { LegalPageLayout } from '../legal/LegalPage'
import { DATA_PROCESSING_DEFAULT } from '@/lib/legalDefaults'
export const revalidate = 3600
export const dynamicParams = true
export const metadata: Metadata = {
title: 'Обробка персональних даних — Шуміленд',
}
async function getData() {
try {
const payload = await getPayload({ config: configPromise })
const data = await payload.findGlobal({ slug: 'legal-pages', depth: 0 })
return data?.dataProcessing ?? null
} catch {
return null
}
}
export default async function DataProcessingPage() {
const data = await getData()
return (
<LegalPageLayout
title={data?.title ?? 'Обробка персональних даних'}
content={data?.content ?? DATA_PROCESSING_DEFAULT}
/>
)
}

View file

@ -0,0 +1,28 @@
const FONT = { fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }
interface Props {
title: string
content: string
}
export function LegalPageLayout({ title, content }: Props) {
return (
<main className="min-h-screen bg-[#f1fbeb]">
<div className="bg-[#396817] px-8 py-12">
<div className="mx-auto max-w-[900px]">
<h1 className="text-[clamp(24px,4vw,42px)] font-bold text-white uppercase" style={FONT}>
{title}
</h1>
</div>
</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"
style={FONT}
>
{content}
</div>
</div>
</main>
)
}

View file

@ -0,0 +1,32 @@
import type { Metadata } from 'next'
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { LegalPageLayout } from '../legal/LegalPage'
import { OFFER_DEFAULT } from '@/lib/legalDefaults'
export const revalidate = 3600
export const dynamicParams = true
export const metadata: Metadata = {
title: 'Публічна оферта — Шуміленд',
}
async function getData() {
try {
const payload = await getPayload({ config: configPromise })
const data = await payload.findGlobal({ slug: 'legal-pages', depth: 0 })
return data?.offer ?? null
} catch {
return null
}
}
export default async function OfertaPage() {
const data = await getData()
return (
<LegalPageLayout
title={data?.title ?? 'Публічна оферта'}
content={data?.content ?? OFFER_DEFAULT}
/>
)
}

View file

@ -0,0 +1,32 @@
import type { Metadata } from 'next'
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { LegalPageLayout } from '../legal/LegalPage'
import { PRIVACY_DEFAULT } from '@/lib/legalDefaults'
export const revalidate = 3600
export const dynamicParams = true
export const metadata: Metadata = {
title: 'Політика конфіденційності — Шуміленд',
}
async function getData() {
try {
const payload = await getPayload({ config: configPromise })
const data = await payload.findGlobal({ slug: 'legal-pages', depth: 0 })
return data?.privacy ?? null
} catch {
return null
}
}
export default async function PrivacyPage() {
const data = await getData()
return (
<LegalPageLayout
title={data?.title ?? 'Політика конфіденційності'}
content={data?.content ?? PRIVACY_DEFAULT}
/>
)
}

View file

@ -0,0 +1,32 @@
import type { Metadata } from 'next'
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { LegalPageLayout } from '../legal/LegalPage'
import { TERMS_DEFAULT } from '@/lib/legalDefaults'
export const revalidate = 3600
export const dynamicParams = true
export const metadata: Metadata = {
title: 'Умови використання — Шуміленд',
}
async function getData() {
try {
const payload = await getPayload({ config: configPromise })
const data = await payload.findGlobal({ slug: 'legal-pages', depth: 0 })
return data?.terms ?? null
} catch {
return null
}
}
export default async function TermsPage() {
const data = await getData()
return (
<LegalPageLayout
title={data?.title ?? 'Умови використання'}
content={data?.content ?? TERMS_DEFAULT}
/>
)
}

View file

@ -194,6 +194,27 @@ export async function Footer() {
{/* Copyright */}
<div className="mt-12 border-t border-[#1a3009]/20 pt-6 text-center">
{/* Legal links */}
<nav
aria-label="Юридична інформація"
className="mb-4 flex flex-wrap justify-center gap-x-5 gap-y-2"
>
{[
{ label: 'Політика конфіденційності', href: '/privacy-policy' },
{ label: 'Умови використання', href: '/terms-of-use' },
{ label: 'Публічна оферта', href: '/oferta' },
{ label: 'Обробка персональних даних', href: '/data-processing' },
].map((link) => (
<Link
key={link.href}
href={link.href}
className="text-[12px] text-[#1a3009]/50 underline underline-offset-2 transition-colors hover:text-[#1a3009]/80"
style={{ fontFamily: FONT }}
>
{link.label}
</Link>
))}
</nav>
<p className="text-[14px] text-[#1a3009]/60" style={{ fontFamily: FONT }}>
{copyright}
</p>

49
src/globals/LegalPages.ts Normal file
View file

@ -0,0 +1,49 @@
import type { GlobalConfig } from 'payload'
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
export const LegalPages: GlobalConfig = {
slug: 'legal-pages',
label: 'Юридичні сторінки',
admin: { group: 'Сторінки' },
access: { read: () => true, update: isAdminOrEditor },
hooks: { afterChange: [revalidateGlobalAfterChange] },
fields: [
{
name: 'privacy',
type: 'group',
label: 'Політика конфіденційності',
fields: [
{ name: 'title', type: 'text', defaultValue: 'Політика конфіденційності' },
{ name: 'content', type: 'textarea', label: 'Текст сторінки' },
],
},
{
name: 'terms',
type: 'group',
label: 'Умови використання',
fields: [
{ name: 'title', type: 'text', defaultValue: 'Умови використання' },
{ name: 'content', type: 'textarea', label: 'Текст сторінки' },
],
},
{
name: 'offer',
type: 'group',
label: 'Публічна оферта',
fields: [
{ name: 'title', type: 'text', defaultValue: 'Публічна оферта' },
{ name: 'content', type: 'textarea', label: 'Текст сторінки' },
],
},
{
name: 'dataProcessing',
type: 'group',
label: 'Обробка персональних даних',
fields: [
{ name: 'title', type: 'text', defaultValue: 'Обробка персональних даних' },
{ name: 'content', type: 'textarea', label: 'Текст сторінки' },
],
},
],
}

250
src/lib/legalDefaults.ts Normal file
View file

@ -0,0 +1,250 @@
const COMPANY = 'ТОВ «ТЕХНОСМАРТ УКРАЇНА»'
const EDRPOU = '40166430'
const ADDRESS = 'Житомирська обл., м. Житомир, вул. Мала Бердичівська, буд. 16а, каб. 3'
const SITE = 'https://shumiland.com.ua'
const EMAIL = 'smart.office@proton.me'
const PHONE = '+380 67 144 3635'
export const PRIVACY_DEFAULT = `Дата набрання чинності: 1 серпня 2025 р.
1. ЗАГАЛЬНІ ПОЛОЖЕННЯ
Цей документ є Політикою конфіденційності ${COMPANY} (далі «Компанія», «ми»), що регулює порядок збору, обробки та захисту персональних даних користувачів вебсайту ${SITE} (далі «Сайт») відповідно до Закону України «Про захист персональних даних» та Регламенту ЄС 2016/679 (GDPR).
2. ХТО МИ
Оператор персональних даних:
${COMPANY}, ЄДРПОУ ${EDRPOU}
Юридична адреса: ${ADDRESS}
Email: ${EMAIL}
Телефон: ${PHONE}
3. ЯКІ ДАНІ МИ ЗБИРАЄМО
При користуванні Сайтом ми можемо збирати:
Контактні дані (ім'я, email, номер телефону) — при заповненні форм замовлення або зворотнього зв'язку;
Платіжні дані обробляються виключно платіжним провайдером (Monobank), ми не зберігаємо реквізити карток;
Технічні дані (IP-адреса, cookie, дані браузера) для аналітики та покращення роботи Сайту;
Дані щодо замовлень (склад замовлення, дата, сума).
4. МЕТА ОБРОБКИ ДАНИХ
Ми обробляємо персональні дані з метою:
Виконання договірних зобов'язань (продаж квитків, обробка замовлень);
Надсилання підтверджень замовлень та квитків на email;
Зв'язку з клієнтом у разі потреби;
Покращення якості послуг та роботи Сайту;
Дотримання вимог законодавства.
5. ЗБЕРІГАННЯ ТА ЗАХИСТ ДАНИХ
Персональні дані зберігаються на захищених серверах. Ми вживаємо технічних і організаційних заходів для захисту даних від несанкціонованого доступу. Дані зберігаються не довше, ніж це необхідно для зазначених цілей.
6. ПЕРЕДАЧА ДАНИХ ТРЕТІМ ОСОБАМ
Ми не продаємо та не передаємо персональні дані третім особам, крім:
Платіжних сервісів (Monobank) для обробки платежів;
Сервісів аналітики (Google Analytics) знеособлені дані;
Уповноважених державних органів на їхній законний запит.
7. ПРАВА СУБ'ЄКТІВ ДАНИХ
Ви маєте право:
Отримати доступ до своїх персональних даних;
Вимагати їх виправлення або видалення;
Відкликати згоду на обробку в будь-який момент;
Подати скаргу до Уповноваженого з прав людини.
Для реалізації прав звертайтеся: ${EMAIL}
8. COOKIE
Сайт використовує cookie для аналітики та покращення роботи. Ви можете відключити cookie в налаштуваннях браузера.
9. ЗМІНИ ДО ПОЛІТИКИ
Ми можемо оновлювати цю Політику. Актуальна версія завжди доступна на Сайті.
Контакти: ${EMAIL} | ${PHONE}`
export const TERMS_DEFAULT = `Дата набрання чинності: 1 серпня 2025 р.
1. ЗАГАЛЬНІ ПОЛОЖЕННЯ
Ці Умови використання (далі «Умови») регулюють відносини між ${COMPANY}, ЄДРПОУ ${EDRPOU} (далі «Парк», «ми»), та користувачами вебсайту ${SITE} (далі «Сайт», «Користувач»).
Використання Сайту означає повне прийняття цих Умов.
2. ПРЕДМЕТ
Сайт надає інформацію про парк розваг «Шуміленд», а також забезпечує можливість онлайн-придбання квитків на атракції та послуги парку.
3. ПРИДБАННЯ КВИТКІВ
3.1. Квитки реалізуються через онлайн-форму на Сайті.
3.2. Оплата здійснюється через платіжний сервіс Monobank.
3.3. Підтвердження замовлення та квиток надсилаються на вказаний email протягом 15 хвилин після успішної оплати.
3.4. Квиток дійсний на дату та час, зазначені у замовленні.
4. ПРАВИЛА ВІДВІДУВАННЯ
4.1. Відвідувачі зобов'язані дотримуватись правил поведінки на території парку.
4.2. Адміністрація парку має право відмовити у вході або видалити відвідувача, що порушує правила.
4.3. Діти до 10 років допускаються лише у супроводі дорослих.
4.4. Забороняється вносити на територію парку алкогольні напої, наркотичні речовини, зброю.
5. ПОВЕРНЕННЯ ТА ОБМІН КВИТКІВ
5.1. Повернення коштів за придбані квитки можливе не пізніше ніж за 24 години до початку відвідування.
5.2. Для повернення звертайтеся на email: ${EMAIL} із зазначенням номера замовлення.
5.3. Кошти повертаються на картку, з якої здійснювалася оплата, протягом 5-7 банківських днів.
6. ВІДПОВІДАЛЬНІСТЬ
6.1. Парк не несе відповідальності за особисті речі відвідувачів.
6.2. Відвідування атракціонів здійснюється на власний ризик з урахуванням вікових та медичних обмежень.
6.3. Адміністрація парку не несе відповідальності за шкоду, спричинену порушенням правил відвідування.
7. ІНТЕЛЕКТУАЛЬНА ВЛАСНІСТЬ
Усі матеріали Сайту (тексти, зображення, логотипи) є власністю ${COMPANY}. Копіювання без письмового дозволу заборонено.
8. ЗМІНИ УМОВ
Ми залишаємо за собою право змінювати ці Умови. Нова редакція набирає чинності з моменту публікації на Сайті.
Контакти: ${EMAIL} | ${PHONE}`
export const OFFER_DEFAULT = `Дата набрання чинності: 1 серпня 2025 р.
ПУБЛІЧНА ОФЕРТА
про надання послуг парку розваг «Шуміленд»
${COMPANY}, ЄДРПОУ ${EDRPOU}, юридична адреса: ${ADDRESS} (далі «Виконавець»), відповідно до ст. 633, 641 Цивільного кодексу України, публікує цю Публічну оферту (далі «Оферта»), яка є офіційною пропозицією укласти Договір про надання послуг на нижчевикладених умовах.
1. ТЕРМІНИ ТА ВИЗНАЧЕННЯ
Оферта ця публічна пропозиція Виконавця укласти Договір.
Акцепт повна безумовна згода Замовника з умовами Оферти, що виражається в оплаті послуг.
Замовник фізична або юридична особа, яка здійснила Акцепт.
Послуги право відвідування парку розваг «Шуміленд» та користування атракціонами відповідно до придбаного квитка.
2. ПРЕДМЕТ ДОГОВОРУ
2.1. Виконавець зобов'язується надати Замовнику послуги з доступу до парку розваг «Шуміленд» (далі «Парк») відповідно до придбаного квитка.
2.2. Замовник зобов'язується оплатити послуги в розмірі, визначеному на Сайті на момент замовлення.
3. ПОРЯДОК УКЛАДЕННЯ ДОГОВОРУ
3.1. Договір вважається укладеним з моменту повної оплати вартості квитка.
3.2. Акцептом є факт оплати замовлення через платіжну систему.
3.3. Підтвердженням укладення Договору є електронний квиток, надісланий на email Замовника.
4. ВАРТІСТЬ ТА ПОРЯДОК ОПЛАТИ
4.1. Вартість послуг визначається відповідно до діючого прайс-листа, опублікованого на Сайті.
4.2. Оплата здійснюється виключно в безготівковій формі через платіжний сервіс Monobank.
4.3. Ціни зазначені в гривнях (UAH), включаючи всі податки та збори.
5. ПРАВА ТА ОБОВ'ЯЗКИ СТОРІН
5.1. Виконавець зобов'язаний:
забезпечити доступ до Парку згідно з придбаним квитком;
підтримувати атракціони та зони в безпечному стані;
надати необхідну інформацію про умови відвідування.
5.2. Замовник зобов'язаний:
пред'явити квиток при вході;
дотримуватись правил відвідування Парку;
нести відповідальність за поведінку неповнолітніх осіб, яких він супроводжує.
6. ВІДПОВІДАЛЬНІСТЬ СТОРІН
6.1. За невиконання або неналежне виконання умов Договору Сторони несуть відповідальність відповідно до чинного законодавства України.
6.2. Виконавець не несе відповідальності за неможливість надання послуг внаслідок обставин непереборної сили.
7. ПОРЯДОК ПОВЕРНЕННЯ КОШТІВ
7.1. Замовник має право відмовитися від послуг та отримати повне відшкодування коштів не пізніше ніж за 24 години до дати відвідування.
7.2. Заявка на повернення подається на email: ${EMAIL}
7.3. Кошти повертаються протягом 5-7 банківських днів на рахунок, з якого здійснювалася оплата.
8. СТРОК ДІЇ ОФЕРТИ
Оферта діє безстроково до її відкликання Виконавцем.
9. РЕКВІЗИТИ ВИКОНАВЦЯ
${COMPANY}
ЄДРПОУ: ${EDRPOU}
Адреса: ${ADDRESS}
Email: ${EMAIL}
Телефон: ${PHONE}
Сайт: ${SITE}`
export const DATA_PROCESSING_DEFAULT = `Дата набрання чинності: 1 серпня 2025 р.
ЗГОДА НА ОБРОБКУ ПЕРСОНАЛЬНИХ ДАНИХ
${COMPANY}, ЄДРПОУ ${EDRPOU} (далі «Оператор»), відповідно до Закону України «Про захист персональних даних» від 01.06.2010 2297-VI та Регламенту ЄС 2016/679 (GDPR), інформує про умови обробки персональних даних.
1. ОПЕРАТОР ПЕРСОНАЛЬНИХ ДАНИХ
Найменування: ${COMPANY}
ЄДРПОУ: ${EDRPOU}
Адреса: ${ADDRESS}
Email: ${EMAIL}
Телефон: ${PHONE}
2. СКЛАД ПЕРСОНАЛЬНИХ ДАНИХ
Оператор обробляє такі категорії персональних даних:
Прізвище, ім'я (за наявності у замовленні);
Адреса електронної пошти;
Номер телефону;
Інформація про замовлення та відвідування;
Технічні дані (IP-адреса, дані cookie).
3. МЕТА ТА ПРАВОВА ПІДСТАВА ОБРОБКИ
3.1. Виконання договору (ст. 6(1)(b) GDPR) обробка даних, необхідних для надання послуг, продажу квитків та підтвердження замовлень.
3.2. Законний інтерес (ст. 6(1)(f) GDPR) покращення якості послуг, аналітика, безпека Сайту.
3.3. Дотримання юридичного зобов'язання (ст. 6(1)(c) GDPR) виконання вимог законодавства.
4. ПОРЯДОК ОБРОБКИ ДАНИХ
4.1. Збір даних здійснюється при заповненні форм на Сайті або при покупці квитків.
4.2. Дані зберігаються на захищених серверах на території ЄС або в Україні.
4.3. Строк зберігання даних 3 роки з моменту останньої взаємодії або до реалізації права на видалення.
4.4. Персональні дані не передаються третім особам, крім платіжних провайдерів та сервісів аналітики в знеособленому вигляді.
5. ПРАВА СУБ'ЄКТІВ ПЕРСОНАЛЬНИХ ДАНИХ
Відповідно до законодавства ви маєте право:
Доступу до своїх персональних даних;
Виправлення неточних або неповних даних;
Видалення («право бути забутим»);
Обмеження обробки;
Портативності даних;
Заперечення проти обробки;
Відкликання згоди без шкоди для законності обробки до її відкликання.
6. РЕАЛІЗАЦІЯ ПРАВ
Для реалізації будь-якого з перелічених прав направте запит на email: ${EMAIL}
Строк розгляду запиту 30 календарних днів з моменту отримання.
7. ЗАХИСТ ДАНИХ
7.1. Оператор вживає технічних та організаційних заходів безпеки відповідно до ст. 32 GDPR.
7.2. У разі витоку персональних даних Оператор повідомить суб'єктів протягом 72 годин.
8. СКАРГИ
У разі порушення ваших прав ви маєте право подати скаргу до:
Уповноваженого Верховної Ради України з прав людини;
Наглядового органу ЄС у сфері захисту даних (для громадян ЄС).
Контакти Оператора: ${EMAIL} | ${PHONE}`

View file

@ -82,6 +82,7 @@ export interface Config {
'locations-page': LocationsPage
'blog-index-page': BlogIndexPage
'dinosaur-page': DinosaurPage
'legal-pages': LegalPage
}
globalsSelect: {
'home-page': HomePageSelect<false> | HomePageSelect<true>
@ -97,6 +98,7 @@ export interface Config {
'locations-page': LocationsPageSelect<false> | LocationsPageSelect<true>
'blog-index-page': BlogIndexPageSelect<false> | BlogIndexPageSelect<true>
'dinosaur-page': DinosaurPageSelect<false> | DinosaurPageSelect<true>
'legal-pages': LegalPagesSelect<false> | LegalPagesSelect<true>
}
locale: null
widgets: {
@ -2468,6 +2470,31 @@ export interface DinosaurPage {
updatedAt?: string | null
createdAt?: string | null
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "legal-pages".
*/
export interface LegalPage {
id: number
privacy?: {
title?: string | null
content?: string | null
}
terms?: {
title?: string | null
content?: string | null
}
offer?: {
title?: string | null
content?: string | null
}
dataProcessing?: {
title?: string | null
content?: string | null
}
updatedAt?: string | null
createdAt?: string | null
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "home-page_select".
@ -3068,6 +3095,39 @@ export interface DinosaurPageSelect<T extends boolean = true> {
createdAt?: T
globalType?: T
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "legal-pages_select".
*/
export interface LegalPagesSelect<T extends boolean = true> {
privacy?:
| T
| {
title?: T
content?: T
}
terms?:
| T
| {
title?: T
content?: T
}
offer?:
| T
| {
title?: T
content?: T
}
dataProcessing?:
| T
| {
title?: T
content?: T
}
updatedAt?: T
createdAt?: T
globalType?: T
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "collections_widget".