-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Token page with interaction with deferred and marketplace
- Loading branch information
Showing
19 changed files
with
823 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
211 changes: 211 additions & 0 deletions
211
src/js/components/App/pages/Marketplace/pages/Contract/BuyTokenForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import * as React from 'react'; | ||
|
||
import { Contract } from '../../../../../../data/contract'; | ||
import Container from '../../../../../reusable/Container'; | ||
import Heading from '../../../../../reusable/Heading'; | ||
import WaitForMetamask from '../../../../../reusable/WaitForMetamask'; | ||
import MetamaskConnect, { ChainId } from '../../../../../MetamaskConnect'; | ||
import Paragraph from '../../../../../reusable/Paragraph'; | ||
import { useConnectedMetaMask } from 'metamask-react'; | ||
import DeferredClient from '../../../../../../web3/DeferredClient'; | ||
import MarketplaceClient from '../../../../../../web3/MarketplaceClient'; | ||
import { | ||
convertToHumanReadable, | ||
USDT_DECIMALS, | ||
} from '../../../../../../utils/format'; | ||
import Button from '../../../../../reusable/Button'; | ||
import TaskList, { Result } from '../../../../../Task/TaskList'; | ||
import { useAppContext } from '../../../../AppContext'; | ||
import UsdtClient from '../../../../../../web3/UsdtClient'; | ||
import Skeleton from 'react-loading-skeleton'; | ||
|
||
interface Props { | ||
contract: Contract; | ||
} | ||
|
||
const BuyTokenForm = (props: Props) => ( | ||
<Container.Card className="px-0 py-0 pt-4"> | ||
<Heading.H2 className="px-4 text-center">Buy contract tokens</Heading.H2> | ||
<WaitForMetamask otherwise={<LogWithMetamask />}> | ||
<BuyTokenFormInner {...props} /> | ||
</WaitForMetamask> | ||
</Container.Card> | ||
); | ||
|
||
const LogWithMetamask = () => ( | ||
<Container.FlexCols className="items-center gap-4"> | ||
<Paragraph.Center> | ||
Please connect to MetaMask to buy tokens. | ||
</Paragraph.Center> | ||
<MetamaskConnect /> | ||
</Container.FlexCols> | ||
); | ||
|
||
const BuyTokenFormInner = ({ contract }: Props) => { | ||
const { setAppError, setAppSuccess } = useAppContext(); | ||
const { account, ethereum, chainId } = useConnectedMetaMask(); | ||
|
||
const [tokenPrice, setTokenPrice] = React.useState<bigint>(); | ||
const [tokenId, setTokenId] = React.useState<bigint | null>(null); | ||
const [fetchedData, setFetchedData] = React.useState(false); | ||
|
||
const [pendingTx, setPendingTx] = React.useState(false); | ||
|
||
const onBuyToken = () => { | ||
setPendingTx(true); | ||
}; | ||
|
||
const approveUsdt = async (): Promise<Result> => { | ||
if (tokenPrice === undefined) { | ||
return { error: 'Token price is not available' }; | ||
} | ||
// get current approval for marketplace | ||
const marketplaceClient = new MarketplaceClient( | ||
account, | ||
ethereum, | ||
chainId as ChainId, | ||
); | ||
const marketplaceAddress = marketplaceClient.marketplaceAddress(); | ||
|
||
const usdtClient = new UsdtClient(account, ethereum, chainId as ChainId); | ||
// get allowance | ||
const allowance = await usdtClient.allowance(account, marketplaceAddress); | ||
// check if allowance is enough | ||
if (allowance >= tokenPrice) { | ||
return true; | ||
} | ||
|
||
// approve | ||
try { | ||
await usdtClient.approve(marketplaceAddress, tokenPrice); | ||
} catch (e) { | ||
console.error(`Failed to approve marketplace: ${e}`); | ||
return { | ||
error: `Failed to approve marketplace to spend ${convertToHumanReadable(tokenPrice, USDT_DECIMALS)} USDT`, | ||
}; | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
const buyToken = async (): Promise<Result> => { | ||
const marketplaceClient = new MarketplaceClient( | ||
account, | ||
ethereum, | ||
chainId as ChainId, | ||
); | ||
|
||
try { | ||
await marketplaceClient.buyNextToken(contract.id); | ||
} catch (e) { | ||
console.error(`Failed to buy token: ${e}`); | ||
return { error: 'Failed to buy token' }; | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
const onTokenBought = (result: Result) => { | ||
setPendingTx(false); | ||
|
||
if (result === true) { | ||
setAppSuccess(`Token #${tokenId} bought successfully`); | ||
// reload token | ||
loadToken(); | ||
} else { | ||
setAppError(`Failed to buy token: ${result.error}`); | ||
} | ||
}; | ||
|
||
const loadToken = () => { | ||
const deferredClient = new DeferredClient( | ||
account, | ||
ethereum, | ||
chainId as ChainId, | ||
); | ||
const marketplaceClient = new MarketplaceClient( | ||
account, | ||
ethereum, | ||
chainId as ChainId, | ||
); | ||
|
||
deferredClient | ||
.nextTokenIdToBuy(contract.id) | ||
.then((id) => { | ||
setTokenId(id); | ||
setFetchedData(true); | ||
}) | ||
.catch((e) => { | ||
console.error(`Failed to load token data: ${e.message}`); | ||
setFetchedData(true); | ||
}); | ||
marketplaceClient | ||
.tokenPriceForCaller(contract.id) | ||
.then(setTokenPrice) | ||
.catch((e) => { | ||
console.error(`Failed to load token data: ${e.message}`); | ||
}); | ||
}; | ||
|
||
React.useEffect(() => { | ||
loadToken(); | ||
}, [contract]); | ||
|
||
if (!fetchedData) { | ||
return ( | ||
<Container.Container className="w-4/6 mx-auto py-2"> | ||
<Skeleton count={3} /> | ||
</Container.Container> | ||
); | ||
} | ||
|
||
if (tokenId === null || tokenPrice === undefined) { | ||
return ( | ||
<Container.FlexCols className="items-center justify-center gap-4 px-4"> | ||
<Paragraph.Default> | ||
All the tokens for this contract have already been sold. | ||
</Paragraph.Default> | ||
</Container.FlexCols> | ||
); | ||
} | ||
|
||
const tokenPriceUsd = Number( | ||
convertToHumanReadable(tokenPrice, USDT_DECIMALS, true), | ||
); | ||
const tokenPriceUsdString = tokenPriceUsd.toLocaleString('en-US', { | ||
style: 'currency', | ||
currency: contract.currency, | ||
minimumFractionDigits: 2, | ||
}); | ||
|
||
return ( | ||
<Container.FlexCols className="items-center gap-4"> | ||
<span className="block text-lg"> | ||
Buy token <strong>#{tokenId.toString()}</strong> | ||
</span> | ||
<span className="block text-text"> | ||
Token Price: {tokenPriceUsdString} | ||
</span> | ||
<Button.Primary disabled={pendingTx} onClick={onBuyToken}> | ||
Buy token for {tokenPriceUsdString} | ||
</Button.Primary> | ||
<TaskList | ||
run={pendingTx} | ||
title={`Buying token #${tokenId.toString()}`} | ||
onDone={onTokenBought} | ||
tasks={[ | ||
{ | ||
label: `Approve USDT to spend ${tokenPriceUsd} USDT`, | ||
action: approveUsdt, | ||
}, | ||
{ | ||
label: `Buy Deferred token #${tokenId.toString()}`, | ||
action: buyToken, | ||
}, | ||
]} | ||
/> | ||
</Container.FlexCols> | ||
); | ||
}; | ||
|
||
export default BuyTokenForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
src/js/components/App/pages/Marketplace/pages/Contract/RealEstateCard/Progress.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { useConnectedMetaMask } from 'metamask-react'; | ||
import * as React from 'react'; | ||
import WaitForMetamask from '../../../../../../reusable/WaitForMetamask'; | ||
import DeferredClient from '../../../../../../../web3/DeferredClient'; | ||
import { ChainId } from '../../../../../../MetamaskConnect'; | ||
import Container from '../../../../../../reusable/Container'; | ||
import ProgressBar from '../../../../../../reusable/ProgressBar'; | ||
|
||
interface Props { | ||
contractId: bigint; | ||
installments: number; | ||
} | ||
|
||
const Progress = (props: Props) => ( | ||
<WaitForMetamask> | ||
<InnerProgress {...props} /> | ||
</WaitForMetamask> | ||
); | ||
|
||
const InnerProgress = ({ contractId, installments }: Props) => { | ||
const { account, ethereum, chainId } = useConnectedMetaMask(); | ||
|
||
const [progress, setProgress] = React.useState<number>(0); | ||
const [completed, setCompleted] = React.useState<boolean>(false); | ||
const [fetchedData, setFetchedData] = React.useState<boolean>(false); | ||
|
||
React.useEffect(() => { | ||
const deferredClient = new DeferredClient( | ||
account, | ||
ethereum, | ||
chainId as ChainId, | ||
); | ||
|
||
deferredClient.contractProgress(contractId).then((progressBig) => { | ||
const progress = Number(progressBig); | ||
setProgress(progress); | ||
setFetchedData(true); | ||
}); | ||
deferredClient.contractCompleted(contractId).then(setCompleted); | ||
}, [contractId]); | ||
|
||
if (!fetchedData) { | ||
return null; | ||
} | ||
|
||
if (completed) { | ||
return ( | ||
<Container.Container> | ||
<span className="text-text">Mortgage payment progress</span> | ||
<ProgressBar | ||
bgColor="bg-green-500" | ||
textColor="text-white" | ||
progress={installments} | ||
max={installments} | ||
/> | ||
</Container.Container> | ||
); | ||
} | ||
|
||
return ( | ||
<Container.Container> | ||
<span className="text-text">Mortgage payment progress</span> | ||
<ProgressBar progress={progress} max={installments} /> | ||
</Container.Container> | ||
); | ||
}; | ||
|
||
export default Progress; |
31 changes: 0 additions & 31 deletions
31
src/js/components/App/pages/Marketplace/pages/Contract/TokensList.tsx
This file was deleted.
Oops, something went wrong.
33 changes: 0 additions & 33 deletions
33
src/js/components/App/pages/Marketplace/pages/Contract/TokensList/TokenItem.tsx
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.