This commit is contained in:
2025-10-21 13:54:21 +07:00
parent 6c4d6c49fc
commit f89ff598a2
40 changed files with 40767 additions and 145 deletions

105
src/api/apiService.ts Normal file
View File

@@ -0,0 +1,105 @@
import { ArticleDataType } from "@/types/article";
import { ProductDataType } from "@/types/products";
const BASE_URL = process.env.NEXT_PUBLIC_API_BASE ?? "http://localhost:5000";
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
type QueryValue = string | number | boolean | null | undefined;
type QueryParams = Record<string, QueryValue | QueryValue[]>;
interface RequestOptions<TBody = unknown> {
method?: HttpMethod;
params?: QueryParams;
body?: TBody;
signal?: AbortSignal;
cache?: RequestCache;
next?: NextFetchRequestConfig;
}
export interface ListParams extends QueryParams {
limit?: number; // THÊM limit
page?: number;
offset?: number;
q?: string; // tuỳ chọn (search)
sort?: string;
categoryId?: number | string;
}
function buildURL(path: string, params?: QueryParams) {
const url = new URL(path, BASE_URL);
if (params) {
for (const [k, v] of Object.entries(params)) {
if (Array.isArray(v)) {
v.forEach((vv) => {
if (vv !== undefined && vv !== null) url.searchParams.append(k, String(vv));
});
} else if (v !== undefined && v !== null) {
url.searchParams.set(k, String(v));
}
}
}
return url.toString();
}
async function apiRequest<TResp = unknown, TBody = unknown>(
endpoint: string,
options: RequestOptions<TBody> = {}
): Promise<TResp> {
const {
method = "GET",
params,
body,
signal,
cache = "no-store",
next,
} = options;
const url = buildURL(endpoint, params);
const res = await fetch(url, {
method,
headers: { "Content-Type": "application/json" },
body: body && method !== "GET" ? JSON.stringify(body) : undefined,
signal,
cache,
next,
});
if (!res.ok) {
const msg = await res.text().catch(() => res.statusText);
throw new Error(`API ${method} ${endpoint} failed: ${res.status} ${msg}`);
}
return res.json() as Promise<TResp>;
}
// API cho bài viết
export const fetchListArticles = (params?: ListParams) =>
apiRequest<{ list: ArticleDataType[]; total?: number }>("/article", {
params,
});
// GET /articleDetails?path=:slug
export const fetchArticleDetail = (slug: string) =>
apiRequest<ArticleDataType>("/articleDetails", {
params: { path: slug },
});
// API cho công việc
export const fetchListProduct = (params?: ListParams) =>
apiRequest<{ list: ProductDataType[]; total?: number }>("/product", {
params,
});
export const fetchProductDetail = (slug: string) =>
apiRequest<ProductDataType>("/productDetails", {
params: { path: slug },
});
export const fetchProductsByCategoryId = (
categoryId: number | string,
params?: Omit<ListParams, "categoryId">
) =>
fetchListProduct({
...params,
categoryId,
});

View File

@@ -0,0 +1,85 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import page_category from "../../../data/page_category.json";
const PageCategory = () => {
return (
<div className="product-page container">
<div className="global-breadcrumb">
<ol className="ul clearfix">
<li>
<Link href="/" className="nopad-l">
<span>Trang chủ</span>
</Link>
</li>
{page_category.current_category.path.path.map((path, index) => (
<li key={index}>
<Link href={path.url} className="nopad-l">
<span>{path.name}</span>
</Link>
</li>
))}
</ol>
</div>
{Array.isArray(page_category.current_category.children) &&
page_category.current_category.children.length > 0 ? (
<div className="child-collection-group bg-white">
{page_category.current_category.children.map((cate) => (
<Link key={cate.id} href={cate.url}>
{cate.title}
</Link>
))}
</div>
) : (
<></>
)}
<div className="product-col-group d-flex flex-wrap">
<div className="col-left filter-group">
<div className="bg-white"></div>
</div>
<div className="col-right">
<div className="title-group d-flex flex-wrap align-items-center justify-content-between">
<div className="font-600">
<h2 className="title">{page_category.current_category.name}</h2>
<span> (Tổng {page_category.product_count} sản phẩm)</span>
</div>
<div className="sort-by-group d-flex align-items-center">
<select>
<option value="{{ page.current_category.request_path }}">
Sắp xếp theo
</option>
</select>
<Link
href="javascript:void(0)"
className="{% if global.user_settings.product_display_type == 'grid' %}active{% endif %} fa fa-th-large"
></Link>
<Link
href="javascript:void(0)"
className="{% if global.user_settings.product_display_type == 'list' %}active{% endif %} fa fa-th-list"
></Link>
</div>
</div>
<div className="p-container {% if global.user_settings.product_display_type == 'list' %} p-container-list {% endif %}"></div>
<div className="paging">
<a href="{{ _item.url }}" className="active"></a>
</div>
<div className="tag-group">
<a href="">
<i className="fa fa-tag"></i>
</a>
</div>
</div>
</div>
</div>
);
};
export default PageCategory;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -1,26 +0,0 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

View File

@@ -0,0 +1,101 @@
"use client";
import { useState, useEffect } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Scrollbar } from "swiper/modules";
import { ArticleDataType } from "@/types/article";
import { fetchListArticles } from "@/api/apiService";
import ItemArticle from "@/components/article/itemArticle";
type ArticleListResp = { list: ArticleDataType[]; total?: number };
export default function ArticleHome() {
const [articletList, setProductList] = useState<ArticleListResp | null>(null);
const [loadingUi, setLoadingUI] = useState(true);
useEffect(() => {
const getProductList = async () => {
try {
const data = await fetchListArticles({ _limit: 10, _page: 1 });
setProductList(data);
} catch (error) {
console.error("Failed to fetch jobs:", error);
} finally {
setLoadingUI(false);
}
};
getProductList();
}, []);
return (
<div className="bg-white" id="js-home-art-container">
<div
className="container global-footer-art d-flex flex-wrap"
style={{ padding: "24px 6px 33px 6px" }}
>
<div className="item-left">
<h2 className="title">TIN NỔI BẬT TRONG NGÀY</h2>
<div className="d-flex flex-wrap" style={{ minHeight: "360px" }}>
{articletList &&
Array.isArray(articletList) &&
articletList.length > 0 ? (
<div className="big-item footer-art-big" id="js-featured-big">
{articletList.slice(0, 1).map((article) => (
<div key={article.id}>
<ItemArticle article={article} />
</div>
))}
</div>
) : (
<div className="big-item footer-art-big text-center text-2xl py-[50px] font-bold italic">
Danh sách tin tức đang cập nhật
</div>
)}
{articletList &&
Array.isArray(articletList) &&
articletList.length > 0 ? (
<div className="small-items">
{articletList.slice(1, 5).map((article) => (
<ItemArticle key={article.id} article={article} />
))}
</div>
) : (
<div className="big-item footer-art-big text-center text-2xl py-[50px] font-bold italic">
Danh sách tin tức đang cập nhật
</div>
)}
</div>
</div>
<div className="item-right">
<h2 className="title">TIN KHUYẾN MÃI</h2>
{articletList &&
Array.isArray(articletList) &&
articletList.length > 0 ? (
<div className="footer-art-big">
<Swiper
modules={[Navigation, Scrollbar]}
navigation
loop
autoplay
spaceBetween={10}
slidesPerView={1}
>
{articletList.map((article) => (
<SwiperSlide key={article.id}>
<ItemArticle article={article} />
</SwiperSlide>
))}
</Swiper>
</div>
) : (
<div className="big-item footer-art-big text-center text-2xl py-[50px] font-bold italic">
Danh sách tin tức đang cập nhật
</div>
)}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,80 @@
"use client";
import { useState, useEffect } from "react";
import Image from "next/image";
import Link from "next/link";
import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Scrollbar } from "swiper/modules";
import component from "../../../data/component.json";
import { fetchListProduct } from "@/api/apiService";
import ProductItemSlider from "@/components/product/ProductItemSlider";
import { ProductDataType } from "@/types/products";
type ProductListResp = { list: ProductDataType[]; total?: number };
export default function BoxGroupProductCategory() {
const [productList, setProductList] = useState<ProductListResp | null>(null);
useEffect(() => {
const getProductList = async () => {
try {
const data = await fetchListProduct({ _limit: 10, _page: 1 });
setProductList(data);
} catch (error) {
console.error("Failed to fetch jobs:", error);
} finally {
}
};
getProductList();
}, []);
const featuredCategories =
component.product.all_category?.filter((c) => c.is_featured == "1") ?? [];
return (
<div className="box-group-category">
{featuredCategories.map((component) => (
<div className="home-product-group" key={component.id}>
<div className="title-group text-uppercase d-flex align-items-center justify-content-between">
<div className="d-flex align-items-center gap-[10px]">
<h2 className="title">{component.title}</h2>
{component.children.slice(0, 3).map((children) => (
<Link href={children.url} key={children.id}>
<h3>{children.title}</h3>
</Link>
))}
</div>
<div className="blue d-flex align-items-center gap-[5px]">
<Link href={component.url} className="">
XEM THÊM
</Link>
<i className="fa-solid fa-right-long"></i>
</div>
</div>
<div className="home-product-holder">
{productList &&
Array.isArray(productList) &&
productList.length > 0 ? (
<Swiper
modules={[Navigation, Scrollbar]}
navigation
loop
spaceBetween={10}
slidesPerView={5}
>
{productList.map((product) => (
<SwiperSlide key={product.id}>
<ProductItemSlider product={product} />
</SwiperSlide>
))}
</Swiper>
) : (
<div className="text-center text-2xl py-[50px] font-bold italic">
Danh sách sản phẩm đang cập nhật
</div>
)}
</div>
</div>
))}
</div>
);
}

View File

@@ -0,0 +1,112 @@
"use client";
import { useState, useEffect } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Scrollbar } from "swiper/modules";
import { fetchListProduct } from "@/api/apiService";
import ProductItemSlider from "@/components/product/ProductItemSlider";
import { ProductDataType } from "@/types/products";
type ProductListResp = { list: ProductDataType[]; total?: number };
export default function ListProductBestSale() {
const [productList, setProductList] = useState<ProductListResp | null>(null);
const [loadingUi, setLoadingUI] = useState(true);
useEffect(() => {
const getProductList = async () => {
try {
const data = await fetchListProduct({ _limit: 10, _page: 1 });
setProductList(data);
} catch (error) {
console.error("Failed to fetch jobs:", error);
} finally {
setLoadingUI(false);
}
};
getProductList();
}, []);
return (
<div
className="home-product-bestsale-container lazy"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/static/assets/htpstore/images/home-product-bg.png")',
}}
id="js-bestsale-container"
>
<div className="text-center text-uppercase titlt-group">
<h2 className="title">TOP SẢN PHẨM BÁN CHẠY</h2>
<div className="pro-cat-list">
<a href="">
<h3>pc gaming</h3>
</a>
<a href="">
<h3>laptop gaming</h3>
</a>
<a href="">
<h3>máy in canon</h3>
</a>
<a href="">
<h3>màn hình máy tính</h3>
</a>
<a href="">
<h3>màn hình gaming</h3>
</a>
</div>
</div>
{loadingUi ? (
<div
id="js-bestsale-holder"
style={{ minHeight: "390px", position: "relative" }}
>
{[...Array(4)].map((_, index) => (
<div className="item-job" key={index}>
<div className="job-left flex items-center">
<div className="name line-clamp-1 bg-gray-200 h-[21px] w-[300px] mr-[15px] animate-pulse"></div>
<div className="time bg-gray-200 h-[20px] w-[120px] block animate-pulse"></div>
</div>
<div className="job-right flex items-center">
<div className="localhost bg-gray-200 h-[21px] w-[60px] mr-[20px] block animate-pulse"></div>
<div className="more bg-gray-200 h-[21px] block w-[120px] animate-pulse"></div>
</div>
</div>
))}
</div>
) : (
<div
id="js-bestsale-holder"
style={{ minHeight: "390px", position: "relative" }}
>
{productList &&
Array.isArray(productList) &&
productList.length > 0 ? (
<Swiper
modules={[Navigation, Scrollbar]}
navigation
loop
spaceBetween={10}
slidesPerView={5}
>
{productList.map((product) => (
<SwiperSlide key={product.id}>
<ProductItemSlider product={product} />
</SwiperSlide>
))}
</Swiper>
) : (
<div className="text-center text-2xl py-[50px] font-bold italic">
Danh sách sản phẩm đang cập nhật
</div>
)}
</div>
)}
<a href="/san-pham-ban-chay" className="view-more">
XEM THÊM <i className="fa fa-long-arrow-right"></i>
</a>
</div>
);
}

View File

@@ -0,0 +1,47 @@
"use client";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay, Pagination } from "swiper/modules";
const slides = [
{
href: "/",
src: "https://demopc8.hurasoft.com/static/assets/htpstore/images/demopc8-banner-slider-homepage-test-1.png",
alt: "Banner 1",
},
{
href: "/",
src: "https://demopc8.hurasoft.com/static/assets/htpstore/images/demopc8-banner-slider-homepage-test-1.png",
alt: "Banner 2",
},
];
export default function HomeSlider() {
return (
<div id="js-home-slider" className="slider-group">
<Swiper
modules={[Autoplay, Pagination]}
autoplay={{ delay: 3000, disableOnInteraction: false }}
pagination={{ clickable: true }}
loop
slidesPerView={1}
className="custom-dots"
>
{slides.map((s, i) => (
<SwiperSlide key={i} className="item">
<a href={s.href} aria-label={s.alt}>
{/* Dùng <img> để không phụ thuộc cấu hình next/image */}
<img
src={s.src} // Đặt file ảnh vào /public/
width={692}
height={492}
alt={s.alt}
loading="lazy"
style={{ display: "block", width: "100%", height: "auto" }}
/>
</a>
</SwiperSlide>
))}
</Swiper>
</div>
);
}

View File

