summaryrefslogtreecommitdiff
path: root/src/pages/shop
diff options
context:
space:
mode:
Diffstat (limited to 'src/pages/shop')
-rw-r--r--src/pages/shop/brands/[slug].js178
-rw-r--r--src/pages/shop/brands/[slug].jsx23
-rw-r--r--src/pages/shop/brands/index.js79
-rw-r--r--src/pages/shop/cart.js282
-rw-r--r--src/pages/shop/cart.jsx10
-rw-r--r--src/pages/shop/checkout/finish.js47
-rw-r--r--src/pages/shop/checkout/index.js325
-rw-r--r--src/pages/shop/product/[slug].js305
-rw-r--r--src/pages/shop/product/[slug].jsx29
-rw-r--r--src/pages/shop/quotation/finish.js39
-rw-r--r--src/pages/shop/quotation/index.js140
-rw-r--r--src/pages/shop/search.js125
-rw-r--r--src/pages/shop/search.jsx19
13 files changed, 81 insertions, 1520 deletions
diff --git a/src/pages/shop/brands/[slug].js b/src/pages/shop/brands/[slug].js
deleted file mode 100644
index a387e55d..00000000
--- a/src/pages/shop/brands/[slug].js
+++ /dev/null
@@ -1,178 +0,0 @@
-import axios from "axios";
-import { useEffect, useState } from "react";
-import Filter from "@/components/elements/Filter";
-import Footer from "@/components/layouts/Footer";
-import Header from "@/components/layouts/Header";
-import Layout from "@/components/layouts/Layout";
-import Pagination from "@/components/elements/Pagination";
-import ProductCard from "@/components/products/ProductCard";
-import { getIdFromSlug, getNameFromSlug } from "@/core/utils/slug";
-import FilterIcon from "@/icons/filter.svg";
-import apiOdoo from "@/core/utils/apiOdoo";
-import { Swiper, SwiperSlide } from "swiper/react";
-import "swiper/css";
-import "swiper/css/pagination";
-import "swiper/css/autoplay";
-import { Pagination as SwiperPagination } from "swiper";
-import Image from "@/components/elements/Image";
-import LineDivider from "@/components/elements/LineDivider";
-
-export async function getServerSideProps(context) {
- const {
- slug,
- page = 1,
- category = '',
- price_from = '',
- price_to = '',
- order_by = '',
- } = context.query;
-
- let urlParameter = [
- 'q=*',
- `page=${page}`,
- `brand=${getNameFromSlug(slug)}`,
- `category=${category}`,
- `price_from=${price_from}`,
- `price_to=${price_to}`,
- `order_by=${order_by}`
- ].join('&');
- let searchResults = await axios(`${process.env.SELF_HOST}/api/shop/search?${urlParameter}`);
- searchResults = searchResults.data;
-
- const manufacture = await apiOdoo('GET', `/api/v1/manufacture/${getIdFromSlug(slug)}`);
-
- return {
- props: {
- searchResults,
- page,
- slug,
- category,
- price_from,
- price_to,
- order_by,
- manufacture
- }
- };
-}
-
-export default function BrandDetail({
- searchResults,
- page,
- slug,
- category,
- price_from,
- price_to,
- order_by,
- manufacture
-}) {
- const pageCount = Math.ceil(searchResults.response.numFound / searchResults.responseHeader.params.rows);
- const productStart = searchResults.responseHeader.params.start;
- const productRows = searchResults.responseHeader.params.rows;
- const productFound = searchResults.response.numFound;
-
- const [activeFilter, setActiveFilter] = useState(false);
- const [filterCount, setFilterCount] = useState(0);
-
- const route = () => {
- let route = `/shop/brands/${slug}`;
- if (category) route += `&category=${category}`;
- if (price_from) route += `&price_from=${price_from}`;
- if (price_to) route += `&price_to=${price_to}`;
- if (order_by) route += `&order_by=${order_by}`;
- return route;
- }
-
- useEffect(() => {
- let calculateFilterCount = 0;
- if (category) calculateFilterCount++;
- if (price_from || price_to) calculateFilterCount++;
- if (order_by) calculateFilterCount++;
- setFilterCount(calculateFilterCount);
- }, [category, price_from, price_to, order_by]);
-
- return (
- <>
- <Header title={`Distributor ${getNameFromSlug(slug)} Indonesia Harga Official - Indoteknik`} />
- <Filter
- defaultRoute={`/shop/brands/${slug}`}
- isActive={activeFilter}
- closeFilter={() => setActiveFilter(false)}
- defaultPriceFrom={price_from}
- defaultPriceTo={price_to}
- defaultBrand=''
- defaultCategory={category}
- defaultOrderBy={order_by}
- searchResults={searchResults}
- disableFilter={['brand']}
- />
- <Layout>
- <Swiper slidesPerView={1} pagination={{dynamicBullets: true}} modules={[SwiperPagination]}>
- {
- manufacture.banners?.map((banner, index) => (
- <SwiperSlide key={index}>
- <Image
- src={banner}
- alt={`Banner ${manufacture.name}`}
- className="w-full h-auto border-b border-gray_r-6"
- />
- </SwiperSlide>
- ))
- }
- </Swiper>
- <div className="p-4 grid grid-cols-2">
- <div>
- <p className="text-caption-2 text-gray_r-11 mb-2">Produk dari brand:</p>
- { manufacture.logo ? (
- <div className="w-8/12">
- <Image src={manufacture?.logo} alt={manufacture.name} className="border border-gray_r-6 rounded p-3" />
- </div>
- ) : (
- <p className="badge-solid-red text-caption-1">{ manufacture.name }</p>
- ) }
- </div>
- <div className="text-right">
- <p className="text-caption-2 text-gray_r-11 mb-2">Jumlah Produk:</p>
- <p>{ searchResults.response.numFound }</p>
- </div>
- </div>
-
- <LineDivider />
-
- <div className="p-4">
- <h1 className="mb-2">Produk</h1>
- <div className="text-caption-1 mb-4">
- {productFound > 0 ? (
- <>
- Menampilkan&nbsp;
- {pageCount > 1 ? (
- <>
- {productStart + 1}-{
- (productStart + productRows) > productFound ? productFound : productStart + productRows
- }
- &nbsp;dari&nbsp;
- </>
- ) : ''}
- {searchResults.response.numFound}
- &nbsp;produk untuk brand <span className="font-semibold">{getNameFromSlug(slug)}</span>
- </>
- ) : 'Mungkin yang anda cari'}
- </div>
- <button className="btn-light py-2 flex items-center gap-x-2 mb-4" onClick={() => setActiveFilter(true)}>
- <FilterIcon className="w-4 h-4" /> <span>Filter {filterCount > 0 ? `(${filterCount})` : ''}</span>
- </button>
- <div className="grid grid-cols-2 gap-3">
- {searchResults.response.products.map((product) => (
- <ProductCard key={product.id} data={product} />
- ))}
- </div>
-
- <div className="mt-4">
- <Pagination pageCount={pageCount} currentPage={parseInt(page)} url={route()} />
- </div>
- </div>
-
- <Footer />
- </Layout>
- </>
- )
-} \ No newline at end of file
diff --git a/src/pages/shop/brands/[slug].jsx b/src/pages/shop/brands/[slug].jsx
new file mode 100644
index 00000000..4965d4f7
--- /dev/null
+++ b/src/pages/shop/brands/[slug].jsx
@@ -0,0 +1,23 @@
+import BasicLayout from "@/core/components/layouts/BasicLayout"
+import { getIdFromSlug, getNameFromSlug } from "@/core/utils/slug"
+import ProductSearch from "@/lib/product/components/ProductSearch"
+import { useRouter } from "next/router"
+import _ from "lodash"
+import Brand from "@/lib/brand/components/Brand"
+
+export default function BrandDetail() {
+ const router = useRouter()
+ const { slug = '' } = router.query
+ return (
+ <BasicLayout>
+ <Brand id={getIdFromSlug(slug)} />
+ { !_.isEmpty(router.query) && (
+ <ProductSearch
+ query={_.omit(router.query, 'slug')}
+ prefixUrl={`/shop/brands/${slug}`}
+ defaultBrand={getNameFromSlug(slug)}
+ />
+ ) }
+ </BasicLayout>
+ )
+} \ No newline at end of file
diff --git a/src/pages/shop/brands/index.js b/src/pages/shop/brands/index.js
deleted file mode 100644
index bfdcd403..00000000
--- a/src/pages/shop/brands/index.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import Header from "@/components/layouts/Header";
-import apiOdoo from "@/core/utils/apiOdoo";
-import InfiniteScroll from "react-infinite-scroll-component";
-import { useCallback, useEffect, useState } from "react";
-import Spinner from "@/components/elements/Spinner";
-import Layout from "@/components/layouts/Layout";
-import ManufactureCard from "@/components/manufactures/ManufactureCard";
-import Footer from "@/components/layouts/Footer";
-
-export async function getServerSideProps() {
- let initialManufactures = await apiOdoo('GET', '/api/v1/manufacture?limit=31');
- return {props: {initialManufactures}};
-}
-
-export default function Brands({ initialManufactures }) {
- const [manufactures, setManufactures] = useState(initialManufactures.manufactures);
- const [hasMoreManufacture, setHasMoreManufacture] = useState(true);
- const [manufactureStartwith, setManufactureStartWith] = useState('');
-
- const alpha = Array.from(Array(26)).map((e, i) => i + 65);
- const alphabets = alpha.map((x) => String.fromCharCode(x));
-
- const getMoreManufactures = useCallback(async () => {
- const name = manufactureStartwith != '' ? `${manufactureStartwith}%` : '';
- const result = await apiOdoo('GET', `/api/v1/manufacture?limit=30&offset=${manufactures.length}&name=${name}`);
- setHasMoreManufacture(manufactures.length + 30 < result.manufacture_total)
- setManufactures((manufactures) => [...manufactures, ...result.manufactures]);
- }, [ manufactureStartwith ]);
-
- const filterManufactureStartWith = (character) => {
- setManufactures([]);
- if (manufactureStartwith == character) {
- setManufactureStartWith('');
- } else {
- setManufactureStartWith(character);
- }
- };
-
- useEffect(() => {
- getMoreManufactures();
- }, [ getMoreManufactures ]);
-
- return (
- <>
- <Header title='Semua Brand di Indoteknik' />
- <Layout>
- <div className="p-4">
- <h1>Semua Brand di Indoteknik</h1>
- <div className="flex overflow-x-auto gap-x-2 py-2">
- {alphabets.map((alphabet, index) => (
- <button key={index} className={"p-2 py-1 border bg-white border-gray_r-6 rounded w-10 flex-shrink-0" + (manufactureStartwith == alphabet ? ' !bg-yellow_r-9 border-yellow_r-9 ' : '')} onClick={() => filterManufactureStartWith(alphabet)}>
- {alphabet}
- </button>
- ))}
- </div>
- <InfiniteScroll
- dataLength={manufactures.length}
- next={getMoreManufactures}
- hasMore={hasMoreManufacture}
- className="grid grid-cols-4 gap-4 mt-6 !overflow-x-hidden"
- loader={
- <div className="flex justify-center items-center border border-gray-300 p-2 rounded h-14">
- <Spinner className="w-6 h-6 text-gray-600 fill-gray-900"/>
- </div>
- }
- >
- {manufactures?.map((manufacture, index) => (
- manufacture.name ? (
- <ManufactureCard data={manufacture} key={index} />
- ) : ''
- ))}
- </InfiniteScroll>
- </div>
-
- <Footer />
- </Layout>
- </>
- )
-} \ No newline at end of file
diff --git a/src/pages/shop/cart.js b/src/pages/shop/cart.js
deleted file mode 100644
index 1178781b..00000000
--- a/src/pages/shop/cart.js
+++ /dev/null
@@ -1,282 +0,0 @@
-import { useEffect, useState } from "react";
-import { toast } from "react-hot-toast";
-import {
- TrashIcon,
- PlusIcon,
- MinusIcon,
- ExclamationCircleIcon,
-} from "@heroicons/react/24/solid";
-import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
-import { useRouter } from "next/router";
-
-// Helpers
-import {
- createOrUpdateItemCart,
- deleteItemCart,
- getCart
-} from "@/core/utils/cart";
-import { createSlug } from "@/core/utils/slug";
-import apiOdoo from "@/core/utils/apiOdoo";
-import currencyFormat from "@/core/utils/currencyFormat";
-
-// Components
-import Image from "@/components/elements/Image";
-import Layout from "@/components/layouts/Layout";
-import Link from "@/components/elements/Link";
-import Alert from "@/components/elements/Alert";
-import Spinner from "@/components/elements/Spinner";
-import AppBar from "@/components/layouts/AppBar";
-import ProgressBar from "@/components/elements/ProgressBar";
-import LineDivider from "@/components/elements/LineDivider";
-import useConfirmAlert from "@/lib/elements/hooks/useConfirmAlert";
-
-export default function Cart() {
- const router = useRouter();
- const [isLoadingProducts, setIsLoadingProducts] = useState(true);
- const [products, setProducts] = useState([]);
- const [totalPriceBeforeTax, setTotalPriceBeforeTax] = useState(0);
- const [totalTaxAmount, setTotalTaxAmount] = useState(0);
- const [totalDiscountAmount, setTotalDiscountAmount] = useState(0);
-
- useEffect(() => {
- const getProducts = async () => {
- let cart = getCart();
- let productIds = Object.keys(cart);
- if (productIds.length > 0) {
- productIds = productIds.join(',');
- let dataProducts = await apiOdoo('GET', `/api/v1/product_variant/${productIds}`);
- dataProducts = dataProducts.map((product) => ({
- ...product,
- quantity: cart[product.id].quantity,
- selected: cart[product.id].selected,
- }));
- setProducts(dataProducts);
- }
- setIsLoadingProducts(false);
- }
- getProducts();
- }, []);
-
- useEffect(() => {
- for (const product of products) {
- if (product.quantity != '') createOrUpdateItemCart(product.id, product.quantity, product.selected);
- }
- const productsSelected = products.filter((product) => product.selected == true);
- let calculateTotalPriceBeforeTax = 0;
- let calculateTotalTaxAmount = 0;
- let calculateTotalDiscountAmount = 0;
- productsSelected.forEach(product => {
- let priceBeforeTax = product.price.price / 1.11;
- calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity;
- calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity;
- calculateTotalDiscountAmount += (product.price.price - product.price.price_discount) * product.quantity;
- });
- setTotalPriceBeforeTax(calculateTotalPriceBeforeTax);
- setTotalTaxAmount(calculateTotalTaxAmount);
- setTotalDiscountAmount(calculateTotalDiscountAmount);
- }, [products]);
-
- const getProductsSelected = () => {
- return products.filter((product) => product.selected == true);
- }
-
- const updateCart = (productId, quantity) => {
- let productIndexToUpdate = products.findIndex((product) => product.id == productId);
- let productsToUpdate = products;
- productsToUpdate[productIndexToUpdate].quantity = quantity;
- setProducts([...productsToUpdate]);
- };
-
- const blurQuantity = (productId, quantity) => {
- quantity = quantity == ('' || 0) ? 1 : parseInt(quantity);
- if (typeof quantity === 'number') {
- quantity = parseInt(quantity);
- quantity = Math.floor(quantity);
- }
- updateCart(productId, quantity);
- };
-
- const updateQuantity = (productId, quantity) => {
- quantity = quantity == '' ? '' : parseInt(quantity);
- updateCart(productId, quantity);
- };
-
- const plusQuantity = (productId) => {
- let productIndexToUpdate = products.findIndex((product) => product.id == productId);
- let quantity = products[productIndexToUpdate].quantity + 1;
- updateCart(productId, quantity);
- }
-
- const minusQuantity = (productId) => {
- let productIndexToUpdate = products.findIndex((product) => product.id == productId);
- let quantity = products[productIndexToUpdate].quantity - 1;
- updateCart(productId, quantity);
- }
-
- const toggleProductSelected = (productId) => {
- let productIndexToUpdate = products.findIndex((product) => product.id == productId);
- let productsToUpdate = products;
- productsToUpdate[productIndexToUpdate].selected = !productsToUpdate[productIndexToUpdate].selected;
- setProducts([...productsToUpdate]);
- }
-
- const deleteItem = (productId) => {
- let productIndexToUpdate = products.findIndex((product) => product.id == productId);
- let productsToUpdate = products;
- productsToUpdate.splice(productIndexToUpdate, 1);
- setProducts([...productsToUpdate]);
- deleteItemCart(productId);
- toast.success('Berhasil menghapus 1 barang dari keranjang', { duration: 1500 });
- }
-
- const {
- openConfirmAlert,
- ConfirmAlert
- } = useConfirmAlert({
- title: 'Hapus barang dari keranjang',
- caption:'Apakah anda yakin menghapus barang dari keranjang?',
- closeText: 'Batal',
- submitText: 'Hapus',
- onSubmit: deleteItem
- })
-
- return (
- <>
- { ConfirmAlert }
-
- <Layout>
- <AppBar title="Keranjang Saya" />
-
- {isLoadingProducts && (
- <div className="flex justify-center items-center gap-x-3 mt-14">
- <Spinner className="w-10 text-gray_r-8 fill-gray_r-12" />
- </div>
- ) }
-
- { !isLoadingProducts && products.length == 0 && (
- <div className="text-center mt-14">
- <ExclamationTriangleIcon className="w-12 mx-auto"/>
- <p className="mt-2 h2">Keranjang belanja anda masih kosong.</p>
- <Link href="/" className="btn-yellow text-gray_r-12 mx-auto mt-4">Mulai Belanja</Link>
- </div>
- ) }
-
- { !isLoadingProducts && products.length > 0 && (
- <>
- <ProgressBar
- current={1}
- labels={['Keranjang', 'Pembayaran', 'Selesai']}
- />
-
- <LineDivider />
-
- <div className="p-4">
- <Alert type="warning" className="text-caption-2 flex gap-x-3 items-center">
- <div>
- <ExclamationCircleIcon className="w-8 text-yellow_r-11"/>
- </div>
- <span>Mohon dicek kembali & pastikan pesanan kamu sudah sesuai dengan yang kamu butuhkan. Atau bisa hubungi kami.</span>
- </Alert>
- </div>
-
- <LineDivider />
-
- <div className="p-4 flex flex-col gap-y-6">
- <div className="flex justify-between items-center">
- <h2>Daftar Produk Belanja</h2>
- <Link href="/" className="text-caption-1">Cari Produk Lain</Link>
- </div>
- {products.map((product, index) => (
- <div className="flex gap-x-3" key={index}>
- <div className="w-4/12 flex items-center gap-x-2" onClick={() => toggleProductSelected(product.id)}>
- <button
- className={'p-2 rounded border-2 ' + (product.selected ? 'border-yellow_r-9 bg-yellow_r-9' : 'border-gray_r-12')}
- ></button>
- <Image
- src={product.parent.image}
- alt={product.parent.name}
- className="object-contain object-center border border-gray_r-6 h-32 w-full rounded-md"
- />
- </div>
- <div className="w-8/12 flex flex-col">
- <Link href={'/shop/product/' + createSlug(product.parent.name, product.parent.id)} className="product-card__title wrap-line-ellipsis-2">
- {product.parent.name}
- </Link>
- <p className="text-caption-2 text-gray_r-11 mt-1">
- {product.code || '-'}
- {product.attributes.length > 0 ? ` | ${product.attributes.join(', ')}` : ''}
- </p>
- <div className="flex flex-wrap gap-x-1 items-center mb-2 mt-auto">
- {product.price.discount_percentage > 0 && (
- <>
- <p className="text-caption-2 text-gray_r-11 line-through">{currencyFormat(product.price.price)}</p>
- <span className="badge-red">{product.price.discount_percentage}%</span>
- </>
- )}
- <p className="text-caption-2 text-gray_r-12">{currencyFormat(product.price.price_discount)}</p>
- </div>
- <div className="flex items-center">
- <p className="mr-auto text-caption-2 text-gray_r-12 font-bold">{currencyFormat(product.quantity * product.price.price_discount)}</p>
- <div className="flex gap-x-2 items-center">
- <button
- className="btn-red p-2 rounded"
- onClick={() => openConfirmAlert(product.id)}
- >
- <TrashIcon className="text-red_r-11 w-3"/>
- </button>
- <button
- className="btn-light p-2 rounded"
- disabled={product.quantity == 1}
- onClick={() => minusQuantity(product.id)}
- >
- <MinusIcon className={'text-gray_r-12 w-3' + (product.quantity == 1 ? ' text-gray_r-11' : '')}/>
- </button>
- <input
- type="number"
- className="bg-transparent border-none w-6 text-center outline-none"
- onBlur={(e) => blurQuantity(product.id, e.target.value)}
- onChange={(e) => updateQuantity(product.id, e.target.value)}
- value={product.quantity}
- />
- <button className="btn-light p-2 rounded" onClick={() => plusQuantity(product.id)}>
- <PlusIcon className="text-gray_r-12 w-3"/>
- </button>
- </div>
- </div>
- </div>
- </div>
- ))}
- </div>
-
- <div className="p-4 bg-gray_r-1 sticky bottom-0 border-t-4 border-gray_r-4">
- <div className="flex">
- <p>Total</p>
- <p className="text-gray_r-11 ml-1">{getProductsSelected().length > 0 && (
- <>({ getProductsSelected().length } Barang)</>
- )}</p>
- <p className="font-semibold text-red_r-11 ml-auto">{currencyFormat(totalPriceBeforeTax + totalTaxAmount - totalDiscountAmount)}</p>
- </div>
-
- <div className="flex gap-x-3 mt-4">
- <button
- className="flex-1 btn-light"
- disabled={getProductsSelected().length == 0}
- onClick={() => router.push('/shop/quotation')}
- >
- Quotation {getProductsSelected().length > 0 && `(${getProductsSelected().length})`}
- </button>
- <button
- className="flex-1 btn-yellow"
- disabled={getProductsSelected().length == 0}
- onClick={() => router.push('/shop/checkout')}
- >
- Checkout {getProductsSelected().length > 0 && `(${getProductsSelected().length})`}
- </button>
- </div>
- </div>
- </>
- ) }
- </Layout>
- </>
- );
-} \ No newline at end of file
diff --git a/src/pages/shop/cart.jsx b/src/pages/shop/cart.jsx
new file mode 100644
index 00000000..20279e0c
--- /dev/null
+++ b/src/pages/shop/cart.jsx
@@ -0,0 +1,10 @@
+import AppLayout from "@/core/components/layouts/AppLayout"
+import CartComponent from "@/lib/cart/components/Cart"
+
+export default function Cart() {
+ return (
+ <AppLayout title="Keranjang">
+ <CartComponent />
+ </AppLayout>
+ )
+} \ No newline at end of file
diff --git a/src/pages/shop/checkout/finish.js b/src/pages/shop/checkout/finish.js
deleted file mode 100644
index df284f8a..00000000
--- a/src/pages/shop/checkout/finish.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import WithAuth from "@/components/auth/WithAuth";
-import Link from "@/components/elements/Link";
-import AppBar from "@/components/layouts/AppBar";
-import Header from "@/components/layouts/Header";
-import Layout from "@/components/layouts/Layout";
-import apiOdoo from "@/core/utils/apiOdoo";
-import { useAuth } from "@/core/utils/auth";
-import { EnvelopeIcon } from "@heroicons/react/24/outline";
-import { useRouter } from "next/router";
-import { useEffect, useState } from "react";
-
-export default function FinishCheckout() {
- const router = useRouter();
- const { id } = router.query;
- const [ auth ] = useAuth();
- const [ transaction, setTransactions ] = useState(null);
-
- useEffect(() => {
- const loadTransaction = async () => {
- if (auth && id) {
- const dataTransaction = await apiOdoo('GET', `/api/v1/partner/${auth.partner_id}/sale_order/${id}`);
- setTransactions(dataTransaction);
- }
- };
- loadTransaction();
- });
-
- return (
- <WithAuth>
- <Layout>
- <AppBar title="Pembelian Berhasil" />
-
- <div className="m-4 rounded-xl bg-yellow_r-4 text-center border border-yellow_r-7">
- <div className="px-4 py-6 text-yellow_r-12">
- <p className="h2 mb-2">Terima Kasih atas Pembelian Anda</p>
- <p className="text-yellow_r-11 mb-4 leading-6">Rincian belanja sudah kami kirimkan ke email anda. Mohon dicek kembali. jika tidak menerima email, anda dapat menghubungi kami disini.</p>
- <p className="mb-2 font-medium">{ transaction?.name }</p>
- <p className="text-caption-2 text-yellow_r-11">No. Transaksi</p>
- </div>
- <Link href={transaction?.id ? `/my/transaction/${transaction.id}` : '/'} className="bg-yellow_r-6 text-yellow_r-12 rounded-b-xl py-4 block">
- Lihat detail pembelian Anda disini
- </Link>
- </div>
- </Layout>
- </WithAuth>
- );
-} \ No newline at end of file
diff --git a/src/pages/shop/checkout/index.js b/src/pages/shop/checkout/index.js
deleted file mode 100644
index 0a77ebed..00000000
--- a/src/pages/shop/checkout/index.js
+++ /dev/null
@@ -1,325 +0,0 @@
-import { ExclamationCircleIcon } from "@heroicons/react/24/solid"
-import { useEffect, useState } from "react"
-import Alert from "@/components/elements/Alert"
-import AppBar from "@/components/layouts/AppBar"
-import Layout from "@/components/layouts/Layout"
-import LineDivider from "@/components/elements/LineDivider"
-import Link from "@/components/elements/Link"
-import ProgressBar from "@/components/elements/ProgressBar"
-import Spinner from "@/components/elements/Spinner"
-import apiOdoo from "@/core/utils/apiOdoo"
-import { useAuth } from "@/core/utils/auth"
-import { deleteItemCart, getCart } from "@/core/utils/cart"
-import currencyFormat from "@/core/utils/currencyFormat"
-import { getItemAddress } from "@/core/utils/address"
-import { useRouter } from "next/router"
-import WithAuth from "@/components/auth/WithAuth"
-import { toast } from "react-hot-toast"
-import getFileBase64 from "@/core/utils/getFileBase64"
-import VariantCard from "@/components/variants/VariantCard"
-
-export default function Checkout() {
- const router = useRouter()
- const { product_id, qty } = router.query
- const [ auth ] = useAuth()
- const [ addresses, setAddresses ] = useState(null)
- const [ poNumber, setPoNumber ] = useState('')
- const [ poFile, setPoFile ] = useState('')
- const [ selectedAddress, setSelectedAddress ] = useState({
- shipping: null,
- invoicing: null
- })
- const [ selectedPayment, setSelectedPayment ] = useState(null)
- const [ products, setProducts ] = useState(null)
- const [ totalAmount, setTotalAmount ] = useState(0)
- const [ totalDiscountAmount, setTotalDiscountAmount ] = useState(0)
-
- const [ isLoading, setIsLoading ] = useState(false)
-
- const payments = [
- { name: 'BCA', number: '8870-4000-81' },
- { name: 'MANDIRI', number: '155-0067-6869-75' },
- ]
-
- useEffect(() => {
- const getAddresses = async () => {
- if (auth) {
- const dataAddresses = await apiOdoo('GET', `/api/v1/user/${auth.id}/address`)
- setAddresses(dataAddresses)
- }
- }
- getAddresses()
- }, [auth])
-
- useEffect(() => {
- const getProducts = async () => {
- let cart = getCart()
- let productIds = []
- if (product_id) {
- productIds = [parseInt(product_id)]
- } else {
- productIds = Object
- .values(cart)
- .filter((itemCart) => itemCart.selected == true)
- .map((itemCart) => itemCart.product_id)
- }
- if (productIds.length > 0) {
- productIds = productIds.join(',')
- let dataProducts = await apiOdoo('GET', `/api/v1/product_variant/${productIds}`)
- dataProducts = dataProducts.map((product) => {
- if (product_id) {
- product.quantity = 1
- if (qty) product.quantity = parseInt(qty)
- } else {
- product.quantity = cart[product.id].quantity
- }
- return product
- })
- setProducts(dataProducts)
- }
- }
- getProducts()
- }, [router, auth, product_id, qty])
-
- useEffect(() => {
- if (addresses) {
- const matchAddress = (key) => {
- const addressToMatch = getItemAddress(key)
- let foundAddress = addresses.filter((address) => address.id == addressToMatch)
- if (foundAddress.length > 0) {
- return foundAddress[0]
- }
- return addresses[0]
- }
- setSelectedAddress({
- shipping: matchAddress('shipping'),
- invoicing: matchAddress('invoicing'),
- })
- }
- }, [addresses])
-
- useEffect(() => {
- if (products) {
- let calculateTotalAmount = 0
- let calculateTotalDiscountAmount = 0
- products.forEach(product => {
- calculateTotalAmount += product.price.price * product.quantity
- calculateTotalDiscountAmount += (product.price.price - product.price.price_discount) * product.quantity
- })
- setTotalAmount(calculateTotalAmount)
- setTotalDiscountAmount(calculateTotalDiscountAmount)
- }
- }, [products])
-
- const checkout = async () => {
- if (!selectedPayment) {
- toast.error('Mohon pilih metode pembayaran', {
- position: 'bottom-center'
- })
- return
- }
- if (poFile && poFile.size > 5000000) {
- toast.error('Maksimal ukuran file adalah 5MB', {
- position: 'bottom-center'
- })
- return
- }
- setIsLoading(true)
- let productOrder = products.map((product) => ({ 'product_id': product.id, 'quantity': product.quantity }))
- let data = {
- 'partner_shipping_id': selectedAddress.shipping.id,
- 'partner_invoice_id': selectedAddress.invoicing.id,
- 'order_line': JSON.stringify(productOrder),
- 'type': 'sale_order'
- }
- if (poNumber) data.po_number = poNumber
- if (poFile) data.po_file = await getFileBase64(poFile)
-
- const checkoutToOdoo = await apiOdoo('POST', `/api/v1/partner/${auth.partner_id}/sale_order/checkout`, data)
- for (const product of products) {
- deleteItemCart(product.id)
- }
- router.push(`/shop/checkout/finish?id=${checkoutToOdoo.id}`)
- setIsLoading(false)
- }
-
- return (
- <WithAuth>
- <Layout>
- <AppBar title={"Checkout"} />
- { !products && !addresses && (
- <div className="flex justify-center items-center gap-x-3 mt-14">
- <Spinner className="w-10 text-gray_r-8 fill-gray_r-12" />
- </div>
- ) }
-
- { products && addresses && (
- <>
- <ProgressBar
- current={2}
- labels={['Keranjang', 'Pembayaran', 'Selesai']}
- />
-
- <LineDivider/>
-
- <div className="p-4">
- <Alert type="info" className="text-caption-2 flex gap-x-3 items-center">
- <div>
- <ExclamationCircleIcon className="w-6 text-blue-700"/>
- </div>
- <span>Jika mengalami kesulitan dalam melakukan pembelian di website Indoteknik. Hubungi kami disini</span>
- </Alert>
- </div>
-
- <LineDivider/>
-
- <div className="p-4">
- <div className="flex justify-between items-center">
- <h2>Alamat Pengiriman</h2>
- <Link className="text-caption-1" href="/my/address?select=shipping">Pilih Alamat Lain</Link>
- </div>
-
- { selectedAddress.shipping && (
- <div className="mt-4 text-caption-1">
- <div className="badge-red mb-2">{ selectedAddress.shipping.type.charAt(0).toUpperCase() + selectedAddress.shipping.type.slice(1) + ' Address' }</div>
- <p className="font-medium">{ selectedAddress.shipping.name }</p>
- <p className="mt-2 text-gray_r-11">{ selectedAddress.shipping.mobile }</p>
- <p className="mt-1 text-gray_r-11">{ selectedAddress.shipping.street }, { selectedAddress.shipping?.city?.name }</p>
- </div>
- ) }
- </div>
-
- <LineDivider/>
-
- <div className="p-4 flex flex-col gap-y-4">
- {products.map((product, index) => (
- <VariantCard
- data={product}
- openOnClick={false}
- key={index}
- />
- ))}
- </div>
-
- <LineDivider/>
-
- <div className="p-4">
- <div className="flex justify-between items-center">
- <h2>Ringkasan Pesanan</h2>
- <p className="text-gray_r-11 text-caption-1">{products.length} Barang</p>
- </div>
- <hr className="my-4 border-gray_r-6"/>
- <div className="flex flex-col gap-y-4">
- <div className="flex gap-x-2 justify-between">
- <p>Total Belanja</p>
- <p className="font-medium">{currencyFormat(totalAmount)}</p>
- </div>
- <div className="flex gap-x-2 justify-between">
- <p>Total Diskon</p>
- <p className="font-medium text-red_r-11">- {currencyFormat(totalDiscountAmount)}</p>
- </div>
- <div className="flex gap-x-2 justify-between">
- <p>Subtotal</p>
- <p className="font-medium">{currencyFormat(totalAmount - totalDiscountAmount)}</p>
- </div>
- <div className="flex gap-x-2 justify-between">
- <p>PPN 11% (Incl.)</p>
- <p className="font-medium">{currencyFormat((totalAmount - totalDiscountAmount) * 0.11)}</p>
- </div>
- </div>
- <hr className="my-4 border-gray_r-6"/>
- <div className="flex gap-x-2 justify-between mb-4">
- <p>Grand Total</p>
- <p className="font-medium text-yellow_r-11">{currencyFormat(totalAmount - totalDiscountAmount)}</p>
- </div>
- <p className="text-caption-2 text-gray_r-10 mb-2">*) Belum termasuk biaya pengiriman</p>
- <p className="text-caption-2 text-gray_r-10 leading-5">
- Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui <Link href="/">Syarat & Ketentuan</Link> yang berlaku
- </p>
- </div>
-
- <LineDivider/>
-
- <div className="p-4">
- <div className="flex justify-between items-center">
- <h2>Alamat Penagihan</h2>
- <Link className="text-caption-1" href="/my/address?select=invoicing">Pilih Alamat Lain</Link>
- </div>
-
- { selectedAddress.invoicing && (
- <div className="mt-4 text-caption-1">
- <div className="badge-red mb-2">{ selectedAddress.invoicing.type.charAt(0).toUpperCase() + selectedAddress.invoicing.type.slice(1) + ' Address' }</div>
- <p className="font-medium">{ selectedAddress.invoicing.name }</p>
- <p className="mt-2 text-gray_r-11">{ selectedAddress.invoicing.mobile }</p>
- <p className="mt-1 text-gray_r-11">{ selectedAddress.invoicing.street } { selectedAddress.invoicing.street2 }</p>
- </div>
- ) }
- </div>
-
- <LineDivider/>
-
- <div className="p-4">
- <h2>Metode Pembayaran <span className="font-normal text-gray_r-11">(Wajib dipilih)</span></h2>
- <div className="grid gap-y-3 mt-4">
- { payments.map((payment, index) => (
- <button
- type="button"
- className={"text-left border border-gray_r-6 rounded-md p-3 " + (selectedPayment == payment.name && 'border-yellow_r-10 bg-yellow_r-3')}
- onClick={() => setSelectedPayment(payment.name)}
- key={index}
- >
- <p>{payment.name} - {payment.number}</p>
- <p className="mt-1 text-gray_r-11">PT. Indoteknik Dotcom Gemilang</p>
- </button>
- )) }
- </div>
- </div>
-
- <LineDivider/>
-
- <div className="p-4">
- <h2>Purchase Order</h2>
-
- <div className="mt-4 flex gap-x-3">
- <div className="w-6/12">
- <label className="form-label font-normal">
- Dokumen PO
- </label>
- <input
- type="file"
- className="form-input mt-2 h-12"
- accept="image/*,application/pdf"
- onChange={(e) => setPoFile(e.target.files[0])}
- />
- </div>
- <div className="w-6/12">
- <label className="form-label font-normal">Nomor PO</label>
- <input
- type="text"
- className="form-input mt-2 h-12"
- value={poNumber}
- onChange={(e) => setPoNumber(e.target.value)}
- />
- </div>
- </div>
- <p className="text-caption-2 text-gray_r-11 mt-2">Ukuran dokumen PO Maksimal 5MB</p>
- </div>
-
- <LineDivider/>
-
- <div className="flex gap-x-3 p-4">
- <button
- className="flex-1 btn-yellow"
- onClick={checkout}
- disabled={isLoading}
- >
- { isLoading && 'Loading...' }
- { !isLoading && 'Bayar' }
- </button>
- </div>
- </>
- ) }
- </Layout>
- </WithAuth>
- )
-} \ No newline at end of file
diff --git a/src/pages/shop/product/[slug].js b/src/pages/shop/product/[slug].js
deleted file mode 100644
index 61692c1c..00000000
--- a/src/pages/shop/product/[slug].js
+++ /dev/null
@@ -1,305 +0,0 @@
-import Link from "@/components/elements/Link"
-import { useRouter } from "next/router"
-import { useEffect, useState } from "react"
-import Header from "@/components/layouts/Header"
-import apiOdoo from "@/core/utils/apiOdoo"
-import { createSlug, getIdFromSlug } from "@/core/utils/slug"
-import currencyFormat from "@/core/utils/currencyFormat"
-import Layout from "@/components/layouts/Layout"
-import { createOrUpdateItemCart } from "@/core/utils/cart"
-import toast from "react-hot-toast"
-import Footer from "@/components/layouts/Footer"
-import Image from "@/components/elements/Image"
-import LineDivider from "@/components/elements/LineDivider"
-import { HeartIcon as HeartIconSolid } from "@heroicons/react/24/solid"
-import { useAuth } from "@/core/utils/auth"
-import { HeartIcon } from "@heroicons/react/24/outline"
-import LazyLoad from "react-lazy-load"
-import ProductSimilar from "@/components/products/ProductSimilar"
-
-export async function getServerSideProps( context ) {
- const { slug } = context.query
- let product = await apiOdoo('GET', '/api/v1/product/' + getIdFromSlug(slug))
- if (product?.length == 1) {
- product = product[0]
- product.description = product.description.replaceAll('<p>', '||p||')
- product.description = product.description.replaceAll('</p>', '||/p||')
- product.description = product.description.replace(/(<([^>]+)>)/gi, ' ')
- product.description = product.description.replaceAll('||p||', '<p>')
- product.description = product.description.replaceAll('||/p||', '</p>')
- product.description = product.description.trim()
- }
- return { props: { product } }
-}
-
-export default function ProductDetail({ product }) {
- const [ auth ] = useAuth()
- const router = useRouter()
- const { slug } = router.query
- const [selectedVariant, setSelectedVariant] = useState("")
- const [quantity, setQuantity] = useState("1")
- const [activeVariant, setActiveVariant] = useState({
- id: product.id,
- code: product.code,
- price: product.lowest_price,
- stock: product.stock_total,
- weight: product.weight,
- attributes: '',
- })
-
- const [ isAddedToWishlist, setAddedToWishlist ] = useState(false)
- const [ activeTab, setActiveTab ] = useState('specification')
-
- const addOrDeleteWishlist = async () => {
- if (auth) {
- await apiOdoo('POST', `/api/v1/user/${auth.id}/wishlist/create-or-delete`, {
- product_id: product.id
- })
- if (isAddedToWishlist) {
- toast.success('Berhasil menghapus dari wishlist')
- } else {
- toast.success('Berhasil menambahkan ke wishlist')
- }
- setAddedToWishlist(!isAddedToWishlist)
- } else {
- toast.error('Login terlebih dahulu untuk melanjutkan')
- router.push('/login')
- }
- }
-
- useEffect(() => {
- if (auth) {
- const checkWishlist = async () => {
- const wishlist = await apiOdoo('GET', `/api/v1/user/${auth.id}/wishlist?product_id=${product.id}`)
- setAddedToWishlist(wishlist.product_total > 0 ? true : false)
- }
- checkWishlist()
- }
- }, [ auth, product ])
-
- useEffect(() => {
- if (product.variants.length == 1) {
- setSelectedVariant(product.variants[0].id)
- }
- }, [ product ])
-
- useEffect(() => {
- if (selectedVariant != '') {
- let newActiveVariant = product.variants.filter((variant) => {
- return variant.id == selectedVariant
- })
-
- if (newActiveVariant.length == 1) {
- newActiveVariant = newActiveVariant[0]
- setActiveVariant({
- id: newActiveVariant.id,
- code: newActiveVariant.code,
- price: newActiveVariant.price,
- stock: newActiveVariant.stock,
- weight: newActiveVariant.weight,
- attributes: newActiveVariant.attributes.join(', '),
- })
- }
- }
- }, [selectedVariant, product])
-
- const onchangeVariant = (e) => {
- setSelectedVariant(e.target.value)
- }
-
- const onChangeQuantity = (e) => {
- let inputValue = e.target.value
- inputValue = parseInt(inputValue)
- inputValue = Math.floor(inputValue)
- setQuantity(inputValue)
- }
-
- const addItemToCart = () => {
- if (product.variant_total > 1 && !selectedVariant) {
- toast.error('Pilih varian terlebih dahulu untuk menambahkan ke keranjang', { duration: 2000 })
- return false
- }
-
- if (quantity > 0) {
- toast.success('Berhasil menambahkan ke keranjang', { duration: 1500 })
- createOrUpdateItemCart(activeVariant.id, parseInt(quantity))
- } else {
- toast.error('Jumlah barang yang ditambahkan minimal 1 pcs', { duration: 2000 })
- }
-
- return true
- }
-
- const checkoutProduct = () => {
- if (!auth) {
- toast.error('Login terlebih dahulu untuk melanjutkan', { duration: 2000 })
- router.push('/login')
- return
- }
- if (product.variant_total > 1 && !selectedVariant) {
- toast.error('Pilih varian terlebih dahulu untuk melanjutkan pembelian', { duration: 2000 })
- return
- }
- if (quantity < 0) {
- toast.error('Jumlah barang yang ditambahkan minimal 1 pcs', { duration: 2000 })
- return
- }
- router.push(`/shop/checkout?product_id=${activeVariant.id}&qty=${quantity}`)
- }
-
- const TabButton = ({ children, name }) => (
- <button
- type="button"
- className={`font-medium pb-1 ${activeTab == name ? 'text-red_r-11 border-b border-red_r-10' : 'text-gray_r-11'}`}
- onClick={() => setActiveTab(name)}
- >
- { children }
- </button>
- )
-
- return (
- <>
- <Header title={`${product.name} - Indoteknik`}/>
- <Layout>
- <Image
- src={product.image}
- alt={product.name}
- className="border-b border-gray_r-6 w-full h-[300px] object-contain object-center bg-white"
- />
-
- <div className="p-4">
- <div className="flex justify-between gap-x-3">
- <div>
- <Link href={'/shop/brands/' + createSlug(product.manufacture.name, product.manufacture.id)}>
- {product.manufacture.name ?? '-'}
- </Link>
- <h1 className="h2 mt-2 mb-3">{product.name}{activeVariant.attributes ? ' - ' + activeVariant.attributes : ''}</h1>
- </div>
- <button className="h-fit" onClick={addOrDeleteWishlist}>
- { isAddedToWishlist && (
- <HeartIconSolid className="w-6 text-red_r-10" />
- ) }
- { !isAddedToWishlist && (
- <HeartIcon className="w-6" />
- ) }
- </button>
- </div>
-
- {product.variant_total > 1 && !selectedVariant && product.lowest_price.price > 0 ? (
- <p className="text-caption-2 text-gray-800 mb-1">Harga mulai dari:</p>
- ) : ''}
-
- {product.lowest_price.discount_percentage > 0 ? (
- <div className="flex gap-x-1 items-center mb-1">
- <p className="text-caption-2 text-gray_r-11 line-through">{currencyFormat(activeVariant.price.price)}</p>
- <span className="badge-solid-red">{activeVariant.price.discount_percentage}%</span>
- </div>
- ) : ''}
-
- {product.lowest_price.price > 0 ? (
- <p className="text-body-lg font-semibold">{currencyFormat(activeVariant.price.price_discount)}</p>
- ) : (
- <p className="text-gray_r-11">Dapatkan harga terbaik, <a href="">hubungi kami.</a></p>
- )}
- </div>
-
- <LineDivider />
-
- <div className="p-4">
- <div className="">
- <label className="form-label mb-2">Pilih: <span className="text-gray_r-11 font-normal">{product.variant_total} Varian</span></label>
- <select name="variant" className="form-input" value={selectedVariant} onChange={onchangeVariant} >
- <option value="" disabled={selectedVariant != "" ? true : false}>Pilih Varian...</option>
- {product.variants.length > 1 ? (
- product.variants.map((variant) => {
- return (
- <option key={variant.id} value={variant.id}>{variant.attributes.join(', ')}</option>
- )
- })
- ) : (
- <option key={product.variants[0].id} value={product.variants[0].id}>{product.variants[0].name}</option>
- )}
- </select>
- </div>
-
- <label htmlFor="quantity" className="form-label mb-1 mt-3">Jumlah</label>
- <div className="flex gap-x-2 mt-2">
- <input type="number" name="quantity" id="quantity" className="form-input h-full w-5/12 text-center" value={quantity} onChange={onChangeQuantity} />
-
- <button
- className="btn-yellow w-full"
- onClick={addItemToCart}
- disabled={(product.lowest_price.price == 0 ? true : false)}
- >
- Keranjang
- </button>
- <button
- onClick={checkoutProduct}
- className="btn-solid-red w-full"
- >
- Beli
- </button>
- </div>
- </div>
-
- <LineDivider />
-
- <div className="p-4">
- <h2 className="font-bold mb-4">Informasi Produk</h2>
- <div className="flex gap-x-3 mb-4">
- <TabButton name="specification">Spesifikasi</TabButton>
- <TabButton name="description">Deskripsi</TabButton>
- <TabButton name="information">Info Penting</TabButton>
- </div>
-
- <div className={`border border-gray_r-6 rounded divide-y ${activeTab == 'specification' ? 'block' : 'hidden'}`}>
- <ProductSpecification label="Jumlah Varian">
- <p className="text-gray-800">{product.variant_total} Varian</p>
- </ProductSpecification>
- <ProductSpecification label="Nomor SKU">
- <p className="text-gray-800" id="sku_number">SKU-{activeVariant.id}</p>
- </ProductSpecification>
- <ProductSpecification label="Part Number">
- <p className="text-gray-800" id="part_number">{activeVariant.code}</p>
- </ProductSpecification>
- <ProductSpecification label="Stok">
- <div className="flex gap-x-2" id="stock">
- {activeVariant.stock > 0 ? (activeVariant.stock > 5 && (
- <>
- <div className="badge-solid-red">Ready Stock</div>
- <div className="badge-gray">{activeVariant.stock > 5 ? '> 5' : '< 5'}</div>
- </>
- )) : '0'}
- </div>
- </ProductSpecification>
- <ProductSpecification label="Part Number">
- <p className="text-gray-800" id="weight">{activeVariant.weight > 0 ? activeVariant.weight : '1'} KG</p>
- </ProductSpecification>
- </div>
-
- <div
- className={`text-gray-800 leading-7 ${activeTab == 'description' ? 'block' : 'hidden'}`}
- dangerouslySetInnerHTML={{__html: (product.description != '' ? product.description : 'Belum ada deskripsi produk.')}}
- ></div>
- </div>
-
- <LineDivider />
-
- <LazyLoad>
- <ProductSimilar productId={getIdFromSlug(slug || '')} />
- </LazyLoad>
-
- <Footer />
- </Layout>
- </>
- )
-}
-
-const ProductSpecification = ({ children, ...props }) => {
- return (
- <div className="flex p-3 justify-between items-center gap-x-1">
- <h3 className="text-gray-900">{ props.label }</h3>
- { children }
- </div>
- )
-} \ No newline at end of file
diff --git a/src/pages/shop/product/[slug].jsx b/src/pages/shop/product/[slug].jsx
new file mode 100644
index 00000000..84a3c7d4
--- /dev/null
+++ b/src/pages/shop/product/[slug].jsx
@@ -0,0 +1,29 @@
+import Seo from "@/core/components/Seo"
+import BasicLayout from "@/core/components/layouts/BasicLayout"
+import { getIdFromSlug } from "@/core/utils/slug"
+import productApi from "@/lib/product/api/productApi"
+import Product from "@/lib/product/components/Product"
+
+export async function getServerSideProps(context) {
+ const { slug } = context.query
+ let product = await productApi({ id: getIdFromSlug(slug) })
+ if (product?.length == 1) {
+ product = product[0]
+ product.description = product.description.replaceAll('<p>', '||p||')
+ product.description = product.description.replaceAll('</p>', '||/p||')
+ product.description = product.description.replace(/(<([^>]+)>)/gi, ' ')
+ product.description = product.description.replaceAll('||p||', '<p>')
+ product.description = product.description.replaceAll('||/p||', '</p>')
+ product.description = product.description.trim()
+ }
+ return { props: { product } }
+}
+
+export default function ProductDetail({ product }) {
+ return (
+ <BasicLayout>
+ <Seo title={product?.name} />
+ <Product product={product} />
+ </BasicLayout>
+ )
+} \ No newline at end of file
diff --git a/src/pages/shop/quotation/finish.js b/src/pages/shop/quotation/finish.js
deleted file mode 100644
index f7983fef..00000000
--- a/src/pages/shop/quotation/finish.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import WithAuth from "@/components/auth/WithAuth";
-import Link from "@/components/elements/Link";
-import Header from "@/components/layouts/Header";
-import Layout from "@/components/layouts/Layout";
-import { useAuth } from "@/core/utils/auth";
-import { EnvelopeIcon } from "@heroicons/react/24/outline";
-import { useRouter } from "next/router";
-
-export default function FinishQuotation() {
- const router = useRouter();
- const { id } = router.query;
- const [ auth ] = useAuth();
-
- return (
- <WithAuth>
- <Layout>
- <Header title="Penawaran Harga" />
-
- <div className="m-4 px-4 py-6 shadow-md border border-gray_r-3">
- <div className="flex">
- <span className="p-3 mx-auto bg-yellow_r-3 border border-yellow_r-6 rounded">
- <EnvelopeIcon className="w-8 text-yellow_r-11" />
- </span>
- </div>
- <p className="h2 text-center mt-6">
- Terima Kasih { auth?.name }
- </p>
- <p className="text-center mt-3 leading-6 text-gray_r-11">
- Penawaran harga kamu di Indoteknik.com berhasil dikirimkan, tim kami akan segera menghubungi anda.
- </p>
- { id && (
- <Link href={`/my/transaction/${id}`} className="btn-yellow text-gray_r-12 mt-6 w-full">Lihat Penawaran</Link>
- )}
- <Link href="/" className="btn-light text-gray_r-12 mt-2 w-full">Ke Halaman Utama</Link>
- </div>
- </Layout>
- </WithAuth>
- );
-} \ No newline at end of file
diff --git a/src/pages/shop/quotation/index.js b/src/pages/shop/quotation/index.js
deleted file mode 100644
index e1c196db..00000000
--- a/src/pages/shop/quotation/index.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import WithAuth from "@/components/auth/WithAuth";
-import LineDivider from "@/components/elements/LineDivider";
-import Link from "@/components/elements/Link";
-import AppBar from "@/components/layouts/AppBar";
-import Layout from "@/components/layouts/Layout";
-import VariantCard from "@/components/variants/VariantCard";
-import apiOdoo from "@/core/utils/apiOdoo";
-import { useAuth } from "@/core/utils/auth";
-import { deleteItemCart, getCart } from "@/core/utils/cart";
-import currencyFormat from "@/core/utils/currencyFormat";
-import { useRouter } from "next/router";
-import { useEffect, useState } from "react";
-import { toast } from "react-hot-toast";
-
-export default function Quotation() {
- const router = useRouter();
- const [ auth ] = useAuth();
- const [ products, setProducts ] = useState([]);
- const [ totalAmount, setTotalAmount ] = useState(0);
- const [ totalDiscountAmount, setTotalDiscountAmount ] = useState(0);
-
- useEffect(() => {
- const getProducts = async () => {
- let cart = getCart();
- let productIds = Object
- .values(cart)
- .filter((itemCart) => itemCart.selected == true)
- .map((itemCart) => itemCart.product_id);
- if (productIds.length > 0) {
- productIds = productIds.join(',');
- let dataProducts = await apiOdoo('GET', `/api/v1/product_variant/${productIds}`);
- dataProducts = dataProducts.map((product) => ({
- ...product,
- quantity: cart[product.id].quantity,
- selected: cart[product.id].selected,
- }));
- setProducts(dataProducts);
- }
- };
- getProducts();
- }, [ router, auth ]);
-
- useEffect(() => {
- if (products) {
- let calculateTotalAmount = 0;
- let calculateTotalDiscountAmount = 0;
- products.forEach(product => {
- calculateTotalAmount += product.price.price * product.quantity;
- calculateTotalDiscountAmount += (product.price.price - product.price.price_discount) * product.quantity;
- });
- setTotalAmount(calculateTotalAmount);
- setTotalDiscountAmount(calculateTotalDiscountAmount);
- }
- }, [products]);
-
- const submitQuotation = async () => {
- let productOrder = products.map((product) => ({ 'product_id': product.id, 'quantity': product.quantity }));
- let data = {
- 'partner_shipping_id': auth.partner_id,
- 'partner_invoice_id': auth.partner_id,
- 'order_line': JSON.stringify(productOrder)
- };
- const quotation = await apiOdoo('POST', `/api/v1/partner/${auth.partner_id}/sale_order/checkout`, data);
- for (const product of products) {
- deleteItemCart(product.id);
- }
- if (quotation?.id) {
- router.push(`/shop/quotation/finish?id=${quotation.id}`);
- return;
- };
- toast.error('Terdapat kesalahan internal, hubungi kami');
- }
- return (
- <WithAuth>
- <Layout>
- <AppBar title="Penawaran Harga" />
-
- <div className="p-4 flex flex-col gap-y-4">
- <p className="h2">Produk</p>
- {products.map((product, index) => (
- <VariantCard
- data={product}
- openOnClick={false}
- key={index}
- />
- ))}
- </div>
-
- <LineDivider />
-
- <div className="p-4">
- <div className="flex justify-between items-center">
- <p className="h2">Ringkasan Penawaran</p>
- <p className="text-gray_r-11 text-caption-1">{products.length} Barang</p>
- </div>
- <hr className="my-4 border-gray_r-6"/>
- <div className="flex flex-col gap-y-4">
- <div className="flex gap-x-2 justify-between">
- <p>Total Belanja</p>
- <p className="font-medium">{currencyFormat(totalAmount)}</p>
- </div>
- <div className="flex gap-x-2 justify-between">
- <p>Total Diskon</p>
- <p className="font-medium text-red_r-11">{'- ' + currencyFormat(totalDiscountAmount)}</p>
- </div>
- <div className="flex gap-x-2 justify-between">
- <p>Subtotal</p>
- <p className="font-medium">{currencyFormat(totalAmount - totalDiscountAmount)}</p>
- </div>
- <div className="flex gap-x-2 justify-between">
- <p>PPN 11% (Incl.)</p>
- <p className="font-medium">{currencyFormat((totalAmount - totalDiscountAmount) * 0.11)}</p>
- </div>
- </div>
- <hr className="my-4 border-gray_r-6"/>
- <div className="flex gap-x-2 justify-between mb-4">
- <p>Grand Total</p>
- <p className="font-medium text-yellow_r-11">{currencyFormat(totalAmount - totalDiscountAmount)}</p>
- </div>
- <p className="text-caption-2 text-gray_r-10 mb-2">*) Belum termasuk biaya pengiriman</p>
- <p className="text-caption-2 text-gray_r-10 leading-5">
- Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui <Link href="/">Syarat & Ketentuan</Link> yang berlaku
- </p>
- </div>
-
- <LineDivider />
-
- <div className="p-4">
- <button
- type="button"
- className="btn-yellow w-full"
- onClick={submitQuotation}
- >
- Kirim Penawaran
- </button>
- </div>
- </Layout>
- </WithAuth>
- )
-} \ No newline at end of file
diff --git a/src/pages/shop/search.js b/src/pages/shop/search.js
deleted file mode 100644
index 4152bd43..00000000
--- a/src/pages/shop/search.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import axios from "axios";
-import Header from "@/components/layouts/Header";
-import Layout from "@/components/layouts/Layout";
-import Pagination from "@/components/elements/Pagination";
-import ProductCard from "@/components/products/ProductCard";
-import FilterIcon from "@/icons/filter.svg";
-import { useEffect, useState } from "react";
-import Filter from "@/components/elements/Filter";
-import Footer from "@/components/layouts/Footer";
-
-export async function getServerSideProps(context) {
- const {
- q = '*',
- page = 1,
- brand = '',
- category = '',
- price_from = '',
- price_to = '',
- order_by = '',
- } = context.query;
-
- let urlParameter = [
- `page=${page}`,
- `brand=${brand}`,
- `category=${category}`,
- `price_from=${price_from}`,
- `price_to=${price_to}`,
- `order_by=${order_by}`
- ].join('&');
- let searchResults = await axios(`${process.env.SELF_HOST}/api/shop/search?q=${q}&${urlParameter}`);
- searchResults = searchResults.data;
- return { props: { searchResults, q, page, brand, category, price_from, price_to, order_by } };
-}
-
-export default function ShopSearch({
- searchResults,
- q,
- page,
- brand,
- category,
- price_from,
- price_to,
- order_by
-}) {
- const pageCount = Math.ceil(searchResults.response.numFound / searchResults.responseHeader.params.rows);
- const productStart = searchResults.responseHeader.params.start;
- const productRows = searchResults.responseHeader.params.rows;
- const productFound = searchResults.response.numFound;
-
- // Variable for <Filter/> props state
- const [activeFilter, setActiveFilter] = useState(false);
- const [filterCount, setFilterCount] = useState(0);
-
- const route = () => {
- let route = `/shop/search?q=${q}`;
- if (brand) route += `&brand=${brand}`;
- if (category) route += `&category=${category}`;
- if (price_from) route += `&price_from=${price_from}`;
- if (price_to) route += `&price_to=${price_to}`;
- if (order_by) route += `&order_by=${order_by}`;
- return route;
- }
-
- useEffect(() => {
- let calculateFilterCount = 0;
- if (brand) calculateFilterCount++;
- if (category) calculateFilterCount++;
- if (price_from || price_to) calculateFilterCount++;
- if (order_by) calculateFilterCount++;
- setFilterCount(calculateFilterCount);
- }, [brand, category, price_from, price_to, order_by]);
-
- return (
- <>
- <Header title={`Jual ${q} - Indoteknik`} />
- <Filter
- defaultRoute={`/shop/search?q=${q}`}
- isActive={activeFilter}
- closeFilter={() => setActiveFilter(false)}
- defaultPriceFrom={price_from}
- defaultPriceTo={price_to}
- defaultBrand={brand}
- defaultCategory={category}
- defaultOrderBy={order_by}
- searchResults={searchResults}
- />
- <Layout>
- <div className="p-4">
- <h1 className="mb-2">Produk</h1>
- <div className="text-caption-1 mb-4">
- {productFound > 0 ? (
- <>
- Menampilkan&nbsp;
- {pageCount > 1 ? (
- <>
- {productStart + 1}-{
- (productStart + productRows) > productFound ? productFound : productStart + productRows
- }
- &nbsp;dari&nbsp;
- </>
- ) : ''}
- {searchResults.response.numFound}
- &nbsp;produk { q != '*' && (<>untuk pencarian <span className="font-semibold">{q}</span></>) }
- </>
- ) : 'Mungkin yang anda cari'}
- </div>
- <button className="btn-light py-2 flex items-center gap-x-2 mb-4" onClick={() => setActiveFilter(true)}>
- <FilterIcon className="w-4 h-4" /> <span>Filter {filterCount > 0 ? `(${filterCount})` : ''}</span>
- </button>
- <div className="grid grid-cols-2 gap-3">
- {searchResults.response.products.map((product) => (
- <ProductCard key={product.id} data={product} />
- ))}
- </div>
-
- <div className="mt-4">
- <Pagination pageCount={pageCount} currentPage={parseInt(page)} url={route()} />
- </div>
- </div>
-
- <Footer />
- </Layout>
- </>
- )
-} \ No newline at end of file
diff --git a/src/pages/shop/search.jsx b/src/pages/shop/search.jsx
new file mode 100644
index 00000000..e86b1f4e
--- /dev/null
+++ b/src/pages/shop/search.jsx
@@ -0,0 +1,19 @@
+import BasicLayout from "@/core/components/layouts/BasicLayout"
+import ProductSearch from "@/lib/product/components/ProductSearch"
+import { useRouter } from "next/router"
+import _ from "lodash-contrib"
+
+export default function Search() {
+ const router = useRouter()
+
+ return (
+ <BasicLayout>
+ { !_.isEmpty(router.query) && (
+ <ProductSearch
+ query={router.query}
+ prefixUrl="/shop/search"
+ />
+ ) }
+ </BasicLayout>
+ )
+} \ No newline at end of file