Commit 3d99fb77 authored by Phan Ngọc Quốc Văn's avatar Phan Ngọc Quốc Văn

Msdasderge branch 'feature/home_page' of...

Msdasderge branch 'feature/home_page' of https://gitlab.meu-solutions.com/vanhoangit/vcci-news into feature/home_page
parents 04d56f7d 53828441
......@@ -39,3 +39,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
#vscode
.vscode/
\ No newline at end of file
......@@ -44,6 +44,9 @@ importers:
dayjs:
specifier: ^1.11.19
version: 1.11.19
html-react-parser:
specifier: ^5.2.7
version: 5.2.7(@types/react@19.2.2)(react@19.2.0)
lucide-react:
specifier: ^0.548.0
version: 0.548.0(react@19.2.0)
......@@ -71,6 +74,9 @@ importers:
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@4.1.16)
uuid:
specifier: ^13.0.0
version: 13.0.0
zod:
specifier: ^4.1.12
version: 4.1.12
......@@ -1768,6 +1774,19 @@ packages:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
domelementtype@2.3.0:
resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
domhandler@5.0.3:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
domutils@3.2.2:
resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
......@@ -1793,6 +1812,10 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
entities@6.0.1:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
engines: {node: '>= 0.4'}
......@@ -2167,6 +2190,21 @@ packages:
hermes-parser@0.25.1:
resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
html-dom-parser@5.1.1:
resolution: {integrity: sha512-+o4Y4Z0CLuyemeccvGN4bAO20aauB2N9tFEAep5x4OW34kV4PTarBHm6RL02afYt2BMKcr0D2Agep8S3nJPIBg==}
html-react-parser@5.2.7:
resolution: {integrity: sha512-WzIAcqQoZoF49J9aev8NBDLz9TJvt2RmipeYA+/5+5x0sWCwFxqKiq0lysieiSA/G6dbUZ6KGGy65Cx2fjie5Q==}
peerDependencies:
'@types/react': 0.14 || 15 || 16 || 17 || 18 || 19
react: 0.14 || 15 || 16 || 17 || 18 || 19
peerDependenciesMeta:
'@types/react':
optional: true
htmlparser2@10.0.0:
resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==}
http2-client@1.3.5:
resolution: {integrity: sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==}
......@@ -2196,6 +2234,9 @@ packages:
inflected@2.1.0:
resolution: {integrity: sha512-hAEKNxvHf2Iq3H60oMBHkB4wl5jn3TPF3+fXek/sRwAB5gP9xWs4r7aweSF95f99HFoz69pnZTcu8f0SIHV18w==}
inline-style-parser@0.2.4:
resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==}
internal-slot@1.1.0:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
......@@ -2830,6 +2871,9 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-property@2.0.2:
resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==}
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
......@@ -3065,6 +3109,12 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
style-to-js@1.1.18:
resolution: {integrity: sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg==}
style-to-object@1.0.11:
resolution: {integrity: sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow==}
styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'}
......@@ -3267,6 +3317,10 @@ packages:
resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
engines: {node: '>= 4'}
uuid@13.0.0:
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
hasBin: true
v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
......@@ -5089,6 +5143,24 @@ snapshots:
dependencies:
esutils: 2.0.3
dom-serializer@2.0.0:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
entities: 4.5.0
domelementtype@2.3.0: {}
domhandler@5.0.3:
dependencies:
domelementtype: 2.3.0
domutils@3.2.2:
dependencies:
dom-serializer: 2.0.0
domelementtype: 2.3.0
domhandler: 5.0.3
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
......@@ -5113,6 +5185,8 @@ snapshots:
entities@4.5.0: {}
entities@6.0.1: {}
es-abstract@1.24.0:
dependencies:
array-buffer-byte-length: 1.0.2
......@@ -5679,6 +5753,28 @@ snapshots:
dependencies:
hermes-estree: 0.25.1
html-dom-parser@5.1.1:
dependencies:
domhandler: 5.0.3
htmlparser2: 10.0.0
html-react-parser@5.2.7(@types/react@19.2.2)(react@19.2.0):
dependencies:
domhandler: 5.0.3
html-dom-parser: 5.1.1
react: 19.2.0
react-property: 2.0.2
style-to-js: 1.1.18
optionalDependencies:
'@types/react': 19.2.2
htmlparser2@10.0.0:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.2.2
entities: 6.0.1
http2-client@1.3.5: {}
human-signals@8.0.1: {}
......@@ -5698,6 +5794,8 @@ snapshots:
inflected@2.1.0: {}
inline-style-parser@0.2.4: {}
internal-slot@1.1.0:
dependencies:
es-errors: 1.3.0
......@@ -6326,6 +6424,8 @@ snapshots:
react-is@16.13.1: {}
react-property@2.0.2: {}
react-remove-scroll-bar@2.3.8(@types/react@19.2.2)(react@19.2.0):
dependencies:
react: 19.2.0
......@@ -6629,6 +6729,14 @@ snapshots:
strip-json-comments@3.1.1: {}
style-to-js@1.1.18:
dependencies:
style-to-object: 1.0.11
style-to-object@1.0.11:
dependencies:
inline-style-parser: 0.2.4
styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0):
dependencies:
client-only: 0.0.1
......@@ -6853,6 +6961,8 @@ snapshots:
utility-types@3.11.0: {}
uuid@13.0.0: {}
v8-compile-cache-lib@3.0.1: {}
validator@13.15.20: {}
......
......@@ -27,13 +27,43 @@ AXIOS_INSTANCE.interceptors.response.use(
(error) => Promise.reject(error)
)
const useCustomClient = <T>(config: AxiosRequestConfig, options?: AxiosRequestConfig): Promise<T> => {
// Helper function to convert RequestInit headers to Axios format
const convertHeaders = (headers?: HeadersInit): Record<string, string> | undefined => {
if (!headers) return undefined
if (headers instanceof Headers) {
const result: Record<string, string> = {}
headers.forEach((value, key) => {
result[key] = value
})
return result
}
if (Array.isArray(headers)) {
const result: Record<string, string> = {}
headers.forEach(([key, value]) => {
result[key] = value
})
return result
}
return headers as Record<string, string>
}
const useCustomClient = <T>(url: string, options?: RequestInit): Promise<T> => {
const source = Axios.CancelToken.source()
const promise = AXIOS_INSTANCE({
...config,
...options,
// Convert RequestInit to AxiosRequestConfig
const axiosConfig: AxiosRequestConfig = {
url,
method: options?.method || 'GET',
headers: convertHeaders(options?.headers),
data: options?.body,
signal: options?.signal || undefined,
cancelToken: source.token
}).then(({ data, status }) => {
}
const promise = AXIOS_INSTANCE(axiosConfig).then(({ data, status }) => {
return data instanceof Blob ? data : { ...data, statusCode: status }
})
......
export interface NewsItem {
export interface CategoryAdminItem {
id: string
title: string
thumbnail: string
external_link: string
name: string
description: string
release_at: string
is_active: boolean
created_at: string
created_by: string | null
updated_at: string
updated_by: string | null
mode: 'NOW' | string
category: string
}
export interface NewsResponseData {
export interface CategoryAdminResponseData {
count: number
rows: NewsItem[]
rows: CategoryAdminItem[]
totalPages: number
currentPage: number
}
export interface GetNewsResponseType {
export interface GetCategoryAdminResponseType {
message: string
message_en: string
responseData: NewsResponseData
responseData: CategoryAdminResponseData
status: 'success' | 'error'
timeStamp: string
violations: any | null
......
export interface NewsAdminItem {
id: string
title: string
thumbnail: string
external_link: string
description: string
release_at: string
is_active: boolean
created_at: string
created_by: string | null
updated_at: string
updated_by: string | null
mode: 'NOW' | string
category: string
}
export interface NewsAdminResponseData {
count: number
rows: NewsAdminItem[]
totalPages: number
currentPage: number
}
export interface GetNewsAdminResponseType {
message: string
message_en: string
responseData: NewsAdminResponseData
status: 'success' | 'error'
timeStamp: string
violations: any | null
}
//Core
import dayjs from 'dayjs'
// App
import { Spinner } from '@/components/ui'
import AppEditorContent from '@/components/shared/editor-content'
import BASE_URLS from '@/links'
// import { useGetNewsId } from '#/api/endpoints/news';
import { NewsAdminItem } from '@/api/types/news';
import { Link, CalendarFold, Book } from 'lucide-react';
// import { t } from 'i18next'
// Component
const NewsDetailPage = (params: { id: string }) => {
// server
// const { data, isLoading, isError } = useGetNewsId<NewsAdminItem>(getIdFormUrl)
// const { t, i18n } = useTranslation('newsPage')
// const { routeParams, data } = usePageContext()
// const { newsDetail, fallbackClient } = data as Data
// const lang = i18n.language == 'vi' ? 'vi' : 'vi'
// const infoNews = useMemo(() => {
// if (!fallbackClient) return newsDetail?.responseData
// if (!getNewsIdQuery.data) return
// return getNewsIdQuery.data.responseData
// }, [newsDetail?.responseData, fallbackClient, getNewsIdQuery.data])
// Effects
// useEffect(() => {
// // Update document title
// infoNews?.title && (document.title = infoNews.title)
// }, [infoNews])
const isLoading = false;
// Template
return (
<div className='user-news-detail-page pb-10'>
{isLoading ? (
<Spinner />
) : (
<div>
{/* Banner */}
<img src={`${BASE_URLS.imageEndpoint}${data?.responseData?.thumbnail}`} alt='Banner' />
{/* Breadcrumb */}
<div className='app-container py-10'>
<div className='flex gap-4 items-stretch'>
{/* <Breadcrumbs aria-label='breadcrumb'>
<Link
underline='hover'
color='inherit'
href='/'
className='!text-app-blue-secondary !text-sm !leading-normal'
>
{t('breadcrumbHomePage')}
<p>Trang chủ</p>
</Link>
<Link
underline='hover'
color='inherit'
href='/tin-tuc'
className='!text-app-blue-secondary !text-sm !leading-normal'
>
{t('breadcrumbNewsPage')}
<p>Tin tức</p>
</Link>
<Typography className='!text-sm !text-black !leading-normal'>
{t('breadcrumbNewsDetailPage')}
<p>Chi tiết</p>
</Typography>
</Breadcrumbs> */}
</div>
</div>
<div className='app-container bg-white rounded p-10 flex flex-col gap-10'>
{/* Heading */}
<div className='text-app-blue text-[32px] leading-normal font-medium text-center'>{data?.responseData?.title}</div>
{/* body */}
<div className='flex items-start gap-8 flex-col lg:flex-row'>
{/* Info */}
<div className='lg:w-[332px] bg-white p-4 rounded shadow-app-quaternary flex flex-col gap-2'>
<div className='text-base text-app-grey font-semibold leading-normal text-center'>
{/* {t('information')} */}
</div>
{/* date */}
<div className='flex items-center gap-2 text-app-grey'>
<CalendarFold />
<span className='text-base'>{dayjs(data?.responseData?.created_at).format('DD/MM/YYYY')}</span>
</div>
{/* author */}
{/* <div className='flex items-center gap-2 text-app-grey'>
<PersonIcon />
<span className='text-base'>{data?.responseData?.created_by}</span>
</div> */}
{/* Category */}
<div className='flex items-center gap-2 text-app-grey'>
<Book />
<span className='text-base'>{data?.responseData?.category}</span>
</div>
</div>
{/* Content */}
<div className='flex-1 text-app-grey text-base overflow-hidden'>
<AppEditorContent value={data?.responseData?.description ?? ''} />
</div>
</div>
</div>
{/* Related News */}
{/* <RelatedNews newsQuery={getRelatedNewsQuery} lang={lang} newsId={infoNews.id} /> */}
</div>
)}
</div>
)
}
export default NewsDetailPage
\ No newline at end of file
import { NewsItem } from '../../page.type'
// import BASE_URL from '#/config'
import { NewsAdminItem } from '@/api/types/news'
import BASE_URL from '@/links'
import dayjs from 'dayjs';
// import { AppEditorContent } from '#/components';
import AppEditorContent from '@/components/shared/editor-content';
function NewsContent({ news }: { news: NewsItem }) {
function NewsContent({ news }: { news: NewsAdminItem }) {
return (
<a
......
export { default } from './NewsContent';
\ No newline at end of file
'use client'
import Image from 'next/image'
import { Button, Card, Spinner } from '@/components/ui'
import { useEffect, useRef, useState } from 'react'
import { Autoplay } from 'swiper/modules'
import { Swiper, SwiperSlide } from 'swiper/react'
......@@ -9,7 +8,13 @@ import { Swiper as SwiperType } from 'swiper/types'
import 'swiper/css'
import 'swiper/css/navigation'
import 'swiper/css/pagination'
import BASE_URL from '@/links/index'
import NewsContent from './components/news-content/NewsContent'
import { Spinner } from '@/components/ui'
import { useGetCategory } from '@/api/endpoints/category'
import { useGetNews } from '@/api/endpoints/news'
import { GetCategoryAdminResponseType } from '@/api/types/category'
import { GetNewsAdminResponseType } from '@/api/types/news'
const Home = () => {
// states
......@@ -24,29 +29,29 @@ const Home = () => {
const swiperRef = useRef<SwiperType | null>(null)
// server
// const { data: categoryData } = useGetCategory<GetCategoryAdminResponseType>();
// const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
// pageSize: '999',
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
// })
const { data: categoryData } = useGetCategory<GetCategoryAdminResponseType>();
const { data: allData, isLoading } = useGetNews<GetNewsAdminResponseType>({
pageSize: '999',
filters: submitSearch ? `title @=${submitSearch}` : undefined,
})
//tab filter
// let data
// if (tab === 'all') {
// data = allData
// } else {
// // fillter by category
// const filteredRows = allData?.responseData?.rows?.filter(
// (news) => news.category === tab
// )
// data = {
// ...allData,
// responseData: {
// ...allData?.responseData,
// rows: filteredRows ?? []
// }
// }
// }
// //tab filter
let data
if (tab === 'all') {
data = allData
} else {
// fillter by category
const filteredRows = allData?.responseData?.rows?.filter(
(news) => news.category === tab
)
data = {
...allData,
responseData: {
...allData?.responseData,
rows: filteredRows ?? []
}
}
}
// update slidesPerView on resize to match the Swiper breakpoints
useEffect(() => {
......@@ -63,76 +68,6 @@ const Home = () => {
return () => window.removeEventListener('resize', update)
}, [])
//demo
const isLoading = false
const allData = {
responseData: {
rows: [
{
id: 1,
title: 'News Title 1',
thumbnail: 'https://media.istockphoto.com/id/814423752/vi/anh/con-m%E1%BA%AFt-c%E1%BB%A7a-ng%C6%B0%E1%BB%9Di-m%E1%BA%ABu-v%E1%BB%9Bi-trang-%C4%91i%E1%BB%83m-ngh%E1%BB%87-thu%E1%BA%ADt-%C4%91%E1%BA%A7y-m%C3%A0u-s%E1%BA%AFc-c%E1%BA%ADn-c%E1%BA%A3nh.jpg?s=1024x1024&w=is&k=20&c=g9JektNW0igwf2u2mT9uqSLkhzR91ZYviuVLXuhy2JQ='
},
{
id: 2,
title: 'News Title 2',
thumbnail: 'https://media.istockphoto.com/id/814423752/vi/anh/con-m%E1%BA%AFt-c%E1%BB%A7a-ng%C6%B0%E1%BB%9Di-m%E1%BA%ABu-v%E1%BB%9Bi-trang-%C4%91i%E1%BB%83m-ngh%E1%BB%87-thu%E1%BA%ADt-%C4%91%E1%BA%A7y-m%C3%A0u-s%E1%BA%AFc-c%E1%BA%ADn-c%E1%BA%A3nh.jpg?s=1024x1024&w=is&k=20&c=g9JektNW0igwf2u2mT9uqSLkhzR91ZYviuVLXuhy2JQ='
},
{
id: 3,
title: 'News Title 3',
thumbnail: 'https://media.istockphoto.com/id/814423752/vi/anh/con-m%E1%BA%AFt-c%E1%BB%A7a-ng%C6%B0%E1%BB%9Di-m%E1%BA%ABu-v%E1%BB%9Bi-trang-%C4%91i%E1%BB%83m-ngh%E1%BB%87-thu%E1%BA%ADt-%C4%91%E1%BA%A7y-m%C3%A0u-s%E1%BA%AFc-c%E1%BA%ADn-c%E1%BA%A3nh.jpg?s=1024x1024&w=is&k=20&c=g9JektNW0igwf2u2mT9uqSLkhzR91ZYviuVLXuhy2JQ='
},
{
id: 4,
title: 'News Title 4',
thumbnail: 'https://media.istockphoto.com/id/814423752/vi/anh/con-m%E1%BA%AFt-c%E1%BB%A7a-ng%C6%B0%E1%BB%9Di-m%E1%BA%ABu-v%E1%BB%9Bi-trang-%C4%91i%E1%BB%83m-ngh%E1%BB%87-thu%E1%BA%ADt-%C4%91%E1%BA%A7y-m%C3%A0u-s%E1%BA%AFc-c%E1%BA%ADn-c%E1%BA%A3nh.jpg?s=1024x1024&w=is&k=20&c=g9JektNW0igwf2u2mT9uqSLkhzR91ZYviuVLXuhy2JQ='
},
{
id: 5,
title: 'News Title 5',
thumbnail: 'https://media.istockphoto.com/id/814423752/vi/anh/con-m%E1%BA%AFt-c%E1%BB%A7a-ng%C6%B0%E1%BB%9Di-m%E1%BA%ABu-v%E1%BB%9Bi-trang-%C4%91i%E1%BB%83m-ngh%E1%BB%87-thu%E1%BA%ADt-%C4%91%E1%BA%A7y-m%C3%A0u-s%E1%BA%AFc-c%E1%BA%ADn-c%E1%BA%A3nh.jpg?s=1024x1024&w=is&k=20&c=g9JektNW0igwf2u2mT9uqSLkhzR91ZYviuVLXuhy2JQ='
}
]
}
}
const data = {
responseData: {
rows: [
{
id: '1',
title: 'News Title 1',
thumbnail: 'https://media.istockphoto.com/id/814423752/vi/anh/con-m%E1%BA%AFt-c%E1%BB%A7a-ng%C6%B0%E1%BB%9Di-m%E1%BA%ABu-v%E1%BB%9Bi-trang-%C4%91i%E1%BB%83m-ngh%E1%BB%87-thu%E1%BA%ADt-%C4%91%E1%BA%A7y-m%C3%A0u-s%E1%BA%AFc-c%E1%BA%ADn-c%E1%BA%A3nh.jpg?s=1024x1024&w=is&k=20&c=g9JektNW0igwf2u2mT9uqSLkhzR91ZYviuVLXuhy2JQ=',
external_link: 'https://vcci-hcm.org.vn/asdfasdf',
description: 'Description for News Title 1',
release_at: '2023-10-01',
is_active: true,
created_at: '2023-09-01',
created_by: 'Admin',
updated_at: '2023-09-02',
updated_by: 'Admin',
mode: 'NOW',
category: 'General'
},
{
id: '2',
title: 'News Title 2',
thumbnail: 'https://media.istockphoto.com/id/814423752/vi/anh/con-m%E1%BA%AFt-c%E1%BB%A7a-ng%C6%B0%E1%BB%9Di-m%E1%BA%ABu-v%E1%BB%9Bi-trang-%C4%91i%E1%BB%83m-ngh%E1%BB%87-thu%E1%BA%ADt-%C4%91%E1%BA%A7y-m%C3%A0u-s%E1%BA%AFc-c%E1%BA%ADn-c%E1%BA%A3nh.jpg?s=1024x1024&w=is&k=20&c=g9JektNW0igwf2u2mT9uqSLkhzR91ZYviuVLXuhy2JQ=',
external_link: 'https://vcci-hcm.org.vn/asdfasdf',
description: 'Description for News Title 2',
release_at: '2023-10-01',
is_active: true,
created_at: '2023-09-01',
created_by: 'Admin',
updated_at: '2023-09-02',
updated_by: 'Admin',
mode: 'NOW',
category: 'General'
}
]
}
}
//template
return (
isLoading ? (
......@@ -166,7 +101,7 @@ const Home = () => {
// pagination={{ clickable: true }}
autoplay={{ delay: 4000, disableOnInteraction: false }}
loop
spaceBetween={16}
spaceBetween={20}
breakpoints={{
0: { slidesPerView: 1 },
640: { slidesPerView: 2 },
......@@ -183,8 +118,7 @@ const Home = () => {
<SwiperSlide key={news.id}>
<a href={`/tin-tuc/${news.id}`} className='block bg-white shadow-md overflow-hidden relative'>
<img
// src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
src={`${news.thumbnail}`}
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title}
className='w-full h-48 sm:h-56 md:h-64 object-cover'
/>
......@@ -234,7 +168,7 @@ const Home = () => {
>
Tất cả
</button>
{/* {categoryData?.responseData.rows.slice(0, 3).map((category) => (
{categoryData?.responseData.rows.slice(0, 3).map((category) => (
<button
key={category.id}
className={`px-4 py-1 rounded-md border ${category.name === tab ? 'border-blue-700 bg-blue-50' : 'border-gray-300 bg-white'}`}
......@@ -242,7 +176,7 @@ const Home = () => {
>
{category.name}
</button>
))} */}
))}
</div>
{/* News list */}
......@@ -250,17 +184,6 @@ const Home = () => {
{data?.responseData.rows.slice(0, 5).map((news) => (
<NewsContent key={news.id} news={news} />
))}
{/* <div className='w-full flex justify-center mt-4'>
<AppPagination
page={Math.floor(currentIndex / Math.max(slidesPerView, 1)) + 1}
count={Math.ceil((data?.responseData.rows?.length ?? 0) / Math.max(slidesPerView, 1))}
onChange={(_event, value) => {
const toIndex = (value - 1) * Math.max(slidesPerView, 1)
swiperRef.current?.slideTo(toIndex)
}}
/>
</div> */}
</div>
</div>
</div>
......@@ -283,33 +206,92 @@ const Home = () => {
</div>
</div>
{/* Sidebar */}
{/* <div className='lg:flex-1 w-full'>
<div className='bg-white rounded-lg p-4 mb-6 shadow-sm'>
<div className='font-semibold mb-2'>Tìm kiếm</div>
<input
type='text'
placeholder='Tên bài viết...'
value={search}
onChange={(e) => setSearch(e.target.value)}
className='w-full p-2 border border-gray-300 rounded mb-2 focus:outline-none focus:ring-1 focus:ring-blue-500'
/>
<div className='flex gap-2'>
<button
onClick={() => setSubmitSearch(search)}
className='flex-1 bg-[#0056b3] text-white rounded p-2 font-semibold hover:bg-[#004999] transition'
>
Tìm kiếm
</button>
<button
onClick={() => setSearch('')}
className='flex-1 bg-gray-100 text-gray-700 rounded p-2 font-semibold hover:bg-gray-200 transition'
>
Bỏ tìm
</button>
{/* news and quick links section */}
<div className='flex flex-row gap-5'>
<div className='w-[67%]'>
<div>
<div className='flex justify-between items-center'>
<a href='#' className='text-[20px] font-bold uppercase text-blue-900'>
Sự kiện sắp diễn ra
</a>
<a href='#' className='text-blue-900'>{'>>'}</a>
</div>
<hr className=' border-blue-900' />
</div>
<div className='flex flex-row justify-center gap-5 pt-5'>
{/* special news section */}
<div className='bg-gray-500 w-[50%] flex items-center justify-center rounded-lg'>
<p className='text-white'>khung tin tức vip</p>
</div>
{/* news list section */}
<div className='w-[50%]'>
{/* News list */}
<div className='pb-5 overflow-hidden'>
{data?.responseData.rows.slice(0, 5).map((news) => (
<NewsContent key={news.id} news={news} />
))}
</div>
</div>
</div>
</div>
{/* quick links section */}
<div className='w-[33%]'>
<div>
<div className='flex justify-between items-center'>
<a href='#' className='text-[20px] font-bold uppercase text-blue-900'>Lịch sự kiện</a>
<a href='#' className='text-blue-900'>{'>>'}</a>
</div>
<hr className=' border-blue-900' />
</div>
<div className='pt-5'>
<p>🔗 Cẩm nang Hướng dẫn đầu tư kinh doanh tại Việt Nam</p>
<p>🔗 Doanh nghiệp kiến nghị về chính sách và pháp luật</p>
</div>
</div>
</div>
{/* news and quick links section */}
<div className='flex flex-row gap-5'>
{/* Cơ hội kinh doanh */}
<div className='flex flex-col w-[33%]'>
<div className='flex flex-row gap-5'>
<div className='flex justify-between items-center w-full'>
<a href='#' className='text-[20px] font-bold uppercase text-blue-900'>
Cơ hội kinh doanh
</a>
<a href='#' className='text-blue-900'>{'>>'}</a>
</div>
</div>
<hr className=' border-blue-900' />
<div>
{data?.responseData.rows.slice(0, 5).map((news) => (
<NewsContent key={news.id} news={news} />
))}
</div>
</div>
{/* Chính sách & pháp luật */}
<div className='flex flex-col w-[33%]'>
<div className='flex flex-row gap-5'>
<div className='flex justify-between items-center w-full'>
<a href='#' className='text-[20px] font-bold uppercase text-blue-900'>
Chính sách & pháp luật
</a>
<a href='#' className='text-blue-900'>{'>>'}</a>
</div>
</div>
<hr className=' border-blue-900' />
<div>
{data?.responseData.rows.slice(0, 5).map((news) => (
<NewsContent key={news.id} news={news} />
))}
</div>
</div>
</div>
</div> */}
</div>
</>
)
......
.app-editor-container {
min-height: unset !important;
height: auto !important;
border: none !important;
border-radius: 0px !important;
background-color: transparent !important;
line-height: 1.5;
.jodit-wysiwyg {
padding: 0 !important;
overflow-x: visible !important;
}
}
\ No newline at end of file
import { FC, JSX } from 'react';
import htmlParse, { DOMNode, Element, Text } from 'html-react-parser';
import { AppEditorContentProps } from './AppEditorContent.type';
import './AppEditorContent.css';
const AppEditorContent: FC<AppEditorContentProps> = ({ value = '', className = '' }) => {
const transform = (node: DOMNode): JSX.Element | null => {
if (node instanceof Element && node.tagName === 'strong') {
return (
<strong className="custom-strong">
{node.children && Array.isArray(node.children)
? node.children.map((child, index) => {
if (typeof child === 'string') {
return child;
} else if (child instanceof Text) {
return child.data;
}
return null;
})
: null}
</strong>
);
}
return null;
};
return (
<div className="jodit-container app-editor-container">
<div className={`jodit-wysiwyg ${className}`}>{htmlParse(value, { replace: transform })}</div>
</div>
);
};
export default AppEditorContent;
// Props
interface AppEditorContentProps {
value: string
className?: string
}
export type { AppEditorContentProps }
import AppEditorContent from './AppEditorContent'
export default AppEditorContent
......@@ -3,7 +3,7 @@
@theme inline {
/* Fonts */
--font-reddit-sans: RedditSans, sans-serif;
--default-font-family: 'SF Pro Display', -apple-system;
--default-font-family: 'Roboto', sans-serif;
/* Border Radius */
--radius-lg: hsl(var(--radius));
--radius-md: calc(var(--radius) - 2px);
......
......@@ -3,7 +3,7 @@
@apply mr-auto ml-auto px-4;
@media (width >=80rem) {
@apply px-20;
@apply px-40;
}
}
......@@ -12,7 +12,7 @@
}
.sun-editor-editable {
font-family: 'Roboto', sans-serif;
font-family: 'Be Vietnam Pro', sans-serif;
font-size: 16px;
background-color: transparent;
}
......
......@@ -19,7 +19,17 @@
}
],
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@hooks/*": ["./src/hooks/*"],
"@api/*": ["./src/api/*"],
"@lib/*": ["./lib/*"],
"@public/*": ["./public/*"],
"@store/*": ["./src/store/*"],
"@links/*": ["./src/links/*"],
"@app/*": ["./src/app/(main)/*"]
}
},
"include": [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment