feat(cms): full CMS control — remove hardcoded content, add globals and SEO meta
- New BirthdayPage global: hero, form titles, meta SEO for /dni-narodzhennia - New TicketsPage global: hero, section titles, meta SEO for /kvytky - SiteSettings: add tariffCategoryLabels array (key→label mapping for ezy API categories) - DyvoLisPage + GroupVisitsPage: add metaTitle/metaDescription + revalidate hook - /kvytky: fetch birthday packages from CMS, groups from CMS, category labels from SiteSettings - /grupovi-vidviduvannia: remove DEFAULT_GROUPS fallback, CMS-driven meta - /dni-narodzhennia: connect to BirthdayPage global (hero, form titles, meta) - /lokatsii/dyvolis: use CMS meta description from DyvoLisPage global - pnpm override tsx→4.22.0 for Node.js v26 compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2a535671b6
commit
2ec8393de9
13 changed files with 421 additions and 215 deletions
|
|
@ -83,9 +83,15 @@
|
|||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"supertest": "^7.1.0",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"tsx": "^4.21.0",
|
||||
"tsx": "^4.22.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vite-tsconfig-paths": "^6.1.1",
|
||||
"vitest": "^4.1.6"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"tsx": "4.22.0",
|
||||
"payload>tsx": "4.22.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import { Footer } from './src/globals/Footer'
|
|||
import { SiteSettings } from './src/globals/SiteSettings'
|
||||
import { DyvoLisPage } from './src/globals/DyvoLisPage'
|
||||
import { GroupVisitsPage } from './src/globals/GroupVisitsPage'
|
||||
import { BirthdayPage } from './src/globals/BirthdayPage'
|
||||
import { TicketsPage } from './src/globals/TicketsPage'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
|
@ -78,6 +80,8 @@ export default buildConfig({
|
|||
SiteSettings,
|
||||
DyvoLisPage,
|
||||
GroupVisitsPage,
|
||||
BirthdayPage,
|
||||
TicketsPage,
|
||||
],
|
||||
|
||||
plugins: [
|
||||
|
|
@ -107,6 +111,8 @@ export default buildConfig({
|
|||
'site-settings',
|
||||
'dyvolis-page',
|
||||
'group-visits-page',
|
||||
'birthday-page',
|
||||
'tickets-page',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
45
pnpm-lock.yaml
generated
45
pnpm-lock.yaml
generated
|
|
@ -113,7 +113,7 @@ importers:
|
|||
version: 8.59.3(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4))
|
||||
version: 6.0.1(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4))
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.6(vitest@4.1.6)
|
||||
|
|
@ -154,17 +154,17 @@ importers:
|
|||
specifier: ^4.3.0
|
||||
version: 4.3.0
|
||||
tsx:
|
||||
specifier: ^4.21.0
|
||||
version: 4.21.0
|
||||
specifier: ^4.22.0
|
||||
version: 4.22.0
|
||||
typescript:
|
||||
specifier: ^6.0.3
|
||||
version: 6.0.3
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^6.1.1
|
||||
version: 6.1.1(typescript@6.0.3)(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4))
|
||||
version: 6.1.1(typescript@6.0.3)(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4))
|
||||
vitest:
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4))
|
||||
version: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4))
|
||||
|
||||
packages:
|
||||
|
||||
|
|
@ -5306,6 +5306,11 @@ packages:
|
|||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
tsx@4.22.0:
|
||||
resolution: {integrity: sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
type-check@0.4.0:
|
||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -7760,10 +7765,10 @@ snapshots:
|
|||
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
||||
optional: true
|
||||
|
||||
'@vitejs/plugin-react@6.0.1(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4))':
|
||||
'@vitejs/plugin-react@6.0.1(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-rc.7
|
||||
vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4)
|
||||
vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4)
|
||||
|
||||
'@vitest/coverage-v8@4.1.6(vitest@4.1.6)':
|
||||
dependencies:
|
||||
|
|
@ -7777,7 +7782,7 @@ snapshots:
|
|||
obug: 2.1.1
|
||||
std-env: 4.1.0
|
||||
tinyrainbow: 3.1.0
|
||||
vitest: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4))
|
||||
vitest: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4))
|
||||
|
||||
'@vitest/expect@4.1.6':
|
||||
dependencies:
|
||||
|
|
@ -7788,13 +7793,13 @@ snapshots:
|
|||
chai: 6.2.2
|
||||
tinyrainbow: 3.1.0
|
||||
|
||||
'@vitest/mocker@4.1.6(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4))':
|
||||
'@vitest/mocker@4.1.6(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4))':
|
||||
dependencies:
|
||||
'@vitest/spy': 4.1.6
|
||||
estree-walker: 3.0.3
|
||||
magic-string: 0.30.21
|
||||
optionalDependencies:
|
||||
vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4)
|
||||
vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4)
|
||||
|
||||
'@vitest/pretty-format@4.1.6':
|
||||
dependencies:
|
||||
|
|
@ -11148,6 +11153,12 @@ snapshots:
|
|||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
tsx@4.22.0:
|
||||
dependencies:
|
||||
esbuild: 0.28.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
type-check@0.4.0:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
|
@ -11314,17 +11325,17 @@ snapshots:
|
|||
'@types/unist': 3.0.3
|
||||
unist-util-stringify-position: 4.0.0
|
||||
|
||||
vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4)):
|
||||
vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4)):
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
globrex: 0.1.2
|
||||
tsconfck: 3.1.6(typescript@6.0.3)
|
||||
vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4)
|
||||
vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4):
|
||||
vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4):
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
|
|
@ -11338,13 +11349,13 @@ snapshots:
|
|||
jiti: 2.7.0
|
||||
lightningcss: 1.32.0
|
||||
sass: 1.77.4
|
||||
tsx: 4.21.0
|
||||
tsx: 4.22.0
|
||||
yaml: 2.8.4
|
||||
|
||||
vitest@4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4)):
|
||||
vitest@4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(happy-dom@20.9.0)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4)):
|
||||
dependencies:
|
||||
'@vitest/expect': 4.1.6
|
||||
'@vitest/mocker': 4.1.6(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4))
|
||||
'@vitest/mocker': 4.1.6(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4))
|
||||
'@vitest/pretty-format': 4.1.6
|
||||
'@vitest/runner': 4.1.6
|
||||
'@vitest/snapshot': 4.1.6
|
||||
|
|
@ -11361,7 +11372,7 @@ snapshots:
|
|||
tinyexec: 1.1.2
|
||||
tinyglobby: 0.2.16
|
||||
tinyrainbow: 3.1.0
|
||||
vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.21.0)(yaml@2.8.4)
|
||||
vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sass@1.77.4)(tsx@4.22.0)(yaml@2.8.4)
|
||||
why-is-node-running: 2.3.0
|
||||
optionalDependencies:
|
||||
'@types/node': 25.7.0
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@ import configPromise from '@payload-config'
|
|||
import { PageHero } from '@/components/ui/PageHero'
|
||||
import { BirthdayBookingForm } from '@/components/forms/BirthdayBookingForm'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Дні народження — Шуміленд',
|
||||
description:
|
||||
'Святкуйте день народження у Шуміленді! Пакети для дітей та дорослих з розвагами, аніматорами та кейтерингом.',
|
||||
}
|
||||
|
||||
export const revalidate = 60
|
||||
|
||||
async function getBirthdayPageData() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
return await payload.findGlobal({ slug: 'birthday-page', depth: 0 })
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function getPackages() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
|
@ -30,6 +33,16 @@ function formatPrice(price: number): string {
|
|||
return price.toLocaleString('uk-UA').replace(/,/g, ' ')
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const pageData = await getBirthdayPageData()
|
||||
return {
|
||||
title: pageData?.metaTitle ?? 'Дні народження — Шуміленд',
|
||||
description:
|
||||
pageData?.metaDescription ??
|
||||
'Святкуйте день народження у Шуміленді! Пакети для дітей та дорослих з розвагами, аніматорами та кейтерингом.',
|
||||
}
|
||||
}
|
||||
|
||||
export default async function BirthdayPage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
|
@ -37,13 +50,16 @@ export default async function BirthdayPage({
|
|||
}) {
|
||||
const params = await searchParams
|
||||
const defaultPackage = params.package
|
||||
const packages = await getPackages()
|
||||
const [pageData, packages] = await Promise.all([getBirthdayPageData(), getPackages()])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#f1fbeb]">
|
||||
<PageHero
|
||||
title="Дні народження"
|
||||
subtitle="Зробіть свято незабутнім! Оберіть пакет і наші менеджери зв'яжуться з вами для уточнення деталей."
|
||||
title={pageData?.heroTitle ?? 'Дні народження'}
|
||||
subtitle={
|
||||
pageData?.heroSubtitle ??
|
||||
"Зробіть свято незабутнім! Оберіть пакет і наші менеджери зв'яжуться з вами для уточнення деталей."
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="mx-auto max-w-[1204px] px-8 py-16">
|
||||
|
|
@ -100,13 +116,14 @@ export default async function BirthdayPage({
|
|||
className="mb-2 text-[28px] font-bold text-white"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
Замовити святкування
|
||||
{pageData?.formTitle ?? 'Замовити святкування'}
|
||||
</h2>
|
||||
<p
|
||||
className="mb-8 text-[15px] text-white/70"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
Залиште заявку і наш менеджер зв'яжеться з вами протягом 30 хвилин
|
||||
{pageData?.formSubtitle ??
|
||||
"Залиште заявку і наш менеджер зв'яжеться з вами протягом 30 хвилин"}
|
||||
</p>
|
||||
<BirthdayBookingForm defaultPackage={defaultPackage} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@ import { GroupRequestForm } from '@/components/forms/GroupRequestForm'
|
|||
|
||||
export const revalidate = 60
|
||||
|
||||
interface Group {
|
||||
icon?: string | null
|
||||
title: string
|
||||
description: string
|
||||
minPeople: string
|
||||
discount: string
|
||||
}
|
||||
|
||||
async function getGroupVisitsData() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
|
|
@ -16,40 +24,15 @@ async function getGroupVisitsData() {
|
|||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const data = await getGroupVisitsData()
|
||||
return {
|
||||
title: 'Групові відвідування — Шуміленд',
|
||||
title: data?.metaTitle ?? 'Групові відвідування — Шуміленд',
|
||||
description:
|
||||
data?.metaDescription ??
|
||||
'Організуйте групове відвідування Шуміленду. Спеціальні ціни для шкіл, дитячих садків і корпоративних груп.',
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_GROUPS = [
|
||||
{
|
||||
title: 'Шкільні екскурсії',
|
||||
description:
|
||||
'Пізнавальні екскурсії для учнів початкової та середньої школи. Екскурсовод, адаптована програма, безпечний маршрут.',
|
||||
minPeople: '15 осіб',
|
||||
discount: '15%',
|
||||
icon: '🏫',
|
||||
},
|
||||
{
|
||||
title: 'Дитячі садки',
|
||||
description:
|
||||
'Програма для наймолодших — безпечний формат, розвивальні активності, відповідальний супровід.',
|
||||
minPeople: '10 осіб',
|
||||
discount: '20%',
|
||||
icon: '🎒',
|
||||
},
|
||||
{
|
||||
title: 'Корпоративи',
|
||||
description:
|
||||
'Тімбілдинг та корпоративний відпочинок у форматі парку розваг. Ексклюзивні зони, кейтеринг, програма на замовлення.',
|
||||
minPeople: '20 осіб',
|
||||
discount: '10%',
|
||||
icon: '🏢',
|
||||
},
|
||||
]
|
||||
|
||||
export default async function GroupVisitsPage() {
|
||||
const data = await getGroupVisitsData()
|
||||
|
||||
|
|
@ -61,46 +44,47 @@ export default async function GroupVisitsPage() {
|
|||
const formSubtitle =
|
||||
data?.formSubtitle ??
|
||||
'Вкажіть кількість учасників та бажану дату — менеджер зателефонує і погодить деталі.'
|
||||
const groups =
|
||||
data?.groups && data.groups.length > 0 ? (data.groups as typeof DEFAULT_GROUPS) : DEFAULT_GROUPS
|
||||
const groups = (data?.groups ?? []) as Group[]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#f1fbeb]">
|
||||
<PageHero title={heroTitle} subtitle={heroSubtitle} />
|
||||
|
||||
<div className="mx-auto max-w-[1204px] px-8 py-16">
|
||||
<div className="mb-16 grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
{groups.map((g) => (
|
||||
<div
|
||||
key={g.title}
|
||||
className="flex flex-col gap-4 rounded-[24px] bg-[#396817] p-8 shadow-[0_4px_60px_0_rgba(57,104,23,0.15)]"
|
||||
>
|
||||
<span className="text-[40px]">{g.icon}</span>
|
||||
<h2
|
||||
className="text-[22px] font-bold text-white"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
{groups.length > 0 && (
|
||||
<div className="mb-16 grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
{groups.map((g) => (
|
||||
<div
|
||||
key={g.title}
|
||||
className="flex flex-col gap-4 rounded-[24px] bg-[#396817] p-8 shadow-[0_4px_60px_0_rgba(57,104,23,0.15)]"
|
||||
>
|
||||
{g.title}
|
||||
</h2>
|
||||
<p
|
||||
className="text-[14px] leading-relaxed text-white/70"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{g.description}
|
||||
</p>
|
||||
<div className="mt-auto flex gap-4 border-t border-white/10 pt-4">
|
||||
<div>
|
||||
<p className="text-[12px] text-white/50">Від</p>
|
||||
<p className="text-[16px] font-bold text-white">{g.minPeople}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[12px] text-white/50">Знижка</p>
|
||||
<p className="text-[16px] font-bold text-[#f28b4a]">{g.discount}</p>
|
||||
<span className="text-[40px]">{g.icon}</span>
|
||||
<h2
|
||||
className="text-[22px] font-bold text-white"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{g.title}
|
||||
</h2>
|
||||
<p
|
||||
className="text-[14px] leading-relaxed text-white/70"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{g.description}
|
||||
</p>
|
||||
<div className="mt-auto flex gap-4 border-t border-white/10 pt-4">
|
||||
<div>
|
||||
<p className="text-[12px] text-white/50">Від</p>
|
||||
<p className="text-[16px] font-bold text-white">{g.minPeople}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[12px] text-white/50">Знижка</p>
|
||||
<p className="text-[16px] font-bold text-[#f28b4a]">{g.discount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div id="order-form" className="rounded-[24px] bg-[#396817] p-10">
|
||||
<h2
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
import type { Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { getPayload } from 'payload'
|
||||
import configPromise from '@payload-config'
|
||||
import { PageHero } from '@/components/ui/PageHero'
|
||||
import { TariffCardClient } from '@/components/ui/TariffCardClient'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Купити квиток — Шуміленд',
|
||||
description: 'Придбайте квитки до Шуміленду онлайн. Вхідні квитки на всі зони парку.',
|
||||
}
|
||||
import { getSiteSettings } from '@/lib/getSiteSettings'
|
||||
|
||||
export const revalidate = 60
|
||||
|
||||
interface Tariff {
|
||||
id: number
|
||||
name: string
|
||||
price: number
|
||||
categoryTag: string
|
||||
sort: number
|
||||
icon?: string | null
|
||||
stale?: boolean
|
||||
}
|
||||
|
||||
async function getTariffs() {
|
||||
try {
|
||||
const baseUrl = process.env['NEXT_PUBLIC_SITE_URL'] ?? 'http://localhost:3000'
|
||||
|
|
@ -21,30 +29,60 @@ async function getTariffs() {
|
|||
}
|
||||
}
|
||||
|
||||
interface Tariff {
|
||||
id: number
|
||||
name: string
|
||||
price: number
|
||||
categoryTag: string
|
||||
sort: number
|
||||
icon?: string | null
|
||||
stale?: boolean
|
||||
async function getPageData() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
return await payload.findGlobal({ slug: 'tickets-page', depth: 0 })
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const CATEGORY_LABELS: Record<string, string> = {
|
||||
dyno: 'ДиноПарк',
|
||||
dyvolis: 'Диво Ліс',
|
||||
maze: 'Дзеркальний Лабіринт',
|
||||
combo: 'Комбо',
|
||||
family: 'Сімейний',
|
||||
async function getBirthdayPackages() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
const result = await payload.find({
|
||||
collection: 'birthday-packages',
|
||||
sort: 'sort',
|
||||
limit: 10,
|
||||
})
|
||||
return result.docs
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async function getGroupVisitsData() {
|
||||
try {
|
||||
const payload = await getPayload({ config: configPromise })
|
||||
return await payload.findGlobal({ slug: 'group-visits-page', depth: 0 })
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const pageData = await getPageData()
|
||||
return {
|
||||
title: pageData?.metaTitle ?? 'Купити квиток — Шуміленд',
|
||||
description:
|
||||
pageData?.metaDescription ??
|
||||
'Придбайте квитки до Шуміленду онлайн. Вхідні квитки на всі зони парку.',
|
||||
}
|
||||
}
|
||||
|
||||
export default async function TicketsPage() {
|
||||
const data = await getTariffs()
|
||||
const tariffs = data?.tariffs ?? []
|
||||
const hasWarning = !!data?.warning
|
||||
const [tariffData, pageData, birthdayPackages, groupVisitsData, siteSettings] = await Promise.all(
|
||||
[getTariffs(), getPageData(), getBirthdayPackages(), getGroupVisitsData(), getSiteSettings()]
|
||||
)
|
||||
|
||||
const tariffs = tariffData?.tariffs ?? []
|
||||
const hasWarning = !!tariffData?.warning
|
||||
|
||||
const categoryLabelsMap = Object.fromEntries(
|
||||
(siteSettings.tariffCategoryLabels ?? []).map(({ key, label }) => [key, label])
|
||||
)
|
||||
|
||||
// Group by category
|
||||
const grouped = tariffs.reduce<Record<string, Tariff[]>>((acc, t) => {
|
||||
const key = t.categoryTag ?? 'other'
|
||||
acc[key] ??= []
|
||||
|
|
@ -57,8 +95,10 @@ export default async function TicketsPage() {
|
|||
return (
|
||||
<div className="min-h-screen bg-[#f1fbeb]">
|
||||
<PageHero
|
||||
title="Купити квиток"
|
||||
subtitle="Оберіть квиток та придбайте онлайн — без черги на касі"
|
||||
title={pageData?.heroTitle ?? 'Купити квиток'}
|
||||
subtitle={
|
||||
pageData?.heroSubtitle ?? 'Оберіть квиток та придбайте онлайн — без черги на касі'
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="mx-auto max-w-[1204px] px-8 py-16">
|
||||
|
|
@ -89,7 +129,7 @@ export default async function TicketsPage() {
|
|||
className="mb-6 text-[24px] font-bold text-[#272727] uppercase"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{CATEGORY_LABELS[category] ?? category}
|
||||
{categoryLabelsMap[category] ?? category}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{items.map((tariff) => (
|
||||
|
|
@ -107,111 +147,99 @@ export default async function TicketsPage() {
|
|||
))
|
||||
)}
|
||||
|
||||
{/* Birthday packages */}
|
||||
<div>
|
||||
<h2 className="mb-6 text-[24px] font-bold text-[#272727] uppercase" style={FONT_MONT}>
|
||||
Дні народження
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
name: 'Стандарт',
|
||||
desc: 'До 10 дітей · Аніматор 2 год · Торт від закладу',
|
||||
href: '/dni-narodzhennia#order-form',
|
||||
},
|
||||
{
|
||||
name: 'Преміум',
|
||||
desc: 'До 20 дітей · Аніматор 3 год · Декор + фотограф',
|
||||
href: '/dni-narodzhennia#order-form',
|
||||
},
|
||||
{
|
||||
name: 'VIP',
|
||||
desc: 'До 40 гостей · Аніматор 4 год · Повне меню + відео',
|
||||
href: '/dni-narodzhennia#order-form',
|
||||
},
|
||||
].map((pkg) => (
|
||||
<div
|
||||
key={pkg.name}
|
||||
className="flex flex-col gap-4 rounded-[20px] bg-[#396817] p-8 shadow-[0_4px_60px_0_rgba(242,139,74,0.25)]"
|
||||
>
|
||||
<div>
|
||||
<h3
|
||||
className="text-[20px] leading-tight font-bold text-white"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pkg.name}
|
||||
</h3>
|
||||
<p className="mt-2 text-[14px] leading-relaxed text-white/70" style={FONT_MONT}>
|
||||
{pkg.desc}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={pkg.href}
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-[10px] text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg,#f28b4a 0%,#fdcf54 55%,#f28b4a 100%)',
|
||||
}}
|
||||
{birthdayPackages.length > 0 && (
|
||||
<div>
|
||||
<h2 className="mb-6 text-[24px] font-bold text-[#272727] uppercase" style={FONT_MONT}>
|
||||
{pageData?.sectionTitleBirthday ?? 'Дні народження'}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{birthdayPackages.map((pkg) => (
|
||||
<div
|
||||
key={pkg.id}
|
||||
className="flex flex-col gap-4 rounded-[20px] bg-[#396817] p-8 shadow-[0_4px_60px_0_rgba(242,139,74,0.25)]"
|
||||
>
|
||||
Дізнатися ціну
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<h3
|
||||
className="text-[20px] leading-tight font-bold text-white"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pkg.name}
|
||||
</h3>
|
||||
{pkg.features && pkg.features.length > 0 && (
|
||||
<p
|
||||
className="mt-2 text-[14px] leading-relaxed text-white/70"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{pkg.features
|
||||
.slice(0, 3)
|
||||
.map((f: { text: string }) => f.text)
|
||||
.join(' · ')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<Link
|
||||
href={pkg.ctaHref ?? '/dni-narodzhennia#order-form'}
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-[10px] text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg,#f28b4a 0%,#fdcf54 55%,#f28b4a 100%)',
|
||||
}}
|
||||
>
|
||||
{pkg.ctaLabel ?? 'Дізнатися ціну'}
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Group visits */}
|
||||
<div>
|
||||
<h2 className="mb-6 text-[24px] font-bold text-[#272727] uppercase" style={FONT_MONT}>
|
||||
Групові відвідування
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{[
|
||||
{
|
||||
name: 'Шкільні екскурсії',
|
||||
desc: 'Від 15 осіб · Знижка 15% · Екскурсовод',
|
||||
href: '/grupovi-vidviduvannia#order-form',
|
||||
},
|
||||
{
|
||||
name: 'Дитячі садки',
|
||||
desc: 'Від 10 осіб · Знижка 20% · Безпечний формат',
|
||||
href: '/grupovi-vidviduvannia#order-form',
|
||||
},
|
||||
{
|
||||
name: 'Корпоративи',
|
||||
desc: 'Від 20 осіб · Знижка 10% · Ексклюзивні зони',
|
||||
href: '/grupovi-vidviduvannia#order-form',
|
||||
},
|
||||
].map((grp) => (
|
||||
<div
|
||||
key={grp.name}
|
||||
className="flex flex-col gap-4 rounded-[20px] bg-[#396817] p-8 shadow-[0_4px_60px_0_rgba(242,139,74,0.25)]"
|
||||
>
|
||||
<div>
|
||||
<h3
|
||||
className="text-[20px] leading-tight font-bold text-white"
|
||||
style={FONT_MONT}
|
||||
{groupVisitsData?.groups && groupVisitsData.groups.length > 0 && (
|
||||
<div>
|
||||
<h2 className="mb-6 text-[24px] font-bold text-[#272727] uppercase" style={FONT_MONT}>
|
||||
{pageData?.sectionTitleGroups ?? 'Групові відвідування'}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{groupVisitsData.groups.map(
|
||||
(grp: {
|
||||
title: string
|
||||
description: string
|
||||
minPeople: string
|
||||
discount: string
|
||||
}) => (
|
||||
<div
|
||||
key={grp.title}
|
||||
className="flex flex-col gap-4 rounded-[20px] bg-[#396817] p-8 shadow-[0_4px_60px_0_rgba(242,139,74,0.25)]"
|
||||
>
|
||||
{grp.name}
|
||||
</h3>
|
||||
<p className="mt-2 text-[14px] leading-relaxed text-white/70" style={FONT_MONT}>
|
||||
{grp.desc}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href={grp.href}
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-[10px] text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg,#f28b4a 0%,#fdcf54 55%,#f28b4a 100%)',
|
||||
}}
|
||||
>
|
||||
Дізнатися ціну
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
<div>
|
||||
<h3
|
||||
className="text-[20px] leading-tight font-bold text-white"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
{grp.title}
|
||||
</h3>
|
||||
<p
|
||||
className="mt-2 text-[14px] leading-relaxed text-white/70"
|
||||
style={FONT_MONT}
|
||||
>
|
||||
Від {grp.minPeople} · Знижка {grp.discount}
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/grupovi-vidviduvannia#order-form"
|
||||
className="mt-auto flex items-center justify-center rounded-[56px] py-[10px] text-[15px] font-bold text-[#1a1a1a] transition-opacity hover:opacity-90"
|
||||
style={{
|
||||
...FONT_MONT,
|
||||
background: 'linear-gradient(90deg,#f28b4a 0%,#fdcf54 55%,#f28b4a 100%)',
|
||||
}}
|
||||
>
|
||||
Дізнатися ціну
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@ async function getDyvoLisData() {
|
|||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const data = await getDyvoLisData()
|
||||
return {
|
||||
title: data?.heroTitle ? `${data.heroTitle} — Шуміленд` : 'ДивоЛіс — Шуміленд',
|
||||
title:
|
||||
data?.metaTitle ?? (data?.heroTitle ? `${data.heroTitle} — Шуміленд` : 'ДивоЛіс — Шуміленд'),
|
||||
description:
|
||||
data?.metaDescription ??
|
||||
'Казковий топіарний ліс у Шуміленді: фігури з безпечних матеріалів, унікальна ландшафтна композиція та повна свобода для дітей.',
|
||||
}
|
||||
}
|
||||
|
|
|
|||
46
src/globals/BirthdayPage.ts
Normal file
46
src/globals/BirthdayPage.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
export const BirthdayPage: GlobalConfig = {
|
||||
slug: 'birthday-page',
|
||||
label: 'Дні народження — сторінка',
|
||||
access: { read: () => true, update: isAdminOrEditor },
|
||||
hooks: { afterChange: [revalidateGlobalAfterChange] },
|
||||
fields: [
|
||||
{
|
||||
name: 'heroTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Дні народження',
|
||||
},
|
||||
{
|
||||
name: 'heroSubtitle',
|
||||
type: 'text',
|
||||
defaultValue:
|
||||
"Зробіть свято незабутнім! Оберіть пакет і наші менеджери зв'яжуться з вами для уточнення деталей.",
|
||||
},
|
||||
{
|
||||
name: 'formTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Замовити святкування',
|
||||
},
|
||||
{
|
||||
name: 'formSubtitle',
|
||||
type: 'text',
|
||||
defaultValue: "Залиште заявку і наш менеджер зв'яжеться з вами протягом 30 хвилин",
|
||||
},
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'SEO: Meta Title',
|
||||
defaultValue: 'Дні народження — Шуміленд',
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'SEO: Meta Description',
|
||||
defaultValue:
|
||||
'Святкуйте день народження у Шуміленді! Пакети для дітей та дорослих з розвагами, аніматорами та кейтерингом.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
export const DyvoLisPage: GlobalConfig = {
|
||||
slug: 'dyvolis-page',
|
||||
label: 'Диво Ліс — сторінка',
|
||||
access: { read: () => true, update: isAdminOrEditor },
|
||||
hooks: { afterChange: [revalidateGlobalAfterChange] },
|
||||
fields: [
|
||||
// Hero section
|
||||
{
|
||||
|
|
@ -118,5 +120,18 @@ export const DyvoLisPage: GlobalConfig = {
|
|||
defaultValue: 'Динопарк + Диволіс із казковими топіарними фігурами + Дзеркальний лабіринт',
|
||||
admin: { description: 'Підзаголовок під секцією "Комбо" в блоці квитків' },
|
||||
},
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'SEO: Meta Title',
|
||||
defaultValue: 'ДивоЛіс — Шуміленд',
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'SEO: Meta Description',
|
||||
defaultValue:
|
||||
'Казковий топіарний ліс у Шуміленді: фігури з безпечних матеріалів, унікальна ландшафтна композиція та повна свобода для дітей.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
export const GroupVisitsPage: GlobalConfig = {
|
||||
slug: 'group-visits-page',
|
||||
label: 'Групові відвідування — сторінка',
|
||||
access: { read: () => true, update: isAdminOrEditor },
|
||||
hooks: { afterChange: [revalidateGlobalAfterChange] },
|
||||
fields: [
|
||||
{
|
||||
name: 'heroTitle',
|
||||
|
|
@ -69,5 +71,18 @@ export const GroupVisitsPage: GlobalConfig = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'SEO: Meta Title',
|
||||
defaultValue: 'Групові відвідування — Шуміленд',
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'SEO: Meta Description',
|
||||
defaultValue:
|
||||
'Організуйте групове відвідування Шуміленду. Спеціальні ціни для шкіл, дитячих садків і корпоративних груп.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,5 +14,34 @@ export const SiteSettings: GlobalConfig = {
|
|||
{ name: 'defaultMetaTitle', type: 'text' },
|
||||
{ name: 'defaultMetaDescription', type: 'textarea' },
|
||||
{ name: 'defaultOgImage', type: 'upload', relationTo: 'media' },
|
||||
{
|
||||
name: 'tariffCategoryLabels',
|
||||
type: 'array',
|
||||
label: 'Назви категорій тарифів',
|
||||
admin: {
|
||||
description: 'Відповідність ключа з ezy API → назва для відображення на сайті',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'key',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: { description: 'Ключ з ezy API (напр. dyno, dyvolis, maze)' },
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
admin: { description: 'Назва для відображення (напр. ДиноПарк)' },
|
||||
},
|
||||
],
|
||||
defaultValue: [
|
||||
{ key: 'dyno', label: 'ДиноПарк' },
|
||||
{ key: 'dyvolis', label: 'Диво Ліс' },
|
||||
{ key: 'maze', label: 'Дзеркальний Лабіринт' },
|
||||
{ key: 'combo', label: 'Комбо' },
|
||||
{ key: 'family', label: 'Сімейний' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
|||
46
src/globals/TicketsPage.ts
Normal file
46
src/globals/TicketsPage.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import type { GlobalConfig } from 'payload'
|
||||
import { isAdminOrEditor } from '@/access/isAdminOrEditor'
|
||||
import { revalidateGlobalAfterChange } from '@/hooks/revalidatePath'
|
||||
|
||||
export const TicketsPage: GlobalConfig = {
|
||||
slug: 'tickets-page',
|
||||
label: 'Квитки — сторінка',
|
||||
access: { read: () => true, update: isAdminOrEditor },
|
||||
hooks: { afterChange: [revalidateGlobalAfterChange] },
|
||||
fields: [
|
||||
{
|
||||
name: 'heroTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Купити квиток',
|
||||
},
|
||||
{
|
||||
name: 'heroSubtitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Оберіть квиток та придбайте онлайн — без черги на касі',
|
||||
},
|
||||
{
|
||||
name: 'sectionTitleBirthday',
|
||||
type: 'text',
|
||||
label: 'Заголовок секції "Дні народження"',
|
||||
defaultValue: 'Дні народження',
|
||||
},
|
||||
{
|
||||
name: 'sectionTitleGroups',
|
||||
type: 'text',
|
||||
label: 'Заголовок секції "Групові відвідування"',
|
||||
defaultValue: 'Групові відвідування',
|
||||
},
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'SEO: Meta Title',
|
||||
defaultValue: 'Купити квиток — Шуміленд',
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'SEO: Meta Description',
|
||||
defaultValue: 'Придбайте квитки до Шуміленду онлайн. Вхідні квитки на всі зони парку.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ export interface SiteSettingsData {
|
|||
binotelId?: string | null
|
||||
defaultMetaTitle?: string | null
|
||||
defaultMetaDescription?: string | null
|
||||
tariffCategoryLabels?: Array<{ key: string; label: string }> | null
|
||||
}
|
||||
|
||||
export const getSiteSettings = cache(async (): Promise<SiteSettingsData> => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue