diff --git a/src/Footer/Component.tsx b/src/Footer/Component.tsx index 87f4e86..ff942c6 100644 --- a/src/Footer/Component.tsx +++ b/src/Footer/Component.tsx @@ -1,9 +1,12 @@ import { getCachedGlobal } from '@/utilities/getGlobals' import Link from 'next/link' import React from 'react' -import type { Footer, Page } from '@/payload-types' +import type { Footer, Media, Page } from '@/payload-types' import { CMSLink } from '@/components/Link' import { Logo } from '@/components/Logo/Logo' +import { FDImage } from '@/components/FDImage' +import { SocialIconsRow } from '@/components/SocialIcons' +import type { SocialLinkData } from '@/components/SocialIcons' /** Resolves the logo href from the logoLink group field */ function resolveLogoHref(logoLink: Footer['logoLink']): string { @@ -19,68 +22,130 @@ function resolveLogoHref(logoLink: Footer['logoLink']): string { return logoLink.url || '/' } +/** Builds a flat, ordered list of enabled social links from the socialLinks group */ +export function buildSocialLinks(socialLinks: Footer['socialLinks']): SocialLinkData[] { + if (!socialLinks) return [] + const result: SocialLinkData[] = [] + if (socialLinks.linkedinEnabled && socialLinks.linkedinUrl) + result.push({ platform: 'linkedin', url: socialLinks.linkedinUrl, enabled: true }) + if (socialLinks.instagramEnabled && socialLinks.instagramUrl) + result.push({ platform: 'instagram', url: socialLinks.instagramUrl, enabled: true }) + if (socialLinks.facebookEnabled && socialLinks.facebookUrl) + result.push({ platform: 'facebook', url: socialLinks.facebookUrl, enabled: true }) + if (socialLinks.youtubeEnabled && socialLinks.youtubeUrl) + result.push({ platform: 'youtube', url: socialLinks.youtubeUrl, enabled: true }) + if (socialLinks.twitterEnabled && socialLinks.twitterUrl) + result.push({ platform: 'twitter', url: socialLinks.twitterUrl, enabled: true }) + return result +} + export async function Footer() { const footerData = await getCachedGlobal('footer', 1)() as unknown as Footer const columns = footerData?.columns || [] const navItems = footerData?.navItems || [] + const certMarks = footerData?.certMarks || [] const hasColumns = columns.length > 0 + const socialLinks = buildSocialLinks(footerData?.socialLinks) const logoHref = resolveLogoHref(footerData?.logoLink) - const bottomLeft = (footerData?.bottomLeftText || '© {year} Fiber Direkt. Alla rättigheter förbehållna.').replace('{year}', new Date().getFullYear().toString()) - const bottomRight = footerData?.bottomRightText || 'Svenskt datacenter · ISO 27001 · ISO 14001' + const bottomLeft = ( + footerData?.bottomLeftText || '© {year} Fiber Direkt. Alla rättigheter förbehållna.' + ).replace('{year}', new Date().getFullYear().toString()) return ( diff --git a/src/Footer/config.ts b/src/Footer/config.ts index 41ecb43..68b9194 100644 --- a/src/Footer/config.ts +++ b/src/Footer/config.ts @@ -50,6 +50,134 @@ export const Footer: GlobalConfig = { ], }, + // ── Certifieringsmärken ─────────────────────────────────────────────────── + { + name: 'certMarks', + type: 'array', + label: 'Certifieringsmärken', + maxRows: 6, + admin: { + description: 'Logotyper och certifieringsbadges som visas i övre högra hörnet av footern (t.ex. ISO, UC-sigill, Based in Sweden).', + initCollapsed: true, + }, + fields: [ + { + name: 'image', + type: 'upload', + label: 'Bild', + relationTo: 'media', + required: true, + admin: { + description: 'PNG eller SVG, helst med transparent bakgrund.', + }, + }, + { + name: 'alt', + type: 'text', + label: 'Alt-text', + admin: { + description: 'Beskriv certifieringen för tillgänglighet, t.ex. "ISO 27001 certifierad".', + }, + }, + { + name: 'linkUrl', + type: 'text', + label: 'Länk (valfri)', + admin: { + description: 'Länk till certifieringens webbsida (öppnas i ny flik).', + }, + }, + ], + }, + + // ── Sociala medier ──────────────────────────────────────────────────────── + { + name: 'socialLinks', + type: 'group', + label: 'Sociala medier', + admin: { + description: 'Ikoner visas längst ner i footern och i mobilmenyn. Aktivera och ange URL för varje plattform.', + }, + fields: [ + // LinkedIn + { + name: 'linkedinEnabled', + type: 'checkbox', + label: 'Visa LinkedIn', + defaultValue: false, + }, + { + name: 'linkedinUrl', + type: 'text', + label: 'LinkedIn URL', + admin: { + condition: (_, siblingData) => Boolean(siblingData?.linkedinEnabled), + description: 'T.ex. https://www.linkedin.com/company/fiber-direkt', + }, + }, + // Instagram + { + name: 'instagramEnabled', + type: 'checkbox', + label: 'Visa Instagram', + defaultValue: false, + }, + { + name: 'instagramUrl', + type: 'text', + label: 'Instagram URL', + admin: { + condition: (_, siblingData) => Boolean(siblingData?.instagramEnabled), + }, + }, + // Facebook + { + name: 'facebookEnabled', + type: 'checkbox', + label: 'Visa Facebook', + defaultValue: false, + }, + { + name: 'facebookUrl', + type: 'text', + label: 'Facebook URL', + admin: { + condition: (_, siblingData) => Boolean(siblingData?.facebookEnabled), + }, + }, + // YouTube + { + name: 'youtubeEnabled', + type: 'checkbox', + label: 'Visa YouTube', + defaultValue: false, + }, + { + name: 'youtubeUrl', + type: 'text', + label: 'YouTube URL', + admin: { + condition: (_, siblingData) => Boolean(siblingData?.youtubeEnabled), + }, + }, + // Twitter / X + { + name: 'twitterEnabled', + type: 'checkbox', + label: 'Visa X (Twitter)', + defaultValue: false, + }, + { + name: 'twitterUrl', + type: 'text', + label: 'X (Twitter) URL', + admin: { + condition: (_, siblingData) => Boolean(siblingData?.twitterEnabled), + }, + }, + ], + }, + // ── Sitemap columns ─────────────────────────────────────────────────────── { name: 'columns', @@ -91,13 +219,13 @@ export const Footer: GlobalConfig = { ], }, - // ── Simple nav (legacy) ─────────────────────────────────────────────────── + // ── Simple nav (legacy / bottom links) ──────────────────────────────────── { name: 'navItems', type: 'array', - label: 'Enkel navigering (äldre)', + label: 'Nedre navigering', admin: { - description: 'Enkla footer-länkar (visas om inga kolumner finns)', + description: 'Länkar i underfältet (visas som rad bredvid copyright, t.ex. Policys · Legal · Villkor)', initCollapsed: true, }, fields: [ @@ -105,25 +233,19 @@ export const Footer: GlobalConfig = { appearances: false, }), ], - maxRows: 6, + maxRows: 8, }, // ── Bottom text ─────────────────────────────────────────────────────────── { name: 'bottomLeftText', type: 'text', - label: 'Nedre vänster text', + label: 'Copyrighttext (nedre vänster)', defaultValue: '© {year} Fiber Direkt. Alla rättigheter förbehållna.', admin: { description: 'Använd {year} för aktuellt årtal', }, }, - { - name: 'bottomRightText', - type: 'text', - label: 'Nedre höger text', - defaultValue: 'Svenskt datacenter · ISO 27001 · ISO 14001', - }, ], hooks: { afterChange: [revalidateFooter], diff --git a/src/Header/Component.client.tsx b/src/Header/Component.client.tsx index 4aa6fbf..4dbd5c2 100644 --- a/src/Header/Component.client.tsx +++ b/src/Header/Component.client.tsx @@ -5,12 +5,14 @@ import { usePathname } from 'next/navigation' import React, { useEffect, useState } from 'react' import type { Header, Page } from '@/payload-types' +import type { SocialLinkData } from '@/components/SocialIcons' import { Logo } from '@/components/Logo/Logo' import { HeaderNav } from './Nav' interface HeaderClientProps { data: Header + socialLinks?: SocialLinkData[] } function resolveLogoHref(logoLink: Header['logoLink']): string { @@ -26,7 +28,7 @@ function resolveLogoHref(logoLink: Header['logoLink']): string { return logoLink.url || '/' } -export const HeaderClient: React.FC = ({ data }) => { +export const HeaderClient: React.FC = ({ data, socialLinks = [] }) => { const [theme, setTheme] = useState(null) const [isDark, setIsDark] = useState(false) const { headerTheme, setHeaderTheme } = useHeaderTheme() @@ -59,9 +61,9 @@ export const HeaderClient: React.FC = ({ data }) => { >
- + - +
) diff --git a/src/Header/Component.tsx b/src/Header/Component.tsx index c10659f..388edb0 100644 --- a/src/Header/Component.tsx +++ b/src/Header/Component.tsx @@ -2,10 +2,15 @@ import { HeaderClient } from './Component.client' import { getCachedGlobal } from '@/utilities/getGlobals' import React from 'react' -import type { Header } from '@/payload-types' +import type { Header, Footer } from '@/payload-types' +import { buildSocialLinks } from '@/Footer/Component' export async function Header() { - const headerData = await getCachedGlobal("header", 1)() as unknown as Header + const headerData = await getCachedGlobal('header', 1)() as unknown as Header - return + // Also fetch footer social links so the mobile menu can show them + const footerData = await getCachedGlobal('footer', 1)() as unknown as Footer + const socialLinks = buildSocialLinks(footerData?.socialLinks) + + return } diff --git a/src/Header/Nav/index.tsx b/src/Header/Nav/index.tsx index 792bc40..11483dc 100644 --- a/src/Header/Nav/index.tsx +++ b/src/Header/Nav/index.tsx @@ -4,6 +4,8 @@ import type { Header as HeaderType, Page } from '@/payload-types' import Link from 'next/link' import { usePathname } from 'next/navigation' import { MenuIcon, XIcon, ChevronDownIcon, ChevronRightIcon } from 'lucide-react' +import { SocialIconsRow } from '@/components/SocialIcons' +import type { SocialLinkData } from '@/components/SocialIcons' type NavChild = NonNullable[number]['children']>[number]> type NavItem = NonNullable[number] @@ -142,7 +144,10 @@ const MegaMenuPanel: React.FC<{ ) } -export const HeaderNav: React.FC<{ data: HeaderType }> = ({ data }) => { +export const HeaderNav: React.FC<{ data: HeaderType; socialLinks?: SocialLinkData[] }> = ({ + data, + socialLinks = [], +}) => { const navItems = (data?.navItems || []) as NavItem[] const [mobileOpen, setMobileOpen] = useState(false) const [openDropdown, setOpenDropdown] = useState(null) @@ -280,7 +285,7 @@ export const HeaderNav: React.FC<{ data: HeaderType }> = ({ data }) => { {mobileOpen ? : } - {/* ── Mobile full-screen overlay — always navy, no dark: needed ── */} + {/* ── Mobile full-screen overlay ── */}
= ({ data }) => { aria-modal="true" aria-label="Navigeringsmeny" > + {/* Header bar */}
Fiber Direkt @@ -302,6 +308,7 @@ export const HeaderNav: React.FC<{ data: HeaderType }> = ({ data }) => {
+ {/* Nav links */} + + {/* ── Social icons row at bottom of mobile menu ── */} + {socialLinks.length > 0 && ( +
+ +
+ )}
) diff --git a/src/app/(frontend)/not-found.tsx b/src/app/(frontend)/not-found.tsx index 411cc77..c1c5a78 100644 --- a/src/app/(frontend)/not-found.tsx +++ b/src/app/(frontend)/not-found.tsx @@ -9,8 +9,8 @@ export const metadata: Metadata = { export default function NotFound() { return ( -
-
+
+
{/* ── Left: Text content ─────────────────────────────────────── */} @@ -48,10 +48,9 @@ export default function NotFound() {
- {/* ── Right: Modem SVG illustration ──────────────────────────── */} + {/* ── Right: Modem SVG illustration — desktop only ───────────── */}
- -
diff --git a/src/components/AnnouncementBar/AnnouncementBar.tsx b/src/components/AnnouncementBar/AnnouncementBar.tsx index 682b358..e6c996b 100644 --- a/src/components/AnnouncementBar/AnnouncementBar.tsx +++ b/src/components/AnnouncementBar/AnnouncementBar.tsx @@ -59,24 +59,29 @@ export const AnnouncementBarComponent: React.FC = ({ return (
- {text} - {buttonLabel && href && ( - - {buttonLabel} - - )} + {/* Centered content — px-10 leaves room for the dismiss button on both sides */} +
+ {text} + {buttonLabel && href && ( + + {buttonLabel} + + )} +
+ + {/* Dismiss — truly absolute so it doesn't affect centering */} {dismissible && (