summaryrefslogtreecommitdiff
path: root/src-migrate
diff options
context:
space:
mode:
Diffstat (limited to 'src-migrate')
-rw-r--r--src-migrate/libs/auth.ts5
-rw-r--r--src-migrate/libs/odooApi.ts2
-rw-r--r--src-migrate/modules/cart/components/Item.tsx9
-rw-r--r--src-migrate/modules/cart/components/ItemAction.tsx1
-rw-r--r--src-migrate/modules/cart/stores/useCartStore.ts4
-rw-r--r--src-migrate/modules/page-content/index.tsx61
-rw-r--r--src-migrate/modules/product-detail/components/AddToCart.tsx171
-rw-r--r--src-migrate/modules/product-detail/components/PriceAction.tsx2
-rw-r--r--src-migrate/modules/product-detail/components/ProductDetail.tsx2
-rw-r--r--src-migrate/modules/product-promo/components/AddToCart.tsx171
-rw-r--r--src-migrate/modules/product-promo/components/Card.tsx10
-rw-r--r--src-migrate/modules/product-promo/components/Item.tsx37
-rw-r--r--src-migrate/modules/product-promo/components/Modal.tsx8
-rw-r--r--src-migrate/modules/product-promo/components/ModalContent.tsx8
-rw-r--r--src-migrate/modules/product-promo/components/Section.tsx9
-rw-r--r--src-migrate/modules/promo/components/Hero.tsx103
-rw-r--r--src-migrate/modules/promo/components/HeroDiskon.tsx22
-rw-r--r--src-migrate/modules/promo/components/PromoList.tsx8
-rw-r--r--src-migrate/modules/promo/components/PromotinProgram.jsx223
-rw-r--r--src-migrate/modules/promo/components/Voucher.tsx69
-rw-r--r--src-migrate/modules/register/components/Form.tsx252
-rw-r--r--src-migrate/modules/register/components/FormBisnis.tsx715
-rw-r--r--src-migrate/modules/register/components/RegistrasiBisnis.tsx197
-rw-r--r--src-migrate/modules/register/components/RegistrasiIndividu.tsx34
-rw-r--r--src-migrate/modules/register/components/TermCondition.tsx2
-rw-r--r--src-migrate/modules/register/index.tsx232
-rw-r--r--src-migrate/modules/register/stores/useRegisterStore.ts43
-rw-r--r--src-migrate/pages/api/product-variant/[id].tsx1
-rw-r--r--src-migrate/pages/shop/cart/index.tsx144
-rw-r--r--src-migrate/services/product.ts4
-rw-r--r--src-migrate/types/auth.ts1
-rw-r--r--src-migrate/types/cart.ts2
-rw-r--r--src-migrate/types/product.ts1
-rw-r--r--src-migrate/types/promotion.ts7
-rw-r--r--src-migrate/validations/auth.ts158
35 files changed, 2174 insertions, 544 deletions
diff --git a/src-migrate/libs/auth.ts b/src-migrate/libs/auth.ts
index 86ce26e1..e8516747 100644
--- a/src-migrate/libs/auth.ts
+++ b/src-migrate/libs/auth.ts
@@ -1,5 +1,8 @@
import { deleteCookie, getCookie, setCookie } from 'cookies-next';
import { AuthProps } from '~/types/auth';
+// @ts-ignore
+import camelcaseObjectDeep from 'camelcase-object-deep';
+
const COOKIE_KEY = 'auth';
@@ -14,7 +17,7 @@ export const getAuth = (): AuthProps | boolean => {
};
export const setAuth = (user: AuthProps): boolean => {
- setCookie(COOKIE_KEY, JSON.stringify(user));
+ setCookie(COOKIE_KEY, JSON.stringify(camelcaseObjectDeep(user)));
return true;
};
diff --git a/src-migrate/libs/odooApi.ts b/src-migrate/libs/odooApi.ts
index 9482542b..cf839fd9 100644
--- a/src-migrate/libs/odooApi.ts
+++ b/src-migrate/libs/odooApi.ts
@@ -73,7 +73,7 @@ const odooApi = async (
return authResponse.result || null;
} catch (error) {
- console.log(error);
+ // console.log(error);
return null;
}
};
diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx
index 47893498..6ffbb524 100644
--- a/src-migrate/modules/cart/components/Item.tsx
+++ b/src-migrate/modules/cart/components/Item.tsx
@@ -17,11 +17,11 @@ import CartItemSelect from './ItemSelect'
type Props = {
item: CartItemProps
editable?: boolean
+ selfPicking?: boolean
pilihSemuaCart?: boolean
}
-const CartItem = ({ item, editable = true,}: Props) => {
-
+const CartItem = ({ item, editable = true, selfPicking}: Props) => {
return (
<div className={style.wrapper}>
{item.cart_type === 'promotion' && (
@@ -53,6 +53,11 @@ const CartItem = ({ item, editable = true,}: Props) => {
<CartItem.Image item={item} />
<div className={style.details}>
+ {(item.is_in_bu) && (item.on_hand_qty >= item.quantity) && (
+ <div className='text-[10px] text-red-500 italic'>
+ *Barang ini bisa di pickup maksimal pukul 16.00
+ </div>
+ )}
<CartItem.Name item={item} />
<div className='mt-2 flex justify-between w-full'>
diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx
index e5e7f314..7220e362 100644
--- a/src-migrate/modules/cart/components/ItemAction.tsx
+++ b/src-migrate/modules/cart/components/ItemAction.tsx
@@ -13,7 +13,6 @@ import { useDebounce } from 'usehooks-ts'
import { useCartStore } from '../stores/useCartStore'
import { useProductCartContext } from '@/contexts/ProductCartContext'
-
type Props = {
item: CartItem
}
diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts
index 3b50ec32..c2ebf50f 100644
--- a/src-migrate/modules/cart/stores/useCartStore.ts
+++ b/src-migrate/modules/cart/stores/useCartStore.ts
@@ -54,7 +54,7 @@ export const useCartStore = create<State & Action>((set, get) => ({
const computeSummary = (cart: CartProps) => {
let subtotal = 0;
let discount = 0;
- for (const item of cart.products) {
+ for (const item of cart?.products) {
if (!item.selected) continue;
let price = 0;
@@ -71,4 +71,4 @@ const computeSummary = (cart: CartProps) => {
let grandTotal = total + tax;
return { subtotal, discount, total, tax, grandTotal };
-};
+}; \ No newline at end of file
diff --git a/src-migrate/modules/page-content/index.tsx b/src-migrate/modules/page-content/index.tsx
index 547b1957..af597641 100644
--- a/src-migrate/modules/page-content/index.tsx
+++ b/src-migrate/modules/page-content/index.tsx
@@ -1,44 +1,45 @@
-import { useMemo } from "react"
-import { useQuery } from "react-query"
-import { PageContentProps } from "~/types/pageContent"
-import { getPageContent } from "~/services/pageContent"
+import { useMemo } from 'react';
+import { useQuery } from 'react-query';
+import { PageContentProps } from '~/types/pageContent';
+import { getPageContent } from '~/services/pageContent';
type Props = {
- path: string
-}
+ path: string;
+};
const PageContent = ({ path }: Props) => {
- const { data, isLoading } = useQuery<PageContentProps>(`page-content:${path}`, async () => await getPageContent({ path }))
+ const { data, isLoading } = useQuery<PageContentProps>(
+ `page-content:${path}`,
+ async () => await getPageContent({ path })
+ );
const parsedContent = useMemo<string>(() => {
- if (!data) return ''
+ if (!data) return '';
return data.content.replaceAll(
'src="/web/image',
`src="${process.env.NEXT_PUBLIC_ODOO_API_HOST}/web/image`
- )
- }, [data])
+ );
+ }, [data]);
+ console.log('parsedContent', parsedContent);
+ if (isLoading) return <PageContentSkeleton />;
- if (isLoading) return <PageContentSkeleton />
-
- return (
- <div dangerouslySetInnerHTML={{ __html: parsedContent || '' }}></div>
- )
-}
+ return <div dangerouslySetInnerHTML={{ __html: parsedContent || '' }}></div>;
+};
const PageContentSkeleton = () => (
- <div className="animate-pulse grid gap-y-4">
- <div className="w-full h-10 bg-gray-300 rounded" />
- <div className="h-2" />
- <div className="w-full h-4 bg-gray-300 rounded" />
- <div className="w-full h-4 bg-gray-300 rounded" />
- <div className="w-full h-4 bg-gray-300 rounded" />
- <div className="w-8/12 h-4 bg-gray-300 rounded" />
- <div className="h-2" />
- <div className="w-full h-4 bg-gray-300 rounded" />
- <div className="w-full h-4 bg-gray-300 rounded" />
- <div className="w-full h-4 bg-gray-300 rounded" />
- <div className="w-1/2 h-4 bg-gray-300 rounded" />
+ <div className='animate-pulse grid gap-y-4'>
+ <div className='w-full h-10 bg-gray-300 rounded' />
+ <div className='h-2' />
+ <div className='w-full h-4 bg-gray-300 rounded' />
+ <div className='w-full h-4 bg-gray-300 rounded' />
+ <div className='w-full h-4 bg-gray-300 rounded' />
+ <div className='w-8/12 h-4 bg-gray-300 rounded' />
+ <div className='h-2' />
+ <div className='w-full h-4 bg-gray-300 rounded' />
+ <div className='w-full h-4 bg-gray-300 rounded' />
+ <div className='w-full h-4 bg-gray-300 rounded' />
+ <div className='w-1/2 h-4 bg-gray-300 rounded' />
</div>
-)
+);
-export default PageContent \ No newline at end of file
+export default PageContent;
diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx
index 097db98a..a5284637 100644
--- a/src-migrate/modules/product-detail/components/AddToCart.tsx
+++ b/src-migrate/modules/product-detail/components/AddToCart.tsx
@@ -1,19 +1,35 @@
-import { Button, useToast } from '@chakra-ui/react'
+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 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 AddToCart = ({
variantId,
quantity = 1,
- source = 'add_to_cart'
+ source = 'add_to_cart',
+ products
}: Props) => {
const auth = getAuth()
const router = useRouter()
@@ -22,40 +38,65 @@ const AddToCart = ({
isClosable: true
})
- const handleClick = async () => {
+ 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;
-
- toast.promise(
- upsertUserCart({
- userId: auth.id,
+ if (status === 'success') return
+ setStatus('loading')
+ await upsertUserCart({
+ userId: auth.id,
type: 'product',
id: variantId,
qty: quantity,
selected: true,
source: source,
qtyAppend: true
- }),
- {
- loading: { title: 'Menambahkan ke keranjang', description: 'Mohon tunggu...' },
- success: { title: 'Menambahkan ke keranjang', description: 'Berhasil menambahkan ke keranjang belanja' },
- error: { title: 'Menambahkan ke keranjang', description: 'Gagal menambahkan ke keranjang belanja' },
- }
- )
-
+ })
+ 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/checkout?source=buy')
}
}
+ useEffect(() => {
+ if (status === 'success') setTimeout(() => { setStatus('idle') }, 3000)
+ }, [status])
const btnConfig = {
'add_to_cart': {
@@ -69,10 +110,98 @@ const AddToCart = ({
}
return (
- <Button onClick={handleClick} colorScheme={btnConfig[source].colorScheme} className='w-full'>
- {btnConfig[source].text}
- </Button>
+ <div 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 && (
+ <>
+ <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 AddToCart \ No newline at end of file
+export default AddToCart \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx
index 81271f6e..9021264e 100644
--- a/src-migrate/modules/product-detail/components/PriceAction.tsx
+++ b/src-migrate/modules/product-detail/components/PriceAction.tsx
@@ -97,12 +97,14 @@ const PriceAction = ({ product }: Props) => {
className={style['quantity-input']}
/>
<AddToCart
+ products={product}
variantId={activeVariantId}
quantity={Number(quantityInput)}
/>
{!isApproval && (
<AddToCart
source='buy'
+ products={product}
variantId={activeVariantId}
quantity={Number(quantityInput)}
/>
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx
index fad35a7d..e4555913 100644
--- a/src-migrate/modules/product-detail/components/ProductDetail.tsx
+++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx
@@ -129,7 +129,7 @@ const ProductDetail = ({ product }: Props) => {
)}
<div className='h-4 md:h-10' />
- {!!activeVariantId && !isApproval && <ProductPromoSection productId={activeVariantId} />}
+ {!!activeVariantId && !isApproval && <ProductPromoSection product={product} productId={activeVariantId} />}
<div className={style['section-card']}>
<h2 className={style['heading']}>
diff --git a/src-migrate/modules/product-promo/components/AddToCart.tsx b/src-migrate/modules/product-promo/components/AddToCart.tsx
index 87017c14..7b3863f9 100644
--- a/src-migrate/modules/product-promo/components/AddToCart.tsx
+++ b/src-migrate/modules/product-promo/components/AddToCart.tsx
@@ -5,53 +5,71 @@ import { useEffect, useState } from 'react'
import { getAuth } from '~/libs/auth'
import { upsertUserCart } from '~/services/cart'
-import { IPromotion } from '~/types/promotion'
+import { IPromotion, IProductVariantPromo } from '~/types/promotion'
import DesktopView from '../../../../src/core/components/views/DesktopView';
import MobileView from '../../../../src/core/components/views/MobileView';
-
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import ImageNext from 'next/image';
+import Link from 'next/link'
+import LazyLoad from 'react-lazy-load'
+import ProductSimilar from '../../../../src/lib/product/components/ProductSimilar';
+import { IProductDetail } from '~/types/product';
+import { useProductCartContext } from '@/contexts/ProductCartContext'
+import { createSlug } from '~/libs/slug'
+import formatCurrency from '~/libs/formatCurrency'
+import { useProductDetail } from '../../product-detail/stores/useProductDetail';
type Props = {
promotion: IPromotion
+ product: IProductDetail
+ variant: IProductVariantPromo,
}
type Status = 'idle' | 'loading' | 'success'
-const ProductPromoAddToCart = ({ promotion }: Props) => {
+const ProductPromoAddToCart = ({product, promotion, variant }: Props) => {
const auth = getAuth()
const toast = useToast()
const router = useRouter()
-
+ const {askAdminUrl} = useProductDetail();
const [status, setStatus] = useState<Status>('idle')
-
+ const { productCart, setRefreshCart, setProductCart, refreshCart, isLoading, setIsloading } =
+ useProductCartContext()
+ const productSimilarQuery = [
+ promotion?.name,
+ `fq=-product_id_i:${promotion.products[0].product_id}`,
+ ].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 (status === 'success') return
-
- setStatus('loading')
- await upsertUserCart({
- userId: auth.id,
- type: 'promotion',
- id: promotion.id,
- qty: 1,
- selected: true,
- source: 'add_to_cart',
- qtyAppend: true
- })
- setStatus('idle')
-
- toast({
- title: 'Tambah ke keranjang',
- description: 'Berhasil menambahkan barang ke keranjang belanja',
- status: 'success',
- duration: 3000,
- isClosable: true,
- position: 'top',
- })
+ const currentUrl = encodeURIComponent(router.asPath)
+ router.push(`/login?next=${currentUrl}`)
+ return
}
+ if (status === 'success') return
+ setStatus('loading')
+ await upsertUserCart({
+ userId: auth.id,
+ type: 'promotion',
+ id: promotion.id,
+ qty: 1,
+ selected: true,
+ source: 'add_to_cart',
+ 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',
+ })
+}
useEffect(() => {
if (status === 'success') setTimeout(() => { setStatus('idle') }, 3000)
@@ -92,6 +110,97 @@ const ProductPromoAddToCart = ({ promotion }: Props) => {
{status === 'success' && <span>Berhasil</span>}
{status !== 'success' && <span>Keranjang</span>}
</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
+ ? product?.image
+ : variant?.image || ''
+ }
+ alt={product?.name ? product?.name : variant?.display_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 || variant?.manufacture && (
+ <Link
+ href={createSlug('/shop/brands/', product?.manufacture?.name? product?.manufacture?.name : variant?.manufacture?.manufacture_name, product?.manufacture?.id? product?.manufacture?.id.toString() : variant?.manufacture?.manufacture_id.toString())}
+ className=' hover:underline text-red-500'
+ color={"red"}
+ >
+ {product?.manufacture?.name ? product?.manufacture?.name : variant?.manufacture?.manufacture_name}
+ </Link>
+ )}
+ <p className='text-ellipsis overflow-hidden'>
+ {product?.name ? product?.name : variant?.name}
+ </p>
+ <p>
+ {product?.code}
+ </p>
+ {(!!product?.lowest_price && product?.lowest_price?.price > 0) || variant?.price?.price > 0 && (
+ <>
+ <div className='flex items-end gap-x-2'>
+ {(product?.lowest_price?.discount_percentage > 0) || variant?.price?.discount_percentage > 0 && (
+ <>
+ <div className='badge-solid-red'>
+ {Math.floor(product?.lowest_price?.discount_percentage ? product?.lowest_price?.discount_percentage : variant?.price?.discount_percentage)}%
+ </div>
+ <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
+ Rp {formatCurrency(product?.lowest_price?.price ? product?.lowest_price?.price || 0 : variant?.price?.price || 0)}
+ </div>
+ </>
+ )}
+ <div className='text-danger-500 font-semibold'>
+ Rp {formatCurrency(product?.lowest_price?.price_discount? product?.lowest_price?.price_discount || 0 : variant?.price?.price_discount)}
+ </div>
+ </div>
+ </>
+ )}
+
+ {(!!product?.lowest_price && product?.lowest_price?.price === 0) || variant?.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>
</DesktopView>
</div>
)
diff --git a/src-migrate/modules/product-promo/components/Card.tsx b/src-migrate/modules/product-promo/components/Card.tsx
index 728d23ca..b8abe5ec 100644
--- a/src-migrate/modules/product-promo/components/Card.tsx
+++ b/src-migrate/modules/product-promo/components/Card.tsx
@@ -15,16 +15,16 @@ import clsxm from '~/libs/clsxm'
import ProductPromoItem from './Item'
import ProductPromoAddToCart from "./AddToCart"
import ProductPromoCardCountdown from "./CardCountdown"
-
+import { IProductDetail } from '~/types/product';
import MobileView from '../../../../src/core/components/views/MobileView';
import DesktopView from '../../../../src/core/components/views/DesktopView';
type Props = {
promotion: IPromotion
-
+ product: IProductDetail
}
-const ProductPromoCard = ({ promotion}: Props) => {
+const ProductPromoCard = ({product, promotion}: Props) => {
const [products, setProducts] = useState<IProductVariantPromo[]>([])
const [freeProducts, setFreeProducts] = useState<IProductVariantPromo[]>([])
const [error, setError] = useState<string | null>(null)
@@ -132,7 +132,7 @@ const ProductPromoCard = ({ promotion}: Props) => {
</div>
<div>
- <ProductPromoAddToCart promotion={promotion} />
+ <ProductPromoAddToCart product={product} promotion={promotion} variant={products[0]} />
</div>
</div>
@@ -189,7 +189,7 @@ const ProductPromoCard = ({ promotion}: Props) => {
</div>
</div>
<div>
- <ProductPromoAddToCart promotion={promotion} />
+ <ProductPromoAddToCart product={product} promotion={promotion} variant={products[0]}/>
</div>
</div>
diff --git a/src-migrate/modules/product-promo/components/Item.tsx b/src-migrate/modules/product-promo/components/Item.tsx
index b396160f..4b345654 100644
--- a/src-migrate/modules/product-promo/components/Item.tsx
+++ b/src-migrate/modules/product-promo/components/Item.tsx
@@ -1,34 +1,35 @@
-import style from '../styles/item.module.css'
+import style from '../styles/item.module.css';
-import { Tooltip } from '@chakra-ui/react'
+import { Tooltip } from '@chakra-ui/react';
-import Image from '~/components/ui/image'
-import { IProductVariantPromo } from '~/types/promotion'
+import Image from '~/components/ui/image';
+import { IProductVariantPromo } from '~/types/promotion';
type Props = {
- variant: IProductVariantPromo,
- isFree?: boolean
-}
+ variant: IProductVariantPromo;
+ isFree?: boolean;
+};
-const ProductPromoItem = ({
- variant,
- isFree = false
-}: Props) => {
+const ProductPromoItem = ({ variant, isFree = false }: Props) => {
return (
<div className={style.item}>
<div className={style.image}>
- <Image src={variant.image || '/images/noimage.jpeg'} alt={variant.display_name} width={120} height={120} quality={100} />
+ <Image
+ src={variant.image || '/images/noimage.jpeg'}
+ alt={variant.display_name}
+ width={120}
+ height={120}
+ quality={85}
+ />
<div className={style.quantity}>
{variant.qty} pcs {isFree ? '(free)' : ''}
</div>
</div>
<Tooltip label={variant.display_name} placement='top' fontSize='sm'>
- <div className={style.name}>
- {variant.name}
- </div>
+ <div className={style.name}>{variant.name}</div>
</Tooltip>
</div>
- )
-}
+ );
+};
-export default ProductPromoItem \ No newline at end of file
+export default ProductPromoItem;
diff --git a/src-migrate/modules/product-promo/components/Modal.tsx b/src-migrate/modules/product-promo/components/Modal.tsx
index 0de672c2..1722b9df 100644
--- a/src-migrate/modules/product-promo/components/Modal.tsx
+++ b/src-migrate/modules/product-promo/components/Modal.tsx
@@ -3,8 +3,12 @@ import { Modal } from "~/components/ui/modal"
import { useModalStore } from '../stores/useModalStore'
import ProductPromoCategoryTab from './CategoryTab'
import ProductPromoModalContent from './ModalContent'
+import { IProductDetail } from '~/types/product';
-const ProductPromoModal = () => {
+type Props = {
+ product: IProductDetail
+}
+const ProductPromoModal = ({product}:Props) => {
const { active, closeModal } = useModalStore()
return (
@@ -17,7 +21,7 @@ const ProductPromoModal = () => {
<div className='h-4' />
- <ProductPromoModalContent />
+ <ProductPromoModalContent product={product} />
</Modal>
)
}
diff --git a/src-migrate/modules/product-promo/components/ModalContent.tsx b/src-migrate/modules/product-promo/components/ModalContent.tsx
index ab5129f8..256ef61a 100644
--- a/src-migrate/modules/product-promo/components/ModalContent.tsx
+++ b/src-migrate/modules/product-promo/components/ModalContent.tsx
@@ -6,7 +6,11 @@ import { getVariantPromoByCategory } from "~/services/productVariant"
import { useModalStore } from "../stores/useModalStore"
import ProductPromoCard from "./Card"
-const ProductPromoModalContent = () => {
+import { IProductDetail } from '~/types/product';
+type Props = {
+ product: IProductDetail
+}
+const ProductPromoModalContent = ({product}:Props) => {
const { activeTab, variantId } = useModalStore()
const promotionsQuery = useQuery(
@@ -24,7 +28,7 @@ const ProductPromoModalContent = () => {
<Skeleton isLoaded={!promotionsQuery.isLoading} className='min-h-[70vh] max-h-[70vh]'>
<div className="grid grid-cols-1 gap-y-6 pb-6">
{promotions?.data.map((promo) => (
- <ProductPromoCard key={promo.id} promotion={promo} />
+ <ProductPromoCard key={promo.id} promotion={promo} product={product} />
))}
{promotions?.data.length === 0 && (
<div className="py-10 rounded-lg h-fit text-center text-body-1 font-semibold text-gray-800 bg-gray-200">Belum ada promo pada kategori ini</div>
diff --git a/src-migrate/modules/product-promo/components/Section.tsx b/src-migrate/modules/product-promo/components/Section.tsx
index 4e8a7dd5..e1719998 100644
--- a/src-migrate/modules/product-promo/components/Section.tsx
+++ b/src-migrate/modules/product-promo/components/Section.tsx
@@ -9,12 +9,14 @@ import { IPromotion } from '~/types/promotion'
import { useModalStore } from "../stores/useModalStore"
import ProductPromoCard from './Card'
import ProductPromoModal from "./Modal"
+import { IProductDetail } from '~/types/product';
type Props = {
productId: number;
+ product: IProductDetail;
}
-const ProductPromoSection = ({ productId }: Props) => {
+const ProductPromoSection = ({ product, productId }: Props) => {
const promotionsQuery = useQuery({
queryKey: [`promotions.highlight`, productId],
queryFn: async () => await fetch(`/api/product-variant/${productId}/promotion/highlight`).then((res) => res.json()) as { data: IPromotion[] }
@@ -23,14 +25,13 @@ const ProductPromoSection = ({ productId }: Props) => {
const promotions = promotionsQuery.data
const { openModal } = useModalStore()
-
return (
<SmoothRender
isLoaded={(promotions?.data && promotions?.data.length > 0) || false}
height='450px'
duration='700ms'
>
- <ProductPromoModal />
+ <ProductPromoModal product={product}/>
{promotions?.data && promotions?.data.length > 0 && (
<div className={style.titleWrapper}>
@@ -50,7 +51,7 @@ const ProductPromoSection = ({ productId }: Props) => {
>
{promotions?.data.map((promotion) => (
<div key={promotion.id} className="min-w-[400px] max-w-[400px]">
- <ProductPromoCard promotion={promotion} />
+ <ProductPromoCard product={product} promotion={promotion} />
</div>
))}
</Skeleton>
diff --git a/src-migrate/modules/promo/components/Hero.tsx b/src-migrate/modules/promo/components/Hero.tsx
index c5f0afad..7d0aad11 100644
--- a/src-migrate/modules/promo/components/Hero.tsx
+++ b/src-migrate/modules/promo/components/Hero.tsx
@@ -3,34 +3,34 @@ import 'swiper/css';
import Image from 'next/image';
import { useEffect, useMemo } from 'react';
import { useQuery } from 'react-query';
-import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
+import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
import style from '../styles/hero.module.css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
-import { Navigation, Pagination, Autoplay } from 'swiper';
+import { Navigation, Pagination, Autoplay } from 'swiper';
import MobileView from '../../../../src/core/components/views/MobileView';
import DesktopView from '@/core/components/views/DesktopView';
-import {bannerApi} from '../../../../src/api/bannerApi'
+import { bannerApi } from '../../../../src/api/bannerApi';
interface IPromotionProgram {
headlineBanner: string;
descriptionBanner: string;
- image: string ;
+ image: string;
name: string;
}
const swiperBanner: SwiperProps = {
- modules:[Navigation, Pagination, Autoplay],
+ modules: [Navigation, Pagination, Autoplay],
autoplay: {
delay: 6000,
- disableOnInteraction: false
+ disableOnInteraction: false,
},
loop: true,
className: 'h-[400px] w-full',
slidesPerView: 1,
spaceBetween: 10,
- pagination:true,
-}
+ pagination: true,
+};
const swiperBannerMob = {
autoplay: {
delay: 6000,
@@ -43,7 +43,10 @@ const swiperBannerMob = {
};
const Hero = () => {
- const heroBanner = useQuery('allPromo', bannerApi({ type: 'banner-semua-promo' }));
+ const heroBanner = useQuery(
+ 'allPromo',
+ bannerApi({ type: 'banner-semua-promo' })
+ );
const banners: IPromotionProgram[] = useMemo(
() => heroBanner?.data || [],
@@ -54,52 +57,60 @@ const Hero = () => {
...swiperBannerMob,
pagination: { dynamicBullets: false, clickable: true },
};
-
+
return (
<>
<DesktopView>
<div className={style['wrapper']}>
- <Swiper {...swiperBanner}>
- {banners.map((banner, index) => (
- <SwiperSlide key={index} className='flex flex-row'>
- <div className={style['desc-section']}>
- <div className={style['title']}>{banner.headlineBanner? banner.headlineBanner : "Pasti Hemat & Untung Selama Belanja di Indoteknik.com!"}</div>
- <div className='h-4' />
- <div className={style['subtitle']}>{banner.descriptionBanner? banner.descriptionBanner : "Cari paket yang kami sediakan dengan penawaran harga & Nikmati kemudahan dalam setiap transaksi dengan fitur lengkap Pembayaran hingga barang sampai!"}</div>
+ <Swiper {...swiperBanner}>
+ {banners?.map((banner, index) => (
+ <SwiperSlide key={index} className='flex flex-row'>
+ <div className={style['desc-section']}>
+ <div className={style['title']}>
+ {banner?.headlineBanner
+ ? banner?.headlineBanner
+ : 'Pasti Hemat & Untung Selama Belanja di Indoteknik.com!'}
</div>
- <div className={style['banner-section']}>
- <Image
- src={banner.image}
- alt={banner.name}
- width={666}
- height={450}
- quality={90}
- className='w-full h-full object-fit object-center rounded-2xl' />
+ <div className='h-4' />
+ <div className={style['subtitle']}>
+ {banner?.descriptionBanner
+ ? banner?.descriptionBanner
+ : 'Cari paket yang kami sediakan dengan penawaran harga & Nikmati kemudahan dalam setiap transaksi dengan fitur lengkap Pembayaran hingga barang sampai!'}
</div>
- </SwiperSlide>
- ))}
- </Swiper>
- </div>
- </DesktopView>
- <MobileView>
- <Swiper {...swiperBannerMobile}>
- {banners?.map((banner, index) => (
- <SwiperSlide key={index}>
- <Image
- width={439}
- height={150}
- quality={100}
- src={banner.image}
- alt={banner.name}
- className='w-full h-full object-cover object-center rounded-2xl'
- />
+ </div>
+ <div className={style['banner-section']}>
+ <Image
+ src={banner.image}
+ alt={banner.name}
+ width={666}
+ height={450}
+ quality={85}
+ className='w-full h-full object-fit object-center rounded-2xl'
+ />
+ </div>
</SwiperSlide>
))}
</Swiper>
-
+ </div>
+ </DesktopView>
+ <MobileView>
+ <Swiper {...swiperBannerMobile}>
+ {banners?.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ width={439}
+ height={150}
+ quality={85}
+ src={banner?.image}
+ alt={banner?.name}
+ className='w-full h-full object-cover object-center rounded-2xl'
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
</MobileView>
</>
- )
-}
+ );
+};
-export default Hero \ No newline at end of file
+export default Hero;
diff --git a/src-migrate/modules/promo/components/HeroDiskon.tsx b/src-migrate/modules/promo/components/HeroDiskon.tsx
index 6d38c763..8b8edcc0 100644
--- a/src-migrate/modules/promo/components/HeroDiskon.tsx
+++ b/src-migrate/modules/promo/components/HeroDiskon.tsx
@@ -39,7 +39,7 @@ const Hero = () => {
queryFn: () => getBanner({ type: 'banner-promotion' })
})
- const banners = useMemo(() => bannerQuery.data || [], [bannerQuery.data]);
+ const banners = useMemo(() => bannerQuery?.data || [], [bannerQuery?.data]);
useEffect(() => {
if (banners.length > 1) {
@@ -56,8 +56,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={480}
className='w-[446px] h-[446px] object-fill object-center rounded-2xl'
@@ -71,8 +71,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={450}
className='w-[400px] h-[217px] object-cover object-center rounded-2xl '
@@ -86,8 +86,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={450}
className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
@@ -101,8 +101,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={450}
className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
@@ -116,8 +116,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={450}
className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
diff --git a/src-migrate/modules/promo/components/PromoList.tsx b/src-migrate/modules/promo/components/PromoList.tsx
index 42725034..d59d1867 100644
--- a/src-migrate/modules/promo/components/PromoList.tsx
+++ b/src-migrate/modules/promo/components/PromoList.tsx
@@ -59,7 +59,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10);
setPromoItems(items);
- const promoDataPromises = items.map(async (item) => {
+ const promoDataPromises = items?.map(async (item) => {
try {
const response = await fetchPromoItemsSolr(`id:${item.id}`, 0, 10);
return response;
@@ -69,7 +69,7 @@ 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) {
@@ -114,7 +114,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
{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 promotion={promotion} />
+ <ProductPromoCard product={promoItems} promotion={promotion} />
</div>
</SwiperSlide>
))}
@@ -122,7 +122,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
)}
{isMobile && (promoData?.map((promotion: IPromotion) => (
<div key={promotion.id} className="min-w-[400px] max-w-[400px]">
- <ProductPromoCard promotion={promotion} />
+ <ProductPromoCard product={promoItems} promotion={promotion} />
</div>
)))}
diff --git a/src-migrate/modules/promo/components/PromotinProgram.jsx b/src-migrate/modules/promo/components/PromotinProgram.jsx
index 33839944..43e4eedf 100644
--- a/src-migrate/modules/promo/components/PromotinProgram.jsx
+++ b/src-migrate/modules/promo/components/PromotinProgram.jsx
@@ -1,9 +1,7 @@
import React from 'react';
import Image from 'next/image';
-import { InfoIcon } from "lucide-react";
-import MobileView from '../../../../src/core/components/views/MobileView';
-import DesktopView from '@/core/components/views/DesktopView';
-import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
+import { InfoIcon } from 'lucide-react';
+import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import useDevice from '@/core/hooks/useDevice';
@@ -11,9 +9,12 @@ const PromotionProgram = ({ selectedPromo, onSelectPromo }) => {
const { isMobile } = useDevice();
return (
<>
- <div className="text-h-sm md:text-h-lg font-semibold py-4">Serba Serbi Promo</div>
+ <h1 className='text-h-sm md:text-h-lg font-semibold py-4'>
+ {' '}
+ Serba Serbi Promo
+ </h1>
<div className='px-4 sm:px-0'>
- {/* <div className='w-full h-full '>
+ {/* <div className='w-full h-full '>
<div
onClick={() => onSelectPromo('Diskon')}
className={`border p-2 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Diskon' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
@@ -39,93 +40,147 @@ const PromotionProgram = ({ selectedPromo, onSelectPromo }) => {
</div>
</div>
</div> */}
-
- <Swiper slidesPerView={isMobile ? 1.3 : 3} spaceBetween={10}>
- <SwiperSlide>
- <div className='w-full h-full '>
- <div
- onClick={() => onSelectPromo('Bundling')}
- className={`border h-full p-1 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Bundling' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
- >
- <div>
- <Image
- width={24}
- height={24}
- quality={100}
- src='/images/icon_promo/silat.svg'
- alt=''
- className='h-12 w-12 rounded'
- />
- </div>
- <div >
- <div className='flex w-full flex-row items-center justify-start'>
- <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Bundling' ? 'text-red-500' : 'text-gray-900'}`}>Paket Silat</h1>
- <InfoIcon className='mt-[1px] text-red-500' size={14} />
- </div>
- <p className={`text-xs md:text-sm ${selectedPromo === 'Bundling' ? 'text-red-500' : 'text-gray-500'}`}>
- Pilihan bundling barang kombinasi Silat.
- </p>
+
+ <Swiper slidesPerView={isMobile ? 1.3 : 3} spaceBetween={10}>
+ <SwiperSlide>
+ <div className='w-full h-full '>
+ <div
+ onClick={() => onSelectPromo('Bundling')}
+ className={`border h-full p-1 flex items-center gap-x-2 rounded-lg cursor-pointer ${
+ selectedPromo === 'Bundling'
+ ? 'bg-red-50 border-red-500 text-red-500'
+ : 'border-gray-200 text-gray-900'
+ }`}
+ >
+ <div>
+ <Image
+ width={24}
+ height={24}
+ quality={85}
+ src='/images/icon_promo/silat.svg'
+ alt=''
+ className='h-12 w-12 rounded'
+ />
+ </div>
+ <div>
+ <div className='flex w-full flex-row items-center justify-start'>
+ <h1
+ className={`mr-1 font-semibold text-base ${
+ selectedPromo === 'Bundling'
+ ? 'text-red-500'
+ : 'text-gray-900'
+ }`}
+ >
+ Paket Silat
+ </h1>
+ <InfoIcon className='mt-[1px] text-red-500' size={14} />
</div>
+ <p
+ className={`text-xs md:text-sm ${
+ selectedPromo === 'Bundling'
+ ? 'text-red-500'
+ : 'text-gray-500'
+ }`}
+ >
+ Pilihan bundling barang kombinasi Silat.
+ </p>
</div>
</div>
- </SwiperSlide>
- <SwiperSlide>
- <div className='w-full h-full '>
- <div
- onClick={() => onSelectPromo('Loading')}
- className={`border p-2 h-full flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Loading' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
- >
- <div>
- <Image
- width={24}
- height={24}
- quality={100}
- src='/images/icon_promo/barong.svg'
- alt=''
- className='h-12 w-12 rounded'
- />
- </div>
- <div>
- <div className='flex w-full flex-row items-center justify-start'>
- <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Loading' ? 'text-red-500' : 'text-gray-900'}`}>Paket Barong</h1>
- <InfoIcon className='mt-[1px] text-red-500' size={14} />
- </div>
- <p className={`text-xs md:text-sm ${selectedPromo === 'Loading' ? 'text-red-500' : 'text-gray-500'}`}>
- Beli banyak barang/partai barang borong.
- </p>
+ </div>
+ </SwiperSlide>
+ <SwiperSlide>
+ <div className='w-full h-full '>
+ <div
+ onClick={() => onSelectPromo('Loading')}
+ className={`border p-2 h-full flex items-center gap-x-2 rounded-lg cursor-pointer ${
+ selectedPromo === 'Loading'
+ ? 'bg-red-50 border-red-500 text-red-500'
+ : 'border-gray-200 text-gray-900'
+ }`}
+ >
+ <div>
+ <Image
+ width={24}
+ height={24}
+ quality={85}
+ src='/images/icon_promo/barong.svg'
+ alt=''
+ className='h-12 w-12 rounded'
+ />
+ </div>
+ <div>
+ <div className='flex w-full flex-row items-center justify-start'>
+ <h1
+ className={`mr-1 font-semibold text-base ${
+ selectedPromo === 'Loading'
+ ? 'text-red-500'
+ : 'text-gray-900'
+ }`}
+ >
+ Paket Barong
+ </h1>
+ <InfoIcon className='mt-[1px] text-red-500' size={14} />
</div>
+ <p
+ className={`text-xs md:text-sm ${
+ selectedPromo === 'Loading'
+ ? 'text-red-500'
+ : 'text-gray-500'
+ }`}
+ >
+ Beli banyak barang/partai barang borong.
+ </p>
</div>
</div>
- </SwiperSlide>
- <SwiperSlide>
- <div className='w-full h-full '>
- <div
- onClick={() => onSelectPromo('Merchandise')}
- className={`border p-2 h-full flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Merchandise' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
- >
- <div>
- <Image
- width={24}
- height={24}
- quality={100}
- src='/images/icon_promo/angklung.svg'
- alt=''
- className='h-12 w-12 rounded'
- />
- </div>
- <div >
- <div className='flex w-full flex-row items-center justify-start '>
- <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Merchandise' ? 'text-red-500' : 'text-gray-900'}`}>Paket Angklung</h1>
- <InfoIcon className='mt-[1px] text-red-500' size={14} />
- </div>
- <p className={` m1 text-xs md:text-sm ${selectedPromo === 'Merchandise' ? 'text-red-500' : 'text-gray-500'}`}>
- Gratis barang promosi/merchandise menang langsung.
- </p>
+ </div>
+ </SwiperSlide>
+ <SwiperSlide>
+ <div className='w-full h-full '>
+ <div
+ onClick={() => onSelectPromo('Merchandise')}
+ className={`border p-2 h-full flex items-center gap-x-2 rounded-lg cursor-pointer ${
+ selectedPromo === 'Merchandise'
+ ? 'bg-red-50 border-red-500 text-red-500'
+ : 'border-gray-200 text-gray-900'
+ }`}
+ >
+ <div>
+ <Image
+ width={24}
+ height={24}
+ quality={85}
+ src='/images/icon_promo/angklung.svg'
+ alt=''
+ className='h-12 w-12 rounded'
+ />
+ </div>
+ <div>
+ <div className='flex w-full flex-row items-center justify-start '>
+ <h1
+ className={`mr-1 font-semibold text-base ${
+ selectedPromo === 'Merchandise'
+ ? 'text-red-500'
+ : 'text-gray-900'
+ }`}
+ >
+ Paket Angklung
+ </h1>
+ <InfoIcon className='mt-[1px] text-red-500' size={14} />
</div>
+ <p
+ className={` m1 text-xs md:text-sm ${
+ selectedPromo === 'Merchandise'
+ ? 'text-red-500'
+ : 'text-gray-500'
+ }`}
+ >
+ Gratis barang promosi/merchandise menang langsung.
+ </p>
</div>
</div>
- </SwiperSlide>
- </Swiper>
+ </div>
+ </SwiperSlide>
+ </Swiper>
</div>
</>
);
diff --git a/src-migrate/modules/promo/components/Voucher.tsx b/src-migrate/modules/promo/components/Voucher.tsx
index e5877e51..034d13e9 100644
--- a/src-migrate/modules/promo/components/Voucher.tsx
+++ b/src-migrate/modules/promo/components/Voucher.tsx
@@ -55,15 +55,18 @@ const VoucherComponent = () => {
slidesPerView: isMobile ? 1.2 : 3.2,
spaceBetween: 2,
};
-
- const dataVouchers = useMemo(() => voucherQuery.data || [], [voucherQuery.data]);
- const vouchers = auth?.id? listVouchers : dataVouchers;
+ const dataVouchers = useMemo(
+ () => voucherQuery?.data || [],
+ [voucherQuery?.data]
+ );
+ const vouchers = auth?.id ? listVouchers : dataVouchers;
const copyText = (text: string) => {
if (navigator.clipboard && navigator.clipboard.writeText) {
- navigator.clipboard.writeText(text)
+ navigator.clipboard
+ .writeText(text)
.then(() => {
toast({
title: 'Salin ke papan klip',
@@ -72,7 +75,7 @@ const VoucherComponent = () => {
duration: 3000,
isClosable: true,
position: 'top',
- })
+ });
})
.catch(() => {
fallbackCopyTextToClipboard(text);
@@ -80,10 +83,10 @@ const VoucherComponent = () => {
} else {
fallbackCopyTextToClipboard(text);
}
- }
+ };
const fallbackCopyTextToClipboard = (text: string) => {
- const textArea = document.createElement("textarea");
+ const textArea = document.createElement('textarea');
textArea.value = text;
// Tambahkan style untuk menyembunyikan textArea secara visual
textArea.style.position = 'fixed';
@@ -108,43 +111,65 @@ const VoucherComponent = () => {
duration: 3000,
isClosable: true,
position: 'top',
- })
+ });
} catch (err) {
console.error('Fallback: Oops, unable to copy', err);
}
document.body.removeChild(textArea);
- }
+ };
return (
<>
- <div className={style['title']}>Pakai Voucher Belanja</div>
+ <h1 className={style['title']}>Pakai Voucher Belanja</h1>
<div className='h-6' />
- {voucherQuery.isLoading && (
+ {voucherQuery?.isLoading && (
<div className='grid grid-cols-3 gap-x-4 animate-pulse'>
{Array.from({ length: 3 }).map((_, index) => (
- <div key={index} className='w-full h-[160px] bg-gray-200 rounded-xl' />
+ <div
+ key={index}
+ className='w-full h-[160px] bg-gray-200 rounded-xl'
+ />
))}
</div>
)}
- {!voucherQuery.isLoading && (
+ {!voucherQuery?.isLoading && (
<div className={style['voucher-section']}>
<Swiper {...swiperVoucher}>
{vouchers?.map((voucher) => (
<SwiperSlide key={voucher.id} className='pb-2'>
<div className={style['voucher-card']}>
- <Image src={voucher.image} alt={voucher.name} width={128} height={128} className={style['voucher-image']} />
+ <Image
+ src={voucher?.image}
+ alt={voucher?.name}
+ width={128}
+ height={128}
+ className={style['voucher-image']}
+ />
<div className={style['voucher-content']}>
- <div className={style['voucher-title']}>{voucher.name}</div>
- <div className={style['voucher-desc']}>{voucher.description}</div>
+ <div className={style['voucher-title']}>
+ {voucher?.name}
+ </div>
+ <div className={style['voucher-desc']}>
+ {voucher?.description}
+ </div>
<div className={style['voucher-bottom']}>
<div>
- <div className={style['voucher-code-desc']}>Kode Promo</div>
- <div className={style['voucher-code']}>{voucher.code}</div>
+ <div className={style['voucher-code-desc']}>
+ Kode Promo
+ </div>
+ <div className={style['voucher-code']}>
+ {voucher?.code}
+ </div>
</div>
- <button className={style['voucher-copy']} onClick={() => copyText(voucher.code)}>Salin</button>
+ <button
+ className={style['voucher-copy']}
+ onClick={() => copyText(voucher?.code)}
+ >
+ Salin
+ </button>
</div>
</div>
</div>
@@ -154,7 +179,7 @@ const VoucherComponent = () => {
</div>
)}
</>
- )
-}
+ );
+};
-export default VoucherComponent
+export default VoucherComponent;
diff --git a/src-migrate/modules/register/components/Form.tsx b/src-migrate/modules/register/components/Form.tsx
index b834f97a..38e9c810 100644
--- a/src-migrate/modules/register/components/Form.tsx
+++ b/src-migrate/modules/register/components/Form.tsx
@@ -1,179 +1,169 @@
-import { ChangeEvent, useMemo } from "react";
-import { useMutation } from "react-query";
-import { useRegisterStore } from "../stores/useRegisterStore";
-import { RegisterProps } from "~/types/auth";
-import { registerUser } from "~/services/auth";
-import TermCondition from "./TermCondition";
-import FormCaptcha from "./FormCaptcha";
-import { useRouter } from "next/router";
-import { UseToastOptions, useToast } from "@chakra-ui/react";
-import Link from "next/link";
-
-const Form = () => {
- const {
- form,
- isCheckedTNC,
- isValidCaptcha,
- errors,
- updateForm,
- validate,
- } = useRegisterStore()
-
- const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors])
-
- const router = useRouter()
- const toast = useToast()
+import { ChangeEvent, useMemo, useEffect, useRef, useState } from 'react';
+import { useMutation } from 'react-query';
+import { useRegisterStore } from '../stores/useRegisterStore';
+import { RegisterProps } from '~/types/auth';
+import { registerUser } from '~/services/auth';
+import TermCondition from './TermCondition';
+import FormCaptcha from './FormCaptcha';
+import { useRouter } from 'next/router';
+import { UseToastOptions, useToast } from '@chakra-ui/react';
+import Link from 'next/link';
+
+interface FormProps {
+ type: string;
+ required: boolean;
+ isBisnisRegist: boolean;
+ chekValid: boolean;
+ buttonSubmitClick: boolean;
+}
+
+const Form: React.FC<FormProps> = ({
+ type,
+ required,
+ isBisnisRegist = false,
+ chekValid,
+ buttonSubmitClick,
+}) => {
+ const { form, isCheckedTNC, isValidCaptcha, errors, updateForm, validate } =
+ useRegisterStore();
+ const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors]);
+
+ const router = useRouter();
+ const toast = useToast();
+
+ const emailRef = useRef<HTMLInputElement>(null);
+ const nameRef = useRef<HTMLInputElement>(null);
+ const passwordRef = useRef<HTMLInputElement>(null);
+ const phoneRef = useRef<HTMLInputElement>(null);
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
- updateForm(name, value)
- validate()
- }
-
- const mutation = useMutation({
- mutationFn: (data: RegisterProps) => registerUser(data)
- })
-
- const handleSubmit = async (e: ChangeEvent<HTMLFormElement>) => {
- e.preventDefault()
-
- const response = await mutation.mutateAsync(form)
-
- if (response?.register === true) {
- const urlParams = new URLSearchParams({
- activation: 'otp',
- email: form.email,
- redirect: (router.query?.next || '/') as string
- })
- router.push(`${router.route}?${urlParams}`)
- }
-
- const toastProps: UseToastOptions = {
- duration: 5000,
- isClosable: true
- }
-
- switch (response?.reason) {
- case 'EMAIL_USED':
- toast({
- ...toastProps,
- title: 'Email sudah digunakan',
- status: 'warning'
- })
- break;
- case 'NOT_ACTIVE':
- const activationUrl = `${router.route}?activation=email`
- toast({
- ...toastProps,
- title: 'Akun belum aktif',
- description: <>Akun sudah terdaftar namun belum aktif. <Link href={activationUrl} className="underline">Klik untuk aktivasi akun</Link></>,
- status: 'warning'
- })
- break
- }
- }
+ updateForm(name, value);
+ validate();
+ };
+ useEffect(() => {
+ const loadIndustries = async () => {
+ if (!isFormValid) {
+ const options: ScrollIntoViewOptions = {
+ behavior: 'smooth',
+ block: 'center',
+ };
+
+ if (errors.email_partner && emailRef.current) {
+ emailRef.current.scrollIntoView(options);
+ return;
+ }
+ if (errors.name && nameRef.current) {
+ nameRef.current.scrollIntoView(options);
+ return;
+ }
+
+ if (errors.password && passwordRef.current) {
+ passwordRef.current.scrollIntoView(options);
+ return;
+ }
+
+ if (errors.phone && phoneRef.current) {
+ phoneRef.current.scrollIntoView(options);
+ return;
+ }
+ }
+ };
+ loadIndustries();
+ }, [buttonSubmitClick, chekValid]);
return (
- <form className="mt-6 grid grid-cols-1 gap-y-4" onSubmit={handleSubmit}>
+ <form className='mt-6 grid grid-cols-1 gap-y-4'>
<div>
- <label htmlFor="company">
- Nama Perusahaan <span className='text-gray_r-11'>(opsional)</span>
+ <label htmlFor='name' className='text-black font-bold'>
+ Nama Lengkap
</label>
<input
- type="text"
- name="company"
- id="company"
- className="form-input mt-3"
- placeholder="cth: INDOTEKNIK DOTCOM GEMILANG"
- autoCapitalize="true"
- value={form.company}
- onChange={handleInputChange}
- />
- </div>
-
- <div>
- <label htmlFor='name'>Nama Lengkap</label>
-
- <input
type='text'
id='name'
name='name'
- className='form-input mt-3'
+ className='form-input mt-3 transition-all duration-700'
placeholder='Masukan nama lengkap anda'
value={form.name}
+ ref={nameRef}
onChange={handleInputChange}
- aria-invalid={!!errors.name}
- />
-
- {!!errors.name && <span className="form-msg-danger">{errors.name}</span>}
- </div>
-
- <div>
- <label htmlFor='phone'>No Handphone</label>
-
- <input
- type='tel'
- id='phone'
- name='phone'
- className='form-input mt-3'
- placeholder='08xxxxxxxx'
- value={form.phone}
- onChange={handleInputChange}
- aria-invalid={!!errors.phone}
+ aria-invalid={chekValid && !!errors.name}
/>
- {!!errors.phone && <span className="form-msg-danger">{errors.phone}</span>}
+ {chekValid && !!errors.name && (
+ <span className='form-msg-danger'>{errors.name}</span>
+ )}
</div>
<div>
- <label htmlFor='email'>Alamat Email</label>
+ <label htmlFor='email' className='text-black font-bold'>
+ Alamat Email
+ </label>
<input
type='text'
id='email'
name='email'
- className='form-input mt-3'
+ className='form-input mt-3 transition-all duration-500'
placeholder='Masukan alamat email anda'
value={form.email}
+ ref={emailRef}
onChange={handleInputChange}
- autoComplete="username"
- aria-invalid={!!errors.email}
+ autoComplete='username'
+ aria-invalid={chekValid && !!errors.email}
/>
- {!!errors.email && <span className="form-msg-danger">{errors.email}</span>}
+ {chekValid && !!errors.email && (
+ <span className='form-msg-danger'>{errors.email}</span>
+ )}
</div>
<div>
- <label htmlFor='password'>Kata Sandi</label>
+ <label htmlFor='password' className='text-black font-bold'>
+ Kata Sandi
+ </label>
<input
type='password'
name='password'
id='password'
- className='form-input mt-3'
+ className='form-input mt-3 transition-all duration-500'
placeholder='••••••••••••'
value={form.password}
+ ref={passwordRef}
onChange={handleInputChange}
- autoComplete="current-password"
- aria-invalid={!!errors.password}
+ autoComplete='current-password'
+ aria-invalid={chekValid && !!errors.password}
/>
- {!!errors.password && <span className="form-msg-danger">{errors.password}</span>}
+ {chekValid && !!errors.password && (
+ <span className='form-msg-danger'>{errors.password}</span>
+ )}
</div>
- <FormCaptcha />
+ <div>
+ <label htmlFor='phone' className='text-black font-bold'>
+ No Handphone
+ </label>
- <TermCondition />
+ <input
+ type='tel'
+ id='phone'
+ name='phone'
+ className='form-input mt-3 transition-all duration-500'
+ placeholder='08xxxxxxxx'
+ value={form.phone}
+ ref={phoneRef}
+ onChange={handleInputChange}
+ aria-invalid={chekValid && !!errors.phone}
+ />
- <button
- type="submit"
- className="btn-yellow w-full mt-2"
- disabled={!isFormValid || !isCheckedTNC || mutation.isLoading || !isValidCaptcha}
- >
- {mutation.isLoading ? 'Loading...' : 'Daftar'}
- </button>
+ {chekValid && !!errors.phone && (
+ <span className='form-msg-danger'>{errors.phone}</span>
+ )}
+ </div>
</form>
- )
-}
+ );
+};
-export default Form \ No newline at end of file
+export default Form;
diff --git a/src-migrate/modules/register/components/FormBisnis.tsx b/src-migrate/modules/register/components/FormBisnis.tsx
new file mode 100644
index 00000000..e4cf3442
--- /dev/null
+++ b/src-migrate/modules/register/components/FormBisnis.tsx
@@ -0,0 +1,715 @@
+import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
+import { useMutation } from 'react-query';
+import { useRegisterStore } from '../stores/useRegisterStore';
+import { RegisterProps } from '~/types/auth';
+import { registerUser } from '~/services/auth';
+import { useRouter } from 'next/router';
+import {
+ Button,
+ Checkbox,
+ UseToastOptions,
+ color,
+ useToast,
+} from '@chakra-ui/react';
+import Link from 'next/link';
+import getFileBase64 from '@/core/utils/getFileBase64';
+import { Controller, useForm } from 'react-hook-form';
+import HookFormSelect from '@/core/components/elements/Select/HookFormSelect';
+import odooApi from '~/libs/odooApi';
+import { toast } from 'react-hot-toast';
+import { EyeIcon } from '@heroicons/react/24/outline';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import Image from 'next/image';
+import useDevice from '@/core/hooks/useDevice';
+interface FormProps {
+ type: string;
+ required: boolean;
+ isPKP: boolean;
+ chekValid: boolean;
+ buttonSubmitClick: boolean;
+}
+
+interface industry_id {
+ label: string;
+ value: string;
+ category: string;
+}
+
+interface companyType {
+ value: string;
+ label: string;
+}
+
+const form: React.FC<FormProps> = ({
+ type,
+ required,
+ isPKP,
+ chekValid,
+ buttonSubmitClick,
+}) => {
+ const { form, errors, updateForm, validate } = useRegisterStore();
+ const { control, watch, setValue } = useForm();
+ const [selectedCategory, setSelectedCategory] = useState<string>('');
+ const [isChekBox, setIsChekBox] = useState<boolean>(false);
+ const [isExample, setIsExample] = useState<boolean>(false);
+ const { isDesktop, isMobile } = useDevice();
+ // Inside your component
+ const [formattedNpwp, setFormattedNpwp] = useState<string>(''); // State for formatted NPWP
+ const [unformattedNpwp, setUnformattedNpwp] = useState<string>(''); // State for unformatted NPWP
+
+ const [industries, setIndustries] = useState<industry_id[]>([]);
+ const [companyTypes, setCompanyTypes] = useState<companyType[]>([]);
+
+ const router = useRouter();
+ const toast = useToast();
+
+ const emailRef = useRef<HTMLInputElement>(null);
+ const businessNameRef = useRef<HTMLInputElement>(null);
+ const companyTypeRef = useRef<HTMLInputElement>(null);
+ const industryRef = useRef<HTMLDivElement>(null);
+ const addressRef = useRef<HTMLInputElement>(null);
+ const namaWajibPajakRef = useRef<HTMLInputElement>(null);
+ const alamatWajibPajakRef = useRef<HTMLInputElement>(null);
+ const npwpRef = useRef<HTMLInputElement>(null);
+ const sppkpRef = useRef<HTMLInputElement>(null);
+ const docsSppkpRef = useRef<HTMLInputElement>(null);
+ const docsNpwpRef = useRef<HTMLInputElement>(null);
+
+ useEffect(() => {
+ const loadCompanyTypes = async () => {
+ const dataCompanyTypes = await odooApi(
+ 'GET',
+ '/api/v1/partner/company_type'
+ );
+ setCompanyTypes(
+ dataCompanyTypes?.map((o: { id: any; name: any }) => ({
+ value: o.id,
+ label: o.name,
+ }))
+ );
+ };
+ loadCompanyTypes();
+ }, []);
+
+ useEffect(() => {
+ const selectedCompanyType = companyTypes.find(
+ (company) => company.value === watch('companyType')
+ );
+ if (selectedCompanyType) {
+ updateForm('company_type_id', `${selectedCompanyType?.value}`);
+ validate();
+ }
+ }, [watch('companyType'), companyTypes]);
+
+ useEffect(() => {
+ const selectedIndustryType = industries.find(
+ (industry) => industry.value === watch('industry_id')
+ );
+ if (selectedIndustryType) {
+ updateForm('industry_id', `${selectedIndustryType?.value}`);
+ setSelectedCategory(selectedIndustryType.category);
+ validate();
+ }
+ }, [watch('industry_id'), industries]);
+
+ useEffect(() => {
+ const loadIndustries = async () => {
+ const dataIndustries = await odooApi('GET', '/api/v1/partner/industry');
+ setIndustries(
+ dataIndustries?.map((o: { id: any; name: any; category: any }) => ({
+ value: o.id,
+ label: o.name,
+ category: o.category,
+ }))
+ );
+ };
+ loadIndustries();
+ }, []);
+
+ const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
+ const { name, value } = event.target;
+
+ updateForm('type_acc', 'business');
+ updateForm('is_pkp', `${isPKP}`);
+
+ // Update form dengan nilai terbaru dari input yang berubah
+ updateForm(name, value);
+
+ // Jika checkbox aktif, sinkronisasi alamat_wajib_pajak dengan alamat_bisnis
+ if (isChekBox) {
+ if (name === 'alamat_wajib_pajak') {
+ updateForm('alamat_bisnis', value);
+ } else if (name === 'alamat_bisnis') {
+ updateForm('alamat_wajib_pajak', value);
+ }
+ }
+
+ // Validasi setelah perubahan dilakukan
+ validate();
+ };
+
+ const handleInputChangeNpwp = (event: ChangeEvent<HTMLInputElement>) => {
+ const { name, value } = event.target;
+ updateForm('type_acc', `business`);
+ updateForm('is_pkp', `${isPKP}`);
+ updateForm('npwp', value);
+ validate();
+ };
+
+ const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
+ setIsChekBox(!isChekBox);
+ };
+
+ const formatNpwp = (value: string) => {
+ try {
+ const cleaned = ('' + value).replace(/\D/g, '');
+ let match;
+ if (cleaned.length <= 15) {
+ match = cleaned.match(
+ /(\d{0,2})?(\d{0,3})?(\d{0,3})?(\d{0,1})?(\d{0,3})?(\d{0,3})$/
+ );
+ } else {
+ match = cleaned.match(
+ /(\d{0,3})?(\d{0,3})?(\d{0,3})?(\d{0,1})?(\d{0,3})?(\d{0,3})$/
+ );
+ }
+
+ if (match) {
+ return [
+ match[1],
+ match[2] ? '.' : '',
+ match[2],
+ match[3] ? '.' : '',
+ match[3],
+ match[4] ? '.' : '',
+ match[4],
+ match[5] ? '-' : '',
+ match[5],
+ match[6] ? '.' : '',
+ match[6],
+ ].join('');
+ }
+
+ // If match is null, return the original cleaned string or handle as needed
+ return cleaned;
+ } catch (error) {
+ // Handle error or return a default value
+ console.error('Error formatting NPWP:', error);
+ return value;
+ }
+ };
+
+ useEffect(() => {
+ if (isChekBox) {
+ updateForm('isChekBox', 'true');
+ updateForm('alamat_wajib_pajak', `${form.alamat_bisnis}`);
+ validate();
+ } else {
+ updateForm('isChekBox', 'false');
+ validate();
+ }
+ }, [isChekBox]);
+
+ const handleFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
+ const toastProps: UseToastOptions = {
+ duration: 5000,
+ isClosable: true,
+ position: 'top',
+ };
+
+ let fileBase64 = '';
+ const { name } = event.target;
+ const file = event.target.files?.[0];
+
+ // Allowed file extensions
+ const allowedExtensions = ['pdf', 'doc', 'docx', 'png', 'jpg', 'jpeg'];
+
+ if (file) {
+ const fileExtension = file.name.split('.').pop()?.toLowerCase(); // Extract file extension
+
+ // Check if the file extension is allowed
+ if (!fileExtension || !allowedExtensions.includes(fileExtension)) {
+ toast({
+ ...toastProps,
+ title:
+ 'Format file yang diijinkan adalah .pdf, .doc, .docx, .png, .jpg, atau .jpeg',
+ status: 'error',
+ });
+ event.target.value = '';
+ return;
+ }
+
+ // Check for file size
+ if (file.size > 5000000) {
+ toast({
+ ...toastProps,
+ title: 'Maksimal ukuran file adalah 5MB',
+ status: 'error',
+ });
+ event.target.value = '';
+ return;
+ }
+
+ // Convert file to Base64
+ fileBase64 = await getFileBase64(file);
+ updateForm(name, fileBase64); // Update form with the Base64 string
+ validate(); // Perform form validation
+ }
+ };
+ const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors]);
+
+ useEffect(() => {
+ const loadIndustries = async () => {
+ if (!isFormValid) {
+ const options: ScrollIntoViewOptions = {
+ behavior: 'smooth',
+ block: 'center',
+ };
+ if (errors.email_partner && emailRef.current) {
+ emailRef.current.scrollIntoView(options);
+ return;
+ }
+ if (errors.company_type_id && companyTypeRef.current) {
+ companyTypeRef.current.scrollIntoView(options);
+ return;
+ }
+
+ if (errors.business_name && businessNameRef.current) {
+ businessNameRef.current.scrollIntoView(options);
+ return;
+ }
+
+ if (errors.industry_id && industryRef.current) {
+ industryRef.current.scrollIntoView(options);
+ return;
+ }
+
+ if (errors.alamat_bisnis && addressRef.current) {
+ addressRef.current.scrollIntoView(options);
+ return;
+ }
+
+ if (errors.npwp && npwpRef.current) {
+ npwpRef.current.scrollIntoView(options);
+ return;
+ }
+
+ if (errors.sppkp && sppkpRef.current) {
+ sppkpRef.current.scrollIntoView(options);
+ return;
+ }
+ if (errors.sppkp_document && docsSppkpRef.current) {
+ docsSppkpRef.current.scrollIntoView(options);
+ return;
+ }
+ if (errors.npwp_document && docsNpwpRef.current) {
+ docsNpwpRef.current.scrollIntoView(options);
+ return;
+ }
+ }
+ };
+ loadIndustries();
+ }, [buttonSubmitClick, chekValid]);
+ return (
+ <>
+ <BottomPopup
+ className=''
+ title='Contoh SPPKP'
+ active={isExample}
+ close={() => setIsExample(false)}
+ >
+ <div className='flex p-2'>
+ <Image
+ src='/images/NO-SPPKP-FORMAT-TEMPLATE.jpg'
+ alt='Contoh SPPKP'
+ className='w-full h-full '
+ width={800}
+ height={800}
+ quality={85}
+ />
+ </div>
+ </BottomPopup>
+ <form
+ className={` ${
+ type === 'bisnis'
+ ? 'mt-6 grid grid-cols-1 gap-y-4'
+ : 'mt-6 grid grid-cols-2 gap-x-4 gap-y-2'
+ }`}
+ >
+ <div>
+ <label htmlFor='email' className='font-bold'>
+ Email Bisnis{' '}
+ {!isPKP && !required && (
+ <span className='font-normal text-gray_r-11'>(opsional)</span>
+ )}
+ </label>
+
+ <input
+ type='text'
+ id='email_partner'
+ name='email_partner'
+ placeholder='example@email.com'
+ value={!required ? form.email_partner : ''}
+ className={`form-input max-h-11 mt-3 transition-all duration-500 ${
+ required ? 'cursor-no-drop' : ''
+ }`}
+ disabled={required}
+ contentEditable={required}
+ readOnly={required}
+ onChange={handleInputChange}
+ autoComplete='username'
+ ref={emailRef}
+ aria-invalid={
+ chekValid && !required && isPKP && !!errors.email_partner
+ }
+ />
+
+ {chekValid && !required && isPKP && !!errors.email_partner && (
+ <span className='form-msg-danger'>{errors.email_partner}</span>
+ )}
+ </div>
+
+ <div>
+ <label className='font-bold' htmlFor='company'>
+ Nama Bisnis
+ </label>
+ <div className='flex justify-between items-start gap-2 max-h-12 min-h-12 text-sm mt-3'>
+ <div
+ className='w-4/5 pr-1 max-h-11 transition-all duration-500'
+ ref={companyTypeRef}
+ >
+ <Controller
+ name='companyType'
+ control={control}
+ render={(props) => (
+ <HookFormSelect
+ {...props}
+ options={companyTypes}
+ disabled={required}
+ placeholder='Badan Usaha'
+ />
+ )}
+ />
+ {chekValid && !required && !!errors.company_type_id && (
+ <span className='form-msg-danger'>
+ {errors.company_type_id}
+ </span>
+ )}
+ </div>
+ <div className='w-[120%] '>
+ <input
+ type='text'
+ name='business_name'
+ id='business_name'
+ className='form-input max-h-11 transition-all duration-500'
+ placeholder='Nama Perusahaan'
+ autoCapitalize='true'
+ value={form.business_name}
+ ref={businessNameRef}
+ aria-invalid={chekValid && !!errors.business_name}
+ onChange={handleInputChange}
+ />
+
+ {chekValid && !!errors.business_name && (
+ <span className='form-msg-danger'>{errors.business_name}</span>
+ )}
+ </div>
+ </div>
+ </div>
+
+ <div className='mt-8 md:mt-0'>
+ <label className='font-bold' htmlFor='business_name'>
+ Klasifikasi Jenis Usaha
+ </label>
+ <div
+ className='max-h-10 transition-all duration-500'
+ ref={industryRef}
+ >
+ <Controller
+ name='industry_id'
+ control={control}
+ render={(props) => (
+ <HookFormSelect
+ {...props}
+ options={industries}
+ disabled={required}
+ placeholder={'Select industry'}
+ />
+ )}
+ />
+ </div>
+ {selectedCategory && (
+ <span className='text-gray_r-11 text-xs'>
+ Kategori : {selectedCategory}
+ </span>
+ )}
+ {chekValid && !required && !!errors.industry_id && (
+ <span className='form-msg-danger'>{errors.industry_id}</span>
+ )}
+ </div>
+
+ <div>
+ <label htmlFor='alamat_bisnis' className='font-bold'>
+ Alamat Bisnis
+ </label>
+
+ <input
+ type='text'
+ id='alamat_bisnis'
+ name='alamat_bisnis'
+ placeholder='Masukan alamat bisnis anda'
+ value={!required ? form.alamat_bisnis : ''}
+ className={`form-input mt-3 max-h-11 transition-all duration-500 ${
+ required ? 'cursor-no-drop' : ''
+ }`}
+ disabled={required}
+ contentEditable={required}
+ readOnly={required}
+ ref={addressRef}
+ onChange={handleInputChange}
+ aria-invalid={chekValid && !required && !!errors.alamat_bisnis}
+ />
+
+ {chekValid && !required && !!errors.alamat_bisnis && (
+ <span className='form-msg-danger'>{errors.alamat_bisnis}</span>
+ )}
+ </div>
+
+ <div>
+ <label htmlFor='nama_wajib_pajak' className='font-bold'>
+ Nama Wajib Pajak{' '}
+ {!isPKP && !required && (
+ <span className='font-normal text-gray_r-11'>(opsional)</span>
+ )}
+ </label>
+
+ <input
+ type='text'
+ id='nama_wajib_pajak'
+ name='nama_wajib_pajak'
+ placeholder='Masukan nama lengkap anda'
+ value={!required ? form.nama_wajib_pajak : ''}
+ className={`form-input mt-3 max-h-11 transition-all duration-500${
+ required ? 'cursor-no-drop' : ''
+ }`}
+ disabled={required}
+ contentEditable={required}
+ readOnly={required}
+ onChange={handleInputChange}
+ ref={namaWajibPajakRef}
+ aria-invalid={
+ chekValid && isPKP && !required && !!errors.nama_wajib_pajak
+ }
+ />
+
+ {chekValid && isPKP && !required && !!errors.nama_wajib_pajak && (
+ <span className='form-msg-danger'>{errors.nama_wajib_pajak}</span>
+ )}
+ </div>
+
+ <div>
+ <label
+ htmlFor='alamat_wajib_pajak'
+ className='font-bold flex items-center'
+ >
+ <p>
+ Alamat Wajib Pajak{' '}
+ {!isPKP && !required && (
+ <span className='font-normal text-gray_r-11'>(opsional)</span>
+ )}
+ </p>
+ <div className='flex items-center ml-2 mt-1 '>
+ <Checkbox
+ borderColor='gray.600'
+ colorScheme='red'
+ size='md'
+ isChecked={isChekBox}
+ onChange={handleChange}
+ />
+ <span className='text-caption-2 ml-2 font-normal italic'>
+ sama dengan alamat bisnis?
+ </span>
+ </div>
+ </label>
+
+ <input
+ type='text'
+ id='alamat_wajib_pajak'
+ name='alamat_wajib_pajak'
+ placeholder='Masukan alamat wajib pajak anda'
+ value={
+ !required
+ ? isChekBox
+ ? form.alamat_bisnis
+ : form.alamat_wajib_pajak
+ : ''
+ }
+ className={`form-input max-h-11 mt-3 transition-all duration-500 ${
+ required ? 'cursor-no-drop' : ''
+ }`}
+ disabled={isChekBox || required}
+ contentEditable={required}
+ readOnly={required}
+ onChange={handleInputChange}
+ ref={alamatWajibPajakRef}
+ aria-invalid={
+ chekValid && isPKP && !required && !!errors.alamat_wajib_pajak
+ }
+ />
+
+ {chekValid && isPKP && !required && !!errors.alamat_wajib_pajak && (
+ <span className='form-msg-danger'>{errors.alamat_wajib_pajak}</span>
+ )}
+ </div>
+
+ <div>
+ <label htmlFor='npwp' className='font-bold'>
+ Nomor NPWP{' '}
+ {!isPKP && !required && (
+ <span className='font-normal text-gray_r-11'>(opsional)</span>
+ )}
+ </label>
+
+ <input
+ type='tel'
+ id='npwp'
+ name='npwp'
+ className={`form-input max-h-11 mt-3 transition-all duration-500 ${
+ required ? 'cursor-no-drop' : ''
+ }`}
+ disabled={required}
+ contentEditable={required}
+ readOnly={required}
+ ref={npwpRef}
+ placeholder='000.000.000.0-000.000'
+ value={!required ? formattedNpwp : ''}
+ maxLength={21} // Set maximum length to 16 characters
+ onChange={(e) => {
+ if (!required) {
+ const unformatted = e.target.value.replace(/\D/g, ''); // Remove all non-digit characters
+ const formattedValue = formatNpwp(unformatted); // Format the value
+ setUnformattedNpwp(unformatted); // Store unformatted value
+ setFormattedNpwp(formattedValue); // Store formatted value
+ handleInputChangeNpwp({
+ ...e,
+ target: { ...e.target, value: unformatted },
+ }); // Update form state with unformatted value
+ }
+ }}
+ aria-invalid={chekValid && !required && !!errors.npwp}
+ />
+
+ {chekValid && !required && !!errors.npwp && (
+ <span className='form-msg-danger'>{errors.npwp}</span>
+ )}
+ </div>
+
+ <div>
+ <label
+ htmlFor='sppkp'
+ className='font-bold flex flex-row items-center justify-between'
+ >
+ <div className='flex flex-row items-center'>
+ Nomor SPPKP{' '}
+ {!required && (
+ <span className='ml-2 font-normal text-gray_r-11'>
+ (opsional){' '}
+ </span>
+ )}
+ </div>
+ {
+ <div
+ onClick={() => setIsExample(!isExample)}
+ className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400'
+ >
+ <EyeIcon className={`w-4 ${isDesktop && 'mr-2'}`} />
+ {isDesktop && (
+ <p className='font-light text-xs'>Lihat Contoh</p>
+ )}
+ </div>
+ }
+ </label>
+
+ <input
+ type='tel'
+ id='sppkp'
+ name='sppkp'
+ className={`form-input max-h-11 mt-3 transition-all duration-500 ${
+ required ? 'cursor-no-drop' : ''
+ }`}
+ disabled={required}
+ contentEditable={required}
+ readOnly={required}
+ ref={sppkpRef}
+ placeholder='X-XXXPKP/WJPXXX/XX.XXXX/XXXX'
+ onChange={handleInputChange}
+ value={!required ? form.sppkp : ''}
+ aria-invalid={chekValid && !required && !!errors.sppkp}
+ />
+
+ {chekValid && !required && !!errors.sppkp && (
+ <span className='form-msg-danger'>{errors.sppkp}</span>
+ )}
+ </div>
+
+ <div>
+ <label htmlFor='npwp_document' className='font-bold'>
+ Dokumen NPWP{' '}
+ {!isPKP && !required && (
+ <span className='font-normal text-gray_r-11'>(opsional)</span>
+ )}
+ </label>
+
+ <input
+ type='file'
+ id='npwp_document'
+ name='npwp_document'
+ className={`form-input transition-all duration-500 ${
+ type === 'bisnis' ? '' : 'border-none'
+ } mt-3 ${required ? 'cursor-no-drop' : ''}`}
+ disabled={required}
+ contentEditable={required}
+ ref={docsNpwpRef}
+ readOnly={required}
+ onChange={handleFileChange}
+ accept='.pdf,.doc,.docx,.png,.jpg,.jpeg' // Filter file types
+ />
+
+ {chekValid && isPKP && !required && !!errors.npwp_document && (
+ <span className='form-msg-danger'>{errors.npwp_document}</span>
+ )}
+ </div>
+
+ <div>
+ <label htmlFor='sppkp_document' className='font-bold'>
+ Dokumen SPPKP{' '}
+ {!isPKP && !required && (
+ <span className='font-normal text-gray_r-11'>(opsional)</span>
+ )}
+ </label>
+
+ <input
+ type='file'
+ id='sppkp_document'
+ name='sppkp_document'
+ className={`form-input transition-all duration-500 ${
+ type === 'bisnis' ? '' : 'border-none'
+ } mt-3 ${required ? 'cursor-no-drop' : ''}`}
+ disabled={required}
+ contentEditable={required}
+ ref={docsSppkpRef}
+ readOnly={required}
+ onChange={handleFileChange}
+ accept='.pdf,.doc,.docx,.png,.jpg,.jpeg' // Filter file types
+ />
+
+ {chekValid && isPKP && !required && !!errors.sppkp_document && (
+ <span className='form-msg-danger'>{errors.sppkp_document}</span>
+ )}
+ </div>
+ </form>
+ </>
+ );
+};
+
+export default form;
diff --git a/src-migrate/modules/register/components/RegistrasiBisnis.tsx b/src-migrate/modules/register/components/RegistrasiBisnis.tsx
new file mode 100644
index 00000000..ce4d3972
--- /dev/null
+++ b/src-migrate/modules/register/components/RegistrasiBisnis.tsx
@@ -0,0 +1,197 @@
+import { ChangeEvent, useEffect, useMemo, useState } from 'react';
+import FormBisnis from './FormBisnis';
+import Form from './Form';
+import TermCondition from './TermCondition';
+import FormCaptcha from './FormCaptcha';
+import { Radio, RadioGroup, Stack, Divider, Button } from '@chakra-ui/react';
+import React from 'react';
+import { ChevronDownIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
+import { useRegisterStore } from '../stores/useRegisterStore';
+import { useMutation } from 'react-query';
+import { RegisterProps } from '~/types/auth';
+import { registerUser } from '~/services/auth';
+import router from 'next/router';
+import { useRouter } from 'next/router';
+import { UseToastOptions, useToast } from '@chakra-ui/react';
+import Link from 'next/link';
+interface FormProps {
+ chekValid: boolean;
+ buttonSubmitClick: boolean;
+}
+const RegistrasiBisnis: React.FC<FormProps> = ({
+ chekValid,
+ buttonSubmitClick,
+}) => {
+ const [isPKP, setIsPKP] = useState(true);
+ const [isTerdaftar, setIsTerdaftar] = useState(false);
+ const [isDropIndividu, setIsDropIndividu] = useState(true);
+ const [isBisnisClicked, setisBisnisClicked] = useState(true);
+ const [selectedValue, setSelectedValue] = useState('PKP');
+ const [selectedValueBisnis, setSelectedValueBisnis] = useState('false');
+ const { form, isCheckedTNC, isValidCaptcha, errors, validate, updateForm } =
+ useRegisterStore();
+ const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors]);
+ const toast = useToast();
+ const mutation = useMutation({
+ mutationFn: (data: RegisterProps) => registerUser(data),
+ });
+
+ useEffect(() => {
+ if (selectedValue === 'PKP') {
+ updateForm('is_pkp', 'true');
+ validate();
+ } else {
+ updateForm('is_pkp', 'false');
+ validate();
+ }
+ }, [selectedValue]);
+
+ useEffect(() => {
+ if (isTerdaftar) {
+ updateForm('is_terdaftar', 'true');
+ validate();
+ } else {
+ updateForm('is_terdaftar', 'false');
+ validate();
+ }
+ }, [isTerdaftar]);
+
+ const handleChange = (value: string) => {
+ setSelectedValue(value);
+ if (value === 'PKP') {
+ validate();
+ setIsPKP(true);
+ } else {
+ validate();
+ setIsPKP(false);
+ setIsPKP(false);
+ }
+ };
+
+ const handleChangeBisnis = (value: string) => {
+ setSelectedValueBisnis(value);
+ if (value === 'true') {
+ validate();
+ setIsTerdaftar(true);
+ } else {
+ validate();
+ setIsTerdaftar(false);
+ }
+ };
+
+ const handleClick = () => {
+ setIsDropIndividu(!isDropIndividu);
+ };
+
+ const handleClickBisnis = () => {
+ setisBisnisClicked(!isBisnisClicked);
+ };
+
+ return (
+ <>
+ <div className='mt-4 border'>
+ <div className='p-4'>
+ <div onClick={handleClick} className='flex justify-between'>
+ <p className='text-2xl font-semibold text-center md:text-left'>
+ Data Akun
+ </p>
+ {isDropIndividu ? (
+ <div className='flex'>
+ <ChevronDownIcon
+ onClick={handleClick}
+ className='h-6 w-6 text-black'
+ />
+ </div>
+ ) : (
+ <ChevronRightIcon
+ onClick={handleClick}
+ className='h-6 w-6 text-black'
+ />
+ )}
+ </div>
+ {isDropIndividu && (
+ <div>
+ <Divider my={4} />
+ <Form
+ type='bisnis'
+ required={true}
+ isBisnisRegist={true}
+ chekValid={chekValid}
+ buttonSubmitClick={buttonSubmitClick}
+ />
+ </div>
+ )}
+ </div>
+ </div>
+ <div className='mt-4 border'>
+ <div className='p-4'>
+ <div onClick={handleClickBisnis} className='flex justify-between'>
+ <p className='text-2xl font-semibold text-center md:text-left'>
+ Data Bisnis
+ </p>
+ {isBisnisClicked ? (
+ <div className='flex'>
+ <ChevronDownIcon
+ onClick={handleClickBisnis}
+ className='h-6 w-6 text-black'
+ />
+ </div>
+ ) : (
+ <ChevronRightIcon
+ onClick={handleClickBisnis}
+ className='h-6 w-6 text-black'
+ />
+ )}
+ </div>
+ {isBisnisClicked && (
+ <div>
+ <Divider my={4} />
+ <div>
+ <p className='text-black font-bold mb-2'>
+ Bisnis Terdaftar di Indoteknik?
+ </p>
+ <RadioGroup
+ onChange={handleChangeBisnis}
+ value={selectedValueBisnis}
+ >
+ <Stack direction='row'>
+ <Radio colorScheme='red' value='true'>
+ Sudah Terdaftar
+ </Radio>
+ <Radio colorScheme='red' value='false' className='ml-2'>
+ Belum Terdaftar
+ </Radio>
+ </Stack>
+ </RadioGroup>
+ </div>
+ <div className='mt-4'>
+ <p className='text-black font-bold mb-2'>Tipe Bisnis</p>
+ <RadioGroup onChange={handleChange} value={selectedValue}>
+ <Stack direction='row' className='font-bold'>
+ <Radio colorScheme='red' value='PKP'>
+ PKP
+ </Radio>
+ <Radio colorScheme='red' value='Non-PKP' className='ml-4'>
+ Non-PKP
+ </Radio>
+ </Stack>
+ </RadioGroup>
+ </div>
+ <FormBisnis
+ type='bisnis'
+ required={isTerdaftar}
+ isPKP={isPKP}
+ chekValid={chekValid}
+ buttonSubmitClick={buttonSubmitClick}
+ />
+ </div>
+ )}
+ </div>
+ </div>
+
+ <h1 className=''></h1>
+ </>
+ );
+};
+
+export default RegistrasiBisnis;
diff --git a/src-migrate/modules/register/components/RegistrasiIndividu.tsx b/src-migrate/modules/register/components/RegistrasiIndividu.tsx
new file mode 100644
index 00000000..84049065
--- /dev/null
+++ b/src-migrate/modules/register/components/RegistrasiIndividu.tsx
@@ -0,0 +1,34 @@
+import Form from './Form';
+import { useRegisterStore } from '../stores/useRegisterStore';
+import { useEffect } from 'react';
+interface FormProps {
+ chekValid: boolean;
+ buttonSubmitClick: boolean;
+}
+const RegistrasiIndividu: React.FC<FormProps> = ({
+ chekValid,
+ buttonSubmitClick,
+}) => {
+ const { form, errors, updateForm, validate } = useRegisterStore();
+
+ useEffect(() => {
+ updateForm('is_pkp', 'false');
+ updateForm('is_terdaftar', 'false');
+ updateForm('type_acc', 'individu');
+ validate();
+ }, []);
+
+ return (
+ <>
+ <Form
+ type='individu'
+ required={false}
+ isBisnisRegist={false}
+ chekValid={chekValid}
+ buttonSubmitClick={buttonSubmitClick}
+ />
+ </>
+ );
+};
+
+export default RegistrasiIndividu;
diff --git a/src-migrate/modules/register/components/TermCondition.tsx b/src-migrate/modules/register/components/TermCondition.tsx
index b7729deb..d54fe921 100644
--- a/src-migrate/modules/register/components/TermCondition.tsx
+++ b/src-migrate/modules/register/components/TermCondition.tsx
@@ -10,7 +10,7 @@ const TermCondition = () => {
return (
<>
<div className="mt-4 flex items-center gap-x-2">
- <Checkbox id='tnc' name='tnc' isChecked={isCheckedTNC} onChange={toggleCheckTNC} />
+ <Checkbox id='tnc' name='tnc' colorScheme='red' isChecked={isCheckedTNC} onChange={toggleCheckTNC} />
<div>
<label htmlFor="tnc" className="cursor-pointer">Dengan ini saya menyetujui</label>
{' '}
diff --git a/src-migrate/modules/register/index.tsx b/src-migrate/modules/register/index.tsx
index 00931284..da41fd8b 100644
--- a/src-migrate/modules/register/index.tsx
+++ b/src-migrate/modules/register/index.tsx
@@ -1,54 +1,212 @@
-import PageContent from "~/modules/page-content"
-import Form from "./components/Form"
-import Link from "next/link"
-import Image from "next/image"
-import IndoteknikLogo from "~/images/logo.png"
-import AccountActivation from "../account-activation"
+import PageContent from '~/modules/page-content';
+import RegistrasiIndividu from './components/RegistrasiIndividu';
+import RegistrasiBisnis from './components/RegistrasiBisnis';
+import Link from 'next/link';
+import Image from 'next/image';
+import IndoteknikLogo from '~/images/logo.png';
+import AccountActivation from '../account-activation';
+import { useMemo, useState } from 'react';
+import { useRegisterStore } from './stores/useRegisterStore';
+import FormCaptcha from './components/FormCaptcha';
+import TermCondition from './components/TermCondition';
+import { Button } from '@chakra-ui/react';
+import { useMutation } from 'react-query';
+import { UseToastOptions, useToast } from '@chakra-ui/react';
+import { RegisterProps } from '~/types/auth';
+import { registerUser } from '~/services/auth';
+import { useRouter } from 'next/router';
const LOGO_WIDTH = 150;
const LOGO_HEIGHT = LOGO_WIDTH / 3;
const Register = () => {
+ const [isIndividuClicked, setIsIndividuClicked] = useState(true);
+ const [notValid, setNotValid] = useState(false);
+ const [buttonSubmitClick, setButtonSubmitClick] = useState(false);
+ const [isBisnisClicked, setIsBisnisClicked] = useState(false);
+ const { form, isCheckedTNC, isValidCaptcha, resetForm, errors, updateForm } =
+ useRegisterStore();
+
+ const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors]);
+ const toast = useToast();
+ const router = useRouter();
+ const mutation = useMutation({
+ mutationFn: (data: RegisterProps) => registerUser(data),
+ });
+
+ const handleIndividuClick = () => {
+ resetForm();
+ setIsIndividuClicked(true);
+ setIsBisnisClicked(false);
+ };
+
+ const handleBisnisClick = () => {
+ resetForm();
+ updateForm('is_terdaftar', 'false');
+ updateForm('type_acc', 'business');
+ setIsIndividuClicked(false);
+ setIsBisnisClicked(true);
+ };
+ const handleSubmit = async () => {
+ if (!isFormValid) {
+ setNotValid(true);
+ setButtonSubmitClick(!buttonSubmitClick);
+ return;
+ } else {
+ setButtonSubmitClick(!buttonSubmitClick);
+ setNotValid(false);
+ }
+ const response = await mutation.mutateAsync(form);
+ if (response?.register === true) {
+ const urlParams = new URLSearchParams({
+ activation: 'otp',
+ email: form.email,
+ redirect: (router.query?.next || '/') as string,
+ });
+ router.push(`${router.route}?${urlParams}`);
+ }
+
+ const toastProps: UseToastOptions = {
+ duration: 5000,
+ isClosable: true,
+ position: 'top',
+ };
+
+ switch (response?.reason) {
+ case 'EMAIL_USED':
+ toast({
+ ...toastProps,
+ title: 'Email sudah digunakan',
+ status: 'warning',
+ });
+ break;
+ case 'NOT_ACTIVE':
+ const activationUrl = `${router.route}?activation=email`;
+ toast({
+ ...toastProps,
+ title: 'Akun belum aktif',
+ description: (
+ <>
+ Akun sudah terdaftar namun belum aktif.{' '}
+ <Link href={activationUrl} className='underline'>
+ Klik untuk aktivasi akun
+ </Link>
+ </>
+ ),
+ status: 'warning',
+ });
+ break;
+ }
+ };
return (
- <div className="container">
- <div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 pt-10 px-2 md:pt-16">
- <section>
- <Link href='/' className="block md:hidden">
- <Image src={IndoteknikLogo} alt='Logo Indoteknik' width={LOGO_WIDTH} height={LOGO_HEIGHT} className="mx-auto mb-4 w-auto h-auto" priority />
- </Link>
-
- <h1 className="text-2xl font-semibold text-center md:text-left">
- Daftar Akun Indoteknik
- </h1>
- <h2 className="text-gray_r-11 mt-1 mb-10 text-center md:text-left">
- Buat akun sekarang lebih mudah dan terverifikasi
- </h2>
-
- <Form />
-
- <div className='text-gray_r-11 mt-4 text-center md:text-left'>
- Sudah punya akun Indoteknik?{' '}
- <Link href='/login' className='inline font-medium text-danger-500'>
- Masuk
+ <div className='container'>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-x-8 pt-10 px-2 md:pt-16'>
+ <section className=''>
+ <div className='px-8 py-4 border'>
+ <Link href='/' className='block md:hidden'>
+ <Image
+ src={IndoteknikLogo}
+ alt='Logo Indoteknik'
+ width={LOGO_WIDTH}
+ height={LOGO_HEIGHT}
+ className='mx-auto mb-4 w-auto h-auto'
+ priority
+ />
</Link>
- </div>
- <div className='text-gray_r-11 mt-4 text-center md:text-left'>
- Akun anda belum aktif?{' '}
- <Link href='/register?activation=email' className='inline font-medium text-danger-500'>
- Aktivasi
- </Link>
+ <h1 className='text-2xl font-semibold text-center md:text-left'>
+ Daftar Akun Indoteknik
+ </h1>
+ <h2 className='text-gray_r-11 mt-1 mb-4 text-center md:text-left'>
+ Buat akun sekarang lebih mudah dan terverifikasi
+ </h2>
+
+ <label htmlFor='name' className='text-black font-bold'>
+ Tipe Akun
+ </label>
+ <div className='grid grid-cols-2 gap-x-3 mt-2 h-14 font-bold text-black hover:cursor-pointer'>
+ <div
+ className={` border rounded-md flex justify-center items-center transition-colors duration-300 ease-in-out ${
+ isIndividuClicked ? 'bg-red-500 text-white' : ''
+ }`}
+ onClick={handleIndividuClick}
+ >
+ <p>Individu</p>
+ </div>
+ <div
+ className={` border rounded-md flex justify-center items-center transition-colors duration-300 ease-in-out ${
+ isBisnisClicked ? 'bg-red-500 text-white' : ''
+ }`}
+ onClick={handleBisnisClick}
+ >
+ <p>Bisnis</p>
+ </div>
+ </div>
+ <div className='transition-opacity duration-300 ease-in-out'>
+ {isIndividuClicked && (
+ <div className='opacity-100'>
+ <RegistrasiIndividu
+ chekValid={notValid}
+ buttonSubmitClick={buttonSubmitClick}
+ />
+ </div>
+ )}
+ {isBisnisClicked && (
+ <div className='opacity-100'>
+ <RegistrasiBisnis
+ chekValid={notValid}
+ buttonSubmitClick={buttonSubmitClick}
+ />
+ </div>
+ )}
+ </div>
+ <section className='mt-2'>
+ <FormCaptcha />
+ <TermCondition />
+ <Button
+ type='submit'
+ colorScheme='red'
+ className='w-full mt-2'
+ size='lg'
+ onClick={handleSubmit}
+ isDisabled={
+ !isCheckedTNC || mutation.isLoading || !isValidCaptcha
+ }
+ >
+ {mutation.isLoading ? 'Loading...' : 'Daftar'}
+ </Button>
+ </section>
+ <section className='flex justify-center items-center flex-col'>
+ <div className='text-gray_r-11 mt-4 text-center md:text-left'>
+ Sudah punya akun Indoteknik?{' '}
+ <Link
+ href='/login'
+ className='inline font-medium text-danger-500'
+ >
+ Masuk
+ </Link>
+ </div>
+ <div className='text-gray_r-11 mt-4 text-center md:text-left'>
+ Akun anda belum aktif?{' '}
+ <Link
+ href='/register?activation=email'
+ className='inline font-medium text-danger-500'
+ >
+ Aktivasi
+ </Link>
+ </div>
+ </section>
</div>
</section>
- <section className="my-10 md:my-0">
- <PageContent path="/register" />
+ <section className='my-10 md:my-0'>
+ <PageContent path='/register' />
</section>
</div>
<AccountActivation />
</div>
- )
-}
+ );
+};
-export default Register \ No newline at end of file
+export default Register;
diff --git a/src-migrate/modules/register/stores/useRegisterStore.ts b/src-migrate/modules/register/stores/useRegisterStore.ts
index d8abf52b..273472be 100644
--- a/src-migrate/modules/register/stores/useRegisterStore.ts
+++ b/src-migrate/modules/register/stores/useRegisterStore.ts
@@ -1,7 +1,7 @@
import { create } from 'zustand';
import { RegisterProps } from '~/types/auth';
import { registerSchema } from '~/validations/auth';
-import { ZodError } from 'zod';
+import { boolean, ZodError } from 'zod';
type State = {
form: RegisterProps;
@@ -20,18 +20,33 @@ type Action = {
openTNC: () => void;
closeTNC: () => void;
validate: () => void;
+ resetForm: () => void;
};
export const useRegisterStore = create<State & Action>((set, get) => ({
form: {
- company: '',
+ company_type_id: '',
+ business_name: '',
name: '',
+ nama_wajib_pajak : '',
email: '',
+ email_partner: '',
password: '',
phone: '',
+ sppkp_document: '',
+ npwp_document: '',
+ industry_id: '',
+ npwp: '',
+ sppkp: '',
+ is_pkp: '',
+ type_acc:'',
+ is_terdaftar:'',
+ alamat_bisnis:'',
+ alamat_wajib_pajak:'',
},
updateForm: (name, value) =>
set((state) => ({ form: { ...state.form, [name]: value } })),
+
errors: {},
validate: () => {
@@ -48,6 +63,7 @@ export const useRegisterStore = create<State & Action>((set, get) => ({
}
}
},
+
isCheckedTNC: false,
toggleCheckTNC: () => set((state) => ({ isCheckedTNC: !state.isCheckedTNC })),
@@ -57,4 +73,27 @@ export const useRegisterStore = create<State & Action>((set, get) => ({
isValidCaptcha: false,
updateValidCaptcha: (value) => set(() => ({ isValidCaptcha: value })),
+
+ resetForm: () => set({
+ form: {
+ company_type_id: '',
+ business_name: '',
+ name: '',
+ nama_wajib_pajak : '',
+ email: '',
+ email_partner: '',
+ password: '',
+ phone: '',
+ sppkp_document: '',
+ npwp_document: '',
+ industry_id: '',
+ npwp: '',
+ sppkp: '',
+ is_pkp: '',
+ type_acc:'',
+ is_terdaftar:'',
+ alamat_bisnis:'',
+ alamat_wajib_pajak:'',
+ },
+ }),
}));
diff --git a/src-migrate/pages/api/product-variant/[id].tsx b/src-migrate/pages/api/product-variant/[id].tsx
index 955fde6a..2c46ac89 100644
--- a/src-migrate/pages/api/product-variant/[id].tsx
+++ b/src-migrate/pages/api/product-variant/[id].tsx
@@ -38,6 +38,7 @@ const map = async (variant: any, price_tier: string) => {
data.name = variant.name_s
data.default_code = variant.default_code_s
data.price = { discount_percentage: 0, price, price_discount: price }
+ data.manufacture = {manufacture_name: variant.manufacture_name_s, manufacture_id:variant.manufacture_id_i}
return data
}
diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index 5e3e042a..c5386c91 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -14,10 +14,10 @@ import clsxm from '~/libs/clsxm';
import useDevice from '@/core/hooks/useDevice';
import CartSummaryMobile from '~/modules/cart/components/CartSummaryMobile';
import Image from '~/components/ui/image';
-import { CartItem } from '~/types/cart'
-import { deleteUserCart ,upsertUserCart } from '~/services/cart'
+import { CartItem } from '~/types/cart';
+import { deleteUserCart, upsertUserCart } from '~/services/cart';
import { Trash2Icon } from 'lucide-react';
-import { useProductCartContext } from '@/contexts/ProductCartContext'
+import { useProductCartContext } from '@/contexts/ProductCartContext';
const CartPage = () => {
const router = useRouter();
@@ -26,11 +26,11 @@ const CartPage = () => {
const [isSelectedAll, setIsSelectedAll] = useState(false);
const [isButtonChek, setIsButtonChek] = useState(false);
const [buttonSelectNow, setButtonSelectNow] = useState(true);
- const [isLoad, setIsLoad] = useState<boolean>(false)
- const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false)
+ const [isLoad, setIsLoad] = useState<boolean>(false);
+ const [isLoadDelete, setIsLoadDelete] = useState<boolean>(false);
const { loadCart, cart, summary, updateCartItem } = useCartStore();
const useDivvice = useDevice();
- const { setRefreshCart } = useProductCartContext()
+ const { setRefreshCart } = useProductCartContext();
const [isTop, setIsTop] = useState(true);
const [hasChanged, setHasChanged] = useState(false);
const prevCartRef = useRef<CartItem[] | null>(null);
@@ -64,18 +64,19 @@ const CartPage = () => {
const hasSelectedChanged = () => {
if (prevCartRef.current && cart) {
const prevCart = prevCartRef.current;
- return cart.products.some((item, index) =>
- prevCart[index] && prevCart[index].selected !== item.selected
+ return cart.products.some(
+ (item, index) =>
+ prevCart[index] && prevCart[index].selected !== item.selected
);
}
return false;
};
if (hasSelectedChanged()) {
- setHasChanged(true)
+ setHasChanged(true);
// Perform necessary actions here if selection has changed
- }else{
- setHasChanged(false)
+ } else {
+ setHasChanged(false);
}
prevCartRef.current = cart ? [...cart.products] : null;
@@ -83,35 +84,38 @@ const CartPage = () => {
const hasSelectedPromo = useMemo(() => {
if (!cart) return false;
- return cart.products.some(item => item.cart_type === 'promotion' && item.selected);
+ 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(item => item.selected && item.price.price_discount === 0);
+ return cart.products.some(
+ (item) => item.selected && item.price.price_discount === 0
+ );
}, [cart]);
const hasSelectedAll = useMemo(() => {
if (!cart || !Array.isArray(cart.products)) return false;
- return cart.products.every(item => item.selected);
+ return cart.products.every((item) => item.selected);
}, [cart]);
-
useEffect(() => {
const updateCartItems = async () => {
if (typeof auth === 'object' && cart) {
- const upsertPromises = cart.products.map(item =>
+ const upsertPromises = cart.products.map((item) =>
upsertUserCart({
userId: auth.id,
type: item.cart_type,
id: item.id,
qty: item.quantity,
- selected: item.selected
+ selected: item.selected,
})
);
try {
@@ -128,7 +132,7 @@ const CartPage = () => {
const handleCheckout = () => {
router.push('/shop/checkout');
- }
+ };
const handleQuotation = () => {
if (hasSelectedPromo || !hasSelected) {
@@ -136,54 +140,53 @@ const CartPage = () => {
} else {
router.push('/shop/quotation');
}
- }
+ };
const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
-
-
if (cart) {
const updatedCart = {
...cart,
- products: cart.products.map(item => ({
+ products: cart.products.map((item) => ({
...item,
- selected: !hasSelectedAll
- }))
+ selected: !hasSelectedAll,
+ })),
};
-
- updateCartItem(updatedCart);
- if(hasSelectedAll){
+
+ updateCartItem(updatedCart);
+ if (hasSelectedAll) {
setIsSelectedAll(false);
- }else{
+ } else {
setIsSelectedAll(true);
}
}
};
-
const handleDelete = async () => {
if (typeof auth !== 'object' || !cart) return;
- setIsLoadDelete(true)
+ setIsLoadDelete(true);
for (const item of cart.products) {
- if(item.selected === true){
- await deleteUserCart(auth.id, [item.cart_id])
- await loadCart(auth.id)
+ if (item.selected === true) {
+ await deleteUserCart(auth.id, [item.cart_id]);
+ await loadCart(auth.id);
}
}
- setIsLoadDelete(false)
- setRefreshCart(true)
- }
+ setIsLoadDelete(false);
+ setRefreshCart(true);
+ };
return (
<>
- <div className={`${isTop ? 'border-b-[0px]' : 'border-b-[1px]'} sticky top-[157px] bg-white py-4 border-gray-300 z-50 w-3/4`}>
- <div className={`${style['title']}`}>Keranjang Belanja</div>
+ <div
+ className={`${
+ isTop ? 'border-b-[0px]' : 'border-b-[1px]'
+ } sticky md:top-[157px] flex-col bg-white py-4 border-gray-300 z-50 sm:w-full md:w-3/4`}
+ >
+ <h1 className={`${style['title']}`}>Keranjang Belanja</h1>
<div className='h-2' />
<div className={`flex items-center object-center justify-between `}>
<div className='flex items-center object-center'>
- {isLoad && (
- <Spinner className='my-auto' size='sm' />
- )}
+ {isLoad && <Spinner className='my-auto' size='sm' />}
{!isLoad && (
<Checkbox
borderColor='gray.600'
@@ -193,34 +196,31 @@ const CartPage = () => {
onChange={handleChange}
/>
)}
- <p className='p-2 text-caption-2'>
- {hasSelectedAll ? "Uncheck all" : "Select all"}
- </p>
+ <p className='p-2 text-caption-2'>
+ {hasSelectedAll ? 'Uncheck all' : 'Select all'}
+ </p>
+ </div>
+ <div className='delate all flex items-center object-center'>
+ <Tooltip
+ label={clsxm({
+ 'Tidak ada item yang dipilih': !hasSelected,
+ })}
+ >
+ <Button
+ bg='#fadede'
+ variant='outline'
+ colorScheme='red'
+ w='full'
+ isDisabled={!hasSelected}
+ onClick={handleDelete}
+ >
+ {isLoadDelete && <Spinner size='xs' />}
+ {!isLoadDelete && <Trash2Icon size={16} />}
+ <p className='text-sm ml-2'>Hapus Barang</p>
+ </Button>
+ </Tooltip>
</div>
- <div className='delate all flex items-center object-center'>
- <Tooltip
- label={clsxm({
- 'Tidak ada item yang dipilih': !hasSelected,
- })}
- >
- <Button
- bg='#fadede'
- variant='outline'
- colorScheme='red'
- w='full'
- isDisabled={!hasSelected}
- onClick={handleDelete}
- >
- {isLoadDelete && <Spinner size='xs' />}
- {!isLoadDelete && <Trash2Icon size={16} />}
- <p className='text-sm ml-2'>
- Hapus Barang
- </p>
- </Button>
- </Tooltip>
- </div>
</div>
-
</div>
<div className={style['content']}>
@@ -274,7 +274,13 @@ const CartPage = () => {
<CartSummary {...summary} isLoaded={!!cart} />
)}
- <div className={isStepApproval ? style['summary-buttons-step-approval'] : style['summary-buttons']}>
+ <div
+ className={
+ isStepApproval
+ ? style['summary-buttons-step-approval']
+ : style['summary-buttons']
+ }
+ >
<Tooltip
label={
hasSelectedPromo &&
diff --git a/src-migrate/services/product.ts b/src-migrate/services/product.ts
index fe415d11..77b645f0 100644
--- a/src-migrate/services/product.ts
+++ b/src-migrate/services/product.ts
@@ -43,9 +43,9 @@ export const getProductSimilar = async ({
const query = [
`q=${name}`,
'page=1',
- 'orderBy=popular-weekly',
'operation=OR',
- 'priceFrom=1',
+ // 'priceFrom=1',
+ `source=similar`,
];
if (except?.productId) query.push(`fq=-product_id_i:${except.productId}`);
diff --git a/src-migrate/types/auth.ts b/src-migrate/types/auth.ts
index e93a475a..593e120f 100644
--- a/src-migrate/types/auth.ts
+++ b/src-migrate/types/auth.ts
@@ -10,6 +10,7 @@ export type AuthProps = {
name: string;
email: string;
phone: string;
+ npwp: string;
mobile: string;
external: boolean;
company: boolean;
diff --git a/src-migrate/types/cart.ts b/src-migrate/types/cart.ts
index 4e3c8b99..a3115103 100644
--- a/src-migrate/types/cart.ts
+++ b/src-migrate/types/cart.ts
@@ -32,6 +32,8 @@ export type CartItem = {
id: number;
name: string;
stock: number;
+ is_in_bu: boolean;
+ on_hand_qty: number;
weight: number;
attributes: string[];
parent: {
diff --git a/src-migrate/types/product.ts b/src-migrate/types/product.ts
index 681cdc8e..31ea0ce1 100644
--- a/src-migrate/types/product.ts
+++ b/src-migrate/types/product.ts
@@ -12,6 +12,7 @@ export interface IProduct {
variant_total: number;
description: string;
isSni: boolean;
+ is_in_bu: boolean;
isTkdn: boolean;
categories: {
id: string;
diff --git a/src-migrate/types/promotion.ts b/src-migrate/types/promotion.ts
index 85190aad..dce442ad 100644
--- a/src-migrate/types/promotion.ts
+++ b/src-migrate/types/promotion.ts
@@ -10,15 +10,18 @@ export interface IPromotion {
limit_user: number;
limit_trx: number;
price: number;
+ image: string;
total_qty: number;
products: {
product_id: number;
qty: number;
+ name: string;
}[];
free_products: {
product_id: number;
qty: number;
}[];
+
}
export interface IProductVariantPromo {
@@ -34,6 +37,10 @@ export interface IProductVariantPromo {
price_discount: number;
};
qty: number;
+ manufacture: {
+ manufacture_name: string;
+ manufacture_id:number;
+ }
}
export type CategoryPromo = 'bundling' | 'discount_loading' | 'merchandise';
diff --git a/src-migrate/validations/auth.ts b/src-migrate/validations/auth.ts
index 78fc5e71..3abdfb57 100644
--- a/src-migrate/validations/auth.ts
+++ b/src-migrate/validations/auth.ts
@@ -1,17 +1,147 @@
import { z } from 'zod';
-export const registerSchema = z.object({
- name: z.string().min(1, { message: 'Nama harus diisi' }),
- email: z
- .string()
- .min(1, { message: 'Email harus diisi' })
- .email({ message: 'Email harus menggunakan format example@mail.com' }),
- password: z.string().min(6, { message: 'Password minimal 6 karakter' }),
- company: z.string().optional(),
- phone: z
- .string()
- .min(1, { message: 'Nomor telepon harus diisi' })
- .refine((val) => /^\d{10,12}$/.test(val), {
- message: 'Format nomor telepon tidak valid, contoh: 081234567890',
+export const registerSchema = z
+ .object({
+ name: z.string().min(1, { message: 'Nama harus diisi' }),
+ email: z
+ .string()
+ .min(1, { message: 'Email harus diisi' })
+ .email({ message: 'Email harus menggunakan format example@mail.com' }),
+ password: z.string().min(6, { message: 'Password minimal 6 karakter' }),
+ phone: z
+ .string()
+ .min(1, { message: 'Nomor telepon harus diisi' })
+ .refine((val) => /^\d{10,12}$/.test(val), {
+ message: 'Format nomor telepon tidak valid, contoh: 081234567890',
+ }),
+ type_acc: z.string().optional(),
+ nama_wajib_pajak: z.string().optional(),
+ alamat_bisnis: z.string().optional(),
+ alamat_wajib_pajak: z.string().optional(),
+ is_pkp: z.string(),
+ is_terdaftar: z.string(),
+ sppkp_document: z.string().optional(),
+ npwp_document: z.string().optional(),
+ industry_id: z.string().optional(),
+ email_partner: z.string().optional(),
+ business_name: z.string().optional(),
+ company_type_id: z.string().optional(),
+ isChekBox: z.string().optional(),
+ npwp: z.string().optional().refine((val) => !val || /^\d{15,16}$/.test(val), {
+ message: 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.',
}),
-});
+ sppkp: z.string().optional(),
+ })
+ .superRefine((data, ctx) => {
+ if (data.type_acc === 'business') {
+ if (data.is_terdaftar === 'false') {
+ if (data.is_pkp === 'true') {
+ const requiredFields: { field: keyof typeof data; message: string }[] = [
+ { field: 'business_name', message: 'Nama perusahaan harus diisi' },
+ { field: 'alamat_bisnis', message: 'Alamat perusahaan harus diisi' },
+ // { field: 'alamat_wajib_pajak', message: 'Alamat wajib pajak harus diisi' },
+ { field: 'company_type_id', message: 'Badan usaha wajib dipilih' },
+ { field: 'industry_id', message: 'Jenis usaha harus dipilih' },
+ { field: 'sppkp_document', message: 'Document harus diisi' },
+ { field: 'npwp_document', message: 'Document harus diisi' },
+ { field: 'npwp', message: 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.' },
+ { field: 'nama_wajib_pajak', message: 'Nama wajib pajak harus diisi' },
+ ];
+
+ requiredFields.forEach(({ field, message }) => {
+ if (!data[field]) {
+ ctx.addIssue({
+ code: 'custom',
+ path: [field],
+ message,
+ });
+ }
+ });
+
+ if (!data.email_partner || !z.string().email().safeParse(data.email_partner).success) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['email_partner'],
+ message: 'Email partner harus diisi dengan format example@mail.com',
+ });
+ }
+ if(data.isChekBox === 'false'){
+ if (!data.alamat_wajib_pajak) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['alamat_wajib_pajak'],
+ message: 'Alamat wajib pajak harus diisi',
+ });
+ }
+ }
+
+ } else {
+ if (!data.business_name) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['business_name'],
+ message: 'Nama perusahaan harus diisi',
+ });
+ }
+ if (!data.alamat_bisnis) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['alamat_bisnis'],
+ message: 'Alamat perusahaan harus diisi',
+ });
+ }
+
+ if (!data.company_type_id) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['company_type_id'],
+ message: 'Badan usaha wajib dipilih',
+ });
+ }
+ if (!data.industry_id) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['industry_id'],
+ message: 'Jenis usaha harus dipilih',
+ });
+ }
+
+ if (data.npwp && !/^\d{15,16}$/.test(data.npwp)) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['npwp'],
+ message: 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.',
+ });
+ }
+
+ }
+ }else{
+ if (data.is_pkp === 'true') {
+ if (!data.business_name) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['business_name'],
+ message: 'Nama perusahaan harus diisi',
+ });
+ }
+ } else {
+ if (!data.business_name) {
+ ctx.addIssue({
+ code: 'custom',
+ path: ['business_name'],
+ message: 'Nama perusahaan harus diisi',
+ });
+ }
+ }
+ }
+
+ // Remove this unconditional issue addition to prevent blocking form submission
+ // ctx.addIssue({
+ // code: 'custom',
+ // path: ['business_name'],
+ // message: 'Nama perusahaan harus diisi',
+ // });
+ }else{
+
+ }
+ });