@@ -1,15 +1,15 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import "@fortawesome/fontawesome-free/css/all.min.css";
import { Mulish } from "next/font/google";
import "bootstrap/dist/css/bootstrap.min.css";
import "@/styles/style.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
const mulish = Mulish({
subsets: ["latin", "vietnamese"],
display: "swap",
variable: "--font-mulish",
});
export const metadata: Metadata = {
@@ -23,11 +23,14 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<html
suppressHydrationWarning
className={`${mulish.variable} font-sans antialiased`}
>
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
);

View File

@@ -1,103 +1,90 @@
import Image from "next/image";
import Link from "next/link";
import HomeSlider from "./homepage/slider";
import ListProductBestSale from "./homepage/ListProductBestSale";
import BoxGroupProductCategory from "./homepage/BoxGroupProductCategory";
import ArticleHome from "./homepage/ArticleHome";
export default function Home() {
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="homepage">
<div className="container">
<div className="home-banner-group hover-img">
<HomeSlider />
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
<div className="banner-right-group">
<Link href="/">
<Image
src="https://demopc8.hurasoft.com/static/assets/htpstore/images/demopc8-banner-slider-homepage-test-1.png"
width="692"
height="492"
alt="Banner"
className="lazy"
/>
</Link>
<Link href="/">
<Image
src="https://demopc8.hurasoft.com/static/assets/htpstore/images/demopc8-banner-slider-homepage-test-1.png"
width="692"
height="492"
alt="Banner"
className="lazy"
/>
</Link>
</div>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
<div className="home-banner-under hover-img">
<Link href="/">
<Image
src="https://demopc8.hurasoft.com/static/assets/htpstore/images/demopc8-banner-slider-homepage-test-1.png"
width="692"
height="492"
alt="Banner"
className="lazy"
/>
</Link>
<Link href="/">
<Image
src="https://demopc8.hurasoft.com/static/assets/htpstore/images/demopc8-banner-slider-homepage-test-1.png"
width="692"
height="492"
alt="Banner"
className="lazy"
/>
</Link>
<Link href="/">
<Image
src="https://demopc8.hurasoft.com/static/assets/htpstore/images/demopc8-banner-slider-homepage-test-1.png"
width="692"
height="492"
alt="Banner"
className="lazy"
/>
</Link>
</div>
<ListProductBestSale />
<BoxGroupProductCategory />
{/* <div className="home-product-group">
<div className="title-group text-uppercase clearfix">
<h2 className="title">{{ _item.title }}</h2>
<div>
{% for _item_child in _item.children | limit: 4 %}{% if _item_child.is_featured == 1 %}
<a href="{{ _item_child.url }}"><h3>{{ _item_child.title }}</h3></a>
{% endif %}{% endfor %}
<a href="{{ _item.url }}" className="blue">XEM THÊM <i className="fa fa-long-arrow-right"></i></a>
</div>
</div>
<div className="home-product-holder" id="js-product-{{ _item.id }}"><!-- ajax --> </div>
</div> */}
<ArticleHome />
</div>
</div>
);
}

300
src/components/Footer.tsx Normal file
View File

