update 30/01
This commit is contained in:
12549
package-lock.json
generated
12549
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fancyapps/ui": "5.0",
|
||||
"html-react-parser": "^5.2.14",
|
||||
"next": "16.1.0",
|
||||
"photoswipe": "^5.4.4",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"swiper": "^12.0.3"
|
||||
|
||||
53
src/components/product/detail/image/PhotoSwipeImage.tsx
Normal file
53
src/components/product/detail/image/PhotoSwipeImage.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import PhotoSwipeLightbox from 'photoswipe/lightbox'
|
||||
import 'photoswipe/style.css'
|
||||
|
||||
export default function PhotoSwipeImage({
|
||||
images,
|
||||
activeIndex
|
||||
}: {
|
||||
images: string[]
|
||||
activeIndex: number
|
||||
}) {
|
||||
const galleryRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!galleryRef.current) return
|
||||
|
||||
const lightbox = new PhotoSwipeLightbox({
|
||||
gallery: '#pswp-gallery',
|
||||
children: 'a',
|
||||
pswpModule: () => import('photoswipe')
|
||||
})
|
||||
|
||||
lightbox.init()
|
||||
|
||||
return () => lightbox.destroy()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
id="pswp-gallery"
|
||||
ref={galleryRef}
|
||||
className="w-full"
|
||||
>
|
||||
{images.map((img, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={img}
|
||||
data-pswp-width="1200"
|
||||
data-pswp-height="1200"
|
||||
className={index === activeIndex ? 'block' : 'hidden'}
|
||||
>
|
||||
<img
|
||||
src={img}
|
||||
alt=""
|
||||
className="w-full object-contain cursor-zoom-in"
|
||||
/>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,84 +1,56 @@
|
||||
export default function ProductImage() {
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
import { Navigation } from 'swiper/modules'
|
||||
import Link from 'next/link'
|
||||
|
||||
const PhotoSwipeImage = dynamic(
|
||||
() => import('./PhotoSwipeImage'),
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
export default function ProductImage({ data }: any) {
|
||||
const images: string[] = Array.from(
|
||||
new Set([
|
||||
data.productImage.large.replace('250_', ''),
|
||||
...(data.imageCollection?.map((i: any) =>
|
||||
i.image.large.replace('250_', '')
|
||||
) || [])
|
||||
])
|
||||
)
|
||||
|
||||
const [activeIndex, setActiveIndex] = useState(0)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pd-image-top mb-3">
|
||||
<a
|
||||
className="MagicZoom"
|
||||
id="Zoomer"
|
||||
rel="selectors-effect-speed: 600"
|
||||
href="images/product-1.jpg"
|
||||
>
|
||||
<img src="images/product-1.jpg" alt="" />
|
||||
</a>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="pd-image-btn"
|
||||
/>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
className="pd-image-btn btn-next"
|
||||
<div className="pd-image-top mb-4">
|
||||
<PhotoSwipeImage
|
||||
images={images}
|
||||
activeIndex={activeIndex}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="pd-gallery-list">
|
||||
<div className="swiper w-full" id="js-pd-gallery">
|
||||
<div className="swiper-wrapper">
|
||||
<div className="swiper-slide">
|
||||
<a
|
||||
href="images/product-1.jpg"
|
||||
rel="zoom-id:Zoomer"
|
||||
rev="images/product-1.jpg"
|
||||
>
|
||||
<img src="images/product-1.jpg" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="swiper-slide">
|
||||
<a
|
||||
href="images/product-2.jpg"
|
||||
rel="zoom-id:Zoomer"
|
||||
rev="images/product-2.jpg"
|
||||
>
|
||||
<img src="images/product-2.jpg" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="swiper-slide">
|
||||
<a
|
||||
href="images/product-3.jpg"
|
||||
rel="zoom-id:Zoomer"
|
||||
rev="images/product-3.jpg"
|
||||
>
|
||||
<img src="images/product-3.jpg" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="swiper-slide">
|
||||
<a
|
||||
href="images/product-4.jpg"
|
||||
rel="zoom-id:Zoomer"
|
||||
rev="images/product-4.jpg"
|
||||
>
|
||||
<img src="images/product-4.jpg" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="swiper-slide">
|
||||
<a
|
||||
href="images/product-5.jpg"
|
||||
rel="zoom-id:Zoomer"
|
||||
rev="images/product-5.jpg"
|
||||
>
|
||||
<img src="images/product-5.jpg" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
<div className="swiper-slide">
|
||||
<a
|
||||
href="images/product-6.jpg"
|
||||
rel="zoom-id:Zoomer"
|
||||
rev="images/product-6.jpg"
|
||||
>
|
||||
<img src="images/product-6.jpg" alt="" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Swiper
|
||||
modules={[Navigation]}
|
||||
spaceBetween={12}
|
||||
slidesPerView={5}
|
||||
>
|
||||
{images.map((img, index) => (
|
||||
<SwiperSlide key={img}>
|
||||
<img
|
||||
src={img}
|
||||
onClick={() => setActiveIndex(index)}
|
||||
className={`cursor-pointer border rounded-md
|
||||
${activeIndex === index
|
||||
? 'border-[#0678DB]'
|
||||
: 'border-transparent'}`}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -17,10 +17,22 @@ export default async function ProductDetail({ slug }: any) {
|
||||
review,
|
||||
visit,
|
||||
quantity,
|
||||
productSummary
|
||||
|
||||
productSummary,
|
||||
productImage, imageCollection,
|
||||
price, marketPrice, deal_list, price_off, sale_rules,
|
||||
hasVAT, warranty,
|
||||
specialOffer
|
||||
} = slug
|
||||
|
||||
const image = {
|
||||
productImage,
|
||||
imageCollection
|
||||
}
|
||||
|
||||
const priceData = {
|
||||
price, marketPrice, deal_list, price_off, sale_rules, hasVAT, warranty, quantity
|
||||
}
|
||||
|
||||
console.log(slug)
|
||||
|
||||
return (
|
||||
@@ -33,7 +45,7 @@ export default async function ProductDetail({ slug }: any) {
|
||||
|
||||
<div className="gap-6 flex flex-wrap items-start leading-[18px]">
|
||||
<div className="col-left-group w-[424px] sticky top-[90px]">
|
||||
<ProductImage />
|
||||
<ProductImage data={image} />
|
||||
</div>
|
||||
|
||||
<div className="col-middle-group w-[464px]">
|
||||
@@ -69,9 +81,9 @@ export default async function ProductDetail({ slug }: any) {
|
||||
<ProductSummary item={productSummary} />
|
||||
}
|
||||
|
||||
<ProductPrice />
|
||||
<ProductPrice item={priceData} />
|
||||
|
||||
<ProductOffer />
|
||||
<ProductOffer item={specialOffer}/>
|
||||
|
||||
<Buttons />
|
||||
|
||||
|
||||
@@ -1,43 +1,21 @@
|
||||
export default function ProductOffer() {
|
||||
import { formatTextList } from "@/lib/utils"
|
||||
import parse from 'html-react-parser';
|
||||
|
||||
export default function ProductOffer({ item }: any) {
|
||||
return (
|
||||
<>
|
||||
{/* Khuyến mại hấp dẫn */}
|
||||
<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-discount mr-[6px] animation-tada" />
|
||||
<span> Khuyến mại hấp dẫn </span>
|
||||
</div>
|
||||
<div className="rounded-[8px] bg-[#FEF2F2] px-2 py-4">
|
||||
<div className="item">
|
||||
{" "}
|
||||
Giảm ngay 100.000đ khi mua thêm Màn Hình Máy Tính.
|
||||
{item.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" />
|
||||
<span> Quà tặng và ưu đãi kèm theo </span>
|
||||
</div>
|
||||
<div className="item"> Giảm ngay 100.000đ khi mua thêm RAM </div>
|
||||
<div className="item">
|
||||
{" "}
|
||||
Giảm ngay 100.000đ khi mua thêm Card màn hình{" "}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quà tặng và ưu đãi kèm theo */}
|
||||
<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" />
|
||||
<span> Quà tặng và ưu đãi kèm theo </span>
|
||||
</div>
|
||||
<div className="rounded-[8px] bg-[#FEF2F2] px-2 py-4">
|
||||
<div className="item">
|
||||
{" "}
|
||||
Giảm ngay 100.000đ khi mua thêm Màn Hình Máy Tính.
|
||||
</div>
|
||||
<div className="item"> Giảm ngay 100.000đ khi mua thêm RAM </div>
|
||||
<div className="item">
|
||||
{" "}
|
||||
Giảm ngay 100.000đ khi mua thêm Card màn hình{" "}
|
||||
<div className="rounded-[8px] bg-[#FEF2F2] px-2 py-4">
|
||||
{parse(formatTextList(item.all[0].title))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
92
src/components/product/detail/price/DealPrice.tsx
Normal file
92
src/components/product/detail/price/DealPrice.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { formatPrice } from "@/lib/utils";
|
||||
import { DealCountdown } from "@/lib/times"
|
||||
|
||||
export default function DealPrice( {item} : any ) {
|
||||
|
||||
const price = item.sale_rules.price;
|
||||
const normal_price = item.sale_rules.normal_price;
|
||||
const discount = Math.ceil(((normal_price - price) / normal_price) * 100);
|
||||
|
||||
const sale_quantity = item.deal_list[0].sale_quantity;
|
||||
const quantity = item.deal_list[0].quantity;
|
||||
const saleRemainPercent = 100 - (sale_quantity / quantity) * 100;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pd-deal-group rounded-[12px] bg-[#FEF2F2] overflow-hidden border border-[#FA354A] my-3">
|
||||
<div className="group-title p-2 bg-[linear-gradient(270.05deg,#CB0F23_0.04%,#FF3246_99.97%)] flex items-center flex-wrap justify-between gap-2">
|
||||
<p className="m-0 leading-7 font-600 text-18 flex items-center gap-[6px] text-white">
|
||||
<i className="icons icon-flame animation-beat" />
|
||||
<span> BIG SALE </span>
|
||||
</p>
|
||||
|
||||
<div className="relative bg-[#EBEBEB] rounded-[20px] text-center font-500 text-13 leading-[22px] px-[51px]">
|
||||
<i className="w-[22px] h-[28px] absolute left-[-8px] top-[-4px] z-[1] bg-no-repeat bg-center bg-[length:100%_100%] animation-bounce lazy"
|
||||
data-bg="url(images/deal-icon-bolt.png)"
|
||||
/>
|
||||
|
||||
<i className="bg-[#FFE078] absolute inset-0 max-w-[100%] rounded-[20px]"
|
||||
style={{ width: saleRemainPercent+"%" }}
|
||||
/>
|
||||
|
||||
<span className="relative z-[1] block">
|
||||
Còn: {sale_quantity}/{quantity} sản phẩm
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
<div className="leading-8 flex items-baseline flex-wrap mb-1">
|
||||
<p className="pd-price text-[#FF4E2A] font-bold mb-0 mr-3 text-24">
|
||||
{formatPrice(price)} đ
|
||||
</p>
|
||||
|
||||
{ discount > 0 &&
|
||||
<>
|
||||
<del className="mr-2 text-16">
|
||||
{ formatPrice(normal_price) } đ
|
||||
</del>
|
||||
|
||||
<span className="pd-discount bg-[#FA354A] text-white leading-4 rounded-[20px] px-[6px] font-500">
|
||||
-{discount}%
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1 font-500 text-[#2563EB] leading-[22px]">
|
||||
{item.hasVAT == 1 &&
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2"> Giá đã bao gồm VAT </p>
|
||||
}
|
||||
|
||||
{item.hasVAT == 2 &&
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2"> Giá chưa bao gồm VAT </p>
|
||||
}
|
||||
|
||||
{item.warranty &&
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2"> {item.warranty} </p>
|
||||
}
|
||||
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2">
|
||||
{ item.quantity > 0 ? 'Còn hàng' : 'Liên hệ' }
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="my-2 flex items-center leading-6 gap-2">
|
||||
<p className="m-0"> Kết thúc sau: </p>
|
||||
|
||||
<div className="deal-time-holder flex items-center gap-5 text-white text-14 font-600">
|
||||
<DealCountdown endTime={item.deal_list[0].to_time} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="m-0 font-600 text-13 leading-4 text-[#FF4E2A] mt-3">
|
||||
*KHÔNG ÁP DỤNG CỘNG DỒN CHƯƠNG TRÌNH KHUYẾN MẠI KHÁC
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
40
src/components/product/detail/price/Price.tsx
Normal file
40
src/components/product/detail/price/Price.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { formatPrice } from "@/lib/utils";
|
||||
|
||||
export default function Price( {item} : any ) {
|
||||
return (
|
||||
<div className="my-3 border border-[#FA354A] rounded-[12px] leading-[22px] p-4 pd-price-group">
|
||||
<div className="leading-8 flex items-baseline flex-wrap mb-1">
|
||||
<p className="pd-price text-[#FF4E2A] font-bold mb-0 mr-3 text-24">
|
||||
{item.price > 0 ? formatPrice(item.price) + 'đ' : ''}
|
||||
</p>
|
||||
|
||||
{ item.price_off &&
|
||||
<>
|
||||
<del className="mr-2 text-16"> {formatPrice(item.marketPrice)} đ </del>
|
||||
<span className="pd-discount bg-[#FA354A] text-white leading-4 rounded-[20px] px-[6px] font-500">
|
||||
-{item.price_off}%
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1 font-500 text-[#2563EB]">
|
||||
{item.hasVAT == 1 &&
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2"> Giá đã bao gồm VAT </p>
|
||||
}
|
||||
|
||||
{item.hasVAT == 2 &&
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2"> Giá chưa bao gồm VAT </p>
|
||||
}
|
||||
|
||||
{item.warranty &&
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2"> {item.warranty} </p>
|
||||
}
|
||||
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2">
|
||||
{ item.quantity > 0 ? 'Còn hàng' : 'Liên hệ' }
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,86 +1,18 @@
|
||||
export default function ProductPrice() {
|
||||
import DealPrice from "./DealPrice";
|
||||
import Price from "./Price";
|
||||
|
||||
export default function ProductPrice({ item }: any) {
|
||||
if (!item) return null
|
||||
|
||||
const isDeal = item?.sale_rules?.type === 'deal';
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Deal */}
|
||||
<div className="pd-deal-group rounded-[12px] bg-[#FEF2F2] overflow-hidden border border-[#FA354A] ">
|
||||
<div className="group-title p-2 bg-[linear-gradient(270.05deg,#CB0F23_0.04%,#FF3246_99.97%)] flex items-center flex-wrap justify-between gap-2">
|
||||
<p className="m-0 leading-7 font-600 text-18 flex items-center gap-[6px] text-white">
|
||||
<i className="icons icon-flame animation-beat" />
|
||||
<span> BIG SALE </span>
|
||||
</p>
|
||||
<div className="relative bg-[#EBEBEB] rounded-[20px] text-center font-500 text-13 leading-[22px] px-[51px]">
|
||||
<i
|
||||
className="w-[22px] h-[28px] absolute left-[-8px] top-[-4px] z-[1] bg-no-repeat bg-center bg-[length:100%_100%] animation-bounce lazy"
|
||||
data-bg="url(images/deal-icon-bolt.png)"
|
||||
/>
|
||||
<i
|
||||
className="bg-[#FFE078] absolute inset-0 max-w-[100%] rounded-[20px]"
|
||||
style={{ width: "50%" }}
|
||||
/>
|
||||
<span className="relative z-[1] block"> Còn: 3/5 sản phẩm </span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="leading-8 flex items-baseline flex-wrap mb-1">
|
||||
<p className="pd-price text-[#FF4E2A] font-bold mb-0 mr-3 text-24">
|
||||
{" "}
|
||||
48.990.000 đ{" "}
|
||||
</p>
|
||||
<del className="mr-2 text-16"> 52.000.000 đ </del>
|
||||
<span className="pd-discount bg-[#FA354A] text-white leading-4 rounded-[20px] px-[6px] font-500">
|
||||
{" "}
|
||||
-6%{" "}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1 font-500 text-[#2563EB] leading-[22px]">
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2">
|
||||
{" "}
|
||||
Giá đã bao gồm VAT{" "}
|
||||
</p>
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2">
|
||||
{" "}
|
||||
Bảo hành theo từng linh kiện{" "}
|
||||
</p>
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2"> Liên hệ </p>
|
||||
</div>
|
||||
<div className="my-2 flex items-center leading-6 gap-2">
|
||||
<p className="m-0"> Kết thúc sau: </p>
|
||||
<div className="deal-time-holder flex items-center gap-5 text-white text-14 font-600">
|
||||
<p> 00 </p> <p> 00 </p> <p> 00 </p> <p> 00 </p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="m-0 font-600 text-13 leading-4 text-[#FF4E2A] mt-3">
|
||||
{" "}
|
||||
*KHÔNG ÁP DỤNG CỘNG DỒN CHƯƠNG TRÌNH KHUYẾN MẠI KHÁC{" "}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Price */}
|
||||
<div className="my-3 border border-[#FA354A] rounded-[12px] leading-[22px] p-4 pd-price-group">
|
||||
<div className="leading-8 flex items-baseline flex-wrap mb-1">
|
||||
<p className="pd-price text-[#FF4E2A] font-bold mb-0 mr-3 text-24">
|
||||
{" "}
|
||||
48.990.000 đ{" "}
|
||||
</p>
|
||||
<del className="mr-2 text-16"> 52.000.000 đ </del>
|
||||
<span className="pd-discount bg-[#FA354A] text-white leading-4 rounded-[20px] px-[6px] font-500">
|
||||
{" "}
|
||||
-6%{" "}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1 font-500 text-[#2563EB]">
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2">
|
||||
{" "}
|
||||
Giá đã bao gồm VAT{" "}
|
||||
</p>
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2">
|
||||
{" "}
|
||||
Bảo hành theo từng linh kiện{" "}
|
||||
</p>
|
||||
<p className="m-0 bg-[#EFF6FF] rounded-[6px] px-2"> Liên hệ </p>
|
||||
</div>
|
||||
</div>
|
||||
{isDeal ? (
|
||||
<DealPrice item={item} />
|
||||
) : (
|
||||
<Price item={item} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import Link from "next/link"
|
||||
import FancyboxWrapper from "@/components/shared/FancyboxWrapper"
|
||||
|
||||
export default function Static() {
|
||||
return (
|
||||
<>
|
||||
<FancyboxWrapper>
|
||||
<div className="group relative border border-[#D6DAE1] leading-[38px] rounded-[8px] pl-3 pr-2 mb-3">
|
||||
<p className="m-0 flex items-center justify-between cursor-pointer">
|
||||
<span> Xem chi nhánh còn hàng </span>
|
||||
@@ -11,7 +16,7 @@ export default function Static() {
|
||||
<b className="block underline px-2 font-600 mb-2">
|
||||
Showroom Miền Bắc:
|
||||
</b>
|
||||
<a
|
||||
<Link
|
||||
href="https://goo.gl/maps/56ARHjWKoVhpWBCF6"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
@@ -19,8 +24,8 @@ export default function Static() {
|
||||
>
|
||||
<i className="icons icon-location" />
|
||||
<span> 41 Khúc Thừa Dụ, Phường Cầu Giấy, Hà Nội </span>
|
||||
</a>
|
||||
<a
|
||||
</Link>
|
||||
<Link
|
||||
href="https://g.page/hoanghapc?share"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
@@ -28,13 +33,13 @@ export default function Static() {
|
||||
>
|
||||
<i className="icons icon-location" />
|
||||
<span> 94E-94F Đường Láng, Phường Đống Đa, Hà Nội </span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="my-3">
|
||||
<b className="block underline px-2 font-600 mb-2">
|
||||
Showroom Miền Trung:
|
||||
</b>
|
||||
<a
|
||||
<Link
|
||||
href="https://goo.gl/maps/1HQrD6mdf4VMYccs6"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
@@ -42,13 +47,13 @@ export default function Static() {
|
||||
>
|
||||
<i className="icons icon-location" />
|
||||
<span>72 Lê Lợi, Thành Vinh, Nghệ An </span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="my-3">
|
||||
<b className="block underline px-2 font-600 mb-2">
|
||||
Showroom Miền Nam:
|
||||
</b>
|
||||
<a
|
||||
<Link
|
||||
href="https://g.page/hoanghapchcm?share"
|
||||
target="_blank"
|
||||
rel="nofollow"
|
||||
@@ -56,102 +61,102 @@ export default function Static() {
|
||||
>
|
||||
<i className="icons icon-location" />
|
||||
<span>
|
||||
{" "}
|
||||
|
||||
K8bis Bửu Long, Phường Hoà Hưng, Thành phố Hồ Chí Minh
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<i className="block red my-3 px-2">
|
||||
Chú ý: Sản phẩm có thể điều chuyển kho theo yêu cầu của quý khách.
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Yên Tâm Mua Sắm Tại HoangHaPC */}
|
||||
<div className="pd-static-group mb-3 rounded-[12px] bg-[linear-gradient(180.3deg,#259AFF_-18.56%,#114CDD_100.92%)] p-1 pt-2">
|
||||
<p className="group-title text-white leading-[21px] text-16 font-600 mb-2 text-center">
|
||||
{" "}
|
||||
Yên Tâm Mua Sắm Tại HoangHaPC{" "}
|
||||
Yên Tâm Mua Sắm Tại HoangHaPC
|
||||
</p>
|
||||
|
||||
<div className="pd-static-list bg-white p-[16px_8px] leading-[18px] font-500 rounded-[8px]">
|
||||
<p className="last:mb-0 mb-2 item-circle">
|
||||
{" "}
|
||||
Đội ngũ kỹ thuật tư vấn chuyên sâu{" "}
|
||||
Đội ngũ kỹ thuật tư vấn chuyên sâu
|
||||
</p>
|
||||
|
||||
<p className="last:mb-0 mb-2 item-circle">
|
||||
{" "}
|
||||
Thanh toán thuận tiện{" "}
|
||||
Thanh toán thuận tiện
|
||||
</p>
|
||||
|
||||
<p className="last:mb-0 mb-2 item-circle">
|
||||
{" "}
|
||||
Sản phẩm 100% chính hãng{" "}
|
||||
Sản phẩm 100% chính hãng
|
||||
</p>
|
||||
|
||||
<p className="last:mb-0 mb-2 item-circle">
|
||||
{" "}
|
||||
Bảo hành 1 đổi 1 tại nơi sử dụng{" "}
|
||||
Bảo hành 1 đổi 1 tại nơi sử dụng
|
||||
</p>
|
||||
|
||||
<p className="last:mb-0 mb-2 item-circle">
|
||||
{" "}
|
||||
Giá cạnh tranh nhất thị trường{" "}
|
||||
Giá cạnh tranh nhất thị trường
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/* Liên Hệ Với Kinh Doanh Online */}
|
||||
|
||||
<div className="pd-static-group mb-3 rounded-[12px] bg-[linear-gradient(180.3deg,#259AFF_-18.56%,#114CDD_100.92%)] p-1 pt-2">
|
||||
<p className="group-title text-white leading-[21px] text-16 font-600 mb-2 text-center">
|
||||
{" "}
|
||||
Liên Hệ Với Kinh Doanh Online{" "}
|
||||
Liên Hệ Với Kinh Doanh Online
|
||||
</p>
|
||||
|
||||
<div className="pd-static-list bg-white p-[16px_8px] leading-[18px] font-500 rounded-[8px]">
|
||||
<div className="last:mb-0 mb-2 flex gap-2">
|
||||
<i className="icons icon-phone" />
|
||||
<p className="m-0">
|
||||
Hotline Hà Nội:{" "}
|
||||
<a href="tel:0969123666" className="red font-500">
|
||||
{" "}
|
||||
0969123666{" "}
|
||||
</a>
|
||||
Hotline Hà Nội:
|
||||
<Link href="tel:0969123666" className="red font-500">
|
||||
0969123666
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="last:mb-0 mb-2 flex gap-2">
|
||||
<i className="icons icon-phone" />
|
||||
<p className="m-0">
|
||||
Hotline Vinh, Nghệ An:{" "}
|
||||
<a href="tel:0988.163.666" className="red font-500">
|
||||
{" "}
|
||||
0988.163.666{" "}
|
||||
</a>
|
||||
Hotline Vinh, Nghệ An:
|
||||
<Link href="tel:0988.163.666" className="red font-500">
|
||||
0988.163.666
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="last:mb-0 mb-2 flex gap-2">
|
||||
<i className="icons icon-phone" />
|
||||
<p className="m-0">
|
||||
Hotline Hồ Chí Minh:{" "}
|
||||
<a href="tel:0968.123.666" className="red font-500">
|
||||
{" "}
|
||||
0968.123.666{" "}
|
||||
</a>
|
||||
Hotline Hồ Chí Minh:
|
||||
<Link href="tel:0968.123.666" className="red font-500">
|
||||
0968.123.666
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="last:mb-0 mb-2 flex gap-2">
|
||||
<i className="icons icon-phone" />
|
||||
<p className="m-0">
|
||||
Hotline Bảo Hành:{" "}
|
||||
<a href="tel:1900.6100" className="red font-500">
|
||||
{" "}
|
||||
1900.6100{" "}
|
||||
</a>
|
||||
Hotline Bảo Hành:
|
||||
<Link href="tel:1900.6100" className="red font-500">
|
||||
1900.6100
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-[#0678DB] rounded-[12px] px-3 py-4 gap-[6px] flex flex-wrap items-center">
|
||||
<a
|
||||
href="https://hoanghapc.vn/media/lib/17-10-2022/qr-hoang-ha-pc-nhom.png"
|
||||
data-fancybox=""
|
||||
data-fancybox="gallery"
|
||||
className="w-[110px]"
|
||||
>
|
||||
<img
|
||||
data-src="https://hoanghapc.vn/media/lib/17-10-2022/qr-hoang-ha-pc-nhom.png"
|
||||
src="https://hoanghapc.vn/media/lib/17-10-2022/qr-hoang-ha-pc-nhom.png"
|
||||
alt="QR code"
|
||||
width={110}
|
||||
height={110}
|
||||
@@ -159,11 +164,11 @@ export default function Static() {
|
||||
/>
|
||||
</a>
|
||||
<p className="m-0 font-500 w-[calc(100%-116px)]">
|
||||
{" "}
|
||||
|
||||
Tham gia Cộng đồng "Cẩm Nang Build PC - Đồ Họa, Render, Giả Lập" để
|
||||
theo dõi các ưu đãi dành riêng cho thành viên{" "}
|
||||
theo dõi các ưu đãi dành riêng cho thành viên
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
</FancyboxWrapper>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
|
||||
'use client';
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
export default function ProductSummary({ item }: any) {
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
if (!mounted) return null
|
||||
|
||||
|
||||
return (
|
||||
<div className="mb-3 pd-summary-group">
|
||||
<p className="leading-6 mb-2 text-16 font-600"> Thông số sản phẩm </p>
|
||||
@@ -12,33 +22,33 @@ export default function ProductSummary({ item }: any) {
|
||||
}
|
||||
|
||||
|
||||
function renderSummary(data:any) {
|
||||
function renderSummary(data: any) {
|
||||
if (!data) return null;
|
||||
|
||||
if (typeof data === 'string' && data.includes('<')) {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(data, 'text/html');
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(data, 'text/html')
|
||||
|
||||
return Array.from(doc.body.childNodes)
|
||||
.filter(
|
||||
node =>
|
||||
node.nodeType === 1 && node.textContent !== null && node.textContent.trim() !== ''
|
||||
node.nodeType === 1 &&
|
||||
node.textContent &&
|
||||
node.textContent.trim() !== ''
|
||||
)
|
||||
.map((node, index) => (
|
||||
<div key={index} className="item-circle">
|
||||
{node.textContent?.trim()}
|
||||
{node.textContent!.trim()}
|
||||
</div>
|
||||
));
|
||||
))
|
||||
}
|
||||
|
||||
return data
|
||||
.split(/\r?\n/)
|
||||
.filter((line:any) => line.trim() !== '')
|
||||
.map((line:any, index:any) => (
|
||||
.filter((line: string) => line.trim() !== '')
|
||||
.map((line: string, index: number) => (
|
||||
<div key={index} className="item-circle">
|
||||
{line.trim()}
|
||||
</div>
|
||||
));
|
||||
))
|
||||
}
|
||||
|
||||
19
src/components/shared/FancyboxWrapper.tsx
Normal file
19
src/components/shared/FancyboxWrapper.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { Fancybox as NativeFancybox } from '@fancyapps/ui';
|
||||
|
||||
|
||||
export default function FancyboxWrapper({ children }: { children: React.ReactNode }) {
|
||||
useEffect(() => {
|
||||
NativeFancybox.bind('[data-fancybox]', {
|
||||
// Options
|
||||
});
|
||||
|
||||
return () => {
|
||||
NativeFancybox.destroy();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
'use client';
|
||||
import Link from "next/link";
|
||||
import { formatPrice } from "@/lib/utils";
|
||||
import { formatPrice,formatTextList } from "@/lib/utils";
|
||||
import { useProductItem } from "@/hooks/useProductItem"
|
||||
import { useCart } from '@/hooks/useCart';
|
||||
import { useEffect, useState } from "react";
|
||||
import parse from 'html-react-parser';
|
||||
|
||||
export default function ProductItem({item}:any){
|
||||
const [mounted, setMounted] = useState(false);
|
||||
@@ -131,8 +132,7 @@ export default function ProductItem({item}:any){
|
||||
</p>
|
||||
|
||||
<div className="tooltip-spec">
|
||||
<div suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{ __html: displaySummary }}/>
|
||||
{parse(formatTextList(displaySummary, 5))}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -145,8 +145,7 @@ export default function ProductItem({item}:any){
|
||||
</p>
|
||||
|
||||
<div className="tooltip-offer rounded-[8px] bg-[#FEF2F2] px-2 py-4">
|
||||
<div suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{ __html: displayOffer }}/>
|
||||
{parse(formatTextList(displayOffer, 5))}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
55
src/lib/times.tsx
Normal file
55
src/lib/times.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function DealCountdown({ endTime }: { endTime: number }) {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const [timeLeft, setTimeLeft] = useState(getTimeLeft(endTime));
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
|
||||
const timer = setInterval(() => {
|
||||
setTimeLeft(getTimeLeft(endTime));
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, [endTime]);
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
if (timeLeft.total <= 0) {
|
||||
return <span className="text-red-500">Deal đã kết thúc</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p>{timeLeft.days}</p>
|
||||
<p>{timeLeft.hours}</p>
|
||||
<p>{timeLeft.minutes}</p>
|
||||
<p>{timeLeft.seconds}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function getTimeLeft(endTime: number) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const distance = endTime - now;
|
||||
|
||||
if (distance <= 0) {
|
||||
return {
|
||||
total : 0,
|
||||
days : '00',
|
||||
hours : '00',
|
||||
minutes : '00',
|
||||
seconds : '00'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
total : distance,
|
||||
days : String(Math.floor(distance / 86400)).padStart(2, '0'),
|
||||
hours : String(Math.floor((distance % 86400) / 3600)).padStart(2, '0'),
|
||||
minutes : String(Math.floor((distance % 3600) / 60)).padStart(2, '0'),
|
||||
seconds : String(distance % 60).padStart(2, '0'),
|
||||
};
|
||||
}
|
||||
@@ -8,7 +8,7 @@ export function getAllProducts() {
|
||||
|
||||
export function formatTextList(
|
||||
text?: string | any[],
|
||||
limit = 5
|
||||
limit?: number | undefined
|
||||
) {
|
||||
if (!text) return '';
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
@import url('https://cdn.boxicons.com/fonts/basic/boxicons.min.css');
|
||||
@import url('https://fonts.cdnfonts.com/css/sf-pro-display');
|
||||
|
||||
@import "@fancyapps/ui/dist/fancybox/fancybox.css";
|
||||
|
||||
@import "tailwindcss";
|
||||
@import 'swiper/css';
|
||||
@import 'swiper/css/navigation';
|
||||
@@ -11,5 +9,5 @@
|
||||
@import "../../public/styles/tailwind.css";
|
||||
@import './pc_style.css';
|
||||
|
||||
|
||||
.fancybox__dialog .fancybox__container.is-ready {opacity: 1;}
|
||||
.mz-thumb:not(.mz-thumb-selected):hover img{border: 0 !important}
|
||||
@@ -273,7 +273,7 @@ body{min-width:1248px;background:#E8ECF6}
|
||||
.item-circle::after{content:"";background:#fff;width:3.5px;height:3.5px;border-radius:50%;position:absolute;left:6.5px;top:6px}
|
||||
.item-circle * {margin: 0;}
|
||||
.pd-gallery-list a{border:2px solid transparent;overflow:hidden;display:block;pointer-events:auto!important}
|
||||
.pd-gallery-list img{filter:unset!important;display:block;margin:auto;max-width:100%;max-height:100%}
|
||||
.pd-gallery-list img{filter:unset!important;display:block;margin:auto;max-width:100%;max-height:100%;border-width: 2px}
|
||||
.pd-gallery-list .mz-thumb-selected{border-color:#0678DB}
|
||||
.pd-offer-group .icon-discount{background-position:-76px -194px}
|
||||
.pd-offer-group .item{margin:0 0 8px}
|
||||
|
||||
Reference in New Issue
Block a user