From ee4297280c1305c7e03bedd4df63ccf136c28c6c Mon Sep 17 00:00:00 2001 From: Rafi Zadanly Date: Tue, 24 Jan 2023 15:54:48 +0700 Subject: Merapihkan struktur folder --- src/components/Alert.js | 19 --- src/components/AppBar.js | 59 --------- src/components/BottomPopup.js | 25 ---- src/components/ConfirmAlert.js | 25 ---- src/components/Fields.js | 21 --- src/components/Filter.js | 176 ------------------------- src/components/Footer.js | 69 ---------- src/components/Header.js | 164 ----------------------- src/components/Image.js | 13 -- src/components/Layout.js | 20 --- src/components/LineDivider.js | 7 - src/components/Link.js | 9 -- src/components/ManufactureCard.js | 18 --- src/components/Pagination.js | 58 -------- src/components/ProductCard.js | 66 ---------- src/components/ProgressBar.js | 25 ---- src/components/Spinner.js | 13 -- src/components/WithAuth.js | 20 --- src/components/auth/WithAuth.js | 20 +++ src/components/elements/Alert.js | 19 +++ src/components/elements/BottomPopup.js | 25 ++++ src/components/elements/ConfirmAlert.js | 25 ++++ src/components/elements/Fields.js | 21 +++ src/components/elements/Filter.js | 176 +++++++++++++++++++++++++ src/components/elements/Image.js | 13 ++ src/components/elements/LineDivider.js | 7 + src/components/elements/Link.js | 9 ++ src/components/elements/Pagination.js | 58 ++++++++ src/components/elements/ProgressBar.js | 25 ++++ src/components/elements/Spinner.js | 13 ++ src/components/layouts/AppBar.js | 59 +++++++++ src/components/layouts/Footer.js | 69 ++++++++++ src/components/layouts/Header.js | 164 +++++++++++++++++++++++ src/components/layouts/Layout.js | 20 +++ src/components/manufactures/ManufactureCard.js | 18 +++ src/components/product/ProductSlider.js | 44 ------- src/components/products/ProductCard.js | 66 ++++++++++ src/components/products/ProductSlider.js | 44 +++++++ src/core/utils/address.js | 27 ++++ src/core/utils/apiOdoo.js | 44 +++++++ src/core/utils/auth.js | 38 ++++++ src/core/utils/cart.js | 36 +++++ src/core/utils/convertToOption.js | 11 ++ src/core/utils/currencyFormat.js | 8 ++ 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 | 25 ++++ src/core/utils/toTitleCase.js | 8 ++ src/helpers/address.js | 27 ---- src/helpers/apiOdoo.js | 44 ------- src/helpers/auth.js | 38 ------ src/helpers/cart.js | 36 ----- src/helpers/convertToOption.js | 11 -- src/helpers/currencyFormat.js | 8 -- src/helpers/formValidation.js | 107 --------------- src/helpers/getFileBase64.js | 11 -- src/helpers/greeting.js | 9 -- src/helpers/mailer.js | 12 -- src/helpers/slug.js | 25 ---- src/helpers/toTitleCase.js | 8 -- src/pages/404.js | 6 +- src/pages/activate.js | 12 +- src/pages/api/activation-request.js | 4 +- src/pages/api/activation.js | 2 +- src/pages/api/login.js | 2 +- src/pages/api/register.js | 2 +- src/pages/index.js | 14 +- src/pages/login.js | 12 +- src/pages/logout.js | 2 +- src/pages/my/address/[id]/edit.js | 12 +- src/pages/my/address/create.js | 14 +- src/pages/my/address/index.js | 16 ++- src/pages/my/menu.js | 8 +- src/pages/my/profile.js | 12 +- src/pages/my/transactions/[slug].js | 8 +- src/pages/my/transactions/index.js | 16 +-- src/pages/register.js | 10 +- src/pages/shop/brands.js | 79 ----------- src/pages/shop/brands/[slug].js | 16 +-- src/pages/shop/brands/index.js | 79 +++++++++++ src/pages/shop/cart.js | 26 ++-- src/pages/shop/checkout.js | 30 ++--- src/pages/shop/product/[slug].js | 22 ++-- src/pages/shop/search.js | 14 +- 86 files changed, 1397 insertions(+), 1395 deletions(-) delete mode 100644 src/components/Alert.js delete mode 100644 src/components/AppBar.js delete mode 100644 src/components/BottomPopup.js delete mode 100644 src/components/ConfirmAlert.js delete mode 100644 src/components/Fields.js delete mode 100644 src/components/Filter.js delete mode 100644 src/components/Footer.js delete mode 100644 src/components/Header.js delete mode 100644 src/components/Image.js delete mode 100644 src/components/Layout.js delete mode 100644 src/components/LineDivider.js delete mode 100644 src/components/Link.js delete mode 100644 src/components/ManufactureCard.js delete mode 100644 src/components/Pagination.js delete mode 100644 src/components/ProductCard.js delete mode 100644 src/components/ProgressBar.js delete mode 100644 src/components/Spinner.js delete mode 100644 src/components/WithAuth.js create mode 100644 src/components/auth/WithAuth.js create mode 100644 src/components/elements/Alert.js create mode 100644 src/components/elements/BottomPopup.js create mode 100644 src/components/elements/ConfirmAlert.js create mode 100644 src/components/elements/Fields.js create mode 100644 src/components/elements/Filter.js create mode 100644 src/components/elements/Image.js create mode 100644 src/components/elements/LineDivider.js create mode 100644 src/components/elements/Link.js create mode 100644 src/components/elements/Pagination.js create mode 100644 src/components/elements/ProgressBar.js create mode 100644 src/components/elements/Spinner.js create mode 100644 src/components/layouts/AppBar.js create mode 100644 src/components/layouts/Footer.js create mode 100644 src/components/layouts/Header.js create mode 100644 src/components/layouts/Layout.js create mode 100644 src/components/manufactures/ManufactureCard.js delete mode 100644 src/components/product/ProductSlider.js create mode 100644 src/components/products/ProductCard.js create mode 100644 src/components/products/ProductSlider.js create mode 100644 src/core/utils/address.js create mode 100644 src/core/utils/apiOdoo.js create mode 100644 src/core/utils/auth.js create mode 100644 src/core/utils/cart.js create mode 100644 src/core/utils/convertToOption.js create mode 100644 src/core/utils/currencyFormat.js create mode 100644 src/core/utils/formValidation.js create mode 100644 src/core/utils/getFileBase64.js create mode 100644 src/core/utils/greeting.js create mode 100644 src/core/utils/mailer.js create mode 100644 src/core/utils/slug.js create mode 100644 src/core/utils/toTitleCase.js delete mode 100644 src/helpers/address.js delete mode 100644 src/helpers/apiOdoo.js delete mode 100644 src/helpers/auth.js delete mode 100644 src/helpers/cart.js delete mode 100644 src/helpers/convertToOption.js delete mode 100644 src/helpers/currencyFormat.js delete mode 100644 src/helpers/formValidation.js delete mode 100644 src/helpers/getFileBase64.js delete mode 100644 src/helpers/greeting.js delete mode 100644 src/helpers/mailer.js delete mode 100644 src/helpers/slug.js delete mode 100644 src/helpers/toTitleCase.js delete mode 100644 src/pages/shop/brands.js create mode 100644 src/pages/shop/brands/index.js (limited to 'src') diff --git a/src/components/Alert.js b/src/components/Alert.js deleted file mode 100644 index 914d1590..00000000 --- a/src/components/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/AppBar.js b/src/components/AppBar.js deleted file mode 100644 index 4cac8ce5..00000000 --- a/src/components/AppBar.js +++ /dev/null @@ -1,59 +0,0 @@ -import { ChevronLeftIcon, HeartIcon, HomeIcon } from "@heroicons/react/24/outline"; -import Head from "next/head"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import Link from "./Link"; - -const AppBar = ({ title }) => { - const router = useRouter(); - - const [scrollPosition, setScrollPosition] = useState(0); - const handleScrollPosition = () => { - const position = window.pageYOffset; - setScrollPosition(position); - } - - useEffect(() => { - window.addEventListener('scroll', handleScrollPosition, { passive: true }); - - return () => { - window.addEventListener('scroll', handleScrollPosition); - }; - }, []); - - const handleBackButtonClick = (event) => { - event.currentTarget.disabled = true; - router.back(); - } - - return ( - <> - - { title } - Indoteknik - -
0 && "shadow border-b-transparent" ) }> - {/* --- Start Title */} -
- -

{ title }

-
- {/* --- End Title */} - - {/* --- Start Icons */} -
- - - - - - -
- {/* --- End Icons */} -
- - ); -}; - -export default AppBar; \ No newline at end of file diff --git a/src/components/BottomPopup.js b/src/components/BottomPopup.js deleted file mode 100644 index b7f5f2de..00000000 --- a/src/components/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/ConfirmAlert.js b/src/components/ConfirmAlert.js deleted file mode 100644 index 27155011..00000000 --- a/src/components/ConfirmAlert.js +++ /dev/null @@ -1,25 +0,0 @@ -const ConfirmAlert = ({ - title, - caption, - show, - onClose, - onSubmit, -}) => { - return ( - <> - {show && ( -
- )} -
-

{title}

-

{caption}

-
- - -
-
- - ); -}; - -export default ConfirmAlert; \ No newline at end of file diff --git a/src/components/Fields.js b/src/components/Fields.js deleted file mode 100644 index 586a6a22..00000000 --- a/src/components/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/Filter.js b/src/components/Filter.js deleted file mode 100644 index f2051ba8..00000000 --- a/src/components/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/Footer.js b/src/components/Footer.js deleted file mode 100644 index f0ae66b3..00000000 --- a/src/components/Footer.js +++ /dev/null @@ -1,69 +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"; - -export default function Footer() { - return ( -
-
-
-

Kantor Pusat

-

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

- -

Layanan Informasi

-
- -

(021) 2933-8828 / 29

-
-
- -

0812-8080-622

-
-
- -

sales@indoteknik.com

