From f99e0aba70efad0deb907d8e27f09fc9f527c8a4 Mon Sep 17 00:00:00 2001 From: Rafi Zadanly Date: Fri, 17 Feb 2023 17:07:50 +0700 Subject: Refactor --- package.json | 5 +- src/components/auth/WithAuth.js | 20 - src/components/elements/Alert.js | 19 - src/components/elements/BottomPopup.js | 25 -- src/components/elements/ConfirmAlert.js | 27 -- src/components/elements/DescriptionRow.js | 10 - src/components/elements/Disclosure.js | 14 - src/components/elements/Fields.js | 21 - src/components/elements/Filter.js | 176 -------- src/components/elements/Image.js | 17 - src/components/elements/LineDivider.js | 7 - src/components/elements/Link.js | 13 - src/components/elements/Pagination.js | 58 --- src/components/elements/ProgressBar.js | 25 -- src/components/elements/Skeleton.js | 48 --- src/components/elements/Spinner.js | 13 - src/components/layouts/AppBar.js | 47 --- src/components/layouts/Footer.js | 91 ---- src/components/layouts/Header.js | 253 ----------- src/components/layouts/Layout.js | 20 - src/components/manufactures/ManufactureCard.js | 18 - src/components/products/ProductCard.js | 69 --- src/components/products/ProductCategories.js | 62 --- src/components/products/ProductSimilar.js | 25 -- src/components/products/ProductSlider.js | 39 -- src/components/transactions/TransactionDetail.js | 67 --- .../transactions/TransactionStatusBadge.js | 45 -- src/components/variants/VariantCard.js | 92 ---- src/components/variants/VariantGroupCard.js | 31 -- src/core/api/odooApi.js | 47 +++ src/core/api/searchSuggestApi.js | 12 + src/core/components/Seo.jsx | 11 + src/core/components/elements/Appbar/Appbar.jsx | 33 ++ src/core/components/elements/Badge/Badge.jsx | 33 ++ src/core/components/elements/Divider/Divider.jsx | 11 + src/core/components/elements/Image/Image.jsx | 15 + src/core/components/elements/Link/Link.jsx | 17 + src/core/components/elements/NavBar/NavBar.jsx | 31 ++ src/core/components/elements/NavBar/Search.jsx | 89 ++++ .../components/elements/Pagination/Pagination.js | 64 +++ src/core/components/elements/Popup/BottomPopup.jsx | 21 + .../components/elements/Skeleton/BrandSkeleton.jsx | 8 + .../components/elements/Skeleton/ImageSkeleton.jsx | 10 + .../elements/Skeleton/ProductCardSkeleton.jsx | 15 + src/core/components/layouts/AnimationLayout.jsx | 22 + src/core/components/layouts/AppLayout.jsx | 15 + src/core/components/layouts/BasicLayout.jsx | 15 + src/core/hooks/useActive.js | 19 + src/core/hooks/useAuth.js | 14 + src/core/utils/address.js | 27 -- src/core/utils/apiOdoo.js | 44 -- src/core/utils/auth.js | 37 +- src/core/utils/cart.js | 39 +- src/core/utils/convertToOption.js | 11 - src/core/utils/currencyFormat.js | 10 +- src/core/utils/formValidation.js | 107 ----- src/core/utils/getFileBase64.js | 11 - src/core/utils/greeting.js | 9 - src/core/utils/mailer.js | 12 - src/core/utils/slug.js | 24 +- src/core/utils/toTitleCase.js | 6 +- src/icons/chevron-left.svg | 3 - src/icons/chevron-right.svg | 3 - src/icons/close.svg | 1 - src/icons/filter.svg | 1 - src/icons/image-placeholder.svg | 1 - src/icons/instagram.svg | 5 - src/icons/linkedin.svg | 5 - src/icons/menu.svg | 1 - src/icons/minus.svg | 3 - src/icons/plus.svg | 4 - src/icons/search.svg | 4 - src/icons/shopping-cart.svg | 1 - src/icons/trash.svg | 4 - src/images/page-not-found.png | Bin 42280 -> 0 bytes src/lib/brand/api/BrandApi.js | 8 + src/lib/brand/components/Brand.jsx | 70 +++ src/lib/brand/components/BrandCard.jsx | 20 + src/lib/brand/hooks/useBrand.js | 13 + src/lib/cart/api/CartApi.js | 11 + src/lib/cart/components/Cart.jsx | 30 ++ src/lib/cart/hooks/useCart.js | 17 + src/lib/elements/hooks/useBottomPopup.js | 40 -- src/lib/elements/hooks/useConfirmAlert.js | 49 --- src/lib/home/api/categoryHomeApi.js | 8 + src/lib/home/api/categoryHomeIdApi.js | 8 + src/lib/home/api/heroBannerApi.js | 8 + src/lib/home/api/popularProductApi.js | 8 + src/lib/home/api/preferredBrandApi.js | 8 + src/lib/home/components/CategoryHome.jsx | 28 ++ src/lib/home/components/CategoryHomeId.jsx | 19 + src/lib/home/components/HeroBanner.jsx | 50 +++ src/lib/home/components/PopularProduct.jsx | 24 ++ src/lib/home/components/PreferredBrand.jsx | 30 ++ .../components/Skeleton/PopularProductSkeleton.jsx | 10 + .../components/Skeleton/PreferredBrandSkeleton.jsx | 12 + src/lib/home/hooks/useCategoryHome.js | 13 + src/lib/home/hooks/useCategoryHomeId.js | 13 + src/lib/home/hooks/useHeroBanner.js | 13 + src/lib/home/hooks/usePopularProduct.js | 13 + src/lib/home/hooks/usePreferredBrand.js | 13 + src/lib/product/api/productApi.js | 8 + src/lib/product/api/productSearchApi.js | 9 + src/lib/product/api/productSimilarApi.js | 8 + src/lib/product/components/Product.jsx | 276 ++++++++++++ src/lib/product/components/ProductCard.jsx | 68 +++ src/lib/product/components/ProductFilter.jsx | 131 ++++++ src/lib/product/components/ProductSearch.jsx | 95 +++++ src/lib/product/components/ProductSimilar.jsx | 15 + src/lib/product/components/ProductSlider.jsx | 51 +++ .../components/Skeleton/ProductSearchSkeleton.jsx | 14 + src/lib/product/hooks/useProductSearch.js | 15 + src/lib/product/hooks/useProductSimilar.js | 13 + src/pages/404.js | 27 -- src/pages/_app.js | 31 -- src/pages/_app.jsx | 36 ++ src/pages/_error.js | 11 - src/pages/activate.js | 111 ----- src/pages/api/activation-request.js | 31 -- src/pages/api/activation.js | 16 - src/pages/api/login.js | 15 - src/pages/api/register.js | 15 - src/pages/api/shop/search.js | 80 ++-- src/pages/api/shop/suggest.js | 9 +- src/pages/api/token.js | 10 - src/pages/faqs.js | 91 ---- src/pages/index.js | 106 ----- src/pages/index.jsx | 20 + src/pages/login.js | 97 ----- src/pages/logout.js | 14 - src/pages/my/address/[id]/edit.js | 249 ----------- src/pages/my/address/create.js | 234 ----------- src/pages/my/address/index.js | 84 ---- src/pages/my/invoice/[id].js | 149 ------- src/pages/my/invoices.js | 180 -------- src/pages/my/menu.js | 82 ---- src/pages/my/profile.js | 134 ------ src/pages/my/transaction/[id].js | 265 ------------ src/pages/my/transactions.js | 198 --------- src/pages/my/wishlist.js | 60 --- src/pages/register.js | 100 ----- src/pages/shop/brands/[slug].js | 178 -------- src/pages/shop/brands/[slug].jsx | 23 + src/pages/shop/brands/index.js | 79 ---- src/pages/shop/cart.js | 282 ------------- src/pages/shop/cart.jsx | 10 + src/pages/shop/checkout/finish.js | 47 --- src/pages/shop/checkout/index.js | 325 -------------- src/pages/shop/product/[slug].js | 305 -------------- src/pages/shop/product/[slug].jsx | 29 ++ src/pages/shop/quotation/finish.js | 39 -- src/pages/shop/quotation/index.js | 140 ------ src/pages/shop/search.js | 125 ------ src/pages/shop/search.jsx | 19 + src/styles/globals.css | 30 +- src2/components/auth/WithAuth.js | 20 + src2/components/elements/Alert.js | 19 + src2/components/elements/BottomPopup.js | 25 ++ src2/components/elements/ConfirmAlert.js | 27 ++ src2/components/elements/DescriptionRow.js | 10 + src2/components/elements/Disclosure.js | 14 + src2/components/elements/Fields.js | 21 + src2/components/elements/Filter.js | 176 ++++++++ src2/components/elements/Image.js | 17 + src2/components/elements/LineDivider.js | 7 + src2/components/elements/Link.js | 13 + src2/components/elements/Pagination.js | 58 +++ src2/components/elements/ProgressBar.js | 25 ++ src2/components/elements/Skeleton.js | 48 +++ src2/components/elements/Spinner.js | 13 + src2/components/layouts/AppBar.js | 47 +++ src2/components/layouts/Footer.js | 91 ++++ src2/components/layouts/Header.js | 253 +++++++++++ src2/components/layouts/Layout.js | 20 + src2/components/manufactures/ManufactureCard.js | 18 + src2/components/products/ProductCard.js | 69 +++ src2/components/products/ProductCategories.js | 62 +++ src2/components/products/ProductSimilar.js | 25 ++ src2/components/products/ProductSlider.js | 39 ++ src2/components/transactions/TransactionDetail.js | 67 +++ .../transactions/TransactionStatusBadge.js | 45 ++ src2/components/variants/VariantCard.js | 92 ++++ src2/components/variants/VariantGroupCard.js | 31 ++ src2/core/utils/address.js | 27 ++ src2/core/utils/apiOdoo.js | 44 ++ src2/core/utils/auth.js | 38 ++ src2/core/utils/cart.js | 36 ++ src2/core/utils/convertToOption.js | 11 + src2/core/utils/currencyFormat.js | 8 + src2/core/utils/formValidation.js | 107 +++++ src2/core/utils/getFileBase64.js | 11 + src2/core/utils/greeting.js | 9 + src2/core/utils/mailer.js | 12 + src2/core/utils/slug.js | 25 ++ src2/core/utils/toTitleCase.js | 8 + src2/icons/chevron-left.svg | 3 + src2/icons/chevron-right.svg | 3 + src2/icons/close.svg | 1 + src2/icons/filter.svg | 1 + src2/icons/image-placeholder.svg | 1 + src2/icons/instagram.svg | 5 + src2/icons/linkedin.svg | 5 + src2/icons/menu.svg | 1 + src2/icons/minus.svg | 3 + src2/icons/plus.svg | 4 + src2/icons/search.svg | 4 + src2/icons/shopping-cart.svg | 1 + src2/icons/trash.svg | 4 + src2/images/logo.png | Bin 0 -> 49879 bytes src2/images/page-not-found.png | Bin 0 -> 42280 bytes src2/lib/elements/hooks/useBottomPopup.js | 40 ++ src2/lib/elements/hooks/useConfirmAlert.js | 49 +++ src2/pages/404.js | 27 ++ src2/pages/_app.js | 31 ++ src2/pages/_error.js | 11 + src2/pages/activate.js | 111 +++++ src2/pages/api/activation-request.js | 31 ++ src2/pages/api/activation.js | 16 + src2/pages/api/login.js | 15 + src2/pages/api/register.js | 15 + src2/pages/api/shop/search.js | 96 +++++ src2/pages/api/shop/suggest.js | 12 + src2/pages/api/token.js | 10 + src2/pages/faqs.js | 91 ++++ src2/pages/index.js | 106 +++++ src2/pages/login.js | 97 +++++ src2/pages/logout.js | 14 + src2/pages/my/address/[id]/edit.js | 249 +++++++++++ src2/pages/my/address/create.js | 234 +++++++++++ src2/pages/my/address/index.js | 84 ++++ src2/pages/my/invoice/[id].js | 149 +++++++ src2/pages/my/invoices.js | 180 ++++++++ src2/pages/my/menu.js | 82 ++++ src2/pages/my/profile.js | 134 ++++++ src2/pages/my/transaction/[id].js | 265 ++++++++++++ src2/pages/my/transactions.js | 198 +++++++++ src2/pages/my/wishlist.js | 60 +++ src2/pages/register.js | 100 +++++ src2/pages/shop/brands/[slug].js | 178 ++++++++ src2/pages/shop/brands/index.js | 79 ++++ src2/pages/shop/cart.js | 282 +++++++++++++ src2/pages/shop/checkout/finish.js | 47 +++ src2/pages/shop/checkout/index.js | 325 ++++++++++++++ src2/pages/shop/product/[slug].js | 305 ++++++++++++++ src2/pages/shop/quotation/finish.js | 39 ++ src2/pages/shop/quotation/index.js | 140 ++++++ src2/pages/shop/search.js | 125 ++++++ src2/styles/globals.css | 468 +++++++++++++++++++++ tailwind.config.js | 8 +- 249 files changed, 8125 insertions(+), 5659 deletions(-) delete mode 100644 src/components/auth/WithAuth.js delete mode 100644 src/components/elements/Alert.js delete mode 100644 src/components/elements/BottomPopup.js delete mode 100644 src/components/elements/ConfirmAlert.js delete mode 100644 src/components/elements/DescriptionRow.js delete mode 100644 src/components/elements/Disclosure.js delete mode 100644 src/components/elements/Fields.js delete mode 100644 src/components/elements/Filter.js delete mode 100644 src/components/elements/Image.js delete mode 100644 src/components/elements/LineDivider.js delete mode 100644 src/components/elements/Link.js delete mode 100644 src/components/elements/Pagination.js delete mode 100644 src/components/elements/ProgressBar.js delete mode 100644 src/components/elements/Skeleton.js delete mode 100644 src/components/elements/Spinner.js delete mode 100644 src/components/layouts/AppBar.js delete mode 100644 src/components/layouts/Footer.js delete mode 100644 src/components/layouts/Header.js delete mode 100644 src/components/layouts/Layout.js delete mode 100644 src/components/manufactures/ManufactureCard.js delete mode 100644 src/components/products/ProductCard.js delete mode 100644 src/components/products/ProductCategories.js delete mode 100644 src/components/products/ProductSimilar.js delete mode 100644 src/components/products/ProductSlider.js delete mode 100644 src/components/transactions/TransactionDetail.js delete mode 100644 src/components/transactions/TransactionStatusBadge.js delete mode 100644 src/components/variants/VariantCard.js delete mode 100644 src/components/variants/VariantGroupCard.js create mode 100644 src/core/api/odooApi.js create mode 100644 src/core/api/searchSuggestApi.js create mode 100644 src/core/components/Seo.jsx create mode 100644 src/core/components/elements/Appbar/Appbar.jsx create mode 100644 src/core/components/elements/Badge/Badge.jsx create mode 100644 src/core/components/elements/Divider/Divider.jsx create mode 100644 src/core/components/elements/Image/Image.jsx create mode 100644 src/core/components/elements/Link/Link.jsx create mode 100644 src/core/components/elements/NavBar/NavBar.jsx create mode 100644 src/core/components/elements/NavBar/Search.jsx create mode 100644 src/core/components/elements/Pagination/Pagination.js create mode 100644 src/core/components/elements/Popup/BottomPopup.jsx create mode 100644 src/core/components/elements/Skeleton/BrandSkeleton.jsx create mode 100644 src/core/components/elements/Skeleton/ImageSkeleton.jsx create mode 100644 src/core/components/elements/Skeleton/ProductCardSkeleton.jsx create mode 100644 src/core/components/layouts/AnimationLayout.jsx create mode 100644 src/core/components/layouts/AppLayout.jsx create mode 100644 src/core/components/layouts/BasicLayout.jsx create mode 100644 src/core/hooks/useActive.js create mode 100644 src/core/hooks/useAuth.js delete mode 100644 src/core/utils/address.js delete mode 100644 src/core/utils/apiOdoo.js delete mode 100644 src/core/utils/convertToOption.js delete mode 100644 src/core/utils/formValidation.js delete mode 100644 src/core/utils/getFileBase64.js delete mode 100644 src/core/utils/greeting.js delete mode 100644 src/core/utils/mailer.js delete mode 100644 src/icons/chevron-left.svg delete mode 100644 src/icons/chevron-right.svg delete mode 100644 src/icons/close.svg delete mode 100644 src/icons/filter.svg delete mode 100644 src/icons/image-placeholder.svg delete mode 100644 src/icons/instagram.svg delete mode 100644 src/icons/linkedin.svg delete mode 100644 src/icons/menu.svg delete mode 100644 src/icons/minus.svg delete mode 100644 src/icons/plus.svg delete mode 100644 src/icons/search.svg delete mode 100644 src/icons/shopping-cart.svg delete mode 100644 src/icons/trash.svg delete mode 100644 src/images/page-not-found.png create mode 100644 src/lib/brand/api/BrandApi.js create mode 100644 src/lib/brand/components/Brand.jsx create mode 100644 src/lib/brand/components/BrandCard.jsx create mode 100644 src/lib/brand/hooks/useBrand.js create mode 100644 src/lib/cart/api/CartApi.js create mode 100644 src/lib/cart/components/Cart.jsx create mode 100644 src/lib/cart/hooks/useCart.js delete mode 100644 src/lib/elements/hooks/useBottomPopup.js delete mode 100644 src/lib/elements/hooks/useConfirmAlert.js create mode 100644 src/lib/home/api/categoryHomeApi.js create mode 100644 src/lib/home/api/categoryHomeIdApi.js create mode 100644 src/lib/home/api/heroBannerApi.js create mode 100644 src/lib/home/api/popularProductApi.js create mode 100644 src/lib/home/api/preferredBrandApi.js create mode 100644 src/lib/home/components/CategoryHome.jsx create mode 100644 src/lib/home/components/CategoryHomeId.jsx create mode 100644 src/lib/home/components/HeroBanner.jsx create mode 100644 src/lib/home/components/PopularProduct.jsx create mode 100644 src/lib/home/components/PreferredBrand.jsx create mode 100644 src/lib/home/components/Skeleton/PopularProductSkeleton.jsx create mode 100644 src/lib/home/components/Skeleton/PreferredBrandSkeleton.jsx create mode 100644 src/lib/home/hooks/useCategoryHome.js create mode 100644 src/lib/home/hooks/useCategoryHomeId.js create mode 100644 src/lib/home/hooks/useHeroBanner.js create mode 100644 src/lib/home/hooks/usePopularProduct.js create mode 100644 src/lib/home/hooks/usePreferredBrand.js create mode 100644 src/lib/product/api/productApi.js create mode 100644 src/lib/product/api/productSearchApi.js create mode 100644 src/lib/product/api/productSimilarApi.js create mode 100644 src/lib/product/components/Product.jsx create mode 100644 src/lib/product/components/ProductCard.jsx create mode 100644 src/lib/product/components/ProductFilter.jsx create mode 100644 src/lib/product/components/ProductSearch.jsx create mode 100644 src/lib/product/components/ProductSimilar.jsx create mode 100644 src/lib/product/components/ProductSlider.jsx create mode 100644 src/lib/product/components/Skeleton/ProductSearchSkeleton.jsx create mode 100644 src/lib/product/hooks/useProductSearch.js create mode 100644 src/lib/product/hooks/useProductSimilar.js delete mode 100644 src/pages/404.js delete mode 100644 src/pages/_app.js create mode 100644 src/pages/_app.jsx delete mode 100644 src/pages/_error.js delete mode 100644 src/pages/activate.js delete mode 100644 src/pages/api/activation-request.js delete mode 100644 src/pages/api/activation.js delete mode 100644 src/pages/api/login.js delete mode 100644 src/pages/api/register.js delete mode 100644 src/pages/api/token.js delete mode 100644 src/pages/faqs.js delete mode 100644 src/pages/index.js create mode 100644 src/pages/index.jsx delete mode 100644 src/pages/login.js delete mode 100644 src/pages/logout.js delete mode 100644 src/pages/my/address/[id]/edit.js delete mode 100644 src/pages/my/address/create.js delete mode 100644 src/pages/my/address/index.js delete mode 100644 src/pages/my/invoice/[id].js delete mode 100644 src/pages/my/invoices.js delete mode 100644 src/pages/my/menu.js delete mode 100644 src/pages/my/profile.js delete mode 100644 src/pages/my/transaction/[id].js delete mode 100644 src/pages/my/transactions.js delete mode 100644 src/pages/my/wishlist.js delete mode 100644 src/pages/register.js delete mode 100644 src/pages/shop/brands/[slug].js create mode 100644 src/pages/shop/brands/[slug].jsx delete mode 100644 src/pages/shop/brands/index.js delete mode 100644 src/pages/shop/cart.js create mode 100644 src/pages/shop/cart.jsx delete mode 100644 src/pages/shop/checkout/finish.js delete mode 100644 src/pages/shop/checkout/index.js delete mode 100644 src/pages/shop/product/[slug].js create mode 100644 src/pages/shop/product/[slug].jsx delete mode 100644 src/pages/shop/quotation/finish.js delete mode 100644 src/pages/shop/quotation/index.js delete mode 100644 src/pages/shop/search.js create mode 100644 src/pages/shop/search.jsx create mode 100644 src2/components/auth/WithAuth.js create mode 100644 src2/components/elements/Alert.js create mode 100644 src2/components/elements/BottomPopup.js create mode 100644 src2/components/elements/ConfirmAlert.js create mode 100644 src2/components/elements/DescriptionRow.js create mode 100644 src2/components/elements/Disclosure.js create mode 100644 src2/components/elements/Fields.js create mode 100644 src2/components/elements/Filter.js create mode 100644 src2/components/elements/Image.js create mode 100644 src2/components/elements/LineDivider.js create mode 100644 src2/components/elements/Link.js create mode 100644 src2/components/elements/Pagination.js create mode 100644 src2/components/elements/ProgressBar.js create mode 100644 src2/components/elements/Skeleton.js create mode 100644 src2/components/elements/Spinner.js create mode 100644 src2/components/layouts/AppBar.js create mode 100644 src2/components/layouts/Footer.js create mode 100644 src2/components/layouts/Header.js create mode 100644 src2/components/layouts/Layout.js create mode 100644 src2/components/manufactures/ManufactureCard.js create mode 100644 src2/components/products/ProductCard.js create mode 100644 src2/components/products/ProductCategories.js create mode 100644 src2/components/products/ProductSimilar.js create mode 100644 src2/components/products/ProductSlider.js create mode 100644 src2/components/transactions/TransactionDetail.js create mode 100644 src2/components/transactions/TransactionStatusBadge.js create mode 100644 src2/components/variants/VariantCard.js create mode 100644 src2/components/variants/VariantGroupCard.js create mode 100644 src2/core/utils/address.js create mode 100644 src2/core/utils/apiOdoo.js create mode 100644 src2/core/utils/auth.js create mode 100644 src2/core/utils/cart.js create mode 100644 src2/core/utils/convertToOption.js create mode 100644 src2/core/utils/currencyFormat.js create mode 100644 src2/core/utils/formValidation.js create mode 100644 src2/core/utils/getFileBase64.js create mode 100644 src2/core/utils/greeting.js create mode 100644 src2/core/utils/mailer.js create mode 100644 src2/core/utils/slug.js create mode 100644 src2/core/utils/toTitleCase.js create mode 100644 src2/icons/chevron-left.svg create mode 100644 src2/icons/chevron-right.svg create mode 100644 src2/icons/close.svg create mode 100644 src2/icons/filter.svg create mode 100644 src2/icons/image-placeholder.svg create mode 100644 src2/icons/instagram.svg create mode 100644 src2/icons/linkedin.svg create mode 100644 src2/icons/menu.svg create mode 100644 src2/icons/minus.svg create mode 100644 src2/icons/plus.svg create mode 100644 src2/icons/search.svg create mode 100644 src2/icons/shopping-cart.svg create mode 100644 src2/icons/trash.svg create mode 100644 src2/images/logo.png create mode 100644 src2/images/page-not-found.png create mode 100644 src2/lib/elements/hooks/useBottomPopup.js create mode 100644 src2/lib/elements/hooks/useConfirmAlert.js create mode 100644 src2/pages/404.js create mode 100644 src2/pages/_app.js create mode 100644 src2/pages/_error.js create mode 100644 src2/pages/activate.js create mode 100644 src2/pages/api/activation-request.js create mode 100644 src2/pages/api/activation.js create mode 100644 src2/pages/api/login.js create mode 100644 src2/pages/api/register.js create mode 100644 src2/pages/api/shop/search.js create mode 100644 src2/pages/api/shop/suggest.js create mode 100644 src2/pages/api/token.js create mode 100644 src2/pages/faqs.js create mode 100644 src2/pages/index.js create mode 100644 src2/pages/login.js create mode 100644 src2/pages/logout.js create mode 100644 src2/pages/my/address/[id]/edit.js create mode 100644 src2/pages/my/address/create.js create mode 100644 src2/pages/my/address/index.js create mode 100644 src2/pages/my/invoice/[id].js create mode 100644 src2/pages/my/invoices.js create mode 100644 src2/pages/my/menu.js create mode 100644 src2/pages/my/profile.js create mode 100644 src2/pages/my/transaction/[id].js create mode 100644 src2/pages/my/transactions.js create mode 100644 src2/pages/my/wishlist.js create mode 100644 src2/pages/register.js create mode 100644 src2/pages/shop/brands/[slug].js create mode 100644 src2/pages/shop/brands/index.js create mode 100644 src2/pages/shop/cart.js create mode 100644 src2/pages/shop/checkout/finish.js create mode 100644 src2/pages/shop/checkout/index.js create mode 100644 src2/pages/shop/product/[slug].js create mode 100644 src2/pages/shop/quotation/finish.js create mode 100644 src2/pages/shop/quotation/index.js create mode 100644 src2/pages/shop/search.js create mode 100644 src2/styles/globals.css diff --git a/package.json b/package.json index da31e920..e3036b5c 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,12 @@ "dependencies": { "@heroicons/react": "^2.0.13", "@hookform/resolvers": "^2.9.10", + "@tailwindcss/line-clamp": "^0.4.2", "axios": "^1.1.3", + "camelcase-object-deep": "^1.1.7", "cookies-next": "^2.1.1", "framer-motion": "^7.6.7", - "lodash": "^4.17.21", + "lodash-contrib": "^4.1200.1", "next": "13.0.0", "next-progress": "^2.2.0", "nodemailer": "^6.8.0", @@ -25,6 +27,7 @@ "react-infinite-scroll-component": "^6.1.0", "react-lazy-load": "^4.0.1", "react-lazy-load-image-component": "^1.5.5", + "react-query": "^3.39.3", "react-select": "^5.7.0", "swiper": "^8.4.4", "yup": "^0.32.11" diff --git a/src/components/auth/WithAuth.js b/src/components/auth/WithAuth.js deleted file mode 100644 index ef975873..00000000 --- a/src/components/auth/WithAuth.js +++ /dev/null @@ -1,20 +0,0 @@ -import { getAuth } from "@/core/utils/auth"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; - -const WithAuth = ({ children }) => { - const router = useRouter(); - const [response, setResponse] = useState(<>); - - useEffect(() => { - if (!getAuth()) { - router.replace('/login'); - } else { - setResponse(children); - } - }, [children, router]); - - return response; -} - -export default WithAuth; \ No newline at end of file diff --git a/src/components/elements/Alert.js b/src/components/elements/Alert.js deleted file mode 100644 index 914d1590..00000000 --- a/src/components/elements/Alert.js +++ /dev/null @@ -1,19 +0,0 @@ -const Alert = ({ children, className, type }) => { - let typeClass = ''; - switch (type) { - case 'info': - typeClass = ' bg-blue-100 text-blue-900 border-blue-400 ' - break; - case 'success': - typeClass = ' bg-green-100 text-green-900 border-green-400 ' - break; - case 'warning': - typeClass = ' bg-yellow-100 text-yellow-900 border-yellow-400 ' - break; - } - return ( -
{children}
- ); -} - -export default Alert; \ No newline at end of file diff --git a/src/components/elements/BottomPopup.js b/src/components/elements/BottomPopup.js deleted file mode 100644 index c1a56e10..00000000 --- a/src/components/elements/BottomPopup.js +++ /dev/null @@ -1,25 +0,0 @@ -import CloseIcon from "@/icons/close.svg"; - -const BottomPopup = ({ - active = false, - title, - children, - closePopup = () => {} -}) => { - return ( - <> -
-
-
-

{ title }

- -
- { children } -
- - ); -}; - -export default BottomPopup; \ No newline at end of file diff --git a/src/components/elements/ConfirmAlert.js b/src/components/elements/ConfirmAlert.js deleted file mode 100644 index d33abb89..00000000 --- a/src/components/elements/ConfirmAlert.js +++ /dev/null @@ -1,27 +0,0 @@ -const ConfirmAlert = ({ - title, - caption, - show, - onClose, - onSubmit, - closeText, - submitText -}) => { - return ( - <> - {show && ( -
- )} -
-

{title}

-

{caption}

-
- - -
-
- - ); -}; - -export default ConfirmAlert; \ No newline at end of file diff --git a/src/components/elements/DescriptionRow.js b/src/components/elements/DescriptionRow.js deleted file mode 100644 index 7fe9e3a1..00000000 --- a/src/components/elements/DescriptionRow.js +++ /dev/null @@ -1,10 +0,0 @@ -const DescriptionRow = ({ label, children }) => ( -
-

{ label }

-
- { children } -
-
-); - -export default DescriptionRow; \ No newline at end of file diff --git a/src/components/elements/Disclosure.js b/src/components/elements/Disclosure.js deleted file mode 100644 index 1f334be3..00000000 --- a/src/components/elements/Disclosure.js +++ /dev/null @@ -1,14 +0,0 @@ -const { ChevronUpIcon, ChevronDownIcon } = require("@heroicons/react/24/outline"); - -const Disclosure = ({ label, active, onClick }) => ( -
-

{ label }

- { onClick && ( active ? ( - - ) : ( - - ) ) } -
-); - -export default Disclosure; \ No newline at end of file diff --git a/src/components/elements/Fields.js b/src/components/elements/Fields.js deleted file mode 100644 index 586a6a22..00000000 --- a/src/components/elements/Fields.js +++ /dev/null @@ -1,21 +0,0 @@ -import ReactSelect from "react-select"; - -const Select = ({ - field, - ...props -}) => ( - <> - field.onChange(option.value)} - value={field.value ? props.options.find(option => option.value === field.value) : ''} - isDisabled={props.disabled} - {...props} - /> - -); - -export { - Select -}; \ No newline at end of file diff --git a/src/components/elements/Filter.js b/src/components/elements/Filter.js deleted file mode 100644 index f2051ba8..00000000 --- a/src/components/elements/Filter.js +++ /dev/null @@ -1,176 +0,0 @@ -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import BottomPopup from "./BottomPopup"; - -const Filter = ({ - isActive, - closeFilter, - defaultRoute, - defaultPriceFrom, - defaultPriceTo, - defaultCategory, - defaultBrand, - defaultOrderBy, - searchResults, - disableFilter = [] -}) => { - const router = useRouter(); - - const [priceFrom, setPriceFrom] = useState(defaultPriceFrom); - const [priceTo, setPriceTo] = useState(defaultPriceTo); - const [orderBy, setOrderBy] = useState(defaultOrderBy); - const [selectedCategory, setSelectedCategory] = useState(defaultCategory); - const [selectedBrand, setSelectedBrand] = useState(defaultBrand); - const [categories, setCategories] = useState([]); - const [brands, setBrands] = useState([]); - - const filterRoute = () => { - let filterRoute = []; - let filterRoutePrefix = '?'; - if (selectedBrand) filterRoute.push(`brand=${selectedBrand}`); - if (selectedCategory) filterRoute.push(`category=${selectedCategory}`); - if (priceFrom) filterRoute.push(`price_from=${priceFrom}`); - if (priceTo) filterRoute.push(`price_to=${priceTo}`); - if (orderBy) filterRoute.push(`order_by=${orderBy}`); - - if (defaultRoute.includes('?')) filterRoutePrefix = '&'; - if (filterRoute.length > 0) { - filterRoute = filterRoutePrefix + filterRoute.join('&'); - } else { - filterRoute = ''; - } - - return defaultRoute + filterRoute; - } - - useEffect(() => { - const filterCategory = searchResults.facet_counts.facet_fields.category_name_str.filter((category, index) => { - if (index % 2 == 0) { - const productCountInCategory = searchResults.facet_counts.facet_fields.category_name_str[index + 1]; - if (productCountInCategory > 0) return category; - } - }); - setCategories(filterCategory); - - const filterBrand = searchResults.facet_counts.facet_fields.brand_str.filter((brand, index) => { - if (index % 2 == 0) { - const productCountInBrand = searchResults.facet_counts.facet_fields.brand_str[index + 1]; - if (productCountInBrand > 0) return brand; - } - }); - setBrands(filterBrand); - }, [searchResults]); - - const submit = (e) => { - e.preventDefault(); - closeFilter(); - router.push(filterRoute(), undefined, { scroll: false }); - } - - const reset = () => { - setSelectedBrand(''); - setSelectedCategory(''); - setPriceFrom(''); - setPriceTo(''); - setOrderBy(''); - } - - const changeOrderBy = (value) => { - if (orderBy == value) { - setOrderBy(''); - } else { - setOrderBy(value); - } - } - - const sortOptions = [ - { - name: 'Harga Terendah', - value: 'price-asc', - }, - { - name: 'Harga Tertinggi', - value: 'price-desc', - }, - { - name: 'Populer', - value: 'popular', - }, - { - name: 'Ready Stock', - value: 'stock', - }, - ]; - - return ( - <> - -
- {(selectedBrand || selectedCategory || priceFrom || priceTo || orderBy) && ( - - )} - - {!disableFilter.includes('orderBy') && ( -
- -
- {sortOptions.map((sortOption, index) => ( - - ))} -
-
- )} - - {!disableFilter.includes('category') && ( -
- - -
- )} - - {!disableFilter.includes('brand') && ( -
- - -
- )} - - {!disableFilter.includes('price') && ( -
- -
- setPriceFrom(e.target.value)}/> - - setPriceTo(e.target.value)}/> -
-
- )} - -
-
- - ) -}; - -export default Filter; \ No newline at end of file diff --git a/src/components/elements/Image.js b/src/components/elements/Image.js deleted file mode 100644 index 60e249b9..00000000 --- a/src/components/elements/Image.js +++ /dev/null @@ -1,17 +0,0 @@ -import { LazyLoadImage } from "react-lazy-load-image-component" -import "react-lazy-load-image-component/src/effects/opacity.css" - -const Image = ({ ...props }) => { - return ( - - ) -} - -Image.defaultProps = LazyLoadImage.defaultProps - -export default Image \ No newline at end of file diff --git a/src/components/elements/LineDivider.js b/src/components/elements/LineDivider.js deleted file mode 100644 index 4e8c7b52..00000000 --- a/src/components/elements/LineDivider.js +++ /dev/null @@ -1,7 +0,0 @@ -const LineDivider = () => { - return ( -
- ); -}; - -export default LineDivider; \ No newline at end of file diff --git a/src/components/elements/Link.js b/src/components/elements/Link.js deleted file mode 100644 index 065b5c9e..00000000 --- a/src/components/elements/Link.js +++ /dev/null @@ -1,13 +0,0 @@ -import NextLink from "next/link"; - -const Link = ({ children, ...props }) => { - return ( - - {children} - - ) -} - -Link.defaultProps = NextLink.defaultProps - -export default Link \ No newline at end of file diff --git a/src/components/elements/Pagination.js b/src/components/elements/Pagination.js deleted file mode 100644 index ff2a8462..00000000 --- a/src/components/elements/Pagination.js +++ /dev/null @@ -1,58 +0,0 @@ -import Link from "./Link"; - -export default function Pagination({ pageCount, currentPage, url }) { - let firstPage = false; - let lastPage = false; - let dotsPrevPage = false; - let dotsNextPage = false; - let urlParameterPrefix = url.includes('?') ? '&' : '?'; - - return pageCount > 1 && ( -
- {Array.from(Array(pageCount)).map((v, i) => { - let page = i + 1; - let rangePrevPage = currentPage - 2; - let rangeNextPage = currentPage + 2; - let PageComponent = {page}; - let DotsComponent =
...
; - - if (pageCount == 7) { - return PageComponent; - } - - if (currentPage == 1) rangeNextPage += 3; - if (currentPage == 2) rangeNextPage += 2; - if (currentPage == 3) rangeNextPage += 1; - if (currentPage == 4) rangePrevPage -= 1; - if (currentPage == pageCount) rangePrevPage -= 3; - if (currentPage == pageCount - 1) rangePrevPage -= 2; - if (currentPage == pageCount - 2) rangePrevPage -= 1; - if (currentPage == pageCount - 3) rangeNextPage += 1; - - if (page > rangePrevPage && page < rangeNextPage) { - return PageComponent; - } - - if (page == 1 && rangePrevPage >= 1 && !firstPage) { - firstPage = true; - return PageComponent; - } - - if (page == pageCount && rangeNextPage <= pageCount && !lastPage) { - lastPage = true; - return PageComponent; - } - - if (page > currentPage && (pageCount - currentPage) > 1 && !dotsNextPage) { - dotsNextPage = true; - return DotsComponent; - } - - if (page < currentPage && (currentPage - 1) > 1 && !dotsPrevPage) { - dotsPrevPage = true; - return DotsComponent; - } - })} -
- ) -} \ No newline at end of file diff --git a/src/components/elements/ProgressBar.js b/src/components/elements/ProgressBar.js deleted file mode 100644 index 0adedcdf..00000000 --- a/src/components/elements/ProgressBar.js +++ /dev/null @@ -1,25 +0,0 @@ -import { Fragment } from "react"; - -const ProgressBar = ({ current, labels }) => { - return ( -
- {labels.map((label, index) => ( - -
-
- { index + 1 } -
-

{ label }

-
- { index < (labels.length - 1) && ( -
-
-
- ) } -
- ))} -
- ) -} - -export default ProgressBar; \ No newline at end of file diff --git a/src/components/elements/Skeleton.js b/src/components/elements/Skeleton.js deleted file mode 100644 index fbdbc245..00000000 --- a/src/components/elements/Skeleton.js +++ /dev/null @@ -1,48 +0,0 @@ -import ImagePlaceholderIcon from "../../icons/image-placeholder.svg"; - -const SkeletonList = ({ number }) => ( -
- { Array.from(Array(number), (e, i) => ( -
-
-
-
-
-
-
- )) } - Loading... -
-); - -const SkeletonProduct = () => ( -
-
-
- -
-
-
-
-
-
- Loading... -
-
-
- -
-
-
-
-
-
- Loading... -
-
-); - -export { - SkeletonList, - SkeletonProduct -}; \ No newline at end of file diff --git a/src/components/elements/Spinner.js b/src/components/elements/Spinner.js deleted file mode 100644 index 21006ecd..00000000 --- a/src/components/elements/Spinner.js +++ /dev/null @@ -1,13 +0,0 @@ -const Spinner = ({ className }) => { - return ( -
- - Loading... -
- ) -} - -export default Spinner; \ No newline at end of file diff --git a/src/components/layouts/AppBar.js b/src/components/layouts/AppBar.js deleted file mode 100644 index fe74c940..00000000 --- a/src/components/layouts/AppBar.js +++ /dev/null @@ -1,47 +0,0 @@ -import { Bars3Icon, ChevronLeftIcon, HomeIcon, ShoppingCartIcon } from "@heroicons/react/24/outline"; -import Head from "next/head"; -import { useRouter } from "next/router"; -import Link from "../elements/Link"; - -const AppBar = ({ title }) => { - const router = useRouter(); - - const handleBackButtonClick = (event) => { - event.currentTarget.disabled = true; - router.back(); - } - - return ( - <> - - { title } - Indoteknik - -
- {/* --- Start Title */} -
- -

{ title }

-
- {/* --- End Title */} - - {/* --- Start Icons */} -
- - - - - - - - - -
- {/* --- End Icons */} -
- - ); -}; - -export default AppBar; \ No newline at end of file diff --git a/src/components/layouts/Footer.js b/src/components/layouts/Footer.js deleted file mode 100644 index d173a525..00000000 --- a/src/components/layouts/Footer.js +++ /dev/null @@ -1,91 +0,0 @@ -import { - PhoneIcon, - DevicePhoneMobileIcon, - EnvelopeIcon -} from "@heroicons/react/24/outline"; -import Image from "next/image"; -import InstagramIcon from "@/icons/instagram.svg"; -import LinkedinIcon from "@/icons/linkedin.svg"; -import Link from "../elements/Link"; - -export default function Footer() { - return ( -
-
-
-

Kantor Pusat

-

- Jl. Bandengan Utara 85A No. 8-9 RT.3/RW.16, Penjaringan, Kec. Penjaringan -

- -

Layanan Informasi

- - - - -

Panduan Pelanggan

-
- FAQ - Kebijakan Privasi - Pengajuan Tempo - Garansi Produk - Online Quotation - Pengiriman - Pembayaran - Syarat & Ketentuan - -
-
-
-

Jam Operasional

-

- Senin - Jumat: 08:30 - 17:00 -

-

- Sabtu: 08:30 - 14:00 -

- -

Temukan Kami

-
- - -
- -

Pembayaran

-
- BCA Payment - BCA Payment - BCA Payment - BCA Payment - BCA Payment - BCA Payment - BCA Payment - BCA Payment -
- - {/*

Pengiriman

*/} -
-
-
PT. Indoteknik Dotcom Gemilang
-
- ); -} \ No newline at end of file diff --git a/src/components/layouts/Header.js b/src/components/layouts/Header.js deleted file mode 100644 index 23fda642..00000000 --- a/src/components/layouts/Header.js +++ /dev/null @@ -1,253 +0,0 @@ -import Image from "next/image"; -import { Fragment, useCallback, useEffect, useRef, useState } from "react"; -import Head from "next/head"; -import { useRouter } from "next/router"; -import axios from "axios"; -import { - MagnifyingGlassIcon, - Bars3Icon, - ShoppingCartIcon, - ChevronRightIcon, - Cog6ToothIcon, - HeartIcon, - ChevronDownIcon, - ChevronUpIcon -} from "@heroicons/react/24/outline"; - -// Helpers -import { useAuth } from "@/core/utils/auth"; -// Components -import Link from "../elements/Link"; -// Images -import Logo from "@/images/logo.png"; -import greeting from "@/core/utils/greeting"; -import apiOdoo from "@/core/utils/apiOdoo"; - -const menus = [ - { name: 'Semua Brand', href: '/shop/brands' }, - { name: 'Blog Indoteknik', href: '/' }, - { name: 'Tentang Indoteknik', href: '/' }, - { name: 'Pusat Bantuan', href: '/' }, -]; - -export default function Header({ title }) { - const router = useRouter(); - const { q = '' } = router.query; - const [searchQuery, setSearchQuery] = useState(q != '*' ? q : ''); - const [suggestions, setSuggestions] = useState([]); - const searchQueryRef = useRef(); - const [isMenuActive, setIsMenuActive] = useState(false); - const [auth] = useAuth(); - - useEffect(() => { - if (q) { - searchQueryRef.current.blur(); - setSuggestions([]); - }; - }, [q]); - - const clickSuggestion = (value) => { - router.push(`/shop/search?q=${value}`, undefined, { scroll: false }); - }; - - const getSuggestion = useCallback(async () => { - if (searchQuery.trim().length > 0) { - let result = await axios(`${process.env.SELF_HOST}/api/shop/suggest?q=${searchQuery.trim()}`); - setSuggestions(result.data.suggest.mySuggester[searchQuery.trim()].suggestions); - } else { - setSuggestions([]); - } - }, [searchQuery]); - - useEffect(() => { - if (document.activeElement == searchQueryRef.current) getSuggestion(); - }, [getSuggestion]); - - const openMenu = () => setIsMenuActive(true); - const closeMenu = () => setIsMenuActive(false); - - const searchSubmit = (e) => { - e.preventDefault(); - if (searchQuery.length > 0) { - router.push(`/shop/search?q=${searchQuery}`, undefined, { scroll: false }); - } else { - searchQueryRef.current.focus(); - } - } - - const [ isOpenCategory, setOpenCategory ] = useState(false); - const [ categories, setCategories ] = useState([]); - - useEffect(() => { - const loadCategories = async () => { - if (isOpenCategory && categories.length == 0) { - let dataCategories = await apiOdoo('GET', '/api/v1/category/tree'); - dataCategories = dataCategories.map((category) => { - category.childs = category.childs.map((child1Category) => { - return { - ...child1Category, - isOpen: false - } - }) - return { - ...category, - isOpen: false - } - }); - setCategories(dataCategories); - } - } - loadCategories(); - }, [ isOpenCategory, categories ]); - - const toggleCategories = (id = 0) => { - let newCategories = categories.map((category) => { - category.childs = category.childs.map((child1Category) => { - return { - ...child1Category, - isOpen: id == child1Category.id ? !child1Category.isOpen : child1Category.isOpen - } - }) - return { - ...category, - isOpen: id == category.id ? !category.isOpen : category.isOpen - } - }); - setCategories(newCategories); - } - - return ( - <> - - - {title} - - -
-
- { auth && ( - -
-

{ greeting() },

-

{auth.name}

-
-
- -
- - ) } - - { !auth && ( - <> - Masuk - Daftar - - ) } -
-
- { menus.map((menu, index) => ( - - { menu.name } -
- -
- - )) } -
setOpenCategory(!isOpenCategory)}> - Kategori -
- { !isOpenCategory && } - { isOpenCategory && } -
-
- { isOpenCategory && categories.map((category) => ( - -
- - { category.name } - -
toggleCategories(category.id)}> - { !category.isOpen && } - { category.isOpen && } -
-
- { category.isOpen && category.childs.map((child1Category) => ( - -
- - { child1Category.name } - - { child1Category.childs.length > 0 && ( -
toggleCategories(child1Category.id)}> - { !child1Category.isOpen && } - { child1Category.isOpen && } -
- ) } -
- { child1Category.isOpen && child1Category.childs.map((child2Category) => ( - - { child2Category.name } - - )) } -
- )) } -
- )) } -
-
- - -
-
- - Logo Indoteknik - -
- - - - - - - -
-
-
- setSearchQuery(e.target.value)} - onFocus={getSuggestion} - value={searchQuery} - className="form-input rounded-r-none border-r-0 focus:border-gray_r-7" - placeholder="Ketikan nama, merek, part number" - autoComplete="off" - /> - - - - {suggestions.length > 1 && ( -
- {suggestions.map((suggestion, index) => ( -

clickSuggestion(suggestion.term)} className="w-full p-2" key={index}>{suggestion.term}

- ))} -
- )} -
-
- - {suggestions.length > 1 && ( -
setSuggestions([])}>
- )} - - ) -} \ No newline at end of file diff --git a/src/components/layouts/Layout.js b/src/components/layouts/Layout.js deleted file mode 100644 index fd507963..00000000 --- a/src/components/layouts/Layout.js +++ /dev/null @@ -1,20 +0,0 @@ -import { motion } from 'framer-motion'; - -export default function Layout({ children, ...pageProps }) { - const transition = { - ease: 'easeOut', - duration: 0.3 - }; - - return children && ( - - {children} - - ); -} \ No newline at end of file diff --git a/src/components/manufactures/ManufactureCard.js b/src/components/manufactures/ManufactureCard.js deleted file mode 100644 index 73a96902..00000000 --- a/src/components/manufactures/ManufactureCard.js +++ /dev/null @@ -1,18 +0,0 @@ -import { createSlug } from "@/core/utils/slug"; -import Image from "../elements/Image"; -import Link from "../elements/Link"; - -export default function ManufactureCard({ data }) { - const manufacture = data; - return ( - - {manufacture.logo ? ( - {manufacture.name} - ) : manufacture.name} - - ); -} \ No newline at end of file diff --git a/src/components/products/ProductCard.js b/src/components/products/ProductCard.js deleted file mode 100644 index c79a4900..00000000 --- a/src/components/products/ProductCard.js +++ /dev/null @@ -1,69 +0,0 @@ -import Link from "../elements/Link"; -import currencyFormat from "@/core/utils/currencyFormat"; -import { createSlug } from "@/core/utils/slug"; -import { ChevronRightIcon } from "@heroicons/react/20/solid"; -import Image from "../elements/Image"; - - -export default function ProductCard({ - data, - simpleProductTitleLine = false -}) { - let product = data; - return ( -
- - {product.name} - {product.variant_total > 1 ? ( -
{product.variant_total} Varian
- ) : ''} - -
-
- {typeof product.manufacture.name !== "undefined" ? ( - {product.manufacture.name} - ) : ( - - - )} - - {product.name} - -
-
- {product.lowest_price.discount_percentage > 0 ? ( -
-

{currencyFormat(product.lowest_price.price)}

- {product.lowest_price.discount_percentage}% -
- ) : ''} - - {product.lowest_price.price_discount > 0 ? ( -

- {currencyFormat(product.lowest_price.price_discount)} -

- ) : ( - - Tanya Harga - - )} - - {product.stock_total > 0 ? ( -
-
Ready Stock
-
{product.stock_total > 5 ? '> 5' : '< 5'}
-
- ) : ''} -
-
-
- ) -} \ No newline at end of file diff --git a/src/components/products/ProductCategories.js b/src/components/products/ProductCategories.js deleted file mode 100644 index 3b671f29..00000000 --- a/src/components/products/ProductCategories.js +++ /dev/null @@ -1,62 +0,0 @@ -import { useEffect, useState } from "react"; -import ProductSlider from "./ProductSlider"; -import apiOdoo from "@/core/utils/apiOdoo"; -import { LazyLoadComponent } from "react-lazy-load-image-component"; -import { SkeletonProduct } from "../elements/Skeleton"; - -const ProductCategory = ({ id }) => { - const [ content, setContent ] = useState(null); - - useEffect(() => { - const loadContent = async () => { - if (!content) { - const dataContent = await apiOdoo('GET', `/api/v1/categories_homepage?id=${id}`); - setContent(dataContent[0]); - } - } - loadContent(); - }, [id, content]); - - return ( -
- { content ? ( - - ) : } -
- ); -} - -export default function ProductCategories() { - const [ contentIds, setContentIds ] = useState([]); - - useEffect(() => { - const getContentIds = async () => { - if (contentIds.length == 0) { - const dataContentIds = await apiOdoo('GET', '/api/v1/categories_homepage/ids'); - setContentIds(dataContentIds); - } - } - getContentIds(); - }, [ contentIds ]); - - return ( -
- { contentIds.map((contentId) => ( - } key={contentId}> - - - )) } -
- ) -} \ No newline at end of file diff --git a/src/components/products/ProductSimilar.js b/src/components/products/ProductSimilar.js deleted file mode 100644 index 9e2292cb..00000000 --- a/src/components/products/ProductSimilar.js +++ /dev/null @@ -1,25 +0,0 @@ -import apiOdoo from '@/core/utils/apiOdoo'; -import { useEffect, useState } from 'react'; -import ProductSlider from './ProductSlider'; - -export default function ProductSimilar({ productId }) { - const [similarProducts, setSimilarProducts] = useState(null); - - useEffect(() => { - const getSimilarProducts = async () => { - if (productId && !similarProducts) { - const dataSimilarProducts = await apiOdoo('GET', `/api/v1/product/${productId}/similar?limit=20`); - setSimilarProducts(dataSimilarProducts); - } - } - getSimilarProducts(); - }, [productId, similarProducts]); - - - return ( -
-

Kamu Mungkin Juga Suka

- -
- ) -} \ No newline at end of file diff --git a/src/components/products/ProductSlider.js b/src/components/products/ProductSlider.js deleted file mode 100644 index 662a6511..00000000 --- a/src/components/products/ProductSlider.js +++ /dev/null @@ -1,39 +0,0 @@ -import { Swiper, SwiperSlide } from "swiper/react"; -import ProductCard from "./ProductCard"; -import "swiper/css"; -import Image from "../elements/Image"; -import Link from "../elements/Link"; -import { SkeletonProduct } from "../elements/Skeleton"; -import { useState } from "react"; - -export default function ProductSlider({ - products, - simpleProductTitleLine = false, - bannerMode = false -}) { - const [ activeIndex, setActiveIndex ] = useState(0); - const swiperSliderFirstMove = (swiper) => { - setActiveIndex(swiper.activeIndex); - }; - - return ( - <> - { bannerMode && ( - {products.banner.name} 0 ? 'opacity-0' : 'opacity-100')} /> - ) } - - { bannerMode && ( - - - - ) } - {products?.products?.map((product, index) => ( - - - - ))} - - { !products ? : ''} - - ) -} \ No newline at end of file diff --git a/src/components/transactions/TransactionDetail.js b/src/components/transactions/TransactionDetail.js deleted file mode 100644 index 295a4f9f..00000000 --- a/src/components/transactions/TransactionDetail.js +++ /dev/null @@ -1,67 +0,0 @@ -import { useState } from "react"; -import DescriptionRow from "../elements/DescriptionRow"; -import Disclosure from "../elements/Disclosure"; - -const DetailAddress = ({ address }) => { - const fullAddress = []; - if (address?.street) fullAddress.push(address.street); - if (address?.sub_district?.name) fullAddress.push(address.sub_district.name); - if (address?.district?.name) fullAddress.push(address.district.name); - if (address?.city?.name) fullAddress.push(address.city.name); - return ( -
- { address?.name } - { address?.email || '-' } - { address?.mobile || '-' } - { fullAddress.join(', ') } -
- ); -}; - -const TransactionDetailAddress = ({ transaction }) => { - const [ activeSection, setActiveSection ] = useState({ - purchase: false, - shipping: false, - invoice: false, - }); - - const toggleSection = ( name ) => { - setActiveSection({ - ...activeSection, - [name]: !activeSection[name] - }); - }; - - return ( -
- toggleSection('purchase')} - /> - { activeSection.purchase && ( - - ) } - - toggleSection('shipping')} - /> - { activeSection.shipping && ( - - ) } - - toggleSection('invoice')} - /> - { activeSection.invoice && ( - - ) } -
- ); -}; - -export { TransactionDetailAddress }; \ No newline at end of file diff --git a/src/components/transactions/TransactionStatusBadge.js b/src/components/transactions/TransactionStatusBadge.js deleted file mode 100644 index f94fd3fd..00000000 --- a/src/components/transactions/TransactionStatusBadge.js +++ /dev/null @@ -1,45 +0,0 @@ -const TransactionStatusBadge = ({ status }) => { - let badgeProps = { - className: ['h-fit'], - text: '' - }; - switch (status) { - case 'cancel': - badgeProps.className.push('badge-solid-red'); - badgeProps.text = 'Pesanan Batal' - break; - case 'draft': - badgeProps.className.push('badge-red'); - badgeProps.text = 'Pending Quotation' - break; - case 'waiting': - badgeProps.className.push('badge-yellow'); - badgeProps.text = 'Pesanan diterima' - break; - case 'sale': - badgeProps.className.push('badge-yellow'); - badgeProps.text = 'Pesanan diproses' - break; - case 'shipping': - badgeProps.className.push('badge-green'); - badgeProps.text = 'Pesanan dikirim' - break; - case 'partial_shipping': - badgeProps.className.push('badge-green'); - badgeProps.text = 'Dikirim sebagian' - break; - case 'done': - badgeProps.className.push('badge-solid-green'); - badgeProps.text = 'Pesanan Selesai' - break; - } - badgeProps.className = badgeProps.className.join(' '); - - return ( -
- { badgeProps.text } -
- ) -}; - -export default TransactionStatusBadge; \ No newline at end of file diff --git a/src/components/variants/VariantCard.js b/src/components/variants/VariantCard.js deleted file mode 100644 index a821480c..00000000 --- a/src/components/variants/VariantCard.js +++ /dev/null @@ -1,92 +0,0 @@ -import { createSlug } from "@/core/utils/slug"; -import Image from "../elements/Image"; -import Link from "../elements/Link"; -import currencyFormat from "@/core/utils/currencyFormat"; -import { useRouter } from "next/router"; -import { toast } from "react-hot-toast"; -import { createOrUpdateItemCart } from "@/core/utils/cart"; - -export default function VariantCard({ - data, - openOnClick = true, - buyMore = false -}) { - let product = data; - const router = useRouter(); - - const addItemToCart = () => { - toast.success('Berhasil menambahkan ke keranjang', { duration: 1500 }); - createOrUpdateItemCart(product.id, 1); - return; - }; - - const checkoutItem = () => { - router.push(`/shop/checkout?product_id=${product.id}&qty=${product.quantity}`); - } - - const Card = () => ( -
-
- {product.parent.name} -
-
-

- {product.parent.name} -

-

- {product.code || '-'} - {product.attributes.length > 0 ? ` ・ ${product.attributes.join(', ')}` : ''} -

-
- {product.price.discount_percentage > 0 && ( - <> -

{currencyFormat(product.price.price)}

- {product.price.discount_percentage}% - - )} -

{currencyFormat(product.price.price_discount)}

-
-

- {currencyFormat(product.price.price_discount)} × {product.quantity} Barang -

-

- {currencyFormat(product.quantity * product.price.price_discount)} -

-
-
- ); - - if (openOnClick) { - return ( - <> - - - - { buyMore && ( -
- - -
- ) } - - ); - } - - return ; -} \ No newline at end of file diff --git a/src/components/variants/VariantGroupCard.js b/src/components/variants/VariantGroupCard.js deleted file mode 100644 index 462c63cf..00000000 --- a/src/components/variants/VariantGroupCard.js +++ /dev/null @@ -1,31 +0,0 @@ -import { useState } from "react" -import VariantCard from "./VariantCard" - -export default function VariantGroupCard({ - variants, - ...props -}) { - const [ showAll, setShowAll ] = useState(false) - const variantsToShow = showAll ? variants : variants.slice(0, 2) - - return ( - <> - { variantsToShow?.map((variant, index) => ( - - )) } - { variants.length > 2 && ( - - ) } - - ) -} \ No newline at end of file diff --git a/src/core/api/odooApi.js b/src/core/api/odooApi.js new file mode 100644 index 00000000..59d88faa --- /dev/null +++ b/src/core/api/odooApi.js @@ -0,0 +1,47 @@ +import axios from 'axios' +import camelcaseObjectDeep from 'camelcase-object-deep' +import { getCookie, setCookie } from 'cookies-next' +import { getAuth } from '../utils/auth' + +const renewToken = async () => { + let token = await axios.get(process.env.ODOO_HOST + '/api/token') + setCookie('token', token.data.result) + return token.data.result +} + +const getToken = async () => { + let token = getCookie('token') + if (token == undefined) token = await renewToken() + return token +} + +const maxConnectionAttempt = 15 +let connectionAttempt = 0 + +const odooApi = async (method, url, data = {}, headers = {}) => { + connectionAttempt++ + try { + let token = await getToken() + const auth = getAuth() + + let axiosParameter = { + method, + url: process.env.ODOO_HOST + url, + headers: {'Authorization': token, ...headers} + } + if (auth) axiosParameter.headers['Token'] = auth.token + if (method.toUpperCase() == 'POST') axiosParameter.headers['Content-Type'] = 'application/x-www-form-urlencoded' + if (Object.keys(data).length > 0) axiosParameter.data = new URLSearchParams(Object.entries(data)).toString() + + let res = await axios(axiosParameter) + if (res.data.status.code == 401 && connectionAttempt < maxConnectionAttempt) { + await renewToken() + return odooApi(method, url, data, headers) + } + return camelcaseObjectDeep(res.data.result) || [] + } catch (error) { + console.log(error) + } +} + +export default odooApi; \ No newline at end of file diff --git a/src/core/api/searchSuggestApi.js b/src/core/api/searchSuggestApi.js new file mode 100644 index 00000000..b5edebda --- /dev/null +++ b/src/core/api/searchSuggestApi.js @@ -0,0 +1,12 @@ +import axios from "axios" + +const searchSuggestApi = async ({ query }) => { + const dataSearchSuggest = await axios(`${process.env.SELF_HOST}/api/shop/suggest?q=${query.trim()}`) + return dataSearchSuggest +} + +searchSuggestApi.defaultProps = { + query: '' +} + +export default searchSuggestApi \ No newline at end of file diff --git a/src/core/components/Seo.jsx b/src/core/components/Seo.jsx new file mode 100644 index 00000000..bcfaa6ef --- /dev/null +++ b/src/core/components/Seo.jsx @@ -0,0 +1,11 @@ +import Head from "next/head" + +const Seo = ({ title }) => { + return ( + + { title } + + ) +} + +export default Seo \ No newline at end of file diff --git a/src/core/components/elements/Appbar/Appbar.jsx b/src/core/components/elements/Appbar/Appbar.jsx new file mode 100644 index 00000000..0fe087d3 --- /dev/null +++ b/src/core/components/elements/Appbar/Appbar.jsx @@ -0,0 +1,33 @@ +import { useRouter } from "next/router" +import Link from "../Link/Link" +import { HomeIcon, Bars3Icon, ShoppingCartIcon, ChevronLeftIcon } from "@heroicons/react/24/outline" + +const AppBar = ({ title }) => { + const router = useRouter() + + return ( + + ) +} + +export default AppBar \ No newline at end of file diff --git a/src/core/components/elements/Badge/Badge.jsx b/src/core/components/elements/Badge/Badge.jsx new file mode 100644 index 00000000..5d8ebd1c --- /dev/null +++ b/src/core/components/elements/Badge/Badge.jsx @@ -0,0 +1,33 @@ +const Badge = ({ + children, + type, + ...props +}) => { + return ( +
+ { children } +
+ ) +} + +Badge.defaultProps = { + className: '' +} + +const badgeStyle = (type) => { + let className = ['rounded px-1 text-[11px]'] + switch (type) { + case 'solid-red': + className.push('bg-red_r-11 text-white') + break + case 'light': + className.push('bg-gray_r-4 text-gray_r-11') + break + } + return className.join(' ') +} + +export default Badge \ No newline at end of file diff --git a/src/core/components/elements/Divider/Divider.jsx b/src/core/components/elements/Divider/Divider.jsx new file mode 100644 index 00000000..355cd509 --- /dev/null +++ b/src/core/components/elements/Divider/Divider.jsx @@ -0,0 +1,11 @@ +const Divider = (props) => { + return ( +
+ ) +} + +Divider.defaultProps = { + className: '' +} + +export default Divider \ No newline at end of file diff --git a/src/core/components/elements/Image/Image.jsx b/src/core/components/elements/Image/Image.jsx new file mode 100644 index 00000000..be2866e7 --- /dev/null +++ b/src/core/components/elements/Image/Image.jsx @@ -0,0 +1,15 @@ +import { LazyLoadImage } from "react-lazy-load-image-component" +import "react-lazy-load-image-component/src/effects/opacity.css" + +const Image = ({ ...props }) => ( + +) + +Image.defaultProps = LazyLoadImage.defaultProps + +export default Image \ No newline at end of file diff --git a/src/core/components/elements/Link/Link.jsx b/src/core/components/elements/Link/Link.jsx new file mode 100644 index 00000000..a619164d --- /dev/null +++ b/src/core/components/elements/Link/Link.jsx @@ -0,0 +1,17 @@ +import NextLink from "next/link" + +const Link = ({ children, ...props }) => { + return ( + + {children} + + ) +} + +Link.defaultProps = NextLink.defaultProps + +export default Link \ No newline at end of file diff --git a/src/core/components/elements/NavBar/NavBar.jsx b/src/core/components/elements/NavBar/NavBar.jsx new file mode 100644 index 00000000..212fd341 --- /dev/null +++ b/src/core/components/elements/NavBar/NavBar.jsx @@ -0,0 +1,31 @@ +import Image from "next/image" +import IndoteknikLogo from "@/images/logo.png" +import { Bars3Icon, HeartIcon, ShoppingCartIcon } from "@heroicons/react/24/outline" +import Link from "../Link/Link" +import Search from "./Search" + +const NavBar = () => { + return ( + + ) +} + +export default NavBar \ No newline at end of file diff --git a/src/core/components/elements/NavBar/Search.jsx b/src/core/components/elements/NavBar/Search.jsx new file mode 100644 index 00000000..cca1a97c --- /dev/null +++ b/src/core/components/elements/NavBar/Search.jsx @@ -0,0 +1,89 @@ +import searchSuggestApi from "@/core/api/searchSuggestApi" +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline" +import { useCallback, useEffect, useRef, useState } from "react" +import Link from "../Link/Link" +import { useRouter } from "next/router" + +const Search = () => { + const router = useRouter() + const queryRef = useRef() + const [ query, setQuery ] = useState('') + const [ suggestions, setSuggestions ] = useState([]) + + useEffect(() => { + setQuery(router.query.q) + }, [router.query]) + + const loadSuggestion = useCallback(() => { + if (query && document.activeElement == queryRef.current) { + (async () => { + const dataSuggestion = await searchSuggestApi({ query }) + setSuggestions(dataSuggestion.data.suggestions) + })() + return + } else { + setSuggestions([]) + } + }, [ query ]) + + useEffect(() => { + if (query && document.activeElement == queryRef.current) { + loadSuggestion() + } else { + setSuggestions([]) + } + }, [ loadSuggestion, query ]) + + const handleSubmit = (e) => { + e.preventDefault() + if (query) { + router.push(`/shop/search?q=${query}`) + } else { + queryRef.current.focus() + } + } + + const onInputBlur = () => { + setTimeout(() => { + setSuggestions([]) + }, 100) + } + + return ( +
+ setQuery(e.target.value)} + onBlur={onInputBlur} + onFocus={loadSuggestion} + /> + + + { suggestions.length > 1 && ( + <> +
+ {suggestions.map((suggestion, index) => ( + + {suggestion.term} + + ))} +
+ + ) } +
+ ) +} + +export default Search \ No newline at end of file diff --git a/src/core/components/elements/Pagination/Pagination.js b/src/core/components/elements/Pagination/Pagination.js new file mode 100644 index 00000000..485295fe --- /dev/null +++ b/src/core/components/elements/Pagination/Pagination.js @@ -0,0 +1,64 @@ +import Link from "../Link/Link" + +const Pagination = ({ pageCount, currentPage, url, className }) => { + let firstPage = false + let lastPage = false + let dotsPrevPage = false + let dotsNextPage = false + let urlParameterPrefix = url.includes('?') ? '&' : '?' + + return pageCount > 1 && ( +
+ { Array.from(Array(pageCount)).map((v, i) => { + let page = i + 1 + let rangePrevPage = currentPage - 2 + let rangeNextPage = currentPage + 2 + let PageComponent = {page} + let DotsComponent =
...
+ + if (pageCount == 7) { + return PageComponent + } + + if (currentPage == 1) rangeNextPage += 3 + if (currentPage == 2) rangeNextPage += 2 + if (currentPage == 3) rangeNextPage += 1 + if (currentPage == 4) rangePrevPage -= 1 + if (currentPage == pageCount) rangePrevPage -= 3 + if (currentPage == pageCount - 1) rangePrevPage -= 2 + if (currentPage == pageCount - 2) rangePrevPage -= 1 + if (currentPage == pageCount - 3) rangeNextPage += 1 + + if (page > rangePrevPage && page < rangeNextPage) { + return PageComponent + } + + if (page == 1 && rangePrevPage >= 1 && !firstPage) { + firstPage = true + return PageComponent + } + + if (page == pageCount && rangeNextPage <= pageCount && !lastPage) { + lastPage = true + return PageComponent + } + + if (page > currentPage && (pageCount - currentPage) > 1 && !dotsNextPage) { + dotsNextPage = true + return DotsComponent + } + + if (page < currentPage && (currentPage - 1) > 1 && !dotsPrevPage) { + dotsPrevPage = true + return DotsComponent + } + }) } +
+ ) +} + +Pagination.defaultProps = { + className: '' +} + +export default Pagination \ No newline at end of file diff --git a/src/core/components/elements/Popup/BottomPopup.jsx b/src/core/components/elements/Popup/BottomPopup.jsx new file mode 100644 index 00000000..e687cf20 --- /dev/null +++ b/src/core/components/elements/Popup/BottomPopup.jsx @@ -0,0 +1,21 @@ +import { XMarkIcon } from "@heroicons/react/24/outline" + +const BottomPopup = ({ children, active, title, close }) => ( + <> +
+
+
+
{ title }
+ +
+ { children } +
+ +) + +export default BottomPopup \ No newline at end of file diff --git a/src/core/components/elements/Skeleton/BrandSkeleton.jsx b/src/core/components/elements/Skeleton/BrandSkeleton.jsx new file mode 100644 index 00000000..ce5a994d --- /dev/null +++ b/src/core/components/elements/Skeleton/BrandSkeleton.jsx @@ -0,0 +1,8 @@ +const BrandSkeleton = () => ( +
+
+ Loading... +
+) + +export default BrandSkeleton \ No newline at end of file diff --git a/src/core/components/elements/Skeleton/ImageSkeleton.jsx b/src/core/components/elements/Skeleton/ImageSkeleton.jsx new file mode 100644 index 00000000..2cda9536 --- /dev/null +++ b/src/core/components/elements/Skeleton/ImageSkeleton.jsx @@ -0,0 +1,10 @@ +const ImageSkeleton = () => ( +
+
+ +
+ Loading... +
+) + +export default ImageSkeleton \ No newline at end of file diff --git a/src/core/components/elements/Skeleton/ProductCardSkeleton.jsx b/src/core/components/elements/Skeleton/ProductCardSkeleton.jsx new file mode 100644 index 00000000..66b48f79 --- /dev/null +++ b/src/core/components/elements/Skeleton/ProductCardSkeleton.jsx @@ -0,0 +1,15 @@ +const ProductCardSkeleton = () => ( +
+
+ +
+
+
+
+
+
+ Loading... +
+) + +export default ProductCardSkeleton \ No newline at end of file diff --git a/src/core/components/layouts/AnimationLayout.jsx b/src/core/components/layouts/AnimationLayout.jsx new file mode 100644 index 00000000..cdd2d059 --- /dev/null +++ b/src/core/components/layouts/AnimationLayout.jsx @@ -0,0 +1,22 @@ +import { motion } from 'framer-motion' + +const AnimationLayout = ({ children, ...props }) => { + const transition = { + ease: 'easeOut', + duration: 0.3 + } + + return children && ( + + { children } + + ) +} + +export default AnimationLayout \ No newline at end of file diff --git a/src/core/components/layouts/AppLayout.jsx b/src/core/components/layouts/AppLayout.jsx new file mode 100644 index 00000000..7aaa52ca --- /dev/null +++ b/src/core/components/layouts/AppLayout.jsx @@ -0,0 +1,15 @@ +import AppBar from "../elements/Appbar/Appbar" +import AnimationLayout from "./AnimationLayout" + +const AppLayout = ({ children, title }) => { + return ( + <> + + + { children } + + + ) +} + +export default AppLayout \ No newline at end of file diff --git a/src/core/components/layouts/BasicLayout.jsx b/src/core/components/layouts/BasicLayout.jsx new file mode 100644 index 00000000..32c785e5 --- /dev/null +++ b/src/core/components/layouts/BasicLayout.jsx @@ -0,0 +1,15 @@ +import NavBar from "../elements/NavBar/NavBar" +import AnimationLayout from "./AnimationLayout" + +const BasicLayout = ({ children }) => { + return ( + <> + + + { children } + + + ) +} + +export default BasicLayout \ No newline at end of file diff --git a/src/core/hooks/useActive.js b/src/core/hooks/useActive.js new file mode 100644 index 00000000..e3a371cb --- /dev/null +++ b/src/core/hooks/useActive.js @@ -0,0 +1,19 @@ +import { useState } from "react" + +const useActive = () => { + const [ active, setActive ] = useState(false) + + const activate = () => { + setActive(true) + } + + const deactivate = () => { + setActive(false) + } + + return { + activate, deactivate, active + } +} + +export default useActive \ No newline at end of file diff --git a/src/core/hooks/useAuth.js b/src/core/hooks/useAuth.js new file mode 100644 index 00000000..488562f6 --- /dev/null +++ b/src/core/hooks/useAuth.js @@ -0,0 +1,14 @@ +import { getAuth } from "../utils/auth" + +const useAuth = () => { + const [auth, setAuth] = useState(null) + + useEffect(() => { + const handleIsAuthenticated = () => setAuth(getAuth()) + handleIsAuthenticated() + }, []) + + return [auth, setAuth] +} + +export default useAuth \ No newline at end of file diff --git a/src/core/utils/address.js b/src/core/utils/address.js deleted file mode 100644 index c4a19af5..00000000 --- a/src/core/utils/address.js +++ /dev/null @@ -1,27 +0,0 @@ -const getAddress = () => { - const address = localStorage.getItem('address'); - if (address) return JSON.parse(address); - return {}; -} - -const setAddress = (address) => { - localStorage.setItem('address', JSON.stringify(address)); - return true; -} - -const getItemAddress = (key) => { - let address = getAddress(); - return address[key]; -} - -const createOrUpdateItemAddress = (key, value) => { - let address = getAddress(); - address[key] = value; - setAddress(address); - return true; -} - -export { - getItemAddress, - createOrUpdateItemAddress -}; \ No newline at end of file diff --git a/src/core/utils/apiOdoo.js b/src/core/utils/apiOdoo.js deleted file mode 100644 index 4d0adae3..00000000 --- a/src/core/utils/apiOdoo.js +++ /dev/null @@ -1,44 +0,0 @@ -import { getCookie, setCookie } from 'cookies-next'; -import axios from 'axios'; -import { getAuth } from './auth'; - -const renewToken = async () => { - let token = await axios.get(process.env.SELF_HOST + '/api/token'); - setCookie('token', token.data); - return token.data; -}; - -const getToken = async () => { - let token = getCookie('token'); - if (token == undefined) token = await renewToken(); - return token; -}; - -let connectionTry = 0; -const apiOdoo = async (method, url, data = {}, headers = {}) => { - try { - connectionTry++; - let token = await getToken(); - let axiosParameter = { - method, - url: process.env.ODOO_HOST + url, - headers: {'Authorization': token, ...headers} - } - const auth = getAuth(); - - if (auth) axiosParameter.headers['Token'] = auth.token; - if (method.toUpperCase() == 'POST') axiosParameter.headers['Content-Type'] = 'application/x-www-form-urlencoded'; - if (Object.keys(data).length > 0) axiosParameter.data = new URLSearchParams(Object.entries(data)).toString(); - - let res = await axios(axiosParameter); - if (res.data.status.code == 401 && connectionTry < 15) { - await renewToken(); - return apiOdoo(method, url, data, headers); - } - return res.data.result || []; - } catch (error) { - console.log(error) - } -} - -export default apiOdoo; \ No newline at end of file diff --git a/src/core/utils/auth.js b/src/core/utils/auth.js index 62eba2c0..6aeba02b 100644 --- a/src/core/utils/auth.js +++ b/src/core/utils/auth.js @@ -1,38 +1,29 @@ -import { deleteCookie, getCookie, setCookie } from 'cookies-next'; -import { useEffect, useState } from 'react'; +import { + deleteCookie, + getCookie, + setCookie +} from 'cookies-next' const getAuth = () => { - let auth = getCookie('auth'); + let auth = getCookie('auth') if (auth) { - return JSON.parse(auth); + return JSON.parse(auth) } - return false; + return false } const setAuth = (user) => { - setCookie('auth', JSON.stringify(user)); - return true; + setCookie('auth', JSON.stringify(user)) + return true } const deleteAuth = () => { - deleteCookie('auth'); - return true; -} - -const useAuth = () => { - const [auth, setAuth] = useState(null); - - useEffect(() => { - const handleIsAuthenticated = () => setAuth(getAuth()); - handleIsAuthenticated(); - }, []); - - return [auth, setAuth]; + deleteCookie('auth') + return true } export { getAuth, setAuth, - deleteAuth, - useAuth -}; \ No newline at end of file + deleteAuth +} \ No newline at end of file diff --git a/src/core/utils/cart.js b/src/core/utils/cart.js index 66efcbf2..291d511b 100644 --- a/src/core/utils/cart.js +++ b/src/core/utils/cart.js @@ -1,36 +1,37 @@ const getCart = () => { - const cart = localStorage.getItem('cart'); - if (cart) return JSON.parse(cart); - return {}; + const cart = localStorage.getItem('cart') + if (cart) return JSON.parse(cart) + return {} } const setCart = (cart) => { - localStorage.setItem('cart', JSON.stringify(cart)); - return true; + localStorage.setItem('cart', JSON.stringify(cart)) + return true } -const getItemCart = (product_id) => { - let cart = getCart(); - return cart[product_id]; +const getItemCart = ({ productId }) => { + let cart = getCart() + return cart[productId] } -const createOrUpdateItemCart = (product_id, quantity, selected = false) => { - let cart = getCart(); - cart[product_id] = { product_id, quantity, selected }; - setCart(cart); - return true; +const addItemCart = ({ productId, quantity, selected = false }) => { + let cart = getCart() + quantity = parseInt(quantity) + cart[productId] = { productId, quantity, selected } + setCart(cart) + return true } -const deleteItemCart = (product_id) => { - let cart = getCart(); - delete cart[product_id]; - setCart(cart); - return true; +const deleteItemCart = ({ productId }) => { + let cart = getCart() + delete cart[productId] + setCart(cart) + return true } export { getCart, getItemCart, - createOrUpdateItemCart, + addItemCart, deleteItemCart } \ No newline at end of file diff --git a/src/core/utils/convertToOption.js b/src/core/utils/convertToOption.js deleted file mode 100644 index 08fec08f..00000000 --- a/src/core/utils/convertToOption.js +++ /dev/null @@ -1,11 +0,0 @@ -const convertToOption = (data) => { - if (data) { - return { - value: data.id, - label: data.name, - } - } - return null; -}; - -export default convertToOption; \ No newline at end of file diff --git a/src/core/utils/currencyFormat.js b/src/core/utils/currencyFormat.js index dadeaec6..31f4a8dc 100644 --- a/src/core/utils/currencyFormat.js +++ b/src/core/utils/currencyFormat.js @@ -1,8 +1,10 @@ -export default function currencyFormat(value) { +const currencyFormat = (value) => { const currency = new Intl.NumberFormat('id-ID', { style: 'currency', currency: 'IDR', maximumFractionDigits: 0 - }); - return currency.format(value); -} \ No newline at end of file + }) + return currency.format(value) +} + +export default currencyFormat \ No newline at end of file diff --git a/src/core/utils/formValidation.js b/src/core/utils/formValidation.js deleted file mode 100644 index 0e83f4cc..00000000 --- a/src/core/utils/formValidation.js +++ /dev/null @@ -1,107 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; - -const validateForm = (data, queries, hasChangedInputs = null) => { - let result = { valid: true, errors: {} }; - - for (const query in queries) { - if (!hasChangedInputs || (hasChangedInputs && hasChangedInputs[query])) { - const value = data[query]; - const rules = queries[query]; - let errors = []; - let label = null; - for (const rule of rules) { - let emailValidationRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; - if (rule.startsWith('label:')) { - label = rule.replace('label:', ''); - } else if (rule === 'required' && !value) { - errors.push('tidak boleh kosong'); - } else if (rule === 'email' && !value.match(emailValidationRegex)) { - errors.push('harus format johndoe@example.com'); - } else if (rule.startsWith('maxLength:')) { - let maxLength = parseInt(rule.replace('maxLength:', '')); - if (value && value.length > maxLength) errors.push(`maksimal ${maxLength} karakter`); - } - } - if (errors.length > 0) { - result.errors[query] = (label || query) + ' ' + errors.join(', '); - } - } - } - - if (Object.keys(result.errors).length > 0) { - result.valid = false; - } - - return result; -} - -const useFormValidation = ({ initialFormValue = {}, validationScheme = {} }) => { - const [ formInputs, setFormInputs ] = useState(initialFormValue); - const [ formErrors, setFormErrors ] = useState({}); - const [ formValidation ] = useState(validationScheme); - const [ hasChangedInputs, setHasChangedInputs ] = useState({}); - - const handleFormSubmit = (event, func) => { - if (event) { - event.preventDefault(); - - // Make all input to be has changed mode to revalidate - const changedInputs = {}; - for (const key in formInputs) changedInputs[key] = true; - setHasChangedInputs(changedInputs); - - const { valid, errors } = validateForm(formInputs, formValidation, changedInputs); - setFormErrors(errors); - - if (valid) func(); - } - }; - - const setChangedInput = (name, value = true) => { - setHasChangedInputs((hasChangedInputs) => ({ - ...hasChangedInputs, - [name]: value - })); - }; - - const handleInputChange = (event) => { - setFormInputs((formInputs) => ({ - ...formInputs, - [event.target.name]: event.target.value - })); - setChangedInput(event.target.name); - }; - - const handleSelectChange = useCallback((name, value) => { - setFormInputs((formInputs) => ({ - ...formInputs, - [name]: value - })); - setChangedInput(name); - }, []); - - const handleFormReset = () => { - setFormInputs(initialFormValue); - setFormErrors({}); - setHasChangedInputs({}); - } - - useEffect(() => { - if (formInputs) { - const { errors } = validateForm(formInputs, formValidation, hasChangedInputs); - setFormErrors(errors); - } - }, [ formInputs, formValidation, hasChangedInputs ]) - - return { - handleFormReset, - handleFormSubmit, - handleInputChange, - handleSelectChange, - hasChangedInputs, - formInputs, - formErrors - }; - }; - -export default useFormValidation; \ No newline at end of file diff --git a/src/core/utils/getFileBase64.js b/src/core/utils/getFileBase64.js deleted file mode 100644 index 78013e43..00000000 --- a/src/core/utils/getFileBase64.js +++ /dev/null @@ -1,11 +0,0 @@ -const getFileBase64 = file => new Promise((resolve, reject) => { - let reader = new FileReader(); - reader.readAsBinaryString(file); - reader.onload = () => { - let result = reader.result; - resolve(btoa(result)); - }; - reader.onerror = error => reject(error); -}); - -export default getFileBase64; \ No newline at end of file diff --git a/src/core/utils/greeting.js b/src/core/utils/greeting.js deleted file mode 100644 index 7dc19f8f..00000000 --- a/src/core/utils/greeting.js +++ /dev/null @@ -1,9 +0,0 @@ -const greeting = () => { - let hours = new Date().getHours(); - if (hours < 11) return 'Selamat Pagi'; - if (hours < 15) return 'Selamat Siang'; - if (hours < 18) return 'Selamat Sore'; - return 'Selamat Malam'; -} - -export default greeting; \ No newline at end of file diff --git a/src/core/utils/mailer.js b/src/core/utils/mailer.js deleted file mode 100644 index 4e7ff7cc..00000000 --- a/src/core/utils/mailer.js +++ /dev/null @@ -1,12 +0,0 @@ -const nodemailer = require('nodemailer'); -const mailer = nodemailer.createTransport({ - port: process.env.MAIL_PORT, - host: process.env.MAIL_HOST, - auth: { - user: process.env.MAIL_USER, - pass: process.env.MAIL_PASS - }, - secure: true -}); - -export default mailer; \ No newline at end of file diff --git a/src/core/utils/slug.js b/src/core/utils/slug.js index 0a7d30fc..fab37330 100644 --- a/src/core/utils/slug.js +++ b/src/core/utils/slug.js @@ -1,25 +1,25 @@ -import toTitleCase from './toTitleCase'; +import toTitleCase from './toTitleCase' -const createSlug = (name, id) => { - let slug = name?.trim().replace(new RegExp(/[^A-Za-z0-9]/, 'g'), '-').toLowerCase() + '-' + id; - let splitSlug = slug.split('-'); - let filterSlugFromEmptyChar = splitSlug.filter(x => x != ''); - return filterSlugFromEmptyChar.join('-'); +const createSlug = (prefix, name, id) => { + let slug = name?.trim().replace(new RegExp(/[^A-Za-z0-9]/, 'g'), '-').toLowerCase() + '-' + id + let splitSlug = slug.split('-') + let filterSlugFromEmptyChar = splitSlug.filter(x => x != '') + return prefix + filterSlugFromEmptyChar.join('-') } const getIdFromSlug = (slug) => { - let id = slug.split('-'); - return id[id.length-1]; + let id = slug.split('-') + return id[id.length-1] } const getNameFromSlug = (slug) => { - let name = slug.split('-'); - name.pop(); - return toTitleCase(name.join(' ')); + let name = slug.split('-') + name.pop() + return toTitleCase(name.join(' ')) } export { createSlug, getIdFromSlug, getNameFromSlug -}; \ No newline at end of file +} \ No newline at end of file diff --git a/src/core/utils/toTitleCase.js b/src/core/utils/toTitleCase.js index 5cfd70d0..b2751f0b 100644 --- a/src/core/utils/toTitleCase.js +++ b/src/core/utils/toTitleCase.js @@ -1,8 +1,10 @@ -export default function toTitleCase(str) { +const toTitleCase = (str) => { return str.replace( /\w\S*/g, function(txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); } ); -} \ No newline at end of file +} + +export default toTitleCase \ No newline at end of file diff --git a/src/icons/chevron-left.svg b/src/icons/chevron-left.svg deleted file mode 100644 index a22ce386..00000000 --- a/src/icons/chevron-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/chevron-right.svg b/src/icons/chevron-right.svg deleted file mode 100644 index eb58f2f2..00000000 --- a/src/icons/chevron-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/close.svg b/src/icons/close.svg deleted file mode 100644 index 50e0589d..00000000 --- a/src/icons/close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/icons/filter.svg b/src/icons/filter.svg deleted file mode 100644 index c15ce7b9..00000000 --- a/src/icons/filter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/icons/image-placeholder.svg b/src/icons/image-placeholder.svg deleted file mode 100644 index 935e1097..00000000 --- a/src/icons/image-placeholder.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/icons/instagram.svg b/src/icons/instagram.svg deleted file mode 100644 index d90842c6..00000000 --- a/src/icons/instagram.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/icons/linkedin.svg b/src/icons/linkedin.svg deleted file mode 100644 index a68aec96..00000000 --- a/src/icons/linkedin.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/icons/menu.svg b/src/icons/menu.svg deleted file mode 100644 index 5d067e8e..00000000 --- a/src/icons/menu.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/icons/minus.svg b/src/icons/minus.svg deleted file mode 100644 index 12a10199..00000000 --- a/src/icons/minus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/icons/plus.svg b/src/icons/plus.svg deleted file mode 100644 index 2923c684..00000000 --- a/src/icons/plus.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/icons/search.svg b/src/icons/search.svg deleted file mode 100644 index 6de1cdfa..00000000 --- a/src/icons/search.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/icons/shopping-cart.svg b/src/icons/shopping-cart.svg deleted file mode 100644 index 09f14ca6..00000000 --- a/src/icons/shopping-cart.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/icons/trash.svg b/src/icons/trash.svg deleted file mode 100644 index e23673ee..00000000 --- a/src/icons/trash.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/images/page-not-found.png b/src/images/page-not-found.png deleted file mode 100644 index 296c0443..00000000 Binary files a/src/images/page-not-found.png and /dev/null differ diff --git a/src/lib/brand/api/BrandApi.js b/src/lib/brand/api/BrandApi.js new file mode 100644 index 00000000..15634cc4 --- /dev/null +++ b/src/lib/brand/api/BrandApi.js @@ -0,0 +1,8 @@ +import odooApi from "@/core/api/odooApi" + +const BrandApi = async ({ id }) => { + const dataBrand = await odooApi('GET', `/api/v1/manufacture/${id}`) + return dataBrand +} + +export default BrandApi \ No newline at end of file diff --git a/src/lib/brand/components/Brand.jsx b/src/lib/brand/components/Brand.jsx new file mode 100644 index 00000000..a42f4c81 --- /dev/null +++ b/src/lib/brand/components/Brand.jsx @@ -0,0 +1,70 @@ +import useBrand from "../hooks/useBrand" +import Image from "@/core/components/elements/Image/Image" + +import { Swiper, SwiperSlide } from "swiper/react" +import { Pagination, Autoplay } from "swiper" +import "swiper/css" +import "swiper/css/pagination" +import "swiper/css/autoplay" +import Divider from "@/core/components/elements/Divider/Divider" +import ImageSkeleton from "@/core/components/elements/Skeleton/ImageSkeleton" + +const swiperBanner = { + pagination: { dynamicBullets: true }, + autoplay: { + delay: 6000, + disableOnInteraction: false + }, + modules: [Pagination, Autoplay] +} + +const Brand = ({ id }) => { + const { brand } = useBrand({ id }) + + return ( + <> +
+ { brand.isLoading && } + { brand.data && ( + <> + + { brand.data?.banners?.map((banner, index) => ( + + {`Brand + + )) } + +
+
Produk dari brand:
+ { brand?.data?.logo && ( + {brand?.data?.name} + ) } + { !brand?.data?.logo && ( +
+ { brand?.data?.name } +
+ ) } +
+ + ) } +
+ + + ) +} + +export default Brand \ No newline at end of file diff --git a/src/lib/brand/components/BrandCard.jsx b/src/lib/brand/components/BrandCard.jsx new file mode 100644 index 00000000..8783010e --- /dev/null +++ b/src/lib/brand/components/BrandCard.jsx @@ -0,0 +1,20 @@ +import Image from "@/core/components/elements/Image/Image" +import Link from "@/core/components/elements/Link/Link" +import { createSlug } from "@/core/utils/slug" + +const BrandCard = ({ brand }) => { + return ( + + {brand.name} + + ) +} + +export default BrandCard \ No newline at end of file diff --git a/src/lib/brand/hooks/useBrand.js b/src/lib/brand/hooks/useBrand.js new file mode 100644 index 00000000..be42a44c --- /dev/null +++ b/src/lib/brand/hooks/useBrand.js @@ -0,0 +1,13 @@ +import { useQuery } from "react-query" +import BrandApi from "../api/BrandApi" + +const useBrand = ({ id }) => { + const fetchBrand = async () => await BrandApi({ id }) + const { data, isLoading } = useQuery(`brand-${id}`, fetchBrand) + + return { + brand: { data, isLoading } + } +} + +export default useBrand \ No newline at end of file diff --git a/src/lib/cart/api/CartApi.js b/src/lib/cart/api/CartApi.js new file mode 100644 index 00000000..9a5b5053 --- /dev/null +++ b/src/lib/cart/api/CartApi.js @@ -0,0 +1,11 @@ +import odooApi from "@/core/api/odooApi" + +const CartApi = async ({ variantIds }) => { + if (variantIds) { + const dataCart = await odooApi('GET', `/api/v1/product_variant/${variantIds}`) + return dataCart + } + return null +} + +export default CartApi \ No newline at end of file diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx new file mode 100644 index 00000000..5f9ae1c0 --- /dev/null +++ b/src/lib/cart/components/Cart.jsx @@ -0,0 +1,30 @@ +import Link from "@/core/components/elements/Link/Link" +import useCart from "../hooks/useCart" +import Image from "@/core/components/elements/Image/Image" + +const Cart = () => { + const { cart } = useCart() + + return ( +
+
+

Daftar Produk Belanja

+ Cari Produk Lain +
+
+ { cart.data?.map((product) => ( +
+
+ {product?.name} +
+
+
{ product?.parent?.name }
+
+
+ )) } +
+
+ ) +} + +export default Cart \ No newline at end of file diff --git a/src/lib/cart/hooks/useCart.js b/src/lib/cart/hooks/useCart.js new file mode 100644 index 00000000..44931b8a --- /dev/null +++ b/src/lib/cart/hooks/useCart.js @@ -0,0 +1,17 @@ +import { getCart } from "@/core/utils/cart" +import { useQuery } from "react-query" +import _ from "lodash" +import CartApi from "../api/CartApi" + +const useCart = () => { + const cart = getCart() + const variantIds = _.keys(cart).join(',') + const fetchCart = async () => CartApi({ variantIds }) + const { data, isLoading } = useQuery('cart', fetchCart) + + return { + cart: { data, isLoading } + } +} + +export default useCart \ No newline at end of file diff --git a/src/lib/elements/hooks/useBottomPopup.js b/src/lib/elements/hooks/useBottomPopup.js deleted file mode 100644 index 88b72316..00000000 --- a/src/lib/elements/hooks/useBottomPopup.js +++ /dev/null @@ -1,40 +0,0 @@ -import { useState } from "react"; -import dynamic from "next/dynamic"; - -const DynamicBottomPopup = dynamic(() => import('@/components/elements/BottomPopup')); - -const useBottomPopup = ({ - title, - children -}) => { - const [ isOpen, setIsOpen ] = useState(false); - const [ dataPopup, setDataPopup ] = useState(null); - - const closePopup = () => { - setIsOpen(false); - setDataPopup(null); - }; - const openPopup = ( data = null ) => { - setIsOpen(true); - setDataPopup(data); - }; - - const BottomPopup = ( - - { children(dataPopup) } - - ); - - return { - dataPopup, - BottomPopup, - closePopup, - openPopup - } -} - -export default useBottomPopup; \ No newline at end of file diff --git a/src/lib/elements/hooks/useConfirmAlert.js b/src/lib/elements/hooks/useConfirmAlert.js deleted file mode 100644 index 4975c57d..00000000 --- a/src/lib/elements/hooks/useConfirmAlert.js +++ /dev/null @@ -1,49 +0,0 @@ -import { useState } from "react"; -import dynamic from "next/dynamic"; - -const DynamicConfirmAlert = dynamic(() => import('@/components/elements/ConfirmAlert')); - -const useConfirmAlert = ({ - title, - caption, - closeText, - submitText, - onSubmit, -}) => { - const [ isOpen, setIsOpen ] = useState(false); - const [ data, setData ] = useState(null); - - const closeConfirmAlert = () => { - setIsOpen(false); - setData(null); - }; - const openConfirmAlert = ( data = null ) => { - setIsOpen(true); - setData(data); - }; - const handleSubmit = async () => { - await onSubmit(data); - closeConfirmAlert(); - }; - - const ConfirmAlert = ( - - ); - - return { - isOpen, - closeConfirmAlert, - openConfirmAlert, - ConfirmAlert - }; -} - -export default useConfirmAlert; \ No newline at end of file diff --git a/src/lib/home/api/categoryHomeApi.js b/src/lib/home/api/categoryHomeApi.js new file mode 100644 index 00000000..efb31240 --- /dev/null +++ b/src/lib/home/api/categoryHomeApi.js @@ -0,0 +1,8 @@ +import odooApi from "@/core/api/odooApi" + +const categoryHomeIdApi = async ({ id }) => { + const dataCategoryHomeId = await odooApi('GET', `/api/v1/categories_homepage?id=${id}`) + return dataCategoryHomeId +} + +export default categoryHomeIdApi \ No newline at end of file diff --git a/src/lib/home/api/categoryHomeIdApi.js b/src/lib/home/api/categoryHomeIdApi.js new file mode 100644 index 00000000..d5612195 --- /dev/null +++ b/src/lib/home/api/categoryHomeIdApi.js @@ -0,0 +1,8 @@ +import odooApi from "@/core/api/odooApi" + +const categoryHomeIdApi = async () => { + const dataCategoryHomeIds = await odooApi('GET', '/api/v1/categories_homepage/ids') + return dataCategoryHomeIds +} + +export default categoryHomeIdApi \ No newline at end of file diff --git a/src/lib/home/api/heroBannerApi.js b/src/lib/home/api/heroBannerApi.js new file mode 100644 index 00000000..7ba84bc6 --- /dev/null +++ b/src/lib/home/api/heroBannerApi.js @@ -0,0 +1,8 @@ +import odooApi from "@/core/api/odooApi" + +const heroBannerApi = async () => { + const dataHeroBanners = await odooApi('GET', '/api/v1/banner?type=index-a-1') + return dataHeroBanners +} + +export default heroBannerApi \ No newline at end of file diff --git a/src/lib/home/api/popularProductApi.js b/src/lib/home/api/popularProductApi.js new file mode 100644 index 00000000..d7adca83 --- /dev/null +++ b/src/lib/home/api/popularProductApi.js @@ -0,0 +1,8 @@ +import axios from "axios" + +const popularProductApi = async () => { + const dataPopularProducts = await axios(`${process.env.SELF_HOST}/api/shop/search?q=*&page=1&orderBy=popular`) + return dataPopularProducts.data.response +} + +export default popularProductApi \ No newline at end of file diff --git a/src/lib/home/api/preferredBrandApi.js b/src/lib/home/api/preferredBrandApi.js new file mode 100644 index 00000000..f289f387 --- /dev/null +++ b/src/lib/home/api/preferredBrandApi.js @@ -0,0 +1,8 @@ +import odooApi from "@/core/api/odooApi" + +const preferredBrandApi = async () => { + const dataPreferredBrands = await odooApi('GET', '/api/v1/manufacture?level=prioritas') + return dataPreferredBrands +} + +export default preferredBrandApi \ No newline at end of file diff --git a/src/lib/home/components/CategoryHome.jsx b/src/lib/home/components/CategoryHome.jsx new file mode 100644 index 00000000..0bca9846 --- /dev/null +++ b/src/lib/home/components/CategoryHome.jsx @@ -0,0 +1,28 @@ +import ProductSlider from "@/lib/product/components/ProductSlider" +import useCategoryHome from "../hooks/useCategoryHome" +import PopularProductSkeleton from "./Skeleton/PopularProductSkeleton" + +const CategoryHome = ({ id }) => { + const { categoryHome } = useCategoryHome({ id }) + + return ( +
+ { categoryHome.data ? ( + + ) : } +
+ ) +} + +export default CategoryHome \ No newline at end of file diff --git a/src/lib/home/components/CategoryHomeId.jsx b/src/lib/home/components/CategoryHomeId.jsx new file mode 100644 index 00000000..4cbbd1fc --- /dev/null +++ b/src/lib/home/components/CategoryHomeId.jsx @@ -0,0 +1,19 @@ +import { LazyLoadComponent } from "react-lazy-load-image-component" +import useCategoryHomeId from "../hooks/useCategoryHomeId" +import CategoryHome from "./CategoryHome" + +const CategoryHomeId = () => { + const { categoryHomeIds } = useCategoryHomeId() + + return ( +
+ { categoryHomeIds.data?.map((id) => ( + + + + )) } +
+ ) +} + +export default CategoryHomeId \ No newline at end of file diff --git a/src/lib/home/components/HeroBanner.jsx b/src/lib/home/components/HeroBanner.jsx new file mode 100644 index 00000000..604ca8ac --- /dev/null +++ b/src/lib/home/components/HeroBanner.jsx @@ -0,0 +1,50 @@ +import ImageSkeleton from "@/core/components/elements/Skeleton/ImageSkeleton" +import useHeroBanner from "../hooks/useHeroBanner" +import Image from "@/core/components/elements/Image/Image" + +// Swiper +import { Swiper, SwiperSlide } from "swiper/react" +import { Pagination, Autoplay } from "swiper" +import "swiper/css" +import "swiper/css/pagination" +import "swiper/css/autoplay" + +const swiperBanner = { + pagination: { dynamicBullets: true }, + autoplay: { + delay: 6000, + disableOnInteraction: false + }, + modules: [Pagination, Autoplay] +} + +const HeroBanner = () => { + const { heroBanners } = useHeroBanner() + + return ( +
+ { heroBanners.isLoading && } + { !heroBanners.isLoading && ( + + { heroBanners.data?.map((banner, index) => ( + + {banner.name} + + )) } + + ) } +
+ ) +} + +export default HeroBanner \ No newline at end of file diff --git a/src/lib/home/components/PopularProduct.jsx b/src/lib/home/components/PopularProduct.jsx new file mode 100644 index 00000000..87e47218 --- /dev/null +++ b/src/lib/home/components/PopularProduct.jsx @@ -0,0 +1,24 @@ +import { Swiper, SwiperSlide } from "swiper/react" +import usePopularProduct from "../hooks/usePopularProduct" +import ProductCard from "@/lib/product/components/ProductCard" +import PopularProductSkeleton from "./Skeleton/PopularProductSkeleton" +import ProductSlider from "@/lib/product/components/ProductSlider" + +const PopularProduct = () => { + const { popularProducts } = usePopularProduct() + + return ( +
+
Produk Populer
+ { popularProducts.isLoading && } + { !popularProducts.isLoading && ( + + ) } +
+ ) +} + +export default PopularProduct \ No newline at end of file diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx new file mode 100644 index 00000000..3d3b1b69 --- /dev/null +++ b/src/lib/home/components/PreferredBrand.jsx @@ -0,0 +1,30 @@ +import { Swiper, SwiperSlide } from "swiper/react" +import usePreferredBrand from "../hooks/usePreferredBrand" +import PreferredBrandSkeleton from "./Skeleton/PreferredBrandSkeleton" +import BrandCard from "@/lib/brand/components/BrandCard" + +const PreferredBrand = () => { + const { preferredBrands } = usePreferredBrand() + + return ( +
+
Brand Pilihan
+ { preferredBrands.isLoading && } + { !preferredBrands.isLoading && ( + + { preferredBrands.data?.manufactures.map((brand) => ( + + + + )) } + + ) } +
+ ) +} + +export default PreferredBrand \ No newline at end of file diff --git a/src/lib/home/components/Skeleton/PopularProductSkeleton.jsx b/src/lib/home/components/Skeleton/PopularProductSkeleton.jsx new file mode 100644 index 00000000..c5b0fcaa --- /dev/null +++ b/src/lib/home/components/Skeleton/PopularProductSkeleton.jsx @@ -0,0 +1,10 @@ +import ProductCardSkeleton from "@/core/components/elements/Skeleton/ProductCardSkeleton" + +const PopularProductSkeleton = () => ( +
+ + +
+) + +export default PopularProductSkeleton \ No newline at end of file diff --git a/src/lib/home/components/Skeleton/PreferredBrandSkeleton.jsx b/src/lib/home/components/Skeleton/PreferredBrandSkeleton.jsx new file mode 100644 index 00000000..6bdd3c82 --- /dev/null +++ b/src/lib/home/components/Skeleton/PreferredBrandSkeleton.jsx @@ -0,0 +1,12 @@ +import BrandSkeleton from "@/core/components/elements/Skeleton/BrandSkeleton" + +const PreferredBrandSkeleton = () => ( +
+ + + + +
+) + +export default PreferredBrandSkeleton \ No newline at end of file diff --git a/src/lib/home/hooks/useCategoryHome.js b/src/lib/home/hooks/useCategoryHome.js new file mode 100644 index 00000000..14ef2a0f --- /dev/null +++ b/src/lib/home/hooks/useCategoryHome.js @@ -0,0 +1,13 @@ +import categoryHomeApi from "../api/categoryHomeApi" +import { useQuery } from "react-query" + +const useCategoryHome = ({ id }) => { + const fetchCategoryHome = async () => await categoryHomeApi({ id }) + const { isLoading, data } = useQuery(`categoryHome-${id}`, fetchCategoryHome) + + return { + categoryHome: { data, isLoading } + } +} + +export default useCategoryHome \ No newline at end of file diff --git a/src/lib/home/hooks/useCategoryHomeId.js b/src/lib/home/hooks/useCategoryHomeId.js new file mode 100644 index 00000000..bb61b655 --- /dev/null +++ b/src/lib/home/hooks/useCategoryHomeId.js @@ -0,0 +1,13 @@ +import categoryHomeIdApi from "../api/categoryHomeIdApi" +import { useQuery } from "react-query" + +const useCategoryHomeId = () => { + const fetchCategoryHomeId = async () => await categoryHomeIdApi() + const { isLoading, data } = useQuery("categoryHomeId", fetchCategoryHomeId) + + return { + categoryHomeIds: { data, isLoading } + } +} + +export default useCategoryHomeId \ No newline at end of file diff --git a/src/lib/home/hooks/useHeroBanner.js b/src/lib/home/hooks/useHeroBanner.js new file mode 100644 index 00000000..a15dda60 --- /dev/null +++ b/src/lib/home/hooks/useHeroBanner.js @@ -0,0 +1,13 @@ +import heroBannerApi from "../api/heroBannerApi" +import { useQuery } from "react-query" + +const useHeroBanner = () => { + const fetchHeroBanner = async () => await heroBannerApi() + const { isLoading, data } = useQuery("heroBanner", fetchHeroBanner) + + return { + heroBanners: { data, isLoading } + } +} + +export default useHeroBanner \ No newline at end of file diff --git a/src/lib/home/hooks/usePopularProduct.js b/src/lib/home/hooks/usePopularProduct.js new file mode 100644 index 00000000..f69c2f71 --- /dev/null +++ b/src/lib/home/hooks/usePopularProduct.js @@ -0,0 +1,13 @@ +import popularProductApi from "../api/popularProductApi" +import { useQuery } from "react-query" + +const usePopularProduct = () => { + const fetchPopularProduct = async () => await popularProductApi() + const { data, isLoading } = useQuery('popularProduct', fetchPopularProduct) + + return { + popularProducts: { data, isLoading } + } +} + +export default usePopularProduct \ No newline at end of file diff --git a/src/lib/home/hooks/usePreferredBrand.js b/src/lib/home/hooks/usePreferredBrand.js new file mode 100644 index 00000000..4be9793e --- /dev/null +++ b/src/lib/home/hooks/usePreferredBrand.js @@ -0,0 +1,13 @@ +import preferredBrandApi from "../api/preferredBrandApi" +import { useQuery } from "react-query" + +const usePreferredBrand = () => { + const fetchPreferredBrand = async () => await preferredBrandApi() + const { data, isLoading } = useQuery('preferredBrand', fetchPreferredBrand) + + return { + preferredBrands: { data, isLoading } + } +} + +export default usePreferredBrand \ No newline at end of file diff --git a/src/lib/product/api/productApi.js b/src/lib/product/api/productApi.js new file mode 100644 index 00000000..a543f086 --- /dev/null +++ b/src/lib/product/api/productApi.js @@ -0,0 +1,8 @@ +import odooApi from "@/core/api/odooApi" + +const productApi = async ({ id }) => { + const dataProduct = await odooApi('GET', `/api/v1/product/${id}`) + return dataProduct +} + +export default productApi \ No newline at end of file diff --git a/src/lib/product/api/productSearchApi.js b/src/lib/product/api/productSearchApi.js new file mode 100644 index 00000000..86b2914f --- /dev/null +++ b/src/lib/product/api/productSearchApi.js @@ -0,0 +1,9 @@ +import _ from "lodash-contrib" +import axios from "axios" + +const productSearchApi = async ({ query }) => { + const dataProductSearch = await axios(`${process.env.SELF_HOST}/api/shop/search?${query}`) + return dataProductSearch.data +} + +export default productSearchApi \ No newline at end of file diff --git a/src/lib/product/api/productSimilarApi.js b/src/lib/product/api/productSimilarApi.js new file mode 100644 index 00000000..1449d9ca --- /dev/null +++ b/src/lib/product/api/productSimilarApi.js @@ -0,0 +1,8 @@ +import axios from "axios" + +const productSimilarApi = async ({ query }) => { + const dataProductSimilar = await axios(`${process.env.SELF_HOST}/api/shop/search?q=${query}&page=1&orderBy=popular`) + return dataProductSimilar.data.response +} + +export default productSimilarApi \ No newline at end of file diff --git a/src/lib/product/components/Product.jsx b/src/lib/product/components/Product.jsx new file mode 100644 index 00000000..2a3624e7 --- /dev/null +++ b/src/lib/product/components/Product.jsx @@ -0,0 +1,276 @@ +import Badge from "@/core/components/elements/Badge/Badge" +import Divider from "@/core/components/elements/Divider/Divider" +import Image from "@/core/components/elements/Image/Image" +import Link from "@/core/components/elements/Link/Link" +import currencyFormat from "@/core/utils/currencyFormat" +import { useEffect, useState } from "react" +import Select from "react-select" +import ProductSimilar from "./ProductSimilar" +import LazyLoad from "react-lazy-load" +import { toast } from "react-hot-toast" +import { addItemCart } from "@/core/utils/cart" + +const informationTabOptions = [ + { value: 'specification', label: 'Spesifikasi' }, + { value: 'description', label: 'Deskripsi' }, + { value: 'important', label: 'Info Penting' }, +] + +const Product = ({ product }) => { + const [ quantity, setQuantity ] = useState('1') + const [ selectedVariant, setSelectedVariant ] = useState(null) + const [ informationTab, setInformationTab ] = useState(null) + + const [ activeVariant, setActiveVariant ] = useState({ + id: product.id, + code: product.code, + name: product.name, + price: product.lowestPrice, + stock: product.stockTotal, + weight: product.weight, + }) + + const variantOptions = product.variants?.map((variant) => ({ + value: variant.id, + label: + (variant.code ? `[${variant.code}] ` : '') + + + (variant.attributes.length > 0 ? variant.attributes.join(', ') : product.name) + })) + + useEffect(() => { + if (!selectedVariant && variantOptions.length == 1) { + setSelectedVariant(variantOptions[0]) + } + }, [selectedVariant, variantOptions]) + + useEffect(() => { + if (selectedVariant) { + const variant = product.variants.find(variant => variant.id == selectedVariant.value) + const variantAttributes = variant.attributes.length > 0 ? ' - ' + variant.attributes.join(', ') : '' + console.log(variant); + setActiveVariant({ + id: variant.id, + code: variant.code, + name: variant.parent.name + variantAttributes, + price: variant.price, + stock: variant.stock, + weight: variant.weight + }) + } + }, [selectedVariant, product]) + + useEffect(() => { + if (!informationTab) { + setInformationTab(informationTabOptions[0].value) + } + }, [informationTab]) + + const handleClickCart = () => { + if (!selectedVariant) { + toast.error('Pilih varian terlebih dahulu') + return + } + if (!quantity || quantity < 1 || isNaN(parseInt(quantity))) { + toast.error('Jumlah barang minimal 1') + return + } + addItemCart({ + productId: activeVariant.id, + quantity + }) + toast.success('Berhasil menambahkan ke keranjang') + } + + return ( + <> + {product.name} + +
+ { product.manufacture?.name } +

+ {activeVariant?.name} +

+ { activeVariant?.price?.discountPercentage > 0 && ( +
+
+ {currencyFormat(activeVariant?.price?.priceDiscount)} +
+ + {activeVariant?.price?.discountPercentage}% + +
+ ) } +

+ { activeVariant?.price?.price > 0 ? currencyFormat(activeVariant?.price?.price) : ( + + Hubungi kami untuk dapatkan harga terbaik,  + klik disini + + ) } +

+
+ + + +
+
+ + setQuantity(e.target.value)} + /> +
+ + +
+
+ + + +
+

Informasi Produk

+
+ { informationTabOptions.map((option) => ( + setInformationTab(option.value)} + > + {option.label} + + )) } +
+ + + + {product?.variantTotal} Varian + + + SKU-{product?.id} + + + {activeVariant?.code || '-'} + + + { activeVariant?.stock > 0 && ( + +
Ready Stock
+
+ { activeVariant?.stock > 5 ? '> 5' : '< 5' } +
+
+ ) } + { activeVariant?.stock == 0 && ( + + Tanya Stok + + ) } +
+ + { activeVariant?.weight > 0 && ( + { activeVariant?.weight } KG + ) } + { activeVariant?.weight == 0 && ( + + Tanya Berat + + ) } + +
+ + +
+ + + +
+

Kamu Mungkin Juga Suka

+ + + +
+ + ) +} + +const TabButton = ({ children, active, ...props }) => { + const activeClassName = active ? 'text-red_r-11 border-b border-red_r-11' : 'text-gray_r-11' + return ( + + ) +} + +const TabContent = ({ children, active, className, ...props }) => ( +
+ { children } +
+) + +const SpecificationContent = ({ children, label }) => ( +
+ { label } + { children } +
+) + +export default Product \ No newline at end of file diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx new file mode 100644 index 00000000..86ac3a64 --- /dev/null +++ b/src/lib/product/components/ProductCard.jsx @@ -0,0 +1,68 @@ +import Image from "@/core/components/elements/Image/Image" +import Link from "@/core/components/elements/Link/Link" +import currencyFormat from "@/core/utils/currencyFormat" +import { createSlug } from "@/core/utils/slug" + +const ProductCard = ({ product, simpleTitle }) => { + return ( + <> +
+ + {product?.name} + { product.variantTotal > 1 && ( +
{ product.variantTotal } Varian
+ ) } + +
+ + {product?.manufacture?.name} + + + {product?.name} + + { product?.lowestPrice?.discountPercentage > 0 && ( +
+
+ {currencyFormat(product?.lowestPrice?.price)} +
+
+ {product?.lowestPrice?.discountPercentage}% +
+
+ ) } + +
+ { product?.lowestPrice?.priceDiscount > 0 ? currencyFormat(product?.lowestPrice?.priceDiscount) : ( + Call for price + ) } +
+ { product?.stockTotal > 0 && ( +
+
+ Ready Stock +
+
+ { product?.stockTotal > 5 ? '> 5' : '< 5' } +
+
+ ) } +
+
+ + ) +} + +export default ProductCard \ No newline at end of file diff --git a/src/lib/product/components/ProductFilter.jsx b/src/lib/product/components/ProductFilter.jsx new file mode 100644 index 00000000..023b6a8b --- /dev/null +++ b/src/lib/product/components/ProductFilter.jsx @@ -0,0 +1,131 @@ +import BottomPopup from "@/core/components/elements/Popup/BottomPopup" +import { useRouter } from "next/router" +import { useState } from "react" +import _ from "lodash" +import { toQuery } from "lodash-contrib" + +const orderOptions = [ + { value: 'price-asc', label: 'Harga Terendah' }, + { value: 'price-desc', label: 'Harga Tertinggi' }, + { value: 'popular', label: 'Populer' }, + { value: 'stock', label: 'Ready Stock' }, +] + +const ProductFilter = ({ + active, + close, + brands, + categories, + prefixUrl, + defaultBrand = null +}) => { + const router = useRouter() + const { query } = router + const [ order, setOrder ] = useState(query?.orderBy) + const [ brand, setBrand ] = useState(query?.brand) + const [ category, setCategory ] = useState(query?.category) + const [ priceFrom, setPriceFrom ] = useState(query?.priceFrom) + const [ priceTo, setPriceTo ] = useState(query?.priceTo) + + const handleSubmit = () => { + let params = { + q: router.query.q, + orderBy: order, + brand, + category, + priceFrom, + priceTo + } + params = _.pickBy(params, _.identity) + params = toQuery(params) + router.push(`${prefixUrl}?${params}`) + } + + return ( + +
+ { !defaultBrand && ( +
+ + +
+ ) } +
+ + +
+
+ +
+ { orderOptions.map((orderOption) => ( + + )) } +
+
+
+ +
+ setPriceFrom(e.target.value)} + /> + + setPriceTo(e.target.value)} + /> +
+
+ +
+
+ ) +} + +export default ProductFilter \ No newline at end of file diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx new file mode 100644 index 00000000..14df9864 --- /dev/null +++ b/src/lib/product/components/ProductSearch.jsx @@ -0,0 +1,95 @@ +import { useEffect, useState } from "react" +import useProductSearch from "../hooks/useProductSearch" +import ProductCard from "./ProductCard" +import Pagination from "@/core/components/elements/Pagination/Pagination" +import { toQuery } from "lodash-contrib" +import _ from "lodash" +import ProductSearchSkeleton from "./Skeleton/ProductSearchSkeleton" +import ProductFilter from "./ProductFilter" +import useActive from "@/core/hooks/useActive" + +const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { + const { page = 1 } = query + if (defaultBrand) query.brand = defaultBrand.toLowerCase() + const { productSearch } = useProductSearch({ query }) + const [ products, setProducts ] = useState(null) + const popup = useActive() + + const pageCount = Math.ceil(productSearch.data?.response.numFound / productSearch.data?.responseHeader.params.rows) + const productStart = productSearch.data?.responseHeader.params.start + const productRows = productSearch.data?.responseHeader.params.rows + const productFound = productSearch.data?.response.numFound + + const brands = productSearch.data?.facetCounts?.facetFields?.brandStr?.filter((value, index) => { + if (index % 2 === 0) { + return true + } + }) + const categories = productSearch.data?.facetCounts?.facetFields?.categoryNameStr?.filter((value, index) => { + if (index % 2 === 0) { + return true + } + }) + + useEffect(() => { + if (!products) { + setProducts(productSearch.data?.response?.products) + } + }, [query, products, productSearch]) + + if (productSearch.isLoading) { + return + } + + return ( +
+

Produk

+ +
+ { productFound > 0 ? ( + <> + Menampilkan  + {pageCount > 1 ? ( + <> + {productStart + 1}-{ + (productStart + productRows) > productFound ? productFound : productStart + productRows + } +  dari  + + ) : ''} + { productFound } +  produk { query.q && (<>untuk pencarian { query.q }) } + + ) : 'Mungkin yang anda cari'} +
+ + + +
+ { products && products.map((product) => ( + + )) } +
+ + + + +
+ ) +} + +export default ProductSearch \ No newline at end of file diff --git a/src/lib/product/components/ProductSimilar.jsx b/src/lib/product/components/ProductSimilar.jsx new file mode 100644 index 00000000..89cab536 --- /dev/null +++ b/src/lib/product/components/ProductSimilar.jsx @@ -0,0 +1,15 @@ +import PopularProductSkeleton from "@/lib/home/components/Skeleton/PopularProductSkeleton" +import useProductSimilar from "../hooks/useProductSimilar" +import ProductSlider from "./ProductSlider" + +const ProductSimilar = ({ query }) => { + const { productSimilar } = useProductSimilar({ query }) + + if (productSimilar.isLoading) { + return + } + + return +} + +export default ProductSimilar \ No newline at end of file diff --git a/src/lib/product/components/ProductSlider.jsx b/src/lib/product/components/ProductSlider.jsx new file mode 100644 index 00000000..8d677547 --- /dev/null +++ b/src/lib/product/components/ProductSlider.jsx @@ -0,0 +1,51 @@ +import { Swiper, SwiperSlide } from "swiper/react" +import ProductCard from "./ProductCard" +import "swiper/css" +import Image from "@/core/components/elements/Image/Image" +import Link from "@/core/components/elements/Link/Link" +import { useState } from "react" + +const bannerClassName = 'absolute rounded-r top-0 left-0 h-full max-w-[52%] idt-transition border border-gray_r-6' + +const ProductSlider = ({ + products, + simpleTitle = false, + bannerMode = false +}) => { + const [ activeIndex, setActiveIndex ] = useState(0) + const swiperSliderFirstMove = (swiper) => { + setActiveIndex(swiper.activeIndex) + } + + return ( + <> + { bannerMode && ( + {products.banner.name} 0 ? 'opacity-0' : 'opacity-100'}`} + /> + ) } + + { bannerMode && ( + + + + ) } + { products?.products?.map((product, index) => ( + + + + )) } + + + ) +} + +export default ProductSlider \ No newline at end of file diff --git a/src/lib/product/components/Skeleton/ProductSearchSkeleton.jsx b/src/lib/product/components/Skeleton/ProductSearchSkeleton.jsx new file mode 100644 index 00000000..e51a565c --- /dev/null +++ b/src/lib/product/components/Skeleton/ProductSearchSkeleton.jsx @@ -0,0 +1,14 @@ +import ProductCardSkeleton from "@/core/components/elements/Skeleton/ProductCardSkeleton" + +const ProductSearchSkeleton = () => ( +
+ + + + + + +
+) + +export default ProductSearchSkeleton \ No newline at end of file diff --git a/src/lib/product/hooks/useProductSearch.js b/src/lib/product/hooks/useProductSearch.js new file mode 100644 index 00000000..d23a8098 --- /dev/null +++ b/src/lib/product/hooks/useProductSearch.js @@ -0,0 +1,15 @@ +import { useQuery } from "react-query" +import productSearchApi from "../api/productSearchApi" +import _ from "lodash-contrib" + +const useProductSearch = ({ query }) => { + const queryString = _.toQuery(query) + const fetchProductSearch = async () => await productSearchApi({ query: queryString }) + const { data, isLoading } = useQuery(`productSearch-${queryString}`, fetchProductSearch) + + return { + productSearch: { data, isLoading } + } +} + +export default useProductSearch \ No newline at end of file diff --git a/src/lib/product/hooks/useProductSimilar.js b/src/lib/product/hooks/useProductSimilar.js new file mode 100644 index 00000000..444fec0b --- /dev/null +++ b/src/lib/product/hooks/useProductSimilar.js @@ -0,0 +1,13 @@ +import productSimilarApi from "../api/productSimilarApi" +import { useQuery } from "react-query" + +const useProductSimilar = ({ query }) => { + const fetchProductSimilar = async () => await productSimilarApi({ query }) + const { data, isLoading } = useQuery(`productSimilar-${query}`, fetchProductSimilar) + + return { + productSimilar: { data, isLoading } + } +} + +export default useProductSimilar \ No newline at end of file diff --git a/src/pages/404.js b/src/pages/404.js deleted file mode 100644 index 1e1850f2..00000000 --- a/src/pages/404.js +++ /dev/null @@ -1,27 +0,0 @@ -import Image from "next/image"; -import Link from "@/components/elements/Link"; -import Header from "@/components/layouts/Header"; -import Layout from "@/components/layouts/Layout"; -import PageNotFoundImage from "../images/page-not-found.png"; - -export default function PageNotFound() { - return ( - <> -
- -
- Halaman Tidak Ditemukan - Indoteknik -

Halaman tidak ditemukan

-
- - Kembali ke beranda - - - Tanya admin - -
-
-
- - ); -} \ No newline at end of file diff --git a/src/pages/_app.js b/src/pages/_app.js deleted file mode 100644 index 6a40f4e6..00000000 --- a/src/pages/_app.js +++ /dev/null @@ -1,31 +0,0 @@ -import '../styles/globals.css'; -import NextProgress from 'next-progress'; -import { useRouter } from 'next/router'; -import { AnimatePresence } from 'framer-motion'; -import { Toaster } from "react-hot-toast"; - -function MyApp({ Component, pageProps }) { - const router = useRouter(); - - return ( - <> - - - window.scrollTo(0, 0)} - > - - - - ) -} - -export default MyApp diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx new file mode 100644 index 00000000..33573480 --- /dev/null +++ b/src/pages/_app.jsx @@ -0,0 +1,36 @@ +import '../styles/globals.css' +import NextProgress from 'next-progress' +import { useRouter } from 'next/router' +import { AnimatePresence } from 'framer-motion' +import { Toaster } from "react-hot-toast" +import { QueryClient, QueryClientProvider } from 'react-query' + +const queryClient = new QueryClient() + +function MyApp({ Component, pageProps }) { + const router = useRouter() + + return ( + <> + + + + window.scrollTo(0, 0)} + > + + + + + ) +} + +export default MyApp diff --git a/src/pages/_error.js b/src/pages/_error.js deleted file mode 100644 index 107ddf46..00000000 --- a/src/pages/_error.js +++ /dev/null @@ -1,11 +0,0 @@ -import Header from "@/components/layouts/Header"; -import Layout from "@/components/layouts/Layout"; - -export default function Error() { - return ( - -
- - - ); -} \ No newline at end of file diff --git a/src/pages/activate.js b/src/pages/activate.js deleted file mode 100644 index d9b41bf4..00000000 --- a/src/pages/activate.js +++ /dev/null @@ -1,111 +0,0 @@ -import axios from "axios"; -import Head from "next/head"; -import Image from "next/image"; -import Link from "@/components/elements/Link"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import Alert from "@/components/elements/Alert"; -import Layout from "@/components/layouts/Layout"; -import Spinner from "@/components/elements/Spinner"; -import { setAuth } from "@/core/utils/auth"; -import Logo from "@/images/logo.png"; - -export default function Activate() { - const [email, setEmail] = useState(''); - const [isInputFulfilled, setIsInputFulfilled] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [alert, setAlert] = useState(); - const router = useRouter(); - const { token } = router.query; - - useEffect(() => { - if (router.query.email) setEmail(router.query.email); - }, [router]) - - useEffect(() => { - const activateIfTokenExist = async () => { - if (token) { - let activation = await axios.post(`${process.env.SELF_HOST}/api/activation`, {token}); - if (activation.data.activation) { - setAuth(activation.data.user); - setAlert({ - component: <>Selamat, akun anda berhasil diaktifkan, kembali ke beranda., - type: 'success' - }); - } else { - setAlert({ - component: <>Mohon maaf token sudah tidak aktif, lakukan permintaan aktivasi akun kembali atau masuk jika sudah memiliki akun., - type: 'info' - }); - } - } - } - activateIfTokenExist(); - }, [token]); - - useEffect(() => { - setIsInputFulfilled(email != ''); - }, [email]); - - const activationRequest = async (e) => { - e.preventDefault(); - setIsLoading(true); - let activationRequest = await axios.post(`${process.env.SELF_HOST}/api/activation-request`, {email}); - if (activationRequest.data.activation_request) { - setAlert({ - component: <>Mohon cek email anda untuk aktivasi akun Indoteknik, - type: 'success' - }); - } else { - switch (activationRequest.data.reason) { - case 'NOT_FOUND': - setAlert({ - component: <>Email tersebut belum terdaftar, daftar sekarang., - type: 'info' - }); - break; - case 'ACTIVE': - setAlert({ - component: <>Email tersebut sudah terdaftar dan sudah aktif, masuk sekarang., - type: 'info' - }); - break; - } - } - setIsLoading(false); - } - return ( - <> - - Aktivasi Akun Indoteknik - - - - Logo Indoteknik - -

Aktivasi Akun Indoteknik Anda

-

Link aktivasi akan dikirimkan melalui email

- {alert ? ( - {alert.component} - ) : ''} -
- setEmail(e.target.value)} - autoFocus - /> - -
-
- - ) -} \ No newline at end of file diff --git a/src/pages/api/activation-request.js b/src/pages/api/activation-request.js deleted file mode 100644 index 3f33875c..00000000 --- a/src/pages/api/activation-request.js +++ /dev/null @@ -1,31 +0,0 @@ -import apiOdoo from "@/core/utils/apiOdoo"; -import mailer from "@/core/utils/mailer"; - -export default async function handler(req, res) { - try { - const { email } = req.body; - let result = await apiOdoo( - 'POST', - '/api/v1/user/activation-request', - {email} - ); - if (result.activation_request) { - mailer.sendMail({ - from: 'sales@indoteknik.com', - to: result.user.email, - subject: 'Permintaan Aktivasi Akun Indoteknik', - html: ` -

Permintaan Aktivasi Akun Indoteknik

-
-

Aktivasi akun anda melalui link berikut: Aktivasi Akun

- ` - }); - } - delete result.user; - delete result.token; - res.status(200).json(result); - } catch (error) { - console.log(error); - res.status(400).json({ error: error.message }); - } -} \ No newline at end of file diff --git a/src/pages/api/activation.js b/src/pages/api/activation.js deleted file mode 100644 index 8b22af8d..00000000 --- a/src/pages/api/activation.js +++ /dev/null @@ -1,16 +0,0 @@ -import apiOdoo from "@/core/utils/apiOdoo"; - -export default async function handler(req, res) { - try { - const { token } = req.body; - let result = await apiOdoo( - 'POST', - '/api/v1/user/activation', - {token} - ); - res.status(200).json(result); - } catch (error) { - console.log(error); - res.status(400).json({ error: error.message }); - } -} \ No newline at end of file diff --git a/src/pages/api/login.js b/src/pages/api/login.js deleted file mode 100644 index e02a73cb..00000000 --- a/src/pages/api/login.js +++ /dev/null @@ -1,15 +0,0 @@ -import apiOdoo from "@/core/utils/apiOdoo"; - -export default async function handler(req, res) { - try { - const { email, password } = req.body; - let result = await apiOdoo( - 'POST', - '/api/v1/user/login', - {email, password} - ); - res.status(200).json(result); - } catch (error) { - res.status(400).json({ error: error.message }); - } -} \ No newline at end of file diff --git a/src/pages/api/register.js b/src/pages/api/register.js deleted file mode 100644 index 7c8d8b39..00000000 --- a/src/pages/api/register.js +++ /dev/null @@ -1,15 +0,0 @@ -import apiOdoo from "@/core/utils/apiOdoo"; - -export default async function handler(req, res) { - try { - const { email, name, password } = req.body; - let result = await apiOdoo( - 'POST', - '/api/v1/user/register', - {email, name, password} - ); - res.status(200).json(result); - } catch (error) { - res.status(400).json({ error: error.message }); - } -} \ No newline at end of file diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index ad986c86..5e5f1b6a 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -1,4 +1,5 @@ -import axios from "axios"; +import axios from "axios" +import camelcaseObjectDeep from "camelcase-object-deep" const productResponseMap = (products) => { return products.map((product) => { @@ -7,23 +8,23 @@ const productResponseMap = (products) => { image: product.image ? product.image[0] : '', code: product.default_code ? product.default_code[0] : '', name: product.product_name ? product.product_name[0] : '', - lowest_price: { + lowestPrice: { price: product.price ? product.price[0] : 0, - price_discount: product.price_discount ? product.price_discount[0] : 0, - discount_percentage: product.discount ? product.discount[0] : 0, + priceDiscount: product.price_discount ? product.price_discount[0] : 0, + discountPercentage: product.discount ? product.discount[0] : 0, }, - variant_total: product.variant_total ? product.variant_total[0] : 0, - stock_total: product.stock_total ? product.stock_total[0] : 0, + variantTotal: product.variant_total ? product.variant_total[0] : 0, + stockTotal: product.stock_total ? product.stock_total[0] : 0, weight: product.weight ? product.weight[0] : 0, manufacture: {}, categories: [], - }; + } if (product.manufacture_id && product.brand) { productMapped.manufacture = { id: product.manufacture_id ? product.manufacture_id[0] : '', name: product.brand ? product.brand[0] : '', - }; + } } productMapped.categories = [ @@ -31,41 +32,41 @@ const productResponseMap = (products) => { id: product.category_id ? product.category_id[0] : '', name: product.category_name ? product.category_name[0] : '', } - ]; + ] - return productMapped; - }); + return productMapped + }) } export default async function handler(req, res) { const { - q, + q = '*', page = 1, brand = '', category = '', - price_from = 0, - price_to = 0, - order_by = '' - } = req.query; + priceFrom = 0, + priceTo = 0, + orderBy = '' + } = req.query - let paramOrderBy = ''; - switch (order_by) { + let paramOrderBy = '' + switch (orderBy) { case 'price-asc': - paramOrderBy = ', price_discount ASC'; - break; + paramOrderBy = ', price_discount ASC' + break case 'price-desc': - paramOrderBy = ', price_discount DESC'; - break; + paramOrderBy = ', price_discount DESC' + break case 'popular': - paramOrderBy = ', search_rank DESC'; - break; + paramOrderBy = ', search_rank DESC' + break case 'stock': - paramOrderBy = ', stock_total DESC'; - break; + paramOrderBy = ', stock_total DESC' + break } - let limit = 30; - let offset = (page - 1) * limit; + let limit = 30 + let offset = (page - 1) * limit let parameter = [ `facet.query=${q}`, 'facet=true', @@ -77,20 +78,21 @@ export default async function handler(req, res) { `start=${offset}`, `rows=${limit}`, `sort=product_rating DESC ${paramOrderBy}`, - `fq=price_discount:[${price_from == '' ? '*' : price_from} TO ${price_to == '' ? '*' : price_to}]` - ]; + `fq=price_discount:[${priceFrom == '' ? '*' : priceFrom} TO ${priceTo == '' ? '*' : priceTo}]` + ] - if (brand) parameter.push(`fq=brand:${brand}`); - if (category) parameter.push(`fq=category_name:${category}`); + if (brand) parameter.push(`fq=brand:${brand}`) + if (category) parameter.push(`fq=category_name:${category}`) - let result = await axios(process.env.SOLR_HOST + '/solr/products/select?' + parameter.join('&')); + let result = await axios(process.env.SOLR_HOST + '/solr/products/select?' + parameter.join('&')) try { - result.data.response.products = productResponseMap(result.data.response.docs); - result.data.responseHeader.params.start = parseInt(result.data.responseHeader.params.start); - result.data.responseHeader.params.rows = parseInt(result.data.responseHeader.params.rows); - delete result.data.response.docs; - res.status(200).json(result.data); + result.data.response.products = productResponseMap(result.data.response.docs) + result.data.responseHeader.params.start = parseInt(result.data.responseHeader.params.start) + result.data.responseHeader.params.rows = parseInt(result.data.responseHeader.params.rows) + delete result.data.response.docs + result.data = camelcaseObjectDeep(result.data) + res.status(200).json(result.data) } catch (error) { - res.status(400).json({ error: error.message }); + res.status(400).json({ error: error.message }) } } \ No newline at end of file diff --git a/src/pages/api/shop/suggest.js b/src/pages/api/shop/suggest.js index 6db1a851..4e373a92 100644 --- a/src/pages/api/shop/suggest.js +++ b/src/pages/api/shop/suggest.js @@ -1,12 +1,15 @@ import axios from "axios"; export default async function handler(req, res) { - const { q } = req.query; + const { q = '' } = req.query; let result = await axios(process.env.SOLR_HOST + `/solr/products/suggest?suggest=true&suggest.dictionary=mySuggester&suggest.q=${q}`); try { - res.status(200).json(result.data); + res.status(200).json(result.data.suggest.mySuggester[q]); } catch (error) { - res.status(400).json({ error: error.message }); + res.status(400).json({ + numFound: 0, + suggestions: [] + }); } } \ No newline at end of file diff --git a/src/pages/api/token.js b/src/pages/api/token.js deleted file mode 100644 index ec048158..00000000 --- a/src/pages/api/token.js +++ /dev/null @@ -1,10 +0,0 @@ -import axios from "axios"; - -export default async function handler(req, res) { - try { - let result = await axios.get(process.env.ODOO_HOST + '/api/token'); - res.status(200).json(result.data.result); - } catch (error) { - res.status(400).json({ error: error.message }); - } -} \ No newline at end of file diff --git a/src/pages/faqs.js b/src/pages/faqs.js deleted file mode 100644 index cdb8ef52..00000000 --- a/src/pages/faqs.js +++ /dev/null @@ -1,91 +0,0 @@ -import AppBar from "@/components/layouts/AppBar"; -import Layout from "@/components/layouts/Layout"; -import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/outline"; -import { useEffect, useState } from "react"; - -const dataFaqs = [ - { - id: 1, - name: 'Akun', - description: 'Bantuan tentang pengelolaan fitur dan akun' - }, - { - id: 2, - name: 'Pembelian', - description: 'Bantuan seputar status stock, layanan pengiriman & asuransi hingga seluruh indonesia' - }, - { - id: 3, - name: 'Metode Pembayaran', - description: 'Bantuan terkait layanan metode pembayaran' - }, - { - id: 4, - name: 'Quotation', - description: 'Bantuan fitur RFQ & quotation Express' - }, - { - id: 5, - name: 'Faktur Pajak & Invoice', - description: 'Bantuan seputar layanan terbit faktur pajak & invoice' - }, - { - id: 6, - name: 'Pengembalian & Garansi', - description: 'Bantuan cara pengembalian produk & garansi produk' - } -]; - -export default function Faqs() { - const [ faqs, setFaqs ] = useState([]); - - useEffect(() => { - if (faqs.length == 0) { - setFaqs(dataFaqs.map((dataFaq) => ({ - ...dataFaq, - isOpen: false - }))); - } - }, [ faqs ]); - - const toggleFaq = (id) => { - const faqsToUpdate = faqs.map(faq => { - if (faq.id == id) faq.isOpen = !faq.isOpen; - return faq; - }); - setFaqs(faqsToUpdate); - }; - - return ( - - - -
- { faqs.map((faq, index) => ( -
-
-
-

{ faq.name }

-

- { faq.description } -

-
- -
- { faq.isOpen && ( -

- { faq?.content || 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.' } -

- ) } -
- )) } -
-
- ) -} \ No newline at end of file diff --git a/src/pages/index.js b/src/pages/index.js deleted file mode 100644 index 65999ff6..00000000 --- a/src/pages/index.js +++ /dev/null @@ -1,106 +0,0 @@ -import { useEffect, useState } from "react"; -import { Pagination, Autoplay } from "swiper"; -import axios from "axios"; -import { Swiper, SwiperSlide } from "swiper/react"; -import "swiper/css"; -import "swiper/css/pagination"; -import "swiper/css/autoplay"; - -// Helpers -import apiOdoo from "@/core/utils/apiOdoo"; - -// Components -import Header from "@/components/layouts/Header"; -import ProductSlider from "@/components/products/ProductSlider"; -import Layout from "@/components/layouts/Layout"; -import ManufactureCard from "@/components/manufactures/ManufactureCard"; -import Footer from "@/components/layouts/Footer"; -import Image from "@/components/elements/Image"; -import ProductCategories from "@/components/products/ProductCategories"; - -const swiperBanner = { - pagination: { dynamicBullets: true }, - autoplay: { - delay: 6000, - disableOnInteraction: false - }, - modules: [Pagination, Autoplay] -} - -export async function getServerSideProps() { - const heroBanners = await apiOdoo('GET', `/api/v1/banner?type=index-a-1`); - - return { props: { heroBanners } }; -} - -export default function Home({ heroBanners }) { - const [manufactures, setManufactures] = useState(null); - const [popularProducts, setPopularProducts] = useState(null); - - useEffect(() => { - const getManufactures = async () => { - const dataManufactures = await apiOdoo('GET', `/api/v1/manufacture?level=prioritas`); - setManufactures(dataManufactures); - } - getManufactures(); - - const getPopularProducts = async () => { - const dataPopularProducts = await axios(`${process.env.SELF_HOST}/api/shop/search?q=*&page=1&order_by=popular`); - setPopularProducts(dataPopularProducts.data.response); - } - getPopularProducts(); - }, []); - - return ( - <> -
- - - { - heroBanners?.map((banner, index) => ( - - {banner.name} - - )) - } - -
-

Brand Pilihan

- - { - manufactures?.manufactures?.map((manufacture, index) => ( - - - - )) - } - -
-
-

Produk Populer

- -
- - - -
-
Platform Belanja B2B Alat Teknik & Industri di Indonesia
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est -

-
- -