From f4529d1e90ddf6292ab2cb5ca7e3732b70802ea3 Mon Sep 17 00:00:00 2001 From: nguyenvanbao Date: Wed, 4 Feb 2026 14:42:54 +0700 Subject: [PATCH] feat: add country API integration and image fallback component --- api/servicesApi.ts | 118 +++++++++++++++++++++++++++ app/components/ImageWithFallback.tsx | 40 +++++++++ app/services/page.tsx | 81 +++++++----------- 3 files changed, 188 insertions(+), 51 deletions(-) create mode 100644 app/components/ImageWithFallback.tsx diff --git a/api/servicesApi.ts b/api/servicesApi.ts index 1fcf7dc..9bae705 100644 --- a/api/servicesApi.ts +++ b/api/servicesApi.ts @@ -100,6 +100,20 @@ export interface ServicePageData { reviews: ReviewSection; } +export interface Country { + id: number; + name: string; + slug: string; + mainImage: string; + icon: string; + services: string[]; +} + +export interface CountryApiResponse { + success: boolean; + data: Country[]; +} + /* ======================= Utils ======================= */ @@ -230,6 +244,43 @@ export const fetchServiceBySlug = async (slug: string): Promise => { } }; +export const fetchCountries = async (): Promise => { + try { + const apiUrl = getApiUrl(); + const endpoint = `${apiUrl}/api/visa/country`; + + console.log("Fetching countries from endpoint:", endpoint); + + const response = await fetch(endpoint, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + cache: "no-store", + }); + + console.log("Countries API response status:", response.status); + + if (!response.ok) { + console.error("Countries API failed, using fallback data"); + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = (await response.json()) as CountryApiResponse; + + if (!result.success) { + throw new Error("Countries API returned success=false"); + } + + console.log("Countries data received successfully"); + return result.data; + } catch (error) { + console.error("Error fetching countries:", error); + console.log("Using fallback countries data"); + return getFallbackCountries(); + } +}; + /* ======================= Fallback Data ======================= */ @@ -560,3 +611,70 @@ export const getFallbackServicePageData = (): ServicePageData => ({ ], }, }); + +export const getFallbackCountries = (): Country[] => [ + { + id: 1, + name: "Canada", + slug: "canada", + mainImage: "_images/default.jpg", + icon: "img/home-3/choose-us/icon-1.png", + services: ["Immigration Appeal", "Permanent Residency", "Study Visa"], + }, + { + id: 2, + name: "Australia", + slug: "australia", + mainImage: "_images/default.jpg", + icon: "img/home-3/choose-us/icon-2.png", + services: ["Work Visa", "Permanent Residency", "Student Visa"], + }, + { + id: 3, + name: "United Kingdom", + slug: "united-kingdom", + mainImage: "_images/default.jpg", + icon: "img/home-3/choose-us/icon-3.png", + services: ["Study Visa", "Work Visa", "Family Visa"], + }, + { + id: 4, + name: "United States", + slug: "united-states", + mainImage: "_images/default.jpg", + icon: "img/home-3/choose-us/icon-1.png", + services: ["Immigration Appeal", "Work Visa", "Student Visa"], + }, + { + id: 5, + name: "Germany", + slug: "germany", + mainImage: "_images/default.jpg", + icon: "img/home-3/choose-us/icon-2.png", + services: ["Study Visa", "Work Visa", "Permanent Residency"], + }, + { + id: 6, + name: "France", + slug: "france", + mainImage: "_images/default.jpg", + icon: "img/home-3/choose-us/icon-3.png", + services: ["Student Visa", "Work Visa", "Family Visa"], + }, + { + id: 7, + name: "New Zealand", + slug: "new-zealand", + mainImage: "_images/default.jpg", + icon: "img/home-3/choose-us/icon-1.png", + services: ["Work Visa", "Permanent Residency", "Study Visa"], + }, + { + id: 8, + name: "Japan", + slug: "japan", + mainImage: "_images/default.jpg", + icon: "img/home-3/choose-us/icon-2.png", + services: ["Work Visa", "Student Visa", "Business Visa"], + }, +]; diff --git a/app/components/ImageWithFallback.tsx b/app/components/ImageWithFallback.tsx new file mode 100644 index 0000000..0c3cd81 --- /dev/null +++ b/app/components/ImageWithFallback.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useState } from "react"; +import { imageUrl } from "../utils/image"; + +interface ImageWithFallbackProps { + src: string; + alt: string; + fallbackSrc?: string; + className?: string; + style?: React.CSSProperties; +} + +export default function ImageWithFallback({ + src, + alt, + fallbackSrc = "_images/default.jpg", + className, + style, +}: ImageWithFallbackProps) { + const [imgSrc, setImgSrc] = useState(imageUrl(src)); + const [hasError, setHasError] = useState(false); + + const handleError = () => { + if (!hasError) { + setHasError(true); + setImgSrc(imageUrl(fallbackSrc)); + } + }; + + return ( + {alt} + ); +} diff --git a/app/services/page.tsx b/app/services/page.tsx index 944ebed..5147e09 100644 --- a/app/services/page.tsx +++ b/app/services/page.tsx @@ -1,57 +1,18 @@ -import { Metadata } from "next"; -import { fetchServicePageData } from "../../api/servicesApi"; +import { fetchServicePageData, fetchCountries } from "../../api/servicesApi"; import { imageUrl } from "../utils/image"; import Breadcrumb from "../components/Breadcrumb"; +import ImageWithFallback from "../components/ImageWithFallback"; import "./services.css"; export default async function ServicesPage() { const data = await fetchServicePageData(); + const allCountries = await fetchCountries(); 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", - }, - ]; + + // Pagination logic - show only first 5 countries + const COUNTRIES_PER_PAGE = 5; + const displayedCountries = allCountries.slice(0, COUNTRIES_PER_PAGE); + const hasMoreCountries = allCountries.length > COUNTRIES_PER_PAGE; return ( <> @@ -140,10 +101,14 @@ export default async function ServicesPage() {
- {country.map((country: any) => ( + {displayedCountries.map((country: any) => (
- img +
@@ -151,14 +116,28 @@ export default async function ServicesPage() { img
- {country.name} + {country.name}
-

{country.description}

+

+ {country.services && country.services.length > 0 + ? `Services: ${country.services.slice(0, 2).join(", ")}${country.services.length > 2 ? "..." : ""}` + : "Immigration services available"} +

))} + + {/* Show "View More" button if there are more countries */} + {hasMoreCountries && ( +
+ + View All Countries ({allCountries.length}) + + +
+ )}