update 04/02

This commit is contained in:
2026-02-04 16:59:06 +07:00
parent 332cb5da14
commit a499e06b31
20 changed files with 1292 additions and 1394 deletions

View File

@@ -0,0 +1,58 @@
import Link from "next/link";
import ArticleItem from "@/components/shared/ArticleItem";
export default function ArticleCategories({ item, data }: any) {
const firstItem = data[0];
const itemList = data.slice(1, 5);
return (
<div className="article-category-container" id={`js-category-${item.id}`} key={item.id}>
<div className="container">
<div className="group-title flex flex-wrap items-center justify-between gap-4 mb-4 lg:mb-6">
<div className="flex items-center gap-2 lg:gap-3">
<h2 className="m-0 font-600 text-[#004BA4] text-20 leading-6 lg:leading-10 lg:text-[32px]">
{item.title}
</h2>
<Link href={item.url}
className="bx bx-chevron-right border-[1.5px] border-[#0677DB4D] rounded-full w-5 lg:w-8 h-5 lg:h-8 flex items-center justify-center text-[#0678DB] text-18 lg:text-[24px] hover:bg-[#0678DB] hover:text-white"
></Link>
</div>
<div className="w-full lg:w-auto flex gap-2 leading-[30px] text-[#0678DB] font-600 text-16 overflow-auto whitespace-nowrap no-scroll">
{item.children.map((child: any) =>
<Link className="bg-[#EAF1FF] px-2 rounded-[30px] hover:bg-[#0678DB] hover:text-white"
href={child.url}
key={child.id}
>
{child.title}
</Link>
)}
</div>
</div>
{!data || data.length === 0 ?
(
<p className="text-center font-600 uppercase mt-10 text-20"> Tin tức đang cập nhật ... ! </p>
) : (
<div className="article-category-holder grid lg:grid-cols-2 gap-5 lg:gap-8">
<div className="first-item">
<ArticleItem item={firstItem} />
</div>
<div className="grid grid-cols-2 gap-4 lg:gap-6">
{itemList.map((item: any) =>
<ArticleItem
item={item}
key={item.id}
/>
)}
</div>
</div>
)
}
</div>
</div>
)
}

View File

