From 200f30f67373c2713cccef8103d3430bcba22a88 Mon Sep 17 00:00:00 2001 From: Anthony M Date: Fri, 20 May 2022 15:14:26 -0400 Subject: [PATCH] Create basic Redeem page This commit introduces the structure for the redeem page where users will be able to retrieve their deposit from the course. Included are a number of new contract functions used to check the status of a user's stake, the block in which they registered, the course length and finally send a transaction to retrieve their deposit. Still needed is the ability for a user to select whether or not they'd like to retrieve their deposit or mint $LEARN instead. --- src/course/contracts.js | 77 ++++++++++++ src/modules/flashcard/card.js | 34 +----- src/modules/redemptionPage/ConnectButton.js | 28 +++++ .../redemptionPage/RedemptionConnector.js | 36 ++++++ .../redemptionPage/RedemptionDetails.js | 110 ++++++++++++++++++ src/modules/redemptionPage/RegisterButton.js | 28 +++++ src/modules/redemptionPage/index.js | 4 + src/modules/web3/button.js | 37 ++++++ src/modules/web3/index.js | 5 +- src/pages/en/redeem.js | 30 +++++ 10 files changed, 359 insertions(+), 30 deletions(-) create mode 100644 src/modules/redemptionPage/ConnectButton.js create mode 100644 src/modules/redemptionPage/RedemptionConnector.js create mode 100644 src/modules/redemptionPage/RedemptionDetails.js create mode 100644 src/modules/redemptionPage/RegisterButton.js create mode 100644 src/modules/redemptionPage/index.js create mode 100644 src/modules/web3/button.js create mode 100644 src/pages/en/redeem.js diff --git a/src/course/contracts.js b/src/course/contracts.js index 25adb7d58a..d6b8156f9a 100644 --- a/src/course/contracts.js +++ b/src/course/contracts.js @@ -21,6 +21,83 @@ export const isRegistered = async (learner, provider) => { return res } +export const hasStakeInCourse = async (provider) => { + const { chainId } = await provider.getNetwork() + + const deSchoolContract = new Contract( + addresses(chainId).deSchool, + abis.deSchool, + provider + ) + let res = false + try { + res = !!(await deSchoolContract.isDeployed(KERNEL_COURSE_ID)) + } catch (err) { + // throws an error if either the learner is not registered or if the courseId does not exist + /** */ + } + return res +} + +export const getBlockRegistered = async (learner, provider) => { + const { chainId } = await provider.getNetwork() + + const deSchoolContract = new Contract( + addresses(chainId).deSchool, + abis.deSchool, + provider + ) + + let res + try { + res = await deSchoolContract.getBlockRegistered(learner, KERNEL_COURSE_ID) + } catch (err) { + // throws an error if either the learner is not registered or if the courseId does not exist + /** */ + } + return res +} + +export const getCourseLength = async (provider) => { + const { chainId } = await provider.getNetwork() + + const deSchoolContract = new Contract( + addresses(chainId).deSchool, + abis.deSchool, + provider + ) + + let res + try { + res = await deSchoolContract.courses(KERNEL_COURSE_ID)[1] + } catch (err) { + // throws an error if either the learner is not registered or if the courseId does not exist + /** */ + } + return res +} + +export const redeemDeposit = async (signer) => { + const chainId = await signer.getChainId() + + const deSchoolContract = new Contract( + addresses(chainId).deSchool, + abis.deSchool, + signer + ) + + let res + + try { + res = !!(await deSchoolContract.redeem(KERNEL_COURSE_ID)) + } catch (err) { + // throws an error if either the learner is not registered or if the courseId does not exist + /** */ + } + + return res +} + export const getDaiNonce = async (learner, provider) => { const { chainId } = await provider.getNetwork() diff --git a/src/modules/flashcard/card.js b/src/modules/flashcard/card.js index 50761bccec..bcc86ab41c 100644 --- a/src/modules/flashcard/card.js +++ b/src/modules/flashcard/card.js @@ -1,35 +1,11 @@ /** @jsx jsx */ import { Children, Fragment, useState, useEffect } from 'react' -import { jsx, Flex, Text, Box } from 'theme-ui' +import { jsx, Flex } from 'theme-ui' import { useConnect, useAccount, useProvider } from 'wagmi' -import { Button } from '@modules/ui' import { motion } from 'framer-motion' import { Connector } from '@src/course/connect' import { isRegistered } from '@src/course/contracts' -import Web3Modal from '../web3/modal' - -const Web3Control = ({ - children, - onClickButton, - buttonText, - descriptionText, - isDisabled, -}) => { - return ( -
- - {descriptionText} - - - {children} -
- ) -} +import { Modal as Web3Modal, Button as Web3Button } from '@src/modules/web3' const Card = ({ children, @@ -149,7 +125,7 @@ const Card = ({ )} {isConnected && !isUserRegistered && ( - )} {!isConnected && ( - Failed to connect )} - + )} {isConnected && isUserRegistered && ( diff --git a/src/modules/redemptionPage/ConnectButton.js b/src/modules/redemptionPage/ConnectButton.js new file mode 100644 index 0000000000..172158dcf4 --- /dev/null +++ b/src/modules/redemptionPage/ConnectButton.js @@ -0,0 +1,28 @@ +/** @jsx jsx */ + +import { Flex, jsx } from 'theme-ui' +import { useConnect } from 'wagmi' +import { useState } from 'react' + +import { Button as Web3Button } from '@src/modules/web3' +import { Connector } from '@src/course/connect' + +const ConnectButton = () => { + const { connect, connectors } = useConnect() + const [connector] = useState(connectors[Connector.INJECTED]) + + return ( + + { + connect(connector) + }} + /> + + ) +} + +export default ConnectButton diff --git a/src/modules/redemptionPage/RedemptionConnector.js b/src/modules/redemptionPage/RedemptionConnector.js new file mode 100644 index 0000000000..7ad91157f3 --- /dev/null +++ b/src/modules/redemptionPage/RedemptionConnector.js @@ -0,0 +1,36 @@ +/** @jsx jsx */ + +import { jsx } from 'theme-ui' +import { useAccount, useProvider } from 'wagmi' +import { useEffect, useState } from 'react' + +import { hasStakeInCourse } from '@src/course/contracts' +import RegisterButton from './RegisterButton' +import RedemptionDetails from './RedemptionDetails' + +const RedemptionConnector = () => { + const { data: accountData } = useAccount() + const provider = useProvider() + const [hasStakeStakeToRedeem, setHasStakeToRedeem] = useState(true) + + useEffect(() => { + const retrieveAndSetStakeState = async () => { + const _hasStake = await hasStakeInCourse(provider) + setHasStakeToRedeem(_hasStake) + } + if (accountData?.address && provider) { + retrieveAndSetStakeState() + } + }, [accountData?.address, provider]) + + return ( +
+ {hasStakeStakeToRedeem && ( + + )} + {!hasStakeStakeToRedeem && } +
+ ) +} + +export default RedemptionConnector diff --git a/src/modules/redemptionPage/RedemptionDetails.js b/src/modules/redemptionPage/RedemptionDetails.js new file mode 100644 index 0000000000..8847dae925 --- /dev/null +++ b/src/modules/redemptionPage/RedemptionDetails.js @@ -0,0 +1,110 @@ +/** @jsx jsx */ + +import { Flex, jsx } from 'theme-ui' +import { useConnect, useProvider, useBlockNumber, useSigner } from 'wagmi' +import { useEffect, useState } from 'react' + +import { Button as Web3Button } from '@src/modules/web3' +import { Connector } from '@src/course/connect' +import { + getBlockRegistered, + getCourseLength, + redeemDeposit, +} from '@src/course/contracts' + +const isRedemptionTime = (currentBlockNumber, redemptionBlockNumber) => { + return currentBlockNumber >= redemptionBlockNumber +} + +const getTimeUntilRedemptionText = ( + currentBlockNumber, + redemptionBlockNumber +) => { + if (isRedemptionTime(currentBlockNumber, redemptionBlockNumber)) { + return 'You can redeem your deposit now' + } else { + const averageBlockTimeInDays = 14 / 60 / 24 + const daysUntilRedemption = + (redemptionBlockNumber - currentBlockNumber) * averageBlockTimeInDays + + if (daysUntilRedemption <= 1) { + return `You can redeem your deposit today at block number ${redemptionBlockNumber}` + } else { + const roundedDays = Math.round(daysUntilRedemption) + const dayText = roundedDays == 1 ? 'day' : 'days' + return `You can redeem your deposit in ${roundedDays} ${dayText} at block number ${redemptionBlockNumber}` + } + } +} + +const RedemptionDetails = ({ address }) => { + const provider = useProvider() + const { data: signer } = useSigner() + const { data: currentBlockNumber } = useBlockNumber({ watch: true }) + const { connectors } = useConnect() + + const [connector] = useState(connectors[Connector.INJECTED]) + const [blockRegistered, setBlockRegistered] = useState(0) + const [courseLength, setCourseLength] = useState(0) + const [canRedeemDeposit, setCanRedeemDeposit] = useState(false) + + const redemptionBlockNumber = blockRegistered + courseLength + const timeUntilRedemptionText = getTimeUntilRedemptionText( + currentBlockNumber, + redemptionBlockNumber + ) + + useEffect(() => { + const retrieveBlockRegistered = async () => { + const blockNumber = await getBlockRegistered(address, provider) + setBlockRegistered(blockNumber || 28556533) + } + + const retrieveCourseLength = async () => { + const lengthOfCourse = await getCourseLength(provider) + setCourseLength(lengthOfCourse || 1) + } + + if (address && provider) { + retrieveBlockRegistered() + retrieveCourseLength() + } + }, [address, provider]) + + useEffect(() => { + if (currentBlockNumber && blockRegistered) { + setCanRedeemDeposit( + isRedemptionTime(currentBlockNumber, redemptionBlockNumber) + ) + } + }, [blockRegistered, currentBlockNumber]) + + const handleOnPressRedeem = async () => { + await redeemDeposit(signer) + } + + return ( + +

Current block: {currentBlockNumber}

+ +
+ ) +} + +const styles = { + container: { + flexDirection: 'column', + }, + blockNumberText: { + textAlign: 'center', + fontWeight: 'bold', + marginX: 'auto', + }, +} + +export default RedemptionDetails diff --git a/src/modules/redemptionPage/RegisterButton.js b/src/modules/redemptionPage/RegisterButton.js new file mode 100644 index 0000000000..2ec6ae6832 --- /dev/null +++ b/src/modules/redemptionPage/RegisterButton.js @@ -0,0 +1,28 @@ +/** @jsx jsx */ + +import { Flex, jsx } from 'theme-ui' +import { useConnect } from 'wagmi' +import { useState } from 'react' + +import { Button as Web3Button, Modal as Web3Modal } from '@src/modules/web3' +import { Connector } from '@src/course/connect' + +const RegisterButton = () => { + const { connectors } = useConnect() + const [connector] = useState(connectors[Connector.INJECTED]) + const [isModalVisible, setIsModalVisible] = useState(false) + + return ( + + {isModalVisible && } + setIsModalVisible(true)} + /> + + ) +} + +export default RegisterButton diff --git a/src/modules/redemptionPage/index.js b/src/modules/redemptionPage/index.js new file mode 100644 index 0000000000..df39e0a1c1 --- /dev/null +++ b/src/modules/redemptionPage/index.js @@ -0,0 +1,4 @@ +import ConnectButton from './ConnectButton' +import RedemptionConnector from './RedemptionConnector' + +export { ConnectButton, RedemptionConnector } diff --git a/src/modules/web3/button.js b/src/modules/web3/button.js new file mode 100644 index 0000000000..20b0fe665d --- /dev/null +++ b/src/modules/web3/button.js @@ -0,0 +1,37 @@ +/** @jsx jsx */ + +import { jsx, Text, Box } from 'theme-ui' +import { Button } from '@modules/ui' + +const Web3Button = ({ + children, + onClickButton, + buttonText, + descriptionText, + isDisabled, +}) => { + return ( +
+ + {descriptionText} + + + {children} +
+ ) +} + +const styles = { + connectText: { + textAlign: 'center', + fontWeight: 'bold', + marginX: 'auto', + }, +} + +export default Web3Button diff --git a/src/modules/web3/index.js b/src/modules/web3/index.js index e5a9f47b88..bcf5b1081d 100644 --- a/src/modules/web3/index.js +++ b/src/modules/web3/index.js @@ -1 +1,4 @@ -export { default as Web3 } from './modal' +import Modal from './modal' +import Button from './button' + +export { Modal, Button } diff --git a/src/pages/en/redeem.js b/src/pages/en/redeem.js new file mode 100644 index 0000000000..7781d6a727 --- /dev/null +++ b/src/pages/en/redeem.js @@ -0,0 +1,30 @@ +/** @jsx jsx */ + +import { Flex, jsx } from 'theme-ui' +import { useConnect } from 'wagmi' + +import { Heading } from '@modules/ui/heading' +import { ConnectButton, RedemptionConnector } from '@src/modules/redemptionPage' + +const RedeemPage = () => { + const { isConnected } = useConnect() + + return ( + + Redeem + {isConnected && } + {!isConnected && } + + ) +} + +const styles = { + container: { + width: '100%', + justifyContent: 'center', + alignItems: 'center', + flexDirection: 'column', + }, +} + +export default RedeemPage