feat: update ipal-kit, integrate Media/SiteSettings and configure PL/DE languages
This commit is contained in:
@@ -40,3 +40,5 @@ dev.db*
|
||||
|
||||
# media uploads in local dev if any
|
||||
/public/media
|
||||
/media
|
||||
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
"license": "ISC",
|
||||
"packageManager": "[email protected]",
|
||||
"dependencies": {
|
||||
"@intecion/ipal-kit": "^1.0.17",
|
||||
"@intecion/ipal-kit": "^1.0.18",
|
||||
"@payloadcms/db-postgres": "^3.88.0",
|
||||
"@payloadcms/db-sqlite": "^3.88.0",
|
||||
"@payloadcms/next": "^3.88.0",
|
||||
|
||||
Generated
+5
-6
@@ -19,8 +19,8 @@ importers:
|
||||
.:
|
||||
dependencies:
|
||||
'@intecion/ipal-kit':
|
||||
specifier: ^1.0.17
|
||||
version: 1.0.17(e4f897fbda4e3a120e33294a7b805575)
|
||||
specifier: ^1.0.18
|
||||
version: 1.0.18(e4f897fbda4e3a120e33294a7b805575)
|
||||
'@payloadcms/db-postgres':
|
||||
specifier: 3.88.0
|
||||
version: 3.88.0(@libsql/[email protected])([email protected]([email protected])([email protected]))
|
||||
@@ -946,9 +946,8 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@intecion/[email protected]7':
|
||||
resolution: {integrity: sha512-huzjbq4ntCFiTk4eYnGHmMADdKN5rzwBfYqmjuHE+tcxGqqrT8ffvzR3f2O0GiqMvJyMHd6Jw0qbu9a7J2tyzQ==, tarball: https://git.intecion.net/api/packages/IntecionSoftware/npm/%40intecion%2Fipal-kit/-/1.0.17/ipal-kit-1.0.17.tgz}
|
||||
engines: {node: ^18.20.2 || >=20.9.0, pnpm: ^9 || ^10 || ^11}
|
||||
'@intecion/[email protected]8':
|
||||
resolution: {integrity: sha512-NTQEoWFuGcDBRPlnk1iEeVa75BvNkLmtXChLQE9B3epZ0vcrbTrFQgbXjkhgyc5LFFahZTQ/fEYLPOB/9ZDiuA==, tarball: https://git.intecion.net/api/packages/IntecionSoftware/npm/%40intecion%2Fipal-kit/-/1.0.18/ipal-kit-1.0.18.tgz}
|
||||
peerDependencies:
|
||||
'@payloadcms/next': 3.88.0
|
||||
'@payloadcms/plugin-form-builder': 3.88.0
|
||||
@@ -3635,7 +3634,7 @@ snapshots:
|
||||
'@img/[email protected]':
|
||||
optional: true
|
||||
|
||||
'@intecion/[email protected]7(e4f897fbda4e3a120e33294a7b805575)':
|
||||
'@intecion/[email protected]8(e4f897fbda4e3a120e33294a7b805575)':
|
||||
dependencies:
|
||||
'@payloadcms/next': 3.88.0(@types/[email protected])([email protected])([email protected])([email protected](@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
|
||||
'@payloadcms/plugin-form-builder': 3.88.0(@types/[email protected])([email protected])([email protected](@types/[email protected])([email protected]([email protected]))([email protected])([email protected]))([email protected]([email protected])([email protected]))([email protected]([email protected]))([email protected])([email protected])
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 115" fill="none">
|
||||
<!-- 3 Stars / Sparkles (Cyan) -->
|
||||
<!-- Top Right Star -->
|
||||
<path d="M85 6 C85 11 89 15 94 15 C89 15 85 19 85 24 C85 19 81 15 76 15 C81 15 85 11 85 6 Z" fill="#01aeee"/>
|
||||
<!-- Middle Star -->
|
||||
<path d="M72 17 C72 22 76 26 81 26 C76 26 72 30 72 35 C72 30 68 26 63 26 C68 26 72 22 72 17 Z" fill="#01aeee"/>
|
||||
<!-- Far Right Star -->
|
||||
<path d="M91 27 C91 30.5 94 33.5 97.5 33.5 C94 33.5 91 36.5 91 40 C91 36.5 88 33.5 84.5 33.5 C88 33.5 91 30.5 91 27 Z" fill="#01aeee"/>
|
||||
|
||||
<!-- Red Roof -->
|
||||
<path d="M46 15 C47.5 13 48.5 13 50 15 L78 49 C79.5 50.8 78 54 75 54 L69 54 L69 44 C69 41.5 67 39.5 64.5 39.5 L59 39.5 L59 54 L21 54 C18 54 16.5 50.8 18 49 Z" fill="#ee1d23"/>
|
||||
|
||||
<!-- White Chimney Outline / Shape -->
|
||||
<path d="M60 41 L67 41 L67 53 L60 53 Z" fill="#ffffff"/>
|
||||
|
||||
<!-- White Roof Trim / Gutter Separation -->
|
||||
<path d="M12 55 L48 29 C49.2 28.1 50.8 28.1 52 29 L84 55 L78 61 L50 38 L20 61 Z" fill="#ffffff"/>
|
||||
|
||||
<!-- Blue House Base -->
|
||||
<path d="M22 62 L50 40 L74 62 C79 66.5 80 73 78 79 C75 92 63 103 48 103 C33 103 21 92 18 79 C16 73 17 66.5 22 62 Z" fill="#01aeee"/>
|
||||
|
||||
<!-- White Arched Window -->
|
||||
<path d="M40 73 C40 67.5 43.5 64 48 64 C52.5 64 56 67.5 56 73 L56 87 L40 87 Z" fill="#ffffff"/>
|
||||
<!-- Window Cross Divider (Cyan) -->
|
||||
<rect x="47" y="64" width="2" height="23" fill="#01aeee"/>
|
||||
<rect x="40" y="74" width="16" height="2" fill="#01aeee"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,33 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 120" fill="none">
|
||||
<!-- House Icon (Left) -->
|
||||
<g transform="translate(10, 5) scale(0.95)">
|
||||
<!-- 3 Stars / Sparkles (Cyan) -->
|
||||
<path d="M85 6 C85 11 89 15 94 15 C89 15 85 19 85 24 C85 19 81 15 76 15 C81 15 85 11 85 6 Z" fill="#01aeee"/>
|
||||
<path d="M72 17 C72 22 76 26 81 26 C76 26 72 30 72 35 C72 30 68 26 63 26 C68 26 72 22 72 17 Z" fill="#01aeee"/>
|
||||
<path d="M91 27 C91 30.5 94 33.5 97.5 33.5 C94 33.5 91 36.5 91 40 C91 36.5 88 33.5 84.5 33.5 C88 33.5 91 30.5 91 27 Z" fill="#01aeee"/>
|
||||
|
||||
<!-- Red Roof -->
|
||||
<path d="M46 15 C47.5 13 48.5 13 50 15 L78 49 C79.5 50.8 78 54 75 54 L69 54 L69 44 C69 41.5 67 39.5 64.5 39.5 L59 39.5 L59 54 L21 54 C18 54 16.5 50.8 18 49 Z" fill="#ee1d23"/>
|
||||
|
||||
<!-- White Chimney Outline / Shape -->
|
||||
<path d="M60 41 L67 41 L67 53 L60 53 Z" fill="#ffffff"/>
|
||||
|
||||
<!-- White Roof Trim / Gutter Separation -->
|
||||
<path d="M12 55 L48 29 C49.2 28.1 50.8 28.1 52 29 L84 55 L78 61 L50 38 L20 61 Z" fill="#ffffff"/>
|
||||
|
||||
<!-- Blue House Base -->
|
||||
<path d="M22 62 L50 40 L74 62 C79 66.5 80 73 78 79 C75 92 63 103 48 103 C33 103 21 92 18 79 C16 73 17 66.5 22 62 Z" fill="#01aeee"/>
|
||||
|
||||
<!-- White Arched Window -->
|
||||
<path d="M40 73 C40 67.5 43.5 64 48 64 C52.5 64 56 67.5 56 73 L56 87 L40 87 Z" fill="#ffffff"/>
|
||||
<!-- Window Cross Divider (Cyan) -->
|
||||
<rect x="47" y="64" width="2" height="23" fill="#01aeee"/>
|
||||
<rect x="40" y="74" width="16" height="2" fill="#01aeee"/>
|
||||
</g>
|
||||
|
||||
<!-- POI Text -->
|
||||
<text x="122" y="75" font-family="system-ui, -apple-system, sans-serif" font-weight="900" font-style="italic" font-size="62" letter-spacing="1" fill="#231f20">POI</text>
|
||||
|
||||
<!-- CLEAN HOUSE Text -->
|
||||
<text x="122" y="98" font-family="system-ui, -apple-system, sans-serif" font-weight="800" font-style="italic" font-size="16.5" letter-spacing="4" fill="#231f20">CLEAN HOUSE</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,33 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 120" fill="none">
|
||||
<!-- House Icon (Left) -->
|
||||
<g transform="translate(10, 5) scale(0.95)">
|
||||
<!-- 3 Stars / Sparkles (Cyan) -->
|
||||
<path d="M85 6 C85 11 89 15 94 15 C89 15 85 19 85 24 C85 19 81 15 76 15 C81 15 85 11 85 6 Z" fill="#01aeee"/>
|
||||
<path d="M72 17 C72 22 76 26 81 26 C76 26 72 30 72 35 C72 30 68 26 63 26 C68 26 72 22 72 17 Z" fill="#01aeee"/>
|
||||
<path d="M91 27 C91 30.5 94 33.5 97.5 33.5 C94 33.5 91 36.5 91 40 C91 36.5 88 33.5 84.5 33.5 C88 33.5 91 30.5 91 27 Z" fill="#01aeee"/>
|
||||
|
||||
<!-- Red Roof -->
|
||||
<path d="M46 15 C47.5 13 48.5 13 50 15 L78 49 C79.5 50.8 78 54 75 54 L69 54 L69 44 C69 41.5 67 39.5 64.5 39.5 L59 39.5 L59 54 L21 54 C18 54 16.5 50.8 18 49 Z" fill="#ee1d23"/>
|
||||
|
||||
<!-- White Chimney Outline / Shape -->
|
||||
<path d="M60 41 L67 41 L67 53 L60 53 Z" fill="#ffffff"/>
|
||||
|
||||
<!-- White Roof Trim / Gutter Separation -->
|
||||
<path d="M12 55 L48 29 C49.2 28.1 50.8 28.1 52 29 L84 55 L78 61 L50 38 L20 61 Z" fill="#ffffff"/>
|
||||
|
||||
<!-- Blue House Base -->
|
||||
<path d="M22 62 L50 40 L74 62 C79 66.5 80 73 78 79 C75 92 63 103 48 103 C33 103 21 92 18 79 C16 73 17 66.5 22 62 Z" fill="#01aeee"/>
|
||||
|
||||
<!-- White Arched Window -->
|
||||
<path d="M40 73 C40 67.5 43.5 64 48 64 C52.5 64 56 67.5 56 73 L56 87 L40 87 Z" fill="#ffffff"/>
|
||||
<!-- Window Cross Divider (Cyan) -->
|
||||
<rect x="47" y="64" width="2" height="23" fill="#01aeee"/>
|
||||
<rect x="40" y="74" width="16" height="2" fill="#01aeee"/>
|
||||
</g>
|
||||
|
||||
<!-- POI Text (White) -->
|
||||
<text x="122" y="75" font-family="system-ui, -apple-system, sans-serif" font-weight="900" font-style="italic" font-size="62" letter-spacing="1" fill="#ffffff">POI</text>
|
||||
|
||||
<!-- CLEAN HOUSE Text (White) -->
|
||||
<text x="122" y="98" font-family="system-ui, -apple-system, sans-serif" font-weight="800" font-style="italic" font-size="16.5" letter-spacing="4" fill="#ffffff">CLEAN HOUSE</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -5,6 +5,8 @@ import { createPageMetadata } from '@intecion/ipal-kit'
|
||||
import { i18nConfig } from '@/i18n.config'
|
||||
import { blockRegistry } from '@/blocks/registry'
|
||||
import { getCachedPayload, resolveRoute } from '@/lib/content'
|
||||
import { getSettings } from '@/lib/payload'
|
||||
import type { Media } from '@/payload-types'
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
@@ -23,7 +25,31 @@ const pageMetadata = createPageMetadata({
|
||||
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const { locale, slug } = await params
|
||||
return pageMetadata({ payload: await getCachedPayload(), locale, slug: slug ?? [] })
|
||||
const payload = await getCachedPayload()
|
||||
const pageMeta = await pageMetadata({ payload, locale, slug: slug ?? [] })
|
||||
|
||||
if (!pageMeta.openGraph?.images || pageMeta.openGraph.images.length === 0) {
|
||||
const settings = await getSettings(locale)
|
||||
const shareImage =
|
||||
(typeof settings?.defaultShareImage === 'object' && settings?.defaultShareImage && 'url' in settings.defaultShareImage
|
||||
? (settings.defaultShareImage as Media)
|
||||
: null) ||
|
||||
(typeof settings?.logo === 'object' && settings?.logo && 'url' in settings.logo
|
||||
? (settings.logo as Media)
|
||||
: null)
|
||||
|
||||
if (shareImage?.url) {
|
||||
return {
|
||||
...pageMeta,
|
||||
openGraph: {
|
||||
...pageMeta.openGraph,
|
||||
images: [{ url: shareImage.url }],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pageMeta
|
||||
}
|
||||
|
||||
export default async function Page({ params, searchParams }: PageProps) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import type { ReactNode } from 'react'
|
||||
import type { Metadata } from 'next'
|
||||
import {
|
||||
getConsentTexts,
|
||||
getAnalyticsConfig,
|
||||
@@ -18,13 +19,51 @@ import { Footer, type FooterNavItem } from '@/components/Footer'
|
||||
import { CustomCookieBanner } from '@/components/CustomCookieBanner'
|
||||
import '../styles.css'
|
||||
|
||||
import type { Company, Navigation, Page } from '@/payload-types'
|
||||
import type { Company, Media, Navigation, Page } from '@/payload-types'
|
||||
|
||||
interface LayoutProps {
|
||||
children: ReactNode
|
||||
params: Promise<{ locale: string }>
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
|
||||
const { locale } = await params
|
||||
const settings = await getSettings(locale)
|
||||
const favicon =
|
||||
typeof settings?.favicon === 'object' && settings?.favicon && 'url' in settings.favicon
|
||||
? (settings.favicon as Media)
|
||||
: null
|
||||
const faviconUrl = favicon?.url || undefined
|
||||
|
||||
const shareImage =
|
||||
(typeof settings?.defaultShareImage === 'object' && settings?.defaultShareImage && 'url' in settings.defaultShareImage
|
||||
? (settings.defaultShareImage as Media)
|
||||
: null) ||
|
||||
(typeof settings?.logo === 'object' && settings?.logo && 'url' in settings.logo
|
||||
? (settings.logo as Media)
|
||||
: null)
|
||||
const shareImageUrl = shareImage?.url || undefined
|
||||
|
||||
return {
|
||||
...(faviconUrl
|
||||
? {
|
||||
icons: {
|
||||
icon: faviconUrl,
|
||||
shortcut: faviconUrl,
|
||||
apple: faviconUrl,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(shareImageUrl
|
||||
? {
|
||||
openGraph: {
|
||||
images: [{ url: shareImageUrl }],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
function resolveNavItemHref(item: { page?: Page | number | string | null; url?: string | null }): string {
|
||||
if (item.url) return item.url
|
||||
if (item.page && typeof item.page === 'object' && 'slug' in item.page && item.page.slug) {
|
||||
@@ -40,6 +79,10 @@ export default async function LocaleLayout({ children, params }: LayoutProps) {
|
||||
|
||||
const payload = await getCachedPayload()
|
||||
const settings = await getSettings(locale)
|
||||
const logo =
|
||||
typeof settings?.logo === 'object' && settings?.logo && 'url' in settings.logo
|
||||
? (settings.logo as Media)
|
||||
: null
|
||||
const privacyPage = (settings as { privacyPolicy?: unknown })?.privacyPolicy
|
||||
const cookiePage = (settings as { cookiePolicy?: unknown })?.cookiePolicy
|
||||
const homePage = (settings as { homepage?: unknown })?.homepage
|
||||
@@ -154,6 +197,8 @@ export default async function LocaleLayout({ children, params }: LayoutProps) {
|
||||
currentLocale={locale}
|
||||
contactHref={contactHref}
|
||||
contactLabel={contactLabel}
|
||||
logo={logo}
|
||||
locales={i18nConfig.locales}
|
||||
/>
|
||||
<main className="flex-1">{children}</main>
|
||||
<Footer
|
||||
@@ -179,6 +224,7 @@ export default async function LocaleLayout({ children, params }: LayoutProps) {
|
||||
legalTitle={(navigation as { legalTitle?: string })?.legalTitle}
|
||||
creditLabel="Powered by NetScale.pl"
|
||||
creditHref="https://netscale.pl"
|
||||
logo={logo}
|
||||
/>
|
||||
<CustomCookieBanner />
|
||||
<CookieButton className="fixed bottom-3 left-3 z-40 bg-dark hover:bg-primary text-primary hover:text-dark border-2 border-primary p-2 transition-all shadow-lg cursor-pointer" />
|
||||
|
||||
@@ -35,20 +35,22 @@ export function HeroBlockComponent(props: HeroBlockProps) {
|
||||
const heroImgSrc =
|
||||
(typeof backgroundImage === 'object' && backgroundImage?.url) ||
|
||||
(typeof backgroundImage === 'string' && backgroundImage) ||
|
||||
'/images/paving-hero.webp'
|
||||
null
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* Hero Banner Section with Next.js Image */}
|
||||
<section className="relative w-full min-h-[calc(100vh-8rem)] max-h-[780px] flex items-center overflow-hidden">
|
||||
<Image
|
||||
src={heroImgSrc}
|
||||
alt={heading}
|
||||
fill
|
||||
priority
|
||||
sizes="100vw"
|
||||
className="object-cover object-center z-0"
|
||||
/>
|
||||
<section className="relative w-full min-h-[calc(100vh-8rem)] max-h-[780px] flex items-center overflow-hidden bg-dark">
|
||||
{heroImgSrc && (
|
||||
<Image
|
||||
src={heroImgSrc}
|
||||
alt={heading}
|
||||
fill
|
||||
priority
|
||||
sizes="100vw"
|
||||
className="object-cover object-center z-0"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Dark gradient overlay for text readability */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-dark/95 via-dark/70 to-black/30 z-[1]" />
|
||||
|
||||
@@ -20,12 +20,12 @@ export function ProcessStepsBlockComponent(props: ProcessStepsBlockProps) {
|
||||
const bgUrl =
|
||||
(typeof backgroundImage === 'object' && backgroundImage?.url) ||
|
||||
(typeof backgroundImage === 'string' && backgroundImage) ||
|
||||
'/images/process-parallax.webp'
|
||||
null
|
||||
|
||||
return (
|
||||
<section
|
||||
className="relative py-28 border-b-2 border-dark bg-fixed bg-center bg-cover overflow-hidden"
|
||||
style={{ backgroundImage: `url('${bgUrl}')` }}
|
||||
className="relative py-28 border-b-2 border-dark bg-dark bg-fixed bg-center bg-cover overflow-hidden"
|
||||
style={bgUrl ? { backgroundImage: `url('${bgUrl}')` } : undefined}
|
||||
>
|
||||
{/* Dark overlay for contrast */}
|
||||
<div className="absolute inset-0 bg-dark/85 backdrop-blur-[1px]" />
|
||||
|
||||
@@ -20,12 +20,12 @@ export function TechEquipmentBlockComponent(props: TechEquipmentBlockProps) {
|
||||
const bgUrl =
|
||||
(typeof backgroundImage === 'object' && backgroundImage?.url) ||
|
||||
(typeof backgroundImage === 'string' && backgroundImage) ||
|
||||
'/images/paving-parallax.webp'
|
||||
null
|
||||
|
||||
return (
|
||||
<section
|
||||
className="relative py-28 border-b-2 border-dark bg-fixed bg-center bg-cover overflow-hidden"
|
||||
style={{ backgroundImage: `url('${bgUrl}')` }}
|
||||
className="relative py-28 border-b-2 border-dark bg-dark bg-fixed bg-center bg-cover overflow-hidden"
|
||||
style={bgUrl ? { backgroundImage: `url('${bgUrl}')` } : undefined}
|
||||
>
|
||||
{/* Dark overlay for contrast */}
|
||||
<div className="absolute inset-0 bg-dark/85 backdrop-blur-[1px]" />
|
||||
|
||||
@@ -2,6 +2,8 @@ import React from 'react'
|
||||
import { Phone, Mail, ShieldCheck, Cookie, FileText } from 'lucide-react'
|
||||
import { Logo } from '@/components/Logo'
|
||||
|
||||
import type { Media } from '@/payload-types'
|
||||
|
||||
export interface FooterNavItem {
|
||||
id?: string
|
||||
label: string
|
||||
@@ -31,6 +33,7 @@ interface FooterProps {
|
||||
legalTitle?: string | null
|
||||
creditLabel?: string | null
|
||||
creditHref?: string | null
|
||||
logo?: Media | { url?: string | null; alt?: string | null } | null
|
||||
}
|
||||
|
||||
export function Footer({
|
||||
@@ -56,6 +59,7 @@ export function Footer({
|
||||
legalTitle,
|
||||
creditLabel,
|
||||
creditHref,
|
||||
logo,
|
||||
}: FooterProps) {
|
||||
return (
|
||||
<footer className="bg-dark text-[#d0d0d0] pt-14 pb-8 border-t-4 border-primary">
|
||||
@@ -64,7 +68,11 @@ export function Footer({
|
||||
{/* 1. Company Info */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Logo variant="dark" className="h-12 w-auto" alt={companyName || undefined} />
|
||||
{logo ? (
|
||||
<Logo className="h-12 w-auto" alt={companyName || undefined} media={logo} />
|
||||
) : companyName ? (
|
||||
<span className="font-black text-xl tracking-tight text-white uppercase">{companyName}</span>
|
||||
) : null}
|
||||
</div>
|
||||
{footerTagline && (
|
||||
<p className="text-xs sm:text-sm text-[#a0a0a0] leading-relaxed">
|
||||
|
||||
@@ -5,6 +5,8 @@ import { ArrowRight, Globe, ChevronDown } from 'lucide-react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { Logo } from '@/components/Logo'
|
||||
|
||||
import type { Media } from '@/payload-types'
|
||||
|
||||
export interface NavItem {
|
||||
id?: string
|
||||
label: string
|
||||
@@ -21,6 +23,8 @@ interface HeaderProps {
|
||||
contactHref?: string | null
|
||||
contactLabel?: string | null
|
||||
phoneLabel?: string | null
|
||||
logo?: Media | { url?: string | null; alt?: string | null } | null
|
||||
locales?: readonly { code: string; label: string }[] | { code: string; label: string }[]
|
||||
}
|
||||
|
||||
export function Header({
|
||||
@@ -33,6 +37,8 @@ export function Header({
|
||||
contactHref,
|
||||
contactLabel,
|
||||
phoneLabel,
|
||||
logo,
|
||||
locales,
|
||||
}: HeaderProps) {
|
||||
const pathname = usePathname() || ''
|
||||
const [langDropdownOpen, setLangDropdownOpen] = useState(false)
|
||||
@@ -63,10 +69,10 @@ export function Header({
|
||||
(item) => !contactLabel || item.label.trim().toLowerCase() !== contactLabel.trim().toLowerCase()
|
||||
)
|
||||
|
||||
const languages = ['pl', 'en'].map((code) => ({
|
||||
code,
|
||||
label: code.toUpperCase(),
|
||||
}))
|
||||
const languages =
|
||||
locales && locales.length > 0
|
||||
? locales
|
||||
: [{ code: currentLocale, label: currentLocale.toUpperCase() }]
|
||||
|
||||
const activeLang = languages.find((l) => l.code === currentLocale) || languages[0]
|
||||
|
||||
@@ -106,7 +112,11 @@ export function Header({
|
||||
{/* Main Navigation Bar */}
|
||||
<div className="container mx-auto flex h-20 items-center justify-between px-4 sm:px-6 lg:px-12 max-w-7xl">
|
||||
<a href={homeHref} className="flex items-center gap-3">
|
||||
<Logo className="h-12 w-auto" alt={companyName || undefined} />
|
||||
{logo ? (
|
||||
<Logo className="h-12 w-auto" alt={companyName || undefined} media={logo} />
|
||||
) : companyName ? (
|
||||
<span className="font-black text-xl tracking-tight text-dark uppercase">{companyName}</span>
|
||||
) : null}
|
||||
</a>
|
||||
|
||||
{/* Navigation Items */}
|
||||
|
||||
+29
-6
@@ -1,5 +1,6 @@
|
||||
import React from 'react'
|
||||
import Image from 'next/image'
|
||||
import type { Media } from '@/payload-types'
|
||||
|
||||
interface LogoProps {
|
||||
className?: string
|
||||
@@ -8,13 +9,29 @@ interface LogoProps {
|
||||
width?: number
|
||||
priority?: boolean
|
||||
alt?: string
|
||||
media?: Media | { url?: string | null; alt?: string | null } | null
|
||||
src?: string | null
|
||||
}
|
||||
|
||||
export function LogoIcon({ className = 'h-11 w-auto', alt = '', ...props }: { className?: string; alt?: string }) {
|
||||
export function LogoIcon({
|
||||
className = 'h-11 w-auto',
|
||||
alt = '',
|
||||
media,
|
||||
...props
|
||||
}: {
|
||||
className?: string
|
||||
alt?: string
|
||||
media?: Media | { url?: string | null; alt?: string | null } | null
|
||||
}) {
|
||||
const src = (typeof media === 'object' && media?.url) || null
|
||||
const mediaAlt = (typeof media === 'object' && media?.alt) || alt || ''
|
||||
|
||||
if (!src) return null
|
||||
|
||||
return (
|
||||
<Image
|
||||
src="/images/logo-icon.webp"
|
||||
alt={alt}
|
||||
src={src}
|
||||
alt={mediaAlt}
|
||||
width={140}
|
||||
height={140}
|
||||
priority
|
||||
@@ -26,17 +43,21 @@ export function LogoIcon({ className = 'h-11 w-auto', alt = '', ...props }: { cl
|
||||
|
||||
export function Logo({
|
||||
className = 'h-12 w-auto',
|
||||
variant = 'light',
|
||||
priority = true,
|
||||
alt = '',
|
||||
media,
|
||||
src: customSrc,
|
||||
...props
|
||||
}: LogoProps) {
|
||||
const src = variant === 'dark' ? '/images/logo-white.webp' : '/images/logo.webp'
|
||||
const src = customSrc || (typeof media === 'object' && media?.url) || null
|
||||
const mediaAlt = (typeof media === 'object' && media?.alt) || alt || ''
|
||||
|
||||
if (!src) return null
|
||||
|
||||
return (
|
||||
<Image
|
||||
src={src}
|
||||
alt={alt}
|
||||
alt={mediaAlt}
|
||||
width={240}
|
||||
height={84}
|
||||
priority={priority}
|
||||
@@ -45,3 +66,5 @@ export function Logo({
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ export const i18nConfig = {
|
||||
defaultLocale: 'pl',
|
||||
locales: [
|
||||
{ code: 'pl', label: 'PL' },
|
||||
{ code: 'en', label: 'UK' },
|
||||
{ code: 'de', label: 'DE' },
|
||||
],
|
||||
} as const satisfies I18nConfig
|
||||
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
import { cache } from 'react'
|
||||
import { getSiteSettings } from '@intecion/ipal-kit'
|
||||
import type { SiteSetting } from '@/payload-types'
|
||||
import { getCachedPayload } from './content'
|
||||
|
||||
export const getSettings = cache(async (locale: string) =>
|
||||
getSiteSettings(await getCachedPayload(), { locale: locale as never, depth: 2 }),
|
||||
getSiteSettings<SiteSetting>(await getCachedPayload(), { locale: locale as never, depth: 2 }),
|
||||
)
|
||||
|
||||
|
||||
+112
-38
@@ -1,3 +1,5 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
export async function seed(payload: Payload) {
|
||||
@@ -130,18 +132,95 @@ export async function seed(payload: Payload) {
|
||||
contactForm = { id: Number(existingForms.docs[0].id) }
|
||||
}
|
||||
|
||||
// 4. Sprawdź czy strony już istnieją
|
||||
const existingPages = await payload.find({
|
||||
collection: 'pages',
|
||||
// 4. Seedowanie mediów (logo oraz favicon)
|
||||
let logoMediaId: number | null = null
|
||||
let faviconMediaId: number | null = null
|
||||
|
||||
const existingLogo = await payload.find({
|
||||
collection: 'media',
|
||||
locale: 'pl',
|
||||
where: {
|
||||
or: [
|
||||
{ filename: { contains: 'logo-poi' } },
|
||||
{ alt: { equals: 'Logo POI Clean House' } },
|
||||
],
|
||||
},
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
if (existingPages.totalDocs > 0) {
|
||||
payload.logger.info('Strony już istnieją w bazie. Pomijam seedowanie stron.')
|
||||
return
|
||||
if (existingLogo.totalDocs === 0) {
|
||||
payload.logger.info('Tworzę logo w kolekcji Media...')
|
||||
const filePath = path.resolve(process.cwd(), 'public/images/logo-poi.svg')
|
||||
if (fs.existsSync(filePath)) {
|
||||
const createdLogo = await payload.create({
|
||||
collection: 'media',
|
||||
locale: 'pl',
|
||||
filePath,
|
||||
data: {
|
||||
alt: 'Logo POI Clean House',
|
||||
},
|
||||
})
|
||||
logoMediaId = Number(createdLogo.id)
|
||||
}
|
||||
} else {
|
||||
logoMediaId = Number(existingLogo.docs[0].id)
|
||||
}
|
||||
|
||||
payload.logger.info('Tworzę strony serwisu...')
|
||||
const existingFavicon = await payload.find({
|
||||
collection: 'media',
|
||||
locale: 'pl',
|
||||
where: {
|
||||
or: [
|
||||
{ filename: { contains: 'favicon-poi' } },
|
||||
{ alt: { equals: 'Favicon icon POI Clean House' } },
|
||||
],
|
||||
},
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
if (existingFavicon.totalDocs === 0) {
|
||||
payload.logger.info('Tworzę favicon w kolekcji Media...')
|
||||
const filePath = path.resolve(process.cwd(), 'public/images/favicon-poi.svg')
|
||||
if (fs.existsSync(filePath)) {
|
||||
const createdFavicon = await payload.create({
|
||||
collection: 'media',
|
||||
locale: 'pl',
|
||||
filePath,
|
||||
data: {
|
||||
alt: 'Favicon icon POI Clean House',
|
||||
},
|
||||
})
|
||||
faviconMediaId = Number(createdFavicon.id)
|
||||
}
|
||||
} else {
|
||||
faviconMediaId = Number(existingFavicon.docs[0].id)
|
||||
}
|
||||
|
||||
// 5. Sprawdź czy strony już istnieją
|
||||
const existingPages = await payload.find({
|
||||
collection: 'pages',
|
||||
limit: 20,
|
||||
})
|
||||
|
||||
let homePageId: number | null = null
|
||||
let privacyPageId: number | null = null
|
||||
let cookiePageId: number | null = null
|
||||
let realizacjePageId: number | null = null
|
||||
let blogPageId: number | null = null
|
||||
let kontaktPageId: number | null = null
|
||||
|
||||
if (existingPages.totalDocs > 0) {
|
||||
payload.logger.info('Strony już istnieją w bazie. Odczytuję ID stron...')
|
||||
for (const page of existingPages.docs) {
|
||||
if (page.slug === 'home') homePageId = Number(page.id)
|
||||
if (page.slug === 'polityka-prywatnosci') privacyPageId = Number(page.id)
|
||||
if (page.slug === 'polityka-cookies') cookiePageId = Number(page.id)
|
||||
if (page.slug === 'realizacje') realizacjePageId = Number(page.id)
|
||||
if (page.slug === 'blog') blogPageId = Number(page.id)
|
||||
if (page.slug === 'kontakt') kontaktPageId = Number(page.id)
|
||||
}
|
||||
} else {
|
||||
payload.logger.info('Tworzę strony serwisu...')
|
||||
|
||||
// A. Strona Główna
|
||||
const homePage = await payload.create({
|
||||
@@ -578,24 +657,36 @@ W każdej chwili możesz zmienić swoje preferencje dotyczące plików cookies k
|
||||
},
|
||||
})
|
||||
|
||||
// 4. Przypisanie stron systemowych do globala site-settings
|
||||
payload.logger.info('Konfiguruję strony systemowe w SiteSettings...')
|
||||
homePageId = Number(homePage.id)
|
||||
privacyPageId = Number(privacyPage.id)
|
||||
cookiePageId = Number(cookiePage.id)
|
||||
realizacjePageId = Number(realizacjePage.id)
|
||||
blogPageId = Number(blogPage.id)
|
||||
kontaktPageId = Number(kontaktPage.id)
|
||||
}
|
||||
|
||||
// 6. Przypisanie stron systemowych oraz mediów do globala site-settings
|
||||
payload.logger.info('Konfiguruję SiteSettings (logo, favicon, defaultShareImage, strony systemowe)...')
|
||||
try {
|
||||
await payload.updateGlobal({
|
||||
slug: 'site-settings',
|
||||
locale: 'pl',
|
||||
data: {
|
||||
siteName: 'POI Clean House',
|
||||
homepage: homePage.id,
|
||||
privacyPolicy: privacyPage.id,
|
||||
cookiePolicy: cookiePage.id,
|
||||
titleOrder: 'page-first',
|
||||
titleSeparator: '|',
|
||||
...(logoMediaId ? { logo: logoMediaId, defaultShareImage: logoMediaId } : {}),
|
||||
...(faviconMediaId ? { favicon: faviconMediaId } : {}),
|
||||
...(homePageId ? { homepage: homePageId } : {}),
|
||||
...(privacyPageId ? { privacyPolicy: privacyPageId } : {}),
|
||||
...(cookiePageId ? { cookiePolicy: cookiePageId } : {}),
|
||||
} as never,
|
||||
})
|
||||
} catch (err) {
|
||||
payload.logger.warn(`Nie udało się automatycznie zaktualizować siteSettings: ${err}`)
|
||||
}
|
||||
|
||||
// 5. Przypisanie menu do globala navigation
|
||||
// 7. Przypisanie menu do globala navigation
|
||||
payload.logger.info('Konfiguruję menu w Navigation...')
|
||||
try {
|
||||
await payload.updateGlobal({
|
||||
@@ -611,42 +702,24 @@ W każdej chwili możesz zmienić swoje preferencje dotyczące plików cookies k
|
||||
label: 'Cennik',
|
||||
url: '#cennik',
|
||||
},
|
||||
{
|
||||
label: 'Realizacje',
|
||||
page: realizacjePage.id,
|
||||
},
|
||||
{
|
||||
label: 'Blog',
|
||||
page: blogPage.id,
|
||||
},
|
||||
...(realizacjePageId ? [{ label: 'Realizacje', page: realizacjePageId }] : []),
|
||||
...(blogPageId ? [{ label: 'Blog', page: blogPageId }] : []),
|
||||
],
|
||||
ctaButton: {
|
||||
label: 'Kontakt',
|
||||
page: kontaktPage.id,
|
||||
...(kontaktPageId ? { page: kontaktPageId } : { url: '/kontakt' }),
|
||||
},
|
||||
footerTagline:
|
||||
'Wyspecjalizowane usługi mycia gorącowodnego, usuwania mchu, odplamiania oleju, spoinowania i impregnacji hydrofobowej nawierzchni brukowych.',
|
||||
footerItems: [
|
||||
{
|
||||
label: 'Strona główna',
|
||||
page: homePage.id,
|
||||
},
|
||||
...(homePageId ? [{ label: 'Strona główna', page: homePageId }] : []),
|
||||
{
|
||||
label: 'Cennik usług',
|
||||
url: '#cennik',
|
||||
},
|
||||
{
|
||||
label: 'Galeria realizacji',
|
||||
page: realizacjePage.id,
|
||||
},
|
||||
{
|
||||
label: 'Poradnik i Blog',
|
||||
page: blogPage.id,
|
||||
},
|
||||
{
|
||||
label: 'Kontakt i wycena',
|
||||
page: kontaktPage.id,
|
||||
},
|
||||
...(realizacjePageId ? [{ label: 'Galeria realizacji', page: realizacjePageId }] : []),
|
||||
...(blogPageId ? [{ label: 'Poradnik i Blog', page: blogPageId }] : []),
|
||||
...(kontaktPageId ? [{ label: 'Kontakt i wycena', page: kontaktPageId }] : []),
|
||||
],
|
||||
} as never,
|
||||
})
|
||||
@@ -656,3 +729,4 @@ W każdej chwili możesz zmienić swoje preferencje dotyczące plików cookies k
|
||||
|
||||
payload.logger.info('Seedowanie zakończone pomyślnie!')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user