From c916a43f8eaad071dc414819845e79e020edad45 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Thu, 21 Nov 2024 04:54:28 +0100 Subject: [PATCH] feat: separate the sections, delete some useless code --- .../side-chain/switch-court-chain.js | 31 +- .../convert-pnk/claim-tokens-button.js | 73 --- .../convert-pnk/convert-pnk-card.js | 230 --------- .../convert-pnk/convert-pnk-form.js | 437 ------------------ src/containers/convert-pnk/convert-pnk.js | 30 +- ...aw-stpnk-card.js => convert-stpnk-card.js} | 11 +- 6 files changed, 33 insertions(+), 779 deletions(-) delete mode 100644 src/containers/convert-pnk/claim-tokens-button.js delete mode 100644 src/containers/convert-pnk/convert-pnk-card.js delete mode 100644 src/containers/convert-pnk/convert-pnk-form.js rename src/containers/convert-pnk/{wihdraw-stpnk-card.js => convert-stpnk-card.js} (93%) diff --git a/src/components/side-chain/switch-court-chain.js b/src/components/side-chain/switch-court-chain.js index bd8394b9..1e77e7bb 100644 --- a/src/components/side-chain/switch-court-chain.js +++ b/src/components/side-chain/switch-court-chain.js @@ -21,7 +21,7 @@ import useAccount from "../../hooks/use-account"; import usePromise from "../../hooks/use-promise"; import useInterval from "../../hooks/use-interval"; import useForceUpdate from "../../hooks/use-force-update"; -import { chainIdToNetworkName, chainIdToNetworkShortName } from "../../helpers/networks"; +import { chainIdToNetworkName } from "../../helpers/networks"; import { useSetRequiredChainId } from "../required-chain-id-gateway"; import AnnouncementBanner from "./announcement-banner"; import MultiBalance from "../multi-balance"; @@ -36,8 +36,6 @@ export default function SwitchCourtChain() { const account = useAccount(); const hasAccount = !!account; - const pnkTokenSymbol = useMemo(() => getTokenSymbol(chainId, "PNK"), [chainId]); - const destinationChainId = React.useMemo(() => { try { return getCounterPartyChainId(chainId); @@ -64,11 +62,16 @@ export default function SwitchCourtChain() { ) : isSupportedMainChain(destinationChainId) ? ( - - - Send {pnkTokenSymbol} to {chainIdToNetworkShortName[destinationChainId]} - - + + Convert stPNK to xPNK + + } @@ -482,3 +485,15 @@ const StyledResponsiveBannerButton = styled(Button).attrs((props) => ({ min-height: 48px; } `; + +const StyledLink = styled(Link)` + display: flex; + text-decoration: none; + align-items: center; +`; + +const StyledCustomButton = styled(CustomButton)` + display: flex; + text-decoration: none; + align-items: center; +`; diff --git a/src/containers/convert-pnk/claim-tokens-button.js b/src/containers/convert-pnk/claim-tokens-button.js deleted file mode 100644 index 3f5b7ec9..00000000 --- a/src/containers/convert-pnk/claim-tokens-button.js +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import t from "prop-types"; -import styled from "styled-components/macro"; -import { Button } from "antd"; -import { getSideChainParamsFromMainChainId } from "../../api/side-chain"; -import useChainId from "../../hooks/use-chain-id"; - -export default function ClaimTokensButton({ onDone }) { - const chainId = useChainId(); - - const url = React.useMemo(() => { - try { - return getSideChainParamsFromMainChainId(chainId).bridgeAppHistoryUrl; - } catch (err) { - return null; - } - }, [chainId]); - - const [hasClicked, setHasClicked] = React.useState(false); - const handleClick = React.useCallback(() => { - setHasClicked(true); - }, []); - - const onDoneOnce = useCallbackOnce(onDone); - - React.useEffect(() => { - if (hasClicked) { - const handleFocus = () => onDoneOnce(); - - window.addEventListener("focus", handleFocus); - - return () => { - window.removeEventListener("focus", handleFocus); - }; - } - }, [hasClicked, onDoneOnce]); - - return ( - - - - ); -} - -function useCallbackOnce(fn) { - const callRef = React.useRef(false); - - return React.useCallback( - (...args) => { - if (!callRef.current) { - callRef.current = true; - fn(...args); - } - }, - [fn] - ); -} - -ClaimTokensButton.propTypes = { - onDone: t.func, -}; - -ClaimTokensButton.defaultProps = { - onDone: () => {}, -}; - -const StyledWrapper = styled.div` - display: flex; - justify-content: center; - padding: 1rem 0; -`; diff --git a/src/containers/convert-pnk/convert-pnk-card.js b/src/containers/convert-pnk/convert-pnk-card.js deleted file mode 100644 index 3f66e4a5..00000000 --- a/src/containers/convert-pnk/convert-pnk-card.js +++ /dev/null @@ -1,230 +0,0 @@ -import React, { useMemo } from "react"; -import styled from "styled-components"; -import { Link, useHistory, useLocation } from "react-router-dom"; -import { Card, Divider } from "antd"; -import { ButtonLink } from "../../adapters/antd"; -import { getTokenSymbol } from "../../helpers/get-token-symbol"; -import SteppedContent from "../../components/stepped-content"; -import { getCounterPartyChainId, isSupportedMainChain, isSupportedSideChain } from "../../api/side-chain"; -import { chainIdToNetworkShortName } from "../../helpers/networks"; -import useChainId from "../../hooks/use-chain-id"; -import useQueryParams from "../../hooks/use-query-params"; -import ConvertPnkForm from "./convert-pnk-form"; -import SwitchChainButton from "./switch-chain-button"; -import ClaimTokensButton from "./claim-tokens-button"; - -export default function ConvertPnkCard() { - const currentChainId = useChainId(); - const counterPartyChainId = getCounterPartyChainId(currentChainId); - - const originChainId = isSupportedMainChain(counterPartyChainId) ? currentChainId : counterPartyChainId; - const targetChainId = isSupportedMainChain(counterPartyChainId) ? counterPartyChainId : currentChainId; - - const pnkTokenSymbolOriginChainId = useMemo(() => getTokenSymbol(originChainId, "PNK"), [originChainId]); - const pnkTokenSymbolTargetChainId = useMemo(() => getTokenSymbol(targetChainId, "PNK"), [targetChainId]); - - const { step, next, first } = useStep(); - - const handleFormDone = React.useCallback(() => { - next(); - }, [next]); - - React.useEffect(() => { - if (step === 1 && isSupportedMainChain(currentChainId)) { - next(); - } - }, [step, currentChainId, next]); - - React.useEffect(() => { - if (step === 2 && isSupportedSideChain(currentChainId)) { - first(); - } - }, [step, currentChainId, first]); - - return ( - - Send {pnkTokenSymbolOriginChainId} to {chainIdToNetworkShortName[targetChainId]} - - } - > - - Keep in mind that {pnkTokenSymbolOriginChainId} that are staked or locked cannot be sent to{" "} - {chainIdToNetworkShortName[targetChainId]}. To just get xPNK, use the form below. - - - ( -
- - - 🎉 - {" "} - You have successfully sent your {pnkTokenSymbolOriginChainId} to{" "} - {chainIdToNetworkShortName[targetChainId]}!{" "} - - 🎉 - - - - Go to the Home Page - -
- )} - steps={[ - { - title: <>Convert {pnkTokenSymbolOriginChainId}, - children() { - return isSupportedSideChain(currentChainId) ? ( - - ) : ( - - ); - }, - }, - { - title: `Switch to ${chainIdToNetworkShortName[targetChainId]}`, - children() { - return ; - }, - }, - { - title: <>Claim {pnkTokenSymbolTargetChainId}, - children() { - return ; - }, - }, - ]} - /> -
- ); -} - -function useStep() { - const history = useHistory(); - const location = useLocation(); - - const queryParams = useQueryParams(); - const stepFromUrl = queryParams.step ? queryParams.step - 1 : 0; - - const [step, setStep] = React.useState(stepFromUrl); - - const setPersistedStep = React.useCallback( - (newStep, additionalParams = {}) => { - setStep(newStep); - - const { step: _, ...rest } = additionalParams; - const newQueryParams = Object.fromEntries( - Object.entries({ - ...queryParams, - step: newStep + 1, - ...rest, - }).filter(([_, value]) => value !== undefined) - ); - const navigate = step === newStep ? history.replace : history.push; - navigate({ ...location, search: new URLSearchParams(newQueryParams).toString() }); - }, - [history, step, queryParams, location] - ); - - const next = React.useCallback( - (additionalParams) => { - setPersistedStep(step + 1, additionalParams); - }, - [setPersistedStep, step] - ); - - const previous = React.useCallback( - (additionalParams) => { - setPersistedStep(step - 1, additionalParams); - }, - [setPersistedStep, step] - ); - - const first = React.useCallback( - (additionalParams) => { - setPersistedStep(0, additionalParams); - }, - [setPersistedStep] - ); - - return { - step, - next, - previous, - first, - }; -} - -const StyledCard = styled(Card)` - margin: 20px auto 0; - max-width: 768px; - border-radius: 12px; - box-shadow: 0px 6px 36px #bc9cff; - - .ant-card-head { - border: none; - } - - .ant-card-head-title { - text-align: center; - font-size: 36px; - color: #4d00b4; - padding-bottom: 0; - } - - .ant-card-actions { - background: none; - border: none; - padding: 0 24px 20px; - display: flex; - gap: 16px; - - > li { - text-align: inherit; - border: none; - width: auto !important; - padding: 0; - margin: 0; - :last-child { - margin-left: auto; - } - } - - ::after, - ::before { - display: none; - } - } -`; - -const StyledExplainerText = styled.p` - color: rgba(0, 0, 0, 0.45); - font-size: 14px; - text-align: center; -`; - -const StyledFinalMessage = styled.p` - color: rgba(0, 0, 0, 0.85); - font-size: 24px; - font-weight: bold; - text-align: center; - margin: 0; - padding: 1rem 0; -`; - -const StyledDivider = styled(Divider)` - border: none !important; - background: none !important; -`; diff --git a/src/containers/convert-pnk/convert-pnk-form.js b/src/containers/convert-pnk/convert-pnk-form.js deleted file mode 100644 index 09c19028..00000000 --- a/src/containers/convert-pnk/convert-pnk-form.js +++ /dev/null @@ -1,437 +0,0 @@ -import React, { useMemo } from "react"; -import t from "prop-types"; -import styled from "styled-components/macro"; -import { Alert, Button, Col, Form, Icon, InputNumber, Row, Skeleton } from "antd"; -import { useDebouncedCallback } from "use-debounce"; -import Web3 from "web3"; -import { useSideChainApi } from "../../api/side-chain"; -import BalanceTable from "../../components/balance-table"; -import MultiTransactionStatus from "../../components/multi-transaction-status"; -import { getTokenSymbol } from "../../helpers/get-token-symbol"; -import { chainIdToNetworkShortName } from "../../helpers/networks"; -import useAccount from "../../hooks/use-account"; -import { useAsyncGenerator } from "../../hooks/use-generators"; -import usePromise from "../../hooks/use-promise"; -import { drizzleReactHooks } from "@drizzle/react-plugin"; - -const { useDrizzleState } = drizzleReactHooks; - -const { fromWei, toWei, toBN } = Web3.utils; - -export default function ConvertPnkFormWrapper({ onDone }) { - const sideChainApi = useSideChainApi(); - const account = useAccount(); - - const tokenStats = usePromise( - React.useCallback(() => sideChainApi.getTokenStats({ address: account }), [account, sideChainApi]) - ); - - const hasAvailableTokens = toBN(tokenStats.value?.available ?? 0).gt(toBN(0)); - - const { run, isRunning, isDone, transactions, error } = useWithdrawTokens(sideChainApi.withdraw); - - const handleFinish = React.useCallback( - (values) => { - const amountInWei = toWei(String(values.origin)); - run({ - address: account, - amount: amountInWei, - }); - }, - [run, account] - ); - - React.useEffect(() => { - if (isDone) { - onDone(); - } - }, [isDone, onDone]); - - return ( - <> - - - - {transactions.length > 0 ? ( - <> - - - - ) : null} - - ); -} - -ConvertPnkFormWrapper.propTypes = { - onDone: t.func, -}; - -ConvertPnkFormWrapper.defaultProps = { - onDone: () => {}, -}; - -function useWithdrawTokens(withdrawTokens) { - const { run, ...withdrawResult } = useAsyncGenerator(withdrawTokens); - - const [withdraw] = withdrawResult?.value ?? []; - - const transactions = React.useMemo( - () => - [ - { - state: withdraw?.state, - txHash: withdraw?.txHash, - title: "Withdraw and Convert", - }, - ].filter(({ state }) => !!state), - [withdraw?.state, withdraw?.txHash] - ); - - return { - run, - transactions, - ...withdrawResult, - }; -} - -function PnkBalanceTable({ balance, locked, pendingStake, staked, available, error }) { - const chainId = useDrizzleState((ds) => ds.web3.networkId); - const pnkTokenSymbol = useMemo(() => getTokenSymbol(chainId, "PNK"), [chainId]); - - return ( - <> - - - - - {pendingStake ? ( - - ) : null} - - - - {pendingStake ? ( - <> - - -

- When you stake or unstake on a Court, the Kleros main smart contract might not be - able to process the stake changes right away. It usually takes a couple of minutes to a few hours for - your stake changes to be processed. -

-

- This means that if you have just unstaked, you will not be able to convert those {pnkTokenSymbol}{" "} - right now. On the other hand, if you just staked, you can convert those {pnkTokenSymbol} now, but the - pending stake changes will be discarded. -

- - } - /> - - ) : null} - - ); -} - -PnkBalanceTable.propTypes = { - balance: t.oneOfType([t.string, t.number, t.object]), - locked: t.oneOfType([t.string, t.number, t.object]), - pendingStake: t.oneOfType([t.string, t.number, t.object]), - staked: t.oneOfType([t.string, t.number, t.object]), - available: t.oneOfType([t.string, t.number, t.object]), - error: t.instanceOf(Error), -}; - -const ConvertPnkForm = Form.create()(({ form, maxAvailable, isSubmitting, disabled, onFinish }) => { - const sideChainApi = useSideChainApi(); - - const { chainId, destinationChainId } = sideChainApi; - - const pnkTokenSymbol = useMemo(() => getTokenSymbol(chainId, "PNK"), [chainId]); - const pnkDestinationTokenSymbol = useMemo(() => getTokenSymbol(destinationChainId, "PNK"), [destinationChainId]); - - const feeRatio = usePromise(React.useCallback(() => sideChainApi.getFeeRatio(), [sideChainApi])); - - const { validateFieldsAndScroll, getFieldDecorator, getFieldsError, setFieldsValue } = form; - - const maxAvailableNumeric = Math.trunc(Number(fromWei(maxAvailable ?? "0"))); - - const originDecorator = getFieldDecorator("origin", { - rules: [ - { required: true, message: "Amount is required." }, - async function validateBalance(_, value) { - if (value > maxAvailableNumeric) { - throw new Error("Not enough available tokens."); - } - }, - ], - }); - - const destinationDecorator = getFieldDecorator("destination", { - rules: [{ required: true, message: "Amount is required." }], - }); - - const handleChangeOriginValue = useDebouncedCallback( - React.useCallback( - async (value) => { - const valueInWei = toWei(String(value || 0)); - const newDestinationValue = String(await sideChainApi.getRelayedAmount({ originalAmount: valueInWei })); - setFieldsValue({ - destination: Number(Number(fromWei(newDestinationValue)).toFixed(2)), - }); - }, - [sideChainApi, setFieldsValue] - ), - 500 - ); - - const handleUseMaxClick = React.useCallback(() => { - setFieldsValue({ origin: maxAvailableNumeric }); - handleChangeOriginValue(maxAvailableNumeric); - }, [setFieldsValue, maxAvailableNumeric, handleChangeOriginValue]); - - const handleChangeDestinationValue = useDebouncedCallback( - React.useCallback( - async (value) => { - const valueInWei = toWei(String(value || 0)); - const newOriginValue = String(await sideChainApi.getRequiredAmount({ desiredAmount: valueInWei })); - setFieldsValue({ - origin: Number(Number(fromWei(newOriginValue)).toFixed(2)), - }); - }, - [sideChainApi, setFieldsValue] - ), - [500] - ); - - const handleSubmit = React.useCallback( - (evt) => { - evt.preventDefault(); - validateFieldsAndScroll((err, values) => { - if (err) { - console.debug("Form validation error:", err); - return; - } - - onFinish(values); - }); - }, - [validateFieldsAndScroll, onFinish] - ); - - return ( -
-
- - There is a{" "} - {Number.isNaN(Number(feeRatio?.value)) ? ( - - ) : ( - formatPercent(feeRatio?.value) - )}{" "} - fee charged by the Token Bridge operators when sending tokens back to{" "} - {chainIdToNetworkShortName[destinationChainId]}. - - - - - - {pnkTokenSymbol} - use max. - - } - > - {originDecorator( - - )} - - - - - - - - {destinationDecorator( - - )} - - - - - -
- ); -}); - -function formatPercent(value) { - const nf = new Intl.NumberFormat([], { style: "percent", maximumFractionDigits: 3 }); - return nf.format(value); -} - -function hasErrors(fieldsError) { - return Object.keys(fieldsError).some((field) => fieldsError[field]); -} - -const StyledRow = styled(Row)` - display: flex; - gap: 12px; - && { - ::before, - ::after { - display: none; - } - } - @media (max-width: 768px) { - gap: 0; - flex-wrap: wrap; - } -`; - -const StyledSeparatorCol = styled(Col)` - flex: 24px 0 0; - display: flex; - justify-content: center; - align-items: center; - .anticon { - width: 24px; - height: 24px; - transition: all 0.2s ease-in; - > svg { - width: 100%; - height: 100%; - fill: #009aff; - } - } - @media (max-width: 768px) { - flex-basis: 100%; - margin-top: -12px; - margin-bottom: -12px; - .anticon { - transform: rotate(90deg); - } - } -`; - -const StyledFieldCol = styled(Col)` - flex: 50% 1 1; - @media (max-width: 768px) { - flex-basis: 100%; - } -`; - -const StyledFormItem = styled(Form.Item)` - && { - .ant-input-number { - width: 100%; - } - } -`; - -const StyledButtonLink = styled.button.attrs((...rest) => ({ ...rest, type: "button" }))` - background: none; - border: none; - padding: 0; - cursor: pointer; - font-weight: inherit; - display: inline-block; - color: #009aff; -`; - -const StyledCompositeLabel = styled.span` - display: flex; - align-items: center; - gap: 8px; - - ${StyledButtonLink} { - margin-left: auto; - } -`; - -const StyledSkeleton = styled(Skeleton)``; - -const StyledFeeNote = styled.div` - color: rgba(0, 0, 0, 0.45); - font-size: 12px; - text-align: center; - display: flex; - align-items: center; - justify-content: center; - - ${StyledSkeleton} { - display: inline-flex; - width: 30px; - - ::before, - ::after { - content: " "; - white-space: pre; - } - - .ant-skeleton-title { - margin: 0; - } - } -`; - -const StyledSpacer = styled.span` - display: flex; - clear: both; - width: 100%; - margin-bottom: var(--size, 1rem); -`; diff --git a/src/containers/convert-pnk/convert-pnk.js b/src/containers/convert-pnk/convert-pnk.js index 8576130e..4e1498b4 100644 --- a/src/containers/convert-pnk/convert-pnk.js +++ b/src/containers/convert-pnk/convert-pnk.js @@ -1,16 +1,13 @@ -import React, { useMemo } from "react"; +import React from "react"; import styled from "styled-components/macro"; import { drizzleReactHooks } from "@drizzle/react-plugin"; import { Divider, Spin } from "antd"; import { getCounterPartyChainId, isSupportedMainChain, SideChainApiProvider } from "../../api/side-chain"; import { getReadOnlyWeb3 } from "../../bootstrap/web3"; -import { getTokenSymbol } from "../../helpers/get-token-symbol"; import TopBanner from "../../components/top-banner"; -import { chainIdToNetworkName } from "../../helpers/networks"; import useChainId from "../../hooks/use-chain-id"; import C404 from "../404"; -import ConvertPnkCard from "./convert-pnk-card"; -import WithdrawStPnk from "./wihdraw-stpnk-card"; +import ConvertStPnk from "./convert-stpnk-card"; const { useDrizzle } = drizzleReactHooks; @@ -53,30 +50,11 @@ export default function ConvertPnkWrapper() { } function ConvertPnk() { - const currentChainId = useChainId(); - const counterPartyChainId = getCounterPartyChainId(currentChainId); - - const originChainId = isSupportedMainChain(counterPartyChainId) ? currentChainId : counterPartyChainId; - const targetChainId = isSupportedMainChain(counterPartyChainId) ? counterPartyChainId : currentChainId; - - const originChainTokenSymbol = useMemo(() => getTokenSymbol(originChainId, "PNK"), [originChainId]); - const targetChainTokenSymbol = useMemo(() => getTokenSymbol(targetChainId, "PNK"), [targetChainId]); - return ( <> - - Send your {originChainTokenSymbol} to get {targetChainTokenSymbol} back on{" "} - {chainIdToNetworkName[targetChainId]} - - } - /> - - + - + ); } diff --git a/src/containers/convert-pnk/wihdraw-stpnk-card.js b/src/containers/convert-pnk/convert-stpnk-card.js similarity index 93% rename from src/containers/convert-pnk/wihdraw-stpnk-card.js rename to src/containers/convert-pnk/convert-stpnk-card.js index 9aa1a459..1de5ab01 100644 --- a/src/containers/convert-pnk/wihdraw-stpnk-card.js +++ b/src/containers/convert-pnk/convert-stpnk-card.js @@ -105,7 +105,7 @@ function hasErrors(fieldsError) { return Object.keys(fieldsError).some((field) => fieldsError[field]); } -const WithdrawStPnkForm = Form.create()(({ form, maxAvailable, isSubmitting, disabled }) => { +const ConvertStPnkForm = Form.create()(({ form, maxAvailable, isSubmitting, disabled }) => { const { chainId } = useSideChainApi(); const { drizzle } = useDrizzle(); const { account } = useDrizzleState((drizzleState) => ({ @@ -184,7 +184,7 @@ const WithdrawStPnkForm = Form.create()(({ form, maxAvailable, isSubmitting, dis ); }); -const WithdrawStPnk = () => { +const ConvertStPnk = () => { const sideChainApi = useSideChainApi(); const account = useAccount(); const tokenStats = usePromise( @@ -199,11 +199,12 @@ const WithdrawStPnk = () => { margin-top: -1rem; `} > - Use this if you only want to obtain xPNK, for example, for usage in Gnosis Chain exchanges. + Use this if you only want to obtain xPNK, for example, for bridging it to Mainnet via the Gnosis Bridge, or for + trading it in Gnosis Chain exchanges. - + ); }; -export default WithdrawStPnk; +export default ConvertStPnk;