refactor json header, footer, home and styling ui

This commit is contained in:
2026-02-03 12:09:24 +07:00
parent 1add9d4d9d
commit 5b29928f16
11 changed files with 1476 additions and 502 deletions

View File

@@ -28,7 +28,7 @@ const VisaSolutions = ({ data }: VisaSolutionsProps) => {
<div key={index} className={`service-wrapper ${index === 1 ? 'active' : ''}`}>
<div className="container">
<div className="service-item">
<div className="image-hover d-none d-md-block bg-cover" style={{ backgroundImage: "url('/assets/img/home-1/hover-bg.jpg')" }}></div>
<div className="left-item">
<h5 className="number">{item.number}</h5>
<h3>

View File

@@ -1,23 +1,22 @@
import Link from 'next/link';
import footerData from './footer.json';
const FooterBottom = () => {
const { bottom } = footerData;
return (
<div className="footer-bottom">
<div className="container">
<div className="footer-wrapper">
<p>
Copyright© <span>GRAMENTHEME</span> All Rights Reserved.
{bottom.copyright.text} <span>{bottom.copyright.brand}</span> {bottom.copyright.rights}
</p>
<ul className="bottom-list">
<li>
<Link href="/contact">Terms & Conditions</Link>
</li>
<li>
<Link href="/contact">Privacy Policy</Link>
</li>
<li>
<Link href="/contact">Contact Us</Link>
</li>
{bottom.menuLinks.map((item, index) => (
<li key={index}>
<Link href={item.href}>{item.label}</Link>
</li>
))}
</ul>
</div>
</div>

View File

@@ -1,46 +1,37 @@
import Link from 'next/link';
import footerData from './footer.json';
const FooterTop = () => {
const { top } = footerData;
return (
<footer className="footer-section fix bg-cover" style={{ backgroundImage: "url('/assets/img/home-1/footer-bg.jpg')" }}>
<footer className="footer-section fix bg-cover" style={{ backgroundImage: `url('${top.bgImage}')` }}>
<div className="container">
<div className="footer-wrapper">
<div className="row">
<div className="col-xl-12">
<div className="footer-item">
<h2>
<a href="tel:+16336547896">+84 961 83 4040</a>
<a href={top.phone.href}>{top.phone.display}</a>
</h2>
<h2 className="text">734 Luy Ban Bich St, Tan Thanh Ward, Tan Phu Dist, HCMC</h2>
<h2 className="text">{top.address}</h2>
<div className="footer-list-item">
<Link href="/">
<img src="/assets/img/logo/white-logo.svg" alt="img" />
<Link href={top.logo.href}>
<img src={top.logo.src} alt={top.logo.alt} />
</Link>
<ul className="footer-list">
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/about">About Us</Link>
</li>
<li>
<Link href="/country-details">Visa</Link>
</li>
<li>
<Link href="/news-details">Pages</Link>
</li>
<li>
<Link href="/news">Article</Link>
</li>
<li>
<Link href="/contact">Contact Us</Link>
</li>
{top.menuLinks.map((item, index) => (
<li key={index}>
<Link href={item.href}>{item.label}</Link>
</li>
))}
</ul>
<div className="social-icon">
<a href="#"><i className="fa-brands fa-twitter"></i></a>
<a href="#"><i className="fa-brands fa-instagram"></i></a>
<a href="#"><i className="fa-brands fa-linkedin"></i></a>
<a href="#"><i className="fa-brands fa-youtube"></i></a>
{top.socialLinks.map((social, index) => (
<a key={index} href={social.href}>
<i className={social.icon}></i>
</a>
))}
</div>
</div>
</div>

View File

@@ -0,0 +1,80 @@
{
"top": {
"bgImage": "/assets/img/home-1/footer-bg.jpg",
"phone": {
"display": "+84 961 83 4040",
"href": "tel:+84961834040"
},
"address": "734 Luy Ban Bich St, Tan Thanh Ward, Tan Phu Dist, HCMC",
"logo": {
"src": "/assets/img/logo/white-logo.svg",
"alt": "logo",
"href": "/"
},
"menuLinks": [
{
"label": "Home",
"href": "/"
},
{
"label": "About Us",
"href": "/about"
},
{
"label": "Visa",
"href": "/country-details"
},
{
"label": "Pages",
"href": "/news-details"
},
{
"label": "Article",
"href": "/news"
},
{
"label": "Contact Us",
"href": "/contact"
}
],
"socialLinks": [
{
"icon": "fa-brands fa-twitter",
"href": "#"
},
{
"icon": "fa-brands fa-instagram",
"href": "#"
},
{
"icon": "fa-brands fa-linkedin",
"href": "#"
},
{
"icon": "fa-brands fa-youtube",
"href": "#"
}
]
},
"bottom": {
"copyright": {
"text": "Copyright©",
"brand": "GRAMENTHEME",
"rights": "All Rights Reserved."
},
"menuLinks": [
{
"label": "Terms & Conditions",
"href": "/contact"
},
{
"label": "Privacy Policy",
"href": "/contact"
},
{
"label": "Contact Us",
"href": "/contact"
}
]
}
}

View File

@@ -1,14 +1,36 @@
'use client';
import { useState } from 'react';
import HeaderTop from './HeaderTop';
import HeaderBottom from './HeaderBottom';
import Offcanvas from './Offcanvas';
const Header = () => {
const [isOffcanvasOpen, setIsOffcanvasOpen] = useState(false);
const [isSearchOpen, setIsSearchOpen] = useState(false);
const toggleOffcanvas = () => setIsOffcanvasOpen(!isOffcanvasOpen);
const toggleSearch = () => setIsSearchOpen(!isSearchOpen);
return (
<>
<HeaderTop />
<HeaderBottom />
{/* Search Popup - kept here for now as part of header logic/structure */}
<div className="search-popup">
<div className="search-popup__overlay search-toggler"></div>
<HeaderBottom
onToggleOffcanvas={toggleOffcanvas}
onToggleSearch={toggleSearch}
/>
<Offcanvas
isOpen={isOffcanvasOpen}
onClose={() => setIsOffcanvasOpen(false)}
/>
{/* Search Popup */}
<div className={`search-popup ${isSearchOpen ? 'active' : ''}`}>
<div
className="search-popup__overlay search-toggler"
onClick={() => setIsSearchOpen(false)}
></div>
<div className="search-popup__content">
<form role="search" method="get" className="search-popup__form" action="#">
<input type="text" id="search" name="search" placeholder="Search Here..." />

View File

@@ -3,116 +3,50 @@
import Link from 'next/link';
import HeaderMenu from './HeaderMenu';
const homeMegaMenu = (
<>
<div className="homemenu">
<div className="homemenu-thumb">
<img src="/assets/img/header/home-1.jpg" alt="img" />
<div className="demo-button">
<Link href="/" className="theme-btn">
Multi Page <i className="fa-solid fa-arrow-right"></i>
</Link>
</div>
</div>
<div className="homemenu-content text-center">
<h4 className="homemenu-title">
Home 01
</h4>
</div>
</div>
<div className="homemenu">
<div className="homemenu-thumb mb-15">
<img src="/assets/img/header/home-2.jpg" alt="img" />
<div className="demo-button">
<Link href="/index-2" className="theme-btn">
Multi Page <i className="fa-solid fa-arrow-right"></i>
</Link>
</div>
</div>
<div className="homemenu-content text-center">
<h4 className="homemenu-title">
Home 02
</h4>
</div>
</div>
<div className="homemenu">
<div className="homemenu-thumb mb-15">
<img src="/assets/img/header/home-3.jpg" alt="img" />
<div className="demo-button">
<Link href="/index-3" className="theme-btn">
Multi Page <i className="fa-solid fa-arrow-right"></i>
</Link>
</div>
</div>
<div className="homemenu-content text-center">
<h4 className="homemenu-title">
Home 03
</h4>
</div>
</div>
</>
);
import headerData from './header.json';
const menuItems = [
{
label: 'Home',
href: '/',
megaMenuContent: homeMegaMenu
},
{
label: 'About Us',
href: '/about'
},
{
label: 'Pages',
href: '#',
submenu: [
{
label: 'Service',
href: '#',
submenu: [
{ label: 'Service', href: '/service' },
{ label: 'Service Details', href: '/service-details' }
]
},
{
label: 'Country List',
href: '#',
submenu: [
{ label: 'Country List', href: '/country-list' },
{ label: 'Country Details', href: '/country-details' }
]
},
{ label: 'Our Pricing', href: '/pricing' },
{ label: 'Appointment', href: '/appointment' },
{ label: '404 Page', href: '/404' },
{ label: 'Coming Soon', href: '/coming-soon' }
]
},
{
label: 'VISA',
href: '#',
submenu: [
{ label: 'Visa List', href: '/country-list' },
{ label: 'Visa Details', href: '/country-details' }
]
},
{
label: 'Blog',
href: '#',
submenu: [
{ label: 'Blog Grid', href: '/news-grid' },
{ label: 'Blog Standard', href: '/news' },
{ label: 'Blog Details', href: '/news-details' }
]
},
{
label: 'Contact Us',
href: '/contact'
}
];
const HeaderBottom = () => {
// Map the JSON data to satisfy the HeaderMenu props interface
interface JsonMenuItem {
label: string;
href: string;
children?: JsonMenuItem[];
}
interface MenuItem {
label: string;
href: string;
submenu?: MenuItem[];
megaMenuContent?: React.ReactNode;
}
// We need to recursively map 'children' to 'submenu'
const mapMenuItems = (items: JsonMenuItem[]): MenuItem[] => {
return items.map(item => {
const newItem: MenuItem = {
label: item.label,
href: item.href,
};
if (item.children && item.children.length > 0) {
newItem.submenu = mapMenuItems(item.children);
}
return newItem;
});
};
const menuItems: MenuItem[] = mapMenuItems(headerData.menu as JsonMenuItem[]);
interface HeaderBottomProps {
onToggleOffcanvas: () => void;
onToggleSearch: () => void;
}
const HeaderBottom: React.FC<HeaderBottomProps> = ({ onToggleOffcanvas, onToggleSearch }) => {
return (
<header id="header-sticky" className="header-1">
<div className="container-fluid">
@@ -130,15 +64,22 @@ const HeaderBottom = () => {
</div>
<div className="header-right d-flex align-items-center mt-0">
<div className="header-call-item">
<a href="#" className="main-header__search search-toggler">
<button
onClick={onToggleSearch}
className="main-header__search search-toggler"
>
<i className="fa-regular fa-magnifying-glass"></i>
</a>
</button>
<Link href="/contact" className="theme-btn">
Apply now
<i className="fa-solid fa-arrow-right"></i>
</Link>
<div className="header__hamburger my-auto">
<div className="sidebar__toggle">
<div
className="sidebar__toggle"
onClick={onToggleOffcanvas}
style={{ cursor: 'pointer' }}
>
<i className="fa-solid fa-bars-staggered"></i>
</div>
</div>

View File

@@ -1,29 +1,32 @@
import Link from 'next/link';
import headerData from './header.json';
const HeaderTop = () => {
const { phone, email, location, socialLinks, languages } = headerData.top;
return (
<div className="header-top-section">
<div className="container-fluid">
<div className="header-top-wrapper">
<div className="header-left">
<ul className="list d-flex align-items-center mb-0 p-0 list-unstyled">
<li className="style-2 d-flex align-items-center me-4">
<span className="me-2">Help Line</span>
<i className="fa-solid fa-phone me-2"></i>
<a href="tel:+84961834040">+84 961 83 4040</a>
</li>
<ul className="list d-flex align-items-center mb-0 p-0 list-unstyled">
<li className="style-2 d-flex align-items-center me-4">
<span className="me-2">Help Line</span>
<i className="fa-solid fa-phone me-2"></i>
<a href={`tel:${phone.replace(/\s/g, '')}`}>{phone}</a>
</li>
<li className="d-flex align-items-center me-4">
<i className="fa-solid fa-location-dot me-2"></i>
734 Luy Ban Bich St, Tan Thanh Ward, Tan Phu Dist, HCMC
</li>
<li className="d-flex align-items-center me-4">
<i className="fa-solid fa-location-dot me-2"></i>
{location}
</li>
<li className="d-flex align-items-center">
<i className="fa-solid fa-envelope me-2"></i>
<a href="mailto:get-info@hai.edu.vn">get-info@hai.edu.vn</a>
</li>
</ul>
</div>
<li className="d-flex align-items-center">
<i className="fa-solid fa-envelope me-2"></i>
<a href={`mailto:${email}`}>{email}</a>
</li>
</ul>
</div>
<div className="header-right">
<div className="flag-wrap">
@@ -32,26 +35,27 @@ const HeaderTop = () => {
</div>
<div className="nice-select" tabIndex={0}>
<span className="current">
English
{languages[0]?.name || 'English'}
</span>
<ul className="list">
<li data-value="1" className="option selected focus">
English
</li>
<li data-value="1" className="option">
Bangla
</li>
<li data-value="1" className="option">
Hindi
</li>
{languages.map((lang, index) => (
<li
key={index}
data-value={lang.value}
className={`option ${index === 0 ? 'selected focus' : ''}`}
>
{lang.name}
</li>
))}
</ul>
</div>
</div>
<div className="social-item">
<a href="#"><i className="fa-brands fa-linkedin"></i></a>
<a href="#"><i className="fa-brands fa-twitter"></i></a>
<a href="#"><i className="fa-brands fa-instagram"></i></a>
<a href="#"><i className="fa-brands fa-youtube"></i></a>
{socialLinks.map((social, index) => (
<a key={index} href={social.url} target="_blank" rel="noopener noreferrer">
<i className={social.icon}></i>
</a>
))}
</div>
</div>
</div>

View File

@@ -0,0 +1,150 @@
'use client';
import React from 'react';
import Image from 'next/image';
import Link from 'next/link';
import headerData from './header.json';
interface SocialLink {
platform: string;
url: string;
icon: string;
}
interface MenuItem {
label: string;
href: string;
children?: MenuItem[];
}
interface OffcanvasProps {
isOpen: boolean;
onClose: () => void;
}
const Offcanvas: React.FC<OffcanvasProps> = ({ isOpen, onClose }) => {
// Explicitly casting headerData to the expected structure
const data = headerData as {
top: {
socialLinks: SocialLink[];
};
offcanvas: {
description: string;
contactInfo: {
address: string;
email: string;
workingHours: string;
phone: string;
};
};
menu: MenuItem[];
};
const { offcanvas, top, menu } = data;
return (
<>
<div className="fix-area">
<div className={`offcanvas__info ${isOpen ? 'info-open' : ''}`}>
<div className="offcanvas__wrapper">
<div className="offcanvas__content">
<div className="offcanvas__top mb-5 d-flex justify-content-between align-items-center">
<div className="offcanvas__logo">
<Link href="/">
<Image
src="/assets/img/logo/black-logo.svg"
alt="logo-img"
width={150}
height={50}
priority
/>
</Link>
</div>
<div className="offcanvas__close">
<button onClick={onClose} aria-label="Close menu">
<i className="fas fa-times"></i>
</button>
</div>
</div>
<p className="text d-none d-xl-block">
{offcanvas.description}
</p>
{/* Mobile Menu Area */}
<div className="mobile-menu fix mb-3 d-xl-none">
<nav className="mean-nav">
<ul>
{menu.map((item: MenuItem, idx: number) => (
<li key={idx}>
<Link href={item.href} onClick={onClose}>
{item.label}
</Link>
</li>
))}
</ul>
</nav>
</div>
<div className="offcanvas__contact d-xl-block">
<h4 className="d-xl-block">Contact Info</h4>
<ul className="d-xl-block">
<li className="d-flex align-items-center">
<div className="offcanvas__contact-icon">
<i className="fal fa-map-marker-alt"></i>
</div>
<div className="offcanvas__contact-text">
<a target="_blank" href="#" rel="noopener noreferrer">
{offcanvas.contactInfo.address}
</a>
</div>
</li>
<li className="d-flex align-items-center">
<div className="offcanvas__contact-icon mr-15">
<i className="fal fa-envelope"></i>
</div>
<div className="offcanvas__contact-text">
<a href={`mailto:${offcanvas.contactInfo.email}`}>
<span>{offcanvas.contactInfo.email}</span>
</a>
</div>
</li>
<li className="d-flex align-items-center">
<div className="offcanvas__contact-icon mr-15">
<i className="fal fa-clock"></i>
</div>
<div className="offcanvas__contact-text">
<span>{offcanvas.contactInfo.workingHours}</span>
</div>
</li>
<li className="d-flex align-items-center">
<div className="offcanvas__contact-icon mr-15">
<i className="far fa-phone"></i>
</div>
<div className="offcanvas__contact-text">
<a href={`tel:${offcanvas.contactInfo.phone.replace(/\s/g, '')}`}>
{offcanvas.contactInfo.phone}
</a>
</div>
</li>
</ul>
<div className="social-icon d-flex align-items-center">
{top.socialLinks.map((social: SocialLink, idx: number) => (
<a key={idx} href={social.url} target="_blank" rel="noopener noreferrer">
<i className={social.icon}></i>
</a>
))}
</div>
</div>
</div>
</div>
</div>
</div>
<div
className={`offcanvas__overlay ${isOpen ? 'overlay-open' : ''}`}
onClick={onClose}
></div>
</>
);
};
export default Offcanvas;

View File

@@ -0,0 +1,142 @@
{
"top": {
"phone": "+09 378 357 5222",
"email": "info@hailearning.edu.vn",
"location": "69 Street, 5th Avenue LA, United States",
"socialLinks": [
{
"platform": "linkedin",
"url": "https://linkedin.com",
"icon": "fa-brands fa-linkedin"
},
{
"platform": "twitter",
"url": "https://twitter.com",
"icon": "fa-brands fa-twitter"
},
{
"platform": "instagram",
"url": "https://instagram.com",
"icon": "fa-brands fa-instagram"
},
{
"platform": "youtube",
"url": "https://youtube.com",
"icon": "fa-brands fa-youtube"
}
],
"languages": [
{
"name": "English",
"value": "1"
},
{
"name": "Bangla",
"value": "2"
},
{
"name": "Hindi",
"value": "3"
}
]
},
"offcanvas": {
"description": "Nullam dignissim, ante scelerisque the is euismod fermentum odio sem semper the is erat, a feugiat leo urna eget eros. Duis Aenean a imperdiet risus.",
"contactInfo": {
"address": "Main Street, Melbourne, Australia",
"email": "info@hailearning.edu.vn",
"workingHours": "Mod-Friday, 09am - 05pm",
"phone": "+09 378 357 5222"
}
},
"menu": [
{
"label": "Home",
"href": "/",
"children": []
},
{
"label": "About Us",
"href": "/about",
"children": []
},
{
"label": "Pages",
"href": "#",
"children": [
{
"label": "Service",
"href": "/service",
"children": [
{
"label": "Service",
"href": "/service"
},
{
"label": "Service Details",
"href": "/service-details"
}
]
},
{
"label": "Country List",
"href": "/country-list",
"children": [
{
"label": "Country List",
"href": "/country-list"
},
{
"label": "Country Details",
"href": "/country-details"
}
]
},
{
"label": "Our Pricing",
"href": "/pricing"
},
{
"label": "Appointment",
"href": "/appointment"
}
]
},
{
"label": "VISA",
"href": "#",
"children": [
{
"label": "Visa List",
"href": "/visa-list"
},
{
"label": "Visa Details",
"href": "/visa-details"
}
]
},
{
"label": "Blog",
"href": "#",
"children": [
{
"label": "Blog Grid",
"href": "/blog-grid"
},
{
"label": "Blog Standard",
"href": "/blog"
},
{
"label": "Blog Details",
"href": "/blog-details"
}
]
},
{
"label": "Contact Us",
"href": "/contact"
}
]
}