fix: TS non-null assertion in seed + formatting cleanup across components
Fixes Object possibly undefined TS2532 in findOrUploadMedia (seed/route.ts:50) that was blocking production build. All other changes are whitespace/ordering only (Tailwind class order, SVG attribute expansion). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
0895f25434
commit
d015c07f7f
11 changed files with 68 additions and 54 deletions
|
|
@ -72,4 +72,4 @@
|
|||
"why-parents-3.png (webp newer)",
|
||||
"why-parents-4.png (webp newer)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,15 +35,23 @@ for (const [base, variants] of byBase) {
|
|||
if (!heavy) continue
|
||||
const srcPath = path.join(ROOT, heavy)
|
||||
const stat = await fs.stat(srcPath).catch(() => null)
|
||||
if (!stat || stat.size < MIN_BYTES) { manifest.skipped.push(path.basename(srcPath)); continue }
|
||||
if (!stat || stat.size < MIN_BYTES) {
|
||||
manifest.skipped.push(path.basename(srcPath))
|
||||
continue
|
||||
}
|
||||
const outPath = path.join(ROOT, `${base}.webp`)
|
||||
const outStat = await fs.stat(outPath).catch(() => null)
|
||||
if (outStat && outStat.mtimeMs > stat.mtimeMs) { manifest.skipped.push(path.basename(srcPath) + ' (webp newer)'); continue }
|
||||
if (outStat && outStat.mtimeMs > stat.mtimeMs) {
|
||||
manifest.skipped.push(path.basename(srcPath) + ' (webp newer)')
|
||||
continue
|
||||
}
|
||||
await sharp(srcPath).webp({ quality: QUALITY }).toFile(outPath)
|
||||
manifest.encoded.push({ src: path.basename(srcPath), out: `${base}.webp`, before: stat.size })
|
||||
}
|
||||
|
||||
const manifestPath = path.join(process.cwd(), 'scripts/optimize-images.manifest.json')
|
||||
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
console.log(`Deleted ${manifest.deletedDuplicates.length} duplicates, encoded ${manifest.encoded.length} files, skipped ${manifest.skipped.length}.`)
|
||||
console.log(
|
||||
`Deleted ${manifest.deletedDuplicates.length} duplicates, encoded ${manifest.encoded.length} files, skipped ${manifest.skipped.length}.`
|
||||
)
|
||||
console.log('Manifest:', manifestPath)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ async function findOrUploadMedia(
|
|||
limit: 1,
|
||||
overrideAccess: true,
|
||||
})
|
||||
if (existing.docs.length > 0) return existing.docs[0].id as string
|
||||
if (existing.docs.length > 0) return existing.docs[0]!.id as string
|
||||
const fullPath = path.resolve(process.cwd(), localPath)
|
||||
if (!fs.existsSync(fullPath)) return null
|
||||
const buffer = fs.readFileSync(fullPath)
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ const DEFAULT_NAV: NavLinkItem[] = [
|
|||
label: 'Локації',
|
||||
href: '/lokatsii',
|
||||
children: [
|
||||
{ label: 'ДиноПарк', href: '/lokatsii#dynopark' },
|
||||
{ label: 'Диво Ліс', href: '/lokatsii#dyvolis' },
|
||||
{ label: 'Дзеркальний Лабіринт', href: '/lokatsii#maze' },
|
||||
{ label: 'Тир з призами', href: '/lokatsii#tir' },
|
||||
{ label: 'Дитячий майданчик', href: '/lokatsii#playground' },
|
||||
{ label: 'ДиноПарк', href: '/lokatsii#dynopark' },
|
||||
{ label: 'Диво Ліс', href: '/lokatsii#dyvolis' },
|
||||
{ label: 'Дзеркальний Лабіринт', href: '/lokatsii#maze' },
|
||||
{ label: 'Тир з призами', href: '/lokatsii#tir' },
|
||||
{ label: 'Дитячий майданчик', href: '/lokatsii#playground' },
|
||||
],
|
||||
},
|
||||
{ label: 'Блог', href: '/blog' },
|
||||
|
|
|
|||
|
|
@ -47,8 +47,7 @@ export function HeaderClient({ navLinks, ctaLabel, ctaHref, logo }: HeaderClient
|
|||
backgroundColor: 'rgba(34, 62, 13, 0.42)',
|
||||
backdropFilter: 'blur(22px) saturate(160%)',
|
||||
WebkitBackdropFilter: 'blur(22px) saturate(160%)',
|
||||
boxShadow:
|
||||
'inset 0 1px 0 0 rgba(255,255,255,0.18), 0 1px 0 0 rgba(34,62,13,0.18)',
|
||||
boxShadow: 'inset 0 1px 0 0 rgba(255,255,255,0.18), 0 1px 0 0 rgba(34,62,13,0.18)',
|
||||
}}
|
||||
>
|
||||
{/* Highlight sheen (top edge) — gives the glass-y look from Figma */}
|
||||
|
|
@ -56,12 +55,11 @@ export function HeaderClient({ navLinks, ctaLabel, ctaHref, logo }: HeaderClient
|
|||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-x-0 top-0 h-[34px]"
|
||||
style={{
|
||||
background:
|
||||
'linear-gradient(to bottom, rgba(255,255,255,0.10), rgba(255,255,255,0))',
|
||||
background: 'linear-gradient(to bottom, rgba(255,255,255,0.10), rgba(255,255,255,0))',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative flex items-center justify-between h-[60px] lg:h-[120px] px-[20px] lg:px-[30px]">
|
||||
<div className="relative flex h-[60px] items-center justify-between px-[20px] lg:h-[120px] lg:px-[30px]">
|
||||
{/* Logo */}
|
||||
<Link href="/" aria-label="Шуміленд — на головну" className="shrink-0">
|
||||
{logo ? (
|
||||
|
|
@ -78,19 +76,19 @@ export function HeaderClient({ navLinks, ctaLabel, ctaHref, logo }: HeaderClient
|
|||
className="absolute"
|
||||
style={{ top: '91.32%', right: '21.81%', bottom: '0.97%', left: '22.26%' }}
|
||||
>
|
||||
<img src={LOGO_G1} alt="" aria-hidden="true" className="block w-full h-full" />
|
||||
<img src={LOGO_G1} alt="" aria-hidden="true" className="block h-full w-full" />
|
||||
</div>
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ top: '71.76%', right: '2.82%', bottom: '7.3%', left: '1.41%' }}
|
||||
>
|
||||
<img src={LOGO_G2} alt="" aria-hidden="true" className="block w-full h-full" />
|
||||
<img src={LOGO_G2} alt="" aria-hidden="true" className="block h-full w-full" />
|
||||
</div>
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ top: '1.61%', right: '2.82%', bottom: '38.73%', left: '21.27%' }}
|
||||
>
|
||||
<img src={LOGO_G3} alt="" aria-hidden="true" className="block w-full h-full" />
|
||||
<img src={LOGO_G3} alt="" aria-hidden="true" className="block h-full w-full" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -98,14 +96,14 @@ export function HeaderClient({ navLinks, ctaLabel, ctaHref, logo }: HeaderClient
|
|||
|
||||
{/* Nav — desktop */}
|
||||
<nav aria-label="Головна навігація" className="hidden lg:flex">
|
||||
<ul className="flex items-center gap-[24px] list-none m-0 p-0">
|
||||
<ul className="m-0 flex list-none items-center gap-[24px] p-0">
|
||||
{navLinks.map((link) => (
|
||||
<li key={link.href} className="relative group">
|
||||
<li key={link.href} className="group relative">
|
||||
<NavLink href={link.href}>{link.label}</NavLink>
|
||||
|
||||
{link.children && (
|
||||
<div
|
||||
className="pointer-events-none absolute top-full left-0 mt-2 min-w-[220px] overflow-hidden rounded-[16px] py-2 z-50 opacity-0 translate-y-1 transition-all duration-150 group-hover:pointer-events-auto group-hover:opacity-100 group-hover:translate-y-0"
|
||||
className="pointer-events-none absolute top-full left-0 z-50 mt-2 min-w-[220px] translate-y-1 overflow-hidden rounded-[16px] py-2 opacity-0 transition-all duration-150 group-hover:pointer-events-auto group-hover:translate-y-0 group-hover:opacity-100"
|
||||
style={{
|
||||
backgroundColor: 'rgba(34, 62, 13, 0.42)',
|
||||
backdropFilter: 'blur(22px) saturate(160%)',
|
||||
|
|
@ -127,7 +125,7 @@ export function HeaderClient({ navLinks, ctaLabel, ctaHref, logo }: HeaderClient
|
|||
<Link
|
||||
key={child.href}
|
||||
href={child.href}
|
||||
className="relative block px-5 py-[10px] text-white font-bold text-[16px] transition-colors hover:text-[#f28b4a]"
|
||||
className="relative block px-5 py-[10px] text-[16px] font-bold text-white transition-colors hover:text-[#f28b4a]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{child.label}
|
||||
|
|
@ -148,7 +146,7 @@ export function HeaderClient({ navLinks, ctaLabel, ctaHref, logo }: HeaderClient
|
|||
{/* Hamburger — mobile */}
|
||||
<button
|
||||
onClick={() => setMenuOpen((v) => !v)}
|
||||
className="lg:hidden text-white p-2 -mr-2"
|
||||
className="-mr-2 p-2 text-white lg:hidden"
|
||||
aria-label={menuOpen ? 'Закрити меню' : 'Відкрити меню'}
|
||||
aria-expanded={menuOpen}
|
||||
>
|
||||
|
|
@ -176,32 +174,32 @@ export function HeaderClient({ navLinks, ctaLabel, ctaHref, logo }: HeaderClient
|
|||
{/* Mobile dropdown */}
|
||||
{menuOpen && (
|
||||
<div
|
||||
className="lg:hidden mx-auto max-w-[1204px] mt-1 rounded-[20px] px-6 py-5"
|
||||
className="mx-auto mt-1 max-w-[1204px] rounded-[20px] px-6 py-5 lg:hidden"
|
||||
style={{
|
||||
backgroundColor: 'rgba(34,62,13,0.85)',
|
||||
backdropFilter: 'blur(22px) saturate(160%)',
|
||||
WebkitBackdropFilter: 'blur(22px) saturate(160%)',
|
||||
}}
|
||||
>
|
||||
<ul className="flex flex-col gap-5 list-none m-0 p-0">
|
||||
<ul className="m-0 flex list-none flex-col gap-5 p-0">
|
||||
{navLinks.map((link) => (
|
||||
<li key={link.href}>
|
||||
<Link
|
||||
href={link.href}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className="text-white font-bold text-[18px]"
|
||||
className="text-[18px] font-bold text-white"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
{link.children && (
|
||||
<ul className="flex flex-col gap-2 mt-2 ml-4 list-none p-0">
|
||||
<ul className="mt-2 ml-4 flex list-none flex-col gap-2 p-0">
|
||||
{link.children.map((child) => (
|
||||
<li key={child.href}>
|
||||
<Link
|
||||
href={child.href}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className="text-white/70 font-medium text-[16px] hover:text-[#f28b4a] transition-colors"
|
||||
className="text-[16px] font-medium text-white/70 transition-colors hover:text-[#f28b4a]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{child.label}
|
||||
|
|
@ -216,7 +214,7 @@ export function HeaderClient({ navLinks, ctaLabel, ctaHref, logo }: HeaderClient
|
|||
<Link
|
||||
href={ctaHref}
|
||||
onClick={() => setMenuOpen(false)}
|
||||
className="inline-flex items-center bg-[#f28b4a] text-white font-bold text-[18px] px-7 py-[10px] rounded-[64px] transition-shadow hover:shadow-[0_0_20px_0_#f28b4a]"
|
||||
className="inline-flex items-center rounded-[64px] bg-[#f28b4a] px-7 py-[10px] text-[18px] font-bold text-white transition-shadow hover:shadow-[0_0_20px_0_#f28b4a]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{ctaLabel}
|
||||
|
|
|
|||
|
|
@ -61,15 +61,23 @@ export function LocationsSlider({ locations }: { locations: LocationData[] }) {
|
|||
aria-label="Попередня локація"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<path d="M13 4L7 10L13 16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M13 4L7 10L13 16"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div
|
||||
className="relative overflow-hidden"
|
||||
style={{
|
||||
maskImage: 'linear-gradient(90deg, transparent 0%, black 5%, black 95%, transparent 100%)',
|
||||
WebkitMaskImage: 'linear-gradient(90deg, transparent 0%, black 5%, black 95%, transparent 100%)',
|
||||
maskImage:
|
||||
'linear-gradient(90deg, transparent 0%, black 5%, black 95%, transparent 100%)',
|
||||
WebkitMaskImage:
|
||||
'linear-gradient(90deg, transparent 0%, black 5%, black 95%, transparent 100%)',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
|
@ -90,22 +98,22 @@ export function LocationsSlider({ locations }: { locations: LocationData[] }) {
|
|||
className="object-cover transition-transform duration-700 ease-out group-hover:scale-105"
|
||||
priority={idx < 2}
|
||||
/>
|
||||
<div className="absolute right-0 top-0 flex h-full w-[327px] max-w-[60%] flex-col justify-center gap-[28px] bg-[rgba(34,62,13,0.8)] px-[30px]">
|
||||
<div className="absolute top-0 right-0 flex h-full w-[327px] max-w-[60%] flex-col justify-center gap-[28px] bg-[rgba(34,62,13,0.8)] px-[30px]">
|
||||
<div className="flex flex-col gap-3 text-white">
|
||||
<h3
|
||||
className="text-[24px] font-bold leading-[1.1] uppercase"
|
||||
className="text-[24px] leading-[1.1] font-bold uppercase"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{loc.name}
|
||||
</h3>
|
||||
<p
|
||||
className="text-[20px] font-medium leading-[1.5] text-[#fdcf54]"
|
||||
className="text-[20px] leading-[1.5] font-medium text-[#fdcf54]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{loc.tagline}
|
||||
</p>
|
||||
<p
|
||||
className="line-clamp-5 text-[15px] font-normal leading-[1.5] text-white/90"
|
||||
className="line-clamp-5 text-[15px] leading-[1.5] font-normal text-white/90"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{loc.description}
|
||||
|
|
@ -124,7 +132,13 @@ export function LocationsSlider({ locations }: { locations: LocationData[] }) {
|
|||
aria-label="Наступна локація"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<path d="M7 4L13 10L7 16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path
|
||||
d="M7 4L13 10L7 16"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -137,7 +137,11 @@ export function Reviews({ data, title }: ReviewsProps) {
|
|||
>
|
||||
{review.ago}
|
||||
</span>
|
||||
<StarRating value={review.rating ?? 5} size={16} className="flex gap-[2px]" />
|
||||
<StarRating
|
||||
value={review.rating ?? 5}
|
||||
size={16}
|
||||
className="flex gap-[2px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ export function WhyParents({ items, sideGallery, title }: WhyParentsProps) {
|
|||
>
|
||||
<div className="overflow-hidden">
|
||||
<p
|
||||
className="pt-2 text-[16px] font-light leading-[1.6] text-[#272727]"
|
||||
className="pt-2 text-[16px] leading-[1.6] font-light text-[#272727]"
|
||||
style={{ fontFamily: 'var(--font-poppins, Poppins), sans-serif' }}
|
||||
>
|
||||
{item.description}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ interface PageHeroProps {
|
|||
export function PageHero({ title, subtitle, bgSrc, children }: PageHeroProps) {
|
||||
const src = bgSrc ?? DEFAULT_BG
|
||||
return (
|
||||
<div className="relative -mt-[60px] overflow-hidden px-8 pb-16 pt-[calc(60px+48px)] lg:-mt-[120px] lg:pt-[calc(120px+64px)]">
|
||||
<div className="relative -mt-[60px] overflow-hidden px-8 pt-[calc(60px+48px)] pb-16 lg:-mt-[120px] lg:pt-[calc(120px+64px)]">
|
||||
<Image
|
||||
src={src}
|
||||
alt=""
|
||||
|
|
@ -28,13 +28,10 @@ export function PageHero({ title, subtitle, bgSrc, children }: PageHeroProps) {
|
|||
className="object-cover"
|
||||
style={{ zIndex: -2 }}
|
||||
/>
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
style={{ zIndex: -1, background: 'rgba(34,62,13,0.55)' }}
|
||||
/>
|
||||
<div className="absolute inset-0" style={{ zIndex: -1, background: 'rgba(34,62,13,0.55)' }} />
|
||||
<div className="relative mx-auto max-w-[1140px] text-white">
|
||||
<h1
|
||||
className="text-[40px] font-bold uppercase leading-[1.1] lg:text-[56px]"
|
||||
className="text-[40px] leading-[1.1] font-bold uppercase lg:text-[56px]"
|
||||
style={{ fontFamily: 'var(--font-montserrat, Montserrat), sans-serif' }}
|
||||
>
|
||||
{title}
|
||||
|
|
|
|||
|
|
@ -93,8 +93,7 @@ export const HomePage: GlobalConfig = {
|
|||
name: 'images',
|
||||
type: 'array',
|
||||
admin: {
|
||||
description:
|
||||
'Завантажте 9 фото для слайдера Фотогалерея — порядок і alt керуйте тут.',
|
||||
description: 'Завантажте 9 фото для слайдера Фотогалерея — порядок і alt керуйте тут.',
|
||||
},
|
||||
fields: [
|
||||
{ name: 'image', type: 'upload', relationTo: 'media' },
|
||||
|
|
|
|||
|
|
@ -19,13 +19,7 @@ interface Options {
|
|||
*/
|
||||
export function useAutoScroll<T extends HTMLElement>(
|
||||
ref: RefObject<T | null>,
|
||||
{
|
||||
speed = 1,
|
||||
intervalMs = 16,
|
||||
step = false,
|
||||
pauseOnHover = true,
|
||||
disabled = false,
|
||||
}: Options = {},
|
||||
{ speed = 1, intervalMs = 16, step = false, pauseOnHover = true, disabled = false }: Options = {}
|
||||
) {
|
||||
useEffect(() => {
|
||||
if (disabled) return
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue