diff options
| -rw-r--r-- | public/images/icon_promo/angklung.svg | 9 | ||||
| -rw-r--r-- | public/images/icon_promo/barong.svg | 9 | ||||
| -rw-r--r-- | public/images/icon_promo/silat.svg | 9 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/Hero.tsx | 68 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/PromoList.jsx | 194 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/PromotinProgram.jsx | 95 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/PromotinProgram.tsx | 9 | ||||
| -rw-r--r-- | src-migrate/modules/promo/components/Voucher.tsx | 84 | ||||
| -rw-r--r-- | src-migrate/modules/promo/styles/hero.module.css | 27 | ||||
| -rw-r--r-- | src-migrate/modules/promo/styles/voucher.module.css | 43 | ||||
| -rw-r--r-- | src-migrate/pages/shop/promo/index.tsx | 35 | ||||
| -rw-r--r-- | src-migrate/services/voucher.ts | 8 | ||||
| -rw-r--r-- | src-migrate/types/banner.ts | 6 | ||||
| -rw-r--r-- | src-migrate/types/voucher.ts | 8 | ||||
| -rw-r--r-- | src/core/components/elements/Navbar/NavbarDesktop.jsx | 12 | ||||
| -rw-r--r-- | src/pages/shop/promo/index.jsx | 38 |
16 files changed, 648 insertions, 6 deletions
diff --git a/public/images/icon_promo/angklung.svg b/public/images/icon_promo/angklung.svg new file mode 100644 index 00000000..a650d816 --- /dev/null +++ b/public/images/icon_promo/angklung.svg @@ -0,0 +1,9 @@ +<svg width="39" height="38" viewBox="0 0 39 38" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<rect x="0.666992" y="-0.00439453" width="38" height="38" fill="url(#pattern0_3825_7139)"/> +<defs> +<pattern id="pattern0_3825_7139" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image0_3825_7139" transform="scale(0.00478469)"/> +</pattern> +<image id="image0_3825_7139" width="209" height="209" xlink:href="data:image/png;base64,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"/> +</defs> +</svg> diff --git a/public/images/icon_promo/barong.svg b/public/images/icon_promo/barong.svg new file mode 100644 index 00000000..22e51ff9 --- /dev/null +++ b/public/images/icon_promo/barong.svg @@ -0,0 +1,9 @@ +<svg width="39" height="38" viewBox="0 0 39 38" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<rect x="0.333008" y="-0.00439453" width="38" height="38" fill="url(#pattern0_3850_5455)"/> +<defs> +<pattern id="pattern0_3850_5455" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image0_3850_5455" transform="scale(0.00478469)"/> +</pattern> +<image id="image0_3850_5455" width="209" height="209" xlink:href="data:image/png;base64,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"/> +</defs> +</svg> diff --git a/public/images/icon_promo/silat.svg b/public/images/icon_promo/silat.svg new file mode 100644 index 00000000..112ecaff --- /dev/null +++ b/public/images/icon_promo/silat.svg @@ -0,0 +1,9 @@ +<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +<rect y="-0.00439453" width="38" height="38" fill="url(#pattern0_3821_6700)"/> +<defs> +<pattern id="pattern0_3821_6700" patternContentUnits="objectBoundingBox" width="1" height="1"> +<use xlink:href="#image0_3821_6700" transform="scale(0.005)"/> +</pattern> +<image id="image0_3821_6700" width="200" height="200" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRF+MHE8IKJ6URO5iUx9KGm/e/w5BYi7WRs++Dh5zU/9rG1+tDT73N78pKX61Rd4gYT////3hCh9wAAABF0Uk5T/////////////////////wAlrZliAAADrklEQVR42uzd65KjIBAFYGgF4iXR93/arYw3FNTZjRqOe/pnYtX4VY0a+wKqvUkoQgghhBBCCDkIUpX6lHiqx5UQUzTnhbsQUjdnRnkdRE6F6OsgxakQdx3kVEcjhPwrJHL77K8e2bjD9oe48Bv3LUjkGL1/vfaHRB6vihBCCCGEEEIISR6SO3EKH5Jn3ckpbEjPmFEAIR7Do8BBFoyRAgaJMHoKFESy1fe/QpAgs8jERj6Fg2T5+2OLDvlhvHOTAQUKMjBiFCCIzwgpMJAlY0lJHFJuMOYUkzakSwuvMTxK3SYOMVrqnbqAyd0rKk0K8tEfuBmkUGfEFyDnhM1vAmnsXSDTk4sQQsAhWaRe179xykbs1gNVCg9E2f+Lu79kCSGEEEIIIYSQTciFbU4RSP6Sp8GHdGULqw02ZKq+TBRAyLyINFDgIGEtrKOAQeIlvTcFCpKvlvSs/gASSYP1/ZVuI1PWH/IMv3nuQn4RBz0Qz+pFkUhJz7SlwEOGfL8SaIhftlhSgCDL6sucAgOJFZF8SuIQt1PSGylF4pCq2Svp9ZQ8cUirXuJ2SnpVLZK3qUM+CUJmkNKJiK7QIeXwK08qZMjj5T2paoMKebjY+w8cZMkYKVgQ5daGDB9QELvx5pMhQWb/T3XpvtuceQiku8IjFwwWZEqfBRQkiJ/QDCg4kDkjoKBAQsaCUqUNqbYYPiVr04b8rKKwzhgptkwd0lZqd9kRo5Rpk4ecU3ojhBBCCCGEEEIIIYQQQggh5H+EHD715jgIQwghhBDyC4iNjLRZRMhGv9ZWNhZqWoEQQgghhBBCEoTIwVH8FeShtToIcv0gzHQKlfRr82FDpg6KiQIImbfnDd3XcJBw9qVrmQeDqOi01ZsCBXmuDo1l+bFTb8MddH9LgsSn3urLpt6sfpjYWOJBkMvG9/oG9ZCCBXHjlkTBkpxIEDfbWWlBwYG4YIOoGSVxyGudMaekPvXWfWXd6nZdA0UnDmlz22SbPZ+teRbxBTk5LHZTiNFZU+TwkOEa9IZTESH+DX6k4EGWvx56ChrERJZY75Zeh4LEGD0FClKsl8oyQYIsTj27wdRbl3iKvMqBQcb8WUCBgvgZzSUFCCLLc51RYCASKwF4lMQh1RbDp1iTNqSVTcZE0W3ikDbXz73to6tFiSZNyDmlN0IIIYQQQgghhBBCCCGEEEIIuQICFoQAQOQukBckpAohJaJjarfxsoPlxnbfaYbUJgbBDkIIIYQQQr4SfwQYAF0sNCgAdJuiAAAAAElFTkSuQmCC"/> +</defs> +</svg> diff --git a/src-migrate/modules/promo/components/Hero.tsx b/src-migrate/modules/promo/components/Hero.tsx new file mode 100644 index 00000000..d8bb0be4 --- /dev/null +++ b/src-migrate/modules/promo/components/Hero.tsx @@ -0,0 +1,68 @@ +import 'swiper/css'; + +import Image from 'next/image'; +import { useEffect, useMemo } from 'react'; +import { useQuery } from 'react-query'; +import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react'; + +import { getBanner } from '~/services/banner'; +import style from '../styles/hero.module.css'; +import 'swiper/css/navigation'; +import { Navigation, Pagination } from 'swiper'; + +const swiperBanner: SwiperProps = { + modules:[Navigation, Pagination], + autoplay: { + delay: 6000, + disableOnInteraction: false + }, + loop: true, + className: 'h-[400px] w-full', + slidesPerView: 1, + spaceBetween: 10, + navigation:true, +} + +const Hero = () => { + const bannerQuery = useQuery({ + queryKey: ['banner.all-promo'], + queryFn: () => getBanner({ type: 'banner-promotion' }) + }) + + const banners = useMemo(() => bannerQuery.data || [], [bannerQuery.data]); + + useEffect(() => { + if (banners.length > 1) { + swiperBanner.slidesPerView = 1.1; + swiperBanner.loop = true; + } + }, [banners]); + + return ( + <div className={style['wrapper']}> + <div className={style['desc-section']}> + <div className={style['title']}>Pasti Hemat & Untung Selama Belanja di Indoteknik.com!</div> + <div className='h-4' /> + <div className={style['subtitle']}>Cari paket yang kami sediakan dengan penawaran harga & Nikmati kemudahan dalam setiap transaksi dengan fitur lengkap Pembayaran hingga barang sampai! </div> + </div> + + <div className={style['banner-section']}> + <Swiper {...swiperBanner}> + {banners.map((banner, index) => ( + <SwiperSlide key={index}> + <Image + src={banner.image} + alt={banner.name} + width={666} + height={450} + className='w-full h-full object-cover object-center rounded-2xl' + /> + </SwiperSlide> + ))} + </Swiper> + </div> + </div> + ) +} + +export default Hero
\ No newline at end of file diff --git a/src-migrate/modules/promo/components/PromoList.jsx b/src-migrate/modules/promo/components/PromoList.jsx new file mode 100644 index 00000000..af2958aa --- /dev/null +++ b/src-migrate/modules/promo/components/PromoList.jsx @@ -0,0 +1,194 @@ +import Image from 'next/image'; +import Link from '@/core/components/elements/Link/Link'; +import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card' +// import { fetchPromoItemsSolr, fetchVariantSolr } from '../../../api/promoApi' + + +export const fetchPromoItemsSolr = async (type) => { + // let query = type ? `type_value_s:${type}` : '*:*'; + let start = 0 + let rows = 100 + try { + const queryParams = new URLSearchParams({ q: type }); + const response = await fetch(`/solr/promotion_program_lines/select?${queryParams.toString()}&rows=${rows}&start=${start}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + const promotions = await map(data.response.docs); + return promotions; + } catch (error) { + console.error("Error fetching promotion data:", error); + return []; + } +}; + + +const PromoList = ({ selectedPromo }) => { + + useEffect(() => { + const loadPromo = async () => { + setLoading(true); + + + try { + const items = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug}`); + setPromoItems(items); + + if (items.length === 0) { + setPromoData([]) + setLoading(false); + return; + } + + const brandArray = Array.isArray(brand) ? brand : brand.split(','); + const categoryArray = Array.isArray(category) ? category : category.split(','); + + const promoDataPromises = items.map(async (item) => { + + try { + let brandQuery = ''; + if (brand) { + brandQuery = brandArray.map(b => `manufacture_name_s:${b}`).join(' OR '); + brandQuery = `(${brandQuery})`; + } + + let categoryQuery = ''; + if (category) { + categoryQuery = categoryArray.map(c => `category_name:${c}`).join(' OR '); + categoryQuery = `(${categoryQuery})`; + } + + let priceQuery = ''; + if (priceFrom && priceTo) { + priceQuery = `price_f:[${priceFrom} TO ${priceTo}]`; + } else if (priceFrom) { + priceQuery = `price_f:[${priceFrom} TO *]`; + } else if (priceTo) { + priceQuery = `price_f:[* TO ${priceTo}]`; + } + + let combinedQuery = ''; + let combinedQueryPrice = `${priceQuery}`; + if (brand && category && priceFrom || priceTo) { + combinedQuery = `${brandQuery} AND ${categoryQuery} `; + } else if (brand && category) { + combinedQuery = `${brandQuery} AND ${categoryQuery}`; + } else if (brand && priceFrom || priceTo) { + combinedQuery = `${brandQuery}`; + } else if (category && priceFrom || priceTo) { + combinedQuery = `${categoryQuery}`; + } else if (brand) { + combinedQuery = brandQuery; + } else if (category) { + combinedQuery = categoryQuery; + } + + if (combinedQuery && priceFrom || priceTo) { + const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`); + const product = response.response.docs[0]; + const product_id = product.id; + const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} AND ${combinedQueryPrice}`); + return response2; + }else if(combinedQuery){ + const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`); + const product = response.response.docs[0]; + const product_id = product.id; + const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} `); + return response2; + } else { + const response = await fetchPromoItemsSolr(`id:${item.id}`); + return response; + } + } catch (fetchError) { + return []; + } + }); + + const promoDataArray = await Promise.all(promoDataPromises); + const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []); + setPromoData(mergedPromoData); + + const dataBrandCategoryPromises = promoDataArray.map(async (promoData) => { + if (promoData) { + const dataBrandCategory = promoData.map(async (item) => { + let response; + if(category){ + const categoryQuery = categoryArray.map(c => `category_name:${c}`).join(' OR '); + response = await fetchVariantSolr(`id:${item.products[0].product_id} AND (${categoryQuery})`); + }else{ + response = await fetchVariantSolr(`id:${item.products[0].product_id}`) + } + + + if (response.response?.docs?.length > 0) { + const product = response.response.docs[0]; + const manufactureNameS = product.manufacture_name; + if (Array.isArray(manufactureNameS)) { + for (let i = 0; i < manufactureNameS.length; i += 2) { + const brand = manufactureNameS[i]; + const qty = 1; + const existingBrandIndex = brandsData.findIndex(b => b.brand === brand); + if (existingBrandIndex !== -1) { + brandsData[existingBrandIndex].qty += qty; + } else { + brandsData.push({ brand, qty }); + } + } + } + + const categoryNameS = product.category_name; + if (Array.isArray(categoryNameS)) { + for (let i = 0; i < categoryNameS.length; i += 2) { + const name = categoryNameS[i]; + const qty = 1; + const existingCategoryIndex = categoriesData.findIndex(c => c.name === name); + if (existingCategoryIndex !== -1) { + categoriesData[existingCategoryIndex].qty += qty; + } else { + categoriesData.push({ name, qty }); + } + } + } + } + }); + + return Promise.all(dataBrandCategory); + } + }); + + await Promise.all(dataBrandCategoryPromises); + setBrands(brandsData); + setCategories(categoriesData); + setLoading(false); + + } catch (loadError) { + // console.error("Error loading promo items:", loadError) + setLoading(false); + } + } + + if (slug) { + loadPromo() + } + },[slug, brand, category, priceFrom, priceTo, currentPage]); + + let title = ''; + + if (selectedPromo === 'Bundling') { + title = 'Kombinasi Kilat Pilihan Kami!'; + } else if (selectedPromo === 'Loading') { + title = 'Belanja Borong Pilihan Kami!'; + } else if (selectedPromo === 'Merchandise') { + title = 'Gratis Merchandise Spesial Indoteknik'; + } + + return ( + <div> + <h1 className='text-h-sm md:text-h-lg font-semibold mb-4'>{title}</h1> + <div>DISINI CARD {selectedPromo}</div> + </div> + ); +} + +export default PromoList; diff --git a/src-migrate/modules/promo/components/PromotinProgram.jsx b/src-migrate/modules/promo/components/PromotinProgram.jsx new file mode 100644 index 00000000..a7e5dfef --- /dev/null +++ b/src-migrate/modules/promo/components/PromotinProgram.jsx @@ -0,0 +1,95 @@ +import React from 'react'; +import Image from 'next/image'; +import { InfoIcon } from "lucide-react" + +const PromotionProgram = ({ selectedPromo, onSelectPromo }) => { + return ( + <> + <div className="text-h-sm md:text-h-lg font-semibold mb-4">Serba Serbi Promo</div> + <div className='px-4 sm:px-0'> + <div className='grid md:grid-cols-3 grid-cols-2 justify-between gap-2 items-center'> + <div className='w-full'> + <div + onClick={() => onSelectPromo('Bundling')} + className={`border p-2 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Bundling' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`} + > + <div> + <Image + width={24} + height={24} + quality={100} + src='/images/icon_promo/silat.svg' + alt='' + className='h-20 w-20 rounded' + /> + </div> + <div> + <div className='flex w-full flex-row items-center justify-start'> + <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Bundling' ? 'text-red-500' : 'text-gray-900'}`}>Paket Silat</h1> + <InfoIcon className='mt-[1px] text-red-500' size={14} /> + </div> + <p className={`text-xs md:text-sm ${selectedPromo === 'Bundling' ? 'text-red-500' : 'text-gray-500'}`}> + Pilihan bundling barang kombinasi Silat. + </p> + </div> + </div> + </div> + <div className='w-full'> + <div + onClick={() => onSelectPromo('Loading')} + className={`border p-2 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Loading' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`} + > + <div> + <Image + width={24} + height={24} + quality={100} + src='/images/icon_promo/barong.svg' + alt='' + className='h-20 w-20 rounded' + /> + </div> + <div> + <div className='flex w-full flex-row items-center justify-start'> + <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Loading' ? 'text-red-500' : 'text-gray-900'}`}>Paket Barong</h1> + <InfoIcon className='mt-[1px] text-red-500' size={14} /> + </div> + <p className={`text-xs md:text-sm ${selectedPromo === 'Loading' ? 'text-red-500' : 'text-gray-500'}`}> + Beli banyak barang/partai barang borong. + </p> + </div> + </div> + </div> + <div className='w-full'> + <div + onClick={() => onSelectPromo('Merchandise')} + className={`border p-2 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Merchandise' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`} + > + <div> + <Image + width={24} + height={24} + quality={100} + src='/images/icon_promo/angklung.svg' + alt='' + className='h-20 w-20 rounded' + /> + </div> + <div > + <div className='flex w-full flex-row items-center justify-start '> + <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Merchandise' ? 'text-red-500' : 'text-gray-900'}`}>Paket Angklung</h1> + <InfoIcon className='mt-[1px] text-red-500' size={14} /> + </div> + <p className={` m1 text-xs md:text-sm ${selectedPromo === 'Merchandise' ? 'text-red-500' : 'text-gray-500'}`}> + Gratis barang promosi/merchandise menang langsung. + </p> + </div> + </div> + </div> + </div> + </div> + </> + ); +}; + +export default PromotionProgram; diff --git a/src-migrate/modules/promo/components/PromotinProgram.tsx b/src-migrate/modules/promo/components/PromotinProgram.tsx new file mode 100644 index 00000000..19f228ea --- /dev/null +++ b/src-migrate/modules/promo/components/PromotinProgram.tsx @@ -0,0 +1,9 @@ +const PromotionProgram = () => { + return ( + <> + <div className="">Serba Serbi Promo</div> + </> + ) +} + +export default PromotionProgram
\ No newline at end of file diff --git a/src-migrate/modules/promo/components/Voucher.tsx b/src-migrate/modules/promo/components/Voucher.tsx new file mode 100644 index 00000000..14d3c301 --- /dev/null +++ b/src-migrate/modules/promo/components/Voucher.tsx @@ -0,0 +1,84 @@ +import { useMemo } from 'react' +import { useQuery } from 'react-query' +import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react' +import { getVoucher } from '~/services/voucher' +import style from '../styles/voucher.module.css' +import Image from 'next/image' +import { useToast } from '@chakra-ui/react' + +const swiperVoucher: SwiperProps = { + autoplay: { + delay: 6000, + disableOnInteraction: false + }, + loop: false, + className: 'h-[160px] w-full', + slidesPerView: 3, + spaceBetween: 16 +} + +const Voucher = () => { + const toast = useToast(); + const voucherQuery = useQuery({ + queryKey: ['voucher.all-voucher'], + queryFn: getVoucher + }) + + const vouchers = useMemo(() => voucherQuery.data || [], [voucherQuery.data]); + + const copyText = (text: string) => { + navigator.clipboard.writeText(text) + toast({ + title: 'Salin ke papan klip', + description: 'Kode voucher berhasil disalin', + status: 'success', + duration: 3000, + isClosable: true, + position: 'bottom', + }) + } + + return ( + <> + <div className={style['title']}>Pakai Voucher Belanja</div> + + <div className='h-6' /> + + {voucherQuery.isLoading && ( + <div className='grid grid-cols-3 gap-x-4 animate-pulse'> + {Array.from({ length: 3 }).map((_, index) => ( + <div key={index} className='w-full h-[160px] bg-gray-200 rounded-xl' /> + ))} + </div> + )} + {!voucherQuery.isLoading && ( + <div className={style['voucher-section']}> + <Swiper {...swiperVoucher}> + {vouchers.map((voucher) => ( + <SwiperSlide key={voucher.id} className='pb-2'> + <div className={style['voucher-card']}> + <Image src={voucher.image} alt={voucher.name} width={128} height={128} className={style['voucher-image']} /> + + <div className={style['voucher-content']}> + <div className={style['voucher-title']}>{voucher.name}</div> + <div className={style['voucher-desc']}>{voucher.description}</div> + <div className={style['voucher-bottom']}> + <div> + <div className={style['voucher-code-desc']}>Kode Promo</div> + <div className={style['voucher-code']}>{voucher.code}</div> + </div> + <button className={style['voucher-copy']} onClick={() => copyText(voucher.code)}>Salin</button> + </div> + </div> + </div> + </SwiperSlide> + ))} + </Swiper> + </div> + )} + + </> + ) +} + +export default Voucher
\ No newline at end of file diff --git a/src-migrate/modules/promo/styles/hero.module.css b/src-migrate/modules/promo/styles/hero.module.css new file mode 100644 index 00000000..70d725be --- /dev/null +++ b/src-migrate/modules/promo/styles/hero.module.css @@ -0,0 +1,27 @@ +.wrapper { + @apply bg-warning-100/50 rounded-xl w-full h-[460px] flex; +} + +.desc-section { + @apply w-full md:w-5/12 + flex flex-col + md:justify-center + p-6 md:pl-10; +} + +.title { + @apply text-title-sm md:text-title-lg + leading-[30px] md:leading-[42px] + font-semibold; +} + +.subtitle { + @apply text-body-2 leading-7 text-gray-700; +} + +.banner-section { + @apply md:w-7/12 + flex flex-col + md:justify-center + md:pr-10; +} diff --git a/src-migrate/modules/promo/styles/voucher.module.css b/src-migrate/modules/promo/styles/voucher.module.css new file mode 100644 index 00000000..24d3aac2 --- /dev/null +++ b/src-migrate/modules/promo/styles/voucher.module.css @@ -0,0 +1,43 @@ +.title { + @apply text-h-sm md:text-h-lg font-semibold; +} + +.voucher-section { + @apply w-full; +} + +.voucher-card { + @apply w-full h-full rounded-xl border border-gray-200 shadow-md p-4 flex gap-x-4; +} + +.voucher-image { + @apply bg-gray-100 rounded-lg w-4/12 h-full object-contain object-center; +} + +.voucher-content { + @apply flex-1 flex flex-col; +} + +.voucher-title { + @apply font-medium text-body-1 leading-6 mb-1; +} + +.voucher-desc { + @apply text-gray-800 line-clamp-2 text-caption-1; +} + +.voucher-bottom { + @apply flex justify-between mt-auto; +} + +.voucher-code-desc { + @apply text-gray-500 text-caption-1; +} + +.voucher-code { + @apply text-red-700 font-medium; +} + +.voucher-copy { + @apply bg-gray-200 hover:bg-danger-500 text-danger-500 hover:text-white transition-colors rounded-lg flex items-center justify-center px-6; +} diff --git a/src-migrate/pages/shop/promo/index.tsx b/src-migrate/pages/shop/promo/index.tsx new file mode 100644 index 00000000..5fdd80aa --- /dev/null +++ b/src-migrate/pages/shop/promo/index.tsx @@ -0,0 +1,35 @@ +import dynamic from 'next/dynamic' +import React, { useState } from 'react' +import { LazyLoadComponent } from 'react-lazy-load-image-component' +import Hero from '~/modules/promo/components/Hero' +import PromotionProgram from '~/modules/promo/components/PromotinProgram' +import Voucher from '~/modules/promo/components/Voucher' +const PromoList = dynamic(() => import('../../../modules/promo/components/PromoList')); + +const PromoPage = () => { + const [selectedPromo, setSelectedPromo] = useState('Bundling'); + return ( + <> + <LazyLoadComponent> + <Hero /> + </LazyLoadComponent> + + <div className='h-10' /> + + <LazyLoadComponent> + <PromotionProgram + selectedPromo={selectedPromo} + onSelectPromo={setSelectedPromo} + /> + </LazyLoadComponent> + <PromoList selectedPromo={selectedPromo} /> + <div className='h-10' /> + + <LazyLoadComponent> + <Voucher /> + </LazyLoadComponent> + </> + ) +} + +export default PromoPage
\ No newline at end of file diff --git a/src-migrate/services/voucher.ts b/src-migrate/services/voucher.ts new file mode 100644 index 00000000..447b448e --- /dev/null +++ b/src-migrate/services/voucher.ts @@ -0,0 +1,8 @@ +import odooApi from '~/libs/odooApi'; +import { IVoucher } from '~/types/voucher'; + +export const getVoucher = async (): Promise<IVoucher[]> => { + const url = `/api/v1/voucher`; + + return await odooApi('GET', url); +}; diff --git a/src-migrate/types/banner.ts b/src-migrate/types/banner.ts index dbccc378..e1604ad4 100644 --- a/src-migrate/types/banner.ts +++ b/src-migrate/types/banner.ts @@ -1,8 +1,8 @@ export interface IBanner { - background_color: string | false; - group_by_week: number | false; image: string; name: string; sequence: number; - url: string; + url: string | false; + group_by_week: number | false; + background_color: string | false; } diff --git a/src-migrate/types/voucher.ts b/src-migrate/types/voucher.ts new file mode 100644 index 00000000..3e90f449 --- /dev/null +++ b/src-migrate/types/voucher.ts @@ -0,0 +1,8 @@ +export interface IVoucher { + id: number; + image: string; + name: string; + code: string; + description: string | false; + remaining_time: string; +} diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx index 308f2623..8cb4239c 100644 --- a/src/core/components/elements/Navbar/NavbarDesktop.jsx +++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx @@ -171,6 +171,14 @@ const NavbarDesktop = () => { </button> <div className='w-6/12 flex px-1 divide-x divide-gray_r-6'> <Link + href='/shop/promo' + className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition' + target='_blank' + rel='noreferrer' + > + Semua Promo + </Link> + <Link href='/shop/brands' className={`${ router.asPath === '/shop/brands' && 'bg-gray_r-3' @@ -201,9 +209,7 @@ const NavbarDesktop = () => { </Link> <Link href='/video' - className={`${ - router.asPath === '/video' && 'bg-gray_r-3' - } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition`} + className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition' target='_blank' rel='noreferrer' > diff --git a/src/pages/shop/promo/index.jsx b/src/pages/shop/promo/index.jsx new file mode 100644 index 00000000..0d831c21 --- /dev/null +++ b/src/pages/shop/promo/index.jsx @@ -0,0 +1,38 @@ +import BasicLayout from '@/core/components/layouts/BasicLayout'; +import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from '@chakra-ui/react'; +import Link from 'next/link'; +import Promo from '~/pages/shop/promo'; + +import React from 'react'; + +const PromoPage = () => { + return ( + <BasicLayout> + <div className='container mx-auto py-4 md:py-6 pb-0'> + <Breadcrumb> + <BreadcrumbItem> + <BreadcrumbLink + as={Link} + href='/' + className='!text-danger-500 whitespace-nowrap' + > + Home + </BreadcrumbLink> + </BreadcrumbItem> + + <BreadcrumbItem isCurrentPage> + <BreadcrumbLink className='whitespace-nowrap'> + Semua Promo + </BreadcrumbLink> + </BreadcrumbItem> + </Breadcrumb> + + <div className='h-10' /> + + <Promo /> + </div> + </BasicLayout> + ); +}; + +export default PromoPage; |
