Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
VCCI-News
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Văn Hoàng
VCCI-News
Commits
d5228e26
Commit
d5228e26
authored
Sep 06, 2026
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fixL change import image next optimize to components next unoptimize
parent
54b553bc
Changes
32
Hide whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
124 additions
and
145 deletions
+124
-145
index.tsx
src/app/(main)/(home)/components/banner/index.tsx
+3
-8
index.tsx
src/app/(main)/(home)/components/events/index.tsx
+7
-5
index.tsx
src/app/(main)/(home)/components/featured-news/index.tsx
+7
-5
index.tsx
...p/(main)/(home)/components/horizontal-ad-banner/index.tsx
+4
-19
index.tsx
src/app/(main)/(home)/components/members/index.tsx
+13
-9
index.tsx
src/app/(main)/(home)/components/news/index.tsx
+13
-9
index.tsx
src/app/(main)/(home)/components/quick-links/index.tsx
+6
-10
index.tsx
src/app/(main)/(home)/components/video-and-patners/index.tsx
+8
-6
ArticlePage.tsx
src/app/(main)/[...slug]/templates/ArticlePage.tsx
+2
-2
CatalogPage.tsx
src/app/(main)/[...slug]/templates/CatalogPage.tsx
+2
-2
StructuredPostContent.tsx
src/app/(main)/[...slug]/templates/StructuredPostContent.tsx
+2
-2
AboutVcciHcmPage.tsx
......slug]/templates/information-pages/AboutVcciHcmPage.tsx
+2
-2
MarketProfilePage.tsx
.....slug]/templates/information-pages/MarketProfilePage.tsx
+2
-2
page.tsx
src/app/(main)/search/page.tsx
+2
-2
page.tsx
src/app/(main)/video/page.tsx
+2
-2
advertisement-list.tsx
src/app/admin/advertisements/advertisement-list.tsx
+3
-3
config-item-dialog.tsx
src/app/admin/base-config/_components/config-item-dialog.tsx
+2
-2
config-item-preview.tsx
...app/admin/base-config/_components/config-item-preview.tsx
+2
-2
page.tsx
src/app/admin/base-config/page.tsx
+3
-3
page.tsx
src/app/admin/dashboard/page.tsx
+3
-3
page.tsx
src/app/admin/header-config/[categoryId]/page.tsx
+2
-2
media-form-dialog.tsx
src/app/admin/media/_components/media-form-dialog.tsx
+2
-2
page.tsx
src/app/admin/media/page.tsx
+2
-2
member-form.tsx
src/app/admin/members/[id]/_components/member-form.tsx
+2
-2
member-row.tsx
src/app/admin/members/_components/member-row.tsx
+2
-2
admin-news-form-content.tsx
...p/admin/news/[id]/_components/admin-news-form-content.tsx
+2
-2
page.tsx
src/app/admin/news/page.tsx
+2
-2
image-picker.tsx
src/components/admin/image-picker.tsx
+2
-2
post-content-editor.tsx
src/components/admin/post-content-editor.tsx
+2
-2
header.tsx
src/components/layout/main/header.tsx
+5
-13
safe-image.tsx
src/components/shared/safe-image.tsx
+7
-7
index.tsx
src/components/shared/sidebar-advertisements/index.tsx
+6
-9
No files found.
src/app/(main)/(home)/components/banner/index.tsx
View file @
d5228e26
"use client"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
Swiper
,
SwiperSlide
}
from
"swiper/react"
;
import
{
Autoplay
}
from
"swiper/modules"
;
import
{
Swiper
as
SwiperType
}
from
"swiper/types"
;
...
...
@@ -84,18 +84,13 @@ function BannerSlideItem({
:
"/thumbnail.png"
;
return
(
<
Image
<
Safe
Image
src=
{
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
alt
}
width=
{
2560
}
height=
{
720
}
sizes=
"100vw"
onError=
{
(
e
)
=>
{
const
img
=
e
.
currentTarget
as
HTMLImageElement
;
if
(
!
img
.
src
.
includes
(
"thumbnail.png"
))
{
img
.
src
=
"/thumbnail.png"
;
}
}
}
className=
"w-full h-[220px] sm:h-[320px] md:h-[430px] lg:h-[540px] object-cover"
/>
);
...
...
src/app/(main)/(home)/components/events/index.tsx
View file @
d5228e26
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
useHomePosts
}
from
"@/app/(main)/(home)/lib/use-home-posts"
;
import
dayjs
from
"dayjs"
;
import
Link
from
"next/link"
;
...
...
@@ -48,8 +48,9 @@ function Events() {
className=
"group flex h-full cursor-pointer flex-col overflow-hidden rounded-[14px] bg-white text-[#20408f] shadow-[0_14px_28px_rgba(10,39,95,0.18)]"
>
<
div
className=
"relative h-[180px] overflow-hidden md:h-[220px] xl:h-[248px]"
>
<
Image
src=
{
featuredEvent
.
thumbnail
?.
url
??
featuredFallback
}
<
SafeImage
src=
{
featuredEvent
.
thumbnail
?.
url
}
fallbackSrc=
{
featuredFallback
}
alt=
{
featuredEvent
.
thumbnail
?.
alt
||
featuredEvent
.
title
}
width=
{
720
}
height=
{
520
}
...
...
@@ -114,8 +115,9 @@ function Events() {
className=
"group flex flex-1 cursor-pointer items-center gap-3 rounded-[14px] 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-[14px]"
>
<
Image
src=
{
item
.
thumbnail
?.
url
??
sideFallbacks
[
index
]
}
<
SafeImage
src=
{
item
.
thumbnail
?.
url
}
fallbackSrc=
{
sideFallbacks
[
index
]
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
width=
{
160
}
height=
{
160
}
...
...
src/app/(main)/(home)/components/featured-news/index.tsx
View file @
d5228e26
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
useHomePosts
}
from
"@/app/(main)/(home)/lib/use-home-posts"
;
import
dayjs
from
"dayjs"
;
import
{
ChevronRight
}
from
"lucide-react"
;
...
...
@@ -51,8 +51,9 @@ function FeaturedNews() {
className=
"group relative block cursor-pointer overflow-hidden rounded-[16px] bg-[#0d2f5f] shadow-[0_16px_32px_rgba(28,52,120,0.2)] md:rounded-[16px] md:min-h-[320px] lg:min-h-[380px]"
>
<
div
className=
"relative h-full min-h-[195px] md:min-h-[320px] lg:min-h-[380px]"
>
<
Image
src=
{
primaryItem
.
thumbnail
?.
url
??
primaryFallback
}
<
SafeImage
src=
{
primaryItem
.
thumbnail
?.
url
}
fallbackSrc=
{
primaryFallback
}
alt=
{
primaryItem
.
thumbnail
?.
alt
||
primaryItem
.
title
}
width=
{
1200
}
height=
{
800
}
...
...
@@ -97,8 +98,9 @@ function FeaturedNews() {
className=
"group relative block cursor-pointer overflow-hidden rounded-[16px] bg-[#27447f] shadow-[0_16px_32px_rgba(28,52,120,0.2)] min-h-[165px]"
>
<
div
className=
"relative flex h-full min-h-[165px]"
>
<
Image
src=
{
item
.
thumbnail
?.
url
??
secondaryFallbacks
[
index
]
}
<
SafeImage
src=
{
item
.
thumbnail
?.
url
}
fallbackSrc=
{
secondaryFallbacks
[
index
]
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
width=
{
600
}
height=
{
420
}
...
...
src/app/(main)/(home)/components/horizontal-ad-banner/index.tsx
View file @
d5228e26
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
Link
from
"next/link"
;
import
{
useState
}
from
"react"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
links
from
"@/links"
;
...
...
@@ -14,16 +13,8 @@ function HorizontalAdBanner() {
const
ad
=
ads
[
0
];
const
href
=
ad
?.
link
||
FALLBACK_HREF
;
const
initialSrc
=
ad
?.
file
?.
path
?
links
.
resolveImageUrl
(
ad
.
file
.
path
)
:
FALLBACK_SRC
;
const
[
src
,
setSrc
]
=
useState
(
initialSrc
);
const
[
errored
,
setErrored
]
=
useState
(
false
);
const
src
=
ad
?.
file
?.
path
?
links
.
resolveImageUrl
(
ad
.
file
.
path
)
:
FALLBACK_SRC
;
// Reset state khi ad thay đổi
if
(
ad
?.
file
?.
path
&&
!
errored
&&
src
!==
initialSrc
)
{
setSrc
(
initialSrc
);
}
const
isGif
=
src
.
toLowerCase
().
endsWith
(
".gif"
);
const
title
=
ad
?.
name
||
"Quảng cáo VCCI HCM"
;
const
alt
=
ad
?.
alt
||
title
;
...
...
@@ -36,19 +27,13 @@ function HorizontalAdBanner() {
style=
{
{
aspectRatio
:
"1600 / 200"
}
}
title=
{
title
}
>
<
Image
<
Safe
Image
src=
{
src
}
fallbackSrc=
{
FALLBACK_SRC
}
alt=
{
alt
}
fill
sizes=
"100vw"
className=
"object-cover"
unoptimized=
{
isGif
}
onError=
{
()
=>
{
if
(
src
!==
FALLBACK_SRC
)
{
setSrc
(
FALLBACK_SRC
);
setErrored
(
true
);
}
}
}
/>
</
Link
>
);
...
...
src/app/(main)/(home)/components/members/index.tsx
View file @
d5228e26
'use client'
;
import
{
useHomePosts
}
from
"@/app/(main)/(home)/lib/use-home-posts"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
ChevronRight
}
from
"lucide-react"
;
import
memberImages
from
"@/constants/memberImages"
;
import
{
MOCK_FEATURED_MEMBERS_RESPONSE
}
from
"@/mockdata/bff-fallback"
;
...
...
@@ -113,8 +113,9 @@ function Members() {
>
<
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"
>
<
Image
src=
{
resolveMemberImage
(
member
.
avatar
,
index
)
??
"/img-error.png"
}
<
SafeImage
src=
{
resolveMemberImage
(
member
.
avatar
,
index
)
}
fallbackSrc=
"/img-error.png"
alt=
{
member
.
name
??
""
}
width=
{
260
}
height=
{
180
}
...
...
@@ -130,8 +131,9 @@ function Members() {
<>
<
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"
>
<
Image
src=
{
resolveMemberImage
(
member
.
avatar
,
index
)
??
"/img-error.png"
}
<
SafeImage
src=
{
resolveMemberImage
(
member
.
avatar
,
index
)
}
fallbackSrc=
"/img-error.png"
alt=
{
member
.
name
??
""
}
width=
{
260
}
height=
{
180
}
...
...
@@ -212,8 +214,9 @@ function Members() {
className=
"group relative block cursor-pointer overflow-hidden rounded-[14px] shadow-[0_16px_32px_rgba(31,59,124,0.12)]"
>
<
div
className=
"aspect-[16/10] overflow-hidden xl:aspect-[1.25/1]"
>
<
Image
src=
{
item
.
thumbnail
?.
url
??
MEMBER_CONNECTION_FALLBACK_IMAGE
}
<
SafeImage
src=
{
item
.
thumbnail
?.
url
}
fallbackSrc=
{
MEMBER_CONNECTION_FALLBACK_IMAGE
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
width=
{
520
}
height=
{
420
}
...
...
@@ -254,8 +257,9 @@ function Members() {
className=
"group relative block cursor-pointer overflow-hidden rounded-[14px] shadow-[0_16px_32px_rgba(31,59,124,0.12)]"
>
<
div
className=
"aspect-[16/10] overflow-hidden"
>
<
Image
src=
{
item
.
thumbnail
?.
url
??
MEMBER_CONNECTION_FALLBACK_IMAGE
}
<
SafeImage
src=
{
item
.
thumbnail
?.
url
}
fallbackSrc=
{
MEMBER_CONNECTION_FALLBACK_IMAGE
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
width=
{
520
}
height=
{
420
}
...
...
src/app/(main)/(home)/components/news/index.tsx
View file @
d5228e26
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
useHomePosts
}
from
"@/app/(main)/(home)/lib/use-home-posts"
;
import
dayjs
from
"dayjs"
;
import
Link
from
"next/link"
;
...
...
@@ -83,8 +83,9 @@ function News() {
className=
"group block cursor-pointer overflow-hidden rounded-[16px] border border-[#dbe4f2] bg-white shadow-[0_8px_24px_rgba(31,59,124,0.08)]"
>
<
div
className=
"relative aspect-[1.4/1] overflow-hidden"
>
<
Image
src=
{
featuredArticle
.
thumbnail
?.
url
??
"/thumbnail.png"
}
<
SafeImage
src=
{
featuredArticle
.
thumbnail
?.
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
featuredArticle
.
thumbnail
?.
alt
||
featuredArticle
.
title
}
width=
{
720
}
height=
{
580
}
...
...
@@ -151,8 +152,9 @@ function News() {
className=
"group flex flex-1 cursor-pointer items-center gap-3 rounded-[16px] border border-[#dbe4f2] bg-white px-4 py-2.5 shadow-[0_8px_24px_rgba(31,59,124,0.08)] transition-all hover:-translate-y-0.5 hover:shadow-[0_14px_28px_rgba(31,59,124,0.12)]"
>
<
div
className=
"h-20 w-20 shrink-0 overflow-hidden rounded-[14px]"
>
<
Image
src=
{
news
.
thumbnail
?.
url
??
"/thumbnail.png"
}
<
SafeImage
src=
{
news
.
thumbnail
?.
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
news
.
thumbnail
?.
alt
||
news
.
title
}
width=
{
160
}
height=
{
160
}
...
...
@@ -227,8 +229,9 @@ function News() {
className=
"group block cursor-pointer overflow-hidden rounded-[16px] border border-[#dbe4f2] bg-white shadow-[0_8px_24px_rgba(31,59,124,0.08)]"
>
<
div
className=
"relative aspect-[16/9] overflow-hidden"
>
<
Image
src=
{
featuredArticle
.
thumbnail
?.
url
??
"/thumbnail.png"
}
<
SafeImage
src=
{
featuredArticle
.
thumbnail
?.
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
featuredArticle
.
thumbnail
?.
alt
||
featuredArticle
.
title
}
width=
{
720
}
height=
{
580
}
...
...
@@ -292,8 +295,9 @@ function News() {
className=
"group flex cursor-pointer items-center gap-3 rounded-[16px] border border-[#dbe4f2] bg-white px-4 py-2.5 shadow-[0_8px_24px_rgba(31,59,124,0.08)] transition-all hover:-translate-y-0.5 hover:shadow-[0_14px_28px_rgba(31,59,124,0.12)]"
>
<
div
className=
"h-16 w-16 shrink-0 overflow-hidden rounded-[14px]"
>
<
Image
src=
{
news
.
thumbnail
?.
url
??
"/thumbnail.png"
}
<
SafeImage
src=
{
news
.
thumbnail
?.
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
news
.
thumbnail
?.
alt
||
news
.
title
}
width=
{
160
}
height=
{
160
}
...
...
src/app/(main)/(home)/components/quick-links/index.tsx
View file @
d5228e26
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
Link
from
"next/link"
;
import
{
useMemo
,
useState
}
from
"react"
;
import
{
useMemo
}
from
"react"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
links
from
"@/links"
;
import
type
{
Advertisement
}
from
"@/api/vcci-news/models/advertisement"
;
...
...
@@ -11,8 +11,7 @@ import { getFallbackImage } from "@/lib/utils/fallback-image";
const
FALLBACK_HREF
=
links
.
externalApiOrigin
;
function
AdItem
({
item
,
fallbackSrc
}:
{
item
:
Advertisement
;
fallbackSrc
:
string
})
{
const
initialSrc
=
item
.
file
?.
path
?
links
.
resolveImageUrl
(
item
.
file
.
path
)
:
fallbackSrc
;
const
[
src
,
setSrc
]
=
useState
(
initialSrc
);
const
src
=
item
.
file
?.
path
?
links
.
resolveImageUrl
(
item
.
file
.
path
)
:
fallbackSrc
;
return
(
<
Link
...
...
@@ -23,16 +22,13 @@ function AdItem({ item, fallbackSrc }: { item: Advertisement; fallbackSrc: strin
title=
{
item
.
name
}
>
<
div
className=
"aspect-[16/10] overflow-hidden sm:aspect-[16/10] lg:aspect-[7/4] xl:aspect-[3/2]"
>
<
Image
<
Safe
Image
src=
{
src
}
fallbackSrc=
{
fallbackSrc
}
alt=
{
item
.
alt
||
item
.
name
}
width=
{
2048
}
height=
{
1365
}
className=
"h-full w-full object-cover object-[center_80%]"
unoptimized
onError=
{
()
=>
{
if
(
src
!==
fallbackSrc
)
setSrc
(
fallbackSrc
);
}
}
/>
</
div
>
</
Link
>
...
...
@@ -49,7 +45,7 @@ function FallbackAdItem({ src }: { src: string }) {
title=
"Quảng cáo VCCI HCM"
>
<
div
className=
"aspect-[16/10] overflow-hidden sm:aspect-[16/10] lg:aspect-[7/4] xl:aspect-[3/2]"
>
<
Image
<
Safe
Image
src=
{
src
}
alt=
"Quảng cáo VCCI HCM"
width=
{
2048
}
...
...
src/app/(main)/(home)/components/video-and-patners/index.tsx
View file @
d5228e26
"use client"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
partnerImages
from
"@/constants/partnerImages"
;
import
{
ChevronRight
,
Play
}
from
"lucide-react"
;
import
Link
from
"next/link"
;
...
...
@@ -68,8 +68,9 @@ const renderPartnerContent = (partners: Organization[]) => {
className=
"block"
>
<
div
className=
"flex h-[96px] items-center justify-center rounded-[16px] 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]"
>
<
Image
src=
{
resolvePartnerImage
(
partner
.
avatar
,
index
)
??
"/img-error.png"
}
<
SafeImage
src=
{
resolvePartnerImage
(
partner
.
avatar
,
index
)
}
fallbackSrc=
"/img-error.png"
alt=
{
partner
.
name
??
""
}
width=
{
140
}
height=
{
72
}
...
...
@@ -79,8 +80,9 @@ const renderPartnerContent = (partners: Organization[]) => {
</
a
>
)
:
(
<
div
className=
"flex h-[96px] items-center justify-center rounded-[16px] border border-[#edf1f7] bg-white px-5 py-4 shadow-[0_8px_20px_rgba(31,59,124,0.05)] xl:h-[151px]"
>
<
Image
src=
{
resolvePartnerImage
(
partner
.
avatar
,
index
)
??
"/img-error.png"
}
<
SafeImage
src=
{
resolvePartnerImage
(
partner
.
avatar
,
index
)
}
fallbackSrc=
"/img-error.png"
alt=
{
partner
.
name
??
""
}
width=
{
140
}
height=
{
72
}
...
...
@@ -179,7 +181,7 @@ function VideoAndPartners() {
className=
"overflow-hidden rounded-[16px] border border-[#e5ebf4] bg-white shadow-[0_10px_22px_rgba(31,59,124,0.08)] transition-all hover:-translate-y-0.5 hover:shadow-[0_16px_30px_rgba(31,59,124,0.12)]"
>
<
div
className=
"group relative aspect-[1.95/1] overflow-hidden"
>
<
Image
<
Safe
Image
src=
{
video
.
thumbnail
}
alt=
{
video
.
name
}
width=
{
640
}
...
...
src/app/(main)/[...slug]/templates/ArticlePage.tsx
View file @
d5228e26
...
...
@@ -5,7 +5,7 @@ import Link from "next/link";
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
Spinner
}
from
"@/components/ui"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
ListCategory
from
"@/components/base/list-category"
;
...
...
@@ -155,7 +155,7 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp
className=
"group grid items-center gap-5 sm:grid-cols-[250px_minmax(0,1fr)]"
>
<
div
className=
"relative overflow-hidden rounded-md bg-[#edf1f5] aspect-[25/15] sm:aspect-[5/3]"
>
<
Image
<
Safe
Image
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
alt=
{
item
.
title
}
width=
{
520
}
...
...
src/app/(main)/[...slug]/templates/CatalogPage.tsx
View file @
d5228e26
...
...
@@ -5,7 +5,7 @@ import Link from "next/link";
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
Spinner
}
from
"@/components/ui"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
ListCategory
from
"@/components/base/list-category"
;
...
...
@@ -108,7 +108,7 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp
>
<
div
className=
"overflow-hidden bg-white shadow-[0_10px_24px_rgba(17,24,39,0.08)]"
>
<
div
className=
"relative aspect-3/4 overflow-hidden bg-white"
>
<
Image
<
Safe
Image
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
alt=
{
item
.
title
}
width=
{
520
}
...
...
src/app/(main)/[...slug]/templates/StructuredPostContent.tsx
View file @
d5228e26
"use client"
;
import
parse
from
"html-react-parser"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
getDynamicPostBodyHtml
}
from
"./data"
;
import
type
{
DynamicPostContentSection
,
DynamicPostItem
}
from
"./types"
;
...
...
@@ -32,7 +32,7 @@ function StructuredImageSection({ section }: { section: DynamicPostContentSectio
key=
{
`${section.id}-${image.id || image.url}-${item.position}`
}
className=
"overflow-hidden rounded-[18px] bg-white"
>
<
Image
<
Safe
Image
src=
{
image
.
url
}
alt=
{
image
.
alt
||
image
.
name
||
"Hình ảnh bài viết"
}
width=
{
1200
}
...
...
src/app/(main)/[...slug]/templates/information-pages/AboutVcciHcmPage.tsx
View file @
d5228e26
...
...
@@ -6,7 +6,7 @@ import { ShieldCheck, Target, Zap } from "lucide-react";
import
parse
from
"html-react-parser"
;
import
Link
from
"next/link"
;
import
{
useGetApiV10Post
}
from
"@/api/vcci-news/endpoints/post"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
buildDynamicPostHref
,
buildVisibleNewsFilters
,
stripHtml
}
from
"../data"
;
import
links
from
"@/links"
;
import
StructuredPostContent
from
"../StructuredPostContent"
;
...
...
@@ -325,7 +325,7 @@ export default function AboutVcciHcmPage({
className=
"group overflow-hidden rounded-[22px] bg-white shadow-[0_18px_38px_rgba(28,52,120,0.16)] transition-transform hover:-translate-y-1"
>
<
div
className=
"relative aspect-[1.28] overflow-hidden"
>
<
Image
<
Safe
Image
src=
{
item
.
thumbnailUrl
}
alt=
{
item
.
thumbnailAlt
}
width=
{
720
}
...
...
src/app/(main)/[...slug]/templates/information-pages/MarketProfilePage.tsx
View file @
d5228e26
...
...
@@ -2,7 +2,7 @@
import
{
useMemo
,
useState
}
from
"react"
;
import
{
FileText
,
Globe2
,
Newspaper
,
TrendingUp
}
from
"lucide-react"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
type
{
DynamicPostItem
}
from
"../types"
;
type
MarketProfilePageProps
=
{
...
...
@@ -256,7 +256,7 @@ export default function MarketProfilePage({ post }: MarketProfilePageProps) {
})
}
</
div
>
<
Image
<
Safe
Image
src=
{
activeRegion
.
image
}
alt=
{
activeRegion
.
imageAlt
}
width=
{
1200
}
...
...
src/app/(main)/search/page.tsx
View file @
d5228e26
...
...
@@ -3,7 +3,7 @@
import
{
Suspense
,
useEffect
,
useState
}
from
"react"
;
import
Link
from
"next/link"
;
import
{
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
SidebarAdvertisements
from
"@/components/shared/sidebar-advertisements"
;
import
{
Pagination
}
from
"@components/base/pagination"
;
import
{
Button
}
from
"@/components/ui/button"
;
...
...
@@ -54,7 +54,7 @@ function SearchResultItem({ item, index }: { item: DynamicPostItem; index: numbe
className=
"group grid gap-5 sm:grid-cols-[250px_minmax(0,1fr)]"
>
<
div
className=
"overflow-hidden rounded-md bg-[#edf1f5]"
>
<
Image
<
Safe
Image
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
alt=
{
item
.
title
}
width=
{
520
}
...
...
src/app/(main)/video/page.tsx
View file @
d5228e26
...
...
@@ -3,7 +3,7 @@
import
{
Suspense
}
from
"react"
;
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
Play
}
from
"lucide-react"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
import
{
Spinner
}
from
"@/components/ui/spinner"
;
import
{
useGetApiV10Video
}
from
"@/api/vcci-news/endpoints/video"
;
...
...
@@ -106,7 +106,7 @@ function VideoPageContent() {
className=
"group overflow-hidden rounded-[18px] border border-[#e5ebf4] bg-white shadow-[0_12px_30px_rgba(31,59,124,0.08)] transition-all hover:-translate-y-0.5 hover:shadow-[0_18px_38px_rgba(31,59,124,0.14)]"
>
<
div
className=
"relative aspect-video overflow-hidden bg-[#edf1f5]"
>
<
Image
<
Safe
Image
src=
{
video
.
thumbnail
}
alt=
{
video
.
name
}
width=
{
900
}
...
...
src/app/admin/advertisements/advertisement-list.tsx
View file @
d5228e26
...
...
@@ -45,7 +45,7 @@ import {
import
{
PermissionGate
}
from
"@/components/shared/permission-gate"
;
import
{
usePermission
}
from
"@/hooks/usePermission"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
type
{
AdminMediaItem
}
from
"@/mockdata/admin-news"
;
import
links
from
"@/links"
;
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
...
...
@@ -369,7 +369,7 @@ export function AdvertisementList({
style=
{
{
aspectRatio
:
previewAspect
,
width
:
type
===
"horizontal"
?
112
:
72
}
}
>
{
imageUrl
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
imageUrl
}
alt=
{
ad
.
alt
||
ad
.
name
}
fill
...
...
@@ -514,7 +514,7 @@ export function AdvertisementList({
style=
{
{
aspectRatio
:
previewAspect
,
width
:
type
===
"horizontal"
?
200
:
120
}
}
>
{
form
.
filePreviewUrl
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
form
.
filePreviewUrl
}
alt=
"Preview"
fill
...
...
src/app/admin/base-config/_components/config-item-dialog.tsx
View file @
d5228e26
"use client"
;
import
{
ImagePlus
,
Save
}
from
"lucide-react"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Dialog
,
...
...
@@ -114,7 +114,7 @@ export function ConfigItemDialog({
<
div
className=
"overflow-hidden rounded-3xl border border-dashed border-[#063e8e]/20 bg-[#eef4ff]/60"
>
<
div
className=
"relative aspect-[16/9]"
>
{
previewMedia
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
previewMedia
.
url
}
alt=
{
previewMedia
.
alt
||
previewMedia
.
name
}
fill
...
...
src/app/admin/base-config/_components/config-item-preview.tsx
View file @
d5228e26
...
...
@@ -2,7 +2,7 @@
import
type
{
BaseConfigBannerItem
}
from
"@/mockdata/base-config"
;
import
type
{
AdminMediaItem
}
from
"@/mockdata/admin-news"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
export
function
ConfigItemPreview
({
title
,
...
...
@@ -28,7 +28,7 @@ export function ConfigItemPreview({
>
<
div
className=
"relative aspect-[16/10] overflow-hidden bg-[#eef4ff]"
>
{
media
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
media
.
url
}
alt=
{
media
.
alt
||
media
.
name
}
fill
...
...
src/app/admin/base-config/page.tsx
View file @
d5228e26
...
...
@@ -14,7 +14,7 @@ import {
import
{
toast
}
from
"sonner"
;
import
{
AdminDeleteDialog
}
from
"@/components/admin/admin-delete-dialog"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Badge
}
from
"@/components/ui/badge"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
...
...
@@ -845,7 +845,7 @@ export default function AdminBaseConfigPage() {
<
div
className=
"relative flex min-h-[320px] items-center justify-center overflow-hidden rounded-[24px] border border-dashed border-[#063e8e]/18 bg-[#eef4ff]"
>
{
currentLogoMedia
?
(
<
div
className=
"relative h-[220px] w-[220px]"
>
<
Safe
Next
Image
<
SafeImage
src=
{
currentLogoMedia
.
url
}
alt=
{
currentLogoMedia
.
alt
||
currentLogoMedia
.
name
}
fill
...
...
@@ -993,7 +993,7 @@ export default function AdminBaseConfigPage() {
<
div
className=
"rounded-[28px] border border-[#063e8e]/10 bg-[#f8fbff] p-4 sm:p-5"
>
<
div
className=
"relative aspect-[16/6] overflow-hidden rounded-[24px] border border-[#063e8e]/12 bg-[#eef4ff]"
>
{
currentBannerMedia
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
currentBannerMedia
.
url
}
alt=
{
currentBannerMedia
.
alt
||
currentBannerMedia
.
name
}
fill
...
...
src/app/admin/dashboard/page.tsx
View file @
d5228e26
...
...
@@ -16,7 +16,7 @@ import {
Sparkles
,
Users
,
}
from
"lucide-react"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Badge
}
from
"@/components/ui/badge"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Card
,
CardContent
,
CardDescription
,
CardHeader
,
CardTitle
}
from
"@/components/ui/card"
;
...
...
@@ -439,7 +439,7 @@ export default function AdminDashboardPage() {
>
<
div
className=
"relative h-20 w-28 shrink-0 overflow-hidden rounded-2xl bg-[#eef4ff]"
>
{
item
.
thumbnail
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
thumbnail
.
url
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
fill
...
...
@@ -493,7 +493,7 @@ export default function AdminDashboardPage() {
<
div
className=
"mt-4 grid grid-cols-3 gap-3"
>
{
mediaItems
.
slice
(
0
,
3
).
map
((
item
)
=>
(
<
div
key=
{
item
.
id
}
className=
"relative aspect-square overflow-hidden rounded-2xl bg-[#eef4ff]"
>
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
url
}
alt=
{
item
.
alt
||
item
.
name
}
fill
...
...
src/app/admin/header-config/[categoryId]/page.tsx
View file @
d5228e26
...
...
@@ -15,7 +15,7 @@ import { AdminRowActions } from "@/components/admin/admin-row-actions";
import
{
AdminStatsGrid
}
from
"@/components/admin/admin-stats-grid"
;
import
{
AdminTableLayout
}
from
"@/components/admin/admin-table-layout"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Badge
}
from
"@/components/ui/badge"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
...
...
@@ -315,7 +315,7 @@ export default function HeaderCategoryPostsPage() {
<
TableCell
className=
"text-center"
>
<
div
className=
"relative mx-auto h-16 w-24 overflow-hidden rounded-xl border border-[#063e8e]/15 bg-[#063e8e]/[0.03]"
>
{
item
.
thumbnail
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
thumbnail
.
url
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
fill
...
...
src/app/admin/media/_components/media-form-dialog.tsx
View file @
d5228e26
...
...
@@ -8,7 +8,7 @@ import {
X
,
}
from
"lucide-react"
;
import
{
toast
}
from
"sonner"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Dialog
,
...
...
@@ -103,7 +103,7 @@ export function MediaFormDialog({
<
div
className=
"overflow-hidden rounded-[28px] border border-[#063e8e]/10 bg-white shadow-[inset_0_1px_0_rgba(255,255,255,0.8)]"
>
<
div
className=
"relative aspect-[16/10] bg-[radial-gradient(circle_at_top,#d9e8ff_0%,#f7faff_58%,#ffffff_100%)]"
>
{
form
.
previewUrl
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
form
.
previewUrl
}
alt=
{
form
.
name
}
fill
...
...
src/app/admin/media/page.tsx
View file @
d5228e26
...
...
@@ -9,7 +9,7 @@ import {
import
{
toast
}
from
"sonner"
;
import
{
AdminDeleteDialog
}
from
"@/components/admin/admin-delete-dialog"
;
import
{
AdminTableLayout
}
from
"@/components/admin/admin-table-layout"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
...
...
@@ -161,7 +161,7 @@ export default function AdminMediaPage() {
className=
"group overflow-hidden rounded-[28px] border border-[#063e8e]/10 bg-white shadow-[0_18px_45px_rgba(6,62,142,0.08)] transition duration-300 hover:-translate-y-1 hover:shadow-[0_28px_60px_rgba(6,62,142,0.14)]"
>
<
div
className=
"relative aspect-square overflow-hidden bg-[radial-gradient(circle_at_top,#dce9ff_0%,#f8fbff_55%,#ffffff_100%)]"
>
<
Safe
Next
Image
<
SafeImage
src=
{
resolveCmsFileUrl
(
item
.
path
)
}
alt=
{
item
.
original
??
""
}
fill
...
...
src/app/admin/members/[id]/_components/member-form.tsx
View file @
d5228e26
...
...
@@ -7,7 +7,7 @@ import { useRouter } from "next/navigation";
import
{
toast
}
from
"sonner"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
import
{
AdminPostContentEditor
}
from
"@/components/admin/post-content-editor"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Label
}
from
"@/components/ui/label"
;
...
...
@@ -351,7 +351,7 @@ export function AdminMemberForm({ memberId }: AdminMemberFormProps) {
{
form
.
image
?
(
<
div
className=
"space-y-3"
>
<
div
className=
"relative aspect-video overflow-hidden rounded-xl border border-[#063e8e]/15"
>
<
Safe
Next
Image
<
SafeImage
src=
{
form
.
image
.
url
}
alt=
{
form
.
image
.
alt
||
form
.
image
.
name
}
fill
...
...
src/app/admin/members/_components/member-row.tsx
View file @
d5228e26
"use client"
;
import
{
AdminRowActions
}
from
"@/components/admin/admin-row-actions"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Badge
}
from
"@/components/ui/badge"
;
import
{
TableCell
,
...
...
@@ -47,7 +47,7 @@ export function MemberRow({
<
TableCell
className=
"px-4 py-3 text-center"
>
{
item
.
image
?
(
<
div
className=
"mx-auto h-12 w-16 overflow-hidden rounded-lg border border-[#063e8e]/15"
>
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
image
.
url
}
alt=
{
item
.
image
.
alt
||
item
.
name
}
width=
{
64
}
...
...
src/app/admin/news/[id]/_components/admin-news-form-content.tsx
View file @
d5228e26
...
...
@@ -15,7 +15,7 @@ import { AdminImagePicker } from "@/components/admin/image-picker";
import
{
AdminPostContentEditor
}
from
"@/components/admin/post-content-editor"
;
import
{
PostHistoryViewer
}
from
"./post-history-viewer"
;
import
{
AdminRichTextEditor
}
from
"@/components/shared/rich-text-editor"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
PermissionGate
}
from
"@/components/shared/permission-gate"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Checkbox
}
from
"@/components/ui/checkbox"
;
...
...
@@ -530,7 +530,7 @@ export function AdminNewsFormContent() {
<
div
className=
"relative overflow-hidden rounded-2xl border border-[#063e8e]/15 bg-white"
>
<
div
className=
"relative aspect-[16/11]"
>
{
form
.
thumbnail
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
form
.
thumbnail
.
url
}
alt=
{
form
.
thumbnail
.
alt
||
form
.
thumbnail
.
name
}
fill
...
...
src/app/admin/news/page.tsx
View file @
d5228e26
...
...
@@ -15,7 +15,7 @@ import { AdminDeleteDialog } from "@/components/admin/admin-delete-dialog";
import
{
AdminRowActions
}
from
"@/components/admin/admin-row-actions"
;
import
{
AdminStatsGrid
}
from
"@/components/admin/admin-stats-grid"
;
import
{
AdminTableLayout
}
from
"@/components/admin/admin-table-layout"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Badge
}
from
"@/components/ui/badge"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
...
...
@@ -449,7 +449,7 @@ export default function AdminNewsPage() {
<
TableCell
className=
"text-center"
>
<
div
className=
"relative mx-auto h-16 w-24 overflow-hidden rounded-xl border border-[#063e8e]/15 bg-[#063e8e]/[0.03]"
>
{
item
.
thumbnail
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
thumbnail
.
url
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
fill
...
...
src/components/admin/image-picker.tsx
View file @
d5228e26
...
...
@@ -12,7 +12,7 @@ import {
DialogHeader
,
DialogTitle
,
}
from
"@/components/ui/dialog"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
type
{
AdminMediaItem
}
from
"@/mockdata/admin-news"
;
import
{
toAdminMediaItem
}
from
"@/lib/utils/file"
;
import
{
getApiV10File
,
postApiV10FileUpload
}
from
"@/api/vcci-news/endpoints/file"
;
...
...
@@ -210,7 +210,7 @@ export function AdminImagePicker({
)
}
>
<
div
className=
"relative aspect-[4/3] overflow-hidden bg-[#063e8e]/[0.04]"
>
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
url
}
alt=
{
item
.
alt
||
item
.
name
}
fill
...
...
src/components/admin/post-content-editor.tsx
View file @
d5228e26
...
...
@@ -4,7 +4,7 @@ import * as React from "react";
import
{
Image
as
ImageIcon
,
Pencil
,
Plus
,
Type
,
Upload
,
X
}
from
"lucide-react"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
import
{
AdminRichTextEditor
}
from
"@/components/shared/rich-text-editor"
;
import
{
Safe
NextImage
}
from
"@/components/admin/safe-next
-image"
;
import
{
Safe
Image
}
from
"@/components/shared/safe
-image"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Label
}
from
"@/components/ui/label"
;
import
{
...
...
@@ -409,7 +409,7 @@ export function AdminPostContentEditor({
}
}
className=
"relative h-64 w-full cursor-pointer"
>
<
Safe
Next
Image
<
SafeImage
src=
{
currentImage
.
image
.
url
}
alt=
{
currentImage
.
image
.
alt
||
currentImage
.
image
.
name
}
fill
...
...
src/components/layout/main/header.tsx
View file @
d5228e26
...
...
@@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
import
{
useRouter
}
from
"next/navigation"
;
import
{
Facebook
,
Linkedin
,
Menu
,
Twitter
,
X
,
Youtube
}
from
"lucide-react"
;
import
{
useQuery
}
from
"@tanstack/react-query"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
Link
from
"next/link"
;
const
fallbackLogo
=
"/logo.png"
;
import
{
useGetApiV10Logo
}
from
"@/api/vcci-news/endpoints/logo"
;
...
...
@@ -335,18 +335,14 @@ function Header() {
href=
"/"
className=
"flex w-[136px] shrink-0 items-center xl:w-[152px]"
>
<
Image
<
Safe
Image
width=
{
108
}
height=
{
40
}
className=
"h-auto max-h-10 w-[108px] object-contain"
src=
{
currentLogo
?.
logo_url
?
links
.
resolveImageUrl
(
currentLogo
.
logo_url
)
:
fallbackLogo
}
fallbackSrc=
{
fallbackLogo
}
alt=
{
currentLogo
?.
logo_name
||
"VCCI-HCM"
}
priority
unoptimized=
{
Boolean
(
currentLogo
?.
logo_url
)
}
onError=
{
(
e
)
=>
{
const
img
=
e
.
currentTarget
;
if
(
img
.
src
!==
fallbackLogo
)
img
.
src
=
fallbackLogo
;
}
}
/>
</
Link
>
...
...
@@ -394,18 +390,14 @@ function Header() {
className=
"flex w-[136px] shrink-0 items-center"
onClick=
{
()
=>
setToggleMenu
(
false
)
}
>
<
Image
<
Safe
Image
width=
{
108
}
height=
{
40
}
className=
"h-auto max-h-10 w-[108px] object-contain"
src=
{
currentLogo
?.
logo_url
?
links
.
resolveImageUrl
(
currentLogo
.
logo_url
)
:
fallbackLogo
}
fallbackSrc=
{
fallbackLogo
}
alt=
{
currentLogo
?.
logo_name
||
"VCCI-HCM"
}
priority
unoptimized=
{
Boolean
(
currentLogo
?.
logo_url
)
}
onError=
{
(
e
)
=>
{
const
img
=
e
.
currentTarget
;
if
(
img
.
src
!==
fallbackLogo
)
img
.
src
=
fallbackLogo
;
}
}
/>
</
Link
>
<
button
...
...
src/components/
admin/safe-next
-image.tsx
→
src/components/
shared/safe
-image.tsx
View file @
d5228e26
"use client"
;
import
Image
,
{
type
ImageProps
}
from
"next/image"
;
import
*
as
React
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
interface
Safe
Next
ImageProps
extends
Omit
<
ImageProps
,
"src"
>
{
interface
SafeImageProps
extends
Omit
<
ImageProps
,
"src"
>
{
src
?:
string
|
null
;
fallbackSrc
?:
string
;
}
export
function
Safe
Next
Image
({
export
function
SafeImage
({
src
,
alt
,
fallbackSrc
=
"/img-error.png"
,
...
props
}:
Safe
Next
ImageProps
)
{
const
[
currentSrc
,
setCurrentSrc
]
=
React
.
useState
(
src
||
fallbackSrc
);
const
[
hasFailed
,
setHasFailed
]
=
React
.
useState
(
false
);
}:
SafeImageProps
)
{
const
[
currentSrc
,
setCurrentSrc
]
=
useState
(
src
||
fallbackSrc
);
const
[
hasFailed
,
setHasFailed
]
=
useState
(
false
);
React
.
useEffect
(()
=>
{
useEffect
(()
=>
{
setCurrentSrc
(
src
||
fallbackSrc
);
setHasFailed
(
false
);
},
[
fallbackSrc
,
src
]);
...
...
src/components/shared/sidebar-advertisements/index.tsx
View file @
d5228e26
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
Link
from
"next/link"
;
import
{
useMemo
,
useState
}
from
"react"
;
import
{
useMemo
}
from
"react"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
links
from
"@/links"
;
import
type
{
Advertisement
}
from
"@/api/vcci-news/models/advertisement"
;
...
...
@@ -11,8 +11,7 @@ import { getFallbackImage } from "@/lib/utils/fallback-image";
const
FALLBACK_HREF
=
"https://vcci-hcm.org.vn"
;
function
SidebarAdItem
({
item
,
fallbackSrc
}:
{
item
:
Advertisement
;
fallbackSrc
:
string
})
{
const
initialSrc
=
item
.
file
?.
path
?
links
.
resolveImageUrl
(
item
.
file
.
path
)
:
fallbackSrc
;
const
[
src
,
setSrc
]
=
useState
(
initialSrc
);
const
src
=
item
.
file
?.
path
?
links
.
resolveImageUrl
(
item
.
file
.
path
)
:
fallbackSrc
;
return
(
<
Link
...
...
@@ -23,15 +22,13 @@ function SidebarAdItem({ item, fallbackSrc }: { item: Advertisement; fallbackSrc
title=
{
item
.
name
}
>
<
div
className=
"relative aspect-[16/10]"
>
<
Image
<
Safe
Image
src=
{
src
}
fallbackSrc=
{
fallbackSrc
}
alt=
{
item
.
alt
||
item
.
name
}
fill
sizes=
"(max-width: 768px) 100vw, 300px"
className=
"h-full w-full object-cover"
onError=
{
()
=>
{
if
(
src
!==
fallbackSrc
)
setSrc
(
fallbackSrc
);
}
}
/>
</
div
>
</
Link
>
...
...
@@ -48,7 +45,7 @@ function FallbackSidebarAdItem({ src }: { src: string }) {
title=
"Quảng cáo VCCI HCM"
>
<
div
className=
"relative aspect-[16/10]"
>
<
Image
<
Safe
Image
src=
{
src
}
alt=
"Quảng cáo VCCI HCM"
fill
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment