Commit f01580e6 authored by Lê Bảo Hồng Đức's avatar Lê Bảo Hồng Đức

fix

parent 1334e92b
......@@ -20,6 +20,12 @@ const nextConfig: NextConfig = {
port: "",
pathname: "/wp-content/uploads/**",
},
{
protocol: "https",
hostname: "vccihcm.vn",
port: "",
pathname: "/images/**",
},
],
},
};
......
'use client';
import ImageNext from "@/components/shared/image-next";
import { useHomePosts } from "@/app/(main)/(home)/lib/use-home-posts";
import ImageNext from "@/components/shared/image-next";
import memberImages from "@/constants/memberImages";
import Link from "next/link";
import { useEffect, useState } from "react";
import { Autoplay } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
const MEMBER_CONNECTION_FALLBACK_IMAGE = "/home/20-2048x1365.webp";
const FEATURED_MEMBER_API_URL = "/api/featured-members";
const FEATURED_MEMBER_MORE_URL =
"https://vccihcm.vn/giao-thuong-b2b?filters=users.status_id+%3D%3D+36ca1cc5-7b6e-4f9f-b973-69c5207deb62&sortField=created_at&sortOrder=ASC";
const VCCI_HCM_ORIGIN = "https://vccihcm.vn";
type FeaturedMember = {
id: string;
name: string;
avatar?: string | null;
};
type FeaturedMembersResponse = {
responseData?: {
rows?: FeaturedMember[];
};
};
const resolveMemberImage = (avatar: string | null | undefined, index: number) => {
if (avatar?.startsWith("http://") || avatar?.startsWith("https://")) {
return avatar;
}
if (avatar?.startsWith("/")) {
return `${VCCI_HCM_ORIGIN}${avatar}`;
}
return memberImages[index % memberImages.length] ?? "/img-error.png";
};
const fallbackMembers: FeaturedMember[] = Array.from({ length: 9 }, (_, index) => ({
id: `fallback-member-${index}`,
name: `Hội viên tiêu biểu ${index + 1}`,
avatar: memberImages[index % memberImages.length],
}));
function Members() {
const { memberConnectionPosts, categoryLinks, categoryNames } = useHomePosts();
const [featuredMembers, setFeaturedMembers] = useState<FeaturedMember[]>([]);
const featuredConnection = memberConnectionPosts[0];
const sectionLink =
categoryLinks.get(categoryNames.ketNoiHoiVien.toLowerCase()) ?? "/hoi-vien/ket-noi-hoi-vien";
......@@ -16,6 +55,38 @@ function Members() {
featuredConnection?.thumbnail?.url ?? MEMBER_CONNECTION_FALLBACK_IMAGE;
const connectionImageAlt =
featuredConnection?.thumbnail?.alt || featuredConnection?.title || "VCCI HCM";
const displayMembers = featuredMembers.length > 0 ? featuredMembers : fallbackMembers;
useEffect(() => {
let isMounted = true;
const fetchFeaturedMembers = async () => {
try {
const response = await fetch(FEATURED_MEMBER_API_URL, {
cache: "no-store",
});
if (!response.ok) {
throw new Error(`Cannot load featured members: ${response.status}`);
}
const data = (await response.json()) as FeaturedMembersResponse;
const rows = data.responseData?.rows ?? [];
if (isMounted) {
setFeaturedMembers(rows.slice(0, 9));
}
} catch (error) {
console.error(error);
}
};
fetchFeaturedMembers();
return () => {
isMounted = false;
};
}, []);
return (
<section className="flex flex-col gap-5 pb-8 xl:flex-row xl:items-stretch">
......@@ -25,11 +96,13 @@ function Members() {
<h2 className="client-section-title uppercase text-[#20449a]">
Hội viên tiêu biểu
</h2>
<div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-white" />
<div className="mt-2.5 h-1 w-10 rounded-full bg-white" />
</div>
<Link
href={sectionLink}
href={FEATURED_MEMBER_MORE_URL}
target="_blank"
rel="noreferrer"
className="pt-1 text-sm font-semibold text-[#1e2f5e] transition-colors hover:text-[#20449a]"
>
Xem thêm
......@@ -38,24 +111,40 @@ function Members() {
<div className="mt-4 border-t border-[#e7aa00] pt-5" />
<div className="grid gap-4 sm:grid-cols-3">
{memberImages.slice(0, 3).map((src, index) => (
<div
key={src}
className="rounded-[24px] bg-white p-[7px] shadow-[0_10px_22px_rgba(158,114,0,0.16)]"
<Swiper
modules={[Autoplay]}
autoplay={{ delay: 4200, disableOnInteraction: false }}
observer
observeParents
updateOnWindowResize
slidesPerView="auto"
spaceBetween={16}
className="w-full"
>
<div className="flex aspect-[1.06/1] items-center justify-center overflow-hidden rounded-[18px] bg-[#f4f7fb] px-4 py-5">
{displayMembers.map((member, index) => (
<SwiperSlide
key={member.id}
className="!h-auto !w-full md:!w-[calc(50%-8px)] xl:!w-[calc(33.333%-10.67px)]"
>
<article className="rounded-[20px] bg-white p-[7px] shadow-[0_10px_22px_rgba(158,114,0,0.16)]">
<div className="flex h-[210px] items-center justify-center overflow-hidden rounded-[14px] bg-white px-4 py-5">
<div className="flex h-full w-full max-w-[260px] items-center justify-center">
<ImageNext
src={src}
alt={`Hội viên tiêu biểu ${index + 1}`}
width={320}
height={220}
className="max-h-full max-w-full object-contain"
src={resolveMemberImage(member.avatar, index)}
alt={member.name}
width={260}
height={180}
className="h-[180px] w-[260px] max-w-full object-contain"
/>
</div>
</div>
<h3 className="mt-3 line-clamp-2 min-h-[40px] px-1 text-center text-sm font-semibold leading-5 text-[#1e2f5e]">
{member.name}
</h3>
</article>
</SwiperSlide>
))}
</div>
</Swiper>
</aside>
<aside className="w-full xl:w-[31%] xl:min-w-[320px]">
......
......@@ -6,6 +6,44 @@ import partnerImages from "@/constants/partnerImages";
import { ChevronRight, Play } from "lucide-react";
import Link from "next/link";
import { fetchClientVideos } from "@/lib/api/videos";
import { Autoplay } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
type Partner = {
id: string;
name: string;
avatar?: string | null;
website?: string | null;
};
type PartnerResponse = {
responseData?: {
rows?: Partner[];
};
};
const PARTNER_API_URL = "/api/partners";
const VCCI_HCM_ORIGIN = "https://vccihcm.vn";
const resolvePartnerImage = (avatar: string | null | undefined, index: number) => {
if (avatar?.startsWith("http://") || avatar?.startsWith("https://")) {
return avatar;
}
if (avatar?.startsWith("/")) {
return `${VCCI_HCM_ORIGIN}${avatar}`;
}
return partnerImages[index % partnerImages.length] ?? "/img-error.png";
};
const fallbackPartners: Partner[] = Array.from({ length: 6 }, (_, index) => ({
id: `fallback-partner-${index}`,
name: `Đối tác ${index + 1}`,
avatar: partnerImages[index % partnerImages.length],
website: null,
}));
function VideoAndPartners() {
const videosQuery = useQuery({
......@@ -14,7 +52,25 @@ function VideoAndPartners() {
staleTime: 60 * 1000,
});
const partnersQuery = useQuery({
queryKey: ["home-partners"],
queryFn: async () => {
const response = await fetch(PARTNER_API_URL, {
cache: "no-store",
});
if (!response.ok) {
throw new Error(`Cannot load partners: ${response.status}`);
}
return (await response.json()) as PartnerResponse;
},
staleTime: 60 * 1000,
});
const videos = videosQuery.data?.rows ?? [];
const partners = partnersQuery.data?.responseData?.rows?.slice(0, 12) ?? [];
const displayPartners = partners.length > 0 ? partners : fallbackPartners;
return (
<section className="flex flex-col gap-6 pb-10 xl:flex-row xl:items-stretch">
......@@ -93,22 +149,52 @@ function VideoAndPartners() {
</div>
</div>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 xl:h-[318px] xl:grid-rows-2">
{partnerImages.slice(0, 6).map((src, index) => (
<div
key={src}
className="flex h-[96px] items-center justify-center rounded-[14px] border border-[#edf1f7] bg-white px-5 py-4 shadow-[0_8px_20px_rgba(31,59,124,0.05)] xl:h-auto"
<Swiper
modules={[Autoplay]}
autoplay={{ delay: 4200, disableOnInteraction: false }}
observer
observeParents
updateOnWindowResize
slidesPerView="auto"
spaceBetween={16}
className="w-full"
>
{displayPartners.map((partner, index) => (
<SwiperSlide
key={partner.id}
className="!h-auto !w-full sm:!w-[calc(50%-8px)] xl:!w-[calc(33.333%-10.67px)]"
>
{partner.website ? (
<a
href={partner.website}
target="_blank"
rel="noreferrer"
className="block"
>
<div className="flex h-[96px] items-center justify-center rounded-[14px] border border-[#edf1f7] bg-white px-5 py-4 shadow-[0_8px_20px_rgba(31,59,124,0.05)] transition-all hover:-translate-y-0.5 hover:shadow-[0_14px_24px_rgba(31,59,124,0.1)] xl:h-[151px]">
<ImageNext
src={src}
alt={`Đối tác ${index + 1}`}
src={resolvePartnerImage(partner.avatar, index)}
alt={partner.name}
width={140}
height={72}
className="max-h-full w-full object-contain"
/>
</div>
))}
</a>
) : (
<div className="flex h-[96px] items-center justify-center rounded-[14px] border border-[#edf1f7] bg-white px-5 py-4 shadow-[0_8px_20px_rgba(31,59,124,0.05)] xl:h-[151px]">
<ImageNext
src={resolvePartnerImage(partner.avatar, index)}
alt={partner.name}
width={140}
height={72}
className="max-h-full w-full object-contain"
/>
</div>
)}
</SwiperSlide>
))}
</Swiper>
</aside>
</section>
);
......
import { NextResponse } from "next/server";
const FEATURED_MEMBER_API_URL =
"https://vccihcm.vn/api/v1.0/organizations?filters=users.status_id+%3D%3D+36ca1cc5-7b6e-4f9f-b973-69c5207deb62&pageSize=9&sortField=created_at&sortOrder=ASC";
export async function GET() {
try {
const response = await fetch(FEATURED_MEMBER_API_URL, {
headers: {
Accept: "application/json",
},
next: { revalidate: 300 },
});
if (!response.ok) {
return NextResponse.json(
{ message: "Không thể tải dữ liệu hội viên tiêu biểu" },
{ status: response.status },
);
}
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
console.error("Failed to fetch featured members", error);
return NextResponse.json(
{ message: "Không thể tải dữ liệu hội viên tiêu biểu" },
{ status: 500 },
);
}
}
import { NextResponse } from "next/server";
const PARTNER_API_URL =
"https://vccihcm.vn/api/v1.0/organizations?filters=type%3D%3DSPONSOR&pageSize=12&sortField=sort_order&sortOrder=ASC";
export async function GET() {
try {
const response = await fetch(PARTNER_API_URL, {
headers: {
Accept: "application/json",
},
next: { revalidate: 300 },
});
if (!response.ok) {
return NextResponse.json(
{ message: "Không thể tải dữ liệu đối tác" },
{ status: response.status },
);
}
const data = await response.json();
return NextResponse.json(data);
} catch (error) {
console.error("Failed to fetch partners", error);
return NextResponse.json(
{ message: "Không thể tải dữ liệu đối tác" },
{ status: 500 },
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment