diff options
| author | HATEC\SPVDEV001 <tri.susilo@altama.co.id> | 2023-04-11 11:06:38 +0700 |
|---|---|---|
| committer | HATEC\SPVDEV001 <tri.susilo@altama.co.id> | 2023-04-11 11:06:38 +0700 |
| commit | 3df233e0c23e7d4503931ab6ec8ffc41642ac104 (patch) | |
| tree | ccc032defe422f5fafc4a08af672833b2fe41835 /src/core | |
| parent | 006c77a85786c24199db157d1d70f48b47311d35 (diff) | |
| parent | f0a720441def88187b3913268238c379362fb9d3 (diff) | |
Merge branch 'master' into development_tri/feedback_UAT
Diffstat (limited to 'src/core')
24 files changed, 230 insertions, 86 deletions
diff --git a/src/core/api/odooApi.js b/src/core/api/odooApi.js index 25ee9adf..fe9fcdd2 100644 --- a/src/core/api/odooApi.js +++ b/src/core/api/odooApi.js @@ -18,6 +18,17 @@ const getToken = async () => { const maxConnectionAttempt = 15 let connectionAttempt = 0 +/** + * The `odooApi` function is used to make API requests to an Odoo backend with customizable parameters such as `method`, `url`, `data`, and `headers`. + * + * @async + * @function + * @param {string} method - HTTP method for the API request (e.g., GET, POST, PUT, DELETE). + * @param {string} url - URL endpoint for the API request. + * @param {Object} data - Data to be sent in the request payload. + * @param {Object} headers - Custom headers to be sent in the request. + * @returns {Promise} - A Promise that resolves to the API response data or an empty array. + */ const odooApi = async (method, url, data = {}, headers = {}) => { connectionAttempt++ try { diff --git a/src/core/components/elements/Alert/Alert.jsx b/src/core/components/elements/Alert/Alert.jsx index 695be8a3..cf84b591 100644 --- a/src/core/components/elements/Alert/Alert.jsx +++ b/src/core/components/elements/Alert/Alert.jsx @@ -1,3 +1,13 @@ +/** + * The Alert component is used to display different types of alerts or notifications + * with different styles based on the provided type prop. + * + * @param {Object} props - Props received by the component. + * @param {ReactNode} props.children - The content to be displayed inside the alert. + * @param {string} props.className - Additional CSS class names for the alert. + * @param {string} props.type - The type of the alert ('info', 'success', or 'warning'). + * @returns {JSX.Element} - Rendered Alert component. + */ const Alert = ({ children, className, type }) => { let typeClass = '' switch (type) { diff --git a/src/core/components/elements/Appbar/Appbar.jsx b/src/core/components/elements/Appbar/Appbar.jsx index 098d0a33..f1456a7c 100644 --- a/src/core/components/elements/Appbar/Appbar.jsx +++ b/src/core/components/elements/Appbar/Appbar.jsx @@ -2,38 +2,33 @@ import { useRouter } from 'next/router' import Link from '../Link/Link' import { HomeIcon, Bars3Icon, ShoppingCartIcon, ChevronLeftIcon } from '@heroicons/react/24/outline' +/** + * The AppBar component is a navigation component used to display a header or toolbar + * in a web application. + * + * @param {Object} props - Props received by the component. + * @param {string} props.title - The title to be displayed on the AppBar. + * @returns {JSX.Element} - Rendered AppBar component. + */ const AppBar = ({ title }) => { const router = useRouter() return ( <nav className='sticky top-0 z-50 bg-white border-b border-gray_r-6 flex justify-between'> <div className='flex items-center'> - <button - type='button' - className='p-4' - onClick={() => router.back()} - > + <button type='button' className='p-4' onClick={() => router.back()}> <ChevronLeftIcon className='w-6 stroke-2' /> </button> <div className='font-medium text-h-sm line-clamp-1'>{title}</div> </div> <div className='flex items-center px-2'> - <Link - href='/shop/cart' - className='py-4 px-2' - > + <Link href='/shop/cart' className='py-4 px-2'> <ShoppingCartIcon className='w-6 text-gray_r-12' /> </Link> - <Link - href='/' - className='py-4 px-2' - > + <Link href='/' className='py-4 px-2'> <HomeIcon className='w-6 text-gray_r-12' /> </Link> - <Link - href='/my/menu' - className='py-4 px-2' - > + <Link href='/my/menu' className='py-4 px-2'> <Bars3Icon className='w-6 text-gray_r-12' /> </Link> </div> diff --git a/src/core/components/elements/Divider/Divider.jsx b/src/core/components/elements/Divider/Divider.jsx index ce54a2ea..f3650b00 100644 --- a/src/core/components/elements/Divider/Divider.jsx +++ b/src/core/components/elements/Divider/Divider.jsx @@ -1,3 +1,11 @@ +/** + * The Divider component is used to create a horizontal line or divider with a specific + * background color based on the provided className prop. + * + * @param {Object} props - Props that are passed to the Divider component. + * @param {string} props.className - Additional CSS classes to apply to the divider. + * @returns {JSX.Element} - Rendered Divider component. + */ const Divider = (props) => <div className={`h-1 bg-gray_r-4 ${props.className}`} /> Divider.defaultProps = { diff --git a/src/core/components/elements/Image/Image.jsx b/src/core/components/elements/Image/Image.jsx index ac82aaaf..ba6bf50d 100644 --- a/src/core/components/elements/Image/Image.jsx +++ b/src/core/components/elements/Image/Image.jsx @@ -1,6 +1,14 @@ import { LazyLoadImage } from 'react-lazy-load-image-component' import 'react-lazy-load-image-component/src/effects/opacity.css' +/** + * The `Image` component is used to display lazy-loaded images. + * + * @param {Object} props - Props passed to the `Image` component. + * @param {string} props.src - URL of the image to be displayed. + * @param {string} props.alt - Alternative text to be displayed if the image is not found. + * @returns {JSX.Element} - Rendered `Image` component. + */ const Image = ({ ...props }) => ( <> <LazyLoadImage diff --git a/src/core/components/elements/Link/Link.jsx b/src/core/components/elements/Link/Link.jsx index 360444a6..f6b39d45 100644 --- a/src/core/components/elements/Link/Link.jsx +++ b/src/core/components/elements/Link/Link.jsx @@ -1,14 +1,14 @@ import NextLink from 'next/link' -import { useRouter } from 'next/router' -import { useEffect } from 'react' +/** + * The `Link` component is used to render Next.js links with customizable properties such as `children`, `scroll`, and `className`. + * + * @param {Object} props - Props passed to the `Link` component. + * @param {ReactNode} props.children - Child elements to be rendered as link content. + * @param {string} props.className - Additional CSS class to be applied to the link. + * @returns {JSX.Element} - Rendered `Link` component. + */ const Link = ({ children, ...props }) => { - const router = useRouter() - - useEffect(() => { - router.events.on('routeChangeComplete', () => window.scrollTo({ top: 0, behavior: 'smooth' })) - }, [router]) - return ( <NextLink {...props} diff --git a/src/core/components/elements/Navbar/NavbarMobile.jsx b/src/core/components/elements/Navbar/NavbarMobile.jsx index 3998875b..24ff3a51 100644 --- a/src/core/components/elements/Navbar/NavbarMobile.jsx +++ b/src/core/components/elements/Navbar/NavbarMobile.jsx @@ -16,12 +16,7 @@ const NavbarMobile = () => { <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} - /> + <Image src={IndoteknikLogo} alt='Indoteknik Logo' width={120} height={40} /> </Link> <div className='flex gap-x-3'> <Link href='/my/wishlist'> @@ -30,10 +25,7 @@ const NavbarMobile = () => { <Link href='/shop/cart'> <ShoppingCartIcon className='w-6 text-gray_r-12' /> </Link> - <button - type='button' - onClick={open} - > + <button type='button' onClick={open}> <Bars3Icon className='w-6 text-gray_r-12' /> </button> </div> diff --git a/src/core/components/elements/Navbar/Search.jsx b/src/core/components/elements/Navbar/Search.jsx index d0627b24..3046782b 100644 --- a/src/core/components/elements/Navbar/Search.jsx +++ b/src/core/components/elements/Navbar/Search.jsx @@ -45,10 +45,7 @@ const Search = () => { return ( <> - <form - onSubmit={handleSubmit} - className='flex-1 flex relative' - > + <form onSubmit={handleSubmit} className='flex-1 flex relative'> <input type='text' ref={queryRef} @@ -59,10 +56,7 @@ const Search = () => { onBlur={onInputBlur} onFocus={loadSuggestion} /> - <button - type='submit' - className='rounded-r border border-l-0 border-gray_r-6 px-2' - > + <button type='submit' className='rounded-r border border-l-0 border-gray_r-6 px-2'> <MagnifyingGlassIcon className='w-6' /> </button> diff --git a/src/core/components/elements/Pagination/Pagination.js b/src/core/components/elements/Pagination/Pagination.js index 18964fc4..c009171f 100644 --- a/src/core/components/elements/Pagination/Pagination.js +++ b/src/core/components/elements/Pagination/Pagination.js @@ -26,10 +26,7 @@ const Pagination = ({ pageCount, currentPage, url, className }) => { </Link> ) let DotsComponent = ( - <div - key={i} - className='pagination-dots' - > + <div key={i} className='pagination-dots'> ... </div> ) diff --git a/src/core/components/elements/Popup/BottomPopup.jsx b/src/core/components/elements/Popup/BottomPopup.jsx index 1d65c7a3..80afa8d9 100644 --- a/src/core/components/elements/Popup/BottomPopup.jsx +++ b/src/core/components/elements/Popup/BottomPopup.jsx @@ -7,7 +7,7 @@ import ReactDOM from 'react-dom' const transition = { ease: 'linear', duration: 0.2 } -const BottomPopup = ({ children, active = false, title, close }) => { +const BottomPopup = ({ children, active = false, title, close, className = '' }) => { useEffect(() => { if (active) { document.querySelector('html, body').classList.add('overflow-hidden') @@ -35,7 +35,7 @@ const BottomPopup = ({ children, active = false, title, close }) => { animate={{ bottom: 0 }} exit={{ bottom: '-100%' }} transition={transition} - className='fixed left-0 w-full border-t border-gray_r-6 rounded-t-xl z-[60] p-4 pt-0 bg-white max-h-[80vh] overflow-auto' + className={`fixed left-0 w-full border-t border-gray_r-6 rounded-t-xl z-[60] p-4 pt-0 bg-white max-h-[80vh] overflow-auto ${className}`} > <div className='flex justify-between py-4'> <div className='font-semibold text-h-sm'>{title}</div> @@ -53,7 +53,7 @@ const BottomPopup = ({ children, active = false, title, close }) => { animate={{ bottom: '50%', opacity: 1 }} exit={{ bottom: '45%', opacity: 0 }} transition={transition} - className='fixed left-1/2 -translate-x-1/2 translate-y-1/2 md:w-1/4 lg:w-1/3 border border-gray_r-6 rounded-xl z-[60] p-4 pt-0 bg-white max-h-[80vh] overflow-auto' + className={`fixed left-1/2 -translate-x-1/2 translate-y-1/2 md:w-1/4 lg:w-1/3 border border-gray_r-6 rounded-xl z-[60] p-4 pt-0 bg-white max-h-[80vh] overflow-auto ${className}`} > <div className='flex justify-between py-4'> <div className='font-semibold text-h-sm'>{title}</div> diff --git a/src/core/components/elements/Skeleton/BrandSkeleton.jsx b/src/core/components/elements/Skeleton/BrandSkeleton.jsx index 9a34fb9b..9a7a51f9 100644 --- a/src/core/components/elements/Skeleton/BrandSkeleton.jsx +++ b/src/core/components/elements/Skeleton/BrandSkeleton.jsx @@ -1,8 +1,5 @@ const BrandSkeleton = () => ( - <div - role='status' - className='animate-pulse' - > + <div role='status' className='animate-pulse'> <div className='h-12 bg-gray-200 rounded'></div> <span className='sr-only'>Loading...</span> </div> diff --git a/src/core/components/elements/Skeleton/ImageSkeleton.jsx b/src/core/components/elements/Skeleton/ImageSkeleton.jsx index 39d06331..2ca6b2a3 100644 --- a/src/core/components/elements/Skeleton/ImageSkeleton.jsx +++ b/src/core/components/elements/Skeleton/ImageSkeleton.jsx @@ -1,12 +1,6 @@ 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 - > + <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' diff --git a/src/core/components/elements/Skeleton/ProductCardSkeleton.jsx b/src/core/components/elements/Skeleton/ProductCardSkeleton.jsx index ddc0d3bc..84d1c0d1 100644 --- a/src/core/components/elements/Skeleton/ProductCardSkeleton.jsx +++ b/src/core/components/elements/Skeleton/ProductCardSkeleton.jsx @@ -3,10 +3,7 @@ const ProductCardSkeleton = () => ( 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 - > + <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' diff --git a/src/core/components/elements/Spinner/LogoSpinner.jsx b/src/core/components/elements/Spinner/LogoSpinner.jsx new file mode 100644 index 00000000..73b84e84 --- /dev/null +++ b/src/core/components/elements/Spinner/LogoSpinner.jsx @@ -0,0 +1,15 @@ +import Image from 'next/image' +import IndoteknikLogo from '@/images/LOGO-INDOTEKNIK-GEAR.png' + +const LogoSpinner = ({ ...props }) => ( + <Image + src={IndoteknikLogo} + alt='Indoteknik Logo' + width={64} + height={64} + className='page-loader' + {...props} + /> +) + +export default LogoSpinner diff --git a/src/core/hooks/useDevice.js b/src/core/hooks/useDevice.js index a8584692..7ecf5a31 100644 --- a/src/core/hooks/useDevice.js +++ b/src/core/hooks/useDevice.js @@ -22,7 +22,6 @@ const useDevice = () => { } }, []) - return { isMobile, isDesktop diff --git a/src/core/hooks/useSidebar.js b/src/core/hooks/useSidebar.js index 4da61ac2..c463fd81 100644 --- a/src/core/hooks/useSidebar.js +++ b/src/core/hooks/useSidebar.js @@ -15,12 +15,7 @@ const useSidebar = () => { return { open: activate, - Sidebar: ( - <SidebarComponent - active={active} - close={deactivate} - /> - ) + Sidebar: <SidebarComponent active={active} close={deactivate} /> } } diff --git a/src/core/utils/address.js b/src/core/utils/address.js index c545d34b..b20feba3 100644 --- a/src/core/utils/address.js +++ b/src/core/utils/address.js @@ -1,28 +1,53 @@ +/** + * Gets the address data from local storage. + * + * @returns {object} - Returns the address data as an object, or an empty object if not found. + */ const getAddress = () => { - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' && window.localStorage) { const address = localStorage.getItem('address') if (address) return JSON.parse(address) } return {} } +/** + * Sets the address data to local storage. + * + * @param {object} address - The address data to be stored as an object. + * @returns {boolean} - Returns `true` if the address data is successfully stored, `false` otherwise. + */ const setAddress = (address) => { - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' && window.localStorage) { localStorage.setItem('address', JSON.stringify(address)) + return true } - return + return false } +/** + * Gets the value of a specific key from the address data. + * + * @param {string} key - The key of the address data to be retrieved. + * @returns {*} - The value of the specified key, or false if the key does not exist. + */ const getItemAddress = (key) => { let address = getAddress() - return address[key] + return address[key] || false } +/** + * Updates the value of a specific key in the address data. + * + * @param {string} key - The key of the address data to be updated. + * @param {*} value - The new value to be set for the specified key. + * @returns {boolean} - Returns `true` + */ const updateItemAddress = (key, value) => { let address = getAddress() address[key] = value setAddress(address) - return + return true } export { getItemAddress, updateItemAddress } diff --git a/src/core/utils/auth.js b/src/core/utils/auth.js index 13e0e79d..cddff2b8 100644 --- a/src/core/utils/auth.js +++ b/src/core/utils/auth.js @@ -1,18 +1,32 @@ import { deleteCookie, getCookie, setCookie } from 'cookies-next' +/** + * Retrieves authentication data from cookie and returns it as an object. + * + * @returns {Object|boolean} - Returns the authentication data as an object if available in cookie, otherwise `false`. + */ const getAuth = () => { let auth = getCookie('auth') - if (auth) { - return JSON.parse(auth) - } + if (auth) return JSON.parse(auth) return false } +/** + * Sets the authentication data in cookie with the given user data. + * + * @param {Object} user - The user data to be set as authentication data in cookie. + * @returns {boolean} - Returns `true`. + */ const setAuth = (user) => { setCookie('auth', JSON.stringify(user)) return true } +/** + * Deletes the authentication data stored in cookie. + * + * @returns {boolean} - Returns `true`. + */ const deleteAuth = () => { deleteCookie('auth') return true diff --git a/src/core/utils/cart.js b/src/core/utils/cart.js index fd42ee4e..2bdffb1c 100644 --- a/src/core/utils/cart.js +++ b/src/core/utils/cart.js @@ -1,23 +1,51 @@ +/** + * Retrieves cart data from localStorage, if available. + * + * @returns {Object} - The cart data as an object, or an empty object if not found. + */ const getCart = () => { - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' && window.localStorage) { const cart = localStorage.getItem('cart') if (cart) return JSON.parse(cart) } return {} } +/** + * Saves cart data to localStorage, if available. + * + * @param {Object} cart - The cart data to be saved. + * @returns {boolean} - Returns `true` if cart data is saved successfully, `false` otherwise. + */ const setCart = (cart) => { - if (typeof window !== 'undefined') { + if (typeof window !== 'undefined' && window.localStorage) { localStorage.setItem('cart', JSON.stringify(cart)) + return true } - return true + return false } +/** + * Retrieves an item from the cart data based on the given product ID. + * + * @param {Object} options - The options object containing the productId. + * @param {string} options.productId - The ID of the product to be retrieved from the cart. + * @returns {Object} - Returns the item object from the cart data, or `undefined` if not found. + */ const getItemCart = ({ productId }) => { let cart = getCart() return cart[productId] } +/** + * Updates an item in the cart data with the given productId, quantity, and selected status. + * + * @param {Object} options - The options object containing the productId, quantity, and selected status. + * @param {string} options.productId - The ID of the product to be updated in the cart. + * @param {number} options.quantity - The new quantity of the product in the cart. + * @param {boolean} [options.selected=false] - The new selected status of the product in the cart. Default is `false`. + * @returns {boolean} - Returns `true`. + */ const updateItemCart = ({ productId, quantity, selected = false }) => { let cart = getCart() quantity = parseInt(quantity) @@ -26,6 +54,13 @@ const updateItemCart = ({ productId, quantity, selected = false }) => { return true } +/** + * Deletes an item from the cart data with the given productId. + * + * @param {Object} options - The options object containing the productId. + * @param {string} options.productId - The ID of the product to be deleted from the cart. + * @returns {boolean} - Returns `true`. + */ const deleteItemCart = ({ productId }) => { let cart = getCart() delete cart[productId] diff --git a/src/core/utils/currencyFormat.js b/src/core/utils/currencyFormat.js index 12b68111..d0eba4c4 100644 --- a/src/core/utils/currencyFormat.js +++ b/src/core/utils/currencyFormat.js @@ -1,3 +1,9 @@ +/** + * Formats a numeric value as a currency string in Indonesian Rupiah (IDR) format. + * + * @param {number} value - The numeric value to be formatted as currency. + * @returns {string} - The currency string in IDR format. + */ const currencyFormat = (value) => { const currency = new Intl.NumberFormat('id-ID', { style: 'currency', diff --git a/src/core/utils/getFileBase64.js b/src/core/utils/getFileBase64.js index 4fa7316b..ed6ae000 100644 --- a/src/core/utils/getFileBase64.js +++ b/src/core/utils/getFileBase64.js @@ -1,5 +1,13 @@ -const getFileBase64 = (file) => - new Promise((resolve, reject) => { +/** + * Converts a File object to base64 string using FileReader. + * + * @param {File} file - The File object to be converted. + * @returns {Promise<string>} - A Promise that resolves with the base64 string + * representing the contents of the File, or rejects with an error if there's + * any issue with the file reading process. + */ +const getFileBase64 = (file) => { + return new Promise((resolve, reject) => { let reader = new FileReader() reader.readAsBinaryString(file) reader.onload = () => { @@ -8,5 +16,6 @@ const getFileBase64 = (file) => } reader.onerror = (error) => reject(error) }) +} export default getFileBase64 diff --git a/src/core/utils/greeting.js b/src/core/utils/greeting.js index aaaade7a..d349033e 100644 --- a/src/core/utils/greeting.js +++ b/src/core/utils/greeting.js @@ -1,3 +1,13 @@ +/** + * Generates a greeting based on the current time of day. + * The greeting is determined by the hour of the day: + * - "Selamat Pagi" for hours before 11:00 + * - "Selamat Siang" for hours between 11:00 and 15:00 + * - "Selamat Sore" for hours between 15:00 and 18:00 + * - "Selamat Malam" for hours after 18:00 + * + * @returns {string} - The generated greeting message. + */ const greeting = () => { let hours = new Date().getHours() if (hours < 11) return 'Selamat Pagi' diff --git a/src/core/utils/slug.js b/src/core/utils/slug.js index 7010008a..d5eecd3e 100644 --- a/src/core/utils/slug.js +++ b/src/core/utils/slug.js @@ -1,5 +1,14 @@ import toTitleCase from './toTitleCase' +/** + * Creates a slug from input parameters by converting the name and appending it with an ID. + * The slug is generated by removing special characters, converting to lowercase, and joining with hyphens. + * + * @param {string} prefix - The prefix to be added to the generated slug. + * @param {string} name - The name used to generate the slug. + * @param {number} id - The ID to be appended to the slug. + * @returns {string} - The generated slug with the prefix, name, and ID. + */ const createSlug = (prefix, name, id) => { let slug = name @@ -13,11 +22,26 @@ const createSlug = (prefix, name, id) => { return prefix + filterSlugFromEmptyChar.join('-') } +/** + * Extracts the ID from a slug. + * The ID is retrieved from the last segment of the slug, separated by hyphens. + * + * @param {string} slug - The slug from which to extract the ID. + * @returns {string} - The extracted ID from the slug. + */ const getIdFromSlug = (slug) => { let id = slug.split('-') return id[id.length - 1] } +/** + * Extracts the name from a slug. + * The name is retrieved from all segments of the slug, except for the last one, separated by hyphens. + * The retrieved name is then converted to title case. + * + * @param {string} slug - The slug from which to extract the name. + * @returns {string} - The extracted name from the slug in title case. + */ const getNameFromSlug = (slug) => { let name = slug.split('-') name.pop() diff --git a/src/core/utils/toTitleCase.js b/src/core/utils/toTitleCase.js index 4335824d..dab1dc33 100644 --- a/src/core/utils/toTitleCase.js +++ b/src/core/utils/toTitleCase.js @@ -1,3 +1,12 @@ +/** + * Converts a string to title case. + * Title case capitalizes the first character of each word in the string, + * and sets the remaining characters to lowercase. + * + * @param {string} str - The input string to be converted to title case. + * @returns {string} - The string in title case. + */ + const toTitleCase = (str) => { return str.replace(/\w\S*/g, function (txt) { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() |
