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"
;
"use client"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
Swiper
,
SwiperSlide
}
from
"swiper/react"
;
import
{
Swiper
,
SwiperSlide
}
from
"swiper/react"
;
import
{
Autoplay
}
from
"swiper/modules"
;
import
{
Autoplay
}
from
"swiper/modules"
;
import
{
Swiper
as
SwiperType
}
from
"swiper/types"
;
import
{
Swiper
as
SwiperType
}
from
"swiper/types"
;
...
@@ -84,18 +84,13 @@ function BannerSlideItem({
...
@@ -84,18 +84,13 @@ function BannerSlideItem({
:
"/thumbnail.png"
;
:
"/thumbnail.png"
;
return
(
return
(
<
Image
<
Safe
Image
src=
{
url
}
src=
{
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
alt
}
alt=
{
alt
}
width=
{
2560
}
width=
{
2560
}
height=
{
720
}
height=
{
720
}
sizes=
"100vw"
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"
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'
;
'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
{
useHomePosts
}
from
"@/app/(main)/(home)/lib/use-home-posts"
;
import
dayjs
from
"dayjs"
;
import
dayjs
from
"dayjs"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
...
@@ -48,8 +48,9 @@ function Events() {
...
@@ -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)]"
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]"
>
<
div
className=
"relative h-[180px] overflow-hidden md:h-[220px] xl:h-[248px]"
>
<
Image
<
SafeImage
src=
{
featuredEvent
.
thumbnail
?.
url
??
featuredFallback
}
src=
{
featuredEvent
.
thumbnail
?.
url
}
fallbackSrc=
{
featuredFallback
}
alt=
{
featuredEvent
.
thumbnail
?.
alt
||
featuredEvent
.
title
}
alt=
{
featuredEvent
.
thumbnail
?.
alt
||
featuredEvent
.
title
}
width=
{
720
}
width=
{
720
}
height=
{
520
}
height=
{
520
}
...
@@ -114,8 +115,9 @@ function Events() {
...
@@ -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"
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]"
>
<
div
className=
"h-[64px] w-[64px] shrink-0 overflow-hidden rounded-[14px]"
>
<
Image
<
SafeImage
src=
{
item
.
thumbnail
?.
url
??
sideFallbacks
[
index
]
}
src=
{
item
.
thumbnail
?.
url
}
fallbackSrc=
{
sideFallbacks
[
index
]
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
width=
{
160
}
width=
{
160
}
height=
{
160
}
height=
{
160
}
...
...
src/app/(main)/(home)/components/featured-news/index.tsx
View file @
d5228e26
'use client'
;
'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
{
useHomePosts
}
from
"@/app/(main)/(home)/lib/use-home-posts"
;
import
dayjs
from
"dayjs"
;
import
dayjs
from
"dayjs"
;
import
{
ChevronRight
}
from
"lucide-react"
;
import
{
ChevronRight
}
from
"lucide-react"
;
...
@@ -51,8 +51,9 @@ function FeaturedNews() {
...
@@ -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]"
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]"
>
<
div
className=
"relative h-full min-h-[195px] md:min-h-[320px] lg:min-h-[380px]"
>
<
Image
<
SafeImage
src=
{
primaryItem
.
thumbnail
?.
url
??
primaryFallback
}
src=
{
primaryItem
.
thumbnail
?.
url
}
fallbackSrc=
{
primaryFallback
}
alt=
{
primaryItem
.
thumbnail
?.
alt
||
primaryItem
.
title
}
alt=
{
primaryItem
.
thumbnail
?.
alt
||
primaryItem
.
title
}
width=
{
1200
}
width=
{
1200
}
height=
{
800
}
height=
{
800
}
...
@@ -97,8 +98,9 @@ function FeaturedNews() {
...
@@ -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]"
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]"
>
<
div
className=
"relative flex h-full min-h-[165px]"
>
<
Image
<
SafeImage
src=
{
item
.
thumbnail
?.
url
??
secondaryFallbacks
[
index
]
}
src=
{
item
.
thumbnail
?.
url
}
fallbackSrc=
{
secondaryFallbacks
[
index
]
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
width=
{
600
}
width=
{
600
}
height=
{
420
}
height=
{
420
}
...
...
src/app/(main)/(home)/components/horizontal-ad-banner/index.tsx
View file @
d5228e26
'use client'
;
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
import
{
useState
}
from
"react"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
links
from
"@/links"
;
import
links
from
"@/links"
;
...
@@ -14,16 +13,8 @@ function HorizontalAdBanner() {
...
@@ -14,16 +13,8 @@ function HorizontalAdBanner() {
const
ad
=
ads
[
0
];
const
ad
=
ads
[
0
];
const
href
=
ad
?.
link
||
FALLBACK_HREF
;
const
href
=
ad
?.
link
||
FALLBACK_HREF
;
const
initialSrc
=
ad
?.
file
?.
path
?
links
.
resolveImageUrl
(
ad
.
file
.
path
)
:
FALLBACK_SRC
;
const
src
=
ad
?.
file
?.
path
?
links
.
resolveImageUrl
(
ad
.
file
.
path
)
:
FALLBACK_SRC
;
const
[
src
,
setSrc
]
=
useState
(
initialSrc
);
const
[
errored
,
setErrored
]
=
useState
(
false
);
// 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
title
=
ad
?.
name
||
"Quảng cáo VCCI HCM"
;
const
alt
=
ad
?.
alt
||
title
;
const
alt
=
ad
?.
alt
||
title
;
...
@@ -36,19 +27,13 @@ function HorizontalAdBanner() {
...
@@ -36,19 +27,13 @@ function HorizontalAdBanner() {
style=
{
{
aspectRatio
:
"1600 / 200"
}
}
style=
{
{
aspectRatio
:
"1600 / 200"
}
}
title=
{
title
}
title=
{
title
}
>
>
<
Image
<
Safe
Image
src=
{
src
}
src=
{
src
}
fallbackSrc=
{
FALLBACK_SRC
}
alt=
{
alt
}
alt=
{
alt
}
fill
fill
sizes=
"100vw"
sizes=
"100vw"
className=
"object-cover"
className=
"object-cover"
unoptimized=
{
isGif
}
onError=
{
()
=>
{
if
(
src
!==
FALLBACK_SRC
)
{
setSrc
(
FALLBACK_SRC
);
setErrored
(
true
);
}
}
}
/>
/>
</
Link
>
</
Link
>
);
);
...
...
src/app/(main)/(home)/components/members/index.tsx
View file @
d5228e26
'use client'
;
'use client'
;
import
{
useHomePosts
}
from
"@/app/(main)/(home)/lib/use-home-posts"
;
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
{
ChevronRight
}
from
"lucide-react"
;
import
memberImages
from
"@/constants/memberImages"
;
import
memberImages
from
"@/constants/memberImages"
;
import
{
MOCK_FEATURED_MEMBERS_RESPONSE
}
from
"@/mockdata/bff-fallback"
;
import
{
MOCK_FEATURED_MEMBERS_RESPONSE
}
from
"@/mockdata/bff-fallback"
;
...
@@ -113,8 +113,9 @@ function Members() {
...
@@ -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-[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"
>
<
div
className=
"flex h-full w-full max-w-[260px] items-center justify-center"
>
<
Image
<
SafeImage
src=
{
resolveMemberImage
(
member
.
avatar
,
index
)
??
"/img-error.png"
}
src=
{
resolveMemberImage
(
member
.
avatar
,
index
)
}
fallbackSrc=
"/img-error.png"
alt=
{
member
.
name
??
""
}
alt=
{
member
.
name
??
""
}
width=
{
260
}
width=
{
260
}
height=
{
180
}
height=
{
180
}
...
@@ -130,8 +131,9 @@ function Members() {
...
@@ -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-[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"
>
<
div
className=
"flex h-full w-full max-w-[260px] items-center justify-center"
>
<
Image
<
SafeImage
src=
{
resolveMemberImage
(
member
.
avatar
,
index
)
??
"/img-error.png"
}
src=
{
resolveMemberImage
(
member
.
avatar
,
index
)
}
fallbackSrc=
"/img-error.png"
alt=
{
member
.
name
??
""
}
alt=
{
member
.
name
??
""
}
width=
{
260
}
width=
{
260
}
height=
{
180
}
height=
{
180
}
...
@@ -212,8 +214,9 @@ function Members() {
...
@@ -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)]"
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]"
>
<
div
className=
"aspect-[16/10] overflow-hidden xl:aspect-[1.25/1]"
>
<
Image
<
SafeImage
src=
{
item
.
thumbnail
?.
url
??
MEMBER_CONNECTION_FALLBACK_IMAGE
}
src=
{
item
.
thumbnail
?.
url
}
fallbackSrc=
{
MEMBER_CONNECTION_FALLBACK_IMAGE
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
width=
{
520
}
width=
{
520
}
height=
{
420
}
height=
{
420
}
...
@@ -254,8 +257,9 @@ function Members() {
...
@@ -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)]"
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"
>
<
div
className=
"aspect-[16/10] overflow-hidden"
>
<
Image
<
SafeImage
src=
{
item
.
thumbnail
?.
url
??
MEMBER_CONNECTION_FALLBACK_IMAGE
}
src=
{
item
.
thumbnail
?.
url
}
fallbackSrc=
{
MEMBER_CONNECTION_FALLBACK_IMAGE
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
alt=
{
item
.
thumbnail
?.
alt
||
item
.
title
}
width=
{
520
}
width=
{
520
}
height=
{
420
}
height=
{
420
}
...
...
src/app/(main)/(home)/components/news/index.tsx
View file @
d5228e26
'use client'
;
'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
{
useHomePosts
}
from
"@/app/(main)/(home)/lib/use-home-posts"
;
import
dayjs
from
"dayjs"
;
import
dayjs
from
"dayjs"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
...
@@ -83,8 +83,9 @@ function News() {
...
@@ -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)]"
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"
>
<
div
className=
"relative aspect-[1.4/1] overflow-hidden"
>
<
Image
<
SafeImage
src=
{
featuredArticle
.
thumbnail
?.
url
??
"/thumbnail.png"
}
src=
{
featuredArticle
.
thumbnail
?.
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
featuredArticle
.
thumbnail
?.
alt
||
featuredArticle
.
title
}
alt=
{
featuredArticle
.
thumbnail
?.
alt
||
featuredArticle
.
title
}
width=
{
720
}
width=
{
720
}
height=
{
580
}
height=
{
580
}
...
@@ -151,8 +152,9 @@ function News() {
...
@@ -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)]"
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]"
>
<
div
className=
"h-20 w-20 shrink-0 overflow-hidden rounded-[14px]"
>
<
Image
<
SafeImage
src=
{
news
.
thumbnail
?.
url
??
"/thumbnail.png"
}
src=
{
news
.
thumbnail
?.
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
news
.
thumbnail
?.
alt
||
news
.
title
}
alt=
{
news
.
thumbnail
?.
alt
||
news
.
title
}
width=
{
160
}
width=
{
160
}
height=
{
160
}
height=
{
160
}
...
@@ -227,8 +229,9 @@ function News() {
...
@@ -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)]"
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"
>
<
div
className=
"relative aspect-[16/9] overflow-hidden"
>
<
Image
<
SafeImage
src=
{
featuredArticle
.
thumbnail
?.
url
??
"/thumbnail.png"
}
src=
{
featuredArticle
.
thumbnail
?.
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
featuredArticle
.
thumbnail
?.
alt
||
featuredArticle
.
title
}
alt=
{
featuredArticle
.
thumbnail
?.
alt
||
featuredArticle
.
title
}
width=
{
720
}
width=
{
720
}
height=
{
580
}
height=
{
580
}
...
@@ -292,8 +295,9 @@ function News() {
...
@@ -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)]"
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]"
>
<
div
className=
"h-16 w-16 shrink-0 overflow-hidden rounded-[14px]"
>
<
Image
<
SafeImage
src=
{
news
.
thumbnail
?.
url
??
"/thumbnail.png"
}
src=
{
news
.
thumbnail
?.
url
}
fallbackSrc=
"/thumbnail.png"
alt=
{
news
.
thumbnail
?.
alt
||
news
.
title
}
alt=
{
news
.
thumbnail
?.
alt
||
news
.
title
}
width=
{
160
}
width=
{
160
}
height=
{
160
}
height=
{
160
}
...
...
src/app/(main)/(home)/components/quick-links/index.tsx
View file @
d5228e26
'use client'
;
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
import
{
useMemo
,
useState
}
from
"react"
;
import
{
useMemo
}
from
"react"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
links
from
"@/links"
;
import
links
from
"@/links"
;
import
type
{
Advertisement
}
from
"@/api/vcci-news/models/advertisement"
;
import
type
{
Advertisement
}
from
"@/api/vcci-news/models/advertisement"
;
...
@@ -11,8 +11,7 @@ import { getFallbackImage } from "@/lib/utils/fallback-image";
...
@@ -11,8 +11,7 @@ import { getFallbackImage } from "@/lib/utils/fallback-image";
const
FALLBACK_HREF
=
links
.
externalApiOrigin
;
const
FALLBACK_HREF
=
links
.
externalApiOrigin
;
function
AdItem
({
item
,
fallbackSrc
}:
{
item
:
Advertisement
;
fallbackSrc
:
string
})
{
function
AdItem
({
item
,
fallbackSrc
}:
{
item
:
Advertisement
;
fallbackSrc
:
string
})
{
const
initialSrc
=
item
.
file
?.
path
?
links
.
resolveImageUrl
(
item
.
file
.
path
)
:
fallbackSrc
;
const
src
=
item
.
file
?.
path
?
links
.
resolveImageUrl
(
item
.
file
.
path
)
:
fallbackSrc
;
const
[
src
,
setSrc
]
=
useState
(
initialSrc
);
return
(
return
(
<
Link
<
Link
...
@@ -23,16 +22,13 @@ function AdItem({ item, fallbackSrc }: { item: Advertisement; fallbackSrc: strin
...
@@ -23,16 +22,13 @@ function AdItem({ item, fallbackSrc }: { item: Advertisement; fallbackSrc: strin
title=
{
item
.
name
}
title=
{
item
.
name
}
>
>
<
div
className=
"aspect-[16/10] overflow-hidden sm:aspect-[16/10] lg:aspect-[7/4] xl:aspect-[3/2]"
>
<
div
className=
"aspect-[16/10] overflow-hidden sm:aspect-[16/10] lg:aspect-[7/4] xl:aspect-[3/2]"
>
<
Image
<
Safe
Image
src=
{
src
}
src=
{
src
}
fallbackSrc=
{
fallbackSrc
}
alt=
{
item
.
alt
||
item
.
name
}
alt=
{
item
.
alt
||
item
.
name
}
width=
{
2048
}
width=
{
2048
}
height=
{
1365
}
height=
{
1365
}
className=
"h-full w-full object-cover object-[center_80%]"
className=
"h-full w-full object-cover object-[center_80%]"
unoptimized
onError=
{
()
=>
{
if
(
src
!==
fallbackSrc
)
setSrc
(
fallbackSrc
);
}
}
/>
/>
</
div
>
</
div
>
</
Link
>
</
Link
>
...
@@ -49,7 +45,7 @@ function FallbackAdItem({ src }: { src: string }) {
...
@@ -49,7 +45,7 @@ function FallbackAdItem({ src }: { src: string }) {
title=
"Quảng cáo VCCI HCM"
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]"
>
<
div
className=
"aspect-[16/10] overflow-hidden sm:aspect-[16/10] lg:aspect-[7/4] xl:aspect-[3/2]"
>
<
Image
<
Safe
Image
src=
{
src
}
src=
{
src
}
alt=
"Quảng cáo VCCI HCM"
alt=
"Quảng cáo VCCI HCM"
width=
{
2048
}
width=
{
2048
}
...
...
src/app/(main)/(home)/components/video-and-patners/index.tsx
View file @
d5228e26
"use client"
;
"use client"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
partnerImages
from
"@/constants/partnerImages"
;
import
partnerImages
from
"@/constants/partnerImages"
;
import
{
ChevronRight
,
Play
}
from
"lucide-react"
;
import
{
ChevronRight
,
Play
}
from
"lucide-react"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
...
@@ -68,8 +68,9 @@ const renderPartnerContent = (partners: Organization[]) => {
...
@@ -68,8 +68,9 @@ const renderPartnerContent = (partners: Organization[]) => {
className=
"block"
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]"
>
<
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
<
SafeImage
src=
{
resolvePartnerImage
(
partner
.
avatar
,
index
)
??
"/img-error.png"
}
src=
{
resolvePartnerImage
(
partner
.
avatar
,
index
)
}
fallbackSrc=
"/img-error.png"
alt=
{
partner
.
name
??
""
}
alt=
{
partner
.
name
??
""
}
width=
{
140
}
width=
{
140
}
height=
{
72
}
height=
{
72
}
...
@@ -79,8 +80,9 @@ const renderPartnerContent = (partners: Organization[]) => {
...
@@ -79,8 +80,9 @@ const renderPartnerContent = (partners: Organization[]) => {
</
a
>
</
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]"
>
<
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
<
SafeImage
src=
{
resolvePartnerImage
(
partner
.
avatar
,
index
)
??
"/img-error.png"
}
src=
{
resolvePartnerImage
(
partner
.
avatar
,
index
)
}
fallbackSrc=
"/img-error.png"
alt=
{
partner
.
name
??
""
}
alt=
{
partner
.
name
??
""
}
width=
{
140
}
width=
{
140
}
height=
{
72
}
height=
{
72
}
...
@@ -179,7 +181,7 @@ function VideoAndPartners() {
...
@@ -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)]"
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"
>
<
div
className=
"group relative aspect-[1.95/1] overflow-hidden"
>
<
Image
<
Safe
Image
src=
{
video
.
thumbnail
}
src=
{
video
.
thumbnail
}
alt=
{
video
.
name
}
alt=
{
video
.
name
}
width=
{
640
}
width=
{
640
}
...
...
src/app/(main)/[...slug]/templates/ArticlePage.tsx
View file @
d5228e26
...
@@ -5,7 +5,7 @@ import Link from "next/link";
...
@@ -5,7 +5,7 @@ import Link from "next/link";
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
Spinner
}
from
"@/components/ui"
;
import
{
Spinner
}
from
"@/components/ui"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
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
{
Button
}
from
"@/components/ui/button"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
ListCategory
from
"@/components/base/list-category"
;
import
ListCategory
from
"@/components/base/list-category"
;
...
@@ -155,7 +155,7 @@ export default function ArticlePage({ category, allCategories }: ArticlePageProp
...
@@ -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)]"
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]"
>
<
div
className=
"relative overflow-hidden rounded-md bg-[#edf1f5] aspect-[25/15] sm:aspect-[5/3]"
>
<
Image
<
Safe
Image
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
alt=
{
item
.
title
}
alt=
{
item
.
title
}
width=
{
520
}
width=
{
520
}
...
...
src/app/(main)/[...slug]/templates/CatalogPage.tsx
View file @
d5228e26
...
@@ -5,7 +5,7 @@ import Link from "next/link";
...
@@ -5,7 +5,7 @@ import Link from "next/link";
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
Spinner
}
from
"@/components/ui"
;
import
{
Spinner
}
from
"@/components/ui"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
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
{
Button
}
from
"@/components/ui/button"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
ListCategory
from
"@/components/base/list-category"
;
import
ListCategory
from
"@/components/base/list-category"
;
...
@@ -108,7 +108,7 @@ export default function CatalogPage({ category, allCategories }: CatalogPageProp
...
@@ -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=
"overflow-hidden bg-white shadow-[0_10px_24px_rgba(17,24,39,0.08)]"
>
<
div
className=
"relative aspect-3/4 overflow-hidden bg-white"
>
<
div
className=
"relative aspect-3/4 overflow-hidden bg-white"
>
<
Image
<
Safe
Image
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
alt=
{
item
.
title
}
alt=
{
item
.
title
}
width=
{
520
}
width=
{
520
}
...
...
src/app/(main)/[...slug]/templates/StructuredPostContent.tsx
View file @
d5228e26
"use client"
;
"use client"
;
import
parse
from
"html-react-parser"
;
import
parse
from
"html-react-parser"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
getDynamicPostBodyHtml
}
from
"./data"
;
import
{
getDynamicPostBodyHtml
}
from
"./data"
;
import
type
{
DynamicPostContentSection
,
DynamicPostItem
}
from
"./types"
;
import
type
{
DynamicPostContentSection
,
DynamicPostItem
}
from
"./types"
;
...
@@ -32,7 +32,7 @@ function StructuredImageSection({ section }: { section: DynamicPostContentSectio
...
@@ -32,7 +32,7 @@ function StructuredImageSection({ section }: { section: DynamicPostContentSectio
key=
{
`${section.id}-${image.id || image.url}-${item.position}`
}
key=
{
`${section.id}-${image.id || image.url}-${item.position}`
}
className=
"overflow-hidden rounded-[18px] bg-white"
className=
"overflow-hidden rounded-[18px] bg-white"
>
>
<
Image
<
Safe
Image
src=
{
image
.
url
}
src=
{
image
.
url
}
alt=
{
image
.
alt
||
image
.
name
||
"Hình ảnh bài viết"
}
alt=
{
image
.
alt
||
image
.
name
||
"Hình ảnh bài viết"
}
width=
{
1200
}
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";
...
@@ -6,7 +6,7 @@ import { ShieldCheck, Target, Zap } from "lucide-react";
import
parse
from
"html-react-parser"
;
import
parse
from
"html-react-parser"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
import
{
useGetApiV10Post
}
from
"@/api/vcci-news/endpoints/post"
;
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
{
buildDynamicPostHref
,
buildVisibleNewsFilters
,
stripHtml
}
from
"../data"
;
import
links
from
"@/links"
;
import
links
from
"@/links"
;
import
StructuredPostContent
from
"../StructuredPostContent"
;
import
StructuredPostContent
from
"../StructuredPostContent"
;
...
@@ -325,7 +325,7 @@ export default function AboutVcciHcmPage({
...
@@ -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"
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"
>
<
div
className=
"relative aspect-[1.28] overflow-hidden"
>
<
Image
<
Safe
Image
src=
{
item
.
thumbnailUrl
}
src=
{
item
.
thumbnailUrl
}
alt=
{
item
.
thumbnailAlt
}
alt=
{
item
.
thumbnailAlt
}
width=
{
720
}
width=
{
720
}
...
...
src/app/(main)/[...slug]/templates/information-pages/MarketProfilePage.tsx
View file @
d5228e26
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
import
{
useMemo
,
useState
}
from
"react"
;
import
{
useMemo
,
useState
}
from
"react"
;
import
{
FileText
,
Globe2
,
Newspaper
,
TrendingUp
}
from
"lucide-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"
;
import
type
{
DynamicPostItem
}
from
"../types"
;
type
MarketProfilePageProps
=
{
type
MarketProfilePageProps
=
{
...
@@ -256,7 +256,7 @@ export default function MarketProfilePage({ post }: MarketProfilePageProps) {
...
@@ -256,7 +256,7 @@ export default function MarketProfilePage({ post }: MarketProfilePageProps) {
})
}
})
}
</
div
>
</
div
>
<
Image
<
Safe
Image
src=
{
activeRegion
.
image
}
src=
{
activeRegion
.
image
}
alt=
{
activeRegion
.
imageAlt
}
alt=
{
activeRegion
.
imageAlt
}
width=
{
1200
}
width=
{
1200
}
...
...
src/app/(main)/search/page.tsx
View file @
d5228e26
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
import
{
Suspense
,
useEffect
,
useState
}
from
"react"
;
import
{
Suspense
,
useEffect
,
useState
}
from
"react"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
import
{
useRouter
,
useSearchParams
}
from
"next/navigation"
;
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
SidebarAdvertisements
from
"@/components/shared/sidebar-advertisements"
;
import
{
Pagination
}
from
"@components/base/pagination"
;
import
{
Pagination
}
from
"@components/base/pagination"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
...
@@ -54,7 +54,7 @@ function SearchResultItem({ item, index }: { item: DynamicPostItem; index: numbe
...
@@ -54,7 +54,7 @@ function SearchResultItem({ item, index }: { item: DynamicPostItem; index: numbe
className=
"group grid gap-5 sm:grid-cols-[250px_minmax(0,1fr)]"
className=
"group grid gap-5 sm:grid-cols-[250px_minmax(0,1fr)]"
>
>
<
div
className=
"overflow-hidden rounded-md bg-[#edf1f5]"
>
<
div
className=
"overflow-hidden rounded-md bg-[#edf1f5]"
>
<
Image
<
Safe
Image
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
src=
{
resolveDynamicPostImage
(
item
.
thumbnail
)
}
alt=
{
item
.
title
}
alt=
{
item
.
title
}
width=
{
520
}
width=
{
520
}
...
...
src/app/(main)/video/page.tsx
View file @
d5228e26
...
@@ -3,7 +3,7 @@
...
@@ -3,7 +3,7 @@
import
{
Suspense
}
from
"react"
;
import
{
Suspense
}
from
"react"
;
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
Play
}
from
"lucide-react"
;
import
{
Play
}
from
"lucide-react"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
import
{
Spinner
}
from
"@/components/ui/spinner"
;
import
{
Spinner
}
from
"@/components/ui/spinner"
;
import
{
useGetApiV10Video
}
from
"@/api/vcci-news/endpoints/video"
;
import
{
useGetApiV10Video
}
from
"@/api/vcci-news/endpoints/video"
;
...
@@ -106,7 +106,7 @@ function VideoPageContent() {
...
@@ -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)]"
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]"
>
<
div
className=
"relative aspect-video overflow-hidden bg-[#edf1f5]"
>
<
Image
<
Safe
Image
src=
{
video
.
thumbnail
}
src=
{
video
.
thumbnail
}
alt=
{
video
.
name
}
alt=
{
video
.
name
}
width=
{
900
}
width=
{
900
}
...
...
src/app/admin/advertisements/advertisement-list.tsx
View file @
d5228e26
...
@@ -45,7 +45,7 @@ import {
...
@@ -45,7 +45,7 @@ import {
import
{
PermissionGate
}
from
"@/components/shared/permission-gate"
;
import
{
PermissionGate
}
from
"@/components/shared/permission-gate"
;
import
{
usePermission
}
from
"@/hooks/usePermission"
;
import
{
usePermission
}
from
"@/hooks/usePermission"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
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
type
{
AdminMediaItem
}
from
"@/mockdata/admin-news"
;
import
links
from
"@/links"
;
import
links
from
"@/links"
;
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
...
@@ -369,7 +369,7 @@ export function AdvertisementList({
...
@@ -369,7 +369,7 @@ export function AdvertisementList({
style=
{
{
aspectRatio
:
previewAspect
,
width
:
type
===
"horizontal"
?
112
:
72
}
}
style=
{
{
aspectRatio
:
previewAspect
,
width
:
type
===
"horizontal"
?
112
:
72
}
}
>
>
{
imageUrl
?
(
{
imageUrl
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
imageUrl
}
src=
{
imageUrl
}
alt=
{
ad
.
alt
||
ad
.
name
}
alt=
{
ad
.
alt
||
ad
.
name
}
fill
fill
...
@@ -514,7 +514,7 @@ export function AdvertisementList({
...
@@ -514,7 +514,7 @@ export function AdvertisementList({
style=
{
{
aspectRatio
:
previewAspect
,
width
:
type
===
"horizontal"
?
200
:
120
}
}
style=
{
{
aspectRatio
:
previewAspect
,
width
:
type
===
"horizontal"
?
200
:
120
}
}
>
>
{
form
.
filePreviewUrl
?
(
{
form
.
filePreviewUrl
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
form
.
filePreviewUrl
}
src=
{
form
.
filePreviewUrl
}
alt=
"Preview"
alt=
"Preview"
fill
fill
...
...
src/app/admin/base-config/_components/config-item-dialog.tsx
View file @
d5228e26
"use client"
;
"use client"
;
import
{
ImagePlus
,
Save
}
from
"lucide-react"
;
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
{
Button
}
from
"@/components/ui/button"
;
import
{
import
{
Dialog
,
Dialog
,
...
@@ -114,7 +114,7 @@ export function ConfigItemDialog({
...
@@ -114,7 +114,7 @@ export function ConfigItemDialog({
<
div
className=
"overflow-hidden rounded-3xl border border-dashed border-[#063e8e]/20 bg-[#eef4ff]/60"
>
<
div
className=
"overflow-hidden rounded-3xl border border-dashed border-[#063e8e]/20 bg-[#eef4ff]/60"
>
<
div
className=
"relative aspect-[16/9]"
>
<
div
className=
"relative aspect-[16/9]"
>
{
previewMedia
?
(
{
previewMedia
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
previewMedia
.
url
}
src=
{
previewMedia
.
url
}
alt=
{
previewMedia
.
alt
||
previewMedia
.
name
}
alt=
{
previewMedia
.
alt
||
previewMedia
.
name
}
fill
fill
...
...
src/app/admin/base-config/_components/config-item-preview.tsx
View file @
d5228e26
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
import
type
{
BaseConfigBannerItem
}
from
"@/mockdata/base-config"
;
import
type
{
BaseConfigBannerItem
}
from
"@/mockdata/base-config"
;
import
type
{
AdminMediaItem
}
from
"@/mockdata/admin-news"
;
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
({
export
function
ConfigItemPreview
({
title
,
title
,
...
@@ -28,7 +28,7 @@ export function ConfigItemPreview({
...
@@ -28,7 +28,7 @@ export function ConfigItemPreview({
>
>
<
div
className=
"relative aspect-[16/10] overflow-hidden bg-[#eef4ff]"
>
<
div
className=
"relative aspect-[16/10] overflow-hidden bg-[#eef4ff]"
>
{
media
?
(
{
media
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
media
.
url
}
src=
{
media
.
url
}
alt=
{
media
.
alt
||
media
.
name
}
alt=
{
media
.
alt
||
media
.
name
}
fill
fill
...
...
src/app/admin/base-config/page.tsx
View file @
d5228e26
...
@@ -14,7 +14,7 @@ import {
...
@@ -14,7 +14,7 @@ import {
import
{
toast
}
from
"sonner"
;
import
{
toast
}
from
"sonner"
;
import
{
AdminDeleteDialog
}
from
"@/components/admin/admin-delete-dialog"
;
import
{
AdminDeleteDialog
}
from
"@/components/admin/admin-delete-dialog"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
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
{
Badge
}
from
"@/components/ui/badge"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
import
{
...
@@ -845,7 +845,7 @@ export default function AdminBaseConfigPage() {
...
@@ -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]"
>
<
div
className=
"relative flex min-h-[320px] items-center justify-center overflow-hidden rounded-[24px] border border-dashed border-[#063e8e]/18 bg-[#eef4ff]"
>
{
currentLogoMedia
?
(
{
currentLogoMedia
?
(
<
div
className=
"relative h-[220px] w-[220px]"
>
<
div
className=
"relative h-[220px] w-[220px]"
>
<
Safe
Next
Image
<
SafeImage
src=
{
currentLogoMedia
.
url
}
src=
{
currentLogoMedia
.
url
}
alt=
{
currentLogoMedia
.
alt
||
currentLogoMedia
.
name
}
alt=
{
currentLogoMedia
.
alt
||
currentLogoMedia
.
name
}
fill
fill
...
@@ -993,7 +993,7 @@ export default function AdminBaseConfigPage() {
...
@@ -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=
"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]"
>
<
div
className=
"relative aspect-[16/6] overflow-hidden rounded-[24px] border border-[#063e8e]/12 bg-[#eef4ff]"
>
{
currentBannerMedia
?
(
{
currentBannerMedia
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
currentBannerMedia
.
url
}
src=
{
currentBannerMedia
.
url
}
alt=
{
currentBannerMedia
.
alt
||
currentBannerMedia
.
name
}
alt=
{
currentBannerMedia
.
alt
||
currentBannerMedia
.
name
}
fill
fill
...
...
src/app/admin/dashboard/page.tsx
View file @
d5228e26
...
@@ -16,7 +16,7 @@ import {
...
@@ -16,7 +16,7 @@ import {
Sparkles
,
Sparkles
,
Users
,
Users
,
}
from
"lucide-react"
;
}
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
{
Badge
}
from
"@/components/ui/badge"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Card
,
CardContent
,
CardDescription
,
CardHeader
,
CardTitle
}
from
"@/components/ui/card"
;
import
{
Card
,
CardContent
,
CardDescription
,
CardHeader
,
CardTitle
}
from
"@/components/ui/card"
;
...
@@ -439,7 +439,7 @@ export default function AdminDashboardPage() {
...
@@ -439,7 +439,7 @@ export default function AdminDashboardPage() {
>
>
<
div
className=
"relative h-20 w-28 shrink-0 overflow-hidden rounded-2xl bg-[#eef4ff]"
>
<
div
className=
"relative h-20 w-28 shrink-0 overflow-hidden rounded-2xl bg-[#eef4ff]"
>
{
item
.
thumbnail
?
(
{
item
.
thumbnail
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
thumbnail
.
url
}
src=
{
item
.
thumbnail
.
url
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
fill
fill
...
@@ -493,7 +493,7 @@ export default function AdminDashboardPage() {
...
@@ -493,7 +493,7 @@ export default function AdminDashboardPage() {
<
div
className=
"mt-4 grid grid-cols-3 gap-3"
>
<
div
className=
"mt-4 grid grid-cols-3 gap-3"
>
{
mediaItems
.
slice
(
0
,
3
).
map
((
item
)
=>
(
{
mediaItems
.
slice
(
0
,
3
).
map
((
item
)
=>
(
<
div
key=
{
item
.
id
}
className=
"relative aspect-square overflow-hidden rounded-2xl bg-[#eef4ff]"
>
<
div
key=
{
item
.
id
}
className=
"relative aspect-square overflow-hidden rounded-2xl bg-[#eef4ff]"
>
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
url
}
src=
{
item
.
url
}
alt=
{
item
.
alt
||
item
.
name
}
alt=
{
item
.
alt
||
item
.
name
}
fill
fill
...
...
src/app/admin/header-config/[categoryId]/page.tsx
View file @
d5228e26
...
@@ -15,7 +15,7 @@ import { AdminRowActions } from "@/components/admin/admin-row-actions";
...
@@ -15,7 +15,7 @@ import { AdminRowActions } from "@/components/admin/admin-row-actions";
import
{
AdminStatsGrid
}
from
"@/components/admin/admin-stats-grid"
;
import
{
AdminStatsGrid
}
from
"@/components/admin/admin-stats-grid"
;
import
{
AdminTableLayout
}
from
"@/components/admin/admin-table-layout"
;
import
{
AdminTableLayout
}
from
"@/components/admin/admin-table-layout"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
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
{
Badge
}
from
"@/components/ui/badge"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
import
{
...
@@ -315,7 +315,7 @@ export default function HeaderCategoryPostsPage() {
...
@@ -315,7 +315,7 @@ export default function HeaderCategoryPostsPage() {
<
TableCell
className=
"text-center"
>
<
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]"
>
<
div
className=
"relative mx-auto h-16 w-24 overflow-hidden rounded-xl border border-[#063e8e]/15 bg-[#063e8e]/[0.03]"
>
{
item
.
thumbnail
?
(
{
item
.
thumbnail
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
thumbnail
.
url
}
src=
{
item
.
thumbnail
.
url
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
fill
fill
...
...
src/app/admin/media/_components/media-form-dialog.tsx
View file @
d5228e26
...
@@ -8,7 +8,7 @@ import {
...
@@ -8,7 +8,7 @@ import {
X
,
X
,
}
from
"lucide-react"
;
}
from
"lucide-react"
;
import
{
toast
}
from
"sonner"
;
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
{
Button
}
from
"@/components/ui/button"
;
import
{
import
{
Dialog
,
Dialog
,
...
@@ -103,7 +103,7 @@ export function MediaFormDialog({
...
@@ -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=
"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%)]"
>
<
div
className=
"relative aspect-[16/10] bg-[radial-gradient(circle_at_top,#d9e8ff_0%,#f7faff_58%,#ffffff_100%)]"
>
{
form
.
previewUrl
?
(
{
form
.
previewUrl
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
form
.
previewUrl
}
src=
{
form
.
previewUrl
}
alt=
{
form
.
name
}
alt=
{
form
.
name
}
fill
fill
...
...
src/app/admin/media/page.tsx
View file @
d5228e26
...
@@ -9,7 +9,7 @@ import {
...
@@ -9,7 +9,7 @@ import {
import
{
toast
}
from
"sonner"
;
import
{
toast
}
from
"sonner"
;
import
{
AdminDeleteDialog
}
from
"@/components/admin/admin-delete-dialog"
;
import
{
AdminDeleteDialog
}
from
"@/components/admin/admin-delete-dialog"
;
import
{
AdminTableLayout
}
from
"@/components/admin/admin-table-layout"
;
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
{
Pagination
}
from
"@/components/base/pagination"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
import
{
...
@@ -161,7 +161,7 @@ export default function AdminMediaPage() {
...
@@ -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)]"
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%)]"
>
<
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
)
}
src=
{
resolveCmsFileUrl
(
item
.
path
)
}
alt=
{
item
.
original
??
""
}
alt=
{
item
.
original
??
""
}
fill
fill
...
...
src/app/admin/members/[id]/_components/member-form.tsx
View file @
d5228e26
...
@@ -7,7 +7,7 @@ import { useRouter } from "next/navigation";
...
@@ -7,7 +7,7 @@ import { useRouter } from "next/navigation";
import
{
toast
}
from
"sonner"
;
import
{
toast
}
from
"sonner"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
import
{
AdminPostContentEditor
}
from
"@/components/admin/post-content-editor"
;
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
{
Button
}
from
"@/components/ui/button"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Input
}
from
"@/components/ui/input"
;
import
{
Label
}
from
"@/components/ui/label"
;
import
{
Label
}
from
"@/components/ui/label"
;
...
@@ -351,7 +351,7 @@ export function AdminMemberForm({ memberId }: AdminMemberFormProps) {
...
@@ -351,7 +351,7 @@ export function AdminMemberForm({ memberId }: AdminMemberFormProps) {
{
form
.
image
?
(
{
form
.
image
?
(
<
div
className=
"space-y-3"
>
<
div
className=
"space-y-3"
>
<
div
className=
"relative aspect-video overflow-hidden rounded-xl border border-[#063e8e]/15"
>
<
div
className=
"relative aspect-video overflow-hidden rounded-xl border border-[#063e8e]/15"
>
<
Safe
Next
Image
<
SafeImage
src=
{
form
.
image
.
url
}
src=
{
form
.
image
.
url
}
alt=
{
form
.
image
.
alt
||
form
.
image
.
name
}
alt=
{
form
.
image
.
alt
||
form
.
image
.
name
}
fill
fill
...
...
src/app/admin/members/_components/member-row.tsx
View file @
d5228e26
"use client"
;
"use client"
;
import
{
AdminRowActions
}
from
"@/components/admin/admin-row-actions"
;
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
{
Badge
}
from
"@/components/ui/badge"
;
import
{
import
{
TableCell
,
TableCell
,
...
@@ -47,7 +47,7 @@ export function MemberRow({
...
@@ -47,7 +47,7 @@ export function MemberRow({
<
TableCell
className=
"px-4 py-3 text-center"
>
<
TableCell
className=
"px-4 py-3 text-center"
>
{
item
.
image
?
(
{
item
.
image
?
(
<
div
className=
"mx-auto h-12 w-16 overflow-hidden rounded-lg border border-[#063e8e]/15"
>
<
div
className=
"mx-auto h-12 w-16 overflow-hidden rounded-lg border border-[#063e8e]/15"
>
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
image
.
url
}
src=
{
item
.
image
.
url
}
alt=
{
item
.
image
.
alt
||
item
.
name
}
alt=
{
item
.
image
.
alt
||
item
.
name
}
width=
{
64
}
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";
...
@@ -15,7 +15,7 @@ import { AdminImagePicker } from "@/components/admin/image-picker";
import
{
AdminPostContentEditor
}
from
"@/components/admin/post-content-editor"
;
import
{
AdminPostContentEditor
}
from
"@/components/admin/post-content-editor"
;
import
{
PostHistoryViewer
}
from
"./post-history-viewer"
;
import
{
PostHistoryViewer
}
from
"./post-history-viewer"
;
import
{
AdminRichTextEditor
}
from
"@/components/shared/rich-text-editor"
;
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
{
PermissionGate
}
from
"@/components/shared/permission-gate"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Checkbox
}
from
"@/components/ui/checkbox"
;
import
{
Checkbox
}
from
"@/components/ui/checkbox"
;
...
@@ -530,7 +530,7 @@ export function AdminNewsFormContent() {
...
@@ -530,7 +530,7 @@ export function AdminNewsFormContent() {
<
div
className=
"relative overflow-hidden rounded-2xl border border-[#063e8e]/15 bg-white"
>
<
div
className=
"relative overflow-hidden rounded-2xl border border-[#063e8e]/15 bg-white"
>
<
div
className=
"relative aspect-[16/11]"
>
<
div
className=
"relative aspect-[16/11]"
>
{
form
.
thumbnail
?
(
{
form
.
thumbnail
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
form
.
thumbnail
.
url
}
src=
{
form
.
thumbnail
.
url
}
alt=
{
form
.
thumbnail
.
alt
||
form
.
thumbnail
.
name
}
alt=
{
form
.
thumbnail
.
alt
||
form
.
thumbnail
.
name
}
fill
fill
...
...
src/app/admin/news/page.tsx
View file @
d5228e26
...
@@ -15,7 +15,7 @@ import { AdminDeleteDialog } from "@/components/admin/admin-delete-dialog";
...
@@ -15,7 +15,7 @@ import { AdminDeleteDialog } from "@/components/admin/admin-delete-dialog";
import
{
AdminRowActions
}
from
"@/components/admin/admin-row-actions"
;
import
{
AdminRowActions
}
from
"@/components/admin/admin-row-actions"
;
import
{
AdminStatsGrid
}
from
"@/components/admin/admin-stats-grid"
;
import
{
AdminStatsGrid
}
from
"@/components/admin/admin-stats-grid"
;
import
{
AdminTableLayout
}
from
"@/components/admin/admin-table-layout"
;
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
{
Badge
}
from
"@/components/ui/badge"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
Button
}
from
"@/components/ui/button"
;
import
{
import
{
...
@@ -449,7 +449,7 @@ export default function AdminNewsPage() {
...
@@ -449,7 +449,7 @@ export default function AdminNewsPage() {
<
TableCell
className=
"text-center"
>
<
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]"
>
<
div
className=
"relative mx-auto h-16 w-24 overflow-hidden rounded-xl border border-[#063e8e]/15 bg-[#063e8e]/[0.03]"
>
{
item
.
thumbnail
?
(
{
item
.
thumbnail
?
(
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
thumbnail
.
url
}
src=
{
item
.
thumbnail
.
url
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
alt=
{
item
.
thumbnail
.
alt
||
item
.
thumbnail
.
name
}
fill
fill
...
...
src/components/admin/image-picker.tsx
View file @
d5228e26
...
@@ -12,7 +12,7 @@ import {
...
@@ -12,7 +12,7 @@ import {
DialogHeader
,
DialogHeader
,
DialogTitle
,
DialogTitle
,
}
from
"@/components/ui/dialog"
;
}
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
type
{
AdminMediaItem
}
from
"@/mockdata/admin-news"
;
import
{
toAdminMediaItem
}
from
"@/lib/utils/file"
;
import
{
toAdminMediaItem
}
from
"@/lib/utils/file"
;
import
{
getApiV10File
,
postApiV10FileUpload
}
from
"@/api/vcci-news/endpoints/file"
;
import
{
getApiV10File
,
postApiV10FileUpload
}
from
"@/api/vcci-news/endpoints/file"
;
...
@@ -210,7 +210,7 @@ export function AdminImagePicker({
...
@@ -210,7 +210,7 @@ export function AdminImagePicker({
)
}
)
}
>
>
<
div
className=
"relative aspect-[4/3] overflow-hidden bg-[#063e8e]/[0.04]"
>
<
div
className=
"relative aspect-[4/3] overflow-hidden bg-[#063e8e]/[0.04]"
>
<
Safe
Next
Image
<
SafeImage
src=
{
item
.
url
}
src=
{
item
.
url
}
alt=
{
item
.
alt
||
item
.
name
}
alt=
{
item
.
alt
||
item
.
name
}
fill
fill
...
...
src/components/admin/post-content-editor.tsx
View file @
d5228e26
...
@@ -4,7 +4,7 @@ import * as React from "react";
...
@@ -4,7 +4,7 @@ import * as React from "react";
import
{
Image
as
ImageIcon
,
Pencil
,
Plus
,
Type
,
Upload
,
X
}
from
"lucide-react"
;
import
{
Image
as
ImageIcon
,
Pencil
,
Plus
,
Type
,
Upload
,
X
}
from
"lucide-react"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
import
{
AdminImagePicker
}
from
"@/components/admin/image-picker"
;
import
{
AdminRichTextEditor
}
from
"@/components/shared/rich-text-editor"
;
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
{
Button
}
from
"@/components/ui/button"
;
import
{
Label
}
from
"@/components/ui/label"
;
import
{
Label
}
from
"@/components/ui/label"
;
import
{
import
{
...
@@ -409,7 +409,7 @@ export function AdminPostContentEditor({
...
@@ -409,7 +409,7 @@ export function AdminPostContentEditor({
}
}
}
}
className=
"relative h-64 w-full cursor-pointer"
className=
"relative h-64 w-full cursor-pointer"
>
>
<
Safe
Next
Image
<
SafeImage
src=
{
currentImage
.
image
.
url
}
src=
{
currentImage
.
image
.
url
}
alt=
{
currentImage
.
image
.
alt
||
currentImage
.
image
.
name
}
alt=
{
currentImage
.
image
.
alt
||
currentImage
.
image
.
name
}
fill
fill
...
...
src/components/layout/main/header.tsx
View file @
d5228e26
...
@@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
...
@@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
import
{
useRouter
}
from
"next/navigation"
;
import
{
useRouter
}
from
"next/navigation"
;
import
{
Facebook
,
Linkedin
,
Menu
,
Twitter
,
X
,
Youtube
}
from
"lucide-react"
;
import
{
Facebook
,
Linkedin
,
Menu
,
Twitter
,
X
,
Youtube
}
from
"lucide-react"
;
import
{
useQuery
}
from
"@tanstack/react-query"
;
import
{
useQuery
}
from
"@tanstack/react-query"
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
const
fallbackLogo
=
"/logo.png"
;
const
fallbackLogo
=
"/logo.png"
;
import
{
useGetApiV10Logo
}
from
"@/api/vcci-news/endpoints/logo"
;
import
{
useGetApiV10Logo
}
from
"@/api/vcci-news/endpoints/logo"
;
...
@@ -335,18 +335,14 @@ function Header() {
...
@@ -335,18 +335,14 @@ function Header() {
href=
"/"
href=
"/"
className=
"flex w-[136px] shrink-0 items-center xl:w-[152px]"
className=
"flex w-[136px] shrink-0 items-center xl:w-[152px]"
>
>
<
Image
<
Safe
Image
width=
{
108
}
width=
{
108
}
height=
{
40
}
height=
{
40
}
className=
"h-auto max-h-10 w-[108px] object-contain"
className=
"h-auto max-h-10 w-[108px] object-contain"
src=
{
currentLogo
?.
logo_url
?
links
.
resolveImageUrl
(
currentLogo
.
logo_url
)
:
fallbackLogo
}
src=
{
currentLogo
?.
logo_url
?
links
.
resolveImageUrl
(
currentLogo
.
logo_url
)
:
fallbackLogo
}
fallbackSrc=
{
fallbackLogo
}
alt=
{
currentLogo
?.
logo_name
||
"VCCI-HCM"
}
alt=
{
currentLogo
?.
logo_name
||
"VCCI-HCM"
}
priority
priority
unoptimized=
{
Boolean
(
currentLogo
?.
logo_url
)
}
onError=
{
(
e
)
=>
{
const
img
=
e
.
currentTarget
;
if
(
img
.
src
!==
fallbackLogo
)
img
.
src
=
fallbackLogo
;
}
}
/>
/>
</
Link
>
</
Link
>
...
@@ -394,18 +390,14 @@ function Header() {
...
@@ -394,18 +390,14 @@ function Header() {
className=
"flex w-[136px] shrink-0 items-center"
className=
"flex w-[136px] shrink-0 items-center"
onClick=
{
()
=>
setToggleMenu
(
false
)
}
onClick=
{
()
=>
setToggleMenu
(
false
)
}
>
>
<
Image
<
Safe
Image
width=
{
108
}
width=
{
108
}
height=
{
40
}
height=
{
40
}
className=
"h-auto max-h-10 w-[108px] object-contain"
className=
"h-auto max-h-10 w-[108px] object-contain"
src=
{
currentLogo
?.
logo_url
?
links
.
resolveImageUrl
(
currentLogo
.
logo_url
)
:
fallbackLogo
}
src=
{
currentLogo
?.
logo_url
?
links
.
resolveImageUrl
(
currentLogo
.
logo_url
)
:
fallbackLogo
}
fallbackSrc=
{
fallbackLogo
}
alt=
{
currentLogo
?.
logo_name
||
"VCCI-HCM"
}
alt=
{
currentLogo
?.
logo_name
||
"VCCI-HCM"
}
priority
priority
unoptimized=
{
Boolean
(
currentLogo
?.
logo_url
)
}
onError=
{
(
e
)
=>
{
const
img
=
e
.
currentTarget
;
if
(
img
.
src
!==
fallbackLogo
)
img
.
src
=
fallbackLogo
;
}
}
/>
/>
</
Link
>
</
Link
>
<
button
<
button
...
...
src/components/
admin/safe-next
-image.tsx
→
src/components/
shared/safe
-image.tsx
View file @
d5228e26
"use client"
;
"use client"
;
import
Image
,
{
type
ImageProps
}
from
"next/image"
;
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
;
src
?:
string
|
null
;
fallbackSrc
?:
string
;
fallbackSrc
?:
string
;
}
}
export
function
Safe
Next
Image
({
export
function
SafeImage
({
src
,
src
,
alt
,
alt
,
fallbackSrc
=
"/img-error.png"
,
fallbackSrc
=
"/img-error.png"
,
...
props
...
props
}:
Safe
Next
ImageProps
)
{
}:
SafeImageProps
)
{
const
[
currentSrc
,
setCurrentSrc
]
=
React
.
useState
(
src
||
fallbackSrc
);
const
[
currentSrc
,
setCurrentSrc
]
=
useState
(
src
||
fallbackSrc
);
const
[
hasFailed
,
setHasFailed
]
=
React
.
useState
(
false
);
const
[
hasFailed
,
setHasFailed
]
=
useState
(
false
);
React
.
useEffect
(()
=>
{
useEffect
(()
=>
{
setCurrentSrc
(
src
||
fallbackSrc
);
setCurrentSrc
(
src
||
fallbackSrc
);
setHasFailed
(
false
);
setHasFailed
(
false
);
},
[
fallbackSrc
,
src
]);
},
[
fallbackSrc
,
src
]);
...
...
src/components/shared/sidebar-advertisements/index.tsx
View file @
d5228e26
'use client'
;
'use client'
;
import
Image
from
"next/
image"
;
import
{
SafeImage
}
from
"@/components/shared/safe-
image"
;
import
Link
from
"next/link"
;
import
Link
from
"next/link"
;
import
{
useMemo
,
useState
}
from
"react"
;
import
{
useMemo
}
from
"react"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
{
useAdvertisements
}
from
"@/app/(main)/(home)/lib/use-advertisements"
;
import
links
from
"@/links"
;
import
links
from
"@/links"
;
import
type
{
Advertisement
}
from
"@/api/vcci-news/models/advertisement"
;
import
type
{
Advertisement
}
from
"@/api/vcci-news/models/advertisement"
;
...
@@ -11,8 +11,7 @@ import { getFallbackImage } from "@/lib/utils/fallback-image";
...
@@ -11,8 +11,7 @@ import { getFallbackImage } from "@/lib/utils/fallback-image";
const
FALLBACK_HREF
=
"https://vcci-hcm.org.vn"
;
const
FALLBACK_HREF
=
"https://vcci-hcm.org.vn"
;
function
SidebarAdItem
({
item
,
fallbackSrc
}:
{
item
:
Advertisement
;
fallbackSrc
:
string
})
{
function
SidebarAdItem
({
item
,
fallbackSrc
}:
{
item
:
Advertisement
;
fallbackSrc
:
string
})
{
const
initialSrc
=
item
.
file
?.
path
?
links
.
resolveImageUrl
(
item
.
file
.
path
)
:
fallbackSrc
;
const
src
=
item
.
file
?.
path
?
links
.
resolveImageUrl
(
item
.
file
.
path
)
:
fallbackSrc
;
const
[
src
,
setSrc
]
=
useState
(
initialSrc
);
return
(
return
(
<
Link
<
Link
...
@@ -23,15 +22,13 @@ function SidebarAdItem({ item, fallbackSrc }: { item: Advertisement; fallbackSrc
...
@@ -23,15 +22,13 @@ function SidebarAdItem({ item, fallbackSrc }: { item: Advertisement; fallbackSrc
title=
{
item
.
name
}
title=
{
item
.
name
}
>
>
<
div
className=
"relative aspect-[16/10]"
>
<
div
className=
"relative aspect-[16/10]"
>
<
Image
<
Safe
Image
src=
{
src
}
src=
{
src
}
fallbackSrc=
{
fallbackSrc
}
alt=
{
item
.
alt
||
item
.
name
}
alt=
{
item
.
alt
||
item
.
name
}
fill
fill
sizes=
"(max-width: 768px) 100vw, 300px"
sizes=
"(max-width: 768px) 100vw, 300px"
className=
"h-full w-full object-cover"
className=
"h-full w-full object-cover"
onError=
{
()
=>
{
if
(
src
!==
fallbackSrc
)
setSrc
(
fallbackSrc
);
}
}
/>
/>
</
div
>
</
div
>
</
Link
>
</
Link
>
...
@@ -48,7 +45,7 @@ function FallbackSidebarAdItem({ src }: { src: string }) {
...
@@ -48,7 +45,7 @@ function FallbackSidebarAdItem({ src }: { src: string }) {
title=
"Quảng cáo VCCI HCM"
title=
"Quảng cáo VCCI HCM"
>
>
<
div
className=
"relative aspect-[16/10]"
>
<
div
className=
"relative aspect-[16/10]"
>
<
Image
<
Safe
Image
src=
{
src
}
src=
{
src
}
alt=
"Quảng cáo VCCI HCM"
alt=
"Quảng cáo VCCI HCM"
fill
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