SFS/components/footer.tsx
2025-12-16 10:03:26 +05:30

358 lines
17 KiB
TypeScript

"use client"
import Image from "next/image"
import Link from "next/link"
import { useEffect, useState } from "react"
import { ensureHttpsImageUrl } from "@/lib/utils"
interface QuickLink {
label: string
href: string
}
interface ContactItem {
svg: string
text: string
link: string | null
}
interface SocialIcon {
name: string
svg: string
link?: string | null
}
export default function Footer() {
const [logoUrl, setLogoUrl] = useState("")
const [quickLinks, setQuickLinks] = useState<QuickLink[]>([])
const [quickLinksHeading, setQuickLinksHeading] = useState("Quick Links")
const [resourcesLinks, setResourcesLinks] = useState<QuickLink[]>([])
const [resourcesHeading, setResourcesHeading] = useState("Resources")
const [contactHeading, setContactHeading] = useState("Contact")
const [contactItems, setContactItems] = useState<ContactItem[]>([])
const [tagline, setTagline] = useState("Transforming education through innovative technology solutions.")
const [copyright, setCopyright] = useState("© 2025 School For Schools. All rights reserved.")
const [privacyPolicy, setPrivacyPolicy] = useState("")
const [socialIcons, setSocialIcons] = useState<SocialIcon[]>([])
useEffect(() => {
const loadFooterData = async () => {
try {
// Use Next.js API route instead of direct Strapi calls
const res = await fetch("/api/footer", {
headers: { Accept: "application/json" },
})
if (!res.ok) {
throw new Error(`Footer API status ${res.status}`)
}
const data = await res.json()
// Load Quick Links
if (data.quickLinks) {
const json = data.quickLinks
const items = Array.isArray(json?.data) ? json.data : []
if (items.length > 0) {
const item = items[0]
const attrs = item?.attributes || item || {}
if (attrs.heading) setQuickLinksHeading(attrs.heading)
const links: QuickLink[] = []
let index = 1
while (true) {
const labelKey = `list${index}` as keyof typeof attrs
const linkKey = `list${index}_link` as keyof typeof attrs
const label = attrs[labelKey]
const href = attrs[linkKey]
if (!label || !href) break
links.push({ label: String(label), href: String(href) })
index++
}
if (links.length > 0) setQuickLinks(links)
}
}
// Load Resources Links
if (data.resources) {
const json = data.resources
const items = Array.isArray(json?.data) ? json.data : []
if (items.length > 0) {
const item = items[0]
const attrs = item?.attributes || item || {}
if (attrs.heading) setResourcesHeading(attrs.heading)
const links: QuickLink[] = []
let index = 1
while (true) {
const labelKey = `resource${index}` as keyof typeof attrs
const linkKey = `resource${index}_link` as keyof typeof attrs
const label = attrs[labelKey]
const href = attrs[linkKey]
if (!label || !href) break
links.push({ label: String(label), href: String(href) })
index++
}
if (links.length > 0) setResourcesLinks(links)
}
}
// Load Contact Links
if (data.contact) {
const json = data.contact
const items = Array.isArray(json?.data) ? json.data : []
if (items.length > 0) {
const item = items[0]
const attrs = item?.attributes || item || {}
if (attrs.heading) setContactHeading(attrs.heading)
const contacts: ContactItem[] = []
if (attrs.email && attrs.email_svg) {
contacts.push({
svg: attrs.email_svg,
text: attrs.email,
link: attrs.email_link || `mailto:${attrs.email}`,
})
}
if (attrs.phone && attrs.phone_svg) {
contacts.push({
svg: attrs.phone_svg,
text: attrs.phone,
link: attrs.phone_link || `tel:${attrs.phone.replace(/\D/g, '')}`,
})
}
if (attrs.address && attrs.address_svg) {
contacts.push({
svg: attrs.address_svg,
text: attrs.address,
link: attrs.address_link || null,
})
}
if (contacts.length > 0) setContactItems(contacts)
}
}
// Load Tagline
if (data.logoTag) {
const json = data.logoTag
const item = json?.data
if (item) {
const attrs = item?.attributes || item || {}
const taglineText = attrs?.tag_line || attrs?.tagline || ""
if (taglineText) setTagline(taglineText)
}
}
// Load Privacy Policy
if (data.privacy) {
const json = data.privacy
const item = json?.data
if (item) {
const attrs = item?.attributes || item || {}
const privacyText = attrs?.privacy_policy || ""
if (privacyText) setPrivacyPolicy(privacyText)
}
}
// Load Social Icons
if (data.socialIcons) {
const json = data.socialIcons
const items = Array.isArray(json?.data) ? json.data : []
const icons: SocialIcon[] = items.map((item: any) => {
const attrs = item?.attributes || item || {}
return {
name: attrs?.company_name || "",
svg: attrs?.company_svg || "",
link: attrs?.company_link || null,
}
}).filter((icon: SocialIcon) => icon.name && icon.svg)
if (icons.length > 0) setSocialIcons(icons)
}
// Load Logo
if (data.logo) {
const json = data.logo
const logoData = json?.data?.logo || json?.data?.attributes?.logo?.data || json?.data?.attributes?.logo
if (logoData) {
const logo = Array.isArray(logoData) ? logoData[0] : logoData
const logoObj = logo?.attributes || logo
const url =
logoObj?.url ||
logoObj?.formats?.small?.url ||
logoObj?.formats?.thumbnail?.url ||
logoObj?.formats?.medium?.url ||
logoObj?.formats?.large?.url ||
""
if (url) {
// Ensure HTTPS to prevent Mixed Content errors
const absolute = ensureHttpsImageUrl(url)
setLogoUrl(absolute)
}
}
}
} catch (err) {
console.error("Failed to load footer data:", err)
}
}
loadFooterData()
}, [])
return (
<footer className="w-full bg-[#525252] text-white py-6 md:py-8 lg:py-12 px-4 md:px-8">
<div className="max-w-[1400px] mx-auto grid grid-cols-1 md:grid-cols-4 gap-4 md:gap-6 lg:gap-8">
{/* Column 1: Logo & Description */}
<div className="flex flex-col items-start gap-2 md:gap-3 lg:gap-4">
{logoUrl && (
<div className="relative w-auto" style={{ minHeight: "32px" }}>
<Image
src={logoUrl}
alt="School For Schools"
width={200}
height={48}
className="h-8 md:h-10 lg:h-12 object-contain"
style={{ width: "auto", maxWidth: "200px" }}
/>
</div>
)}
<p className="text-gray-300 text-xs leading-relaxed max-w-xs">
{tagline}
</p>
</div>
{/* Column 2: Quick Links */}
{quickLinks.length > 0 && (
<div className="flex flex-col gap-2 md:gap-3 lg:gap-4">
<h3 className="text-sm font-semibold text-white uppercase tracking-wider">
{quickLinksHeading}
</h3>
<ul className="flex flex-col gap-1 md:gap-1.5 lg:gap-2">
{quickLinks.map((link, index) => (
<li key={`${link.label}-${index}`}>
<Link
href={link.href}
className="text-gray-300 text-xs md:text-sm hover:text-[#F48120] transition-colors"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
)}
{/* Column 3: Resources */}
{resourcesLinks.length > 0 && (
<div className="flex flex-col gap-2 md:gap-3 lg:gap-4">
<h3 className="text-sm font-semibold text-white uppercase tracking-wider">
{resourcesHeading}
</h3>
<ul className="flex flex-col gap-1 md:gap-1.5 lg:gap-2">
{resourcesLinks.map((link, index) => (
<li key={`${link.label}-${index}`}>
<Link
href={link.href}
className="text-gray-300 text-xs md:text-sm hover:text-[#F48120] transition-colors"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
)}
{/* Column 4: Contact */}
{contactItems.length > 0 && (
<div className="flex flex-col gap-2 md:gap-3 lg:gap-4">
<h3 className="text-sm font-semibold text-white uppercase tracking-wider">
{contactHeading}
</h3>
<ul className="flex flex-col gap-2 md:gap-2.5 lg:gap-3">
{contactItems.map((contact, index) => (
<li key={`contact-${index}`} className="flex items-start gap-3">
<span
dangerouslySetInnerHTML={{ __html: contact.svg }}
className="shrink-0"
/>
{contact.link ? (
<a
href={contact.link}
className="text-gray-300 text-xs md:text-sm hover:text-[#F48120] transition-colors"
>
{contact.text}
</a>
) : (
<span className="text-gray-300 text-xs md:text-sm max-w-[200px]">
{contact.text}
</span>
)}
</li>
))}
</ul>
</div>
)}
</div>
{/* Footer Bottom Line */}
<div className="max-w-[1400px] mx-auto mt-6 md:mt-8 lg:mt-12 pt-4 md:pt-5 lg:pt-6 border-t border-gray-300">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
{/* Left: Copyright */}
<div className="flex-1 text-left">
<p className="text-gray-400 text-xs">
{copyright}
</p>
</div>
{/* Center: Social Media Icons */}
{socialIcons.length > 0 && (
<div className="flex items-center justify-center gap-4 flex-1">
{socialIcons.map((icon, index) => (
<a
key={`social-${index}`}
href={icon.link || "#"}
className="flex items-center justify-center hover:opacity-80 transition-opacity"
aria-label={icon.name}
>
<span
dangerouslySetInnerHTML={{ __html: icon.svg }}
className="w-6 h-6"
/>
</a>
))}
</div>
)}
{/* Right: Privacy Policy Links */}
{privacyPolicy && (
<div className="flex-1 text-right">
<div className="text-gray-400 text-xs flex flex-wrap gap-x-1 justify-end">
{privacyPolicy.match(/([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/g)?.map((policy, index, array) => {
const trimmed = policy.trim()
if (!trimmed) return null
return (
<span key={index}>
<Link
href="#"
className="hover:text-[#F48120] transition-colors"
>
{trimmed}
</Link>
{index < array.length - 1 && " "}
</span>
)
}) || privacyPolicy.split(" ").map((word, index, array) => (
<span key={index}>
<Link
href="#"
className="hover:text-[#F48120] transition-colors"
>
{word}
</Link>
{index < array.length - 1 && " "}
</span>
))}
</div>
</div>
)}
</div>
</div>
</footer>
)
}