diff options
Diffstat (limited to 'src-migrate/modules')
11 files changed, 304 insertions, 108 deletions
diff --git a/src-migrate/modules/footer-banner/index.tsx b/src-migrate/modules/footer-banner/index.tsx index 7db1363c..b214493d 100644 --- a/src-migrate/modules/footer-banner/index.tsx +++ b/src-migrate/modules/footer-banner/index.tsx @@ -1,15 +1,19 @@ import Link from "next/link" +import React, { useMemo } from "react"; import { useQuery } from "react-query" import Image from "~/components/ui/image" import { getBanner } from "~/services/banner" +import { getRandomInt } from '@/utils/getRandomInt' const FooterBanner = () => { const fetchFooterBanner = useQuery({ queryKey: 'footerBanner', queryFn: () => getBanner({ type: 'bottom-search-promotion' }) }) - - const banner = fetchFooterBanner?.data?.[0] || false + // ubah dari static menjadid dynamic dengan menggunakan random index + const length = useMemo(() => fetchFooterBanner.data?.length, [fetchFooterBanner.data]); + const randomIndex = useMemo(() => getRandomInt(length), [length]); + const banner = fetchFooterBanner?.data?.[randomIndex] || false; return banner && ( <> @@ -25,5 +29,4 @@ const FooterBanner = () => { </> ) } - export default FooterBanner
\ No newline at end of file diff --git a/src-migrate/modules/header/components/HeaderDesktop.tsx b/src-migrate/modules/header/components/HeaderDesktop.tsx index 8f5a8efa..131fa7da 100644 --- a/src-migrate/modules/header/components/HeaderDesktop.tsx +++ b/src-migrate/modules/header/components/HeaderDesktop.tsx @@ -54,7 +54,7 @@ const HeaderDesktop = () => { <Image src='/images/socials/Whatsapp-2.png' alt='Whatsapp' width={48} height={48} /> <div> <div className='font-semibold'>Whatsapp</div> - 0812 8080 622 (Chat) + 0817 1718 1922 (Chat) </div> </a> </div> diff --git a/src-migrate/modules/product-card/components/ProductCard.tsx b/src-migrate/modules/product-card/components/ProductCard.tsx index 4ddebda5..0febfadb 100644 --- a/src-migrate/modules/product-card/components/ProductCard.tsx +++ b/src-migrate/modules/product-card/components/ProductCard.tsx @@ -1,8 +1,8 @@ import style from '../styles/product-card.module.css' - +import ImageNext from 'next/image'; import clsx from 'clsx' import Link from 'next/link' -import { useMemo } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import Image from '~/components/ui/image' import useUtmSource from '~/hooks/useUtmSource' import clsxm from '~/libs/clsxm' @@ -18,6 +18,7 @@ type Props = { const ProductCard = ({ product, layout = 'vertical' }: Props) => { const utmSource = useUtmSource() + const URL = { product: createSlug('/shop/product/', product.name, product.id.toString()) + `?utm_source=${utmSource}`, @@ -40,6 +41,8 @@ const ProductCard = ({ product, layout = 'vertical' }: Props) => { [style['image-h']]: layout === 'horizontal', })}> <Link href={URL.product}> + + <div className="relative"> <Image src={image} alt={product.name} @@ -47,6 +50,32 @@ const ProductCard = ({ product, layout = 'vertical' }: Props) => { height={128} className='object-contain object-center h-full w-full' /> + <div className="absolute top-0 right-0 flex mt-2"> + <div className="gambarB "> + {product.isSni && ( + <ImageNext + src="/images/sni-logo.png" + alt="SNI Logo" + className="w-3 h-4 object-contain object-top sm:h-4" + width={50} + height={50} + /> + )} + </div> + <div className="gambarC "> + {product.isTkdn && ( + <ImageNext + src="/images/TKDN.png" + alt="TKDN" + className="w-5 h-4 object-contain object-top ml-1 mr-1 sm:h-6" + width={50} + height={50} + /> + )} + </div> + </div> + </div> + {product.variant_total > 1 && ( <div className={style['variant-badge']}>{product.variant_total} Varian</div> )} diff --git a/src-migrate/modules/product-detail/components/Image.tsx b/src-migrate/modules/product-detail/components/Image.tsx index 3d7777f8..30ca0d34 100644 --- a/src-migrate/modules/product-detail/components/Image.tsx +++ b/src-migrate/modules/product-detail/components/Image.tsx @@ -1,5 +1,5 @@ import style from '../styles/image.module.css'; - +import ImageNext from 'next/image'; import React, { useEffect, useMemo, useState } from 'react' import { InfoIcon } from 'lucide-react' import { Tooltip } from '@chakra-ui/react' @@ -14,9 +14,10 @@ type Props = { const Image = ({ product }: Props) => { const flashSale = product.flash_sale - const [count, setCount] = useState(flashSale?.remaining_time || 0); + + useEffect(() => { let interval: NodeJS.Timeout; @@ -42,15 +43,43 @@ const Image = ({ product }: Props) => { return ( <div className={style['wrapper']}> - <ImageUI - src={image} - alt={product.name} - width={256} - height={256} - className={style['image']} - loading='eager' - priority - /> + {/* <div className="relative"> */} + <ImageUI + src={image} + alt={product.name} + width={256} + height={256} + className={style['image']} + loading='eager' + priority + /> + <div className="absolute top-4 right-10 flex "> + <div className="gambarB "> + {product.isSni && ( + <ImageNext + src="/images/sni-logo.png" + alt="SNI Logo" + className="w-12 h-8 object-contain object-top sm:h-6" + width={50} + height={50} + /> + )} + </div> + <div className="gambarC "> + {product.isTkdn && ( + <ImageNext + src="/images/TKDN.png" + alt="TKDN" + className="w-16 h-8 object-contain object-top ml-1 mr-1 sm:h-6" + width={50} + height={50} + /> + )} + </div> + </div> + {/* </div> */} + + <div className={style['absolute-info']}> <Tooltip diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index d91ea49c..81271f6e 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -25,7 +25,17 @@ const PriceAction = ({ product }: Props) => { } = useProductDetail(); useEffect(() => { - setActive(product.variants[0]); + setActive(product.variants[0]) + if(product.variants.length > 2 && product.variants[0].price.price === 0){ + const variants = product.variants + for (let i = 0; i < variants.length; i++) { + if(variants[i].price.price > 0){ + setActive(variants[i]) + break; + } + } + } + }, [product, setActive]); diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index 6cd72353..fad35a7d 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -24,6 +24,8 @@ import SimilarSide from './SimilarSide' import VariantList from './VariantList' import { getAuth } from '~/libs/auth' +import { gtagProductDetail } from '@/core/utils/googleTag' + type Props = { product: IProductDetail } @@ -37,6 +39,10 @@ const ProductDetail = ({ product }: Props) => { const { setAskAdminUrl, askAdminUrl, activeVariantId, setIsApproval, isApproval } = useProductDetail() useEffect(() => { + gtagProductDetail(product); + },[product]) + + useEffect(() => { const createdAskUrl = whatsappUrl({ template: 'product', payload: { diff --git a/src-migrate/modules/product-promo/components/AddToCart.tsx b/src-migrate/modules/product-promo/components/AddToCart.tsx index 192dd231..87017c14 100644 --- a/src-migrate/modules/product-promo/components/AddToCart.tsx +++ b/src-migrate/modules/product-promo/components/AddToCart.tsx @@ -7,6 +7,9 @@ import { getAuth } from '~/libs/auth' import { upsertUserCart } from '~/services/cart' import { IPromotion } from '~/types/promotion' +import DesktopView from '../../../../src/core/components/views/DesktopView'; +import MobileView from '../../../../src/core/components/views/MobileView'; + type Props = { promotion: IPromotion } @@ -55,21 +58,42 @@ const ProductPromoAddToCart = ({ promotion }: Props) => { }, [status]) return ( - <Button - colorScheme='yellow' - px={2} - w='110px' - gap={1} - isDisabled={status === 'loading'} - onClick={handleButton} - > - {status === 'success' && <CheckIcon size={16} />} - {status === 'loading' && <Spinner size='xs' mr={1.5} />} - {status === 'idle' && <PlusIcon size={16} />} + <div> + <MobileView> + <Button + colorScheme='yellow' + px={2} + w='36px' + gap={1} + isDisabled={status === 'loading'} + onClick={handleButton} + > + {status === 'success' && <CheckIcon size={16} />} + {status === 'loading' && <Spinner size='xs' mr={1.5} />} + {status === 'idle' && <PlusIcon size={16} />} + + {status === 'success' && <span>Berhasil</span>} + {/* {status !== 'success' && <span>Keranjang</span>} */} + </Button> + </MobileView> + <DesktopView> + <Button + colorScheme='yellow' + px={2} + w='110px' + gap={1} + isDisabled={status === 'loading'} + onClick={handleButton} + > + {status === 'success' && <CheckIcon size={16} />} + {status === 'loading' && <Spinner size='xs' mr={1.5} />} + {status === 'idle' && <PlusIcon size={16} />} - {status === 'success' && <span>Berhasil</span>} - {status !== 'success' && <span>Keranjang</span>} - </Button> + {status === 'success' && <span>Berhasil</span>} + {status !== 'success' && <span>Keranjang</span>} + </Button> + </DesktopView> + </div> ) } diff --git a/src-migrate/modules/product-promo/components/Card.tsx b/src-migrate/modules/product-promo/components/Card.tsx index 59110098..56e29e38 100644 --- a/src-migrate/modules/product-promo/components/Card.tsx +++ b/src-migrate/modules/product-promo/components/Card.tsx @@ -16,38 +16,52 @@ import ProductPromoItem from './Item' import ProductPromoAddToCart from "./AddToCart" import ProductPromoCardCountdown from "./CardCountdown" +import MobileView from '../../../../src/core/components/views/MobileView'; +import DesktopView from '../../../../src/core/components/views/DesktopView'; + type Props = { promotion: IPromotion + } -const ProductPromoCard = ({ promotion }: Props) => { +const ProductPromoCard = ({ promotion}: Props) => { const [products, setProducts] = useState<IProductVariantPromo[]>([]) + const [freeProducts, setFreeProducts] = useState<IProductVariantPromo[]>([]) + const [error, setError] = useState<string | null>(null) useEffect(() => { const getProducts = async () => { - const datas = [] - for (const product of promotion.products) { - const res = await getVariantById(product.product_id) - res.data.qty = product.qty - datas.push(res.data) + try { + const datas = [] + for (const product of promotion.products) { + const res = await getVariantById(product.product_id) + res.data.qty = product.qty + datas.push(res.data) + } + setProducts(datas) + } catch (err) { + setError('Failed to fetch product variants.') + console.error(err) } - setProducts(datas) } getProducts() }, [promotion.products]) - const [freeProducts, setFreeProducts] = useState<IProductVariantPromo[]>([]) - useEffect(() => { const getFreeProducts = async () => { - const datas = [] - for (const product of promotion.free_products) { - const res = await getVariantById(product.product_id) - res.data.qty = product.qty - datas.push(res.data) + try { + const datas = [] + for (const product of promotion.free_products) { + const res = await getVariantById(product.product_id) + res.data.qty = product.qty + datas.push(res.data) + } + setFreeProducts(datas) + } catch (err) { + setError('Failed to fetch free product variants.') + console.error(err) } - setFreeProducts(datas) } getFreeProducts() @@ -63,62 +77,130 @@ const ProductPromoCard = ({ promotion }: Props) => { const allProducts = [...products, ...freeProducts] - return ( - <div className={style.card}> - <ProductPromoCardCountdown promotion={promotion} /> + - <div className='px-4 mt-4 text-caption-1'> - <div className="flex justify-between items-center"> - <div className={style.title}>{promotion.name}</div> + return ( + <div> + <MobileView> + <div className={style.card}> + <ProductPromoCardCountdown promotion={promotion} /> + + <div className='px-4 mt-4 text-caption-1'> + <div className="flex justify-between items-center"> + <div className={style.title}>{promotion.name}</div> + + <Tooltip label={PROMO_CATEGORY[promotion.type.value].description} placement="top" bgColor='red.600' p={1} rounded={6}> + {/* <div className={style.badgeType} > */} + {/* Paket {PROMO_CATEGORY[promotion.type.value].alias} */} + <InfoIcon className={style.badgeType} size={25} /> + {/* </div> */} + </Tooltip> + </div> - <Tooltip label={PROMO_CATEGORY[promotion.type.value].description} placement="top" bgColor='red.600' p={2} rounded={6}> - <div className={style.badgeType}> - Paket {PROMO_CATEGORY[promotion.type.value].alias} - <InfoIcon size={16} /> + <Skeleton className={clsxm(style.productSection, { 'justify-center': allProducts.length === 2 })} isLoaded={allProducts.length > 0}> + {allProducts.map((product, index) => ( + <React.Fragment key={product.id}> + <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.6 }}> + <ProductPromoItem + variant={product} + isFree={index + 1 > products.length && promotion.type.value === 'merchandise'} + // isFree={index + 1 > products.length } + /> + </motion.div> + <motion.div initial={{ y: 30, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.5, delay: 0.1 }}> + {index + 1 < allProducts.length && ( + <div className="h-fit p-1 rounded-full border border-danger-500 text-danger-500 mt-[38px]"> + <PlusIcon size={14} strokeWidth='2px' /> + </div> + )} + </motion.div> + </React.Fragment> + ))} + </Skeleton> + + <div className={style.priceSection}> + <div className={style.priceCol}> + <Skeleton className={style.priceRow} isLoaded={priceTotal > 0}> + <span className={style.basePrice}>Rp{formatCurrency(priceTotal)}</span> + <span className="text-[11px]">Hemat <span className={style.savingAmt}>Rp {formatCurrency(priceTotal - promotion.price)}</span></span> + </Skeleton> + + <div className={style.priceRow}> + <span className={style.price}>Rp{formatCurrency(promotion.price)}</span> + <span className={style.totalItems}>(Total {promotion.total_qty} barang)</span> + </div> + </div> - </Tooltip> - </div> - - <Skeleton className={clsxm(style.productSection, { 'justify-center': allProducts.length === 2 })} isLoaded={allProducts.length > 0}> - {allProducts.map((product, index) => ( - <> - <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.6 }}> - <ProductPromoItem - variant={product} - isFree={index + 1 > products.length && promotion.type.value === 'merchandise'} - /> - </motion.div> - <motion.div initial={{ y: 30, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.5, delay: 0.1 }}> - {index + 1 < allProducts.length && ( - <div className="h-fit p-1 rounded-full border border-danger-500 text-danger-500 mt-[38px]"> - <PlusIcon size={14} strokeWidth='2px' /> - </div> - )} - </motion.div> - </> - ))} - </Skeleton> - - <div className={style.priceSection}> - <div className={style.priceCol}> - <Skeleton className={style.priceRow} isLoaded={priceTotal > 0}> - <span className={style.basePrice}>Rp{formatCurrency(priceTotal)}</span> - <span>Hemat <span className={style.savingAmt}>Rp {formatCurrency(priceTotal - promotion.price)}</span></span> - </Skeleton> - - <div className={style.priceRow}> - <span className={style.price}>Rp{formatCurrency(promotion.price)}</span> - <span className={style.totalItems}>(Total {promotion.total_qty} barang)</span> + <div> + <ProductPromoAddToCart promotion={promotion} /> </div> + </div> - <div> - <ProductPromoAddToCart promotion={promotion} /> + </div> + </div> + </MobileView> + <DesktopView> + <div className={style.card}> + <ProductPromoCardCountdown promotion={promotion} /> + + <div className='px-4 mt-4 text-caption-1'> + <div className="flex justify-between items-center"> + <div className={style.title}>{promotion.name}</div> + + <Tooltip label={PROMO_CATEGORY[promotion.type.value].description} placement="top" bgColor='red.600' p={2} rounded={6}> + <div className={style.badgeType}> + Paket {PROMO_CATEGORY[promotion.type.value].alias} + <InfoIcon size={16} /> + </div> + </Tooltip> </div> + <Skeleton className={clsxm(style.productSection, { 'justify-center': allProducts.length === 2 })} isLoaded={allProducts.length > 0}> + {allProducts.map((product, index) => ( + <React.Fragment key={product.id}> + <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: 0.6 }}> + <ProductPromoItem + variant={product} + isFree={index + 1 > products.length && promotion.type.value === 'merchandise'} + // isFree={index + 1 > products.length } + /> + </motion.div> + <motion.div initial={{ y: 30, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.5, delay: 0.1 }}> + {index + 1 < allProducts.length && ( + <div className="h-fit p-1 rounded-full border border-danger-500 text-danger-500 mt-[38px]"> + <PlusIcon size={14} strokeWidth='2px' /> + </div> + )} + </motion.div> + </React.Fragment> + ))} + </Skeleton> + + <div className={style.priceSection}> + <div className={style.priceCol}> + <Skeleton className={style.priceRow} isLoaded={priceTotal > 0}> + <span className={style.basePrice}>Rp{formatCurrency(priceTotal)}</span> + <span>Hemat <span className={style.savingAmt}>Rp {formatCurrency(priceTotal - promotion.price)}</span></span> + </Skeleton> + + <div className={style.priceRow}> + <span className={style.price}>Rp{formatCurrency(promotion.price)}</span> + <span className={style.totalItems}>(Total {promotion.total_qty} barang)</span> + </div> + </div> + <div> + <ProductPromoAddToCart promotion={promotion} /> + </div> + + </div> </div> </div> + </DesktopView> </div> + // shouldRender && ( + + // ) ) } -export default ProductPromoCard
\ No newline at end of file +export default ProductPromoCard diff --git a/src-migrate/modules/product-promo/components/Section.tsx b/src-migrate/modules/product-promo/components/Section.tsx index 5fc0da4c..4e8a7dd5 100644 --- a/src-migrate/modules/product-promo/components/Section.tsx +++ b/src-migrate/modules/product-promo/components/Section.tsx @@ -50,7 +50,7 @@ const ProductPromoSection = ({ productId }: Props) => { > {promotions?.data.map((promotion) => ( <div key={promotion.id} className="min-w-[400px] max-w-[400px]"> - <ProductPromoCard promotion={promotion} /> + <ProductPromoCard promotion={promotion} /> </div> ))} </Skeleton> diff --git a/src-migrate/modules/product-promo/styles/card.module.css b/src-migrate/modules/product-promo/styles/card.module.css index a2ad9af6..faa3b370 100644 --- a/src-migrate/modules/product-promo/styles/card.module.css +++ b/src-migrate/modules/product-promo/styles/card.module.css @@ -44,3 +44,15 @@ .totalItems { @apply text-gray_r-9; } + +@media only screen and (max-width: 384px) { + .basePrice { + @apply text-[13px]; + } + .price{ + @apply text-[15px]; + } + .totalItems{ + @apply text-[11px]; + } + }
\ No newline at end of file diff --git a/src-migrate/modules/side-banner/index.tsx b/src-migrate/modules/side-banner/index.tsx index be52c554..6214edfb 100644 --- a/src-migrate/modules/side-banner/index.tsx +++ b/src-migrate/modules/side-banner/index.tsx @@ -1,29 +1,30 @@ -import Link from "next/link" -import { useQuery } from "react-query" -import Image from "~/components/ui/image" -import { getBanner } from "~/services/banner" +import React, { useMemo } from "react"; +import Link from "next/link"; +import { useQuery } from "react-query"; +import Image from "~/components/ui/image"; +import { getBanner } from "~/services/banner"; +import { getRandomInt } from '@/utils/getRandomInt'; const SideBanner = () => { const fetchSideBanner = useQuery({ queryKey: 'sideBanner', queryFn: () => getBanner({ type: 'side-banner-search' }) - }) - - const banner = fetchSideBanner?.data?.[0] || false + }); + // ubah dari static menjadid dynamic dengan menggunakan random index + const length = useMemo(() => fetchSideBanner.data?.length, [fetchSideBanner.data]); + const randomIndex = useMemo(() => getRandomInt(length), [length]); + const banner = fetchSideBanner?.data?.[randomIndex] || false; return banner && ( <> - {banner.url && ( + {banner.url ? ( <Link href={banner.url}> <Image src={banner.image} alt={banner.name} width={315} height={450} className='object-cover object-center rounded-lg' /> </Link> - )} - - {!banner.url && ( + ) : ( <Image src={banner.image} alt={banner.name} width={315} height={450} className='object-cover object-center rounded-lg' /> )} </> - ) + ); } - -export default SideBanner
\ No newline at end of file +export default SideBanner; |
