update 30/12

This commit is contained in:
2025-12-30 18:05:53 +07:00
parent 19b55a3d93
commit da37dc67e7
20 changed files with 621 additions and 1319 deletions

View File

@@ -1,43 +1,41 @@
// src/app/[slug]/page.tsx
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { findBySlug } from "@/lib/slugMap"; import { findBySlug } from "@/lib/slugMap";
import ProductCategory from "@/components/product/Category"; import ProductCategory from "@/components/product/Category";
import ProductDetail from "@/components/product/ProductDetail"; import ProductDetail from "@/components/product/ProductDetail";
import ArticleHome from "@/components/article/Home";
import ArticleCategory from "@/components/article/Category"; import ArticleCategory from "@/components/article/Category";
import ArticleDetail from "@/components/article/ArticleDetail"; import ArticleDetail from "@/components/article/ArticleDetail";
import ArticleHome from "@/components/article/Home";
export default async function SlugPage({ export default async function SlugPage({
params, params,
}: { }: {
params: Promise<{ slug: string }>; params: { slug: string };
}) { }) {
const { slug } = await params; const { slug } = await params;
if (!slug) return notFound(); if (!slug) return notFound();
const result = findBySlug(slug); const result = findBySlug(slug);
if (!result) return notFound(); if (!result) return notFound();
switch (result.type) { switch (result.type) {
case "product_category": case "product_category":
return <ProductCategory slug={slug} />; return <ProductCategory data={result.data} />;
case "product_detail": case "product_detail":
return <ProductDetail slug={slug} />; return <ProductDetail data={result.data} />;
case "article_home": case "article_home":
return <ArticleHome slug={slug} />; return <ArticleHome slug={slug} />;
case "article_category": case "article_category":
return <ArticleCategory slug={slug} />; return <ArticleCategory data={result.data} />;
case "article_detail": case "article_detail":
return <ArticleDetail slug={slug} />; return <ArticleDetail slug={result.data.slug} />;
default: default:
return notFound(); return notFound();
} }
} }

8
src/app/cart/page.tsx Normal file
View File

@@ -0,0 +1,8 @@
import CartHome from "@/components/cart/Home";
export default function Home() {
return (
<CartHome />
)
}

View File

@@ -0,0 +1,8 @@
import SendResult from "@/components/cart/Send";
export default function SendCartPage() {
return (
<SendResult />
)
}

8
src/app/tin-tuc/page.tsx Normal file
View File

@@ -0,0 +1,8 @@
import ArticleHome from "@/components/article/Home";
export default function Home() {
return (
<ArticleHome slug="" />
)
}

View File

