summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorRafi Zadanly <zadanlyr@gmail.com>2023-02-17 17:07:50 +0700
committerRafi Zadanly <zadanlyr@gmail.com>2023-02-17 17:07:50 +0700
commitf99e0aba70efad0deb907d8e27f09fc9f527c8a4 (patch)
treef0ac96e4e736a1d385e32553f0e641ee27e11fd3 /src/core
parent90e1edab9b6a8ccc09a49fed3addbec2cbc4e4c3 (diff)
Refactor
Diffstat (limited to 'src/core')
-rw-r--r--src/core/api/odooApi.js47
-rw-r--r--src/core/api/searchSuggestApi.js12
-rw-r--r--src/core/components/Seo.jsx11
-rw-r--r--src/core/components/elements/Appbar/Appbar.jsx33
-rw-r--r--src/core/components/elements/Badge/Badge.jsx33
-rw-r--r--src/core/components/elements/Divider/Divider.jsx11
-rw-r--r--src/core/components/elements/Image/Image.jsx15
-rw-r--r--src/core/components/elements/Link/Link.jsx17
-rw-r--r--src/core/components/elements/NavBar/NavBar.jsx31
-rw-r--r--src/core/components/elements/NavBar/Search.jsx89
-rw-r--r--src/core/components/elements/Pagination/Pagination.js64
-rw-r--r--src/core/components/elements/Popup/BottomPopup.jsx21
-rw-r--r--src/core/components/elements/Skeleton/BrandSkeleton.jsx8
-rw-r--r--src/core/components/elements/Skeleton/ImageSkeleton.jsx10
-rw-r--r--src/core/components/elements/Skeleton/ProductCardSkeleton.jsx15
-rw-r--r--src/core/components/layouts/AnimationLayout.jsx22
-rw-r--r--src/core/components/layouts/AppLayout.jsx15
-rw-r--r--src/core/components/layouts/BasicLayout.jsx15
-rw-r--r--src/core/hooks/useActive.js19
-rw-r--r--src/core/hooks/useAuth.js14
-rw-r--r--src/core/utils/address.js27
-rw-r--r--src/core/utils/apiOdoo.js44
-rw-r--r--src/core/utils/auth.js37
-rw-r--r--src/core/utils/cart.js39
-rw-r--r--src/core/utils/convertToOption.js11
-rw-r--r--src/core/utils/currencyFormat.js10
-rw-r--r--src/core/utils/formValidation.js107
-rw-r--r--src/core/utils/getFileBase64.js11
-rw-r--r--src/core/utils/greeting.js9
-rw-r--r--src/core/utils/mailer.js12
-rw-r--r--src/core/utils/slug.js24
-rw-r--r--src/core/utils/toTitleCase.js6
32 files changed, 558 insertions, 281 deletions
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 (
+ <Head>
+ <title>{ title }</title>
+ </Head>
+ )
+}
+
+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 (
+ <nav className="sticky top-0 z-50 bg-white shadow flex justify-between">
+ <div className="flex items-center">
+ <button type="button" className="p-4" onClick={() => router.back()}>
+ <ChevronLeftIcon className="w-6 stroke-2" />
+ </button>
+ <div className="font-semibold text-h-md">
+ { title }
+ </div>
+ </div>
+ <div className="flex items-center px-2">
+ <Link href="/shop/cart" className="py-4 px-2">
+ <ShoppingCartIcon className="w-6 text-gray_r-12" />
+ </Link>
+ <Link href="/shop/cart" className="py-4 px-2">
+ <HomeIcon className="w-6 text-gray_r-12" />
+ </Link>
+ <Link href="/shop/cart" className="py-4 px-2">
+ <Bars3Icon className="w-6 text-gray_r-12" />
+ </Link>
+ </div>
+ </nav>
+ )
+}
+
+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 (
+ <div
+ { ...props }
+ className={`${badgeStyle(type)} ${props?.className}`}
+ >
+ { children }
+ </div>
+ )
+}
+
+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 (
+ <div className={`h-1 bg-gray_r-4 ${props.className}`} />
+ )
+}
+
+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 }) => (
+ <LazyLoadImage
+ { ...props }
+ effect="opacity"
+ src={props.src || '/images/noimage.jpeg'}
+ alt={props.src ? props.alt : 'Image Not Found - Indoteknik'}
+ />
+)
+
+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 (
+ <NextLink
+ {...props}
+ scroll={false}
+ className={`block font-medium text-red_r-11 ${props?.className}`}
+ >
+ {children}
+ </NextLink>
+ )
+}
+
+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 (
+ <nav className="px-4 py-2 pb-3 sticky top-0 z-50 bg-white shadow">
+ <div className="flex justify-between items-center mb-2">
+ <Link href="/">
+ <Image src={IndoteknikLogo} alt="Indoteknik Logo" width={120} height={40} />
+ </Link>
+ <div className="flex gap-x-3">
+ <button type="button">
+ <HeartIcon className="w-6 text-gray_r-12" />
+ </button>
+ <Link href="/shop/cart">
+ <ShoppingCartIcon className="w-6 text-gray_r-12" />
+ </Link>
+ <button type="button">
+ <Bars3Icon className="w-6 text-gray_r-12" />
+ </button>
+ </div>
+ </div>
+ <Search />
+ </nav>
+ )
+}
+
+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 (
+ <form
+ onSubmit={handleSubmit}
+ className="flex relative"
+ >
+ <input
+ type="text"
+ ref={queryRef}
+ className="form-input p-3 rounded-r-none border-r-0 focus:border-gray_r-6"
+ placeholder="Ketik nama, part number, merk"
+ value={query}
+ onChange={(e) => setQuery(e.target.value)}
+ onBlur={onInputBlur}
+ onFocus={loadSuggestion}
+ />
+ <button
+ type="submit"
+ className="rounded-r border border-l-0 border-gray_r-6 px-2"
+ >
+ <MagnifyingGlassIcon className="w-6" />
+ </button>
+
+ { suggestions.length > 1 && (
+ <>
+ <div className="absolute w-full top-[50px] rounded-b bg-gray_r-1 border border-gray_r-6 divide-y divide-gray_r-6">
+ {suggestions.map((suggestion, index) => (
+ <Link href={`/shop/search?q=${suggestion.term}`} key={index} className="px-3 py-3 !text-gray_r-12 font-normal">
+ {suggestion.term}
+ </Link>
+ ))}
+ </div>
+ </>
+ ) }
+ </form>
+ )
+}
+
+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 && (
+ <div className={`pagination ${className}`}>
+ { Array.from(Array(pageCount)).map((v, i) => {
+ let page = i + 1
+ let rangePrevPage = currentPage - 2
+ let rangeNextPage = currentPage + 2
+ let PageComponent = <Link key={i} href={`${url + urlParameterPrefix}page=${page}`} className={"pagination-item" + (page == currentPage ? " pagination-item--active " : "")}>{page}</Link>
+ let DotsComponent = <div key={i} className="pagination-dots">...</div>
+
+ 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
+ }
+ }) }
+ </div>
+ )
+}
+
+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 }) => (
+ <>
+ <div
+ onClick={close}
+ className={`overlay ${active ? 'block' : 'hidden'}`}
+ />
+ <div className={`fixed bottom-0 left-0 w-full border-t border-gray_r-6 rounded-t-xl z-[60] p-4 pt-0 bg-white ${active ? 'block' : 'hidden'}`}>
+ <div className="flex justify-between py-4">
+ <div className="font-semibold text-h-sm">{ title }</div>
+ <button type="button" onClick={close}>
+ <XMarkIcon className="w-5 stroke-2" />
+ </button>
+ </div>
+ { children }
+ </div>
+ </>
+)
+
+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 = () => (
+ <div role="status" className="animate-pulse">
+ <div className="h-12 bg-gray-200 rounded"></div>
+ <span className="sr-only">Loading...</span>
+ </div>
+)
+
+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 = () => (
+ <div role="status" className="animate-pulse">
+ <div className="flex items-center justify-center h-56 mb-4 bg-gray-300 rounded" aria-busy>
+ <svg className="w-12 h-12 text-gray-200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 640 512"><path d="M480 80C480 35.82 515.8 0 560 0C604.2 0 640 35.82 640 80C640 124.2 604.2 160 560 160C515.8 160 480 124.2 480 80zM0 456.1C0 445.6 2.964 435.3 8.551 426.4L225.3 81.01C231.9 70.42 243.5 64 256 64C268.5 64 280.1 70.42 286.8 81.01L412.7 281.7L460.9 202.7C464.1 196.1 472.2 192 480 192C487.8 192 495 196.1 499.1 202.7L631.1 419.1C636.9 428.6 640 439.7 640 450.9C640 484.6 612.6 512 578.9 512H55.91C25.03 512 .0006 486.1 .0006 456.1L0 456.1z"/></svg>
+ </div>
+ <span className="sr-only">Loading...</span>
+ </div>
+)
+
+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 = () => (
+ <div role="status" className="p-4 max-w-sm rounded border border-gray-300 shadow animate-pulse md:p-6">
+ <div className="flex items-center justify-center h-36 mb-4 bg-gray-300 rounded" aria-busy>
+ <svg className="w-12 h-12 text-gray-200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" viewBox="0 0 640 512"><path d="M480 80C480 35.82 515.8 0 560 0C604.2 0 640 35.82 640 80C640 124.2 604.2 160 560 160C515.8 160 480 124.2 480 80zM0 456.1C0 445.6 2.964 435.3 8.551 426.4L225.3 81.01C231.9 70.42 243.5 64 256 64C268.5 64 280.1 70.42 286.8 81.01L412.7 281.7L460.9 202.7C464.1 196.1 472.2 192 480 192C487.8 192 495 196.1 499.1 202.7L631.1 419.1C636.9 428.6 640 439.7 640 450.9C640 484.6 612.6 512 578.9 512H55.91C25.03 512 .0006 486.1 .0006 456.1L0 456.1z"/></svg>
+ </div>
+ <div className="h-2 bg-gray-200 rounded-full w-10 mb-1"></div>
+ <div className="h-2.5 bg-gray-200 rounded-full w-full mb-4"></div>
+ <div className="h-2 bg-gray-200 rounded-full mb-2.5"></div>
+ <div className="h-2 bg-gray-200 rounded-full mb-2.5"></div>
+ <div className="h-2 bg-gray-200 rounded-full"></div>
+ <span className="sr-only">Loading...</span>
+ </div>
+)
+
+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 && (
+ <motion.main
+ initial={{ opacity: 0, x: 0, y: 0 }}
+ animate={{ opacity: 1, x: 0, y: 0 }}
+ exit={{ opacity: 0, x: 15, y: 0 }}
+ transition={transition}
+ {...props}
+ >
+ { children }
+ </motion.main>
+ )
+}
+
+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 (
+ <>
+ <AppBar title={title}/>
+ <AnimationLayout>
+ { children }
+ </AnimationLayout>
+ </>
+ )
+}
+
+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 (
+ <>
+ <NavBar />
+ <AnimationLayout>
+ { children }
+ </AnimationLayout>
+ </>
+ )
+}
+
+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