forked from UKSOURCE/hailearning.edu.vn
feat: Refactor home page components
This commit is contained in:
@@ -46,11 +46,11 @@ const FAQSection = ({ data }: FAQSectionProps) => {
|
|||||||
<div key={index} className="accordion-item wow fadeInUp" data-wow-delay={`.${(index + 1) * 2}s`}>
|
<div key={index} className="accordion-item wow fadeInUp" data-wow-delay={`.${(index + 1) * 2}s`}>
|
||||||
<h5 className="accordion-header" id={`heading${index}`}>
|
<h5 className="accordion-header" id={`heading${index}`}>
|
||||||
<button
|
<button
|
||||||
className={`accordion-button ${index !== 1 ? 'collapsed' : ''}`}
|
className="accordion-button collapsed"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="collapse"
|
data-bs-toggle="collapse"
|
||||||
data-bs-target={`#collapse${index}`}
|
data-bs-target={`#collapse${index}`}
|
||||||
aria-expanded={index === 1 ? 'true' : 'false'}
|
aria-expanded="false"
|
||||||
aria-controls={`collapse${index}`}
|
aria-controls={`collapse${index}`}
|
||||||
>
|
>
|
||||||
{item.question}
|
{item.question}
|
||||||
@@ -58,7 +58,7 @@ const FAQSection = ({ data }: FAQSectionProps) => {
|
|||||||
</h5>
|
</h5>
|
||||||
<div
|
<div
|
||||||
id={`collapse${index}`}
|
id={`collapse${index}`}
|
||||||
className={`accordion-collapse collapse ${index === 1 ? 'show' : ''}`}
|
className="accordion-collapse collapse"
|
||||||
aria-labelledby={`heading${index}`}
|
aria-labelledby={`heading${index}`}
|
||||||
data-bs-parent="#accordionExample"
|
data-bs-parent="#accordionExample"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { getCmsImageUrl } from '@/utils/image';
|
import { getCmsImageUrl } from '@/utils/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
interface HeroSectionProps {
|
interface HeroSlide {
|
||||||
data: {
|
|
||||||
title: string;
|
title: string;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -14,12 +13,48 @@ interface HeroSectionProps {
|
|||||||
label: string;
|
label: string;
|
||||||
href: string;
|
href: string;
|
||||||
};
|
};
|
||||||
backgroundImage: string;
|
heroImage?: string;
|
||||||
videoUrl: string;
|
videoUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HeroSectionProps {
|
||||||
|
data: {
|
||||||
|
backgroundImage: string;
|
||||||
|
// Optional multi-slide support from CMS
|
||||||
|
slides?: HeroSlide[];
|
||||||
|
// Legacy single-slide fields (fallback)
|
||||||
|
title?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
description?: string;
|
||||||
|
primaryButton?: {
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
secondaryButton?: {
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
|
};
|
||||||
|
heroImage?: string;
|
||||||
|
videoUrl?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeroSection = ({ data }: HeroSectionProps) => {
|
const HeroSection = ({ data }: HeroSectionProps) => {
|
||||||
|
const slides: HeroSlide[] =
|
||||||
|
(data.slides && data.slides.length > 0)
|
||||||
|
? data.slides
|
||||||
|
: [{
|
||||||
|
title: data.title || '',
|
||||||
|
subtitle: data.subtitle || '',
|
||||||
|
description: data.description || '',
|
||||||
|
primaryButton: data.primaryButton || { label: '', href: '#' },
|
||||||
|
secondaryButton: data.secondaryButton || { label: '', href: '#' },
|
||||||
|
heroImage: data.heroImage,
|
||||||
|
videoUrl: data.videoUrl || '',
|
||||||
|
}];
|
||||||
|
|
||||||
|
const firstSlide = slides[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="hero-section hero-1 fix bg-cover" style={{ backgroundImage: `url('${getCmsImageUrl(data.backgroundImage)}')` }}>
|
<section className="hero-section hero-1 fix bg-cover" style={{ backgroundImage: `url('${getCmsImageUrl(data.backgroundImage)}')` }}>
|
||||||
<div className="left-shape">
|
<div className="left-shape">
|
||||||
@@ -49,46 +84,51 @@ const HeroSection = ({ data }: HeroSectionProps) => {
|
|||||||
<div className="col-lg-6">
|
<div className="col-lg-6">
|
||||||
<div className="swiper hero-slider">
|
<div className="swiper hero-slider">
|
||||||
<div className="swiper-wrapper">
|
<div className="swiper-wrapper">
|
||||||
<div className="swiper-slide">
|
{slides.map((slide, index) => (
|
||||||
|
<div className="swiper-slide" key={index}>
|
||||||
<div className="hero-content">
|
<div className="hero-content">
|
||||||
<h6>{data.subtitle}</h6>
|
<h6>{slide.subtitle}</h6>
|
||||||
<h1>
|
<h1>
|
||||||
{data.title}
|
{slide.title}
|
||||||
<a href={data.videoUrl} className="video-btn video-popup">
|
{slide.videoUrl && (
|
||||||
|
<a href={slide.videoUrl} className="video-btn video-popup">
|
||||||
<i className="fa-solid fa-play"></i>
|
<i className="fa-solid fa-play"></i>
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
{data.description}
|
{slide.description}
|
||||||
</p>
|
</p>
|
||||||
<div className="hero-button">
|
<div className="hero-button">
|
||||||
<Link href={data.primaryButton.href} className="theme-btn">
|
{slide.primaryButton?.href && (
|
||||||
{data.primaryButton.label}
|
<Link href={slide.primaryButton.href} className="theme-btn">
|
||||||
|
{slide.primaryButton.label}
|
||||||
<i className="fa-solid fa-arrow-right"></i>
|
<i className="fa-solid fa-arrow-right"></i>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={data.secondaryButton.href} className="theme-btn style-2">
|
)}
|
||||||
{data.secondaryButton.label}
|
{slide.secondaryButton?.href && (
|
||||||
|
<Link href={slide.secondaryButton.href} className="theme-btn style-2">
|
||||||
|
{slide.secondaryButton.label}
|
||||||
<i className="fa-solid fa-arrow-right"></i>
|
<i className="fa-solid fa-arrow-right"></i>
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-lg-6">
|
<div className="col-lg-6">
|
||||||
<div className="swiper image-slider">
|
<div className="swiper image-slider">
|
||||||
<div className="swiper-wrapper">
|
<div className="swiper-wrapper">
|
||||||
<div className="swiper-slide">
|
{slides.map((slide, index) => (
|
||||||
|
<div className="swiper-slide" key={index}>
|
||||||
<div className="hero-image">
|
<div className="hero-image">
|
||||||
<img src="/assets/img/home-1/hero/man.png" alt="img" />
|
<img src={slide.heroImage || firstSlide.heroImage || "/assets/img/home-1/hero/man.png"} alt="img" />
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="swiper-slide">
|
|
||||||
<div className="hero-image">
|
|
||||||
<img src="/assets/img/home-1/hero/man.png" alt="img" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ import Link from 'next/link';
|
|||||||
interface WhyChooseUsProps {
|
interface WhyChooseUsProps {
|
||||||
data: {
|
data: {
|
||||||
heading: string;
|
heading: string;
|
||||||
|
highlightWord?: string;
|
||||||
subheading: string;
|
subheading: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
mainImage?: string;
|
||||||
|
secondaryImage?: string;
|
||||||
items: {
|
items: {
|
||||||
icon: string;
|
icon: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -19,6 +22,25 @@ interface WhyChooseUsProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const WhyChooseUs = ({ data }: WhyChooseUsProps) => {
|
const WhyChooseUs = ({ data }: WhyChooseUsProps) => {
|
||||||
|
const highlight = data.highlightWord?.trim();
|
||||||
|
|
||||||
|
let headingContent: React.ReactNode = data.heading;
|
||||||
|
|
||||||
|
if (highlight) {
|
||||||
|
const index = data.heading.indexOf(highlight);
|
||||||
|
if (index !== -1) {
|
||||||
|
const before = data.heading.slice(0, index);
|
||||||
|
const after = data.heading.slice(index + highlight.length);
|
||||||
|
headingContent = (
|
||||||
|
<>
|
||||||
|
{before}
|
||||||
|
<span>{highlight}</span>
|
||||||
|
{after}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="about-section section-padding fix pb-0">
|
<section className="about-section section-padding fix pb-0">
|
||||||
<div className="top-shape">
|
<div className="top-shape">
|
||||||
@@ -29,9 +51,9 @@ const WhyChooseUs = ({ data }: WhyChooseUsProps) => {
|
|||||||
<div className="row g-4">
|
<div className="row g-4">
|
||||||
<div className="col-lg-6">
|
<div className="col-lg-6">
|
||||||
<div className="about-image">
|
<div className="about-image">
|
||||||
<img src="/assets/img/home-1/about/about-1.jpg" alt="img" className="wow img-custom-anim-left" />
|
<img src={data.mainImage || "/assets/img/home-1/about/about-1.jpg"} alt="img" className="wow img-custom-anim-left" />
|
||||||
<div className="about-image-2">
|
<div className="about-image-2">
|
||||||
<img src="/assets/img/home-1/about/about-02.jpg" alt="img" className="wow img-custom-anim-right" />
|
<img src={data.secondaryImage || "/assets/img/home-1/about/about-02.jpg"} alt="img" className="wow img-custom-anim-right" />
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-shape">
|
<div className="bg-shape">
|
||||||
<img src="/assets/img/home-1/about/Vector.png" alt="img" />
|
<img src="/assets/img/home-1/about/Vector.png" alt="img" />
|
||||||
@@ -49,7 +71,7 @@ const WhyChooseUs = ({ data }: WhyChooseUsProps) => {
|
|||||||
<div className="section-title mb-0">
|
<div className="section-title mb-0">
|
||||||
<span className="sub-title wow fadeInUp">{data.subheading}</span>
|
<span className="sub-title wow fadeInUp">{data.subheading}</span>
|
||||||
<h2 className="split-text-right split-text-in-right">
|
<h2 className="split-text-right split-text-in-right">
|
||||||
{data.heading.split(' Dreams ')[0]} <span>Dreams</span> {data.heading.split(' Dreams ')[1]}
|
{headingContent}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text wow fadeInUp" data-wow-delay=".3s">
|
<p className="text wow fadeInUp" data-wow-delay=".3s">
|
||||||
|
|||||||
@@ -22,7 +22,13 @@ const FooterBottom = () => {
|
|||||||
loadFooterData();
|
loadFooterData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { bottom } = data;
|
// Ensure we always have a valid `bottom` object, even if API shape changes
|
||||||
|
const bottom = data?.bottom || footerData.bottom;
|
||||||
|
|
||||||
|
// If bottom is still missing, avoid rendering to prevent runtime errors
|
||||||
|
if (!bottom) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="footer-bottom">
|
<div className="footer-bottom">
|
||||||
|
|||||||
@@ -22,10 +22,19 @@ const FooterTop = () => {
|
|||||||
loadFooterData();
|
loadFooterData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { top } = data;
|
// Ensure we always have a valid `top` object, even if API shape changes
|
||||||
|
const top = data?.top || footerData.top;
|
||||||
|
|
||||||
|
// If for some reason `top` is still missing, avoid rendering to prevent runtime errors
|
||||||
|
if (!top) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="footer-section fix bg-cover" style={{ backgroundImage: `url('${top.bgImage}')` }}>
|
<footer
|
||||||
|
className="footer-section fix bg-cover"
|
||||||
|
style={top.bgImage ? { backgroundImage: `url('${top.bgImage}')` } : undefined}
|
||||||
|
>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="footer-wrapper">
|
<div className="footer-wrapper">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
|
||||||
"hero": {
|
"hero": {
|
||||||
|
"backgroundImage": "/assets/img/home-1/hero/bg.jpg",
|
||||||
|
"slides": [
|
||||||
|
{
|
||||||
"title": "From Application to Visa – We've Got You Covered",
|
"title": "From Application to Visa – We've Got You Covered",
|
||||||
"subtitle": "Global Education Simplified",
|
"subtitle": "Global Education Simplified",
|
||||||
"description": "We guide you through every step of the education visa process, from initial application to final approval, ensuring a smooth, hassle-free journey.",
|
"description": "We guide you through every step of the education visa process, from initial application to final approval, ensuring a smooth, hassle-free journey.",
|
||||||
@@ -12,11 +15,16 @@
|
|||||||
"label": "Book Free Consultation",
|
"label": "Book Free Consultation",
|
||||||
"href": "/contact"
|
"href": "/contact"
|
||||||
},
|
},
|
||||||
"backgroundImage": "/assets/img/home-1/hero/bg.jpg",
|
"heroImage": "/assets/img/home-1/hero/man.png",
|
||||||
"videoUrl": "https://www.youtube.com/watch?v=Cn4G2lZ_g2I"
|
"videoUrl": "https://www.youtube.com/watch?v=Cn4G2lZ_g2I"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"whyChooseUs": {
|
"whyChooseUs": {
|
||||||
"heading": "Turning Study Abroad Dreams Into Reality",
|
"heading": "Turning Study Abroad Dreams Into Reality",
|
||||||
|
"highlightWord": "Dreams",
|
||||||
|
"mainImage": "/assets/img/home-1/about/about-1.jpg",
|
||||||
|
"secondaryImage": "/assets/img/home-1/about/about-02.jpg",
|
||||||
"subheading": "About Our Consultancy",
|
"subheading": "About Our Consultancy",
|
||||||
"description": "We guide students with expert visa consulting, ensuring a smooth process from application to approval, turning study abroad aspirations into life-changing opportunities for a brighter future.",
|
"description": "We guide students with expert visa consulting, ensuring a smooth process from application to approval, turning study abroad aspirations into life-changing opportunities for a brighter future.",
|
||||||
"items": [
|
"items": [
|
||||||
|
|||||||
Reference in New Issue
Block a user