summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorHATEC\SPVDEV001 <tri.susilo@altama.co.id>2023-05-08 16:44:09 +0700
committerHATEC\SPVDEV001 <tri.susilo@altama.co.id>2023-05-08 16:44:09 +0700
commit486f85a45fc7e0669576f59824a31be472ed25bb (patch)
tree0268afa8efe48746e040611ba41ad2cafda7ad08 /src/lib
parentcff198277e14450f8d20d9e18548325e6f277682 (diff)
parent30fc50600009ca54f085d594d838803c107e87f2 (diff)
Merge branch 'master' into development_tri/implementasi_raja_ongkir
# Conflicts: # src/lib/checkout/components/Checkout.jsx
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/auth/components/CompanyProfile.jsx2
-rw-r--r--src/lib/auth/components/Menu.jsx7
-rw-r--r--src/lib/auth/components/PersonalProfile.jsx4
-rw-r--r--src/lib/auth/components/RegisterDesktop.jsx14
-rw-r--r--src/lib/auth/components/RegisterMobile.jsx11
-rw-r--r--src/lib/auth/hooks/useRegister.js13
-rw-r--r--src/lib/brand/components/Brand.jsx29
-rw-r--r--src/lib/cart/components/Cart.jsx156
-rw-r--r--src/lib/category/components/Category.jsx11
-rw-r--r--src/lib/checkout/components/Checkout.jsx18
-rw-r--r--src/lib/checkout/components/FinishCheckout.jsx2
-rw-r--r--src/lib/checkout/email/FinishCheckoutEmail.jsx3
-rw-r--r--src/lib/content/components/PageContent.jsx19
-rw-r--r--src/lib/flashSale/api/flashSaleApi.js8
-rw-r--r--src/lib/flashSale/components/FlashSale.jsx66
-rw-r--r--src/lib/form/components/KunjunganService.jsx70
-rw-r--r--src/lib/form/components/MediaRelations.jsx237
-rw-r--r--src/lib/form/components/Merchant.jsx264
-rw-r--r--src/lib/form/components/PembayaranTempo.jsx2
-rw-r--r--src/lib/form/components/SuratDukungan.jsx140
-rw-r--r--src/lib/home/components/HeroBanner.jsx9
-rw-r--r--src/lib/home/components/PreferredBrand.jsx2
-rw-r--r--src/lib/invoice/components/Invoices.jsx12
-rw-r--r--src/lib/product/api/productApi.js4
-rw-r--r--src/lib/product/api/productSearchApi.js4
-rw-r--r--src/lib/product/components/Product/ProductDesktop.jsx209
-rw-r--r--src/lib/product/components/Product/ProductMobile.jsx25
-rw-r--r--src/lib/product/components/ProductCard.jsx10
-rw-r--r--src/lib/product/components/ProductSearch.jsx64
-rw-r--r--src/lib/quotation/components/Quotation.jsx9
-rw-r--r--src/lib/transaction/components/Transaction.jsx2
-rw-r--r--src/lib/transaction/components/Transactions.jsx19
-rw-r--r--src/lib/variant/components/VariantCard.jsx9
33 files changed, 1132 insertions, 322 deletions
diff --git a/src/lib/auth/components/CompanyProfile.jsx b/src/lib/auth/components/CompanyProfile.jsx
index 854aa246..13d4a194 100644
--- a/src/lib/auth/components/CompanyProfile.jsx
+++ b/src/lib/auth/components/CompanyProfile.jsx
@@ -76,7 +76,7 @@ const CompanyProfile = () => {
Dibawah ini adalah data usaha yang anda masukkan, periksa kembali data usaha anda.
</div>
</div>
- <div className='p-2 bg-gray_r-3 rounded'>
+ <div className='ml-2 p-2 bg-gray_r-3 rounded'>
{!isOpen && <ChevronDownIcon className='w-6' />}
{isOpen && <ChevronUpIcon className='w-6' />}
</div>
diff --git a/src/lib/auth/components/Menu.jsx b/src/lib/auth/components/Menu.jsx
index 9a73609d..8a8e2e8a 100644
--- a/src/lib/auth/components/Menu.jsx
+++ b/src/lib/auth/components/Menu.jsx
@@ -9,10 +9,13 @@ const Menu = () => {
return (
<div className='grid grid-cols-1 bg-white border border-gray_r-6 rounded py-2 px-4'>
<div className='mt-4 mb-1 font-medium'>Menu</div>
- <LinkItem href='/my/transactions' active={routeStartWith('/my/transaction')}>
+ <LinkItem href='/my/quotations' active={routeStartWith('/my/quotations')}>
+ Daftar Quotation
+ </LinkItem>
+ <LinkItem href='/my/transactions' active={routeStartWith('/my/transactions')}>
Daftar Transaksi
</LinkItem>
- <LinkItem href='/my/invoices' active={routeStartWith('/my/invoice')}>
+ <LinkItem href='/my/invoices' active={routeStartWith('/my/invoices')}>
Invoice & Faktur Pajak
</LinkItem>
<LinkItem href='/my/wishlist' active={routeStartWith('/my/wishlist')}>
diff --git a/src/lib/auth/components/PersonalProfile.jsx b/src/lib/auth/components/PersonalProfile.jsx
index 4a533ae9..bca54e24 100644
--- a/src/lib/auth/components/PersonalProfile.jsx
+++ b/src/lib/auth/components/PersonalProfile.jsx
@@ -9,7 +9,7 @@ import editPersonalProfileApi from '../api/editPersonalProfileApi'
const PersonalProfile = () => {
const auth = useAuth()
- const [isOpen, setIsOpen] = useState(false)
+ const [isOpen, setIsOpen] = useState(true)
const toggle = () => setIsOpen(!isOpen)
const { register, setValue, handleSubmit } = useForm({
defaultValues: {
@@ -54,7 +54,7 @@ const PersonalProfile = () => {
Dibawah ini adalah data diri yang anda masukan, periksa kembali data diri anda
</div>
</div>
- <div className='p-2 bg-gray_r-3 rounded'>
+ <div className='ml-2 p-2 bg-gray_r-3 rounded'>
{!isOpen && <ChevronDownIcon className='w-6' />}
{isOpen && <ChevronUpIcon className='w-6' />}
</div>
diff --git a/src/lib/auth/components/RegisterDesktop.jsx b/src/lib/auth/components/RegisterDesktop.jsx
index 482a9ce3..f624fba7 100644
--- a/src/lib/auth/components/RegisterDesktop.jsx
+++ b/src/lib/auth/components/RegisterDesktop.jsx
@@ -4,6 +4,7 @@ import Link from '@/core/components/elements/Link/Link'
import Alert from '@/core/components/elements/Alert/Alert'
import PageContent from '@/lib/content/components/PageContent'
import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import ReCAPTCHA from 'react-google-recaptcha'
const RegisterDesktop = () => {
const {
@@ -16,6 +17,7 @@ const RegisterDesktop = () => {
fullnameRef,
emailRef,
passwordRef,
+ recaptchaRef,
tnd,
setTnd
} = useRegister()
@@ -89,6 +91,7 @@ const RegisterDesktop = () => {
placeholder='••••••••••••'
/>
</div>
+ <ReCAPTCHA ref={recaptchaRef} sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} />
<div class='flex items-center mt-4 '>
<input
type='checkbox'
@@ -96,11 +99,12 @@ const RegisterDesktop = () => {
className='form-input flex items-start w-fit mr-2'
required
/>
- <label
- onClick={() => setTnd(true)}
- className='inline cursor-pointer text-danger-500'
- >
- Syarat dan Ketentuan
+ <label className='inline'>
+ Dengan ini saya menyetujui{' '}
+ <span onClick={() => setTnd(true)} className='cursor-pointer text-danger-500'>
+ syarat dan ketentuan
+ </span>{' '}
+ yang berlaku
</label>
</div>
<button
diff --git a/src/lib/auth/components/RegisterMobile.jsx b/src/lib/auth/components/RegisterMobile.jsx
index f5c818e7..80ea6ab0 100644
--- a/src/lib/auth/components/RegisterMobile.jsx
+++ b/src/lib/auth/components/RegisterMobile.jsx
@@ -6,6 +6,7 @@ import useRegister from '../hooks/useRegister'
import MobileView from '@/core/components/views/MobileView'
import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
import PageContent from '@/lib/content/components/PageContent'
+import ReCAPTCHA from 'react-google-recaptcha'
const RegisterMobile = () => {
const {
@@ -18,6 +19,7 @@ const RegisterMobile = () => {
fullnameRef,
emailRef,
passwordRef,
+ recaptchaRef,
tnd,
setTnd
} = useRegister()
@@ -93,6 +95,7 @@ const RegisterMobile = () => {
placeholder='••••••••••••'
/>
</div>
+ <ReCAPTCHA ref={recaptchaRef} sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} />
<div class='flex items-center mt-4 '>
<input
type='checkbox'
@@ -100,8 +103,12 @@ const RegisterMobile = () => {
className='form-input flex items-start w-fit mr-2'
required
/>
- <label onClick={() => setTnd(true)} className='inline cursor-pointer text-danger-500'>
- Syarat dan Ketentuan
+ <label className='inline'>
+ Dengan ini saya menyetujui{' '}
+ <span onClick={() => setTnd(true)} className='cursor-pointer text-danger-500'>
+ syarat dan ketentuan
+ </span>{' '}
+ yang berlaku
</label>
</div>
<button type='submit' className='btn-yellow w-full mt-2' disabled={!isValid || isLoading}>
diff --git a/src/lib/auth/hooks/useRegister.js b/src/lib/auth/hooks/useRegister.js
index 4b0b0d60..1a9412f8 100644
--- a/src/lib/auth/hooks/useRegister.js
+++ b/src/lib/auth/hooks/useRegister.js
@@ -33,8 +33,20 @@ const useRegister = () => {
companyNameRef.current.value = ''
}
+ const recaptchaRef = useRef(null)
+
const handleSubmit = async (e) => {
e.preventDefault()
+
+ const recaptchaValue = recaptchaRef.current.getValue()
+ if (!recaptchaValue) {
+ setAlert({
+ children: 'ReCaptcha harus diisi',
+ type: 'info'
+ })
+ return
+ }
+
setAlert(null)
setIsLoading(true)
const { fullname, email, password, companyName } = inputVal()
@@ -75,6 +87,7 @@ const useRegister = () => {
fullnameRef,
emailRef,
passwordRef,
+ recaptchaRef,
tnd,
setTnd
}
diff --git a/src/lib/brand/components/Brand.jsx b/src/lib/brand/components/Brand.jsx
index 6ebb8aa7..3c411969 100644
--- a/src/lib/brand/components/Brand.jsx
+++ b/src/lib/brand/components/Brand.jsx
@@ -1,5 +1,5 @@
import useBrand from '../hooks/useBrand'
-import Image from '@/core/components/elements/Image/Image'
+import Image from 'next/image'
import { Swiper, SwiperSlide } from 'swiper/react'
import { Pagination, Autoplay } from 'swiper'
@@ -29,6 +29,15 @@ const Brand = ({ id }) => {
<>
<div>
{brand.isLoading && <ImageSkeleton />}
+ {brand.data?.banners?.length == 0 && (
+ <Image
+ src='/images/default-banner-brand.jpg'
+ alt='Brand - Indoteknik'
+ width={1024}
+ height={512}
+ className='w-full h-auto'
+ />
+ )}
{brand.data && (
<>
<Swiper
@@ -43,6 +52,8 @@ const Brand = ({ id }) => {
<Image
src={banner}
alt={`Brand ${brand.data?.name} - Indoteknik`}
+ width={1024}
+ height={512}
className='w-full h-auto'
/>
</SwiperSlide>
@@ -55,6 +66,8 @@ const Brand = ({ id }) => {
src={brand?.data?.logo}
alt={brand?.data?.name}
className='w-32 p-2 border borde-gray_r-6 rounded'
+ width={256}
+ height={128}
/>
)}
{!brand?.data?.logo && (
@@ -69,10 +82,20 @@ const Brand = ({ id }) => {
<Divider />
</>
</MobileView>
+
<DesktopView>
<div className='container mx-auto mt-10 mb-3'>
<div className='min-h-[150px]'>
{brand.isLoading && <ImageSkeleton />}
+ {brand.data?.banners?.length == 0 && (
+ <Image
+ src='/images/default-banner-brand.jpg'
+ alt='Brand - Indoteknik'
+ width={1024}
+ height={512}
+ className='w-full h-auto'
+ />
+ )}
{brand.data && (
<>
<Swiper
@@ -87,6 +110,8 @@ const Brand = ({ id }) => {
<Image
src={banner}
alt={`Brand ${brand.data?.name} - Indoteknik`}
+ width={1024}
+ height={512}
className='w-full h-auto'
/>
</SwiperSlide>
@@ -99,6 +124,8 @@ const Brand = ({ id }) => {
src={brand?.data?.logo}
alt={brand?.data?.name}
className='w-32 p-2 border borde-gray_r-6 rounded'
+ width={1024}
+ height={512}
/>
)}
{!brand?.data?.logo && (
diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx
index d0685fe3..1008bffc 100644
--- a/src/lib/cart/components/Cart.jsx
+++ b/src/lib/cart/components/Cart.jsx
@@ -16,6 +16,7 @@ import MobileView from '@/core/components/views/MobileView'
import DesktopView from '@/core/components/views/DesktopView'
import ProductCard from '@/lib/product/components/ProductCard'
import productSearchApi from '@/lib/product/api/productSearchApi'
+import whatsappUrl from '@/core/utils/whatsappUrl'
const Cart = () => {
const router = useRouter()
@@ -72,7 +73,7 @@ const Cart = () => {
}, [products])
useEffect(() => {
- const LoadProductSImilar = async () => {
+ const LoadProductSimilar = async () => {
const randProductIndex = Math.floor(Math.random() * products.length)
const productLoad = await productSearchApi({
query: `q=${products?.[randProductIndex].parent.name}&limit=10`
@@ -80,8 +81,8 @@ const Cart = () => {
setProductRecomendation(productLoad)
}
- if (products?.length > 0) LoadProductSImilar()
- }, [products])
+ if (products?.length > 0 && !productRecomendation) LoadProductSimilar()
+ }, [products, productRecomendation])
const updateQuantity = (value, productId, operation = '') => {
let productIndex = products.findIndex((product) => product.id == productId)
@@ -329,91 +330,92 @@ const Cart = () => {
<td colSpan={6}>Keranjang belanja anda masih kosong</td>
</tr>
)}
- {products && products?.map((product) => (
- <tr key={product.id}>
- <td>
- <input
- type='checkbox'
- onClick={() => toggleSelected(product.id)}
- checked={product?.selected}
- className='accent-danger-500 w-4'
- />
- </td>
- <td className='flex'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- className='w-[20%] flex-shrink-0'
- >
- <Image
- src={product?.parent?.image}
- alt={product?.name}
- className='object-contain object-center border border-gray_r-6 h-28 w-full rounded-md'
+ {products &&
+ products?.map((product) => (
+ <tr key={product.id}>
+ <td>
+ <input
+ type='checkbox'
+ onClick={() => toggleSelected(product.id)}
+ checked={product?.selected}
+ className='accent-danger-500 w-4'
/>
- </Link>
- <div className='px-2 text-left'>
+ </td>
+ <td className='flex'>
<Link
href={createSlug(
'/shop/product/',
product?.parent.name,
product?.parent.id
)}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ className='w-[20%] flex-shrink-0'
>
- {product?.parent?.name}
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-28 w-full rounded-md'
+ />
</Link>
- <div className='text-gray_r-11 mt-2'>
- {product?.code}{' '}
- {product?.attributes.length > 0
- ? `| ${product?.attributes.join(', ')}`
- : ''}
- </div>
- </div>
- </td>
- <td>
- <input
- className='form-input w-16 py-2 text-center bg-gray_r-1'
- type='number'
- value={product?.quantity}
- onChange={(e) => updateQuantity(e.target.value, product?.id)}
- onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
- />
- </td>
- <td>
- {product?.price?.discountPercentage > 0 && (
- <div className='flex gap-x-1 items-center justify-center mt-3'>
- <div className='text-gray_r-11 line-through text-caption-1'>
- {currencyFormat(product?.price?.price)}
+ <div className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
</div>
- <div className='badge-solid-red'>
- {product?.price?.discountPercentage}%
+ </div>
+ </td>
+ <td>
+ <input
+ className='form-input w-16 py-2 text-center bg-gray_r-1'
+ type='number'
+ value={product?.quantity}
+ onChange={(e) => updateQuantity(e.target.value, product?.id)}
+ onBlur={(e) => updateQuantity(e.target.value, product?.id, 'BLUR')}
+ />
+ </td>
+ <td>
+ {product?.price?.discountPercentage > 0 && (
+ <div className='flex gap-x-1 items-center justify-center mt-3'>
+ <div className='text-gray_r-11 line-through text-caption-1'>
+ {currencyFormat(product?.price?.price)}
+ </div>
+ <div className='badge-solid-red'>
+ {product?.price?.discountPercentage}%
+ </div>
</div>
+ )}
+ <div className='font-normal mt-1'>
+ {currencyFormat(product?.price?.priceDiscount)}
</div>
- )}
- <div className='font-normal mt-1'>
- {currencyFormat(product?.price?.priceDiscount)}
- </div>
- </td>
- <td>
- <div className='text-danger-500 font-medium'>
- {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
- </div>
- </td>
- <td>
- <div className='flex justify-center items-center h-full'>
- <button
- className='btn-red p-1 ml-1'
- onClick={() => setDeleteConfirmation(product)}
- >
- <TrashIcon className='w-4' />
- </button>
- </div>
- </td>
- </tr>
- ))}
+ </td>
+ <td>
+ <div className='text-danger-500 font-medium'>
+ {currencyFormat(product?.price?.priceDiscount * product?.quantity)}
+ </div>
+ </td>
+ <td>
+ <div className='flex justify-center items-center h-full'>
+ <button
+ className='btn-red p-1 ml-1'
+ onClick={() => setDeleteConfirmation(product)}
+ >
+ <TrashIcon className='w-4' />
+ </button>
+ </div>
+ </td>
+ </tr>
+ ))}
</tbody>
</table>
@@ -428,7 +430,7 @@ const Cart = () => {
Tanya stock untuk pembelian anda sebelum melanjutkan pembayaran!
<span>
{' '}
- <a href='https://wa.me/628128080622' className='text-danger-500'>
+ <a href={whatsappUrl()} className='text-danger-500'>
Hubungi Kami
</a>
</span>
diff --git a/src/lib/category/components/Category.jsx b/src/lib/category/components/Category.jsx
index 9f34362c..884a871f 100644
--- a/src/lib/category/components/Category.jsx
+++ b/src/lib/category/components/Category.jsx
@@ -1,6 +1,7 @@
import odooApi from '@/core/api/odooApi'
import Link from '@/core/components/elements/Link/Link'
import DesktopView from '@/core/components/views/DesktopView'
+import { createSlug } from '@/core/utils/slug'
import { useEffect, useState } from 'react'
const Category = () => {
@@ -32,7 +33,7 @@ const Category = () => {
{categories.map((category) => (
<div key={category.id}>
<Link
- href={`/shop/search?category=${category.name}`}
+ href={createSlug('/shop/category/', category.name, category.id)}
className='category-mega-box__parent'
>
{category.name}
@@ -42,7 +43,7 @@ const Category = () => {
{category.childs.map((child1Category) => (
<div key={child1Category.id}>
<Link
- href={`/shop/search?category=${child1Category.name}`}
+ href={createSlug('/shop/category/', child1Category.name, child1Category.id)}
className='category-mega-box__child-one mb-4'
>
{child1Category.name}
@@ -50,7 +51,11 @@ const Category = () => {
<div className='flex flex-col gap-y-3'>
{child1Category.childs.map((child2Category) => (
<Link
- href={`/shop/search?category=${child2Category.name}`}
+ href={createSlug(
+ '/shop/category/',
+ child2Category.name,
+ child2Category.id
+ )}
className='category-mega-box__child-two'
key={child2Category.id}
>
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 111117c9..f777a3a8 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -20,6 +20,8 @@ import Image from '@/core/components/elements/Image/Image'
import MobileView from '@/core/components/views/MobileView'
import DesktopView from '@/core/components/views/DesktopView'
import ExpedisiList from '../api/ExpedisiList'
+import whatsappUrl from '@/core/utils/whatsappUrl'
+import { createSlug } from '@/core/utils/slug'
const Checkout = () => {
const router = useRouter()
@@ -166,8 +168,8 @@ const Checkout = () => {
quantity: product.quantity
}))
let data = {
- partner_shipping_id: selectedAddress.shipping.id,
- partner_invoice_id: selectedAddress.invoicing.id,
+ partner_shipping_id: auth.partnerId,
+ partner_invoice_id: auth.partnerId,
order_line: JSON.stringify(productOrder),
delivery_amount : biayaKirim,
type: 'sale_order'
@@ -342,7 +344,7 @@ const Checkout = () => {
<div className='px-4 mb-4'>
<span className='text-danger-500'>
*) Terdapat produk yang belum memiliki harga,{' '}
- <a href='https://wa.me/628128080622' className='underline'>
+ <a href={whatsappUrl()} className='underline'>
Hubungi Kami untuk meminta harga.
</a>
</span>
@@ -465,7 +467,13 @@ const Checkout = () => {
{product.price.priceDiscount > 0 ? (
currencyFormat(product?.price?.priceDiscount * product?.quantity)
) : (
- <a href='https://wa.me/628128080622' className='underline'>
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='underline'
+ >
Call For Price{' '}
</a>
)}
@@ -562,7 +570,7 @@ const Checkout = () => {
<div className='mt-4'>
<span className='text-danger-500'>
*) Terdapat produk yang belum memiliki harga,{' '}
- <a href='https://wa.me/628128080622' className='underline'>
+ <a href={whatsappUrl()} className='underline'>
Hubungi Kami untuk meminta harga.
</a>
</span>
diff --git a/src/lib/checkout/components/FinishCheckout.jsx b/src/lib/checkout/components/FinishCheckout.jsx
index cd93e3a4..92245e31 100644
--- a/src/lib/checkout/components/FinishCheckout.jsx
+++ b/src/lib/checkout/components/FinishCheckout.jsx
@@ -14,7 +14,7 @@ const FinishCheckout = ({ query }) => {
<p className='text-caption-2 text-warning-800'>No. Transaksi</p>
</div>
<Link
- href='/my/transactions'
+ href='/my/quotations'
className='bg-warning-400 text-warning-900 rounded-b-xl py-4 block'
>
Lihat detail pembelian Anda disini
diff --git a/src/lib/checkout/email/FinishCheckoutEmail.jsx b/src/lib/checkout/email/FinishCheckoutEmail.jsx
index e8f63afa..d40ce7d4 100644
--- a/src/lib/checkout/email/FinishCheckoutEmail.jsx
+++ b/src/lib/checkout/email/FinishCheckoutEmail.jsx
@@ -1,5 +1,6 @@
import currencyFormat from '@/core/utils/currencyFormat'
import toTitleCase from '@/core/utils/toTitleCase'
+import whatsappUrl from '@/core/utils/whatsappUrl'
import {
Body,
Column,
@@ -255,7 +256,7 @@ const FinishCheckoutEmail = ({ transaction, payment, statusPayment }) => {
Jika ada pertanyaan seputar teknis pembayaran {transaction.address.customer.name}{' '}
dapat hubungi kami melalui Email{' '}
<a href='mailto:sales@indoteknik.com'>(sales@indoteknik.com)</a> atau Whatsapp{' '}
- <a href='https://wa.me/628128080622' target='_blank' rel='noreferrer'>
+ <a href={whatsappUrl()} target='_blank' rel='noreferrer'>
(+62 812-8080-622)
</a>
.
diff --git a/src/lib/content/components/PageContent.jsx b/src/lib/content/components/PageContent.jsx
index 61e5381d..8c467999 100644
--- a/src/lib/content/components/PageContent.jsx
+++ b/src/lib/content/components/PageContent.jsx
@@ -6,13 +6,21 @@ const PageContent = ({ path }) => {
const fetchContent = async () => await pageContentApi({ path })
const content = useQuery(`content-${path}`, fetchContent)
+ if (content.isLoading) {
+ return (
+ <div className='flex justify-center my-6'>
+ <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
+ </div>
+ )
+ }
+
if (content.data?.id) {
let parsedContent = content.data.content
parsedContent = parsedContent.replaceAll(
'src="/web/image',
`src="${process.env.NEXT_PUBLIC_ODOO_API_HOST}/web/image`
)
- const contentClassNames = `
+ const contentClassNames = `
prose
prose-gray
prose-a:text-danger-500
@@ -23,19 +31,12 @@ const PageContent = ({ path }) => {
prose-img:mb-1
prose-img:inline-block
prose-hr:my-3
+ max-w-none
`
return <div className={contentClassNames} dangerouslySetInnerHTML={{ __html: parsedContent }} />
}
- if (content.isLoading) {
- return (
- <div className='flex justify-center my-6'>
- <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' />
- </div>
- )
- }
-
return <></>
}
diff --git a/src/lib/flashSale/api/flashSaleApi.js b/src/lib/flashSale/api/flashSaleApi.js
new file mode 100644
index 00000000..115b07dc
--- /dev/null
+++ b/src/lib/flashSale/api/flashSaleApi.js
@@ -0,0 +1,8 @@
+import odooApi from '@/core/api/odooApi'
+
+const flashSaleApi = async () => {
+ const flashSale = await odooApi('GET', '/api/v1/flashsale/header')
+ return flashSale
+}
+
+export default flashSaleApi
diff --git a/src/lib/flashSale/components/FlashSale.jsx b/src/lib/flashSale/components/FlashSale.jsx
new file mode 100644
index 00000000..e4a4a25c
--- /dev/null
+++ b/src/lib/flashSale/components/FlashSale.jsx
@@ -0,0 +1,66 @@
+import { useEffect, useState } from 'react'
+import flashSaleApi from '../api/flashSaleApi'
+import Image from '@/core/components/elements/Image/Image'
+import CountDown from '@/core/components/elements/CountDown/CountDown'
+import productSearchApi from '@/lib/product/api/productSearchApi'
+import ProductSlider from '@/lib/product/components/ProductSlider'
+
+const FlashSale = () => {
+ const [flashSales, setFlashSales] = useState(null)
+
+ useEffect(() => {
+ const loadFlashSales = async () => {
+ const dataFlashSales = await flashSaleApi()
+ setFlashSales(dataFlashSales)
+ }
+ loadFlashSales()
+ }, [])
+
+ 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>
+ <CountDown initialTime={flashSale.duration} />
+ </div>
+
+ <div className='relative'>
+ <Image
+ src={flashSale.banner}
+ alt={flashSale.name}
+ className='w-full rounded mb-4 hidden sm:block'
+ />
+ <Image
+ src={flashSale.bannerMobile}
+ alt={flashSale.name}
+ className='w-full rounded mb-4 block sm:hidden'
+ />
+ <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=500`,
+ operation: 'AND'
+ })
+ setProducts(dataProducts.response)
+ }
+ loadProducts()
+ }, [flashSaleId])
+
+ return <ProductSlider products={products} />
+}
+
+export default FlashSale
diff --git a/src/lib/form/components/KunjunganService.jsx b/src/lib/form/components/KunjunganService.jsx
index dfe5873e..076f6814 100644
--- a/src/lib/form/components/KunjunganService.jsx
+++ b/src/lib/form/components/KunjunganService.jsx
@@ -6,7 +6,7 @@ import ReCAPTCHA from 'react-google-recaptcha'
import { Controller, useForm } from 'react-hook-form'
import { toast } from 'react-hot-toast'
import * as Yup from 'yup'
-import createLeadsApi from '../api/createLeadsApi'
+import createLeadApi from '../api/createLeadApi'
const CreateKunjunganService = () => {
const {
@@ -41,18 +41,32 @@ const CreateKunjunganService = () => {
}
const data = {
...values,
- name : 'Pengajuan Kunjungan Service - ' + values.company,
- contact_name : values.cp,
- email_from : values.email,
- phone : values.mobile,
- description : "\r\n Nama Perusahaan : " + values.company + " \r\n Alamat : " + values.address + " \r\n Propinsi : " + values.city + " \r\n Telepon: " + values.phone + " \r\n Handphone : " + values.mobile +" \r\n Email : " + values.email + " \r\n Keterangan : " + values.description ,
+ name: 'Pengajuan Kunjungan Service - ' + values.company,
+ contact_name: values.cp,
+ email_from: values.email,
+ phone: values.mobile,
+ description:
+ '\r\n Nama Perusahaan : ' +
+ values.company +
+ ' \r\n Alamat : ' +
+ values.address +
+ ' \r\n Propinsi : ' +
+ values.city +
+ ' \r\n Telepon: ' +
+ values.phone +
+ ' \r\n Handphone : ' +
+ values.mobile +
+ ' \r\n Email : ' +
+ values.email +
+ ' \r\n Keterangan : ' +
+ values.description
}
- const create_leads = await createLeadsApi({ data })
+ const create_leads = await createLeadApi({ data })
if (create_leads) {
toast.success('Berhasil menambahkan alamat')
reset()
- recaptchaRef.current.reset()
+ recaptchaRef.current.reset()
}
}
return (
@@ -125,9 +139,7 @@ const CreateKunjunganService = () => {
type='text'
className='form-input'
/>
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.cp?.message}
- </div>
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.cp?.message}</div>
</div>
</div>
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
@@ -156,7 +168,9 @@ const CreateKunjunganService = () => {
</div>
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
<div>
- <label className='form-label mb-2'>Sebutkan: Merek, Tipe, Permasalahan, Service, Perawatan</label>
+ <label className='form-label mb-2'>
+ Sebutkan: Merek, Tipe, Permasalahan, Service, Perawatan
+ </label>
<textarea {...register('description')} type='text' className='form-input' />
<div className='text-caption-2 text-danger-500 mt-1'>
{errors.description?.message}
@@ -181,24 +195,24 @@ const CreateKunjunganService = () => {
)
}
const validationSchema = Yup.object().shape({
- company: Yup.string().required('Harus di-isi'),
- email: Yup.string().email('Format harus seperti contoh@email.com').required('Harus di-isi'),
- phone: Yup.string().required('Harus di-isi'),
- city: Yup.string().required('Harus di-isi'),
- cp: Yup.string().required('Harus di-isi'),
- mobile: Yup.string().required('Harus di-isi'),
- email: Yup.string().required('Harus di-isi'),
- address: Yup.string().required('Harus di-isi')
+ company: Yup.string().required('Harus di-isi'),
+ email: Yup.string().email('Format harus seperti contoh@email.com').required('Harus di-isi'),
+ phone: Yup.string().required('Harus di-isi'),
+ city: Yup.string().required('Harus di-isi'),
+ cp: Yup.string().required('Harus di-isi'),
+ mobile: Yup.string().required('Harus di-isi'),
+ email: Yup.string().required('Harus di-isi'),
+ address: Yup.string().required('Harus di-isi')
})
const defaultValues = {
- company:'',
- email: '',
- phone: '',
- city: '',
- cp: '',
- mobile: '',
- email: '',
- address: ''
+ company: '',
+ email: '',
+ phone: '',
+ city: '',
+ cp: '',
+ mobile: '',
+ email: '',
+ address: ''
}
export default CreateKunjunganService
diff --git a/src/lib/form/components/MediaRelations.jsx b/src/lib/form/components/MediaRelations.jsx
new file mode 100644
index 00000000..05ae7e03
--- /dev/null
+++ b/src/lib/form/components/MediaRelations.jsx
@@ -0,0 +1,237 @@
+import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'
+import cityApi from '@/lib/address/api/cityApi'
+import { yupResolver } from '@hookform/resolvers/yup'
+import React, { useEffect, useRef, useState } from 'react'
+import ReCAPTCHA from 'react-google-recaptcha'
+import { Controller, useForm } from 'react-hook-form'
+import { toast } from 'react-hot-toast'
+import * as Yup from 'yup'
+import createLeadApi from '../api/createLeadApi'
+
+const CreateMediaRelations = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ control,
+ reset
+ } = useForm({
+ resolver: yupResolver(validationSchema),
+ defaultValues
+ })
+ const list_unit = [
+ {
+ value: 'Media Cetak',
+ label: 'Media Cetak'
+ },
+ {
+ value: 'Media',
+ label: 'Hospitality'
+ },
+ {
+ value: 'Automotive',
+ label: 'Automotive'
+ },
+ {
+ value: 'Retail',
+ label: 'Retail'
+ },
+ {
+ value: 'Maining',
+ label: 'Maining'
+ },
+ {
+ value: 'Lain - Lain',
+ label: 'Lain - Lain'
+ }
+ ]
+ const [cities, setCities] = useState([])
+ const [company_unit, setCompany_unit] = useState(list_unit)
+
+ const recaptchaRef = useRef(null)
+
+ useEffect(() => {
+ const loadCities = async () => {
+ let dataCities = await cityApi()
+ dataCities = dataCities.map((city) => ({ value: city.id, label: city.name }))
+ setCities(dataCities)
+ }
+ loadCities()
+ }, [])
+
+ const onSubmitHandler = async (values) => {
+ const attachment = document.getElementById('attachment').files[0]
+
+ const recaptchaValue = recaptchaRef.current.getValue()
+ if (!recaptchaValue) {
+ toast.error('Catcha harus diisi')
+ return
+ }
+ const data = {
+ ...values,
+ name: 'Form Merchant - ' + values.company,
+ contact_name: values.cp,
+ email_from: values.email,
+ phone: values.phone,
+ description:
+ 'Nama Perusahaan : ' +
+ values.company +
+ ' \r\n Alamat : ' +
+ values.address +
+ ' \r\n Kota : ' +
+ values.city +
+ ' \r\n Unit Perusahaan : ' +
+ values.company_unit +
+ ' \r\n Telepon: ' +
+ values.phone +
+ ' \r\n Email : ' +
+ values.email +
+ ' \r\n Website : ' +
+ values.website +
+ ' \r\n No Hp : ' +
+ values.mobile +
+ 'Keterangan : ' +
+ values.description
+ }
+ const create_leads = await createLeadApi({ data })
+ if (create_leads) {
+ toast.success('Berhasil menambahkan data')
+ reset()
+ recaptchaRef.current.reset()
+ }
+ }
+ return (
+ <div className='container mx-auto p-4 md:p-0 my-0 md:my-10'>
+ <h1 className='text-h-sm md:text-title-sm font-semibold mb-6'>Form Media dan Relasi</h1>
+ <div className='w-full p-4 bg-white border border-gray_r-6 rounded'>
+ <form onSubmit={handleSubmit(onSubmitHandler)}>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Nama Lengkap *</label>
+ <input
+ {...register('name')}
+ placeholder='Jhone Doe'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.name?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Nama Perusahan *</label>
+ <input
+ {...register('company')}
+ placeholder='PT.Indoteknik'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.company?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Alamat Email *</label>
+ <input
+ {...register('email')}
+ placeholder='contoh@email.com'
+ type='email'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.email?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>No. Telp *</label>
+ <input
+ {...register('phone')}
+ placeholder='021-XXXX'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.phone?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Jenis Media / Partnetship *</label>
+ <Controller
+ name='company_unit'
+ control={control}
+ render={(props) => <HookFormSelect {...props} options={company_unit} />}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.company_unit?.message}
+ </div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Jika anda memiliki media lain, sebutkan </label>
+ <input
+ {...register('other_media')}
+ placeholder='021-XXXX'
+ type='text'
+ className='form-input'
+ />
+ </div>
+ </div>
+
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>
+ Lampiran (Company Peofile, Proposal Kerjasama, dll)
+ </label>
+ <input
+ {...register('attachment')}
+ type='file'
+ className='form-input'
+ accept='application/pdf'
+ id='attachment'
+ placeholder='Hello'
+ />
+ </div>
+ </div>
+
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <ReCAPTCHA ref={recaptchaRef} sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} />
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <button type='submit' className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto'>
+ Simpan
+ </button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ )
+}
+const validationSchema = Yup.object().shape({
+ company: Yup.string().required('Harus di-isi'),
+ email: Yup.string().email('Format harus seperti contoh@email.com').required('Harus di-isi'),
+ phone: Yup.string().required('Harus di-isi'),
+ cp: Yup.string().required('Harus di-isi'),
+ city: Yup.string().required('Harus di-isi'),
+ company_unit: Yup.string().required('Harus di-isi'),
+ address: Yup.string().required('Harus di-isi'),
+ website: Yup.string().required('Harus di-isi'),
+ mobile: Yup.string().required('Harus di-isi')
+})
+const defaultValues = {
+ company: '',
+ email: '',
+ phone: '',
+ city: '',
+ company_unit: '',
+ cp: '',
+ address: '',
+ website: '',
+ mobile: ''
+}
+
+export default CreateMediaRelations
diff --git a/src/lib/form/components/Merchant.jsx b/src/lib/form/components/Merchant.jsx
new file mode 100644
index 00000000..75b4e132
--- /dev/null
+++ b/src/lib/form/components/Merchant.jsx
@@ -0,0 +1,264 @@
+import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'
+import cityApi from '@/lib/address/api/cityApi'
+import { yupResolver } from '@hookform/resolvers/yup'
+import React, { useEffect, useRef, useState } from 'react'
+import ReCAPTCHA from 'react-google-recaptcha'
+import { Controller, useForm } from 'react-hook-form'
+import { toast } from 'react-hot-toast'
+import * as Yup from 'yup'
+import createLeadApi from '../api/createLeadApi'
+
+const CreateMerchant = () => {
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ control,
+ reset
+ } = useForm({
+ resolver: yupResolver(validationSchema),
+ defaultValues
+ })
+ const list_unit = [
+ {
+ value: 'Manufacturing',
+ label: 'Manufacturing'
+ },
+ {
+ value: 'Hospitality',
+ label: 'Hospitality'
+ },
+ {
+ value: 'Automotive',
+ label: 'Automotive'
+ },
+ {
+ value: 'Retail',
+ label: 'Retail'
+ },
+ {
+ value: 'Maining',
+ label: 'Maining'
+ },
+ {
+ value: 'Lain - Lain',
+ label: 'Lain - Lain'
+ }
+ ]
+ const [cities, setCities] = useState([])
+ const [company_unit, setCompany_unit] = useState(list_unit)
+
+ const recaptchaRef = useRef(null)
+
+ useEffect(() => {
+ const loadCities = async () => {
+ let dataCities = await cityApi()
+ dataCities = dataCities.map((city) => ({ value: city.id, label: city.name }))
+ setCities(dataCities)
+ }
+ loadCities()
+ }, [])
+
+ const onSubmitHandler = async (values) => {
+ const recaptchaValue = recaptchaRef.current.getValue()
+ if (!recaptchaValue) {
+ toast.error('Catcha harus diisi')
+ return
+ }
+ const data = {
+ ...values,
+ name: 'Form Merchant - ' + values.company,
+ contact_name: values.cp,
+ email_from: values.email,
+ phone: values.phone,
+ description:
+ 'Nama Perusahaan : ' +
+ values.company +
+ ' \r\n Alamat : ' +
+ values.address +
+ ' \r\n Kota : ' +
+ values.city +
+ ' \r\n Unit Perusahaan : ' +
+ values.company_unit +
+ ' \r\n Telepon: ' +
+ values.phone +
+ ' \r\n Email : ' +
+ values.email +
+ ' \r\n Website : ' +
+ values.website +
+ ' \r\n No Hp : ' +
+ values.mobile +
+ 'Keterangan : ' +
+ values.description
+ }
+ const create_leads = await createLeadApi({ data })
+ if (create_leads) {
+ toast.success('Berhasil menambahkan data')
+ reset()
+ recaptchaRef.current.reset()
+ }
+ }
+ return (
+ <div className='container mx-auto p-4 md:p-0 my-0 md:my-10'>
+ <h1 className='text-h-sm md:text-title-sm font-semibold mb-6'>Form Merchant</h1>
+ <div className='w-full p-4 bg-white border border-gray_r-6 rounded'>
+ <div
+ class='flex items-center bg-blue-100 border border-blue-400 text-blue-500 font-bold px-4 py-3 mb-4'
+ role='alert'
+ >
+ <p>
+ Penjualan online adalah hal yang HARUS dilakukan mulai sekarang. Perubahan dalam banyak
+ industri dan pola pembelian. Gabung dengan platform kami dan mulai penjualan lansung di
+ ribuan perusahaan d seluruh Indonesia.{' '}
+ </p>
+ </div>
+ <form onSubmit={handleSubmit(onSubmitHandler)}>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Nama Perusahan *</label>
+ <input
+ {...register('company')}
+ placeholder='PT.Indoteknik'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.company?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Alamat*</label>
+ <input
+ {...register('address')}
+ placeholder='jl. Bandengan no.31 '
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.address?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>No. Telp *</label>
+ <input
+ {...register('phone')}
+ placeholder='021-XXXX'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.phone?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Kota*</label>
+ <Controller
+ name='city'
+ control={control}
+ render={(props) => <HookFormSelect {...props} options={cities} />}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.city?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Unit Perusahaan*</label>
+ <Controller
+ name='company_unit'
+ control={control}
+ render={(props) => <HookFormSelect {...props} options={company_unit} />}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.company_unit?.message}
+ </div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Website *</label>
+ <input
+ {...register('website')}
+ placeholder='https://indoteknik.com'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.website?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Contact Person*</label>
+ <input
+ {...register('cp')}
+ placeholder='Jhone doe'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.cp?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>No HP *</label>
+ <input
+ {...register('mobile')}
+ placeholder='628XXXXXXX'
+ type='text'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.mobile?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Alamat Email *</label>
+ <input
+ {...register('email')}
+ placeholder='contoh@email.com'
+ type='email'
+ className='form-input'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.email?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <ReCAPTCHA ref={recaptchaRef} sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} />
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <button type='submit' className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto'>
+ Simpan
+ </button>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ )
+}
+const validationSchema = Yup.object().shape({
+ company: Yup.string().required('Harus di-isi'),
+ email: Yup.string().email('Format harus seperti contoh@email.com').required('Harus di-isi'),
+ phone: Yup.string().required('Harus di-isi'),
+ cp: Yup.string().required('Harus di-isi'),
+ city: Yup.string().required('Harus di-isi'),
+ company_unit: Yup.string().required('Harus di-isi'),
+ address: Yup.string().required('Harus di-isi'),
+ website: Yup.string().required('Harus di-isi'),
+ mobile: Yup.string().required('Harus di-isi')
+})
+const defaultValues = {
+ company: '',
+ email: '',
+ phone: '',
+ city: '',
+ company_unit: '',
+ cp: '',
+ address: '',
+ website: '',
+ mobile: ''
+}
+
+export default CreateMerchant
diff --git a/src/lib/form/components/PembayaranTempo.jsx b/src/lib/form/components/PembayaranTempo.jsx
index 5f32753e..ffdb0961 100644
--- a/src/lib/form/components/PembayaranTempo.jsx
+++ b/src/lib/form/components/PembayaranTempo.jsx
@@ -52,7 +52,7 @@ const PembayaranTempo = () => {
return (
<div className='container mx-auto p-4 md:p-0 my-0 md:my-10'>
<h1 className='text-h-sm md:text-title-sm font-semibold mb-6'>Pembayaran Tempo</h1>
-
+
<div className='w-full grid grid-cols-1 md:grid-cols-2'>
<form onSubmit={handleSubmit(onSubmitHandler)} className='grid grid-cols-1 gap-y-6'>
<div>
diff --git a/src/lib/form/components/SuratDukungan.jsx b/src/lib/form/components/SuratDukungan.jsx
index 22452b3c..0eab84a4 100644
--- a/src/lib/form/components/SuratDukungan.jsx
+++ b/src/lib/form/components/SuratDukungan.jsx
@@ -6,7 +6,7 @@ import ReCAPTCHA from 'react-google-recaptcha'
import { Controller, useForm } from 'react-hook-form'
import { toast } from 'react-hot-toast'
import * as Yup from 'yup'
-import createLeadsApi from '../api/createLeadsApi'
+import createLeadsApi from '../api/createLeadApi'
const CreateSuratDukungan = () => {
const {
@@ -41,18 +41,34 @@ const CreateSuratDukungan = () => {
}
const data = {
...values,
- name : 'Pengajuan Kunjungan Service - ' + values.company,
- contact_name : values.cp,
- email_from : values.email,
- phone : values.mobile,
- description : "\r\n Nama Perusahaan : " + values.company + " \r\n Alamat : " + values.address + " \r\n Propinsi : " + values.city + " \r\n Telepon: " + values.phone + " \r\n Handphone : " + values.mobile +" \r\n Email : " + values.email + " \r\n Keterangan : " + values.description ,
+ name: 'Permintaan Surat Dukungan - ' + values.company,
+ contact_name: values.company,
+ email_from: values.email,
+ phone: values.phone,
+ description:
+ 'Nama Perusahaan : ' +
+ values.company +
+ ' \r\n Alamat : ' +
+ values.address +
+ ' \r\n Npwp : ' +
+ values.npwp +
+ ' \r\n Telepon: ' +
+ values.phone +
+ ' \r\n Email : ' +
+ values.email +
+ ' \r\n Pengadaan : ' +
+ values.pengadaan +
+ ' \r\n Alamat 2 : ' +
+ values.address2 +
+ 'Keterangan : ' +
+ values.description
}
const create_leads = await createLeadsApi({ data })
if (create_leads) {
toast.success('Berhasil menambahkan alamat')
reset()
- recaptchaRef.current.reset()
+ recaptchaRef.current.reset()
}
}
return (
@@ -63,9 +79,10 @@ const CreateSuratDukungan = () => {
class='flex items-center bg-blue-100 border border-blue-400 text-blue-500 font-bold px-4 py-3 mb-4'
role='alert'
>
- <p>
- Lengkapi form berikut untuk melakukan konfirmasi pembayaran.
- </p>
+ <p>Lengkapi form berikut untuk melakukan konfirmasi pembayaran.</p>
+ </div>
+ <div className=' w-full md:w-[50%] p-4 bg-gray-50 border border-gray_r-6 rounded text-center'>
+ <h1>Data Peserta</h1>
</div>
<form onSubmit={handleSubmit(onSubmitHandler)}>
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
@@ -82,18 +99,6 @@ const CreateSuratDukungan = () => {
</div>
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
<div>
- <label className='form-label mb-2'>No. Telp *</label>
- <input
- {...register('phone')}
- placeholder='021-XXXX'
- type='text'
- className='form-input'
- />
- <div className='text-caption-2 text-danger-500 mt-1'>{errors.phone?.message}</div>
- </div>
- </div>
- <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
- <div>
<label className='form-label mb-2'>Alamat*</label>
<input
{...register('address')}
@@ -106,39 +111,26 @@ const CreateSuratDukungan = () => {
</div>
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
<div>
- <label className='form-label mb-2'>Kota*</label>
- <Controller
- name='city'
- control={control}
- render={(props) => <HookFormSelect {...props} options={cities} />}
- />
- <div className='text-caption-2 text-danger-500 mt-1'>{errors.city?.message}</div>
- </div>
- </div>
- <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
- <div>
- <label className='form-label mb-2'>Contact Person*</label>
+ <label className='form-label mb-2'>No. Telp *</label>
<input
- {...register('cp')}
- placeholder='Jhone doe'
+ {...register('phone')}
+ placeholder='021-XXXX'
type='text'
className='form-input'
/>
- <div className='text-caption-2 text-danger-500 mt-1'>
- {errors.cp?.message}
- </div>
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.phone?.message}</div>
</div>
</div>
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
<div>
- <label className='form-label mb-2'>No HP *</label>
+ <label className='form-label mb-2'>NPWP *</label>
<input
- {...register('mobile')}
- placeholder='628XXXXXXX'
+ {...register('npwp')}
+ placeholder='npwp number'
type='text'
className='form-input'
/>
- <div className='text-caption-2 text-danger-500 mt-1'>{errors.mobile?.message}</div>
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.npwp?.message}</div>
</div>
</div>
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
@@ -153,10 +145,36 @@ const CreateSuratDukungan = () => {
<div className='text-caption-2 text-danger-500 mt-1'>{errors.email?.message}</div>
</div>
</div>
+ <div className='w-[50%] mt-10 p-4 bg-gray-50 border border-gray_r-6 rounded text-center'>
+ <h1>Data Peserta</h1>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Pengadaan *</label>
+ <input {...register('pengadaan')} placeholder='' type='text' className='form-input' />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.pengadaan?.message}</div>
+ </div>
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
+ <div>
+ <label className='form-label mb-2'>Alamat *</label>
+ <textarea
+ {...register('address2')}
+ placeholder=''
+ type='text'
+ className='form-input h-[120px]'
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.address2?.message}</div>
+ </div>
+ </div>
+ <div className='w-[50%] mt-10 p-4 bg-gray-50 border border-gray_r-6 rounded text-center'>
+ <h1>Data Produk</h1>
+ </div>
+
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 mt-4'>
<div>
- <label className='form-label mb-2'>Sebutkan: Merek, Tipe, Permasalahan, Service, Perawatan</label>
- <textarea {...register('description')} type='text' className='form-input' />
+ <label className='form-label mb-2'>Produk</label>
+ <textarea {...register('description')} type='text' className='form-input h-[120px]' />
<div className='text-caption-2 text-danger-500 mt-1'>
{errors.description?.message}
</div>
@@ -180,24 +198,24 @@ const CreateSuratDukungan = () => {
)
}
const validationSchema = Yup.object().shape({
- company: Yup.string().required('Harus di-isi'),
- email: Yup.string().email('Format harus seperti contoh@email.com').required('Harus di-isi'),
- phone: Yup.string().required('Harus di-isi'),
- city: Yup.string().required('Harus di-isi'),
- cp: Yup.string().required('Harus di-isi'),
- mobile: Yup.string().required('Harus di-isi'),
- email: Yup.string().required('Harus di-isi'),
- address: Yup.string().required('Harus di-isi')
+ company: Yup.string().required('Harus di-isi'),
+ email: Yup.string().email('Format harus seperti contoh@email.com').required('Harus di-isi'),
+ phone: Yup.string().required('Harus di-isi'),
+ npwp: Yup.string().required('Harus di-isi'),
+ pengadaan: Yup.string().required('Harus di-isi'),
+ email: Yup.string().required('Harus di-isi'),
+ address: Yup.string().required('Harus di-isi'),
+ address2: Yup.string().required('Harus di-isi')
})
const defaultValues = {
- company:'',
- email: '',
- phone: '',
- city: '',
- cp: '',
- mobile: '',
- email: '',
- address: ''
+ company: '',
+ email: '',
+ phone: '',
+ pengadaan: '',
+ npwp: '',
+ email: '',
+ address: '',
+ address2: ''
}
export default CreateSuratDukungan
diff --git a/src/lib/home/components/HeroBanner.jsx b/src/lib/home/components/HeroBanner.jsx
index e6136e03..50cfc0ff 100644
--- a/src/lib/home/components/HeroBanner.jsx
+++ b/src/lib/home/components/HeroBanner.jsx
@@ -10,6 +10,7 @@ import 'swiper/css/pagination'
import 'swiper/css/autoplay'
import MobileView from '@/core/components/views/MobileView'
import DesktopView from '@/core/components/views/DesktopView'
+import Link from '@/core/components/elements/Link/Link'
const HeroBanner = () => {
const { heroBanners } = useHeroBanner()
@@ -41,7 +42,9 @@ const HeroBanner = () => {
<Swiper slidesPerView={1} className='border border-gray_r-6' {...swiperBannerMobile}>
{heroBanners.data?.map((banner, index) => (
<SwiperSlide key={index}>
- <Image src={banner.image} alt={banner.name} className='w-full h-auto' />
+ <Link href={banner.url || ''} className='w-full h-auto'>
+ <Image src={banner.image} alt={banner.name} className='w-full h-auto' />
+ </Link>
</SwiperSlide>
))}
</Swiper>
@@ -50,7 +53,9 @@ const HeroBanner = () => {
<Swiper slidesPerView={1} className='border border-gray_r-6' {...swiperBannerDesktop}>
{heroBanners.data?.map((banner, index) => (
<SwiperSlide key={index}>
- <Image src={banner.image} alt={banner.name} className='w-full h-auto' />
+ <Link href={banner.url || ''} className='w-full h-auto'>
+ <Image src={banner.image} alt={banner.name} className='w-full h-auto' />
+ </Link>
</SwiperSlide>
))}
</Swiper>
diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx
index f97943cb..34c50220 100644
--- a/src/lib/home/components/PreferredBrand.jsx
+++ b/src/lib/home/components/PreferredBrand.jsx
@@ -14,7 +14,7 @@ const PreferredBrand = () => {
<div className='flex justify-between items-center mb-4'>
<div className='font-medium sm:text-h-lg'>Brand Pilihan</div>
{isDesktop && (
- <Link href='/' className='btn-yellow !text-gray_r-12'>
+ <Link href='/shop/brands' className='btn-yellow !text-gray_r-12'>
Lihat Semua
</Link>
)}
diff --git a/src/lib/invoice/components/Invoices.jsx b/src/lib/invoice/components/Invoices.jsx
index 6f7d54a0..96686bbb 100644
--- a/src/lib/invoice/components/Invoices.jsx
+++ b/src/lib/invoice/components/Invoices.jsx
@@ -24,7 +24,7 @@ const Invoices = () => {
const router = useRouter()
const { q = '', page = 1 } = router.query
- const limit = 10
+ const limit = 15
const query = {
name: q,
@@ -36,7 +36,7 @@ const Invoices = () => {
const [inputQuery, setInputQuery] = useState(q)
const [toOthers, setToOthers] = useState(null)
- const pageCount = Math.ceil(invoices?.data?.saleOrderTotal / limit)
+ const pageCount = Math.ceil(invoices?.data?.invoiceTotal / limit)
let pageQuery = _.omit(query, ['limit', 'offset'])
pageQuery = _.pickBy(pageQuery, _.identity)
pageQuery = toQuery(pageQuery)
@@ -78,7 +78,7 @@ const Invoices = () => {
{invoices.data?.invoices?.map((invoice, index) => (
<div className='p-4 shadow border border-gray_r-3 rounded-md' key={index}>
<div className='grid grid-cols-2'>
- <Link href={`/my/invoice/${invoice.id}`}>
+ <Link href={`${router.pathname}/${invoice.id}`}>
<span className='text-caption-2 text-gray_r-11'>No. Invoice</span>
<h2 className='text-danger-500 mt-1'>{invoice.name}</h2>
</Link>
@@ -91,7 +91,7 @@ const Invoices = () => {
<EllipsisVerticalIcon className='w-5 h-5' onClick={() => setToOthers(invoice)} />
</div>
</div>
- <Link href={`/my/invoice/${invoice.id}`}>
+ <Link href={`${router.pathname}/${invoice.id}`}>
<div className='grid grid-cols-2 text-caption-2 text-gray_r-11 mt-2 font-normal'>
<p>{invoice.invoiceDate}</p>
<p className='text-right'>{invoice.paymentTerm}</p>
@@ -214,7 +214,7 @@ const Invoices = () => {
{invoices.data?.invoices?.map((invoice) => (
<tr key={invoice.id}>
<td>
- <Link href={`/my/invoice/${invoice.id}`}>{invoice.name}</Link>
+ <Link href={`${router.pathname}/${invoice.id}`}>{invoice.name}</Link>
</td>
<td>{invoice.purchaseOrderName || '-'}</td>
<td>{invoice.invoiceDate}</td>
@@ -235,7 +235,7 @@ const Invoices = () => {
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={`/my/transactions${pageQuery}`}
+ url={`/my/invoices${pageQuery}`}
className='mt-2 mb-2'
/>
</div>
diff --git a/src/lib/product/api/productApi.js b/src/lib/product/api/productApi.js
index 8156d1ec..33f1265c 100644
--- a/src/lib/product/api/productApi.js
+++ b/src/lib/product/api/productApi.js
@@ -1,8 +1,8 @@
import odooApi from '@/core/api/odooApi'
-const productApi = async ({ id }) => {
+const productApi = async ({ id, headers = {} }) => {
if (!id) return
- const dataProduct = await odooApi('GET', `/api/v2/product/${id}`)
+ const dataProduct = await odooApi('GET', `/api/v2/product/${id}`, {}, headers)
return dataProduct
}
diff --git a/src/lib/product/api/productSearchApi.js b/src/lib/product/api/productSearchApi.js
index 71fb72e6..1626b7b7 100644
--- a/src/lib/product/api/productSearchApi.js
+++ b/src/lib/product/api/productSearchApi.js
@@ -1,9 +1,9 @@
import _ from 'lodash-contrib'
import axios from 'axios'
-const productSearchApi = async ({ query }) => {
+const productSearchApi = async ({ query, operation = 'AND' }) => {
const dataProductSearch = await axios(
- `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}&operation=OR`
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}&operation=${operation}`
)
return dataProductSearch.data
}
diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx
index bb2b2db9..8ce6da00 100644
--- a/src/lib/product/components/Product/ProductDesktop.jsx
+++ b/src/lib/product/components/Product/ProductDesktop.jsx
@@ -11,7 +11,9 @@ import { updateItemCart } from '@/core/utils/cart'
import { useRouter } from 'next/router'
import { createSlug } from '@/core/utils/slug'
import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import { Toast } from 'flowbite-react'
+import ProductCard from '../ProductCard'
+import productSimilarApi from '../../api/productSimilarApi'
+import whatsappUrl from '@/core/utils/whatsappUrl'
const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
const router = useRouter()
@@ -59,7 +61,6 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
selected: true
})
setAddCartAlert(true)
- // toast.success('Berhasil menambahkan ke keranjang')
}
const handleBuy = (variantId) => {
@@ -85,61 +86,117 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
`fq=-manufacture_id_i:${product.manufacture?.id || 0}`
].join('&')
+ const [productSimilarInBrand, setProductSimilarInBrand] = useState(null)
+
+ useEffect(() => {
+ const loadProductSimilarInBrand = async () => {
+ const productSimilarQuery = [product?.name, `fq=-product_id_i:${product.id}`].join('&')
+ const dataProductSimilar = await productSimilarApi({ query: productSimilarQuery })
+ setProductSimilarInBrand(dataProductSimilar.products)
+ }
+ if (!productSimilarInBrand) loadProductSimilarInBrand()
+ }, [product, productSimilarInBrand])
+
return (
<DesktopView>
<div className='container mx-auto pt-10'>
<div className='flex'>
- <div className='w-[30%]'>
- <Image
- src={product.image}
- alt={product.name}
- className='h-96 object-contain object-center w-full border border-gray_r-4'
- />
- </div>
- <div className='w-[50%] px-4'>
- <h1 className='text-title-md leading-10 font-medium'>{product?.name}</h1>
- <div className='mt-10'>
- <div className='flex p-3'>
- <div className='w-1/4 text-gray_r-12/70'>Nomor SKU</div>
- <div className='w-3/4'>SKU-{product.id}</div>
- </div>
- <div className='flex p-3 bg-gray_r-4'>
- <div className='w-1/4 text-gray_r-12/70'>Part Number</div>
- <div className='w-3/4'>{product.code || '-'}</div>
+ <div className='w-full flex flex-wrap'>
+ <div className='w-5/12'>
+ <Image
+ src={product.image}
+ alt={product.name}
+ className='h-96 object-contain object-center w-full border border-gray_r-4'
+ />
+ </div>
+
+ <div className='w-7/12 px-4'>
+ <h1 className='text-title-md leading-10 font-medium'>{product?.name}</h1>
+ <div className='mt-10'>
+ <div className='flex p-3'>
+ <div className='w-1/4 text-gray_r-12/70'>Nomor SKU</div>
+ <div className='w-3/4'>SKU-{product.id}</div>
+ </div>
+ <div className='flex p-3 bg-gray_r-4'>
+ <div className='w-1/4 text-gray_r-12/70'>Part Number</div>
+ <div className='w-3/4'>{product.code || '-'}</div>
+ </div>
+ <div className='flex p-3'>
+ <div className='w-1/4 text-gray_r-12/70'>Manufacture</div>
+ <div className='w-3/4'>
+ {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 bg-gray_r-4'>
+ <div className='w-1/4 text-gray_r-12/70'>Berat Barang</div>
+ <div className='w-3/4'>
+ {product?.weight > 0 && <span>{product?.weight} KG</span>}
+ {product?.weight == 0 && (
+ <a
+ href={whatsappUrl('productWeight', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 font-medium'
+ >
+ Tanya Berat
+ </a>
+ )}
+ </div>
+ </div>
</div>
- <div className='flex p-3'>
- <div className='w-1/4 text-gray_r-12/70'>Manufacture</div>
- <div className='w-3/4'>
- {product.manufacture?.name ? (
- <Link
- href={createSlug(
- '/shop/brands/',
- product.manufacture?.name,
- product.manufacture?.id
- )}
+ </div>
+
+ <div className='w-full'>
+ <div className='mt-12'>
+ <div className='text-h-lg font-semibold'>Informasi Produk</div>
+ <div className='flex gap-x-4 mt-6 mb-4'>
+ {informationTabOptions.map((option) => (
+ <TabButton
+ value={option.value}
+ key={option.value}
+ active={informationTab == option.value}
+ onClick={() => setInformationTab(option.value)}
>
- {product.manufacture?.name}
- </Link>
- ) : (
- <div>-</div>
- )}
+ {option.label}
+ </TabButton>
+ ))}
</div>
- </div>
- <div className='flex p-3 bg-gray_r-4'>
- <div className='w-1/4 text-gray_r-12/70'>Berat Barang</div>
- <div className='w-3/4'>
- {product?.weight > 0 && <span>{product?.weight} KG</span>}
- {product?.weight == 0 && (
- <a href='https://wa.me' className='text-danger-500 font-medium'>
- Tanya Berat
- </a>
- )}
+ <div className='flex'>
+ <div className='w-3/4 leading-7 product__description'>
+ <TabContent active={informationTab == 'description'}>
+ <span
+ dangerouslySetInnerHTML={{
+ __html:
+ product.description != ''
+ ? product.description
+ : 'Belum ada deskripsi produk.'
+ }}
+ />
+ </TabContent>
+
+ <TabContent active={informationTab == 'information'}>
+ Belum ada informasi.
+ </TabContent>
+ </div>
</div>
</div>
</div>
</div>
- <div className='w-[20%]'>
+ <div className='w-[25%]'>
{product.variants.length > 1 && product.lowestPrice.priceDiscount > 0 && (
<div className='text-gray_r-12/80'>Harga mulai dari: </div>
)}
@@ -160,7 +217,13 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
) : (
<span className='text-gray_r-12/90 font-normal text-h-sm'>
Hubungi kami untuk dapatkan harga terbaik,&nbsp;
- <a href='https://wa.me/' className='text-danger-500 underline'>
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 underline'
+ >
klik disini
</a>
</span>
@@ -209,6 +272,20 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
Wishlist
</button>
</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'>
+ {productSimilarInBrand &&
+ productSimilarInBrand?.map((product) => (
+ <div className='py-2' key={product.id}>
+ <ProductCard product={product} variant='horizontal' />
+ </div>
+ ))}
+ </div>
+ </div>
</div>
</div>
@@ -244,7 +321,13 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
{variant.price.priceDiscount > 0 ? (
currencyFormat(variant.price.priceDiscount)
) : (
- <a href='https://wa.me/' className='text-red_r-11'>
+ <a
+ href={whatsappUrl('product', {
+ name: variant.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-red_r-11'
+ >
Call for price
</a>
)}
@@ -283,38 +366,6 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => {
</div>
)}
- <div className='mt-12'>
- <div className='text-h-lg font-semibold'>Informasi Produk</div>
- <div className='flex gap-x-4 mt-6 mb-4'>
- {informationTabOptions.map((option) => (
- <TabButton
- value={option.value}
- key={option.value}
- active={informationTab == option.value}
- onClick={() => setInformationTab(option.value)}
- >
- {option.label}
- </TabButton>
- ))}
- </div>
- <div className='flex'>
- <div className='w-3/4 leading-7 product__description'>
- <TabContent active={informationTab == 'description'}>
- <span
- dangerouslySetInnerHTML={{
- __html:
- product.description != ''
- ? product.description
- : 'Belum ada deskripsi produk.'
- }}
- />
- </TabContent>
-
- <TabContent active={informationTab == 'information'}>Belum ada informasi.</TabContent>
- </div>
- </div>
- </div>
-
<div className='my-12'>
<div className='text-h-lg font-semibold mb-6'>Kamu Mungkin Juga Suka</div>
<LazyLoad>
diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx
index b75a191b..426fe1b8 100644
--- a/src/lib/product/components/Product/ProductMobile.jsx
+++ b/src/lib/product/components/Product/ProductMobile.jsx
@@ -13,6 +13,7 @@ import MobileView from '@/core/components/views/MobileView'
import { toast } from 'react-hot-toast'
import { createSlug } from '@/core/utils/slug'
import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import whatsappUrl from '@/core/utils/whatsappUrl'
const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
const router = useRouter()
@@ -152,7 +153,13 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
) : (
<span className='text-gray_r-11 leading-6 font-normal'>
Hubungi kami untuk dapatkan harga terbaik,&nbsp;
- <a href='https://wa.me/' className='text-danger-500 underline'>
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 underline'
+ >
klik disini
</a>
</span>
@@ -236,7 +243,13 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
</span>
)}
{activeVariant?.stock == 0 && (
- <a href='https://wa.me' className='text-danger-500 font-medium'>
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 font-medium'
+ >
Tanya Stok
</a>
)}
@@ -244,7 +257,13 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => {
<SpecificationContent label='Berat Barang'>
{activeVariant?.weight > 0 && <span>{activeVariant?.weight} KG</span>}
{activeVariant?.weight == 0 && (
- <a href='https://wa.me' className='text-danger-500 font-medium'>
+ <a
+ href={whatsappUrl('productWeight', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='text-danger-500 font-medium'
+ >
Tanya Berat
</a>
)}
diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index a1c30d00..9300643e 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -2,8 +2,14 @@ import Image from '@/core/components/elements/Image/Image'
import Link from '@/core/components/elements/Link/Link'
import currencyFormat from '@/core/utils/currencyFormat'
import { createSlug } from '@/core/utils/slug'
+import whatsappUrl from '@/core/utils/whatsappUrl'
const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
+ const callForPriceWhatsapp = whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })
+
if (variant == 'vertical') {
return (
<div className='rounded shadow-sm border border-gray_r-4 h-full bg-white'>
@@ -58,7 +64,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
{product?.lowestPrice.priceDiscount > 0 ? (
currencyFormat(product?.lowestPrice.priceDiscount)
) : (
- <a href='https://wa.me/'>Call for price</a>
+ <a href={callForPriceWhatsapp}>Call for price</a>
)}
</div>
@@ -130,7 +136,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
{product?.lowestPrice?.priceDiscount > 0 ? (
currencyFormat(product?.lowestPrice?.priceDiscount)
) : (
- <a href='https://wa.me/'>Call for price</a>
+ <a href={callForPriceWhatsapp}>Call for price</a>
)}
</div>
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index 81e7948b..cc85589d 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react'
+import { useEffect, useMemo, useState } from 'react'
import useProductSearch from '../hooks/useProductSearch'
import ProductCard from './ProductCard'
import Pagination from '@/core/components/elements/Pagination/Pagination'
@@ -12,6 +12,9 @@ import DesktopView from '@/core/components/views/DesktopView'
import NextImage from 'next/image'
import ProductFilterDesktop from './ProductFilterDesktop'
import { useRouter } from 'next/router'
+import searchSpellApi from '@/core/api/searchSpellApi'
+import Link from '@/core/components/elements/Link/Link'
+import whatsappUrl from '@/core/utils/whatsappUrl'
const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
const router = useRouter()
@@ -19,6 +22,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
if (defaultBrand) query.brand = defaultBrand.toLowerCase()
const { productSearch } = useProductSearch({ query })
const [products, setProducts] = useState(null)
+ const [spellings, setSpellings] = useState(null)
const popup = useActive()
const pageCount = Math.ceil(
@@ -28,6 +32,30 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
const productRows = productSearch.data?.responseHeader.params.rows
const productFound = productSearch.data?.response.numFound
+ useEffect(() => {
+ if (productFound == 0 && query.q) {
+ searchSpellApi({ query: query.q }).then((response) => {
+ const oddIndexSuggestions = response.data.spellcheck.suggestions.filter(
+ (_, index) => index % 2 === 1
+ )
+ const oddIndexCollations = response.data.spellcheck.collations.filter(
+ (_, index) => index % 2 === 1
+ )
+ const dataSpellings = oddIndexSuggestions.reduce((acc, curr) => {
+ oddIndexCollations.forEach((collation) => {
+ acc.push(collation.collationQuery)
+ })
+ curr.suggestion.forEach((s) => {
+ if (!acc.includes(s.word)) acc.push(s.word)
+ })
+ return acc
+ }, [])
+
+ setSpellings(dataSpellings)
+ })
+ }
+ }, [productFound, query])
+
const brands = productSearch.data?.facetCounts?.facetFields?.manufactureName?.filter(
(value, index) => {
if (index % 2 === 0) {
@@ -66,6 +94,20 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
}
}, [query, products, productSearch])
+ const SpellingComponent = useMemo(() => {
+ return (
+ <>
+ Mungkin yang anda cari{' '}
+ {spellings?.map((spelling, i) => (
+ <Link href={`/shop/search?q=${spelling}`} key={i} className='inline'>
+ {spelling}
+ {i + 1 < spellings.length ? ', ' : ''}
+ </Link>
+ ))}
+ </>
+ )
+ }, [spellings])
+
return (
<>
<MobileView>
@@ -97,13 +139,15 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
)}
</>
) : (
- 'Mungkin yang anda cari'
+ SpellingComponent
)}
</div>
- <button className='btn-light mb-6 py-2 px-5' onClick={popup.activate}>
- Filter
- </button>
+ {productFound > 0 && (
+ <button className='btn-light mb-6 py-2 px-5' onClick={popup.activate}>
+ Filter
+ </button>
+ )}
<div className='grid grid-cols-2 gap-3'>
{products &&
@@ -127,6 +171,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
/>
</div>
</MobileView>
+
<DesktopView>
<div className='container mx-auto mt-10 flex mb-3'>
<div className='w-3/12'>
@@ -164,7 +209,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
)}
</>
) : (
- 'Mungkin yang anda cari'
+ SpellingComponent
)}
</div>
<div className='justify-end flex '>
@@ -202,7 +247,12 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
<div className='text-gray_r-12/90'>
<span>
Barang yang anda cari tidak ada?{' '}
- <a href='#' className='text-danger-500'>
+ <a
+ href={whatsappUrl('productSearch', {
+ name: query.q
+ })}
+ className='text-danger-500'
+ >
Hubungi Kami
</a>
</span>
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index a68889f5..6ff40327 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -15,7 +15,6 @@ import VariantGroupCard from '@/lib/variant/components/VariantGroupCard'
import MobileView from '@/core/components/views/MobileView'
import DesktopView from '@/core/components/views/DesktopView'
import Image from '@/core/components/elements/Image/Image'
-import variantPriceApi from '@/lib/variant/api/variantPriceApi'
const Quotation = () => {
const router = useRouter()
@@ -32,15 +31,9 @@ const Quotation = () => {
.map((o) => o.productId)
.join(',')
const dataProducts = await CartApi({ variantIds })
- const productsWithQuantity = dataProducts?.map(async (product) => {
- const productPrice = await variantPriceApi({ id: product.id })
+ const productsWithQuantity = dataProducts?.map((product) => {
return {
...product,
- price: {
- price: productPrice.priceExclude,
- discountPercentage: productPrice.discount,
- priceDiscount: productPrice.priceExcludeAfterDiscount
- },
quantity: getItemCart({ productId: product.id }).quantity
}
})
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index 3e3f2cc7..30bb454a 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -2,7 +2,7 @@ import Spinner from '@/core/components/elements/Spinner/Spinner'
import useTransaction from '../hooks/useTransaction'
import TransactionStatusBadge from './TransactionStatusBadge'
import Divider from '@/core/components/elements/Divider/Divider'
-import { useEffect, useMemo, useRef, useState } from 'react'
+import { useMemo, useRef, useState } from 'react'
import { downloadPurchaseOrder, downloadQuotation } from '../utils/transactions'
import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
import uploadPoApi from '../api/uploadPoApi'
diff --git a/src/lib/transaction/components/Transactions.jsx b/src/lib/transaction/components/Transactions.jsx
index 13417707..bfb8c66b 100644
--- a/src/lib/transaction/components/Transactions.jsx
+++ b/src/lib/transaction/components/Transactions.jsx
@@ -19,15 +19,16 @@ import MobileView from '@/core/components/views/MobileView'
import DesktopView from '@/core/components/views/DesktopView'
import Menu from '@/lib/auth/components/Menu'
-const Transactions = () => {
+const Transactions = ({ context = '' }) => {
const router = useRouter()
const { q = '', page = 1 } = router.query
- const limit = 10
+ const limit = 15
const query = {
name: q,
offset: (page - 1) * limit,
+ context,
limit
}
const { transactions } = useTransactions({ query })
@@ -48,13 +49,13 @@ const Transactions = () => {
}
const pageCount = Math.ceil(transactions?.data?.saleOrderTotal / limit)
- let pageQuery = _.omit(query, ['limit', 'offset'])
+ let pageQuery = _.omit(query, ['limit', 'offset', 'context'])
pageQuery = _.pickBy(pageQuery, _.identity)
pageQuery = toQuery(pageQuery)
const handleSubmit = (e) => {
e.preventDefault()
- router.push(`/my/transactions?q=${inputQuery}`)
+ router.push(`${router.pathname}?q=${inputQuery}`)
}
return (
@@ -89,7 +90,7 @@ const Transactions = () => {
{transactions.data?.saleOrders?.map((saleOrder, index) => (
<div className='p-4 shadow border border-gray_r-3 rounded-md' key={index}>
<div className='grid grid-cols-2'>
- <Link href={`/my/transaction/${saleOrder.id}`}>
+ <Link href={`${router.pathname}/${saleOrder.id}`}>
<span className='text-caption-2 text-gray_r-11'>No. Transaksi</span>
<h2 className='text-danger-500 mt-1'>{saleOrder.name}</h2>
</Link>
@@ -101,7 +102,7 @@ const Transactions = () => {
/>
</div>
</div>
- <Link href={`/my/transaction/${saleOrder.id}`}>
+ <Link href={`${router.pathname}/${saleOrder.id}`}>
<div className='grid grid-cols-2 mt-3'>
<div>
<span className='text-caption-2 text-gray_r-11'>No. Purchase Order</span>
@@ -135,7 +136,7 @@ const Transactions = () => {
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={`/my/transactions${pageQuery}`}
+ url={router.pathname + pageQuery}
className='mt-2 mb-2'
/>
@@ -249,7 +250,7 @@ const Transactions = () => {
{transactions.data?.saleOrders?.map((saleOrder) => (
<tr key={saleOrder.id}>
<td>
- <Link href={`/my/transaction/${saleOrder.id}`}>{saleOrder.name}</Link>
+ <Link href={`${router.pathname}/${saleOrder.id}`}>{saleOrder.name}</Link>
</td>
<td>-</td>
<td className='!text-left'>{saleOrder.sales}</td>
@@ -267,7 +268,7 @@ const Transactions = () => {
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={`/my/transactions${pageQuery}`}
+ url={router.pathname + pageQuery}
className='mt-2 mb-2'
/>
</div>
diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx
index 28f3e193..0f9f02f6 100644
--- a/src/lib/variant/components/VariantCard.jsx
+++ b/src/lib/variant/components/VariantCard.jsx
@@ -6,6 +6,7 @@ import Link from '@/core/components/elements/Link/Link'
import { createSlug } from '@/core/utils/slug'
import currencyFormat from '@/core/utils/currencyFormat'
import { updateItemCart } from '@/core/utils/cart'
+import whatsappUrl from '@/core/utils/whatsappUrl'
const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
const router = useRouter()
@@ -60,7 +61,13 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
{product.price.priceDiscount > 0 ? (
currencyFormat(product.quantity * product.price.priceDiscount)
) : (
- <a href='https://wa.me/628128080622' className='underline text-danger-500'>
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ url: createSlug('/shop/product/', product.name, product.id, true)
+ })}
+ className='underline text-danger-500'
+ >
Call For Price{' '}
</a>
)}