summaryrefslogtreecommitdiff
path: root/src-migrate/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src-migrate/modules')
-rw-r--r--src-migrate/modules/page-content/index.tsx30
-rw-r--r--src-migrate/modules/product-detail/components/AddToQuotation.tsx227
-rw-r--r--src-migrate/modules/product-detail/components/PriceAction.tsx11
-rw-r--r--src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx17
-rw-r--r--src-migrate/modules/promo/components/PromoList.tsx79
5 files changed, 323 insertions, 41 deletions
diff --git a/src-migrate/modules/page-content/index.tsx b/src-migrate/modules/page-content/index.tsx
index edecb855..3423ca8b 100644
--- a/src-migrate/modules/page-content/index.tsx
+++ b/src-migrate/modules/page-content/index.tsx
@@ -1,4 +1,4 @@
-import { useMemo } from 'react';
+import { useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { PageContentProps } from '~/types/pageContent';
import { getPageContent } from '~/services/pageContent';
@@ -8,18 +8,38 @@ type Props = {
};
const PageContent = ({ path }: Props) => {
+ const [localData, setData] = useState<PageContentProps>();
+ const [shouldFetch, setShouldFetch] = useState(false);
+
+ useEffect(() => {
+ const localData = localStorage.getItem(`page-content:${path}`);
+ if (localData) {
+ setData(JSON.parse(localData));
+ }else{
+ setShouldFetch(true);
+ }
+ },[])
+
const { data, isLoading } = useQuery<PageContentProps>(
`page-content:${path}`,
- async () => await getPageContent({ path })
+ async () => await getPageContent({ path }), {
+ enabled: shouldFetch,
+ onSuccess: (data) => {
+ if (data) {
+ localStorage.setItem(`page-content:${path}`, JSON.stringify(data));
+ setData(data);
+ }
+ },
+ }
);
const parsedContent = useMemo<string>(() => {
- if (!data) return '';
- return data.content.replaceAll(
+ if (!localData) return '';
+ return localData.content.replaceAll(
'src="/web/image',
`src="${process.env.NEXT_PUBLIC_ODOO_API_HOST}/web/image`
);
- }, [data]);
+ }, [localData]);
if (isLoading) return <PageContentSkeleton />;
return <div dangerouslySetInnerHTML={{ __html: parsedContent || '' }}></div>;
diff --git a/src-migrate/modules/product-detail/components/AddToQuotation.tsx b/src-migrate/modules/product-detail/components/AddToQuotation.tsx
new file mode 100644
index 00000000..09a7501d
--- /dev/null
+++ b/src-migrate/modules/product-detail/components/AddToQuotation.tsx
@@ -0,0 +1,227 @@
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import style from '../styles/price-action.module.css';
+import { Button, Link, useToast } from '@chakra-ui/react';
+import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
+import product from 'next-seo/lib/jsonld/product';
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import Image from '~/components/ui/image';
+import { getAuth } from '~/libs/auth';
+import { upsertUserCart } from '~/services/cart';
+import LazyLoad from 'react-lazy-load';
+import ProductSimilar from '../../../../src/lib/product/components/ProductSimilar';
+import { IProductDetail } from '~/types/product';
+import ImageNext from 'next/image';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+import { createSlug } from '~/libs/slug';
+import formatCurrency from '~/libs/formatCurrency';
+import { useProductDetail } from '../stores/useProductDetail';
+
+type Props = {
+ variantId: number | null;
+ quantity?: number;
+ source?: 'buy' | 'add_to_cart';
+ products: IProductDetail;
+};
+
+type Status = 'idle' | 'loading' | 'success';
+
+const AddToQuotation = ({
+ variantId,
+ quantity = 1,
+ source = 'add_to_cart',
+ products,
+}: Props) => {
+ const auth = getAuth();
+ const router = useRouter();
+ const toast = useToast({
+ position: 'top',
+ isClosable: true,
+ });
+
+ const { askAdminUrl } = useProductDetail();
+
+ const [product, setProducts] = useState(products);
+ const [status, setStatus] = useState<Status>('idle');
+ const {
+ productCart,
+ setRefreshCart,
+ setProductCart,
+ refreshCart,
+ isLoading,
+ setIsloading,
+ } = useProductCartContext();
+
+ const productSimilarQuery = [
+ product?.name,
+ `fq=-product_id_i:${product.id}`,
+ `fq=-manufacture_id_i:${product.manufacture?.id || 0}`,
+ ].join('&');
+ const [addCartAlert, setAddCartAlert] = useState(false);
+
+ const handleButton = async () => {
+ if (typeof auth !== 'object') {
+ const currentUrl = encodeURIComponent(router.asPath);
+ router.push(`/login?next=${currentUrl}`);
+ return;
+ }
+
+ if (!variantId || isNaN(quantity) || typeof auth !== 'object') return;
+ if (status === 'success') return;
+ setStatus('loading');
+ await upsertUserCart({
+ userId: auth.id,
+ type: 'product',
+ id: variantId,
+ qty: quantity,
+ selected: true,
+ source: source,
+ qtyAppend: true,
+ });
+ setStatus('idle');
+ setRefreshCart(true);
+ setAddCartAlert(true);
+
+ toast({
+ title: 'Tambah ke keranjang',
+ description: 'Berhasil menambahkan barang ke keranjang belanja',
+ status: 'success',
+ duration: 3000,
+ isClosable: true,
+ position: 'top',
+ });
+
+ if (source === 'buy') {
+ router.push('/shop/quotation?source=buy');
+ }
+ };
+ useEffect(() => {
+ if (status === 'success')
+ setTimeout(() => {
+ setStatus('idle');
+ }, 3000);
+ }, [status]);
+
+ const btnConfig = {
+ add_to_cart: {
+ colorScheme: 'yellow',
+
+ text: 'Keranjang',
+ },
+ buy: {
+ colorScheme: 'red',
+ text: 'Beli',
+ },
+ };
+
+ return (
+ <div className='w-full'>
+ <Button
+ onClick={handleButton}
+ color={'red'}
+ colorScheme='white'
+ className='w-full border-2 p-2 hover:bg-slate-100 flex items-center'
+ >
+ <ImageNext
+ src='/images/writing.png'
+ alt='penawaran instan'
+ className='w-fit h-fit p-1'
+ width={20}
+ height={20}
+ />
+ Penawaran Harga Instan
+ </Button>
+ <BottomPopup
+ className='!container'
+ title='Berhasil Ditambahkan'
+ active={addCartAlert}
+ close={() => {
+ setAddCartAlert(false);
+ }}
+ >
+ <div className='flex mt-4'>
+ <div className='w-[10%]'>
+ <ImageNext
+ src={product.image}
+ alt={product.name}
+ className='h-32 object-contain object-center w-full border border-gray_r-4'
+ width={80}
+ height={80}
+ />
+ </div>
+ <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'>
+ {!!product.manufacture.name ? (
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture.name,
+ product.manufacture.id.toString()
+ )}
+ className=' hover:underline'
+ color={'red'}
+ >
+ {product.manufacture.name}
+ </Link>
+ ) : (
+ '-'
+ )}
+ <p className='text-ellipsis overflow-hidden'>{product.name}</p>
+ <p>{product.code}</p>
+ {!!product.lowest_price && product.lowest_price.price > 0 && (
+ <>
+ <div className='flex items-end gap-x-2'>
+ {product.lowest_price.discount_percentage > 0 && (
+ <>
+ <div className='badge-solid-red'>
+ {Math.floor(product.lowest_price.discount_percentage)}%
+ </div>
+ <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
+ Rp {formatCurrency(product.lowest_price.price || 0)}
+ </div>
+ </>
+ )}
+ <div className='text-danger-500 font-semibold'>
+ Rp{' '}
+ {formatCurrency(product.lowest_price.price_discount || 0)}
+ </div>
+ </div>
+ </>
+ )}
+
+ {!!product.lowest_price && product.lowest_price.price === 0 && (
+ <span>
+ Hubungi kami untuk dapatkan harga terbaik,{' '}
+ <Link
+ href={askAdminUrl}
+ target='_blank'
+ className='font-medium underline'
+ color={'red'}
+ >
+ klik disini
+ </Link>
+ </span>
+ )}
+ </div>
+ <div className='ml-3 flex items-center font-normal'>
+ <Link
+ href='/shop/cart'
+ className='flex-1 py-2 text-gray_r-12 btn-yellow'
+ >
+ Lihat Keranjang
+ </Link>
+ </div>
+ </div>
+ <div className='mt-8 mb-4'>
+ <div className='text-h-sm font-semibold mb-6'>
+ Kamu Mungkin Juga Suka
+ </div>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+ </BottomPopup>
+ </div>
+ );
+};
+
+export default AddToQuotation;
diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx
index 3544fa26..a3126cdd 100644
--- a/src-migrate/modules/product-detail/components/PriceAction.tsx
+++ b/src-migrate/modules/product-detail/components/PriceAction.tsx
@@ -7,6 +7,9 @@ import formatCurrency from '~/libs/formatCurrency';
import { IProductDetail } from '~/types/product';
import { useProductDetail } from '../stores/useProductDetail';
import AddToCart from './AddToCart';
+import AddToQuotation from './AddToQuotation';
+import Link from 'next/link';
+import { getAuth } from '~/libs/auth';
type Props = {
product: IProductDetail;
@@ -160,6 +163,14 @@ const PriceAction = ({ product }: Props) => {
/>
)}
</div>
+ <div className='mt-4'>
+ <AddToQuotation
+ source='buy'
+ products={product}
+ variantId={activeVariantId}
+ quantity={Number(quantityInput)}
+ />
+ </div>
</div>
);
};
diff --git a/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx b/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx
new file mode 100644
index 00000000..5685b83a
--- /dev/null
+++ b/src-migrate/modules/promo/components/FlashSaleNonDisplay.tsx
@@ -0,0 +1,17 @@
+import dynamic from 'next/dynamic';
+import React from 'react';
+import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton';
+const FlashSaleNonDisplay = dynamic(
+ () => import('@/lib/flashSale/components/FlashSaleNonDisplay'),
+ {
+ loading: () => <FlashSaleSkeleton />,
+ }
+);
+const FlashSalePromo = () => {
+ return (
+ <>
+ <FlashSaleNonDisplay />
+ </>
+ );
+};
+export default FlashSalePromo;
diff --git a/src-migrate/modules/promo/components/PromoList.tsx b/src-migrate/modules/promo/components/PromoList.tsx
index d59d1867..9f808718 100644
--- a/src-migrate/modules/promo/components/PromoList.tsx
+++ b/src-migrate/modules/promo/components/PromoList.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
-import { Button, Skeleton } from '@chakra-ui/react'
-import clsxm from "~/libs/clsxm"
+import { Button, Skeleton } from '@chakra-ui/react';
+import clsxm from '~/libs/clsxm';
import ProductPromoCard from '../../product-promo/components/Card';
import { fetchPromoItemsSolr } from '../../../../src/api/promoApi';
import { Swiper, SwiperSlide } from 'swiper/react';
@@ -8,7 +8,7 @@ import SwiperCore, { Navigation, Pagination } from 'swiper';
import useDevice from '@/core/hooks/useDevice';
import LogoSpinner from '../../../../src/core/components/elements/Spinner/LogoSpinner';
import usePromoStore from './promoStore';
-import Link from "next/link"
+import Link from 'next/link';
import { IPromotion } from '~/types/promotion';
interface PromoListProps {
selectedPromo: string; // Tipe selectedPromo ditetapkan sebagai string
@@ -32,11 +32,11 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
const swiperBanner = {
modules: [Navigation],
- className: 'h-[400px] w-full',
+ className: 'h-full w-full',
slidesPerView: isMobile ? 1.1 : 3.25,
spaceBetween: 10,
- navigation:isMobile? true : false,
- allowTouchMove:isMobile? false : true,
+ navigation: isMobile ? true : false,
+ allowTouchMove: isMobile ? false : true,
};
useEffect(() => {
@@ -56,7 +56,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
const fetchPromotions = async () => {
setIsLoading(true);
try {
- const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10);
+ const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10);
setPromoItems(items);
const promoDataPromises = items?.map(async (item) => {
@@ -69,9 +69,11 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
});
const promoDataArray = await Promise.all(promoDataPromises);
- const mergedPromoData = promoDataArray?.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
+ const mergedPromoData = promoDataArray?.reduce(
+ (accumulator, currentValue) => accumulator.concat(currentValue),
+ []
+ );
setPromoData(mergedPromoData);
-
} catch (error) {
console.error('Error fetching promo items:', error);
} finally {
@@ -92,44 +94,49 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
<div className='flex justify-between items-center'>
<h1 className='text-h-sm md:text-h-lg font-semibold py-4'>{title}</h1>
<div>
- <Link href={`/shop/promo/${slug}`} className='!text-red-500 font-semibold'>
+ <Link
+ href={`/shop/promo/${slug}`}
+ className='!text-red-500 font-semibold'
+ >
Lihat Semua
</Link>
</div>
</div>
{isLoading ? (
- <div className="loading-spinner flex justify-center">
+ <div className='loading-spinner flex justify-center'>
<LogoSpinner width={48} height={48} />
</div>
) : (
<Skeleton
- isLoaded={!isLoading}
- className={clsxm(
- "flex gap-x-4 overflow-x-auto px-4 md:px-0", {
- "min-h-[340px]": promoData[0] && promoData?.length > 0
- })}
- >
- {isDesktop && (
- <Swiper {...swiperBanner}>
- {promoData?.map((promotion: IPromotion) => (
- <SwiperSlide key={promotion.id}>
- <div className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-full lg:w-full xl:w-full">
- <ProductPromoCard product={promoItems} promotion={promotion} />
- </div>
- </SwiperSlide>
- ))}
- </Swiper>
- )}
- {isMobile && (promoData?.map((promotion: IPromotion) => (
- <div key={promotion.id} className="min-w-[400px] max-w-[400px]">
- <ProductPromoCard product={promoItems} promotion={promotion} />
- </div>
- )))}
-
- </Skeleton>
+ isLoaded={!isLoading}
+ className={clsxm('flex gap-x-4 overflow-x-auto px-4 md:px-0', {
+ 'min-h-[340px]': promoData[0] && promoData?.length > 0,
+ })}
+ >
+ {isDesktop && (
+ <Swiper {...swiperBanner}>
+ {promoData?.map((promotion: IPromotion) => (
+ <SwiperSlide key={promotion.id}>
+ <div className='min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-full lg:w-full xl:w-full'>
+ <ProductPromoCard
+ product={promoItems}
+ promotion={promotion}
+ />
+ </div>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ )}
+ {isMobile &&
+ promoData?.map((promotion: IPromotion) => (
+ <div key={promotion.id} className='min-w-[400px] max-w-[400px]'>
+ <ProductPromoCard product={promoItems} promotion={promotion} />
+ </div>
+ ))}
+ </Skeleton>
)}
</div>
);
};
-export default PromoList; \ No newline at end of file
+export default PromoList;