update
This commit is contained in:
@@ -5,7 +5,8 @@ import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
import 'swiper/css/pagination';
|
||||
import '@styles/globals.css';
|
||||
import Header from '@components/layout/Header';
|
||||
import Header from '@/components/layout/other/Header';
|
||||
import Footer from '@/components/layout/other/Footer';
|
||||
|
||||
import PreLoader from '@components/common/PreLoader';
|
||||
|
||||
@@ -29,6 +30,7 @@ export default function RootLayout({
|
||||
<>
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</>
|
||||
)}
|
||||
</body>
|
||||
|
||||
53
src/app/[...slug]/page.tsx
Normal file
53
src/app/[...slug]/page.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { useParams } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { findCategoryBySlug } from '@/lib/category';
|
||||
|
||||
const CategoryPage: React.FC = () => {
|
||||
const params = useParams();
|
||||
const slugArray = params?.slug as string[];
|
||||
|
||||
// tìm danh mục hiện tại theo slug
|
||||
const category: Category | null = findCategoryBySlug(slugArray, categories);
|
||||
|
||||
if (!category) {
|
||||
return (
|
||||
<div className="container mx-auto py-8">
|
||||
<h1 className="text-2xl font-bold">Không tìm thấy danh mục</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-8">
|
||||
{/* Breadcrumb */}
|
||||
<nav className="mb-4 text-sm text-gray-600">
|
||||
{slugArray.map((slug, idx) => (
|
||||
<span key={idx}>
|
||||
<Link href={`/${slugArray.slice(0, idx + 1).join('/')}`}>{slug}</Link>
|
||||
{idx < slugArray.length - 1 && ' / '}
|
||||
</span>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
{/* Tiêu đề danh mục */}
|
||||
<h1 className="mb-6 text-2xl font-bold">{category.name}</h1>
|
||||
|
||||
{/* Nếu có danh mục con thì hiển thị */}
|
||||
{category.children && category.children.length > 0 ? (
|
||||
<div className="grid grid-cols-2 gap-6 md:grid-cols-4">
|
||||
{category.children.map((child) => (
|
||||
<div key={child.id} className="rounded-lg border p-4">
|
||||
<Link href={`/${slugArray.join('/')}/${child.slug}`}>{child.name}</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p>Danh mục này chưa có danh mục con hoặc sản phẩm.</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryPage;
|
||||
File diff suppressed because it is too large
Load Diff
56
src/components/layout/home/BoxArticle/ItemArticle/index.tsx
Normal file
56
src/components/layout/home/BoxArticle/ItemArticle/index.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { Article } from '@/types';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
|
||||
type ItemArticleProps = {
|
||||
item: Article;
|
||||
};
|
||||
|
||||
const ItemArticle: React.FC<ItemArticleProps> = ({ item }) => {
|
||||
// chọn link: nếu có external_url thì dùng, ngược lại dùng url
|
||||
const linkHref = item.external_url && item.external_url !== '' ? item.external_url : item.url;
|
||||
|
||||
// chọn ảnh: nếu có original thì dùng, ngược lại ảnh mặc định
|
||||
const imageSrc =
|
||||
item.image?.original && item.image.original !== ''
|
||||
? item.image.original
|
||||
: '/static/assets/nguyencong_2023/images/not-image.png';
|
||||
|
||||
// chọn thời gian: ưu tiên article_time, fallback createDate
|
||||
const timeDisplay =
|
||||
item.article_time && item.article_time !== '' ? item.article_time : item.createDate;
|
||||
|
||||
return (
|
||||
<div className="item-article flex gap-3">
|
||||
<Link href={linkHref} className="img-article boder-radius-10 position-relative">
|
||||
<Image
|
||||
className="boder-radius-10"
|
||||
src={imageSrc}
|
||||
width={265}
|
||||
height={180}
|
||||
alt={item.title}
|
||||
/>
|
||||
{/* icon video nếu cần */}
|
||||
<i className="sprite sprite-icon-play-video-detail icon-video-feature incon-play-youtube"></i>
|
||||
<i className="sprite sprite-play-youtube incon-play-youtube"></i>
|
||||
</Link>
|
||||
|
||||
<div className="content-article content-article-item flex flex-1 flex-col">
|
||||
<Link href={linkHref} className="title-article">
|
||||
<h3 className="line-clamp-2 font-[400]">{item.title}</h3>
|
||||
</Link>
|
||||
|
||||
<p className="time-article flex items-center gap-4">
|
||||
<i className="sprite sprite-clock-item-article"></i>
|
||||
<span>{timeDisplay}</span>
|
||||
</p>
|
||||
|
||||
<p className="descreption-article line-clamp-2">{item.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemArticle;
|
||||
282
src/components/layout/home/BoxArticle/dataArticle.ts
Normal file
282
src/components/layout/home/BoxArticle/dataArticle.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
import { ListArticle } from '@/types';
|
||||
|
||||
export const dataArticle: ListArticle = [
|
||||
{
|
||||
id: 4200,
|
||||
title: 'Top PC 15 triệu tối ưu hiệu năng nhất trong mùa bão giá RAM',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Chỉ với 15 triệu đồng, người dùng đã có thể sở hữu một bộ máy tính tối ưu hiệu năng cho nhu cầu học tập, làm việc và giải trí. Nguyễn Công PC mang đến nhiều cấu hình cân bằng giữa sức mạnh và giá trị, đảm bảo hoạt động mượt mà trong mọi tác vụ. Đây là lựa chọn lý tưởng cho những ai muốn đầu tư một hệ thống mạnh mẽ với chi phí hợp lý.',
|
||||
createDate: '10-12-2025, 5:44 pm',
|
||||
createBy: '75',
|
||||
lastUpdate: '22-12-2025, 5:03 pm',
|
||||
lastUpdateBy: '75',
|
||||
visit: 157,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Diệu Linh',
|
||||
counter: 1,
|
||||
url: '/top-pc-15-trieu-toi-uu-hieu-nang-cho-gaming-va-lam-viec',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4200-chi-voi-15-trieu-ban-da-co-ngay-mot-bo-pc-chat-luong-dam-bao-hieu-nang1.jpg',
|
||||
original:
|
||||
'https://nguyencongpc.vn/media/news/4200-chi-voi-15-trieu-ban-da-co-ngay-mot-bo-pc-chat-luong-dam-bao-hieu-nang1.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4195,
|
||||
title: 'Cách nhận chứng chỉ Google Gemini Educator làm đẹp CV của bạn ngay hôm nay!',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Chứng chỉ Google Gemini Educator giúp bạn khẳng định kỹ năng sử dụng AI trong giáo dục và công nghệ. Việc sở hữu chứng chỉ này không chỉ tăng tính chuyên nghiệp cho CV mà còn mở ra nhiều cơ hội nghề nghiệp mới. Bài viết sẽ hướng dẫn bạn cách đăng ký, học và nhận chứng chỉ nhanh chóng nhất.',
|
||||
createDate: '08-12-2025, 11:26 am',
|
||||
createBy: '75',
|
||||
lastUpdate: '08-12-2025, 12:07 pm',
|
||||
lastUpdateBy: '75',
|
||||
visit: 3067,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Diệu Linh',
|
||||
counter: 2,
|
||||
url: '/cach-nhan-chung-chi-google-gemini-educator-mien-phi-nam-2025',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4195-cach-nhan-chung-chi-google-gemini-educator-mien-phi-nam-20251.jpg',
|
||||
original:
|
||||
'https://nguyencongpc.vn/media/news/4195-cach-nhan-chung-chi-google-gemini-educator-mien-phi-nam-20251.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2722,
|
||||
title: 'Top 100+ cấu hình PC Gaming giá tốt nhất năm 2025',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Trong bài viết, Nguyễn Công PC đã tổng hợp hơn 100 cấu hình PC gaming tối ưu nhất năm 2025, phù hợp với nhiều mức ngân sách từ phổ thông đến cao cấp. Mỗi cấu hình cân bằng giữa hiệu năng và giá thành, đáp ứng nhu cầu chơi game mượt mà, đồ họa sắc nét và khả năng nâng cấp linh hoạt trong tương lai.\r\n\r\n\r\n',
|
||||
createDate: '16-01-2024, 10:52 am',
|
||||
createBy: '50',
|
||||
lastUpdate: '06-12-2025, 4:30 pm',
|
||||
lastUpdateBy: '53',
|
||||
visit: 36705,
|
||||
is_featured: 0,
|
||||
article_time: '07-11-2025, 9:00 am',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 3,
|
||||
url: '/top-100-cau-hinh-pc-gaming-gia-tot-nhat',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-2722-pc-gaming.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/2722-pc-gaming.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2718,
|
||||
title: 'Top 50 cấu hình PC đồ họa giá tốt nhất hiện nay',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Với đà phát triển của truyền thông, công nghệ số, kỹ thuật số,... Cần rất nhiều công cụ để hỗ trợ cho công việc, làm việc của bạn. Sức mạnh ngành chuyền thông nói riêng cũng như công nghệ nói chung càng ngày càng phát triển mạnh mẽ, vượt trội, chính vì để hỗ trợ cho việc xây dựng các bộ (PC Render) làm việc cũng như giải trí đang là nhu cầu lơn trên thị trường hiện nay.\r\n\r\n',
|
||||
createDate: '15-01-2024, 1:39 pm',
|
||||
createBy: '50',
|
||||
lastUpdate: '24-11-2025, 10:23 am',
|
||||
lastUpdateBy: '74',
|
||||
visit: 24390,
|
||||
is_featured: 0,
|
||||
article_time: '05-11-2025, 10:00 am',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Anh Tuấn',
|
||||
counter: 4,
|
||||
url: '/top-cau-hinh-do-hoa-gia-tot-nhat',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-2718-pc-do-hoa.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/2718-pc-do-hoa.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4203,
|
||||
title:
|
||||
'NGUYỄN CÔNG PC - NHÀ TÀI TRỢ KIM CƯƠNG CHÀO TÂN SINH VIÊN ĐẠI HỌC KIẾN TRÚC HÀ NỘI 2025',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Máy tính Nguyễn Công tiếp tục khẳng định vị thế là đối tác tin cậy hàng đầu khi vinh dự trở thành Nhà Tài Trợ Kim Cương liên tục trong 7 năm (2019 – 2025) cho chương trình Chào Tân Sinh Viên của Đại học Kiến Trúc Hà Nội (HAU).',
|
||||
createDate: '15-12-2025, 10:09 am',
|
||||
createBy: '74',
|
||||
lastUpdate: '15-12-2025, 4:00 pm',
|
||||
lastUpdateBy: '53',
|
||||
visit: 51,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 5,
|
||||
url: '/nguyen-cong-pc-nha-tai-tro-kim-cuong-chao-tan-sinh-vien-dai-hoc-kien-truc-ha-noi-2025',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4203-nguyen-cong-pc-nha-tai-tro-kim-cuong-chao-tan-sinh-vien-dai-hoc-kien-truc-ha-noi-2025-06.jpg',
|
||||
original:
|
||||
'https://nguyencongpc.vn/media/news/4203-nguyen-cong-pc-nha-tai-tro-kim-cuong-chao-tan-sinh-vien-dai-hoc-kien-truc-ha-noi-2025-06.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4199,
|
||||
title: 'Đại chiến đồ họa: Canva và Photoshop: Ai là "Vua" thiết kế hiện nay',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Canva và Photoshop đang là hai nền tảng thiết kế phổ biến nhất, mỗi công cụ sở hữu những ưu – nhược điểm riêng. Trong khi Canva mang đến sự tiện lợi và tốc độ, Photoshop lại vượt trội về sức mạnh xử lý và khả năng sáng tạo chuyên sâu. Cuộc đối đầu này giúp người dùng lựa chọn đúng công cụ phù hợp với nhu cầu thiết kế của mình.',
|
||||
createDate: '09-12-2025, 6:56 pm',
|
||||
createBy: '75',
|
||||
lastUpdate: '11-12-2025, 2:21 pm',
|
||||
lastUpdateBy: '75',
|
||||
visit: 72,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Diệu Linh',
|
||||
counter: 6,
|
||||
url: '/dai-chien-do-hoa-canva-va-photoshop-ai-la-vua-thiet-ke-hien-nay',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4199-dai-chien-do-hoa-canva-va-photoshop-ai-la-vua-thiet-ke-hien-nay5.jpg',
|
||||
original:
|
||||
'https://nguyencongpc.vn/media/news/4199-dai-chien-do-hoa-canva-va-photoshop-ai-la-vua-thiet-ke-hien-nay5.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4197,
|
||||
title: 'Người dùng nên nâng cấp Windows 11 hiện đại hay tiếp tục sử dụng Windows 10 ổn định? ',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '09-12-2025, 11:03 am',
|
||||
createBy: '75',
|
||||
lastUpdate: '09-12-2025, 5:23 pm',
|
||||
lastUpdateBy: '75',
|
||||
visit: 92,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Diệu Linh',
|
||||
counter: 7,
|
||||
url: '/nguoi-dung-nen-nang-cap-windows-11-hien-dai-hay-tiep-tuc-su-dung-windows-10-on-dinh',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4197-nguoi-dung-nen-nang-cap-windows-11-hien-dai-hay-tiep-tuc-su-dung-windows-10-on-dinh2.jpg',
|
||||
original:
|
||||
'https://nguyencongpc.vn/media/news/4197-nguoi-dung-nen-nang-cap-windows-11-hien-dai-hay-tiep-tuc-su-dung-windows-10-on-dinh2.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3954,
|
||||
title: 'Hướng Dẫn Các Bước Cài Đặt Plugin Sketch Up Nhanh Chóng, Đơn Giản Nhất',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Theo dõi các hướng dẫn chi tiết cách cài đặt plugin cho phần mềm SketchUp cùng Nguyễn Công PC để giúp người dùng mở rộng chức năng, tiết kiệm thời gian thiết kế và nâng cao hiệu suất làm việc. ',
|
||||
createDate: '21-07-2025, 10:39 am',
|
||||
createBy: '75',
|
||||
lastUpdate: '22-07-2025, 9:06 am',
|
||||
lastUpdateBy: '75',
|
||||
visit: 6532,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Diệu Linh',
|
||||
counter: 8,
|
||||
url: '/huong-dan-cac-buoc-cai-dat-plugin-sketch-up-nhanh-chong-don-gian-nhat',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-3954-huong-dan-cac-buoc-cai-dat-plugin-sketch-up-nhanh-chong-don-gian-nhat10.jpg',
|
||||
original:
|
||||
'https://nguyencongpc.vn/media/news/3954-huong-dan-cac-buoc-cai-dat-plugin-sketch-up-nhanh-chong-don-gian-nhat10.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4198,
|
||||
title: 'Bạn đã biết cách tạo ảnh AI cực hot với công cụ Nano Banana từ Gemini chưa?',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Nano Banana là công cụ AI mới giúp người dùng tạo ảnh nhanh, đẹp và chuẩn ý tưởng chỉ từ vài dòng mô tả. Tại Nguyễn Công PC, bạn có thể dễ dàng trải nghiệm Nano Banana với giao diện đơn giản, tốc độ xử lý mạnh mẽ. Công cụ này phù hợp cho designer, marketer, người làm nội dung và bất kỳ ai muốn tạo hình ảnh chuyên nghiệp.',
|
||||
createDate: '09-12-2025, 4:59 pm',
|
||||
createBy: '75',
|
||||
lastUpdate: '09-12-2025, 6:48 pm',
|
||||
lastUpdateBy: '75',
|
||||
visit: 137,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Diệu Linh',
|
||||
counter: 9,
|
||||
url: '/ban-da-biet-cach-tao-anh-ai-cuc-hot-voi-cong-cu-nano-banana-tu-gemini-chua',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4198-ban-da-biet-cach-tao-anh-ai-cuc-hot-voi-cong-cu-nano-banana-tu-gemini-chua7.jpg',
|
||||
original:
|
||||
'https://nguyencongpc.vn/media/news/4198-ban-da-biet-cach-tao-anh-ai-cuc-hot-voi-cong-cu-nano-banana-tu-gemini-chua7.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4118,
|
||||
title: 'Windows 10 chính thức ngừng hoạt động, người dùng cần làm gì để giữ máy tính an toàn?',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary:
|
||||
'Microsoft đã chính thức ngừng hỗ trợ Windows 10, đồng nghĩa với việc hệ điều hành này không còn nhận được các bản cập nhật bảo mật. Người dùng tiếp tục sử dụng có nguy cơ cao bị tấn công mạng hoặc gặp lỗi nghiêm trọng. Vì vậy, việc nâng cấp hoặc áp dụng các biện pháp bảo vệ bổ sung là điều cấp thiết để giữ máy tính an toàn.',
|
||||
createDate: '14-10-2025, 5:37 pm',
|
||||
createBy: '75',
|
||||
lastUpdate: '15-10-2025, 10:14 am',
|
||||
lastUpdateBy: '75',
|
||||
visit: 325,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: '',
|
||||
author: 'Diệu Linh',
|
||||
counter: 10,
|
||||
url: '/windows-10-chinh-thuc-ngung-hoat-dong-nguoi-dung-can-lam-gi-de-giu-may-tinh-an-toan',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4118-huong-dan-reset-windows-10-ve-trang-thai-moi-cai-dat14.jpg',
|
||||
original:
|
||||
'https://nguyencongpc.vn/media/news/4118-huong-dan-reset-windows-10-ve-trang-thai-moi-cai-dat14.jpg',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,26 @@
|
||||
import { FaCaretRight } from 'react-icons/fa';
|
||||
import { dataArticle } from './dataArticle';
|
||||
import ItemArticle from './ItemArticle';
|
||||
|
||||
const BoxArticle: React.FC = () => {
|
||||
return (
|
||||
<div className="box-article-group boder-radius-10">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="title-box">
|
||||
<h2 className="title-box font-[600]">Tin tức công nghệ</h2>
|
||||
</div>
|
||||
<a href="/tin-cong-nghe" className="btn-article-group flex items-center gap-1">
|
||||
<span>Xem tất cả</span>
|
||||
<FaCaretRight size={16} />
|
||||
</a>
|
||||
</div>
|
||||
<div className="list-article-group flex items-center gap-10">
|
||||
{dataArticle.slice(0, 4).map((item, index) => (
|
||||
<ItemArticle item={item} key={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoxArticle;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
'use client';
|
||||
import React, { useState } from 'react';
|
||||
import { Article } from '@/types';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { FaTimes } from 'react-icons/fa';
|
||||
|
||||
type ItemArticleProps = {
|
||||
item: Article;
|
||||
};
|
||||
|
||||
const ItemArticleVideo: React.FC<ItemArticleProps> = ({ item }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
// chọn link: nếu có external_url thì dùng, ngược lại dùng url
|
||||
const linkHref = item.external_url && item.external_url !== '' ? item.external_url : item.url;
|
||||
|
||||
// chọn ảnh: nếu có original thì dùng, ngược lại ảnh mặc định
|
||||
const imageSrc =
|
||||
item.image?.original && item.image.original !== ''
|
||||
? item.image.original
|
||||
: '/static/assets/nguyencong_2023/images/not-image.png';
|
||||
|
||||
// chọn thời gian: ưu tiên article_time, fallback createDate
|
||||
const timeDisplay =
|
||||
item.article_time && item.article_time !== '' ? item.article_time : item.createDate;
|
||||
|
||||
const getYoutubeEmbedUrl = (url: string): string => {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
// nếu là link youtube dạng watch?v=...
|
||||
if (urlObj.hostname.includes('youtube.com')) {
|
||||
const videoId = urlObj.searchParams.get('v');
|
||||
if (videoId) {
|
||||
return `https://www.youtube.com/embed/${videoId}?autoplay=1`;
|
||||
}
|
||||
}
|
||||
// nếu là link youtu.be/xxxx
|
||||
if (urlObj.hostname.includes('youtu.be')) {
|
||||
const videoId = urlObj.pathname.replace('/', '');
|
||||
if (videoId) {
|
||||
return `https://www.youtube.com/embed/${videoId}?autoplay=1`;
|
||||
}
|
||||
}
|
||||
// fallback: trả về chính url
|
||||
return url;
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="item-article flex gap-3">
|
||||
<Link
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setOpen(true);
|
||||
}}
|
||||
href="javascript:void(0)"
|
||||
className="img-article boder-radius-10 relative"
|
||||
>
|
||||
<Image
|
||||
className="boder-radius-10"
|
||||
src={imageSrc}
|
||||
width={265}
|
||||
height={180}
|
||||
alt={item.title}
|
||||
/>
|
||||
{/* icon video nếu cần */}
|
||||
<i className="sprite sprite-icon-play-video-detail icon-video-feature incon-play-youtube"></i>
|
||||
<i className="sprite sprite-play-youtube incon-play-youtube"></i>
|
||||
</Link>
|
||||
|
||||
<div className="content-article content-article-item flex flex-1 flex-col">
|
||||
<Link href={linkHref} className="title-article">
|
||||
<h3 className="line-clamp-2 font-[400]">{item.title}</h3>
|
||||
</Link>
|
||||
|
||||
<p className="time-article flex items-center gap-4">
|
||||
<i className="sprite sprite-clock-item-article"></i>
|
||||
<span>{timeDisplay}</span>
|
||||
</p>
|
||||
|
||||
<p className="descreption-article line-clamp-2">{item.summary}</p>
|
||||
</div>
|
||||
</div>
|
||||
{open && (
|
||||
<dialog id="video_modal" className="modal modal-open">
|
||||
<div className="modal-box w-11/12 max-w-3xl">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-bold">{item.title}</h3>
|
||||
<form method="dialog">
|
||||
<button
|
||||
className="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<FaTimes size={16} />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div className="video-wrapper mt-4">
|
||||
<iframe
|
||||
width="100%"
|
||||
height="400"
|
||||
src={getYoutubeEmbedUrl(item.external_url)}
|
||||
title={item.title}
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<form method="dialog" className="modal-backdrop">
|
||||
<button onClick={() => setOpen(false)}>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemArticleVideo;
|
||||
265
src/components/layout/home/BoxArticleVideo/dataArticle.ts
Normal file
265
src/components/layout/home/BoxArticleVideo/dataArticle.ts
Normal file
@@ -0,0 +1,265 @@
|
||||
import { ListArticle } from '@/types';
|
||||
|
||||
export const dataArticle: ListArticle = [
|
||||
{
|
||||
id: 4185,
|
||||
title: 'Chuyện RAM ĐẮT - Góc nhìn mà anh em chưa thấy',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '28-11-2025, 11:51 am',
|
||||
createBy: '53',
|
||||
lastUpdate: '28-11-2025, 11:51 am',
|
||||
lastUpdateBy: '53',
|
||||
visit: 8,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=rH9Aq_2yZEc',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 1,
|
||||
url: '/chuyen-ram-dat-goc-nhin-ma-anh-em-chua-thay',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4185---efwegweg.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/4185---efwegweg.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4184,
|
||||
title: 'Build PC GAMING tầm giá 20 Triệu trong mùa BÃO RAM - Cũng KHOAI phết',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '28-11-2025, 11:49 am',
|
||||
createBy: '53',
|
||||
lastUpdate: '28-11-2025, 11:49 am',
|
||||
lastUpdateBy: '53',
|
||||
visit: 7,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=c-JQPclPXmg',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 2,
|
||||
url: '/build-pc-gaming-tam-gia-20-trieu-trong-mua-bao-ram-cung-khoai-phet',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4184-maxresdefault.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/4184-maxresdefault.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4171,
|
||||
title: 'Điểm dừng cho PC GAMING - Nhiều tiền thì cũng PHÍ',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '10-11-2025, 2:41 pm',
|
||||
createBy: '53',
|
||||
lastUpdate: '10-11-2025, 2:41 pm',
|
||||
lastUpdateBy: '53',
|
||||
visit: 8,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=xUpMSpaa_H0',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 3,
|
||||
url: '/diem-dung-cho-pc-gaming-nhieu-tien-thi-cung-phi',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4171-dvsdfgrsdf.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/4171-dvsdfgrsdf.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3683,
|
||||
title: 'Bộ PC KHỦNG BỐ tới đâu mà đích thân Chủ Tịch MaxHome phải tự đi build ???',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '12-03-2025, 9:59 am',
|
||||
createBy: '53',
|
||||
lastUpdate: '12-03-2025, 9:59 am',
|
||||
lastUpdateBy: '53',
|
||||
visit: 64,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=Ir9zlznA9ms',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 4,
|
||||
url: '/bo-pc-khung-bo-toi-dau-ma-dich-than-chu-tich-maxhome-phai-tu-di-build',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-3683-tymyumyj.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/3683-tymyumyj.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4107,
|
||||
title: 'Intel ĐẮT quá nên BUILD PC với AMD chỉ 17 TRIỆU mà chiến ALL GAME',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '04-10-2025, 5:39 pm',
|
||||
createBy: '53',
|
||||
lastUpdate: '04-10-2025, 5:40 pm',
|
||||
lastUpdateBy: '53',
|
||||
visit: 7,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=DBuud_Lwt6w',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 5,
|
||||
url: '/intel-dat-qua-nen-build-pc-voi-amd-chi-17-trieu-ma-chien-all-game',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4107---gherthert.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/4107---gherthert.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4079,
|
||||
title: 'Tôi thấy chán PC HIỆU NĂNG/GIÁ THÀNH sau khi thấy bộ PC này',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '20-09-2025, 10:42 am',
|
||||
createBy: '53',
|
||||
lastUpdate: '20-09-2025, 10:42 am',
|
||||
lastUpdateBy: '53',
|
||||
visit: 28,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=ceT_nSB1JCA',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 6,
|
||||
url: '/toi-thay-chan-pc-hieu-nang-gia-thanh-sau-khi-thay-bo-pc-nay',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4079-ewgergherth.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/4079-ewgergherth.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4004,
|
||||
title: 'Sinh Viên ĐỒ HOẠ lên cấu hình PC nào dưới 20 TRIỆU trong 2025',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '15-08-2025, 2:04 pm',
|
||||
createBy: '53',
|
||||
lastUpdate: '15-08-2025, 2:04 pm',
|
||||
lastUpdateBy: '53',
|
||||
visit: 44,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=k6rIzVmU9bA',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 7,
|
||||
url: '/sinh-vien-do-hoa-len-cau-hinh-pc-nao-duoi-20-trieu-trong-2025',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-4004-dhtrhj.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/4004-dhtrhj.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3951,
|
||||
title: 'Cấu hình PC 10 Triệu cả Màn hình - Test GAME AAA vẫn OK',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '19-07-2025, 4:57 pm',
|
||||
createBy: '53',
|
||||
lastUpdate: '19-07-2025, 4:57 pm',
|
||||
lastUpdateBy: '53',
|
||||
visit: 43,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=QCQwdLcosQc',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 8,
|
||||
url: '/cau-hinh-pc-10-trieu-ca-man-hinh-test-game-aaa-van-ok',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-3951-dfbeadbeat.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/3951-dfbeadbeat.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3950,
|
||||
title:
|
||||
'Tại sao mình ít làm video CORE ULTRA - Có đáng không 40 Triệu cho Ultra 7 265K + RTX 5070',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '19-07-2025, 4:56 pm',
|
||||
createBy: '53',
|
||||
lastUpdate: '19-07-2025, 5:00 pm',
|
||||
lastUpdateBy: '53',
|
||||
visit: 51,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=Y6PBwYe5My0',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 9,
|
||||
url: '/tai-sao-minh-it-lam-video-core-ultra-co-dang-khong-40-trieu-cho-ultra-7-265k-rtx-5070',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-3950-maxresdefault.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/3950-maxresdefault.jpg',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3949,
|
||||
title: 'Cấu hình PC PHỔ BIẾN nhất THẾ GIỚI gaming - Cũng rẻ phết',
|
||||
extend: {
|
||||
pixel_code: '',
|
||||
},
|
||||
summary: '',
|
||||
createDate: '19-07-2025, 4:54 pm',
|
||||
createBy: '53',
|
||||
lastUpdate: '19-07-2025, 4:54 pm',
|
||||
lastUpdateBy: '53',
|
||||
visit: 28,
|
||||
is_featured: 0,
|
||||
article_time: '',
|
||||
review_rate: 0,
|
||||
review_count: 0,
|
||||
video_code: '',
|
||||
external_url: 'https://www.youtube.com/watch?v=KXfA10koGDk',
|
||||
author: 'Trần Mạnh',
|
||||
counter: 10,
|
||||
url: '/cau-hinh-pc-pho-bien-nhat-the-gioi-gaming-cung-re-phet',
|
||||
image: {
|
||||
thum: 'https://nguyencongpc.vn/media/news/120-3949----herthrtn.jpg',
|
||||
original: 'https://nguyencongpc.vn/media/news/3949----herthrtn.jpg',
|
||||
},
|
||||
},
|
||||
];
|
||||
31
src/components/layout/home/BoxArticleVideo/index.tsx
Normal file
31
src/components/layout/home/BoxArticleVideo/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { FaCaretRight } from 'react-icons/fa';
|
||||
import Link from 'next/link';
|
||||
import { dataArticle } from './dataArticle';
|
||||
import ItemArticleVideo from './ItemArticleVideo';
|
||||
const BoxArticleVideo: React.FC = () => {
|
||||
return (
|
||||
<div className="box-videos-group box-article-group boder-radius-10 relative">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="title-box">
|
||||
<h2 className="title-box font-[600]">Youtube Channel</h2>
|
||||
</div>
|
||||
<Link
|
||||
href="https://www.youtube.com/NguyenCongPC"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
className="btn-article-group flex items-center gap-2"
|
||||
>
|
||||
<span>Xem tất cả </span>
|
||||
<FaCaretRight size={16} />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="list-videos-group list-article-group flex items-center gap-10">
|
||||
{dataArticle.slice(0, 4).map((item, index) => (
|
||||
<ItemArticleVideo item={item} key={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoxArticleVideo;
|
||||
@@ -1,83 +0,0 @@
|
||||
import React from 'react';
|
||||
import Tippy from '@tippyjs/react';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { Product } from '@/types/TypeListProduct';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
type ProductItemProps = {
|
||||
item: Product;
|
||||
};
|
||||
|
||||
const formatCurrency = (value: number | string) => {
|
||||
const num = typeof value === 'string' ? parseInt(value) : value;
|
||||
return num.toLocaleString('vi-VN');
|
||||
};
|
||||
|
||||
const ItemProduct: React.FC<ProductItemProps> = ({ item }) => {
|
||||
const offers = item.specialOffer?.all ?? [];
|
||||
|
||||
return (
|
||||
<div className="product-item js-p-item">
|
||||
<a href={item.productUrl} className="product-image relative">
|
||||
{item.productImage.large ? (
|
||||
<Image src={item.productImage.large} width="203" height="203" alt={item.productName} />
|
||||
) : (
|
||||
<Image src="https://nguyencongpc.vn/static/assets/nguyencong_2023/images/not-image.png" alt={item.productName} />
|
||||
)}
|
||||
|
||||
<span className="p-type-holder">
|
||||
{item.productType.isHot === 1 && (
|
||||
<i className="p-icon-type p-icon-hot"></i>
|
||||
)}
|
||||
{item.productType.isNew === 1 && (
|
||||
<i className="p-icon-type p-icon-new"></i>
|
||||
)}
|
||||
</span>
|
||||
|
||||
<span className="p-type-holder p-type-holder-2">
|
||||
{item.productType.isBestSale === 1 && (
|
||||
<i className="p-icon-type p-icon-best-sale"></i>
|
||||
)}
|
||||
</span>
|
||||
</a>
|
||||
<div className="product-info">
|
||||
<Link href={item.productUrl}>
|
||||
<h3 className="product-title line-clamp-3">{item.productName}</h3>
|
||||
</Link>
|
||||
{item.marketPrice > 0 ? (
|
||||
<div className="product-martket-main flex items-center">
|
||||
<p className="product-market-price">
|
||||
{item.marketPrice.toLocaleString()}<u>đ</u>
|
||||
</p>
|
||||
<div className="product-percent-price">-{Math.round(item.price_off)} %</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="product-martket-main flex items-center">
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="product-price-main font-[600]">
|
||||
{item.price > '0'
|
||||
? `${formatCurrency(item.price)}đ`
|
||||
: 'Liên hệ'}
|
||||
</div>
|
||||
{item.specialOffer?.all?.length ? (
|
||||
<div className="product-offer line-clamp-2"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.specialOffer!.all![0].title,
|
||||
}} />
|
||||
) : (
|
||||
<div className="product-offer line-clamp-2"></div>
|
||||
)}
|
||||
|
||||
{item.extend?.buy_count ? (
|
||||
<div style={{ height: 18 }}> <b>Đã bán: </b> <span>{item.extend.buy_count}</span> </div>
|
||||
) : (
|
||||
<div style={{ height: 18, display: 'block' }}> </div> )}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemProduct;
|
||||
84
src/components/layout/home/BoxCategory/ItemProduct/index.tsx
Normal file
84
src/components/layout/home/BoxCategory/ItemProduct/index.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import Tippy from '@tippyjs/react';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { Product } from '@/types';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
type ProductItemProps = {
|
||||
item: Product;
|
||||
};
|
||||
|
||||
const formatCurrency = (value: number | string) => {
|
||||
const num = typeof value === 'string' ? parseInt(value) : value;
|
||||
return num.toLocaleString('vi-VN');
|
||||
};
|
||||
|
||||
const ItemProduct: React.FC<ProductItemProps> = ({ item }) => {
|
||||
const offers = item.specialOffer?.all ?? [];
|
||||
|
||||
return (
|
||||
<div className="product-item js-p-item">
|
||||
<a href={item.productUrl} className="product-image relative">
|
||||
{item.productImage.large ? (
|
||||
<Image src={item.productImage.large} width="203" height="203" alt={item.productName} />
|
||||
) : (
|
||||
<Image
|
||||
src="https://nguyencongpc.vn/static/assets/nguyencong_2023/images/not-image.png"
|
||||
alt={item.productName}
|
||||
/>
|
||||
)}
|
||||
|
||||
<span className="p-type-holder">
|
||||
{item.productType.isHot === 1 && <i className="p-icon-type p-icon-hot"></i>}
|
||||
{item.productType.isNew === 1 && <i className="p-icon-type p-icon-new"></i>}
|
||||
</span>
|
||||
|
||||
<span className="p-type-holder p-type-holder-2">
|
||||
{item.productType.isBestSale === 1 && <i className="p-icon-type p-icon-best-sale"></i>}
|
||||
</span>
|
||||
</a>
|
||||
<div className="product-info">
|
||||
<Link href={item.productUrl}>
|
||||
<h3 className="product-title line-clamp-3">{item.productName}</h3>
|
||||
</Link>
|
||||
{item.marketPrice > 0 ? (
|
||||
<div className="product-martket-main flex items-center">
|
||||
<p className="product-market-price">
|
||||
{item.marketPrice.toLocaleString()}
|
||||
<u>đ</u>
|
||||
</p>
|
||||
<div className="product-percent-price">-{Math.round(item.price_off)} %</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="product-martket-main flex items-center"></div>
|
||||
)}
|
||||
|
||||
<div className="product-price-main font-[600]">
|
||||
{item.price > '0' ? `${formatCurrency(item.price)}đ` : 'Liên hệ'}
|
||||
</div>
|
||||
{item.specialOffer?.all?.length ? (
|
||||
<div
|
||||
className="product-offer line-clamp-2"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: item.specialOffer!.all![0].title,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="product-offer line-clamp-2"></div>
|
||||
)}
|
||||
|
||||
{item.extend?.buy_count ? (
|
||||
<div style={{ height: 18 }}>
|
||||
{' '}
|
||||
<b>Đã bán: </b> <span>{item.extend.buy_count}</span>{' '}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ height: 18, display: 'block' }}> </div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemProduct;
|
||||
@@ -4,53 +4,54 @@ import Link from 'next/link';
|
||||
import { FaCaretDown } from 'react-icons/fa';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Autoplay, Navigation, Pagination } from 'swiper/modules';
|
||||
import ItemProduct from './ItemProduct'
|
||||
import ItemProduct from './ItemProduct';
|
||||
|
||||
import { Category } from '../../../../types/Menu';
|
||||
import { InfoCategory } from '@/types';
|
||||
|
||||
|
||||
import { menuData } from '../../Header/menuData';
|
||||
import {productData} from './productData'
|
||||
import { menuData } from '@/components/layout/other/Header/menuData';
|
||||
import { productData } from './productData';
|
||||
|
||||
const BoxListCategory: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
{menuData[0].product.all_category.map((item,index) => (
|
||||
{menuData[0].product.all_category.map((item, index) => (
|
||||
<div className="box-product-category boder-radius-10" key={index}>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="title">
|
||||
<h2 className="title-box font-[600]">{item.title}</h2>
|
||||
<h2 className="title-box font-[600]">{item.title}</h2>
|
||||
</div>
|
||||
<div className="list-category-child flex items-center justify-end flex-1">
|
||||
|
||||
{item.children.slice(0, 4).map((item2,index2) => (
|
||||
<Link key={index2} href={item2.url} className="title-category">{item2.title}</Link>
|
||||
))}
|
||||
|
||||
<Link href={item.url} className="title-all-category flex items-center gap-2">
|
||||
<span>Xem tất cả </span>
|
||||
<FaCaretDown size={16} /></Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="box-list-item-category swiper-product-category">
|
||||
<Swiper
|
||||
modules={[Autoplay, Navigation, Pagination]}
|
||||
spaceBetween={12}
|
||||
slidesPerView={5}
|
||||
loop={true}
|
||||
navigation={true}
|
||||
>
|
||||
{productData.map((item,index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<ItemProduct item={item} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div className="list-category-child flex flex-1 items-center justify-end">
|
||||
{item.children.slice(0, 4).map((item2, index2) => (
|
||||
<Link key={index2} href={item2.url} className="title-category">
|
||||
{item2.title}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
export default BoxListCategory
|
||||
<Link href={item.url} className="title-all-category flex items-center gap-2">
|
||||
<span>Xem tất cả </span>
|
||||
<FaCaretDown size={16} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="box-list-item-category swiper-product-category">
|
||||
<Swiper
|
||||
modules={[Autoplay, Navigation, Pagination]}
|
||||
spaceBetween={12}
|
||||
slidesPerView={5}
|
||||
loop={true}
|
||||
navigation={true}
|
||||
>
|
||||
{productData.map((item, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<ItemProduct item={item} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoxListCategory;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,101 +0,0 @@
|
||||
import React from 'react';
|
||||
import Tippy from '@tippyjs/react';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { DealType } from '@/types/TypeListProductDeal';
|
||||
|
||||
type ProductItemProps = {
|
||||
item: DealType;
|
||||
};
|
||||
|
||||
const formatCurrency = (value: number | string) => {
|
||||
const num = typeof value === 'string' ? parseInt(value) : value;
|
||||
return num.toLocaleString('vi-VN');
|
||||
};
|
||||
|
||||
const ProductItem: React.FC<ProductItemProps> = ({ item }) => {
|
||||
const { product_info } = item;
|
||||
const offers = product_info.specialOffer?.all ?? [];
|
||||
|
||||
return (
|
||||
<div
|
||||
className="product-item"
|
||||
>
|
||||
<a
|
||||
href={product_info.productUrl}
|
||||
className="product-image relative"
|
||||
>
|
||||
{product_info.productImage.large ? (
|
||||
<img
|
||||
src={product_info.productImage.large}
|
||||
width="164"
|
||||
height="164"
|
||||
alt={product_info.productName}
|
||||
className="lazy"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src="/static/assets/nguyencong_2023/images/not-image.png"
|
||||
width="164"
|
||||
height="164"
|
||||
alt={product_info.productName}
|
||||
className="lazy"
|
||||
/>
|
||||
)}
|
||||
<span className="p-type-holder">
|
||||
{product_info.productType.isHot === 1 && (
|
||||
<i className="p-icon-type p-icon-hot"></i>
|
||||
)}
|
||||
{product_info.productType.isNew === 1 && (
|
||||
<i className="p-icon-type p-icon-new"></i>
|
||||
)}
|
||||
</span>
|
||||
</a>
|
||||
<div className="product-info">
|
||||
<a href={product_info.productUrl}>
|
||||
<h3 className="product-title line-clamp-3">
|
||||
{product_info.productName}
|
||||
</h3>
|
||||
</a>
|
||||
<div className="product-martket-main flex items-center">
|
||||
{product_info.marketPrice > 0 ? (
|
||||
<p className="product-market-price">
|
||||
{product_info.marketPrice.toLocaleString()} ₫
|
||||
</p>
|
||||
) : (
|
||||
<p className="product-market-price">
|
||||
{product_info.sale_rules.normal_price.toLocaleString()} ₫
|
||||
</p>
|
||||
)}
|
||||
<div className="product-percent-price">
|
||||
-{product_info.price_off || 0}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="product-price-main font-bold">
|
||||
{item.price > '0'
|
||||
? `${formatCurrency(product_info.price)}đ`
|
||||
: 'Liên hệ'}
|
||||
</div>
|
||||
<div
|
||||
className="p-quantity-sale"
|
||||
>
|
||||
<i className="sprite sprite-fire-deal"></i>
|
||||
<div className="bg-gradient"></div>
|
||||
<p className="js-line-deal-left"></p>
|
||||
<span>
|
||||
Còn {Number(item.quantity) - Number(item.sale_quantity)}/{Number(item.quantity)} sản phẩm
|
||||
</span>
|
||||
</div>
|
||||
{offers.length > 0 && (
|
||||
<div
|
||||
className="product-offer line-clamp-2"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: product_info.specialOffer!.all![0].title,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductItem;
|
||||
83
src/components/layout/home/BoxDeal/ProductItem/index.tsx
Normal file
83
src/components/layout/home/BoxDeal/ProductItem/index.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import Tippy from '@tippyjs/react';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import { DealType } from '@/types';
|
||||
|
||||
type ProductItemProps = {
|
||||
item: DealType;
|
||||
};
|
||||
|
||||
const formatCurrency = (value: number | string) => {
|
||||
const num = typeof value === 'string' ? parseInt(value) : value;
|
||||
return num.toLocaleString('vi-VN');
|
||||
};
|
||||
|
||||
const ProductItem: React.FC<ProductItemProps> = ({ item }) => {
|
||||
const { product_info } = item;
|
||||
const offers = product_info.specialOffer?.all ?? [];
|
||||
|
||||
return (
|
||||
<div className="product-item">
|
||||
<a href={product_info.productUrl} className="product-image relative">
|
||||
{product_info.productImage.large ? (
|
||||
<img
|
||||
src={product_info.productImage.large}
|
||||
width="164"
|
||||
height="164"
|
||||
alt={product_info.productName}
|
||||
className="lazy"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src="/static/assets/nguyencong_2023/images/not-image.png"
|
||||
width="164"
|
||||
height="164"
|
||||
alt={product_info.productName}
|
||||
className="lazy"
|
||||
/>
|
||||
)}
|
||||
<span className="p-type-holder">
|
||||
{product_info.productType.isHot === 1 && <i className="p-icon-type p-icon-hot"></i>}
|
||||
{product_info.productType.isNew === 1 && <i className="p-icon-type p-icon-new"></i>}
|
||||
</span>
|
||||
</a>
|
||||
<div className="product-info">
|
||||
<a href={product_info.productUrl}>
|
||||
<h3 className="product-title line-clamp-3">{product_info.productName}</h3>
|
||||
</a>
|
||||
<div className="product-martket-main flex items-center">
|
||||
{product_info.marketPrice > 0 ? (
|
||||
<p className="product-market-price">{product_info.marketPrice.toLocaleString()} ₫</p>
|
||||
) : (
|
||||
<p className="product-market-price">
|
||||
{product_info.sale_rules.normal_price.toLocaleString()} ₫
|
||||
</p>
|
||||
)}
|
||||
<div className="product-percent-price">-{product_info.price_off || 0}%</div>
|
||||
</div>
|
||||
<div className="product-price-main font-bold">
|
||||
{item.price > '0' ? `${formatCurrency(product_info.price)}đ` : 'Liên hệ'}
|
||||
</div>
|
||||
<div className="p-quantity-sale">
|
||||
<i className="sprite sprite-fire-deal"></i>
|
||||
<div className="bg-gradient"></div>
|
||||
<p className="js-line-deal-left"></p>
|
||||
<span>
|
||||
Còn {Number(item.quantity) - Number(item.sale_quantity)}/{Number(item.quantity)} sản
|
||||
phẩm
|
||||
</span>
|
||||
</div>
|
||||
{offers.length > 0 && (
|
||||
<div
|
||||
className="product-offer line-clamp-2"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: product_info.specialOffer!.all![0].title,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductItem;
|
||||
@@ -4,7 +4,7 @@ import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Autoplay, Navigation, Pagination } from 'swiper/modules';
|
||||
import { FaCaretRight } from 'react-icons/fa';
|
||||
import CounDown from './CounDown';
|
||||
import ProductItem from './ProductItem'
|
||||
import ProductItem from './ProductItem';
|
||||
import { productDealData } from './productDealData';
|
||||
|
||||
const BoxProductDeal: React.FC = () => {
|
||||
@@ -31,11 +31,11 @@ const BoxProductDeal: React.FC = () => {
|
||||
loop={true}
|
||||
navigation={true}
|
||||
>
|
||||
{productDealData.map((item,index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<ProductItem item={item} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
{productDealData.map((item, index) => (
|
||||
<SwiperSlide key={index}>
|
||||
<ProductItem item={item} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,35 @@
|
||||
'use client';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface TypeReview {
|
||||
avatar: string;
|
||||
text: string;
|
||||
author: string;
|
||||
}
|
||||
|
||||
type ItemReviewProps = {
|
||||
item: TypeReview;
|
||||
};
|
||||
|
||||
const ItemReview: React.FC<ItemReviewProps> = ({ item }) => {
|
||||
return (
|
||||
<div className="item-review-customer-hompage relative flex items-center justify-between">
|
||||
<div className="left-review relative">
|
||||
<Image src={item.avatar} width={88} height={88} className="lazy" alt="avatar" />
|
||||
</div>
|
||||
<div className="right-reivew">
|
||||
<p className="text-reivew line-clamp-2">{item.text}</p>
|
||||
<b className="author-review font-[500]">{item.author}</b>
|
||||
<Image
|
||||
src="https://nguyencongpc.vn/static/assets/nguyencong_2023/images/5star-customer.png"
|
||||
width={80}
|
||||
height={15}
|
||||
alt="rating"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default ItemReview;
|
||||
43
src/components/layout/home/BoxReviewCustomer/dataReview.ts
Normal file
43
src/components/layout/home/BoxReviewCustomer/dataReview.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
interface Review {
|
||||
avatar: string;
|
||||
text: string;
|
||||
author: string;
|
||||
}
|
||||
export const dataReview: Review[] = [
|
||||
{
|
||||
avatar: 'https://nguyencongpc.vn/static/assets/nguyencong_2023/images/avatar_nc.png',
|
||||
text: 'Cảm thấy hài lòng khi sử dụng sản phẩm và dịch vụ của Nguyễn Công PC',
|
||||
author: '- Cầu thủ ĐTQG Hoàng Đức -',
|
||||
},
|
||||
{
|
||||
avatar: 'https://nguyencongpc.vn/media/news/0709_khoapub.jpg',
|
||||
text: 'Đến Nguyễn Công PC build là yên tâm rồi, tiền bạc không thành vấn đề. Anh chốt bộ PC 300 củ.',
|
||||
author: '- Youtuber Khoa Pug -',
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
'https://nguyencongpc.vn/media/lib/02-06-2025/z6664231683539_3ee948af405f481eaf36691798831c43saochp.jpg',
|
||||
text: 'Hệ thống đào tạo đồ họa và thiết kế - thi công của anh tất cả bộ máy đều của Nguyễn Công PC',
|
||||
author: '- Mr. Tuấn VietCG -',
|
||||
},
|
||||
{
|
||||
avatar: 'https://nguyencongpc.vn/media/news/0709_tranngoc.jpg',
|
||||
text: 'Không cần lo về giá vì đã có Nguyễn Công PC.',
|
||||
author: '- MC Trần Ngọc -',
|
||||
},
|
||||
{
|
||||
avatar: 'https://nguyencongpc.vn/media/news/0709_tienlinh.jpg',
|
||||
text: 'Thằng em Hoàng Đức của mình rất hài lòng vì bộ PC mình sắm cho nó tại đây',
|
||||
author: '- Cầu thủ ĐTQG Tiến Linh -',
|
||||
},
|
||||
{
|
||||
avatar: 'https://nguyencongpc.vn/media/lib/02-06-2025/chimse.jpg',
|
||||
text: 'Từ lúc sắm PC tại Nguyễn Công PC vẩy E không trượt phát nào',
|
||||
author: '- Chim sẻ đi nắng -',
|
||||
},
|
||||
{
|
||||
avatar: 'https://nguyencongpc.vn/media/lib/02-06-2025/sep-tuan-maxhome.jpg',
|
||||
text: '12 chi nhánh Maxhome trên toàn quốc đều sử dụng máy tính tại NC. Mình build PC ở đây hơn 10 năm rồi vẫn rất hài lòng.',
|
||||
author: '- Mr. Tuấn CEO Maxhome -',
|
||||
},
|
||||
];
|
||||
30
src/components/layout/home/BoxReviewCustomer/index.tsx
Normal file
30
src/components/layout/home/BoxReviewCustomer/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
'use client';
|
||||
import { dataReview } from './dataReview';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Autoplay, Navigation, Pagination } from 'swiper/modules';
|
||||
import ItemReview from './ItemReview';
|
||||
const BoxReviewCustomer: React.FC = () => {
|
||||
return (
|
||||
<div className="box-review-from-customer boder-radius-10">
|
||||
<div className="title-box">
|
||||
<h2 className="title-box font-[600]">Đánh giá từ khách hàng về Nguyễn Công PC</h2>
|
||||
</div>
|
||||
<div className="list-review-customer-homepage">
|
||||
<Swiper
|
||||
modules={[Autoplay, Navigation, Pagination]}
|
||||
spaceBetween={15}
|
||||
slidesPerView={3}
|
||||
loop={true}
|
||||
pagination={{ clickable: true }}
|
||||
>
|
||||
{dataReview.map((item, index) => (
|
||||
<SwiperSlide key={index} className="item">
|
||||
<ItemReview item={item} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default BoxReviewCustomer;
|
||||
@@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { Category } from '@types/Menu';
|
||||
import { Category } from '@/types/global/Menu';
|
||||
|
||||
const ItemCategory: React.FC<{ item: Category }> = ({ item }) => {
|
||||
return (
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { menuData } from '../../Header/menuData';
|
||||
import ItemCategory from './itemCategory';
|
||||
import { Category } from '../../../../types/Menu';
|
||||
import { menuData } from '../../other/Header/menuData';
|
||||
import ItemCategory from './ItemCategory';
|
||||
import { InfoCategory } from '@/types';
|
||||
|
||||
const renderFeaturedCategories = (categories: Category[]) => {
|
||||
const renderFeaturedCategories = (categories: InfoCategory[]) => {
|
||||
return categories.map((cat, idx) => (
|
||||
<React.Fragment key={idx}>
|
||||
{cat.is_featured == '1' && <ItemCategory item={cat} />}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,9 @@ import SliderHome from './SliderHome';
|
||||
import BoxProductDeal from './BoxDeal';
|
||||
import CategoryFeature from './CategoryFeature';
|
||||
import BoxListCategory from './BoxCategory';
|
||||
import BoxArticle from './BoxArticle';
|
||||
import BoxArticleVideo from './BoxArticleVideo';
|
||||
import BoxReviewCustomer from './BoxReviewCustomer';
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
@@ -17,6 +20,15 @@ const Home = () => {
|
||||
|
||||
{/* DANH SÁCH DANH MỤC */}
|
||||
<BoxListCategory />
|
||||
|
||||
{/* tin tức công nghệ */}
|
||||
<BoxArticle />
|
||||
|
||||
{/* tin tức video */}
|
||||
<BoxArticleVideo />
|
||||
|
||||
{/* đánh giá từa khách hàng */}
|
||||
<BoxReviewCustomer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
86
src/components/layout/other/Footer/IconFixRight/index.tsx
Normal file
86
src/components/layout/other/Footer/IconFixRight/index.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { FaFacebookF, FaYoutube, FaAngleUp } from 'react-icons/fa';
|
||||
import { FaFacebookMessenger } from 'react-icons/fa';
|
||||
import { SiZalo } from 'react-icons/si';
|
||||
|
||||
const IconFixRight: React.FC = () => {
|
||||
return (
|
||||
<div className="global-fixed-right">
|
||||
<Link
|
||||
href="https://www.facebook.com/NGUYENCONGPC.VN"
|
||||
aria-label="Face Book"
|
||||
target="_blank"
|
||||
className="fix-face flex items-center justify-center"
|
||||
>
|
||||
<FaFacebookF size={20} />
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="https://www.youtube.com/c/NGUYENCONGPC"
|
||||
aria-label="Youtube"
|
||||
target="_blank"
|
||||
className="fix-youtube flex items-center justify-center"
|
||||
>
|
||||
<FaYoutube size={22} />
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="javascript:window.scrollTo({ top: 0, behavior: 'smooth' });"
|
||||
className="scroll-top-btn items-center justify-center"
|
||||
title="Di chuyển lên đầu trang!"
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<FaAngleUp size={20} />
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="https://m.me/nguyencongpc.vn"
|
||||
target="_blank"
|
||||
className="messenger flex items-center"
|
||||
>
|
||||
<Image
|
||||
src="https://nguyencongpc.vn/static/assets/nguyencong_2023/images/facebook_messenger.png"
|
||||
width="40"
|
||||
height="40"
|
||||
alt="mes"
|
||||
className="lazy"
|
||||
/>
|
||||
<div className="contact-info">
|
||||
<b className="d-block">Chat Facebook</b>
|
||||
<span>(8h-22h30)</span>
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="https://zalo.me/nguyencongpc"
|
||||
target="_blank"
|
||||
className="flex items-center"
|
||||
style={{
|
||||
background: '#fff',
|
||||
bottom: '135px',
|
||||
width: '170px',
|
||||
padding: '10px',
|
||||
borderRadius: '15px',
|
||||
boxShadow: '0 1px 2px 1px #01010133',
|
||||
color: '#007bff',
|
||||
fontSize: '14px',
|
||||
height: '65px',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
src="https://nguyencongpc.vn/media/lib/24-01-2024/zalo.png"
|
||||
width="40"
|
||||
height="40"
|
||||
alt="mes"
|
||||
className="lazy"
|
||||
style={{ marginRight: '10px' }}
|
||||
/>
|
||||
<div className="contact-info">
|
||||
<b className="d-block">Chat Zalo</b>
|
||||
<span>(8h-22h30)</span>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default IconFixRight;
|
||||
218
src/components/layout/other/Footer/index.tsx
Normal file
218
src/components/layout/other/Footer/index.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import IconFixRight from './IconFixRight';
|
||||
const Footer: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
<footer className="footer-main">
|
||||
{/* Chính sách */}
|
||||
<div className="footer-policy">
|
||||
<div className="container flex items-center justify-between gap-12">
|
||||
<div className="item flex items-center justify-center">
|
||||
<i className="sprite sprite-giaohang-footer"></i>
|
||||
<p className="text box-title-policy m-0">
|
||||
<b className="block">Chính sách giao hàng</b>
|
||||
<span className="grey block">nhận hàng và thanh toán tại nhà</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="item flex items-center justify-center">
|
||||
<i className="sprite sprite-doitra-footer"></i>
|
||||
<p className="text box-title-policy m-0">
|
||||
<b className="block">đổi trả dễ dàng</b>
|
||||
<span className="grey block">1 đổi 1 trong 15 ngày</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="item flex items-center justify-center">
|
||||
<i className="sprite sprite-thanhtoan-footer"></i>
|
||||
<p className="text box-title-policy m-0">
|
||||
<b className="block">thanh toán tiện lợi</b>
|
||||
<span className="grey block">tiền mặt, CK, trả góp 0%</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="item flex items-center justify-center">
|
||||
<i className="sprite sprite-hotro-footer"></i>
|
||||
<p className="text box-title-policy m-0">
|
||||
<b className="block">hỗ trợ nhiệt tình</b>
|
||||
<span className="grey block">tư vấn, giải đáp mọi thắc mắc</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Box info */}
|
||||
<div className="box-info-main">
|
||||
<div className="justify-content-between footer-list-info-main container flex">
|
||||
<div className="item-info-main">
|
||||
<p className="title font-weight-700">Giới thiệu nguyễn công</p>
|
||||
<Link href="https://nguyencongpc.vn/pages/profile.html" className="text">
|
||||
Giới thiệu công ty
|
||||
</Link>
|
||||
<Link href="/lien-he" className="text">
|
||||
Thông tin liên hệ
|
||||
</Link>
|
||||
<Link href="/tin-tuc" className="text">
|
||||
Tin tức
|
||||
</Link>
|
||||
<Link href="/tuyen-dung" className="text">
|
||||
Tuyển dụng
|
||||
</Link>
|
||||
<Link href="#fancybox-showroom" className="text">
|
||||
Hệ thống cửa hàng
|
||||
</Link>
|
||||
|
||||
<div className="list-social-footer flex items-center">
|
||||
<Link
|
||||
href="https://www.facebook.com/MAY.TINH.NGUYEN.CONG"
|
||||
className="item-social"
|
||||
aria-label="Facebook"
|
||||
>
|
||||
<i className="sprite sprite-fb-footer"></i>
|
||||
</Link>
|
||||
<Link
|
||||
href="https://www.youtube.com/c/NGUYENCONGPC"
|
||||
className="item-social"
|
||||
aria-label="Youtube"
|
||||
>
|
||||
<i className="sprite sprite-youtube-fotoer"></i>
|
||||
</Link>
|
||||
<a href="javascript:;" className="item-social" aria-label="Instagram">
|
||||
<i className="sprite sprite-instagram-footer"></i>
|
||||
</a>
|
||||
<a href="javascript:;" className="item-social" aria-label="Tiktok">
|
||||
<i className="sprite sprite-tiktok-footer"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div className="bct-footer flex gap-3">
|
||||
<Link
|
||||
href="http://online.gov.vn/Home/WebDetails/47532"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
>
|
||||
<Image
|
||||
src="https://nguyencongpc.vn/static/assets/nguyencong_2023/images/footer-bct.png"
|
||||
alt="bộ công thương"
|
||||
className="lazy"
|
||||
width={132}
|
||||
height="1"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="https://www.dmca.com/compliance/nguyencongpc.vn"
|
||||
title="DMCA Compliance information for nguyencongpc.vn"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
>
|
||||
<img
|
||||
src="https://www.dmca.com/img/dmca-compliant-grayscale.png"
|
||||
alt="DMCA compliant"
|
||||
width={115}
|
||||
height={40}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
<div>
|
||||
<i className="sprite sprite-dmca-footer"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="item-info-main">
|
||||
<p className="title font-weight-700">Hỗ trợ khách hàng</p>
|
||||
<Link href="/huong-dan-mua-hang-truc-tuyen" className="text">
|
||||
Hướng dẫn mua hàng trực tuyến
|
||||
</Link>
|
||||
<Link href="/pages/chinh-sach-thanh-toan.html" className="text">
|
||||
Hướng dẫn thanh toán
|
||||
</Link>
|
||||
<Link href="/lien-he" className="text">
|
||||
Gửi yêu cầu bảo hành
|
||||
</Link>
|
||||
<Link href="/lien-he" className="text">
|
||||
Góp ý, Khiếu Nại
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="item-info-main">
|
||||
<p className="title font-weight-700">Chính sách chung</p>
|
||||
<Link href="/pages/chinh-sach-chung.html" className="text">
|
||||
Chính sách, quy định chung
|
||||
</Link>
|
||||
<Link href="/pages/chinh-sach-van-chuyen.html" className="text">
|
||||
Chính sách vận chuyển
|
||||
</Link>
|
||||
<Link href="/pages/chinh-sach-bao-hanh.html" className="text">
|
||||
Chính sách bảo hành
|
||||
</Link>
|
||||
<Link href="/pages/dich-vu-tinh-phi.html" className="text">
|
||||
Dịch vụ tính phí
|
||||
</Link>
|
||||
<Link href="/chinh-sach-nhap-lai" className="text">
|
||||
Chính sách nhập lại tính phí
|
||||
</Link>
|
||||
<Link href="/pages/chinh-sach-doanh-nghiep.html" className="text">
|
||||
Chính sách cho doanh nghiệp
|
||||
</Link>
|
||||
<Link href="/bao-mat-thong-tin-khach-hang" className="text">
|
||||
Bảo mật thông tin khách hàng
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="item-info-main">
|
||||
<p className="title font-weight-700">Thông tin khuyến mại</p>
|
||||
<Link href="https://khuyenmai.nguyencongpc.vn" className="text">
|
||||
Thông tin khuyến mại
|
||||
</Link>
|
||||
<Link href="/san-pham-xa-hang" className="text">
|
||||
Sản phẩm khuyến mại
|
||||
</Link>
|
||||
<Link href="/san-pham-ban-chay" className="text">
|
||||
Sản phẩm bán chạy
|
||||
</Link>
|
||||
<Link href="/san-pham-moi" className="text">
|
||||
Sản phẩm mới
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer bottom */}
|
||||
<div className="footer-bottom">
|
||||
<div className="container">
|
||||
<div className="copyright">
|
||||
<p>WEBSITE ĐƯỢC SỞ HỮU VÀ QUẢN LÝ BỞI NGUYỄN VIẾT CÔNG</p>
|
||||
<b>CÔNG TY TNHH MÁY TÍNH NGUYỄN CÔNG</b>
|
||||
<p>Địa chỉ: 17 Hà Kế Tấn - Phường Phương Liệt - Hà Nội</p>
|
||||
<p>Mã số thuế: 0107568451 do Sở Kế Hoạch và Đầu Tư TP.Hà Nội (17/09/2016)</p>
|
||||
<p>
|
||||
Mua hàng: <Link href="tel:0866666166">089.9999.191</Link> -{' '}
|
||||
<a href="tel:0812666665">0812.666.665</a>
|
||||
</p>
|
||||
<p className="list-contact-footer flex items-center">
|
||||
<span>
|
||||
GÓP Ý : <Link href="tel:0979999191">097.9999.191</Link> -{' '}
|
||||
<a href="tel:0983333388">098.33333.88</a>.
|
||||
</span>
|
||||
<span>
|
||||
Email: <Link href="mailto:info@nguyencongpc.vn">info@nguyencongpc.vn</Link>.
|
||||
</span>
|
||||
<span>
|
||||
Website: <Link href="/">nguyencongpc.vn</Link>.
|
||||
</span>
|
||||
<span>
|
||||
Fanpage:{' '}
|
||||
<a href="https://www.facebook.com/MAY.TINH.NGUYEN.CONG">
|
||||
facebook.com/MAY.TINH.NGUYEN.CONG
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<IconFixRight />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
5383
src/components/layout/other/Header/menuData.ts
Normal file
5383
src/components/layout/other/Header/menuData.ts
Normal file
File diff suppressed because it is too large
Load Diff
29127
src/data/product/category/index.ts
Normal file
29127
src/data/product/category/index.ts
Normal file
File diff suppressed because one or more lines are too long
59
src/lib/category.ts
Normal file
59
src/lib/category.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { CategoryDetail } from '@/types';
|
||||
|
||||
/**
|
||||
* Tìm danh mục theo mảng slug (ví dụ: ["pc-gaming","cao-cap","rtx-4090"])
|
||||
*/
|
||||
export function findCategoryBySlug(
|
||||
slugs: string[],
|
||||
categories: CategoryDetail[],
|
||||
): CategoryDetail | null {
|
||||
let currentList: CategoryDetail[] = categories;
|
||||
let currentCategory: CategoryDetail | null = null;
|
||||
|
||||
for (const slug of slugs) {
|
||||
const found = currentList.find((cat) => cat.slug === slug);
|
||||
if (!found) return null;
|
||||
currentCategory = found;
|
||||
currentList = found.children || [];
|
||||
}
|
||||
|
||||
return currentCategory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy tất cả danh mục con của một danh mục
|
||||
*/
|
||||
export function getChildren(category: CategoryDetail): CategoryDetail[] {
|
||||
return category.children || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tìm danh mục theo id
|
||||
*/
|
||||
export function findCategoryById(id: number, categories: CategoryDetail[]): CategoryDetail | null {
|
||||
for (const cat of categories) {
|
||||
if (cat.id === id) return cat;
|
||||
if (cat.children) {
|
||||
const found = findCategoryById(id, cat.children);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tìm đường dẫn breadcrumb từ root đến danh mục hiện tại
|
||||
*/
|
||||
export function getBreadcrumb(slugs: string[], categories: CategoryDetail[]): CategoryDetail[] {
|
||||
const breadcrumb: CategoryDetail[] = [];
|
||||
let currentList: CategoryDetail[] = categories;
|
||||
|
||||
for (const slug of slugs) {
|
||||
const found = currentList.find((cat) => cat.slug === slug);
|
||||
if (!found) break;
|
||||
breadcrumb.push(found);
|
||||
currentList = found.children || [];
|
||||
}
|
||||
|
||||
return breadcrumb;
|
||||
}
|
||||
@@ -1839,21 +1839,21 @@ textarea::placeholder {
|
||||
line-height: 16px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.footer .footer-policy {
|
||||
.footer-main .footer-policy {
|
||||
background: #e5e5e5;
|
||||
padding: 27px 0;
|
||||
}
|
||||
.footer .footer-policy .item {
|
||||
.footer-main .footer-policy .item {
|
||||
width: 25%;
|
||||
padding: 15px 12px;
|
||||
border-radius: 10px;
|
||||
height: 88px;
|
||||
background: #fff;
|
||||
}
|
||||
.footer .footer-policy .box-title-policy {
|
||||
.footer-main .footer-policy .box-title-policy {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.footer .footer-policy .box-title-policy b {
|
||||
.footer-main .footer-policy .box-title-policy b {
|
||||
text-transform: uppercase;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
@@ -1861,54 +1861,54 @@ textarea::placeholder {
|
||||
color: #000;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.footer .footer-policy .box-title-policy span {
|
||||
.footer-main .footer-policy .box-title-policy span {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.footer .box-info-main {
|
||||
.footer-main .box-info-main {
|
||||
background: #fff;
|
||||
}
|
||||
.footer .footer-list-info-main {
|
||||
.footer-main .footer-list-info-main {
|
||||
padding: 18px 10px 20px;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
gap: 10px;
|
||||
}
|
||||
.footer .footer-list-info-main .item-info-main {
|
||||
.footer-main .footer-list-info-main .item-info-main {
|
||||
width: 25%;
|
||||
}
|
||||
.footer .footer-list-info-main .list-social-footer {
|
||||
.footer-main .footer-list-info-main .list-social-footer {
|
||||
margin: 10px 0;
|
||||
gap: 8px;
|
||||
}
|
||||
.footer .footer-list-info-main .bct-footer {
|
||||
.footer-main .footer-list-info-main .bct-footer {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.footer .footer-list-info-main .item-social {
|
||||
.footer-main .footer-list-info-main .item-social {
|
||||
border-radius: 5px;
|
||||
}
|
||||
.footer .footer-list-info-main p {
|
||||
.footer-main .footer-list-info-main p {
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.footer .footer-list-info-main a {
|
||||
.footer-main .footer-list-info-main a {
|
||||
display: block;
|
||||
}
|
||||
.footer .footer-list-info-main a:hover {
|
||||
.footer-main .footer-list-info-main a:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.footer .footer-bottom {
|
||||
.footer-main .footer-bottom {
|
||||
background: #f4f4f4;
|
||||
padding: 15px 0;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
.footer .footer-bottom .list-contact-footer {
|
||||
.footer-main .footer-bottom .list-contact-footer {
|
||||
gap: 20px;
|
||||
}
|
||||
.footer .footer-bottom a:hover {
|
||||
.footer-main .footer-bottom a:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
@@ -2167,7 +2167,8 @@ textarea::placeholder {
|
||||
.page-hompage .box-banner-under-slider {
|
||||
margin-top: -50px;
|
||||
}
|
||||
.swiper .swiper-button-prev svg,.swiper .swiper-button-next svg {
|
||||
.swiper .swiper-button-prev svg,
|
||||
.swiper .swiper-button-next svg {
|
||||
width: 8px !important;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
export interface BannerItem {
|
||||
id: string,
|
||||
display: string,
|
||||
fileUrl: string,
|
||||
desUrl: string,
|
||||
title: string,
|
||||
width: number,
|
||||
height: number,
|
||||
fileType: string,
|
||||
summary: string
|
||||
}
|
||||
|
||||
export interface BannerFooterData {
|
||||
banner_feedback: BannerItem[];
|
||||
banner_column_right: BannerItem[];
|
||||
banner_column_left: BannerItem[];
|
||||
|
||||
}
|
||||
|
||||
export interface BannerHeaderData {
|
||||
banner_buildpc: BannerItem[];
|
||||
banner_header_top_mb_2023: BannerItem[];
|
||||
banner_header_top: BannerItem[];
|
||||
banner_page_deal_2023: BannerItem[];
|
||||
banner_column_left: BannerItem[];
|
||||
banner_column_right: BannerItem[];
|
||||
}
|
||||
|
||||
export interface BannerHomePageData {
|
||||
slider_home: BannerItem[];
|
||||
banner_under_slider_trangchu: BannerItem[];
|
||||
banner_slider_mobile_2023: BannerItem[];
|
||||
banner_product_category: BannerItem[];
|
||||
banner_slider_homepage_main: BannerItem[];
|
||||
banner_underslider_trangchu_mobile: BannerItem[];
|
||||
banner_bot_home: BannerItem[];
|
||||
banner_mid_home: BannerItem[];
|
||||
banner_right_home: BannerItem[];
|
||||
banner_collection_pc: BannerItem[];
|
||||
}
|
||||
|
||||
|
||||
export interface TemplateBanner {
|
||||
footer: BannerFooterData;
|
||||
header: BannerHeaderData;
|
||||
homepage: BannerHomePageData;
|
||||
}
|
||||
|
||||
export type BannerType = TemplateBanner[];
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
export interface Category {
|
||||
id: string;
|
||||
title: string;
|
||||
parentId: string;
|
||||
thumnail: string;
|
||||
big_image: string;
|
||||
isParent: string;
|
||||
url: string;
|
||||
is_featured: string;
|
||||
summary: string;
|
||||
children: Category[]; // Đệ quy: Quan trọng nhất để định nghĩa cấp con
|
||||
}
|
||||
|
||||
export interface ProductCategory {
|
||||
product: {
|
||||
all_category: Category[];
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export type MenuTypes = ProductCategory[];
|
||||
20
src/types/global/Menu.ts
Normal file
20
src/types/global/Menu.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export interface InfoCategory {
|
||||
id: string;
|
||||
title: string;
|
||||
parentId: string;
|
||||
thumnail: string;
|
||||
big_image: string;
|
||||
isParent: string;
|
||||
url: string;
|
||||
is_featured: string;
|
||||
summary: string;
|
||||
children: InfoCategory[]; // Đệ quy: Quan trọng nhất để định nghĩa cấp con
|
||||
}
|
||||
|
||||
export interface all_category {
|
||||
product: {
|
||||
all_category: InfoCategory[];
|
||||
};
|
||||
}
|
||||
|
||||
export type MenuTypes = all_category[];
|
||||
1
src/types/global/index.ts
Normal file
1
src/types/global/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@/types/global/Menu';
|
||||
47
src/types/home/Banner.ts
Normal file
47
src/types/home/Banner.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export interface BannerItem {
|
||||
id: string;
|
||||
display: string;
|
||||
fileUrl: string;
|
||||
desUrl: string;
|
||||
title: string;
|
||||
width: number;
|
||||
height: number;
|
||||
fileType: string;
|
||||
summary: string;
|
||||
}
|
||||
|
||||
export interface BannerFooterData {
|
||||
banner_feedback: BannerItem[];
|
||||
banner_column_right: BannerItem[];
|
||||
banner_column_left: BannerItem[];
|
||||
}
|
||||
|
||||
export interface BannerHeaderData {
|
||||
banner_buildpc: BannerItem[];
|
||||
banner_header_top_mb_2023: BannerItem[];
|
||||
banner_header_top: BannerItem[];
|
||||
banner_page_deal_2023: BannerItem[];
|
||||
banner_column_left: BannerItem[];
|
||||
banner_column_right: BannerItem[];
|
||||
}
|
||||
|
||||
export interface BannerHomePageData {
|
||||
slider_home: BannerItem[];
|
||||
banner_under_slider_trangchu: BannerItem[];
|
||||
banner_slider_mobile_2023: BannerItem[];
|
||||
banner_product_category: BannerItem[];
|
||||
banner_slider_homepage_main: BannerItem[];
|
||||
banner_underslider_trangchu_mobile: BannerItem[];
|
||||
banner_bot_home: BannerItem[];
|
||||
banner_mid_home: BannerItem[];
|
||||
banner_right_home: BannerItem[];
|
||||
banner_collection_pc: BannerItem[];
|
||||
}
|
||||
|
||||
export interface TemplateBanner {
|
||||
footer: BannerFooterData;
|
||||
header: BannerHeaderData;
|
||||
homepage: BannerHomePageData;
|
||||
}
|
||||
|
||||
export type BannerType = TemplateBanner[];
|
||||
33
src/types/home/TypeArticle.ts
Normal file
33
src/types/home/TypeArticle.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
// types/Article.ts
|
||||
export interface ArticleImage {
|
||||
thum: string;
|
||||
original: string;
|
||||
}
|
||||
|
||||
export interface ArticleExtend {
|
||||
pixel_code: string;
|
||||
}
|
||||
|
||||
export interface Article {
|
||||
id: number;
|
||||
title: string;
|
||||
extend: ArticleExtend;
|
||||
summary: string;
|
||||
createDate: string;
|
||||
createBy: string;
|
||||
lastUpdate: string;
|
||||
lastUpdateBy: string;
|
||||
visit: number;
|
||||
is_featured: number;
|
||||
article_time: string;
|
||||
review_rate: number;
|
||||
review_count: number;
|
||||
video_code: string;
|
||||
external_url: string;
|
||||
author: string;
|
||||
counter: number;
|
||||
url: string;
|
||||
image: ArticleImage;
|
||||
}
|
||||
|
||||
export type ListArticle = Article[];
|
||||
4
src/types/home/index.ts
Normal file
4
src/types/home/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from '@/types/home/Banner';
|
||||
export * from '@/types/home/TypeArticle';
|
||||
export * from '@/types/home/TypeListProduct';
|
||||
export * from '@/types/home/TypeListProductDeal';
|
||||
3
src/types/index.ts
Normal file
3
src/types/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from '@/types/global';
|
||||
export * from '@/types/home';
|
||||
export * from '@/types/product';
|
||||
24
src/types/product/category/index.ts
Normal file
24
src/types/product/category/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export interface CategoryDetail {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
parentId?: number;
|
||||
children?: CategoryDetail[];
|
||||
}
|
||||
|
||||
export interface CategoryDetailNew {
|
||||
title: string;
|
||||
keywords: string;
|
||||
description: string;
|
||||
canonical: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export interface current_category {
|
||||
id: string;
|
||||
catPath: string;
|
||||
childListId: string;
|
||||
display_option: string;
|
||||
}
|
||||
1
src/types/product/index.ts
Normal file
1
src/types/product/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from '@/types/product/category';
|
||||
Reference in New Issue
Block a user