-
- - {/*

Panduan Pelanggan

*/} -
-
-

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/Header.js b/src/components/Header.js deleted file mode 100644 index 71f8fd58..00000000 --- a/src/components/Header.js +++ /dev/null @@ -1,164 +0,0 @@ -import Image from "next/image"; -import { 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 -} from "@heroicons/react/24/outline"; - -// Helpers -import { useAuth } from "../helpers/auth"; -// Components -import Link from "./Link"; -// Images -import Logo from "../images/logo.png"; -import greeting from "../helpers/greeting"; - -const menus = [ - { name: 'Semua Brand', href: '/shop/brands' }, - { name: 'Blog Indoteknik', href: '/' }, - { name: 'Kategori', href: '/' }, -]; - -export default function Header({ title }) { - const router = useRouter(); - const { q = '' } = router.query; - const [searchQuery, setSearchQuery] = useState(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(); - } - } - - return ( - <> - - - {title} - - -
-
- { auth && ( - -
-

{ greeting() },

-

{auth.name}

-
-
- -
- - ) } - - { !auth && ( - <> - Masuk - Daftar - - ) } -
-
- { menus.map((menu, index) => ( - - { menu.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/Image.js b/src/components/Image.js deleted file mode 100644 index f06272b0..00000000 --- a/src/components/Image.js +++ /dev/null @@ -1,13 +0,0 @@ -import { LazyLoadImage } from "react-lazy-load-image-component"; -import 'react-lazy-load-image-component/src/effects/blur.css'; - -export default function Image({ src, alt, className = "" }) { - return ( - - ); -} \ No newline at end of file diff --git a/src/components/Layout.js b/src/components/Layout.js deleted file mode 100644 index fd507963..00000000 --- a/src/components/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/LineDivider.js b/src/components/LineDivider.js deleted file mode 100644 index 4e8c7b52..00000000 --- a/src/components/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/Link.js b/src/components/Link.js deleted file mode 100644 index d354bb1b..00000000 --- a/src/components/Link.js +++ /dev/null @@ -1,9 +0,0 @@ -import NextLink from "next/link"; - -export default function Link({ children, ...pageProps }) { - return ( - - {children} - - ) -} \ No newline at end of file diff --git a/src/components/ManufactureCard.js b/src/components/ManufactureCard.js deleted file mode 100644 index 52a964d5..00000000 --- a/src/components/ManufactureCard.js +++ /dev/null @@ -1,18 +0,0 @@ -import { createSlug } from "../helpers/slug"; -import Image from "./Image"; -import Link from "./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/Pagination.js b/src/components/Pagination.js deleted file mode 100644 index 1cb1bca0..00000000 --- a/src/components/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 ( -
- {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/ProductCard.js b/src/components/ProductCard.js deleted file mode 100644 index 2cb0d99b..00000000 --- a/src/components/ProductCard.js +++ /dev/null @@ -1,66 +0,0 @@ -import Link from "./Link"; -import currencyFormat from "../helpers/currencyFormat"; -import { createSlug } from "../helpers/slug"; -import { ChevronRightIcon } from "@heroicons/react/20/solid"; -import Image from "./Image"; - - -export default function ProductCard({ data }) { - 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/ProgressBar.js b/src/components/ProgressBar.js deleted file mode 100644 index 0adedcdf..00000000 --- a/src/components/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/Spinner.js b/src/components/Spinner.js deleted file mode 100644 index 21006ecd..00000000 --- a/src/components/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/WithAuth.js b/src/components/WithAuth.js deleted file mode 100644 index 74518b3b..00000000 --- a/src/components/WithAuth.js +++ /dev/null @@ -1,20 +0,0 @@ -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import { getAuth } from "../helpers/auth"; - -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/auth/WithAuth.js b/src/components/auth/WithAuth.js new file mode 100644 index 00000000..ef975873 --- /dev/null +++ b/src/components/auth/WithAuth.js @@ -0,0 +1,20 @@ +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 new file mode 100644 index 00000000..914d1590 --- /dev/null +++ b/src/components/elements/Alert.js @@ -0,0 +1,19 @@ +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 new file mode 100644 index 00000000..deb1b895 --- /dev/null +++ b/src/components/elements/BottomPopup.js @@ -0,0 +1,25 @@ +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 new file mode 100644 index 00000000..27155011 --- /dev/null +++ b/src/components/elements/ConfirmAlert.js @@ -0,0 +1,25 @@ +const ConfirmAlert = ({ + title, + caption, + show, + onClose, + onSubmit, +}) => { + return ( + <> + {show && ( +
+ )} +
+

{title}

+

{caption}

+
+ + +
+
+ + ); +}; + +export default ConfirmAlert; \ No newline at end of file diff --git a/src/components/elements/Fields.js b/src/components/elements/Fields.js new file mode 100644 index 00000000..586a6a22 --- /dev/null +++ b/src/components/elements/Fields.js @@ -0,0 +1,21 @@ +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 new file mode 100644 index 00000000..f2051ba8 --- /dev/null +++ b/src/components/elements/Filter.js @@ -0,0 +1,176 @@ +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 new file mode 100644 index 00000000..f06272b0 --- /dev/null +++ b/src/components/elements/Image.js @@ -0,0 +1,13 @@ +import { LazyLoadImage } from "react-lazy-load-image-component"; +import 'react-lazy-load-image-component/src/effects/blur.css'; + +export default function Image({ src, alt, className = "" }) { + return ( + + ); +} \ No newline at end of file diff --git a/src/components/elements/LineDivider.js b/src/components/elements/LineDivider.js new file mode 100644 index 00000000..4e8c7b52 --- /dev/null +++ b/src/components/elements/LineDivider.js @@ -0,0 +1,7 @@ +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 new file mode 100644 index 00000000..d354bb1b --- /dev/null +++ b/src/components/elements/Link.js @@ -0,0 +1,9 @@ +import NextLink from "next/link"; + +export default function Link({ children, ...pageProps }) { + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/src/components/elements/Pagination.js b/src/components/elements/Pagination.js new file mode 100644 index 00000000..1cb1bca0 --- /dev/null +++ b/src/components/elements/Pagination.js @@ -0,0 +1,58 @@ +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 ( +
+ {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 new file mode 100644 index 00000000..0adedcdf --- /dev/null +++ b/src/components/elements/ProgressBar.js @@ -0,0 +1,25 @@ +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/Spinner.js b/src/components/elements/Spinner.js new file mode 100644 index 00000000..21006ecd --- /dev/null +++ b/src/components/elements/Spinner.js @@ -0,0 +1,13 @@ +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 new file mode 100644 index 00000000..f9dddf9d --- /dev/null +++ b/src/components/layouts/AppBar.js @@ -0,0 +1,59 @@ +import { ChevronLeftIcon, HeartIcon, HomeIcon } from "@heroicons/react/24/outline"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import Link from "../elements/Link"; + +const AppBar = ({ title }) => { + const router = useRouter(); + + const [scrollPosition, setScrollPosition] = useState(0); + const handleScrollPosition = () => { + const position = window.pageYOffset; + setScrollPosition(position); + } + + useEffect(() => { + window.addEventListener('scroll', handleScrollPosition, { passive: true }); + + return () => { + window.addEventListener('scroll', handleScrollPosition); + }; + }, []); + + const handleBackButtonClick = (event) => { + event.currentTarget.disabled = true; + router.back(); + } + + return ( + <> + + { title } - Indoteknik + +
0 && "shadow border-b-transparent" ) }> + {/* --- 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 new file mode 100644 index 00000000..e48daf7f --- /dev/null +++ b/src/components/layouts/Footer.js @@ -0,0 +1,69 @@ +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"; + +export default function Footer() { + return ( +
+
+
+

Kantor Pusat

+

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

+ +

Layanan Informasi

+
+ +

(021) 2933-8828 / 29

+
+
+ +

0812-8080-622

+
+
+ +

sales@indoteknik.com

+
+ + {/*

Panduan Pelanggan

*/} +
+
+

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 new file mode 100644 index 00000000..5cd0a1bc --- /dev/null +++ b/src/components/layouts/Header.js @@ -0,0 +1,164 @@ +import Image from "next/image"; +import { 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 +} 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"; + +const menus = [ + { name: 'Semua Brand', href: '/shop/brands' }, + { name: 'Blog Indoteknik', href: '/' }, + { name: 'Kategori', href: '/' }, +]; + +export default function Header({ title }) { + const router = useRouter(); + const { q = '' } = router.query; + const [searchQuery, setSearchQuery] = useState(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(); + } + } + + return ( + <> + + + {title} + + +
+
+ { auth && ( + +
+

{ greeting() },

+

{auth.name}

+
+
+ +
+ + ) } + + { !auth && ( + <> + Masuk + Daftar + + ) } +
+
+ { menus.map((menu, index) => ( + + { menu.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 new file mode 100644 index 00000000..fd507963 --- /dev/null +++ b/src/components/layouts/Layout.js @@ -0,0 +1,20 @@ +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 new file mode 100644 index 00000000..73a96902 --- /dev/null +++ b/src/components/manufactures/ManufactureCard.js @@ -0,0 +1,18 @@ +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/product/ProductSlider.js b/src/components/product/ProductSlider.js deleted file mode 100644 index 84dc4258..00000000 --- a/src/components/product/ProductSlider.js +++ /dev/null @@ -1,44 +0,0 @@ -import { Swiper, SwiperSlide } from "swiper/react"; -import ProductCard from "../ProductCard"; -import ImagePlaceholderIcon from "../../icons/image-placeholder.svg"; -import "swiper/css"; - -export default function ProductSlider({ products }) { - return ( - <> - - {products?.products?.map((product, index) => ( - - - - ))} - - {products == null ? ( -
-
-
- -
-
-
-
-
-
- Loading... -
-
-
- -
-
-
-
-
-
- Loading... -
-
- ) : ''} - - ) -} \ No newline at end of file diff --git a/src/components/products/ProductCard.js b/src/components/products/ProductCard.js new file mode 100644 index 00000000..e32463a7 --- /dev/null +++ b/src/components/products/ProductCard.js @@ -0,0 +1,66 @@ +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 }) { + 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/ProductSlider.js b/src/components/products/ProductSlider.js new file mode 100644 index 00000000..ddc505c7 --- /dev/null +++ b/src/components/products/ProductSlider.js @@ -0,0 +1,44 @@ +import { Swiper, SwiperSlide } from "swiper/react"; +import ProductCard from "./ProductCard"; +import ImagePlaceholderIcon from "../../icons/image-placeholder.svg"; +import "swiper/css"; + +export default function ProductSlider({ products }) { + return ( + <> + + {products?.products?.map((product, index) => ( + + + + ))} + + {products == null ? ( +
+
+
+ +
+
+
+
+
+
+ Loading... +
+
+
+ +
+
+
+
+
+
+ Loading... +
+
+ ) : ''} + + ) +} \ No newline at end of file diff --git a/src/core/utils/address.js b/src/core/utils/address.js new file mode 100644 index 00000000..c4a19af5 --- /dev/null +++ b/src/core/utils/address.js @@ -0,0 +1,27 @@ +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 new file mode 100644 index 00000000..4d0adae3 --- /dev/null +++ b/src/core/utils/apiOdoo.js @@ -0,0 +1,44 @@ +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 new file mode 100644 index 00000000..62eba2c0 --- /dev/null +++ b/src/core/utils/auth.js @@ -0,0 +1,38 @@ +import { deleteCookie, getCookie, setCookie } from 'cookies-next'; +import { useEffect, useState } from 'react'; + +const getAuth = () => { + let auth = getCookie('auth'); + if (auth) { + return JSON.parse(auth); + } + return false; +} + +const setAuth = (user) => { + 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]; +} + +export { + getAuth, + setAuth, + deleteAuth, + useAuth +}; \ No newline at end of file diff --git a/src/core/utils/cart.js b/src/core/utils/cart.js new file mode 100644 index 00000000..66efcbf2 --- /dev/null +++ b/src/core/utils/cart.js @@ -0,0 +1,36 @@ +const getCart = () => { + const cart = localStorage.getItem('cart'); + if (cart) return JSON.parse(cart); + return {}; +} + +const setCart = (cart) => { + localStorage.setItem('cart', JSON.stringify(cart)); + return true; +} + +const getItemCart = (product_id) => { + let cart = getCart(); + return cart[product_id]; +} + +const createOrUpdateItemCart = (product_id, quantity, selected = false) => { + let cart = getCart(); + cart[product_id] = { product_id, quantity, selected }; + setCart(cart); + return true; +} + +const deleteItemCart = (product_id) => { + let cart = getCart(); + delete cart[product_id]; + setCart(cart); + return true; +} + +export { + getCart, + getItemCart, + createOrUpdateItemCart, + deleteItemCart +} \ No newline at end of file diff --git a/src/core/utils/convertToOption.js b/src/core/utils/convertToOption.js new file mode 100644 index 00000000..08fec08f --- /dev/null +++ b/src/core/utils/convertToOption.js @@ -0,0 +1,11 @@ +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 new file mode 100644 index 00000000..dadeaec6 --- /dev/null +++ b/src/core/utils/currencyFormat.js @@ -0,0 +1,8 @@ +export default function 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 diff --git a/src/core/utils/formValidation.js b/src/core/utils/formValidation.js new file mode 100644 index 00000000..0e83f4cc --- /dev/null +++ b/src/core/utils/formValidation.js @@ -0,0 +1,107 @@ +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 new file mode 100644 index 00000000..78013e43 --- /dev/null +++ b/src/core/utils/getFileBase64.js @@ -0,0 +1,11 @@ +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 new file mode 100644 index 00000000..7dc19f8f --- /dev/null +++ b/src/core/utils/greeting.js @@ -0,0 +1,9 @@ +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 new file mode 100644 index 00000000..4e7ff7cc --- /dev/null +++ b/src/core/utils/mailer.js @@ -0,0 +1,12 @@ +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 new file mode 100644 index 00000000..0a7d30fc --- /dev/null +++ b/src/core/utils/slug.js @@ -0,0 +1,25 @@ +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 getIdFromSlug = (slug) => { + let id = slug.split('-'); + return id[id.length-1]; +} + +const getNameFromSlug = (slug) => { + let name = slug.split('-'); + name.pop(); + return toTitleCase(name.join(' ')); +} + +export { + createSlug, + getIdFromSlug, + getNameFromSlug +}; \ No newline at end of file diff --git a/src/core/utils/toTitleCase.js b/src/core/utils/toTitleCase.js new file mode 100644 index 00000000..5cfd70d0 --- /dev/null +++ b/src/core/utils/toTitleCase.js @@ -0,0 +1,8 @@ +export default function 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 diff --git a/src/helpers/address.js b/src/helpers/address.js deleted file mode 100644 index c4a19af5..00000000 --- a/src/helpers/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/helpers/apiOdoo.js b/src/helpers/apiOdoo.js deleted file mode 100644 index 4d0adae3..00000000 --- a/src/helpers/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/helpers/auth.js b/src/helpers/auth.js deleted file mode 100644 index 62eba2c0..00000000 --- a/src/helpers/auth.js +++ /dev/null @@ -1,38 +0,0 @@ -import { deleteCookie, getCookie, setCookie } from 'cookies-next'; -import { useEffect, useState } from 'react'; - -const getAuth = () => { - let auth = getCookie('auth'); - if (auth) { - return JSON.parse(auth); - } - return false; -} - -const setAuth = (user) => { - 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]; -} - -export { - getAuth, - setAuth, - deleteAuth, - useAuth -}; \ No newline at end of file diff --git a/src/helpers/cart.js b/src/helpers/cart.js deleted file mode 100644 index 66efcbf2..00000000 --- a/src/helpers/cart.js +++ /dev/null @@ -1,36 +0,0 @@ -const getCart = () => { - const cart = localStorage.getItem('cart'); - if (cart) return JSON.parse(cart); - return {}; -} - -const setCart = (cart) => { - localStorage.setItem('cart', JSON.stringify(cart)); - return true; -} - -const getItemCart = (product_id) => { - let cart = getCart(); - return cart[product_id]; -} - -const createOrUpdateItemCart = (product_id, quantity, selected = false) => { - let cart = getCart(); - cart[product_id] = { product_id, quantity, selected }; - setCart(cart); - return true; -} - -const deleteItemCart = (product_id) => { - let cart = getCart(); - delete cart[product_id]; - setCart(cart); - return true; -} - -export { - getCart, - getItemCart, - createOrUpdateItemCart, - deleteItemCart -} \ No newline at end of file diff --git a/src/helpers/convertToOption.js b/src/helpers/convertToOption.js deleted file mode 100644 index 08fec08f..00000000 --- a/src/helpers/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/helpers/currencyFormat.js b/src/helpers/currencyFormat.js deleted file mode 100644 index dadeaec6..00000000 --- a/src/helpers/currencyFormat.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function 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 diff --git a/src/helpers/formValidation.js b/src/helpers/formValidation.js deleted file mode 100644 index 0e83f4cc..00000000 --- a/src/helpers/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/helpers/getFileBase64.js b/src/helpers/getFileBase64.js deleted file mode 100644 index 78013e43..00000000 --- a/src/helpers/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/helpers/greeting.js b/src/helpers/greeting.js deleted file mode 100644 index 7dc19f8f..00000000 --- a/src/helpers/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/helpers/mailer.js b/src/helpers/mailer.js deleted file mode 100644 index 4e7ff7cc..00000000 --- a/src/helpers/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/helpers/slug.js b/src/helpers/slug.js deleted file mode 100644 index 0a7d30fc..00000000 --- a/src/helpers/slug.js +++ /dev/null @@ -1,25 +0,0 @@ -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 getIdFromSlug = (slug) => { - let id = slug.split('-'); - return id[id.length-1]; -} - -const getNameFromSlug = (slug) => { - let name = slug.split('-'); - name.pop(); - return toTitleCase(name.join(' ')); -} - -export { - createSlug, - getIdFromSlug, - getNameFromSlug -}; \ No newline at end of file diff --git a/src/helpers/toTitleCase.js b/src/helpers/toTitleCase.js deleted file mode 100644 index 5cfd70d0..00000000 --- a/src/helpers/toTitleCase.js +++ /dev/null @@ -1,8 +0,0 @@ -export default function 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 diff --git a/src/pages/404.js b/src/pages/404.js index b7f8b8ef..1e1850f2 100644 --- a/src/pages/404.js +++ b/src/pages/404.js @@ -1,7 +1,7 @@ import Image from "next/image"; -import Link from "../components/Link"; -import Header from "../components/Header"; -import Layout from "../components/Layout"; +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() { diff --git a/src/pages/activate.js b/src/pages/activate.js index c738af81..d9b41bf4 100644 --- a/src/pages/activate.js +++ b/src/pages/activate.js @@ -1,14 +1,14 @@ import axios from "axios"; import Head from "next/head"; import Image from "next/image"; -import Link from "../components/Link"; +import Link from "@/components/elements/Link"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import Alert from "../components/Alert"; -import Layout from "../components/Layout"; -import Spinner from "../components/Spinner"; -import { setAuth } from "../helpers/auth"; -import Logo from "../images/logo.png"; +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(''); diff --git a/src/pages/api/activation-request.js b/src/pages/api/activation-request.js index 42ad5364..3f33875c 100644 --- a/src/pages/api/activation-request.js +++ b/src/pages/api/activation-request.js @@ -1,5 +1,5 @@ -import apiOdoo from "../../helpers/apiOdoo"; -import mailer from "../../helpers/mailer"; +import apiOdoo from "@/core/utils/apiOdoo"; +import mailer from "@/core/utils/mailer"; export default async function handler(req, res) { try { diff --git a/src/pages/api/activation.js b/src/pages/api/activation.js index 67ec1c9e..8b22af8d 100644 --- a/src/pages/api/activation.js +++ b/src/pages/api/activation.js @@ -1,4 +1,4 @@ -import apiOdoo from "../../helpers/apiOdoo"; +import apiOdoo from "@/core/utils/apiOdoo"; export default async function handler(req, res) { try { diff --git a/src/pages/api/login.js b/src/pages/api/login.js index a747294b..e02a73cb 100644 --- a/src/pages/api/login.js +++ b/src/pages/api/login.js @@ -1,4 +1,4 @@ -import apiOdoo from "../../helpers/apiOdoo"; +import apiOdoo from "@/core/utils/apiOdoo"; export default async function handler(req, res) { try { diff --git a/src/pages/api/register.js b/src/pages/api/register.js index 323cab24..7c8d8b39 100644 --- a/src/pages/api/register.js +++ b/src/pages/api/register.js @@ -1,4 +1,4 @@ -import apiOdoo from "../../helpers/apiOdoo"; +import apiOdoo from "@/core/utils/apiOdoo"; export default async function handler(req, res) { try { diff --git a/src/pages/index.js b/src/pages/index.js index 511f2ecd..35175dce 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -7,15 +7,15 @@ import "swiper/css/pagination"; import "swiper/css/autoplay"; // Helpers -import apiOdoo from "../helpers/apiOdoo"; +import apiOdoo from "@/core/utils/apiOdoo"; // Components -import Header from "../components/Header"; -import ProductSlider from "../components/product/ProductSlider"; -import Layout from "../components/Layout"; -import ManufactureCard from "../components/ManufactureCard"; -import Footer from "../components/Footer"; -import Image from "../components/Image"; +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"; export async function getServerSideProps() { const heroBanners = await apiOdoo('GET', `/api/v1/banner?type=index-a-1`); diff --git a/src/pages/login.js b/src/pages/login.js index 0dfacfaa..cd48e5e6 100644 --- a/src/pages/login.js +++ b/src/pages/login.js @@ -1,14 +1,14 @@ import axios from "axios"; import Head from "next/head"; import Image from "next/image"; -import Link from "../components/Link"; +import Link from "@/components/elements/Link"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import Alert from "../components/Alert"; -import Layout from "../components/Layout"; -import Spinner from "../components/Spinner"; -import { setAuth } from "../helpers/auth"; -import Logo from "../images/logo.png"; +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 Login() { const router = useRouter(); diff --git a/src/pages/logout.js b/src/pages/logout.js index de749a37..8ea21fab 100644 --- a/src/pages/logout.js +++ b/src/pages/logout.js @@ -1,6 +1,6 @@ import { useRouter } from "next/router"; import { useEffect } from "react"; -import { deleteAuth } from "../helpers/auth"; +import { deleteAuth } from "@/core/utils/auth"; export default function Logout() { const router = useRouter(); diff --git a/src/pages/my/address/[id]/edit.js b/src/pages/my/address/[id]/edit.js index e65ba699..838d39e7 100644 --- a/src/pages/my/address/[id]/edit.js +++ b/src/pages/my/address/[id]/edit.js @@ -1,12 +1,12 @@ import { Controller, useForm } from "react-hook-form" -import WithAuth from "../../../../components/WithAuth"; -import Layout from "../../../../components/Layout"; -import AppBar from "../../../../components/AppBar"; +import WithAuth from "@/components/auth/WithAuth"; +import Layout from "@/components/layouts/Layout"; +import AppBar from "@/components/layouts/AppBar"; import { yupResolver } from "@hookform/resolvers/yup"; import * as Yup from "yup"; -import { Select } from "../../../../components/Fields"; +import { Select } from "@/components/elements/Fields"; import { useEffect, useState } from "react"; -import apiOdoo from "../../../../helpers/apiOdoo"; +import apiOdoo from "@/core/utils/apiOdoo"; import { toast } from "react-hot-toast"; import { useRouter } from "next/router"; @@ -17,7 +17,7 @@ const validationSchema = Yup.object().shape({ mobile: Yup.string().required('Harus di-isi'), street: Yup.string().required('Harus di-isi'), zip: Yup.string().required('Harus di-isi'), - city: Yup.number().required('Harus di-pilih'), + city: Yup.string().required('Harus di-pilih'), }); const types = [ diff --git a/src/pages/my/address/create.js b/src/pages/my/address/create.js index 71f73a64..4c7a8130 100644 --- a/src/pages/my/address/create.js +++ b/src/pages/my/address/create.js @@ -1,13 +1,13 @@ import { Controller, useForm } from "react-hook-form" -import WithAuth from "../../../components/WithAuth"; -import Layout from "../../../components/Layout"; -import AppBar from "../../../components/AppBar"; +import WithAuth from "@/components/auth/WithAuth"; +import Layout from "@/components/layouts/Layout"; +import AppBar from "@/components/layouts/AppBar"; import { yupResolver } from "@hookform/resolvers/yup"; import * as Yup from "yup"; -import { Select } from "../../../components/Fields"; +import { Select } from "@/components/elements/Fields"; import { useEffect, useState } from "react"; -import apiOdoo from "../../../helpers/apiOdoo"; -import { useAuth } from "../../../helpers/auth"; +import apiOdoo from "@/core/utils/apiOdoo"; +import { useAuth } from "@/core/utils/auth"; import { toast } from "react-hot-toast"; import { useRouter } from "next/router"; @@ -18,7 +18,7 @@ const validationSchema = Yup.object().shape({ mobile: Yup.string().required('Harus di-isi'), street: Yup.string().required('Harus di-isi'), zip: Yup.string().required('Harus di-isi'), - city: Yup.number().required('Harus di-pilih'), + city: Yup.string().required('Harus di-pilih'), }); const defaultValues = { diff --git a/src/pages/my/address/index.js b/src/pages/my/address/index.js index 236e4b60..dbe082ff 100644 --- a/src/pages/my/address/index.js +++ b/src/pages/my/address/index.js @@ -1,12 +1,14 @@ import { useEffect, useState } from "react"; -import AppBar from "../../../components/AppBar"; -import Layout from "../../../components/Layout"; -import Link from "../../../components/Link"; -import WithAuth from "../../../components/WithAuth"; -import apiOdoo from "../../../helpers/apiOdoo"; -import { useAuth } from "../../../helpers/auth"; import { useRouter } from "next/router"; -import { createOrUpdateItemAddress, getItemAddress } from "../../../helpers/address"; + +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import Link from "@/components/elements/Link"; +import WithAuth from "@/components/auth/WithAuth"; + +import apiOdoo from "@/core/utils/apiOdoo"; +import { useAuth } from "@/core/utils/auth"; +import { createOrUpdateItemAddress, getItemAddress } from "@/core/utils/address"; export default function Address() { const router = useRouter(); diff --git a/src/pages/my/menu.js b/src/pages/my/menu.js index f43c1fe9..e7ece135 100644 --- a/src/pages/my/menu.js +++ b/src/pages/my/menu.js @@ -1,8 +1,8 @@ -import AppBar from "../../components/AppBar"; -import Layout from "../../components/Layout"; -import Link from "../../components/Link"; -import { useAuth } from "../../helpers/auth"; +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import Link from "@/components/elements/Link"; +import { useAuth } from "@/core/utils/auth"; import { ArrowRightOnRectangleIcon, ChatBubbleLeftRightIcon, diff --git a/src/pages/my/profile.js b/src/pages/my/profile.js index d4b7fb6d..97891259 100644 --- a/src/pages/my/profile.js +++ b/src/pages/my/profile.js @@ -1,14 +1,14 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import { toast } from "react-hot-toast"; -import AppBar from "../../components/AppBar"; -import Layout from "../../components/Layout"; -import WithAuth from "../../components/WithAuth"; -import apiOdoo from "../../helpers/apiOdoo"; +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import WithAuth from "@/components/auth/WithAuth"; +import apiOdoo from "@/core/utils/apiOdoo"; import { useAuth, setAuth as setAuthCookie, getAuth -} from "../../helpers/auth"; +} from "@/core/utils/auth"; export default function MyProfile() { const [auth, setAuth] = useAuth(); diff --git a/src/pages/my/transactions/[slug].js b/src/pages/my/transactions/[slug].js index b2381247..a76b0c4d 100644 --- a/src/pages/my/transactions/[slug].js +++ b/src/pages/my/transactions/[slug].js @@ -1,8 +1,8 @@ import { ArrowDownOnSquareIcon, ArrowDownTrayIcon, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon } from "@heroicons/react/24/outline"; -import AppBar from "../../../components/AppBar"; -import Layout from "../../../components/Layout"; -import LineDivider from "../../../components/LineDivider"; -import WithAuth from "../../../components/WithAuth"; +import AppBar from "@/components/layouts/AppBar"; +import Layout from "@/components/layouts/Layout"; +import LineDivider from "@/components/elements/LineDivider"; +import WithAuth from "@/components/auth/WithAuth"; import { useState } from "react"; const Row = ({ label, children }) => ( diff --git a/src/pages/my/transactions/index.js b/src/pages/my/transactions/index.js index 7792c647..b1e31661 100644 --- a/src/pages/my/transactions/index.js +++ b/src/pages/my/transactions/index.js @@ -1,14 +1,14 @@ import { useRouter } from "next/router"; -import AppBar from "../../../components/AppBar"; -import BottomPopup from "../../../components/BottomPopup"; -import Layout from "../../../components/Layout"; -import WithAuth from "../../../components/WithAuth"; +import AppBar from "@/components/layouts/AppBar"; +import BottomPopup from "@/components/elements/BottomPopup"; +import Layout from "@/components/layouts/Layout"; +import WithAuth from "@/components/auth/WithAuth"; import { useEffect, useState } from "react"; -import { useAuth } from "../../../helpers/auth"; -import apiOdoo from "../../../helpers/apiOdoo"; -import currencyFormat from "../../../helpers/currencyFormat"; +import { useAuth } from "@/core/utils/auth"; +import apiOdoo from "@/core/utils/apiOdoo"; +import currencyFormat from "@/core/utils/currencyFormat"; import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; -import Link from "../../../components/Link"; +import Link from "@/components/elements/Link"; export default function Transactions() { const [ auth ] = useAuth(); diff --git a/src/pages/register.js b/src/pages/register.js index 46938d0b..046ae686 100644 --- a/src/pages/register.js +++ b/src/pages/register.js @@ -1,12 +1,12 @@ import axios from "axios"; import Head from "next/head"; import Image from "next/image"; -import Link from "../components/Link"; +import Link from "@/components/elements/Link"; import { useEffect, useState } from "react"; -import Alert from "../components/Alert"; -import Layout from "../components/Layout"; -import Spinner from "../components/Spinner"; -import Logo from "../images/logo.png"; +import Alert from "@/components/elements/Alert"; +import Layout from "@/components/layouts/Layout"; +import Spinner from "@/components/elements/Spinner"; +import Logo from "@/images/logo.png"; export default function Login() { const [email, setEmail] = useState(''); diff --git a/src/pages/shop/brands.js b/src/pages/shop/brands.js deleted file mode 100644 index d82fc31d..00000000 --- a/src/pages/shop/brands.js +++ /dev/null @@ -1,79 +0,0 @@ -import Header from "../../components/Header"; -import apiOdoo from "../../helpers/apiOdoo"; -import InfiniteScroll from "react-infinite-scroll-component"; -import { useEffect, useState } from "react"; -import Spinner from "../../components/Spinner"; -import Layout from "../../components/Layout"; -import ManufactureCard from "../../components/ManufactureCard"; -import Footer from "../../components/Footer"; - -export async function getServerSideProps() { - let initialManufactures = await apiOdoo('GET', '/api/v1/manufacture?limit=31'); - return {props: {initialManufactures}}; -} - -export default function Brands({ initialManufactures }) { - const [manufactures, setManufactures] = useState(initialManufactures.manufactures); - const [hasMoreManufacture, setHasMoreManufacture] = useState(true); - const [manufactureStartwith, setManufactureStartWith] = useState(''); - - const alpha = Array.from(Array(26)).map((e, i) => i + 65); - const alphabets = alpha.map((x) => String.fromCharCode(x)); - - const getMoreManufactures = async () => { - const name = manufactureStartwith != '' ? `${manufactureStartwith}%` : ''; - const result = await apiOdoo('GET', `/api/v1/manufacture?limit=30&offset=${manufactures.length}&name=${name}`); - setHasMoreManufacture(manufactures.length + 30 < result.manufacture_total) - setManufactures((manufactures) => [...manufactures, ...result.manufactures]); - }; - - const filterManufactureStartWith = (character) => { - setManufactures([]); - if (manufactureStartwith == character) { - setManufactureStartWith(''); - } else { - setManufactureStartWith(character); - } - }; - - useEffect(() => { - getMoreManufactures(); - }, [manufactureStartwith]); - - return ( - <> -
- -
-

Semua Brand di Indoteknik

-
- {alphabets.map((alphabet, index) => ( - - ))} -
- - -
- } - > - {manufactures?.map((manufacture, index) => ( - manufacture.name ? ( - - ) : '' - ))} - -
- -
- - - ) -} \ No newline at end of file diff --git a/src/pages/shop/brands/[slug].js b/src/pages/shop/brands/[slug].js index 8e67ef1e..928da0d0 100644 --- a/src/pages/shop/brands/[slug].js +++ b/src/pages/shop/brands/[slug].js @@ -1,13 +1,13 @@ import axios from "axios"; import { useEffect, useState } from "react"; -import Filter from "../../../components/Filter"; -import Footer from "../../../components/Footer"; -import Header from "../../../components/Header"; -import Layout from "../../../components/Layout"; -import Pagination from "../../../components/Pagination"; -import ProductCard from "../../../components/ProductCard"; -import { getNameFromSlug } from "../../../helpers/slug"; -import FilterIcon from "../../../icons/filter.svg"; +import Filter from "@/components/elements/Filter"; +import Footer from "@/components/layouts/Footer"; +import Header from "@/components/layouts/Header"; +import Layout from "@/components/layouts/Layout"; +import Pagination from "@/components/elements/Pagination"; +import ProductCard from "@/components/products/ProductCard"; +import { getNameFromSlug } from "@/core/utils/slug"; +import FilterIcon from "@/icons/filter.svg"; export async function getServerSideProps(context) { const { diff --git a/src/pages/shop/brands/index.js b/src/pages/shop/brands/index.js new file mode 100644 index 00000000..fec2405a --- /dev/null +++ b/src/pages/shop/brands/index.js @@ -0,0 +1,79 @@ +import Header from "@/components/layouts/Header"; +import apiOdoo from "@/core/utils/apiOdoo"; +import InfiniteScroll from "react-infinite-scroll-component"; +import { useEffect, useState } from "react"; +import Spinner from "@/components/elements/Spinner"; +import Layout from "@/components/layouts/Layout"; +import ManufactureCard from "@/components/manufactures/ManufactureCard"; +import Footer from "@/components/layouts/Footer"; + +export async function getServerSideProps() { + let initialManufactures = await apiOdoo('GET', '/api/v1/manufacture?limit=31'); + return {props: {initialManufactures}}; +} + +export default function Brands({ initialManufactures }) { + const [manufactures, setManufactures] = useState(initialManufactures.manufactures); + const [hasMoreManufacture, setHasMoreManufacture] = useState(true); + const [manufactureStartwith, setManufactureStartWith] = useState(''); + + const alpha = Array.from(Array(26)).map((e, i) => i + 65); + const alphabets = alpha.map((x) => String.fromCharCode(x)); + + const getMoreManufactures = async () => { + const name = manufactureStartwith != '' ? `${manufactureStartwith}%` : ''; + const result = await apiOdoo('GET', `/api/v1/manufacture?limit=30&offset=${manufactures.length}&name=${name}`); + setHasMoreManufacture(manufactures.length + 30 < result.manufacture_total) + setManufactures((manufactures) => [...manufactures, ...result.manufactures]); + }; + + const filterManufactureStartWith = (character) => { + setManufactures([]); + if (manufactureStartwith == character) { + setManufactureStartWith(''); + } else { + setManufactureStartWith(character); + } + }; + + useEffect(() => { + getMoreManufactures(); + }, [manufactureStartwith]); + + return ( + <> +
+ +
+

Semua Brand di Indoteknik

+
+ {alphabets.map((alphabet, index) => ( + + ))} +
+ + +
+ } + > + {manufactures?.map((manufacture, index) => ( + manufacture.name ? ( + + ) : '' + ))} + +
+ +