145 lines
5.3 KiB
TypeScript
145 lines
5.3 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { Lightbulb, BookOpen, Bot, Users, TrendingUp } from "lucide-react"
|
|
|
|
import heroFallback from "./hero-fallback.json"
|
|
// JSON file is empty by default, so assert the expected shape to avoid `never`
|
|
const fallbackData: ApiCard[] = heroFallback as ApiCard[]
|
|
|
|
// Icons mapping to match the order of API response
|
|
const ICONS = [Lightbulb, BookOpen, Bot, Users, TrendingUp]
|
|
|
|
type ApiCard = {
|
|
cardtitle?: string
|
|
carddescription?: string
|
|
svg_icon?: string | null
|
|
}
|
|
|
|
type ApiResponse = {
|
|
heading?: string
|
|
description?: string
|
|
cards?: ApiCard[]
|
|
}
|
|
|
|
export default function HeroSection() {
|
|
const [features, setFeatures] = useState<{ icon?: any; svg?: string; title: string; description: string }[]>([])
|
|
const [heading, setHeading] = useState("Why SFS")
|
|
const [description, setDescription] = useState("We combine expertise in education with cutting-edge technology to deliver solutions that truly make a difference.")
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
try {
|
|
const response = await fetch("/api/whysfs")
|
|
if (!response.ok) throw new Error("API failed")
|
|
|
|
const json: ApiResponse = await response.json()
|
|
|
|
if (json.heading) setHeading(json.heading)
|
|
if (json.description) setDescription(json.description)
|
|
|
|
const mappedFeatures = (json.cards ?? []).map((item: ApiCard, index: number) => ({
|
|
svg: item.svg_icon || undefined,
|
|
icon: ICONS[index % ICONS.length], // fallback icon if svg missing
|
|
title: item.cardtitle || "Feature",
|
|
description: item.carddescription || "",
|
|
}))
|
|
|
|
// Swap positions: move 5th to 2nd and 2nd to 5th when available
|
|
if (mappedFeatures.length >= 5) {
|
|
const temp = mappedFeatures[1]
|
|
mappedFeatures[1] = mappedFeatures[4]
|
|
mappedFeatures[4] = temp
|
|
}
|
|
|
|
if (mappedFeatures.length === 0) {
|
|
throw new Error("No data found")
|
|
}
|
|
|
|
setFeatures(mappedFeatures)
|
|
} catch (error) {
|
|
console.warn("Failed to fetch Why SFS content, using fallback:", error)
|
|
// Use Fallback
|
|
const mappedFeatures = fallbackData.map((item, index) => ({
|
|
icon: ICONS[index % ICONS.length],
|
|
title: item.cardtitle || "Feature",
|
|
description: item.carddescription || "",
|
|
}))
|
|
|
|
if (mappedFeatures.length >= 5) {
|
|
const temp = mappedFeatures[1]
|
|
mappedFeatures[1] = mappedFeatures[4]
|
|
mappedFeatures[4] = temp
|
|
}
|
|
setFeatures(mappedFeatures)
|
|
}
|
|
}
|
|
|
|
fetchData()
|
|
}, [])
|
|
|
|
if (features.length === 0) {
|
|
return (
|
|
<section className="w-full bg-white py-12 md:py-20 lg:py-24">
|
|
<div className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="text-center mb-12 md:mb-16 lg:mb-20">
|
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-[#353535] mb-6 leading-tight">{heading}</h1>
|
|
{/* Loading state placeholder */}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<section className="w-full bg-white py-12 md:py-20 lg:py-24">
|
|
<div className="max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8">
|
|
{/* Hero Content */}
|
|
<div className="text-center mb-12 md:mb-16 lg:mb-20">
|
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-[#353535] mb-6 leading-tight">{heading}</h1>
|
|
<p className="text-base md:text-lg text-[#353535] max-w-2xl mx-auto leading-relaxed">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Features Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 bg-white">
|
|
{features.map((feature, index) => {
|
|
return (
|
|
<div
|
|
key={index}
|
|
className="group bg-[#E8E8E8] hover:bg-[#F48120] p-6 flex flex-col justify-start transition-colors duration-300 min-h-[220px]"
|
|
>
|
|
<div className="mb-6">
|
|
<div className="w-16 h-16 flex items-center justify-start">
|
|
{feature.svg ? (
|
|
<span
|
|
aria-hidden
|
|
dangerouslySetInnerHTML={{ __html: feature.svg }}
|
|
className="block w-14 h-14 text-[#353535] group-hover:text-white transition-colors duration-300 [&>svg]:w-full [&>svg]:h-full [&>svg]:fill-none [&>svg]:stroke-current [&>svg]:shrink-0 [&>svg]:[stroke-width:1]"
|
|
/>
|
|
) : (
|
|
feature.icon && (
|
|
<feature.icon
|
|
strokeWidth={1}
|
|
className="w-14 h-14 text-[#353535] group-hover:text-white transition-colors duration-300 shrink-0"
|
|
/>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
<h3 className="text-lg font-bold mb-4 leading-tight text-[#353535] group-hover:text-white transition-colors duration-300">
|
|
{feature.title}
|
|
</h3>
|
|
<p className="text-sm leading-relaxed text-[#353535]/80 group-hover:text-white/90 transition-colors duration-300">
|
|
{feature.description}
|
|
</p>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|