@@ -0,0 +1,167 @@
import Link from "next/link";
export default function Tiktok() {
return (
<div className="article-tiktok-container py-8 lg:py-16">
<div className="group-title flex items-center justify-between flex-wrap gap-4 mb-6">
<h2 className="flex items-center m-0 leading-8 font-600 text-20 text-[#004BA4] gap-[10px] lg:gap-4 lg:text-[32px] lg:leading-10">
<i
className="lazy bg-no-repeat bg-center bg-[length:100%_100%] w-8 h-8 lg:w-12 lg:h-12"
data-bg="url(images/logo-tiktok.png)"
/>
<span> Tiktok Channel </span>
</h2>
<div className="flex flex-wrap items-center justify-center text-center bg-[#DCE8FF] rounded-[16px_0_] leading-5 lg:leading-6 lg:text-[16px] gap-3 lg:w-[823px] w-full p-3 lg:p-4">
<i className="icons icon-finger !w-6 !h-6 animation-wiggle" />
<div className="lg:flex flex-wrap">
<p className="m-0 mr-1">
Theo dõi kênh tiktok của Hoàng PC:
</p>
<a
href="https://www.tiktok.com/@hoangha.pc"
target="_blank"
rel="nofollow"
className="text-[#0678DB] font-600 table lg:text-[16px]"
>
ORIGINAL SOUND - HOÀNG PC
</a>
</div>
</div>
</div>
<div className="swiper overflow-hidden" id="js-tiktok-slide">
<div className="swiper-wrapper">
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
<div className="swiper-slide">
<a href="" target="_blank" rel="nofollow">
<span className="block relative mb-2 rounded-[12px] overflow-hidden pb-[344px]">
<img
data-src="https://hoanghapccdn.com/media/lib/27-07-2023/don-hang-khach-vip.jpg"
alt=""
width={1}
height={1}
className="block lazy w-full absolute h-full object-cover"
/>
</span>
<span className="block leading-[22px] font-600 text-16">
Đơn hàng đc biệt cho khách VIP
</span>
</a>
</div>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,63 @@
'use client';
import Link from 'next/link';
import Image from 'next/image';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Pagination, Autoplay } from 'swiper/modules';
import ArticleItem from "@/components/shared/ArticleItem";
export default function TopArticleList({ item }: any) {
const leftItems = item.slice(0, 7);
const rightItems = item.slice(7, 10);
return (
<div className="top-article-container container flex flex-wrap gap-6 !mb-2 lg:!mb-6">
<div className="col-left w-full lg:w-[718px] swiper overflow-hidden relative">
<Swiper
spaceBetween={10}
slidesPerView={1}
loop={true}
autoplay={{
delay: 3000,
disableOnInteraction: false,
}}
modules={[Pagination, Autoplay]}
pagination={{ clickable: true }}
>
{leftItems.map((item: any) => (
<SwiperSlide key={item.id}>
<Link href={item.url}
{...(item.external_url ? {
target: "_blank",
rel: "noopener noreferrer"
} : {})}
>
<Image
src={item.image.original}
alt={item.title}
width={100}
height={100}
unoptimized
className="block w-full rounded-[16px]"
/>
</Link>
</SwiperSlide>
))}
</Swiper>
</div>
<div className="col-right w-full lg:w-[calc(100%-742px)]">
<p className="font-600 text-20 leading-6 lg:text-[24px] lg:leading-8 mb-3">
Tin nổi bật
</p>
<div className="flex flex-col gap-4">
{
rightItems.map((item: any) => <ArticleItem key={item.id} item={item} />)
}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,88 @@
import Link from "next/link";
import { useState, useEffect, useMemo } from "react";
import { VideoData } from "@/data/articles/Video";
export default function Video() {
const { total, list } = VideoData;
const [active, setActive] = useState<number | null>(null);
const [url, setUrl] = useState<string>("");
// set video mặc định
useEffect(() => {
if (list?.length > 0) {
setActive(list[0].id);
setUrl(list[0].video_code);
}
}, [list]);
const videoId = useMemo(() => {
if (!url) return null;
// https://www.youtube.com/watch?v=xxxx
if (url.includes('v=')) {
return url.split('v=')[1].split('&')[0];
}
// https://youtu.be/xxxx
if (url.includes('youtu.be/')) {
return url.split('youtu.be/')[1].split('?')[0];
}
return null;
}, [url]);
return (list.length > 0 &&
<div className="article-video-container lg:flex flex-wrap gap-4 mt-16">
<div className="lg:w-[732px] video-holder">
{videoId && (
<iframe
src={`https://www.youtube.com/embed/${videoId}`}
allowFullScreen
/>
)}
</div>
<div className="lg:w-[calc(100%-748px)] rounded-[16px] overflow-hidden bg-[#EAF1FF]">
<p className="group-title m-0 text-white flex items-center gap-2 lg:gap-3 font-600 p-[8px_12px] leading-[18px] lg:text-[20px] lg:leading-6 lg:p-[12px_16px] bg-[linear-gradient(180.3deg,#259AFF_-18.56%,#114CDD_100.92%)]">
<i className="w-[18px] h-[18px] lg:w-6 lg:h-6 lazy bg-no-repeat bg-center bg-[length:100%_100%]"
style={{ backgroundImage: 'url(/images/icon-playlist.png)' }}
/>
<Link href="/video"> Trending video </Link>
</p>
<div className="h-[385px] p-4 pr-1 relative">
<div className="h-full overflow-auto flex flex-col gap-4">
{list.map((item: any) =>
<button
type="button"
key={item.id}
onClick={() => {
setActive(item.id);
setUrl(item.video_code)
}}
className={`video-item text-left
${active === item.id ? 'color-main' : ''}
`}
>
<span className="video-img">
<img
src={item.image.original}
alt={item.title}
width={120}
height={66}
/>
</span>
<p className="video-title m-0">
<span>{item.title}</span>
</p>
</button>
)}
</div>
<div className="gradient-overlay"></div>
</div>
</div>
</div>
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
import { CartData } from "@/data/cart"
import { CartData } from "@/data/cart";
import { formatPrice } from "@/lib/utils";
import EmptyCart from "./EmptyCart";
import Form from "../form";
import CartItem from "@/components/shared/CartItem"
import { formatPrice } from "@/lib/utils";
export default function Home() {
console.log(CartData);
const hasCart = CartData.data.length;
if (hasCart == 0 ) {

View File

@@ -84,8 +84,6 @@ export default function Form() {
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setSubmit(false)
if (!checkField()) return;
console.log("Form hợp lệ:", form);

View File

@@ -1,72 +1,42 @@
import Link from "next/link";
import { useCart } from "@/hooks/useCart";
import { formatPrice, getAllProducts } from "@/lib/utils"
import { formatPrice } from "@/lib/utils"
import CartItem from "@/components/other/header/cart/index"
import { CartData } from '@/data/cart';
export default function Cart() {
const {
cartItems,
cartCount,
totalItems,
loading,
} = useCart();
const allProducts = getAllProducts();
// Lọc sản phẩm có trong giỏ hàng với quantity
const productsInCart = cartItems
.map(cartItem => {
const product = allProducts.find(p => p.id === cartItem.id);
if (product) {
return {
...product,
cartQuantity: cartItem.cartQuantity,
};
}
return null;
})
.filter(item => item !== null);
// Tính tổng tiền
const totalPrice = productsInCart.reduce((sum, item) => {
if (item && item.price) {
return sum + (item.price * item.quantity);
}
return sum;
}, 0);
const hasProducts = cartCount > 0;
const productsInCart = CartData.data;
const totalPrice = CartData.cart_summary.total_value;
const totalItems = CartData.data.length;
const hasProducts = totalItems > 0;
return (
<>
<div className="header-cart-item">
<Link href="/cart" rel="nofollow" className="flex items-center">
<i className="icon-cart relative mr-[10px]">
<b className="js-cart-count cart-count">{cartCount}</b>
<b className="js-cart-count cart-count">{totalItems}</b>
</i>
<span className="text"> Giỏ <br />Hàng </span>
</Link>
{!loading && hasProducts &&
{hasProducts &&
<div className="header-cart-hover">
<div className="cart-items-holder">
<p className="text-center underline font-600 red border-b" style={{ margin: '0', padding: "10px" }}>
1 số sp thêm từ DEAL sẽ không trong DB tĩnh nên không hiển thị
</p>
{productsInCart.map((item:any) =>
<CartItem key={item.id} item={item} />
<CartItem key={item.item_id} item={item} />
)}
</div>
<div className="cart-price-hover">
<p className="grey m-0 text-right">
Tổng tiền hàng
(<span className="red"><span className="js-cart-count">{totalItems}</span> sản phẩm</span>):
<span className="red text-18 font-600" style={{ verticalAlign: "top" }}> {formatPrice(totalPrice)}đ </span>
(<span className="red">
<span className="js-cart-count">{totalItems}</span> sản phẩm
</span>):
<span className="red text-18 font-600" style={{ verticalAlign: "top" }}>
{formatPrice(totalPrice)}đ
</span>
</p>
<Link href="/cart" className="d-block text-center text-white btn-goCart"> THANH TOÁN NGAY </Link>
</div>

View File

@@ -2,26 +2,29 @@ import Link from "next/link";
import { formatPrice } from "@/lib/utils";
export default function CartItem({item}:any) {
const { in_cart , item_info } = item;
return(
<div className="cart-item">
<Link href={item.productUrl} className="cart-img">
<Link href={item_info.productUrl} className="cart-img">
<img
src={item.productImage.small}
alt={item.productName}
src={item_info.productImage.small}
alt={item_info.productName}
width={100}
height={100}
/>
</Link>
<div className="cart-text">
<Link href={item.productUrl} className="d-block font-700" style={{ marginBottom: "5px" }}>
{item.productName}
<Link href={item_info.productUrl} className="d-block font-700" style={{ marginBottom: "5px" }}>
{item_info.productName}
</Link>
<p className="m-0 d-flex justify-content-between">
<b>x{item.cartQuantity}</b>
<b>x{in_cart.quantity}</b>
<b className="red">
{item.price > 0 ? formatPrice(item.price) +'đ' : 'Liên hệ'}
{in_cart.total_price > 0 ? formatPrice(in_cart.total_price) +'đ' : 'Liên hệ'}
</b>
</p>
</div>

View File

@@ -4,7 +4,7 @@ import parse from 'html-react-parser';
export default function ProductOffer({ item }: any) {
return (
<>
{item.specialOffer.all.length > 0 &&
{item.specialOffer?.all.length > 0 &&
<div className="pd-offer-group mb-4 bg-[linear-gradient(182.15deg,#FFA480_-18.44%,#EB0C23_60.76%)] p-1 pt-2 rounded-[8px]">
<div className="group-title font-600 text-white flex items-center leading-[22px] mb-2 px-2 text-16">
<i className="icons icon-gift mr-2 animation-tada -mt-1" />

View File

@@ -2,8 +2,11 @@ import Link from "next/link";
import { formatArticleTime } from "@/lib/utils";
export default function ArticleItem({ item }: any) {
const url = item.external_url ? item.external_url : item.url;
const time = item.article_time ? item.article_time : item.lastUpdate;
if (!item) return null;
const url = item.external_url || item.url;
const time = item.article_time || item.lastUpdate;
return (
<>

View File

@@ -11,16 +11,19 @@ export default function FixedButtons() {
};
window.addEventListener('scroll', onScroll);
return () => window.removeEventListener('scroll', onScroll);
}, []);
useEffect(() => {
const timer = setTimeout(() => setAnimate(true), 800);
return () => clearTimeout(timer);
return () => {
clearTimeout(timer);
window.removeEventListener('scroll', onScroll);
}
}, []);
const goTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
return (

206
src/data/articles/Job.tsx Normal file
View File

@@ -0,0 +1,206 @@
export const JobData = {
"total": 3,
"list": [
{
"id": 1185,
"type": "job",
"changeCount": 0,
"sellerId": 0,
"article_category": "36",
"title": "Tuy\u1ec3n D\u1ee5ng Nh\u00e2n Vi\u00ean Nam K\u1ef9 Thu\u1eadt Ph\u1ea7n C\u1ee9ng M\u00e1y T\u00ednh",
"video_code": "",
"external_url": "",
"url": "\/tuyen-dung-nhan-vien-nam-ky-thuat-phan-cung-may-tinh",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": {
"salary": "12 Tri\u1ec7u \u0110\u1ebfn 15 Tri\u1ec7u ",
"vacancy_num": "5",
"end_date": "30-11-2025",
"location": "K8bis B\u1eedu Long, Ph\u01b0\u1eddng Ho\u00e0 H\u01b0ng, Th\u00e0nh ph\u1ed1 H\u1ed3 Ch\u00ed Minh"
},
"summary": "",
"tags": "",
"createDate": "31-10-2022, 1:42 pm",
"createBy": 22,
"create_by_name": "Mai V\u0103n H\u1ecdc",
"lastUpdate": "12-11-2025, 8:45 am",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 10743,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "Tuy\u1ec3n D\u1ee5ng Nh\u00e2n Vi\u00ean Nam K\u1ef9 Thu\u1eadt Ph\u1ea7n C\u1ee9ng M\u00e1y T\u00ednh",
"meta_keywords": "",
"meta_description": "Ho\u00e0ng H\u00e0 PC tuy\u1ec3n d\u1ee5ng k\u1ef9 thu\u1eadt ph\u1ea7n c\u1ee9ng m\u00e1y t\u00ednh: Ch\u1ecbu tr\u00e1ch nhi\u1ec7m tr\u1ef1c ti\u1ebfp thi c\u00f4ng vi\u1ec7c l\u1eafp \u0111\u1eb7t c\u00e1c s\u1ea3n ph\u1ea9m theo y\u00eau c\u1ea7u c\u1ee7a c\u1ea5p tr\u00ean.",
"url_canonical": "",
"article_time": "",
"allow_se_index": 1,
"comment_count": 0,
"comment_rate": 0,
"counter": 1,
"image": {
"thum": "https:\/\/hoanghapccdn.com\/media\/news\/120_1185_tuyen_dung_ky_thuat.jpg",
"original": "https:\/\/hoanghapccdn.com\/media\/news\/1185_tuyen_dung_ky_thuat.jpg"
},
"categories": [
{
"id": 36,
"type": "job",
"name": "K\u1ef9 thu\u1eadt m\u00e1y t\u00ednh",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 345,
"item_count": 3,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/ky-thuat-may-tinh",
"relate_product": null
}
]
},
{
"id": 1497,
"type": "job",
"changeCount": 0,
"sellerId": 0,
"article_category": "36",
"title": "Tuy\u1ec3n D\u1ee5ng Nh\u00e2n Vi\u00ean K\u1ef9 Thu\u1eadt Tri\u1ec3n Khai H\u1ea1 T\u1ea7ng M\u1ea1ng",
"video_code": "",
"external_url": "",
"url": "\/tuyen-dung-nhan-vien-ky-thuat-trien-khai-ha-tang-mang",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": {
"salary": "12 Tri\u1ec7u - 15 Tri\u1ec7u ",
"vacancy_num": "1",
"end_date": "30-11-2024",
"location": "41 Kh\u00fac Th\u1eeba D\u1ee5, Ph\u01b0\u1eddng D\u1ecbch V\u1ecdng, Qu\u1eadn C\u1ea7u Gi\u1ea5y, H\u00e0 N\u1ed9i"
},
"summary": "",
"tags": "",
"createDate": "16-10-2024, 11:30 am",
"createBy": 22,
"create_by_name": "Mai V\u0103n H\u1ecdc",
"lastUpdate": "16-10-2024, 11:32 am",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 695,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "",
"meta_keywords": "",
"meta_description": "",
"url_canonical": "",
"article_time": "",
"allow_se_index": 1,
"comment_count": 0,
"comment_rate": 0,
"counter": 2,
"image": {
"thum": "",
"original": ""
},
"categories": [
{
"id": 36,
"type": "job",
"name": "K\u1ef9 thu\u1eadt m\u00e1y t\u00ednh",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 345,
"item_count": 3,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/ky-thuat-may-tinh",
"relate_product": null
}
]
},
{
"id": 1178,
"type": "job",
"changeCount": 0,
"sellerId": 0,
"article_category": "34",
"title": "Nh\u00e2n vi\u00ean Quay D\u1ef1ng Cameraman\/ Editor",
"video_code": "",
"external_url": "",
"url": "\/nhan-vien-quay-dung-cameraman-editor",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": {
"salary": "10 - 15 Tri\u1ec7u",
"vacancy_num": "1",
"end_date": "31-07-2025",
"location": "41 Kh\u00fac Th\u1eeba D\u1ee5, C\u1ea7u Gi\u1ea5y"
},
"summary": "",
"tags": "",
"createDate": "18-10-2022, 2:52 pm",
"createBy": 22,
"create_by_name": "Mai V\u0103n H\u1ecdc",
"lastUpdate": "30-06-2025, 4:15 pm",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 620,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "Nh\u00e2n vi\u00ean Quay D\u1ef1ng Cameraman\/ Editor",
"meta_keywords": "",
"meta_description": "V\u1ecb tr\u00ed Quay d\u1ef1ng Cameraman\/ Editor gi\u00fap n\u00e2ng t\u1ea7m h\u00ecnh \u1ea3nh cho c\u00f4ng ty, t\u1ea1o n\u00ean nh\u1eefng video viral c\u0169ng nh\u01b0 l\u00e0 b\u1ed9 m\u1eb7t c\u1ee7a c\u00f4ng ty trong truy\u1ec1n th\u00f4ng.",
"url_canonical": "",
"article_time": "",
"allow_se_index": 1,
"comment_count": 0,
"comment_rate": 0,
"counter": 3,
"image": {
"thum": "",
"original": ""
},
"categories": [
{
"id": 34,
"type": "job",
"name": "Marketing",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 0,
"item_count": 3,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/marketing",
"relate_product": null
}
]
}
]
}

377
src/data/articles/Video.tsx Normal file
View File

@@ -0,0 +1,377 @@
export const VideoData = {
"total": 6,
"list": [
{
"id": 1241,
"type": "video",
"changeCount": 0,
"sellerId": 0,
"article_category": ",31,",
"title": "PC \u0110\u1ed3 H\u1ecda Si\u00eau Kh\u1ecfe - \u0110\u1eb9p 13900K + VGA RTX 4070",
"video_code": "https:\/\/www.youtube.com\/watch?v=kGSYaCyNPvg",
"external_url": "",
"url": "\/unbox-pc-do-hoa-sieu-khoe-dep",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": false,
"summary": "",
"tags": "",
"createDate": "27-07-2023, 3:04 pm",
"createBy": 22,
"create_by_name": "Mai V\u0103n H\u1ecdc",
"lastUpdate": "27-07-2023, 3:13 pm",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 337,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "PC \u0110\u1ed3 H\u1ecda Si\u00eau Kh\u1ecfe - \u0110\u1eb9p 13900K + VGA RTX 4070",
"meta_keywords": "",
"meta_description": "Unbox PC \u0110\u1ed3 H\u1ecda Si\u00eau Kh\u1ecfe - \u0110\u1eb9p T\u1ed1i \u01afu Ph\u1ea7n C\u1ee9ng i9-13900K, Card \u0111\u1ed3 h\u1ecda RTX 4070 12GB, Bo m\u1ea1ch ch\u1ee7 Z790 c\u1ef1c kh\u1ecfe cho anh em.\r\n",
"url_canonical": "",
"article_time": "",
"allow_se_index": 0,
"comment_count": 0,
"comment_rate": 0,
"counter": 1,
"image": {
"thum": "https:\/\/hoanghapccdn.com\/media\/news\/120_1241_pc_do_hoa_13900k_4070.jpg",
"original": "https:\/\/hoanghapccdn.com\/media\/news\/1241_pc_do_hoa_13900k_4070.jpg"
},
"categories": [
{
"id": 31,
"type": "video",
"name": "Trending video",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 413,
"item_count": 6,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/trending-video",
"relate_product": null
}
]
},
{
"id": 1181,
"type": "video",
"changeCount": 0,
"sellerId": 0,
"article_category": ",31,",
"title": "Mini PC White Nh\u1ecf G\u1ecdn - Tinh T\u1ebf - Hi\u1ec7u N\u0103ng Cao",
"video_code": "https:\/\/www.youtube.com\/watch?v=nwkc_lvEJMA",
"external_url": "",
"url": "\/pc-may-ao-gia-lap-xeon-e5-2676v3-64gb-rx-580-8gb",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": false,
"summary": "",
"tags": "",
"createDate": "18-10-2022, 3:03 pm",
"createBy": 22,
"create_by_name": "Mai V\u0103n H\u1ecdc",
"lastUpdate": "27-07-2023, 3:15 pm",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 366,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "Mini PC White Nh\u1ecf G\u1ecdn - Tinh T\u1ebf - Hi\u1ec7u N\u0103ng Cao",
"meta_keywords": "",
"meta_description": "HHPC MINI WHITE i5 13600K l\u00e0 b\u1ed9 PC gaming s\u1edf h\u1eefu s\u1eafc tr\u1eafng, hi\u1ec7n \u0111\u1ea1i v\u00e0 si\u00eau nh\u1ecf g\u1ecdn. T\u1ed1i \u01b0u v\u1ec1 c\u1ea5u h\u00ecnh mang t\u1edbi cho game th\u1ee7 tr\u1ea3i nghi\u1ec7m ch\u01a1i game m\u00e3n nh\u00e3n",
"url_canonical": "",
"article_time": "",
"allow_se_index": 0,
"comment_count": 0,
"comment_rate": 0,
"counter": 2,
"image": {
"thum": "https:\/\/hoanghapccdn.com\/media\/news\/120_1181_pc_mini_white_13600k.jpg",
"original": "https:\/\/hoanghapccdn.com\/media\/news\/1181_pc_mini_white_13600k.jpg"
},
"categories": [
{
"id": 31,
"type": "video",
"name": "Trending video",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 413,
"item_count": 6,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/trending-video",
"relate_product": null
}
]
},
{
"id": 1180,
"type": "video",
"changeCount": 0,
"sellerId": 0,
"article_category": ",31,",
"title": "PC Full Tr\u1eafng 5x Tri\u1ec7u Linh Ki\u1ec7n Th\u1ebf H\u1ec7 M\u1edbi Hi\u1ec7u N\u0103ng Kh\u1ee7ng",
"video_code": "https:\/\/www.youtube.com\/watch?v=08varEBPXwc",
"external_url": "",
"url": "\/build-pc-do-hoa-40-trieu-full-trang-intel-i9-12900k-ram-32gb-rtx-3060",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": false,
"summary": "",
"tags": "",
"createDate": "18-10-2022, 2:57 pm",
"createBy": 22,
"create_by_name": "Mai V\u0103n H\u1ecdc",
"lastUpdate": "27-07-2023, 3:17 pm",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 827,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "PC Full Tr\u1eafng 5x Tri\u1ec7u Linh Ki\u1ec7n Th\u1ebf H\u1ec7 M\u1edbi Hi\u1ec7u N\u0103ng Kh\u1ee7ng",
"meta_keywords": "",
"meta_description": "HHPC WHITE CORE i9 13900K n\u1eb1m trong b\u1ed9 s\u01b0u t\u1eadp nh\u1eefng c\u1ea5u h\u00ecnh m\u00e1y t\u00ednh si\u00eau \u0111\u1eb9p t\u1ea1i Ho\u00e0ng H\u00e0 PC, kh\u00f4ng ch\u1ec9 t\u1ed1i \u01b0u v\u1ec1 ph\u1ea7n c\u1ee9ng v\u00e0 t\u1ed1t nh\u1ea5t trong t\u1ea7m gi\u00e1. ",
"url_canonical": "",
"article_time": "",
"allow_se_index": 0,
"comment_count": 0,
"comment_rate": 0,
"counter": 3,
"image": {
"thum": "https:\/\/hoanghapccdn.com\/media\/news\/120_1180_pc_do_hoa_13900k_4070_1.jpg",
"original": "https:\/\/hoanghapccdn.com\/media\/news\/1180_pc_do_hoa_13900k_4070_1.jpg"
},
"categories": [
{
"id": 31,
"type": "video",
"name": "Trending video",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 413,
"item_count": 6,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/trending-video",
"relate_product": null
}
]
},
{
"id": 1179,
"type": "video",
"changeCount": 0,
"sellerId": 0,
"article_category": ",31,",
"title": "C\u1ea5u H\u00ecnh \"Ch\u1ea5t Ch\u01a1i\" V\u1edbi i9-13900K - RTX 3070 Si\u00eau Kh\u1ecfe!",
"video_code": "https:\/\/www.youtube.com\/watch?v=QAwiNtcYi3M",
"external_url": "build-pc-do-hoa-da-nang-i7-12700f-rtx-3060",
"url": "\/build-pc-do-hoa-da-nang-intel-core-i7-12700f-va-rtx-3060-12gb",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": false,
"summary": "",
"tags": "",
"createDate": "18-10-2022, 2:57 pm",
"createBy": 22,
"create_by_name": "Mai V\u0103n H\u1ecdc",
"lastUpdate": "27-07-2023, 3:19 pm",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 393,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "C\u1ea5u H\u00ecnh \"Ch\u1ea5t Ch\u01a1i\" V\u1edbi i9-13900K - RTX 3070 Si\u00eau Kh\u1ecfe!",
"meta_keywords": "",
"meta_description": "Build pc \u0111\u1ed3 h\u1ecda \u0111a n\u0103ng Intel Core i9-13900K v\u00e0 RTX 3070 t\u1ea1i Ho\u00e0ng H\u00e0 PC gi\u00e1 c\u1ef1c \u01b0u \u0111\u00e3i, b\u1ea3o h\u00e0nh t\u1eadn nh\u00e0. H\u1ed7 tr\u1ee3 tr\u1ea3 g\u00f3p.\r\n",
"url_canonical": "",
"article_time": "",
"allow_se_index": 0,
"comment_count": 0,
"comment_rate": 0,
"counter": 4,
"image": {
"thum": "https:\/\/hoanghapccdn.com\/media\/news\/120_1179_pc_do_hoa_13900k_3070.jpg",
"original": "https:\/\/hoanghapccdn.com\/media\/news\/1179_pc_do_hoa_13900k_3070.jpg"
},
"categories": [
{
"id": 31,
"type": "video",
"name": "Trending video",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 413,
"item_count": 6,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/trending-video",
"relate_product": null
}
]
},
{
"id": 1170,
"type": "video",
"changeCount": 0,
"sellerId": 0,
"article_category": ",31,",
"title": "PC Gaming Full Tr\u1eafng Si\u00eau \u0110\u1eb9p V\u1edbi i7-13700K vs RTX 4070",
"video_code": "https:\/\/www.youtube.com\/watch?v=TvJQnov_Wjs",
"external_url": "",
"url": "\/build-pc-da-nang-12900k-va-rtx-3060",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": false,
"summary": "",
"tags": "",
"createDate": "04-10-2022, 8:38 pm",
"createBy": 27,
"create_by_name": "",
"lastUpdate": "27-07-2023, 3:22 pm",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 440,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "PC Gaming Full Tr\u1eafng Si\u00eau \u0110\u1eb9p V\u1edbi i7-13700K vs RTX 4070",
"meta_keywords": "",
"meta_description": "Build PC \u0110a n\u0103ng i7-13700K v\u00e0 RTX 4070 l\u00e0m vi\u1ec7c t\u1ea1i Ho\u00e0ng H\u00e0 PC gi\u00e1 c\u1ef1c \u01b0u \u0111\u00e3i, b\u1ea3o h\u00e0nh t\u1eadn nh\u00e0. H\u1ed7 tr\u1ee3 tr\u1ea3 g\u00f3p. Mi\u1ec5n ph\u00ed v\u1eadn chuy\u1ec3n.\r\n",
"url_canonical": "",
"article_time": "",
"allow_se_index": 0,
"comment_count": 0,
"comment_rate": 0,
"counter": 5,
"image": {
"thum": "https:\/\/hoanghapccdn.com\/media\/news\/120_1170_pc_do_hoa_13700k_4070ti.jpg",
"original": "https:\/\/hoanghapccdn.com\/media\/news\/1170_pc_do_hoa_13700k_4070ti.jpg"
},
"categories": [
{
"id": 31,
"type": "video",
"name": "Trending video",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 413,
"item_count": 6,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/trending-video",
"relate_product": null
}
]
},
{
"id": 1169,
"type": "video",
"changeCount": 0,
"sellerId": 0,
"article_category": ",31,",
"title": "Review PC H\u01a1n 50 Tri\u1ec7u C\u1ef1c Ng\u1ea7u Full \u0110en",
"video_code": "https:\/\/www.youtube.com\/watch?v=kOAs-q4Qp6k",
"external_url": "",
"url": "\/buid-pc-do-hoa-cao-cap-intel-12900k-rtx-3070",
"redirect_url": "",
"url_hash": "0",
"image_background": "",
"extend": false,
"summary": "",
"tags": "",
"createDate": "04-10-2022, 7:41 pm",
"createBy": 27,
"create_by_name": "",
"lastUpdate": "27-07-2023, 3:24 pm",
"lastUpdateBy": 22,
"lastUpdateByUser": "Mai V\u0103n H\u1ecdc",
"review_rate": 0,
"review_count": 0,
"visit": 447,
"like_count": 0,
"is_featured": 0,
"album_id": 0,
"search_fulltext": null,
"meta_title": "Review PC H\u01a1n 50 Tri\u1ec7u C\u1ef1c Ng\u1ea7u Full \u0110en",
"meta_keywords": "",
"meta_description": "Build PC \u0111\u1ed3 h\u1ecda cao c\u1ea5p Intel Core i9 13900K, RTX 4070 t\u1ea1i Ho\u00e0ng H\u00e0 PC gi\u00e1 c\u1ef1c r\u1ebb, b\u1ea3o h\u00e0nh t\u1eadn nh\u00e0. H\u1ed7 tr\u1ee3 tr\u1ea3 g\u00f3p. Mi\u1ec5n ph\u00ed v\u1eadn chuy\u1ec3n.\r\n",
"url_canonical": "",
"article_time": "",
"allow_se_index": 0,
"comment_count": 0,
"comment_rate": 0,
"counter": 6,
"image": {
"thum": "https:\/\/hoanghapccdn.com\/media\/news\/120_1169_pc_do_hoa_13900k_4070ti.jpg",
"original": "https:\/\/hoanghapccdn.com\/media\/news\/1169_pc_do_hoa_13900k_4070ti.jpg"
},
"categories": [
{
"id": 31,
"type": "video",
"name": "Trending video",
"summary": "",
"description": "",
"isParent": 0,
"imgUrl": "",
"parentId": 0,
"extend": null,
"visit": 413,
"item_count": 6,
"display_option": "child_article",
"lastUpdateBy": 27,
"request_path": "\/trending-video",
"relate_product": null
}
]
}
]
}

View File

@@ -215,7 +215,7 @@ export const articleList = [
"review_count": 0,
"visit": 214588,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "7 C\u1ea5u H\u00ecnh M\u00e1y T\u00ednh Cho Streamer Livestream\u2714\ufe0fChuy\u00ean Nghi\u1ec7p",
@@ -655,7 +655,7 @@ export const articleList = [
"review_count": 0,
"visit": 2333,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "RAM v\u00e0 SSD \u0111ang t\u0103ng gi\u00e1 phi m\u00e3 t\u1ea1i Vi\u1ec7t Nam: Chuy\u1ec7n g\u00ec \u0111ang x\u1ea3y ra?",
@@ -717,7 +717,7 @@ export const articleList = [
"review_count": 0,
"visit": 802,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "",
@@ -903,7 +903,7 @@ export const articleList = [
"review_count": 0,
"visit": 15176,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "Build 1 B\u1ed9 PC C\u1ea7n Nh\u1eefng G\u00ec? C\u00e1c B\u01b0\u1edbc L\u1eafp R\u00e1p 1 B\u1ed9 M\u00e1y T\u00ednh",
@@ -965,7 +965,7 @@ export const articleList = [
"review_count": 0,
"visit": 663,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "ZADAK ra m\u1eaft RAM DDR4 2 t\u1ea7ng, dung l\u01b0\u1ee3ng 32 GB\/thanh",
@@ -1298,7 +1298,7 @@ export const articleList = [
"review_count": 0,
"visit": 11822,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "",
@@ -1360,7 +1360,7 @@ export const articleList = [
"review_count": 0,
"visit": 1567,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "",
@@ -1484,7 +1484,7 @@ export const articleList = [
"review_count": 0,
"visit": 879,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "",
@@ -1608,7 +1608,7 @@ export const articleList = [
"review_count": 0,
"visit": 604,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "",
@@ -1670,7 +1670,7 @@ export const articleList = [
"review_count": 0,
"visit": 182444,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "Khuy\u1ebfn M\u1ea1i Si\u00eau H\u1ea5p D\u1eabn D\u00e0nh Cho H\u1ecdc Sinh, Sinh Vi\u00ean - HHPC",
@@ -1856,7 +1856,7 @@ export const articleList = [
"review_count": 0,
"visit": 923,
"like_count": 0,
"is_featured": 0,
"is_featured": 1,
"album_id": 0,
"search_fulltext": null,
"meta_title": "",

View File

@@ -493,7 +493,7 @@ export const CartData = {
}
],
"cart_summary" : {
"total_value": 28990000,
"total_value": 717240000,
"total_quantity": 4,
"total_item": 6,
"total_weight": 0,

View File

@@ -9,8 +9,6 @@ import {
increaseQuantity,
decreaseQuantity,
updateQuantity,
getProductQuantity,
isProductInCart,
CART_CHANGE_EVENT,
type CartItem,
} from '../services/cart';
@@ -77,13 +75,13 @@ export function useCart() {
removeProductFromCart(productId);
}, []);
const increase = useCallback((productId: number, amount: number = 1) => {
const success = increaseQuantity(productId, amount);
const increase = useCallback((productId: number) => {
const success = increaseQuantity(productId);
return success;
}, []);
const decrease = useCallback((productId: number, amount: number = 1) => {
const success = decreaseQuantity(productId, amount);
const decrease = useCallback((productId: number) => {
const success = decreaseQuantity(productId);
if (!success) {
console.log('Số lượng tối thiểu: 1');
}
@@ -106,18 +104,18 @@ export function useCart() {
}, []);
const isInCart = useCallback(
(productId: number) => cartItems?.some(item => item.id === productId) ?? false,
(productId: number) => cartItems?.some(item => item.item_info.id === productId) ?? false,
[cartItems]
);
const getQuantity = useCallback(
(productId: number) => {
return cartItems?.find(item => item.id === productId)?.cartQuantity ?? 0;
return cartItems?.find(item => item.item_info.id === productId)?.in_cart.quantity ?? 0;
},
[cartItems]
);
const totalItems = cartItems?.reduce((sum, item) => sum + item.cartQuantity, 0) ?? 0;
const totalItems = cartItems?.reduce((sum, item) => sum + item.in_cart.quantity, 0) ?? 0;
return {
cartItems: cartItems ?? [],

61
src/hooks/useScrollSpy.ts Normal file
View File

@@ -0,0 +1,61 @@
'use client';
import { useEffect } from 'react';
export default function useScrollSpy({
menuSelector = '.js-category-tab',
offsetTop = 250,
scrollOffset = 120,
} = {}) {
useEffect(() => {
const menuItems = document.querySelectorAll(menuSelector);
if (!menuItems.length) return;
const sections = Array.from(menuItems)
.map(item => {
const id = item.getAttribute('href');
if (!id) return null;
return document.querySelector(id);
})
.filter(Boolean);
const onScroll = () => {
const scrollPos = window.scrollY + offsetTop;
let currentId = '';
sections.forEach(section => {
if (section.offsetTop <= scrollPos) {
currentId = section.id;
}
});
menuItems.forEach(item => item.classList.remove('active'));
if (currentId) {
document
.querySelector(`${menuSelector}[href="#${currentId}"]`)
?.classList.add('active');
}
};
window.addEventListener('scroll', onScroll);
menuItems.forEach(item => {
item.addEventListener('click', e => {
e.preventDefault();
const targetId = item.getAttribute('href');
const target = targetId && document.querySelector(targetId);
if (!target) return;
window.scrollTo({
top: target.offsetTop - scrollOffset,
behavior: 'smooth',
});
});
});
return () => {
window.removeEventListener('scroll', onScroll);
};
}, [menuSelector, offsetTop, scrollOffset]);
}

View File

@@ -1,55 +1,81 @@
'use client';
import { CartData } from '@/data/cart';
/* ================= TYPES ================= */
export interface CartItem {
id: number;
cartQuantity: number;
id: string;
item_id: string;
item_type: 'product';
item_info: {
id: number; // productId thật
priceUnit: string;
marketPrice: number;
hasVAt: number;
weight: number;
};
in_cart: {
quantity: number;
buyer_note: string;
price: number;
total_price: number;
weight: number;
};
}
const CART_KEY = 'cart_products';
const CART_CHANGE_EVENT = 'cart-changed';
// Helper để dispatch event khi cart thay đổi
/* ================= HELPERS ================= */
function notifyCartChange() {
if (typeof window !== 'undefined') {
window.dispatchEvent(new Event(CART_CHANGE_EVENT));
}
}
// 1. Lấy danh sách sản phẩm trong giỏ
/* ================= CORE ================= */
/**
* 1. Lấy danh sách sản phẩm trong giỏ
*/
export function getCartItems(): CartItem[] {
if (typeof window === 'undefined') return [];
try {
const raw = localStorage.getItem(CART_KEY);
return raw ? (JSON.parse(raw) as CartItem[]) : [];
} catch (error) {
console.error('Invalid cart data', error);
return [];
}
return Array.isArray((CartData as any)?.data)
? (CartData as any).data
: [];
}
// 2. Thêm sản phẩm vào giỏ
export function addProductToCart(productId: number, quantity: number = 1): { success: boolean; message: string; isNew: boolean } {
if (typeof window === 'undefined') {
return { success: false, message: 'Window is undefined', isNew: false };
}
const items = getCartItems();
const existingItem = items.find(item => item.id === productId);
/**
* 2. Kiểm tra sản phẩm đã có trong giỏ hay chưa
*/
export function isProductInCart(productId: number): boolean {
return getCartItems().some(
item => item.item_info?.id === productId
);
}
if (existingItem) {
existingItem.cartQuantity += quantity;
localStorage.setItem(CART_KEY, JSON.stringify(items));
notifyCartChange(); // Notify change
/**
* 3. Add sản phẩm vào giỏ (CHỈ CHECK KHÔNG LƯU)
*/
export function addProductToCart(
productId: number,
quantity: number = 1
): { success: boolean; message: string; isNew: boolean } {
if (isProductInCart(productId)) {
return {
success: true,
message: `Đã cập nhật số lượng thành ${existingItem.cartQuantity}`,
success: false,
message: 'Sản phẩm đã tồn tại trong giỏ hàng',
isNew: false
};
}
items.push({ id: productId, cartQuantity: quantity });
localStorage.setItem(CART_KEY, JSON.stringify(items));
notifyCartChange(); // Notify change
/**
* 👉 await cartApi.add({ productId, quantity })
*/
notifyCartChange();
return {
success: true,
message: 'Đã thêm sản phẩm vào giỏ hàng',
@@ -57,91 +83,89 @@ export function addProductToCart(productId: number, quantity: number = 1): { suc
};
}
// 3. Tăng số lượng sản phẩm
export function increaseQuantity(productId: number, amount: number = 1): boolean {
if (typeof window === 'undefined') return false;
/**
* 4. Tăng số lượng
*/
export function increaseQuantity(productId: number): boolean {
if (!isProductInCart(productId)) return false;
const items = getCartItems();
const item = items.find(item => item.id === productId);
if (item) {
item.cartQuantity += amount;
localStorage.setItem(CART_KEY, JSON.stringify(items));
notifyCartChange(); // Notify change
return true;
}
return false;
/**
* await cartApi.increase(productId)
*/
notifyCartChange();
return true;
}
// 4. Giảm số lượng sản phẩm
export function decreaseQuantity(productId: number, amount: number = 1): boolean {
if (typeof window === 'undefined') return false;
/**
* 5. Giảm số lượng
*/
export function decreaseQuantity(productId: number): boolean {
const item = getCartItems().find(
i => i.item_info.id === productId
);
const items = getCartItems();
const item = items.find(item => item.id === productId);
if (!item || item.in_cart.quantity <= 1) return false;
if (item) {
const newQuantity = item.cartQuantity - amount;
if (newQuantity < 1) {
return false;
}
item.cartQuantity = newQuantity;
localStorage.setItem(CART_KEY, JSON.stringify(items));
notifyCartChange(); // Notify change
return true;
}
return false;
/**
* await cartApi.decrease(productId)
*/
notifyCartChange();
return true;
}
// 5. Update số lượng trực tiếp
export function updateQuantity(productId: number, cartQuantity: number): boolean {
if (typeof window === 'undefined') return false;
/**
* 6. Update số lượng trực tiếp
*/
export function updateQuantity(
productId: number,
quantity: number
): boolean {
if (quantity < 1 || !isProductInCart(productId)) return false;
if (cartQuantity < 1) {
return false;
}
const items = getCartItems();
const item = items.find(item => item.id === productId);
if (item) {
item.cartQuantity = cartQuantity;
localStorage.setItem(CART_KEY, JSON.stringify(items));
notifyCartChange(); // Notify change
return true;
}
return false;
/**
* await cartApi.update(productId, quantity)
*/
notifyCartChange();
return true;
}
// 6. Xóa 1 sản phẩm
export function removeProductFromCart(productId: number) {
if (typeof window === 'undefined') return;
/**
* 7. Xóa 1 sản phẩm khỏi giỏ
*/
export function removeProductFromCart(productId: number): boolean {
if (!isProductInCart(productId)) return false;
const items = getCartItems();
const newItems = items.filter(item => item.id !== productId);
localStorage.setItem(CART_KEY, JSON.stringify(newItems));
notifyCartChange(); // Notify change
/**
* await cartApi.remove(productId)
*/
notifyCartChange();
return true;
}
// 7. Xóa toàn bộ giỏ hàng
export function clearCart() {
if (typeof window === 'undefined') return;
localStorage.removeItem(CART_KEY);
notifyCartChange(); // Notify change
/**
* 8. Xóa toàn bộ giỏ hàng
*/
export function clearCart(): boolean {
if (!getCartItems().length) return false;
/**
* await cartApi.clear()
*/
notifyCartChange();
return true;
}
// 8. Lấy cartQuantity của 1 sản phẩm
/**
* 9. Lấy số lượng sản phẩm trong giỏ
*/
export function getProductQuantity(productId: number): number {
const items = getCartItems();
const item = items.find(item => item.id === productId);
return item?.cartQuantity ?? 0;
return (
getCartItems().find(
item => item.item_info.id === productId
)?.in_cart.quantity ?? 0
);
}
// 9. Kiểm tra sản phẩm có trong giỏ hàng không
export function isProductInCart(productId: number): boolean {
const items = getCartItems();
return items.some(item => item.id === productId);
}
/* ================= EXPORT ================= */
// 10. Export event name để hooks sử dụng
export { CART_CHANGE_EVENT };

View File

@@ -322,10 +322,12 @@ body{min-width:1248px;background:#E8ECF6}
.top-article-container .art-item .art-img{margin:0;width:192px;padding-bottom:130px}
.top-article-container .art-item .art-text{width:calc(100% - 204px)}
.article-categories-group {z-index: 12}
.article-categories-group a{text-align:center;flex:1;padding-bottom:20px;position:relative}
.article-categories-group a::after{content:"";position:absolute;left:0;bottom:0;right:0;max-width:100%;height:3px;background:transparent;transition:.25s all;width:0}
.article-categories-group a:hover,.article-categories-group a.active{color:#0678DB}
.article-categories-group a:hover::after,.article-categories-group a.active::after{background:#0678DB;width:100%}
.article-categories-group a, .article-categories-group button{text-align:center;flex:1;padding-bottom:20px;position:relative;cursor: pointer;}
.article-categories-group a::after, .article-categories-group button::after{content:"";position:absolute;left:0;bottom:0;right:0;max-width:100%;height:3px;background:transparent;transition:.25s all;width:0}
.article-categories-group a:hover,.article-categories-group a.active,
.article-categories-group button:hover,.article-categories-group button.active{color:#0678DB}
.article-categories-group a:hover::after,.article-categories-group a.active::after,
.article-categories-group button:hover::after,.article-categories-group button.active::after{background:#0678DB;width:100%}
.article-category-container{padding:64px 0}
.article-category-container:nth-child(even){background:#E8ECF6}
.article-category-container .art-summary{display:none}