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

[tag]0.1-vcci

parents 010e75cf bc53cf05
Pipeline #52482 passed with stages
in 4 minutes
...@@ -72,7 +72,7 @@ function BannerSlideItem({ ...@@ -72,7 +72,7 @@ function BannerSlideItem({
if (isPending) { if (isPending) {
return ( return (
<Skeleton className="w-full h-[200px] sm:h-[300px] md:h-[400px] lg:h-[500px]" /> <Skeleton className="w-full h-[220px] sm:h-[320px] md:h-[430px] lg:h-[540px]" />
); );
} }
...@@ -80,7 +80,7 @@ function BannerSlideItem({ ...@@ -80,7 +80,7 @@ function BannerSlideItem({
? src ? src
: file : file
? resolveCmsFileUrl(file.path) ? resolveCmsFileUrl(file.path)
: "/img-error.png"; : "/thumbnail.png";
return ( return (
<ImageNext <ImageNext
...@@ -89,7 +89,8 @@ function BannerSlideItem({ ...@@ -89,7 +89,8 @@ function BannerSlideItem({
width={2560} width={2560}
height={720} height={720}
sizes="100vw" sizes="100vw"
className="w-full h-[200px] sm:h-[300px] md:h-[400px] lg:h-[500px] object-cover" fallback="/thumbnail.png"
className="w-full h-[220px] sm:h-[320px] md:h-[430px] lg:h-[540px] object-cover"
/> />
); );
} }
...@@ -119,7 +120,7 @@ const Banner = () => { ...@@ -119,7 +120,7 @@ const Banner = () => {
if (isPending && !rows.length) { if (isPending && !rows.length) {
return ( return (
<div className="w-full h-[200px] sm:h-[300px] md:h-[400px] lg:h-[500px] bg-slate-100 flex items-center justify-center"> <div className="w-full h-[220px] sm:h-[320px] md:h-[430px] lg:h-[540px] bg-slate-100 flex items-center justify-center">
<Skeleton className="w-full h-full" /> <Skeleton className="w-full h-full" />
</div> </div>
); );
...@@ -127,7 +128,7 @@ const Banner = () => { ...@@ -127,7 +128,7 @@ const Banner = () => {
if (!rows || rows.length === 0) { if (!rows || rows.length === 0) {
return ( return (
<div className="w-full h-[200px] sm:h-[300px] md:h-[400px] lg:h-[500px] bg-slate-100 flex items-center justify-center"> <div className="w-full h-[220px] sm:h-[320px] md:h-[430px] lg:h-[540px] bg-slate-100 flex items-center justify-center">
<Skeleton className="w-full h-full" /> <Skeleton className="w-full h-full" />
</div> </div>
); );
......
...@@ -43,10 +43,10 @@ function BusinessOpportunities() { ...@@ -43,10 +43,10 @@ function BusinessOpportunities() {
<span className="mt-1 h-[40px] w-[2px] shrink-0 rounded-full bg-[#f7b500] transition-opacity duration-200 group-hover:opacity-0" /> <span className="mt-1 h-[40px] w-[2px] shrink-0 rounded-full bg-[#f7b500] transition-opacity duration-200 group-hover:opacity-0" />
<div className="hidden min-w-0 group-hover:block"> <div className="hidden min-w-0 group-hover:block">
<h3 className="line-clamp-2 text-[16px] font-bold leading-[1.45] text-[#264798] md:text-[17px]"> <h3 className="line-clamp-2 text-[15px] font-bold leading-[1.45] text-[#264798] md:text-[16px]">
{item.title} {item.title}
</h3> </h3>
<p className="mt-2 text-[13px] text-[#9aa8c1]"> <p className="mt-1.5 text-[13px] text-[#9aa8c1]">
{dayjs(item.publishedAt || item.createdAt).format("DD/MM/YYYY")} {dayjs(item.publishedAt || item.createdAt).format("DD/MM/YYYY")}
</p> </p>
</div> </div>
......
...@@ -32,13 +32,13 @@ function Events() { ...@@ -32,13 +32,13 @@ function Events() {
</Link> </Link>
</div> </div>
<div className="grid items-stretch gap-3 xl:grid-cols-[minmax(0,1.02fr)_minmax(270px,0.98fr)]"> <div className="grid items-stretch gap-3 md:grid-cols-[minmax(0,1.02fr)_minmax(270px,0.98fr)]">
{featuredEvent ? ( {featuredEvent ? (
<Link <Link
href={featuredEvent.externalLink} href={featuredEvent.externalLink}
className="flex h-full flex-col overflow-hidden rounded-[22px] bg-white text-[#20408f] shadow-[0_14px_28px_rgba(10,39,95,0.18)]" className="group flex h-full cursor-pointer flex-col overflow-hidden rounded-[22px] bg-white text-[#20408f] shadow-[0_14px_28px_rgba(10,39,95,0.18)]"
> >
<div className="h-[220px] overflow-hidden md:h-[235px] xl:h-[248px]"> <div className="relative h-[180px] overflow-hidden md:h-[220px] xl:h-[248px]">
<ImageNext <ImageNext
src={featuredEvent.thumbnail?.url ?? "/thumbnail.png"} src={featuredEvent.thumbnail?.url ?? "/thumbnail.png"}
alt={featuredEvent.thumbnail?.alt || featuredEvent.title} alt={featuredEvent.thumbnail?.alt || featuredEvent.title}
...@@ -46,10 +46,13 @@ function Events() { ...@@ -46,10 +46,13 @@ function Events() {
height={520} height={520}
className="h-full w-full object-cover" className="h-full w-full object-cover"
/> />
<span className="absolute left-3 top-3 inline-flex rounded-full bg-[#f7b500] px-3 py-1 text-[12px] font-bold text-[#15357a]">
{featuredEvent.categories[0]?.name || "Sự kiện"}
</span>
</div> </div>
<div className="flex flex-col p-3 pt-2.5"> <div className="flex flex-col p-3 pt-2.5">
<h3 className="text-[16px] font-bold uppercase leading-[1.28] text-[#22459b] line-clamp-2 md:text-[18px]"> <h3 className="text-[16px] font-bold uppercase leading-[1.28] text-[#22459b] line-clamp-2 transition-colors duration-200 group-hover:text-[#f7b500] md:text-[18px]">
{featuredEvent.title} {featuredEvent.title}
</h3> </h3>
{(() => { {(() => {
...@@ -85,7 +88,7 @@ function Events() { ...@@ -85,7 +88,7 @@ function Events() {
</Link> </Link>
) : ( ) : (
<div className="flex h-full flex-col overflow-hidden rounded-[22px] bg-white text-[#20408f] shadow-[0_14px_28px_rgba(10,39,95,0.12)]"> <div className="flex h-full flex-col overflow-hidden rounded-[22px] bg-white text-[#20408f] shadow-[0_14px_28px_rgba(10,39,95,0.12)]">
<div className="h-[220px] bg-[#d7e3f9] md:h-[235px] xl:h-[248px]" /> <div className="h-[180px] bg-[#d7e3f9] md:h-[220px] xl:h-[248px]" />
<div className="space-y-2 p-3 pt-2.5"> <div className="space-y-2 p-3 pt-2.5">
<div className="h-6 w-5/6 rounded bg-[#e7eefb]" /> <div className="h-6 w-5/6 rounded bg-[#e7eefb]" />
<div className="h-4 w-24 rounded bg-[#eef3fb]" /> <div className="h-4 w-24 rounded bg-[#eef3fb]" />
...@@ -93,13 +96,13 @@ function Events() { ...@@ -93,13 +96,13 @@ function Events() {
</div> </div>
)} )}
<div className="flex h-full flex-col gap-3"> <div className="grid h-full grid-cols-1 gap-3 sm:grid-cols-2 md:grid-cols-1">
{sideSlots.map((item, index) => {sideSlots.map((item, index) =>
item ? ( item ? (
<Link <Link
key={item.id} key={item.id}
href={item.externalLink} href={item.externalLink}
className="flex flex-1 items-center gap-3 rounded-[18px] bg-white/10 p-2.5 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.08)] backdrop-blur-sm transition-colors hover:bg-white/14" className="group flex flex-1 cursor-pointer items-center gap-3 rounded-[18px] bg-white/10 p-2.5 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.08)] backdrop-blur-sm transition-colors hover:bg-white/14"
> >
<div className="h-[64px] w-[64px] shrink-0 overflow-hidden rounded-[12px]"> <div className="h-[64px] w-[64px] shrink-0 overflow-hidden rounded-[12px]">
<ImageNext <ImageNext
...@@ -112,7 +115,7 @@ function Events() { ...@@ -112,7 +115,7 @@ function Events() {
</div> </div>
<div className="min-w-0"> <div className="min-w-0">
<h4 className="line-clamp-1 text-[15px] font-semibold leading-[1.35] text-white"> <h4 className="line-clamp-1 text-[15px] font-semibold leading-[1.35] text-white transition-colors duration-200 group-hover:text-[#f7b500]">
{item.title} {item.title}
</h4> </h4>
{(() => { {(() => {
...@@ -139,12 +142,22 @@ function Events() { ...@@ -139,12 +142,22 @@ function Events() {
</p> </p>
) : null; ) : null;
})()} })()}
<p className="mt-1 text-[12px] text-white/78"> <div className="mt-1 flex items-center gap-2">
{item.categories[0]?.name && (
<span className="text-[12px] font-medium text-[#f7b500]">
{item.categories[0].name}
</span>
)}
{item.categories[0]?.name && (
<span className="text-[12px] text-white/50"></span>
)}
<p className="text-[12px] text-white/78">
{dayjs(item.startedAt || item.publishedAt || item.createdAt).format( {dayjs(item.startedAt || item.publishedAt || item.createdAt).format(
"DD/MM/YYYY", "DD/MM/YYYY",
)} )}
</p> </p>
</div> </div>
</div>
</Link> </Link>
) : ( ) : (
<div <div
......
...@@ -36,32 +36,32 @@ function FeaturedNews() { ...@@ -36,32 +36,32 @@ function FeaturedNews() {
</Link> </Link>
</div> </div>
<div className="grid gap-5 xl:grid-cols-[minmax(0,1.14fr)_minmax(0,0.96fr)]"> <div className="grid gap-5 lg:grid-cols-[minmax(0,1.14fr)_minmax(0,0.96fr)]">
{primaryItem ? ( {primaryItem ? (
<Link <Link
href={primaryItem.externalLink} href={primaryItem.externalLink}
className="group relative block min-h-[260px] overflow-hidden rounded-[24px] bg-[#0d2f5f] shadow-[0_18px_38px_rgba(28,52,120,0.22)] md:min-h-[320px] xl:min-h-[350px]" className="group relative block cursor-pointer overflow-hidden rounded-[20px] bg-[#0d2f5f] shadow-[0_16px_32px_rgba(28,52,120,0.2)] md:rounded-[24px] md:min-h-[320px] lg:min-h-[380px]"
> >
<div className="relative h-full min-h-[260px] md:min-h-[320px] xl:min-h-[350px]"> <div className="relative h-full min-h-[195px] md:min-h-[320px] lg:min-h-[380px]">
<ImageNext <ImageNext
src={primaryItem.thumbnail?.url ?? "/thumbnail.png"} src={primaryItem.thumbnail?.url ?? "/thumbnail.png"}
alt={primaryItem.thumbnail?.alt || primaryItem.title} alt={primaryItem.thumbnail?.alt || primaryItem.title}
width={1200} width={1200}
height={800} height={800}
className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.04]" className="absolute inset-0 h-full w-full object-cover"
/> />
<div className="absolute inset-0 bg-linear-to-t from-[#26356d] via-[#53669b]/34 to-transparent" /> <div className="absolute inset-0 bg-linear-to-t from-[#26356d] via-[#53669b]/34 to-transparent" />
<div className="relative flex h-full flex-col justify-end p-4 md:p-5"> <div className="relative flex h-full flex-col justify-end p-3.5 md:p-5">
<span className="mb-2 inline-flex w-fit rounded-[10px] bg-[#ffc400] px-3 py-1 text-sm font-bold text-[#1d3f90]"> <span className="mb-2 inline-flex w-fit rounded-[10px] bg-[#ffc400] px-3 py-1 text-sm font-bold text-[#1d3f90]">
{primaryItem.categories[0]?.name || "Tin nổi bật"} {primaryItem.categories[0]?.name || "Tin nổi bật"}
</span> </span>
<h3 className="max-w-3xl line-clamp-3 text-[20px] font-bold leading-[1.28] text-white md:text-[28px] xl:text-[32px]"> <h3 className="max-w-3xl line-clamp-2 text-[16px] font-bold leading-[1.32] text-white transition-colors duration-200 group-hover:text-[#f7b500] md:line-clamp-3 md:text-[28px] lg:text-[32px]">
{primaryItem.title} {primaryItem.title}
</h3> </h3>
<p className="mt-2 text-base font-medium text-white/78 md:text-[17px]"> <p className="mt-1.5 text-[15px] font-medium text-white/78 md:mt-2 md:text-[17px]">
{dayjs(primaryItem.publishedAt || primaryItem.createdAt).format( {dayjs(primaryItem.publishedAt || primaryItem.createdAt).format(
"DD/MM/YYYY", "DD/MM/YYYY",
)} )}
...@@ -70,8 +70,8 @@ function FeaturedNews() { ...@@ -70,8 +70,8 @@ function FeaturedNews() {
</div> </div>
</Link> </Link>
) : ( ) : (
<div className="relative min-h-[260px] overflow-hidden rounded-3xl bg-[#e9eef8] shadow-[0_18px_38px_rgba(28,52,120,0.12)] md:min-h-[320px] xl:min-h-[350px]"> <div className="relative overflow-hidden rounded-[20px] bg-[#e9eef8] shadow-[0_16px_32px_rgba(28,52,120,0.12)] md:rounded-3xl md:min-h-[320px] lg:min-h-[380px]">
<div className="flex h-full min-h-[260px] flex-col justify-end p-4 md:min-h-80 md:p-5 xl:min-h-[350px]"> <div className="flex h-full min-h-[195px] flex-col justify-end p-3.5 md:min-h-80 md:p-5 lg:min-h-[380px]">
<span className="mb-2 h-8 w-28 rounded-[10px] bg-white/80" /> <span className="mb-2 h-8 w-28 rounded-[10px] bg-white/80" />
<div className="h-8 w-3/4 rounded bg-white/90 md:h-10" /> <div className="h-8 w-3/4 rounded bg-white/90 md:h-10" />
<div className="mt-2 h-5 w-28 rounded bg-white/70" /> <div className="mt-2 h-5 w-28 rounded bg-white/70" />
...@@ -86,15 +86,15 @@ function FeaturedNews() { ...@@ -86,15 +86,15 @@ function FeaturedNews() {
<Link <Link
key={item.id} key={item.id}
href={item.externalLink} href={item.externalLink}
className="group relative block min-h-[195px] overflow-hidden rounded-[20px] bg-[#27447f] shadow-[0_16px_32px_rgba(28,52,120,0.2)] md:min-h-[205px] xl:min-h-[215px]" className="group relative block cursor-pointer overflow-hidden rounded-[20px] bg-[#27447f] shadow-[0_16px_32px_rgba(28,52,120,0.2)] md:min-h-[205px] lg:min-h-[215px]"
> >
<div className="relative h-full min-h-[195px] md:min-h-[205px] xl:min-h-[215px]"> <div className="relative h-full min-h-[195px] md:min-h-[205px] lg:min-h-[215px]">
<ImageNext <ImageNext
src={item.thumbnail?.url ?? "/thumbnail.png"} src={item.thumbnail?.url ?? "/thumbnail.png"}
alt={item.thumbnail?.alt || item.title} alt={item.thumbnail?.alt || item.title}
width={600} width={600}
height={420} height={420}
className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.05]" className="absolute inset-0 h-full w-full object-cover"
/> />
<div className="absolute inset-0 bg-linear-to-t from-[#5a6796] via-[#405083]/34 to-transparent" /> <div className="absolute inset-0 bg-linear-to-t from-[#5a6796] via-[#405083]/34 to-transparent" />
...@@ -103,7 +103,7 @@ function FeaturedNews() { ...@@ -103,7 +103,7 @@ function FeaturedNews() {
{item.categories[0]?.name || "Tin nổi bật"} {item.categories[0]?.name || "Tin nổi bật"}
</span> </span>
<h4 className="line-clamp-2 text-[16px] font-bold leading-[1.32] text-white md:text-[17px]"> <h4 className="line-clamp-2 text-[16px] font-bold leading-[1.32] text-white transition-colors duration-200 group-hover:text-[#f7b500] md:text-[17px]">
{item.title} {item.title}
</h4> </h4>
...@@ -118,9 +118,9 @@ function FeaturedNews() { ...@@ -118,9 +118,9 @@ function FeaturedNews() {
) : ( ) : (
<div <div
key={`featured-placeholder-${index}`} key={`featured-placeholder-${index}`}
className="min-h-[195px] rounded-[20px] bg-[#dde5f3] shadow-[0_16px_32px_rgba(28,52,120,0.1)] md:min-h-[205px] xl:min-h-[215px]" className="rounded-[20px] bg-[#dde5f3] shadow-[0_16px_32px_rgba(28,52,120,0.1)] md:min-h-[205px] lg:min-h-[215px]"
> >
<div className="flex h-full min-h-[195px] flex-col justify-end p-3.5 md:min-h-[205px] xl:min-h-[215px]"> <div className="flex h-full min-h-[195px] flex-col justify-end p-3.5 md:min-h-[205px] lg:min-h-[215px]">
<span className="mb-2 h-7 w-24 rounded-[10px] bg-white/80" /> <span className="mb-2 h-7 w-24 rounded-[10px] bg-white/80" />
<div className="h-6 w-5/6 rounded bg-white/90" /> <div className="h-6 w-5/6 rounded bg-white/90" />
<div className="mt-2 h-4 w-24 rounded bg-white/70" /> <div className="mt-2 h-4 w-24 rounded bg-white/70" />
...@@ -130,25 +130,28 @@ function FeaturedNews() { ...@@ -130,25 +130,28 @@ function FeaturedNews() {
)} )}
</div> </div>
<div className="overflow-hidden rounded-[28px] bg-linear-to-r from-[#214b95] to-[#2b66bb] px-5 py-5 text-white shadow-[0_18px_38px_rgba(28,52,120,0.2)] md:px-7"> <div className="flex h-full min-h-full items-center justify-center overflow-hidden rounded-[28px] bg-linear-to-r from-[#214b95] to-[#2b66bb] px-5 py-5 text-white shadow-[0_18px_38px_rgba(28,52,120,0.2)] md:px-7">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> <div className="flex w-full flex-col items-center justify-center gap-4 text-center md:flex-row md:gap-6 md:text-left lg:gap-8 xl:gap-12">
<div> <div className="flex flex-col items-center md:items-start">
<p className="text-[12px] uppercase tracking-[0.4em] text-white/80"> <p className="text-[11px] uppercase tracking-[0.2em] text-white/80 lg:text-[12px] lg:tracking-[0.4em]">
Quảng bá & tiếp cận Quảng bá & tiếp cận
</p> </p>
<h3 className="mt-2 max-w-[520px] text-[20px] font-extrabold uppercase leading-[1.02] xl:text-[28px]"> <h3 className="mt-2 text-[20px] font-extrabold uppercase leading-[1.1] lg:text-[18px] xl:text-[22px]">
Cộng đồng doanh nghiệp <span>Cộng đồng</span>
<br className="hidden lg:block" />
<span className="lg:hidden"> </span>
<span>doanh nghiệp</span>
</h3> </h3>
</div> </div>
<div className="w-full max-w-60 rounded-[999px] bg-white px-4 py-3 text-[#173f88] shadow-[0_10px_24px_rgba(8,25,74,0.12)]"> <div className="shrink-0 flex flex-col gap-2 rounded-[20px] bg-white px-4 py-3 text-[#173f88] shadow-[0_10px_24px_rgba(8,25,74,0.12)] sm:min-w-fit lg:w-auto lg:rounded-[999px]">
<div className="flex items-center gap-3 text-sm font-medium"> <div className="flex items-center gap-2 text-[13px] font-medium sm:text-sm">
<Mail className="h-5 w-5 shrink-0" /> <Mail className="h-4 w-4 shrink-0 sm:h-5 sm:w-5" />
<span>info@vcci-hcm.org.vn</span> <span className="whitespace-nowrap">info@vcci-hcm.org.vn</span>
</div> </div>
<div className="mt-2 flex items-center gap-3 text-sm font-medium"> <div className="flex items-center gap-2 text-[13px] font-medium sm:text-sm">
<Phone className="h-5 w-5 shrink-0" /> <Phone className="h-4 w-4 shrink-0 sm:h-5 sm:w-5" />
<span>+84 28 3932 6598</span> <span className="whitespace-nowrap">+84 28 3932 6598</span>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -9,6 +9,7 @@ import { useEffect, useState } from "react"; ...@@ -9,6 +9,7 @@ import { useEffect, useState } from "react";
import { Autoplay } from "swiper/modules"; import { Autoplay } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react"; import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css"; import "swiper/css";
import dayjs from "dayjs";
const MEMBER_CONNECTION_FALLBACK_IMAGE = "/home/20-2048x1365.webp"; const MEMBER_CONNECTION_FALLBACK_IMAGE = "/home/20-2048x1365.webp";
const FEATURED_MEMBER_API_URL = `${links.siteURL}api/featured-members`; const FEATURED_MEMBER_API_URL = `${links.siteURL}api/featured-members`;
...@@ -50,13 +51,9 @@ function Members() { ...@@ -50,13 +51,9 @@ function Members() {
const [featuredMembers, setFeaturedMembers] = useState<FeaturedMemberState>({ const [featuredMembers, setFeaturedMembers] = useState<FeaturedMemberState>({
status: "loading", status: "loading",
}); });
const featuredConnection = memberConnectionPosts[0]; const connectionPosts = memberConnectionPosts.slice(0, 2);
const sectionLink = const sectionLink =
categoryLinks.get(categoryNames.ketNoiHoiVien.toLowerCase()) ?? "/hoi-vien/ket-noi-hoi-vien"; categoryLinks.get(categoryNames.ketNoiHoiVien.toLowerCase()) ?? "/hoi-vien/ket-noi-hoi-vien";
const connectionImage =
featuredConnection?.thumbnail?.url ?? MEMBER_CONNECTION_FALLBACK_IMAGE;
const connectionImageAlt =
featuredConnection?.thumbnail?.alt || featuredConnection?.title || "VCCI HCM";
const displayMembers = const displayMembers =
featuredMembers.status === "ready" ? featuredMembers.rows.slice(0, 9) : []; featuredMembers.status === "ready" ? featuredMembers.rows.slice(0, 9) : [];
...@@ -187,22 +184,113 @@ function Members() { ...@@ -187,22 +184,113 @@ function Members() {
</h2> </h2>
<div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-[#f7b500]" /> <div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-[#f7b500]" />
</div> </div>
</div>
<Link <Link
href={sectionLink} href={sectionLink}
className="block overflow-hidden rounded-[20px] shadow-[0_16px_32px_rgba(31,59,124,0.12)]" className="pt-1 text-sm font-semibold text-[#24469c] transition-colors hover:text-[#1b55a1]"
>
Xem tất cả
</Link>
</div>
<div>
{connectionPosts.length > 0 ? (
<>
{/* Mobile + xl+: Swiper */}
<div className="md:hidden xl:block">
<Swiper
modules={[Autoplay]}
autoplay={{ delay: 4000, disableOnInteraction: false }}
loop={connectionPosts.length > 1}
slidesPerView={1}
className="w-full overflow-hidden rounded-[20px]"
>
{connectionPosts.map((item) => (
<SwiperSlide key={item.id}>
<Link
href={item.externalLink}
className="group relative block cursor-pointer overflow-hidden rounded-[20px] shadow-[0_16px_32px_rgba(31,59,124,0.12)]"
>
<div className="aspect-[16/10] overflow-hidden xl:aspect-[1.25/1]">
<ImageNext
src={item.thumbnail?.url ?? MEMBER_CONNECTION_FALLBACK_IMAGE}
alt={item.thumbnail?.alt || item.title}
width={520}
height={420}
className="h-full w-full object-cover object-[center_80%]"
/>
</div>
<div className="absolute inset-0 bg-linear-to-t from-[#0d2f5f]/85 via-[#0d2f5f]/30 to-transparent" />
<div className="absolute inset-x-0 bottom-0 p-4">
<h4 className="line-clamp-2 text-[15px] font-bold leading-[1.32] text-white transition-colors duration-200 group-hover:text-[#f7b500]">
{item.title}
</h4>
<div className="mt-1.5 flex items-center gap-2">
{item.categories[0]?.name && (
<>
<span className="text-[12px] font-medium text-[#f7b500]">
{item.categories[0].name}
</span>
<span className="text-[12px] text-white/50"></span>
</>
)}
<p className="text-[12px] font-medium text-white/78">
{dayjs(item.publishedAt || item.createdAt).format("DD/MM/YYYY")}
</p>
</div>
</div>
</Link>
</SwiperSlide>
))}
</Swiper>
</div>
{/* md to < xl: Grid 2 columns */}
<div className="hidden gap-4 md:grid md:grid-cols-2 xl:hidden">
{connectionPosts.map((item) => (
<Link
key={item.id}
href={item.externalLink}
className="group relative block cursor-pointer overflow-hidden rounded-[20px] shadow-[0_16px_32px_rgba(31,59,124,0.12)]"
> >
<div className="aspect-[1.25/1] overflow-hidden rounded-[20px]"> <div className="aspect-[16/10] overflow-hidden">
<ImageNext <ImageNext
src={connectionImage} src={item.thumbnail?.url ?? MEMBER_CONNECTION_FALLBACK_IMAGE}
alt={connectionImageAlt} alt={item.thumbnail?.alt || item.title}
width={520} width={520}
height={420} height={420}
className="h-full w-full object-cover" className="h-full w-full object-cover object-[center_80%]"
/> />
</div> </div>
<div className="absolute inset-0 bg-linear-to-t from-[#0d2f5f]/85 via-[#0d2f5f]/30 to-transparent" />
<div className="absolute inset-x-0 bottom-0 p-4">
<h4 className="line-clamp-2 text-[15px] font-bold leading-[1.32] text-white transition-colors duration-200 group-hover:text-[#f7b500]">
{item.title}
</h4>
<div className="mt-1.5 flex items-center gap-2">
{item.categories[0]?.name && (
<>
<span className="text-[12px] font-medium text-[#f7b500]">
{item.categories[0].name}
</span>
<span className="text-[12px] text-white/50"></span>
</>
)}
<p className="text-[12px] font-medium text-white/78">
{dayjs(item.publishedAt || item.createdAt).format("DD/MM/YYYY")}
</p>
</div>
</div>
</Link> </Link>
))}
</div>
</>
) : (
<div className="rounded-[16px] bg-[#eef3fb] px-5 py-10 text-center text-sm text-[#7f8eab]">
Chưa có thông tin.
</div>
)}
</div>
</aside> </aside>
</section> </section>
); );
......
...@@ -43,10 +43,10 @@ function PolicyAndLaws() { ...@@ -43,10 +43,10 @@ function PolicyAndLaws() {
<span className="mt-1 h-[40px] w-[2px] shrink-0 rounded-full bg-[#f7b500] transition-opacity duration-200 group-hover:opacity-0" /> <span className="mt-1 h-[40px] w-[2px] shrink-0 rounded-full bg-[#f7b500] transition-opacity duration-200 group-hover:opacity-0" />
<div className="hidden min-w-0 group-hover:block"> <div className="hidden min-w-0 group-hover:block">
<h3 className="line-clamp-2 text-[16px] font-bold leading-[1.45] text-[#264798] md:text-[17px]"> <h3 className="line-clamp-2 text-[15px] font-bold leading-[1.45] text-[#264798] md:text-[16px]">
{item.title} {item.title}
</h3> </h3>
<p className="mt-2 text-[13px] text-[#9aa8c1]"> <p className="mt-1.5 text-[13px] text-[#9aa8c1]">
{dayjs(item.publishedAt || item.createdAt).format("DD/MM/YYYY")} {dayjs(item.publishedAt || item.createdAt).format("DD/MM/YYYY")}
</p> </p>
</div> </div>
......
'use client'; 'use client';
import { useHomePosts } from "@/app/(main)/(home)/lib/use-home-posts";
import ImageNext from "@/components/shared/image-next"; import ImageNext from "@/components/shared/image-next";
import Link from "next/link"; import Link from "next/link";
function QuickLinks() { function Advertisements({ count = 2 }: { count?: number }) {
const { quickLinkPosts } = useHomePosts(); const mdCols = count >= 3 ? "md:grid-cols-3" : "md:grid-cols-2";
const linkSlots = Array.from({ length: 4 }, (_, index) => quickLinkPosts[index] ?? null);
return ( return (
<aside className="w-full xl:grid xl:w-[32%] xl:grid-rows-[0.74fr_0.88fr] xl:gap-4"> <aside className={`flex w-full flex-col gap-4 md:grid ${mdCols} xl:grid xl:order-2 xl:w-[22%] xl:grid-cols-1 xl:gap-4 xl:self-center`}>
<div className="rounded-[22px] border border-[#dbe4f2] bg-white p-4 shadow-[0_8px_24px_rgba(31,59,124,0.08)] xl:h-full">
<h2 className="client-section-title uppercase text-[#24469c]">
Liên kết nhanh
</h2>
<div className="mt-3 h-[5px] w-[68px] rounded-full bg-[#f7b500]" />
<div className="mt-4 space-y-3">
{linkSlots.map((item, index) =>
item ? (
<Link <Link
key={item.id} href="https://hardwaretools.com.vn/"
href={item.externalLink} className="block overflow-hidden rounded-[28px] shadow-[0_12px_28px_rgba(31,59,124,0.14)]"
className="flex items-start gap-3 text-[15px] leading-[1.32] text-[#556684] transition-colors hover:text-[#21408f]"
> >
<span className="mt-1 text-[#e2a500]"></span> <div className="aspect-[16/10] overflow-hidden sm:aspect-[16/10] lg:aspect-[7/4] xl:aspect-[3/2]">
<span className="line-clamp-1">{item.title}</span> <ImageNext
src="/home/20-2048x1365.webp"
alt="Quảng cáo 1"
width={2048}
height={1365}
className="h-full w-full object-cover object-[center_80%]"
/>
</div>
</Link> </Link>
) : (
<div <Link
key={`quick-link-placeholder-${index}`} href="https://hardwaretools.com.vn/"
className="flex items-start gap-3" className="block overflow-hidden rounded-[28px] shadow-[0_12px_28px_rgba(31,59,124,0.14)]"
> >
<span className="mt-1 h-4 w-2 shrink-0 rounded bg-[#f5d774]" /> <div className="aspect-[16/10] overflow-hidden sm:aspect-[16/10] lg:aspect-[7/4] xl:aspect-[3/2]">
<span className="h-5 w-5/6 rounded bg-[#eef3fb]" /> <ImageNext
</div> src="/home/20-2048x1365.webp"
), alt="Quảng cáo 2"
)} width={2048}
</div> height={1365}
className="h-full w-full object-cover object-[center_80%]"
/>
</div> </div>
</Link>
{count >= 3 && (
<Link <Link
href="https://hardwaretools.com.vn/" href="https://hardwaretools.com.vn/"
className="mt-4 block overflow-hidden rounded-[28px] shadow-[0_12px_28px_rgba(31,59,124,0.14)] xl:mt-0 xl:h-full" className="block overflow-hidden rounded-[28px] shadow-[0_12px_28px_rgba(31,59,124,0.14)]"
> >
<div className="aspect-[1.55/1] overflow-hidden xl:h-full xl:aspect-auto"> <div className="aspect-[16/10] overflow-hidden sm:aspect-[16/10] lg:aspect-[7/4] xl:aspect-[3/2]">
<ImageNext <ImageNext
src="/home/20-2048x1365.webp" src="/home/20-2048x1365.webp"
alt="Liên kết nhanh" alt="Quảng cáo 3"
width={2048} width={2048}
height={1365} height={1365}
className="h-full w-full object-cover" className="h-full w-full object-cover object-[center_80%]"
/> />
</div> </div>
</Link> </Link>
)}
</aside> </aside>
); );
} }
export default QuickLinks; export default Advertisements;
...@@ -169,7 +169,7 @@ function VideoAndPartners() { ...@@ -169,7 +169,7 @@ function VideoAndPartners() {
alt={video.name} alt={video.name}
width={640} width={640}
height={440} height={440}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.03]" className="h-full w-full object-cover"
/> />
<div className="absolute inset-0 bg-black/18" /> <div className="absolute inset-0 bg-black/18" />
<div className="absolute inset-0 flex items-center justify-center"> <div className="absolute inset-0 flex items-center justify-center">
......
...@@ -267,6 +267,21 @@ function createCategoryPostsQuery(categoryId: string, pageSize: string) { ...@@ -267,6 +267,21 @@ function createCategoryPostsQuery(categoryId: string, pageSize: string) {
}); });
} }
function createMultiCategoryPostsQuery(categoryIds: string[], pageSize: string) {
return new URLSearchParams({
page: "1",
pageSize,
sortField: "release_at",
sortOrder: "desc",
filters: [
`category.id==(${categoryIds.join("|")})`,
"is_hidden==false",
"is_active==true",
"type==news",
].join(","),
});
}
function createEventCalendarQuery(currentMonth: Date) { function createEventCalendarQuery(currentMonth: Date) {
// We'll filter by date on client-side for more flexibility // We'll filter by date on client-side for more flexibility
return new URLSearchParams({ return new URLSearchParams({
...@@ -310,7 +325,10 @@ async function fetchHomePostsFromApi() { ...@@ -310,7 +325,10 @@ async function fetchHomePostsFromApi() {
const tinVcciQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.tinVcci, "6"); const tinVcciQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.tinVcci, "6");
const tinKinhTeQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.tinKinhTe, "6"); const tinKinhTeQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.tinKinhTe, "6");
const chuyenDeQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.chuyenDe, "6"); const chuyenDeQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.chuyenDe, "6");
const eventQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.suKien, "5"); const eventQuery = createMultiCategoryPostsQuery(
[HOME_CATEGORY_IDS.suKien, HOME_CATEGORY_IDS.daoTao],
"5"
);
const policyQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.chinhSachPhapLuat, "6"); const policyQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.chinhSachPhapLuat, "6");
const quickLinksQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.lienKetNhanh, "6"); const quickLinksQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.lienKetNhanh, "6");
const trainingQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.daoTao, "10"); const trainingQuery = createCategoryPostsQuery(HOME_CATEGORY_IDS.daoTao, "10");
......
'use client' 'use client'
import FeaturedNews from "./components/featured-news"; import FeaturedNews from "./components/featured-news";
import QuickLinks from "./components/quick-links"; import Advertisements from "./components/quick-links";
import News from "./components/news"; import News from "./components/news";
import Events from "./components/events"; import Events from "./components/events";
import BusinessOpportunities from "./components/business-opportunities"; import BusinessOpportunities from "./components/business-opportunities";
...@@ -29,9 +29,9 @@ const Page = () => { ...@@ -29,9 +29,9 @@ const Page = () => {
</Link> </Link>
</div> */} </div> */}
<section className="flex flex-col lg:flex-row pb-8 gap-5 mb-0"> <section className="flex flex-col xl:flex-row pb-8 gap-5 mb-0">
<News /> <News />
<QuickLinks /> <Advertisements count={3} />
</section > </section >
<section className="flex flex-col gap-5 xl:flex-row xl:items-stretch" > <section className="flex flex-col gap-5 xl:flex-row xl:items-stretch" >
...@@ -39,7 +39,7 @@ const Page = () => { ...@@ -39,7 +39,7 @@ const Page = () => {
<EventsCalendar /> <EventsCalendar />
</section > </section >
<div className="flex flex-col lg:flex-row gap-5 pb-10 mb-0" > <div className="flex flex-col lg:flex-row gap-5" >
<div className="flex flex-col flex-1"> <div className="flex flex-col flex-1">
{/* <div> {/* <div>
<Link href="https://vcci-hcm.org.vn/wp-content/uploads/2022/11/MEDIA-KIT_VCCI-HCM-2022-Final.pdf"> <Link href="https://vcci-hcm.org.vn/wp-content/uploads/2022/11/MEDIA-KIT_VCCI-HCM-2022-Final.pdf">
...@@ -52,9 +52,12 @@ const Page = () => { ...@@ -52,9 +52,12 @@ const Page = () => {
</Link> </Link>
</div> */} </div> */}
<section className="flex flex-col md:flex-row gap-5 pt-8"> <section className="flex flex-col xl:flex-row gap-5">
<div className="flex flex-col md:flex-row gap-5 pt-8 flex-1 order-2 xl:order-1">
<BusinessOpportunities /> <BusinessOpportunities />
<PolicyAndLaws /> <PolicyAndLaws />
</div>
<Advertisements />
</section> </section>
</div> </div>
......
'use client'; 'use client';
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { Globe2, Newspaper, TrendingUp } from "lucide-react"; import { FileText, Globe2, Newspaper, TrendingUp } from "lucide-react";
import ImageNext from "@/components/shared/image-next"; import ImageNext from "@/components/shared/image-next";
import type { DynamicPostItem } from "../types"; import type { DynamicPostItem } from "../types";
...@@ -236,7 +236,9 @@ export default function MarketProfilePage({ post }: MarketProfilePageProps) { ...@@ -236,7 +236,9 @@ export default function MarketProfilePage({ post }: MarketProfilePageProps) {
<div className="mt-7 overflow-hidden rounded-[30px] border border-[#dce7f7] bg-white shadow-[0_18px_42px_rgba(17,24,39,0.06)]"> <div className="mt-7 overflow-hidden rounded-[30px] border border-[#dce7f7] bg-white shadow-[0_18px_42px_rgba(17,24,39,0.06)]">
<div className="relative min-h-[280px] bg-[#f3f7ff] p-4 sm:p-5"> <div className="relative min-h-[280px] bg-[#f3f7ff] p-4 sm:p-5">
<div className="absolute inset-x-4 top-4 z-10 flex flex-wrap gap-2 sm:inset-x-6 sm:top-6"> <div className="absolute inset-x-4 top-4 z-10 flex flex-wrap gap-2 sm:inset-x-6 sm:top-6">
{activeRegion.markets.map((item) => ( {activeRegion.markets.map((item) => {
const hasDoc = item.href && item.href !== "#";
return (
<a <a
key={`${activeRegion.key}-${item.name}`} key={`${activeRegion.key}-${item.name}`}
href={item.href} href={item.href}
...@@ -246,8 +248,12 @@ export default function MarketProfilePage({ post }: MarketProfilePageProps) { ...@@ -246,8 +248,12 @@ export default function MarketProfilePage({ post }: MarketProfilePageProps) {
> >
<span className={`h-2.5 w-2.5 rounded-full ${item.tone}`} /> <span className={`h-2.5 w-2.5 rounded-full ${item.tone}`} />
<span>{item.name}</span> <span>{item.name}</span>
{hasDoc && (
<FileText className="h-3.5 w-3.5 text-[#2450b5]" />
)}
</a> </a>
))} );
})}
</div> </div>
<ImageNext <ImageNext
......
...@@ -143,6 +143,28 @@ export default function AdminNewsletterEmailsPage() { ...@@ -143,6 +143,28 @@ export default function AdminNewsletterEmailsPage() {
return ( return (
<div className="space-y-8"> <div className="space-y-8">
<div className="flex items-start gap-3 rounded-xl border border-red-300 bg-red-50 px-4 py-3 text-red-800">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="mt-0.5 h-5 w-5 shrink-0 text-red-500"
>
<path
fillRule="evenodd"
d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25Zm-1.5 5.25a.75.75 0 0 0-.75.75v6a.75.75 0 0 0 1.5 0v-6a.75.75 0 0 0-.75-.75Zm0 9a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
clipRule="evenodd"
/>
</svg>
<div className="text-sm">
<p className="font-semibold">Cảnh báo xóa dữ liệu</p>
<p className="mt-0.5 text-red-700">
Việc xóa email sẽ làm mất dữ liệu hoàn toàn và không thể hoàn tác
được. Vui lòng cân nhắc thật kỹ trước khi xóa.
</p>
</div>
</div>
<AdminTableLayout <AdminTableLayout
searchValue={search} searchValue={search}
searchPlaceholder="Tìm kiếm email đăng ký..." searchPlaceholder="Tìm kiếm email đăng ký..."
......
...@@ -326,6 +326,30 @@ export default function HeaderConfigPage() { ...@@ -326,6 +326,30 @@ export default function HeaderConfigPage() {
return ( return (
<div className="space-y-8"> <div className="space-y-8">
<div className="flex items-start gap-3 rounded-xl border border-amber-300 bg-amber-50 px-4 py-3 text-amber-800">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
className="mt-0.5 h-5 w-5 shrink-0 text-amber-500"
>
<path
fillRule="evenodd"
d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z"
clipRule="evenodd"
/>
</svg>
<div className="text-sm">
<p className="font-semibold">Lưu ý quan trọng</p>
<p className="mt-0.5 text-amber-700">
Việc chỉnh sửa hoặc xóa danh mục sẽ ảnh hưởng trực tiếp đến cấu trúc
Website và có thể gây mất dữ liệu trang liên quan. Vui lòng thao tác
cẩn thận và kiểm tra kỹ trước khi xác nhận. Nếu phát sinh lỗi, xin vui
lòng báo ngay cho bên kỹ thuật để được hỗ trợ.
</p>
</div>
</div>
<HeaderCategoryStats <HeaderCategoryStats
total={flatRows.length} total={flatRows.length}
root={flatRows.filter((item) => !item.parentId).length} root={flatRows.filter((item) => !item.parentId).length}
......
...@@ -33,6 +33,24 @@ import { ...@@ -33,6 +33,24 @@ import {
const PAGE_SIZE = 10; const PAGE_SIZE = 10;
function resolveApiError(error: unknown, fallback: string) {
if (error && typeof error === "object" && "response" in error) {
const axiosError = error as { response?: { status?: number; data?: { message?: string; error?: string } } };
const status = axiosError.response?.status;
const apiMessage = axiosError.response?.data?.message || axiosError.response?.data?.error;
if (status && apiMessage) {
return `[Lỗi ${status}] ${apiMessage}`;
}
if (status) {
return `[Lỗi ${status}] ${fallback}`;
}
}
if (error instanceof Error && error.message) {
return error.message;
}
return fallback;
}
const inputClassName = const inputClassName =
"rounded-2xl border-[#063e8e]/15 bg-white text-gray-700 shadow-sm placeholder:text-gray-400 focus-visible:ring-[#063e8e]/20"; "rounded-2xl border-[#063e8e]/15 bg-white text-gray-700 shadow-sm placeholder:text-gray-400 focus-visible:ring-[#063e8e]/20";
...@@ -305,7 +323,7 @@ export default function AdminMediaPage() { ...@@ -305,7 +323,7 @@ export default function AdminMediaPage() {
setItems(result.rows); setItems(result.rows);
setTotal(result.count); setTotal(result.count);
} catch (error) { } catch (error) {
toast.error(error instanceof Error ? error.message : "Không thể tải danh sách ảnh"); toast.error(resolveApiError(error, "Không thể tải danh sách ảnh"));
setItems([]); setItems([]);
setTotal(0); setTotal(0);
} finally { } finally {
...@@ -337,11 +355,11 @@ export default function AdminMediaPage() { ...@@ -337,11 +355,11 @@ export default function AdminMediaPage() {
file: data.file, file: data.file,
original: data.name, original: data.name,
}); });
toast.success("Đã tải ảnh lên"); toast.success("Đã tải ảnh lên thành công");
setDialogOpen(false); setDialogOpen(false);
await load(); await load();
} catch (error) { } catch (error) {
toast.error(error instanceof Error ? error.message : "Không thể tải ảnh lên"); toast.error(resolveApiError(error, "Không thể tải ảnh lên"));
} finally { } finally {
setSaving(false); setSaving(false);
} }
...@@ -352,11 +370,11 @@ export default function AdminMediaPage() { ...@@ -352,11 +370,11 @@ export default function AdminMediaPage() {
try { try {
await deleteCmsFile(deleteTarget.id); await deleteCmsFile(deleteTarget.id);
toast.success("Đã xóa ảnh"); toast.success("Đã xóa ảnh thành công");
setDeleteTarget(null); setDeleteTarget(null);
await load(); await load();
} catch (error) { } catch (error) {
toast.error(error instanceof Error ? error.message : "Không thể xóa ảnh"); toast.error(resolveApiError(error, "Không thể xóa ảnh"));
} }
}; };
......
...@@ -9,20 +9,6 @@ const LEGACY_MEDIA_HOSTS = new Set([ ...@@ -9,20 +9,6 @@ const LEGACY_MEDIA_HOSTS = new Set([
const normalizeOrigin = (value?: string | null) => const normalizeOrigin = (value?: string | null) =>
value?.trim().replace(/\/+$/, "").replace(/\/api\/v\d+\.\d+$/, "") || ""; value?.trim().replace(/\/+$/, "").replace(/\/api\/v\d+\.\d+$/, "") || "";
const extractUploadPath = (pathname: string) => {
const markers = ["/api/uploads/", "/uploads/", "/images/", "/wp-content/uploads/"];
for (const marker of markers) {
const index = pathname.indexOf(marker);
if (index >= 0) {
const cleanPath = pathname.slice(index).replace(/^\/api\/uploads\//, "/uploads/");
return cleanPath;
}
}
return null;
};
const normalizeUploadPath = (pathname: string) => { const normalizeUploadPath = (pathname: string) => {
if (pathname.includes("/api/uploads/")) { if (pathname.includes("/api/uploads/")) {
return pathname.replace(/^.*\/api\/uploads\//, "/uploads/"); return pathname.replace(/^.*\/api\/uploads\//, "/uploads/");
...@@ -107,6 +93,13 @@ export const resolveUploadUrl = (value?: string | null) => { ...@@ -107,6 +93,13 @@ export const resolveUploadUrl = (value?: string | null) => {
if (/^https?:\/\//i.test(trimmed)) { if (/^https?:\/\//i.test(trimmed)) {
try { try {
const url = new URL(trimmed); const url = new URL(trimmed);
// Keep URLs from current backend/frontend origins as-is
const currentOrigins = [backendOrigin, frontendOrigin].filter(Boolean);
if (currentOrigins.some(origin => url.origin === origin)) {
return trimmed;
}
const normalizedPath = normalizeUploadPath(url.pathname); const normalizedPath = normalizeUploadPath(url.pathname);
if (normalizedPath !== url.pathname) { if (normalizedPath !== url.pathname) {
...@@ -114,10 +107,8 @@ export const resolveUploadUrl = (value?: string | null) => { ...@@ -114,10 +107,8 @@ export const resolveUploadUrl = (value?: string | null) => {
} }
if (LEGACY_MEDIA_HOSTS.has(url.hostname)) { if (LEGACY_MEDIA_HOSTS.has(url.hostname)) {
const uploadPath = extractUploadPath(url.pathname); // Keep legacy media URLs as-is since they already have correct domain
if (uploadPath) { return trimmed;
return backendOrigin ? `${backendOrigin}${uploadPath}` : uploadPath;
}
} }
} catch { } catch {
return trimmed; return trimmed;
......
declare module "swiper/css";
declare module "swiper/css/bundle";
declare module "swiper/css/effect-fade";
declare module "swiper/css/navigation";
declare module "swiper/css/pagination";
declare module "swiper/css/scrollbar";
declare module "swiper/css/thumbs";
declare module "swiper/css/free-mode";
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