@@ -0,0 +1,300 @@
"use client";
import Link from "next/link";
import { useCallback, useEffect, useMemo, useState } from "react";
export default function GlobalFooter() {
// Newsletter state
const [email, setEmail] = useState("");
const [sending, setSending] = useState(false);
const [sent, setSent] = useState<null | "ok" | "err">(null);
// So sánh sản phẩm (demo): danh sách id đang chọn
const [compareOpen, setCompareOpen] = useState(false);
const compareItems = useMemo<string[]>(() => [], []); // TODO: truyền từ props hoặc context
const subscribeNewsletter = useCallback(async () => {
if (!email.trim()) return;
try {
setSending(true);
// TODO: gọi API thực tế của bạn ở đây
// await fetch("/api/newsletter", { method: "POST", body: JSON.stringify({ email }) });
await new Promise((r) => setTimeout(r, 600));
setSent("ok");
setEmail("");
} catch {
setSent("err");
} finally {
setSending(false);
}
}, [email]);
const compareClose = useCallback(() => setCompareOpen(false), []);
const compareLink = useCallback(() => {
// TODO: điều hướng đến trang so sánh thực tế, ví dụ: /so-sanh?ids=1,2,3
// router.push(`/so-sanh?ids=${compareItems.join(",")}`);
alert("Đi đến trang so sánh (demo).");
}, [compareItems]);
const goTop = useCallback(() => {
window.scrollTo({ top: 0, behavior: "smooth" });
}, []);
// Ví dụ: tự mở khối so sánh khi có item
useEffect(() => {
if (compareItems.length > 0) setCompareOpen(true);
}, [compareItems]);
return (
<>
<div className="global-footer-container bg-white">
{/* Newsletter */}
<div className="footer-newsletter-group text-white">
<p className="text-20 font-800 text-center">
ĐĂNG NHẬN EMAIL THÔNG BÁO KHUYẾN MẠI HOẶC Đ ĐƯC VẤN MIỄN PHÍ
</p>
<div className="newsletter-form position-relative">
<input
type="email"
id="js-email-newsletter"
className="newsletter-input"
placeholder="Nhập email của bạn"
value={email}
onChange={(e) => setEmail(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && subscribeNewsletter()}
/>
<button
type="button"
onClick={subscribeNewsletter}
disabled={sending}
>
{sending ? "ĐANG GỬI..." : "GỬI"}
</button>
</div>
{sent === "ok" && (
<p className="text-center mt-2" role="status">
Đăng thành công!
</p>
)}
{sent === "err" && (
<p className="text-center mt-2 text-danger" role="alert">
lỗi xảy ra, vui lòng thử lại.
</p>
)}
</div>
{/* Social / policies */}
<div className="footer-social-group container text-15 font-300">
<div className="row">
<div className="col-3 d-flex flex-wrap align-items-center">
<i className="icons icon-truck" aria-hidden />
<p className="text">
<b>CHÍNH SÁCH GIAO HÀNG</b>
<span>Nhận hàng thanh toán tại nhà</span>
</p>
</div>
<div className="col-3 d-flex flex-wrap align-items-center">
<i className="icons icon-trade" aria-hidden />
<p className="text">
<b>ĐI TRẢ DỄ DÀNG</b>
<span>Dùng thử trong vòng 3 ngày</span>
</p>
</div>
<div className="col-3 d-flex flex-wrap align-items-center">
<i className="icons icon-payment" aria-hidden />
<p className="text">
<b>THANH TOÁN TIỆN LỢI</b>
<span>Trả tiền mặt, CK, trả góp 0%</span>
</p>
</div>
<div className="col-3 d-flex flex-wrap align-items-center">
<i className="icons icon-comment" aria-hidden />
<p className="text">
<b>HỖ TRỢ NHIỆT TÌNH</b>
<span> vấn, giải đáp mọi thắc mắc</span>
</p>
</div>
</div>
</div>
{/* Info columns */}
<div className="footer-info-group">
<div className="container">
<div className="row">
<div className="col-3">
<p className="title">GIỚI THIỆU HTPSTORE</p>
<div className="info-list">
<Link href="/gioi-thieu">Giới thiệu công ty</Link>
<Link href="/lien-he">Thông tin liên hệ</Link>
<Link href="/tuyen-dung">Thông tin tuyển dụng</Link>
<Link href="/tin-tuc">Tin tức</Link>
</div>
</div>
<div className="col-3">
<p className="title">HỖ TRỢ KHÁCH HÀNG</p>
<div className="info-list">
<Link href="/huong-dan-mua-hang-truc-tuyen">
Hướng dẫn mua hàng trực tuyến
</Link>
<Link href="/huong-dan-thanh-toan">Hướng dẫn thanh toán</Link>
<Link href="/huong-dan-mua-hang-tra-gop">
Hướng dẫn mua hàng trả góp
</Link>
<Link href="/in-hoa-don-dien-tu">In hóa đơn điện tử</Link>
<Link href="/gui-yeu-cau-bao-hanh">Gửi yêu cầu bảo hành</Link>
<Link href="/gop-y-khieu-nai">Góp ý, Khiếu Nại</Link>
<Link href="/?show_version=mobile" className="view-mobile">
Xem giao diện bản mobile
</Link>
</div>
</div>
<div className="col-3">
<p className="title">CHÍNH SÁCH CHUNG</p>
<div className="info-list">
<Link href="/chinh-sach-quy-dinh-chung">
Chính sách, quy đnh chung
</Link>
<Link href="/chinh-sach-van-chuyen">
Chính sách vận chuyển
</Link>
<Link href="/chinh-sach-bao-hanh">Chính sách bảo hành</Link>
<Link href="/chinh-sach-doi-tra-va-hoan-tien">
Chính sách đi trả hoàn tiền
</Link>
<Link href="/chinh-sach-hang-chinh-hang">
Chính sách hàng chính hãng
</Link>
<Link href="/bao-mat-thong-tin-khach-hang">
Bảo mật thông tin khách hàng
</Link>
</div>
</div>
<div className="col-3">
<p className="title">THÔNG TIN KHUYẾN MẠI</p>
<div className="info-list">
<Link href="/khuyen-mai">Thông tin khuyến mại</Link>
<Link href="/san-pham-xa-hang">Sản phẩm khuyến mại</Link>
<Link href="/san-pham-ban-chay">Sản phẩm bán chạy</Link>
<Link href="/san-pham-moi">Sản phẩm mới</Link>
</div>
</div>
</div>
</div>
</div>
</div>
{/* Copyright */}
<div className="container" style={{ padding: "30px 10px" }}>
<p style={{ color: "#0033CC", fontWeight: 300, margin: 0 }}>
© 2021 HTPSTORE. All rights reserved
<br />
Công Ty TNHH Thương Mại Phát Triển Công Nghệ Hưng Thịnh Phát
<br />
Đa chỉ: Tòa Hope Residences H5-TM3, Số 1 Nguyễn Lam, Phường Phúc
Đng, Quận Long Biên, Thành phố Nội.
<br />
Giấy phép đăng kinh doanh: 0109802587 do Sở Kế Hoạch Đu
Tp. Nội cấp
<br />
Email:{" "}
<a href="mailto:linhbk@htpstore.com.vn">linhbk@htpstore.com.vn</a>.
Điện thoại: 0243.206.8456 - Hotline: 0965.530.489
</p>
</div>
{/* Thông báo thành công (ẩn/hiện bằng state nếu cần) */}
<div className="success-form" style={{ display: "none" }}>
<div className="content-container">
<div className="success-checkmark">
<div className="check-icon">
<span className="icon-line line-tip"></span>
<span className="icon-line line-long"></span>
<div className="icon-circle"></div>
<div className="icon-fix"></div>
</div>
</div>
<div className="text-center content-text text-24">
Thêm sản phẩm vào giỏ hàng thành công!
</div>
</div>
</div>
{/* So sánh sản phẩm */}
{compareOpen && (
<div className="global-compare-group">
<div className="title text-22 text-white d-flex align-items-center justify-content-between font-600">
<p className="m-0">SO SÁNH SẢN PHẨM</p>
<button
type="button"
className="close-compare text-white fa fa-times"
onClick={compareClose}
aria-label="Đóng so sánh"
/>
</div>
<div
className="text-center red mt-2 text-18 font-500"
id="js-alert"
></div>
<div className="pro-compare-holder">
<div className="compare-pro-holder clearfix" id="js-compare-holder">
{/* TODO: render danh sách sản phẩm so sánh ở đây */}
</div>
<button type="button" className="btn-compare" onClick={compareLink}>
SO SÁNH
</button>
</div>
</div>
)}
{/* Tooltip container (tuỳ logic, bạn có thể render portal) */}
<div id="tooltip" />
{/* Fixed right buttons */}
<div className="global-fixed-right">
<a
href="#"
target="_blank"
rel="nofollow"
className="fa fa-facebook"
aria-label="Facebook"
/>
<a
href="#"
target="_blank"
rel="nofollow"
className="fa fa-youtube-play"
aria-label="YouTube"
/>
<Link
href="/lien-he"
target="_blank"
className="fa fa-envelope"
aria-label="Liên hệ"
/>
<Link
href="/gioi-thieu"
target="_blank"
className="fa fa-comment"
aria-label="Giới thiệu"
/>
<button
type="button"
className="fa fa-angle-up"
id="js-goTop"
aria-label="Lên đầu trang"
onClick={goTop}
style={{ background: "transparent", border: 0, cursor: "pointer" }}
/>
</div>
</>
);
}

945
src/components/Header.tsx Normal file
View File

@@ -0,0 +1,945 @@
"use client";
import Link from "next/link";
import Image from "next/image";
import { usePathname } from "next/navigation";
import { useMemo, useState } from "react";
export type Category = {
id?: string | number;
url: string;
title: string;
thumbnail?: string; // tương đương _item.thumnail
children?: Category[]; // cấp 2 -> cấp 3 -> cấp 4
};
export default function GlobalHeader() {
const pathname = usePathname();
const isHome = pathname === "/" || pathname === "";
return (
<>
<div className="global-header-container">
<div className="header-top-group">
<div className="container d-flex align-items-center justify-content-between text-13 position-relative">
<div className="item-left">
<a
href={`tel:0243.206.8456`}
className="bg-linear d-inline-block"
>
<i className="fa fa-phone fa-rotate-270" /> CSKHL{" "}
<b>0243.206.8456</b>
</a>
<div className="header-support d-inline-block">
<p className="bg-linear m-0">
<i className="fa fa-headphones" /> Hỗ trợ khách hàng
</p>
{/* include 'other/header_hotline' → thay bằng block custom nếu cần */}
<div className="header-hotline-dropdown">
{/* bạn có thể render danh sách hotline tại đây */}
</div>
</div>
</div>
<div className="item-right">
<Link href="/san-pham-da-xem">
<i className="fa fa-check" /> Sản phẩm đã xem
</Link>
<Link href="/tin-tuc">
<i className="fa-solid fa-newspaper" /> Tin tức nổi bật
</Link>
</div>
</div>
</div>
<div className="global-header-main-container">
<div className="container header-main-group position-relative">
<div className="header-middle-group d-flex flex-wrap align-items-center">
<Link
href="/"
className="logo text-center block"
aria-label="Trang chủ"
>
{/* Bạn có thể dùng <img> thường nếu không muốn cấu hình next/image */}
<Image
src="https://demopc8.hurasoft.com/static/assets/htpstore/images/logo_logo.png"
alt="Logo"
width={190}
height={79}
className="inline-block h-auto w-auto"
priority
/>
</Link>
{/* Search */}
<div className="header-search-group position-relative">
<form
method="get"
action="/tim"
name="search"
className="bg-white d-block position-relative overflow-hidden"
>
<input
type="text"
id="js-seach-input"
name="q"
placeholder="Nhập tên sản phẩm, từ khóa cần tìm."
autoComplete="off"
className="text-search"
/>
<button
type="submit"
className="btn-search fa fa-search"
aria-label="Tìm kiếm"
/>
</form>
<div
className="autocomplete-suggestions"
id="js-seach-result"
/>
</div>
{/* Right info */}
<div className="header-right-group d-flex align-items-center justify-content-between text-15 font-300 text-white">
<div className="item">
<i className="icons icon-phone" />
<p className="text">
<span>Hotline</span>
<b className="text-18 d-block font-800">0965.530.489</b>
</p>
</div>
<div className="item">
<i className="icons icon-user" />
<p className="text">
<Link href="/dang-ky">Đăng </Link>
<Link href="/dang-nhap">Đăng nhập</Link>
</p>
</div>
<Link href="/cart" className="item item-cart">
<i className="icons icon-cart" />
<span className="cart-count js-cart-count">0</span>
</Link>
</div>
</div>
<div className="header-bottom-group d-flex flex-wrap align-items-center">
{/* Category Menu */}
<div className="header-menu-group ">
<p className="title text-18 font-700 text-white m-0 text-center">
<i className="fa fa-bars" /> DANH MỤC SẢN PHẨM
</p>
<div
className={`menu-holder ${!isHome ? "menu-other-page" : ""}`}
>
<div className="item">
<a href="/man-hinh-may-tinh" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_9_1688117425.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_9_1688117425.png")',
}}
></span>
<span className="cat-title">Màn Hình Máy Tính</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/man-hinh-theo-kich-thuoc" className="cat-2">
Màn Hình Theo Kích Thước
</a>
<div className="cat-3-holder">
<a href="/man-hinh-may-tinh-17-inch-21-inch">
17 inch - 21.5 inch
</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-may-tinh-22-inch-24-inch">
22 inch - 24 inch
</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-may-tinh-25-inch-27-inch">
25 inch - 27 inch
</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-may-tinh-28-inch-32-inch">
28 inch - 32 inch
</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-may-tinh-32-inch-49-inch">
32 inch - 49 inch
</a>
</div>
</div>
<div className="sub-item">
<a href="/man-hinh-theo-hang" className="cat-2">
Màn Hình Theo Hãng
</a>
<div className="cat-3-holder">
<a href="/man-hinh-benq">Màn Hình BenQ</a>
<div className="cat-4-holder">
<a href="/ben-q-hang">Ben Q Hãng</a>
</div>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-lg">Màn Hình LG</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-viewsonic">Màn Hình Viewsonic</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-samsung">Màn Hình Samsung</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-hkc">Màn Hình HKC</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-dell">Màn Hình Dell</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-hp">Màn Hình HP</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-asus">Màn Hình Asus</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-aoc">Màn Hình AOC</a>
</div>
</div>
<div className="sub-item">
<a href="/man-hinh-theo-nhu-cau" className="cat-2">
Màn Hình Theo Nhu Cầu
</a>
<div className="cat-3-holder">
<a href="/man-hinh-choi-game">Màn Hình Chơi Game</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-do-hoa-thiet-ke">
Màn Hình Đ Họa,Thiết Kế
</a>
</div>
<div className="cat-3-holder">
<a href="/man-hinh-van-phong">Màn Hình Văn Phòng</a>
</div>
</div>
</div>
</div>
<div className="item">
<a href="/pc-workstation" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_1_1663215484.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_1_1663215484.png")',
}}
></span>
<span className="cat-title">PC, Workstation</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a
href="/hoang-ha-workstation-intel-core-i7-i9"
className="cat-2"
>
HH WORKSTATION - INTEL CORE
</a>
</div>
<div className="sub-item">
<a
href="/hoang-ha-workstation-intel-xeon"
className="cat-2"
>
HH WORKSTATION - INTEL XEON
</a>
</div>
<div className="sub-item">
<a
href="/hoang-ha-workstation-amd-ryzen"
className="cat-2"
>
HH WORKSTATION - AMD RYZEN
</a>
</div>
<div className="sub-item">
<a
href="/hhpc-workstation-render-edit-video"
className="cat-2"
>
WORKSTATION RENDER - VIDEO
</a>
</div>
<div className="sub-item">
<a href="/server-may-ao-gia-lap" className="cat-2">
SERVER - MÁY O - GIẢ LẬP
</a>
</div>
<div className="sub-item">
<a href="/pc-dong-bo" className="cat-2">
MÁY TÍNH ĐNG BỘ
</a>
</div>
<div className="sub-item">
<a href="/may-tinh-van-phong" className="cat-2">
MÁY TÍNH VĂN PHÒNG
</a>
</div>
<div className="sub-item">
<a href="/pc-gaming" className="cat-2">
PC GAMING
</a>
</div>
<div className="sub-item">
<a
href="/machine-learning-ai-tensorflow"
className="cat-2"
>
MACHINE LEARNING / AI
</a>
</div>
</div>
</div>
<div className="item">
<a href="/gaming-gear" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_25_1663215532.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_25_1663215532.png")',
}}
></span>
<span className="cat-title">Gaming Gear</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/ban-phim" className="cat-2">
Bàn Phím
</a>
</div>
<div className="sub-item">
<a href="/chuot-may-tinh" className="cat-2">
Chuột Máy Tính
</a>
</div>
<div className="sub-item">
<a href="/ban-di-chuot-gaming" className="cat-2">
Bàn Di Chuột
</a>
</div>
<div className="sub-item">
<a href="/tai-nghe" className="cat-2">
Tai Nghe
</a>
</div>
<div className="sub-item">
<a href="/ban-may-tinh" className="cat-2">
Bàn Gaming
</a>
</div>
<div className="sub-item">
<a href="/loa-vi-tinh" className="cat-2">
Loa Vi Tính
</a>
</div>
<div className="sub-item">
<a href="/ghe-game" className="cat-2">
Ghế Gaming
</a>
</div>
<div className="sub-item">
<a href="/phu-kien" className="cat-2">
Phụ Kiện
</a>
</div>
<div className="sub-item">
<a href="/thiet-bi-studio-stream" className="cat-2">
Thiết Bị Studio, Stream
</a>
</div>
</div>
</div>
<div className="item">
<a href="/cpu-bo-vi-xu-ly" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_2_1663215326.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_2_1663215326.png")',
}}
></span>
<span className="cat-title">CPU - Bộ Vi Xử </span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/cpu-intel" className="cat-2">
CPU Intel
</a>
<div className="cat-3-holder">
<a href="/cpu-intel-core-i3">Intel Core i3</a>
</div>
<div className="cat-3-holder">
<a href="/cpu-intel-core-i5">Intel Core i5</a>
</div>
<div className="cat-3-holder">
<a href="/cpu-intel-core-i7">Intel Core i7</a>
</div>
<div className="cat-3-holder">
<a href="/cpu-intel-core-i9">Intel Core i9</a>
</div>
<div className="cat-3-holder">
<a href="/cpu-intel-xeon">Intel Xeon</a>
</div>
</div>
<div className="sub-item">
<a href="/cpu-amd" className="cat-2">
CPU AMD
</a>
<div className="cat-3-holder">
<a href="/cpu-amd-ryzen-3">AMD Ryzen 3</a>
</div>
<div className="cat-3-holder">
<a href="/cpu-amd-ryzen-5">AMD Ryzen 5</a>
</div>
<div className="cat-3-holder">
<a href="/cpu-amd-ryzen-7">AMD Ryzen 7</a>
</div>
<div className="cat-3-holder">
<a href="/cpu-amd-ryzen-9">AMD Ryzen 9</a>
</div>
<div className="cat-3-holder">
<a href="/amd-ryzen-threadripper">
AMD Ryzen Threadripper
</a>
</div>
</div>
</div>
</div>
<div className="item">
<a href="/vga-card-man-hinh" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_6_1663215223.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_6_1663215223.png")',
}}
></span>
<span className="cat-title">VGA - Card Màn Hình</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/vga-nvidia" className="cat-2">
VGA NVIDIA
</a>
<div className="cat-3-holder">
<a href="/gtx-1050-1050ti-series">
NVIDIA GTX 1050/1050Ti
</a>
</div>
<div className="cat-3-holder">
<a href="/gtx-1060">NVIDIA GTX 1060</a>
</div>
<div className="cat-3-holder">
<a href="/gtx-1660-1660ti-series">
NVIDIA GTX 1660/1660Ti
</a>
</div>
<div className="cat-3-holder">
<a href="/nvidia-gtx-1650">NVIDIA GTX 1650 / Super</a>
</div>
<div className="cat-3-holder">
<a href="/rtx-2060">NVIDIA RTX 2060 / Super</a>
</div>
<div className="cat-3-holder">
<a href="/rtx-2070">NVIDIA RTX 2070 / Super</a>
</div>
<div className="cat-3-holder">
<a href="/rtx-2080">NVIDIA RTX 2080 / Super</a>
</div>
<div className="cat-3-holder">
<a href="/rtx-2080ti">NVIDIA RTX 2080Ti</a>
</div>
<div className="cat-3-holder">
<a href="/nvidia-quadro">NVIDIA Quadro</a>
</div>
<div className="cat-3-holder">
<a href="/nvidia-rtx-3060-ti">NVIDIA RTX 3060 Ti</a>
</div>
<div className="cat-3-holder">
<a href="/nvidia-rtx-3070">NVIDIA RTX 3070</a>
</div>
<div className="cat-3-holder">
<a href="/nvidia-rtx-3080">NVIDIA RTX 3080</a>
</div>
<div className="cat-3-holder">
<a href="/nvidia-rtx-3090">NVIDIA RTX 3090</a>
</div>
</div>
<div className="sub-item">
<a href="/vga-amd-radeon" className="cat-2">
VGA AMD Radeon
</a>
</div>
</div>
</div>
<div className="item">
<a href="/main-bo-mach-chu" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_3_1663215458.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_3_1663215458.png")',
}}
></span>
<span className="cat-title">Mainboard - Bo Mạch Chủ</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/mainboard-intel" className="cat-2">
Mainboard Intel
</a>
<div className="cat-3-holder">
<a href="/mainboard-intel-b365">
Mainboard Intel B365
</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-intel-b460">
Mainboard Intel B460
</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-intel-z390">
Mainboard Intel Z390
</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-intel-z490">
Mainboard Intel Z490
</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-intel-c246">
Mainboard Intel C246
</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-intel-x299">
Mainboard Intel X299
</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-intel-khac">
Mainboard Intel Khác
</a>
</div>
</div>
<div className="sub-item">
<a href="/mainboard-amd" className="cat-2">
Mainboard AMD
</a>
<div className="cat-3-holder">
<a href="/mainboard-amd-b450">Mainboard AMD B450</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-amd-x370">Mainboard AMD X370</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-amd-x470">Mainboard AMD X470</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-amd-x570">Mainboard AMD X570</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-amd-x399">Mainboard AMD X399</a>
</div>
<div className="cat-3-holder">
<a href="/mainboard-amd-trx40">Mainboard AMD TRX40</a>
</div>
</div>
</div>
</div>
<div className="item">
<a href="/ram-bo-nho-trong" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_4_1663215185.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_4_1663215185.png")',
}}
></span>
<span className="cat-title">RAM - Bộ Nhớ Trong</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/ram-theo-hang" className="cat-2">
Ram Theo Hãng
</a>
<div className="cat-3-holder">
<a href="/ram-corsair">RAM CORSAIR</a>
</div>
<div className="cat-3-holder">
<a href="/ram-gskill">RAM GSKILL</a>
</div>
<div className="cat-3-holder">
<a href="/ram-ecc">RAM SERVER ECC - REGISTERED</a>
</div>
<div className="cat-3-holder">
<a href="/ram-team">RAM TEAM</a>
</div>
<div className="cat-3-holder">
<a href="/ram-adata">RAM ADATA</a>
</div>
</div>
</div>
</div>
<div className="item">
<a href="/hdd-ssd-nas" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_5_1663215160.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_5_1663215160.png")',
}}
></span>
<span className="cat-title">HDD - SSD - NAS</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/o-cung-hdd" className="cat-2">
Cứng HDD
</a>
</div>
<div className="sub-item">
<a href="/o-cung-the-ran-ssd" className="cat-2">
Cứng Thể Rắn SSD
</a>
</div>
<div className="sub-item">
<a href="/o-cung-di-dong-usb" className="cat-2">
Cứng Di Đng - USB
</a>
</div>
<div className="sub-item">
<a href="/o-luu-tru-nas" className="cat-2">
Lưu Trữ NAS
</a>
</div>
</div>
</div>
<div className="item">
<a href="/psu-nguon-may-tinh" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_7_1663214223.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_7_1663214223.png")',
}}
></span>
<span className="cat-title">PSU - Nguồn máy tính</span>
</a>
</div>
<div className="item">
<a href="/case-vo-may-tinh" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_8_1663215125.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_8_1663215125.png")',
}}
></span>
<span className="cat-title">Case - Vỏ Máy Tính</span>
</a>
</div>
<div className="item">
<a href="/thiet-bi-mang" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_23_1663214442.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_23_1663214442.png")',
}}
></span>
<span className="cat-title">Thiết Bị Mạng</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/thiet-bi-phat-wifi" className="cat-2">
Thiết Bị Phát Wifi
</a>
<div className="cat-3-holder">
<a href="/asus">Bộ Phát Wifi Asus</a>
</div>
<div className="cat-3-holder">
<a href="/bo-phat-wifi-linksys">
Bộ Phát Wifi Linksys
</a>
</div>
<div className="cat-3-holder">
<a href="/thiet-bi-mang-tp-link">
Bộ Phát Wifi TP-LINK
</a>
</div>
</div>
<div className="sub-item">
<a href="/usb-card-mang" className="cat-2">
USB - Card Mạng
</a>
<div className="cat-3-holder">
<a href="/usb-wifi">USB- Wifi</a>
</div>
<div className="cat-3-holder">
<a href="/card-wifi">Card Wifi</a>
</div>
<div className="cat-3-holder">
<a href="/card-lan">Card Lan</a>
</div>
</div>
<div className="sub-item">
<a href="/switch-bo-chia-mang" className="cat-2">
Switch - Bộ Chia Mạng
</a>
<div className="cat-3-holder">
<a href="/toc-do-10-100-mbps">Tốc đ 10/100 Mbps</a>
</div>
<div className="cat-3-holder">
<a href="/toc-do-10-100-1000mbps">
Tốc đ 10/100/1000Mbps
</a>
</div>
</div>
</div>
</div>
<div className="item">
<a href="/tan-nhiet-cooling" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_icon_24_1663214938.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_icon_24_1663214938.png")',
}}
></span>
<span className="cat-title">Tản Nhiệt Cooling</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/tan-nhiet-cpu" className="cat-2">
Tản Nhiệt Khí
</a>
</div>
<div className="sub-item">
<a href="/quat-tan-nhiet" className="cat-2">
Quạt Tản Nhiệt
</a>
</div>
<div className="sub-item">
<a href="/tan-nhiet-nuoc-aio" className="cat-2">
Tản Nhiệt Nước AIO
</a>
</div>
</div>
</div>
<div className="item">
<a href="/phan-mem-ban-quyen" className="cat-1">
<span
className="cat-thumb lazy"
data-bg="url('https://demopc8.hurasoft.com/media/category/cat_858ddc83e5347068f6ecf9b26713e49f.png')"
data-was-processed="true"
style={{
backgroundImage:
'url("https://demopc8.hurasoft.com/media/category/cat_858ddc83e5347068f6ecf9b26713e49f.png")',
}}
></span>
<span className="cat-title">Phần Mềm Bản Quyền</span>
</a>
<div className="sub-menu">
<div className="sub-item">
<a href="/phan-mem-microsoft-windows" className="cat-2">
Phần mềm Microsoft Windows
</a>
</div>
<div className="sub-item">
<a href="/phan-mem-microsoft-office" className="cat-2">
Phần mềm Microsoft Office
</a>
</div>
<div className="sub-item">
<a href="/phan-mem-diet-virus" className="cat-2">
Phần mềm diệt virus
</a>
</div>
</div>
</div>
</div>
</div>
{/* Right USP list */}
<div className="header-bottom-right d-flex flex-wrap align-items-center font-300 text-16">
<Link href="" className="item" target="_blank">
<i className="icons icon-warranty" />
<span className="text">Bảo hành tận nơi</span>
</Link>
<Link href="" className="item" target="_blank">
<i className="icons icon-ship" />
<span className="text">Giao hàng toàn quốc</span>
</Link>
<Link href="" className="item" target="_blank">
<i className="icons icon-payment" />
<span className="text">Thanh toán tại nhà</span>
</Link>
<Link href="" className="item" target="_blank">
<i className="icons icon-trade" />
<span className="text">Đi trả trong 3 ngày</span>
</Link>
</div>
</div>
</div>
</div>
</div>
</>
);
}

