feat: refactor service API schema and update layout structure

This commit is contained in:
nguyenvanbao
2026-02-03 16:24:55 +07:00
parent a2a215dfd7
commit adef27b214
6 changed files with 92 additions and 84 deletions

View File

@@ -8,7 +8,6 @@
======================= */ ======================= */
export interface ServiceItem { export interface ServiceItem {
id: string;
slug: string; slug: string;
name: string; name: string;
description: string; description: string;
@@ -58,7 +57,6 @@ export interface DestinationSection {
subTitle: string; subTitle: string;
mainTitle: string; mainTitle: string;
}; };
items: CountryItem[];
} }
export interface VisaItem { export interface VisaItem {
@@ -90,11 +88,6 @@ export interface ReviewSection {
subTitle: string; subTitle: string;
mainTitle: string; mainTitle: string;
}; };
viewAllButton: {
text: string;
icon: string;
link: string;
};
thumb: string; thumb: string;
items: ClientReviewItem[]; items: ClientReviewItem[];
} }
@@ -251,7 +244,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
}, },
items: [ items: [
{ {
id: "immigration-appeal",
slug: "immigration-appeal", slug: "immigration-appeal",
name: "Immigration Appeal & Legal Support", name: "Immigration Appeal & Legal Support",
description: description:
@@ -311,7 +303,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
}, },
}, },
{ {
id: "scholarship-guidance",
slug: "scholarship-guidance", slug: "scholarship-guidance",
name: "Scholarship & Study Grant Guidance", name: "Scholarship & Study Grant Guidance",
description: description:
@@ -373,7 +364,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
}, },
}, },
{ {
id: "permanent-residency",
slug: "permanent-residency", slug: "permanent-residency",
name: "Permanent Residency (PR) Services", name: "Permanent Residency (PR) Services",
description: description:
@@ -435,7 +425,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
}, },
}, },
{ {
id: "citizenship-naturalization",
slug: "citizenship-naturalization", slug: "citizenship-naturalization",
name: "Citizenship & Naturalization Guidance", name: "Citizenship & Naturalization Guidance",
description: description:
@@ -505,52 +494,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
subTitle: "Countries we offer", subTitle: "Countries we offer",
mainTitle: "Choose Your Immigration Destination", 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: { visas: {
@@ -590,11 +533,6 @@ export const getFallbackServicePageData = (): ServicePageData => ({
subTitle: "What Our Clients Say", subTitle: "What Our Clients Say",
mainTitle: "Immigration Success Stories", 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", thumb: "/assets/img/home-3/test-thumb.jpg",
items: [ items: [
{ {

View File

@@ -1,9 +1,8 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import "./globals.css"; import "./globals.css";
// import Header from "./components/Header";
import Header from "./components/Header"; // import Footer from "./components/Footer";
import Footer from "./components/Footer";
import Loader from "./components/Loader"; import Loader from "./components/Loader";
import BackToTop from "./components/BackToTop"; import BackToTop from "./components/BackToTop";
import MouseCursor from "./components/MouseCursor"; import MouseCursor from "./components/MouseCursor";
@@ -48,18 +47,33 @@ export default function RootLayout({
<Loader /> <Loader />
<BackToTop /> <BackToTop />
<MouseCursor /> <MouseCursor />
<Header /> {/* <Header /> */}
{children} {children}
<Footer /> {/* <Footer /> */}
{/* Scripts */} {/* Scripts */}
<Script src="/assets/js/jquery-3.7.1.min.js" strategy="beforeInteractive" /> <Script
<Script src="/assets/js/viewport.jquery.js" strategy="afterInteractive" /> src="/assets/js/jquery-3.7.1.min.js"
<Script src="/assets/js/bootstrap.bundle.min.js" strategy="afterInteractive" /> strategy="beforeInteractive"
<Script src="/assets/js/jquery.nice-select.min.js" strategy="afterInteractive" /> />
<Script src="/assets/js/jquery.waypoints.js" strategy="afterInteractive" /> <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/odometer.min.js" strategy="afterInteractive" />
<Script <Script
src="/assets/js/swiper-bundle.min.js" src="/assets/js/swiper-bundle.min.js"

View File

@@ -16,7 +16,6 @@ export async function generateMetadata({
try { try {
const { slug } = await params; const { slug } = await params;
const data = await fetchServiceBySlug(slug); const data = await fetchServiceBySlug(slug);
if (!data || !data.serviceDetails) { if (!data || !data.serviceDetails) {
return { return {
title: "Service Not Found", title: "Service Not Found",
@@ -112,6 +111,10 @@ export default async function ServiceDetailsPage({
<div className="accordion" id="accordionExample"> <div className="accordion" id="accordionExample">
{faq.items.map((faqItem: any, index: number) => { {faq.items.map((faqItem: any, index: number) => {
const isExpanded = faqItem.isExpanded; const isExpanded = faqItem.isExpanded;
const questionNumber = String(index + 1).padStart(
2,
"0",
);
return ( return (
<div <div
key={`faq-${index}`} key={`faq-${index}`}
@@ -132,7 +135,7 @@ export default async function ServiceDetailsPage({
} }
aria-controls={`collapse${index}`} aria-controls={`collapse${index}`}
> >
{faqItem.question} {questionNumber}. {faqItem.question}
</button> </button>
</h5> </h5>
<div <div

View File

@@ -11,6 +11,51 @@ export const metadata: Metadata = {
export default async function ServicesPage() { export default async function ServicesPage() {
const data = await fetchServicePageData(); const data = await fetchServicePageData();
const { services, destinations, visas, reviews } = data; 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 ( return (
<> <>
@@ -99,7 +144,7 @@ export default async function ServicesPage() {
</h2> </h2>
</div> </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"> <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 key={country.id} className="col destination-offer-item">
<div className="choose-us-image"> <div className="choose-us-image">
<img src={imageUrl(country.image)} alt="img" /> <img src={imageUrl(country.image)} alt="img" />
@@ -122,7 +167,7 @@ export default async function ServicesPage() {
</section> </section>
{/* Service-Visa Section Start */} {/* Service-Visa Section Start */}
<section className="service-visa-section fix"> {/* <section className="service-visa-section fix">
<div className="container"> <div className="container">
<div className="service-visa-wrapper"> <div className="service-visa-wrapper">
{visas.items.map((visa: any, index: number) => ( {visas.items.map((visa: any, index: number) => (
@@ -144,7 +189,7 @@ export default async function ServicesPage() {
))} ))}
</div> </div>
</div> </div>
</section> </section> */}
{/* Testimonial Section3 Start */} {/* Testimonial Section3 Start */}
<section className="testimonial-section section-padding fix"> <section className="testimonial-section section-padding fix">

View File

@@ -1,5 +1,13 @@
export const imageUrl = (path?: string) => { 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; 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB