diff --git a/components/MobileNotification.tsx b/components/MobileNotification.tsx new file mode 100644 index 00000000..43130a78 --- /dev/null +++ b/components/MobileNotification.tsx @@ -0,0 +1,41 @@ +import { Flex, Box, Button, Heading, Modal, ModalContent, ModalOverlay } from '@chakra-ui/react' +import { PropsWithChildren } from 'react' + +type Props = { + isOpen: boolean + toggleNotification: () => void +} + +function MobileNotification({ isOpen, toggleNotification, children }: PropsWithChildren) { + return ( + + + + + NOTIFICATION + + {children} + + + + + + + + ) +} + +export default MobileNotification diff --git a/components/professions/Inventory/ItemGrid.tsx b/components/professions/Inventory/ItemGrid.tsx index a0a13821..cfacdffa 100644 --- a/components/professions/Inventory/ItemGrid.tsx +++ b/components/professions/Inventory/ItemGrid.tsx @@ -32,12 +32,14 @@ function ItemGrid({ onClose, onClickItem, loading, data }: Props) { key={`${value.indexItem}${index}`} className="container-item click-cursor" > - onClickItem(value)} - src={`/images/inventory/items/${value.type}Amount.png`} - alt="img" - /> -
{value?.ids?.length}
+ {!value.type.includes("Card") && <> + onClickItem(value)} + src={`/images/inventory/items/${value.type}Amount.png`} + alt="img" + /> +
{value?.ids?.length}
+ } ) })} diff --git a/components/professions/Inventory/ItemGridMobile.tsx b/components/professions/Inventory/ItemGridMobile.tsx new file mode 100644 index 00000000..362ca89a --- /dev/null +++ b/components/professions/Inventory/ItemGridMobile.tsx @@ -0,0 +1,143 @@ +import { Button, Spinner, Text, useDisclosure } from '@chakra-ui/react' +import { useCallback, useMemo, useState } from 'react' +import { Grid, GridItem } from '@chakra-ui/react' +import ItemNotify from './ItemNotify' + +type Item = { + type: + | 'sushi' + | 'ore' + | 'hammer' + | 'fish' + | 'openianCard' + | 'blacksmithCard' + | 'supplierCard' + ids: number[] +} + +type Props = { + loading: boolean + data: Item[] + currentFilter: 'all' | 'ore' | 'food' | 'nftCard' + onToggleInventory: () => void +} +function ItemGridMobile({ loading, data, currentFilter, onToggleInventory }: Props) { + const { isOpen, onToggle: onToggleItemNotify } = useDisclosure() + const [selectedItem, setSelectedItem] = useState({ + type: 'fish', + ids: [] + }) + const renderData = useMemo(() => { + let itemData = data + if (currentFilter === 'ore') { + itemData = data.filter( + (item) => item.type === 'ore' || item.type === 'hammer' + ) + } else if (currentFilter === 'food') { + itemData = data.filter( + (item) => item.type === 'fish' || item.type === 'sushi' + ) + } else if (currentFilter === 'nftCard') { + itemData = data.filter((item) => item.type.includes('Card')) + } + return [...itemData, ...Array(20 - itemData?.length)] + }, [data, currentFilter]) + + const onSelectItem = useCallback( + (item) => { + setSelectedItem(item) + onToggleItemNotify() + }, + [onToggleItemNotify] + ) + + return ( + <> + + {loading && } + {renderData.map((value, index) => { + if (!value) { + return ( + + ) + } + + return ( + + + + ) + })} + + + + + ) +} + +export default ItemGridMobile diff --git a/components/professions/Inventory/ItemNotify.tsx b/components/professions/Inventory/ItemNotify.tsx new file mode 100644 index 00000000..bedb90e0 --- /dev/null +++ b/components/professions/Inventory/ItemNotify.tsx @@ -0,0 +1,165 @@ +import { + Button, + Flex, + Modal, + ModalBody, + ModalContent, + ModalOverlay, + useDisclosure, +} from '@chakra-ui/react' +import styles from '@components/workshop/BuyerBoard.module.css' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faXmark } from '@fortawesome/free-solid-svg-icons' +import { useCallback, useMemo } from 'react' +import { useSelector } from 'react-redux' +import { useRouter } from 'next/router' +import ItemSellModal from './ItemSellModal' + +type Item = { + type: + | 'sushi' + | 'ore' + | 'hammer' + | 'fish' + | 'openianCard' + | 'blacksmithCard' + | 'supplierCard' + ids: number[] +} + +type Props = { + isOpen: boolean + selectedItem: Item + toggleItemNotify: () => void + onToggleInventory: () => void +} + +function ItemNotify({ + isOpen, + selectedItem, + toggleItemNotify, + onToggleInventory, +}: Props) { + const { isOpen: isItemSellOpen, onToggle: onToggleItemSell } = useDisclosure() + const profile = useSelector((state: any) => state.ProfileStore.profile) + const router = useRouter() + + const renderItemImg = useMemo(() => { + if (selectedItem.type.includes('Card')) { + return ( + + ) + } + return ( + + ) + }, [selectedItem]) + + const renderItemName = useMemo(() => { + if (selectedItem.type.includes('Card')) { + return selectedItem.type.replace('Card', ' NFT Card') + } + + return selectedItem.type + }, [selectedItem]) + + const renderItemInfo = useMemo(() => { + if (selectedItem.type === 'fish') { + return "Fish is the main ingredient for making Sushi and Suppliers are paying good money for them. Let's go catch some!!!" + } else if (selectedItem.type === 'ore') { + return "Ore is the main material to make Hammers and BlackSmiths are paying good money for them. Let's go mine some !!!" + } else if (selectedItem.type === 'hammer') { + return 'Hammer is item that help Openians doing their Mining quest' + } else if (selectedItem.type === 'sushi') { + return 'Sushi is only item that help increase Stamina Point.' + } + }, [selectedItem]) + + const isCanUseItem = useMemo(() => { + return ( + selectedItem.ids.length !== 0 && + ((selectedItem.type === 'fish' && profile?._profession === '2') || + (selectedItem.type === 'ore' && profile?._profession === '3') || + (selectedItem.type === 'hammer' && profile?._profession === '1') || + selectedItem.type === 'sushi' || + (selectedItem.type.includes('Card') && profile?._profession === '0')) + ) + }, [selectedItem]) + + const handleUseItem = useCallback(() => { + if (selectedItem.type !== 'sushi') { + router.push('/professions') + toggleItemNotify() + onToggleInventory() + } else { + } + }, [toggleItemNotify, onToggleInventory]) + + return ( + <> + + + + +
+
+ +
+ +
+
+ {renderItemImg} +
+
+
{renderItemName}
+
{renderItemInfo}
+ + + + +
+
+
+
+ + + + ) +} + +export default ItemNotify diff --git a/components/professions/Inventory/ItemSellModal.tsx b/components/professions/Inventory/ItemSellModal.tsx new file mode 100644 index 00000000..c8e3df51 --- /dev/null +++ b/components/professions/Inventory/ItemSellModal.tsx @@ -0,0 +1,288 @@ +import { + Text, + Button, + Modal, + ModalBody, + ModalContent, + ModalOverlay, +} from '@chakra-ui/react' +import styles from '@components/workshop/BuyerBoard.module.css' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faMinus, faPlus, faXmark } from '@fortawesome/free-solid-svg-icons' +import { useMemo, useCallback, useRef, useState } from 'react' +import useTransactionState, { + TRANSACTION_STATE, +} from 'hooks/useTransactionState' +import { getApprovalAll, setApprovedAll } from 'utils/itemContract' +import { listMultiItems } from 'utils/NFTMarket' +import { listingHero } from 'utils/HeroMarketUtils' +import MobileNotification from '@components/MobileNotification' + +type Item = { + type: + | 'sushi' + | 'ore' + | 'hammer' + | 'fish' + | 'openianCard' + | 'blacksmithCard' + | 'supplierCard' + ids: number[] +} +import LoadingModal from '@components/LoadingModal' + +type Props = { + isOpen: boolean + selectedItem: Item + toggleItemSell: () => void + toggleItemNotify: () => void +} + +function ItemSellModal({ + isOpen, + selectedItem, + toggleItemSell, + toggleItemNotify, +}: Props) { + const priceRef = useRef() + const [price, setPrice] = useState(0) + const [sellingAmount, setSellingAmount] = useState(0) + const [totalAmount, setTotalAmount] = useState(0) + const [isLoading, setIsLoading] = useState(false) + const [listingResult, setListingResult] = useState(undefined) + const handleTxStateChange = useTransactionState() + + const renderItemImg = useMemo(() => { + if (selectedItem.type.includes('Card')) { + return ( + + ) + } + return ( + + ) + }, [selectedItem]) + + const renderItemName = useMemo(() => { + if (selectedItem.type.includes('Card')) { + return selectedItem.type.replace('Card', ' NFT Card') + } + + return selectedItem.type + }, [selectedItem]) + + const calcTotalAmount = (_price = 1, _setTotalAmount = 1) => { + if (selectedItem.type.includes("Card")) { + setTotalAmount(_price) + } else { + setTotalAmount(_price * _setTotalAmount) + } + } + + const checkPriceInput = (e) => { + const regex = new RegExp('^[0-9]+$') + if ( + !regex.test(e.target.value) && + (e.keyCode === 69 || e.keyCode === 189) + ) { + // eslint-disable-next-line eqeqeq + priceRef.current.value = priceRef.current.value + } else { + let _price = parseInt(priceRef.current.value, 10) + + if (priceRef.current.value === '') { + _price = 0 + } + + setPrice(_price) + calcTotalAmount(_price, sellingAmount) + } + } + + const checkIfEmpty = () => { + if (priceRef.current.value === '') { + priceRef.current.value = '0' + } + + const _price = parseInt(priceRef.current.value, 10) + setPrice(_price) + calcTotalAmount(_price, sellingAmount) + } + + const handleIncreaseSellingItemAmount = () => { + if (sellingAmount < selectedItem.ids.length) { + setSellingAmount(sellingAmount + 1) + calcTotalAmount(price, sellingAmount + 1) + } + } + + const handleDecreaseSellingItemAmount = () => { + if (sellingAmount > 0) { + setSellingAmount(sellingAmount - 1) + calcTotalAmount(price, sellingAmount - 1) + } + } + + const setApproved = async () => { + await setApprovedAll() + } + + const getApprovedStatus = useCallback(async () => { + const isApproved = await getApprovalAll() + if (!isApproved) { + setApproved() + } + return isApproved + }, []) + + const handleConfirmSell = useCallback(async () => { + toggleItemNotify() + toggleItemSell() + const title = 'Sell item(s)' + if (totalAmount > 0) { + setIsLoading(true) + getApprovedStatus() + let result = null; + if (selectedItem.type.includes("Card")) { + result = await listingHero(selectedItem.ids[0], price, (txHash) => { + handleTxStateChange(title, txHash, TRANSACTION_STATE.WAITING) + }) + } else { + const itemSellIds = selectedItem.ids.slice(0, sellingAmount) + result = await listMultiItems(itemSellIds, price, (txHash) => { + handleTxStateChange(title, txHash, TRANSACTION_STATE.WAITING) + }) + } + if (result !== null) { + setListingResult(true) + handleTxStateChange(title, result.transactionHash, result.status) + } else { + setListingResult(false) + handleTxStateChange(title, '', TRANSACTION_STATE.NOT_EXECUTED) + } + setPrice(0) + setSellingAmount(0) + setTotalAmount(0) + setIsLoading(false) + return result + } + }, [ + price, + sellingAmount, + getApprovedStatus, + selectedItem, + ]) + + const onToggleNotification = () => { + setListingResult(undefined) + } + + return ( + <> + {isLoading && } + + + + +
+
Sell Items
+ +
+ +
+ +
+
+ {renderItemImg} +
+
+ +
{renderItemName}
+ +
+
+ Price +
+ + coin +
+
+
+ { !selectedItem.type.includes("Card") && + <> +
+ +
{sellingAmount}
+ +
+
+ Total +
+ {totalAmount} + coin +
+
+ + } + +
+
+
+
+ + + + Listing items to Market + {listingResult ? ' successfully!' : ' failed!'} + + + + ) +} + +export default ItemSellModal diff --git a/components/professions/Inventory/index.tsx b/components/professions/Inventory/index.tsx index 348623db..17a47581 100644 --- a/components/professions/Inventory/index.tsx +++ b/components/professions/Inventory/index.tsx @@ -6,7 +6,9 @@ import React, { useState, } from 'react' import styled from '@emotion/styled' +import mobileStyle from '@components/workshop/workshop.module.css' import { fetchUserInventoryItemAmount } from 'utils/Item' +import { fetchUserProfessionNFTAmount } from 'utils/professions' import { listMultiItems } from 'utils/NFTMarket' import useTransactionState, { TRANSACTION_STATE, @@ -21,7 +23,15 @@ import { Text, useDisclosure, } from '@chakra-ui/react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { + faBowlFood, + faGavel, + faScroll, +} from '@fortawesome/free-solid-svg-icons' import ItemGrid from './ItemGrid' +import ItemGridMobile from './ItemGridMobile' + type Item = { type: 'sushi' | 'ore' | 'hammer' | 'fish' @@ -39,7 +49,8 @@ function Inventory(_, ref) { const [price, setPrice] = useState(null) const [amountItems, setAmountItems] = useState(null) const [isOpenNotify, setIsOpenNotify] = useState(null) - const [data, setData] = useState(null) + const [data, setData] = useState([]) + const [currentFilter, setCurrentFilter] = useState<'all' | 'ore' | 'food' | 'nftCard'>('all') const handleTxStateChange = useTransactionState() useImperativeHandle( @@ -56,9 +67,10 @@ function Inventory(_, ref) { }, []) const getItemsIndex = useCallback(async () => { - const result = await fetchUserInventoryItemAmount() + const itemList = await fetchUserInventoryItemAmount() + const nftList = await fetchUserProfessionNFTAmount() - setData(result) + setData([...itemList, ...nftList]) }, []) const handleSelling = useCallback(async () => { @@ -132,12 +144,26 @@ function Inventory(_, ref) { setAmountItems(selectedItem?.ids?.length) }, [selectedItem?.ids?.length]) + const addClassSelected = (itemName) => { + if (currentFilter === itemName) { + return mobileStyle.selected + } + } + + const handleFilter = useCallback( + (item) => () => { + setCurrentFilter(item) + }, + [] + ) + return ( + {/* PC */}
{isOpenNotify ? ( @@ -247,6 +273,53 @@ function Inventory(_, ref) { )}
+ + {/* Mobile */} +
+
+
+
+
+ inventory-img +
Inventory
+
+
+ + + + +
+ + +
+
+
+
@@ -271,6 +344,10 @@ const InventoryCSS = styled.div({ alignItems: 'center', justifyContent: 'center', overflow: 'auto', + '@media (max-width: 1024px)': { + display: 'none', + visibility: 'hidden', + }, '::-webkit-scrollbar': { display: 'none', }, @@ -519,4 +596,24 @@ const InventoryCSS = styled.div({ }, }, }, + 'modal-mobile-inventory': { + display: 'none', + visibility: 'hidden', + '@media (max-width: 1024px)': { + display: 'block', + visability: 'visable', + marginTop: '150px', + }, + '.main-mobile': { + display: 'flex', + flexWrap: 'wrap', + justifyContent: 'center', + paddingBottom: '50px', + overflow: 'auto', + width: '100vw', + '@media (max-width: 1024px)': { + padding: '80px 0', + }, + } + } }) diff --git a/components/workshop/BuyerBoard.module.css b/components/workshop/BuyerBoard.module.css index 4eef3ef4..81bf01d9 100644 --- a/components/workshop/BuyerBoard.module.css +++ b/components/workshop/BuyerBoard.module.css @@ -331,6 +331,7 @@ .itemNameMobile { font-weight: 500; font-size: 19px; + text-transform: capitalize; } .itemInfoMobile { @@ -450,6 +451,28 @@ font-size: 16px; background-color: #472805 !important; border-radius: 10px; + outline: none; + border: none; } + .itemBtnConfirm:hover { + background: #472805 !important; + } + + .itemBtnConfirm:focus { + box-shadow: none !important; + background: #472805 !important; + } + + .itemBtnConfirm[disabled] { + background: #a8a8a880 !important; + color: #777 !important; + font-weight: 700 !important; + } + + .sellInput { + width: 100%; + background-color: transparent; + outline: none; + } } \ No newline at end of file diff --git a/components/workshop/workshop.module.css b/components/workshop/workshop.module.css index df6f1ac9..92015f8a 100644 --- a/components/workshop/workshop.module.css +++ b/components/workshop/workshop.module.css @@ -4,6 +4,10 @@ position: relative; } +.backgroundLayout { + display: none; +} + .workShopBg { background-image: url('/images/workshop/background.png'); background-repeat: no-repeat; @@ -269,7 +273,7 @@ } } -@media only screen and (max-width: 970px) { +@media only screen and (max-width: 1024px) { .workShopBg { display: none; } @@ -325,10 +329,9 @@ .body { padding: 0 15px; - margin-top: 60px; + margin: 60px auto 15px; background: #405160; - width: 100%; - margin-bottom: 20px; + max-width: 335px; border-radius: 10px; } diff --git a/components/worldmap/WorldMenu/index.tsx b/components/worldmap/WorldMenu/index.tsx index 5d8a2211..b875c0b8 100644 --- a/components/worldmap/WorldMenu/index.tsx +++ b/components/worldmap/WorldMenu/index.tsx @@ -65,6 +65,7 @@ function WorldMenu(props: Props) { display="flex" alignItems="center" justifyContent="center" + zIndex="9999" > { } } +const NFT_TYPES = ['openianCard', 'supplierCard', 'blacksmithCard'] + +export async function fetchUserProfessionNFTAmount(): Promise< + { type: string; ids: number[] }[] +> { + const nftList = await fetchUserProfessionNFT() + + const nftListIds = NFT_TYPES.map((type, index) => { + return nftList.map((nft) => { + if (nft.trait === index + 1) { + return nft.heroId + } + }) + }) + + const results = NFT_TYPES.map((type, index) => ({ + type, + ids: nftListIds[index][0] !== undefined ? nftListIds[index] : [], + })) + return results +} + export const activateProfession = async ( profession, heroId,