View File

@@ -0,0 +1,62 @@
"use client";
import Image from "next/image";
import Link from "next/link";
type ArticleImage = { original: string; width?: number; height?: number };
export type Article = {
url: string;
title: string;
image?: ArticleImage | null;
article_time?: string | null;
createDate?: string | null;
review_count?: number | null;
visit?: number | null;
};
type Props = { article: Article };
function formatArtDate(dateStr?: string | null) {
if (!dateStr) return "";
const d = new Date(dateStr);
if (isNaN(d.getTime())) return "";
const dd = `${d.getDate()}`.padStart(2, "0");
const mm = `${d.getMonth() + 1}`.padStart(2, "0");
const yyyy = d.getFullYear();
return `${dd}/${mm}/${yyyy}`;
}
export default function ItemArticle({ article }: Props) {
const href = article.url || "#";
const imgSrc =
"https://demopc8.hurasoft.com" + article.image?.original ||
"/static/assets/images/not-image.png";
const displayDate =
formatArtDate(article.article_time) || formatArtDate(article.createDate);
const comments = article.review_count ?? 0;
const views = article.visit ?? 0;
return (
<div className="footer-art-item art-item flex gap-3">
<Link href={href} className="img">
<Image
src={imgSrc}
alt={article.title || ""}
fill
className="object-cover"
/>
</Link>
<div className="text min-w-0">
<Link href={href} className="line-clamp-2 font-medium hover:underline">
{article.title}
</Link>
<time className="mt-1 block text-sm text-gray-500">
<i className="fa fa-calendar" /> {displayDate}&nbsp;
<i className="fa fa-comment ml-3" /> {comments}&nbsp;
<i className="fa fa-eye ml-3" /> {views}
</time>
</div>
</div>
);
}

View File

@@ -0,0 +1,3 @@
"use client";
import Image from "next/image";
import Link from "next/link";

View File

View File

@@ -0,0 +1,95 @@
"use client";
import Image from "next/image";
import { ProductDataType } from "@/types/products";
const ProductItem = ({ product }: { product: ProductDataType }) => (
<div className="p-item">
<a href="/nguon-may-tinh-jetek-rm750w" className="p-img">
<Image
src="/media/product/250_2042_jetek_rm_750w_1.jpg"
alt="Nguồn Máy Tính JETEK RM 750W - 80 PLUS GOLD"
/>
</a>
<div className="p-text">
<p className="p-sku">: 0</p>
<a href="/nguon-may-tinh-jetek-rm750w" className="p-name">
Nguồn Máy Tính JETEK RM 750W - 80 PLUS GOLD
</a>
<div className="p-price-group">
<div>
<del></del>
<span className="p-price">1.990.000 đ</span>
</div>
<a
href="javascript:void(0)"
className="p-btn-cart"
onclick="addProductToCart(2042, 0)"
></a>
</div>
<a
href="javascript:void(0)"
className="p-btn-compare js-p-compare"
onclick="compare_addProduct(2042,'/media/product/250_2042_jetek_rm_750w_1.jpg', this)"
data-id="{2042"
>
So sánh
</a>
</div>
<div className="p-tooltip">
<p className="tooltip-title">
Nguồn Máy Tính JETEK RM 750W - 80 PLUS GOLD
</p>
<div className="tooltip-content">
<div className="tooltip-summary">
<span className="item">
<i
className="fa fa-certificate"
style="color: #dd1616;margin-right: 5px;"
></i>
Nguồn JETEK RM 750W
</span>
<span className="item">
<i
className="fa fa-certificate"
style="color: #dd1616;margin-right: 5px;"
></i>
Chuẩn hiệu suất: 80 Plus Gold
</span>
<span className="item">
<i
className="fa fa-certificate"
style="color: #dd1616;margin-right: 5px;"
></i>
Công suất: 750W - Hiệu suất 90%
</span>
<span className="item">
<i
className="fa fa-certificate"
style="color: #dd1616;margin-right: 5px;"
></i>
Công nghệ DC to DC
</span>
<span className="item">
<i
className="fa fa-certificate"
style="color: #dd1616;margin-right: 5px;"
></i>
Quạt: 1 x 120 mm (Smart Fan)
</span>
</div>
<div className="position-relative text-center">
<span className="p-price">1.990.000 đ</span>
</div>
</div>
</div>
</div>
);
export default ProductItem;

View File

@@ -0,0 +1,131 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import type { ProductDataType } from "@/types/products";
type Props = {
product: ProductDataType;
onAddToCart?: (productId: number) => void;
onCompare?: (p: ProductDataType) => void;
};
function formatVND(n?: number | null) {
if (!n || n <= 0) return "";
return n.toLocaleString("vi-VN") + " đ";
}
function pickPrice(p: ProductDataType) {
// Ưu tiên promotion_price nếu có, rồi đến sale_rules.price, rồi price
const normal =
(p.sale_rules?.normal_price as number | undefined) ??
p.marketPrice ??
p.price ??
0;
const current =
(p.promotion_price as number | null) ??
(p.sale_rules?.price as number | undefined) ??
p.price ??
0;
const hasDiscount = normal > 0 && current > 0 && current < normal;
return { current, normal, hasDiscount };
}
function getBullets(p: ProductDataType, max = 5): string[] {
const raw = (p.productSummary || "").replace(/\r/g, "\n");
let parts =
raw
.split(/\n|•|-||—/g)
.map((s) => s.trim())
.filter(Boolean) || [];
if (parts.length === 0) {
parts = [
p.brand?.name ? `Thương hiệu: ${p.brand.name}` : "",
p.warranty ? `Bảo hành: ${p.warranty}` : "",
].filter(Boolean);
}
return parts.slice(0, max);
}
const ProductItemSlider = ({ product, onAddToCart, onCompare }: Props) => {
const { current, normal, hasDiscount } = pickPrice(product);
const img =
"https://demopc8.hurasoft.com" + product.productImage?.large ||
"https://demopc8.hurasoft.com" + product.productImage?.small ||
"https://demopc8.hurasoft.com/static/assets/giaodien_2025/images/not-image.png";
const href = product.productUrl || "#";
const name = product.productName || "Sản phẩm";
const sku = product.productSKU || "—";
const productId = product.productId ?? Number(product.id);
return (
<div className="p-item">
<Link href={href} className="p-img" aria-label={name}>
<Image
src={img}
alt={name}
width={250}
height={250}
className="block w-full h-auto object-contain"
sizes="(max-width: 768px) 60vw, 250px"
priority={false}
/>
</Link>
<div className="p-text">
<p className="p-sku">: {sku}</p>
<Link href={href} className="p-name">
{name}
</Link>
<div className="p-price-group">
<div>
{hasDiscount ? <del>{formatVND(normal)}</del> : <del />}
<span className="p-price">{formatVND(current)}</span>
</div>
<button
type="button"
className="p-btn-cart"
onClick={() => onAddToCart?.(productId)}
aria-label="Thêm vào giỏ"
/>
</div>
<button
type="button"
className="p-btn-compare js-p-compare"
onClick={() => onCompare?.(product)}
data-id={productId}
>
So sánh
</button>
</div>
{/* Tooltip */}
<div className="p-tooltip">
<p className="tooltip-title">{name}</p>
<div className="tooltip-content">
<div className="tooltip-summary">
{getBullets(product).map((t, i) => (
<span className="item" key={i}>
<i
className="fa fa-certificate"
style={{ color: "#dd1616", marginRight: 5 }}
aria-hidden="true"
/>
{t}
</span>
))}
</div>
<div className="position-relative text-center">
<span className="p-price">{formatVND(current)}</span>
</div>
</div>
</div>
</div>
);
};
export default ProductItemSlider;

3263
src/styles/style.css Normal file

File diff suppressed because it is too large Load Diff

33
src/types/article.ts Normal file
View File

@@ -0,0 +1,33 @@
// types/article.ts
export type FeaturedFlag = 0 | 1;
export interface ArticleImage {
thum: string;
original: string;
}
export interface Article {
id: number;
title: string;
extend: boolean;
summary: string;
createDate: string; // ví dụ: "03-12-2020, 10:34 am"
createBy: number;
lastUpdate: string; // ví dụ: "08-12-2020, 3:44 pm"
lastUpdateBy: number;
visit: number;
is_featured: FeaturedFlag;
article_time: string;
review_rate: number;
review_count: number;
video_code: string;
external_url: string;
author: string;
counter: number;
url: string;
image: ArticleImage;
}
export interface ArticleDataType {
article: Article[];
}

104
src/types/products.ts Normal file
View File

@@ -0,0 +1,104 @@
export type Currency = "vnd" | string;
export interface ImageSizes {
small: string;
large: string;
original: string;
}
export interface ImageItem {
image: ImageSizes;
alt: string;
}
export interface Brand {
id: number;
brand_index: string;
name: string;
image: string;
url: string;
}
export interface ReviewInfo {
rate: number;
total: number;
}
export interface CommentInfo {
rate: number;
total: number;
}
export interface SaleRules {
price: number;
normal_price: number;
min_purchase: number;
max_purchase: number;
remain_quantity: number;
from_time: number | string;
to_time: number | string;
type: string;
}
export interface Category {
id: number;
catPath: string;
name: string;
url: string;
}
export interface ProductTypeFlags {
isNew: 0 | 1;
isHot: 0 | 1;
isBestSale: 0 | 1;
isSaleOff: 0 | 1;
"online-only": 0 | 1;
}
export interface ProductDataType {
id: number;
productId: number;
priceUnit: string;
marketPrice: number;
price: number;
price_off: number | string; // ví dụ có nơi là %, có nơi là "" -> để union
currency: Currency;
sale_rules: SaleRules;
lastUpdate: string; // "YYYY-MM-DD HH:mm:ss"
warranty: string;
productName: string;
productSummary: string;
package_accessory?: string;
productImage: ImageSizes;
imageCollection: { image: ImageSizes; alt: string }[];
productUrl: string;
brand: Brand;
visit: number;
rating: number;
reviewCount: number;
review: ReviewInfo;
comment: CommentInfo;
quantity: number;
productSKU: string;
productModel: string;
hasVAT: 0 | 1;
condition: string;
config_count: number;
configurable: number;
component_count: number;
specialOffer?: Record<string, unknown>;
specialOfferGroup?: unknown[];
productType: ProductTypeFlags;
bulk_price: unknown | null;
thum_poster: string;
thum_poster_type: string;
addon: unknown[];
variants: unknown[];
variant_option: unknown[];
extend: unknown | null;
weight: number;
promotion_price: number | null;
deal_list: unknown[];
pricing_traces: Array<{ price: number; type: string; type_id?: number }>;
categories: Category[];
}