summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2024-11-28 10:47:43 +0700
committerit-fixcomart <it@fixcomart.co.id>2024-11-28 10:47:43 +0700
commit5bc7a6807847610b190ea9d5046021d2db15afc5 (patch)
treee895b02c65bf97e3c6c970bb8d777922120f4570
parent7ed3fd96322d08bd91434b8ec4dcbc542a610998 (diff)
parent952421c810b53ec4d25ad5ef605bae1bd1d5d616 (diff)
Merge branch 'new-release' into Feature/switch-account
-rw-r--r--package.json2
-rw-r--r--public/images/CHECKOUT-PESANAN.svg958
-rw-r--r--public/images/writing.pngbin0 -> 7010 bytes
-rw-r--r--src-migrate/modules/cart/components/Item.tsx17
-rw-r--r--src-migrate/modules/page-content/index.tsx29
-rw-r--r--src-migrate/modules/product-card/components/ProductCard.tsx176
-rw-r--r--src-migrate/modules/product-detail/components/AddToCart.tsx312
-rw-r--r--src-migrate/modules/product-detail/components/AddToQuotation.tsx227
-rw-r--r--src-migrate/modules/product-detail/components/Image.tsx118
-rw-r--r--src-migrate/modules/product-detail/components/Information.tsx234
-rw-r--r--src-migrate/modules/product-detail/components/PriceAction.tsx139
-rw-r--r--src-migrate/modules/product-detail/components/ProductDetail.tsx195
-rw-r--r--src-migrate/modules/product-detail/stores/useProductDetail.ts12
-rw-r--r--src-migrate/modules/product-detail/styles/information.module.css4
-rw-r--r--src-migrate/modules/product-detail/styles/price-action.module.css5
-rw-r--r--src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx17
-rw-r--r--src-migrate/modules/promo/components/PromoList.tsx79
-rw-r--r--src-migrate/modules/register/components/TermCondition.tsx48
-rw-r--r--src-migrate/pages/shop/cart/index.tsx8
-rw-r--r--src-migrate/pages/shop/promo/index.tsx41
-rw-r--r--src-migrate/types/cart.ts1
-rw-r--r--src-migrate/types/product.ts3
-rw-r--r--src-migrate/types/productVariant.ts1
-rw-r--r--src/components/ui/HeroBanner.jsx37
-rw-r--r--src/components/ui/HeroBannerSecondary.jsx69
-rw-r--r--src/core/components/elements/Footer/BasicFooter.jsx2
-rw-r--r--src/core/components/elements/Footer/SimpleFooter.jsx2
-rw-r--r--src/core/components/elements/Navbar/NavbarDesktop.jsx42
-rw-r--r--src/core/components/elements/Navbar/TopBanner.jsx53
-rw-r--r--src/core/components/layouts/BasicLayout.jsx41
-rw-r--r--src/core/utils/whatsappUrl.js21
-rw-r--r--src/lib/address/api/cityApi.js4
-rw-r--r--src/lib/address/api/stateApi.js8
-rw-r--r--src/lib/address/components/CreateAddress.jsx51
-rw-r--r--src/lib/address/components/EditAddress.jsx61
-rw-r--r--src/lib/cart/components/Cartheader.jsx354
-rw-r--r--src/lib/checkout/components/Checkout.jsx43
-rw-r--r--src/lib/checkout/components/FinishCheckout.jsx95
-rw-r--r--src/lib/flashSale/components/FlashSale.jsx94
-rw-r--r--src/lib/flashSale/components/FlashSaleNonDisplay.jsx68
-rw-r--r--src/lib/home/api/categoryManagementApi.js8
-rw-r--r--src/lib/home/components/BannerSection.jsx40
-rw-r--r--src/lib/home/components/CategoryDynamic.jsx236
-rw-r--r--src/lib/home/components/CategoryDynamicMobile.jsx56
-rw-r--r--src/lib/home/components/PreferredBrand.jsx81
-rw-r--r--src/lib/home/components/PromotionProgram.jsx87
-rw-r--r--src/lib/home/components/ServiceList.jsx4
-rw-r--r--src/lib/product/components/Product/ProductDesktopVariant.jsx419
-rw-r--r--src/lib/product/components/Product/ProductMobileVariant.jsx209
-rw-r--r--src/lib/product/components/ProductCard.jsx15
-rw-r--r--src/lib/product/components/ProductFilter.jsx127
-rw-r--r--src/lib/product/components/ProductFilterDesktop.jsx165
-rw-r--r--src/lib/product/components/ProductSearch.jsx20
-rw-r--r--src/lib/quotation/components/Quotation.jsx5
-rw-r--r--src/lib/review/components/CustomerReviews.jsx25
-rw-r--r--src/lib/shipment/components/Shipments.jsx204
-rw-r--r--src/lib/transaction/api/transactionsApi.js3
-rw-r--r--src/lib/transaction/components/Transaction.jsx6
-rw-r--r--src/lib/treckingAwb/component/Manifest.jsx124
-rw-r--r--src/lib/variant/components/VariantCard.jsx22
-rw-r--r--src/pages/_app.jsx2
-rw-r--r--src/pages/_document.jsx13
-rw-r--r--src/pages/api/banner-section.js44
-rw-r--r--src/pages/api/category-management.js85
-rw-r--r--src/pages/api/flashsale-header.js40
-rw-r--r--src/pages/api/hero-banner.js45
-rw-r--r--src/pages/api/page-content.js43
-rw-r--r--src/pages/api/search-flashsale.js45
-rw-r--r--src/pages/api/shop/brands.js30
-rw-r--r--src/pages/api/shop/preferredBrand.js61
-rw-r--r--src/pages/api/shop/product-detail.js2
-rw-r--r--src/pages/api/shop/search.js49
-rw-r--r--src/pages/api/shop/url-category_brand.js20
-rw-r--r--src/pages/api/shop/variant-detail.js31
-rw-r--r--src/pages/garansi-resmi.jsx13
-rw-r--r--src/pages/index.jsx253
-rw-r--r--src/pages/my/address/[id]/edit.jsx5
-rw-r--r--src/pages/pembayaran-tempo.jsx13
-rw-r--r--src/pages/shop/checkout/[status].jsx22
-rw-r--r--src/pages/shop/find/[slug].jsx69
-rw-r--r--src/pages/shop/product/variant/[slug].jsx7
-rw-r--r--src/pages/sitemap/categories-brand.xml.js35
-rw-r--r--src/pages/sitemap/categories-brand/[page].js43
-rw-r--r--src/utils/capializeFIrstWord.js9
-rw-r--r--src/utils/solrMapping.js4
85 files changed, 4786 insertions, 1724 deletions
diff --git a/package.json b/package.json
index a846749c..fd8dee81 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
},
"dependencies": {
"@chakra-ui/react": "^2.8.1",
+ "@choc-ui/chakra-autocomplete": "^5.6.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@heroicons/react": "^2.0.13",
@@ -47,6 +48,7 @@
"react-query": "^3.39.3",
"react-select": "^5.8.0",
"react-web-share": "^2.0.2",
+ "redis": "^4.7.0",
"snakecase-keys": "^5.5.0",
"swiper": "^8.4.4",
"tw-merge": "^0.0.1-alpha.3",
diff --git a/public/images/CHECKOUT-PESANAN.svg b/public/images/CHECKOUT-PESANAN.svg
new file mode 100644
index 00000000..6291b4cd
--- /dev/null
+++ b/public/images/CHECKOUT-PESANAN.svg
@@ -0,0 +1,958 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 500 500" style="enable-background:new 0 0 500 500;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#EBEBEB;}
+ .st1{fill:#FAFAFA;}
+ .st2{fill:#E0E0E0;}
+ .st3{fill:#F5F5F5;}
+ .st4{fill:#FFFFFF;}
+ .st5{display:none;}
+ .st6{display:inline;}
+ .st7{display:inline;fill:#EBEBEB;}
+ .st8{fill:#263238;}
+ .st9{fill:#EA0000;}
+ .st10{font-family:'Gilroy-Bold';}
+ .st11{font-size:12.4079px;}
+ .st12{font-family:'Gilroy-Regular';}
+ .st13{font-size:9.0789px;}
+ .st14{fill:#FFBE9D;}
+ .st15{fill:#EB996E;}
+ .st16{fill:#455A64;}
+ .st17{fill:none;stroke:#FFFFFF;stroke-width:2.000000e-02;stroke-miterlimit:10;}
+ .st18{opacity:0.3;}
+ .st19{fill:#E8505B;}
+ .st20{fill:#CC0000;}
+</style>
+<g id="Background_Complete">
+ <g>
+ <g>
+ <g>
+ <path class="st0" d="M450.1,142.2L360.5,162c-3.7,0.8-7.3-1.5-8.1-5.2l-18.7-84.3c-0.8-3.7,1.5-7.3,5.2-8.1l89.6-19.8
+ c3.7-0.8,7.3,1.5,8.1,5.2l18.7,84.3C456,137.8,453.7,141.4,450.1,142.2z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st1" d="M414.9,114.2c0,0-0.2,0.1-0.7,0.2c-0.5,0.1-1.2,0.3-2,0.5c-1.8,0.4-4.3,1-7.4,1.7
+ c-6.3,1.4-15,3.4-24.7,5.6l-0.2,0l-0.1-0.2c-0.5-1.3-1-2.6-1.5-4c-4.3-11.6-8.2-22-10.7-28.8l0.2,0.1
+ c-2.8,0.6-5,1.1-6.6,1.4c-0.7,0.1-1.3,0.3-1.8,0.4c-0.4,0.1-0.6,0.1-0.6,0.1c0,0,0.2-0.1,0.6-0.2c0.4-0.1,1-0.3,1.7-0.4
+ c1.6-0.4,3.8-0.9,6.6-1.5l0.1,0l0,0.1c2.6,6.8,6.5,17.1,10.9,28.7c0.5,1.4,1,2.7,1.5,4l-0.3-0.1c9.8-2.2,18.5-4.1,24.8-5.5
+ c3.1-0.7,5.6-1.2,7.4-1.6c0.8-0.2,1.5-0.3,2-0.4C414.7,114.2,414.9,114.2,414.9,114.2z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st1" d="M377.5,115c0,0,0.1,0,0.2-0.1c0.1,0,0.3-0.1,0.5-0.1c0.5-0.1,1.1-0.3,2-0.5c1.8-0.4,4.3-1,7.4-1.7
+ c6.4-1.5,15.3-3.5,25.7-5.8l-0.2,0.2c-0.2-4.5-0.4-9.3-0.7-14.4c-0.1-2.6-0.2-5.1-0.3-7.5l0.3,0.2c-12,2.7-22.6,5-30.3,6.7
+ c-3.8,0.8-6.8,1.5-9,1.9c-1,0.2-1.8,0.4-2.4,0.5c-0.3,0-0.5,0.1-0.6,0.1c-0.1,0-0.2,0-0.2,0c0,0,0.1,0,0.2-0.1
+ c0.2,0,0.4-0.1,0.6-0.2c0.6-0.1,1.4-0.3,2.4-0.6c2.1-0.5,5.2-1.2,8.9-2.1c7.6-1.7,18.2-4.1,30.2-6.9l0.3-0.1l0,0.3
+ c0.1,2.4,0.2,5,0.3,7.5c0.2,5,0.4,9.9,0.6,14.4l0,0.2l-0.2,0c-10.4,2.3-19.3,4.3-25.7,5.7c-3.2,0.7-5.7,1.2-7.5,1.6
+ c-0.8,0.2-1.5,0.3-2,0.4c-0.2,0-0.4,0.1-0.5,0.1C377.6,115,377.5,115,377.5,115z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st1" d="M391,129.5c0,0-0.1-0.3-0.4-0.8c-0.3-0.5-0.8-1.1-1.7-1.5c-0.9-0.4-2.1-0.4-3.2,0.3
+ c-1.1,0.7-1.8,2.1-1.4,3.5c0.3,1.4,1.6,2.4,2.8,2.6c1.3,0.2,2.4-0.4,3-1.1c0.6-0.7,0.8-1.6,0.9-2.1
+ C391,129.8,390.9,129.5,391,129.5c0,0,0.2,0.3,0.2,0.9c0,0.6-0.1,1.5-0.8,2.3c-0.6,0.8-1.9,1.5-3.3,1.3
+ c-1.4-0.1-2.9-1.3-3.2-2.9c-0.4-1.6,0.5-3.3,1.7-4c1.2-0.8,2.7-0.7,3.6-0.2c1,0.5,1.5,1.2,1.7,1.7
+ C391,129.2,391,129.5,391,129.5z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st1" d="M413.3,124.5c0,0-0.1-0.3-0.4-0.8c-0.3-0.5-0.8-1.1-1.7-1.5c-0.9-0.4-2.1-0.4-3.2,0.3
+ c-1.1,0.7-1.8,2.1-1.4,3.5c0.3,1.4,1.6,2.4,2.8,2.6c1.3,0.2,2.4-0.4,3-1.1c0.6-0.7,0.8-1.6,0.9-2.1
+ C413.4,124.8,413.3,124.5,413.3,124.5c0,0,0.2,0.3,0.2,0.9c0,0.6-0.1,1.5-0.8,2.3c-0.6,0.8-1.9,1.5-3.3,1.3
+ c-1.4-0.1-2.9-1.3-3.2-2.9c-0.4-1.6,0.5-3.3,1.7-4c1.2-0.8,2.7-0.7,3.6-0.2c1,0.5,1.5,1.2,1.7,1.7
+ C413.4,124.1,413.4,124.5,413.3,124.5z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M419.6,79.8c1.2,5.5-2.2,10.9-7.7,12.2c-5.5,1.2-10.9-2.2-12.2-7.7c-1.2-5.5,2.2-10.9,7.7-12.2
+ C412.9,70.8,418.3,74.3,419.6,79.8z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st2" d="M414.2,77.1c0.1,0-0.3,0.8-0.9,1.9c-0.6,1.1-1.5,2.7-2.5,4.4c-0.4,0.6-0.7,1.2-1.1,1.8l-0.1,0.2
+ l-0.2-0.1c-2.6-1.4-4.4-2.5-4.3-2.6c0.1-0.1,2,0.8,4.6,2.2l-0.3,0.1c0.3-0.6,0.7-1.2,1.1-1.8c1-1.7,2-3.2,2.7-4.3
+ C413.7,77.7,414.1,77,414.2,77.1z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st2" d="M72.3,194.4c-1.8-6.5-10.3-5.9-10.3-5.9l0.2,4.5c4.9-0.8,8,4.9,8,4.9L72.3,194.4z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st2" d="M197,118.9c9.8,3,16-8.4,16-8.4l-6-3.4c-2.9,7-12.8,6.2-12.8,6.2L197,118.9z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st2" d="M278.7,179.9c-1.9-5.4-9.1-4.4-9.1-4.4l0.4,3.8c4.1-1,7,3.7,7,3.7L278.7,179.9z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st2" d="M165.8,52.7c-1.9-5.4-9.1-4.4-9.1-4.4l0.4,3.8c4.1-1,7,3.7,7,3.7L165.8,52.7z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st0" d="M80.4,122.2c-1.9-5.4-9.1-4.4-9.1-4.4l0.4,3.8c4.1-1,7,3.7,7,3.7L80.4,122.2z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st0" d="M62.5,71.4c-5.7,0.9-6,8.1-6,8.1l3.9,0.2c-0.2-4.2,4.9-6.2,4.9-6.2L62.5,71.4z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st0" d="M21.6,240.4c9.9,3,16-8.4,16-8.4l-6-3.4c-2.9,7-12.8,6.2-12.8,6.2L21.6,240.4z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st2" d="M302.2,119.6c0.7-4.6-4.8-6.5-4.8-6.5l-1.1,2.9c3.3,0.8,3.6,5.2,3.6,5.2L302.2,119.6z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st0" d="M269.5,69.5c-3.8,2.5-1.7,7.9-1.7,7.9l2.9-1.1c-1.6-3,1.5-6.2,1.5-6.2L269.5,69.5z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <polygon class="st3" points="436.1,171.6 422.8,210.7 462.1,218.5 464.4,177.2 "/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st4" d="M460,182.4c0,0-0.1,0.4-0.4,1.1c-0.4,0.7-1,1.6-2,2.6c-1,1-2.4,2-4.2,2.6c-1.8,0.6-3.9,0.7-6,0.3
+ c-2.1-0.4-4-1.2-5.5-2.5c-1.5-1.2-2.3-2.7-2.8-4c-0.5-1.3-0.6-2.5-0.7-3.2c0-0.8,0-1.2,0.1-1.2c0.1,0,0,1.7,1.1,4.2
+ c0.5,1.2,1.4,2.6,2.8,3.7c1.4,1.1,3.2,1.9,5.2,2.3c2,0.4,4,0.3,5.7-0.2c1.7-0.5,3-1.4,4-2.3
+ C459.2,183.9,459.9,182.3,460,182.4z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</g>
+<g id="Background_Simple" class="st5">
+ <g class="st6">
+ <path class="st0" d="M462.6,168.7c-10.1-32-28.2-62.7-55.9-80.6c-25.8-16.7-58.9-21-88-11.3c-29,9.7-53.4,33.1-64.6,62.2
+ c-6,15.6-8.4,32.4-13.4,48.4c-5,16-13.4,31.9-27.6,40.2c-15.9,9.3-35.8,7.3-53.6,2.5c-17.7-4.8-35.1-12.1-53.5-12.5
+ c-38-0.8-67.2,33.9-75.5,72c-9.2,42.5-4.1,86.6,51.5,117.4c0.9,0.5,1.2,0.7,1.1,0.7c20.2,12.8,32,22.3,54.3,28.4
+ c52.7,12.9,110.8,2.8,152.9,0c54.8-3.7,73.2-18.6,107.5-43.8c32.9-24.2,56.8-60.6,67.8-100.6C476.7,251.5,475.1,208.2,462.6,168.7
+ z"/>
+ <path class="st0" d="M83.2,407.6c-0.4-0.2-0.7-0.5-1.1-0.7C78.8,405.6,83,407.6,83.2,407.6z"/>
+ </g>
+ <path class="st7" d="M175.1,60.3c-10.8-8.5-25.8-11-39.1-7.5c-11.6,3-22.4,10.8-26.5,22.1c-3.3,9-2,19.3,2.2,27.9
+ c5.9,12.2,17.5,21.6,30.7,24.9c13.2,3.2,27.8,0.3,38.7-7.8l-1.8,0.7c6.6-4.5,7.8-13.9,9.9-23.7C191.9,83.5,185.9,68.8,175.1,60.3z"
+ />
+</g>
+<g id="Desk">
+ <g>
+ <g>
+ <path class="st8" d="M474.1,454.2c0,0.1-102.6,0.3-229.1,0.3c-126.5,0-229.1-0.1-229.1-0.3s102.5-0.3,229.1-0.3
+ C371.5,453.9,474.1,454,474.1,454.2z"/>
+ </g>
+ </g>
+</g>
+<g id="Screen">
+ <g>
+ <g>
+ <g>
+ <rect x="32.8" y="145.8" class="st1" width="243.9" height="190.9"/>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M276.6,336.7c0-1.8-0.1-75.7-0.2-190.9l0.2,0.2c-69.5,0-153.4,0-243.8,0h0l0.3-0.3c0,68.8,0,133.9,0,190.9
+ l-0.2-0.2C177.5,336.6,274.6,336.7,276.6,336.7c-2,0-99.1,0.1-243.9,0.2l-0.2,0l0-0.2c0-57,0-122.1,0-190.9l0-0.3h0.3h0
+ c90.4,0,174.3,0,243.8,0l0.2,0l0,0.2C276.7,261,276.6,334.9,276.6,336.7z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st9" d="M190.9,310.7h-72.5c-8.3,0-15-6.7-15-15l0,0c0-8.3,6.7-15,15-15h72.5c8.3,0,15,6.7,15,15l0,0
+ C206,304,199.2,310.7,190.9,310.7z"/>
+ </g>
+ <g>
+ <path class="st1" d="M136.4,298.7c-0.8,0-1.4-0.2-2-0.5c-0.5-0.3-0.9-0.8-1.1-1.4l1.5-0.9c0.3,0.7,0.8,1.1,1.6,1.1
+ c0.7,0,1-0.2,1-0.6c0-0.2-0.1-0.4-0.3-0.5c-0.2-0.1-0.6-0.3-1.3-0.4c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.5-0.3-0.7-0.4
+ c-0.2-0.2-0.4-0.4-0.5-0.7s-0.2-0.6-0.2-0.9c0-0.7,0.3-1.3,0.8-1.7c0.5-0.4,1.1-0.6,1.8-0.6c0.6,0,1.2,0.1,1.7,0.4
+ c0.5,0.3,0.9,0.7,1.1,1.3l-1.5,0.9c-0.1-0.3-0.3-0.5-0.5-0.7c-0.2-0.1-0.5-0.2-0.8-0.2c-0.3,0-0.5,0.1-0.6,0.2
+ c-0.1,0.1-0.2,0.3-0.2,0.4c0,0.2,0.1,0.4,0.3,0.5c0.2,0.1,0.6,0.3,1.1,0.5c0.3,0.1,0.5,0.2,0.7,0.2c0.2,0.1,0.4,0.2,0.6,0.3
+ c0.3,0.1,0.5,0.3,0.6,0.4s0.3,0.3,0.4,0.6c0.1,0.2,0.2,0.5,0.2,0.8c0,0.7-0.3,1.3-0.8,1.7C137.9,298.5,137.2,298.7,136.4,298.7z
+ "/>
+ <path class="st1" d="M142,296.8h3.1v1.7h-4.8v-7.7h4.8v1.7h-3v1.3h2.8v1.7H142V296.8z"/>
+ <path class="st1" d="M148.1,296.8h2.8v1.7h-4.5v-7.7h1.8V296.8z"/>
+ <path class="st1" d="M153.6,296.8h3.1v1.7h-4.8v-7.7h4.8v1.7h-3v1.3h2.8v1.7h-2.8V296.8z"/>
+ <path class="st1" d="M160.6,298.7c-0.8,0-1.4-0.2-2-0.5c-0.5-0.3-0.9-0.8-1.1-1.4l1.5-0.9c0.3,0.7,0.8,1.1,1.6,1.1
+ c0.7,0,1-0.2,1-0.6c0-0.2-0.1-0.4-0.3-0.5c-0.2-0.1-0.6-0.3-1.3-0.4c-0.3-0.1-0.6-0.2-0.8-0.3c-0.2-0.1-0.5-0.3-0.7-0.4
+ c-0.2-0.2-0.4-0.4-0.5-0.7s-0.2-0.6-0.2-0.9c0-0.7,0.3-1.3,0.8-1.7c0.5-0.4,1.1-0.6,1.8-0.6c0.6,0,1.2,0.1,1.7,0.4
+ c0.5,0.3,0.9,0.7,1.1,1.3l-1.5,0.9c-0.1-0.3-0.3-0.5-0.5-0.7c-0.2-0.1-0.5-0.2-0.8-0.2c-0.3,0-0.5,0.1-0.6,0.2
+ c-0.1,0.1-0.2,0.3-0.2,0.4c0,0.2,0.1,0.4,0.3,0.5c0.2,0.1,0.6,0.3,1.1,0.5c0.3,0.1,0.5,0.2,0.7,0.2c0.2,0.1,0.4,0.2,0.6,0.3
+ c0.3,0.1,0.5,0.3,0.6,0.4s0.3,0.3,0.4,0.6c0.1,0.2,0.2,0.5,0.2,0.8c0,0.7-0.3,1.3-0.8,1.7C162.1,298.5,161.4,298.7,160.6,298.7z
+ "/>
+ <path class="st1" d="M169.4,298.5l-0.4-1.2h-3l-0.4,1.2h-1.9l2.6-7.7h2.2l2.6,7.7H169.4z M166.6,295.7h1.9l-1-2.8L166.6,295.7z"
+ />
+ <path class="st1" d="M172.1,290.8h1.8v7.7h-1.8V290.8z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st9" d="M156.7,219c-10.7,0-19.4-8.7-19.4-19.4c0-10.7,8.7-19.4,19.4-19.4s19.4,8.7,19.4,19.4
+ C176.1,210.3,167.4,219,156.7,219z M156.7,182.2c-9.6,0-17.4,7.8-17.4,17.4c0,9.6,7.8,17.4,17.4,17.4c9.6,0,17.4-7.8,17.4-17.4
+ C174.1,190,166.3,182.2,156.7,182.2z"/>
+ </g>
+ <g>
+ <path class="st9" d="M155.3,205.6c-0.2,0-0.5-0.1-0.7-0.2c-1.7-1.5-4.6-4.1-5-4.6c-0.3-0.5-0.2-1.1,0.3-1.4
+ c0.5-0.3,1.1-0.2,1.4,0.3c0.3,0.3,2.1,2,4,3.7l9.8-9.3c0.4-0.4,1-0.4,1.4,0c0.4,0.4,0.4,1,0,1.4l-10.5,9.9
+ C155.8,205.5,155.5,205.6,155.3,205.6z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M276.6,164.5c0,0.1-54.5,0.3-121.6,0.3c-67.2,0-121.6-0.1-121.6-0.3s54.4-0.3,121.6-0.3
+ C222.2,164.2,276.6,164.3,276.6,164.5z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <circle class="st8" cx="41.4" cy="154.9" r="1.4"/>
+ </g>
+ <g>
+ <circle class="st8" cx="46.7" cy="154.9" r="1.4"/>
+ </g>
+ <g>
+ <path class="st8" d="M53.3,154.9c0,0.8-0.6,1.4-1.4,1.4c-0.8,0-1.4-0.6-1.4-1.4c0-0.8,0.6-1.4,1.4-1.4
+ C52.6,153.5,53.3,154.1,53.3,154.9z"/>
+ </g>
+ </g>
+ <text transform="matrix(1 0 0 1 61.8303 245.5229)" class="st8 st10 st11">Pembelianmu sudah kami terima</text>
+ <text transform="matrix(1 0 0 1 69.6563 258.5025)" class="st8 st12 st13">Mohon cek pesananmu di detail pesanan</text>
+ </g>
+</g>
+<g id="Character">
+ <g>
+ <g>
+ <path class="st14" d="M436.9,274.1c0,0,12.1-7.4,14.9-5.2c3.9,3.1,5.3,8.4,5.3,8.4s6.4-24.4,12.5-23.5c3.5,0.5,1.8,3.3,0.2,6.7
+ c-1.5,3.2-4,14.9-4,14.9s11.6-13.8,15-13.5c2.6,0.2,3,2.5-0.8,6.2c-3.8,3.7-10.1,15.4-10.1,15.4s8.1-7.6,11.5-7.6
+ c2.7,0,4.4,1.8,0.9,4.8c-3.5,3-8.7,5.3-11.5,10.7c-3.4,6.7-10.8,23.4-10.8,23.4l-25.7-9.1c0,0-4.2-17.1-4.4-21.2
+ c0,0,0.4-8.8,0.3-10.3c0-1.6,4.1-2,5.3,1c1.3,3,0.7,6.1,1.2,8.1c0.5,2,1.4,3.8,2.9,4.7c1.5,0.9,5.2,1,6.1-0.1
+ c0.9-1.1,2.1-4.3,2.3-6.3c0.2-2-0.1-5.1-0.1-5.1S437.4,280.9,436.9,274.1z"/>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st14" d="M147.9,353.7L130,347c0,0-18.2-10.7-21.6-6.9c0,0-3.6-1.7-5.2,2.1c-0.9,2.2,2.1,5.5,2.1,5.5s-1,1,0.6,4.2
+ c1.5,3.3,18.8,13.6,18.8,13.6s22.5,12.2,31.5,18.1l15.7-21.3l-2.2-0.8c0,0-1.3-10.6-5.6-13.5c-4.7-3.1-10.4-11.4-11.7-14.7
+ c-1.3-3.3-7.3-1.8-6.3,5c1,6.8,6.3,8.5,5.8,13.1C151.4,356,147.9,353.7,147.9,353.7z"/>
+ </g>
+ <g>
+ <g>
+ <path class="st15" d="M120.5,356.2c-0.1,0.1-3.6-1.7-7.8-4.1c-4.2-2.4-7.6-4.4-7.5-4.5c0.1-0.1,3.6,1.7,7.8,4.1
+ C117.2,354,120.5,356.1,120.5,356.2z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st15" d="M129.2,351.2c0,0.1-1.3-0.4-3.2-1.3c-2-0.9-4.6-2.1-7.5-3.6c-2.9-1.5-5.5-3-7.2-4.1
+ c-0.9-0.6-1.6-1.1-2.1-1.4c-0.5-0.4-0.7-0.6-0.7-0.6c0-0.1,1.1,0.7,3,1.7c1.8,1.1,4.4,2.5,7.3,4c2.9,1.5,5.5,2.7,7.5,3.7
+ C128.1,350.5,129.2,351.1,129.2,351.2z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st15" d="M133,361c0,0-0.6-0.3-1.6-0.9c-1.1-0.6-2.5-1.5-4.2-2.5l0,0l0-0.1c-0.8-1.7-1.8-3.7-3-5.8
+ c-0.6-1-1.3-2.1-2.3-3c-0.9-0.9-2.4-1-3.5-0.3c-0.2,0.1-0.5,0.4-0.6,0.5c0,0,0,0.1,0,0.1l0.1,0.2c0,0.1,0.1,0.3,0.2,0.4
+ c0.2,0.6,0.5,1.2,0.7,1.8c0.5,1.2,0.9,2.3,1.4,3.3c0.9,2.1,1.7,4.1,2.4,5.8l0-0.1c1.5,1.7,2.7,3.1,3.6,4.2
+ c0.4,0.5,0.7,0.9,1,1.2c0.2,0.3,0.3,0.4,0.3,0.4c0,0-0.2-0.1-0.4-0.4c-0.3-0.3-0.6-0.7-1-1.1c-0.9-1-2.2-2.4-3.7-4.1l0,0l0,0
+ c-0.7-1.7-1.6-3.6-2.5-5.7c-0.4-1.1-0.9-2.2-1.4-3.3c-0.2-0.6-0.5-1.2-0.7-1.8c-0.1-0.2-0.1-0.3-0.2-0.5l-0.1-0.2
+ c0-0.1,0-0.2-0.1-0.4c0.1-0.3,0.2-0.4,0.4-0.5c0.1-0.1,0.3-0.2,0.4-0.3c0.6-0.4,1.4-0.6,2.2-0.6c0.8,0,1.4,0.4,2,0.9
+ c1,1,1.7,2.1,2.4,3.1c1.3,2.1,2.2,4.2,3,5.9l-0.1-0.1c1.7,1.1,3.1,2,4.1,2.7C132.5,360.6,133,361,133,361z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st15" d="M150.9,361.2c0,0.1-0.9-0.2-2.2-0.5c-1.3-0.4-3.2-0.9-5.3-0.9c-2.1,0-3.9,0.5-5.1,1.2
+ c-1.2,0.7-1.7,1.4-1.8,1.4c0,0,0.4-0.8,1.6-1.7c1.2-0.8,3.1-1.5,5.3-1.5c2.2,0.1,4.1,0.6,5.4,1.1
+ C150.1,360.8,150.9,361.1,150.9,361.2z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st15" d="M149.5,356.5c0.1,0.1-0.3,0.9-0.8,1.9c-0.4,1-0.7,1.8-0.9,1.8c-0.1,0-0.1-1,0.4-2
+ C148.8,357.1,149.4,356.4,149.5,356.5z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M252.6,271.5c1.2-8.4,11-20.5,19.7-20.5c-5-7.2-5.1-17.4-0.2-24.6c3.3-5,8.8-8.7,11-14.3
+ c2.6-6.3,0.6-13.4,0.9-20.2c0.4-9.9,5.5-19,10.5-27.6c4.6-7.9,10.2-16.5,19.3-18.5c5.7-1.2,11.7,0.6,17.1,2.9
+ c25.7,11,45.8,34.1,52.6,60.8c1.6,6.4,2.7,13.3,7.2,18.2c4.7,5.2,13,8.1,14.4,14.9c1,4.9-2.1,9.8-1.7,14.8
+ c0.4,4.2,8.2,16.3,10,20.1c3.1,6.5,2,13.6-1.2,20.1c-3.2,6.5-11.2,12.9-18.3,14.7l-79.8-26.6c-7.5,4.3-15,20.8-23.6,21.8
+ c-8.6,1-19.1,4.8-27.4-4.3C254.4,293.8,251.3,279.9,252.6,271.5z"/>
+ </g>
+ <g>
+ <path class="st8" d="M384.9,192.1c-0.9,6.7,0.9,17.7,4.6,23.4c2.4,3.8,6.5,6.3,10.6,8.5c4,2.2,8.3,4.3,11.3,7.7
+ c3.1,3.4,4.7,8.5,2.7,12.5c7.2,2.7,10.4,11.8,7.8,18.9c-2.6,7.1-9.9,11.8-17.4,13.3l-32.9-9.9l-7.2-42l19-32.4"/>
+ </g>
+ <g>
+ <g>
+ <path class="st16" d="M388.9,234.9c0,0,0.2,0.1,0.6,0.3c0.4,0.2,1.1,0.5,1.8,0.9c1.5,0.9,3.6,2.4,5.8,4.8
+ c1.1,1.2,2.1,2.6,3.1,4.3c0.9,1.7,1.8,3.5,2.4,5.6c1.3,4.1,1.6,9.1,0.1,14l-0.1-0.3c1.9,1.1,3.7,2.4,5.4,4.1
+ c4.2,4.2,6.6,9.6,7.2,14.7c0.6,5.1-0.7,9.7-2.4,13.2c-1.7,3.5-3.8,5.8-5.4,7.3c-0.8,0.7-1.4,1.2-1.9,1.6
+ c-0.5,0.3-0.7,0.5-0.7,0.5c0,0,0.2-0.2,0.7-0.6c0.4-0.3,1.1-0.9,1.8-1.6c1.5-1.5,3.6-3.8,5.2-7.3c1.6-3.4,2.8-8,2.2-13
+ c-0.6-5-3-10.3-7.1-14.3c-1.6-1.6-3.4-3-5.2-4l-0.2-0.1l0.1-0.2c1.4-4.8,1.2-9.7,0-13.7c-0.6-2.1-1.4-3.9-2.3-5.5
+ c-0.9-1.6-1.9-3.1-3-4.2c-2.1-2.4-4.1-4-5.6-4.9c-0.7-0.5-1.4-0.8-1.7-1C389.1,235.1,388.9,234.9,388.9,234.9z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st9" d="M291.6,278.4c0,0-14.7,4.5-19.9,12c-3.6,5.3-41,86.2-41,86.2l-56.7-17l-20.3,26.8l63.1,35
+ c11.7,6.5,25.9,6.4,37.5-0.2l0,0c6.5-3.7,11.7-9.1,15.1-15.7l16-30.7L291.6,278.4z"/>
+ </g>
+ <g>
+ <g>
+ <path class="st9" d="M276.5,454.5l8.9-38.1l-6.7-27.7c-4.3-14.3-8-37.5-3.3-56.2l15.7-53.8l17.2-7.9l39.4-6.8
+ c1.5,0.5,41.2,11.7,41.2,11.7c5.5,1.3,7.7,2.2,7.7,2.2l4.6,29.5l-13.7,48.6l-6,48.4l13.1,50.3H276.5z"/>
+ </g>
+ <g>
+ <g>
+ <path class="st17" d="M273.6,335.9"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st17" d="M366,339.4"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <path class="st14" d="M299.3,274.9l11.4-5.1l51.5,0.7l12.2,7.1c0,0-42.6,47-53.3,48.6C294.3,330,299.3,274.9,299.3,274.9z"/>
+ </g>
+ <g>
+ <path class="st9" d="M395.5,277.4c0,0,7.7,1.7,14.3,15.9c6.6,14.1,17.3,53.4,17.3,53.4l4.6-41.6l37.3,4.1l-7.2,99.5
+ c0,0-13.2,19.1-35.3,17.4c-22.1-1.8-50.1-71.5-50.1-71.5L395.5,277.4z"/>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M421.4,397.6c0,0,0-0.1,0-0.2c0-0.2,0-0.4,0-0.7c0-0.7,0.1-1.6,0.2-2.7c0.1-2.4,0.4-5.8,0.8-9.9
+ c0.8-8.4,2.2-19.9,3.9-32.7c1.7-12.7,3.2-24.3,4.1-32.7c0.4-4.2,0.8-7.6,1-9.9c0.1-1.1,0.2-2,0.3-2.7c0-0.3,0.1-0.5,0.1-0.7
+ c0-0.2,0-0.2,0-0.2c0,0,0,0.1,0,0.2c0,0.2,0,0.4,0,0.7c0,0.7-0.1,1.6-0.2,2.7c-0.1,2.4-0.4,5.8-0.8,9.9
+ c-0.8,8.4-2.2,19.9-3.9,32.7c-1.7,12.7-3.2,24.3-4.1,32.6c-0.4,4.2-0.8,7.6-1,9.9c-0.1,1.1-0.2,2-0.3,2.7
+ c0,0.3-0.1,0.5-0.1,0.7C421.4,397.5,421.4,397.6,421.4,397.6z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M231.4,398c0,0-0.2-0.3-0.4-0.7c-0.2-0.5-0.5-1.2-0.9-2.1c-0.7-1.8-1.3-4.5-1.6-7.5
+ c-0.3-3-0.1-5.7,0.2-7.6c0.1-1,0.3-1.7,0.4-2.3c0.1-0.5,0.2-0.8,0.3-0.8c0.1,0-0.1,1.2-0.3,3.1c-0.2,1.9-0.3,4.6,0,7.5
+ c0.3,2.9,0.9,5.5,1.5,7.4C231,396.9,231.4,398,231.4,398z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M237.6,399c0,0-0.3-0.2-0.7-0.7c-0.4-0.5-0.9-1.2-1.6-2.2c-1.2-1.9-2.7-4.7-3.9-8
+ c-1.1-3.3-1.7-6.4-1.9-8.7c-0.1-1.1-0.2-2.1-0.2-2.7c0-0.6,0-1,0.1-1c0.1,0,0.2,1.4,0.5,3.6c0.3,2.2,0.9,5.3,2.1,8.6
+ c1.1,3.3,2.6,6,3.7,8C236.9,397.8,237.7,398.9,237.6,399z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M281.7,310c0,0,0,0.3-0.2,0.8c-0.2,0.6-0.4,1.3-0.6,2.3c-0.6,2-1.4,4.9-2.4,8.4
+ c-1.9,7.1-4.6,16.9-5.9,28.1c-0.6,5.6-0.8,10.9-0.2,15.8c0.5,4.8,1.6,9.1,2.6,12.6c1.1,3.5,2.1,6.3,2.7,8.3
+ c0.3,0.9,0.6,1.7,0.8,2.2c0.2,0.5,0.3,0.8,0.2,0.8c0,0-0.1-0.3-0.3-0.8c-0.2-0.5-0.5-1.3-0.9-2.2c-0.7-1.9-1.8-4.7-2.9-8.2
+ c-1.1-3.5-2.2-7.8-2.8-12.7c-0.6-4.9-0.5-10.3,0.2-15.9c1.3-11.2,4.1-21.1,6.1-28.1c1-3.5,1.9-6.4,2.5-8.3
+ c0.3-0.9,0.5-1.7,0.7-2.3C281.6,310.3,281.7,310,281.7,310z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M386,375.6c0,0-0.1-0.1-0.1-0.2c-0.1-0.2-0.2-0.4-0.3-0.7c-0.3-0.7-0.7-1.6-1.3-2.9
+ c-0.6-1.2-1.3-2.8-2-4.6c-0.7-1.8-1.5-3.9-2.4-6.1c-0.9-2.3-1.7-4.8-2.7-7.5c-0.4-1.4-0.9-2.8-1.3-4.3
+ c-0.2-0.7-0.5-1.5-0.7-2.2c-0.2-0.8-0.4-1.5-0.6-2.3c-1.7-6.2-3.1-13.2-3.5-20.7c-0.5-7.5,0.3-14.7,2.2-20.9
+ c1.8-6.3,4.9-11.4,8.3-15c3.3-3.6,6.8-5.7,9.3-6.8c1.3-0.5,2.3-0.9,3-1.1c0.3-0.1,0.6-0.2,0.8-0.2c0.2,0,0.3-0.1,0.3-0.1
+ c0,0-0.1,0-0.3,0.1c-0.2,0.1-0.5,0.2-0.8,0.3c-0.7,0.2-1.7,0.6-2.9,1.2c-2.4,1.2-5.9,3.3-9.1,6.9c-3.3,3.6-6.3,8.7-8.1,14.8
+ c-1.9,6.2-2.5,13.3-2.1,20.7c0.4,7.4,1.8,14.4,3.5,20.6c0.2,0.8,0.4,1.5,0.6,2.3c0.2,0.8,0.4,1.5,0.7,2.2
+ c0.4,1.5,0.9,2.9,1.3,4.3c1,2.7,1.8,5.2,2.6,7.5c0.9,2.3,1.7,4.3,2.4,6.1c0.7,1.8,1.4,3.3,1.9,4.6c0.5,1.2,0.9,2.2,1.2,2.9
+ c0.1,0.3,0.2,0.6,0.3,0.8C385.9,375.5,386,375.6,386,375.6z"/>
+ </g>
+ </g>
+ <g class="st18">
+ <g>
+ <path d="M371.8,330.5c-4.9,23-4.9,46.8,3.5,68.8c0.5,1.3,2.4,5.8,3.8,5.9c1.7,0.1,2.6-2.1,2.9-3.8c0.6-3.5,1-9.3,1.6-12.8
+ c0.8-4.4,1.8-14.6,1.8-14.6s-4-9.2-5.8-14.2c-3.3-9.2-6.1-17.9-7.6-28.6"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M367.8,419.8c0,0-0.1,0.2-0.4,0.6c-0.3,0.4-0.7,1-1.2,1.7c-0.3,0.4-0.6,0.8-0.9,1.2
+ c-0.3,0.4-0.7,0.9-1.1,1.3c-0.8,0.9-1.7,2.1-2.9,3.2c-0.6,0.6-1.2,1.1-1.8,1.8c-0.6,0.6-1.4,1.2-2.1,1.8
+ c-1.4,1.3-3.1,2.4-4.8,3.6c-3.5,2.3-7.6,4.6-12.2,6.4c-4.6,1.7-9.1,2.9-13.3,3.5c-2.1,0.2-4.1,0.5-6,0.6
+ c-0.9,0-1.8,0.1-2.7,0.1c-0.9,0-1.7-0.1-2.5-0.1c-1.6,0-3-0.2-4.3-0.4c-0.6-0.1-1.2-0.1-1.8-0.2c-0.5-0.1-1-0.2-1.5-0.3
+ c-0.8-0.2-1.5-0.3-2-0.4c-0.5-0.1-0.7-0.2-0.7-0.2c0,0,0.3,0,0.7,0.1c0.5,0.1,1.2,0.2,2,0.3c0.4,0.1,0.9,0.2,1.5,0.2
+ c0.5,0.1,1.1,0.1,1.8,0.2c1.3,0.1,2.7,0.3,4.3,0.3c0.8,0,1.6,0,2.5,0c0.9,0,1.8-0.1,2.7-0.1c1.9,0,3.8-0.3,5.9-0.6
+ c4.1-0.7,8.6-1.8,13.2-3.5c4.5-1.8,8.6-4,12.1-6.3c1.7-1.2,3.4-2.3,4.8-3.6c0.7-0.6,1.4-1.1,2.1-1.7c0.6-0.6,1.2-1.2,1.8-1.7
+ c1.2-1.1,2.1-2.2,3-3.1c0.4-0.5,0.8-0.9,1.2-1.3c0.3-0.4,0.6-0.8,0.9-1.2c0.5-0.7,0.9-1.2,1.3-1.6
+ C367.6,420,367.8,419.8,367.8,419.8z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M359.7,404.6c0,0,0,0.4-0.1,1.1c-0.1,0.7-0.3,1.7-0.7,2.9c-0.7,2.4-2,5.7-4.1,9c-2.1,3.3-4.6,5.8-6.5,7.4
+ c-1,0.8-1.8,1.4-2.4,1.8c-0.6,0.4-0.9,0.6-0.9,0.6c-0.1-0.1,1.2-1,3.1-2.7c1.9-1.7,4.2-4.2,6.3-7.5c2.1-3.2,3.4-6.4,4.2-8.8
+ C359.3,406.1,359.6,404.6,359.7,404.6z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st9" d="M341.5,139.4c14,0.1,27.6,8,34.7,20c7.1,12,7.3,27.8,0.6,40c-2.3,4.2-5.6,8.1-10.1,9.6
+ c1.5-23.9-8-48.3-25.3-64.9"/>
+ </g>
+ <g>
+ <path class="st9" d="M357.8,143.1c-2.1-4.3,0.1-9.7,3.8-12.7c3.7-3,8.5-4.3,13.2-5.4c4.6-1.1,9.5-2.3,13.2-5.2
+ c-0.5,7.5-2.4,15.2-7.3,20.9c-4.8,5.7-13.2,8.9-20.2,6"/>
+ </g>
+ <g>
+ <path class="st9" d="M362.6,148c-3.2,1.3-7,0.5-9.8-1.4c-2.8-2-4.9-4.9-6.6-7.9c-2.1-3.7-3.7-7.7-4.2-11.9s0-8.6,2.1-12.3
+ c5.5,5.8,11.5,10.7,14.6,18.1c1.8,4.3,1.6,11.3-0.4,15.5"/>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M378.1,172.2c0,0,0.1,0.5,0.3,1.4c0.1,0.5,0.2,1,0.2,1.6c0.1,0.6,0.1,1.4,0.2,2.2
+ c0.1,1.6,0.1,3.6-0.1,5.7c-0.2,2.2-0.5,4.5-1.1,6.9c-0.6,2.4-1.3,4.7-2.1,6.7c-0.8,2-1.7,3.8-2.5,5.2
+ c-0.4,0.7-0.8,1.3-1.1,1.9c-0.3,0.6-0.7,1-0.9,1.4c-0.5,0.7-0.8,1.1-0.9,1.1c0,0,0.2-0.5,0.7-1.2c0.2-0.4,0.5-0.9,0.8-1.4
+ c0.3-0.6,0.7-1.2,1.1-1.9c0.8-1.4,1.6-3.2,2.4-5.2c0.8-2,1.5-4.2,2.1-6.6c0.6-2.4,0.9-4.7,1.1-6.9c0.2-2.1,0.3-4.1,0.2-5.7
+ c0-0.8-0.1-1.5-0.1-2.2c0-0.6-0.1-1.2-0.1-1.6C378.1,172.8,378.1,172.2,378.1,172.2z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M375,145.4c0,0-0.4,0.3-1.1,0.9c-0.8,0.4-2,1.1-3.6,1.4c-3.2,0.7-7.9,0.6-12.8-0.7l-0.1,0l0-0.1
+ c-0.1-0.3-0.1-0.6-0.2-0.9c-0.5-2.6-0.6-5.2-0.3-7.4c0.3-2.3,1.1-4.3,2.1-5.7c1-1.5,2.2-2.3,3-2.8c0.9-0.5,1.3-0.7,1.3-0.7
+ c0,0-0.4,0.3-1.2,0.8c-0.7,0.6-1.9,1.4-2.8,2.9c-0.9,1.4-1.7,3.3-1.9,5.6c-0.3,2.2-0.2,4.7,0.4,7.3c0.1,0.3,0.1,0.6,0.2,0.9
+ l-0.2-0.2c4.8,1.3,9.4,1.5,12.5,0.9c1.6-0.3,2.8-0.8,3.6-1.2C374.6,145.7,375,145.4,375,145.4z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M379.1,132.5c0,0.1-1.5,0.3-3.8,1c-1.1,0.3-2.5,0.8-3.9,1.5c-1.4,0.7-3,1.5-4.5,2.5c-1.5,1-2.9,2.1-4,3.1
+ c-1.2,1.1-2.1,2.1-2.9,3c-1.5,1.8-2.3,3.1-2.4,3.1c0,0,0.1-0.3,0.5-0.9c0.3-0.6,0.9-1.4,1.6-2.4c0.7-1,1.7-2,2.9-3.1
+ c1.2-1.1,2.5-2.2,4.1-3.2c1.6-1,3.1-1.8,4.6-2.5c1.5-0.6,2.8-1.1,4-1.4c1.2-0.3,2.1-0.5,2.8-0.6
+ C378.7,132.5,379.1,132.4,379.1,132.5z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M362.4,147.9c0,0,0,0.1,0,0.3c0,0.2-0.1,0.5-0.3,0.8c-0.3,0.7-0.9,1.7-2,2.5c-1.1,0.8-2.6,1.5-4.5,1.5
+ c-1.8-0.1-3.8-1.2-4.9-2.9c-1.2-1.7-1.5-3.9-0.9-5.7c0.6-1.8,1.8-3,3-3.7c1.2-0.7,2.3-0.9,3-0.9c0.4,0,0.7,0,0.9,0
+ c0.2,0,0.3,0.1,0.3,0.1c0,0.1-0.4,0-1.1,0.1c-0.7,0.1-1.8,0.4-2.9,1.1c-1.1,0.7-2.2,1.9-2.7,3.5c-0.5,1.6-0.2,3.6,0.9,5.2
+ c1.1,1.6,2.9,2.6,4.5,2.7c1.7,0.1,3.2-0.5,4.2-1.3c1-0.8,1.7-1.6,2-2.3C362.3,148.3,362.4,147.9,362.4,147.9z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M350.3,148.1c0,0-0.3-0.5,0.1-1.3c0.2-0.4,0.6-0.7,1.2-0.9c0.5-0.2,1.2-0.2,1.8-0.2
+ c2.5,0.2,4.3,1.2,4.2,1.3c-0.1,0.1-1.9-0.6-4.3-0.7c-1.2-0.1-2.2,0.2-2.6,0.8C350.3,147.6,350.4,148.1,350.3,148.1z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M344.1,114.4c0.1,0-1.1,1.5-1.5,4.3c-0.2,1.4-0.3,3.1-0.2,5c0,1.9,0.1,3.9,0.5,6.1
+ c0.9,4.2,2.7,7.8,4.3,10.1c0.8,1.2,1.5,2.1,2,2.7c0.5,0.6,0.8,0.9,0.8,0.9c-0.1,0.1-1.4-1.1-3.1-3.4
+ c-1.7-2.3-3.5-5.9-4.5-10.2c-0.4-2.2-0.5-4.3-0.5-6.2c0-1.9,0.1-3.6,0.4-5c0.3-1.4,0.7-2.5,1.1-3.2
+ C343.8,114.7,344.1,114.4,344.1,114.4z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M349.3,126.3c0.1,0,0.6,0.6,1.4,1.7c0.8,1.1,1.8,2.7,2.7,4.6c0.9,1.9,1.4,3.7,1.8,5
+ c0.3,1.3,0.4,2.2,0.3,2.2c-0.2,0-0.8-3.3-2.6-7C351.2,129.1,349.2,126.4,349.3,126.3z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M347.6,132.5c0.1-0.1,1.8,1.6,4,3.6c2.2,2,4,3.6,3.9,3.8c-0.1,0.1-2-1.4-4.2-3.4
+ C349.1,134.5,347.5,132.6,347.6,132.5z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M373.8,156c0,0,0,0.1-0.1,0.1c-0.1,0.1-0.3,0.2-0.4,0.4c-0.4,0.3-1,0.8-1.7,1.3c-1.5,1-3.7,2.2-6.3,3.1
+ c-2.6,0.9-5.1,1.2-6.9,1.3c-0.9,0-1.6,0-2.1,0c-0.2,0-0.4,0-0.6,0c-0.1,0-0.2,0-0.2,0c0,0,0.1,0,0.2,0c0.2,0,0.4,0,0.6,0
+ c0.5,0,1.2,0,2.1-0.1c1.8-0.1,4.2-0.5,6.8-1.3c2.6-0.9,4.8-2,6.3-3c0.7-0.5,1.3-0.9,1.7-1.2c0.2-0.1,0.3-0.2,0.5-0.3
+ C373.7,156,373.8,155.9,373.8,156z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M379.7,168.1c0,0,0,0.1-0.1,0.1c-0.1,0.1-0.3,0.2-0.4,0.4c-0.4,0.3-1,0.8-1.7,1.3c-1.5,1-3.7,2.2-6.3,3.2
+ c-2.6,0.9-5.1,1.4-6.8,1.6c-0.9,0.1-1.6,0.1-2.1,0.1c-0.2,0-0.4,0-0.6,0c-0.1,0-0.2,0-0.2,0c0,0,0.1,0,0.2,0
+ c0.2,0,0.4,0,0.6,0c0.5,0,1.2-0.1,2.1-0.2c1.8-0.2,4.2-0.7,6.8-1.6c2.6-0.9,4.7-2.1,6.2-3.1c0.8-0.5,1.3-0.9,1.7-1.2
+ c0.2-0.1,0.3-0.2,0.5-0.3C379.6,168.1,379.7,168,379.7,168.1z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M378.1,185.7c0,0-0.1,0.1-0.4,0.3c-0.3,0.2-0.7,0.4-1.2,0.7c-1.1,0.6-2.6,1.3-4.4,1.7
+ c-1.8,0.5-3.5,0.6-4.7,0.6c-0.6,0-1.1,0-1.4,0c-0.3,0-0.5-0.1-0.5-0.1c0,0,0.2,0,0.5,0c0.3,0,0.8,0,1.4,0
+ c1.2,0,2.9-0.2,4.6-0.7c1.8-0.5,3.3-1.1,4.4-1.7c0.5-0.3,1-0.5,1.3-0.7C377.9,185.8,378.1,185.7,378.1,185.7z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M381.5,183c0,0.1-0.6,0.2-1.4,0.4c-0.8,0.1-1.4,0.2-1.4,0.2s0.6-0.2,1.4-0.4
+ C380.8,183,381.4,182.9,381.5,183z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M371.5,203.8c0,0.1-1,0.2-2.3,0.1c-1.3-0.1-2.3-0.4-2.2-0.4c0-0.1,1,0.2,2.3,0.2
+ C370.5,203.8,371.5,203.7,371.5,203.8z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M352.6,151.5c0,0.1-0.6,0.4-1.3,0.9c-0.8,0.4-1.4,0.7-1.4,0.7s0.6-0.4,1.3-0.9
+ C351.9,151.7,352.5,151.4,352.6,151.5z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M347.6,141c0,0.1-1.4,0.5-2.9,1.6c-1.5,1.1-2.4,2.3-2.5,2.3c0,0,0.2-0.3,0.6-0.8c0.4-0.5,1-1.1,1.8-1.6
+ c0.8-0.5,1.5-0.9,2.1-1.1C347.2,141,347.6,140.9,347.6,141z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M356.6,152.6c-0.1,0,0.3-1.4,0.1-3c-0.2-1.7-0.9-2.9-0.8-2.9c0,0,0.2,0.3,0.4,0.8
+ c0.2,0.5,0.5,1.3,0.6,2.1c0.1,0.9,0.1,1.6,0,2.2C356.7,152.3,356.6,152.6,356.6,152.6z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M387.5,120c0,0-0.4,0.5-1.1,1.2c-0.7,0.7-1.9,1.7-3.5,2.7c-1.6,1-3.5,2-5.8,3c-2.2,1-4.7,2-7.2,3.2
+ c-2.5,1.2-4.8,2.6-6.6,4.2c-1.8,1.6-3.1,3.4-3.9,5.1c-0.8,1.7-1.1,3.1-1.2,4.1c-0.1,1,0,1.6,0,1.6c0,0,0-0.1,0-0.4
+ c0-0.3,0-0.7,0-1.2c0.1-1,0.4-2.5,1.1-4.2c0.8-1.7,2.1-3.5,3.9-5.1c1.8-1.6,4.1-3.1,6.7-4.3c2.5-1.2,5-2.2,7.3-3.2
+ c2.2-1,4.2-2,5.7-2.9c1.6-1,2.7-1.9,3.5-2.6c0.4-0.3,0.7-0.6,0.9-0.8C387.4,120.1,387.5,120,387.5,120z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M384.8,131.9c0,0,0,0.1-0.1,0.2c-0.1,0.2-0.3,0.4-0.4,0.6c-0.4,0.5-0.9,1.3-1.7,2.2
+ c-1.5,1.8-3.7,4.2-6.5,6.4c-2.8,2.2-5.7,3.8-7.8,4.7c-1.1,0.5-1.9,0.8-2.6,1.1c-0.3,0.1-0.5,0.2-0.7,0.2
+ c-0.2,0.1-0.3,0.1-0.3,0.1c0,0,0.1,0,0.2-0.1c0.2-0.1,0.4-0.2,0.7-0.3c0.6-0.2,1.5-0.6,2.5-1.1c2.1-1,4.9-2.6,7.7-4.8
+ c2.8-2.2,5-4.5,6.5-6.3c0.7-0.9,1.3-1.6,1.7-2.2c0.2-0.2,0.3-0.4,0.4-0.6C384.8,131.9,384.8,131.8,384.8,131.9z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st1" d="M343.1,117.8c0,0,1.2,0.5,3,1.6c1.8,1.1,4.1,3,6.1,5.5c2,2.6,3.3,5.2,4,7.2c0.7,2,0.9,3.3,0.9,3.3
+ c0,0,0-0.1-0.1-0.2c-0.1-0.2-0.1-0.4-0.2-0.7c-0.1-0.6-0.4-1.4-0.8-2.4c-0.7-2-2-4.6-4-7.2c-2-2.5-4.3-4.4-6-5.5
+ c-0.9-0.6-1.6-1-2.1-1.3c-0.2-0.1-0.4-0.2-0.6-0.3C343.2,117.8,343.1,117.8,343.1,117.8z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M292.9,155.4c0.4,5.3-3.1,10-6.6,14c-3.5,4-7.5,8.1-8.2,13.4c-1.4,9.3,7.8,18,5.9,27.2
+ c-1,4.9-5.1,8.9-5.8,13.9c-0.7,4.8,1.9,9.6,5.1,13.2c3.2,3.7,6.9,3.1,9.8,7.1c3,4.2,7.8,8.5,12.3,10.9
+ c10.2,5.6,22.5-2.5,27.4-4"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M370.3,177.1c4.7-2.1,5.1-18.9-6.7-20.9c-4.7-0.8-8.7-3.1-12.7-5.8c-3.5-2.4-4.6-8.4-7.6-11.4
+ c-7.3-7.4-18.8-10.5-28.9-7.8c-10.1,2.7-18.4,11.2-20.9,21.4c-1.7,6.8-0.5,14.9,4.9,19.5c6.3,5.3,15.5,4.1,23.6,2.5
+ c8-1.6,17.1-3.3,23.8,1.5c4,2.8,6.4,7.5,8.8,11.7c2.5,4.3,5.6,8.6,10.2,10.3c0,0,3.6,8.4,0.7,14.2s3.4-4.1,3.4-4.1
+ S380.4,187.6,370.3,177.1"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st14" d="M310.4,295.7c-0.1-11,0-42,0-41.9c0,0-17.5-2.9-21.1-25.8c-1.8-11.4-1.2-30-0.2-45.3
+ c0.9-13.7,10-30.5,23.7-29.1l42.7,10.9c4.2,0.4,7.3,4,7.2,8.2l0,0l-2.6,106L310.4,295.7z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M296.4,195.7c-0.1,1.5,1.2,2.9,2.8,3c1.6,0.1,3-1.1,3-2.6c0.1-1.5-1.2-2.9-2.8-3
+ C297.8,193,296.4,194.2,296.4,195.7z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M293.1,192.4c0.4,0.4,2.6-1.3,5.7-1.2c3.2,0,5.5,1.6,5.8,1.2c0.2-0.2-0.2-0.9-1.2-1.6
+ c-1-0.7-2.7-1.4-4.6-1.4c-2,0-3.6,0.7-4.6,1.5C293.3,191.5,292.9,192.2,293.1,192.4z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M324.1,197.4c0.4,0.4,2.6-1.3,5.7-1.2c3.2,0,5.5,1.6,5.8,1.2c0.2-0.2-0.2-0.9-1.2-1.6
+ c-1-0.7-2.7-1.4-4.6-1.4c-2,0-3.6,0.7-4.6,1.5C324.3,196.5,324,197.2,324.1,197.4z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M312.6,216.4c0-0.2-1.9-0.5-5.1-0.9c-0.8-0.1-1.6-0.2-1.7-0.8c-0.2-0.6,0.2-1.5,0.5-2.4
+ c0.8-1.9,1.6-4,2.4-6.1c3.3-8.7,5.7-15.9,5.4-16c-0.4-0.1-3.3,6.8-6.7,15.6c-0.8,2.2-1.6,4.2-2.3,6.2
+ c-0.3,0.9-0.8,2-0.4,3.2c0.2,0.6,0.8,1,1.3,1.2c0.5,0.2,1,0.2,1.4,0.2C310.6,216.6,312.6,216.6,312.6,216.4z"
+ />
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st15" d="M310.4,253.8c0,0,15.4,1,30.6-8.1c-0.3,0.1-8.4,15.9-30.3,13.5L310.4,253.8z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <path class="st15" d="M313.5,225.5c-0.4-1.9,1.9-2.6,3.6-3.6c1.7-1,2.6-2.4,4.2-1.2c0.9,0.7,1.5,1.7,1.9,2.7
+ c0.3,0.7,0.4,1.5,0.3,2.2c-0.2,0.9-0.7,1.7-1.4,2.3c-0.9,0.9-2.1,1.6-3.4,1.8c-1.3,0.2-2.7-0.1-3.7-0.9
+ c-1-0.8-1.6-2.2-1.4-3.5"/>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M321.9,218.6c-0.5,0-0.5,3.4-3.5,5.8c-2.9,2.4-6.6,2-6.6,2.5c0,0.2,0.8,0.7,2.4,0.7
+ c1.5,0.1,3.7-0.4,5.5-1.9c1.8-1.5,2.6-3.5,2.7-4.9C322.5,219.4,322.2,218.6,321.9,218.6z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M323.7,190.4c0.3,0.9,3.5,0.5,7.2,0.9c3.7,0.4,6.7,1.5,7.2,0.7c0.2-0.4-0.3-1.2-1.5-2
+ c-1.2-0.8-3.1-1.6-5.3-1.9c-2.2-0.2-4.3,0.1-5.6,0.6C324.2,189.3,323.5,189.9,323.7,190.4z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M293.6,181.5c0.6,0.7,2.7,0,5.3-0.1c2.6-0.1,4.8,0.5,5.3-0.3c0.2-0.4-0.1-1.1-1.1-1.8
+ c-1-0.7-2.5-1.2-4.3-1.2c-1.8,0.1-3.4,0.7-4.3,1.4C293.6,180.4,293.3,181.1,293.6,181.5z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st8" d="M365.1,241c-6.3,1.3-6.9-1.9-11.5-6.3c-4.7-4.4-7.1-11.1-6.3-17.4c0.7-5.6,3.7-11.3,1.8-16.6
+ c-2.6-7-12.4-9.2-15-16.2c-1.5-4.1-0.2-8.8-1.9-12.8c-1.6-3.8-5.6-5.9-9.4-7.3c-3.8-1.4-8-2.4-11.2-4.9
+ c-3.2-2.5-5.2-7.2-3.2-10.8c17.1,2,33.9,6.1,50,12.1c3.7,1.4,7.7,3.1,9.9,6.4c2.3,3.5,2.2,8,2,12.3
+ c-1,20.5-1.9,41.1-2.9,61.6"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st16" d="M379.7,233.3c0,0,0.1-0.1,0.3-0.3c0.1-0.2,0.4-0.5,0.6-0.9c0.5-0.8,1.1-2.2,1.5-4
+ c0.4-1.8,0.4-4.2-0.4-6.8c-0.7-2.6-2.5-5.3-5.3-7.2c-2.7-1.9-6.2-3.2-8.5-6.3c-1.1-1.5-1.6-3.4-1.2-5.4c0.3-2,1.2-3.9,2-5.9
+ c1.7-4.1,1.8-9.2-0.3-13.7c-1-2.2-2.6-4.3-4.6-5.8c-2-1.5-4.5-2.3-7-2.9c-2.5-0.6-4.9-0.9-7.2-1.6c-2.3-0.7-4.5-1.7-6.1-3.3
+ c-1.6-1.6-2.3-3.8-2.6-6c-0.3-2.2-0.5-4.5-1.1-6.6c-1.1-4.3-3.3-8.1-6.2-10.8c-2.9-2.7-6.4-4.3-9.7-4.8
+ c-3.3-0.5-6.5,0.1-8.8,1.3c-2.4,1.2-4,2.9-5.1,4.4c-1.1,1.5-1.6,2.9-1.8,3.9c-0.2,1-0.2,1.5-0.2,1.5c0.1,0.1,0.2-2.1,2.3-5.1
+ c1.1-1.4,2.7-3,5-4.2c2.3-1.1,5.3-1.7,8.5-1.1c3.2,0.5,6.5,2.1,9.2,4.7c2.7,2.6,4.8,6.3,5.9,10.5c0.6,2.1,0.7,4.3,1,6.6
+ c0.3,2.3,1.1,4.8,2.9,6.5c1.8,1.8,4.1,2.9,6.5,3.6c2.4,0.7,4.9,1.1,7.3,1.6c2.4,0.5,4.8,1.3,6.6,2.7c1.9,1.4,3.4,3.3,4.3,5.4
+ c1.9,4.2,2,9,0.3,12.9c-0.8,2-1.7,4-2,6.1c-0.4,2.1,0.2,4.4,1.4,6c2.5,3.3,6.1,4.6,8.8,6.4c2.7,1.8,4.4,4.3,5.2,6.7
+ c0.8,2.4,0.8,4.7,0.5,6.5C381.1,231.7,379.6,233.3,379.7,233.3z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M349.9,210.6c-5.2,11.9-6.1,25.6-2.7,38.1c1,3.7,2.5,7.6,1.6,11.4c-1,4.1-4.4,7.1-6.9,10.4
+ c-4.3,5.8-6,13.5-4.5,20.6c1.5,7.1,6.1,13.4,12.4,17c0.7-3.8,4.2-6.3,7.6-8c3.5-1.6,7.3-2.8,9.9-5.6c2.6-2.8,3.4-7.8,0.3-10.1
+ c8.8-2.3,13.8-12.8,11.9-21.7c-1.9-8.9-9.1-15.9-17.3-19.8"/>
+ </g>
+ <g>
+ <path class="st8" d="M359.3,299.4c-1.6,0.9-3.1,1.6-3.3,3.4c-0.2,2.1,1.8,5.8,2.5,7.8c1.4,3.9,0.3,8.5-2.6,11.4
+ c-3,2.9-7.6,3.8-11.5,2.3c2.8,0.2,5.2-2.5,5.5-5.3c0.3-2.8-1.1-5.5-2.8-7.8c-1.7-2.3-3.7-4.3-5.1-6.7
+ c-2.9-5-7.2-18.5-3.9-26.1"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st16" d="M367.3,284.9c0,0,0.2-0.2,0.5-0.5c0.4-0.3,0.9-0.7,1.6-1.3c1.3-1.1,3.2-2.8,5.1-5.3
+ c1.9-2.5,3.9-5.7,4.9-9.7c1-4,1.1-8.7-0.6-13.1c-0.9-2.2-2.1-4.2-3.6-5.8c-1.5-1.7-3.1-3.2-4.6-4.6c-1.5-1.4-2.9-2.8-4.1-4.3
+ c-1.2-1.4-2.1-3-2.7-4.4c-1.3-2.9-1.3-5.6-0.9-7.3c0.2-0.9,0.5-1.5,0.7-1.9c0.2-0.4,0.4-0.6,0.4-0.6c0.1,0-0.5,0.8-0.9,2.5
+ c-0.4,1.7-0.3,4.3,1,7.2c0.6,1.4,1.5,2.9,2.7,4.3c1.2,1.4,2.6,2.8,4.1,4.2c1.5,1.4,3.1,2.9,4.6,4.6c1.5,1.7,2.8,3.7,3.7,6
+ c1.8,4.6,1.7,9.4,0.6,13.4c-1.1,4.1-3.1,7.4-5,9.8c-2,2.5-3.9,4.2-5.3,5.2c-0.7,0.5-1.2,0.9-1.6,1.2
+ C367.6,284.7,367.3,284.9,367.3,284.9z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</g>
+<g id="Plant">
+ <g>
+ <g>
+ <g>
+ <path class="st19" d="M79.9,404.3c2.5,3.2,7,1.8,8.9-0.4c2-2.2,2.7-5.1,3.3-8c1.4-6.5,2.9-13.3,1.1-19.7
+ c-0.5-1.9-1.4-3.8-2.8-5.2c-1.5-1.4-3.6-2.1-5.5-1.5c-2.2,0.7-3.5,3.1-4.4,5.2c-2.6,6.4-3.8,13.4-3.4,20.3
+ C77.4,398.4,78,401.7,79.9,404.3"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st20" d="M79.9,404.3c2.5,3.2,7,1.8,8.9-0.4c2-2.2,2.7-5.1,3.3-8c1.4-6.5,2.9-13.3,1.1-19.7
+ c-0.5-1.9-1.4-3.8-2.8-5.2c-1.5-1.4-3.6-2.1-5.5-1.5c-2.2,0.7-3.5,3.1-4.4,5.2c-2.6,6.4-3.8,13.4-3.4,20.3
+ C77.4,398.4,78,401.7,79.9,404.3"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st9" d="M89.7,420.5c3.7-2.2,8.1-2.7,12.4-2.3c2.3,0.2,4.7,0.8,6.3,2.4c1.6,1.6,2.1,4.6,0.5,6.1
+ c-1,0.9-2.5,1.2-3.8,1.2c-3.3,0.2-7-0.2-9.5,2c-1.4,1.2-2.3,3.2-4.1,3.8c-1.7,0.7-3.8-0.3-4.8-1.8c-1-1.5-1.2-3.5-0.9-5.3
+ C85.8,426.7,86,422.7,89.7,420.5z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st9" d="M73.9,409.4c2.3-3.1,2.5-7.5,1.2-11.2c-1.4-3.7-4.1-6.7-7.2-9.1c-3.6-2.8-7.8-5-12.2-6.2
+ c-1.7-0.5-3.6-0.9-5.3-0.6c-1.8,0.3-3.5,1.3-4.3,3c-1.1,2.4,0.3,5.2,1.7,7.5c2.5,4,5.4,7.8,8.5,11.3c2.5,2.8,5.3,5.6,9,6.7
+ c3.6,1.1,7.3,0.7,9-1.8"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M82.9,454.3c0,0,0.1-0.5,0.1-1.5c0-1.1,0.1-2.5,0.1-4.1c0.1-1.7,0.2-3.8,0.5-6.1c0.3-2.3,0.9-4.7,1.7-7.3
+ c0.8-2.5,1.9-4.8,3.2-6.7c1.3-1.9,2.9-3.3,4.4-4.1c1.5-0.9,2.8-1.3,3.8-1.4c1-0.2,1.5-0.3,1.5-0.3c0,0-0.5,0-1.5,0.1
+ c-1,0.1-2.4,0.5-4,1.3c-1.6,0.8-3.2,2.2-4.6,4.2c-1.4,1.9-2.5,4.3-3.4,6.8c-0.8,2.6-1.4,5.1-1.7,7.4c-0.3,2.3-0.4,4.4-0.4,6.2
+ c0,1.8,0,3.2,0.1,4.1C82.8,453.7,82.9,454.3,82.9,454.3z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M83,451.1c0,0,0-0.2,0-0.7c0-0.5-0.1-1.2-0.1-2c-0.1-1.7-0.3-4.2-0.4-7.3c-0.2-6.2-0.2-14.8,0.2-24.3
+ c0.4-9.5,1.3-18,2.2-24.2c0.2-1.5,0.4-2.9,0.6-4.1c0.2-1.2,0.4-2.3,0.5-3.1c0.1-0.8,0.3-1.5,0.3-2c0.1-0.5,0.1-0.7,0.1-0.7
+ c0,0-0.1,0.2-0.2,0.7c-0.1,0.5-0.3,1.2-0.5,2c-0.2,0.8-0.4,1.9-0.6,3.1c-0.2,1.2-0.5,2.6-0.7,4.1c-1,6.1-1.9,14.7-2.3,24.2
+ c-0.4,9.5-0.4,18.1,0,24.3c0.2,3.1,0.4,5.6,0.6,7.3c0.1,0.8,0.2,1.5,0.2,2C83,450.9,83,451.1,83,451.1z"/>
+ </g>
+ </g>
+ </g>
+ <g>
+ <g>
+ <g>
+ <path class="st8" d="M82,434.6c0,0,0-0.7-0.1-2.1c-0.2-1.3-0.5-3.3-1-5.6c-0.5-2.3-1.3-5.1-2.4-8.1c-1-3-2.4-6.3-4.1-9.5
+ c-3.3-6.6-7.5-11.9-11.1-15.1c-0.8-0.9-1.8-1.5-2.5-2.1c-0.4-0.3-0.7-0.6-1.1-0.8c-0.3-0.2-0.7-0.4-1-0.6
+ c-1.2-0.7-1.8-1.1-1.8-1c-0.1,0.1,2.6,1.5,6.1,4.8c3.5,3.3,7.6,8.5,10.9,15.1c1.7,3.2,3,6.5,4.1,9.4c1.1,3,1.9,5.7,2.5,8
+ c0.6,2.3,0.9,4.2,1.2,5.5C81.8,433.8,82,434.6,82,434.6z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</g>
+<g id="Confetti">
+ <g>
+ <g>
+ <path class="st19" d="M234.6,205.8c-1.8-6.5-10.3-5.9-10.3-5.9l0.2,4.5c4.9-0.8,8,4.9,8,4.9L234.6,205.8z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st19" d="M151,129.7c-4.6,0.5-5.1,6.2-5.1,6.2l3.1,0.4c0-3.4,4.2-4.8,4.2-4.8L151,129.7z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st19" d="M193.7,68.8c2.8,7.1,12.3,5.5,12.3,5.5l-0.8-5.1c-5.4,1.5-9.5-4.6-9.5-4.6L193.7,68.8z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st19" d="M87.1,189.1c-4.6,0.5-5.1,6.2-5.1,6.2l3.1,0.4c0-3.4,4.2-4.8,4.2-4.8L87.1,189.1z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st19" d="M106,91.5c0.7-4.6-4.8-6.5-4.8-6.5l-1.1,2.9c3.3,0.8,3.6,5.2,3.6,5.2L106,91.5z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st19" d="M27.6,161.5c-0.8,7.2,8.7,8.9,8.7,8.9l1.7-4.7c-5.7-0.5-6.5-7.2-6.5-7.2L27.6,161.5z"/>
+ </g>
+ </g>
+ <g>
+ <g>
+ <path class="st19" d="M252.8,127c4.6,0.3,6.1-5.3,6.1-5.3l-3-0.9c-0.5,3.3-4.9,4-4.9,4L252.8,127z"/>
+ </g>
+ </g>
+</g>
+</svg>
diff --git a/public/images/writing.png b/public/images/writing.png
new file mode 100644
index 00000000..17fedd74
--- /dev/null
+++ b/public/images/writing.png
Binary files differ
diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx
index 6ffbb524..ab2e7ce1 100644
--- a/src-migrate/modules/cart/components/Item.tsx
+++ b/src-migrate/modules/cart/components/Item.tsx
@@ -36,26 +36,29 @@ const CartItem = ({ item, editable = true, selfPicking}: Props) => {
)}
<div className='w-2' />
<div>
- Selamat! Pembelian anda lebih hemat {' '}
+ Selamat! Pembelian anda lebih hemat{' '}
<span className={style.savingAmt}>
- Rp{formatCurrency((item.package_price || 0) * item.quantity - item.subtotal)}
+ Rp
+ {formatCurrency(
+ (item.package_price || 0) * item.quantity - item.subtotal
+ )}
</span>
</div>
</div>
)}
<div className={style.mainProdWrapper}>
- {editable && (
- <CartItemSelect item={item} />
- )}
+ {editable && <CartItemSelect item={item} />}
<div className='w-4' />
<CartItem.Image item={item} />
<div className={style.details}>
- {(item.is_in_bu) && (item.on_hand_qty >= item.quantity) && (
+ {item?.available_quantity > 0 && (
<div className='text-[10px] text-red-500 italic'>
- *Barang ini bisa di pickup maksimal pukul 16.00
+ {item.quantity <= item?.available_quantity
+ ? '*Barang ini bisa di pickup maksimal pukul 16.00'
+ : `*${item?.available_quantity} Barang ini bisa di pickup maksimal pukul 16.00`}
</div>
)}
<CartItem.Name item={item} />
diff --git a/src-migrate/modules/page-content/index.tsx b/src-migrate/modules/page-content/index.tsx
index edecb855..54ee0a04 100644
--- a/src-migrate/modules/page-content/index.tsx
+++ b/src-migrate/modules/page-content/index.tsx
@@ -1,4 +1,4 @@
-import { useMemo } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { PageContentProps } from '~/types/pageContent';
import { getPageContent } from '~/services/pageContent';
@@ -8,18 +8,31 @@ type Props = {
};
const PageContent = ({ path }: Props) => {
- const { data, isLoading } = useQuery<PageContentProps>(
- `page-content:${path}`,
- async () => await getPageContent({ path })
- );
+ const [localData, setData] = useState<PageContentProps>();
+ const [shouldFetch, setShouldFetch] = useState(false);
+ const [isLoading, setIsLoading] = useState(false);
+
+ useEffect(() => {
+ const fetchData = async () => {
+ setIsLoading(true);
+ const res = await fetch(`/api/page-content?path=${path}`);
+ const { data } = await res.json();
+ if (data) {
+ setData(data);
+ }
+ setIsLoading(false);
+ };
+
+ fetchData();
+ }, []);
const parsedContent = useMemo<string>(() => {
- if (!data) return '';
- return data.content.replaceAll(
+ if (!localData) return '';
+ return localData.content.replaceAll(
'src="/web/image',
`src="${process.env.NEXT_PUBLIC_ODOO_API_HOST}/web/image`
);
- }, [data]);
+ }, [localData]);
if (isLoading) return <PageContentSkeleton />;
return <div dangerouslySetInnerHTML={{ __html: parsedContent || '' }}></div>;
diff --git a/src-migrate/modules/product-card/components/ProductCard.tsx b/src-migrate/modules/product-card/components/ProductCard.tsx
index 0febfadb..a439cdc8 100644
--- a/src-migrate/modules/product-card/components/ProductCard.tsx
+++ b/src-migrate/modules/product-card/components/ProductCard.tsx
@@ -1,95 +1,108 @@
-import style from '../styles/product-card.module.css'
+import style from '../styles/product-card.module.css';
import ImageNext from 'next/image';
-import clsx from 'clsx'
-import Link from 'next/link'
-import React, { useEffect, useMemo, useState } from 'react'
-import Image from '~/components/ui/image'
-import useUtmSource from '~/hooks/useUtmSource'
-import clsxm from '~/libs/clsxm'
-import formatCurrency from '~/libs/formatCurrency'
-import { formatToShortText } from '~/libs/formatNumber'
-import { createSlug } from '~/libs/slug'
-import { IProduct } from '~/types/product'
-
+import clsx from 'clsx';
+import Link from 'next/link';
+import React, { useEffect, useMemo, useState } from 'react';
+import Image from '~/components/ui/image';
+import useUtmSource from '~/hooks/useUtmSource';
+import clsxm from '~/libs/clsxm';
+import formatCurrency from '~/libs/formatCurrency';
+import { formatToShortText } from '~/libs/formatNumber';
+import { createSlug } from '~/libs/slug';
+import { IProduct } from '~/types/product';
+import useDevice from '@/core/hooks/useDevice';
type Props = {
- product: IProduct
- layout?: 'vertical' | 'horizontal'
-}
+ product: IProduct;
+ layout?: 'vertical' | 'horizontal';
+};
const ProductCard = ({ product, layout = 'vertical' }: Props) => {
- const utmSource = useUtmSource()
-
+ const utmSource = useUtmSource();
+ const { isDesktop, isMobile } = useDevice();
const URL = {
- product: createSlug('/shop/product/', product.name, product.id.toString()) + `?utm_source=${utmSource}`,
- manufacture: createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString()),
- }
+ product:
+ createSlug('/shop/product/', product.name, product.id.toString()) +
+ `?utm_source=${utmSource}`,
+ manufacture: createSlug(
+ '/shop/brands/',
+ product.manufacture.name,
+ product.manufacture.id.toString()
+ ),
+ };
const image = useMemo(() => {
- if (product.image) return product.image + '?ratio=square'
- return '/images/noimage.jpeg'
- }, [product.image])
+ if (!isDesktop && product.image_mobile) {
+ return product.image_mobile + '?ratio=square';
+ } else {
+ if (product.image) return product.image + '?ratio=square';
+ return '/images/noimage.jpeg';
+ }
+ }, [product.image, product.image_mobile]);
return (
- <div className={clsxm(style['wrapper'], {
- [style['wrapper-v']]: layout === 'vertical',
- [style['wrapper-h']]: layout === 'horizontal',
- })}
+ <div
+ className={clsxm(style['wrapper'], {
+ [style['wrapper-v']]: layout === 'vertical',
+ [style['wrapper-h']]: layout === 'horizontal',
+ })}
>
- <div className={clsxm('relative', {
- [style['image-v']]: layout === 'vertical',
- [style['image-h']]: layout === 'horizontal',
- })}>
+ <div
+ className={clsxm('relative', {
+ [style['image-v']]: layout === 'vertical',
+ [style['image-h']]: layout === 'horizontal',
+ })}
+ >
<Link href={URL.product}>
-
- <div className="relative">
- <Image
- src={image}
- alt={product.name}
- width={128}
- 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 className='relative'>
+ <Image
+ src={image}
+ alt={product.name}
+ width={128}
+ 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>
- </div>
{product.variant_total > 1 && (
- <div className={style['variant-badge']}>{product.variant_total} Varian</div>
+ <div className={style['variant-badge']}>
+ {product.variant_total} Varian
+ </div>
)}
</Link>
</div>
- <div className={clsxm({
- [style['content-v']]: layout === 'vertical',
- [style['content-h']]: layout === 'horizontal',
- })}>
- <Link
- href={URL.manufacture}
- className={style['brand']}
- >
+ <div
+ className={clsxm({
+ [style['content-v']]: layout === 'vertical',
+ [style['content-h']]: layout === 'horizontal',
+ })}
+ >
+ <Link href={URL.manufacture} className={style['brand']}>
{product.manufacture.name}
</Link>
@@ -113,17 +126,15 @@ const ProductCard = ({ product, layout = 'vertical' }: Props) => {
<div className='h-1.5' />
<div className={style['price-inc']}>
- Inc PPN:
- Rp {formatCurrency(Math.round(product.lowest_price.price * 1.11))}
+ Inc PPN: Rp{' '}
+ {formatCurrency(Math.round(product.lowest_price.price * 1.11))}
</div>
<div className='h-1' />
<div className='flex items-center gap-x-2.5'>
{product.stock_total > 0 && (
- <div className={style['ready-stock']}>
- Ready Stock
- </div>
+ <div className={style['ready-stock']}>Ready Stock</div>
)}
{product.qty_sold > 0 && (
<div className={style['sold']}>
@@ -131,14 +142,11 @@ const ProductCard = ({ product, layout = 'vertical' }: Props) => {
</div>
)}
</div>
-
</div>
</div>
- )
-}
-
-const classPrefix = ({ layout }: Props) => {
+ );
+};
-}
+const classPrefix = ({ layout }: Props) => {};
-export default ProductCard \ No newline at end of file
+export default ProductCard;
diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx
index a5284637..280e4a7a 100644
--- a/src-migrate/modules/product-detail/components/AddToCart.tsx
+++ b/src-migrate/modules/product-detail/components/AddToCart.tsx
@@ -1,51 +1,55 @@
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
import style from '../styles/price-action.module.css';
-import { Button, Link, useToast } from '@chakra-ui/react'
-import product from 'next-seo/lib/jsonld/product'
-import { useRouter } from 'next/router'
-import { useEffect, useState } from 'react'
-import Image from '~/components/ui/image'
-import { getAuth } from '~/libs/auth'
-import { upsertUserCart } from '~/services/cart'
+import { Button, Link, useToast } from '@chakra-ui/react';
+import product from 'next-seo/lib/jsonld/product';
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import Image from '~/components/ui/image';
+import { getAuth } from '~/libs/auth';
+import { upsertUserCart } from '~/services/cart';
import LazyLoad from 'react-lazy-load';
import ProductSimilar from '../../../../src/lib/product/components/ProductSimilar';
import { IProductDetail } from '~/types/product';
import ImageNext from 'next/image';
-import { useProductCartContext } from '@/contexts/ProductCartContext'
-import { createSlug } from '~/libs/slug'
-import formatCurrency from '~/libs/formatCurrency'
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+import { createSlug } from '~/libs/slug';
+import formatCurrency from '~/libs/formatCurrency';
import { useProductDetail } from '../stores/useProductDetail';
type Props = {
- variantId: number | null,
+ variantId: number | null;
quantity?: number;
source?: 'buy' | 'add_to_cart';
- products : IProductDetail
-}
+ products: IProductDetail;
+};
-type Status = 'idle' | 'loading' | 'success'
+type Status = 'idle' | 'loading' | 'success';
const AddToCart = ({
variantId,
quantity = 1,
source = 'add_to_cart',
- products
+ products,
}: Props) => {
- const auth = getAuth()
- const router = useRouter()
+ let auth = getAuth();
+ const router = useRouter();
const toast = useToast({
position: 'top',
- isClosable: true
- })
+ isClosable: true,
+ });
- const {
- askAdminUrl,
- } = useProductDetail();
+ const { askAdminUrl } = useProductDetail();
const [product, setProducts] = useState(products);
- const [status, setStatus] = useState<Status>('idle')
- const { productCart, setRefreshCart, setProductCart, refreshCart, isLoading, setIsloading } =
- useProductCartContext()
+ const [status, setStatus] = useState<Status>('idle');
+ const {
+ productCart,
+ setRefreshCart,
+ setProductCart,
+ refreshCart,
+ isLoading,
+ setIsloading,
+ } = useProductCartContext();
const productSimilarQuery = [
product?.name,
@@ -55,32 +59,48 @@ const AddToCart = ({
const [addCartAlert, setAddCartAlert] = useState(false);
const handleButton = async () => {
- if (typeof auth !== 'object') {
- const currentUrl = encodeURIComponent(router.asPath)
- router.push(`/login?next=${currentUrl}`)
- return;
+ let isLoggedIn = typeof auth === 'object';
+
+ if (!isLoggedIn) {
+ const currentUrl = encodeURIComponent(router.asPath);
+ await router.push(`/login?next=${currentUrl}`);
+
+ // Tunggu login berhasil, misalnya dengan memantau perubahan status auth.
+ const authCheckInterval = setInterval(() => {
+ const newAuth = getAuth();
+ if (typeof newAuth === 'object') {
+ isLoggedIn = true;
+ auth = newAuth; // Update nilai auth setelah login
+ clearInterval(authCheckInterval);
+ }
+ }, 500); // Periksa status login setiap 500ms
+
+ await new Promise((resolve) => {
+ const checkLogin = setInterval(() => {
+ if (isLoggedIn) {
+ clearInterval(checkLogin);
+ resolve(null);
+ }
+ }, 500);
+ });
}
-
- if (
- !variantId ||
- isNaN(quantity) ||
- typeof auth !== 'object'
- ) return;
- if (status === 'success') return
- setStatus('loading')
+
+ if (!variantId || isNaN(quantity) || typeof auth !== 'object') return;
+ if (status === 'success') return;
+ setStatus('loading');
await upsertUserCart({
userId: auth.id,
- type: 'product',
- id: variantId,
- qty: quantity,
- selected: true,
- source: source,
- qtyAppend: true
- })
- setStatus('idle')
+ type: 'product',
+ id: variantId,
+ qty: quantity,
+ selected: true,
+ source: source,
+ qtyAppend: true,
+ });
+ setStatus('idle');
setRefreshCart(true);
setAddCartAlert(true);
-
+
toast({
title: 'Tambah ke keranjang',
description: 'Berhasil menambahkan barang ke keranjang belanja',
@@ -88,120 +108,130 @@ const AddToCart = ({
duration: 3000,
isClosable: true,
position: 'top',
- })
-
+ });
+
if (source === 'buy') {
- router.push('/shop/checkout?source=buy')
+ router.push('/shop/checkout?source=buy');
}
- }
+ };
useEffect(() => {
- if (status === 'success') setTimeout(() => { setStatus('idle') }, 3000)
- }, [status])
+ if (status === 'success')
+ setTimeout(() => {
+ setStatus('idle');
+ }, 3000);
+ }, [status]);
const btnConfig = {
- 'add_to_cart': {
+ add_to_cart: {
colorScheme: 'yellow',
- text: 'Keranjang'
+ text: 'Keranjang',
},
- 'buy': {
+ buy: {
colorScheme: 'red',
- text: 'Beli'
- }
- }
+ text: 'Beli',
+ },
+ };
return (
<div className='w-full'>
- <Button onClick={handleButton} colorScheme={btnConfig[source].colorScheme} className='w-full'>
+ <Button
+ onClick={handleButton}
+ colorScheme={btnConfig[source].colorScheme}
+ className='w-full'
+ >
{btnConfig[source].text}
</Button>
<BottomPopup
- className='!container'
- title='Berhasil Ditambahkan'
- active={addCartAlert}
- close={() => {
- setAddCartAlert(false);
- }}
- >
- <div className='flex mt-4'>
- <div className='w-[10%]'>
- <ImageNext
- src={product.image}
- alt={product.name}
- className='h-32 object-contain object-center w-full border border-gray_r-4'
- width={80}
- height={80}
- />
- </div>
- <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'>
- {!!product.manufacture.name ? (
- <Link
- href={createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString())}
- className=' hover:underline'
- color={"red"}
- >
- {product.manufacture.name}
- </Link>
- ) : '-'}
- <p className='text-ellipsis overflow-hidden'>
- {product.name}
- </p>
- <p>
- {product.code}
- </p>
- {!!product.lowest_price && product.lowest_price.price > 0 && (
+ className='!container'
+ title='Berhasil Ditambahkan'
+ active={addCartAlert}
+ close={() => {
+ setAddCartAlert(false);
+ }}
+ >
+ <div className='flex mt-4'>
+ <div className='w-[10%]'>
+ <ImageNext
+ src={product.image}
+ alt={product.name}
+ className='h-32 object-contain object-center w-full border border-gray_r-4'
+ width={80}
+ height={80}
+ />
+ </div>
+ <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'>
+ {!!product.manufacture.name ? (
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture.name,
+ product.manufacture.id.toString()
+ )}
+ className=' hover:underline'
+ color={'red'}
+ >
+ {product.manufacture.name}
+ </Link>
+ ) : (
+ '-'
+ )}
+ <p className='text-ellipsis overflow-hidden'>{product.name}</p>
+ <p>{product.code}</p>
+ {!!product.lowest_price && product.lowest_price.price > 0 && (
+ <>
+ <div className='flex items-end gap-x-2'>
+ {product.lowest_price.discount_percentage > 0 && (
<>
- <div className='flex items-end gap-x-2'>
- {product.lowest_price.discount_percentage > 0 && (
- <>
- <div className='badge-solid-red'>
- {Math.floor(product.lowest_price.discount_percentage)}%
- </div>
- <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
- Rp {formatCurrency(product.lowest_price.price || 0)}
- </div>
- </>
- )}
- <div className='text-danger-500 font-semibold'>
- Rp {formatCurrency(product.lowest_price.price_discount || 0)}
- </div>
+ <div className='badge-solid-red'>
+ {Math.floor(product.lowest_price.discount_percentage)}%
+ </div>
+ <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
+ Rp {formatCurrency(product.lowest_price.price || 0)}
</div>
</>
)}
+ <div className='text-danger-500 font-semibold'>
+ Rp{' '}
+ {formatCurrency(product.lowest_price.price_discount || 0)}
+ </div>
+ </div>
+ </>
+ )}
- {!!product.lowest_price && product.lowest_price.price === 0 && (
- <span>
- Hubungi kami untuk dapatkan harga terbaik,{' '}
- <Link
- href={askAdminUrl}
- target='_blank'
- className='font-medium underline'
- color={'red'}
- >
- klik disini
- </Link>
- </span>
- )}
- </div>
- <div className='ml-3 flex items-center font-normal'>
- <Link
- href='/shop/cart'
- className='flex-1 py-2 text-gray_r-12 btn-yellow'
- >
- Lihat Keranjang
- </Link>
- </div>
+ {!!product.lowest_price && product.lowest_price.price === 0 && (
+ <span>
+ Hubungi kami untuk dapatkan harga terbaik,{' '}
+ <Link
+ href={askAdminUrl}
+ target='_blank'
+ className='font-medium underline'
+ color={'red'}
+ >
+ klik disini
+ </Link>
+ </span>
+ )}
+ </div>
+ <div className='ml-3 flex items-center font-normal'>
+ <Link
+ href='/shop/cart'
+ className='flex-1 py-2 text-gray_r-12 btn-yellow'
+ >
+ Lihat Keranjang
+ </Link>
</div>
- <div className='mt-8 mb-4'>
- <div className='text-h-sm font-semibold mb-6'>
- Kamu Mungkin Juga Suka
- </div>
- <LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
- </LazyLoad>
+ </div>
+ <div className='mt-8 mb-4'>
+ <div className='text-h-sm font-semibold mb-6'>
+ Kamu Mungkin Juga Suka
</div>
- </BottomPopup>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+ </BottomPopup>
</div>
- )
-}
+ );
+};
-export default AddToCart \ No newline at end of file
+export default AddToCart;
diff --git a/src-migrate/modules/product-detail/components/AddToQuotation.tsx b/src-migrate/modules/product-detail/components/AddToQuotation.tsx
new file mode 100644
index 00000000..f9b6c2b3
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/AddToQuotation.tsx
@@ -0,0 +1,227 @@
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import style from '../styles/price-action.module.css';
+import { Button, Link, useToast } from '@chakra-ui/react';
+import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
+import product from 'next-seo/lib/jsonld/product';
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import Image from '~/components/ui/image';
+import { getAuth } from '~/libs/auth';
+import { upsertUserCart } from '~/services/cart';
+import LazyLoad from 'react-lazy-load';
+import ProductSimilar from '../../../../src/lib/product/components/ProductSimilar';
+import { IProductDetail } from '~/types/product';
+import ImageNext from 'next/image';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+import { createSlug } from '~/libs/slug';
+import formatCurrency from '~/libs/formatCurrency';
+import { useProductDetail } from '../stores/useProductDetail';
+
+type Props = {
+ variantId: number | null;
+ quantity?: number;
+ source?: 'buy' | 'add_to_cart';
+ products: IProductDetail;
+};
+
+type Status = 'idle' | 'loading' | 'success';
+
+const AddToQuotation = ({
+ variantId,
+ quantity = 1,
+ source = 'add_to_cart',
+ products,
+}: Props) => {
+ const auth = getAuth();
+ const router = useRouter();
+ const toast = useToast({
+ position: 'top',
+ isClosable: true,
+ });
+
+ const { askAdminUrl } = useProductDetail();
+
+ const [product, setProducts] = useState(products);
+ const [status, setStatus] = useState<Status>('idle');
+ const {
+ productCart,
+ setRefreshCart,
+ setProductCart,
+ refreshCart,
+ isLoading,
+ setIsloading,
+ } = useProductCartContext();
+
+ const productSimilarQuery = [
+ product?.name,
+ `fq=-product_id_i:${product.id}`,
+ `fq=-manufacture_id_i:${product.manufacture?.id || 0}`,
+ ].join('&');
+ const [addCartAlert, setAddCartAlert] = useState(false);
+
+ const handleButton = async () => {
+ if (typeof auth !== 'object') {
+ const currentUrl = encodeURIComponent(router.asPath);
+ router.push(`/login?next=${currentUrl}`);
+ return;
+ }
+
+ if (!variantId || isNaN(quantity) || typeof auth !== 'object') return;
+ if (status === 'success') return;
+ setStatus('loading');
+ await upsertUserCart({
+ userId: auth.id,
+ type: 'product',
+ id: variantId,
+ qty: quantity,
+ selected: true,
+ source: source,
+ qtyAppend: true,
+ });
+ setStatus('idle');
+ setRefreshCart(true);
+ setAddCartAlert(true);
+
+ toast({
+ title: 'Tambah ke keranjang',
+ description: 'Berhasil menambahkan barang ke keranjang belanja',
+ status: 'success',
+ duration: 3000,
+ isClosable: true,
+ position: 'top',
+ });
+
+ if (source === 'buy') {
+ router.push('/shop/quotation?source=buy');
+ }
+ };
+ useEffect(() => {
+ if (status === 'success')
+ setTimeout(() => {
+ setStatus('idle');
+ }, 3000);
+ }, [status]);
+
+ const btnConfig = {
+ add_to_cart: {
+ colorScheme: 'yellow',
+
+ text: 'Keranjang',
+ },
+ buy: {
+ colorScheme: 'red',
+ text: 'Beli',
+ },
+ };
+
+ return (
+ <div className='w-full'>
+ <Button
+ onClick={handleButton}
+ color={'red'}
+ colorScheme='white'
+ className='w-full border-2 p-2 gap-1 hover:bg-slate-100 flex items-center'
+ >
+ <ImageNext
+ src='/images/writing.png'
+ alt='penawaran instan'
+ className=''
+ width={25}
+ height={25}
+ />
+ Penawaran Harga Instan
+ </Button>
+ <BottomPopup
+ className='!container'
+ title='Berhasil Ditambahkan'
+ active={addCartAlert}
+ close={() => {
+ setAddCartAlert(false);
+ }}
+ >
+ <div className='flex mt-4'>
+ <div className='w-[10%]'>
+ <ImageNext
+ src={product.image}
+ alt={product.name}
+ className='h-32 object-contain object-center w-full border border-gray_r-4'
+ width={80}
+ height={80}
+ />
+ </div>
+ <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'>
+ {!!product.manufacture.name ? (
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture.name,
+ product.manufacture.id.toString()
+ )}
+ className=' hover:underline'
+ color={'red'}
+ >
+ {product.manufacture.name}
+ </Link>
+ ) : (
+ '-'
+ )}
+ <p className='text-ellipsis overflow-hidden'>{product.name}</p>
+ <p>{product.code}</p>
+ {!!product.lowest_price && product.lowest_price.price > 0 && (
+ <>
+ <div className='flex items-end gap-x-2'>
+ {product.lowest_price.discount_percentage > 0 && (
+ <>
+ <div className='badge-solid-red'>
+ {Math.floor(product.lowest_price.discount_percentage)}%
+ </div>
+ <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
+ Rp {formatCurrency(product.lowest_price.price || 0)}
+ </div>
+ </>
+ )}
+ <div className='text-danger-500 font-semibold'>
+ Rp{' '}
+ {formatCurrency(product.lowest_price.price_discount || 0)}
+ </div>
+ </div>
+ </>
+ )}
+
+ {!!product.lowest_price && product.lowest_price.price === 0 && (
+ <span>
+ Hubungi kami untuk dapatkan harga terbaik,{' '}
+ <Link
+ href={askAdminUrl}
+ target='_blank'
+ className='font-medium underline'
+ color={'red'}
+ >
+ klik disini
+ </Link>
+ </span>
+ )}
+ </div>
+ <div className='ml-3 flex items-center font-normal'>
+ <Link
+ href='/shop/cart'
+ className='flex-1 py-2 text-gray_r-12 btn-yellow'
+ >
+ Lihat Keranjang
+ </Link>
+ </div>
+ </div>
+ <div className='mt-8 mb-4'>
+ <div className='text-h-sm font-semibold mb-6'>
+ Kamu Mungkin Juga Suka
+ </div>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+ </BottomPopup>
+ </div>
+ );
+};
+
+export default AddToQuotation;
diff --git a/src-migrate/modules/product-detail/components/Image.tsx b/src-migrate/modules/product-detail/components/Image.tsx
index 30ca0d34..96ae2027 100644
--- a/src-migrate/modules/product-detail/components/Image.tsx
+++ b/src-migrate/modules/product-detail/components/Image.tsx
@@ -1,22 +1,22 @@
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'
+import React, { useEffect, useMemo, useState } from 'react';
+import { InfoIcon } from 'lucide-react';
+import { Tooltip } from '@chakra-ui/react';
-import { IProductDetail } from '~/types/product'
-import ImageUI from '~/components/ui/image'
+import { IProductDetail } from '~/types/product';
+import ImageUI from '~/components/ui/image';
import moment from 'moment';
-
+import useDevice from '@/core/hooks/useDevice';
type Props = {
- product: IProductDetail
-}
+ product: IProductDetail;
+};
const Image = ({ product }: Props) => {
- const flashSale = product.flash_sale
+ const flashSale = product.flash_sale;
const [count, setCount] = useState(flashSale?.remaining_time || 0);
-
+ const { isDesktop, isMobile } = useDevice();
useEffect(() => {
let interval: NodeJS.Timeout;
@@ -34,59 +34,60 @@ const Image = ({ product }: Props) => {
};
}, [flashSale?.remaining_time]);
- const duration = moment.duration(count, 'seconds')
-
+ const duration = moment.duration(count, 'seconds');
const image = useMemo(() => {
- if (product.image) return product.image + '?ratio=square'
- return '/images/noimage.jpeg'
- }, [product.image])
+ if (!isDesktop && product.image_mobile) {
+ return product.image_mobile + '?ratio=square';
+ } else {
+ if (product.image) return product.image + '?ratio=square';
+ return '/images/noimage.jpeg';
+ }
+ }, [product.image, product.image_mobile]);
return (
<div className={style['wrapper']}>
{/* <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> */}
-
-
+ <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
placement='bottom-end'
label='Gambar atau foto berperan sebagai ilustrasi produk. Kadang tidak sesuai dengan kondisi terbaru dengan berbagai perubahan dan perbaikan. Hubungi admin kami untuk informasi yang lebih baik perihal gambar.'
>
- <div className="text-gray-600">
+ <div className='text-gray-600'>
<InfoIcon size={20} />
</div>
</Tooltip>
@@ -94,7 +95,7 @@ const Image = ({ product }: Props) => {
{flashSale.remaining_time > 0 && (
<div className='absolute bottom-0 w-full h-14'>
- <div className="relative w-full h-full">
+ <div className='relative w-full h-full'>
<ImageUI
src='/images/BG-FLASH-SALE.jpg'
alt='Flash Sale Indoteknik'
@@ -105,7 +106,9 @@ const Image = ({ product }: Props) => {
<div className={style['flashsale']}>
<div className='flex items-center gap-x-3'>
- <div className={style['disc-badge']}>{Math.floor(product.lowest_price.discount_percentage)}%</div>
+ <div className={style['disc-badge']}>
+ {Math.floor(product.lowest_price.discount_percentage)}%
+ </div>
<div className={style['flashsale-text']}>
<ImageUI
src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg'
@@ -122,12 +125,11 @@ const Image = ({ product }: Props) => {
<span>{duration.seconds().toString().padStart(2, '0')}</span>
</div>
</div>
-
</div>
</div>
)}
</div>
- )
-}
+ );
+};
-export default Image \ No newline at end of file
+export default Image;
diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx
index 75ae3c41..5e1ea186 100644
--- a/src-migrate/modules/product-detail/components/Information.tsx
+++ b/src-migrate/modules/product-detail/components/Information.tsx
@@ -1,56 +1,232 @@
-import style from '../styles/information.module.css'
+import {
+ AutoComplete,
+ AutoCompleteInput,
+ AutoCompleteItem,
+ AutoCompleteList,
+} from '@choc-ui/chakra-autocomplete';
+import style from '../styles/information.module.css';
-import React from 'react'
-import dynamic from 'next/dynamic'
-import Link from 'next/link'
-import { useQuery } from 'react-query'
+import dynamic from 'next/dynamic';
+import Link from 'next/link';
+import { useEffect, useRef, useState } from 'react';
-import { IProductDetail } from '~/types/product'
-import { IProductVariantSLA } from '~/types/productVariant'
-import { createSlug } from '~/libs/slug'
-import { getVariantSLA } from '~/services/productVariant'
-import { formatToShortText } from '~/libs/formatNumber'
+import currencyFormat from '@/core/utils/currencyFormat';
+import { InputGroup, InputRightElement } from '@chakra-ui/react';
+import { ChevronDownIcon } from '@heroicons/react/24/outline';
+import Image from 'next/image';
+import { formatToShortText } from '~/libs/formatNumber';
+import { createSlug } from '~/libs/slug';
+import { getVariantSLA } from '~/services/productVariant';
+import { IProductDetail } from '~/types/product';
+import { useProductDetail } from '../stores/useProductDetail';
-const Skeleton = dynamic(() => import('@chakra-ui/react').then((mod) => mod.Skeleton))
+const Skeleton = dynamic(() =>
+ import('@chakra-ui/react').then((mod) => mod.Skeleton)
+);
type Props = {
- product: IProductDetail
-}
+ product: IProductDetail;
+};
const Information = ({ product }: Props) => {
- const querySLA = useQuery<IProductVariantSLA>({
- queryKey: ['variant-sla', product.variants[0]?.id],
- queryFn: () => getVariantSLA(product.variants[0].id),
- enabled: product.variant_total === 1
- })
+ const { selectedVariant, setSelectedVariant, setSla, setActive, sla } =
+ useProductDetail();
- const sla = querySLA?.data
+ const [inputValue, setInputValue] = useState<string | null>(
+ selectedVariant?.code + ' - ' + selectedVariant?.attributes[0]
+ );
+ const [disableFilter, setDisableFilter] = useState<boolean>(false);
+ const inputRef = useRef<HTMLInputElement>(null);
+
+ const [variantOptions, setVariantOptions] = useState<any[]>(
+ product?.variants
+ );
+ // let variantOptions = product?.variants;
+
+ // const querySLA = useQuery<IProductVariantSLA>({
+ // queryKey: ['variant-sla', selectedVariant?.id],
+ // queryFn: () => getVariantSLA(selectedVariant?.id),
+ // enabled: !!selectedVariant?.id,
+ // });
+ // const sla = querySLA?.data;
+
+ const getsla = async () => {
+ const querySLA = await getVariantSLA(selectedVariant?.id);
+ setSla(querySLA);
+ };
+
+ useEffect(() => {
+ if (selectedVariant) {
+ getsla();
+ setInputValue(
+ selectedVariant?.code +
+ (selectedVariant?.attributes[0]
+ ? ' - ' + selectedVariant?.attributes[0]
+ : '')
+ );
+ }
+ }, [selectedVariant]);
+
+ const handleOnChange = (vals: any) => {
+ setDisableFilter(true);
+ let code = vals.replace(/\s-\s.*$/, '').trim();
+ let variant = variantOptions.find((item) => item.code === code);
+ setSelectedVariant(variant);
+ setInputValue(
+ variant?.code +
+ (variant?.attributes[0] ? ' - ' + variant?.attributes[0] : '')
+ );
+ if (variant) {
+ const filteredOptions = product?.variants.filter(
+ (item) => item !== variant
+ );
+ const newOptions = [variant, ...filteredOptions];
+ setVariantOptions(newOptions);
+ }
+ };
+
+ const handleOnKeyUp = (e: any) => {
+ setDisableFilter(false);
+ setInputValue(e.target.value);
+ };
return (
<div className={style['wrapper']}>
+ <div className='realtive mb-5'>
+ <label className='form-label mb-2 text-lg text-red-600'>
+ Pilih Variant * :{' '}
+ <span className='text-gray_r-9 text-sm'>
+ {product?.variants?.length} Variants
+ </span>{' '}
+ </label>
+ <AutoComplete
+ disableFilter={disableFilter}
+ openOnFocus
+ className='form-input'
+ onChange={(vals) => handleOnChange(vals)}
+ >
+ <InputGroup>
+ <AutoCompleteInput
+ ref={inputRef}
+ value={inputValue as string}
+ onChange={(e) => handleOnKeyUp(e)}
+ onFocus={() => setDisableFilter(true)}
+ />
+ <InputRightElement className='mr-4'>
+ <ChevronDownIcon
+ className='h-6 w-6 text-gray-500'
+ onClick={() => inputRef?.current?.focus()}
+ />
+ </InputRightElement>
+ </InputGroup>
+
+ <AutoCompleteList>
+ {variantOptions.map((option, cid) => (
+ <AutoCompleteItem
+ key={`option-${cid}`}
+ value={
+ option.code +
+ (option?.attributes[0] ? ' - ' + option?.attributes[0] : '')
+ }
+ _selected={
+ option.id === selectedVariant?.id
+ ? {
+ bg: 'gray.300',
+ }
+ : undefined
+ }
+ textTransform='capitalize'
+ >
+ <div
+ key={cid}
+ className='flex gap-x-2 w-full justify-between px-3 items-center p-2'
+ >
+ <div className='text-small'>
+ {option.code +
+ (option?.attributes[0]
+ ? ' - ' + option?.attributes[0]
+ : '')}
+ </div>
+ <div
+ className={
+ option?.price?.discount_percentage
+ ? 'flex gap-x-4 items-center justify-between'
+ : ''
+ }
+ >
+ {option?.price?.discount_percentage > 0 && (
+ <>
+ <div className='badge-solid-red text-xs'>
+ {Math.floor(option?.price?.discount_percentage)}%
+ </div>
+ <div className='min-w-16 sm:min-w-24 text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
+ {currencyFormat(option?.price?.price)}
+ </div>
+ </>
+ )}
+ <div className='min-w-20 sm:min-w-28 text-danger-500 font-semibold'>
+ {currencyFormat(option?.price?.price_discount)}
+ </div>
+ </div>
+ </div>
+ </AutoCompleteItem>
+ ))}
+ </AutoCompleteList>
+ </AutoComplete>
+ </div>
+
<div className={style['row']}>
- <div className={style['label']}>SKU Number</div>
- <div className={style['value']}>SKU-{product.id}</div>
+ <div className={style['label']}>Item Code</div>
+ <div className={style['value']}>{selectedVariant?.code}</div>
</div>
<div className={style['row']}>
<div className={style['label']}>Manufacture</div>
<div className={style['value']}>
{!!product.manufacture.name ? (
<Link
- href={createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString())}
- className='text-danger-500 hover:underline'
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture.name,
+ product.manufacture.id.toString()
+ )}
>
- {product.manufacture.name}
+ {product?.manufacture.logo ? (
+ <Image
+ height={50}
+ width={100}
+ src={product.manufacture.logo}
+ alt={product.manufacture.name}
+ className='h-8 object-fit'
+ />
+ ) : (
+ <p className='font-bold text-red-500'>
+ {product.manufacture.name}
+ </p>
+ )}
</Link>
- ) : '-'}
+ ) : (
+ '-'
+ )}
+ </div>
+ </div>
+ <div className={style['row']}>
+ <div className={style['label']}>Berat Barang</div>
+ <div className={style['value']}>
+ {selectedVariant?.weight > 0 ? `${selectedVariant?.weight} Kg` : '-'}
</div>
</div>
<div className={style['row']}>
<div className={style['label']}>Terjual</div>
- <div className={style['value']}>{product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'}</div>
+ <div className={style['value']}>
+ {product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'}
+ </div>
+ </div>
+ <div className={style['row']}>
+ <div className={style['label']}>Persiapan Barang</div>
+ <div className={style['value']}>{sla?.sla_date}</div>
</div>
</div>
- )
-}
+ );
+};
-export default Information \ No newline at end of file
+export default Information;
diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx
index 9021264e..0b27b1b3 100644
--- a/src-migrate/modules/product-detail/components/PriceAction.tsx
+++ b/src-migrate/modules/product-detail/components/PriceAction.tsx
@@ -1,12 +1,17 @@
import style from '../styles/price-action.module.css';
-import React, { useEffect } from 'react';
+import Image from 'next/image';
+import Link from 'next/link';
+import { useEffect, useState } from 'react';
import formatCurrency from '~/libs/formatCurrency';
import { IProductDetail } from '~/types/product';
import { useProductDetail } from '../stores/useProductDetail';
import AddToCart from './AddToCart';
-import Link from 'next/link';
+import AddToQuotation from './AddToQuotation';
import { getAuth } from '~/libs/auth';
+import useDevice from '@/core/hooks/useDevice';
+import odooApi from '~/libs/odooApi';
+import { Button, Skeleton } from '@chakra-ui/react';
type Props = {
product: IProductDetail;
@@ -22,27 +27,58 @@ const PriceAction = ({ product }: Props) => {
askAdminUrl,
isApproval,
setIsApproval,
+ selectedVariant,
+ sla,
} = useProductDetail();
-
+ const [qtyPickUp, setQtyPickUp] = useState(0);
+ const { isDesktop, isMobile } = useDevice();
useEffect(() => {
- setActive(product.variants[0])
- if(product.variants.length > 2 && product.variants[0].price.price === 0){
- const variants = product.variants
+ setActive(selectedVariant);
+ 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])
+ if (variants[i].price.price > 0) {
+ setActive(variants[i]);
break;
}
}
}
-
- }, [product, setActive]);
+ }, [product, setActive, selectedVariant]);
+ useEffect(() => {
+ const fetchData = async () => {
+ const qty_available = await odooApi(
+ 'GET',
+ `/api/v1/product_variant/${selectedVariant.id}/qty_available`
+ );
+
+ setQtyPickUp(qty_available?.qty);
+ };
+ fetchData();
+ }, [selectedVariant]);
+ useEffect(() => {
+ setQuantityInput('1');
+ }, [selectedVariant]);
+
+ let voucherPastiHemat = 0;
+
+ if (
+ product?.voucher_pasti_hemat
+ ? product?.voucher_pasti_hemat.length
+ : voucherPastiHemat > 0
+ ) {
+ const stringVoucher = product?.voucher_pasti_hemat[0];
+ const validJsonString = stringVoucher.replace(/'/g, '"');
+ voucherPastiHemat = JSON.parse(validJsonString);
+ }
return (
<div
- className='block md:sticky top-[150px] bg-white py-0 md:py-6 z-10'
+ className={`block md:sticky md:top-[150px] md:py-6 fixed bottom-0 left-0 right-0 bg-white p-2 z-10 ${
+ isMobile &&
+ 'pb-6 pt-6 rounded-lg shadow-[rgba(0,0,4,0.1)_0px_-4px_4px_0px] '
+ }`}
id='price-section'
>
{!!activePrice && activePrice.price > 0 && (
@@ -84,18 +120,69 @@ const PriceAction = ({ product }: Props) => {
)}
<div className='h-4' />
+ <div className='flex gap-x-5 items-center'>
+ <div className='relative flex items-center'>
+ <button
+ type='button'
+ className='absolute left-0 px-2 py-1 h-full text-gray-500'
+ onClick={() =>
+ setQuantityInput(String(Math.max(1, Number(quantityInput) - 1)))
+ }
+ >
+ -
+ </button>
+ <input
+ type='number'
+ id='quantity'
+ min={1}
+ value={quantityInput}
+ onChange={(e) => setQuantityInput(e.target.value)}
+ className={style['quantity-input']}
+ />
+ <button
+ type='button'
+ className='absolute right-0 px-2 py-1 h-full text-gray-500'
+ onClick={() => setQuantityInput(String(Number(quantityInput) + 1))}
+ >
+ +
+ </button>
+ </div>
+
+ <div>
+ <Skeleton
+ isLoaded={sla}
+ h='21px'
+ // w={16}
+ className={sla?.qty < 10 ? 'text-red-600 font-medium' : ''}
+ >
+ Stock : {sla?.qty}{' '}
+ </Skeleton>
+ {/* <span className={sla?.qty < 10 ? 'text-red-600 font-medium' : ''}>
+ {' '}
+ </span> */}
+ </div>
+ <div>
+ {selectedVariant?.is_in_bu && (
+ <Link href='/panduan-pick-up-service' className='group'>
+ <Image
+ src='/images/PICKUP-NOW.png'
+ className='group-hover:scale-105 transition-transform duration-200'
+ alt='pickup now'
+ width={100}
+ height={12}
+ />
+ </Link>
+ )}
+ </div>
+ </div>
+ {qtyPickUp > 0 && (
+ <div className='text-[12px] mt-1 text-red-500 italic'>
+ * {qtyPickUp} barang bisa di pickup
+ </div>
+ )}
+ <div className='h-4' />
- <div className={style['action-wrapper']}>
- <label htmlFor='quantity' className='hidden'>
- Quantity
- </label>
- <input
- type='number'
- id='quantity'
- value={quantityInput}
- onChange={(e) => setQuantityInput(e.target.value)}
- className={style['quantity-input']}
- />
+ <div className={`${style['action-wrapper']}`}>
<AddToCart
products={product}
variantId={activeVariantId}
@@ -110,6 +197,14 @@ const PriceAction = ({ product }: Props) => {
/>
)}
</div>
+ <div className='mt-4'>
+ <AddToQuotation
+ source='buy'
+ products={product}
+ variantId={activeVariantId}
+ quantity={Number(quantityInput)}
+ />
+ </div>
</div>
);
};
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx
index e4555913..b036cc2d 100644
--- a/src-migrate/modules/product-detail/components/ProductDetail.tsx
+++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx
@@ -1,46 +1,52 @@
-import style from '../styles/product-detail.module.css'
-
-import Link from 'next/link'
-import { useRouter } from 'next/router'
-import { useEffect } from 'react'
-
-import { Button } from '@chakra-ui/react'
-import { MessageCircleIcon, Share2Icon } from 'lucide-react'
-import { LazyLoadComponent } from 'react-lazy-load-image-component'
-import { RWebShare } from 'react-web-share'
-
-import useDevice from '@/core/hooks/useDevice'
-import { whatsappUrl } from '~/libs/whatsappUrl'
-import ProductPromoSection from '~/modules/product-promo/components/Section'
-import { IProductDetail } from '~/types/product'
-import { useProductDetail } from '../stores/useProductDetail'
-import AddToWishlist from './AddToWishlist'
-import Breadcrumb from './Breadcrumb'
-import ProductImage from './Image'
-import Information from './Information'
-import PriceAction from './PriceAction'
-import SimilarBottom from './SimilarBottom'
-import SimilarSide from './SimilarSide'
-import VariantList from './VariantList'
-import { getAuth } from '~/libs/auth'
-
-import { gtagProductDetail } from '@/core/utils/googleTag'
+import style from '../styles/product-detail.module.css';
+
+import Link from 'next/link';
+import { useRouter } from 'next/router';
+import { useEffect } from 'react';
+
+import { Button } from '@chakra-ui/react';
+import { MessageCircleIcon, Share2Icon } from 'lucide-react';
+import { LazyLoadComponent } from 'react-lazy-load-image-component';
+import { RWebShare } from 'react-web-share';
+
+import useDevice from '@/core/hooks/useDevice';
+import { getAuth } from '~/libs/auth';
+import { whatsappUrl } from '~/libs/whatsappUrl';
+import ProductPromoSection from '~/modules/product-promo/components/Section';
+import { IProductDetail } from '~/types/product';
+import { useProductDetail } from '../stores/useProductDetail';
+import AddToWishlist from './AddToWishlist';
+import Breadcrumb from './Breadcrumb';
+import ProductImage from './Image';
+import Information from './Information';
+import PriceAction from './PriceAction';
+import SimilarBottom from './SimilarBottom';
+import SimilarSide from './SimilarSide';
+
+import { gtagProductDetail } from '@/core/utils/googleTag';
type Props = {
- product: IProductDetail
-}
+ product: IProductDetail;
+};
-const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST
+const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST;
const ProductDetail = ({ product }: Props) => {
- const { isDesktop, isMobile } = useDevice()
- const router = useRouter()
- const auth = getAuth()
- const { setAskAdminUrl, askAdminUrl, activeVariantId, setIsApproval, isApproval } = useProductDetail()
+ const { isDesktop, isMobile } = useDevice();
+ const router = useRouter();
+ const auth = getAuth();
+ const {
+ setAskAdminUrl,
+ askAdminUrl,
+ activeVariantId,
+ setIsApproval,
+ isApproval,
+ setSelectedVariant,
+ } = useProductDetail();
useEffect(() => {
gtagProductDetail(product);
- },[product])
+ }, [product]);
useEffect(() => {
const createdAskUrl = whatsappUrl({
@@ -48,76 +54,43 @@ const ProductDetail = ({ product }: Props) => {
payload: {
manufacture: product.manufacture.name,
productName: product.name,
- url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath
+ url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath,
},
- fallbackUrl: router.asPath
- })
+ fallbackUrl: router.asPath,
+ });
- setAskAdminUrl(createdAskUrl)
- }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl])
+ setAskAdminUrl(createdAskUrl);
+ }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]);
useEffect(() => {
if (typeof auth === 'object') {
setIsApproval(auth?.feature?.soApproval);
}
+ setSelectedVariant(product?.variants[0])
}, []);
return (
<>
<div className='md:flex md:flex-wrap'>
- <div className="w-full mb-4 md:mb-0 px-4 md:px-0">
+ <div className='w-full mb-4 md:mb-0 px-4 md:px-0'>
<Breadcrumb id={product.id} name={product.name} />
</div>
<div className='md:w-9/12 md:flex md:flex-col md:pr-4 md:pt-6'>
<div className='md:flex md:flex-wrap'>
- <div className="md:w-4/12">
+ <div className='md:w-4/12'>
<ProductImage product={product} />
</div>
<div className='md:w-8/12 px-4 md:pl-6'>
<div className='h-6 md:h-0' />
- <h1 className={style['title']}>
- {product.name}
- </h1>
+ <h1 className={style['title']}>{product.name}</h1>
- <div className='h-6 md:h-8' />
+ <div className='h-3 md:h-0' />
<Information product={product} />
<div className='h-6' />
-
- <div className="flex gap-x-5">
- <Button
- as={Link}
- href={askAdminUrl}
- variant='link'
- target='_blank'
- colorScheme='gray'
- leftIcon={<MessageCircleIcon size={18} />}
- >
- Ask Admin
- </Button>
-
- <AddToWishlist productId={product.id} />
-
- <RWebShare
- data={{
- text: 'Check out this product',
- title: `${product.name} - Indoteknik.com`,
- url: SELF_HOST + router.asPath
- }}
- >
- <Button
- variant='link'
- colorScheme='gray'
- leftIcon={<Share2Icon size={18} />}
- >
- Share
- </Button>
- </RWebShare>
- </div>
-
</div>
</div>
@@ -131,38 +104,72 @@ const ProductDetail = ({ product }: Props) => {
<div className='h-4 md:h-10' />
{!!activeVariantId && !isApproval && <ProductPromoSection product={product} productId={activeVariantId} />}
- <div className={style['section-card']}>
+ {/* <div className={style['section-card']}>
<h2 className={style['heading']}>
Variant ({product.variant_total})
</h2>
<div className='h-4' />
<VariantList variants={product.variants} />
- </div>
+ </div> */}
<div className='h-0 md:h-6' />
<div className={style['section-card']}>
- <h2 className={style['heading']}>
- Informasi Produk
- </h2>
+ <h2 className={style['heading']}>Informasi Produk</h2>
<div className='h-4' />
<div
className={style['description']}
- dangerouslySetInnerHTML={{ __html: !product.description || product.description == '<p><br></p>' ? 'Belum ada deskripsi' : product.description }}
+ dangerouslySetInnerHTML={{
+ __html:
+ !product.description || product.description == '<p><br></p>'
+ ? 'Belum ada deskripsi'
+ : product.description,
+ }}
/>
</div>
</div>
</div>
{isDesktop && (
- <div className="md:w-3/12">
+ <div className='md:w-3/12'>
<PriceAction product={product} />
+ <div className='flex gap-x-5 items-center justify-center'>
+ <Button
+ as={Link}
+ href={askAdminUrl}
+ variant='link'
+ target='_blank'
+ colorScheme='gray'
+ leftIcon={<MessageCircleIcon size={18} />}
+ >
+ Ask Admin
+ </Button>
+
+ <span>|</span>
+
+ <AddToWishlist productId={product.id} />
+
+ <span>|</span>
+
+ <RWebShare
+ data={{
+ text: 'Check out this product',
+ title: `${product.name} - Indoteknik.com`,
+ url: SELF_HOST + router.asPath,
+ }}
+ >
+ <Button
+ variant='link'
+ colorScheme='gray'
+ leftIcon={<Share2Icon size={18} />}
+ >
+ Share
+ </Button>
+ </RWebShare>
+ </div>
<div className='h-6' />
-
- <div className={style['heading']}>
- Produk Serupa
- </div>
+ <div className={style['heading']}>Produk Serupa</div>
<div className='h-4' />
@@ -171,9 +178,7 @@ const ProductDetail = ({ product }: Props) => {
)}
<div className='md:w-full pt-4 md:py-10 px-4 md:px-0'>
- <div className={style['heading']}>
- Kamu Mungkin Juga Suka
- </div>
+ <div className={style['heading']}>Kamu Mungkin Juga Suka</div>
<div className='h-6' />
@@ -185,7 +190,7 @@ const ProductDetail = ({ product }: Props) => {
<div className='h-6 md:h-0' />
</div>
</>
- )
-}
+ );
+};
-export default ProductDetail \ No newline at end of file
+export default ProductDetail;
diff --git a/src-migrate/modules/product-detail/stores/useProductDetail.ts b/src-migrate/modules/product-detail/stores/useProductDetail.ts
index eb409930..dee6b342 100644
--- a/src-migrate/modules/product-detail/stores/useProductDetail.ts
+++ b/src-migrate/modules/product-detail/stores/useProductDetail.ts
@@ -7,6 +7,8 @@ type State = {
quantityInput: string;
askAdminUrl: string;
isApproval : boolean;
+ selectedVariant : any;
+ sla : any;
};
type Action = {
@@ -14,6 +16,8 @@ type Action = {
setQuantityInput: (value: string) => void;
setAskAdminUrl: (url: string) => void;
setIsApproval : (value : boolean) => void;
+ setSelectedVariant : (value : any) => void;
+ setSla : (value : any) => void;
};
export const useProductDetail = create<State & Action>((set, get) => ({
@@ -22,6 +26,8 @@ export const useProductDetail = create<State & Action>((set, get) => ({
quantityInput: '1',
askAdminUrl: '',
isApproval : false,
+ selectedVariant: null,
+ sla : null,
setActive: (variant) => {
set({ activeVariantId: variant?.id, activePrice: variant?.price });
},
@@ -33,5 +39,11 @@ export const useProductDetail = create<State & Action>((set, get) => ({
},
setIsApproval : (value : boolean) => {
set({ isApproval : value })
+ },
+ setSelectedVariant : (value : any) => {
+ set({ selectedVariant : value })
+ },
+ setSla : (value : any ) => {
+ set({ sla : value })
}
}));
diff --git a/src-migrate/modules/product-detail/styles/information.module.css b/src-migrate/modules/product-detail/styles/information.module.css
index c9b29020..5aa64fe5 100644
--- a/src-migrate/modules/product-detail/styles/information.module.css
+++ b/src-migrate/modules/product-detail/styles/information.module.css
@@ -3,11 +3,11 @@
}
.row {
- @apply flex p-3 rounded;
+ @apply flex p-4 rounded-sm bg-gray-100;
}
.row:nth-child(odd) {
- @apply bg-gray-100;
+ @apply bg-white;
}
.label {
diff --git a/src-migrate/modules/product-detail/styles/price-action.module.css b/src-migrate/modules/product-detail/styles/price-action.module.css
index 651de958..cea50bff 100644
--- a/src-migrate/modules/product-detail/styles/price-action.module.css
+++ b/src-migrate/modules/product-detail/styles/price-action.module.css
@@ -8,7 +8,10 @@
@apply flex gap-x-2.5;
}
.quantity-input {
- @apply px-2 rounded text-center border border-gray-300 w-14 h-10 focus:outline-none;
+ @apply w-24 h-10 text-center border border-gray-300 rounded focus:outline-none;
+ /* Padding di kiri dan kanan untuk memberi ruang bagi tombol */
+ padding-left: 2rem;
+ padding-right: 2rem;
}
.contact-us {
diff --git a/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx b/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx
new file mode 100644
index 00000000..5685b83a
--- /dev/null
+++ b/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx
@@ -0,0 +1,17 @@
+import dynamic from 'next/dynamic';
+import React from 'react';
+import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton';
+const FlashSaleNonDisplay = dynamic(
+ () => import('@/lib/flashSale/components/FlashSaleNonDisplay'),
+ {
+ loading: () => <FlashSaleSkeleton />,
+ }
+);
+const FlashSalePromo = () => {
+ return (
+ <>
+ <FlashSaleNonDisplay />
+ </>
+ );
+};
+export default FlashSalePromo;
diff --git a/src-migrate/modules/promo/components/PromoList.tsx b/src-migrate/modules/promo/components/PromoList.tsx
index d59d1867..9f808718 100644
--- a/src-migrate/modules/promo/components/PromoList.tsx
+++ b/src-migrate/modules/promo/components/PromoList.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
-import { Button, Skeleton } from '@chakra-ui/react'
-import clsxm from "~/libs/clsxm"
+import { Button, Skeleton } from '@chakra-ui/react';
+import clsxm from '~/libs/clsxm';
import ProductPromoCard from '../../product-promo/components/Card';
import { fetchPromoItemsSolr } from '../../../../src/api/promoApi';
import { Swiper, SwiperSlide } from 'swiper/react';
@@ -8,7 +8,7 @@ import SwiperCore, { Navigation, Pagination } from 'swiper';
import useDevice from '@/core/hooks/useDevice';
import LogoSpinner from '../../../../src/core/components/elements/Spinner/LogoSpinner';
import usePromoStore from './promoStore';
-import Link from "next/link"
+import Link from 'next/link';
import { IPromotion } from '~/types/promotion';
interface PromoListProps {
selectedPromo: string; // Tipe selectedPromo ditetapkan sebagai string
@@ -32,11 +32,11 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
const swiperBanner = {
modules: [Navigation],
- className: 'h-[400px] w-full',
+ className: 'h-full w-full',
slidesPerView: isMobile ? 1.1 : 3.25,
spaceBetween: 10,
- navigation:isMobile? true : false,
- allowTouchMove:isMobile? false : true,
+ navigation: isMobile ? true : false,
+ allowTouchMove: isMobile ? false : true,
};
useEffect(() => {
@@ -56,7 +56,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
const fetchPromotions = async () => {
setIsLoading(true);
try {
- const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10);
+ const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10);
setPromoItems(items);
const promoDataPromises = items?.map(async (item) => {
@@ -69,9 +69,11 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
});
const promoDataArray = await Promise.all(promoDataPromises);
- const mergedPromoData = promoDataArray?.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
+ const mergedPromoData = promoDataArray?.reduce(
+ (accumulator, currentValue) => accumulator.concat(currentValue),
+ []
+ );
setPromoData(mergedPromoData);
-
} catch (error) {
console.error('Error fetching promo items:', error);
} finally {
@@ -92,44 +94,49 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
<div className='flex justify-between items-center'>
<h1 className='text-h-sm md:text-h-lg font-semibold py-4'>{title}</h1>
<div>
- <Link href={`/shop/promo/${slug}`} className='!text-red-500 font-semibold'>
+ <Link
+ href={`/shop/promo/${slug}`}
+ className='!text-red-500 font-semibold'
+ >
Lihat Semua
</Link>
</div>
</div>
{isLoading ? (
- <div className="loading-spinner flex justify-center">
+ <div className='loading-spinner flex justify-center'>
<LogoSpinner width={48} height={48} />
</div>
) : (
<Skeleton
- isLoaded={!isLoading}
- className={clsxm(
- "flex gap-x-4 overflow-x-auto px-4 md:px-0", {
- "min-h-[340px]": promoData[0] && promoData?.length > 0
- })}
- >
- {isDesktop && (
- <Swiper {...swiperBanner}>
- {promoData?.map((promotion: IPromotion) => (
- <SwiperSlide key={promotion.id}>
- <div className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-full lg:w-full xl:w-full">
- <ProductPromoCard product={promoItems} promotion={promotion} />
- </div>
- </SwiperSlide>
- ))}
- </Swiper>
- )}
- {isMobile && (promoData?.map((promotion: IPromotion) => (
- <div key={promotion.id} className="min-w-[400px] max-w-[400px]">
- <ProductPromoCard product={promoItems} promotion={promotion} />
- </div>
- )))}
-
- </Skeleton>
+ isLoaded={!isLoading}
+ className={clsxm('flex gap-x-4 overflow-x-auto px-4 md:px-0', {
+ 'min-h-[340px]': promoData[0] && promoData?.length > 0,
+ })}
+ >
+ {isDesktop && (
+ <Swiper {...swiperBanner}>
+ {promoData?.map((promotion: IPromotion) => (
+ <SwiperSlide key={promotion.id}>
+ <div className='min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-full lg:w-full xl:w-full'>
+ <ProductPromoCard
+ product={promoItems}
+ promotion={promotion}
+ />
+ </div>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ )}
+ {isMobile &&
+ promoData?.map((promotion: IPromotion) => (
+ <div key={promotion.id} className='min-w-[400px] max-w-[400px]'>
+ <ProductPromoCard product={promoItems} promotion={promotion} />
+ </div>
+ ))}
+ </Skeleton>
)}
</div>
);
};
-export default PromoList; \ No newline at end of file
+export default PromoList;
diff --git a/src-migrate/modules/register/components/TermCondition.tsx b/src-migrate/modules/register/components/TermCondition.tsx
index d54fe921..44275917 100644
--- a/src-migrate/modules/register/components/TermCondition.tsx
+++ b/src-migrate/modules/register/components/TermCondition.tsx
@@ -1,34 +1,48 @@
-import { Checkbox } from '@chakra-ui/react'
-import React from 'react'
-import { Modal } from '~/components/ui/modal'
-import { useRegisterStore } from "../stores/useRegisterStore";
-import PageContent from '~/modules/page-content'
+import { Checkbox } from '@chakra-ui/react';
+import React from 'react';
+import { Modal } from '~/components/ui/modal';
+import { useRegisterStore } from '../stores/useRegisterStore';
+
+import dynamic from 'next/dynamic';
+const PageContent = dynamic(
+ () => import('@/lib/content/components/PageContent')
+);
const TermCondition = () => {
- const { isOpenTNC, closeTNC, isCheckedTNC, toggleCheckTNC, openTNC } = useRegisterStore()
+ const { isOpenTNC, closeTNC, isCheckedTNC, toggleCheckTNC, openTNC } =
+ useRegisterStore();
return (
<>
- <div className="mt-4 flex items-center gap-x-2">
- <Checkbox id='tnc' name='tnc' colorScheme='red' isChecked={isCheckedTNC} onChange={toggleCheckTNC} />
+ <div className='mt-4 flex items-center gap-x-2'>
+ <Checkbox
+ id='tnc'
+ name='tnc'
+ colorScheme='red'
+ isChecked={isCheckedTNC}
+ onChange={toggleCheckTNC}
+ />
<div>
- <label htmlFor="tnc" className="cursor-pointer">Dengan ini saya menyetujui</label>
- {' '}
+ <label htmlFor='tnc' className='cursor-pointer'>
+ Dengan ini saya menyetujui
+ </label>{' '}
<span
- className="font-medium text-danger-500 cursor-pointer"
+ className='font-medium text-danger-500 cursor-pointer'
onClick={openTNC}
>
syarat dan ketentuan
</span>
- <label htmlFor="tnc" className="ml-2 cursor-pointer">yang berlaku</label>
+ <label htmlFor='tnc' className='ml-2 cursor-pointer'>
+ yang berlaku
+ </label>
</div>
</div>
- <Modal active={isOpenTNC} close={closeTNC} >
- <PageContent path='/register#tnd' />
+ <Modal active={isOpenTNC} close={closeTNC}>
+ <PageContent path='/registerTnd' />
</Modal>
</>
- )
-}
+ );
+};
-export default TermCondition \ No newline at end of file
+export default TermCondition;
diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index c5386c91..24baa933 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -84,19 +84,19 @@ const CartPage = () => {
const hasSelectedPromo = useMemo(() => {
if (!cart) return false;
- return cart.products.some(
+ return cart?.products?.some(
(item) => item.cart_type === 'promotion' && item.selected
);
}, [cart]);
const hasSelected = useMemo(() => {
if (!cart) return false;
- return cart.products.some((item) => item.selected);
+ return cart?.products?.some((item) => item.selected);
}, [cart]);
const hasSelectNoPrice = useMemo(() => {
if (!cart) return false;
- return cart.products.some(
+ return cart?.products?.some(
(item) => item.selected && item.price.price_discount === 0
);
}, [cart]);
@@ -230,7 +230,7 @@ const CartPage = () => {
</div>
<div className={style['items']}>
- {cart?.products.map((item) => (
+ {cart?.products?.map((item) => (
<CartItemModule key={item.id} item={item} />
))}
diff --git a/src-migrate/pages/shop/promo/index.tsx b/src-migrate/pages/shop/promo/index.tsx
index febe31a4..689c2537 100644
--- a/src-migrate/pages/shop/promo/index.tsx
+++ b/src-migrate/pages/shop/promo/index.tsx
@@ -1,13 +1,14 @@
-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'
-import FlashSale from '../../../modules/promo/components/FlashSale'
-const PromoList = dynamic(() => import('../../../modules/promo/components/PromoList'));
-
-
+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';
+import FlashSale from '../../../modules/promo/components/FlashSale';
+import FlashSaleNonDisplay from '../../../modules/promo/components/FlashSaleNonDisplay';
+const PromoList = dynamic(
+ () => import('../../../modules/promo/components/PromoList')
+);
const PromoPage = () => {
const [selectedPromo, setSelectedPromo] = useState('Bundling');
@@ -17,22 +18,26 @@ const PromoPage = () => {
<Hero />
</LazyLoadComponent>
<LazyLoadComponent>
- <PromotionProgram
- selectedPromo={selectedPromo}
- onSelectPromo={setSelectedPromo}
- />
+ <PromotionProgram
+ selectedPromo={selectedPromo}
+ onSelectPromo={setSelectedPromo}
+ />
<PromoList selectedPromo={selectedPromo} />
</LazyLoadComponent>
-
+
<LazyLoadComponent>
<FlashSale />
</LazyLoadComponent>
<h1 className='h-1'></h1>
<LazyLoadComponent>
+ <FlashSaleNonDisplay />
+ </LazyLoadComponent>
+ <h1 className='h-1'></h1>
+ <LazyLoadComponent>
<Voucher />
</LazyLoadComponent>
</>
- )
-}
+ );
+};
-export default PromoPage \ No newline at end of file
+export default PromoPage;
diff --git a/src-migrate/types/cart.ts b/src-migrate/types/cart.ts
index a3115103..05fdcadb 100644
--- a/src-migrate/types/cart.ts
+++ b/src-migrate/types/cart.ts
@@ -34,6 +34,7 @@ export type CartItem = {
stock: number;
is_in_bu: boolean;
on_hand_qty: number;
+ available_quantity: number;
weight: number;
attributes: string[];
parent: {
diff --git a/src-migrate/types/product.ts b/src-migrate/types/product.ts
index 31ea0ce1..85ea702a 100644
--- a/src-migrate/types/product.ts
+++ b/src-migrate/types/product.ts
@@ -3,6 +3,7 @@ import { IProductVariantDetail } from './productVariant';
export interface IProduct {
id: number;
image: string;
+ image_mobile: string;
code: string;
display_name: string;
name: string;
@@ -31,7 +32,9 @@ export interface IProduct {
manufacture: {
id: number;
name: string;
+ logo: string;
};
+ voucher_pasti_hemat : any;
}
export interface IProductDetail extends IProduct {
diff --git a/src-migrate/types/productVariant.ts b/src-migrate/types/productVariant.ts
index 861b216a..5144e7c1 100644
--- a/src-migrate/types/productVariant.ts
+++ b/src-migrate/types/productVariant.ts
@@ -4,6 +4,7 @@ export interface IProductVariantDetail {
code: string;
name: string;
weight: number;
+ is_in_bu: boolean;
is_flashsale: {
remaining_time: number;
is_flashsale: boolean;
diff --git a/src/components/ui/HeroBanner.jsx b/src/components/ui/HeroBanner.jsx
index 64838b85..2eea5915 100644
--- a/src/components/ui/HeroBanner.jsx
+++ b/src/components/ui/HeroBanner.jsx
@@ -6,7 +6,7 @@ import 'swiper/css/pagination';
import { Swiper, SwiperSlide } from 'swiper/react';
import Image from 'next/image';
-import { useMemo } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { bannerApi } from '@/api/bannerApi';
@@ -27,7 +27,20 @@ const swiperBanner = {
};
const HeroBanner = () => {
- const heroBanner = useQuery('heroBanner', bannerApi({ type: 'index-a-1' }));
+ // const heroBanner = useQuery('heroBanner', bannerApi({ type: 'index-a-1' }));
+ const [data, setData] = useState(null);
+ useEffect(() => {
+ const fetchData = async () => {
+ const res = await fetch(`/api/hero-banner?type=index-a-1`);
+ const { data } = await res.json();
+ if (data) {
+ setData(data);
+ }
+ };
+
+ fetchData();
+ }, []);
+ const heroBanner = data;
const swiperBannerMobile = {
...swiperBanner,
@@ -44,9 +57,9 @@ const HeroBanner = () => {
};
const BannerComponent = useMemo(() => {
- if (!heroBanner.data) return null;
+ if (!heroBanner) return null;
- return heroBanner.data.map((banner, index) => (
+ return heroBanner.map((banner, index) => (
<SwiperSlide key={index}>
<Link href={banner.url} className='w-full h-auto'>
<Image
@@ -56,22 +69,22 @@ const HeroBanner = () => {
width={1152}
height={768}
className='w-full h-auto'
- priority={index === 0}
- loading={index === 0 ? 'eager' : 'lazy'}
- placeholder="blur"
- blurDataURL="/images/indoteknik-placeholder.png"
- sizes="(max-width: 768px) 100vw, 50vw"
+ priority={index === 0}
+ loading={index === 0 ? 'eager' : 'lazy'}
+ placeholder='blur'
+ blurDataURL='/images/indoteknik-placeholder.png'
+ sizes='(max-width: 768px) 100vw, 50vw'
/>
</Link>
</SwiperSlide>
));
- }, [heroBanner.data]);
+ }, [heroBanner]);
return (
<>
<MobileView>
<SmoothRender
- isLoaded={heroBanner.data?.length > 0}
+ isLoaded={heroBanner?.length > 0}
height='68vw'
duration='750ms'
delay='100ms'
@@ -81,7 +94,7 @@ const HeroBanner = () => {
</MobileView>
<DesktopView>
- {heroBanner.data?.length > 0 && (
+ {heroBanner?.length > 0 && (
<Swiper {...swiperBannerDesktop}>{BannerComponent}</Swiper>
)}
</DesktopView>
diff --git a/src/components/ui/HeroBannerSecondary.jsx b/src/components/ui/HeroBannerSecondary.jsx
index a7b32a4a..6074c9a6 100644
--- a/src/components/ui/HeroBannerSecondary.jsx
+++ b/src/components/ui/HeroBannerSecondary.jsx
@@ -1,39 +1,58 @@
-import Link from '@/core/components/elements/Link/Link'
-import { getRandomInt } from '@/utils/getRandomInt'
-import Image from 'next/image'
-import { useMemo } from 'react'
-import { useQuery } from 'react-query'
-import { HeroBannerSkeleton } from '../skeleton/BannerSkeleton'
-import { bannerApi } from '@/api/bannerApi'
+import Link from '@/core/components/elements/Link/Link';
+import { getRandomInt } from '@/utils/getRandomInt';
+import Image from 'next/image';
+import { useMemo, useEffect, useState } from 'react';
+import { useQuery } from 'react-query';
+import { HeroBannerSkeleton } from '../skeleton/BannerSkeleton';
+import { bannerApi } from '@/api/bannerApi';
const HeroBannerSecondary = () => {
- const heroBannerSecondary = useQuery('heroBannerSecondary', bannerApi({ type: 'index-a-2' }))
+ const [heroBannerSecondary, setHeroBannerSecondary] = useState([]);
+ const [isLoading, setIsLoading] = useState(false);
+ // const heroBannerSecondary = useQuery(
+ // 'heroBannerSecondary',
+ // bannerApi({ type: 'index-a-2' })
+ // );
- const randomIndex = useMemo(() => {
- if (!heroBannerSecondary.data) return null
- const length = heroBannerSecondary.data?.length
- return getRandomInt(length)
- }, [heroBannerSecondary.data])
+ useEffect(() => {
+ const fetchData = async () => {
+ setIsLoading(true);
+ const res = await fetch(`/api/hero-banner?type=index-a-2`);
+ const { data } = await res.json();
+ if (data) {
+ setHeroBannerSecondary(data);
+ }
+ setIsLoading(false);
+ };
+
+ fetchData();
+ }, []);
- if (heroBannerSecondary.isLoading) return <HeroBannerSkeleton />
+ const randomIndex = useMemo(() => {
+ if (!heroBannerSecondary) return null;
+ const length = heroBannerSecondary?.length;
+ return getRandomInt(length);
+ }, [heroBannerSecondary]);
+ if (isLoading) return <HeroBannerSkeleton />;
return (
- heroBannerSecondary.data && randomIndex !== null && (
- <Link href={heroBannerSecondary.data[randomIndex].url} className="h-full">
+ heroBannerSecondary &&
+ randomIndex !== null && (
+ <Link href={heroBannerSecondary[randomIndex]?.url} className='h-full'>
<Image
- src={heroBannerSecondary.data[randomIndex].image}
+ src={heroBannerSecondary[randomIndex]?.image}
width={512}
height={1024}
- alt={heroBannerSecondary.data[randomIndex].name}
- className="object-cover object-center h-full"
- loading="lazy"
- placeholder="blur"
- blurDataURL="/images/indoteknik-placeholder.png"
- sizes="(max-width: 768px) 100vw, 50vw"
+ alt={heroBannerSecondary[randomIndex]?.name}
+ className='object-cover object-center h-full'
+ loading='lazy'
+ placeholder='blur'
+ blurDataURL='/images/indoteknik-placeholder.png'
+ sizes='(max-width: 768px) 100vw, 50vw'
/>
</Link>
)
);
-}
+};
-export default HeroBannerSecondary
+export default HeroBannerSecondary;
diff --git a/src/core/components/elements/Footer/BasicFooter.jsx b/src/core/components/elements/Footer/BasicFooter.jsx
index 4688b15b..05dc4d8c 100644
--- a/src/core/components/elements/Footer/BasicFooter.jsx
+++ b/src/core/components/elements/Footer/BasicFooter.jsx
@@ -264,7 +264,7 @@ const InformationCenter = () => (
<li className='text-gray_r-12/80 flex items-center'>
<PhoneArrowUpRightIcon className='w-[18px] mr-2' />
<a href='tel:02129338828' target='_blank' rel='noreferrer'>
- (021) 2933-8828
+ (021) 29338828
</a>
</li>
<li className='text-gray_r-12/80 flex items-center'>
diff --git a/src/core/components/elements/Footer/SimpleFooter.jsx b/src/core/components/elements/Footer/SimpleFooter.jsx
index 371b1652..1f5e13e7 100644
--- a/src/core/components/elements/Footer/SimpleFooter.jsx
+++ b/src/core/components/elements/Footer/SimpleFooter.jsx
@@ -13,7 +13,7 @@ const SimpleFooter = () => (
<ul className='flex flex-col gap-y-2'>
<li className='text-gray_r-12/80 flex items-center'>
<PhoneArrowUpRightIcon className='w-[18px] mr-2' />
- <a href='tel:02129338828'>(021) 2933-8828 / 29</a>
+ <a href='tel:02129338828'>(021) 29338828</a>
</li>
<li className='text-gray_r-12/80 flex items-center'>
<EnvelopeIcon className='w-[18px] mr-2' />
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx
index eebfbcd5..fa3df5bf 100644
--- a/src/core/components/elements/Navbar/NavbarDesktop.jsx
+++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx
@@ -5,34 +5,28 @@ import { createSlug } from '@/core/utils/slug';
import whatsappUrl from '@/core/utils/whatsappUrl';
import IndoteknikLogo from '@/images/logo.png';
import Cardheader from '@/lib/cart/components/Cartheader';
-import Quotationheader from '../../../../../src/lib/quotation/components/Quotationheader.jsx';
import Category from '@/lib/category/components/Category';
-import { useProductCartContext } from '@/contexts/ProductCartContext';
+import useTransactions from '@/lib/transaction/hooks/useTransactions';
+import {
+ Menu,
+ MenuButton,
+ MenuItem,
+ MenuList,
+ useDisclosure
+} from '@chakra-ui/react';
import {
ChevronDownIcon,
- DocumentCheckIcon,
- HeartIcon,
- ArrowUpRightIcon,
+ HeartIcon
} from '@heroicons/react/24/outline';
import dynamic from 'next/dynamic';
-import Image from 'next/image';
+import { default as Image, default as NextImage } from 'next/image';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useState } from 'react';
+import { useCartStore } from '~/modules/cart/stores/useCartStore';
+import Quotationheader from '../../../../../src/lib/quotation/components/Quotationheader.jsx';
import DesktopView from '../../views/DesktopView';
import Link from '../Link/Link';
import NavbarUserDropdown from './NavbarUserDropdown';
-import NextImage from 'next/image';
-import {
- Button,
- Menu,
- MenuButton,
- MenuItem,
- MenuList,
- useDisclosure,
-} from '@chakra-ui/react';
-import style from './style/NavbarDesktop.module.css';
-import useTransactions from '@/lib/transaction/hooks/useTransactions';
-import { useCartStore } from '~/modules/cart/stores/useCartStore';
const Search = dynamic(() => import('./Search'), { ssr: false });
const TopBanner = dynamic(() => import('./TopBanner'), { ssr: false });
@@ -397,7 +391,7 @@ const SocialMedias = () => (
>
<NextImage
src='/images/socials/youtube.webp'
- alt='Youtube - Indoteknik.com'
+ // alt='Youtube - Indoteknik.com'
width={24}
height={24}
/>
@@ -409,7 +403,7 @@ const SocialMedias = () => (
>
<NextImage
src='/images/socials/tiktok.png'
- alt='TikTok - Indoteknik.com'
+ // alt='TikTok - Indoteknik.com'
width={24}
height={24}
/>
@@ -429,7 +423,7 @@ const SocialMedias = () => (
>
<NextImage
src='/images/socials/Facebook.png'
- alt='Facebook - Indoteknik.com'
+ // alt='Facebook - Indoteknik.com'
width={24}
height={24}
/>
@@ -441,7 +435,7 @@ const SocialMedias = () => (
>
<NextImage
src='/images/socials/Instagram.png'
- alt='Instagram - Indoteknik.com'
+ // alt='Instagram - Indoteknik.com'
width={24}
height={24}
/>
@@ -453,7 +447,7 @@ const SocialMedias = () => (
>
<NextImage
src='/images/socials/Linkedin.png'
- alt='Linkedin - Indoteknik.com'
+ // alt='Linkedin - Indoteknik.com'
width={24}
height={24}
/>
@@ -465,7 +459,7 @@ const SocialMedias = () => (
>
<NextImage
src='/images/socials/g_maps.png'
- alt='Maps - Indoteknik.com'
+ // alt='Maps - Indoteknik.com'
width={24}
height={24}
/>
diff --git a/src/core/components/elements/Navbar/TopBanner.jsx b/src/core/components/elements/Navbar/TopBanner.jsx
index f438ae67..709495ce 100644
--- a/src/core/components/elements/Navbar/TopBanner.jsx
+++ b/src/core/components/elements/Navbar/TopBanner.jsx
@@ -1,22 +1,37 @@
import Image from 'next/image';
-import { useQuery } from 'react-query';import useDevice from '@/core/hooks/useDevice'
+import { useQuery } from 'react-query';
+import useDevice from '@/core/hooks/useDevice';
import odooApi from '@/core/api/odooApi';
import SmoothRender from '~/components/ui/smooth-render';
import Link from '../Link/Link';
import { background } from '@chakra-ui/react';
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
const TopBanner = ({ onLoad = () => {} }) => {
- const { isDesktop, isMobile } = useDevice()
- const topBanner = useQuery({
- queryKey: 'topBanner',
- queryFn: async () => await odooApi('GET', '/api/v1/banner?type=top-banner'),
- refetchOnWindowFocus: false,
- });
+ const [topBanner, setTopBanner] = useState([]);
+ const { isDesktop, isMobile } = useDevice();
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const res = await fetch(`/api/hero-banner?type=top-banner`);
+ const { data } = await res.json();
+ if (data) {
+ setTopBanner(data);
+ }
+ };
+
+ fetchData();
+ }, []);
+
+ // const topBanner = useQuery({
+ // queryKey: 'topBanner',
+ // queryFn: async () => await odooApi('GET', '/api/v1/banner?type=top-banner'),
+ // refetchOnWindowFocus: false,
+ // });
// const backgroundColor = topBanner.data?.[0]?.backgroundColor || 'transparent';
- const hasData = topBanner.data?.length > 0;
- const data = topBanner.data?.[0] || null;
+ const hasData = topBanner?.length > 0;
+ const data = topBanner?.[0] || null;
useEffect(() => {
if (hasData) {
@@ -31,17 +46,15 @@ const TopBanner = ({ onLoad = () => {} }) => {
duration='700ms'
delay='300ms'
className='h-auto'
- >
+ >
<Link
- href={data?.url}
- className="block bg-cover bg-center h-3 md:h-6 lg:h-[36px]"
- style={{
- backgroundImage: `url('${data?.image}')`,
- }}
- >
- </Link>
-
- </SmoothRender>
+ href={data?.url}
+ className='block bg-cover bg-center h-3 md:h-6 lg:h-[36px]'
+ style={{
+ backgroundImage: `url('${data?.image}')`,
+ }}
+ ></Link>
+ </SmoothRender>
);
};
diff --git a/src/core/components/layouts/BasicLayout.jsx b/src/core/components/layouts/BasicLayout.jsx
index c4674344..1b62bf05 100644
--- a/src/core/components/layouts/BasicLayout.jsx
+++ b/src/core/components/layouts/BasicLayout.jsx
@@ -8,6 +8,7 @@ import odooApi from '@/core/api/odooApi';
import whatsappUrl from '@/core/utils/whatsappUrl';
import Navbar from '../elements/Navbar/Navbar';
import styles from './BasicLayout.module.css'; // Import modul CSS
+import useDevice from '@/core/hooks/useDevice';
const AnimationLayout = dynamic(() => import('./AnimationLayout'), {
ssr: false,
@@ -23,6 +24,9 @@ const BasicLayout = ({ children }) => {
const [highlight, setHighlight] = useState(false);
const [buttonPosition, setButtonPosition] = useState(null);
const [wobble, setWobble] = useState(false);
+ const [isProductPage, setIsProductPage] = useState(false);
+
+ const { isDesktop, isMobile } = useDevice();
const router = useRouter();
const buttonRef = useRef(null);
@@ -43,13 +47,16 @@ const BasicLayout = ({ children }) => {
setUrlPath(router.asPath);
}
+ if (router.pathname.includes('/shop/product/')) {
+ setIsProductPage(true);
+ }
}, [product, router]);
useEffect(() => {
const handleMouseOut = (event) => {
const rect = buttonRef.current.getBoundingClientRect();
if (event.clientY <= 0) {
- setButtonPosition(rect)
+ setButtonPosition(rect);
setHighlight(true);
} else {
setHighlight(false);
@@ -92,13 +99,15 @@ const BasicLayout = ({ children }) => {
return (
<>
- {highlight && buttonPosition && (
+ {highlight && buttonPosition && (
<div
className={styles['overlay-highlight']}
style={{
- '--button-x': `${buttonPosition.x + buttonPosition.width / 2}px`,
+ '--button-x': `${buttonPosition.x + buttonPosition.width / 2}px`,
'--button-y': `${buttonPosition.y + buttonPosition.height / 2}px`,
- '--button-radius': `${Math.max(buttonPosition.width, buttonPosition.height) / 2}px`
+ '--button-radius': `${
+ Math.max(buttonPosition.width, buttonPosition.height) / 2
+ }px`,
}}
onAnimationEnd={() => setHighlight(false)}
/>
@@ -106,11 +115,25 @@ const BasicLayout = ({ children }) => {
<Navbar />
<AnimationLayout>
{children}
- <div className='fixed bottom-4 right-4 sm:bottom-14 sm:right-10 z-50'>
- <div className='flex flex-row items-center'>
- <a href={whatsappUrl(templateWA, payloadWA, urlPath)} className='flex flex-row items-center' rel='noopener noreferrer' target='_blank'>
- <span className={`text-green-300 text-lg font-bold mr-4 ${wobble ? 'animate-wobble' : ''}`} onAnimationEnd={() => setWobble(false)}>
- Whatsapp
+ <div
+ className={`fixed ${
+ isMobile && isProductPage ? 'bottom-40' : 'bottom-16'
+ } right-4 sm:bottom-14 sm:right-10 z-50`}
+ >
+ <div className='flex flex-row items-center'>
+ <a
+ href={whatsappUrl(templateWA, payloadWA, urlPath)}
+ className='flex flex-row items-center'
+ rel='noopener noreferrer'
+ target='_blank'
+ >
+ <span
+ className={`text-green-300 text-lg font-bold mr-4 ${
+ wobble ? 'animate-wobble' : ''
+ }`}
+ onAnimationEnd={() => setWobble(false)}
+ >
+ {isDesktop && 'Whatsapp'}
</span>
</a>
<a
diff --git a/src/core/utils/whatsappUrl.js b/src/core/utils/whatsappUrl.js
index 7a129aa6..c840e105 100644
--- a/src/core/utils/whatsappUrl.js
+++ b/src/core/utils/whatsappUrl.js
@@ -2,28 +2,31 @@ import { getAuth } from "./auth"
const whatsappUrl = (template = 'default', payload, urlPath = null) => {
let user = getAuth()
- if(!user){
- if(urlPath) return `/login?next=${urlPath}`
- if(!urlPath) return '/login'
- }
+ // if(!user){
+ // if(urlPath) return `/login?next=${urlPath}`
+ // if(!urlPath) return '/login'
+ // }
let parentName = user.parentName || '-'
let url = 'https://wa.me/6281717181922'
let text = 'Hallo Indoteknik.com,'
+ if(user){
+ text += `Saya ${user.name}, Saya dari ${parentName}`
+ }
switch (template) {
case 'product':
- text += ` Saya ${user.name} , Saya dari ${parentName} Saya mencari barang dibawah ini\n\n: Brand = ${payload?.manufacture}\n\n Item Name = ${payload?.name}\n\nLink : ${payload?.url}`
+ text += ` Saya mencari barang dibawah ini\n\n: Brand = ${payload?.manufacture}\n\n Item Name = ${payload?.name}\n\nLink : ${payload?.url}`
break
case 'productWeight':
- text += ` Saya ${user.name} , Saya dari ${parentName} Saya mencari barang dibawah ini\n\n: Brand = ${payload?.manufacture}\n\n Item Name = ${payload?.name}\n\nLink : ${payload?.url}`
+ text += ` Saya mencari barang dibawah ini\n\n: Brand = ${payload?.manufacture}\n\n Item Name = ${payload?.name}\n\nLink : ${payload?.url}`
break
case 'productSearch':
- text += `Saya lagi cari-cari produk ${payload?.name}, bisa bantu saya cari produknya?`
+ text += ` Saya lagi cari-cari produk ${payload?.name}, bisa bantu saya cari produknya?`
break
case null:
- text += `Saya ${user.name}, Saya dari ${parentName} Bisa tolong bantu kebutuhan saya?`
+ text += ` Bisa tolong bantu kebutuhan saya?`
break;
default:
- text += `Saya ${user.name}, Saya dari ${parentName} Bisa tolong bantu kebutuhan saya?`
+ text += ` Bisa tolong bantu kebutuhan saya?`
break
}
if (text) url += `?text=${encodeURI(text)}`
diff --git a/src/lib/address/api/cityApi.js b/src/lib/address/api/cityApi.js
index 7873435b..0b0201e6 100644
--- a/src/lib/address/api/cityApi.js
+++ b/src/lib/address/api/cityApi.js
@@ -1,7 +1,7 @@
import odooApi from '@/core/api/odooApi'
-const cityApi = async () => {
- const dataCities = await odooApi('GET', '/api/v1/city')
+const cityApi = async ({stateId}) => {
+ const dataCities = await odooApi('GET', '/api/v1/city?state_id='+stateId)
return dataCities
}
diff --git a/src/lib/address/api/stateApi.js b/src/lib/address/api/stateApi.js
new file mode 100644
index 00000000..cea49e7e
--- /dev/null
+++ b/src/lib/address/api/stateApi.js
@@ -0,0 +1,8 @@
+import odooApi from '@/core/api/odooApi'
+
+const stateApi = async () => {
+ const dataState = await odooApi('GET', '/api/v1/state')
+ return dataState
+}
+
+export default stateApi \ No newline at end of file
diff --git a/src/lib/address/components/CreateAddress.jsx b/src/lib/address/components/CreateAddress.jsx
index e315affe..9d70e8fc 100644
--- a/src/lib/address/components/CreateAddress.jsx
+++ b/src/lib/address/components/CreateAddress.jsx
@@ -12,6 +12,7 @@ import { toast } from 'react-hot-toast';
import { yupResolver } from '@hookform/resolvers/yup';
import Menu from '@/lib/auth/components/Menu';
import useAddresses from '../hooks/useAddresses';
+import stateApi from '../api/stateApi';
const CreateAddress = () => {
const auth = useAuth();
@@ -28,23 +29,40 @@ const CreateAddress = () => {
defaultValues,
});
const { addresses = [] } = useAddresses(); // Ensure addresses is an array
+ const [states, setState] = useState([]);
const [cities, setCities] = useState([]);
const [districts, setDistricts] = useState([]);
const [subDistricts, setSubDistricts] = useState([]);
const [filteredTypes, setFilteredTypes] = useState(types); // State to manage filtered types
useEffect(() => {
- const loadCities = async () => {
- let dataCities = await cityApi();
- dataCities = dataCities.map((city) => ({
- value: city.id,
- label: city.name,
+ const loadState = async () => {
+ let dataState = await stateApi();
+ dataState = dataState.map((state) => ({
+ value: state.id,
+ label: state.name,
}));
- setCities(dataCities);
+ setState(dataState);
};
- loadCities();
+ loadState();
}, []);
+ const watchState = watch('state');
+ useEffect(() => {
+ setValue('city', '');
+ if (watchState) {
+ const loadCities = async () => {
+ let dataCities = await cityApi({stateId: watchState});
+ dataCities = dataCities.map((city) => ({
+ value: city.id,
+ label: city.name,
+ }));
+ setCities(dataCities);
+ };
+ loadCities();
+ }
+ }, [watchState, setValue]);
+
useEffect(() => {
if (addresses) {
let hasContactAddress = false;
@@ -100,6 +118,7 @@ const CreateAddress = () => {
const onSubmitHandler = async (values) => {
const data = {
...values,
+ state_id: values.state,
city_id: values.city,
district_id: values.district,
sub_district_id: values.subDistrict,
@@ -205,12 +224,26 @@ const CreateAddress = () => {
</div>
<div>
+ <label className='form-label mb-2'>Provinsi</label>
+ <Controller
+ name='state'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={states} />
+ )}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.state?.message}
+ </div>
+ </div>
+
+ <div>
<label className='form-label mb-2'>Kota</label>
<Controller
name='city'
control={control}
render={(props) => (
- <HookFormSelect {...props} options={cities} />
+ <HookFormSelect {...props} options={cities} disabled={!watchState}/>
)}
/>
<div className='text-caption-2 text-danger-500 mt-1'>
@@ -270,6 +303,7 @@ const validationSchema = Yup.object().shape({
mobile: Yup.string().required('Harus di-isi'),
street: Yup.string().required('Harus di-isi'),
zip: Yup.string().required('Harus di-isi'),
+ state: Yup.string().required('Harus di-pilih'),
city: Yup.string().required('Harus di-pilih'),
district: Yup.string().required('Harus di-pilih'),
});
@@ -280,6 +314,7 @@ const defaultValues = {
email: '',
mobile: '',
street: '',
+ state: '',
city: '',
district: '',
subDistrict: '',
diff --git a/src/lib/address/components/EditAddress.jsx b/src/lib/address/components/EditAddress.jsx
index 182c8a31..23cf72a9 100644
--- a/src/lib/address/components/EditAddress.jsx
+++ b/src/lib/address/components/EditAddress.jsx
@@ -13,6 +13,7 @@ import { toast } from 'react-hot-toast';
import Menu from '@/lib/auth/components/Menu';
import useAuth from '@/core/hooks/useAuth';
import odooApi from '@/core/api/odooApi';
+import stateApi from '../api/stateApi';
const EditAddress = ({ id, defaultValues }) => {
const auth = useAuth();
@@ -29,9 +30,11 @@ const EditAddress = ({ id, defaultValues }) => {
resolver: yupResolver(validationSchema),
defaultValues,
});
+
+ const [states, setStates] = useState([]);
const [cities, setCities] = useState([]);
const [districts, setDistricts] = useState([]);
- const [subDistricts, setSubDistricts] = useState([]);
+ const [subDistricts, setSubDistricts] = useState([]);
useEffect(() => {
const loadProfile = async () => {
@@ -48,16 +51,38 @@ const EditAddress = ({ id, defaultValues }) => {
}, [auth?.parentId]);
useEffect(() => {
- const loadCities = async () => {
- let dataCities = await cityApi();
- dataCities = dataCities.map((city) => ({
- value: city.id,
- label: city.name,
+ const loadStates = async () => {
+ let dataStates = await stateApi();
+ dataStates = dataStates.map((state) => ({
+ value: state.id,
+ label: state.name,
}));
- setCities(dataCities);
+ setStates(dataStates);
};
- loadCities();
- }, []);
+ loadStates();
+ },[])
+
+ const watchState = watch('state');
+ useEffect(() => {
+ setValue('city', '');
+ if(watchState) {
+ const loadCities = async () => {
+ let dataCities = await cityApi({ stateId: watchState });
+ dataCities = dataCities.map((city) => ({
+ value: city.id,
+ label: city.name,
+ }));
+ setCities(dataCities);
+ let oldCity = getValues('oldCity');
+ if (oldCity) {
+ setValue('city', oldCity);
+ setValue('oldCity', '');
+ }
+ };
+ loadCities();
+ }
+
+ }, [watchState, setValue, getValues]);
const watchCity = watch('city');
useEffect(() => {
@@ -107,6 +132,7 @@ const EditAddress = ({ id, defaultValues }) => {
const data = {
...values,
phone: values.mobile,
+ state_id: values.state,
city_id: values.city,
district_id: values.district,
sub_district_id: values.subDistrict,
@@ -242,12 +268,26 @@ const EditAddress = ({ id, defaultValues }) => {
</div>
<div>
+ <label className='form-label mb-2'>Provinsi</label>
+ <Controller
+ name='state'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={states} />
+ )}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.state?.message}
+ </div>
+ </div>
+
+ <div>
<label className='form-label mb-2'>Kota</label>
<Controller
name='city'
control={control}
render={(props) => (
- <HookFormSelect {...props} options={cities} />
+ <HookFormSelect {...props} options={cities} disabled={!watchState} />
)}
/>
<div className='text-caption-2 text-danger-500 mt-1'>
@@ -308,6 +348,7 @@ const validationSchema = Yup.object().shape({
mobile: Yup.string().required('Harus di-isi'),
street: Yup.string().required('Harus di-isi'),
zip: Yup.string().required('Harus di-isi'),
+ state : Yup.string().required('Harus di-pilih'),
city: Yup.string().required('Harus di-pilih'),
district: Yup.string().required('Harus di-pilih'),
});
diff --git a/src/lib/cart/components/Cartheader.jsx b/src/lib/cart/components/Cartheader.jsx
index ddb77c1f..1c30bb13 100644
--- a/src/lib/cart/components/Cartheader.jsx
+++ b/src/lib/cart/components/Cartheader.jsx
@@ -1,105 +1,115 @@
-import { useCallback, useEffect, useMemo, useState } from 'react'
-import { getCartApi } from '../api/CartApi'
-import currencyFormat from '@/core/utils/currencyFormat'
-import { createSlug } from '@/core/utils/slug'
-import useAuth from '@/core/hooks/useAuth'
-import { useRouter } from 'next/router'
-import odooApi from '@/core/api/odooApi'
-import { useProductCartContext } from '@/contexts/ProductCartContext'
-import Image from '@/core/components/elements/Image/Image'
-import whatsappUrl from '@/core/utils/whatsappUrl'
-import { AnimatePresence, motion } from 'framer-motion'
-import style from '../../../../src-migrate/modules/cart/styles/item-promo.module.css'
-const { ShoppingCartIcon, PhotoIcon } = require('@heroicons/react/24/outline')
-const { default: Link } = require('next/link')
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { getCartApi } from '../api/CartApi';
+import currencyFormat from '@/core/utils/currencyFormat';
+import { createSlug } from '@/core/utils/slug';
+import useAuth from '@/core/hooks/useAuth';
+import { useRouter } from 'next/router';
+import odooApi from '@/core/api/odooApi';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+import Image from '@/core/components/elements/Image/Image';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import { AnimatePresence, motion } from 'framer-motion';
+import style from '../../../../src-migrate/modules/cart/styles/item-promo.module.css';
+const { ShoppingCartIcon, PhotoIcon } = require('@heroicons/react/24/outline');
+const { default: Link } = require('next/link');
const Cardheader = (cartCount) => {
-
- const router = useRouter()
- const [subTotal, setSubTotal] = useState(null)
- const [buttonLoading, SetButtonTerapkan] = useState(false)
- const itemLoading = [1, 2, 3]
- const auth = useAuth()
- const [countCart, setCountCart] = useState(null)
- const { productCart, setRefreshCart, setProductCart, refreshCart, isLoading, setIsloading } =
- useProductCartContext()
+ const router = useRouter();
+ const [subTotal, setSubTotal] = useState(null);
+ const [buttonLoading, SetButtonTerapkan] = useState(false);
+ const itemLoading = [1, 2, 3];
+ const auth = useAuth();
+ const [countCart, setCountCart] = useState(null);
+ const {
+ productCart,
+ setRefreshCart,
+ setProductCart,
+ refreshCart,
+ isLoading,
+ setIsloading,
+ } = useProductCartContext();
- const [isHovered, setIsHovered] = useState(false)
- const [isTop, setIsTop] = useState(true)
+ const [isHovered, setIsHovered] = useState(false);
+ const [isTop, setIsTop] = useState(true);
const products = useMemo(() => {
- return productCart?.products || []
- }, [productCart])
+ return productCart?.products || [];
+ }, [productCart]);
const handleMouseEnter = () => {
- setIsHovered(true)
- getCart()
- }
+ setIsHovered(true);
+ getCart();
+ };
const handleMouseLeave = () => {
- setIsHovered(false)
- }
+ setIsHovered(false);
+ };
const getCart = () => {
if (!productCart && auth) {
- refreshCartf()
+ refreshCartf();
}
- }
+ };
const refreshCartf = useCallback(async () => {
- setIsloading(true)
- let cart = await getCartApi()
- setProductCart(cart)
- setCountCart(cart?.productTotal)
- setIsloading(false)
- }, [setProductCart, setIsloading])
+ setIsloading(true);
+ let cart = await getCartApi();
+ setProductCart(cart);
+ setCountCart(cart?.products?.length);
+ setIsloading(false);
+ }, [setProductCart, setIsloading]);
useEffect(() => {
- if (!products) return
+ if (!products) return;
- let calculateTotalPriceBeforeTax = 0
- let calculateTotalTaxAmount = 0
- let calculateTotalDiscountAmount = 0
+ let calculateTotalPriceBeforeTax = 0;
+ let calculateTotalTaxAmount = 0;
+ let calculateTotalDiscountAmount = 0;
for (const product of products) {
- if (product.quantity == '') continue
+ if (product.quantity == '') continue;
- let priceBeforeTax = product.price.price / 1.11
- calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity
- calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity
+ let priceBeforeTax = product.price.price / 1.11;
+ calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity;
+ calculateTotalTaxAmount +=
+ (product.price.price - priceBeforeTax) * product.quantity;
calculateTotalDiscountAmount +=
- (product.price.price - product.price.priceDiscount) * product.quantity
+ (product.price.price - product.price.priceDiscount) * product.quantity;
}
let subTotal =
- calculateTotalPriceBeforeTax - calculateTotalDiscountAmount + calculateTotalTaxAmount
- setSubTotal(subTotal)
- }, [products])
+ calculateTotalPriceBeforeTax -
+ calculateTotalDiscountAmount +
+ calculateTotalTaxAmount;
+ setSubTotal(subTotal);
+ }, [products]);
useEffect(() => {
if (refreshCart) {
- refreshCartf()
+ refreshCartf();
}
- setRefreshCart(false)
- }, [refreshCart, refreshCartf, setRefreshCart])
+ setRefreshCart(false);
+ }, [refreshCart, refreshCartf, setRefreshCart]);
useEffect(() => {
- setCountCart(cartCount.cartCount)
- setRefreshCart(false)
- }, [cartCount])
+ setCountCart(cartCount.cartCount);
+ setRefreshCart(false);
+ }, [cartCount]);
useEffect(() => {
const handleScroll = () => {
- setIsTop(window.scrollY === 0)
- }
- window.addEventListener('scroll', handleScroll)
+ setIsTop(window.scrollY === 0);
+ };
+ window.addEventListener('scroll', handleScroll);
return () => {
- window.removeEventListener('scroll', handleScroll)
- }
- }, [])
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
const handleCheckout = async () => {
- SetButtonTerapkan(true)
- let checkoutAll = await odooApi('POST', `/api/v1/user/${auth.id}/cart/select-all`)
- router.push('/shop/checkout')
- }
-
+ SetButtonTerapkan(true);
+ let checkoutAll = await odooApi(
+ 'POST',
+ `/api/v1/user/${auth.id}/cart/select-all`
+ );
+ router.push('/shop/checkout');
+ };
return (
<div className='relative group'>
@@ -152,8 +162,13 @@ const Cardheader = (cartCount) => {
className='w-full max-w-md p-2 bg-white border border-gray-200 rounded-lg shadow overflow-hidden'
>
<div className='p-2 flex justify-between items-center'>
- <h5 className='text-base font-semibold leading-none'>Keranjang Belanja</h5>
- <Link href='/shop/cart' class='text-sm font-medium text-red-600 underline'>
+ <h5 className='text-base font-semibold leading-none'>
+ Keranjang Belanja
+ </h5>
+ <Link
+ href='/shop/cart'
+ class='text-sm font-medium text-red-600 underline'
+ >
Lihat Semua
</Link>
</div>
@@ -163,7 +178,10 @@ const Cardheader = (cartCount) => {
<div className='justify-center p-4'>
<p className='text-gray-500 text-center '>
Silahkan{' '}
- <Link href='/login' className='text-red-600 underline leading-6'>
+ <Link
+ href='/login'
+ className='text-red-600 underline leading-6'
+ >
Login
</Link>{' '}
Untuk Melihat Daftar Keranjang Belanja Anda
@@ -172,7 +190,11 @@ const Cardheader = (cartCount) => {
)}
{isLoading &&
itemLoading.map((item) => (
- <div key={item} role='status' className='max-w-sm animate-pulse'>
+ <div
+ key={item}
+ role='status'
+ className='max-w-sm animate-pulse'
+ >
<div className='flex items-center space-x-4 mb- 2'>
<div className='flex-shrink-0'>
<PhotoIcon className='h-16 w-16 text-gray-500' />
@@ -194,14 +216,17 @@ const Cardheader = (cartCount) => {
)}
{auth && products.length > 0 && !isLoading && (
<>
- <ul role='list' className='divide-y divide-gray-200 dark:divide-gray-700'>
+ <ul
+ role='list'
+ className='divide-y divide-gray-200 dark:divide-gray-700'
+ >
{products &&
products?.map((product, index) => (
<>
<li className='py-1 sm:py-2'>
<div className='flex items-center space-x-4'>
<div className='bagian gambar flex-shrink-0'>
- {product.cartType === 'promotion' && (
+ {product.cartType === 'promotion' && (
<Image
src={product.imageProgram[0]}
alt={product.name}
@@ -227,10 +252,10 @@ const Cardheader = (cartCount) => {
</div>
<div className='bagian tulisan dan harga flex-1 min-w-0'>
{product.cartType === 'promotion' && (
- <p className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'>
- {product.name}
- </p>
- )}
+ <p className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'>
+ {product.name}
+ </p>
+ )}
{product.cartType === 'product' && (
<Link
href={createSlug(
@@ -252,27 +277,32 @@ const Cardheader = (cartCount) => {
{product?.price?.discountPercentage}%
</div>
<div className='text-gray_r-11 line-through text-caption-2'>
- {currencyFormat(product?.price?.price)}
+ {currencyFormat(
+ product?.price?.price
+ )}
</div>
</div>
)}
-
+
<div className='flex justify-between items-center'>
<div className='font-semibold text-sm text-red-600'>
{product?.price?.priceDiscount > 0 ? (
- currencyFormat(product?.price?.priceDiscount)
+ currencyFormat(
+ product?.price?.priceDiscount
+ )
) : (
<span className='text-gray_r-12/90 font-normal text-caption-1'>
<a
href={whatsappUrl('product', {
name: product.name,
- manufacture: product.manufacture?.name,
+ manufacture:
+ product.manufacture?.name,
url: createSlug(
'/shop/product/',
product.name,
product.id,
true
- )
+ ),
})}
className='text-danger-500 underline'
rel='noopener noreferrer'
@@ -286,56 +316,112 @@ const Cardheader = (cartCount) => {
</div>
</div>
</div>
- <div className="flex flex-col w-3/4">
- {product.products?.map((product) =>
- <div key={product.id} className='md:ml-8 ml-4 mt-2 flex'>
- <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '>
- {product?.image && <Image src={product.image} alt={product.name} width={40} height={40} className='w-full h-full object-fill' />}
- </Link>
-
- <div className="ml-4 w-full flex flex-col gap-y-1">
- <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className="text-caption-2 font-medium text-gray-900 truncate dark:text-white">
- {product.displayName}
- </Link>
-
- <div className='flex w-full'>
- <div className="flex flex-col">
- {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
- <div>
- <span className="text-gray-500 text-caption-1">Berat Barang: </span>
- <span className="text-gray-500 text-caption-1">{product.packageWeight} Kg</span>
- </div>
- </div>
+ <div className='flex flex-col w-3/4'>
+ {product.products?.map((product) => (
+ <div
+ key={product.id}
+ className='md:ml-8 ml-4 mt-2 flex'
+ >
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product.parent.name,
+ product.parent.id.toString()
+ )}
+ className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '
+ >
+ {product?.image && (
+ <Image
+ src={product.image}
+ alt={product.name}
+ width={40}
+ height={40}
+ className='w-full h-full object-fill'
+ />
+ )}
+ </Link>
+
+ <div className='ml-4 w-full flex flex-col gap-y-1'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product.parent.name,
+ product.parent.id.toString()
+ )}
+ className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'
+ >
+ {product.displayName}
+ </Link>
+
+ <div className='flex w-full'>
+ <div className='flex flex-col'>
+ {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
+ <div>
+ <span className='text-gray-500 text-caption-1'>
+ Berat Barang:{' '}
+ </span>
+ <span className='text-gray-500 text-caption-1'>
+ {product.packageWeight} Kg
+ </span>
</div>
</div>
-
</div>
+ </div>
+ </div>
+ ))}
+ {product.freeProducts?.map((product) => (
+ <div
+ key={product.id}
+ className='md:ml-8 ml-4 mt-2 flex'
+ >
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product.parent.name,
+ product.parent.id.toString()
)}
- {product.freeProducts?.map((product) =>
- <div key={product.id} className='md:ml-8 ml-4 mt-2 flex'>
- <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '>
- {product?.image && <Image src={product.image} alt={product.name} width={40} height={40} className='w-full h-full object-fill' />}
+ className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '
+ >
+ {product?.image && (
+ <Image
+ src={product.image}
+ alt={product.name}
+ width={40}
+ height={40}
+ className='w-full h-full object-fill'
+ />
+ )}
+ </Link>
+
+ <div className='ml-4 w-full flex flex-col gap-y-1'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product.parent.name,
+ product.parent.id.toString()
+ )}
+ className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'
+ >
+ {product.displayName}
</Link>
-
- <div className="ml-4 w-full flex flex-col gap-y-1">
- <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className="text-caption-2 font-medium text-gray-900 truncate dark:text-white">
- {product.displayName}
- </Link>
-
- <div className='flex w-full'>
- <div className="flex flex-col">
- {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
- <div>
- <span className="text-gray-500 text-caption-1">Berat Barang: </span>
- <span className="text-gray-500 text-caption-1">{product.packageWeight} Kg</span>
- </div>
- </div>
+
+ <div className='flex w-full'>
+ <div className='flex flex-col'>
+ {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
+ <div>
+ <span className='text-gray-500 text-caption-1'>
+ Berat Barang:{' '}
+ </span>
+ <span className='text-gray-500 text-caption-1'>
+ {product.packageWeight} Kg
+ </span>
</div>
</div>
-
</div>
- )}
+ </div>
</div>
+ ))}
+ </div>
</li>
</>
))}
@@ -347,8 +433,12 @@ const Cardheader = (cartCount) => {
{auth && products.length > 0 && !isLoading && (
<>
<div className='mt-3'>
- <span className='text-gray-400 text-caption-2'>Subtotal Sebelum PPN : </span>
- <span className='font-semibold text-red-600'>{currencyFormat(subTotal)}</span>
+ <span className='text-gray-400 text-caption-2'>
+ Subtotal Sebelum PPN :{' '}
+ </span>
+ <span className='font-semibold text-red-600'>
+ {currencyFormat(subTotal)}
+ </span>
</div>
<div className='mt-5 mb-2'>
<button
@@ -357,7 +447,9 @@ const Cardheader = (cartCount) => {
onClick={handleCheckout}
disabled={buttonLoading}
>
- {buttonLoading ? 'Loading...' : 'Lanjutkan Ke Pembayaran'}
+ {buttonLoading
+ ? 'Loading...'
+ : 'Lanjutkan Ke Pembayaran'}
</button>
</div>
</>
@@ -368,7 +460,7 @@ const Cardheader = (cartCount) => {
)}
</AnimatePresence>
</div>
- )
-}
+ );
+};
-export default Cardheader
+export default Cardheader;
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 4c7e852f..6fb5cdb4 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -37,6 +37,18 @@ const SELF_PICKUP_ID = 32;
const { checkoutApi } = require('../api/checkoutApi');
const { getProductsCheckout } = require('../api/checkoutApi');
+function convertToInternational(number) {
+ if (typeof number !== 'string') {
+ throw new Error("Input harus berupa string");
+ }
+
+ if (number.startsWith('08')) {
+ return '+62' + number.slice(2);
+ }
+
+ return number;
+}
+
const Checkout = () => {
const router = useRouter();
const query = router.query.source ?? null;
@@ -413,7 +425,12 @@ const Checkout = () => {
Math.round(parseInt(finalShippingAmt * 1.1) / 1000) * 1000;
const finalGT = GT < 0 ? 0 : GT;
setGrandTotal(finalGT);
- }, [biayaKirim, cartCheckout?.grandTotal, activeVoucher, activeVoucherShipping]);
+ }, [
+ biayaKirim,
+ cartCheckout?.grandTotal,
+ activeVoucher,
+ activeVoucherShipping,
+ ]);
const checkout = async () => {
const file = poFile.current.files[0];
@@ -442,6 +459,7 @@ const Checkout = () => {
const productOrder = products.map((product) => ({
product_id: product.id,
quantity: product.quantity,
+ available_quantity: product?.availableQuantity,
}));
let data = {
// partner_shipping_id: auth.partnerId,
@@ -483,6 +501,13 @@ const Checkout = () => {
transaction_id: isCheckouted.id,
});
+ gtag('set', 'user_data', {
+ email: auth.email,
+ phone_number: convertToInternational(auth.mobile) ?? convertToInternational(auth.phone),
+ });
+
+ gtag('config', 'AW-954540379', { ' allow_enhanced_conversions':true } ) ;
+
for (const product of products) deleteItemCart({ productId: product.id });
if (grandTotal > 0) {
const payment = await axios.post(
@@ -500,7 +525,7 @@ const Checkout = () => {
}
}
- /* const midtrans = async () => {
+ /* const midtrans = async () => {
for (const product of products) deleteItemCart({ productId: product.id });
if (grandTotal > 0) {
const payment = await axios.post(
@@ -1192,7 +1217,11 @@ const Checkout = () => {
<div className='text-gray_r-11'>
Biaya Kirim <p className='text-xs mt-1'>{etdFix}</p>
</div>
- <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000)}</div>
+ <div>
+ {currencyFormat(
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
</div>
{activeVoucherShipping && voucherShippingAmt && (
<div className='flex gap-x-2 justify-between'>
@@ -1493,7 +1522,11 @@ const Checkout = () => {
Biaya Kirim
<p className='text-xs mt-1'>{etdFix}</p>
</div>
- <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000) }</div>
+ <div>
+ {currencyFormat(
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
</div>
{activeVoucherShipping && voucherShippingAmt && (
<div className='flex gap-x-2 justify-between'>
@@ -1664,7 +1697,7 @@ const SectionAddress = ({ address, label, url }) => (
);
const SectionValidation = ({ address }) =>
- address?.rajaongkirCityId == 0 && (
+ address?.stateId == 0 && (
<BottomPopup active={true} title='Update Alamat'>
<div className='leading-7 text-gray_r-12/80'>
Mohon untuk memperbarui alamat Anda dengan mengklik tombol di bawah ini.{' '}
diff --git a/src/lib/checkout/components/FinishCheckout.jsx b/src/lib/checkout/components/FinishCheckout.jsx
index 92245e31..4a67b252 100644
--- a/src/lib/checkout/components/FinishCheckout.jsx
+++ b/src/lib/checkout/components/FinishCheckout.jsx
@@ -1,27 +1,86 @@
-import Link from '@/core/components/elements/Link/Link'
+import Link from 'next/link';
+import Image from '~/components/ui/image';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import { useEffect, useState } from 'react';
+import odooApi from '@/core/api/odooApi';
+import useDevice from '@/core/hooks/useDevice';
+import useAuth from '@/core/hooks/useAuth';
+import axios from 'axios';
+import { toast } from 'react-hot-toast';
const FinishCheckout = ({ query }) => {
+ const [data, setData] = useState();
+ const [transactionData, setTransactionData] = useState();
+ const { isDesktop, isMobile } = useDevice();
+ const auth = useAuth();
+
+ const so_order = query?.order_id?.replaceAll('-', '/');
+ useEffect(() => {
+ const fetchData = async () => {
+ const fetchedData = await odooApi(
+ 'GET',
+ `/api/v1/sale_order_number?sale_number=${so_order}`
+ );
+ setData(fetchedData[0]);
+ };
+ fetchData();
+ }, [query]);
+
+ // Kirim email ketika komponen ini dimount atau sesuai kondisi
+ const sendEmail = async () => {
+ try {
+ const send = await axios.post(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/finish-checkout?orderName=${query?.order_id}`,
+ {}
+ );
+ if (send.status === 200) {
+ toast.success('Berhasil mengirim rincian pesanan');
+ } else {
+ toast.error('Gagal mengirimkan rincian pesanan');
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error('Gagal mengirimkan rincian pesanan');
+ }
+ };
+
return (
- <div className='mx-auto container p-4 md:p-0 mt-0 md:mt-10'>
- <div className='rounded-xl bg-warning-100 text-center border border-warning-300 w-full md:w-1/2 mx-auto'>
- <div className='px-4 py-6 text-warning-900'>
- <p className='font-semibold mb-2'>Terima Kasih atas Pembelian Anda</p>
- <p className='text-warning-800 mb-4 leading-6'>
- Rincian belanja sudah kami kirimkan ke email anda. Mohon dicek kembali. jika tidak
- menerima email, anda dapat menghubungi kami disini.
- </p>
- <p className='mb-2 font-medium'>{query?.order_id?.replaceAll('-', '/')}</p>
- <p className='text-caption-2 text-warning-800'>No. Transaksi</p>
- </div>
+ <div className='flex flex-col items-center'>
+ <Image
+ src='/images/CHECKOUT-PESANAN.svg'
+ alt='Checkout Pesanan'
+ width={isMobile ? 300 : 450}
+ height={isMobile ? 300 : 450}
+ />
+ <div className='text-title-sm md:text-title-lg text-center font-semibold'>
+ Terima Kasih atas Pembelian Kamu
+ </div>
+ <div className='flex flex-col justify-center items-center text-body-2 md:text-body-1 text-center mt-3 px-24 md:px-36 py-4 border-2 gap-y-2 rounded'>
+ <p className='font-bold'>No. Transaksi</p>
+ <p className='mb-2 font-medium text-red-500 text-xl'>
+ {query?.order_id?.replaceAll('-', '/')}
+ </p>
<Link
- href='/my/quotations'
- className='bg-warning-400 text-warning-900 rounded-b-xl py-4 block'
+ href={`/my/quotations/${data?.id}`}
+ className='btn-solid-red rounded-md text-base'
>
- Lihat detail pembelian Anda disini
+ Cek Detail Transaksi
</Link>
</div>
+ <div className='mt-2 text-center leading-6 text-base p-4 md:p-0 md:max-w-[700px]'>
+ Rincian pembelian sudah kami kirimkan ke email kamu. Mohon dicek
+ kembali. jika tidak menerima email, kamu dapat menghubungi kami{' '}
+ <a className='text-red-500' href={whatsappUrl()}>
+ di sini
+ </a>{' '}
+ atau{' '}
+ <span onClick={sendEmail} className='text-red-500 cursor-pointer'>
+ kirim rincian pesanan ulang
+ </span>
+ .
+ </div>
</div>
- )
-}
+ );
+};
-export default FinishCheckout
+export default FinishCheckout;
diff --git a/src/lib/flashSale/components/FlashSale.jsx b/src/lib/flashSale/components/FlashSale.jsx
index 5be6d4e3..6d90cad7 100644
--- a/src/lib/flashSale/components/FlashSale.jsx
+++ b/src/lib/flashSale/components/FlashSale.jsx
@@ -2,10 +2,8 @@ import Image from 'next/image';
import { useEffect, useState } from 'react';
import CountDown from '@/core/components/elements/CountDown/CountDown';
-import productSearchApi from '@/lib/product/api/productSearchApi';
import ProductSlider from '@/lib/product/components/ProductSlider';
-import flashSaleApi from '../api/flashSaleApi';
import { FlashSaleSkeleton } from '../skeleton/FlashSaleSkeleton';
const FlashSale = () => {
@@ -14,10 +12,14 @@ const FlashSale = () => {
useEffect(() => {
const loadFlashSales = async () => {
- const dataFlashSales = await flashSaleApi();
- setFlashSales(dataFlashSales);
+ const res = await fetch('/api/flashsale-header');
+ const { data } = await res.json();
+ if (data) {
+ setFlashSales(data);
+ }
setIsLoading(false);
};
+
loadFlashSales();
}, []);
@@ -26,54 +28,64 @@ const FlashSale = () => {
}
return (
- flashSales?.length > 0 && (
- <div className='px-4 sm:px-0 grid grid-cols-1 gap-y-8'>
- {flashSales.map((flashSale, index) => (
- <div key={index}>
- <div className='flex gap-x-3 mb-4 justify-between sm:justify-start'>
- <div className='font-medium sm:text-h-lg mt-1.5'>
- {flashSale.name}
+ <div className='sm:mt-4'>
+ {flashSales?.length > 0 && (
+ <div className='px-4 sm:px-0 grid grid-cols-1 gap-y-8 sm:mt-4'>
+ {flashSales.map((flashSale, index) => (
+ <div key={index}>
+ <div className='flex gap-x-3 mb-4 justify-between sm:justify-start'>
+ <div className='font-medium sm:text-h-lg mt-1.5'>
+ {flashSale.name}
+ </div>
+ <CountDown initialTime={flashSale.duration} />
</div>
- <CountDown initialTime={flashSale.duration} />
- </div>
- <div className='relative'>
- <Image
- src={flashSale.banner}
- alt={flashSale.name}
- width={1080}
- height={192}
- className='w-full rounded mb-4 hidden sm:block'
- />
- <Image
- src={flashSale.bannerMobile}
- alt={flashSale.name}
- width={256}
- height={48}
- className='w-full rounded mb-4 block sm:hidden'
- />
- <FlashSaleProduct flashSaleId={flashSale.pricelistId} />
+ <div className='relative'>
+ <Image
+ src={flashSale.banner}
+ alt={flashSale.name}
+ width={1080}
+ height={192}
+ className='w-full rounded mb-4 hidden sm:block'
+ />
+ <Image
+ src={flashSale.bannerMobile}
+ alt={flashSale.name}
+ width={256}
+ height={48}
+ className='w-full rounded mb-4 block sm:hidden'
+ />
+ <FlashSaleProduct
+ flashSaleId={flashSale.pricelistId}
+ duration={flashSale.duration}
+ />
+ </div>
</div>
- </div>
- ))}
- </div>
- )
+ ))}
+ </div>
+ )}
+ </div>
);
};
-const FlashSaleProduct = ({ flashSaleId }) => {
+const FlashSaleProduct = ({ flashSaleId, duration }) => {
const [products, setProducts] = useState(null);
-
useEffect(() => {
+ const data_search = new URLSearchParams({
+ query: `fq=flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=500&orderBy=flashsale-price-asc&source=similar`,
+ operation: 'AND',
+ duration: `${duration}`,
+ });
const loadProducts = async () => {
- const dataProducts = await productSearchApi({
- query: `fq=flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=500&orderBy=flashsale-price-asc`,
- operation: 'AND',
- });
- setProducts(dataProducts.response);
+ const res = await fetch(
+ `/api/search-flashsale?${data_search.toString()}`
+ );
+ const { data } = await res.json();
+ setProducts(data.response);
};
+
loadProducts();
- }, [flashSaleId]);
+ }, []);
return <ProductSlider products={products} />;
};
diff --git a/src/lib/flashSale/components/FlashSaleNonDisplay.jsx b/src/lib/flashSale/components/FlashSaleNonDisplay.jsx
new file mode 100644
index 00000000..4b420fac
--- /dev/null
+++ b/src/lib/flashSale/components/FlashSaleNonDisplay.jsx
@@ -0,0 +1,68 @@
+import Image from 'next/image';
+import { useEffect, useState } from 'react';
+import CountDown from '@/core/components/elements/CountDown/CountDown';
+import productSearchApi from '@/lib/product/api/productSearchApi';
+import ProductSlider from '@/lib/product/components/ProductSlider';
+import flashSaleApi from '../api/flashSaleApi';
+import { FlashSaleSkeleton } from '../skeleton/FlashSaleSkeleton';
+import Link from 'next/link';
+import { useRouter } from 'next/router';
+const FlashSaleNonDisplay = () => {
+ const [flashSales, setFlashSales] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const router = useRouter();
+ useEffect(() => {
+ const loadFlashSales = async () => {
+ const dataFlashSales = await flashSaleApi();
+ setFlashSales(dataFlashSales);
+ setIsLoading(false);
+ };
+ loadFlashSales();
+ }, []);
+ const handleSubmit = () => {
+ router.push(`/shop/search?penawaran=${flashSales[0]?.pricelistId}`);
+ };
+ if (isLoading) {
+ return <FlashSaleSkeleton />;
+ }
+
+ return (
+ flashSales?.length > 0 && (
+ <div className='px-4 sm:px-0 grid grid-cols-1 gap-y-8'>
+ {flashSales.map((flashSale, index) => (
+ <div key={index}>
+ <div className='flex items-center mb-4 justify-between '>
+ <div className='font-medium sm:text-h-lg mt-1.5'>
+ Penawaran Terbatas
+ </div>
+ <div
+ onClick={handleSubmit}
+ className='!text-red-500 font-semibold cursor-pointer'
+ >
+ Lihat Semua
+ </div>
+ </div>
+ <div className='relative'>
+ <FlashSaleProduct flashSaleId={flashSale.pricelistId} />
+ </div>
+ </div>
+ ))}
+ </div>
+ )
+ );
+};
+const FlashSaleProduct = ({ flashSaleId }) => {
+ const [products, setProducts] = useState(null);
+ useEffect(() => {
+ const loadProducts = async () => {
+ const dataProducts = await productSearchApi({
+ query: `fq=-flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=25&orderBy=flashsale-discount-desc&source=similar`,
+ operation: 'AND',
+ });
+ setProducts(dataProducts.response);
+ };
+ loadProducts();
+ }, [flashSaleId]);
+ return <ProductSlider products={products} />;
+};
+export default FlashSaleNonDisplay;
diff --git a/src/lib/home/api/categoryManagementApi.js b/src/lib/home/api/categoryManagementApi.js
index 2ff4fdfc..4101f87a 100644
--- a/src/lib/home/api/categoryManagementApi.js
+++ b/src/lib/home/api/categoryManagementApi.js
@@ -42,3 +42,11 @@ const map = async (promotions) => {
return productMapped;
});
};
+
+export const fetchCategoryManagementVersion = async () => {
+ const response = await fetch(
+ '/solr/admin/cores?action=STATUS&core=category_management'
+ );
+ const data = await response.json();
+ return data.status.category_management.index.version;
+};
diff --git a/src/lib/home/components/BannerSection.jsx b/src/lib/home/components/BannerSection.jsx
index f83c36fc..303b5c4b 100644
--- a/src/lib/home/components/BannerSection.jsx
+++ b/src/lib/home/components/BannerSection.jsx
@@ -1,18 +1,48 @@
import Link from '@/core/components/elements/Link/Link';
import Image from 'next/image';
+import { useEffect, useState } from 'react';
+import { bannerApi } from '../../../api/bannerApi';
const { useQuery } = require('react-query');
const { default: bannerSectionApi } = require('../api/bannerSectionApi');
const BannerSection = () => {
- const fetchBannerSection = async () => await bannerSectionApi();
- const bannerSection = useQuery('bannerSection', fetchBannerSection);
+ const [data, setData] = useState(null);
+ const [shouldFetch, setShouldFetch] = useState(false);
+ useEffect(() => {
+ const fetchCategoryData = async () => {
+ const res = await fetch('/api/banner-section');
+ const { data } = await res.json();
+ if (data) {
+ setData(data);
+ }
+ };
+
+ fetchCategoryData();
+ }, []);
+
+ // const fetchBannerSection = async () => await bannerSectionApi();
+ const getBannerSection = useQuery(
+ 'bannerSection',
+ bannerApi({ type: 'home-banner' }),
+ {
+ enabled: shouldFetch,
+ onSuccess: (data) => {
+ if (data) {
+ localStorage.setItem('Homepage_bannerSection', JSON.stringify(data));
+ setData(data);
+ }
+ },
+ }
+ );
+
+ const bannerSection = data;
return (
- bannerSection.data &&
- bannerSection.data?.length > 0 && (
+ bannerSection &&
+ bannerSection?.length > 0 && (
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
- {bannerSection.data?.map((banner) => (
+ {bannerSection?.map((banner) => (
<Link key={banner.id} href={banner.url}>
<Image
width={1024}
diff --git a/src/lib/home/components/CategoryDynamic.jsx b/src/lib/home/components/CategoryDynamic.jsx
index 49a9a93f..cc4f42b7 100644
--- a/src/lib/home/components/CategoryDynamic.jsx
+++ b/src/lib/home/components/CategoryDynamic.jsx
@@ -1,9 +1,9 @@
-import React, { useEffect, useState, useCallback } from 'react';
+import React, { useEffect, useState } from 'react';
import { fetchCategoryManagementSolr } from '../api/categoryManagementApi';
+import { Skeleton } from '@chakra-ui/react';
import NextImage from 'next/image';
import Link from 'next/link';
import { createSlug } from '@/core/utils/slug';
-import { Skeleton } from '@chakra-ui/react';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import 'swiper/css/navigation';
@@ -12,45 +12,21 @@ import { Pagination } from 'swiper';
const CategoryDynamic = () => {
const [categoryManagement, setCategoryManagement] = useState([]);
- const [isLoading, setIsLoading] = useState(false);
- const loadBrand = useCallback(async () => {
- setIsLoading(true);
- const items = await fetchCategoryManagementSolr();
-
- setIsLoading(false);
- setCategoryManagement(items);
- }, []);
+ const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
- loadBrand();
- }, [loadBrand]);
-
- // const [categoryData, setCategoryData] = useState({});
- // const [subCategoryData, setSubCategoryData] = useState({});
-
- // useEffect(() => {
- // const fetchCategoryData = async () => {
- // if (categoryManagement && categoryManagement.data) {
- // const updatedCategoryData = {};
- // const updatedSubCategoryData = {};
+ const fetchCategoryData = async () => {
+ setIsLoading(true);
+ const res = await fetch('/api/category-management');
+ const { data } = await res.json();
+ if (data) {
+ setCategoryManagement(data);
+ }
+ setIsLoading(false);
+ };
- // for (const category of categoryManagement.data) {
- // const countLevel1 = await odooApi('GET', `/api/v1/category/numFound?parent_id=${category.categoryIdI}`);
-
- // updatedCategoryData[category.categoryIdI] = countLevel1?.numFound;
-
- // for (const subCategory of countLevel1?.children) {
- // updatedSubCategoryData[subCategory.id] = subCategory?.numFound;
- // }
- // }
-
- // setCategoryData(updatedCategoryData);
- // setSubCategoryData(updatedSubCategoryData);
- // }
- // };
-
- // fetchCategoryData();
- // }, [categoryManagement.isLoading]);
+ fetchCategoryData();
+ }, []);
const swiperBanner = {
modules: [Pagination],
@@ -66,115 +42,99 @@ const CategoryDynamic = () => {
return (
<div>
{categoryManagement &&
- categoryManagement?.map((category) => {
- // const countLevel1 = categoryData[category.categoryIdI] || 0;
- return (
- <Skeleton key={category.id} isLoaded={!isLoading}>
- <div key={category.id}>
- <div className='bagian-judul flex flex-row justify-start items-center gap-3 mb-4 mt-4'>
- <h1 className='font-semibold text-[14px] sm:text-h-lg mr-2'>
- {category.name}
- </h1>
- {/* <Skeleton isLoaded={countLevel1 != 0}>
- <p className={`text-gray_r-10 text-sm`}>{countLevel1} Produk tersedia</p>
- </Skeleton> */}
- <Link
- href={createSlug(
- '/shop/category/',
- category?.name,
- category?.category_id
- )}
- className='!text-red-500 font-semibold'
- >
- Lihat Semua
- </Link>
- </div>
-
- {/* Swiper for SubCategories */}
- <Swiper {...swiperBanner}>
- {category.categories.map((subCategory) => {
- // const countLevel2 = subCategoryData[subCategory.idLevel2] || 0;
+ categoryManagement.map((category) => (
+ <Skeleton key={category.id} isLoaded={!isLoading}>
+ <div key={category.id}>
+ <div className='bagian-judul flex flex-row justify-start items-center gap-3 mb-4 mt-4'>
+ <h1 className='font-semibold text-[14px] sm:text-h-lg mr-2'>
+ {category.name}
+ </h1>
+ <Link
+ href={createSlug(
+ '/shop/category/',
+ category?.name,
+ category?.category_id
+ )}
+ className='!text-red-500 font-semibold'
+ >
+ Lihat Semua
+ </Link>
+ </div>
- return (
- <SwiperSlide key={subCategory.id}>
- <div className='border rounded justify-start items-start '>
- <div className='p-3'>
- <div className='flex flex-row border rounded mb-2 justify-start items-center'>
- <NextImage
- src={
- subCategory.image
- ? subCategory.image
- : '/images/noimage.jpeg'
- }
- alt={subCategory.name}
- width={90}
- height={30}
- className='object-fit p-4'
- />
- <div className='bagian-judul flex flex-col justify-center items-start gap-2 ml-2'>
- <h2 className='font-semibold text-lg mr-2'>
- {subCategory?.name}
- </h2>
- {/* <Skeleton isLoaded={countLevel2 != 0}>
- <p className={`text-gray_r-10 text-sm`}>
- {countLevel2} Produk tersedia
- </p>
- </Skeleton> */}
+ <Swiper {...swiperBanner}>
+ {category?.categories?.map((subCategory) => (
+ <SwiperSlide key={subCategory.id}>
+ <div className='border rounded justify-start items-start '>
+ <div className='p-3'>
+ <div className='flex flex-row border rounded mb-2 justify-start items-center'>
+ <NextImage
+ src={
+ subCategory.image
+ ? subCategory.image
+ : '/images/noimage.jpeg'
+ }
+ alt={subCategory.name}
+ width={90}
+ height={30}
+ className='object-fit p-4'
+ />
+ <div className='bagian-judul flex flex-col justify-center items-start gap-2 ml-2'>
+ <h2 className='font-semibold text-lg mr-2'>
+ {subCategory?.name}
+ </h2>
+ <Link
+ href={createSlug(
+ '/shop/category/',
+ subCategory?.name,
+ subCategory?.id_level_2
+ )}
+ className='!text-red-500 font-semibold'
+ >
+ Lihat Semua
+ </Link>
+ </div>
+ </div>
+ <div className='grid grid-cols-2 gap-2 overflow-y-auto max-h-[240px] min-h-[240px] content-start'>
+ {subCategory.child_frontend_id_i.map(
+ (childCategory) => (
+ <div key={childCategory.id} className=''>
<Link
href={createSlug(
'/shop/category/',
- subCategory?.name,
- subCategory?.id_level_2
+ childCategory?.name,
+ childCategory?.id_level_3
)}
- className='!text-red-500 font-semibold'
+ className='flex flex-row gap-2 border rounded group hover:border-red-500'
>
- Lihat Semua
+ <NextImage
+ src={
+ childCategory.image
+ ? childCategory.image
+ : '/images/noimage.jpeg'
+ }
+ alt={childCategory.name}
+ className='p-2 ml-1'
+ width={40}
+ height={40}
+ />
+ <div className='bagian-judul flex flex-col justify-center items-center gap-2 break-words line-clamp-2 group-hover:text-red-500'>
+ <h3 className='font-semibold line-clamp-2 group-hover:text-red-500 text-sm mr-2'>
+ {childCategory.name}
+ </h3>
+ </div>
</Link>
</div>
- </div>
- <div className='grid grid-cols-2 gap-2 overflow-y-auto max-h-[240px] min-h-[240px] content-start'>
- {subCategory.child_frontend_id_i.map(
- (childCategory) => (
- <div key={childCategory.id} className=''>
- <Link
- href={createSlug(
- '/shop/category/',
- childCategory?.name,
- childCategory?.id_level_3
- )}
- className='flex flex-row gap-2 border rounded group hover:border-red-500'
- >
- <NextImage
- src={
- childCategory.image
- ? childCategory.image
- : '/images/noimage.jpeg'
- }
- alt={childCategory.name}
- className='p-2 ml-1'
- width={40}
- height={40}
- />
- <div className='bagian-judul flex flex-col justify-center items-center gap-2 break-words line-clamp-2 group-hover:text-red-500'>
- <h3 className='font-semibold line-clamp-2 group-hover:text-red-500 text-sm mr-2'>
- {childCategory.name}
- </h3>
- </div>
- </Link>
- </div>
- )
- )}
- </div>
- </div>
+ )
+ )}
</div>
- </SwiperSlide>
- );
- })}
- </Swiper>
- </div>
- </Skeleton>
- );
- })}
+ </div>
+ </div>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+ </Skeleton>
+ ))}
</div>
);
};
diff --git a/src/lib/home/components/CategoryDynamicMobile.jsx b/src/lib/home/components/CategoryDynamicMobile.jsx
index 4a8f13cf..67ae6f5f 100644
--- a/src/lib/home/components/CategoryDynamicMobile.jsx
+++ b/src/lib/home/components/CategoryDynamicMobile.jsx
@@ -4,52 +4,46 @@ import Link from 'next/link';
import { createSlug } from '@/core/utils/slug';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
-import { fetchCategoryManagementSolr } from '../api/categoryManagementApi';
+import {
+ fetchCategoryManagementSolr,
+ fetchCategoryManagementVersion,
+} from '../api/categoryManagementApi';
const CategoryDynamicMobile = () => {
const [selectedCategory, setSelectedCategory] = useState({});
const [categoryManagement, setCategoryManagement] = useState([]);
const [isLoading, setIsLoading] = useState(false);
- const loadBrand = useCallback(async () => {
- setIsLoading(true);
- const items = await fetchCategoryManagementSolr();
+ useEffect(() => {
+ const fetchCategoryData = async () => {
+ setIsLoading(true);
+ const res = await fetch('/api/category-management');
+ const { data } = await res.json();
+ if (data) {
+ setCategoryManagement(data);
+ }
+ setIsLoading(false);
+ };
- setIsLoading(false);
- setCategoryManagement(items);
+ fetchCategoryData();
}, []);
useEffect(() => {
- loadBrand();
- }, [loadBrand]);
-
- useEffect(() => {
- const loadPromo = async () => {
- try {
- if (categoryManagement?.length > 0) {
- const initialSelections = categoryManagement.reduce(
- (acc, category) => {
- if (category.categories.length > 0) {
- acc[category.id] = category.categories[0].id_level_2;
- }
- return acc;
- },
- {}
- );
- setSelectedCategory(initialSelections);
+ if (categoryManagement?.length > 0) {
+ const initialSelections = categoryManagement.reduce((acc, category) => {
+ if (category.categories.length > 0) {
+ acc[category.id] = category.categories[0].id_level_2;
}
- } catch (loadError) {
- // console.error("Error loading promo items:", loadError);
- }
- };
-
- loadPromo();
+ return acc;
+ }, {});
+ setSelectedCategory(initialSelections);
+ }
}, [categoryManagement]);
- const handleCategoryLevel2Click = (categoryIdI, idLevel2) => {
+ const handleCategoryLevel2Click = (categoryId, idLevel2) => {
setSelectedCategory((prev) => ({
...prev,
- [categoryIdI]: idLevel2,
+ [categoryId]: idLevel2,
}));
};
diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx
index eefced60..b7a30503 100644
--- a/src/lib/home/components/PreferredBrand.jsx
+++ b/src/lib/home/components/PreferredBrand.jsx
@@ -1,49 +1,50 @@
-import { Swiper, SwiperSlide } from 'swiper/react'
-import { Navigation, Pagination, Autoplay } from 'swiper';
-import { useCallback, useEffect, useState } from 'react'
-import usePreferredBrand from '../hooks/usePreferredBrand'
-import PreferredBrandSkeleton from './Skeleton/PreferredBrandSkeleton'
-import BrandCard from '@/lib/brand/components/BrandCard'
-import useDevice from '@/core/hooks/useDevice'
-import Link from '@/core/components/elements/Link/Link'
-import axios from 'axios'
+import { Swiper, SwiperSlide } from 'swiper/react';
+import { Navigation, Pagination, Autoplay } from 'swiper';
+import { useCallback, useEffect, useState } from 'react';
+import usePreferredBrand from '../hooks/usePreferredBrand';
+import PreferredBrandSkeleton from './Skeleton/PreferredBrandSkeleton';
+import BrandCard from '@/lib/brand/components/BrandCard';
+import useDevice from '@/core/hooks/useDevice';
+import Link from '@/core/components/elements/Link/Link';
+import axios from 'axios';
const PreferredBrand = () => {
- let query = ''
- let params = 'prioritas'
- const [isLoading, setIsLoading] = useState(true)
- const [startWith, setStartWith] = useState(null)
- const [manufactures, setManufactures] = useState([])
+ let query = '';
+ let params = 'prioritas';
+ const [isLoading, setIsLoading] = useState(true);
+ const [startWith, setStartWith] = useState(null);
+ const [manufactures, setManufactures] = useState([]);
const loadBrand = useCallback(async () => {
- setIsLoading(true)
- const name = startWith ? `${startWith}*` : ''
- const result = await axios(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/brands?rows=20`)
-
- setIsLoading(false)
- setManufactures((manufactures) => [...result.data])
- }, [startWith])
+ setIsLoading(true);
+ const name = startWith ? `${startWith}*` : '';
+ const result = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/preferredBrand?rows=20`
+ );
+ setIsLoading(false);
+ setManufactures((manufactures) => [...result.data]);
+ }, [startWith]);
const toggleStartWith = (alphabet) => {
- setManufactures([])
+ setManufactures([]);
if (alphabet == startWith) {
- setStartWith(null)
- return
+ setStartWith(null);
+ return;
}
- setStartWith(alphabet)
- }
+ setStartWith(alphabet);
+ };
useEffect(() => {
- loadBrand()
- }, [])
+ loadBrand();
+ }, []);
// const { preferredBrands } = usePreferredBrand(query)
- const { isMobile, isDesktop } = useDevice()
+ const { isMobile, isDesktop } = useDevice();
const swiperBanner = {
- modules:[Navigation, Pagination, Autoplay],
+ modules: [Navigation, Pagination, Autoplay],
autoplay: {
delay: 4000,
- disableOnInteraction: false
+ disableOnInteraction: false,
},
loop: true,
className: 'h-[70px] md:h-[100px] w-full',
@@ -53,13 +54,17 @@ const PreferredBrand = () => {
dynamicBullets: true,
dynamicMainBullets: isMobile ? 6 : 8,
clickable: true,
- }
- }
- const preferredBrandsData = manufactures ? manufactures.slice(0, 20) : []
+ },
+ };
+ const preferredBrandsData = manufactures ? manufactures.slice(0, 20) : [];
return (
<div className='px-4 sm:px-0'>
<div className='flex justify-between items-center mb-4'>
- <h1 className='font-semibold text-[14px] sm:text-h-lg'><Link href='/shop/brands' className='!text-black font-semibold'>Brand Pilihan</Link></h1>
+ <h1 className='font-semibold text-[14px] sm:text-h-lg'>
+ <Link href='/shop/brands' className='!text-black font-semibold'>
+ Brand Pilihan
+ </Link>
+ </h1>
{isDesktop && (
<Link href='/shop/brands' className='!text-red-500 font-semibold'>
Lihat Semua
@@ -79,7 +84,7 @@ const PreferredBrand = () => {
)}
</div>
</div>
- )
-}
+ );
+};
-export default PreferredBrand \ No newline at end of file
+export default PreferredBrand;
diff --git a/src/lib/home/components/PromotionProgram.jsx b/src/lib/home/components/PromotionProgram.jsx
index ae06bd4d..562fa138 100644
--- a/src/lib/home/components/PromotionProgram.jsx
+++ b/src/lib/home/components/PromotionProgram.jsx
@@ -4,15 +4,56 @@ import { bannerApi } from '@/api/bannerApi';
import useDevice from '@/core/hooks/useDevice';
import { Swiper, SwiperSlide } from 'swiper/react';
import BannerPromoSkeleton from '../components/Skeleton/BannerPromoSkeleton';
+import { useEffect, useState } from 'react';
const { useQuery } = require('react-query');
const BannerSection = () => {
- const promotionProgram = useQuery(
- 'promotionProgram',
- bannerApi({ type: 'banner-promotion' })
- );
const { isMobile, isDesktop } = useDevice();
+ const [data, setData] = useState(null);
+ const [shouldFetch, setShouldFetch] = useState(false);
+ useEffect(() => {
+ const fetchData = async () => {
+ const res = await fetch(`/api/hero-banner?type=banner-promotion`);
+ const { data } = await res.json();
+ if (data) {
+ setData(data);
+ }
+ };
+
+ fetchData();
+ }, []);
+
+ // useEffect(() => {
+ // const localData = localStorage.getItem('Homepage_promotionProgram');
+ // if (localData) {
+ // setData(JSON.parse(localData));
+ // } else {
+ // setShouldFetch(true);
+ // }
+ // }, []);
+
+ // const getPromotionProgram = useQuery(
+ // 'promotionProgram',
+ // bannerApi({ type: 'banner-promotion' }),
+ // {
+ // enabled: shouldFetch,
+ // onSuccess: (data) => {
+ // if (data) {
+ // localStorage.setItem(
+ // 'Homepage_promotionProgram',
+ // JSON.stringify(data)
+ // );
+ // setData(data);
+ // }
+ // },
+ // }
+ // );
+
+ const promotionProgram = data;
- if (promotionProgram.isLoading) {
+ // if (getPromotionProgram?.isLoading && !data) {
+ // return <BannerPromoSkeleton />;
+ // }
+ if (!data) {
return <BannerPromoSkeleton />;
}
@@ -39,28 +80,26 @@ const BannerSection = () => {
</Link>
)}
</div>
- {isDesktop &&
- promotionProgram.data &&
- promotionProgram.data?.length > 0 && (
- <div className='grid grid-cols-3 sm:grid-cols-3 gap-4 rounded-md'>
- {promotionProgram.data?.map((banner) => (
- <Link key={banner.id} href={banner.url}>
- <Image
- width={439}
- height={150}
- quality={85}
- src={banner.image}
- alt={banner.name}
- className='h-auto w-full rounded hover:scale-105 transition duration-500 ease-in-out'
- />
- </Link>
- ))}
- </div>
- )}
+ {isDesktop && promotionProgram && promotionProgram?.length > 0 && (
+ <div className='grid grid-cols-3 sm:grid-cols-3 gap-4 rounded-md'>
+ {promotionProgram?.map((banner) => (
+ <Link key={banner.id} href={banner.url}>
+ <Image
+ width={439}
+ height={150}
+ quality={85}
+ src={banner.image}
+ alt={banner.name}
+ className='h-auto w-full rounded hover:scale-105 transition duration-500 ease-in-out'
+ />
+ </Link>
+ ))}
+ </div>
+ )}
{isMobile && (
<Swiper slidesPerView={1.1} spaceBetween={8} freeMode>
- {promotionProgram.data?.map((banner) => (
+ {promotionProgram?.map((banner) => (
<SwiperSlide key={banner.id}>
<Link key={banner.id} href={banner.url}>
<Image
diff --git a/src/lib/home/components/ServiceList.jsx b/src/lib/home/components/ServiceList.jsx
index 5b16915d..b3cc8fe5 100644
--- a/src/lib/home/components/ServiceList.jsx
+++ b/src/lib/home/components/ServiceList.jsx
@@ -32,7 +32,7 @@ const ServiceList = () => {
</div>
<div className='w-full'>
<Link
- href='/tentang-kami'
+ href='/garansi-resmi'
className='border border-gray-200 p-2 flex items-center gap-x-2 rounded-lg'
>
<div className=''>
@@ -57,7 +57,7 @@ const ServiceList = () => {
</div>
<div className='w-full '>
<Link
- href='/tentang-kami'
+ href='/pembayaran-tempo'
className='border border-gray-200 p-2 flex items-center gap-x-2 rounded-lg'
>
<div className=''>
diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx
index 09b30a44..5dfd452b 100644
--- a/src/lib/product/components/Product/ProductDesktopVariant.jsx
+++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx
@@ -1,12 +1,12 @@
-
-import { Box, Skeleton, Tooltip } from '@chakra-ui/react';
+import { Box, Button, Skeleton, Tooltip } from '@chakra-ui/react';
import { HeartIcon } from '@heroicons/react/24/outline';
-import { Info } from 'lucide-react';
+import { Info, MessageCircleIcon, Share2Icon } from 'lucide-react';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'react-hot-toast';
+import AddToWishlist from '../../../../../src-migrate/modules/product-detail/components/AddToWishlist';
+import { RWebShare } from 'react-web-share';
import LazyLoad from 'react-lazy-load';
-
import { useProductCartContext } from '@/contexts/ProductCartContext';
import odooApi from '@/core/api/odooApi';
import Image from '@/core/components/elements/Image/Image';
@@ -18,10 +18,16 @@ import { updateItemCart } from '@/core/utils/cart';
import currencyFormat from '@/core/utils/currencyFormat';
import { createSlug } from '@/core/utils/slug';
import whatsappUrl from '@/core/utils/whatsappUrl';
+import { getAuth } from '~/libs/auth';
+import ImageNext from 'next/image';
import productSimilarApi from '../../api/productSimilarApi';
import ProductCard from '../ProductCard';
import ProductSimilar from '../ProductSimilar';
+import ProductPromoSection from '~/modules/product-promo/components/Section';
+import SimilarBottom from '~/modules/product-detail/components/SimilarBottom';
+
+const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST;
const ProductDesktopVariant = ({
product,
@@ -30,9 +36,10 @@ const ProductDesktopVariant = ({
isVariant,
}) => {
const router = useRouter();
- const auth = useAuth();
+ let auth = useAuth();
const { slug } = router.query;
-
+ const { srsltid } = router.query;
+ const [askAdminUrl, setAskAdminUrl, isApproval] = useState();
const [lowestPrice, setLowestPrice] = useState(null);
const [addCartAlert, setAddCartAlert] = useState(false);
@@ -40,11 +47,20 @@ const ProductDesktopVariant = ({
const { setRefreshCart } = useProductCartContext();
+ const [quantityInput, setQuantityInput] = useState(1);
+
+ const createdAskUrl = whatsappUrl({
+ template: 'product',
+ payload: {
+ manufacture: product.manufacture.name,
+ productName: product.name,
+ url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath,
+ },
+ fallbackUrl: router.asPath,
+ });
+
const getLowestPrice = useCallback(() => {
const lowest = product.price;
- /* const lowest = prices.reduce((lowest, price) => {
- return price.priceDiscount < lowest.priceDiscount ? price : lowest
- }, prices[0])*/
return lowest;
}, [product]);
@@ -74,10 +90,10 @@ const ProductDesktopVariant = ({
const handleAddToCart = (variant) => {
if (!auth) {
- router.push(`/login?next=/shop/product/${slug}`);
+ router.push(`/login?next=/shop/product/${slug}?srsltid=${srsltid}`);
return;
}
- const quantity = variantQuantityRefs.current[product.id].value;
+ const quantity = quantityInput;
if (!validQuantity(quantity)) return;
updateItemCart({
productId: product.id,
@@ -91,8 +107,34 @@ const ProductDesktopVariant = ({
setAddCartAlert(true);
};
- const handleBuy = (variant) => {
- const quantity = variantQuantityRefs.current[product.id].value;
+ const handleBuy = async (variant) => {
+ const quantity = variantQuantityRefs?.current[product.id]?.value;
+ let isLoggedIn = typeof auth === 'object';
+
+ if (!isLoggedIn) {
+ const currentUrl = encodeURIComponent(router.asPath);
+ await router.push(`/login?next=${currentUrl}`);
+
+ // Tunggu login berhasil, misalnya dengan memantau perubahan status auth.
+ const authCheckInterval = setInterval(() => {
+ const newAuth = getAuth();
+ if (typeof newAuth === 'object') {
+ isLoggedIn = true;
+ auth = newAuth; // Update nilai auth setelah login
+ clearInterval(authCheckInterval);
+ }
+ }, 500); // Periksa status login setiap 500ms
+
+ await new Promise((resolve) => {
+ const checkLogin = setInterval(() => {
+ if (isLoggedIn) {
+ clearInterval(checkLogin);
+ resolve(null);
+ }
+ }, 500);
+ });
+ }
+
if (!validQuantity(quantity)) return;
updateItemCart({
@@ -105,6 +147,45 @@ const ProductDesktopVariant = ({
router.push(`/shop/checkout?source=buy`);
};
+ const handleButton = async (variant) => {
+ const quantity = quantityInput;
+ let isLoggedIn = typeof auth === 'object';
+
+ if (!isLoggedIn) {
+ const currentUrl = encodeURIComponent(router.asPath);
+ await router.push(`/login?next=${currentUrl}`);
+
+ // Tunggu login berhasil, misalnya dengan memantau perubahan status auth.
+ const authCheckInterval = setInterval(() => {
+ const newAuth = getAuth();
+ if (typeof newAuth === 'object') {
+ isLoggedIn = true;
+ auth = newAuth; // Update nilai auth setelah login
+ clearInterval(authCheckInterval);
+ }
+ }, 500); // Periksa status login setiap 500ms
+
+ await new Promise((resolve) => {
+ const checkLogin = setInterval(() => {
+ if (isLoggedIn) {
+ clearInterval(checkLogin);
+ resolve(null);
+ }
+ }, 500);
+ });
+ }
+ if (!validQuantity(quantity)) return;
+
+ updateItemCart({
+ productId: variant,
+ quantity,
+ programLineId: null,
+ selected: true,
+ source: 'buy',
+ });
+ router.push('/shop/quotation?source=buy');
+ };
+
const variantSectionRef = useRef(null);
const goToVariantSection = () => {
if (variantSectionRef.current) {
@@ -160,87 +241,45 @@ const ProductDesktopVariant = ({
<Image
src={product.image + '?variant=True'}
alt={product.name}
- className='h-[430px] object-contain object-center w-full border border-gray_r-4'
+ className='w-full h-[350px]'
/>
</div>
- <div className='w-7/12 px-4'>
+ <div className='w-7/12 px-6'>
<h1 className='text-title-md leading-10 font-medium'>
{product?.name}
</h1>
<div className='mt-10'>
- <div className='flex p-3'>
- <div className='w-4/12 text-gray_r-12/70'>Nomor SKU</div>
- <div className='w-8/12'>SKU-{product.id}</div>
- </div>
<div className='flex p-3 bg-gray_r-4'>
- <div className='w-4/12 text-gray_r-12/70'>Part Number</div>
- <div className='w-8/12'>{product.code || '-'}</div>
+ <div className='w-4/12 text-gray_r-12/70'>Item Code</div>
+ <div className='w-8/12'>{product.code}</div>
</div>
- <div className='flex p-3'>
+ <div className='flex p-3 items-center '>
<div className='w-4/12 text-gray_r-12/70'>Manufacture</div>
<div className='w-8/12'>
- {product.manufacture?.name ? (
- <Link
- href={createSlug(
- '/shop/brands/',
- product.manufacture?.name,
- product.manufacture?.id
- )}
- >
- {product.manufacture?.name}
- </Link>
- ) : (
- <div>-</div>
- )}
- </div>
- </div>
-
- <div className='flex p-3 items-center bg-gray_r-4'>
- <div className='w-4/12 text-gray_r-12/70'>
- Persiapan Barang
- </div>
- <div className='w-8/12'>
- {!product?.sla && <Skeleton width='20%' height='16px' />}
- {product?.sla && (
- <Tooltip
- placement='top'
- label={`Masa Persiapan Barang ${product?.sla?.slaDate}`}
- >
- <Box className='w-fit flex items-center gap-x-2'>
- {product?.sla?.slaDate}
- <Info size={16} />
- </Box>
- </Tooltip>
- )}
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture.name,
+ product.manufacture.id.toString()
+ )}
+ >
+ {product?.manufacture.logo ? (
+ <Image
+ width={100}
+ src={product.manufacture.logo}
+ alt={product.manufacture.name}
+ />
+ ) : (
+ <p className='font-bold text-red-500'>
+ {product.manufacture.name}
+ </p>
+ )}
+ </Link>
</div>
</div>
- <div className='flex p-3'>
- <div className='w-4/12 text-gray_r-12/70'>Stock</div>
- <div className='w-8/12'>
- {!product?.sla && <Skeleton width='10%' height='16px' />}
- {product?.sla?.qty > 0 && <span>{product?.sla?.qty}</span>}
- {product?.sla?.qty == 0 && (
- <a
- href={whatsappUrl('product', {
- name: product.name,
- manufacture: product?.manufacture?.name,
- url: createSlug(
- '/shop/product/',
- product.name,
- product.id,
- true
- ),
- })}
- className='text-danger-500 font-medium'
- >
- Tanya Admin
- </a>
- )}
- </div>
- </div>
- <div className='flex p-3 bg-gray_r-4'>
+ <div className='flex p-3 bg-gray_r-4 '>
<div className='w-4/12 text-gray_r-12/70'>Berat Barang</div>
<div className='w-8/12'>
{product?.weight > 0 && <span>{product?.weight} KG</span>}
@@ -262,24 +301,55 @@ const ProductDesktopVariant = ({
)}
</div>
</div>
+ <div className='flex p-3 items-center '>
+ <div className='w-4/12 text-gray_r-12/70'>Terjual</div>
+ <div className='w-8/12'>-</div>
+ </div>
+
+ <div className='flex p-3 items-center bg-gray_r-4 '>
+ <div className='w-4/12 text-gray_r-12/70'>
+ Persiapan Barang
+ </div>
+ <div className='w-8/12'>
+ {!product?.sla && <Skeleton width='20%' height='16px' />}
+ {product?.sla && (
+ <Tooltip
+ placement='top'
+ label={`Masa Persiapan Barang ${product?.sla?.slaDate}`}
+ >
+ <Box className='w-fit flex items-center gap-x-2'>
+ {product?.sla?.slaDate}
+ <Info size={16} />
+ </Box>
+ </Tooltip>
+ )}
+ </div>
+ </div>
</div>
</div>
- <div className='p-4 md:p-6 md:bg-gray-50 rounded-xl'>
- <h2 className='text-h-md md:text-h-lg font-medium'>Informasi Produk</h2>
- <div className='h-4' />
- <div
- className='leading-relaxed text-gray-700'
- dangerouslySetInnerHTML={{
- __html:
- !product.parent.description || product.parent.description == '<p><br></p>'
- ? 'Belum ada deskripsi'
- : product.parent.description,
- }}
- />
+ <div className='p-4 md:p-6 w-full'>
+ <ProductPromoSection product={product} productId={product.id} />
+
+ <div className='p-4 md:p-6 md:bg-gray-50 rounded-xl'>
+ <h2 className='text-h-md md:text-h-lg font-medium'>
+ Informasi Produk
+ </h2>
+ <div className='h-4' />
+ <div
+ className='leading-relaxed text-gray-700'
+ dangerouslySetInnerHTML={{
+ __html:
+ !product.parent.description ||
+ product.parent.description == '<p><br></p>'
+ ? 'Belum ada deskripsi'
+ : product.parent.description,
+ }}
+ />
+ </div>
</div>
</div>
- <div className='w-[25%]'>
+ <div className='w-[33%]'>
{product?.isFlashsale > 0 &&
product?.price?.discountPercentage > 0 ? (
<>
@@ -337,46 +407,143 @@ const ProductDesktopVariant = ({
)}
</h3>
)}
- <div className='flex gap-x-3 mt-4'>
- <input
- type='number'
- className='form-input w-16 py-2 text-center bg-gray_r-1'
- ref={setVariantQuantityRef(product.id)}
- defaultValue={1}
- />
- <button
- type='button'
+ <div className='flex justify-between items-center py-5 px-3'>
+ <div className='relative flex items-center'>
+ <button
+ type='button'
+ className='absolute left-0 px-2 py-1 h-full text-gray-500'
+ onClick={() =>
+ setQuantityInput(
+ String(Math.max(1, Number(quantityInput) - 1))
+ )
+ }
+ >
+ -
+ </button>
+ <input
+ type='number'
+ id='quantity'
+ min={1}
+ value={quantityInput}
+ onChange={(e) => setQuantityInput(e.target.value)}
+ className=' w-24 h-10 text-center border border-gray-300 rounded focus:outline-none'
+ />
+ <button
+ type='button'
+ className='absolute right-0 px-2 py-1 h-full text-gray-500'
+ onClick={() =>
+ setQuantityInput(String(Number(quantityInput) + 1))
+ }
+ >
+ +
+ </button>
+ </div>
+ <div>
+ <Skeleton
+ isLoaded={!isLoadingSLA}
+ h='21px'
+ // w={16}
+ className={
+ product?.sla?.qty < 10 ? 'text-red-600 font-medium' : ''
+ }
+ >
+ Stock : {product?.sla?.qty}{' '}
+ </Skeleton>
+ </div>
+ <div>
+ {product?.sla?.qty > 0 && (
+ <Link href='/panduan-pick-up-service' className='group'>
+ <Image
+ src='/images/PICKUP-NOW.png'
+ className='group-hover:scale-105 transition-transform duration-200 w-28'
+ alt='pickup now'
+ />
+ </Link>
+ )}
+ </div>
+ </div>
+ <div className='flex gap-x-3'>
+ <Button
onClick={() => handleAddToCart(product.id)}
- className='flex-1 py-2 btn-yellow'
+ className='w-full'
+ colorScheme='yellow'
>
Keranjang
- </button>
- <button
- type='button'
+ </Button>
+ <Button
onClick={() => handleBuy(product.id)}
- className='flex-1 py-2 btn-solid-red'
+ className='w-full'
+ colorScheme='red'
>
Beli
- </button>
+ </Button>
</div>
- <div className='flex mt-4'>
- <button
- className='flex items-center gap-x-1'
- onClick={toggleWishlist}
- >
- {wishlist.data?.productTotal > 0 ? (
- <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
- ) : (
- <HeartIcon className='w-6' />
- )}
- Wishlist
- </button>
+ <Button
+ onClick={() => handleButton(product.id)}
+ color={'red'}
+ colorScheme='white'
+ className='w-full border-2 p-2 gap-1 mt-2 hover:bg-slate-100 flex items-center'
+ >
+ <ImageNext
+ src='/images/writing.png'
+ alt='penawaran instan'
+ className=''
+ width={25}
+ height={25}
+ />
+ Penawaran Harga Instan
+ </Button>
+ <div className='flex py-5'>
+ <div className='flex gap-x-5 items-center justify-center'>
+ <Button
+ as={Link}
+ href={createdAskUrl}
+ variant='link'
+ target='_blank'
+ colorScheme='gray'
+ leftIcon={<MessageCircleIcon size={18} />}
+ >
+ Ask Admin
+ </Button>
+
+ <span>|</span>
+
+ <button
+ className='flex items-center gap-x-1'
+ onClick={toggleWishlist}
+ >
+ {wishlist.data?.productTotal > 0 ? (
+ <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
+ ) : (
+ <HeartIcon className='w-6' />
+ )}
+ Wishlist
+ </button>
+
+ <span>|</span>
+
+ <RWebShare
+ data={{
+ text: 'Check out this product',
+ title: `${product.name} - Indoteknik.com`,
+ url: SELF_HOST + router.asPath,
+ }}
+ >
+ <Button
+ variant='link'
+ colorScheme='gray'
+ leftIcon={<Share2Icon size={18} />}
+ >
+ Share
+ </Button>
+ </RWebShare>
+ </div>
</div>
<div className='border border-gray_r-6 overflow-auto mt-4'>
<div className='font-medium text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10'>
Produk Serupa
</div>
- <div className='h-full divide-y divide-gray_r-6 max-h-96'>
+ <div className='h-full divide-y divide-gray_r-6 max-h-[500px]'>
{productSimilarInBrand &&
productSimilarInBrand?.map((product) => (
<div className='py-2' key={product.id}>
@@ -393,8 +560,11 @@ const ProductDesktopVariant = ({
Kamu Mungkin Juga Suka
</div>
<LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
+ <SimilarBottom product={product} />
</LazyLoad>
+ {/* <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad> */}
</div>
<BottomPopup
@@ -429,8 +599,11 @@ const ProductDesktopVariant = ({
Kamu Mungkin Juga Suka
</div>
<LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
+ <SimilarBottom product={product} />
</LazyLoad>
+ {/* <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad> */}
</div>
</BottomPopup>
</div>
diff --git a/src/lib/product/components/Product/ProductMobileVariant.jsx b/src/lib/product/components/Product/ProductMobileVariant.jsx
index af9e52bb..de5c3f10 100644
--- a/src/lib/product/components/Product/ProductMobileVariant.jsx
+++ b/src/lib/product/components/Product/ProductMobileVariant.jsx
@@ -1,10 +1,10 @@
-import { Skeleton } from '@chakra-ui/react';
+import { Button, Skeleton } from '@chakra-ui/react';
import { HeartIcon } from '@heroicons/react/24/outline';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
import LazyLoad from 'react-lazy-load';
-
+import ImageNext from 'next/image';
import odooApi from '@/core/api/odooApi';
import Divider from '@/core/components/elements/Divider/Divider';
import Image from '@/core/components/elements/Image/Image';
@@ -16,12 +16,15 @@ import currencyFormat from '@/core/utils/currencyFormat';
import { gtagAddToCart } from '@/core/utils/googleTag';
import { createSlug } from '@/core/utils/slug';
import whatsappUrl from '@/core/utils/whatsappUrl';
-
+import { getAuth } from '~/libs/auth';
+import SimilarBottom from '~/modules/product-detail/components/SimilarBottom';
import ProductSimilar from '../ProductSimilar';
const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
const router = useRouter();
-
+ const { slug } = router.query;
+ const { srsltid } = router.query;
+ let auth = getAuth();
const [quantity, setQuantity] = useState('1');
const [selectedVariant, setSelectedVariant] = useState(product.id);
const [informationTab, setInformationTab] = useState(
@@ -73,11 +76,16 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
return isValid;
};
- const handleClickCart = () => {
+ const handleClickCart = async () => {
+ if (!auth) {
+ router.push(`/login?next=/shop/product/${slug}?srsltid=${srsltid}`);
+ return;
+ }
+
if (!validAction()) return;
gtagAddToCart(activeVariant, quantity);
updateItemCart({
- productId: variant,
+ productId: product.id,
quantity,
programLineId: null,
selected: true,
@@ -86,7 +94,33 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
setAddCartAlert(true);
};
- const handleClickBuy = () => {
+ const handleClickBuy = async () => {
+ let isLoggedIn = typeof auth === 'object';
+
+ if (!isLoggedIn) {
+ const currentUrl = encodeURIComponent(router.asPath);
+ await router.push(`/login?next=${currentUrl}`);
+
+ // Tunggu login berhasil, misalnya dengan memantau perubahan status auth.
+ const authCheckInterval = setInterval(() => {
+ const newAuth = getAuth();
+ if (typeof newAuth === 'object') {
+ isLoggedIn = true;
+ auth = newAuth; // Update nilai auth setelah login
+ clearInterval(authCheckInterval);
+ }
+ }, 500); // Periksa status login setiap 500ms
+
+ await new Promise((resolve) => {
+ const checkLogin = setInterval(() => {
+ if (isLoggedIn) {
+ clearInterval(checkLogin);
+ resolve(null);
+ }
+ }, 500);
+ });
+ }
+
if (!validAction()) return;
updateItemCart({
@@ -99,6 +133,20 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
router.push(`/shop/checkout?source=buy`);
};
+ const handleButton = (variant) => {
+ const quantity = quantityInput;
+ if (!validQuantity(quantity)) return;
+
+ updateItemCart({
+ productId: variant,
+ quantity,
+ programLineId: null,
+ selected: true,
+ source: 'buy',
+ });
+ router.push('/shop/quotation?source=buy');
+ };
+
const productSimilarQuery = [
product?.name,
`fq=-product_id_i:${product.id}`,
@@ -120,42 +168,14 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
return (
<MobileView>
- <Image
- src={product.image + '?variant=True'}
- alt={product.name}
- className='h-72 object-contain object-center w-full border-b border-gray_r-4'
- />
-
- <div className='p-4'>
- <div className='flex items-end mb-2'>
- {product.manufacture?.name ? (
- <Link
- href={createSlug(
- '/shop/brands/',
- product.manufacture?.name,
- product.manufacture?.id
- )}
- >
- {product.manufacture?.name}
- </Link>
- ) : (
- <div>-</div>
- )}
- <button type='button' className='ml-auto' onClick={toggleWishlist}>
- {wishlist.data?.productTotal > 0 ? (
- <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
- ) : (
- <HeartIcon className='w-6' />
- )}
- </button>
- </div>
- <h1 className='leading-6 font-medium mb-3'>{activeVariant?.name}</h1>
-
+ <div
+ className={`px-4 block md:sticky md:top-[150px] md:py-6 fixed bottom-0 left-0 right-0 bg-white p-2 z-10 pb-6 pt-6 rounded-lg shadow-[rgba(0,0,4,0.1)_0px_-4px_4px_0px] `}
+ >
{activeVariant.isFlashSale &&
activeVariant?.price?.discountPercentage > 0 ? (
<>
<div className='flex gap-x-1 items-center'>
- <div className='badge-solid-red'>
+ <div className='bg-danger-500 px-2 py-1.5 rounded text-white text-caption-2'>
{activeVariant?.price?.discountPercentage}%
</div>
<div className='text-gray_r-11 line-through text-caption-1'>
@@ -173,7 +193,7 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
</div>
</>
) : (
- <h3 className='text-danger-500 font-semibold mt-1'>
+ <div className='text-danger-500 font-semibold mt-1 text-3xl'>
{activeVariant?.price?.price > 0 ? (
<>
{currencyFormat(activeVariant?.price?.price)}
@@ -203,39 +223,84 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
</a>
</span>
)}
- </h3>
+ </div>
)}
+ <div className=''>
+ <div className='mt-4 mb-2'>Jumlah</div>
+ <div className='flex gap-x-3'>
+ <div className='w-2/12'>
+ <input
+ name='quantity'
+ type='number'
+ className='form-input'
+ value={quantity}
+ onChange={(e) => setQuantity(e.target.value)}
+ />
+ </div>
+ <button
+ type='button'
+ className='btn-yellow flex-1'
+ onClick={handleClickCart}
+ >
+ Keranjang
+ </button>
+ <button
+ type='button'
+ className='btn-solid-red flex-1'
+ onClick={handleClickBuy}
+ >
+ Beli
+ </button>
+ </div>
+ <Button
+ onClick={() => handleButton(product.id)}
+ color={'red'}
+ colorScheme='white'
+ className='w-full border-2 p-2 gap-1 mt-2 hover:bg-slate-100 flex items-center'
+ >
+ <ImageNext
+ src='/images/writing.png'
+ alt='penawaran instan'
+ className=''
+ width={25}
+ height={25}
+ />
+ Penawaran Harga Instan
+ </Button>
+ </div>
</div>
-
- <Divider />
+ <Image
+ src={product.image + '?variant=True'}
+ alt={product.name}
+ className='h-72 object-contain object-center w-full border-b border-gray_r-4'
+ />
<div className='p-4'>
- <div className='mt-4 mb-2'>Jumlah</div>
- <div className='flex gap-x-3'>
- <div className='w-2/12'>
- <input
- name='quantity'
- type='number'
- className='form-input'
- value={quantity}
- onChange={(e) => setQuantity(e.target.value)}
- />
- </div>
- <button
- type='button'
- className='btn-yellow flex-1'
- onClick={handleClickCart}
- >
- Keranjang
- </button>
- <button
- type='button'
- className='btn-solid-red flex-1'
- onClick={handleClickBuy}
- >
- Beli
+ <div className='flex items-end mb-2'>
+ {product.manufacture?.name ? (
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture?.name,
+ product.manufacture?.id
+ )}
+ >
+ {product.manufacture?.name}
+ </Link>
+ ) : (
+ <div>-</div>
+ )}
+ <button type='button' className='ml-auto' onClick={toggleWishlist}>
+ {wishlist.data?.productTotal > 0 ? (
+ <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
+ ) : (
+ <HeartIcon className='w-6' />
+ )}
</button>
</div>
+ <h1 className='font-medium text-h-lg leading-8 md:text-title-md md:leading-10 mb-3'>
+ {activeVariant?.name}
+ </h1>
</div>
<Divider />
@@ -375,8 +440,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
<div className='p-4'>
<h2 className='font-semibold mb-4'>Kamu Mungkin Juga Suka</h2>
<LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
+ <SimilarBottom product={product} />
</LazyLoad>
+ {/* <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad> */}
</div>
<BottomPopup
@@ -409,8 +477,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
Kamu Mungkin Juga Suka
</div>
<LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
+ <SimilarBottom product={product} />
</LazyLoad>
+ {/* <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad> */}
</div>
</BottomPopup>
</MobileView>
diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index d3b50302..3e6a6913 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -10,12 +10,13 @@ import { sellingProductFormat } from '@/core/utils/formatValue';
import { createSlug } from '@/core/utils/slug';
import whatsappUrl from '@/core/utils/whatsappUrl';
import useUtmSource from '~/hooks/useUtmSource';
+import useDevice from '@/core/hooks/useDevice';
const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
const router = useRouter();
const utmSource = useUtmSource();
const [discount, setDiscount] = useState(0);
-
+ const { isDesktop, isMobile } = useDevice();
let voucherPastiHemat = 0;
voucherPastiHemat = product?.newVoucherPastiHemat[0];
@@ -26,9 +27,13 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
});
const image = useMemo(() => {
- if (product.image) return product.image + '?ratio=square';
- return '/images/noimage.jpeg';
- }, [product.image]);
+ if (!isDesktop && product.image_mobile) {
+ return product.image_mobile + '?ratio=square';
+ } else {
+ if (product.image) return product.image + '?ratio=square';
+ return '/images/noimage.jpeg';
+ }
+ }, [product.image, product.image_mobile]);
const URL = {
product:
@@ -143,7 +148,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
<div className='p-2 sm:p-3 pb-3 text-caption-2 sm:text-body-2 leading-5'>
<div className='flex justify-between '>
{product?.manufacture?.name ? (
- <Link href={URL.manufacture} className='mb-1 mt-1'>
+ <Link href={URL.manufacture} className='mb-1 mt-1 truncate'>
{product.manufacture.name}
</Link>
) : (
diff --git a/src/lib/product/components/ProductFilter.jsx b/src/lib/product/components/ProductFilter.jsx
index d52fcb90..947550b7 100644
--- a/src/lib/product/components/ProductFilter.jsx
+++ b/src/lib/product/components/ProductFilter.jsx
@@ -1,88 +1,96 @@
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import { useRouter } from 'next/router'
-import { useState } from 'react'
-import _ from 'lodash'
-import { toQuery } from 'lodash-contrib'
-import { Checkbox } from '@chakra-ui/react'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import { useRouter } from 'next/router';
+import { useState } from 'react';
+import _ from 'lodash';
+import { toQuery } from 'lodash-contrib';
+import { Checkbox } from '@chakra-ui/react';
const orderOptions = [
{ value: 'price-asc', label: 'Harga Terendah' },
{ value: 'price-desc', label: 'Harga Tertinggi' },
{ value: 'popular', label: 'Populer' },
- { value: 'stock', label: 'Ready Stock' }
-]
+ { value: 'stock', label: 'Ready Stock' },
+];
-const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBrand = null }) => {
- const router = useRouter()
- const { query } = router
- const [order, setOrder] = useState(query?.orderBy || 'popular')
- const [brand, setBrand] = useState(query?.brand)
- const [category, setCategory] = useState(query?.category)
- const [priceFrom, setPriceFrom] = useState(query?.priceFrom)
- const [priceTo, setPriceTo] = useState(query?.priceTo)
+const ProductFilter = ({
+ active,
+ close,
+ brands,
+ categories,
+ prefixUrl,
+ defaultBrand = null,
+}) => {
+ const router = useRouter();
+ const { query } = router;
+ const [order, setOrder] = useState(query?.orderBy || 'popular');
+ const [brand, setBrand] = useState(query?.brand);
+ const [category, setCategory] = useState(query?.category);
+ const [priceFrom, setPriceFrom] = useState(query?.priceFrom);
+ const [priceTo, setPriceTo] = useState(query?.priceTo);
- const [stock, setStock] = useState(query?.stock)
+ const [stock, setStock] = useState(query?.stock);
- const [activeRange, setActiveRange] = useState(null)
+ const [activeRange, setActiveRange] = useState(null);
const priceRange = [
{
priceFrom: 100000,
- priceTo: 200000
+ priceTo: 200000,
},
{
priceFrom: 200000,
- priceTo: 300000
+ priceTo: 300000,
},
{
priceFrom: 300000,
- priceTo: 400000
+ priceTo: 400000,
},
{
priceFrom: 400000,
- priceTo: 500000
- }
- ]
+ priceTo: 500000,
+ },
+ ];
const handlePriceFromChange = async (priceFromr, priceTor, index) => {
- await setPriceFrom(priceFromr)
- await setPriceTo(priceTor)
- setActiveRange(index)
- }
+ await setPriceFrom(priceFromr);
+ await setPriceTo(priceTor);
+ setActiveRange(index);
+ };
const handleReadyStockChange = (event) => {
- const value = event.target.value
- const isChecked = event.target.checked
+ const value = event.target.value;
+ const isChecked = event.target.checked;
if (isChecked) {
- setStock(value)
+ setStock(value);
} else {
- setStock(null)
+ setStock(null);
}
- }
+ };
const handleSubmit = () => {
let params = {
+ penawaran: router.query.penawaran,
q: router.query.q,
orderBy: order,
brand,
category,
priceFrom,
priceTo,
- stock: stock
- }
- params = _.pickBy(params, _.identity)
- params = toQuery(params)
- router.push(`${prefixUrl}?${params}`)
- }
+ stock: stock,
+ };
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
+ router.push(`${prefixUrl}?${params}`);
+ };
const formatCurrency = (value) => {
if (value >= 1000) {
- const thousands = Math.floor(value / 1000) // Menghitung ribuan
- return `Rp${thousands}k`
+ const thousands = Math.floor(value / 1000); // Menghitung ribuan
+ return `Rp${thousands}k`;
} else {
- return `Rp${value}`
+ return `Rp${value}`;
}
- }
+ };
return (
<BottomPopup active={active} close={close} title='Filter Produk'>
@@ -101,7 +109,10 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
<option value=''>Pilih Brand...</option>
{brands.map((brand, index) => (
<option value={brand.brand} key={index}>
- {brand.brand} <span className='text-sm text-gray-200'>({brand.qty})</span>
+ {brand.brand}{' '}
+ <span className='text-sm text-gray-200'>
+ ({brand.qty})
+ </span>
</option>
))}
</>
@@ -125,7 +136,10 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
<option value=''>Pilih Kategori...</option>
{categories.map((category, index) => (
<option value={category.name} key={index}>
- {category.name} <span className='text-sm text-gray-200'>({category.qty})</span>
+ {category.name}{' '}
+ <span className='text-sm text-gray-200'>
+ ({category.qty})
+ </span>
</option>
))}
</>
@@ -141,7 +155,9 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
<button
key={orderOption.value}
className={`btn-light px-3 font-normal flex-shrink-0 ${
- order == orderOption.value ? 'bg-warning-500' : 'bg-transparent'
+ order == orderOption.value
+ ? 'bg-warning-500'
+ : 'bg-transparent'
}`}
onClick={() => setOrder(orderOption.value)}
>
@@ -173,13 +189,16 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
{priceRange.map((price, i) => (
<button
key={i}
- onClick={() => handlePriceFromChange(price.priceFrom, price.priceTo, i)}
+ onClick={() =>
+ handlePriceFromChange(price.priceFrom, price.priceTo, i)
+ }
className={`w-full border ${
i === activeRange ? 'border-red-600' : 'border-gray-400'
}
py-2 p-3 rounded-full text-sm whitespace-nowrap`}
>
- {formatCurrency(price.priceFrom)} - {formatCurrency(price.priceTo)}
+ {formatCurrency(price.priceFrom)} -{' '}
+ {formatCurrency(price.priceTo)}
</button>
))}
</div>
@@ -197,12 +216,16 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
</Checkbox>
</div>
</div> */}
- <button type='button' className='btn-solid-red w-full mt-2' onClick={handleSubmit}>
+ <button
+ type='button'
+ className='btn-solid-red w-full mt-2'
+ onClick={handleSubmit}
+ >
Terapkan Filter
</button>
</div>
</BottomPopup>
- )
-}
+ );
+};
-export default ProductFilter
+export default ProductFilter;
diff --git a/src/lib/product/components/ProductFilterDesktop.jsx b/src/lib/product/components/ProductFilterDesktop.jsx
index 73fecab5..d2ecb4d9 100644
--- a/src/lib/product/components/ProductFilterDesktop.jsx
+++ b/src/lib/product/components/ProductFilterDesktop.jsx
@@ -1,7 +1,7 @@
-import { useRouter } from 'next/router'
-import { useEffect, useState } from 'react'
-import _ from 'lodash'
-import { toQuery } from 'lodash-contrib'
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import _ from 'lodash';
+import { toQuery } from 'lodash-contrib';
import {
Accordion,
AccordionButton,
@@ -15,110 +15,119 @@ import {
InputGroup,
InputLeftAddon,
Stack,
- VStack
-} from '@chakra-ui/react'
-import Image from '@/core/components/elements/Image/Image'
-import { formatCurrency } from '@/core/utils/formatValue'
+ VStack,
+} from '@chakra-ui/react';
+import Image from '@/core/components/elements/Image/Image';
+import { formatCurrency } from '@/core/utils/formatValue';
-const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = null }) => {
-
-
- const router = useRouter()
- const { query } = router
- const [order, setOrder] = useState(query?.orderBy)
- const [brandValues, setBrand] = useState(query?.brand?.split(',') || [])
- const [categoryValues, setCategory] = useState(query?.category?.split(',') || [])
- const [priceFrom, setPriceFrom] = useState(query?.priceFrom)
- const [priceTo, setPriceTo] = useState(query?.priceTo)
- const [stock, setStock] = useState(query?.stock)
- const [activeRange, setActiveRange] = useState(null)
- const [activeIndeces, setActiveIndeces] = useState([])
+const ProductFilterDesktop = ({
+ brands,
+ categories,
+ prefixUrl,
+ defaultBrand = null,
+}) => {
+ const router = useRouter();
+ const { query } = router;
+ const [order, setOrder] = useState(query?.orderBy);
+ const [brandValues, setBrand] = useState(query?.brand?.split(',') || []);
+ const [categoryValues, setCategory] = useState(
+ query?.category?.split(',') || []
+ );
+ const [priceFrom, setPriceFrom] = useState(query?.priceFrom);
+ const [priceTo, setPriceTo] = useState(query?.priceTo);
+ const [stock, setStock] = useState(query?.stock);
+ const [activeRange, setActiveRange] = useState(null);
+ const [activeIndeces, setActiveIndeces] = useState([]);
const priceRange = [
{
priceFrom: 100000,
- priceTo: 200000
+ priceTo: 200000,
},
{
priceFrom: 200000,
- priceTo: 300000
+ priceTo: 300000,
},
{
priceFrom: 300000,
- priceTo: 400000
+ priceTo: 400000,
},
{
priceFrom: 400000,
- priceTo: 500000
- }
- ]
+ priceTo: 500000,
+ },
+ ];
const indexRange = priceRange.findIndex((range) => {
- return range.priceFrom === parseInt(priceFrom) && range.priceTo == parseInt(priceTo)
- })
+ return (
+ range.priceFrom === parseInt(priceFrom) &&
+ range.priceTo == parseInt(priceTo)
+ );
+ });
const handleCategoriesChange = (event) => {
- const value = event.target.value
- const isChecked = event.target.checked
+ const value = event.target.value;
+ const isChecked = event.target.checked;
if (isChecked) {
- setCategory([...categoryValues, value])
+ setCategory([...categoryValues, value]);
} else {
- setCategory(categoryValues.filter((val) => val !== value))
+ setCategory(categoryValues.filter((val) => val !== value));
}
- }
+ };
const handleBrandsChange = (event) => {
- const value = event.target.value
- const isChecked = event.target.checked
+ const value = event.target.value;
+ const isChecked = event.target.checked;
if (isChecked) {
- setBrand([...brandValues, value])
+ setBrand([...brandValues, value]);
} else {
- setBrand(brandValues.filter((val) => val !== value))
+ setBrand(brandValues.filter((val) => val !== value));
}
- }
+ };
const handleReadyStockChange = (event) => {
- const value = event.target.value
- const isChecked = event.target.checked
+ const value = event.target.value;
+ const isChecked = event.target.checked;
if (isChecked) {
- setStock(value)
+ setStock(value);
} else {
- setStock(null)
+ setStock(null);
}
- }
+ };
const handlePriceFromChange = async (priceFromr, priceTor, index) => {
- await setPriceFrom(priceFromr)
- await setPriceTo(priceTor)
- setActiveRange(index)
- }
+ await setPriceFrom(priceFromr);
+ await setPriceTo(priceTor);
+ setActiveRange(index);
+ };
const handleSubmit = () => {
let params = {
+ penawaran: router.query.penawaran,
q: router.query.q,
orderBy: order,
brand: brandValues.join(','),
category: categoryValues.join(','),
priceFrom,
priceTo,
- stock: stock
- }
- params = _.pickBy(params, _.identity)
- params = toQuery(params)
+ stock: stock,
+ };
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
- const slug = Array.isArray(router.query.slug) ? router.query.slug[0] : router.query.slug;
+ const slug = Array.isArray(router.query.slug)
+ ? router.query.slug[0]
+ : router.query.slug;
if (slug) {
- if(prefixUrl.includes('category') || prefixUrl.includes('lob')){
- router.push(`${prefixUrl}?${params}`)
- }else{
- router.push(`${prefixUrl}/${slug}?${params}`)
+ if (prefixUrl.includes('category') || prefixUrl.includes('lob')) {
+ router.push(`${prefixUrl}?${params}`);
+ } else {
+ router.push(`${prefixUrl}/${slug}?${params}`);
}
} else {
- router.push(`${prefixUrl}?${params}`)
+ router.push(`${prefixUrl}?${params}`);
}
- }
-
-
+ };
/*const handleIndexAccordion = async () => {
if (brandValues) {
@@ -136,9 +145,8 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
}*/
useEffect(() => {
- setActiveRange(indexRange)
- }, [])
-
+ setActiveRange(indexRange);
+ }, []);
return (
<>
@@ -165,13 +173,17 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
>
<div className='flex items-center gap-2'>
<span>{brand.brand} </span>
- <span className='text-sm text-gray-600'>({brand.qty})</span>
+ <span className='text-sm text-gray-600'>
+ ({brand.qty})
+ </span>
</div>
</Checkbox>
</div>
))
) : (
- <div className='flex items-center gap-2'>Brands tidak tersedia</div>
+ <div className='flex items-center gap-2'>
+ Brands tidak tersedia
+ </div>
)}
</Stack>
</AccordionPanel>
@@ -199,13 +211,17 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
>
<div className='flex items-center gap-2'>
<span>{category.name} </span>
- <span className='text-sm text-gray-600'>({category.qty})</span>
+ <span className='text-sm text-gray-600'>
+ ({category.qty})
+ </span>
</div>
</Checkbox>
</div>
))
) : (
- <div className='flex items-center gap-2'>Kategori tidak tersedia</div>
+ <div className='flex items-center gap-2'>
+ Kategori tidak tersedia
+ </div>
)}
</Stack>
</AccordionPanel>
@@ -243,13 +259,16 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
{priceRange.map((price, i) => (
<button
key={i}
- onClick={() => handlePriceFromChange(price.priceFrom, price.priceTo, i)}
+ onClick={() =>
+ handlePriceFromChange(price.priceFrom, price.priceTo, i)
+ }
className={`w-full border ${
i === activeRange ? 'border-red-600' : 'border-gray-400'
}
py-2 p-3 rounded-full text-sm whitespace-nowrap`}
>
- {formatCurrency(price.priceFrom)} - {formatCurrency(price.priceTo)}
+ {formatCurrency(price.priceFrom)} -{' '}
+ {formatCurrency(price.priceTo)}
</button>
))}
</div>
@@ -282,7 +301,7 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
Terapkan
</Button>
</>
- )
-}
+ );
+};
-export default ProductFilterDesktop
+export default ProductFilterDesktop;
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index 26114acf..f7b044aa 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -79,6 +79,24 @@ const ProductSearch = ({
}
}, [categoryId]);
+ useEffect(() => {
+ const checkIfPenawaran = async () => {
+ if (router.asPath.includes('penawaran')) {
+ query = {
+ ...query,
+ fq: [
+ `-flashsale_id_i:${router.query.penawaran}`,
+ `flashsale_price_f:[1 TO *]`,
+ ],
+ orderBy: 'flashsale-discount-desc',
+ };
+ setFinalQuery(query);
+ setOrderBy('flashsale-discount-desc');
+ }
+ };
+ checkIfPenawaran();
+ }, [router.query]);
+
const collectIds = (category) => {
const ids = [];
function recurse(cat) {
@@ -337,6 +355,7 @@ const ProductSearch = ({
const handleDeleteFilter = async (source, value) => {
let params = {
+ penawaran: router.query.penawaran,
q: router.query.q,
orderBy: orderBy,
brand: brandValues.join(','),
@@ -364,6 +383,7 @@ const ProductSearch = ({
break;
case 'delete':
params = {
+ penawaran: router.query.penawaran,
q: router.query.q,
orderBy: orderBy,
};
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index cf0ad41f..5a2f63a5 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -39,9 +39,12 @@ const { getProductsCheckout } = require('@/lib/checkout/api/checkoutApi');
const Quotation = () => {
const router = useRouter();
const auth = useAuth();
+ const query = router.query.source ?? null;
const { data: cartCheckout } = useQuery('cartCheckout', () =>
- getProductsCheckout()
+ getProductsCheckout({
+ source: query,
+ })
);
const { setRefreshCart } = useProductCartContext();
diff --git a/src/lib/review/components/CustomerReviews.jsx b/src/lib/review/components/CustomerReviews.jsx
index a6e697f0..6ca0fa7b 100644
--- a/src/lib/review/components/CustomerReviews.jsx
+++ b/src/lib/review/components/CustomerReviews.jsx
@@ -3,16 +3,37 @@ import MobileView from '@/core/components/views/MobileView';
import Image from 'next/image';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay } from 'swiper';
+import { useEffect, useState } from 'react';
const { useQuery } = require('react-query');
const { getCustomerReviews } = require('../api/customerReviewsApi');
const CustomerReviews = () => {
- const { data: customerReviews } = useQuery(
+ const [data, setData] = useState(null);
+
+ useEffect(() => {
+ const localData = localStorage.getItem('Homepage_customerReviews');
+ if (localData) {
+ setData(JSON.parse(localData));
+ }
+ },[])
+
+
+ const { data: fetchCustomerReviews } = useQuery(
'customerReviews',
- getCustomerReviews
+ getCustomerReviews,{
+ enabled: !data,
+ onSuccess: (data) => {
+ if (data) {
+ localStorage.setItem('Homepage_customerReviews', JSON.stringify(data));
+ setData(data);
+ }
+ }
+ }
);
+ const customerReviews = data
+
return (
<div className='px-4 sm:px-0'>
<h1 className='font-semibold text-[14px] sm:text-h-lg mb-4'>
diff --git a/src/lib/shipment/components/Shipments.jsx b/src/lib/shipment/components/Shipments.jsx
index 115bbd3a..20dbb013 100644
--- a/src/lib/shipment/components/Shipments.jsx
+++ b/src/lib/shipment/components/Shipments.jsx
@@ -1,62 +1,83 @@
-import DesktopView from '@/core/components/views/DesktopView'
-import MobileView from '@/core/components/views/MobileView'
-import Menu from '@/lib/auth/components/Menu'
-import { EllipsisVerticalIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline'
-import ImageNext from 'next/image'
-import { useRouter } from 'next/router'
-import { useQuery } from 'react-query'
-import _, { forEach } from 'lodash-contrib'
-import Spinner from '@/core/components/elements/Spinner/Spinner'
-import Manifest from '@/lib/treckingAwb/component/Manifest'
-import { useState } from 'react'
-import Pagination from '@/core/components/elements/Pagination/Pagination'
-import Link from 'next/link'
-import TransactionStatusBadge from '@/lib/transaction/components/TransactionStatusBadge'
+import DesktopView from '@/core/components/views/DesktopView';
+import MobileView from '@/core/components/views/MobileView';
+import Menu from '@/lib/auth/components/Menu';
+import {
+ EllipsisVerticalIcon,
+ MagnifyingGlassIcon,
+} from '@heroicons/react/24/outline';
+import ImageNext from 'next/image';
+import { useEffect } from 'react';
+import { useRouter } from 'next/router';
+import { useQuery } from 'react-query';
+import _, { forEach } from 'lodash-contrib';
+import Spinner from '@/core/components/elements/Spinner/Spinner';
+import Manifest from '@/lib/treckingAwb/component/Manifest';
+import { useState } from 'react';
+import Pagination from '@/core/components/elements/Pagination/Pagination';
+import Link from 'next/link';
+import TransactionStatusBadge from '@/lib/transaction/components/TransactionStatusBadge';
-const { listShipments } = require('../api/listShipment')
+const { listShipments } = require('../api/listShipment');
const Shipments = () => {
- const router = useRouter()
- const { q = '', page = 1 } = router.query
- const [paramStatus, setParamStatus] = useState(null)
-
- const limit = 15
+ const router = useRouter();
+ const { q = '', page = 1, status = null } = router.query;
+ const [paramStatus, setParamStatus] = useState(status);
+ const limit = 15;
const query = {
q: q,
status: paramStatus,
offset: (page - 1) * limit,
- limit
- }
- const [inputQuery, setInputQuery] = useState(q)
- const queryString = _.toQuery(query)
+ limit,
+ };
+ const [inputQuery, setInputQuery] = useState(q);
+ const queryString = _.toQuery(query);
const { data: shipments } = useQuery('shipments' + queryString, () =>
listShipments({ query: queryString })
- )
- const [idAWB, setIdAWB] = useState(null)
+ );
+ const [idAWB, setIdAWB] = useState(null);
- const pageCount = Math.ceil(shipments?.pickingTotal / limit)
- let pageQuery = _.omit(query, ['limit', 'offset', 'context'])
- pageQuery = _.pickBy(pageQuery, _.identity)
- pageQuery = _.toQuery(pageQuery)
+ const pageCount = Math.ceil(shipments?.pickingTotal / limit);
+ let pageQuery = _.omit(query, ['limit', 'offset', 'context']);
+ pageQuery = _.pickBy(pageQuery, _.identity);
+ pageQuery = _.toQuery(pageQuery);
const closePopup = () => {
- setIdAWB(null)
- }
+ setIdAWB(null);
+ };
const handleSubmit = async (e) => {
- e.preventDefault()
- router.push(`${router.pathname}?q=${inputQuery}`)
- }
+ e.preventDefault();
+ router.push(`${router.pathname}?q=${inputQuery}`);
+ };
const filterStatus = async (status) => {
if (status === paramStatus) {
- setParamStatus(null)
+ setParamStatus(null);
} else {
- setParamStatus(status)
+ setParamStatus(status);
}
- }
+ };
+
+ useEffect(() => {
+ const resetQuery = () => {
+ const newQuery = {
+ status: paramStatus || undefined,
+ q: '',
+ page: 1,
+ };
+ router.push({
+ pathname: router.pathname,
+ query: newQuery,
+ });
+ };
+
+ if (paramStatus !== status) {
+ resetQuery();
+ }
+ }, [paramStatus]);
return (
<>
<MobileView>
@@ -84,7 +105,10 @@ const Shipments = () => {
</form>
{shipments?.pickings.map((shipment) => (
- <div className='p-4 shadow border border-gray_r-3 rounded-md' key={shipment.id}>
+ <div
+ className='p-4 shadow border border-gray_r-3 rounded-md'
+ key={shipment.id}
+ >
<div className='flex justify-between items-center mb-3'>
<div className='text-caption-2 text-gray_r-11'>
<p>
@@ -93,7 +117,9 @@ const Shipments = () => {
{shipment.carrierName || '-'}
</span>
</p>
- <p className='mt-2'>No. Resi : {shipment.trackingNumber || '-'}</p>
+ <p className='mt-2'>
+ No. Resi : {shipment.trackingNumber || '-'}
+ </p>
</div>
<div className='flex justify-between'>
{shipment?.status === 'completed' && (
@@ -116,11 +142,17 @@ const Shipments = () => {
<hr />
<div className='flex justify-between mt-2 items-center mb-5'>
<div>
- <span className='text-caption-2 text-gray_r-11'>No. Transaksi</span>
+ <span className='text-caption-2 text-gray_r-11'>
+ No. Transaksi
+ </span>
<Link href={`/my/transactions/${shipment.saleOrder.id}`}>
- <h2 className='text-danger-500 mt-1 mb-2'>{shipment.saleOrder.name}</h2>
+ <h2 className='text-danger-500 mt-1 mb-2'>
+ {shipment.saleOrder.name}
+ </h2>
</Link>
- <span className='text-caption-2 text-gray_r-11'>{shipment.date}</span>
+ <span className='text-caption-2 text-gray_r-11'>
+ {shipment.date}
+ </span>
</div>
<div>
<button
@@ -136,7 +168,11 @@ const Shipments = () => {
onClick={() => setIdAWB(shipment.id)}
className='flex items-center mt-1 gap-x-1 min-w-full'
>
- <ImageNext src={`/images/BOX_DELIVERY_GREEN.svg`} width={20} height={20} />
+ <ImageNext
+ src={`/images/BOX_DELIVERY_GREEN.svg`}
+ width={20}
+ height={20}
+ />
<p className='text-sm text-green-700 truncate'>
{shipment.lastManifest.description}
</p>
@@ -148,7 +184,7 @@ const Shipments = () => {
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={router.pathname + pageQuery}
+ url={`${router.pathname}${pageQuery ? '?' + pageQuery : ''}`}
className='mt-2 mb-2'
/>
</div>
@@ -176,7 +212,8 @@ const Shipments = () => {
<path d='M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z' />
</svg>
<div>
- Lacak pengiriman untuk setiap transaksi anda semakin mudah di Indoteknik.com
+ Lacak pengiriman untuk setiap transaksi anda semakin mudah di
+ Indoteknik.com
</div>
</div>
<div className='flex justify-between gap-x-5'>
@@ -190,7 +227,9 @@ const Shipments = () => {
</div>
<div className='p-4 bg-white border border-gray_r-6 rounded'>
<div className='flex mb-6 items-center justify-between'>
- <h1 className='text-title-sm font-semibold'>Detail Pengiriman</h1>
+ <h1 className='text-title-sm font-semibold'>
+ Detail Pengiriman
+ </h1>
<form className='flex gap-x-2' onSubmit={handleSubmit}>
<input
type='text'
@@ -199,7 +238,10 @@ const Shipments = () => {
value={inputQuery}
onChange={(e) => setInputQuery(e.target.value)}
/>
- <button className='btn-light bg-transparent px-3' type='submit'>
+ <button
+ className='btn-light bg-transparent px-3'
+ type='submit'
+ >
<MagnifyingGlassIcon className='w-6' />
</button>
</form>
@@ -254,7 +296,7 @@ const Shipments = () => {
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={router.pathname + pageQuery}
+ url={`${router.pathname}${pageQuery ? '?' + pageQuery : ''}`}
className='mt-2 mb-2'
/>
</div>
@@ -263,16 +305,16 @@ const Shipments = () => {
<Manifest idAWB={idAWB} closePopup={closePopup} />
</DesktopView>
</>
- )
-}
+ );
+};
const CardStatus = ({ device, paramStatus, shipments, filterStatus }) => {
- const status = [`pending`, `shipment`, `completed`]
+ const status = [`pending`, `shipment`, `completed`];
return (
<>
{status.map((value) => {
- const statusData = getStatusLabel(device, value, shipments)
+ const statusData = getStatusLabel(device, value, shipments);
if (device === 'desktop') {
return (
<div
@@ -282,13 +324,15 @@ const CardStatus = ({ device, paramStatus, shipments, filterStatus }) => {
}`}
onClick={() => filterStatus(value)}
>
- <h2 className='mb-2 text-lg font-bold tracking-tight'>{statusData.label}</h2>
+ <h2 className='mb-2 text-lg font-bold tracking-tight'>
+ {statusData.label}
+ </h2>
{statusData.image}
<h1 className='text-xl font-bold'>
{statusData.shipCount} <span className='text-sm'>Pesanan</span>
</h1>
</div>
- )
+ );
} else {
return (
<div
@@ -305,15 +349,15 @@ const CardStatus = ({ device, paramStatus, shipments, filterStatus }) => {
<span className='truncate'>{statusData.shipCount}</span> {'>'}
</h1>
</div>
- )
+ );
}
})}
</>
- )
-}
+ );
+};
const getStatusLabel = (device, status, shipments) => {
- let images = null
+ let images = null;
switch (status) {
case 'pending':
if (device === 'desktop') {
@@ -328,40 +372,48 @@ const getStatusLabel = (device, status, shipments) => {
/>
</div>
</div>
- )
+ );
} else {
images = (
<div>
<ImageNext src='/images/BOX(1).svg' width={15} height={20} />
</div>
- )
+ );
}
return {
label: 'Pending',
shipCount: shipments?.summary?.pendingCount,
- image: images
- }
+ image: images,
+ };
case 'shipment':
if (device === 'desktop') {
images = (
<div className='bg-yellow-100 border border-yellow-200 rounded-sm p-1 w-20 mb-2'>
<div>
- <ImageNext src='/images/BOX_DELIVER_(1).svg' width={30} height={20} />
+ <ImageNext
+ src='/images/BOX_DELIVER_(1).svg'
+ width={30}
+ height={20}
+ />
</div>
</div>
- )
+ );
} else {
images = (
<div>
- <ImageNext src='/images/BOX_DELIVER_(1).svg' width={18} height={20} />
+ <ImageNext
+ src='/images/BOX_DELIVER_(1).svg'
+ width={18}
+ height={20}
+ />
</div>
- )
+ );
}
return {
label: 'Pengiriman',
shipCount: shipments?.summary?.shipmentCount,
- image: images
- }
+ image: images,
+ };
case 'completed':
if (device === 'desktop') {
images = (
@@ -375,22 +427,22 @@ const getStatusLabel = (device, status, shipments) => {
/>
</div>
</div>
- )
+ );
} else {
images = (
<div>
<ImageNext src='/images/open-box(1).svg' width={16} height={20} />
</div>
- )
+ );
}
return {
label: 'Pesanan Tiba',
shipCount: shipments?.summary?.completedCount,
- image: images
- }
+ image: images,
+ };
default:
- return 'Status Tidak Dikenal'
+ return 'Status Tidak Dikenal';
}
-}
+};
-export default Shipments
+export default Shipments;
diff --git a/src/lib/transaction/api/transactionsApi.js b/src/lib/transaction/api/transactionsApi.js
index f4e36e6f..5ea2b5b0 100644
--- a/src/lib/transaction/api/transactionsApi.js
+++ b/src/lib/transaction/api/transactionsApi.js
@@ -3,6 +3,9 @@ import { getAuth } from '@/core/utils/auth'
const transactionsApi = async ({ query }) => {
const auth = getAuth()
+ if (!auth) {
+ return null
+ }
const dataTransactions = await odooApi(
'GET',
`/api/v1/partner/${auth.partnerId}/sale_order?${query}`
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index 4d401037..d001c7f4 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -778,6 +778,10 @@ const Transaction = ({ id }) => {
? `| ${product?.attributes.join(', ')}`
: ''}
</div>
+ <div className='text-[10px] text-red-500 italic mt-2'>
+ {product.availableQuantity} barang ini bisa di
+ pickup maksimal pukul 16.00
+ </div>
</div>
</td>
{/* <td>
@@ -879,7 +883,7 @@ const Transaction = ({ id }) => {
</div>
</div>
</div>
- )}
+ )}
{transaction?.data?.productsRejectLine.length > 0 && (
<div className='text-h-sm font-semibold mt-10 mb-4'>
diff --git a/src/lib/treckingAwb/component/Manifest.jsx b/src/lib/treckingAwb/component/Manifest.jsx
index fbc95702..02d0bc7a 100644
--- a/src/lib/treckingAwb/component/Manifest.jsx
+++ b/src/lib/treckingAwb/component/Manifest.jsx
@@ -1,16 +1,16 @@
-import odooApi from '@/core/api/odooApi'
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'
-import { getAuth } from '@/core/utils/auth'
-import { useEffect, useState } from 'react'
-import { toast } from 'react-hot-toast'
-import ImageNext from 'next/image'
-import { list } from 'postcss'
+import odooApi from '@/core/api/odooApi';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner';
+import { getAuth } from '@/core/utils/auth';
+import { useEffect, useState } from 'react';
+import { toast } from 'react-hot-toast';
+import ImageNext from 'next/image';
+import { list } from 'postcss';
const Manifest = ({ idAWB, closePopup }) => {
- const [manifests, setManifests] = useState(null)
- const [isLoading, setIsLoading] = useState(false)
-
+ const [manifests, setManifests] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ console.log('manifests', manifests);
const formatCustomDate = (date) => {
const months = [
'Jan',
@@ -24,61 +24,60 @@ const Manifest = ({ idAWB, closePopup }) => {
'Sep',
'Oct',
'Nov',
- 'Dec'
- ]
+ 'Dec',
+ ];
- const parts = date.split(' ') // Pisahkan tanggal dan waktu
- const [datePart, timePart] = parts
- const [yyyy, mm, dd] = datePart.split('-')
- const [hh, min] = timePart.split(':')
+ const parts = date.split(' '); // Pisahkan tanggal dan waktu
+ const [datePart, timePart] = parts;
+ const [yyyy, mm, dd] = datePart.split('-');
+ const [hh, min] = timePart.split(':');
- const monthAbbreviation = months[parseInt(mm, 10) - 1]
+ const monthAbbreviation = months[parseInt(mm, 10) - 1];
- return `${dd} ${monthAbbreviation} ${hh}:${min}`
- }
+ return `${dd} ${monthAbbreviation} ${hh}:${min}`;
+ };
const getManifest = async () => {
- setIsLoading(true)
- const auth = getAuth()
- let list
- if(auth){
+ setIsLoading(true);
+ const auth = getAuth();
+ let list;
+ if (auth) {
list = await odooApi(
'GET',
`/api/v1/partner/${auth.partnerId}/stock-picking/${idAWB}/tracking`
- )
- }else{
- list = await odooApi(
- 'GET',
- `/api/v1/stock-picking/${idAWB}/tracking`
- )
+ );
+ } else {
+ list = await odooApi('GET', `/api/v1/stock-picking/${idAWB}/tracking`);
}
- setManifests(list)
- setIsLoading(false)
- }
+ setManifests(list);
+ setIsLoading(false);
+ };
useEffect(() => {
if (idAWB) {
- getManifest()
+ getManifest();
} else {
- setManifests(null)
+ setManifests(null);
}
- }, [idAWB])
+ }, [idAWB]);
- const [copied, setCopied] = useState(false)
+ const [copied, setCopied] = useState(false);
const handleCopyClick = () => {
- const textToCopy = manifests?.waybillNumber
- navigator.clipboard.writeText(textToCopy)
- setCopied(true)
- toast.success('No Resi Berhasil di Copy')
- setTimeout(() => setCopied(false), 2000) // Reset copied state after 2 seconds
- }
+ const textToCopy = manifests?.waybillNumber;
+ navigator.clipboard.writeText(textToCopy);
+ setCopied(true);
+ toast.success('No Resi Berhasil di Copy');
+ setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds
+ };
return (
<>
{isLoading && (
<BottomPopup active={true} close=''>
- <div className='leading-7 text-gray_r-12/80 flex justify-center'>Mohon Tunggu</div>
+ <div className='leading-7 text-gray_r-12/80 flex justify-center'>
+ Mohon Tunggu
+ </div>
<div className='container flex justify-center my-4'>
<LogoSpinner width={48} height={48} />
</div>
@@ -111,11 +110,14 @@ const Manifest = ({ idAWB, closePopup }) => {
</div>
<div className=''>
<h1 className='text-body-1'>
- Estimasi tiba pada <span className='text-gray_r-11 text-sm'>({manifests?.eta})</span>
+ Estimasi tiba pada{' '}
+ <span className='text-gray_r-11 text-sm'>({manifests?.eta})</span>
</h1>
<h1 className='text-sm mt-2 mb-3'>
Dikirim Menggunakan{' '}
- <span className='text-red-500 font-semibold'>{manifests?.deliveryOrder.carrier}</span>
+ <span className='text-red-500 font-semibold'>
+ {manifests?.deliveryOrder.carrier}
+ </span>
</h1>
{manifests?.waybillNumber && (
<div className='flex justify-between items-center'>
@@ -154,10 +156,16 @@ const Manifest = ({ idAWB, closePopup }) => {
{manifests.delivered == true && index == 0 ? (
<div
class={`absolute w-6 h-6 rounded-full mt-1.5 -left-3 border ${
- index == 0 ? 'bg-green-100 border-green-100' : 'bg-gray_r-7 border-white'
+ index == 0
+ ? 'bg-green-100 border-green-100'
+ : 'bg-gray_r-7 border-white'
}`}
>
- <ImageNext src='/images/open-box(1).svg' width={30} height={20} />
+ <ImageNext
+ src='/images/open-box(1).svg'
+ width={30}
+ height={20}
+ />
</div>
) : (
<div
@@ -167,7 +175,9 @@ const Manifest = ({ idAWB, closePopup }) => {
{manifests.delivered != true && (
<div
class={`absolute w-3 h-3 rounded-full mt-1.5 -left-1.5 border ${
- index == 0 ? 'bg-green-600 border-green-600' : 'bg-gray_r-7 border-white'
+ index == 0
+ ? 'bg-green-600 border-green-600'
+ : 'bg-gray_r-7 border-white'
} `}
/>
)}
@@ -176,9 +186,15 @@ const Manifest = ({ idAWB, closePopup }) => {
{formatCustomDate(manifest.datetime)}
</time>
{manifests.delivered == true && index == 0 && (
- <p class={`leading-6 font-semibold text-sm text-green-600 `}>Sudah Sampai</p>
+ <p
+ class={`leading-6 font-semibold text-sm text-green-600 `}
+ >
+ Sudah Sampai
+ </p>
)}
- <p class={`leading-6 text-[12px] text-gray_r-11`}>{manifest.description}</p>
+ <p class={`leading-6 text-[12px] text-gray_r-11`}>
+ {manifest.description}
+ </p>
</li>
</>
))}
@@ -187,7 +203,7 @@ const Manifest = ({ idAWB, closePopup }) => {
</BottomPopup>
)}
</>
- )
-}
+ );
+};
-export default Manifest
+export default Manifest;
diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx
index 68cdf54f..08b7a97e 100644
--- a/src/lib/variant/components/VariantCard.jsx
+++ b/src/lib/variant/components/VariantCard.jsx
@@ -103,30 +103,42 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
</div>
</div>
</div>
-
</div>
<div className='w-8/12 flex flex-col'>
- <p className='product-card__title wrap-line-ellipsis-2'>{product.parent.name}</p>
+ <p className='product-card__title wrap-line-ellipsis-2'>
+ {product.parent.name}
+ </p>
<p className='text-caption-2 text-gray_r-11 mt-1'>
{product.code || '-'}
- {product.attributes.length > 0 ? ` ・ ${product.attributes.join(', ')}` : ''}
+ {product.attributes.length > 0
+ ? ` ・ ${product.attributes.join(', ')}`
+ : ''}
</p>
<p className='text-caption-2 text-gray_r-11 mt-1'>
Berat Item : {product?.weight} Kg x {product?.quantity} Barang
</p>
+ <p className='text-[10px] text-red-500 italic mt-2'>
+ {product.availableQuantity} barang ini bisa di pickup maksimal pukul
+ 16.00
+ </p>
<div className='flex flex-wrap gap-x-1 items-center mt-auto'>
{product.hasFlashsale && (
<>
<p className='text-caption-2 text-gray_r-11 line-through'>
{currencyFormat(product.price.price)}
</p>
- <span className='badge-red'>{product.price.discountPercentage}%</span>
+ <span className='badge-red'>
+ {product.price.discountPercentage}%
+ </span>
</>
)}
</div>
<p className='text-caption-2 text-gray_r-11 mt-1'>
{product.price.priceDiscount > 0
- ? currencyFormat(product.price.priceDiscount) + ' × ' + product.quantity + ' Barang'
+ ? currencyFormat(product.price.priceDiscount) +
+ ' × ' +
+ product.quantity +
+ ' Barang'
: ''}
</p>
<p className='text-caption-2 text-gray_r-12 font-bold mt-2'>
diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx
index bcb41dd6..f52aa5f7 100644
--- a/src/pages/_app.jsx
+++ b/src/pages/_app.jsx
@@ -85,7 +85,7 @@ function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<ScrollToTop />
-
+
<AnimatePresence>
{animateLoader && (
<motion.div
diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx
index 6af6294f..4b67c3f9 100644
--- a/src/pages/_document.jsx
+++ b/src/pages/_document.jsx
@@ -115,6 +115,19 @@ export default function MyDocument() {
}}
/>
+ <Script
+ async
+ id='gtag-config'
+ strategy='afterInteractive'
+ dangerouslySetInnerHTML={{
+ __html: `
+ gtag('config', 'AW-954540379/fCU8CI3Y8OoZENvClMcD', {
+ 'phone_conversion_number': '(021) 29338828'
+ });
+ `,
+ }}
+ />
+
{/* <Script
id='tawk-script-tag'
strategy='afterInteractive'
diff --git a/src/pages/api/banner-section.js b/src/pages/api/banner-section.js
new file mode 100644
index 00000000..7d7040c0
--- /dev/null
+++ b/src/pages/api/banner-section.js
@@ -0,0 +1,44 @@
+import odooApi from '@/core/api/odooApi';
+import { createClient } from 'redis';
+
+const client = createClient();
+
+client.on('error', (err) => console.error('Redis Client Error', err));
+
+const connectRedis = async () => {
+ if (!client.isOpen) {
+ await client.connect();
+ }
+};
+
+export default async function handler(req, res) {
+ try {
+ await connectRedis();
+ const cacheKey = 'hero-banner';
+ // await client.del(cacheKey);
+ let cachedData = await client.get(cacheKey);
+
+ if (cachedData) {
+ const data = JSON.parse(cachedData);
+ return res.status(200).json({ data });
+ } else {
+ const dataBannerSections = await odooApi(
+ 'GET',
+ '/api/v1/banner?type=home-banner'
+ );
+
+ // Simpan hasil fetch ke Redis dengan masa kadaluarsa 3 hari (259200 detik)
+ await client.set(
+ cacheKey,
+ JSON.stringify(dataBannerSections),
+ 'EX',
+ 259200
+ );
+
+ return res.status(200).json({ data: dataBannerSections });
+ }
+ } catch (error) {
+ console.error('Error interacting with Redis or fetching data:', error);
+ return res.status(500).json({ error: 'Internal Server Error' });
+ }
+}
diff --git a/src/pages/api/category-management.js b/src/pages/api/category-management.js
new file mode 100644
index 00000000..f05d8644
--- /dev/null
+++ b/src/pages/api/category-management.js
@@ -0,0 +1,85 @@
+import { createClient } from 'redis';
+// import { fetchCategoryManagementSolr } from '../../lib/home/api/categoryManagementApi';
+const client = createClient();
+client.on('error', (err) => console.error('Redis Client Error', err));
+
+const connectRedis = async () => {
+ if (!client.isOpen) {
+ await client.connect();
+ }
+};
+
+export default async function handler(req, res) {
+ try {
+ await connectRedis();
+ // await client.del('homepage_categoryDynamic');
+
+ let cachedData;
+ if (req.method === 'GET') {
+ cachedData = await client.get('homepage_categoryDynamic');
+
+ if (!cachedData) {
+ const items = await fetchCategoryManagementSolr();
+ await client.set(
+ 'homepage_categoryDynamic',
+ JSON.stringify(items),
+ 'EX',
+ 259200 // Expiry 3 hari
+ );
+ cachedData = await client.get('homepage_categoryDynamic');
+ }
+ const data = cachedData ? JSON.parse(cachedData) : null;
+ res.status(200).json({ data });
+ } else {
+ res.setHeader('Allow', ['GET']);
+ res.status(405).end(`Method ${req.method} Not Allowed`);
+ }
+ } catch (error) {
+ console.error('Error interacting with Redis:', error);
+ res.status(500).json({ error: 'Error interacting with Redis' });
+ }
+}
+
+const fetchCategoryManagementSolr = async () => {
+ let sort = 'sort=sequence_i asc';
+ try {
+ const response = await fetch(
+ `http://34.101.189.218:8983/solr/category_management/query?q=*:*&q.op=OR&indent=true&${sort}&&rows=20`
+ );
+ 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 map = async (promotions) => {
+ return promotions.map((promotion) => {
+ let parsedCategories = promotion.categories.map((category) => {
+ // Parse string JSON utama
+ let parsedCategory = JSON.parse(category);
+
+ // Parse setiap elemen di child_frontend_id_i jika ada
+ if (parsedCategory.child_frontend_id_i) {
+ parsedCategory.child_frontend_id_i =
+ parsedCategory.child_frontend_id_i.map((child) => JSON.parse(child));
+ }
+
+ return parsedCategory;
+ });
+ let productMapped = {
+ id: promotion.id,
+ name: promotion.name_s,
+ image: promotion.image_s,
+ sequence: promotion.sequence_i,
+ numFound: promotion.numFound_i,
+ categories: parsedCategories,
+ category_id: promotion.category_id_i,
+ };
+ return productMapped;
+ });
+};
diff --git a/src/pages/api/flashsale-header.js b/src/pages/api/flashsale-header.js
new file mode 100644
index 00000000..31f8efdd
--- /dev/null
+++ b/src/pages/api/flashsale-header.js
@@ -0,0 +1,40 @@
+import odooApi from '@/core/api/odooApi';
+import { createClient } from 'redis';
+
+const client = createClient();
+
+client.on('error', (err) => console.error('Redis Client Error', err));
+
+const connectRedis = async () => {
+ if (!client.isOpen) {
+ await client.connect();
+ }
+};
+
+export default async function handler(req, res) {
+ try {
+ await connectRedis();
+ const cacheKey = `flashsale_header`;
+ // await client.del(cacheKey);
+ let cachedData = await client.get(cacheKey);
+
+ if (cachedData) {
+ const data = JSON.parse(cachedData);
+ return res.status(200).json({ data });
+ } else {
+ const flashSale = await odooApi('GET', `/api/v1/flashsale/header`);
+
+ await client.set(
+ cacheKey,
+ JSON.stringify(flashSale),
+ 'EX',
+ flashSale.duration
+ );
+ cachedData = await client.get(cacheKey);
+ return res.status(200).json({ data: cachedData });
+ }
+ } catch (error) {
+ console.error('Error interacting with Redis or fetching data:', error);
+ return res.status(500).json({ error: 'Internal Server Error' });
+ }
+}
diff --git a/src/pages/api/hero-banner.js b/src/pages/api/hero-banner.js
new file mode 100644
index 00000000..7a348cfa
--- /dev/null
+++ b/src/pages/api/hero-banner.js
@@ -0,0 +1,45 @@
+import odooApi from '@/core/api/odooApi';
+import { createClient } from 'redis';
+
+const client = createClient();
+
+client.on('error', (err) => console.error('Redis Client Error', err));
+
+const connectRedis = async () => {
+ if (!client.isOpen) {
+ await client.connect();
+ }
+};
+
+export default async function handler(req, res) {
+ const { type } = req.query;
+ try {
+ await connectRedis();
+ const cacheKey = `homepage_bannerSection_${type}`;
+ // await client.del(cacheKey);
+ let cachedData = await client.get(cacheKey);
+
+ if (cachedData) {
+ const data = JSON.parse(cachedData);
+ return res.status(200).json({ data });
+ } else {
+ const dataBannerSections = await odooApi(
+ 'GET',
+ `/api/v1/banner?type=${type}`
+ );
+
+ // Simpan hasil fetch ke Redis dengan masa kadaluarsa 3 hari (259200 detik)
+ await client.set(
+ cacheKey,
+ JSON.stringify(dataBannerSections),
+ 'EX',
+ 259200
+ );
+ cachedData = await client.get(cacheKey);
+ return res.status(200).json({ data: cachedData });
+ }
+ } catch (error) {
+ console.error('Error interacting with Redis or fetching data:', error);
+ return res.status(500).json({ error: 'Internal Server Error' });
+ }
+}
diff --git a/src/pages/api/page-content.js b/src/pages/api/page-content.js
new file mode 100644
index 00000000..a6514505
--- /dev/null
+++ b/src/pages/api/page-content.js
@@ -0,0 +1,43 @@
+import { createClient } from 'redis';
+import { getPageContent } from '~/services/pageContent';
+// import { fetchCategoryManagementSolr } from '../../lib/home/api/categoryManagementApi';
+const client = createClient();
+client.on('error', (err) => console.error('Redis Client Error', err));
+
+const connectRedis = async () => {
+ if (!client.isOpen) {
+ await client.connect();
+ }
+};
+
+export default async function handler(req, res) {
+ const { path } = req.query;
+ try {
+ await connectRedis();
+ // await client.del('onbording-popup');
+
+ let cachedData;
+ if (req.method === 'GET') {
+ cachedData = await client.get(`page-content:${path}`);
+
+ if (!cachedData) {
+ const items = await getPageContent({ path });
+ await client.set(
+ `page-content:${path}`,
+ JSON.stringify(items),
+ 'EX',
+ 604800 // Expiry 1 minggu
+ );
+ cachedData = await client.get(`page-content:${path}`);
+ }
+ const data = cachedData ? JSON.parse(cachedData) : null;
+ res.status(200).json({ data });
+ } else {
+ res.setHeader('Allow', ['GET']);
+ res.status(405).end(`Method ${req.method} Not Allowed`);
+ }
+ } catch (error) {
+ console.error('Error interacting with Redis:', error);
+ res.status(500).json({ error: 'Error interacting with Redis' });
+ }
+}
diff --git a/src/pages/api/search-flashsale.js b/src/pages/api/search-flashsale.js
new file mode 100644
index 00000000..d9e56c83
--- /dev/null
+++ b/src/pages/api/search-flashsale.js
@@ -0,0 +1,45 @@
+import odooApi from '@/core/api/odooApi';
+import { createClient } from 'redis';
+import _ from 'lodash-contrib';
+import axios from 'axios';
+
+const client = createClient();
+
+client.on('error', (err) => console.error('Redis Client Error', err));
+
+const connectRedis = async () => {
+ if (!client.isOpen) {
+ await client.connect();
+ }
+};
+
+export default async function handler(req, res) {
+ const { query, operation, duration } = req.query;
+ try {
+ await connectRedis();
+ const cacheKey = `flashsale_product`;
+ // await client.del(cacheKey);
+ let cachedData = await client.get(cacheKey);
+
+ if (cachedData) {
+ const data = JSON.parse(cachedData);
+ return res.status(200).json({ data });
+ } else {
+ const dataProductSearch = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}&operation=${operation}]`
+ );
+
+ await client.set(
+ cacheKey,
+ JSON.stringify(dataProductSearch.data),
+ 'EX',
+ duration
+ );
+ cachedData = await client.get(cacheKey);
+ return res.status(200).json({ data: cachedData });
+ }
+ } catch (error) {
+ console.error('Error interacting with Redis or fetching data:', error);
+ return res.status(500).json({ error: 'Internal Server Error' });
+ }
+}
diff --git a/src/pages/api/shop/brands.js b/src/pages/api/shop/brands.js
index 9c2824b3..d56e4b13 100644
--- a/src/pages/api/shop/brands.js
+++ b/src/pages/api/shop/brands.js
@@ -1,8 +1,20 @@
import axios from 'axios';
+import { createClient } from 'redis';
const SOLR_HOST = process.env.SOLR_HOST;
+const client = createClient();
+
+client.on('error', (err) => console.error('Redis Client Error', err));
+
+const connectRedis = async () => {
+ if (!client.isOpen) {
+ await client.connect();
+ }
+};
export default async function handler(req, res) {
+ await connectRedis();
+
try {
let params = '*:*';
let sort =
@@ -11,12 +23,12 @@ export default async function handler(req, res) {
if (req.query.params) {
rows = 100;
- switch (req?.query?.params) {
+ switch (req.query.params) {
case 'level_s':
params = 'level_s:prioritas';
break;
case 'search':
- params = `name_s:"${req?.query?.q.toLowerCase()}"`;
+ params = `name_s:"${req.query.q.toLowerCase()}"`;
sort = '';
rows = 1;
break;
@@ -24,11 +36,11 @@ export default async function handler(req, res) {
params = `name_s:${req.query.params}`.toLowerCase();
}
}
- if(req.query.rows) rows = req.query.rows;
-
+ if (req.query.rows) rows = req.query.rows;
+
const url = `${SOLR_HOST}/solr/brands/select?q=${params}&q.op=OR&indent=true&rows=${rows}&${sort}`;
- let brands = await axios(url);
- let dataBrands = responseMap(brands.data.response.docs);
+ const brands = await axios(url);
+ const dataBrands = responseMap(brands.data.response.docs);
res.status(200).json(dataBrands);
} catch (error) {
@@ -39,13 +51,11 @@ export default async function handler(req, res) {
const responseMap = (brands) => {
return brands.map((brand) => {
- let brandMapping = {
+ return {
id: brand.id,
name: brand.display_name_s,
logo: brand.image_s || '',
- sequance: brand.sequence_i || '',
+ sequence: brand.sequence_i || '',
};
-
- return brandMapping;
});
};
diff --git a/src/pages/api/shop/preferredBrand.js b/src/pages/api/shop/preferredBrand.js
new file mode 100644
index 00000000..4cb35c84
--- /dev/null
+++ b/src/pages/api/shop/preferredBrand.js
@@ -0,0 +1,61 @@
+import axios from 'axios';
+import { createClient } from 'redis';
+
+const SOLR_HOST = process.env.SOLR_HOST;
+const client = createClient();
+
+client.on('error', (err) => console.error('Redis Client Error', err));
+
+const connectRedis = async () => {
+ if (!client.isOpen) {
+ await client.connect();
+ }
+};
+
+export default async function handler(req, res) {
+ await connectRedis();
+
+ try {
+ let params = '*:*';
+ let sort =
+ 'sort=if(exists(sequence_i),0,1) asc,sequence_i asc, if(exists(image_s),0,1) asc ';
+ let rows = 20;
+
+ if (req.query.params) {
+ rows = 20;
+ switch (req.query.params) {
+ case 'level_s':
+ params = 'level_s:prioritas';
+ break;
+ case 'search':
+ params = `name_s:"${req.query.q.toLowerCase()}"`;
+ sort = '';
+ rows = 1;
+ break;
+ default:
+ params = `name_s:${req.query.params}`.toLowerCase();
+ }
+ }
+ if (req.query.rows) rows = req.query.rows;
+
+ const url = `${SOLR_HOST}/solr/brands/select?q=${params}&q.op=OR&indent=true&rows=${rows}&${sort}`;
+ const brands = await axios(url);
+ const dataBrands = responseMap(brands.data.response.docs);
+
+ res.status(200).json(dataBrands);
+ } catch (error) {
+ console.error('Error fetching data from Solr:', error);
+ res.status(500).json({ error: 'Internal Server Error' });
+ }
+}
+
+const responseMap = (brands) => {
+ return brands.map((brand) => {
+ return {
+ id: brand.id,
+ name: brand.display_name_s,
+ logo: brand.image_s || '',
+ sequence: brand.sequence_i || '',
+ };
+ });
+};
diff --git a/src/pages/api/shop/product-detail.js b/src/pages/api/shop/product-detail.js
index 247f2a04..faa96028 100644
--- a/src/pages/api/shop/product-detail.js
+++ b/src/pages/api/shop/product-detail.js
@@ -8,7 +8,7 @@ export default async function handler(req, res) {
)
let productVariants = await axios(
process.env.SOLR_HOST +
- `/solr/variants/select?q=template_id_i:${req.query.id}&q.op=OR&indent=true&rows=100&fq=-publish_b:false`
+ `/solr/variants/select?q=template_id_i:${req.query.id}&q.op=OR&indent=true&rows=100&fq=-publish_b:false AND price_tier1_v2_f:[1 TO *]`
)
let auth = req.query.auth === 'false' ? JSON.parse(req.query.auth) : req.query.auth
let result = productMappingSolr(productTemplate.data.response.docs, auth || false)
diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js
index 6269d3ed..63ec7ca0 100644
--- a/src/pages/api/shop/search.js
+++ b/src/pages/api/shop/search.js
@@ -20,9 +20,11 @@ export default async function handler(req, res) {
} = req.query;
let { stock = '' } = req.query;
-
let paramOrderBy = '';
switch (orderBy) {
+ case 'flashsale-discount-desc':
+ paramOrderBy += 'flashsale_discount_f DESC';
+ break;
case 'price-asc':
paramOrderBy += 'price_tier1_v2_f ASC';
break;
@@ -68,16 +70,40 @@ export default async function handler(req, res) {
let checkQ = q.trim().split(/[\s\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/]+/);
let newQ = escapeSolrQuery(q);
- const formattedQuery = `(${newQ.split(' ').map(term => `${term}*`).join(' ') })`;
- const mm = checkQ.length > 2 ? checkQ.length > 5 ? '55%' : '85%' : `${checkQ.length}`;
+ const formattedQuery = `(${newQ
+ .split(' ')
+ .map((term) => (term.length < 2 ? term : `${term}*`)) // Tambahkan '*' hanya jika panjang kata >= 2
+ .join(' ')})`;
+
+ const mm =
+ checkQ.length > 2
+ ? checkQ.length > 5
+ ? '55%'
+ : '85%'
+ : `${checkQ.length}`;
const filterQueries = [
'-publish_b:false',
'product_rating_f:[8 TO *]',
- 'price_tier1_v2_f:[1 TO *]'
+ 'price_tier1_v2_f:[1 TO *]',
];
-
- const fq_ = filterQueries.join('AND ');
+
+ if (fq && source != 'similar' && typeof fq != 'string') {
+ // filterQueries.push(fq);
+ fq.push(...filterQueries);
+ }
+ const fq_ = filterQueries.join(' AND ');
+
+ let keywords = newQ;
+ if (source === 'similar' || checkQ.length < 3) {
+ if (checkQ.length < 2 || checkQ[1].length < 2) {
+ keywords = newQ;
+ } else {
+ keywords = newQ + '*';
+ }
+ } else {
+ keywords = formattedQuery;
+ }
let offset = (page - 1) * limit;
let parameter = [
@@ -87,9 +113,9 @@ export default async function handler(req, res) {
'indent=true',
`facet.query=${escapeSolrQuery(q)}`,
`q.op=OR`,
- `q=${source == 'similar' || checkQ.length < 3 ? checkQ.length < 2 ? newQ : newQ + '*' : formattedQuery }`,
+ `q=${keywords}`,
`defType=edismax`,
- 'qf=name_s description_clean_t category_name manufacture_name_s variants_code_t variants_name_t category_id_ids default_code_s',
+ 'qf=name_s description_clean_t category_name manufacture_name_s variants_code_t variants_name_t category_id_ids default_code_s manufacture_id_i category_id_i ',
`start=${parseInt(offset)}`,
`rows=${limit}`,
`sort=${paramOrderBy}`,
@@ -135,12 +161,13 @@ export default async function handler(req, res) {
if (typeof fq === 'string') parameter.push(`fq=${encodeURIComponent(fq)}`);
// Multi fq in url params
if (Array.isArray(fq))
- parameter = parameter.concat(fq.map((val) => `fq=${encodeURIComponent(val)}`));
-
+ parameter = parameter.concat(
+ fq.map((val) => `fq=${encodeURIComponent(val)}`)
+ );
let result = await axios(
process.env.SOLR_HOST + '/solr/product/select?' + parameter.join('&')
);
-
+
try {
result.data.response.products = productMappingSolr(
result.data.response.docs,
diff --git a/src/pages/api/shop/url-category_brand.js b/src/pages/api/shop/url-category_brand.js
new file mode 100644
index 00000000..160aa166
--- /dev/null
+++ b/src/pages/api/shop/url-category_brand.js
@@ -0,0 +1,20 @@
+import axios from 'axios';
+
+export default async function handler(req, res) {
+ const { url = '', page = 1, limit = 30 } = req.query;
+
+ let offset = (page - 1) * limit;
+
+ const params = [`q.op=AND`, `q=${url ? `"${url}"` : '*'}`, `indent=true`, `rows=${limit}`, `start=${offset}`];
+
+ try {
+ let result = await axios(
+ process.env.SOLR_HOST +
+ `/solr/url_category_brand/select?` +
+ params.join('&')
+ );
+ res.status(200).json(result.data);
+ } catch (error) {
+ res.status(500).json({ error: 'Internal Server Error' });
+ }
+}
diff --git a/src/pages/api/shop/variant-detail.js b/src/pages/api/shop/variant-detail.js
index 08ce75b8..af3525b3 100644
--- a/src/pages/api/shop/variant-detail.js
+++ b/src/pages/api/shop/variant-detail.js
@@ -1,21 +1,28 @@
-import { productMappingSolr, variantsMappingSolr } from '@/utils/solrMapping'
-import axios from 'axios'
+import { productMappingSolr, variantsMappingSolr } from '@/utils/solrMapping';
+import axios from 'axios';
export default async function handler(req, res) {
try {
let productVariants = await axios(
process.env.SOLR_HOST +
`/solr/variants/select?q=id:${req.query.id}&q.op=OR&indent=true`
- )
- let auth = req.query.auth === 'false' ? JSON.parse(req.query.auth) : req.query.auth
+ );
+ let template_id = productVariants.data.response.docs[0].template_id_i;
+ let auth =
+ req.query.auth === 'false' ? JSON.parse(req.query.auth) : req.query.auth;
let productTemplate = await axios(
- process.env.SOLR_HOST + `/solr/product/select?q=id:${req.query.id}&q.op=OR&indent=true`
- )
- let result = variantsMappingSolr(productTemplate.data.response.docs, productVariants.data.response.docs, auth || false)
-
- res.status(200).json(result)
+ process.env.SOLR_HOST +
+ `/solr/product/select?q=id:${template_id}&q.op=OR&indent=true`
+ );
+ let result = variantsMappingSolr(
+ productTemplate.data.response.docs,
+ productVariants.data.response.docs,
+ auth || false
+ );
+
+ res.status(200).json(result);
} catch (error) {
- console.error('Error fetching data from Solr:', error)
- res.status(500).json({ error: 'Internal Server Error' })
+ console.error('Error fetching data from Solr:', error);
+ res.status(500).json({ error: 'Internal Server Error' });
}
-} \ No newline at end of file
+}
diff --git a/src/pages/garansi-resmi.jsx b/src/pages/garansi-resmi.jsx
new file mode 100644
index 00000000..7384a89d
--- /dev/null
+++ b/src/pages/garansi-resmi.jsx
@@ -0,0 +1,13 @@
+import Seo from '@/core/components/Seo'
+import BasicLayout from '@/core/components/layouts/BasicLayout'
+import IframeContent from '@/lib/iframe/components/IframeContent'
+
+export default function GaransiResmi() {
+ return (
+ <BasicLayout>
+ <Seo title='Garansi Resmi - Indoteknik.com' />
+
+ <IframeContent url={`${process.env.NEXT_PUBLIC_ODOO_HOST}/content?url=garansi-resmi`} />
+ </BasicLayout>
+ )
+}
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index 6077c192..2ec1231a 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -1,21 +1,19 @@
-import dynamic from 'next/dynamic';
-import { useEffect, useRef, useState } from 'react';
import { HeroBannerSkeleton } from '@/components/skeleton/BannerSkeleton';
import { PopularProductSkeleton } from '@/components/skeleton/PopularProductSkeleton';
+import odooApi from '@/core/api/odooApi';
import Seo from '@/core/components/Seo';
import DelayRender from '@/core/components/elements/DelayRender/DelayRender';
import DesktopView from '@/core/components/views/DesktopView';
import MobileView from '@/core/components/views/MobileView';
import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton';
-import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton';
import BannerPromoSkeleton from '@/lib/home/components/Skeleton/BannerPromoSkeleton';
-import PromotinProgram from '@/lib/promotinProgram/components/HomePage';
+import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton';
+import dynamic from 'next/dynamic';
+import { useEffect, useRef, useState } from 'react';
+import { getAuth } from '~/libs/auth';
import PagePopupIformation from '~/modules/popup-information'; // need change to dynamic and ssr : false
import CategoryPilihan from '../lib/home/components/CategoryPilihan';
-import odooApi from '@/core/api/odooApi';
-import { getAuth } from '~/libs/auth';
// import { getAuth } from '~/libs/auth';
-import useProductDetail from '~/modules/product-detail/stores/useProductDetail';
const BasicLayout = dynamic(() =>
import('@/core/components/layouts/BasicLayout'),{ssr: false}
@@ -47,36 +45,38 @@ const FlashSale = dynamic(
}
);
-const ProgramPromotion = dynamic(() =>
- import('@/lib/home/components/PromotionProgram'),
-{
- loading: () => <BannerPromoSkeleton />,
-}
+const ProgramPromotion = dynamic(
+ () => import('@/lib/home/components/PromotionProgram'),
+ {
+ loading: () => <BannerPromoSkeleton />,
+ }
);
const BannerSection = dynamic(() =>
- import('@/lib/home/components/BannerSection'), {ssr: false}
-);
-const CategoryHomeId = dynamic(() =>
- import('@/lib/home/components/CategoryHomeId'), {ssr: false}
+ import('@/lib/home/components/BannerSection')
+);
+const CategoryHomeId = dynamic(
+ () => import('@/lib/home/components/CategoryHomeId'),
+ { ssr: false }
);
const CategoryDynamic = dynamic(() =>
- import('@/lib/home/components/CategoryDynamic'), {ssr: false}
+ import('@/lib/home/components/CategoryDynamic')
);
const CategoryDynamicMobile = dynamic(() =>
-import('@/lib/home/components/CategoryDynamicMobile'), {ssr: false}
+ import('@/lib/home/components/CategoryDynamicMobile')
);
-const CustomerReviews = dynamic(() =>
- import('@/lib/review/components/CustomerReviews'), {ssr: false}
+const CustomerReviews = dynamic(
+ () => import('@/lib/review/components/CustomerReviews'),
+ { ssr: false }
); // need to ssr:false
-const ServiceList = dynamic(() => import('@/lib/home/components/ServiceList'), {ssr: false}); // need to ssr: false
+const ServiceList = dynamic(() => import('@/lib/home/components/ServiceList'), {
+ ssr: false,
+}); // need to ssr: false
-
-
-export default function Home({categoryId}) {
+export default function Home({ categoryId }) {
const bannerRef = useRef(null);
const wrapperRef = useRef(null);
@@ -87,123 +87,110 @@ export default function Home({categoryId}) {
bannerRef.current?.querySelector(':first-child')?.clientHeight + 'px';
};
- useEffect(() => {
- const loadCategories = async () => {
- const getCategories = await odooApi('GET', '/api/v1/category/child?partner_id='+{categoryId})
- if(getCategories){
- setDataCategories(getCategories)
- }
- }
- loadCategories()
- }, [])
-
- const [dataCategories, setDataCategories] = useState([])
return (
<>
- <BasicLayout>
- <Seo
- title='Indoteknik.com: B2B Industrial Supply & Solution'
- description='Temukan pilihan produk B2B Industri &amp; Alat Teknik untuk Perusahaan, UMKM &amp; Pemerintah dengan lengkap, mudah dan transparan.'
- additionalMetaTags={[
- {
- name: 'keywords',
- content: 'indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air',
- },
- ]} />
-
- <PagePopupIformation />
-
- <DesktopView>
- <div className='container mx-auto'>
- <div
- className='flex min-h-[400px] h-[460px]'
- ref={wrapperRef}
- onLoad={handleOnLoad}
- >
- <div className='w-2/12'>
- <HeroBannerSecondary />
- </div>
- <div className='w-7/12 px-1' ref={bannerRef}>
- <HeroBanner />
- </div>
- <div className='w-3/12'>
- <DelayRender renderAfter={200}>
- <PopularProduct />
- </DelayRender>
+ <BasicLayout>
+ <Seo
+ title='Indoteknik.com: B2B Industrial Supply & Solution'
+ description='Temukan pilihan produk B2B Industri &amp; Alat Teknik untuk Perusahaan, UMKM &amp; Pemerintah dengan lengkap, mudah dan transparan.'
+ additionalMetaTags={[
+ {
+ name: 'keywords',
+ content:
+ 'indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air',
+ },
+ ]}
+ />
+
+ <PagePopupIformation />
+
+ <DesktopView>
+ <div className='container mx-auto'>
+ <div
+ className='flex min-h-[400px] h-[460px]'
+ ref={wrapperRef}
+ onLoad={handleOnLoad}
+ >
+ <div className='w-2/12'>
+ <HeroBannerSecondary />
+ </div>
+ <div className='w-7/12 px-1' ref={bannerRef}>
+ <HeroBanner />
+ </div>
+ <div className='w-3/12'>
+ <DelayRender renderAfter={200}>
+ <PopularProduct />
+ </DelayRender>
+ </div>
</div>
- </div>
- <div className='my-16 flex flex-col gap-y-8'>
- <ServiceList />
- <div id='flashsale'>
- <PreferredBrand />
+ <div className='my-16 flex flex-col gap-y-8'>
+ <ServiceList />
+ <div id='flashsale'>
+ <PreferredBrand />
+ </div>
+ {!auth?.feature?.soApproval && (
+ <>
+ <DelayRender renderAfter={200}>
+ <ProgramPromotion />
+ </DelayRender>
+ <DelayRender renderAfter={200}>
+ <FlashSale />
+ </DelayRender>
+ </>
+ )}
+ {/* <PromotinProgram /> */}
+ <CategoryPilihan />
+ <CategoryDynamic />
+ <CategoryHomeId />
+ <BannerSection />
+ <CustomerReviews />
</div>
+ </div>
+ </DesktopView>
+ <MobileView>
+ <DelayRender renderAfter={200}>
+ <HeroBanner />
+ </DelayRender>
+ <div className='flex flex-col gap-y-4 my-6'>
+ <DelayRender renderAfter={400}>
+ <ServiceList />
+ </DelayRender>
+ <DelayRender renderAfter={400}>
+ <div id='flashsale'>
+ <PreferredBrand />
+ </div>
+ </DelayRender>
{!auth?.feature?.soApproval && (
<>
- <DelayRender renderAfter={200}>
- <ProgramPromotion />
- </DelayRender>
- <DelayRender renderAfter={200}>
- <FlashSale />
- </DelayRender>
+ <DelayRender renderAfter={400}>
+ <ProgramPromotion />
+ </DelayRender>
+ <DelayRender renderAfter={600}>
+ <FlashSale />
+ </DelayRender>
</>
)}
- {/* <PromotinProgram /> */}
- {dataCategories &&(
- <CategoryPilihan categories={dataCategories} />
- )}
- <CategoryDynamic />
- <CategoryHomeId />
- <BannerSection />
- <CustomerReviews />
+ <DelayRender renderAfter={600}>
+ {/* <PromotinProgram /> */}
+ </DelayRender>
+ <DelayRender renderAfter={600}>
+ <CategoryPilihan />
+ <CategoryDynamicMobile />
+ </DelayRender>
+ <DelayRender renderAfter={800}>
+ <PopularProduct />
+ </DelayRender>
+ <DelayRender renderAfter={1000}>
+ <CategoryHomeId />
+ <BannerSection />
+ </DelayRender>
+ <DelayRender renderAfter={1200}>
+ <CustomerReviews />
+ </DelayRender>
</div>
- </div>
- </DesktopView>
- <MobileView>
- <DelayRender renderAfter={200}>
- <HeroBanner />
- </DelayRender>
- <div className='flex flex-col gap-y-4 my-6'>
- <DelayRender renderAfter={400}>
- <ServiceList />
- </DelayRender>
- <DelayRender renderAfter={400}>
- <div id='flashsale'>
- <PreferredBrand />
- </div>
- </DelayRender>
- {!auth?.feature?.soApproval && (
- <>
- <DelayRender renderAfter={400}>
- <ProgramPromotion />
- </DelayRender>
- <DelayRender renderAfter={600}>
- <FlashSale />
- </DelayRender>
- </>
- )}
- <DelayRender renderAfter={600}>
- {/* <PromotinProgram /> */}
- </DelayRender>
- <DelayRender renderAfter={600}>
- {dataCategories &&(
- <CategoryPilihan categories={dataCategories} />
- )}
- <CategoryDynamicMobile />
- </DelayRender>
- <DelayRender renderAfter={800}>
- <PopularProduct />
- </DelayRender>
- <DelayRender renderAfter={1000}>
- <CategoryHomeId />
- <BannerSection />
- </DelayRender>
- <DelayRender renderAfter={1200}>
- <CustomerReviews />
- </DelayRender>
- </div>
- </MobileView>
- </BasicLayout>
- </>
+ </MobileView>
+ </BasicLayout>
+ </>
);
-} \ No newline at end of file
+}
diff --git a/src/pages/my/address/[id]/edit.jsx b/src/pages/my/address/[id]/edit.jsx
index c552659b..19d7af41 100644
--- a/src/pages/my/address/[id]/edit.jsx
+++ b/src/pages/my/address/[id]/edit.jsx
@@ -37,12 +37,15 @@ export async function getServerSideProps(context) {
mobile: address.mobile,
street: address.street,
zip: address.zip,
- city: address.city?.id || '',
+ state: address.stateId?.id || '',
+ oldCity: address.city?.id || '',
+ city: '',
oldDistrict: address.district?.id || '',
district: '',
oldSubDistrict: address.subDistrict?.id || '',
subDistrict: '',
business_name: '',
};
+ // console.log('ini default',defaultValues);
return { props: { id, defaultValues } };
}
diff --git a/src/pages/pembayaran-tempo.jsx b/src/pages/pembayaran-tempo.jsx
new file mode 100644
index 00000000..363e3099
--- /dev/null
+++ b/src/pages/pembayaran-tempo.jsx
@@ -0,0 +1,13 @@
+import Seo from '@/core/components/Seo'
+import BasicLayout from '@/core/components/layouts/BasicLayout'
+import IframeContent from '@/lib/iframe/components/IframeContent'
+
+export default function PembnayaranTempo() {
+ return (
+ <BasicLayout>
+ <Seo title='Pambayaran Tempo - Indoteknik.com' />
+
+ <IframeContent url={`${process.env.NEXT_PUBLIC_ODOO_HOST}/content?url=pembayaran-tempo`} />
+ </BasicLayout>
+ )
+}
diff --git a/src/pages/shop/checkout/[status].jsx b/src/pages/shop/checkout/[status].jsx
index 2c3bebcf..0d5cffe8 100644
--- a/src/pages/shop/checkout/[status].jsx
+++ b/src/pages/shop/checkout/[status].jsx
@@ -1,22 +1,22 @@
-import BasicLayout from '@/core/components/layouts/BasicLayout'
-import IsAuth from '@/lib/auth/components/IsAuth'
-import FinishCheckoutComponent from '@/lib/checkout/components/FinishCheckout'
-import { useRouter } from 'next/router'
-import axios from 'axios'
-import Seo from '@/core/components/Seo'
+import BasicLayout from '@/core/components/layouts/BasicLayout';
+import IsAuth from '@/lib/auth/components/IsAuth';
+import FinishCheckoutComponent from '@/lib/checkout/components/FinishCheckout';
+import { useRouter } from 'next/router';
+import axios from 'axios';
+import Seo from '@/core/components/Seo';
export async function getServerSideProps(context) {
- const { order_id } = context.query
+ const { order_id } = context.query;
await axios.post(
`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/finish-checkout?orderName=${order_id}`,
{},
{ headers: context.req.headers }
- )
- return { props: {} }
+ );
+ return { props: {} };
}
export default function Finish() {
- const router = useRouter()
+ const router = useRouter();
return (
<>
@@ -28,5 +28,5 @@ export default function Finish() {
</BasicLayout>
</IsAuth>
</>
- )
+ );
}
diff --git a/src/pages/shop/find/[slug].jsx b/src/pages/shop/find/[slug].jsx
new file mode 100644
index 00000000..268b1e56
--- /dev/null
+++ b/src/pages/shop/find/[slug].jsx
@@ -0,0 +1,69 @@
+import axios from 'axios';
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import Seo from '@/core/components/Seo';
+import dynamic from 'next/dynamic';
+import { get } from 'lodash-contrib';
+import { getIdFromSlug, getNameFromSlug } from '@/core/utils/slug';
+import { capitalizeEachWord } from '../../../utils/capializeFIrstWord';
+
+const BasicLayout = dynamic(() =>
+ import('@/core/components/layouts/BasicLayout')
+);
+const ProductSearch = dynamic(() =>
+ import('@/lib/product/components/ProductSearch')
+);
+
+const BASE_URL = 'https://indoteknik.com';
+export default function FindPage() {
+ const route = useRouter();
+ const qSlug = route?.query?.slug || null;
+ const url = BASE_URL + route.asPath.split('?')[0];
+ const [result, setResult] = useState(null);
+ const [query, setQuery] = useState(null);
+
+ const __slug = qSlug ? getNameFromSlug(route?.query?.slug) +' '+ getIdFromSlug(route?.query?.slug) : '';
+ const slug = capitalizeEachWord(__slug);
+
+ const getUrls = async (url) => {
+ try {
+ let response = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/url-category_brand?url=${url}`
+ );
+ let result = response?.data?.response?.docs[0] || null;
+ setResult(result);
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ }
+ };
+
+ useEffect(() => {
+ getUrls(url);
+ }, []);
+
+ useEffect(() => {
+ if (result) {
+ let fq = `category_parent_ids:${result.category_id_i} AND manufacture_id_i:${result.brand_id_i}`;
+ setQuery({
+ fq: fq,
+ });
+ }
+ }, [result]);
+
+ return (
+ <BasicLayout>
+ <Seo
+ title={`Beli ${slug} Original & Harga Terjangkau - indoteknik.com`}
+ description={`Beli ${slug} Kirim Jakarta Surabaya Semarang Makassar Manado Denpasar Balikpapan Medan Palembang Lampung Bali Bandung Makassar Manado.`}
+ additionalMetaTags={[
+ {
+ property: 'keywords',
+ content: `Beli ${slug}, harga ${slug}, ${slug} murah, toko ${slug}, ${slug} jakarta, ${slug} surabaya`,
+
+ },
+ ]}
+ />
+ {query && <ProductSearch query={query} prefixUrl={`${route.asPath}`} />}
+ </BasicLayout>
+ );
+}
diff --git a/src/pages/shop/product/variant/[slug].jsx b/src/pages/shop/product/variant/[slug].jsx
index 42f38774..2c0dd64b 100644
--- a/src/pages/shop/product/variant/[slug].jsx
+++ b/src/pages/shop/product/variant/[slug].jsx
@@ -32,16 +32,9 @@ export async function getServerSideProps(context) {
tier
);
let product = response.data;
- // let product = await variantApi({ id: getIdFromSlug(slug), headers: { Token: authToken } })
if (product?.length == 1) {
product = product[0];
- /* const regexHtmlTags = /(<([^>]+)>)/gi
- const regexHtmlTagsExceptP = /<\/?(?!p\b)[^>]*>/g
- product.description = product.description
- .replace(regexHtmlTagsExceptP, ' ')
- .replace(regexHtmlTags, ' ')
- .trim()*/
} else {
product = null;
}
diff --git a/src/pages/sitemap/categories-brand.xml.js b/src/pages/sitemap/categories-brand.xml.js
new file mode 100644
index 00000000..b23363e9
--- /dev/null
+++ b/src/pages/sitemap/categories-brand.xml.js
@@ -0,0 +1,35 @@
+import productSearchApi from '@/lib/product/api/productSearchApi'
+import { create } from 'xmlbuilder'
+import _ from 'lodash-contrib'
+import axios from 'axios'
+
+export async function getServerSideProps({ res }) {
+ const baseUrl = process.env.SELF_HOST + '/sitemap/categories-brand'
+ const limit = 500
+ const categories = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/url-category_brand?limit=${limit}`
+ )
+ const pageCount = Math.ceil(categories.data.response.numFound / limit)
+ const pages = Array.from({ length: pageCount }, (_, i) => i + 1)
+ const sitemapIndex = create('sitemapindex', { encoding: 'UTF-8' }).att(
+ 'xmlns',
+ 'http://www.sitemaps.org/schemas/sitemap/0.9'
+ )
+
+ const date = new Date()
+ pages.forEach((page) => {
+ const sitemap = sitemapIndex.ele('sitemap')
+ sitemap.ele('loc', `${baseUrl}/${page}.xml`)
+ sitemap.ele('lastmod', date.toISOString().slice(0, 10))
+ })
+
+ res.setHeader('Content-Type', 'text/xml')
+ res.write(sitemapIndex.end())
+ res.end()
+
+ return { props: {} }
+}
+
+export default function SitemapProducts() {
+ return null
+}
diff --git a/src/pages/sitemap/categories-brand/[page].js b/src/pages/sitemap/categories-brand/[page].js
new file mode 100644
index 00000000..6b55e426
--- /dev/null
+++ b/src/pages/sitemap/categories-brand/[page].js
@@ -0,0 +1,43 @@
+
+import productSearchApi from '@/lib/product/api/productSearchApi'
+import { create } from 'xmlbuilder'
+import _ from 'lodash-contrib'
+import { createSlug } from '@/core/utils/slug'
+import axios from 'axios'
+
+export async function getServerSideProps({ query, res }) {
+ const baseUrl = process.env.SELF_HOST + '/shop/product/'
+ const { page } = query
+ const limit = 500
+ const categories = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/url-category_brand?limit=${limit}&page=${page.replace(
+ '.xml',
+ ''
+ )}`
+ )
+
+ const sitemap = create('urlset', { encoding: 'utf-8' }).att(
+ 'xmlns',
+ 'http://www.sitemaps.org/schemas/sitemap/0.9'
+ )
+
+ const date = new Date()
+ categories.data.response.docs.forEach((product) => {
+ const url = sitemap.ele('url')
+ const loc = product.url_s;
+ url.ele('loc', loc)
+ url.ele('lastmod', date.toISOString().slice(0, 10))
+ url.ele('changefreq', 'daily')
+ url.ele('priority', '0.8')
+ })
+
+ res.setHeader('Content-Type', 'text/xml')
+ res.write(sitemap.end())
+ res.end()
+
+ return { props: {} }
+}
+
+export default function SitemapProducts() {
+ return null
+}
diff --git a/src/utils/capializeFIrstWord.js b/src/utils/capializeFIrstWord.js
new file mode 100644
index 00000000..b62d0c06
--- /dev/null
+++ b/src/utils/capializeFIrstWord.js
@@ -0,0 +1,9 @@
+export const capitalizeEachWord = (str) => {
+ return str
+ .split(' ') // Pisahkan string menjadi array kata-kata
+ .map((word) => // Ubah huruf pertama setiap kata menjadi besar
+ word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
+ )
+ .join(' '); // Gabungkan kembali menjadi string
+ };
+ \ No newline at end of file
diff --git a/src/utils/solrMapping.js b/src/utils/solrMapping.js
index f73e966a..ecd62be2 100644
--- a/src/utils/solrMapping.js
+++ b/src/utils/solrMapping.js
@@ -43,6 +43,7 @@ export const productMappingSolr = (products, pricelist) => {
let productMapped = {
id: product.product_id_i || '',
image: product.image_s || '',
+ imageMobile: product.image_mobile_s || '',
code: product.default_code_s || '',
description: product.description_t || '',
displayName: product.display_name_s || '',
@@ -74,6 +75,7 @@ export const productMappingSolr = (products, pricelist) => {
name: product.manufacture_name_s || '',
imagePromotion1: product.image_promotion_1_s || '',
imagePromotion2: product.image_promotion_2_s || '',
+ logo: product.x_logo_manufacture_s || '',
};
}
@@ -127,12 +129,14 @@ export const variantsMappingSolr = (parent, products, pricelist) => {
manufacture: {},
parent: {},
qtySold: product?.qty_sold_f || 0,
+ is_in_bu: product?.is_in_bu_b || false,
};
if (product.manufacture_id_i && product.manufacture_name_s) {
productMapped.manufacture = {
id: product.manufacture_id_i || '',
name: product.manufacture_name_s || '',
+ logo: parent[0]?.x_logo_manufacture_s,
};
}
productMapped.parent = {