feat: Phase 2 complete — content model, blocks, collections, globals
- 11 Page Builder blocks: HeroBlock, TextBlock, FeaturesBlock, LocationCardBlock, PricingBlock, GalleryBlock, FormBlock (with label relation for lead tracking), CTABlock, CountdownBlock, BlogPreviewBlock, MapBlock - Collections: Labels, Pages, LandingPages, Blog, Events, Leads (UTM + googleClientId + label relation), Orders, Tickets - Globals: SiteSettings (GTM/GA4/Binotel/Umami), TicketsConfig, Navigation - Added "type": "module" to package.json for Payload CLI ESM compatibility - payload-types.ts generated (1874 lines) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
04b112d476
commit
61e73033fe
25 changed files with 2927 additions and 4 deletions
|
|
@ -10,7 +10,9 @@
|
|||
"Bash(pnpm approve-builds:*)",
|
||||
"Bash(pnpm info:*)",
|
||||
"Bash(node -e \"const v=require\\('fs'\\).readFileSync\\('/dev/stdin','utf8'\\); const versions=JSON.parse\\(v\\); const compatible=versions.filter\\(v=>{ const [,maj,min,patch]=v.match\\(/^\\(\\\\d+\\)\\\\.\\(\\\\d+\\)\\\\.\\(\\\\d+\\)/\\) || []; if\\(!maj\\) return false; const n=+maj*10000 + +min*100 + +patch; return \\(n>=150209 && n<150300\\) || \\(n>=150309 && n<150400\\) || \\(n>=150411 && n<150500\\); }\\); console.log\\(compatible.slice\\(-5\\).join\\('\\\\n'\\)\\);\")",
|
||||
"Bash(pnpm next:*)"
|
||||
"Bash(pnpm next:*)",
|
||||
"Bash(pnpm payload:*)",
|
||||
"Bash(NODE_OPTIONS='--import tsx/esm' pnpm payload generate:types)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
"version": "1.0.0",
|
||||
"description": "Shumiland — сімейний тематичний парк",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
|
|
|
|||
95
src/blocks/BlogPreviewBlock.ts
Normal file
95
src/blocks/BlogPreviewBlock.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const BlogPreviewBlock: Block = {
|
||||
slug: 'blogPreviewBlock',
|
||||
interfaceName: 'BlogPreviewBlock',
|
||||
labels: {
|
||||
singular: 'Блок анонсів',
|
||||
plural: 'Блоки анонсів',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок секції',
|
||||
admin: {
|
||||
placeholder: 'Новини та події',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Підзаголовок',
|
||||
admin: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
label: 'Джерело контенту',
|
||||
defaultValue: 'blog',
|
||||
options: [
|
||||
{ label: 'Блог (новини)', value: 'blog' },
|
||||
{ label: 'Події', value: 'events' },
|
||||
{ label: 'Блог + події', value: 'both' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'count',
|
||||
type: 'number',
|
||||
label: 'Кількість постів',
|
||||
defaultValue: 3,
|
||||
min: 1,
|
||||
max: 9,
|
||||
admin: {
|
||||
description: 'Скільки останніх постів показувати',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
label: 'Розмітка',
|
||||
defaultValue: 'cards',
|
||||
options: [
|
||||
{ label: 'Картки (3 в ряд)', value: 'cards' },
|
||||
{ label: 'Список', value: 'list' },
|
||||
{ label: 'Великий + малі', value: 'featured' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showMoreLink',
|
||||
type: 'checkbox',
|
||||
label: 'Показати посилання "Всі публікації"',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'moreLinkLabel',
|
||||
type: 'text',
|
||||
label: 'Текст посилання "Більше"',
|
||||
defaultValue: 'Всі публікації',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showMoreLink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'moreLinkUrl',
|
||||
type: 'text',
|
||||
label: 'URL посилання "Більше"',
|
||||
admin: {
|
||||
placeholder: '/blog',
|
||||
condition: (_, siblingData) => siblingData?.showMoreLink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
label: 'Фон секції',
|
||||
defaultValue: 'white',
|
||||
options: [
|
||||
{ label: 'Білий', value: 'white' },
|
||||
{ label: 'Світло-сірий', value: 'gray' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
135
src/blocks/CTABlock.ts
Normal file
135
src/blocks/CTABlock.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const CTABlock: Block = {
|
||||
slug: 'ctaBlock',
|
||||
interfaceName: 'CTABlock',
|
||||
labels: {
|
||||
singular: 'CTA банер',
|
||||
plural: 'CTA банери',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Готові до пригод?',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Опис',
|
||||
admin: {
|
||||
rows: 2,
|
||||
placeholder: 'Придбайте квитки онлайн зі знижкою',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundType',
|
||||
type: 'select',
|
||||
label: 'Тип фону',
|
||||
defaultValue: 'gradient',
|
||||
options: [
|
||||
{ label: 'Градієнт (brand)', value: 'gradient' },
|
||||
{ label: 'Зображення', value: 'image' },
|
||||
{ label: 'Однотонний', value: 'solid' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'backgroundImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Фонове зображення',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.backgroundType === 'image',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
label: 'Колір фону',
|
||||
defaultValue: 'green',
|
||||
options: [
|
||||
{ label: 'Зелений (brand)', value: 'green' },
|
||||
{ label: 'Помаранчевий (brand)', value: 'orange' },
|
||||
{ label: 'Синій (brand)', value: 'blue' },
|
||||
{ label: 'Фіолетовий (brand)', value: 'purple' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.backgroundType === 'solid',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'overlayOpacity',
|
||||
type: 'number',
|
||||
label: 'Затемнення фону (%)',
|
||||
defaultValue: 50,
|
||||
min: 0,
|
||||
max: 90,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.backgroundType === 'image',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'buttons',
|
||||
type: 'array',
|
||||
label: 'Кнопки',
|
||||
minRows: 1,
|
||||
maxRows: 3,
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Текст кнопки',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Купити квиток',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Посилання',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: '/payments',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'variant',
|
||||
type: 'select',
|
||||
label: 'Стиль',
|
||||
defaultValue: 'orange',
|
||||
options: [
|
||||
{ label: 'Помаранчевий', value: 'orange' },
|
||||
{ label: 'Зелений', value: 'green' },
|
||||
{ label: 'Синій', value: 'blue' },
|
||||
{ label: 'Фіолетовий', value: 'purple' },
|
||||
{ label: 'Білий обводка', value: 'outline-white' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'gtmEvent',
|
||||
type: 'text',
|
||||
label: 'GTM подія',
|
||||
admin: {
|
||||
placeholder: 'cta_click',
|
||||
description: 'Назва події для Google Analytics',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'align',
|
||||
type: 'select',
|
||||
label: 'Вирівнювання',
|
||||
defaultValue: 'center',
|
||||
options: [
|
||||
{ label: 'По центру', value: 'center' },
|
||||
{ label: 'Ліворуч', value: 'left' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
97
src/blocks/CountdownBlock.ts
Normal file
97
src/blocks/CountdownBlock.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const CountdownBlock: Block = {
|
||||
slug: 'countdownBlock',
|
||||
interfaceName: 'CountdownBlock',
|
||||
labels: {
|
||||
singular: 'Зворотний відлік',
|
||||
plural: 'Зворотні відліки',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок',
|
||||
admin: {
|
||||
placeholder: 'До відкриття залишилось',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'targetDate',
|
||||
type: 'date',
|
||||
label: 'Дата та час події',
|
||||
required: true,
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
description: 'Коли відлік досягне нуля — покаже expiredMessage',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'expiredMessage',
|
||||
type: 'text',
|
||||
label: 'Повідомлення після закінчення відліку',
|
||||
defaultValue: 'Подія вже відбулась!',
|
||||
},
|
||||
{
|
||||
name: 'showDays',
|
||||
type: 'checkbox',
|
||||
label: 'Показати дні',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'showHours',
|
||||
type: 'checkbox',
|
||||
label: 'Показати години',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'showMinutes',
|
||||
type: 'checkbox',
|
||||
label: 'Показати хвилини',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'showSeconds',
|
||||
type: 'checkbox',
|
||||
label: 'Показати секунди',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
label: 'Фон секції',
|
||||
defaultValue: 'green',
|
||||
options: [
|
||||
{ label: 'Зелений (brand)', value: 'green' },
|
||||
{ label: 'Синій (brand)', value: 'blue' },
|
||||
{ label: 'Фіолетовий (brand)', value: 'purple' },
|
||||
{ label: 'Білий', value: 'white' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cta',
|
||||
type: 'group',
|
||||
label: 'Кнопка (необов\'язково)',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Текст кнопки',
|
||||
admin: {
|
||||
placeholder: 'Дізнатись більше',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Посилання',
|
||||
admin: {
|
||||
placeholder: '/events',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
98
src/blocks/FeaturesBlock.ts
Normal file
98
src/blocks/FeaturesBlock.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const FeaturesBlock: Block = {
|
||||
slug: 'featuresBlock',
|
||||
interfaceName: 'FeaturesBlock',
|
||||
labels: {
|
||||
singular: 'Блок переваг',
|
||||
plural: 'Блоки переваг',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок секції',
|
||||
admin: {
|
||||
placeholder: 'Чому Шуміленд?',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Підзаголовок секції',
|
||||
admin: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
label: 'Кількість колонок',
|
||||
defaultValue: '3',
|
||||
options: [
|
||||
{ label: '2 колонки', value: '2' },
|
||||
{ label: '3 колонки', value: '3' },
|
||||
{ label: '4 колонки', value: '4' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
label: 'Переваги',
|
||||
minRows: 1,
|
||||
maxRows: 12,
|
||||
admin: {
|
||||
description: 'Додайте переваги або особливості',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'text',
|
||||
label: 'Іконка (Lucide React назва)',
|
||||
admin: {
|
||||
placeholder: 'Trees, Shield, UtensilsCrossed, Bus',
|
||||
description: 'Назва іконки з lucide.dev',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Або зображення',
|
||||
admin: {
|
||||
description: 'Якщо вказано — замінює іконку',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Назва',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Безпека та комфорт',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Опис',
|
||||
admin: {
|
||||
rows: 2,
|
||||
placeholder: 'Розкажіть детальніше про цю перевагу',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
label: 'Фон секції',
|
||||
defaultValue: 'white',
|
||||
options: [
|
||||
{ label: 'Білий', value: 'white' },
|
||||
{ label: 'Світло-сірий', value: 'gray' },
|
||||
{ label: 'Зелений (brand)', value: 'green' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
104
src/blocks/FormBlock.ts
Normal file
104
src/blocks/FormBlock.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const FormBlock: Block = {
|
||||
slug: 'formBlock',
|
||||
interfaceName: 'FormBlock',
|
||||
labels: {
|
||||
singular: 'Форма',
|
||||
plural: 'Форми',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок форми',
|
||||
admin: {
|
||||
placeholder: 'Замовити день народження',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Підзаголовок',
|
||||
admin: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'formType',
|
||||
type: 'select',
|
||||
label: 'Тип форми',
|
||||
required: true,
|
||||
defaultValue: 'generic',
|
||||
options: [
|
||||
{ label: 'День народження', value: 'birthday' },
|
||||
{ label: 'Групове відвідування', value: 'group' },
|
||||
{ label: 'Зворотний дзвінок', value: 'callback' },
|
||||
{ label: 'Загальна форма', value: 'generic' },
|
||||
],
|
||||
admin: {
|
||||
description: 'Визначає набір полів форми та тег для ліда',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'relationship',
|
||||
relationTo: 'labels',
|
||||
label: 'Мітка (джерело ліда)',
|
||||
admin: {
|
||||
description:
|
||||
'Мітка буде збережена разом з лідом для трекінгу джерела. Наприклад: "Instagram Літо 2025", "Google Акція", "Landing Динопарк".',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'gtmEvent',
|
||||
type: 'text',
|
||||
label: 'GTM подія (при відправці)',
|
||||
admin: {
|
||||
placeholder: 'lead_submit_birthday',
|
||||
description: 'Назва події для Google Tag Manager / GA4 після успішної відправки',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'successMessage',
|
||||
type: 'textarea',
|
||||
label: 'Повідомлення після відправки',
|
||||
defaultValue: 'Дякуємо! Ми зв\'яжемось з вами найближчим часом.',
|
||||
admin: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
label: 'Фон секції',
|
||||
defaultValue: 'white',
|
||||
options: [
|
||||
{ label: 'Білий', value: 'white' },
|
||||
{ label: 'Світло-сірий', value: 'gray' },
|
||||
{ label: 'Зелений (brand)', value: 'green' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
label: 'Розмітка',
|
||||
defaultValue: 'centered',
|
||||
options: [
|
||||
{ label: 'По центру (вузька)', value: 'centered' },
|
||||
{ label: 'Повна ширина', value: 'full' },
|
||||
{ label: 'З бічним зображенням', value: 'split' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sideImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Бічне зображення',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'split',
|
||||
description: 'Відображається поруч з формою при виборі "З бічним зображенням"',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
81
src/blocks/GalleryBlock.ts
Normal file
81
src/blocks/GalleryBlock.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const GalleryBlock: Block = {
|
||||
slug: 'galleryBlock',
|
||||
interfaceName: 'GalleryBlock',
|
||||
labels: {
|
||||
singular: 'Галерея',
|
||||
plural: 'Галереї',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок',
|
||||
admin: {
|
||||
placeholder: 'Фотогалерея',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Підзаголовок',
|
||||
admin: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
label: 'Тип відображення',
|
||||
defaultValue: 'grid',
|
||||
options: [
|
||||
{ label: 'Сітка (рівні квадрати)', value: 'grid' },
|
||||
{ label: 'Мозаїка (masonry)', value: 'masonry' },
|
||||
{ label: 'Карусель (слайдер)', value: 'carousel' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
label: 'Кількість колонок (для сітки/мозаїки)',
|
||||
defaultValue: '3',
|
||||
options: [
|
||||
{ label: '2 колонки', value: '2' },
|
||||
{ label: '3 колонки', value: '3' },
|
||||
{ label: '4 колонки', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'grid' || siblingData?.layout === 'masonry',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'images',
|
||||
type: 'array',
|
||||
label: 'Зображення',
|
||||
minRows: 1,
|
||||
maxRows: 30,
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Зображення',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'text',
|
||||
label: 'Підпис (необов\'язково)',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'enableLightbox',
|
||||
type: 'checkbox',
|
||||
label: 'Відкривати у повному розмірі при кліку',
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
161
src/blocks/HeroBlock.ts
Normal file
161
src/blocks/HeroBlock.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const HeroBlock: Block = {
|
||||
slug: 'heroBlock',
|
||||
interfaceName: 'HeroBlock',
|
||||
labels: {
|
||||
singular: 'Hero секція',
|
||||
plural: 'Hero секції',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'headline',
|
||||
type: 'text',
|
||||
label: 'Заголовок',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Ласкаво просимо до Шуміленду!',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subheadline',
|
||||
type: 'textarea',
|
||||
label: 'Підзаголовок',
|
||||
admin: {
|
||||
rows: 2,
|
||||
placeholder: 'Парк розваг для всієї родини',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundType',
|
||||
type: 'select',
|
||||
label: 'Тип фону',
|
||||
required: true,
|
||||
defaultValue: 'gradient',
|
||||
options: [
|
||||
{ label: 'Відео', value: 'video' },
|
||||
{ label: 'Зображення', value: 'image' },
|
||||
{ label: 'Градієнт', value: 'gradient' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'backgroundVideo',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Фонове відео',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.backgroundType === 'video',
|
||||
description: 'MP4, рекомендовано до 10MB',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Фонове зображення',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.backgroundType === 'image',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'backgroundGradient',
|
||||
type: 'select',
|
||||
label: 'Варіант градієнту',
|
||||
defaultValue: 'green',
|
||||
options: [
|
||||
{ label: 'Зелений (brand)', value: 'green' },
|
||||
{ label: 'Синій', value: 'blue' },
|
||||
{ label: 'Фіолетовий', value: 'purple' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.backgroundType === 'gradient',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'overlayOpacity',
|
||||
type: 'number',
|
||||
label: 'Затемнення фону (%)',
|
||||
defaultValue: 40,
|
||||
min: 0,
|
||||
max: 90,
|
||||
admin: {
|
||||
description: 'Від 0 (без затемнення) до 90 (майже чорний)',
|
||||
condition: (_, siblingData) => siblingData?.backgroundType !== 'gradient',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'cta',
|
||||
type: 'group',
|
||||
label: 'Основна кнопка CTA',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Текст кнопки',
|
||||
defaultValue: 'Купити квиток',
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Посилання',
|
||||
defaultValue: '/payments',
|
||||
},
|
||||
{
|
||||
name: 'variant',
|
||||
type: 'select',
|
||||
label: 'Стиль',
|
||||
defaultValue: 'orange',
|
||||
options: [
|
||||
{ label: 'Помаранчевий', value: 'orange' },
|
||||
{ label: 'Зелений', value: 'green' },
|
||||
{ label: 'Синій', value: 'blue' },
|
||||
{ label: 'Фіолетовий', value: 'purple' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'gtmEvent',
|
||||
type: 'text',
|
||||
label: 'GTM подія',
|
||||
defaultValue: 'hero_cta_click',
|
||||
admin: {
|
||||
description: 'Назва події для Google Analytics',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'secondaryCta',
|
||||
type: 'group',
|
||||
label: 'Додаткова кнопка (необов\'язково)',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Текст кнопки',
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Посилання',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showScrollIndicator',
|
||||
type: 'checkbox',
|
||||
label: 'Показати індикатор прокрутки',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'minHeight',
|
||||
type: 'select',
|
||||
label: 'Мінімальна висота',
|
||||
defaultValue: 'screen',
|
||||
options: [
|
||||
{ label: 'Повний екран (100vh)', value: 'screen' },
|
||||
{ label: '80% екрану', value: '80vh' },
|
||||
{ label: '60% екрану', value: '60vh' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
100
src/blocks/LocationCardBlock.ts
Normal file
100
src/blocks/LocationCardBlock.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const LocationCardBlock: Block = {
|
||||
slug: 'locationCardBlock',
|
||||
interfaceName: 'LocationCardBlock',
|
||||
labels: {
|
||||
singular: 'Картки локацій',
|
||||
plural: 'Картки локацій',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок секції',
|
||||
admin: {
|
||||
placeholder: 'Наші локації',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Підзаголовок',
|
||||
admin: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'locations',
|
||||
type: 'array',
|
||||
label: 'Локації',
|
||||
minRows: 1,
|
||||
maxRows: 6,
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Назва локації',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Динопарк',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'URL (slug)',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'dinopark',
|
||||
description: 'URL-адреса сторінки, наприклад dinopark',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Короткий опис',
|
||||
required: true,
|
||||
admin: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Зображення',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'badge',
|
||||
type: 'text',
|
||||
label: 'Бейдж (необов\'язково)',
|
||||
admin: {
|
||||
placeholder: 'Новинка! або 23 динозаври',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'text',
|
||||
label: 'Ціна (відображення)',
|
||||
admin: {
|
||||
placeholder: 'від 160 грн',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ctaLabel',
|
||||
type: 'text',
|
||||
label: 'Текст кнопки',
|
||||
defaultValue: 'Дізнатись більше',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showBuyButton',
|
||||
type: 'checkbox',
|
||||
label: 'Показати кнопку "Купити квиток"',
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
120
src/blocks/MapBlock.ts
Normal file
120
src/blocks/MapBlock.ts
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const MapBlock: Block = {
|
||||
slug: 'mapBlock',
|
||||
interfaceName: 'MapBlock',
|
||||
labels: {
|
||||
singular: 'Блок з картою',
|
||||
plural: 'Блоки з картою',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок',
|
||||
admin: {
|
||||
placeholder: 'Як нас знайти',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
type: 'textarea',
|
||||
label: 'Адреса',
|
||||
admin: {
|
||||
rows: 2,
|
||||
placeholder: 'с. Малютянка, Бучанський район, Київська область',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'embedUrl',
|
||||
type: 'text',
|
||||
label: 'URL карти (Google Maps embed)',
|
||||
admin: {
|
||||
placeholder: 'https://www.google.com/maps/embed?pb=...',
|
||||
description: 'Google Maps → Поділитися → Вставити карту → скопіювати src з iframe',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'mapHeight',
|
||||
type: 'select',
|
||||
label: 'Висота карти',
|
||||
defaultValue: 'medium',
|
||||
options: [
|
||||
{ label: 'Низька (300px)', value: 'small' },
|
||||
{ label: 'Середня (450px)', value: 'medium' },
|
||||
{ label: 'Висока (600px)', value: 'large' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'transportInfo',
|
||||
type: 'array',
|
||||
label: 'Як дістатись',
|
||||
maxRows: 5,
|
||||
fields: [
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
label: 'Транспорт',
|
||||
options: [
|
||||
{ label: 'Автомобіль', value: 'car' },
|
||||
{ label: 'Автобус', value: 'bus' },
|
||||
{ label: 'Потяг', value: 'train' },
|
||||
{ label: 'Таксі', value: 'taxi' },
|
||||
{ label: 'Велосипед', value: 'bike' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Опис маршруту',
|
||||
required: true,
|
||||
admin: {
|
||||
rows: 2,
|
||||
placeholder: 'З Києва маршруткою №xxx до зупинки "Шуміленд"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'workingHours',
|
||||
type: 'array',
|
||||
label: 'Графік роботи',
|
||||
maxRows: 7,
|
||||
fields: [
|
||||
{
|
||||
name: 'days',
|
||||
type: 'text',
|
||||
label: 'Дні',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Понеділок – П\'ятниця',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'hours',
|
||||
type: 'text',
|
||||
label: 'Години',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: '10:00 – 20:00',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showDirectionsButton',
|
||||
type: 'checkbox',
|
||||
label: 'Показати кнопку "Прокласти маршрут"',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'directionsUrl',
|
||||
type: 'text',
|
||||
label: 'Посилання на Google Maps (для маршруту)',
|
||||
admin: {
|
||||
placeholder: 'https://goo.gl/maps/...',
|
||||
condition: (_, siblingData) => siblingData?.showDirectionsButton,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
115
src/blocks/PricingBlock.ts
Normal file
115
src/blocks/PricingBlock.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const PricingBlock: Block = {
|
||||
slug: 'pricingBlock',
|
||||
interfaceName: 'PricingBlock',
|
||||
labels: {
|
||||
singular: 'Блок цін',
|
||||
plural: 'Блоки цін',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок',
|
||||
admin: {
|
||||
placeholder: 'Ціни та квитки',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Підзаголовок',
|
||||
admin: {
|
||||
rows: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showTicketSelector',
|
||||
type: 'checkbox',
|
||||
label: 'Показати інтерактивний вибір квитків',
|
||||
defaultValue: true,
|
||||
admin: {
|
||||
description: 'Якщо увімкнено — відображається калькулятор з категоріями з TicketsConfig. Якщо вимкнено — відображається таблиця нижче.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'location',
|
||||
type: 'select',
|
||||
label: 'Фільтр по локації',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showTicketSelector,
|
||||
description: 'Показати квитки тільки для цієї локації',
|
||||
},
|
||||
options: [
|
||||
{ label: 'Всі квитки', value: 'all' },
|
||||
{ label: 'Динопарк', value: 'dinopark' },
|
||||
{ label: 'ДивоЛіс', value: 'dyvo-lis' },
|
||||
{ label: 'Лабіринт', value: 'labiryn' },
|
||||
{ label: 'Комбо', value: 'combo' },
|
||||
],
|
||||
defaultValue: 'all',
|
||||
},
|
||||
{
|
||||
name: 'customTable',
|
||||
type: 'array',
|
||||
label: 'Таблиця цін (ручне заповнення)',
|
||||
admin: {
|
||||
condition: (_, siblingData) => !siblingData?.showTicketSelector,
|
||||
description: 'Заповніть вручну якщо не використовується автоматичний вибір квитків',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
label: 'Категорія / назва',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Динопарк',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
label: 'Опис',
|
||||
admin: {
|
||||
placeholder: 'Включає вхід на територію з 23 динозаврами',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'text',
|
||||
label: 'Ціна',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: '300 грн',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'highlight',
|
||||
type: 'checkbox',
|
||||
label: 'Виділити рядок',
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showFreeCategories',
|
||||
type: 'checkbox',
|
||||
label: 'Показати умови безкоштовного входу',
|
||||
defaultValue: true,
|
||||
admin: {
|
||||
description: 'УБД, діти до 3р, діти з інвалідністю тощо',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
type: 'textarea',
|
||||
label: 'Примітка',
|
||||
admin: {
|
||||
rows: 3,
|
||||
placeholder: '*Знижка діє лише на квитки повної вартості на окремі зони',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
56
src/blocks/TextBlock.ts
Normal file
56
src/blocks/TextBlock.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
export const TextBlock: Block = {
|
||||
slug: 'textBlock',
|
||||
interfaceName: 'TextBlock',
|
||||
labels: {
|
||||
singular: 'Текстовий блок',
|
||||
plural: 'Текстові блоки',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
label: 'Контент',
|
||||
required: true,
|
||||
admin: {
|
||||
description: 'Підтримується форматування: заголовки, списки, посилання, жирний текст',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'align',
|
||||
type: 'select',
|
||||
label: 'Вирівнювання',
|
||||
defaultValue: 'left',
|
||||
options: [
|
||||
{ label: 'Ліворуч', value: 'left' },
|
||||
{ label: 'По центру', value: 'center' },
|
||||
{ label: 'Праворуч', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'maxWidth',
|
||||
type: 'select',
|
||||
label: 'Максимальна ширина',
|
||||
defaultValue: 'full',
|
||||
options: [
|
||||
{ label: 'Повна ширина', value: 'full' },
|
||||
{ label: 'Широка (900px)', value: 'wide' },
|
||||
{ label: 'Середня (720px)', value: 'medium' },
|
||||
{ label: 'Вузька (600px)', value: 'narrow' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
label: 'Фон секції',
|
||||
defaultValue: 'white',
|
||||
options: [
|
||||
{ label: 'Білий', value: 'white' },
|
||||
{ label: 'Світло-сірий', value: 'gray' },
|
||||
{ label: 'Зелений (brand)', value: 'green' },
|
||||
{ label: 'Синій (brand)', value: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
161
src/collections/Blog.ts
Normal file
161
src/collections/Blog.ts
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isEditor, isAdminOrPublished } from '../access'
|
||||
|
||||
export const Blog: CollectionConfig = {
|
||||
slug: 'blog',
|
||||
labels: {
|
||||
singular: 'Пост блогу',
|
||||
plural: 'Пости блогу',
|
||||
},
|
||||
admin: {
|
||||
group: 'Контент',
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'category', 'publishedAt', '_status'],
|
||||
preview: (doc) => {
|
||||
if (doc?.slug) {
|
||||
return `${process.env.NEXT_PUBLIC_SITE_URL}/blog/${doc.slug}`
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
access: {
|
||||
read: isAdminOrPublished,
|
||||
create: isEditor,
|
||||
update: isEditor,
|
||||
delete: isAdmin,
|
||||
},
|
||||
versions: {
|
||||
drafts: {
|
||||
autosave: {
|
||||
interval: 375,
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Як ми готуємось до літнього сезону',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'URL (slug)',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
placeholder: 'litniy-sezon-2026',
|
||||
description: 'Автоматично генерується з заголовку. Доступний за: /blog/{slug}',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'excerpt',
|
||||
type: 'textarea',
|
||||
label: 'Короткий опис (анонс)',
|
||||
admin: {
|
||||
rows: 3,
|
||||
placeholder: 'Розповідаємо про нові атракціони та заходи цього сезону',
|
||||
description: 'Відображається в картці на списку блогу та в META description',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'coverImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Обкладинка',
|
||||
admin: {
|
||||
description: 'Рекомендований розмір: 1200×630 px',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'select',
|
||||
label: 'Категорія',
|
||||
options: [
|
||||
{ label: 'Новини', value: 'news' },
|
||||
{ label: 'Заходи', value: 'events' },
|
||||
{ label: 'Корисне', value: 'tips' },
|
||||
{ label: 'За лаштунками', value: 'behind-scenes' },
|
||||
],
|
||||
admin: {
|
||||
placeholder: 'Оберіть категорію',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'array',
|
||||
label: 'Теги',
|
||||
maxRows: 10,
|
||||
fields: [
|
||||
{
|
||||
name: 'tag',
|
||||
type: 'text',
|
||||
label: 'Тег',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'динопарк',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'publishedAt',
|
||||
type: 'date',
|
||||
label: 'Дата публікації',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayOnly',
|
||||
displayFormat: 'd MMM yyyy',
|
||||
},
|
||||
description: 'Відображається на сторінці. Якщо не вказано — використовується дата створення.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'body',
|
||||
type: 'richText',
|
||||
label: 'Контент',
|
||||
required: true,
|
||||
admin: {
|
||||
description: 'Повний текст статті',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'seo',
|
||||
type: 'group',
|
||||
label: 'SEO',
|
||||
fields: [
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'Meta Title',
|
||||
admin: {
|
||||
description: 'Якщо порожнє — використовується заголовок поста',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'Meta Description',
|
||||
admin: {
|
||||
rows: 3,
|
||||
description: 'Якщо порожнє — використовується анонс поста',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ogImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'OG Image',
|
||||
admin: {
|
||||
description: 'Якщо порожнє — використовується обкладинка',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
141
src/collections/Events.ts
Normal file
141
src/collections/Events.ts
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isEditor, isAdminOrPublished } from '../access'
|
||||
|
||||
export const Events: CollectionConfig = {
|
||||
slug: 'events',
|
||||
labels: {
|
||||
singular: 'Подія',
|
||||
plural: 'Події',
|
||||
},
|
||||
admin: {
|
||||
group: 'Контент',
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'eventDate', 'isFeatured', '_status'],
|
||||
preview: (doc) => {
|
||||
if (doc?.slug) {
|
||||
return `${process.env.NEXT_PUBLIC_SITE_URL}/events/${doc.slug}`
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
access: {
|
||||
read: isAdminOrPublished,
|
||||
create: isEditor,
|
||||
update: isEditor,
|
||||
delete: isAdmin,
|
||||
},
|
||||
versions: {
|
||||
drafts: {
|
||||
autosave: {
|
||||
interval: 375,
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Назва події',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Новорічне шоу з Шуміком',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'URL (slug)',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
placeholder: 'novrichne-sho-z-shumikom',
|
||||
description: 'Доступний за: /events/{slug}',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'excerpt',
|
||||
type: 'textarea',
|
||||
label: 'Короткий опис',
|
||||
admin: {
|
||||
rows: 3,
|
||||
placeholder: 'Незабутнє новорічне шоу для дітей та їхніх батьків',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'coverImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Обкладинка',
|
||||
},
|
||||
{
|
||||
name: 'eventDate',
|
||||
type: 'date',
|
||||
label: 'Дата початку події',
|
||||
required: true,
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'eventEndDate',
|
||||
type: 'date',
|
||||
label: 'Дата закінчення події',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
description: 'Заповніть якщо подія триватиме кілька днів',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isFeatured',
|
||||
type: 'checkbox',
|
||||
label: 'Виділена подія (показувати на головній)',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: 'relatedLanding',
|
||||
type: 'relationship',
|
||||
relationTo: 'landing-pages',
|
||||
label: 'Пов\'язаний лендінг',
|
||||
admin: {
|
||||
description: 'Якщо для події є окремий лендінг — прив\'яжіть його тут',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'body',
|
||||
type: 'richText',
|
||||
label: 'Опис події',
|
||||
admin: {
|
||||
description: 'Детальний опис програми, учасників, умов',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'seo',
|
||||
type: 'group',
|
||||
label: 'SEO',
|
||||
fields: [
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'Meta Title',
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'Meta Description',
|
||||
admin: { rows: 3 },
|
||||
},
|
||||
{
|
||||
name: 'ogImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'OG Image',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
73
src/collections/Labels.ts
Normal file
73
src/collections/Labels.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isEditor } from '@/access'
|
||||
|
||||
const Labels: CollectionConfig = {
|
||||
slug: 'labels',
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
defaultColumns: ['name', 'color', 'isActive', 'createdAt'],
|
||||
group: 'Маркетинг',
|
||||
description: 'Мітки для відстеження джерел лідів (Instagram, Google Ads, подія тощо)',
|
||||
},
|
||||
labels: {
|
||||
singular: 'Мітка',
|
||||
plural: 'Мітки',
|
||||
},
|
||||
access: {
|
||||
read: isEditor,
|
||||
create: isAdmin,
|
||||
update: isAdmin,
|
||||
delete: isAdmin,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Назва мітки',
|
||||
required: true,
|
||||
unique: true,
|
||||
admin: {
|
||||
placeholder: 'Наприклад: Instagram Акція Літо 2026',
|
||||
description: 'Унікальна назва для ідентифікації джерела ліда',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
type: 'select',
|
||||
label: 'Колір',
|
||||
defaultValue: 'blue',
|
||||
options: [
|
||||
{ label: '🟢 Зелений', value: 'green' },
|
||||
{ label: '🔵 Синій', value: 'blue' },
|
||||
{ label: '🟠 Помаранчевий', value: 'orange' },
|
||||
{ label: '🟣 Фіолетовий', value: 'purple' },
|
||||
{ label: '🟡 Жовтий', value: 'yellow' },
|
||||
{ label: '🔴 Червоний', value: 'red' },
|
||||
{ label: '⚫ Чорний', value: 'black' },
|
||||
],
|
||||
admin: {
|
||||
description: 'Колір для відображення в таблиці лідів',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Опис',
|
||||
admin: {
|
||||
placeholder: 'Для якої кампанії або акції ця мітка?',
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
label: 'Активна',
|
||||
defaultValue: true,
|
||||
admin: {
|
||||
description: 'Неактивні мітки не відображаються при виборі форми',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default Labels
|
||||
181
src/collections/LandingPages.ts
Normal file
181
src/collections/LandingPages.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isEditor, isAdminOrPublished } from '../access'
|
||||
import { HeroBlock } from '../blocks/HeroBlock'
|
||||
import { TextBlock } from '../blocks/TextBlock'
|
||||
import { FeaturesBlock } from '../blocks/FeaturesBlock'
|
||||
import { LocationCardBlock } from '../blocks/LocationCardBlock'
|
||||
import { PricingBlock } from '../blocks/PricingBlock'
|
||||
import { GalleryBlock } from '../blocks/GalleryBlock'
|
||||
import { FormBlock } from '../blocks/FormBlock'
|
||||
import { CTABlock } from '../blocks/CTABlock'
|
||||
import { CountdownBlock } from '../blocks/CountdownBlock'
|
||||
import { BlogPreviewBlock } from '../blocks/BlogPreviewBlock'
|
||||
import { MapBlock } from '../blocks/MapBlock'
|
||||
|
||||
export const LandingPages: CollectionConfig = {
|
||||
slug: 'landing-pages',
|
||||
labels: {
|
||||
singular: 'Лендінг',
|
||||
plural: 'Лендінги',
|
||||
},
|
||||
admin: {
|
||||
group: 'Контент',
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'slug', 'eventDate', '_status', 'updatedAt'],
|
||||
preview: (doc) => {
|
||||
if (doc?.slug) {
|
||||
return `${process.env.NEXT_PUBLIC_SITE_URL}/landing/${doc.slug}`
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
access: {
|
||||
read: isAdminOrPublished,
|
||||
create: isEditor,
|
||||
update: isEditor,
|
||||
delete: isAdmin,
|
||||
},
|
||||
versions: {
|
||||
drafts: {
|
||||
autosave: {
|
||||
interval: 375,
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Назва лендінгу',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Новорічне свято 2026',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'URL (slug)',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
placeholder: 'novyy-rik-2026',
|
||||
description: 'Доступний за адресою: /landing/{slug}',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'eventDate',
|
||||
type: 'date',
|
||||
label: 'Дата події',
|
||||
admin: {
|
||||
description: 'Відображається на сторінці та використовується для відліку',
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'stickyCta',
|
||||
type: 'group',
|
||||
label: 'Sticky CTA (плаваюча кнопка знизу)',
|
||||
fields: [
|
||||
{
|
||||
name: 'enabled',
|
||||
type: 'checkbox',
|
||||
label: 'Увімкнути sticky CTA',
|
||||
defaultValue: true,
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Текст кнопки',
|
||||
defaultValue: 'Купити квиток',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.enabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Посилання',
|
||||
defaultValue: '/payments',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.enabled,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'gtmEvent',
|
||||
type: 'text',
|
||||
label: 'GTM подія',
|
||||
defaultValue: 'landing_sticky_cta_click',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.enabled,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'conversionGoal',
|
||||
type: 'select',
|
||||
label: 'Ціль конверсії',
|
||||
defaultValue: 'ticket_purchase',
|
||||
options: [
|
||||
{ label: 'Купівля квитка', value: 'ticket_purchase' },
|
||||
{ label: 'Заявка (лід)', value: 'lead_form' },
|
||||
{ label: 'Дзвінок', value: 'phone_call' },
|
||||
],
|
||||
admin: {
|
||||
description: 'Використовується для аналітики та GTM налаштувань',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
label: 'Блоки лендінгу',
|
||||
blocks: [
|
||||
HeroBlock,
|
||||
TextBlock,
|
||||
FeaturesBlock,
|
||||
LocationCardBlock,
|
||||
PricingBlock,
|
||||
GalleryBlock,
|
||||
FormBlock,
|
||||
CTABlock,
|
||||
CountdownBlock,
|
||||
BlogPreviewBlock,
|
||||
MapBlock,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'seo',
|
||||
type: 'group',
|
||||
label: 'SEO',
|
||||
fields: [
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'Meta Title',
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'Meta Description',
|
||||
admin: { rows: 3 },
|
||||
},
|
||||
{
|
||||
name: 'ogImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'OG Image',
|
||||
},
|
||||
{
|
||||
name: 'noIndex',
|
||||
type: 'checkbox',
|
||||
label: 'Заборонити індексацію (noindex)',
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
189
src/collections/Leads.ts
Normal file
189
src/collections/Leads.ts
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isEditor } from '../access'
|
||||
|
||||
export const Leads: CollectionConfig = {
|
||||
slug: 'leads',
|
||||
labels: {
|
||||
singular: 'Лід',
|
||||
plural: 'Ліди',
|
||||
},
|
||||
admin: {
|
||||
group: 'CRM',
|
||||
useAsTitle: 'name',
|
||||
defaultColumns: ['name', 'phone', 'label', 'tag', 'status', 'createdAt'],
|
||||
listSearchableFields: ['name', 'phone', 'email'],
|
||||
},
|
||||
access: {
|
||||
read: isEditor,
|
||||
create: () => true, // public API can create leads
|
||||
update: isEditor,
|
||||
delete: isAdmin,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Ім\'я',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'text',
|
||||
label: 'Телефон',
|
||||
admin: {
|
||||
placeholder: '+380501234567',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: 'Email',
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'relationship',
|
||||
relationTo: 'labels',
|
||||
label: 'Мітка (джерело)',
|
||||
admin: {
|
||||
description: 'Звідки прийшов лід (мітка форми)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tag',
|
||||
type: 'select',
|
||||
label: 'Тип заявки',
|
||||
options: [
|
||||
{ label: 'День народження', value: 'birthday' },
|
||||
{ label: 'Групове відвідування', value: 'group' },
|
||||
{ label: 'Зворотний дзвінок', value: 'callback' },
|
||||
{ label: 'Загальна', value: 'generic' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
label: 'Статус',
|
||||
defaultValue: 'new',
|
||||
options: [
|
||||
{ label: 'Новий', value: 'new' },
|
||||
{ label: 'В обробці', value: 'contacted' },
|
||||
{ label: 'Конвертований', value: 'converted' },
|
||||
{ label: 'Архів', value: 'archived' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'textarea',
|
||||
label: 'Повідомлення',
|
||||
admin: {
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'guestCount',
|
||||
type: 'number',
|
||||
label: 'Кількість гостей',
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.tag === 'birthday' || siblingData?.tag === 'group',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'eventDate',
|
||||
type: 'date',
|
||||
label: 'Бажана дата',
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.tag === 'birthday' || siblingData?.tag === 'group',
|
||||
date: {
|
||||
pickerAppearance: 'dayOnly',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'organization',
|
||||
type: 'text',
|
||||
label: 'Організація',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.tag === 'group',
|
||||
placeholder: 'Школа №5, ТОВ "Компанія"',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'UTM & Analytics',
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'utmSource',
|
||||
type: 'text',
|
||||
label: 'UTM Source',
|
||||
},
|
||||
{
|
||||
name: 'utmMedium',
|
||||
type: 'text',
|
||||
label: 'UTM Medium',
|
||||
},
|
||||
{
|
||||
name: 'utmCampaign',
|
||||
type: 'text',
|
||||
label: 'UTM Campaign',
|
||||
},
|
||||
{
|
||||
name: 'utmContent',
|
||||
type: 'text',
|
||||
label: 'UTM Content',
|
||||
},
|
||||
{
|
||||
name: 'utmTerm',
|
||||
type: 'text',
|
||||
label: 'UTM Term',
|
||||
},
|
||||
{
|
||||
name: 'googleClientId',
|
||||
type: 'text',
|
||||
label: 'Google Client ID',
|
||||
admin: {
|
||||
description: 'Витягується з cookie _ga для зв\'язку з GA4',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Інтеграції',
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'twentyId',
|
||||
type: 'text',
|
||||
label: 'ID в Twenty CRM',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'binotelCallId',
|
||||
type: 'text',
|
||||
label: 'ID дзвінка в Binotel',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'notes',
|
||||
type: 'textarea',
|
||||
label: 'Внутрішні нотатки',
|
||||
admin: {
|
||||
rows: 3,
|
||||
description: 'Видимо тільки в адмінці',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
164
src/collections/Orders.ts
Normal file
164
src/collections/Orders.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isEditor } from '../access'
|
||||
|
||||
export const Orders: CollectionConfig = {
|
||||
slug: 'orders',
|
||||
labels: {
|
||||
singular: 'Замовлення',
|
||||
plural: 'Замовлення',
|
||||
},
|
||||
admin: {
|
||||
group: 'CRM',
|
||||
useAsTitle: 'orderNumber',
|
||||
defaultColumns: ['orderNumber', 'customerName', 'totalAmount', 'status', 'createdAt'],
|
||||
listSearchableFields: ['orderNumber', 'customerName', 'customerEmail', 'customerPhone'],
|
||||
},
|
||||
access: {
|
||||
read: isEditor,
|
||||
create: () => true, // API route creates orders
|
||||
update: isEditor,
|
||||
delete: isAdmin,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'orderNumber',
|
||||
type: 'text',
|
||||
label: 'Номер замовлення',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'Генерується автоматично',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
label: 'Статус',
|
||||
required: true,
|
||||
defaultValue: 'pending',
|
||||
options: [
|
||||
{ label: 'Очікує оплати', value: 'pending' },
|
||||
{ label: 'Оплачено', value: 'paid' },
|
||||
{ label: 'Скасовано', value: 'cancelled' },
|
||||
{ label: 'Повернення', value: 'refunded' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'customerName',
|
||||
type: 'text',
|
||||
label: 'Ім\'я покупця',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'customerEmail',
|
||||
type: 'email',
|
||||
label: 'Email покупця',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'customerPhone',
|
||||
type: 'text',
|
||||
label: 'Телефон покупця',
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
label: 'Позиції замовлення',
|
||||
fields: [
|
||||
{
|
||||
name: 'categoryName',
|
||||
type: 'text',
|
||||
label: 'Назва категорії',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'quantity',
|
||||
type: 'number',
|
||||
label: 'Кількість',
|
||||
required: true,
|
||||
min: 1,
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'number',
|
||||
label: 'Ціна за одиницю (грн)',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'ezyTariffId',
|
||||
type: 'text',
|
||||
label: 'ID тарифу ezy.com.ua',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'totalAmount',
|
||||
type: 'number',
|
||||
label: 'Сума замовлення (грн)',
|
||||
required: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tickets',
|
||||
type: 'relationship',
|
||||
relationTo: 'tickets',
|
||||
hasMany: true,
|
||||
label: 'Квитки',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ezyPaymentUrl',
|
||||
type: 'text',
|
||||
label: 'URL оплати (Monobank)',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'Повертається від ezy.com.ua після створення платежу',
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'UTM & Analytics',
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'utmSource',
|
||||
type: 'text',
|
||||
label: 'UTM Source',
|
||||
},
|
||||
{
|
||||
name: 'utmMedium',
|
||||
type: 'text',
|
||||
label: 'UTM Medium',
|
||||
},
|
||||
{
|
||||
name: 'utmCampaign',
|
||||
type: 'text',
|
||||
label: 'UTM Campaign',
|
||||
},
|
||||
{
|
||||
name: 'utmContent',
|
||||
type: 'text',
|
||||
label: 'UTM Content',
|
||||
},
|
||||
{
|
||||
name: 'utmTerm',
|
||||
type: 'text',
|
||||
label: 'UTM Term',
|
||||
},
|
||||
{
|
||||
name: 'googleClientId',
|
||||
type: 'text',
|
||||
label: 'Google Client ID',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
143
src/collections/Pages.ts
Normal file
143
src/collections/Pages.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isEditor, isAdminOrPublished } from '../access'
|
||||
import { HeroBlock } from '../blocks/HeroBlock'
|
||||
import { TextBlock } from '../blocks/TextBlock'
|
||||
import { FeaturesBlock } from '../blocks/FeaturesBlock'
|
||||
import { LocationCardBlock } from '../blocks/LocationCardBlock'
|
||||
import { PricingBlock } from '../blocks/PricingBlock'
|
||||
import { GalleryBlock } from '../blocks/GalleryBlock'
|
||||
import { FormBlock } from '../blocks/FormBlock'
|
||||
import { CTABlock } from '../blocks/CTABlock'
|
||||
import { CountdownBlock } from '../blocks/CountdownBlock'
|
||||
import { BlogPreviewBlock } from '../blocks/BlogPreviewBlock'
|
||||
import { MapBlock } from '../blocks/MapBlock'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
labels: {
|
||||
singular: 'Сторінка',
|
||||
plural: 'Сторінки',
|
||||
},
|
||||
admin: {
|
||||
group: 'Контент',
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'slug', '_status', 'updatedAt'],
|
||||
preview: (doc) => {
|
||||
if (doc?.slug) {
|
||||
return `${process.env.NEXT_PUBLIC_SITE_URL}/${doc.slug === 'home' ? '' : doc.slug}`
|
||||
}
|
||||
return null
|
||||
},
|
||||
},
|
||||
access: {
|
||||
read: isAdminOrPublished,
|
||||
create: isEditor,
|
||||
update: isEditor,
|
||||
delete: isAdmin,
|
||||
},
|
||||
versions: {
|
||||
drafts: {
|
||||
autosave: {
|
||||
interval: 375,
|
||||
},
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Назва сторінки',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Головна',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
label: 'URL (slug)',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
placeholder: 'home',
|
||||
description:
|
||||
'URL-адреса сторінки. Для головної — "home". Не змінюйте без потреби — це зламає посилання.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isTemplate',
|
||||
type: 'checkbox',
|
||||
label: 'Шаблонна сторінка',
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description: 'Приховати зі списку звичайних сторінок (для внутрішнього використання)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blocks',
|
||||
type: 'blocks',
|
||||
label: 'Блоки сторінки',
|
||||
blocks: [
|
||||
HeroBlock,
|
||||
TextBlock,
|
||||
FeaturesBlock,
|
||||
LocationCardBlock,
|
||||
PricingBlock,
|
||||
GalleryBlock,
|
||||
FormBlock,
|
||||
CTABlock,
|
||||
CountdownBlock,
|
||||
BlogPreviewBlock,
|
||||
MapBlock,
|
||||
],
|
||||
admin: {
|
||||
description: 'Конструктор сторінки — додавайте та впорядковуйте блоки',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'seo',
|
||||
type: 'group',
|
||||
label: 'SEO',
|
||||
admin: {
|
||||
description: 'Мета-теги для пошукових систем та соцмереж',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'Meta Title',
|
||||
admin: {
|
||||
placeholder: 'Шуміленд — Сімейний тематичний парк',
|
||||
description: 'Рекомендовано: 50–60 символів',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'Meta Description',
|
||||
admin: {
|
||||
rows: 3,
|
||||
placeholder: 'Відвідайте Шуміленд — парк розваг для всієї родини.',
|
||||
description: 'Рекомендовано: 120–160 символів',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ogImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'OG Image (для соцмереж)',
|
||||
admin: {
|
||||
description: 'Рекомендований розмір: 1200×630 px',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'noIndex',
|
||||
type: 'checkbox',
|
||||
label: 'Заборонити індексацію (noindex)',
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
93
src/collections/Tickets.ts
Normal file
93
src/collections/Tickets.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { isAdmin, isEditor } from '../access'
|
||||
|
||||
export const Tickets: CollectionConfig = {
|
||||
slug: 'tickets',
|
||||
labels: {
|
||||
singular: 'Квиток',
|
||||
plural: 'Квитки',
|
||||
},
|
||||
admin: {
|
||||
group: 'CRM',
|
||||
useAsTitle: 'ticketCode',
|
||||
defaultColumns: ['ticketCode', 'categoryName', 'order', 'isUsed', 'createdAt'],
|
||||
listSearchableFields: ['ticketCode'],
|
||||
},
|
||||
access: {
|
||||
read: isEditor,
|
||||
create: () => true, // webhook creates tickets
|
||||
update: isEditor,
|
||||
delete: isAdmin,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'ticketCode',
|
||||
type: 'text',
|
||||
label: 'Код квитка',
|
||||
required: true,
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'Формат: SL-YYYYMMDD-XXXXXX (генерується автоматично)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'relationship',
|
||||
relationTo: 'orders',
|
||||
label: 'Замовлення',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'categoryName',
|
||||
type: 'text',
|
||||
label: 'Категорія квитка',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Динопарк — дорослий',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'number',
|
||||
label: 'Ціна (грн)',
|
||||
},
|
||||
{
|
||||
name: 'qrCodeUrl',
|
||||
type: 'text',
|
||||
label: 'URL QR-коду',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'Генерується автоматично після оплати',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isUsed',
|
||||
type: 'checkbox',
|
||||
label: 'Використаний',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: 'usedAt',
|
||||
type: 'date',
|
||||
label: 'Час використання',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'usedByEmployee',
|
||||
type: 'relationship',
|
||||
relationTo: 'users',
|
||||
label: 'Хто перевірив',
|
||||
admin: {
|
||||
readOnly: true,
|
||||
description: 'Співробітник, який відсканував квиток',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
185
src/globals/Navigation.ts
Normal file
185
src/globals/Navigation.ts
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { isAdmin, isEditor } from '../access'
|
||||
|
||||
export const Navigation: GlobalConfig = {
|
||||
slug: 'navigation',
|
||||
label: 'Навігація',
|
||||
admin: {
|
||||
group: 'Налаштування',
|
||||
description: 'Меню сайту: header та footer',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
update: isEditor,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'headerMenu',
|
||||
type: 'array',
|
||||
label: 'Головне меню (header)',
|
||||
maxRows: 10,
|
||||
admin: {
|
||||
description: 'Пункти меню у шапці сайту',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Назва пункту',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Локації',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Посилання',
|
||||
admin: {
|
||||
placeholder: '/locations',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'hasDropdown',
|
||||
type: 'checkbox',
|
||||
label: 'Має підменю',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: 'dropdown',
|
||||
type: 'array',
|
||||
label: 'Підменю',
|
||||
maxRows: 8,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.hasDropdown,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Назва',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Динопарк',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Посилання',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: '/dinopark',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
label: 'Короткий опис (необов\'язково)',
|
||||
admin: {
|
||||
placeholder: '23 динозаври у натуральну величину',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'headerCta',
|
||||
type: 'group',
|
||||
label: 'Кнопка в шапці',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Текст кнопки',
|
||||
defaultValue: 'Купити квиток',
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'Посилання',
|
||||
defaultValue: '/payments',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'footerColumns',
|
||||
type: 'array',
|
||||
label: 'Колонки футера',
|
||||
maxRows: 5,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Заголовок колонки',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Локації',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'links',
|
||||
type: 'array',
|
||||
label: 'Посилання',
|
||||
maxRows: 10,
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Назва',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Динопарк',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'URL',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: '/dinopark',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isExternal',
|
||||
type: 'checkbox',
|
||||
label: 'Відкривати в новій вкладці',
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'footerBottomLinks',
|
||||
type: 'array',
|
||||
label: 'Посилання в нижній частині футера',
|
||||
maxRows: 5,
|
||||
admin: {
|
||||
description: 'Наприклад: Політика конфіденційності, Публічна оферта',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Назва',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Політика конфіденційності',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'URL',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: '/privacy',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
255
src/globals/SiteSettings.ts
Normal file
255
src/globals/SiteSettings.ts
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { isAdmin } from '../access'
|
||||
|
||||
export const SiteSettings: GlobalConfig = {
|
||||
slug: 'site-settings',
|
||||
label: 'Налаштування сайту',
|
||||
admin: {
|
||||
group: 'Налаштування',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
update: isAdmin,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
type: 'tabs',
|
||||
tabs: [
|
||||
{
|
||||
label: 'Загальне',
|
||||
fields: [
|
||||
{
|
||||
name: 'siteName',
|
||||
type: 'text',
|
||||
label: 'Назва сайту',
|
||||
defaultValue: 'Шуміленд',
|
||||
},
|
||||
{
|
||||
name: 'logo',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Логотип',
|
||||
},
|
||||
{
|
||||
name: 'favicon',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Favicon',
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'text',
|
||||
label: 'Телефон',
|
||||
admin: {
|
||||
placeholder: '+38 (050) 123 45 67',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: 'Email',
|
||||
admin: {
|
||||
placeholder: 'info@shumiland.com.ua',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
type: 'textarea',
|
||||
label: 'Адреса',
|
||||
admin: {
|
||||
rows: 2,
|
||||
placeholder: 'с. Малютянка, Бучанський район, Київська область',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'workingHours',
|
||||
type: 'array',
|
||||
label: 'Графік роботи',
|
||||
maxRows: 7,
|
||||
fields: [
|
||||
{
|
||||
name: 'days',
|
||||
type: 'text',
|
||||
label: 'Дні',
|
||||
required: true,
|
||||
admin: { placeholder: 'Пн–Пт' },
|
||||
},
|
||||
{
|
||||
name: 'hours',
|
||||
type: 'text',
|
||||
label: 'Години',
|
||||
required: true,
|
||||
admin: { placeholder: '10:00–20:00' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Соцмережі',
|
||||
fields: [
|
||||
{
|
||||
name: 'socialLinks',
|
||||
type: 'array',
|
||||
label: 'Посилання на соцмережі',
|
||||
maxRows: 8,
|
||||
fields: [
|
||||
{
|
||||
name: 'platform',
|
||||
type: 'select',
|
||||
label: 'Платформа',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: 'Instagram', value: 'instagram' },
|
||||
{ label: 'Facebook', value: 'facebook' },
|
||||
{ label: 'YouTube', value: 'youtube' },
|
||||
{ label: 'TikTok', value: 'tiktok' },
|
||||
{ label: 'Telegram', value: 'telegram' },
|
||||
{ label: 'Viber', value: 'viber' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'URL',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'https://instagram.com/shumiland',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Підпис (необов\'язково)',
|
||||
admin: {
|
||||
placeholder: '@shumiland',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Аналітика',
|
||||
fields: [
|
||||
{
|
||||
name: 'gtmId',
|
||||
type: 'text',
|
||||
label: 'Google Tag Manager ID',
|
||||
admin: {
|
||||
placeholder: 'GTM-KJTSVLBC',
|
||||
description: 'Формат: GTM-XXXXXXX',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ga4MeasurementId',
|
||||
type: 'text',
|
||||
label: 'GA4 Measurement ID',
|
||||
admin: {
|
||||
placeholder: 'G-XXXXXXXXXX',
|
||||
description: 'Формат: G-XXXXXXXXXX',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'umamiWebsiteId',
|
||||
type: 'text',
|
||||
label: 'Umami Website ID',
|
||||
admin: {
|
||||
placeholder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'umamiScriptUrl',
|
||||
type: 'text',
|
||||
label: 'Umami Script URL',
|
||||
admin: {
|
||||
placeholder: 'https://analytics.shumiland.com.ua/script.js',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Binotel',
|
||||
fields: [
|
||||
{
|
||||
name: 'binotelWidgetHash',
|
||||
type: 'text',
|
||||
label: 'Binotel Widget Hash',
|
||||
admin: {
|
||||
placeholder: 'fgfz5owkoc9rxip2brp2',
|
||||
description: 'Хеш для підключення віджету зворотного дзвінка',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'binotelEnabled',
|
||||
type: 'checkbox',
|
||||
label: 'Увімкнути Binotel віджет',
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'SEO',
|
||||
fields: [
|
||||
{
|
||||
name: 'defaultMetaTitle',
|
||||
type: 'text',
|
||||
label: 'Meta Title (за замовчуванням)',
|
||||
admin: {
|
||||
placeholder: 'Шуміленд — Сімейний тематичний парк',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'defaultMetaDescription',
|
||||
type: 'textarea',
|
||||
label: 'Meta Description (за замовчуванням)',
|
||||
admin: {
|
||||
rows: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'defaultOgImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'OG Image (за замовчуванням)',
|
||||
},
|
||||
{
|
||||
name: 'robotsTxt',
|
||||
type: 'textarea',
|
||||
label: 'robots.txt',
|
||||
admin: {
|
||||
rows: 6,
|
||||
placeholder: 'User-agent: *\nAllow: /\nSitemap: https://shumiland.com.ua/sitemap.xml',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Системне',
|
||||
fields: [
|
||||
{
|
||||
name: 'maintenanceMode',
|
||||
type: 'checkbox',
|
||||
label: 'Режим обслуговування',
|
||||
defaultValue: false,
|
||||
admin: {
|
||||
description:
|
||||
'Якщо увімкнено — всі відвідувачі (крім адмінів) бачать сторінку "Зараз ведуться роботи"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'maintenanceMessage',
|
||||
type: 'textarea',
|
||||
label: 'Повідомлення на сторінці обслуговування',
|
||||
defaultValue: 'Ведуться технічні роботи. Повернемось дуже скоро!',
|
||||
admin: {
|
||||
rows: 3,
|
||||
condition: (data) => data?.maintenanceMode,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
137
src/globals/TicketsConfig.ts
Normal file
137
src/globals/TicketsConfig.ts
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { isAdmin, isEditor } from '../access'
|
||||
|
||||
export const TicketsConfig: GlobalConfig = {
|
||||
slug: 'tickets-config',
|
||||
label: 'Налаштування квитків',
|
||||
admin: {
|
||||
group: 'Налаштування',
|
||||
description: 'Категорії квитків, ціни та умови продажу',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
update: isEditor,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'categories',
|
||||
type: 'array',
|
||||
label: 'Категорії квитків',
|
||||
admin: {
|
||||
description:
|
||||
'Налаштуйте категорії квитків. Вони відображаються на сторінці /payments та в PricingBlock.',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'categoryId',
|
||||
type: 'text',
|
||||
label: 'ID тарифу (ezy.com.ua)',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: '3120',
|
||||
description: 'Числовий ID тарифу з ezy.com.ua',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Назва категорії',
|
||||
required: true,
|
||||
admin: {
|
||||
placeholder: 'Динопарк — дорослий',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
label: 'Короткий опис',
|
||||
admin: {
|
||||
placeholder: 'Від 14 років',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'number',
|
||||
label: 'Ціна (грн)',
|
||||
required: true,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: 'location',
|
||||
type: 'select',
|
||||
label: 'Локація',
|
||||
required: true,
|
||||
options: [
|
||||
{ label: 'Динопарк', value: 'dinopark' },
|
||||
{ label: 'ДивоЛіс', value: 'dyvo-lis' },
|
||||
{ label: 'Лабіринт', value: 'labiryn' },
|
||||
{ label: 'Комбо', value: 'combo' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ageGroup',
|
||||
type: 'select',
|
||||
label: 'Вікова група',
|
||||
options: [
|
||||
{ label: 'Дорослий (14+)', value: 'adult' },
|
||||
{ label: 'Дитина (3–13 р)', value: 'child' },
|
||||
{ label: 'Пенсіонер', value: 'senior' },
|
||||
{ label: 'Без вікового обмеження', value: 'any' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'isFree',
|
||||
type: 'checkbox',
|
||||
label: 'Безкоштовно',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: 'freeCondition',
|
||||
type: 'text',
|
||||
label: 'Умова безкоштовного входу',
|
||||
admin: {
|
||||
placeholder: 'Діти до 3 років',
|
||||
condition: (_, siblingData) => siblingData?.isFree,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
label: 'Активна категорія',
|
||||
defaultValue: true,
|
||||
admin: {
|
||||
description: 'Неактивні категорії не відображаються на сайті',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'number',
|
||||
label: 'Порядок відображення',
|
||||
defaultValue: 0,
|
||||
admin: {
|
||||
description: 'Менше число — вище в списку',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'freeCategoriesNote',
|
||||
type: 'textarea',
|
||||
label: 'Примітка про безкоштовний вхід',
|
||||
admin: {
|
||||
rows: 4,
|
||||
placeholder:
|
||||
'Безкоштовний вхід: діти до 3 років, діти з інвалідністю (при наявності довідки), учасники бойових дій (УБД) при пред\'явленні посвідчення.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'generalNote',
|
||||
type: 'textarea',
|
||||
label: 'Загальна примітка (внизу списку цін)',
|
||||
admin: {
|
||||
rows: 3,
|
||||
placeholder: '*Знижка діє лише на квитки повної вартості на окремі зони',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -4,8 +4,32 @@ import path from 'path'
|
|||
import { buildConfig } from 'payload'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import Users from '@/collections/Users'
|
||||
import Media from '@/collections/Media'
|
||||
import Users from './collections/Users'
|
||||
import Media from './collections/Media'
|
||||
import Labels from './collections/Labels'
|
||||
import { Pages } from './collections/Pages'
|
||||
import { LandingPages } from './collections/LandingPages'
|
||||
import { Blog } from './collections/Blog'
|
||||
import { Events } from './collections/Events'
|
||||
import { Leads } from './collections/Leads'
|
||||
import { Orders } from './collections/Orders'
|
||||
import { Tickets } from './collections/Tickets'
|
||||
|
||||
import { SiteSettings } from './globals/SiteSettings'
|
||||
import { TicketsConfig } from './globals/TicketsConfig'
|
||||
import { Navigation } from './globals/Navigation'
|
||||
|
||||
import { HeroBlock } from './blocks/HeroBlock'
|
||||
import { TextBlock } from './blocks/TextBlock'
|
||||
import { FeaturesBlock } from './blocks/FeaturesBlock'
|
||||
import { LocationCardBlock } from './blocks/LocationCardBlock'
|
||||
import { PricingBlock } from './blocks/PricingBlock'
|
||||
import { GalleryBlock } from './blocks/GalleryBlock'
|
||||
import { FormBlock } from './blocks/FormBlock'
|
||||
import { CTABlock } from './blocks/CTABlock'
|
||||
import { CountdownBlock } from './blocks/CountdownBlock'
|
||||
import { BlogPreviewBlock } from './blocks/BlogPreviewBlock'
|
||||
import { MapBlock } from './blocks/MapBlock'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
|
@ -23,8 +47,20 @@ export default buildConfig({
|
|||
collections: [
|
||||
Users,
|
||||
Media,
|
||||
Labels,
|
||||
Pages,
|
||||
LandingPages,
|
||||
Blog,
|
||||
Events,
|
||||
Leads,
|
||||
Orders,
|
||||
Tickets,
|
||||
],
|
||||
globals: [
|
||||
SiteSettings,
|
||||
TicketsConfig,
|
||||
Navigation,
|
||||
],
|
||||
globals: [],
|
||||
editor: lexicalEditor(),
|
||||
secret: process.env.PAYLOAD_SECRET || 'shumiland-dev-secret-change-in-prod',
|
||||
typescript: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue