up
This commit is contained in:
105
src/api/apiService.ts
Normal file
105
src/api/apiService.ts
Normal 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,
|
||||
});
|
||||
85
src/app/[category]/page.tsx
Normal file
85
src/app/[category]/page.tsx
Normal 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 |
@@ -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;
|
||||
}
|
||||
101
src/app/homepage/ArticleHome.tsx
Normal file
101
src/app/homepage/ArticleHome.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
80
src/app/homepage/BoxGroupProductCategory.tsx
Normal file
80
src/app/homepage/BoxGroupProductCategory.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
112
src/app/homepage/ListProductBestSale.tsx
Normal file
112
src/app/homepage/ListProductBestSale.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
47
src/app/homepage/slider.tsx
Normal file
47
src/app/homepage/slider.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
175
src/app/page.tsx
175
src/app/page.tsx
@@ -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
300
src/components/Footer.tsx
Normal 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 KÝ NHẬN EMAIL THÔNG BÁO KHUYẾN MẠI HOẶC ĐỂ ĐƯỢC TƯ 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 ký thành công!
|
||||
</p>
|
||||
)}
|
||||
{sent === "err" && (
|
||||
<p className="text-center mt-2 text-danger" role="alert">
|
||||
Có 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 và 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>Tư 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ả và 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 Và 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ố Hà Nội.
|
||||
<br />
|
||||
Giấy phép đăng ký kinh doanh: 0109802587 do Sở Kế Hoạch và Đầu Tư
|
||||
Tp.Hà 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
945
src/components/Header.tsx
Normal 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 ký</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ử Lý</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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
62
src/components/article/itemArticle.tsx
Normal file
62
src/components/article/itemArticle.tsx
Normal 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}
|
||||
<i className="fa fa-comment ml-3" /> {comments}
|
||||
<i className="fa fa-eye ml-3" /> {views}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
src/components/article/itemSliderArticle.tsx
Normal file
3
src/components/article/itemSliderArticle.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
0
src/components/product/BoxFilter.tsx
Normal file
0
src/components/product/BoxFilter.tsx
Normal file
95
src/components/product/ProductItem.tsx
Normal file
95
src/components/product/ProductItem.tsx
Normal 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">Mã: 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;
|
||||
131
src/components/product/ProductItemSlider.tsx
Normal file
131
src/components/product/ProductItemSlider.tsx
Normal 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">Mã: {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
3263
src/styles/style.css
Normal file
File diff suppressed because it is too large
Load Diff
33
src/types/article.ts
Normal file
33
src/types/article.ts
Normal 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
104
src/types/products.ts
Normal 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[];
|
||||
}
|
||||
Reference in New Issue
Block a user