This commit is contained in:
2026-02-05 17:22:56 +07:00
parent a499e06b31
commit 52748cb988
30 changed files with 1080 additions and 508 deletions

View File

@@ -0,0 +1,79 @@
import type { Metadata } from "next";
export function metadataBySlug(result: any): Metadata {
console.log('metadataBySlug: ', result)
switch (result.type) {
case "product_category":
return {
title: result.data.title,
description: stripHtml(result.data.summary),
openGraph: {
type: 'website',
images: [
result.data.big_image
],
},
};
case "product_detail":
return {
title: result.data.productName,
description: stripHtml(result.data.productSummary),
openGraph: {
type: 'website',
images: [
result.data.productImage.large.replace('/250_', '/')
],
},
};
case "article_home":
return {
title: "Tin tức",
description: "Tin tức mới nhất",
openGraph: {
type: 'article',
images: [
result.data.productImage.large.replace('/250_', '/')
]
}
};
case "article_category":
return {
title: result.data.title,
description: stripHtml(result.data.summary),
openGraph: {
type: 'article',
images: [
result.data.thumbnail
]
}
};
case "article_detail":
return {
title: result.data.title,
description: result.data.excerpt,
openGraph: {
type: 'article',
}
};
default:
return {
title: "Local PC",
openGraph: {
type: 'website',
images: [
'/images/logo.png'
],
}
};
}
}
function stripHtml(html: string) {
return html.replace(/<[^>]*>/g, '');
}

View File

@@ -1,12 +1,34 @@
// src/app/[slug]/page.tsx // app/[slug]/page.tsx
import { notFound } from "next/navigation"; import { cache } from "react";
import { renderBySlug } from "./renderBySlug";
import { metadataBySlug } from "./metadataBySlug";
import { findBySlug } from "@/lib/slug/slugMap"; import { findBySlug } from "@/lib/slug/slugMap";
import { notFound } from "next/navigation";
import type { Metadata } from "next";
import ProductCategory from "@/components/product/category"; // Cache findBySlug để tránh gọi 2 lần
import ProductDetail from "@/components/product/detail"; const getCachedSlugData = cache(async (slug: string) => {
import ArticleCategory from "@/components/article/category"; if (!slug) return null;
import ArticleDetail from "@/components/article/detail"; // fetch data
import ArticleHome from "@/components/article/home";
return findBySlug(slug);
});
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const result = await getCachedSlugData(slug);
if (!result) {
return { title: "Local PC" };
}
return metadataBySlug(result);
}
export default async function SlugPage({ export default async function SlugPage({
params, params,
@@ -14,34 +36,11 @@ export default async function SlugPage({
params: Promise<{ slug: string }>; params: Promise<{ slug: string }>;
}) { }) {
const { slug } = await params; const { slug } = await params;
if (!slug) { const result = await getCachedSlugData(slug);
notFound();
}
const result = findBySlug(slug);
if (!result) { if (!result) {
notFound(); notFound();
} }
switch (result.type) { return renderBySlug(result, slug);
case "product_category":
return <ProductCategory slug={result.data} />;
case "product_detail":
return <ProductDetail slug={result.data} />;
case "article_home":
return <ArticleHome slug={slug} />;
case "article_category":
return <ArticleCategory slug={result.data} />;
case "article_detail":
return <ArticleDetail slug={result.data.slug} />;
default:
const _exhaustive: never = result;
notFound();
}
} }

View File

@@ -0,0 +1,30 @@
// src/app/[slug]/renderBySlug.tsx
import { notFound } from "next/navigation";
import ProductCategory from "@/components/product/category";
import ProductDetail from "@/components/product/detail";
import ArticleCategory from "@/components/article/category";
import ArticleDetail from "@/components/article/detail";
import ArticleHome from "@/components/article/home";
export function renderBySlug(result: any, slug: string) {
switch (result.type) {
case "product_category":
return <ProductCategory slug={result.data} />;
case "product_detail":
return <ProductDetail slug={result.data} />;
case "article_home":
return <ArticleHome slug={slug} />;
case "article_category":
return <ArticleCategory slug={result.data} />;
case "article_detail":
return <ArticleDetail slug={result.data.slug} />;
default:
notFound();
}
}

View File

@@ -7,7 +7,7 @@ import TooltipProvider from "@/components/providers/TooltipProvider";
import '../styles/globals.css'; import '../styles/globals.css';
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Local Pc", title: "Homepage- Local Pc",
description: "hoanghapc", description: "hoanghapc",
}; };

View File

@@ -3,8 +3,6 @@ import Home from "@/components/home";
export default function HomePage() { export default function HomePage() {
return ( return (
<>
<Home /> <Home />
</>
) )
} }

