diff --git a/package.json b/package.json index 046e189..276b311 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,11 @@ "pnpm": ">=11.0.0" }, "scripts": { - "dev": "next dev", + "dev": "next dev --webpack", "build": "next build", "start": "next start", "typecheck": "tsc --noEmit", - "lint": "next lint", + "lint": "eslint src/", "format": "prettier --write .", "test": "vitest run", "test:watch": "vitest", diff --git a/src/app/(payload)/layout.tsx b/src/app/(payload)/layout.tsx index defcee2..f353bd8 100644 --- a/src/app/(payload)/layout.tsx +++ b/src/app/(payload)/layout.tsx @@ -1,9 +1,30 @@ import React from 'react' +import { RootLayout, handleServerFunctions } from '@payloadcms/next/layouts' +import type { ServerFunctionClient } from 'payload' +import configPromise from '@payload-config' +import { importMap } from './admin/importMap.js' export const metadata = { title: 'Shumiland Admin', } -export default function PayloadLayout({ children }: { children: React.ReactNode }) { - return children +const serverFunction: ServerFunctionClient = async function (args) { + 'use server' + return handleServerFunctions({ + ...args, + config: configPromise, + importMap, + }) +} + +export default function PayloadLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) } diff --git a/src/components/sections/Hero.test.tsx b/src/components/sections/Hero.test.tsx index 88bd9ce..fb64a69 100644 --- a/src/components/sections/Hero.test.tsx +++ b/src/components/sections/Hero.test.tsx @@ -72,7 +72,7 @@ describe('Hero', () => { it('renders subtitle when provided', () => { const hero: HomePageHero = { subtitle: 'The best adventure park' } render() - expect(screen.getByText('The best adventure park')).toBeInTheDocument() + expect(screen.getAllByText('The best adventure park')[0]).toBeInTheDocument() }) it('renders CTA link with correct href', () => { @@ -129,7 +129,7 @@ describe('Hero', () => { } render() expect(screen.getByRole('heading', { level: 1, name: 'Shumiland' })).toBeInTheDocument() - expect(screen.getByText('Discover the magic')).toBeInTheDocument() + expect(screen.getAllByText('Discover the magic')[0]).toBeInTheDocument() const link = screen.getByRole('link', { name: 'Explore' }) expect(link).toHaveAttribute('href', '/explore') }) diff --git a/src/components/sections/Hero.tsx b/src/components/sections/Hero.tsx index 8ea1d3b..9dd10f8 100644 --- a/src/components/sections/Hero.tsx +++ b/src/components/sections/Hero.tsx @@ -1,5 +1,5 @@ /* eslint-disable @next/next/no-img-element */ -import type { HomePageHero } from '@/types/globals' +import type { HomePageHero, Media } from '@/types/globals' import { BtnPrimary } from '@/components/ui/BtnPrimary' const IMG_BG2 = '/images/figma/hero-bg2.png' @@ -10,65 +10,59 @@ interface HeroProps { hero?: HomePageHero | null } -/** - * Hero frame (Figma 1920 × 1080): - * - section bg #223e0d, rounded-b-20, padding 0 10 10 10 - * - 3 background image layers stacked (bg2 → bg1 → family) - * - inner "hero-main" content frame at left:208 top:130 (1504 × 879) - * - blur layer is an L-shape SVG path with a notch in the top-right where - * the family image shows through clearly. - * - title 120px Montserrat 700, line-height 1.2, white - * - subtitle 24px Montserrat 500, max-width 629 - * - btn_primary at the bottom-left of the content frame - * - * Mobile/tablet: title scales down, blur layer becomes a simple fade. - */ +function isMedia(val: unknown): val is Media { + return typeof val === 'object' && val !== null && 'url' in val +} + +function isSafeHref(href: string): boolean { + return !href.trim().toLowerCase().startsWith('javascript:') +} + export function Hero({ hero }: HeroProps) { - const subtitle = - hero?.subtitle ?? - 'Сімейний тематичний парк, де гра допомагає пізнавати світ, а кожна прогулянка перетворюється на незабутню пригоду.' - const ctaLabel = hero?.ctaLabel ?? 'Купити квиток' - const ctaHref = hero?.ctaHref ?? '/kvytky' + if (!hero) return null + + const { title, subtitle, ctaLabel, ctaHref, backgroundImage, backgroundVideo } = hero + const showCta = Boolean(ctaLabel && ctaHref && isSafeHref(ctaHref)) + const bgMedia = isMedia(backgroundImage) ? backgroundImage : null return (
- {/* Background image layers */} - - - + {/* Background layers */} + {backgroundVideo ? ( +
diff --git a/src/components/ui/BtnPrimary.figma.ts b/src/components/ui/BtnPrimary.figma.ts index e2a4551..4b99ff7 100644 --- a/src/components/ui/BtnPrimary.figma.ts +++ b/src/components/ui/BtnPrimary.figma.ts @@ -4,7 +4,8 @@ import figma from 'figma' const instance = figma.selectedInstance -const label = instance.findText('btn_text')?.textContent ?? 'Купити квиток' +const _handle = instance.findText('btn_text') +const label = (_handle && 'textContent' in _handle ? _handle.textContent : null) ?? 'Купити квиток' export default { example: figma.code`${label}`, diff --git a/src/components/ui/NavLink.figma.ts b/src/components/ui/NavLink.figma.ts index f8c735d..6e5523c 100644 --- a/src/components/ui/NavLink.figma.ts +++ b/src/components/ui/NavLink.figma.ts @@ -4,7 +4,8 @@ import figma from 'figma' const instance = figma.selectedInstance -const label = instance.findText('Головна')?.textContent ?? 'Головна' +const _handle = instance.findText('Головна') +const label = (_handle && 'textContent' in _handle ? _handle.textContent : null) ?? 'Головна' export default { example: figma.code`${label}`,