对接盲盒、NFT市场、NFT盒子

This commit is contained in:
myf 2022-06-20 17:47:14 +08:00
parent 202ab6b28f
commit 25e6b44560
46 changed files with 1666 additions and 414 deletions

View File

@ -24,8 +24,8 @@ REACT_APP_SNAPSHOT_VOTING_API = "https://xtjyd0liqe.execute-api.ap-northeast-1.a
# REACT_APP_REQUEST_URL = 'http://192.253.237.94:9090'
REACT_APP_REQUEST_URL = 'http://HousedeMacBook-Air.local:8080'
# REACT_APP_REQUEST_URL = 'http://101.35.117.69:9090'
# REACT_APP_REQUEST_URL = 'http://HousedeMacBook-Air.local:8080'
REACT_APP_REQUEST_URL = 'http://101.35.117.69:9090'
# REACT_APP_REQUEST_URL = 'http://192.168.2.147:8080'
# REACT_APP_REQUEST_URL = 'http://192.168.2.:8080'
# REACT_APP_REQUEST_URL = 'http://6o7g1fv83e.51xd.pub'

BIN
build.zip Normal file

Binary file not shown.

14
public/images/empty.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 173 KiB

View File

@ -1184,6 +1184,7 @@
"social contact demo": "社交demo",
"The rate of": "出率",
"%hour%hour": "%hour%小时",
"%minute%minute": "%minute%分钟",
"limit the quantity of": "限量",
"time limit": "限时",
"nft Smoking in the probability": "NFT抽中概率",
@ -1213,12 +1214,12 @@
"NFT name (grade) of successful synthesis = NFT name (grade) of raw material x quantity + synthesis cost": "合成成功NFT名称等级=原料NFT名称等级×数量+合成费用",
"bazaar": "市场",
"auction": "拍卖",
"I sell": "我的售卖",
"I want to sell": "我要售卖",
"transaction record": "交易记录",
"The total volume": "总交易额",
"The total number of transactions": "总交易数",
"Total number of auctions": "总拍卖次数",
"Total auction commission": "总拍卖返佣",
"Total transaction rebate": "总交易返佣",
"trading value": "出售价格",
"Detail": "详情",
@ -1269,5 +1270,19 @@
"unit price": "单价",
"seller": "卖方",
"purchaser": "买方",
"trading hour": "交易时间"
"trading hour": "交易时间",
"Please select quantity": "请选择数量",
"No data yet": "还没有数据哦~",
"purchase succeeds": "购买成功",
"Copy success": "复制成功",
"NFT Holder": "NFT 持有者",
"NFT With the total": "NFT拥有总量",
"official market": "官方市场",
"finished": "已结束",
"have not started": "未开始",
"category": "类别",
"GIVING": "礼物",
"PROPS": "道具",
"rarity": "稀有度",
"success": "成功"
}

View File

@ -0,0 +1,31 @@
import React from 'react'
import styled, { keyframes } from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Flex, Text, Image } from '@pancakeswap/uikit'
interface EmptyProps {
title?: string
marginTop?: string
}
const EmptyFlex = styled(Flex)`
justify-content: center;
align-items: center;
flex-direction: column;
`
const EmptyText = styled(Text)`
font-size: 16px;
color: #505f79;
margin-top: 12px;
`
const Empty: React.FC<EmptyProps> = ({ title, marginTop = '20px' }) => {
const { t } = useTranslation()
return (
<EmptyFlex style={{ marginTop }}>
<Image src="/images/empty.svg" width={228} height={200} />
<EmptyText>{title ? t(title) : t('No data yet')}</EmptyText>
</EmptyFlex>
)
}
export default Empty

View File

