forked from UKSOURCE/hailearning.edu.vn
feat: refactor service API schema and update layout structure
This commit is contained in:
@@ -8,7 +8,6 @@
|
||||
======================= */
|
||||
|
||||
export interface ServiceItem {
|
||||
id: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
@@ -58,7 +57,6 @@ export interface DestinationSection {
|
||||
subTitle: string;
|
||||
mainTitle: string;
|
||||
};
|
||||
items: CountryItem[];
|
||||
}
|
||||
|
||||
export interface VisaItem {
|
||||
@@ -90,11 +88,6 @@ export interface ReviewSection {
|
||||
subTitle: string;
|
||||
mainTitle: string;
|
||||
};
|
||||
viewAllButton: {
|
||||
text: string;
|
||||
icon: string;
|
||||
link: string;
|
||||
};
|
||||
thumb: string;
|
||||
items: ClientReviewItem[];
|
||||
}
|
||||
@@ -251,7 +244,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
|
||||
},
|
||||
items: [
|
||||
{
|
||||
id: "immigration-appeal",
|
||||
slug: "immigration-appeal",
|
||||
name: "Immigration Appeal & Legal Support",
|
||||
description:
|
||||
@@ -311,7 +303,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "scholarship-guidance",
|
||||
slug: "scholarship-guidance",
|
||||
name: "Scholarship & Study Grant Guidance",
|
||||
description:
|
||||
@@ -373,7 +364,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "permanent-residency",
|
||||
slug: "permanent-residency",
|
||||
name: "Permanent Residency (PR) Services",
|
||||
description:
|
||||
@@ -435,7 +425,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "citizenship-naturalization",
|
||||
slug: "citizenship-naturalization",
|
||||
name: "Citizenship & Naturalization Guidance",
|
||||
description:
|
||||
@@ -505,52 +494,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
|
||||
subTitle: "Countries we offer",
|
||||
mainTitle: "Choose Your Immigration Destination",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
id: "canada",
|
||||
name: "Canada",
|
||||
description:
|
||||
"Canada provides quality education, rich and global opportunities",
|
||||
image: "/assets/img/home-3/choose-us/01.jpg",
|
||||
icon: "/assets/img/home-3/choose-us/icon-1.png",
|
||||
link: "/countries/canada",
|
||||
},
|
||||
{
|
||||
id: "south-korea",
|
||||
name: "South Korea",
|
||||
description:
|
||||
"South Korea offers advanced technology and cultural experiences",
|
||||
image: "/assets/img/home-3/choose-us/02.jpg",
|
||||
icon: "/assets/img/home-3/choose-us/icon-2.png",
|
||||
link: "/countries/south-korea",
|
||||
},
|
||||
{
|
||||
id: "france",
|
||||
name: "France",
|
||||
description: "France offers unique cultural experiences and education",
|
||||
image: "/assets/img/home-3/choose-us/03.jpg",
|
||||
icon: "/assets/img/home-3/choose-us/icon-3.png",
|
||||
link: "/countries/france",
|
||||
},
|
||||
{
|
||||
id: "uk",
|
||||
name: "UK",
|
||||
description:
|
||||
"UK provides world-class education and career opportunities",
|
||||
image: "/assets/img/home-3/choose-us/04.jpg",
|
||||
icon: "/assets/img/home-3/choose-us/icon-2.png",
|
||||
link: "/countries/uk",
|
||||
},
|
||||
{
|
||||
id: "germany",
|
||||
name: "Germany",
|
||||
description:
|
||||
"Germany offers excellent education and work opportunities",
|
||||
image: "/assets/img/home-3/choose-us/05.jpg",
|
||||
icon: "/assets/img/home-3/choose-us/icon-3.png",
|
||||
link: "/countries/germany",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
visas: {
|
||||
@@ -590,11 +533,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
|
||||
subTitle: "What Our Clients Say",
|
||||
mainTitle: "Immigration Success Stories",
|
||||
},
|
||||
viewAllButton: {
|
||||
text: "View All Review",
|
||||
icon: "fa-solid fa-arrow-right",
|
||||
link: "/contact",
|
||||
},
|
||||
thumb: "/assets/img/home-3/test-thumb.jpg",
|
||||
items: [
|
||||
{
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
|
||||
import Header from "./components/Header";
|
||||
import Footer from "./components/Footer";
|
||||
// import Header from "./components/Header";
|
||||
// import Footer from "./components/Footer";
|
||||
import Loader from "./components/Loader";
|
||||
import BackToTop from "./components/BackToTop";
|
||||
import MouseCursor from "./components/MouseCursor";
|
||||
@@ -24,7 +23,7 @@ export default function RootLayout({
|
||||
<head>
|
||||
{/* Favicon */}
|
||||
<link rel="shortcut icon" href="/assets/img/favicon.png" />
|
||||
|
||||
|
||||
{/* Bootstrap min.css */}
|
||||
<link rel="stylesheet" href="/assets/css/bootstrap.min.css" />
|
||||
{/* All Min Css */}
|
||||
@@ -35,7 +34,7 @@ export default function RootLayout({
|
||||
<link rel="stylesheet" href="/assets/css/magnific-popup.css" />
|
||||
{/* MeanMenu.css */}
|
||||
<link rel="stylesheet" href="/assets/css/meanmenu.css" />
|
||||
{/* Odometer.css */}
|
||||
{/* Odometer.css */}
|
||||
<link rel="stylesheet" href="/assets/css/odometer.css" />
|
||||
{/* Swiper Bundle.css */}
|
||||
<link rel="stylesheet" href="/assets/css/swiper-bundle.min.css" />
|
||||
@@ -44,22 +43,37 @@ export default function RootLayout({
|
||||
{/* Main.css */}
|
||||
<link rel="stylesheet" href="/assets/css/main.css" />
|
||||
</head>
|
||||
<body className="smooth-scroll-yes">
|
||||
<body className="smooth-scroll-yes">
|
||||
<Loader />
|
||||
<BackToTop />
|
||||
<MouseCursor />
|
||||
<Header />
|
||||
|
||||
{/* <Header /> */}
|
||||
|
||||
{children}
|
||||
|
||||
<Footer />
|
||||
|
||||
{/* <Footer /> */}
|
||||
|
||||
{/* Scripts */}
|
||||
<Script src="/assets/js/jquery-3.7.1.min.js" strategy="beforeInteractive" />
|
||||
<Script src="/assets/js/viewport.jquery.js" strategy="afterInteractive" />
|
||||
<Script src="/assets/js/bootstrap.bundle.min.js" strategy="afterInteractive" />
|
||||
<Script src="/assets/js/jquery.nice-select.min.js" strategy="afterInteractive" />
|
||||
<Script src="/assets/js/jquery.waypoints.js" strategy="afterInteractive" />
|
||||
<Script
|
||||
src="/assets/js/jquery-3.7.1.min.js"
|
||||
strategy="beforeInteractive"
|
||||
/>
|
||||
<Script
|
||||
src="/assets/js/viewport.jquery.js"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script
|
||||
src="/assets/js/bootstrap.bundle.min.js"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script
|
||||
src="/assets/js/jquery.nice-select.min.js"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script
|
||||
src="/assets/js/jquery.waypoints.js"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
<Script src="/assets/js/odometer.min.js" strategy="afterInteractive" />
|
||||
<Script
|
||||
src="/assets/js/swiper-bundle.min.js"
|
||||
|
||||
@@ -16,7 +16,6 @@ export async function generateMetadata({
|
||||
try {
|
||||
const { slug } = await params;
|
||||
const data = await fetchServiceBySlug(slug);
|
||||
|
||||
if (!data || !data.serviceDetails) {
|
||||
return {
|
||||
title: "Service Not Found",
|
||||
@@ -112,6 +111,10 @@ export default async function ServiceDetailsPage({
|
||||
<div className="accordion" id="accordionExample">
|
||||
{faq.items.map((faqItem: any, index: number) => {
|
||||
const isExpanded = faqItem.isExpanded;
|
||||
const questionNumber = String(index + 1).padStart(
|
||||
2,
|
||||
"0",
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={`faq-${index}`}
|
||||
@@ -132,7 +135,7 @@ export default async function ServiceDetailsPage({
|
||||
}
|
||||
aria-controls={`collapse${index}`}
|
||||
>
|
||||
{faqItem.question}
|
||||
{questionNumber}. {faqItem.question}
|
||||
</button>
|
||||
</h5>
|
||||
<div
|
||||
|
||||
@@ -11,6 +11,51 @@ export const metadata: Metadata = {
|
||||
export default async function ServicesPage() {
|
||||
const data = await fetchServicePageData();
|
||||
const { services, destinations, visas, reviews } = data;
|
||||
const country = [
|
||||
{
|
||||
id: "canada",
|
||||
name: "Canada",
|
||||
description:
|
||||
"Canada provides quality education, rich culture and global opportunities",
|
||||
image: "img/home-3/choose-us/01.jpg",
|
||||
icon: "img/home-3/choose-us/icon-1.png",
|
||||
link: "country-details.html",
|
||||
},
|
||||
{
|
||||
id: "south-korea",
|
||||
name: "South Korea",
|
||||
description:
|
||||
"South Korea offers advanced technology and cultural experiences",
|
||||
image: "img/home-3/choose-us/02.jpg",
|
||||
icon: "img/home-3/choose-us/icon-2.png",
|
||||
link: "country-details.html",
|
||||
},
|
||||
{
|
||||
id: "france",
|
||||
name: "France",
|
||||
description:
|
||||
"France offers rich cultural heritage and educational excellence",
|
||||
image: "img/home-3/choose-us/03.jpg",
|
||||
icon: "img/home-3/choose-us/icon-3.png",
|
||||
link: "country-details.html",
|
||||
},
|
||||
{
|
||||
id: "uk",
|
||||
name: "UK",
|
||||
description: "UK provides world-class education and career opportunities",
|
||||
image: "img/home-3/choose-us/04.jpg",
|
||||
icon: "img/home-3/choose-us/icon-2.png",
|
||||
link: "country-details.html",
|
||||
},
|
||||
{
|
||||
id: "germany",
|
||||
name: "Germany",
|
||||
description: "Germany offers excellent education and strong economy",
|
||||
image: "img/home-3/choose-us/05.jpg",
|
||||
icon: "img/home-3/choose-us/icon-3.png",
|
||||
link: "country-details.html",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -99,7 +144,7 @@ export default async function ServicesPage() {
|
||||
</h2>
|
||||
</div>
|
||||
<div className="destination-offer-wrapper-3 fade-up-anim row g-4 g-xl-4 row-cols-xl-5 row-cols-lg-4 row-cols-md-2 row-cols-1">
|
||||
{destinations.items.map((country: any) => (
|
||||
{country.map((country: any) => (
|
||||
<div key={country.id} className="col destination-offer-item">
|
||||
<div className="choose-us-image">
|
||||
<img src={imageUrl(country.image)} alt="img" />
|
||||
@@ -122,7 +167,7 @@ export default async function ServicesPage() {
|
||||
</section>
|
||||
|
||||
{/* Service-Visa Section Start */}
|
||||
<section className="service-visa-section fix">
|
||||
{/* <section className="service-visa-section fix">
|
||||
<div className="container">
|
||||
<div className="service-visa-wrapper">
|
||||
{visas.items.map((visa: any, index: number) => (
|
||||
@@ -144,7 +189,7 @@ export default async function ServicesPage() {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</section> */}
|
||||
|
||||
{/* Testimonial Section3 Start */}
|
||||
<section className="testimonial-section section-padding fix">
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
export const imageUrl = (path?: string) => {
|
||||
if (!path) return "";
|
||||
// Không có ảnh → ảnh mặc định
|
||||
if (!path) return "/_images/default.jpg";
|
||||
|
||||
// Đã là full URL
|
||||
if (path.startsWith("http")) return path;
|
||||
return `${process.env.NEXT_PUBLIC_API_URL}/${path}`;
|
||||
|
||||
const base = (
|
||||
process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001"
|
||||
).replace(/\/$/, "");
|
||||
|
||||
return `${base}/${path.replace(/^\//, "")}`;
|
||||
};
|
||||
|
||||
BIN
public/_images/default.jpg
Normal file
BIN
public/_images/default.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Reference in New Issue
Block a user