@@ -1,4 +1,4 @@
export default function SendCart() { export default function Home() {
return( return(
<div className="cart-page container"> <div className="cart-page container">
<div className="text-center px-3"> <div className="text-center px-3">

View File

@@ -3,11 +3,11 @@ export default function SendCart() {
<div className="max-w-[656px] m-auto my-10"> <div className="max-w-[656px] m-auto my-10">
<div> <div>
<img <img
src="images/send-cart-image.png" src="images/send-cart-image.png"
alt="send-cart" alt="send-cart"
width={1} width={1}
height={1} height={1}
className="block m-auto w-auto h-auto" className="block m-auto w-auto h-auto"
/> />
<h1 className="mt-10 mb-5 font-600 text-center text-[40px] text-[#004BA4] leading-12"> <h1 className="mt-10 mb-5 font-600 text-center text-[40px] text-[#004BA4] leading-12">
{" "} {" "}

View File

@@ -0,0 +1,19 @@
export default function Banner() {
return (
<div className="swiper custom-dots overflow-hidden relative mb-6" id="js-category-banner">
<div className="swiper-wrapper">
<div className="swiper-slide">
<a href="">
<img
src="images/category/slide-category.png"
alt=""
width=""
height=""
className="block w-full lazy rounded-[24px]"
/>
</a>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,35 @@
export default function FAQ() {
return (
<div className="global-faq-container py-8 lg:py-12 text-18 leading-6">
<div className="text-center mb-8">
<p className="text-[#004BA4] text-20 lg:text-[40px] leading-5 lg:leading-[48px] mb-2 font-600">
Các câu hỏi thường gặp
</p>
<p className="max-w-[620px] m-auto text-16 leading-[21px] lg:text-[18px] lg:leading-6">
Nếu quý khách còn bất câu hỏi nào cần hỗ trợ, vui lòng liên hệ với
chúng tôi qua các số hotline đ đưc vấn giải đáp nhanh chóng
nhất.
</p>
</div>
<div className="faq-item js-faq-item relative">
<button
type="button"
className="bx bx-plus w-10 h-10 rounded-full bg-[#EAF1FF] border border-[#FBFBFB] text-24 absolute right-5 top-4"
aria-label="xem thêm"
/>
<p className="m-0 text-20 font-600">
thể kiểm tra tính tương thích của linh kiện trước khi đt không?
</p>
<div className="faq-answer mt-4">
<p> . Chúng tôi cung cấp công cụ "Build PC" đ bạn dễ dàng chọn kiểm
tra tính tương thích giữa CPU, mainboard, RAM, GPU, các linh kiện
khác trước khi mua hàng.
</p>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,195 @@
export default function ProductFilter() {
return (
<>
<p className="uppercase font-500 text-center border text-[#0678DB] leading-10 border-[#114CDD] rounded-[8px] mb-6">
Lọc sản phẩm
</p>
<div className="product-filter-group">
<div className="filter-category-group text-18 leading-6 mb-6">
<p className="font-600 mb-3"> Danh mục </p>
<p className="leading-9 bg-[#F0F5FF] font-500 relative pl-6 mb-1">
<i className="bg-[#0678DB] w-1 absolute top-0 left-0 bottom-0" />
<span> Tất cả </span>
</p>
<div className="flex flex-col gap-1">
<a
href="/pc-photo-editing"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> Photo Editing </h2>
</a>
<a
href="/pc-edit-render-video"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> PC Video Editing </h2>
</a>
<a
href="/hhpc-3d"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> PC 3D Design, Animation </h2>
</a>
<a
href="/hhpc-3d-render"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> PC Rendering </h2>
</a>
<a
href="/pc-visualization"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> PC Visualization </h2>
</a>
<a
href="/pc-architecture-cad"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> PC Architecture &amp; CAD </h2>
</a>
<a
href="/machine-learning-ai-tensorflow"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> PC Machine Learning / AI </h2>
</a>
<a
href="/server-may-ao-gia-lap"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> Server, Máy o, Giả Lập </h2>
</a>
<a
href="/pc-dep"
className="inline-flex items-start gap-1 py-[6px] hover:text-[#0678DB]"
>
{" "}
<i className="bxr bx-chevrons-right leading-[inherit]" />{" "}
<h2 className="inherit"> PC Đp </h2>
</a>
</div>
</div>
<div className="filter-item-group leading-[22px] text-16 mb-6">
<p className="font-600 text-18 mb-3"> Khoảng giá </p>
<div className="filter-list">
<a href="https://hoanghapc.vn/pc-workstation?max=10000000">
<span> Dưới 10 triệu </span>
<span> (3) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?max=20000000&min=15000000">
<span> 15 triệu - 20 triệu </span>
<span> (3) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?max=25000000&min=20000000">
<span> 20 triệu - 25 triệu </span>
<span> (1) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?max=30000000&min=25000000">
<span> 25 triệu - 30 triệu </span>
<span> (2) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?max=35000000&min=30000000">
<span> 30 triệu - 35 triệu </span>
<span> (2) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?max=40000000&min=35000000">
<span> 35 triệu - 40 triệu </span>
<span> (3) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?max=45000000&min=40000000">
<span> 40 triệu - 45 triệu </span>
<span> (13) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?max=50000000&min=45000000">
<span> 45 triệu - 50 triệu </span>
<span> (13) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?min=50000000">
<span> Trên 50 triệu </span>
<span> (85) </span>
</a>
</div>
</div>
<div className="filter-item-group leading-[22px] text-16 mb-6">
<p className="font-600 text-18 mb-3"> Thương hiệu </p>
<div className="filter-list">
<a href="https://hoanghapc.vn/pc-workstation?brand=6">
<span> GIGABYTE </span>
<span> (2) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?brand=19">
<span> HP </span>
<span> (1) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?brand=7">
<span> MSI </span>
<span> (1) </span>
</a>
</div>
</div>
<div className="filter-item-group leading-[22px] text-16 mb-6">
<p className="font-600 text-18 mb-3"> CPU </p>
<div className="filter-list">
<a href="https://hoanghapc.vn/pc-workstation?filter=397">
<span> Intel Core i7 </span>
<span> (13) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?filter=398">
<span> Intel Core i9 </span>
<span> (20) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?filter=399">
<span> Intel Xeon </span>
<span> (16) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?filter=402">
<span> AMD Ryzen 7 </span>
<span> (5) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?filter=403">
<span> AMD Ryzen 9 </span>
<span> (21) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?filter=404">
<span> AMD Ryzen Threadripper </span>
<span> (8) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?filter=637">
<span> Core Ultra 9 </span>
<span> (18) </span>
</a>
<a href="https://hoanghapc.vn/pc-workstation?filter=636">
<span> Core Ultra 7 </span>
<span> (10) </span>
</a>
</div>
</div>
<a href="/pc-workstation" className="js-partName block text-center uppercase bg-[linear-gradient(165.29deg,#259AFF_8.53%,#114CDD_93.19%)] text-white rounded-[30px] leading-5 text-16 font-500 p-[10px] mt-8">
Bỏ bộ lọc (1)
</a>
</div>
</>
)
}

View File

@@ -0,0 +1,17 @@
export default function Paging() {
return (
<div className="text-center mt-12">
<button
type="button"
className="mb-3 bg-btn text-white rounded-[30px] h-10 font-500 text-16 table max-w-[240px] w-full m-auto mb-3"
aria-label="Xem thêm"
>
TẢI THÊM
</button>
<p className="text-14 leading-[18px] m-0">
{" "}
Hiển thị 1 - 24 trên tổng số 124 sản phẩm{" "}
</p>
</div>
)
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,43 @@
export default function SortByCollection() {
return (
<div className="sort-group flex flex-wrap items-center justify-between gap-4 text-16 leading-[22px] border-b border-[#DEE4EC] pb-3 mb-5">
<p className="m-0"> Tổng 124 sản phẩm </p>
<div className="flex items-center gap-3">
<p className="m-0"> Lọc theo: </p>
<div className="group relative border border-[#D6DAE1] whitespace-nowrap leading-[38px] rounded-[30px] px-4 min-w-[170px]">
<p className="m-0 flex items-center justify-between cursor-pointer">
<span> Lựa chọn </span>{" "}
<i className="bx bx-chevron-down text-[#A0A5AC] text-18 transition-all group-hover:rotate-[-180deg]" />
</p>
<div className="absolute shadow border bg-white opacity-0 z-[-1] right-0 top-[100%] whitespace-nowrap transition group-hover:opacity-100 group-hover:z-[5] leading-[22px] p-1 border border-[#D6DAE1] rounded-[12px] w-full">
<a
href=""
className="bg-[#F2F2F2] block p-[6px_8px] rounded-[8px] mb-[1px] hover:bg-[#F2F2F2]"
>
Mức đ phổ biến
</a>
<a
href=""
className="block p-[6px_8px] rounded-[8px] mb-[1px] hover:bg-[#F2F2F2]"
>
Giá tăng dần
</a>
<a
href=""
className="block p-[6px_8px] rounded-[8px] mb-[1px] hover:bg-[#F2F2F2]"
>
Giá giảm dần
</a>
<a
href=""
className="block p-[6px_8px] rounded-[8px] mb-[1px] hover:bg-[#F2F2F2]"
>
% Giảm giá nhiều
</a>
</div>
</div>
</div>
</div>
)
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,9 @@
export default function ProductDetail() { type Props = {
slug: string;
};
export default async function ProductDetail({ slug }: Props) {
return ( return (
<> <>
<div className="product-detail-page container"> <div className="product-detail-page container">

View File

View File

@@ -0,0 +1,33 @@
// src/lib/articlePage.ts
import { categories } from "../data/categories";
export type ArticleResult =
| { type: "article_home"; data: any }
| { type: "article_category"; data: any }
| { type: "article_detail"; data: { slug: string } };
export function resolveArticlePage(slug: string): ArticleResult | null {
const url = "/" + slug;
// HOME
if (url === "/tin-tuc") {
return { type: "article_home", data: null };
}
// CATEGORY
const cats = categories.article.all_category.article;
for (const parent of cats) {
if (parent.url === url) {
return { type: "article_category", data: parent };
}
for (const child of parent.children ?? []) {
if (child.url === url) {
return { type: "article_category", data: child };
}
}
}
// DETAIL
return { type: "article_detail", data: { slug } };
}

View File

@@ -0,0 +1,32 @@
// hoanghapc/src/lib/productPage.ts
import { categories } from "../data/categories";
import { productList } from "../data/product-list";
export type ProductResult =
| { type: "product_category"; data: any }
| { type: "product_detail"; data: any };
export function resolveProductPage(slug: string): ProductResult | null {
const url = "/" + slug;
// CATEGORY
for (const parent of categories.product.all_category) {
if (parent.url === url) {
return { type: "product_category", data: parent };
}
for (const child of parent.children ?? []) {
if (child.url === url) {
return { type: "product_category", data: child };
}
}
}
// DETAIL
const product = productList.find(p => p.productUrl === url);
if (product) {
return { type: "product_detail", data: product };
}
return null;
}

View File

View File

@@ -1,77 +1,17 @@
// src/lib/slugMap.ts import { resolveArticlePage } from "./resolveArticlePage";
import { categories } from "@/data/categories"; import { resolveProductPage } from "./resolveProductPage";
export type PageType =
| "article_home"
| "article_category"
| "article_detail"
| "product_category"
| "product_detail";
export type SlugResult = export type SlugResult =
| { type: "article_home" } | ReturnType<typeof resolveArticlePage>
| { type: "article_category"; slug: string } | ReturnType<typeof resolveProductPage>;
| { type: "article_detail"; slug: string }
| { type: "product_category"; slug: string }
| { type: "product_detail"; slug: string };
export function findBySlug(rawSlug: string): SlugResult { export function findBySlug(slug?: string): SlugResult | null {
const slug = normalizeSlug(rawSlug); if (!slug) return null;
const url = "/" + slug;
/* 1. ARTICLE HOME */ // PRODUCT
if (url === "/tin-tuc") { const product = resolveProductPage(slug);
return { type: "article_home" }; if (product) return product;
}
/* 2. ARTICLE CATEGORY */ // ARTICLE
const articleCats = categories.article.all_category.article; return resolveArticlePage(slug);
for (const parent of articleCats) {
if (parent.url === url) {
return { type: "article_category", slug };
}
for (const child of parent.children ?? []) {
if (child.url === url) {
return { type: "article_category", slug };
}
}
}
/* 3. PRODUCT CATEGORY */
const productCats = categories.product.all_category;
for (const cat of productCats) {
if (cat.url === url) {
return { type: "product_category", slug };
}
for (const child of cat.children ?? []) {
if (child.url === url) {
return { type: "product_category", slug };
}
}
}
/* 4. PRODUCT DETAIL */
if (isProductDetailSlug(slug)) {
return { type: "product_detail", slug };
}
/* 5. ARTICLE DETAIL (fallback) */
return { type: "article_detail", slug };
}
/* ===============================
* HELPERS
* =============================== */
function normalizeSlug(slug: string) {
return slug.replace(/^\/+/, "").trim();
}
function isProductDetailSlug(slug: string) {
return (
slug.startsWith("hhpc-") ||
slug.startsWith("pc-") ||
slug.startsWith("man-hinh-")
);
} }