View File

@@ -1,8 +1,13 @@
import ArticleHome from "@/components/article/home"; import ArticleHome from "@/components/article/home";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Tin tức ",
description: "hoanghapc",
};
export default function Home() { export default function Home() {
return ( return (
<ArticleHome slug="" /> <ArticleHome />
) )
} }

View File

@@ -1,185 +1,41 @@
export default function ArticleCategory({ slug }: { slug: string }) { 'use client';
return( import Link from "next/link";
import { useEffect } from 'react';
import { categories } from "@/data/categories";
import ArticleItem from "@/components/shared/ArticleItem";
export default function ArticleCategory({ slug }: any) {
useEffect(() => {
document.body.style.background = '#F5F8FF';
}, []);
console.log('slug: ', slug)
const { article } = categories.article.all_category
const categoryList = slug.children.length > 0
? slug.children
: article
console.log('categoryList: ', categoryList)
return (
<> <>
<h1 className="absolute top-[-999px] z-[-1]"> Danh mục tin </h1> <h1 className="absolute top-[-999px] z-[-1]"> Danh mục tin </h1>
<div className="article-page container !mt-8 mt-6"> <div className="article-page container !mt-8 mt-6">
{/* <style>body{background: #F5F8FF}</style> */} {/* <style>body{background: #F5F8FF}</style> */}
<div className="article-categories-group bg-[#F5F8FF] flex justify-between relative overflow-auto whitespace-nowrap uppercase font-500 leading-[18px] text-[#828282] gap-5 lg:gap-1 no-scroll border-b border-[#C5CBD8]"> <div className="article-categories-group bg-[#F5F8FF] flex justify-between relative overflow-auto whitespace-nowrap uppercase font-500 leading-[18px] text-[#828282] gap-5 lg:gap-1 no-scroll border-b border-[#C5CBD8]">
<a href="" className="active"> {categoryList.map((item:any) => (
{" "} <Link className={`${item.id === slug.id ? 'active' : '' }`}
MÁY KHỎE - ĐP{" "} href={item.url}
</a> key={item.id}
<a href=""> TIN CÔNG NGHỆ </a> >
<a href=""> REVIEW SẢN PHẨM </a> {item.title}
<a href=""> BENCHMARKS </a> </Link>
<a href=""> THỦ THUẬT </a> ))}
<a href=""> KHUYẾN MÃI </a>
<a href=""> WIKI </a>
<a href=""> tin game </a>
</div> </div>
{/* Tin tức */} {/* Tin tức */}
<div className="article-holder grid lg:grid-cols-3 grid-cols-2 gap-4 lg:gap-6 my-5"> <div className="article-holder grid lg:grid-cols-3 grid-cols-2 gap-4 lg:gap-6 my-5">
<div className="art-item"> <ArticleItem />
<a href="" className="art-img">
<img
src="https://hoanghapccdn.com/media/news/14_100__c___u_h__nh_m__y_t__nh_______h___a_theo_ng__n_s__ch.jpg"
alt=""
width={1}
height={1}
/>
</a>
<div className="art-text">
<a href="" className="art-title">
<h3>
{" "}
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eum
quidem asperiores provident dicta veniam deleniti eaque
repudiandae cum esse, ducimus officiis quibusdam pariatur neque
voluptates voluptas. Quisquam qui minus dolorum?{" "}
</h3>
</a>
<div className="art-summary">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit,
obcaecati ducimus veritatis aliquid sunt accusamus unde nisi nostrum
fugit facere illo quos. Ad error suscipit, quidem optio aut
laudantium at!
</div>
<div className="art-time">
<i className="bx bx-calendar-alt text-16 text-[#A0A5AC]" />
<time> 23/4/2024 </time>
<i className="w-[1.5px] h-[12px] bg-[#A0A5AC]" />
<span> Mai Văn Học </span>
</div>
</div>
</div>
<div className="art-item">
<a href="" className="art-img">
<img
src="https://hoanghapccdn.com/media/news/14_100__c___u_h__nh_m__y_t__nh_______h___a_theo_ng__n_s__ch.jpg"
alt=""
width={1}
height={1}
/>
</a>
<div className="art-text">
<a href="" className="art-title">
<h3>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eum
quidem asperiores provident dicta veniam deleniti eaque
repudiandae cum esse, ducimus officiis quibusdam pariatur neque
voluptates voluptas. Quisquam qui minus dolorum?
</h3>
</a>
<div className="art-summary">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit,
obcaecati ducimus veritatis aliquid sunt accusamus unde nisi nostrum
fugit facere illo quos. Ad error suscipit, quidem optio aut
laudantium at!
</div>
<div className="art-time">
<i className="bx bx-calendar-alt text-16 text-[#A0A5AC]" />
<time>23/4/2024</time>
<i className="w-[1.5px] h-[12px] bg-[#A0A5AC]" />
<span>Mai Văn Học</span>
</div>
</div>
</div>
<div className="art-item">
<a href="" className="art-img">
<img
src="https://hoanghapccdn.com/media/news/14_100__c___u_h__nh_m__y_t__nh_______h___a_theo_ng__n_s__ch.jpg"
alt=""
width={1}
height={1}
/>
</a>
<div className="art-text">
<a href="" className="art-title">
<h3>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eum
quidem asperiores provident dicta veniam deleniti eaque
repudiandae cum esse, ducimus officiis quibusdam pariatur neque
voluptates voluptas. Quisquam qui minus dolorum?
</h3>
</a>
<div className="art-summary">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit,
obcaecati ducimus veritatis aliquid sunt accusamus unde nisi nostrum
fugit facere illo quos. Ad error suscipit, quidem optio aut
laudantium at!
</div>
<div className="art-time">
<i className="bx bx-calendar-alt text-16 text-[#A0A5AC]" />
<time>23/4/2024</time>
<i className="w-[1.5px] h-[12px] bg-[#A0A5AC]" />
<span>Mai Văn Học</span>
</div>
</div>
</div>
<div className="art-item">
<a href="" className="art-img">
<img
src="https://hoanghapccdn.com/media/news/14_100__c___u_h__nh_m__y_t__nh_______h___a_theo_ng__n_s__ch.jpg"
alt=""
width={1}
height={1}
/>
</a>
<div className="art-text">
<a href="" className="art-title">
<h3>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eum
quidem asperiores provident dicta veniam deleniti eaque
repudiandae cum esse, ducimus officiis quibusdam pariatur neque
voluptates voluptas. Quisquam qui minus dolorum?
</h3>
</a>
<div className="art-summary">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit,
obcaecati ducimus veritatis aliquid sunt accusamus unde nisi nostrum
fugit facere illo quos. Ad error suscipit, quidem optio aut
laudantium at!
</div>
<div className="art-time">
<i className="bx bx-calendar-alt text-16 text-[#A0A5AC]" />
<time>23/4/2024</time>
<i className="w-[1.5px] h-[12px] bg-[#A0A5AC]" />
<span>Mai Văn Học</span>
</div>
</div>
</div>
<div className="art-item">
<a href="" className="art-img">
<img
src="https://hoanghapccdn.com/media/news/14_100__c___u_h__nh_m__y_t__nh_______h___a_theo_ng__n_s__ch.jpg"
alt=""
width={1}
height={1}
/>
</a>
<div className="art-text">
<a href="" className="art-title">
<h3>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eum
quidem asperiores provident dicta veniam deleniti eaque
repudiandae cum esse, ducimus officiis quibusdam pariatur neque
voluptates voluptas. Quisquam qui minus dolorum?
</h3>
</a>
<div className="art-summary">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velit,
obcaecati ducimus veritatis aliquid sunt accusamus unde nisi nostrum
fugit facere illo quos. Ad error suscipit, quidem optio aut
laudantium at!
</div>
<div className="art-time">
<i className="bx bx-calendar-alt text-16 text-[#A0A5AC]" />
<time>23/4/2024</time>
<i className="w-[1.5px] h-[12px] bg-[#A0A5AC]" />
<span>Mai Văn Học</span>
</div>
</div>
</div>
</div> </div>
{/* Video */} {/* Video */}
<div className="article-holder article-video-holder grid lg:grid-cols-3 grid-cols-2 gap-4 lg:gap-6 my-5"> <div className="article-holder article-video-holder grid lg:grid-cols-3 grid-cols-2 gap-4 lg:gap-6 my-5">

View File

@@ -1,166 +1,124 @@
import Link from "next/link"; import Link from "next/link";
import { Swiper, SwiperSlide } from 'swiper/react';
import { Pagination, Autoplay } from 'swiper/modules';
export default function Tiktok() { export default function Tiktok() {
return ( return (
<div className="article-tiktok-container py-8 lg:py-16"> <div className="article-tiktok-container py-8 lg:py-16">
<div className="group-title flex items-center justify-between flex-wrap gap-4 mb-6"> <div className="group-title flex items-center justify-between flex-wrap gap-4 mb-6">
<h2 className="flex items-center m-0 leading-8 font-600 text-20 text-[#004BA4] gap-[10px] lg:gap-4 lg:text-[32px] lg:leading-10"> <h2 className="flex items-center m-0 leading-8 font-600 text-20 text-[#004BA4] gap-[10px] lg:gap-4 lg:text-[32px] lg:leading-10">
<i <i className="lazy bg-no-repeat bg-center bg-[length:100%_100%] w-8 h-8 lg:w-12 lg:h-12"
className="lazy bg-no-repeat bg-center bg-[length:100%_100%] w-8 h-8 lg:w-12 lg:h-12" style={{ backgroundImage: 'url(images/logo-tiktok.png)' }}
data-bg="url(images/logo-tiktok.png)"
/> />
<span> Tiktok Channel </span> <span> Tiktok Channel </span>
</h2> </h2>
<div className="flex flex-wrap items-center justify-center text-center bg-[#DCE8FF] rounded-[16px_0_] leading-5 lg:leading-6 lg:text-[16px] gap-3 lg:w-[823px] w-full p-3 lg:p-4"> <div className="flex flex-wrap items-center justify-center text-center bg-[#DCE8FF] rounded-[16px_0_] leading-5 lg:leading-6 lg:text-[16px] gap-3 lg:w-[823px] w-full p-3 lg:p-4">
<i className="icons icon-finger !w-6 !h-6 animation-wiggle" /> <i className="icons icon-finger !w-6 !h-6 animation-wiggle" />
<div className="lg:flex flex-wrap"> <div className="lg:flex flex-wrap">
<p className="m-0 mr-1"> <p className="m-0 mr-1">
Theo dõi kênh tiktok của Hoàng PC: Theo dõi kênh tiktok của Hoàng PC:
</p> </p>
<a
<Link
href="https://www.tiktok.com/@hoangha.pc" href="https://www.tiktok.com/@hoangha.pc"
target="_blank" target="_blank"
rel="nofollow" rel="nofollow"
className="text-[#0678DB] font-600 table lg:text-[16px]" className="text-[#0678DB] font-600 table lg:text-[16px]"
> >
ORIGINAL SOUND - HOÀNG PC ORIGINAL SOUND - HOÀNG PC
</a> </Link>
</div> </div>
</div> </div>
</div> </div>
<div className="swiper overflow-hidden" id="js-tiktok-slide"> <div className="swiper overflow-hidden" id="js-tiktok-slide">
<div className="swiper-wrapper"> <Swiper
<div className="swiper-slide"> spaceBetween={16}
<a href="" target="_blank" rel="nofollow"> slidesPerView={1.7}
loop={true}
autoplay={{
delay: 3000,
disableOnInteraction: false,
}}
modules={[Pagination, Autoplay]}
speed={1000}
breakpoints= {{
414: {
slidesPerView: 2
},
576: {
slidesPerView: 3
},
768: {
slidesPerView: 4
},
1024: {
slidesPerView: 6
}
}}
>
<SwiperSlide>
<Link href="https://www.tiktok.com/@hoangha.pc/video/7260350983039945985" rel="nofollow" target="_blank">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]"> <span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img <img src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-sieu-vip-pro.jpg" alt="tiktok video" width="1" height="1" className="block lazy w-full absolute h-full object-cover" />
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span> </span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP <span className="block leading-[22px] font-600 text-16">Đơn hàng cho công ty VIP PRO</span>
</span> </Link>
</a> </SwiperSlide>
</div>
<div className="swiper-slide"> <SwiperSlide>
<a href="" target="_blank" rel="nofollow"> <Link href="https://www.tiktok.com/@hoangha.pc/video/7259685093806034177" rel="nofollow" target="_blank">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]"> <span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img <img src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg" alt="tiktok video" width="1" height="1" className="block lazy w-full absolute h-full object-cover" />
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span> </span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP <span className="block leading-[22px] font-600 text-16"> Đơn hàng đc biệt cho khách vip </span>
</span> </Link>
</a> </SwiperSlide>
</div>
<div className="swiper-slide"> <SwiperSlide>
<a href="" target="_blank" rel="nofollow"> <Link href="https://www.tiktok.com/@hoangha.pc/video/7259272220093041926" rel="nofollow" target="_blank">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]"> <span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img <img src="https://hoanghapccdn.com/media/lib/27-07-2023/don-pc-200-trieu.jpg" alt="tiktok video" width="1" height="1" className="block lazy w-full absolute h-full object-cover" />
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span> </span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP <span className="block leading-[22px] font-600 text-16"> Đơn PC hơn 200 c </span>
</span> </Link>
</a> </SwiperSlide>
</div>
<div className="swiper-slide"> <SwiperSlide>
<a href="" target="_blank" rel="nofollow"> <Link href="https://www.tiktok.com/@hoangha.pc/video/7256640817752820997" rel="nofollow" target="_blank">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]"> <span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img <img src="https://hoanghapccdn.com/media/lib/27-07-2023/don-pc-300-trieu.jpg" alt="tiktok video" width="1" height="1" className="block lazy w-full absolute h-full object-cover" />
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span> </span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP <span className="block leading-[22px] font-600 text-16"> Đơn hàng hơn 300 c ? </span>
</span> </Link>
</a> </SwiperSlide>
</div>
<div className="swiper-slide"> <SwiperSlide>
<a href="" target="_blank" rel="nofollow"> <Link href="https://www.tiktok.com/@hoangha.pc/video/7249589730000522501" rel="nofollow" target="_blank">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]"> <span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img <img src="https://hoanghapccdn.com/media/lib/27-07-2023/pc-dau-than.jpg" alt="tiktok video" width="1" height="1" className="block lazy w-full absolute h-full object-cover" />
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span> </span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP <span className="block leading-[22px] font-600 text-16"> PC Full trắng vừa đp vừa khỏe </span>
</span> </Link>
</a> </SwiperSlide>
</div>
<div className="swiper-slide"> <SwiperSlide>
<a href="" target="_blank" rel="nofollow"> <Link href="https://www.tiktok.com/@hoangha.pc/video/7245136755375049989" rel="nofollow" target="_blank">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]"> <span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img <img src="https://hoanghapccdn.com/media/lib/27-07-2023/pc-80-trieu-co-gi.jpg" alt="tiktok video" width="1" height="1" className="block lazy w-full absolute h-full object-cover" />
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span> </span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP <span className="block leading-[22px] font-600 text-16"> PC 80 c ? </span>
</span> </Link>
</a> </SwiperSlide>
</div> </Swiper>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
</div>
</div> </div>
</div> </div>
) )