@ -29,6 +29,11 @@ export default {
56: '0x9ee1c805a9508c0799b157ebbe3108d57c8d8588', // NEED CHANGE 持币人分红
5: '0xd91740ABe3C54f9e9D8A2c80615618a5dd234556',
},
holderPool: {
97: '0xb9d1567848771b984f0330c49933b27f1dea0675',
56: '0xb9d1567848771b984f0330c49933b27f1dea0675', // NEED CHANGE NFT持有者
5: '0xb9d1567848771b984f0330c49933b27f1dea0675',
},
referralChef: {
97: '0x0866962d208e91ea8804db3f547cccf22fe39ea7',
56: '0x0866962d208e91ea8804db3f547cccf22fe39ea7', // NEED CHANGE 邀请或则军团长
@ -47,7 +52,7 @@ export default {
blindBox: {
97: '0x0e226d7b83b511ce224803b1330beb4a59bfa5d6',
56: '0x0e226d7b83b511ce224803b1330beb4a59bfa5d6', // NEED CHANGE 官方市场 盲盒
5: '0x0e226d7b83b511ce224803b1330beb4a59bfa5d6',
5: '0x99d61f4724d520246c43d0f1dea22c1f21c42a4e',
},
lotteryV2: {
97: '0x5790c3534F30437641541a0FA04C992799602998',
@ -57,7 +62,7 @@ export default {
multiCall: {
56: '0xfF6FD90A470Aaa0c1B8A54681746b07AcdFedc9B',
97: '0x8F3273Fb89B075b1645095ABaC6ed17B2d4Bc576',
5: '0xfF6FD90A470Aaa0c1B8A54681746b07AcdFedc9B',
5: '0x3a648f94783e35526072c02d7ce89c385b41ac19',
},
pancakeProfile: {
56: '0xDf4dBf6536201370F95e06A0F8a7a70fE40E388a',

View File

@ -1311,6 +1311,7 @@
"social contact demo": "social contact demo",
"The rate of": "The rate of",
"%hour%hour": "%hour%hour",
"%minute%minute": "%minute%minute",
"limit the quantity of": "limit the quantity of",
"time limit": "time limit",
"nft Smoking in the probability": "nft Smoking in the probability",
@ -1340,12 +1341,12 @@
"NFT name (grade) of successful synthesis = NFT name (grade) of raw material x quantity + synthesis cost": "NFT name (grade) of successful synthesis = NFT name (grade) of raw material x quantity + synthesis cost",
"bazaar": "bazaar",
"auction": "auction",
"I sell": "I sell",
"I want to sell": "I want to sell",
"transaction record": "transaction record",
"The total volume": "The total volume",
"The total number of transactions": "The total number of transactions",
"Total number of auctions": "Total number of auctions",
"Total auction commission": "Total auction commission",
"Total transaction rebate": "Total transaction rebate",
"trading value": "trading value",
"Detail": "Detail",
@ -1396,5 +1397,19 @@
"unit price": "unit price",
"seller": "seller",
"purchaser": "purchaser",
"trading hour": "trading hour"
"trading hour": "trading hour",
"Please select quantity": "Please select quantity",
"No data yet": "No data yet~",
"purchase succeeds": "purchase succeeds",
"Copy success": "Copy success",
"NFT Holder": "NFT Holder",
"NFT With the total": "NFT With the total",
"official market": "official market",
"finished": "finished",
"have not started": "have not started",
"category": "category",
"GIVING": "GIVING",
"PROPS": "PROPS",
"rarity": "rarity",
"success": "success"
}

View File

@ -28,7 +28,8 @@ import {
getBoardchefContract,
getReferralRewardchefContract,
getIdoPurchaseContract,
getHccGiftNftContract,
getBlindBoxContract,
getHolderPoolContract,
} from 'utils/contractHelpers'
// Imports below migrated from Exchange useContract.ts
@ -115,9 +116,13 @@ export const useIdoPurchase = () => {
const { library } = useActiveWeb3React()
return useMemo(() => getIdoPurchaseContract(library.getSigner()), [library])
}
export const useHccGiftNft = () => {
export const useBlindBox = () => {
const { library } = useActiveWeb3React()
return useMemo(() => getHccGiftNftContract(library.getSigner()), [library])
return useMemo(() => getBlindBoxContract(library.getSigner()), [library])
}
export const useHolderPool = () => {
const { library } = useActiveWeb3React()
return useMemo(() => getHolderPoolContract(library.getSigner()), [library])
}
export const useReferralRewardchef = () => {
const { library } = useActiveWeb3React()

View File

@ -7,5 +7,33 @@ export const getOfficialPage = (params) => {
params,
})
}
export const getOfficialPurchase = (id, params) => {
return request.request({
url: `/high_city/app/api/market/official/purchase/${id}`,
method: 'get',
params,
})
}
export const checkBuyResult = (params) => {
return request.request({
url: `/high_city/app/api/market/official/purchase/tx`,
method: 'get',
params,
})
}
export const getPurchaseRecord = (params) => {
return request.request({
url: `/high_city/app/api/market/official/purchase/record`,
method: 'get',
params,
})
}
export const getNftDetail = (id, params) => {
return request.request({
url: `/high_city/app/api/nft/detail/${id}`,
method: 'get',
params,
})
}
export default getOfficialPage

View File

@ -14,10 +14,18 @@ export const getBoxDetail = (id) => {
method: 'get',
})
}
export const getPurchase = (id) => {
export const getPurchase = (id, params) => {
return request.request({
url: `/high_city/app/api/box/purchase/${id}`,
method: 'get',
params,
})
}
export const checkBuyResult = (params) => {
return request.request({
url: `/high_city/app/api/box/purchase/tx`,
method: 'get',
params,
})
}

18
src/services/nftBox.ts Normal file
View File

@ -0,0 +1,18 @@
import request from 'utils/request'
export const getSelfPage = (params) => {
return request.request({
url: '/high_city/app/api/nft/self/page',
method: 'get',
params,
})
}
export const getNftDetails = (token, params) => {
return request.request({
url: `/high_city/app/api/nft/detail/${token}`,
method: 'get',
params,
})
}
export default getSelfPage

View File

@ -6,6 +6,7 @@ export interface ListProps {
price?: undefined
priceList?: PriceProps[]
type?: string
record?: boolean
}
export interface CoverResourceProps {
path?: string

View File

@ -80,3 +80,6 @@ export const getIdoPurchaseAddress = () => {
export const getBlindBoxAddress = () => {
return getAddress(addresses.blindBox)
}
export const getHolderPoolAddress = () => {
return getAddress(addresses.holderPool)
}

View File

@ -13,6 +13,7 @@ export const stakeBoard = async (masterChefContract, pid, amount) => {
}
export const unstakeBoard = async (masterChefContract) => {
console.log(masterChefContract)
const tx = await masterChefContract.withdrawHCC(options)
const receipt = await tx.wait()
return receipt.status

View File

@ -29,6 +29,7 @@ import {
getReferralRewardAddress,
getIdoPurchaseAddress,
getBlindBoxAddress,
getHolderPoolAddress,
} from 'utils/addressHelpers'
// ABI
@ -62,6 +63,7 @@ import MultiCallAbi from 'config/abi/Multicall.json'
import bunnySpecialCakeVaultAbi from 'config/abi/bunnySpecialCakeVault.json'
import bunnySpecialPredictionAbi from 'config/abi/bunnySpecialPrediction.json'
import idoPurchase from 'config/abi/idoPurchase.json'
import holderPool from 'config/abi/holderPool.json'
import blindBox from 'config/abi/blindBox.json'
import { ChainLinkOracleContract, PredictionsContract } from './types'
@ -134,7 +136,10 @@ export const getReferralRewardchefContract = (signer?: ethers.Signer | ethers.pr
export const getIdoPurchaseContract = (signer?: ethers.Signer | ethers.providers.Provider) => {
return getContract(idoPurchase, getIdoPurchaseAddress(), signer)
}
export const getHccGiftNftContract = (signer?: ethers.Signer | ethers.providers.Provider) => {
export const getHolderPoolContract = (signer?: ethers.Signer | ethers.providers.Provider) => {
return getContract(holderPool, getHolderPoolAddress(), signer)
}
export const getBlindBoxContract = (signer?: ethers.Signer | ethers.providers.Provider) => {
return getContract(blindBox, getBlindBoxAddress(), signer)
}
export const getClaimRefundContract = (signer?: ethers.Signer | ethers.providers.Provider) => {

View File

@ -0,0 +1,49 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Flex, Button, Text, Image } from '@pancakeswap/uikit'
import { getContract } from 'services/referral'
import FlexCom from './FlexCom'
interface DetailProps {
typeIndex: number
}
const DetailFlex = styled(Flex)`
flex-direction: column;
width: 100%;
height: 278px;
background: #f5ffff;
opacity: 1;
border-radius: 20px;
padding: 24px 30px;
margin-top: 24px;
`
const AssetsInfo: React.FC<DetailProps> = ({ typeIndex }) => {
const { t } = useTranslation()
const [link, setLink] = useState('')
const getLinkAddress = async () => {
const data = await getContract()
setLink(data)
}
useEffect(() => {
getLinkAddress()
}, [])
return (
<DetailFlex>
{typeIndex !== 0 && <FlexCom name={t('owner')} value="钱包地址" />}
<FlexCom
name={t('Contract address')}
typeLink={`https://bscscan.com/token/${link}`}
value={`${link && link.substring(0, 6)}...${link && link.substring(link.length - 4, link.length)}`}
/>
{typeIndex !== 0 && <FlexCom name="token ID" value="token ID" />}
<FlexCom name={t('Assets agreement')} value="ERC721" />
<FlexCom name={t('Assets and chain')} value="BSC" />
</DetailFlex>
)
}
export default AssetsInfo

View File

@ -0,0 +1,182 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Flex, Button, Text, Image } from '@pancakeswap/uikit'
import { useAccount } from 'state/userInfo/hooks'
import { fetchBlindBoxUserAllowances } from 'state/blindBox'
import { getAddress } from 'utils/addressHelpers'
import { useERC20 } from 'hooks/useContract'
import tokens from 'config/constants/tokens'
import useRefresh from 'hooks/useRefresh'
import useToast from 'hooks/useToast'
import { ListProps } from 'types/bazaar'
import { checkBuyResult } from 'services/bazaar'
import UnlockButton from 'components/UnlockButton'
import { useBuyTransaction, useApproveHcc } from '../hooks'
import { useCheckHccTokenBalance, useCheckTokenBalance } from '../../BlindBox/hooks'
interface DetailProps {
detail: ListProps
}
const PriceButton = styled(Button)`
width: 100%;
height: 60px;
background: linear-gradient(269deg, #1fc8d3 0%, #1fd4b0 100%);
border-radius: 30px;
font-size: 16px;
margin: 30px 0;
`
const AuthorizationBtn = styled(Button)`
width: 100%;
height: 60px;
background: linear-gradient(269deg, #1fc8d3 0%, #1fd4b0 100%);
border-radius: 30px;
font-size: 16px;
margin: 30px 0;
`
const UnlockButtonDiv = styled(UnlockButton)`
width: 100%;
height: 60px;
background: linear-gradient(269deg, #1fc8d3 0%, #1fd4b0 100%);
border-radius: 30px;
font-size: 16px;
margin: 30px 0;
`
const BtnStatus: React.FC<DetailProps> = ({ detail }) => {
const { t } = useTranslation()
const [txId, setTxId] = useState()
const [price, setPrice] = useState('')
const [allowanceList, setAllowanceList] = useState({ USDT: 0, HCC: 0 })
const [loading, setLoading] = useState(false)
const { toastSuccess, toastError } = useToast()
const { fastRefresh } = useRefresh()
const account = useAccount()
// const [link, setLink] = useState('')
const [buyVisible, setBuyVisible] = useState(false)
const usdtContract = useERC20(getAddress(tokens.usdt.address))
const hccContract = useERC20(getAddress(tokens.hcc.address))
const { onApprove: onUsdtApprove } = useApproveHcc(usdtContract)
const { onApprove: onHccApprove } = useApproveHcc(hccContract)
const getAllowances = async () => {
const allowances = await fetchBlindBoxUserAllowances(account)
detail.priceList &&
detail.priceList.forEach((item) => {
if (detail.priceList.length === 2) {
if (allowances.usdt && allowances.hcc) {
setBuyVisible(true)
}
} else {
console.log(' ')
if (allowances.hcc && item.label === 'HCC') {
setBuyVisible(true)
} else if (allowances.usdt && item.label === 'USDT') {
setBuyVisible(true)
}
}
})
setAllowanceList({
USDT: allowances.usdt,
HCC: allowances.hcc,
})
}
useEffect(() => {
if (detail.priceList) {
const priceList = detail.priceList.map((item) => {
return `${Number(item.value).toFixed(3)} ${item.label}`
})
setPrice(priceList.join('-'))
}
if (account) {
getAllowances()
}
}, [account])
const buyTransaction = useBuyTransaction()
const [onCheckBalanceHcc] = useCheckHccTokenBalance()
const [onCheckBalanceUsdt] = useCheckTokenBalance()
const handleBuy = async () => {
let HccBalance = true
detail.priceList &&
detail.priceList.forEach((item) => {
if (item.label === 'HCC') {
HccBalance = onCheckBalanceHcc(Number(item.value))
} else {
HccBalance = onCheckBalanceUsdt(Number(item.value))
}
})
if (!HccBalance) {
return
}
setLoading(true)
const res = await buyTransaction(detail.id, {
id: detail.id,
num: 1,
})
setTxId(res.hash)
}
const getTransactionResult = async () => {
const res = await checkBuyResult({ tx: txId })
if (res) {
setLoading(false)
setTxId(undefined)
toastSuccess(t('purchase succeeds'))
}
}
useEffect(() => {
if (txId && loading) {
getTransactionResult()
}
}, [fastRefresh])
const handleApprove = async (approve) => {
try {
setLoading(true)
await approve()
setLoading(false)
getAllowances()
} catch (e) {
console.error(e)
}
}
return (
<>
{buyVisible && <PriceButton onClick={handleBuy}>{t('Buy It Now')}</PriceButton>}
<Flex style={{ width: '100%' }}>
{detail.priceList.map((item, i) =>
item.label === 'HCC' && !allowanceList.HCC ? (
<AuthorizationBtn
key={item.label}
disabled={loading}
onClick={() => {
handleApprove(onHccApprove)
}}
>
{t('Approve %coin% Contract', { coin: 'HCC' })}
</AuthorizationBtn>
) : item.label === 'USDT' && !allowanceList.USDT ? (
<AuthorizationBtn
key={item.label}
disabled={loading}
onClick={() => {
handleApprove(onUsdtApprove)
}}
>
{t('Approve %coin% Contract', { coin: 'USDT' })}
</AuthorizationBtn>
) : (
''
),
)}
</Flex>
</>
)
}
export default BtnStatus

View File

@ -1,12 +1,16 @@
import React, { useState, useEffect } from 'react'
import qs from 'querystring'
import { useLocation, useHistory, useParams } from 'react-router-dom'
import styled, { keyframes } from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import Pagination from '@mui/material/Pagination'
import { Flex, Text, Input, Image, Dropdown } from '@pancakeswap/uikit'
import { useAccount } from 'state/userInfo/hooks'
import useRefresh from 'hooks/useRefresh'
import useToast from 'hooks/useToast'
import { ListProps } from 'types/bazaar'
import Empty from 'components/Empty'
import { useGetOfficialPage } from '../hooks'
import HeaderOperation from './HeaderOperation'
import ContentShop from './ContentShop'
@ -129,10 +133,13 @@ const FlexOption = styled(Flex)`
const Content: React.FC = () => {
const { t } = useTranslation()
const location = useLocation()
const history = useHistory()
const { toastSuccess, toastError } = useToast()
const [detail, setDetail] = useState<ListProps>({})
const [detailVisible, setDetailVisible] = useState(false)
const typeList = [
{ label: t('All'), type: 1 },
{ label: t('official market'), type: 1 },
{ label: t('bazaar'), type: 2 },
{ label: t('auction'), type: 3 },
]
@ -155,10 +162,10 @@ const Content: React.FC = () => {
const [list, setList] = useState<ListProps[]>([])
const statusList = [
{ label: t('All'), id: '1', grade: '' },
{ label: t('common'), id: '5', grade: 'NORMAL' },
{ label: t('uncommon'), id: '4', grade: 'RARE' },
{ label: t('epic'), id: '2', grade: 'EPIC' },
{ label: t('legend'), id: '3', grade: 'LEGEND' },
{ label: t('uncommon'), id: '4', grade: 'RARE' },
{ label: t('common'), id: '5', grade: 'NORMAL' },
]
const [statusIndex, setStatusIndex] = useState(0)
@ -173,6 +180,7 @@ const Content: React.FC = () => {
const params = {
page: pageNum,
size: 10,
name: searchTitle,
grade: searchGrade,
}
const data = await getOfficialPage(params)
@ -187,6 +195,15 @@ const Content: React.FC = () => {
arr.push(obj)
})
setList(arr)
if (location.search) {
const locationData = qs.parse(location.search.slice(1))
arr.forEach((item) => {
if (item.id === locationData.id) {
setDetail(item)
setDetailVisible(true)
}
})
}
setCount(getTotalPageNum(data.total, data.size))
}
@ -194,16 +211,19 @@ const Content: React.FC = () => {
getData()
}, [pageNum, searchGrade])
useEffect(() => {
setPriceSelect({ label: t('Prices go from low to high'), value: '3' })
}, [t])
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
const { value } = evt.target
setSearchTitle(value)
}
const searchList = () => {
console.log('search')
setPage(1)
getData()
}
const showDetail = (val) => {
console.log(val)
setDetail(val)
setDetailVisible(!detailVisible)
}
@ -220,10 +240,27 @@ const Content: React.FC = () => {
setGrade(grade)
setPage(1)
}
const changePage = (index) => {
if (index !== 0) {
toastError(t('This page is not currently open'))
return
}
setTypeIndex(index)
}
const lookDetail = (val) => {
setDetail(val)
setDetailVisible(true)
// detail
}
const closeDetail = () => {
history.push('/bazaar')
setDetailVisible(false)
}
return (
<>
{detailVisible && <ShopDetail detail={detail} typeIndex={typeIndex} close={() => setDetailVisible(false)} />}
{detailVisible && <ShopDetail detail={detail} typeIndex={typeIndex} close={closeDetail} />}
{!detailVisible && (
<>
@ -233,7 +270,7 @@ const Content: React.FC = () => {
return (
<TypeItem
key={item.type}
onClick={() => setTypeIndex(index)}
onClick={() => changePage(index)}
className={typeIndex === index ? 'active' : ''}
>
{item.label}
@ -242,7 +279,7 @@ const Content: React.FC = () => {
})}
</TypeFlex>
<HeaderOperation activeIndex={typeIndex} />
<HeaderOperation activeIndex={typeIndex} getDetail={(v) => lookDetail(v)} />
</HeaderFlex>
<Transaction />
<StatusFlex>
@ -281,25 +318,27 @@ const Content: React.FC = () => {
</Dropdown>
</SelectMain>
)}
<SelectMain>
<Dropdown
position="bottom"
target={
<SelectFlex>
<Image src="/images/nft/bottom-arrows.svg" width={12} height={7} />
<SelectText>{priceSelect.label}</SelectText>
</SelectFlex>
}
>
{newPrice.map((item) => {
return (
<FlexOption key={item.value} onClick={() => setPriceSelect(item)}>
{item.label}
</FlexOption>
)
})}
</Dropdown>
</SelectMain>
{typeIndex !== 0 && (
<SelectMain>
<Dropdown
position="bottom"
target={
<SelectFlex>
<Image src="/images/nft/bottom-arrows.svg" width={12} height={7} />
<SelectText>{priceSelect.label}</SelectText>
</SelectFlex>
}
>
{newPrice.map((item) => {
return (
<FlexOption key={item.value} onClick={() => setPriceSelect(item)}>
{item.label}
</FlexOption>
)
})}
</Dropdown>
</SelectMain>
)}
</Flex>
</StatusFlex>
<SearchDiv>
@ -311,9 +350,12 @@ const Content: React.FC = () => {
</InputMain>
</SearchDiv>
<ContentShop list={list} getDetail={(v) => showDetail(v)} />
<Flex justifyContent="center" padding={10}>
<Pagination count={count} onChange={pageChange} page={pageNum} />
</Flex>
{list.length > 0 && (
<Flex justifyContent="center" padding={10}>
<Pagination count={count} onChange={pageChange} page={pageNum} />
</Flex>
)}
{list.length === 0 && <Empty />}
</>
)}
</>

View File

@ -3,6 +3,7 @@ import styled, { keyframes } from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { formatTimeNumber } from 'utils/formatBalance'
import { Flex, Text, Image } from '@pancakeswap/uikit'
import useToast from 'hooks/useToast'
import { ListProps } from 'types/bazaar'
import { useAccount } from 'state/userInfo/hooks'
import useRefresh from 'hooks/useRefresh'
@ -67,6 +68,8 @@ const FooterValue = styled(Flex)`
const ContentShop: React.FC<ContentShop> = ({ list, getDetail }) => {
const { t } = useTranslation()
const account = useAccount()
const { toastSuccess, toastError } = useToast()
const showDetail = (id) => {
getDetail(id)
@ -80,7 +83,7 @@ const ContentShop: React.FC<ContentShop> = ({ list, getDetail }) => {
<ShopList
item={item}
width={278}
height={280}
height={302}
img={item.coverResource.url}
grade={item.grade}
borderRadius="20px 20px 0 0"

View File

@ -18,6 +18,10 @@ const FlexDiv = styled(Flex)`
justify-content: space-between;
align-items: center;
margin-top: 14px;
& > .linkText:hover {
color: #1fc7d4 !important;
border-bottom: 1px solid #1fc7d4 !important;
}
`
const TextLink = styled(Text)`
cursor: pointer;
@ -46,6 +50,7 @@ const FlexCom: React.FC<FlexProps> = ({
color={rightColor}
style={{ color: textColor, borderBottom: `1px solid ${textColor}` }}
onClick={openPage}
className="linkText"
>
{value}
</TextLink>

View File

@ -11,6 +11,7 @@ import SellModal from './SellModal'
interface HeaderOperationProps {
activeIndex?: number
getDetail?: (v) => void
}
const HeaderButton = styled(Button)`
@ -34,16 +35,18 @@ const HeaderButton = styled(Button)`
}
`
const HeaderOperation: React.FC<HeaderOperationProps> = ({ activeIndex }) => {
const HeaderOperation: React.FC<HeaderOperationProps> = ({ activeIndex, getDetail }) => {
const { t } = useTranslation()
const [onTransactionRecord] = useModal(<TransactionRecord />)
const [onTransactionRecord] = useModal(
<TransactionRecord activeIndex={activeIndex} recordDetail={(v) => getDetail(v)} />,
)
const [onAuctionRecord] = useModal(<AuctionRecord />)
const [onSellModal] = useModal(<SellModal />)
return (
<Flex alignContent="center">
<HeaderButton onClick={onSellModal}>{t('I sell')}</HeaderButton>
<HeaderButton onClick={onSellModal}>{t('I want to sell')}</HeaderButton>
{/* 当顶部切换选中的是全部和市场则显示交易记录,选中拍卖时展示拍卖纪录 */}
{activeIndex === 2 ? (
<HeaderButton onClick={onAuctionRecord}>{t('Auctions a record')}</HeaderButton>

View File

@ -1,21 +1,25 @@
import React, { useState, useEffect } from 'react'
import styled, { keyframes } from 'styled-components'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Heading, Flex, Button, Text, Input, Image, useModal } from '@pancakeswap/uikit'
import { Flex, Button, Text, Image } from '@pancakeswap/uikit'
import { useAccount } from 'state/userInfo/hooks'
import useRefresh from 'hooks/useRefresh'
import useToast from 'hooks/useToast'
import { ListProps } from 'types/bazaar'
import { getContract } from 'services/referral'
import { checkBuyResult } from 'services/bazaar'
import UnlockButton from 'components/UnlockButton'
import ShopList from './ShopList'
import FlexCom from './FlexCom'
import AuctionTable from './AuctionTable'
import TransactionTable from './TransactionTable'
import AuctionRule from './AuctionRule'
import AssetsInfo from './AssetsInfo'
import BtnStatus from './BtnStatus'
interface DetailProps {
close: () => void
detail: ListProps
typeIndex: number | string
typeIndex: number
}
const HeaderFlex = styled(Flex)`
@ -35,7 +39,7 @@ const ShopText = styled(Text)`
const MainFlex = styled(Flex)`
margin-top: 36px;
padding: 30px;
background: rgba(255, 255, 255, 0.39);
background: #fff;
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.16);
border-radius: 20px;
align-items: center;
@ -51,23 +55,17 @@ const MainFlex = styled(Flex)`
`
const ShopFlex = styled(Flex)`
width: 476px;
height: 590px;
flex-direction: column;
background: #fff;
box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.15);
opacity: 1;
border-radius: 20px;
position: relative;
`
const Detail = styled.div`
const Detail = styled(Flex)`
width: 614px;
height: 590px;
/* min-height: 580px; */
flex-direction: column;
justify-content: space-between;
/* height: 590px; */
box-sizing: border-box;
background: rgba(245, 255, 255, 0.39);
box-shadow: 0px 2px 8px rgba(0, 67, 70, 0.15);
border-radius: 20px;
padding: 20px 30px;
margin-left: 30px;
${({ theme }) => theme.mediaQueries.xs} {
margin-top: 30px;
@ -84,7 +82,6 @@ const TitleText = styled(Text)`
font-size: 28px;
color: #333333;
text-align: center;
margin-top: 8px;
`
const PriceButton = styled(Button)`
width: 100%;
@ -94,18 +91,78 @@ const PriceButton = styled(Button)`
font-size: 16px;
margin: 30px 0;
`
const AuthorizationBtn = styled(Button)`
width: 100%;
height: 60px;
background: linear-gradient(269deg, #1fc8d3 0%, #1fd4b0 100%);
border-radius: 30px;
font-size: 16px;
margin: 30px 0;
`
const UnlockButtonDiv = styled(UnlockButton)`
width: 100%;
height: 60px;
background: linear-gradient(269deg, #1fc8d3 0%, #1fd4b0 100%);
border-radius: 30px;
font-size: 16px;
margin: 30px 0;
`
const DetailFlexInfo = styled(Flex)`
flex-direction: column;
width: 100%;
height: 304px;
background: #f5ffff;
box-shadow: 0px 2px 8px rgba(0, 67, 70, 0.15);
border-radius: 20px;
padding: 26px 30px;
`
const DetailHeaderFlex = styled(Flex)`
justify-content: space-between;
align-items: center;
`
const ShopDetail: React.FC<DetailProps> = ({ close, detail, typeIndex }) => {
const { t } = useTranslation()
const [link, setLink] = useState('')
const [txId, setTxId] = useState()
const [price, setPrice] = useState('')
const [loading, setLoading] = useState(false)
const { toastSuccess, toastError } = useToast()
const { fastRefresh } = useRefresh()
const account = useAccount()
useEffect(() => {
if (detail.priceList) {
const priceList = detail.priceList.map((item) => {
return `${Number(item.value).toFixed(3)} ${item.label}`
})
setPrice(priceList.join('-'))
}
}, [account])
const getLinkAddress = async () => {
const data = await getContract()
setLink(data)
const getTransactionResult = async () => {
const res = await checkBuyResult({ tx: txId })
if (res) {
setLoading(false)
setTxId(undefined)
toastSuccess(t('purchase succeeds'))
}
}
useEffect(() => {
getLinkAddress()
}, [])
if (txId && loading) {
getTransactionResult()
}
}, [fastRefresh])
const getLink = () => {
const createInput = document.createElement('input')
createInput.value = `${window.location.href}?id=${detail.id}`
document.body.appendChild(createInput)
createInput.select()
document.execCommand('Copy')
createInput.remove()
toastSuccess(t('Copy success'))
}
return (
<>
<HeaderFlex>
@ -118,52 +175,57 @@ const ShopDetail: React.FC<DetailProps> = ({ close, detail, typeIndex }) => {
item={detail}
width={476}
height={606}
img={detail.coverResource.url}
img={detail?.coverResource.url}
grade={detail.grade}
borderRadius="20px"
/>
</ShopFlex>
<Detail>
<Flex justifyContent="flex-end">
<Image src="/images/nft/share.svg" width={35} height={35} style={{ cursor: 'pointer' }} />
</Flex>
<TitleText>{detail.name}</TitleText>
{typeIndex !== 0 && (
<DetailFlexInfo>
<DetailHeaderFlex>
<TitleText>{detail.name}</TitleText>
<Image
src="/images/nft/share.svg"
width={35}
height={35}
onClick={getLink}
style={{ cursor: 'pointer' }}
/>
</DetailHeaderFlex>
{typeIndex !== 0 && (
<FlexCom
name={t('Auction countdown')}
value="10:57:55:79"
size="18px"
rightSize="30px"
textColor="#666666"
/>
)}
<FlexCom
name={t('Auction countdown')}
value="10:57:55:79"
name={t('present price%price%', { price: '' })}
value={price}
size="18px"
rightSize="30px"
textColor="#333333"
textColor="#1FC7D4"
/>
)}
<FlexCom
name={t('present price%price%', { price: 'HCC=100U' })}
value="70.000.000"
size="18px"
rightSize="30px"
textColor="#1FC7D4"
/>
{typeIndex === 0 ? (
<PriceButton>{t('Buy It Now')}</PriceButton>
) : (
<PriceButton>{t('Fixed markup (%price% premium)', { price: '10%' })}</PriceButton>
)}
{typeIndex !== 0 && <FlexCom name={t('owner')} value="钱包地址" />}
{/* <FlexCom name={t('Contract address')} value={t('Contract address')} /> */}
<FlexCom
name={t('Contract address')}
typeLink={`https://bscscan.com/token/${link}`}
value={`${link && link.substring(0, 6)}...${link && link.substring(link.length - 4, link.length)}`}
/>
{typeIndex !== 0 && <FlexCom name="token ID" value="token ID" />}
<FlexCom name={t('Assets agreement')} value="ERC721" />
<FlexCom name={t('Assets and chain')} value="BSC" />
{detail.record ? (
''
) : typeIndex === 0 ? (
!account ? (
<UnlockButtonDiv />
) : (
<BtnStatus detail={detail} />
)
) : (
<PriceButton>{t('Fixed markup (%price% premium)', { price: '10%' })}</PriceButton>
)}
</DetailFlexInfo>
<AssetsInfo typeIndex={typeIndex} />
</Detail>
</MainFlex>
<AuctionTable />
<TransactionTable />
<AuctionRule />
{typeIndex !== 0 && <AuctionTable />}
{typeIndex !== 0 && <TransactionTable />}
{typeIndex !== 0 && <AuctionRule />}
</>
)
}

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'
import styled, { keyframes } from 'styled-components'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Flex, Image } from '@pancakeswap/uikit'
@ -49,14 +49,25 @@ const ShopItem = styled(Flex)`
text-align: center;
transform: rotate(-45deg);
position: relative;
padding: 2px 0;
left: -36px;
top: 14px;
width: 121px;
padding: 8px 0;
left: -33px;
top: 26px;
width: 150px;
color: white;
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1);
letter-spacing: 1px;
font-size: 12px;
font-size: 14px;
${({ theme }) => theme.mediaQueries.xs} {
padding: 2px 0;
left: -43px;
top: 16px;
}
${({ theme }) => theme.mediaQueries.lg} {
padding: 8px 0;
left: -33px;
top: 26px;
}
}
& > .epic {
background: linear-gradient(-90deg, #efea48 0%, #f32121 100%);
@ -72,10 +83,6 @@ const ShopItem = styled(Flex)`
}
}
`
const ItemText = styled(Flex)`
padding: 25px 0;
justify-content: center;
`
interface ShopListItemProps {
item?: Detail
@ -130,10 +137,6 @@ const ShopList: React.FC<ShopListItemProps> = ({
{grade === 'RARE' && <div className="ribbon1 uncommon">{t('uncommon')}</div>}
{grade === 'NORMAL' && <div className="ribbon1 common">{t('common')}</div>}
</div>
{/* {item.type === 1 && <Image src="/images/nft/epic-icon.svg" width={width} height={height} />}
{item.type === 2 && <Image src="/images/nft/legend-icon.svg" width={width} height={height} />}
{item.type === 3 && <Image src="/images/nft/uncommon-icon.svg" width={width} height={height} />}
{item.type === 4 && <Image src="/images/nft/box.svg" width={width} height={height} />} */}
{img ? (
<Image src={img} width={width} height={height} />
) : (

View File

@ -50,23 +50,23 @@ const Transaction: React.FC = () => {
return (
<TransactionFlex>
<TransactionItem>
<TransactionItemNum>1.000000</TransactionItemNum>
<TransactionItemNum>0</TransactionItemNum>
<TransactionItemLabel>{t('The total volume')}</TransactionItemLabel>
</TransactionItem>
<Separate />
<TransactionItem>
<TransactionItemNum>1.000000</TransactionItemNum>
<TransactionItemNum>0</TransactionItemNum>
<TransactionItemLabel>{t('The total number of transactions')}</TransactionItemLabel>
</TransactionItem>
<Separate />
<TransactionItem>
<TransactionItemNum>1.000000</TransactionItemNum>
<TransactionItemNum>0</TransactionItemNum>
<TransactionItemLabel>{t('Total number of auctions')}</TransactionItemLabel>
</TransactionItem>
<Separate />
<TransactionItem>
<TransactionItemNum>1.000000</TransactionItemNum>
<TransactionItemLabel>{t('Total auction commission')}</TransactionItemLabel>
<TransactionItemNum>0</TransactionItemNum>
<TransactionItemLabel>{t('Total transaction rebate')}</TransactionItemLabel>
</TransactionItem>
</TransactionFlex>
)

View File

@ -1,10 +1,16 @@
import React, { useState } from 'react'
import React, { useState, useEffect } from 'react'
import dayjs from 'dayjs'
import styled from 'styled-components'
import Pagination from '@mui/material/Pagination'
import { Text, Button, Image, Flex } from '@pancakeswap/uikit'
import { useTranslation } from 'contexts/Localization'
import Empty from 'components/Empty'
import { useGetPurchaseRecord, useGetNftDetail } from '../hooks'
interface TransactionRecordProps {
onDismiss?: () => void
activeIndex?: number
recordDetail?: (v) => void
}
const FlexMain = styled.div`
@ -70,7 +76,10 @@ const ThemedItem = styled.div`
color: #666666;
border-top: 1px solid #e3e3e3;
`
const TableBody = styled.div``
const TableBody = styled.div`
height: 420px;
overflow-y: auto;
`
const TrFlex = styled(Flex)`
padding: 10px 0;
border-top: 1px solid #e3e3e3;
@ -112,7 +121,7 @@ const HashText = styled(Text)`
border-bottom: 1px solid #1fc7d4;
`
const TransactionRecord: React.FC<TransactionRecordProps> = ({ onDismiss }) => {
const TransactionRecord: React.FC<TransactionRecordProps> = ({ onDismiss, activeIndex, recordDetail }) => {
const { t } = useTranslation()
const typeList = [
@ -125,27 +134,70 @@ const TransactionRecord: React.FC<TransactionRecordProps> = ({ onDismiss }) => {
const ThemedList = [t('NFT name'), t('price'), t('Time'), t('state'), t('operation')]
const list = [
{ name: 'Cat goddess Emerald ', icon: '', price: '1', time: '2022-02-02', status: t('Has been selling'), id: '1' },
]
const [list, setList] = useState([])
const [pageNum, setPage] = useState(1)
const [count, setCount] = useState(undefined)
const getPurchaseRecord = useGetPurchaseRecord()
const getList = async () => {
const result = await getPurchaseRecord(pageNum, 10)
setCount(getTotalPageNum(result.total, result.size))
const arr = []
result.content.forEach((item) => {
const obj = item
obj.priceList = []
Object.keys(obj.price).forEach((childItem) => {
obj.priceList.push({ label: childItem, value: obj.price[childItem] })
})
obj.price = undefined
arr.push(obj)
})
setList(arr)
}
useEffect(() => {
getList()
}, [pageNum])
const pageChange = (event, page) => {
setPage(page)
}
const getTotalPageNum = (total, pageSize) => {
const countTotal = ((Number(total) + Number(pageSize) - 1) / Number(pageSize)).toString()
return parseInt(countTotal)
}
const goHash = (val) => {
window.open(`https://goerli.etherscan.io/tx/${val.tx}`)
}
const getNftDetail = useGetNftDetail()
const lookDetail = async (item) => {
const res = await getNftDetail(item.token, { token: item.token })
const obj = res.info
obj.record = true
recordDetail(obj)
onDismiss()
}
return (
<FlexMain>
<CloseImage src="/images/nft/close.svg" width={15} height={15} onClick={onDismiss} />
<HeaderText>{t('transaction record')}</HeaderText>
<TypeFlex>
{typeList.map((item, index) => {
return (
<TypeItem
key={item.value}
className={index === typeIndex ? 'active' : ''}
onClick={() => setTypeIndex(index)}
>
{item.label}
</TypeItem>
)
})}
</TypeFlex>
{activeIndex !== 0 && (
<TypeFlex>
{typeList.map((item, index) => {
return (
<TypeItem
key={item.value}
className={index === typeIndex ? 'active' : ''}
onClick={() => setTypeIndex(index)}
>
{item.label}
</TypeItem>
)
})}
</TypeFlex>
)}
<>
<TableThemed>
{ThemedList.map((item) => {
@ -153,24 +205,35 @@ const TransactionRecord: React.FC<TransactionRecordProps> = ({ onDismiss }) => {
})}
</TableThemed>
<TableBody>
{list.length === 0 && <Empty />}
{list.map((item) => {
return (
<TrFlex key={item.id}>
<TdFlex>{item.goodsName}</TdFlex>
<TdFlex>
<TdImage src="/images/nft/epic-icon.svg" width={24} height={40} />
{item.name}
{item.priceList.map((childItem, childIndex) => {
return (
<Flex alignItems="center" key={childItem.label}>
<>{Number(childItem.value).toFixed(2)}</>
<Text color="text">{childItem.label}</Text>
{childIndex === 0 && item.priceList.length === 2 && <Text margin="0 5px">-</Text>}
</Flex>
)
})}
</TdFlex>
<TdFlex>{item.price}</TdFlex>
<TdFlex>{item.time}</TdFlex>
<TdFlex>{item.status}</TdFlex>
<TdFlex>{dayjs(Number(item.tradeTime)).format('YYYY-MM-DD HH:mm:ss')}</TdFlex>
<TdFlex>{item.status ? item.status : t('success')}</TdFlex>
<TdBtnFlex>
<DetailButton>{t('Detail')}</DetailButton>
<HashText>{t('deal Hash')}</HashText>
<DetailButton onClick={() => lookDetail(item)}>{t('Detail')}</DetailButton>
<HashText onClick={() => goHash(item)}>{t('deal Hash')}</HashText>
</TdBtnFlex>
</TrFlex>
)
})}
</TableBody>
<Flex justifyContent="center" padding={10}>
<Pagination count={count} onChange={pageChange} page={pageNum} />
</Flex>
</>
</FlexMain>
)

View File

@ -1,5 +1,8 @@
import { useCallback } from 'react'
import { getOfficialPage } from 'services/bazaar'
import { useBlindBox } from 'hooks/useContract'
import { getAddress, getBlindBoxAddress } from 'utils/addressHelpers'
import { getOfficialPage, getOfficialPurchase, getPurchaseRecord, getNftDetail } from 'services/bazaar'
import { ethers, Contract } from 'ethers'
export const useGetOfficialPage = () => {
const data = async (params) => {
@ -9,4 +12,76 @@ export const useGetOfficialPage = () => {
return data
}
// 授权usdt
export const useApproveUsdt = (tokenContract: Contract) => {
const handleApprove = useCallback(async () => {
try {
const tx = await tokenContract.approve(getBlindBoxAddress(), ethers.constants.MaxUint256)
const receipt = await tx.wait()
return receipt.status
} catch (e) {
return false
}
}, [tokenContract])
return { onApprove: handleApprove }
}
export const useApproveHcc = (tokenContract: Contract) => {
const handleApprove = useCallback(async () => {
try {
const tx = await tokenContract.approve(getBlindBoxAddress(), ethers.constants.MaxUint256)
const receipt = await tx.wait()
return receipt.status
} catch (e) {
return false
}
}, [tokenContract])
return { onApprove: handleApprove }
}
// 购买
// export const useGetPurchase = () => {
// const data = async (id, params) => {
// const result = await getOfficialPurchase(id, params)
// console.log(result)
// return result
// }
// return data
// }
// 交易记录
export const useGetPurchaseRecord = () => {
const data = async (page, size) => {
const result = await getPurchaseRecord({ page, size })
console.log(result)
return result
}
return data
}
// 购买合约
export const useBuyTransaction = () => {
const blindBox = useBlindBox()
const address = getBlindBoxAddress()
const transaction = async (id, params) => {
const result = await getOfficialPurchase(id, params)
const { num, hccPrice, otherPaymentPrice, timestamp, code, type, sign } = result
const mintParams = [address, num, hccPrice, otherPaymentPrice, timestamp, code, type, sign]
const res = await blindBox.mint(...mintParams)
console.log(res)
return res
}
return transaction
}
// 交易详情
export const useGetNftDetail = () => {
const data = async (token, params) => {
const result = await getNftDetail(token, params)
console.log(result)
return result
}
return data
}
export default useGetOfficialPage

View File

@ -1,5 +1,7 @@
import React, { useEffect } from 'react'
import React, { useEffect, useState } from 'react'
import dayjs from 'dayjs'
import styled from 'styled-components'
import useRefresh from 'hooks/useRefresh'
import { useTranslation } from 'contexts/Localization'
import { Flex, Text, useModal } from '@pancakeswap/uikit'
import SeriesDetail from './SeriesDetail'
@ -19,18 +21,8 @@ interface DetailProp {
interface OperationProp {
detail: DetailProp
totalNumber?: number
}
// interface listProps {
// beginTime?: string
// coverResource: coverResourceProps
// endTime?: string
// id?: string
// name?: string
// price: priceProps
// purchased?: string | number
// total?: string | number
// type?: string
// }
interface coverResourceProps {
path?: string
url?: string
@ -72,17 +64,109 @@ const TipFlex = styled(Flex)`
cursor: pointer;
`
const Header: React.FC<OperationProp> = ({ detail }) => {
const Header: React.FC<OperationProp> = ({ detail, totalNumber }) => {
const { t } = useTranslation()
const [onSeriesDetail] = useModal(<SeriesDetail detail={detail} />)
const [countDown, setCountDown] = useState('')
const [quantitativeType, setQuantitativeType] = useState(0)
const [timeLimit, setTimeLimit] = useState(0)
const { fastRefresh } = useRefresh()
useEffect(() => {
if (detail.type === 'QUANTITATIVE') {
const num = totalNumber
switch (new Date(detail.beginTime).getTime() > new Date().getTime()) {
case true:
setQuantitativeType(0)
break
case false:
if (num > 0) {
setQuantitativeType(1)
} else {
setQuantitativeType(2)
}
break
default:
setQuantitativeType(0)
}
} else {
const date2 = dayjs(new Date(detail.endTime).getTime()).diff(dayjs())
switch (new Date(detail.beginTime).getTime() > new Date().getTime()) {
case true:
setTimeLimit(0)
break
case false:
if (date2 > 0) {
setTimeLimit(1)
} else {
setTimeLimit(2)
}
break
default:
setTimeLimit(1)
}
}
}, [fastRefresh])
const countDownFun = (date) => {
const date1 = dayjs()
const date2 = dayjs(date)
const time = date2.diff(date1)
if (time > 0) {
const hour = Math.floor(time / (1000 * 60 * 60))
const minute = Math.floor((time / (1000 * 60)) % 60)
const second = Math.round((time / 1000) % 60)
setCountDown(`${hour}:${minute}:${second}`)
} else {
setTimeLimit(2)
}
}
useEffect(() => {
const date2 = dayjs(new Date(detail.endTime).getTime()).diff(dayjs())
if (date2 > 0) {
setTimeout(() => {
const time = new Date(detail.endTime).getTime() - 1
countDownFun(time)
}, 1000)
} else {
setTimeLimit(2)
}
}, [countDown])
const getTime = (date) => {
const date1 = dayjs()
const date2 = dayjs(date)
const time = date2.diff(date1)
return time
}
return (
<HeaderFlex>
<div />
<Flex>
<TextKey>{t('time remaining')}</TextKey>
<TextVal>{t('%hour%hour', { hour: 7 })}</TextVal>
</Flex>
<div style={{ width: '35px', marginLeft: '20px' }} />
{detail.type === 'QUANTITATIVE' && (
<>
{quantitativeType === 0 && <TextKey>{t('have not started')}</TextKey>}
{quantitativeType === 1 && (
<Flex>
<TextKey>{t('remaining quantity')}</TextKey>
<TextVal>{totalNumber}</TextVal>
</Flex>
)}
{quantitativeType === 2 && <TextKey>{t('finished')}</TextKey>}
</>
)}
{detail.type === 'TIME_LIMIT' && (
<>
{timeLimit === 0 && <TextKey>{t('have not started')}</TextKey>}
{timeLimit === 1 && (
<Flex>
<TextKey>{t('time remaining')}</TextKey>
<TextVal>{countDown}</TextVal>
</Flex>
)}
{timeLimit === 2 && <TextKey>{t('finished')}</TextKey>}
</>
)}
<TipFlex onClick={onSeriesDetail}>?</TipFlex>
</HeaderFlex>
)

View File

@ -1,20 +1,17 @@
import React, { useEffect, useState } from 'react'
import React from 'react'
import styled from 'styled-components'
import { formatTimeNumber } from 'utils/formatBalance'
import BigNumber from 'bignumber.js'
import { useTranslation } from 'contexts/Localization'
import { Flex, Text } from '@pancakeswap/uikit'
import { TOKEN_SYMBOL } from 'config/index'
import { ListProp } from 'types/blindBox'
import StepCom from './StepCom'
interface OperationProp {
detail: ListProp
stepNum: number
getNum?: (v) => void
}
const DetailDiv = styled.div`
margin-top: -100px;
padding-bottom: 45px;
`
@ -34,13 +31,12 @@ const HeaderText = styled(Text)`
margin: 0 5px 0 10px;
`
const Operation: React.FC<OperationProp> = ({ detail, getNum }) => {
const Operation: React.FC<OperationProp> = ({ detail, getNum, stepNum }) => {
const { t } = useTranslation()
const getStepValue = (v) => {
getNum(v)
}
return (
<DetailDiv>
<DetailInfo>
@ -53,7 +49,7 @@ const Operation: React.FC<OperationProp> = ({ detail, getNum }) => {
{detail.priceList.map((item, index) => {
return (
<Flex alignItems="center" key={item.label}>
<HeaderText>{formatTimeNumber(item.value)}</HeaderText>
<HeaderText>{item.value}</HeaderText>
<Text color="text">{item.label}</Text>
{index === 0 && detail.priceList.length === 2 && <Text marginLeft="10px">-</Text>}
</Flex>
@ -63,7 +59,7 @@ const Operation: React.FC<OperationProp> = ({ detail, getNum }) => {
</DetailInfo>
<DetailInfo>
<Text color="textSubtle">{t('quantity')}</Text>
<StepCom number={detail.num} value={(v) => getStepValue(v)} />
<StepCom number={stepNum} value={(v) => getStepValue(v)} />
</DetailInfo>
</DetailDiv>
)

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react'
import dayjs from 'dayjs'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Modal, Flex, Text, Image } from '@pancakeswap/uikit'
@ -89,13 +90,17 @@ const SeriesDetail: React.FC<SeriesDetailProp> = ({ name, value, onDismiss, deta
const [list, setList] = useState([])
const [totalNum, setTotal] = useState(0)
const [size, setSize] = useState(0)
const [typeStatus, setType] = useState('')
const [countDown, setCountDown] = useState('')
const [timeLimit, setTimeLimit] = useState(0)
const getDetail = useGetBoxDetail()
const getData = async () => {
const { contentList, total } = await getDetail(detail.id)
const { contentList, total, type } = await getDetail(detail.id)
setSize(contentList.length)
setTotal(total)
setType(type)
const dataList = []
contentList.forEach((item, index) => {
contentList.forEach((item) => {
const has = dataList.findIndex((o) => o.grade === item.grade)
if (has === -1) {
dataList.push({
@ -111,13 +116,69 @@ const SeriesDetail: React.FC<SeriesDetailProp> = ({ name, value, onDismiss, deta
useEffect(() => {
getData()
}, [])
if (detail.type === 'TIME_LIMIT') {
const date2 = dayjs(new Date(detail.endTime).getTime()).diff(dayjs())
switch (new Date(detail.beginTime).getTime() > new Date().getTime()) {
case true:
setTimeLimit(0)
break
case false:
if (date2 > 0) {
setTimeLimit(1)
} else {
setTimeLimit(2)
}
break
default:
setTimeLimit(1)
}
}
}, [detail])
const countDownFun = (date) => {
const date1 = dayjs()
const date2 = dayjs(date)
const time = date2.diff(date1)
if (time > 0) {
const hour = Math.floor(time / (1000 * 60 * 60))
const minute = Math.floor((time / (1000 * 60)) % 60)
const second = Math.round((time / 1000) % 60)
setCountDown(`${hour}:${minute}:${second}`)
} else {
setTimeLimit(2)
}
}
useEffect(() => {
const date2 = dayjs(new Date(detail.endTime).getTime()).diff(dayjs())
if (date2 > 0) {
setTimeout(() => {
const time = new Date(detail.endTime).getTime() - 1
countDownFun(time)
}, 1000)
} else {
setTimeLimit(2)
}
}, [countDown])
const getTime = (date) => {
const date1 = dayjs()
const date2 = dayjs(date)
const time = date2.diff(date1)
return time
}
return (
<Main title={detail.name} onDismiss={onDismiss}>
<FlexBetween>
<TypeText>{t('limit the quantity of')}</TypeText>
<TypeNum>{totalNum}</TypeNum>
<TypeText>{detail.type === 'QUANTITATIVE' ? t('limit the quantity of') : t('time limit')}</TypeText>
{detail.type === 'QUANTITATIVE' && <TypeNum>{totalNum}</TypeNum>}
{detail.type === 'TIME_LIMIT' && (
<>
{timeLimit === 0 && <TypeNum>{t('have not started')}</TypeNum>}
{timeLimit === 1 && <TypeNum>{countDown}</TypeNum>}
{timeLimit === 2 && <TypeNum>{t('finished')}</TypeNum>}
</>
)}
</FlexBetween>
<Detail>
<FlexBetween style={{ paddingRight: '20px' }}>
@ -134,7 +195,7 @@ const SeriesDetail: React.FC<SeriesDetailProp> = ({ name, value, onDismiss, deta
{item.grade === 'NORMAL' && <ShopText>{t('common')}</ShopText>}
<Shop>
{item.list.map((childItem) => {
return <ShopList key={childItem.id} item={childItem} grade={item.grade} />
return <ShopList key={childItem.id} item={childItem} grade={item.grade} type={typeStatus} />
})}
</Shop>
</ShopMain>

View File

@ -1,13 +1,12 @@
import React, { useState, useEffect } from 'react'
import styled, { keyframes } from 'styled-components'
import React from 'react'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Flex, Image } from '@pancakeswap/uikit'
import { coverResourceProps } from 'types/blindBox'
const ShopItem = styled.div`
/* height: 358px; */
width: 211px;
border-radius: 20px;
/* background: rgba(255, 255, 255, 0.39);
box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.15); */
position: relative;
overflow: hidden;
@ -45,16 +44,16 @@ const ShopItem = styled.div`
}
}
& > .epic {
background: linear-gradient(180deg, #efea48 0%, #f32121 100%);
background: linear-gradient(110deg, #efea48 0%, #f32121 100%);
}
& > .legend {
background: linear-gradient(180deg, #4b84f5 0%, #bc21f3 100%);
background: linear-gradient(110deg, #4b84f5 0%, #bc21f3 100%);
}
& > .uncommon {
background: linear-gradient(180deg, #3dffec 0%, #24bf52 100%);
background: linear-gradient(110deg, #3dffec 0%, #24bf52 100%);
}
& > .common {
background: linear-gradient(180deg, #b5e9f3 0%, #1195d9 100%);
background: linear-gradient(110deg, #b5e9f3 0%, #1195d9 100%);
}
}
`
@ -65,15 +64,17 @@ const ItemText = styled(Flex)`
color: #707070;
text-align: center;
`
const ImageType = styled.img`
const ImageType = styled(Image)`
border-radius: 20px;
`
const ProbabilityFlex = styled(Flex)`
width: 100%;
font-size: 26px;
justify-content: center;
color: #3cbbcc;
`
const ProbabilityTitle = styled(Flex)`
width: 100%;
font-size: 18px;
color: #666666;
margin-top: 5px;
@ -83,16 +84,19 @@ const ProbabilityTitle = styled(Flex)`
interface ShopListItemProps {
item?: Detail
grade?: string
type?: string
}
interface Detail {
goodsId?: string
grade?: string
id?: string
num?: string
name?: string
proportion?: number | string
coverResource?: coverResourceProps
}
const ShopList: React.FC<ShopListItemProps> = ({ item, grade }) => {
const ShopList: React.FC<ShopListItemProps> = ({ item, grade, type }) => {
const { t } = useTranslation()
return (
@ -103,14 +107,10 @@ const ShopList: React.FC<ShopListItemProps> = ({ item, grade }) => {
{grade === 'RARE' && <div className="ribbon1 uncommon">{t('uncommon')}</div>}
{grade === 'NORMAL' && <div className="ribbon1 common">{t('common')}</div>}
</div>
{/* <ImageType src="/images/nft/epic.svg" width={211} height={213} /> */}
{grade === 'EPIC' && <ImageType src="/images/nft/epic.svg" width={211} height={213} />}
{grade === 'LEGEND' && <ImageType src="/images/nft/legend.svg" width={211} height={213} />}
{grade === 'RARE' && <ImageType src="/images/nft/uncommon.svg" width={211} height={213} />}
{grade === 'NORMAL' && <ImageType src="/images/nft/common.svg" width={211} height={213} />}
<ImageType src={item.coverResource.url} width={211} height={213} />
<ItemText>{item.name}</ItemText>
<ProbabilityFlex>{item.proportion}%</ProbabilityFlex>
<ProbabilityTitle>{t('The rate of')}</ProbabilityTitle>
<ProbabilityFlex>{type === 'QUANTITATIVE' ? item.num : `${item.proportion}%`}</ProbabilityFlex>
<ProbabilityTitle>{type === 'QUANTITATIVE' ? t('quantity') : t('The rate of')}</ProbabilityTitle>
</ShopItem>
)
}

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import styled from 'styled-components'
import { Flex, Text, Button } from '@pancakeswap/uikit'
@ -32,7 +32,11 @@ interface StepProp {
}
const StepCom: React.FC<StepProp> = ({ number, max = 5, value }) => {
const [valNumber, setInputState] = useState(number)
const [valNumber, setInputState] = useState(1)
useEffect(() => {
setInputState(number)
}, [number])
const onChange = (type) => {
let num = valNumber
@ -42,7 +46,7 @@ const StepCom: React.FC<StepProp> = ({ number, max = 5, value }) => {
value(num)
setInputState(num)
} else {
if (valNumber === 0) return
if (valNumber === 1) return
num -= 1
value(num)
setInputState(num)

View File

@ -1,16 +1,66 @@
import { useCallback } from 'react'
import { useHccGiftNft } from 'hooks/useContract'
import { useBlindBox } from 'hooks/useContract'
import blindBox from 'config/abi/blindBox.json'
import useTokenBalance from 'hooks/useTokenBalance'
import { getAddress, getBlindBoxAddress } from 'utils/addressHelpers'
import multicall from 'utils/multicall'
import { ethers, Contract } from 'ethers'
import tokensList from 'config/constants/tokens'
import useToast from 'hooks/useToast'
import { useTranslation } from 'contexts/Localization'
import { getBoxPage, getBoxDetail, getPurchase } from 'services/blindBox'
import { getBalanceNumber, getDecimalAmountNumber } from 'utils/formatBalance'
export const useCheckTokenBalance = () => {
const { balance: usdtTokenBalance } = useTokenBalance(getAddress(tokensList.usdt.address))
const { toastWarning } = useToast()
const { t } = useTranslation()
const onCheck = useCallback(
(usdtAmount) => {
if (getBalanceNumber(usdtTokenBalance) <= usdtAmount) {
toastWarning(t('Insufficient Balance'))
return false
}
return true
},
[usdtTokenBalance],
)
return [onCheck]
}
export const useCheckHccTokenBalance = () => {
const { balance: hccTokenBalance } = useTokenBalance(getAddress(tokensList.hcc.address))
const { toastWarning } = useToast()
const { t } = useTranslation()
const onCheck = useCallback(
(hccAmount) => {
if (getBalanceNumber(hccTokenBalance) <= hccAmount) {
toastWarning(t('Insufficient Balance'))
return false
}
return true
},
[hccTokenBalance],
)
return [onCheck]
}
// nft盒子
export const useGetList = () => {
const data = async (page, size) => {
const result = await getBoxPage({ page, size })
return result
const { content } = await getBoxPage({ page, size })
const arr = content.map((item) => {
const obj = item
obj.num = 1
obj.priceList = []
Object.keys(obj.price).forEach((childItem) => {
obj.priceList.push({ label: childItem, value: obj.price[childItem] })
})
obj.price = undefined
return obj
})
return arr
}
return data
}
@ -37,7 +87,7 @@ export const useApproveUsdt = (tokenContract: Contract) => {
return { onApprove: handleApprove }
}
export const useApproveHccGiftNft = (tokenContract: Contract) => {
export const useApproveHcc = (tokenContract: Contract) => {
const handleApprove = useCallback(async () => {
try {
const tx = await tokenContract.approve(getBlindBoxAddress(), ethers.constants.MaxUint256)
@ -51,13 +101,20 @@ export const useApproveHccGiftNft = (tokenContract: Contract) => {
return { onApprove: handleApprove }
}
// 购买
export const useGetPurchase = () => {
const data = async (id) => {
const result = await getPurchase(id)
return result
// 购买合约
export const useBuyTransaction = () => {
const blindBoxFun = useBlindBox()
const address = getBlindBoxAddress()
const transaction = async (id, params) => {
const result = await getPurchase(id, params)
const { num, hccPrice, otherPaymentPrice, timestamp, code, type, sign, key } = result
const mintParams = [address, num, hccPrice, otherPaymentPrice, timestamp, code, type, sign]
const res = await blindBoxFun.mint(...mintParams)
res.key = key
res.code = code
return res
}
return data
return transaction
}
export default useGetList

View File

@ -1,20 +1,31 @@
import React, { useEffect, useState } from 'react'
import dayjs from 'dayjs'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { useAccount } from 'state/userInfo/hooks'
import { getAddress } from 'utils/addressHelpers'
import { getAddress, getBlindBoxAddress } from 'utils/addressHelpers'
import { fetchBlindBoxUserAllowances } from 'state/blindBox'
import { useERC20 } from 'hooks/useContract'
import UnlockButton from 'components/UnlockButton'
import SwiperCore, { Keyboard, Mousewheel, Pagination } from 'swiper'
import { Swiper, SwiperSlide } from 'swiper/react'
import { Card, Text, Flex, Image, Button } from '@pancakeswap/uikit'
import { Flex, Image, Button } from '@pancakeswap/uikit'
import { checkBuyResult } from 'services/blindBox'
import useRefresh from 'hooks/useRefresh'
import useToast from 'hooks/useToast'
import { UnOpenModel } from 'components/Modal'
import tokens from 'config/constants/tokens'
import { ListProp } from 'types/blindBox'
import Header from './component/Header'
import Operation from './component/Operation'
import { useGetList, useApproveHccGiftNft } from './hooks'
import {
useGetList,
useBuyTransaction,
useApproveHcc,
useApproveUsdt,
useCheckHccTokenBalance,
useCheckTokenBalance,
} from './hooks'
import 'swiper/swiper.min.css'
import 'swiper/components/pagination/pagination.min.css'
@ -29,7 +40,7 @@ const MainFlex = styled(Flex)`
`
const SwiperDiv = styled(Swiper)`
height: 730px;
height: 750px;
position: relative;
& > .swiper-wrapper > .swiper-slide > div > div {
background: transparent;
@ -38,15 +49,16 @@ const SwiperDiv = styled(Swiper)`
.swiper-pagination-custom,
.swiper-pagination-fraction {
/* position: absolute; */
bottom: 130px;
bottom: 80px;
}
& > .swiper-pagination {
bottom: 130px !important;
display: ${(props) => props.color};
bottom: 80px !important;
${({ theme }) => theme.mediaQueries.xs} {
bottom: 260px !important;
bottom: 140px !important;
}
${({ theme }) => theme.mediaQueries.lg} {
bottom: 130px !important;
bottom: 80px !important;
}
& > .swiper-pagination-bullet {
background: #fff;
@ -144,29 +156,46 @@ const BuyButton = styled(Button)`
}
`
const AuthorizationBtn = styled(Button)`
width: 40%;
margin: 20px auto 0px auto;
border-radius: 50px;
background: linear-gradient(180deg, #7be0fc 0%, #ac7bf1 100%);
border: none;
width: 500px;
height: 45px;
background: linear-gradient(180deg, #7be0fc 0%, #9961f0 100%);
opacity: 1;
border-radius: 23px;
margin-top: 10px;
${({ theme }) => theme.mediaQueries.xs} {
width: 350px;
}
${({ theme }) => theme.mediaQueries.lg} {
width: 500px;
}
`
const BlindBox: React.FC = () => {
const { t } = useTranslation()
const [txId, setTxId] = useState()
const [keyId, setKeyId] = useState()
const [code, setCode] = useState()
const [allowanceList, setAllowanceList] = useState({ usdt: 0, hcc: 0 })
const [buyVisible, setBuyVisible] = useState(false)
const { toastSuccess, toastError } = useToast()
const { fastRefresh } = useRefresh()
const [loading, setLoading] = useState(false)
const account = useAccount()
const [blindBoxList, setBlindBoxList] = useState<ListProp[]>()
const [buyNum, setBuyNum] = useState(0)
const [buyNum, setBuyNum] = useState(1)
const usdtContract = useERC20(getAddress(tokens.usdt.address))
const hccContract = useERC20(getAddress(tokens.hcc.address))
const { onApprove: onUsdtApprove } = useApproveHccGiftNft(usdtContract)
const { onApprove: onHccApprove } = useApproveHccGiftNft(hccContract)
const { onApprove: onUsdtApprove } = useApproveHcc(usdtContract)
const { onApprove: onHccApprove } = useApproveHcc(hccContract)
const getAllowances = async () => {
const allowances = await fetchBlindBoxUserAllowances(account)
if (allowances.usdt && allowances.hcc) {
setBuyVisible(true)
}
setAllowanceList({
usdt: allowances.usdt,
hcc: allowances.hcc,
@ -187,39 +216,78 @@ const BlindBox: React.FC = () => {
const getList = useGetList()
const getData = async () => {
const { content } = await getList(1, 10)
const arr = []
content.forEach((item) => {
const obj = item
obj.num = 0
obj.priceList = []
Object.keys(obj.price).forEach((childItem) => {
obj.priceList.push({ label: childItem, value: obj.price[childItem] })
})
obj.price = undefined
arr.push(obj)
})
const arr = await getList(1, 10)
setBlindBoxList(arr)
}
useEffect(() => {
if (account) {
getAllowances()
}
}, [account])
const buyTransaction = useBuyTransaction()
const [onCheckBalanceHcc] = useCheckHccTokenBalance()
const [onCheckBalanceUsdt] = useCheckTokenBalance()
const handleBuy = async (val) => {
if (buyNum === 0) {
toastError(t('Please select quantity'))
return
}
let HccBalance = true
val.priceList &&
val.priceList.forEach((item) => {
if (item.label === 'HCC') {
HccBalance = onCheckBalanceHcc(Number(item.value))
} else {
HccBalance = onCheckBalanceUsdt(Number(item.value))
}
})
if (!HccBalance) {
return
}
setLoading(true)
const res = await buyTransaction(val.id, {
id: val.id,
num: buyNum,
})
setTxId(res.hash)
setKeyId(res.key)
setCode(res.code)
}
const getTransactionResult = async () => {
const res = await checkBuyResult({ tx: txId, key: keyId, boxId: code })
if (res) {
setLoading(false)
setTxId(undefined)
setKeyId(undefined)
setCode(undefined)
setBuyNum(1)
toastSuccess(t('purchase succeeds'))
getData()
}
}
useEffect(() => {
if (txId && loading) {
getTransactionResult()
}
}, [fastRefresh])
useEffect(() => {
getData()
}, [])
const handleBuy = () => {
console.log(buyNum)
const swiperChange = () => {
setBuyNum(1)
}
const getTime = (date) => {
const date1 = dayjs()
const date2 = dayjs(date)
const time = date2.diff(date1)
return time
}
return (
<MainFlex>
{/* <UnOpenModel /> */}
<SwiperDiv
loop
pagination={{ clickable: true }}
spaceBetween={16}
freeModeMomentumRatio={0.25}
freeModeMomentumVelocityRatio={0.5}
>
const renderContent = (): JSX.Element => {
return (
<>
{blindBoxList?.map((item) => {
return (
<SwiperSlide key={item.id}>
@ -232,37 +300,73 @@ const BlindBox: React.FC = () => {
<div className="ribbon1 limitTime">{t('time limit')}</div>
)}
</div>
<Header detail={item} />
<Image src={item.coverResource.url} width={500} height={460} marginTop="-40px" />
<Operation detail={item} getNum={(v) => setBuyNum(v)} />
<Header detail={item} totalNumber={Number(item.total) - Number(item.purchased)} />
<Flex justifyContent="center" marginTop={10}>
<Image src={item.coverResource.url} width={410} height={370} />
</Flex>
<Operation detail={item} stepNum={buyNum} getNum={(v) => setBuyNum(v)} />
</BlindBoxCard>
{account ? <BuyButton onClick={handleBuy}>{t('Buy It Now')}</BuyButton> : <UnlockButtonDiv />}
{!allowanceList.usdt && (
<AuthorizationBtn
disabled={loading}
onClick={() => {
handleApprove(onUsdtApprove)
}}
{!account && <UnlockButtonDiv />}
{buyVisible && account && (
<BuyButton
disabled={
new Date(item.beginTime).getTime() > new Date().getTime() ||
(item.type === 'TIME_LIMIT' && getTime(item.endTime) <= 0) ||
(item.type === 'QUANTITATIVE' && Number(item.total) - Number(item.purchased) <= 0)
}
onClick={() => handleBuy(item)}
>
{t('Approve %coin% Contract', { coin: 'USDT' })}
</AuthorizationBtn>
{t('Buy It Now')}
</BuyButton>
)}
{!allowanceList.hcc && (
<AuthorizationBtn
disabled={loading}
onClick={() => {
handleApprove(onHccApprove)
}}
>
{t('Approve %coin% Contract', { coin: 'HCC' })}
</AuthorizationBtn>
)}
<Flex style={{ marginTop: '80px', width: '500px' }}>
{!allowanceList.usdt && (
<AuthorizationBtn
disabled={loading}
onClick={() => {
handleApprove(onUsdtApprove)
}}
>
{t('Approve %coin% Contract', { coin: 'USDT' })}
</AuthorizationBtn>
)}
{!allowanceList.hcc && (
<AuthorizationBtn
disabled={loading}
onClick={() => {
handleApprove(onHccApprove)
}}
>
{t('Approve %coin% Contract', { coin: 'HCC' })}
</AuthorizationBtn>
)}
</Flex>
</BlindBoxFlex>
</SwiperSlide>
)
})}
</>
)
}
return (
<MainFlex>
{/* <UnOpenModel /> */}
<SwiperDiv
color={blindBoxList?.length === 1 ? 'none' : ''}
onSlideChange={swiperChange}
loop
pagination={{ clickable: true }}
spaceBetween={16}
freeModeMomentumRatio={0.25}
freeModeMomentumVelocityRatio={0.5}
>
{renderContent()}
</SwiperDiv>
</MainFlex>
)

View File

@ -5,11 +5,12 @@ import { Flex, Text } from '@pancakeswap/uikit'
interface FlexProp {
name?: string | number
value?: string | number
marginBottom?: string
}
const FlexText: React.FC<FlexProp> = ({ name, value }) => {
const FlexText: React.FC<FlexProp> = ({ name, value, marginBottom = '0px' }) => {
return (
<Flex justifyContent="space-between">
<Flex justifyContent="space-between" style={{ marginBottom }}>
<Text fontSize="12px">{name}</Text>
<Text fontSize="12px">{value}</Text>
</Flex>

View File

@ -0,0 +1,101 @@
import React from 'react'
import dayjs from 'dayjs'
import BigNumber from 'bignumber.js'
import styled from 'styled-components'
import { Flex, Text, Button } from '@pancakeswap/uikit'
import { provider as ProviderType } from 'web3-core'
import { getAddress, getHolderPoolAddress } from 'utils/addressHelpers'
import { usePriceHccUsdt } from 'state/hooks'
import { getBalanceAmount } from 'utils/formatBalance'
import { BIG_ZERO } from 'utils/bigNumber'
import { useTranslation } from 'contexts/Localization'
import { useWithdraw } from '../../hooks/useHolderPoolBoard'
import CardHeading from './CardHeading'
import FlexText from './FlexText'
const FCard = styled.div`
align-self: baseline;
background: ${(props) => props.theme.card.background};
border-radius: 32px;
box-shadow: 0px 2px 30px 0px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
position: relative;
text-align: center;
min-height: 410px;
`
const ContentDiv = styled.div`
padding: 0 20px;
`
const InfoDiv = styled(Flex)`
flex-direction: column;
justify-content: space-between;
margin-bottom: 10px;
`
const PriceCoin = styled.div`
text-align: right;
`
interface NodeCardProps {
board: any
removed: boolean
provider?: ProviderType
account?: string
boardsData?: any
}
const HolderPoolBoardCard: React.FC<NodeCardProps> = ({ board, account, boardsData }) => {
const { t } = useTranslation()
const hccPriceUsdt = usePriceHccUsdt()
// const rawEarningsBalance = account ? getBalanceAmount(new BigNumber(board.estimatedProfit)).toNumber() : BIG_ZERO
// const displayBalance = rawEarningsBalance.toFixed(3, BigNumber.ROUND_DOWN)
const withdraw = useWithdraw()
const harvest = async () => {
const res = await withdraw(account)
console.log(res)
}
return (
<FCard>
<CardHeading
name={t(board.name)}
img={board.img}
tokenSymbol={board.tokenSymbol}
amount={board.userData?.amount}
/>
<ContentDiv>
{account && (
<InfoDiv>
<FlexText marginBottom="10px" name={t('NFT With the total')} value={board.amount as number} />
<FlexText
marginBottom="10px"
name={t('The total amount of dividends')}
value={board.receiveReward + board.estimatedProfit}
/>
<Flex justifyContent="space-between" alignItems="center">
<Text fontSize="12px" color="#1FC7D4">
{t('Unclaimed income')}
</Text>
<PriceCoin>
<Text fontSize="16px" color="#1FC7D4">
{board.estimatedProfit}
</Text>
<Text fontSize="12px" color="#9BE5EB">
{hccPriceUsdt ? (hccPriceUsdt * board.estimatedProfit).toFixed(3) : 0} USDT
</Text>
</PriceCoin>
</Flex>
</InfoDiv>
)}
{/* disabled={Number(displayBalance) <= 0} */}
<Button width="100%" onClick={harvest} disabled={board.estimatedProfit <= 0}>
{t('Harvest')}
</Button>
</ContentDiv>
</FCard>
)
}
export default HolderPoolBoardCard

View File

@ -0,0 +1,54 @@
import { useCallback } from 'react'
import holderPoolABI from 'config/abi/holderPool.json'
import { getHolderPoolAddress } from 'utils/addressHelpers'
import { unstakeBoard, unstakeForceBoard } from 'utils/calls'
import { useHolderPool } from 'hooks/useContract'
import {
getBalanceNumber,
getDecimalAmount,
formatDivNumber,
getDecimalAmountNumber,
formatTimeNumber,
} from 'utils/formatBalance'
import BigNumber from 'bignumber.js'
import multicall from 'utils/multicall'
// 获取信息
export const useUserInfo = () => {
const transaction = async (account) => {
const holderPoolAddress = getHolderPoolAddress()
const calls = [
{
address: holderPoolAddress,
name: 'userInfo',
params: [account],
},
{
address: holderPoolAddress,
name: 'pendingHCC',
params: [account],
},
]
const [userInfo, estimatedProfit] = await multicall(holderPoolABI, calls)
return {
amount: new BigNumber(userInfo?.amount._hex).toNumber(),
receiveReward: getBalanceNumber(userInfo?.receiveReward._hex),
rewardDebt: new BigNumber(userInfo?.rewardDebt._hex).toNumber(),
estimatedProfit: getBalanceNumber(estimatedProfit),
}
}
return transaction
}
export const useWithdraw = () => {
const holderPool = useHolderPool()
const transaction = async (account) => {
const res = await holderPool.withdrawHCC(account)
const receipt = await res.wait()
return receipt.status
}
return transaction
}
export default useUserInfo

View File

@ -2,6 +2,7 @@ import BigNumber from 'bignumber.js'
import React, { useEffect, useCallback, useMemo, useState, useRef } from 'react'
import { Route, useRouteMatch, useLocation } from 'react-router-dom'
import { useDispatch } from 'react-redux'
import { Address } from 'config/constants/types'
import boardsConfig from 'config/constants/boards'
import tokens from 'config/constants/tokens'
import { getAddress, getBoardAddress } from 'utils/addressHelpers'
@ -21,9 +22,21 @@ import { fetchBoardUserDataAsync, fetchBoardsPublicDataAsync } from 'state/actio
import { useAccount } from 'state/userInfo/hooks'
import { useTranslation } from 'contexts/Localization'
import { UnOpenModel } from 'components/Modal'
import { useUserInfo } from './hooks/useHolderPoolBoard'
import BoardCard from './components/BoardCard/BoardCard'
import HolderPoolBoardCard from './components/BoardCard/HolderPoolBoardCard'
import HeaderItem from './components/HeaderItem'
interface userInfoProps {
pid?: number
name?: string
amount?: number
receiveReward?: number
rewardDebt?: number
tokenAddresses?: Address
estimatedProfit?: number
}
const PageContent = styled.div`
background-image: url('/images/recommend/bg.svg');
background-repeat: no-repeat;
@ -75,6 +88,8 @@ const Boards: React.FC = () => {
const [boardsDataList, setBoardsDataList] = useState([])
const [nftHolder, setNftHolder] = useState<userInfoProps>({})
// 获取分红总额
const fetchBoardShares = async () => {
const boardsData = await Promise.all(
@ -117,7 +132,21 @@ const Boards: React.FC = () => {
setTotalAmount(total)
setShareOutBonus(totalReward)
}
const userInfo = useUserInfo()
const getUserInfo = async () => {
const res = await userInfo(account)
setNftHolder({
pid: 3,
name: 'NFT Holder',
amount: res.amount,
receiveReward: res.receiveReward,
estimatedProfit: res.estimatedProfit,
rewardDebt: res.rewardDebt,
tokenAddresses: tokens.hcc.address,
})
}
useEffect(() => {
getUserInfo()
dispatch(fetchBoardsPublicDataAsync())
fetchBoardShares()
if (account) {
@ -132,6 +161,7 @@ const Boards: React.FC = () => {
{boardsList.map((board, index) => (
<BoardCard boardsData={boardsDataList[index]} key={board.pid} board={board} account={account} removed />
))}
{nftHolder.pid && <HolderPoolBoardCard board={nftHolder} account={account} removed />}
</FlexLayout>
</div>
)
@ -139,7 +169,7 @@ const Boards: React.FC = () => {
return (
<PageContent>
<UnOpenModel />
{/* <UnOpenModel /> */}
<Page>
<Header>
<HeadingText>{t('Total capital pool')}</HeadingText>

View File

@ -164,7 +164,11 @@ const ExchangeCard: React.FC<Props> = ({ status, roundDetail, time }) => {
{t('Immediately change')}
</Button>
) : (
<Button width="100%" onClick={immediatelyChange} disabled={status !== 'proceed' || !hccPrice}>
<Button
width="100%"
onClick={immediatelyChange}
disabled={status !== 'proceed' || !hccPrice || Number(hccPrice) > roundDetail?.remaining}
>
{t('Immediately change')}
</Button>
)

View File

@ -0,0 +1,79 @@
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Flex, Button, Text, Image } from '@pancakeswap/uikit'
import { getContract } from 'services/referral'
import FlexCom from './FlexText'
interface DetailProps {
detail: Detail
}
interface Detail {
address?: string
createdAt?: string
id?: string
info?: InfoProps
metadata?: any
token?: string
updatedAt?: string
}
interface InfoProps {
coverResource?: CoverResourceProps
grade?: string
id?: string
name?: string
price?: any
type?: string
}
interface CoverResourceProps {
path?: string
url?: string
}
const DetailFlex = styled(Flex)`
flex-direction: column;
justify-content: space-between;
height: 380px;
background-color: #f5ffff;
border-radius: 20px;
padding: 50px 30px;
`
const TitleText = styled(Text)`
font-size: 28px;
color: #333333;
text-align: center;
`
const AssetsInfo: React.FC<DetailProps> = ({ detail }) => {
const { t } = useTranslation()
const [link, setLink] = useState('')
const getLinkAddress = async () => {
const data = await getContract()
setLink(data)
}
useEffect(() => {
getLinkAddress()
}, [])
return (
<DetailFlex>
<TitleText>{detail?.info.name}</TitleText>
{detail?.info.grade === 'EPIC' && <FlexCom name={t('rarity')} value={t('epic')} />}
{detail?.info.grade === 'LEGEND' && <FlexCom name={t('rarity')} value={t('legend')} />}
{detail?.info.grade === 'RARE' && <FlexCom name={t('rarity')} value={t('uncommon')} />}
{detail?.info.grade === 'NORMAL' && <FlexCom name={t('rarity')} value={t('common')} />}
{detail?.info.type === 'PROPS' && <FlexCom name={t('category')} value={t('PROPS')} />}
{detail?.info.type === 'GIVING' && <FlexCom name={t('category')} value={t('GIVING')} />}
<FlexCom name="NFT TOKEN" value={`#${detail?.token}`} />
<FlexCom
name={t('Contract address')}
typeLink={`https://bscscan.com/token/${link}`}
value={`${link && link.substring(0, 6)}...${link && link.substring(link.length - 4, link.length)}`}
/>
<FlexCom name={t('Assets agreement')} value="ERC721" />
<FlexCom name={t('Assets and chain')} value="BSC" />
</DetailFlex>
)
}
export default AssetsInfo

View File

@ -5,25 +5,12 @@ import { useTranslation } from 'contexts/Localization'
import { Heading, Flex } from '@pancakeswap/uikit'
import { useAccount } from 'state/userInfo/hooks'
import useRefresh from 'hooks/useRefresh'
import Empty from 'components/Empty'
import { useGetSelfPage } from '../hooks'
import ShopList from './ShopList'
import NumMain from './NumMain'
const aaa = keyframes`
0% {
background-position: 0 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
`
const PageContent = styled.div`
background: rgba(255, 255, 255, 0.39);
padding: 30px 0;
/* max-width: 70%; */
max-width: 1180px;
margin: 0 auto;
`
@ -31,7 +18,6 @@ const StatusFlex = styled(Flex)`
align-items: center;
flex-wrap: wrap;
margin-top: 30px;
/* padding: 0 23px; */
& > .active {
background: linear-gradient(90deg, #1fd4b0 0%, #1fc9d3 100%);
color: #fff;
@ -83,42 +69,53 @@ const Shop = styled.div`
grid-template-columns: repeat(4, 1fr);
}
`
const MainDiv = styled.div`
/* padding: 0 35px; */
`
const NftBox: React.FC = () => {
const { t } = useTranslation()
const list = [
{ label: 'Cat goddess Emerald ', type: 1, id: 1 },
{ label: 'Cat goddess Emerald ', type: 2, id: 2 },
{ label: 'Cat goddess Emerald ', type: 3, id: 3 },
{ label: 'Cat goddess Emerald ', type: 4, id: 4 },
{ label: 'Cat goddess Emerald ', type: 1, id: 5 },
]
const [pageNum, setPage] = useState(1)
const [count, setCount] = useState(undefined)
const [grade, setGrade] = useState('')
const [list, setList] = useState([])
const status = [
{ label: t('All'), id: '1' },
{ label: t('epic'), id: '2' },
{ label: t('legend'), id: '3' },
{ label: t('uncommon'), id: '4' },
{ label: t('common'), id: '5' },
{ label: t('All'), id: '1', grade: '' },
{ label: t('common'), id: '5', grade: 'NORMAL' },
{ label: t('uncommon'), id: '4', grade: 'RARE' },
{ label: t('epic'), id: '2', grade: 'EPIC' },
{ label: t('legend'), id: '3', grade: 'LEGEND' },
]
const [statusIndex, setStatusIndex] = useState(0)
const cutStatus = (index) => {
setStatusIndex(index)
setPage(1)
setGrade(status[index].grade)
}
const pageChange = (event, page) => {
console.log(event)
console.log(page)
setPage(page)
}
const getSelfPage = useGetSelfPage()
const getData = async () => {
const params = {
page: pageNum,
size: 8,
grade,
}
const { size, total, content } = await getSelfPage(params)
setList(content)
setCount(getTotalPageNum(total, size))
}
const getTotalPageNum = (total, pageSize) => {
const countTotal = ((Number(total) + Number(pageSize) - 1) / Number(pageSize)).toString()
return parseInt(countTotal)
}
useEffect(() => {
getData()
}, [pageNum, grade])
return (
<PageContent>
<MainDiv>
<NumMain quantity={100} withNumber={100} />
</MainDiv>
<StatusFlex>
{status.map((item, index) => {
return (
@ -132,16 +129,19 @@ const NftBox: React.FC = () => {
)
})}
</StatusFlex>
<HeadingTitle scale="lg">{t('Did not have')}</HeadingTitle>
<HeadingTitle scale="lg">{t('already owned')}</HeadingTitle>
{/* 商品 */}
<Shop>
{list.map((item) => {
return <ShopList item={item} />
return <ShopList item={item} key={item.id} />
})}
</Shop>
<Flex justifyContent="center">
<Pagination count={10} onChange={pageChange} />
</Flex>
{list.length > 0 && (
<Flex justifyContent="center" padding={10}>
<Pagination count={count} onChange={pageChange} page={pageNum} />
</Flex>
)}
{list.length === 0 && <Empty />}
</PageContent>
)
}

View File

@ -1,19 +1,15 @@
import React from 'react'
import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Modal, Flex, Text, Image, Button } from '@pancakeswap/uikit'
import FlexText from './FlexText'
import { useGetNftDetails } from '../hooks'
import AssetsInfo from './AssetsInfo'
interface DetailProp {
item?: Detail
token?: string
onDismiss?: () => void
}
interface Detail {
label?: string
type?: number | string
id?: number | string
}
const Main = styled(Modal)`
width: 1050px;
background-color: #fff;
@ -58,7 +54,6 @@ const MainFlex = styled(Flex)`
}
`
const ShopItem = styled(Flex)`
/* height: 358px; */
border-radius: 20px;
justify-content: center;
align-items: center;
@ -134,18 +129,6 @@ const DetailFlex = styled(Flex)`
margin-top: 0px;
}
`
const CardMain = styled.div`
height: 380px;
background-color: #f5ffff;
border-radius: 20px;
padding: 50px 30px;
`
const TitleText = styled(Text)`
font-size: 28px;
color: #333333;
text-align: center;
`
const SellButton = styled(Button)`
height: 60px;
@ -160,55 +143,34 @@ const CenterDiv = styled.div`
overflow-y: auto;
`
const DetailModal: React.FC<DetailProp> = ({ item, onDismiss }) => {
const DetailModal: React.FC<DetailProp> = ({ token, onDismiss }) => {
const { t } = useTranslation()
const getNftDetail = useGetNftDetails()
const [detail, setDetail] = useState(undefined)
const getClassBcg = () => {
let bcg = ''
switch (item.type) {
case 1:
bcg = 'epicBcg'
break
case 2:
bcg = 'legendBcg'
break
case 3:
bcg = 'uncommonBcg'
break
case 4:
bcg = 'commonBcg'
break
default:
bcg = 'epicBcg'
}
return bcg
const getDetail = async () => {
const res = await getNftDetail(token, { token })
setDetail(res)
}
useEffect(() => {
getDetail()
}, [token])
return (
<Main title="" onDismiss={onDismiss}>
<CenterDiv>
<MainFlex>
<ShopItem className={getClassBcg()}>
<ShopItem>
<div className="ribbon">
{item.type === 1 && <div className="ribbon1 epic">{t('epic')}</div>}
{item.type === 2 && <div className="ribbon1 legend">{t('legend')}</div>}
{item.type === 3 && <div className="ribbon1 uncommon">{t('uncommon')}</div>}
{item.type === 4 && <div className="ribbon1 common">{t('common')}</div>}
{detail?.info.grade === 'EPIC' && <div className="ribbon1 epic">{t('epic')}</div>}
{detail?.info.grade === 'LEGEND' && <div className="ribbon1 legend">{t('legend')}</div>}
{detail?.info.grade === 'RARE' && <div className="ribbon1 uncommon">{t('uncommon')}</div>}
{detail?.info.grade === 'NORMAL' && <div className="ribbon1 common">{t('common')}</div>}
</div>
{item.type === 1 && <Image src="/images/nft/epic-icon.svg" width={267} height={405} />}
{item.type === 2 && <Image src="/images/nft/legend-icon.svg" width={267} height={405} />}
{item.type === 3 && <Image src="/images/nft/uncommon-icon.svg" width={267} height={405} />}
{item.type === 4 && <Image src="/images/nft/box.svg" width={267} height={405} />}
<Image src={detail?.info.coverResource.url} width={400} height={510} />
</ShopItem>
<DetailFlex>
<CardMain>
<TitleText>Cat goddess Emerald</TitleText>
<FlexText title={t('Total quantity of ownership')} value="10000" marginTop="58px" />
<FlexText title="NFT TOKEN" value="10000" />
<FlexText title={t('Contract address')} value="合约地址" />
<FlexText title={t('Assets agreement')} value="资产协议" />
<FlexText title={t('Assets and chain')} value="资产公链" />
</CardMain>
<AssetsInfo detail={detail} />
<SellButton>{t('Selling immediately')}</SellButton>
</DetailFlex>
</MainFlex>

View File

@ -1,29 +1,63 @@
import React from 'react'
import styled from 'styled-components'
import { useTranslation } from 'contexts/Localization'
import { Text, Flex } from '@pancakeswap/uikit'
import { Flex, Text, Link } from '@pancakeswap/uikit'
interface FlexProp {
title: number | string
value: number | string
marginTop?: string
fontSize?: string
color?: string
interface FlexProps {
name: string
value: string
paddings?: string
leftColor?: string
rightColor?: string
typeLink?: string
size?: string
textColor?: string
rightSize?: string
}
const MainFlex = styled(Flex)`
font-size: 14px;
color: #666666;
align-items: center;
const FlexDiv = styled(Flex)`
justify-content: space-between;
align-items: center;
margin-top: 14px;
& > .linkText:hover {
color: #1fc7d4 !important;
border-bottom: 1px solid #1fc7d4 !important;
}
`
const TextLink = styled(Text)`
cursor: pointer;
`
const FlexText: React.FC<FlexProp> = ({ title, value, marginTop = '20px', color = '#666666', fontSize = '14px' }) => {
const FlexCom: React.FC<FlexProps> = ({
name,
value,
paddings = '0px',
leftColor = '#666666',
rightColor = 'textSubtle',
typeLink,
size = '14px',
textColor = '#666666',
rightSize = '14px',
}) => {
const openPage = () => {
window.open(typeLink)
}
return (
<MainFlex style={{ marginTop }}>
<Text style={{ fontSize, color }}>{title}</Text>
<Text style={{ fontSize, color }}>{value}</Text>
</MainFlex>
<FlexDiv style={{ padding: paddings }}>
<Text style={{ fontSize: size, color: leftColor }}>{name}</Text>
{typeLink ? (
<TextLink
color={rightColor}
style={{ color: textColor, borderBottom: `1px solid ${textColor}` }}
onClick={openPage}
className="linkText"
>
{value}
</TextLink>
) : (
<Text style={{ color: textColor, fontSize: rightSize }}>{value}</Text>
)}
</FlexDiv>
)
}
export default FlexText
export default FlexCom

View File

@ -59,28 +59,40 @@ interface ShopListItemProps {
item?: Detail
}
interface Detail {
label?: string
type?: number | string
id?: number | string
address?: string
createdAt?: string
id?: string
info: InfoProps
token?: string
updatedAt?: string
}
interface InfoProps {
coverResource?: CoverResourceProps
grade?: string
id?: string
name?: string
price?: any
type?: string
}
interface CoverResourceProps {
path?: string
url?: string
}
const ShopList: React.FC<ShopListItemProps> = ({ item }) => {
const { t } = useTranslation()
const [onDetailModal] = useModal(<DetailModal item={item} />)
const [onDetailModal] = useModal(<DetailModal token={item.token} />)
return (
<ShopItem key={item.id} onClick={onDetailModal}>
<div className="ribbon">
{item.type === 1 && <div className="ribbon1 epic">{t('epic')}</div>}
{item.type === 2 && <div className="ribbon1 legend">{t('legend')}</div>}
{item.type === 3 && <div className="ribbon1 uncommon">{t('uncommon')}</div>}
{item.type === 4 && <div className="ribbon1 common">{t('common')}</div>}
{item.info.grade === 'EPIC' && <div className="ribbon1 epic">{t('epic')}</div>}
{item.info.grade === 'LEGEND' && <div className="ribbon1 legend">{t('legend')}</div>}
{item.info.grade === 'RARE' && <div className="ribbon1 uncommon">{t('uncommon')}</div>}
{item.info.grade === 'NORMAL' && <div className="ribbon1 common">{t('common')}</div>}
</div>
{item.type === 1 && <Image src="/images/nft/epic.svg" width={278} height={280} />}
{item.type === 2 && <Image src="/images/nft/legend.svg" width={278} height={280} />}
{item.type === 3 && <Image src="/images/nft/uncommon.svg" width={278} height={280} />}
{item.type === 4 && <Image src="/images/nft/common.svg" width={278} height={280} />}
<ItemText>{item.label}</ItemText>
<Image src={item.info.coverResource.url} width={278} height={302} />
<ItemText>{item.info.name}</ItemText>
</ShopItem>
)
}

View File

@ -0,0 +1,31 @@
import { useCallback } from 'react'
import { useBlindBox } from 'hooks/useContract'
import { getAddress, getBlindBoxAddress } from 'utils/addressHelpers'
import { getSelfPage, getNftDetails } from 'services/nftBox'
import { ethers, Contract } from 'ethers'
export const useGetSelfPage = () => {
const data = async (params) => {
const result = await getSelfPage(params)
return result
}
return data
}
// export const useGetNftDetail = () => {
// const data = async (token, params) => {
// const result = await getNftDetail(token, params)
// console.log(result)
// return result
// }
// return data
// }
export const useGetNftDetails = () => {
const data = async (token, params) => {
const result = await getNftDetails(token, params)
return result
}
return data
}
export default useGetSelfPage

View File

@ -6,8 +6,10 @@ import { UnOpenModel } from 'components/Modal'
import Box from './components/Box'
const PageContent = styled.div`
background: rgba(255, 255, 255, 0.39);
min-height: calc(100vh - 64px);
background-image: url('/images/home/bg.svg');
background-size: cover;
background-repeat: no-repeat;
/* background-image: url('/images/page/nftBox.jpg');
background-position: 50%;