View File

@@ -1,22 +1,16 @@
'use client'; 'use client';
import Link from "next/link"; import Link from "next/link";
import { useEffect } from 'react'; import { useEffect } from 'react';
import useScrollSpy from "@/hooks/useScrollSpy";
import { articleList } from "@/data/articles"; import { articleList } from "@/data/articles";
import { categories } from "@/data/categories"; import { categories } from "@/data/categories";
import useScrollSpy from "@/hooks/useScrollSpy";
import TopArticleList from "./TopArticleList"; import TopArticleList from "./TopArticleList";
import ArticleCategories from "./ArticleCategories"; import ArticleCategories from "./ArticleCategories";
import Video from "./Video" import Video from "./Video"
import Tiktok from "./Tiktok"; import Tiktok from "./Tiktok";
export default function ArticleHome() { export default function ArticleHome() {
const {
article,
video,
job
} = categories.article.all_category;
console.log(video, job)
const top_article_list = articleList const top_article_list = articleList
.flatMap(item => item.list) .flatMap(item => item.list)
@@ -44,7 +38,7 @@ export default function ArticleHome() {
<div className="article-categories-group bg-[#F5F8FF] sticky top-[68px] lg:top-[76px] left-0 right-0 z-[2] pt-5"> <div className="article-categories-group bg-[#F5F8FF] sticky top-[68px] lg:top-[76px] left-0 right-0 z-[2] pt-5">
<div className="container flex justify-between relative overflow-auto whitespace-nowrap uppercase font-500 leading-[18px] text-[#828282] gap-5 lg:gap-1 no-scroll border-b border-[#C5CBD8]"> <div className="container flex justify-between relative overflow-auto whitespace-nowrap uppercase font-500 leading-[18px] text-[#828282] gap-5 lg:gap-1 no-scroll border-b border-[#C5CBD8]">
{article.map((item) => ( {categories.article.all_category.article.map((item:any) => (
<Link className="js-category-tab" <Link className="js-category-tab"
href={`#js-category-${item.id}`} href={`#js-category-${item.id}`}
key={item.id} key={item.id}
@@ -56,7 +50,7 @@ export default function ArticleHome() {
</div> </div>
<div> <div>
{article.map((item: any) =>{ {categories.article.all_category.article.map((item: any) =>{
const data = articleList.find(i => i.id === item.id)?.list || []; const data = articleList.find(i => i.id === item.id)?.list || [];
return <ArticleCategories return <ArticleCategories

View File

@@ -2,7 +2,7 @@
import { Swiper, SwiperSlide } from 'swiper/react'; import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination, Autoplay } from 'swiper/modules'; import { Navigation, Pagination, Autoplay } from 'swiper/modules';
import { categories } from "@/data/categories" import { categories } from "@/data/categories"
import { productList } from '@/data/productList'; import { productList } from '@/data/products/productList';
import CategoryIcon from "./CategoryIcon" import CategoryIcon from "./CategoryIcon"
import ProductItem from "@/components/shared/ProductItem" import ProductItem from "@/components/shared/ProductItem"
import Link from 'next/link'; import Link from 'next/link';

View File

@@ -1,5 +1,5 @@
'use client'; 'use client';
import { useState, useEffect } from "react"; import { useState } from "react";
export default function FAQ({ faq }: any) { export default function FAQ({ faq }: any) {

View File

@@ -1,4 +1,5 @@
import { productCategory } from "@/data/productCategory"; import { productCategory } from "@/data/products/productCategory";
import { productList } from "@/data/products/productList";
import ProductFilter from "./filter"; import ProductFilter from "./filter";
import Static from "./static"; import Static from "./static";
@@ -6,7 +7,7 @@ import FAQ from "./faq";
import Banner from "./banner"; import Banner from "./banner";
import SortByCollection from "./sort"; import SortByCollection from "./sort";
import ProductList from "./productList"; import ProductList from "./productList";
import { productList } from "@/data/productList";
export default function ProductCategory({ slug }: any) { export default function ProductCategory({ slug }: any) {

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState } from "react"; import { useState } from "react";
import { productList } from "@/data/productList"; import { productList } from "@/data/products/productList";
import ProductItem from "@/components/shared/ProductItem"; import ProductItem from "@/components/shared/ProductItem";
const PRODUCT_PER_PAGE = 30; const PRODUCT_PER_PAGE = 30;

View File

@@ -8,15 +8,18 @@ import CommentList from "./CommentList";
export default function Comment() { export default function Comment() {
const [star, setStar] = useState<number | null>(null); const [star, setStar] = useState<number | null>(null);
const productComment = CommentData.list.filter( (item:any) => item.item_type === "product" );
const filteredComments = useMemo(() => { const filteredComments = useMemo(() => {
if (star === null) return CommentData.list; if (star === null) return productComment;
return CommentData.list.filter(item => Number(item.rate) === star); return productComment.filter(item => Number(item.rate) === star);
}, [star]); }, [star]);
return ( return (
<div> <div>
<div className="flex items-center justify-between leading-8 gap-2 mb-4"> <div className="flex items-center justify-between leading-8 gap-2 mb-4">
<p className="m-0 text-18 font-500"> {CommentData.list.length} Bình luận </p> <p className="m-0 text-18 font-500"> {filteredComments.length} Bình luận </p>
<div className="flex flex-wrap gap-2 text-14 font-500 pd-comment-btn"> <div className="flex flex-wrap gap-2 text-14 font-500 pd-comment-btn">
<button type="button" <button type="button"

View File

@@ -4,7 +4,6 @@ import { useState } from 'react'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { Swiper, SwiperSlide } from 'swiper/react' import { Swiper, SwiperSlide } from 'swiper/react'
import { Navigation } from 'swiper/modules' import { Navigation } from 'swiper/modules'
import Link from 'next/link'
const PhotoSwipeImage = dynamic( const PhotoSwipeImage = dynamic(
() => import('./PhotoSwipeImage'), () => import('./PhotoSwipeImage'),

View File

@@ -1,5 +1,6 @@
import parse from 'html-react-parser'; import parse from 'html-react-parser';
import { formatPrice } from "@/lib/utils"; import { formatPrice } from "@/lib/utils";
import ProductImage from "./images"; import ProductImage from "./images";
import Static from "./static"; import Static from "./static";
import ProductDescription from "./description" import ProductDescription from "./description"

View File

@@ -2,7 +2,7 @@
import { useEffect, useState, useMemo } from "react"; import { useEffect, useState, useMemo } from "react";
import { Swiper, SwiperSlide } from 'swiper/react'; import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination, Autoplay } from 'swiper/modules'; import { Navigation, Pagination, Autoplay } from 'swiper/modules';
import { ProductHistory } from "@/data/productHistory"; import { ProductHistory } from "@/data/products/productHistory";
import ProductItem from "@/components/shared/ProductItem" import ProductItem from "@/components/shared/ProductItem"
export default function ProductTab({ item }: any) { export default function ProductTab({ item }: any) {

View File

@@ -7,6 +7,7 @@ import ReviewList from "./ReviewList";
export default function Review( {item} : any ) { export default function Review( {item} : any ) {
const [ show, setShow ] = useState(false); const [ show, setShow ] = useState(false);
const productReview = item.list.filter( (item:any) => item.item_type === "product" );
return ( return (
<> <>
@@ -34,9 +35,9 @@ export default function Review( {item} : any ) {
<ReviewForm /> <ReviewForm />
</div> </div>
{item.list.length > 0 && {productReview.length > 0 &&
<div className="text-14 leading-[18px] mt-4"> <div className="text-14 leading-[18px] mt-4">
<ReviewList item={item.list}/> <ReviewList item={productReview}/>
</div> </div>
} }
</div> </div>

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,77 @@ export const CommentData = {
"total": 0, "total": 0,
}, },
"list": [ "list": [
{
"id": "1",
"item_type": "article",
"item_id": "2",
"people_like_count": "0",
"people_dislike_count": "0",
"reply_count": "0",
"is_user_admin": "0",
"user_avatar": "",
"user_name": "VU THANH CHUNG - S\u0110T: 0988358888",
"rate": "5",
"title": "HHPC CORE i7 14700KF | 64GB | NVIDIA RTX 5070 Ti 16G",
"content": "Test review article ",
"files": [],
"approved": "0",
"post_time": "1766984975",
"counter": 1,
"new_replies": [
{
"id": "2",
"item_type": "article",
"item_id": "6434",
"people_like_count": "0",
"people_dislike_count": "0",
"reply_count": "1",
"is_user_admin": "0",
"user_avatar": "",
"user_name": "hura test - S\u0110T: 0987654321",
"rate": "2",
"title": "HuraSoft - S\u1ea3n ph\u1ea9m test (Kh\u00f4ng x\u00f3a)",
"content": "Test reply article 1",
"files": [],
"approved": "1",
"post_time": "1760330892",
"counter": 5,
"new_replies": [
{
"id": 742,
"comment_id": 8760,
"user_avatar": "0",
"user_name": "Hurasoft \u0110\u1ee9c",
"is_user_admin": 1,
"people_like_count": 0,
"approved": 1,
"people_dislike_count": 0,
"content": "admin test",
"post_time": 1760331038
}
]
},
{
"id": "8757",
"item_type": "article",
"item_id": "6270",
"people_like_count": "0",
"people_dislike_count": "0",
"reply_count": "0",
"is_user_admin": "0",
"user_avatar": "",
"user_name": "Ch\u1ecbu - S\u1ed1 \u0111t : 0938553437",
"rate": "5",
"title": "\u0110\u00e1nh gi\u00e1 s\u1ea3n ph\u1ea9m Fan Xigmatek Infinity Pro 2 RGB Reverse",
"content": "5 sao",
"files": [],
"approved": "0",
"post_time": "1759639059",
"counter": 6,
"new_replies": []
},
]
},
{ {
"id": "10187", "id": "10187",
"item_type": "product", "item_type": "product",

View File

@@ -19,6 +19,77 @@ export const ReviewData = {
"total": 20, "total": 20,
}, },
"list": [ "list": [
{
"id": "1",
"item_type": "article",
"item_id": "2",
"people_like_count": "0",
"people_dislike_count": "0",
"reply_count": "0",
"is_user_admin": "0",
"user_avatar": "",
"user_name": "VU THANH CHUNG - S\u0110T: 0988358888",
"rate": "5",
"title": "HHPC CORE i7 14700KF | 64GB | NVIDIA RTX 5070 Ti 16G",
"content": "Test review article ",
"files": [],
"approved": "0",
"post_time": "1766984975",
"counter": 1,
"new_replies": [
{
"id": "2",
"item_type": "article",
"item_id": "6434",
"people_like_count": "0",
"people_dislike_count": "0",
"reply_count": "1",
"is_user_admin": "0",
"user_avatar": "",
"user_name": "hura test - S\u0110T: 0987654321",
"rate": "2",
"title": "HuraSoft - S\u1ea3n ph\u1ea9m test (Kh\u00f4ng x\u00f3a)",
"content": "Test reply article 1",
"files": [],
"approved": "1",
"post_time": "1760330892",
"counter": 5,
"new_replies": [
{
"id": 742,
"comment_id": 8760,
"user_avatar": "0",
"user_name": "Hurasoft \u0110\u1ee9c",
"is_user_admin": 1,
"people_like_count": 0,
"approved": 1,
"people_dislike_count": 0,
"content": "admin test",
"post_time": 1760331038
}
]
},
{
"id": "8757",
"item_type": "article",
"item_id": "6270",
"people_like_count": "0",
"people_dislike_count": "0",
"reply_count": "0",
"is_user_admin": "0",
"user_avatar": "",
"user_name": "Ch\u1ecbu - S\u1ed1 \u0111t : 0938553437",
"rate": "5",
"title": "\u0110\u00e1nh gi\u00e1 s\u1ea3n ph\u1ea9m Fan Xigmatek Infinity Pro 2 RGB Reverse",
"content": "5 sao",
"files": [],
"approved": "0",
"post_time": "1759639059",
"counter": 6,
"new_replies": []
},
]
},
{ {
"id": "8768", "id": "8768",
"item_type": "product", "item_type": "product",

View File

@@ -1,6 +1,7 @@
// src/lib/articlePage.ts // src/lib/articlePage.ts
import { categories } from "@/data/categories"; import { categories } from "@/data/categories";
import { articleList } from "@/data/articles"; import { articleList } from "@/data/articles";
import { ArticleDetail } from "@/data/articleDetail"
export type ArticleResult = export type ArticleResult =
| { type: "article_home"; data: any } | { type: "article_home"; data: any }
@@ -12,28 +13,51 @@ export function resolveArticlePage(slug: string): ArticleResult | null {
// HOME // HOME
if (url === "/tin-tuc") { if (url === "/tin-tuc") {
return { type: "article_home", data: null }; return {
type: "article_home",
data : null
};
} }
// CATEGORY // CATEGORY
const cats = categories.article.all_category.article; const cats = categories.article.all_category.article;
for (const parent of cats) { for (const parent of cats) {
if (parent.url === url) { if (parent.url === url) {
return { type: "article_category", data: parent }; return {
type: "article_category",
data: parent
};
} }
for (const child of parent.children ?? []) { for (const child of parent.children ?? []) {
if (child.url === url) { if (child.url === url) {
return { type: "article_category", data: child }; return {
type: "article_category",
data: child
};
} }
} }
} }
// DETAIL // DETAIL
const allArticles = articleList.flatMap(article => article.list); const detail = articleList
for (const article of allArticles) { .flatMap(article => article.list)
if (article.url === url) { .find((a: any) => a.url === url);
return { type: "article_detail", data: { slug } }; if(detail) {
const data = {
...detail,
content : ArticleDetail.content,
author : ArticleDetail.author,
image_list : ArticleDetail.image_list,
related : ArticleDetail.related,
tag_list : ArticleDetail.tag_list,
} }
return {
type: "article_detail",
data
};
} }
return null;
} }

View File

@@ -1,7 +1,7 @@
// hoanghapc/src/lib/productPage.ts // hoanghapc/src/lib/productPage.ts
import { categories } from "@/data/categories"; import { categories } from "@/data/categories";
import { productList } from "@/data/productList"; import { productList } from "@/data/products/productList";
import { productDetail } from "@/data/productDetail" import { productDetail } from "@/data/products/productDetail"
export type ProductResult = export type ProductResult =
| { type: "product_category"; data: any } | { type: "product_category"; data: any }

View File

@@ -15,8 +15,8 @@ export function findBySlug(slug?: string): SlugResult | null {
if (product) return product; if (product) return product;
// ARTICLE // ARTICLE
const articler = resolveArticlePage(slug); const article = resolveArticlePage(slug);
if (articler) return articler; if (article) return article;
// 404 // 404
return null; return null;

View File

@@ -1,5 +1,5 @@
// Add tất cả sp trong data product vào 1 mảng // Add tất cả sp trong data product vào 1 mảng
import { productList } from '@/data/productList'; import { productList } from '@/data/products/productList';
export function getAllProducts() { export function getAllProducts() {
return productList.flatMap((group: any) => group.list); return productList.flatMap((group: any) => group.list);

View File

@@ -60,7 +60,7 @@ body{min-width:1248px;background:#E8ECF6}
.icon-gift{background-position:-40px -159px} .icon-gift{background-position:-40px -159px}
.icon-card{background-position:-5px -193px} .icon-card{background-position:-5px -193px}
.icon-flame{background-position:-40px -194px} .icon-flame{background-position:-40px -194px}
.icon-goTop{position:fixed;bottom:100px;right:10px;width:48px;height:48px;line-height:46px;border-radius:50%;border:1px solid #E0E0E0;color:#fff;background:#3947b9;font-size:28px;z-index:8;} .icon-goTop{position:fixed;bottom:100px;right:10px;width:48px;height:48px;line-height:46px;border-radius:50%;border:1px solid #E0E0E0;color:#fff;background:#3947b9;font-size:28px;z-index:10;}
.icon-finger{background-position:-155px -222px} .icon-finger{background-position:-155px -222px}
.inherit,.art-item .art-title *,.p-item .p-name *,.deal-item .deal-name *{font-weight:inherit!important;font-size:inherit!important;line-height:inherit!important;margin:0} .inherit,.art-item .art-title *,.p-item .p-name *,.deal-item .deal-name *{font-weight:inherit!important;font-size:inherit!important;line-height:inherit!important;margin:0}
.custom-dots .swiper-pagination-bullet{width:20px;height:6px;border:2px solid rgba(255,255,255,0.7);border-radius:8px 0 8px 0;background:transparent;opacity:1;margin:0 3px!important;transition:.15s all} .custom-dots .swiper-pagination-bullet{width:20px;height:6px;border:2px solid rgba(255,255,255,0.7);border-radius:8px 0 8px 0;background:transparent;opacity:1;margin:0 3px!important;transition:.15s all}