From 6a9676699b81a8f9e521433fd88643c48b6cac13 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Wed, 13 Nov 2024 11:06:57 -0500 Subject: [PATCH 01/22] feat: rename Unstake to Withdraw in the app --- src/components/forms/validation.ts | 2 +- src/components/modals/StakingModal.tsx | 89 ++++++++++--------- ...stakeAllModal.tsx => WithdrawAllModal.tsx} | 14 +-- ...UnstakeWarning.tsx => WithdrawWarning.tsx} | 4 +- src/pages/Staking/MyStakesTable.tsx | 12 +-- tests/components/forms/validation.test.ts | 4 +- 6 files changed, 65 insertions(+), 60 deletions(-) rename src/components/modals/{UnstakeAllModal.tsx => WithdrawAllModal.tsx} (94%) rename src/components/modals/{UnstakeWarning.tsx => WithdrawWarning.tsx} (87%) diff --git a/src/components/forms/validation.ts b/src/components/forms/validation.ts index 220ccc98..a2352d40 100644 --- a/src/components/forms/validation.ts +++ b/src/components/forms/validation.ts @@ -86,7 +86,7 @@ export const validateNumberRange = ( }; }; -export const validateUnstakeAmount = ( +export const validateWithdrawAmount = ( propertyName: string, ticker: string, currentStake: number, diff --git a/src/components/modals/StakingModal.tsx b/src/components/modals/StakingModal.tsx index 5dc38897..0b428bfa 100644 --- a/src/components/modals/StakingModal.tsx +++ b/src/components/modals/StakingModal.tsx @@ -20,14 +20,14 @@ import Tooltip from '../Tooltip'; import ErrorMessageIcon from '../forms/ErrorMessageIcon'; import { validateIOAmount, - validateUnstakeAmount, validateWalletAddress, + validateWithdrawAmount, } from '../forms/validation'; import { InfoIcon } from '../icons'; import BaseModal from './BaseModal'; import BlockingMessageModal from './BlockingMessageModal'; import SuccessModal from './SuccessModal'; -import UnstakeWarning from './UnstakeWarning'; +import WithdrawWarning from './WithdrawWarning'; const StakingModal = ({ onClose, @@ -49,7 +49,7 @@ const StakingModal = ({ useState(''); const [amountToStake, setAmountToStake] = useState(''); - const [amountToUnstake, setAmountToUnstake] = useState(''); + const [amountToWithdraw, setAmountToWithdraw] = useState(''); const [showBlockingMessageModal, setShowBlockingMessageModal] = useState(false); @@ -75,9 +75,9 @@ const StakingModal = ({ const newTotalStake = tab == 0 ? currentStake + parseFloat(amountToStake) - : currentStake - parseFloat(amountToUnstake); + : currentStake - parseFloat(amountToWithdraw); const newStake = - tab == 0 ? parseFloat(amountToStake) : -parseFloat(amountToUnstake); + tab == 0 ? parseFloat(amountToStake) : -parseFloat(amountToWithdraw); const rewardsInfo = useRewardsInfo(gateway, newStake); const EAY = rewardsInfo && newTotalStake > 0 @@ -102,8 +102,8 @@ const StakingModal = ({ minRequiredStakeToAdd, balances?.io, ), - unstakeAmount: validateUnstakeAmount( - 'Unstake Amount', + withdrawAmount: validateWithdrawAmount( + 'Withdraw Amount', ticker, existingStake, minDelegatedStake, @@ -117,7 +117,7 @@ const StakingModal = ({ if (tab == 0) { return validators.stakeAmount(amountToStake) == undefined; } else { - return validators.unstakeAmount(amountToUnstake) == undefined; + return validators.withdrawAmount(amountToWithdraw) == undefined; } }; @@ -132,7 +132,7 @@ const StakingModal = ({ if (tab == 0) { setAmountToStake((balances?.io || 0) + ''); } else { - setAmountToUnstake(currentStake + ''); + setAmountToWithdraw(currentStake + ''); } }; @@ -162,7 +162,7 @@ const StakingModal = ({ const { id: txID } = await arIOWriteableSDK.decreaseDelegateStake( { target: gatewayOwnerWallet, - decreaseQty: new IOToken(parseFloat(amountToUnstake)).toMIO(), + decreaseQty: new IOToken(parseFloat(amountToWithdraw)).toMIO(), }, WRITE_OPTIONS, ); @@ -195,7 +195,7 @@ const StakingModal = ({ const errorMessages = { gatewayOwner: validators.address(gatewayOwnerWallet), stakeAmount: validators.stakeAmount(amountToStake), - unstakeAmount: validators.unstakeAmount(amountToUnstake), + withdrawAmount: validators.withdrawAmount(amountToWithdraw), cannotStake: (balances?.io || 0) < minRequiredStakeToAdd ? `Insufficient balance, at least ${minRequiredStakeToAdd} IO required.` @@ -212,13 +212,13 @@ const StakingModal = ({ className={`${tab == 0 ? selectedTabClassNames : nonSelectedTabClassNames} rounded-tl-lg`} onClick={() => setTab(0)} > - Staking + Stake
@@ -246,7 +246,7 @@ const StakingModal = ({ {tab == 0 ? balances && `Available: ${formatWithCommas(balances.io)} ${ticker}` - : `Available to Unstake: ${formatWithCommas(currentStake)} ${ticker}`} + : `Available to Withdraw: ${formatWithCommas(currentStake)} ${ticker}`}
@@ -257,8 +257,8 @@ const StakingModal = ({ disabled={disableInput} readOnly={disableInput} type="text" - placeholder={`Enter amount of ${ticker} to ${tab == 0 ? 'stake' : 'unstake'}`} - value={tab == 0 ? amountToStake : amountToUnstake} + placeholder={`Enter amount of ${ticker} to ${tab == 0 ? 'stake' : 'withdraw'}`} + value={tab == 0 ? amountToStake : amountToWithdraw} onChange={(e) => { const textValue = e.target.value; @@ -269,7 +269,7 @@ const StakingModal = ({ if (tab == 0) { setAmountToStake(textValue); } else { - setAmountToUnstake(textValue); + setAmountToWithdraw(textValue); } }} > @@ -287,10 +287,10 @@ const StakingModal = ({ /> )} {tab == 1 && - amountToUnstake?.length > 0 && - errorMessages.unstakeAmount && ( + amountToWithdraw?.length > 0 && + errorMessages.withdrawAmount && ( )} @@ -324,26 +324,29 @@ const StakingModal = ({ value={gateway ? gateway.settings.fqdn : '-'} /> - -

{EAY_TOOLTIP_TEXT}

- {EAY_TOOLTIP_FORMULA} -
- } - > - - - } - /> - + {tab == 0 && ( + +

{EAY_TOOLTIP_TEXT}

+ + {EAY_TOOLTIP_FORMULA} + + + } + > + + + } + /> + )}
- +
@@ -368,7 +371,9 @@ const StakingModal = ({ isFormValid() ? tab == 0 ? formatWithCommas(currentStake + parseFloat(amountToStake)) - : formatWithCommas(currentStake - parseFloat(amountToUnstake)) + : formatWithCommas( + currentStake - parseFloat(amountToWithdraw), + ) : '-' } ${ticker}`} /> @@ -381,8 +386,8 @@ const StakingModal = ({ className="mt-8 h-[3.25rem] w-full" onClick={submitForm} buttonType={ButtonType.PRIMARY} - title={tab == 0 ? `Stake ${ticker}` : `Unstake ${ticker}`} - text={tab == 0 ? `Stake ${ticker}` : `Unstake ${ticker}`} + title={tab == 0 ? `Stake ${ticker}` : `Withdraw ${ticker}`} + text={tab == 0 ? `Stake ${ticker}` : `Withdraw ${ticker}`} /> diff --git a/src/components/modals/UnstakeAllModal.tsx b/src/components/modals/WithdrawAllModal.tsx similarity index 94% rename from src/components/modals/UnstakeAllModal.tsx rename to src/components/modals/WithdrawAllModal.tsx index c430ae72..113f6504 100644 --- a/src/components/modals/UnstakeAllModal.tsx +++ b/src/components/modals/WithdrawAllModal.tsx @@ -5,12 +5,12 @@ import { showErrorToast } from '@src/utils/toast'; import { useState } from 'react'; import Button, { ButtonType } from '../Button'; import BaseModal from './BaseModal'; -import UnstakeWarning from './UnstakeWarning'; +import WithdrawWarning from './WithdrawWarning'; import BlockingMessageModal from './BlockingMessageModal'; import SuccessModal from './SuccessModal'; import { useQueryClient } from '@tanstack/react-query'; -const UnstakeAllModal = ({ +const WithdrawAllModal = ({ onClose, activeStakes, }: { @@ -80,7 +80,7 @@ const UnstakeAllModal = ({
-
Unstake All
+
Withdraw All
Withdraw all delegated stakes.
@@ -111,7 +111,7 @@ const UnstakeAllModal = ({ ))} - +
@@ -130,8 +130,8 @@ const UnstakeAllModal = ({
} + title="Withdraw" + text={
Withdraw
} className="w-full" />
@@ -158,4 +158,4 @@ const UnstakeAllModal = ({ ); }; -export default UnstakeAllModal; +export default WithdrawAllModal; diff --git a/src/components/modals/UnstakeWarning.tsx b/src/components/modals/WithdrawWarning.tsx similarity index 87% rename from src/components/modals/UnstakeWarning.tsx rename to src/components/modals/WithdrawWarning.tsx index 09fe1ef6..fc2aecfd 100644 --- a/src/components/modals/UnstakeWarning.tsx +++ b/src/components/modals/WithdrawWarning.tsx @@ -1,6 +1,6 @@ import { WarningTriangleIcon } from '../icons'; -const UnstakeWarning = () => { +const WithdrawWarning = () => { return (
@@ -14,4 +14,4 @@ const UnstakeWarning = () => { ); }; -export default UnstakeWarning; +export default WithdrawWarning; diff --git a/src/pages/Staking/MyStakesTable.tsx b/src/pages/Staking/MyStakesTable.tsx index 2181b432..73c811d0 100644 --- a/src/pages/Staking/MyStakesTable.tsx +++ b/src/pages/Staking/MyStakesTable.tsx @@ -17,7 +17,7 @@ import { import CancelWithdrawalModal from '@src/components/modals/CancelWithdrawalModal'; import InstantWithdrawalModal from '@src/components/modals/InstantWithdrawalModal'; import StakingModal from '@src/components/modals/StakingModal'; -import UnstakeAllModal from '@src/components/modals/UnstakeAllModal'; +import WithdrawAllModal from '@src/components/modals/WithdrawAllModal'; import useGateways from '@src/hooks/useGateways'; import { useGlobalState } from '@src/store'; import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; @@ -58,7 +58,7 @@ const MyStakesTable = () => { const [tableMode, setTableMode] = useState('activeStakes'); - const [showUnstakeAllModal, setShowUnstakeAllModal] = useState(false); + const [showWithdrawAllModal, setShowWithdrawAllModal] = useState(false); const [stakingModalWalletAddress, setStakingModalWalletAddress] = useState(); const [showQuickStake, setShowQuickStake] = useState(false); @@ -338,7 +338,7 @@ const MyStakesTable = () => { active={true} title="Withdraw All" text="Withdraw All" - onClick={() => setShowUnstakeAllModal(true)} + onClick={() => setShowWithdrawAllModal(true)} /> )} +
+
+ +
+ + {showBlockingMessageModal && ( + setShowBlockingMessageModal(false)} + message="Sign the following data with your wallet to proceed." + > + )} + {showSuccessModal && ( + { + setShowSuccessModal(false); + onClose(); + onSuccess(); + }} + title="Congratulations" + bodyText={ +
+
You have successfully updated your stake.
+
+
Transaction ID:
+ +
+
+ } + /> + )} + + ); +}; + +export default ReviewStakeModal; diff --git a/src/components/modals/ReviewWithdrawalModal.tsx b/src/components/modals/ReviewWithdrawalModal.tsx new file mode 100644 index 00000000..351ff63c --- /dev/null +++ b/src/components/modals/ReviewWithdrawalModal.tsx @@ -0,0 +1,239 @@ +import { AoGatewayWithAddress, IOToken } from '@ar.io/sdk/web'; +import { log, WRITE_OPTIONS } from '@src/constants'; +import { useGlobalState } from '@src/store'; +import { WithdrawalType } from '@src/types'; +import { formatAddress, formatDateTime, formatWithCommas } from '@src/utils'; +import { ArweaveTransactionID } from '@src/utils/ArweaveTransactionId'; +import { showErrorToast } from '@src/utils/toast'; +import { useQueryClient } from '@tanstack/react-query'; +import dayjs from 'dayjs'; +import { useEffect, useState } from 'react'; +import Button, { ButtonType } from '../Button'; +import { LinkArrowIcon } from '../icons'; +import LabelValueRow from '../LabelValueRow'; +import BaseModal from './BaseModal'; +import BlockingMessageModal from './BlockingMessageModal'; +import SuccessModal from './SuccessModal'; + +const ReviewWithdrawalModal = ({ + gateway, + amountToWithdraw, + withdrawalType, + onSuccess, + onClose, + walletAddress, + ticker, + withdrawalFee, + returningAmount, +}: { + gateway: AoGatewayWithAddress; + amountToWithdraw: number; + withdrawalType: WithdrawalType; + walletAddress: ArweaveTransactionID; + onClose: () => void; + onSuccess: () => void; + ticker: string; + withdrawalFee: number; + returningAmount?: number | string; +}) => { + const queryClient = useQueryClient(); + const arIOWriteableSDK = useGlobalState((state) => state.arIOWriteableSDK); + + const [txid, setTxid] = useState(); + + const [showBlockingMessageModal, setShowBlockingMessageModal] = + useState(false); + const [showSuccessModal, setShowSuccessModal] = useState(false); + + const [dateOfReturn, setDateOfReturn] = useState(''); + + const [confirmText, setConfirmText] = useState(''); + + const termsAccepted = confirmText === 'WITHDRAW'; + + useEffect(() => { + setDateOfReturn( + withdrawalType === 'expedited' + ? 'Instant' + : formatDateTime(dayjs(new Date()).add(30, 'day').toDate()), + ); + }, [withdrawalType]); + + const submitForm = async () => { + if (arIOWriteableSDK) { + setShowBlockingMessageModal(true); + + try { + const instant = withdrawalType === 'expedited'; + + const { id: txID } = await arIOWriteableSDK.decreaseDelegateStake( + { + target: gateway.gatewayAddress, + decreaseQty: new IOToken(amountToWithdraw).toMIO(), + instant, + }, + WRITE_OPTIONS, + ); + setTxid(txID); + + log.info(`Decrease Delegate Stake txID: ${txID}`); + + queryClient.invalidateQueries({ + queryKey: ['gateway', walletAddress.toString()], + refetchType: 'all', + }); + queryClient.invalidateQueries({ + queryKey: ['gateways'], + refetchType: 'all', + }); + queryClient.invalidateQueries({ + queryKey: ['balances'], + refetchType: 'all', + }); + queryClient.invalidateQueries({ + queryKey: ['delegateStakes'], + refetchType: 'all', + }); + + setShowSuccessModal(true); + } catch (e: any) { + showErrorToast(`${e}`); + } finally { + setShowBlockingMessageModal(false); + } + } + }; + + return ( + <> + +
+
+ Review +
+
+ + + + + + + + +
+ +
+ + + {withdrawalType === 'expedited' && ( + <> + + + + )} +
+ +
+
+
+ Please type "WITHDRAW" in the text box to proceed. +
+ setConfirmText(e.target.value)} + className={ + 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-3 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' + } + value={confirmText} + /> +
+ +
+
+
+ +
+
+
+
+ + {showBlockingMessageModal && ( + setShowBlockingMessageModal(false)} + message="Sign the following data with your wallet to proceed." + > + )} + {showSuccessModal && ( + { + setShowSuccessModal(false); + onClose(); + onSuccess(); + }} + title="Congratulations" + bodyText={ +
+
You have successfully updated your stake.
+
+
Transaction ID:
+ +
+
+ } + /> + )} + + ); +}; + +export default ReviewWithdrawalModal; diff --git a/src/components/modals/StakingModal.tsx b/src/components/modals/StakingModal.tsx index a96bd0cf..a1538d1c 100644 --- a/src/components/modals/StakingModal.tsx +++ b/src/components/modals/StakingModal.tsx @@ -1,17 +1,13 @@ -import { IOToken, mIOToken } from '@ar.io/sdk/web'; -import { - EAY_TOOLTIP_FORMULA, - EAY_TOOLTIP_TEXT, - WRITE_OPTIONS, - log, -} from '@src/constants'; +import { mIOToken } from '@ar.io/sdk/web'; +import { Label, Radio, RadioGroup } from '@headlessui/react'; +import { EAY_TOOLTIP_FORMULA, EAY_TOOLTIP_TEXT } from '@src/constants'; import useBalances from '@src/hooks/useBalances'; import useDelegateStakes from '@src/hooks/useDelegateStakes'; import useGateway from '@src/hooks/useGateway'; import useRewardsInfo from '@src/hooks/useRewardsInfo'; import { useGlobalState } from '@src/store'; -import { formatWithCommas } from '@src/utils'; -import { showErrorToast } from '@src/utils/toast'; +import { WithdrawalType } from '@src/types'; +import { formatAddress, formatWithCommas } from '@src/utils'; import { useQueryClient } from '@tanstack/react-query'; import { MathJax } from 'better-react-mathjax'; import { useEffect, useState } from 'react'; @@ -24,11 +20,10 @@ import { validateWalletAddress, validateWithdrawAmount, } from '../forms/validation'; -import { InfoIcon } from '../icons'; +import { CircleCheckIcon, CircleIcon, InfoIcon } from '../icons'; import BaseModal from './BaseModal'; -import BlockingMessageModal from './BlockingMessageModal'; -import SuccessModal from './SuccessModal'; -import WithdrawWarning from './WithdrawWarning'; +import ReviewStakeModal from './ReviewStakeModal'; +import ReviewWithdrawalModal from './ReviewWithdrawalModal'; const StakingModal = ({ onClose, @@ -52,10 +47,12 @@ const StakingModal = ({ const [currentStake, setCurrentStake] = useState(0); const [amountToStake, setAmountToStake] = useState(''); const [amountToWithdraw, setAmountToWithdraw] = useState(''); + const [withdrawalType, setWithdrawalType] = + useState('standard'); - const [showBlockingMessageModal, setShowBlockingMessageModal] = + const [showReviewStakeModal, setShowReviewStakeModal] = useState(false); + const [showReviewWithdrawalModal, setShowReviewWithdrawalModal] = useState(false); - const [showSuccessModal, setShowSuccessModal] = useState(false); const gatewayOwnerWallet = ownerWallet?.toString() ?? userEnteredWalletAddress; @@ -87,7 +84,7 @@ const StakingModal = ({ tab == 0 ? parseFloat(amountToStake) : -parseFloat(amountToWithdraw); const rewardsInfo = useRewardsInfo(gateway, newStake); const EAY = - rewardsInfo && newTotalStake > 0 + rewardsInfo && newTotalStake > 0 && !isNaN(rewardsInfo.EAY) ? (rewardsInfo.EAY * 100).toLocaleString('en-us', { maximumFractionDigits: 2, }) + '%' @@ -98,6 +95,16 @@ const StakingModal = ({ : 500; const minRequiredStakeToAdd = currentStake > 0 ? 1 : minDelegatedStake; + const withdrawalFee = + withdrawalType === 'expedited' ? 0.5 * parseFloat(amountToWithdraw) : 0; + const returningAmount = isNaN(parseFloat(amountToWithdraw)) + ? '-' + : +( + isNaN(withdrawalFee) + ? parseFloat(amountToWithdraw) + : parseFloat(amountToWithdraw) - withdrawalFee + ).toFixed(4); + const validators = { address: validateWalletAddress('Gateway Owner'), stakeAmount: validateIOAmount( @@ -125,8 +132,11 @@ const StakingModal = ({ } }; + const parsedStake = parseFloat( + amountToStake.length === 0 ? '0' : amountToStake, + ); const remainingBalance = - isFormValid() && balances ? balances.io - parseFloat(amountToStake) : '-'; + balances && parsedStake <= balances.io ? balances.io - parsedStake : -1; const baseTabClassName = 'text-center py-3'; const selectedTabClassNames = `${baseTabClassName} bg-grey-700 border-b border-red-400`; @@ -147,59 +157,6 @@ const StakingModal = ({ !allowDelegatedStaking)) || (tab == 1 && currentStake <= 0); - const submitForm = async () => { - if (walletAddress && arIOWriteableSDK && gateway && isFormValid()) { - setShowBlockingMessageModal(true); - - try { - if (tab == 0) { - const { id: txID } = await arIOWriteableSDK.delegateStake( - { - target: gatewayOwnerWallet, - stakeQty: new IOToken(parseFloat(amountToStake)).toMIO(), - }, - WRITE_OPTIONS, - ); - - log.info(`Increase Delegate Stake txID: ${txID}`); - } else { - const { id: txID } = await arIOWriteableSDK.decreaseDelegateStake( - { - target: gatewayOwnerWallet, - decreaseQty: new IOToken(parseFloat(amountToWithdraw)).toMIO(), - }, - WRITE_OPTIONS, - ); - - log.info(`Decrease Delegate Stake txID: ${txID}`); - } - - queryClient.invalidateQueries({ - queryKey: ['gateway', walletAddress.toString()], - refetchType: 'all', - }); - queryClient.invalidateQueries({ - queryKey: ['gateways'], - refetchType: 'all', - }); - queryClient.invalidateQueries({ - queryKey: ['balances'], - refetchType: 'all', - }); - queryClient.invalidateQueries({ - queryKey: ['delegateStakes'], - refetchType: 'all', - }); - - setShowSuccessModal(true); - } catch (e: any) { - showErrorToast(`${e}`); - } finally { - setShowBlockingMessageModal(false); - } - } - }; - const errorMessages = { gatewayOwner: validators.address(gatewayOwnerWallet), stakeAmount: validators.stakeAmount(amountToStake), @@ -215,45 +172,64 @@ const StakingModal = ({ return (
-
+
-
Gateway Owner:
- {ownerWallet ? ( -
{ownerWallet}
- ) : ( - { - setUserEnteredWalletAddress(e.target.value); - }} - maxLength={43} +
+ {ownerWallet ? ( + + ) : ( + <> +
Gateway Owner:
+ { + setUserEnteredWalletAddress(e.target.value); + }} + maxLength={43} + /> + + )} + + - )} + + +
+
Amount:
{tab == 0 ? balances && - `Available: ${formatWithCommas(balances.io)} ${ticker}` + `Available: ${remainingBalance >= 0 ? formatWithCommas(+remainingBalance) : '-'} ${ticker}` : `Available to Withdraw: ${formatWithCommas(currentStake)} ${ticker}`}
@@ -311,30 +287,89 @@ const StakingModal = ({ text="Max" />
-
+
+ {tab == 1 && ( + setWithdrawalType(v)} + > + +
+
+ + + +
+

+ 30 day withdrawal period with no fees. +

+
+
+ + +
+
+ + + +
+

+ Instant withdrawal with 50% fee. +

+
+
+
+ )} +
+
+
+ {tab == 1 && withdrawalType == 'expedited' && ( + <> + + + + )} + +
{tab == 0 && ( )} - {tab == 0 && ( )} -
- -
-
-
- - - {tab == 0 && ( - - )} -
- {showBlockingMessageModal && ( - setShowBlockingMessageModal(false)} - message="Sign the following data with your wallet to proceed." - > + {showReviewStakeModal && gateway && walletAddress && ( + setShowReviewStakeModal(false)} + onSuccess={() => onClose()} + ticker={ticker} + walletAddress={walletAddress} + /> )} - {showSuccessModal && ( - { - setShowSuccessModal(false); - onClose(); - }} - title="Congratulations" - bodyText="You have successfully updated stake." + {showReviewWithdrawalModal && gateway && walletAddress && ( + setShowReviewWithdrawalModal(false)} + onSuccess={() => onClose()} + ticker={ticker} + walletAddress={walletAddress} + withdrawalFee={withdrawalFee} + returningAmount={returningAmount} /> )}
diff --git a/src/hooks/useDelegateStakes.ts b/src/hooks/useDelegateStakes.ts index f97ad1e3..47c9b61a 100644 --- a/src/hooks/useDelegateStakes.ts +++ b/src/hooks/useDelegateStakes.ts @@ -11,7 +11,7 @@ const useDelegateStakes = (address?: string) => { const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); const res = useQuery({ - queryKey: ['delegateStakes', address], + queryKey: ['delegateStakes', arIOReadSDK, address], queryFn: async () => { if (!address) { throw new Error('Address is not set'); @@ -30,6 +30,7 @@ const useDelegateStakes = (address?: string) => { cursor, limit: 10, }); + pageResult.items.forEach((d) => { if (d.type === 'stake') { retVal.stakes.push(d); diff --git a/src/types.ts b/src/types.ts index 0342af36..7adef506 100644 --- a/src/types.ts +++ b/src/types.ts @@ -68,3 +68,5 @@ export interface ArNSAssessment { resolvedStatusCode: number; timings?: ArNSAssessmentTimings; } + +export type WithdrawalType = 'standard' | 'expedited'; diff --git a/tailwind.config.js b/tailwind.config.js index 8abb3758..175f58c1 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -42,6 +42,7 @@ export default { 'streak-up': '#3DB7C2', 'text-red': '#DB4354', warning: '#ffb938', + 'stroke-low': 'rgba(202, 202, 214, 0.08)' }, }, plugins: [ diff --git a/yarn.lock b/yarn.lock index 470931fc..ca87b910 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1805,23 +1805,46 @@ dependencies: "@floating-ui/dom" "^1.0.0" +"@floating-ui/react-dom@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" + integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/react@^0.26.16": + version "0.26.27" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.27.tgz#402f7b4b2702650662705fe9cbe0f1d5607846a1" + integrity sha512-jLP72x0Kr2CgY6eTYi/ra3VA9LOkTo4C+DUTrbFgFOExKy3omYVmwMjNKqxAHdsnyLS96BIDLcO2SlnsNf8KUQ== + dependencies: + "@floating-ui/react-dom" "^2.1.2" + "@floating-ui/utils" "^0.2.8" + tabbable "^6.0.0" + "@floating-ui/utils@^0.2.0": version "0.2.2" resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz" integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== +"@floating-ui/utils@^0.2.8": + version "0.2.8" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" + integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== + "@fontsource/rubik@^5.0.19": version "5.0.20" resolved "https://registry.npmjs.org/@fontsource/rubik/-/rubik-5.0.20.tgz" integrity sha512-4iEk1Nnnz4kzrpfsjfHXOm7HDVtsDfs8uihhE4LaXqQuxnY8lERZWJhtGAKILDwbx3gsnVXI+0beUNLRmaHeCw== -"@headlessui/react@^1.7.19": - version "1.7.19" - resolved "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz" - integrity sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw== +"@headlessui/react@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.2.0.tgz#a8e32f0899862849a1ce1615fa280e7891431ab7" + integrity sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ== dependencies: - "@tanstack/react-virtual" "^3.0.0-beta.60" - client-only "^0.0.1" + "@floating-ui/react" "^0.26.16" + "@react-aria/focus" "^3.17.1" + "@react-aria/interactions" "^3.21.3" + "@tanstack/react-virtual" "^3.8.1" "@humanwhocodes/config-array@^0.11.14": version "0.11.14" @@ -2574,6 +2597,57 @@ dependencies: "@randlabs/communication-bridge" "1.0.1" +"@react-aria/focus@^3.17.1": + version "3.18.4" + resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.18.4.tgz#a6e95896bc8680d1b5bcd855e983fc2c195a1a55" + integrity sha512-91J35077w9UNaMK1cpMUEFRkNNz0uZjnSwiyBCFuRdaVuivO53wNC9XtWSDNDdcO5cGy87vfJRVAiyoCn/mjqA== + dependencies: + "@react-aria/interactions" "^3.22.4" + "@react-aria/utils" "^3.25.3" + "@react-types/shared" "^3.25.0" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + +"@react-aria/interactions@^3.21.3", "@react-aria/interactions@^3.22.4": + version "3.22.4" + resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.22.4.tgz#88ed61ab6a485f869bc1f65ae6688d48ca96064b" + integrity sha512-E0vsgtpItmknq/MJELqYJwib+YN18Qag8nroqwjk1qOnBa9ROIkUhWJerLi1qs5diXq9LHKehZDXRlwPvdEFww== + dependencies: + "@react-aria/ssr" "^3.9.6" + "@react-aria/utils" "^3.25.3" + "@react-types/shared" "^3.25.0" + "@swc/helpers" "^0.5.0" + +"@react-aria/ssr@^3.9.6": + version "3.9.6" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.6.tgz#a9e8b351acdc8238f2b5215b0ce904636c6ea690" + integrity sha512-iLo82l82ilMiVGy342SELjshuWottlb5+VefO3jOQqQRNYnJBFpUSadswDPbRimSgJUZuFwIEYs6AabkP038fA== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-aria/utils@^3.25.3": + version "3.25.3" + resolved "https://registry.yarnpkg.com/@react-aria/utils/-/utils-3.25.3.tgz#cad9bffc07b045cdc283df2cb65c18747acbf76d" + integrity sha512-PR5H/2vaD8fSq0H/UB9inNbc8KDcVmW6fYAfSWkkn+OAdhTTMVKqXXrZuZBWyFfSD5Ze7VN6acr4hrOQm2bmrA== + dependencies: + "@react-aria/ssr" "^3.9.6" + "@react-stately/utils" "^3.10.4" + "@react-types/shared" "^3.25.0" + "@swc/helpers" "^0.5.0" + clsx "^2.0.0" + +"@react-stately/utils@^3.10.4": + version "3.10.4" + resolved "https://registry.yarnpkg.com/@react-stately/utils/-/utils-3.10.4.tgz#310663a834b67048d305e1680ed258130092fe51" + integrity sha512-gBEQEIMRh5f60KCm7QKQ2WfvhB2gLUr9b72sqUdIZ2EG+xuPgaIlCBeSicvjmjBvYZwOjoOEnmIkcx2GHp/HWw== + dependencies: + "@swc/helpers" "^0.5.0" + +"@react-types/shared@^3.25.0": + version "3.25.0" + resolved "https://registry.yarnpkg.com/@react-types/shared/-/shared-3.25.0.tgz#7223baf72256e918a3c29081bb1ecc6fad4fbf58" + integrity sha512-OZSyhzU6vTdW3eV/mz5i6hQwQUhkRs7xwY2d1aqPvTdMe0+2cY7Fwp45PAiwYLEj73i9ro2FxF9qC4DvHGSCgQ== + "@remix-run/router@1.16.1": version "1.16.1" resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz" @@ -3114,6 +3188,13 @@ resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== +"@swc/helpers@^0.5.0": + version "0.5.15" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.15.tgz#79efab344c5819ecf83a43f3f9f811fc84b516d7" + integrity sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g== + dependencies: + tslib "^2.8.0" + "@swc/helpers@^0.5.11": version "0.5.11" resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz" @@ -3157,22 +3238,22 @@ dependencies: "@tanstack/table-core" "8.17.3" -"@tanstack/react-virtual@^3.0.0-beta.60": - version "3.5.0" - resolved "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.5.0.tgz" - integrity sha512-rtvo7KwuIvqK9zb0VZ5IL7fiJAEnG+0EiFZz8FUOs+2mhGqdGmjKIaT1XU7Zq0eFqL0jonLlhbayJI/J2SA/Bw== +"@tanstack/react-virtual@^3.8.1": + version "3.10.9" + resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.10.9.tgz#40606b6dd8aba8e977f576d8f7df07f69ca63eea" + integrity sha512-OXO2uBjFqA4Ibr2O3y0YMnkrRWGVNqcvHQXmGvMu6IK8chZl3PrDxFXdGZ2iZkSrKh3/qUYoFqYe+Rx23RoU0g== dependencies: - "@tanstack/virtual-core" "3.5.0" + "@tanstack/virtual-core" "3.10.9" "@tanstack/table-core@8.17.3": version "8.17.3" resolved "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.17.3.tgz" integrity sha512-mPBodDGVL+fl6d90wUREepHa/7lhsghg2A3vFpakEhrhtbIlgNAZiMr7ccTgak5qbHqF14Fwy+W1yFWQt+WmYQ== -"@tanstack/virtual-core@3.5.0": - version "3.5.0" - resolved "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.5.0.tgz" - integrity sha512-KnPRCkQTyqhanNC0K63GBG3wA8I+D1fQuVnAvcBF8f13akOKeQp1gSbu6f77zCxhEk727iV5oQnbHLYzHrECLg== +"@tanstack/virtual-core@3.10.9": + version "3.10.9" + resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.10.9.tgz#55710c92b311fdaa8d8c66682a0dbdd684bc77c4" + integrity sha512-kBknKOKzmeR7lN+vSadaKWXaLS0SZZG+oqpQ/k80Q6g9REn6zRHS/ZYdrIzHnpHgy/eWs00SujveUN/GJT2qTw== "@testing-library/dom@^9.0.0": version "9.3.4" @@ -4724,9 +4805,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: - version "1.0.30001621" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz" - integrity sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA== + version "1.0.30001680" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz" + integrity sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA== catering@^2.1.0, catering@^2.1.1: version "2.1.1" @@ -4854,11 +4935,6 @@ cli-width@^3.0.0: resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -client-only@^0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" - integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== - cliui@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" @@ -10506,6 +10582,11 @@ symbol-tree@^3.2.4: resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== +tabbable@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + tailwind-scrollbar@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-3.1.0.tgz" @@ -10759,6 +10840,11 @@ tslib@^2.1.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tty-browserify@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz" From d0ccae3a724d06ddd1726018d76e8c141ba01588 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 19 Nov 2024 09:37:06 -0500 Subject: [PATCH 04/22] fix: lint errors --- src/components/modals/ReviewStakeModal.tsx | 2 +- src/components/modals/ReviewWithdrawalModal.tsx | 2 +- src/components/modals/StakingModal.tsx | 10 ++++------ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/modals/ReviewStakeModal.tsx b/src/components/modals/ReviewStakeModal.tsx index 1b509831..4c14e1d2 100644 --- a/src/components/modals/ReviewStakeModal.tsx +++ b/src/components/modals/ReviewStakeModal.tsx @@ -88,7 +88,7 @@ const ReviewStakeModal = ({ showCloseButton={false} >
-
+
Review
diff --git a/src/components/modals/ReviewWithdrawalModal.tsx b/src/components/modals/ReviewWithdrawalModal.tsx index 351ff63c..24add9ba 100644 --- a/src/components/modals/ReviewWithdrawalModal.tsx +++ b/src/components/modals/ReviewWithdrawalModal.tsx @@ -112,7 +112,7 @@ const ReviewWithdrawalModal = ({ showCloseButton={false} >
-
+
Review
diff --git a/src/components/modals/StakingModal.tsx b/src/components/modals/StakingModal.tsx index a1538d1c..d7a609c7 100644 --- a/src/components/modals/StakingModal.tsx +++ b/src/components/modals/StakingModal.tsx @@ -8,7 +8,6 @@ import useRewardsInfo from '@src/hooks/useRewardsInfo'; import { useGlobalState } from '@src/store'; import { WithdrawalType } from '@src/types'; import { formatAddress, formatWithCommas } from '@src/utils'; -import { useQueryClient } from '@tanstack/react-query'; import { MathJax } from 'better-react-mathjax'; import { useEffect, useState } from 'react'; import Button, { ButtonType } from '../Button'; @@ -33,11 +32,8 @@ const StakingModal = ({ onClose: () => void; ownerWallet?: string; }) => { - const queryClient = useQueryClient(); - const walletAddress = useGlobalState((state) => state.walletAddress); const { data: balances } = useBalances(walletAddress); - const arIOWriteableSDK = useGlobalState((state) => state.arIOWriteableSDK); const ticker = useGlobalState((state) => state.ticker); const [tab, setTab] = useState(0); @@ -172,7 +168,7 @@ const StakingModal = ({ return (
-
+
From 8f48afa4604d58e46ae484d2eb1c2c8792e48343 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 19 Nov 2024 14:20:27 -0500 Subject: [PATCH 06/22] fix: adjust modals for consistency --- src/components/modals/BaseModal.tsx | 8 ++++---- src/components/modals/CancelWithdrawalModal.tsx | 4 ++-- src/components/modals/InstantWithdrawalModal.tsx | 6 +++--- src/components/modals/LeaveNetworkModal.tsx | 4 ++-- src/components/modals/ReviewStakeModal.tsx | 2 +- src/components/modals/ReviewWithdrawalModal.tsx | 4 ++-- src/components/modals/StakingModal.tsx | 4 ++-- src/components/modals/WithdrawAllModal.tsx | 2 +- src/pages/Gateway/PropertyDisplayPanel.tsx | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/components/modals/BaseModal.tsx b/src/components/modals/BaseModal.tsx index 5c3a6ab2..55fe3389 100644 --- a/src/components/modals/BaseModal.tsx +++ b/src/components/modals/BaseModal.tsx @@ -1,4 +1,4 @@ -import { Dialog } from '@headlessui/react'; +import { Dialog, DialogPanel } from '@headlessui/react'; import { ReactElement } from 'react'; import { CloseIcon } from '../icons'; @@ -21,7 +21,7 @@ const BaseModal = ({ />
- {showCloseButton && ( @@ -29,10 +29,10 @@ const BaseModal = ({ )} -
+
{children}
- +
); diff --git a/src/components/modals/CancelWithdrawalModal.tsx b/src/components/modals/CancelWithdrawalModal.tsx index 79f6886f..22b0d1a2 100644 --- a/src/components/modals/CancelWithdrawalModal.tsx +++ b/src/components/modals/CancelWithdrawalModal.tsx @@ -76,7 +76,7 @@ const CancelWithdrawalModal = ({
-
+
Please type "CONFIRM" in the text box to proceed. @@ -85,7 +85,7 @@ const CancelWithdrawalModal = ({ type="text" onChange={(e) => setConfirmText(e.target.value)} className={ - 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-3 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' + 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-4 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' } value={confirmText} /> diff --git a/src/components/modals/InstantWithdrawalModal.tsx b/src/components/modals/InstantWithdrawalModal.tsx index 4c1479fe..ccbb1b84 100644 --- a/src/components/modals/InstantWithdrawalModal.tsx +++ b/src/components/modals/InstantWithdrawalModal.tsx @@ -142,7 +142,7 @@ const InstantWithdrawalModal = ({
-
+
-
+
Please type "WITHDRAW" in the text box to proceed. @@ -189,7 +189,7 @@ const InstantWithdrawalModal = ({ type="text" onChange={(e) => setConfirmText(e.target.value)} className={ - 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-3 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' + 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-4 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' } value={confirmText} /> diff --git a/src/components/modals/LeaveNetworkModal.tsx b/src/components/modals/LeaveNetworkModal.tsx index 5605a9e1..6f7b31ba 100644 --- a/src/components/modals/LeaveNetworkModal.tsx +++ b/src/components/modals/LeaveNetworkModal.tsx @@ -94,14 +94,14 @@ const LeaveNetworkModal = ({ onClose }: { onClose: () => void }) => {
-
+
Please type "LEAVE NETWORK" in the text box to proceed.
setLeaveNetworkText(e.target.value)} className={ - 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-3 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' + 'h-7 w-full rounded-md border border-grey-700 bg-grey-1000 p-4 text-sm text-mid outline-none placeholder:text-grey-400 focus:text-high' } value={leaveNetworkText} /> diff --git a/src/components/modals/ReviewStakeModal.tsx b/src/components/modals/ReviewStakeModal.tsx index 4c14e1d2..bdea002c 100644 --- a/src/components/modals/ReviewStakeModal.tsx +++ b/src/components/modals/ReviewStakeModal.tsx @@ -118,7 +118,7 @@ const ReviewStakeModal = ({
-
+
-
+
Fee:
- AR
diff --git a/src/pages/Gateway/PropertyDisplayPanel.tsx b/src/pages/Gateway/PropertyDisplayPanel.tsx index 3faae473..6ae0afbe 100644 --- a/src/pages/Gateway/PropertyDisplayPanel.tsx +++ b/src/pages/Gateway/PropertyDisplayPanel.tsx @@ -79,7 +79,7 @@ const PropertyDisplayPanel = ({ const [isConnectModalOpen, setIsConnectModalOpen] = useState(false); const [isLeaveNetworkModalOpen, setLeaveNetworkModalOpen] = - useState(false); + useState(true); const gatewayAddress = gateway ? `${gateway.settings.protocol}://${gateway.settings.fqdn}:${gateway.settings.port}` From 97a5621cb58b44c5f3acd5b0beffaa72c19313c0 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 19 Nov 2024 14:23:18 -0500 Subject: [PATCH 07/22] fix: reset leave network modal back to false by default --- src/pages/Gateway/PropertyDisplayPanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Gateway/PropertyDisplayPanel.tsx b/src/pages/Gateway/PropertyDisplayPanel.tsx index 6ae0afbe..3faae473 100644 --- a/src/pages/Gateway/PropertyDisplayPanel.tsx +++ b/src/pages/Gateway/PropertyDisplayPanel.tsx @@ -79,7 +79,7 @@ const PropertyDisplayPanel = ({ const [isConnectModalOpen, setIsConnectModalOpen] = useState(false); const [isLeaveNetworkModalOpen, setLeaveNetworkModalOpen] = - useState(true); + useState(false); const gatewayAddress = gateway ? `${gateway.settings.protocol}://${gateway.settings.fqdn}:${gateway.settings.port}` From d5fd4fdc5be3df9dc237827c113c67e19973a884 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 19 Nov 2024 14:24:27 -0500 Subject: [PATCH 08/22] chore: remove unused fee field --- src/components/modals/WithdrawAllModal.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/modals/WithdrawAllModal.tsx b/src/components/modals/WithdrawAllModal.tsx index 2b0983b4..2933d112 100644 --- a/src/components/modals/WithdrawAllModal.tsx +++ b/src/components/modals/WithdrawAllModal.tsx @@ -119,10 +119,6 @@ const WithdrawAllModal = ({
-
-
Fee:
-
- AR
-
Total Withdrawal:
From 1336a7bdd1c3e3ab8a5f7291797448a16f7922f4 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 19 Nov 2024 15:24:32 -0500 Subject: [PATCH 09/22] chore: update to use non-deprecated API --- src/components/Profile.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index 69d31ce3..b42d11fe 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -1,5 +1,5 @@ /* eslint-disable tailwindcss/migration-from-tailwind-2 */ -import { Popover } from '@headlessui/react'; +import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'; import { useGlobalState } from '@src/store'; import { formatBalance, formatWalletAddress } from '@src/utils'; import { forwardRef, useState } from 'react'; @@ -45,9 +45,9 @@ const Profile = () => { return walletAddress ? ( - + - +
@@ -110,7 +110,7 @@ const Profile = () => { Logout
-
+
) : walletStateInitialized ? (
From c7d606018d7c02310113cd437bea82a3243427dc Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 19 Nov 2024 15:24:42 -0500 Subject: [PATCH 10/22] fix: lint warnings --- src/components/modals/CancelWithdrawalModal.tsx | 2 +- src/components/modals/InstantWithdrawalModal.tsx | 2 +- src/components/modals/LeaveNetworkModal.tsx | 2 +- src/components/modals/StakingModal.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/modals/CancelWithdrawalModal.tsx b/src/components/modals/CancelWithdrawalModal.tsx index 22b0d1a2..7a6789f2 100644 --- a/src/components/modals/CancelWithdrawalModal.tsx +++ b/src/components/modals/CancelWithdrawalModal.tsx @@ -76,7 +76,7 @@ const CancelWithdrawalModal = ({
-
+
Please type "CONFIRM" in the text box to proceed. diff --git a/src/components/modals/InstantWithdrawalModal.tsx b/src/components/modals/InstantWithdrawalModal.tsx index ccbb1b84..a104a25e 100644 --- a/src/components/modals/InstantWithdrawalModal.tsx +++ b/src/components/modals/InstantWithdrawalModal.tsx @@ -180,7 +180,7 @@ const InstantWithdrawalModal = ({ />
-
+
Please type "WITHDRAW" in the text box to proceed. diff --git a/src/components/modals/LeaveNetworkModal.tsx b/src/components/modals/LeaveNetworkModal.tsx index 6f7b31ba..557d07a2 100644 --- a/src/components/modals/LeaveNetworkModal.tsx +++ b/src/components/modals/LeaveNetworkModal.tsx @@ -94,7 +94,7 @@ const LeaveNetworkModal = ({ onClose }: { onClose: () => void }) => {
-
+
Please type "LEAVE NETWORK" in the text box to proceed.
-
+
{tab == 1 && withdrawalType == 'expedited' && ( <> Date: Tue, 19 Nov 2024 15:36:08 -0500 Subject: [PATCH 11/22] chore: update to use non-deprecated API --- src/components/AssessmentDetailsPanel.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/AssessmentDetailsPanel.tsx b/src/components/AssessmentDetailsPanel.tsx index 901c04a1..6b9c35f9 100644 --- a/src/components/AssessmentDetailsPanel.tsx +++ b/src/components/AssessmentDetailsPanel.tsx @@ -1,4 +1,4 @@ -import { Dialog } from '@headlessui/react'; +import { Dialog, DialogPanel } from '@headlessui/react'; import { CaretDoubleRightIcon, CheckSquareIcon, @@ -177,7 +177,7 @@ const AssessmentDetailsPanel = ({ />
- )}
- +
); From ff7713af7988df9f96048f300b4c72ee7a05bfa3 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 19 Nov 2024 18:45:09 -0500 Subject: [PATCH 12/22] feat: show user wallet address in profile button when logged in --- src/components/Profile.tsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index b42d11fe..9d7a8698 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -1,10 +1,12 @@ /* eslint-disable tailwindcss/migration-from-tailwind-2 */ import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'; +import useBalances from '@src/hooks/useBalances'; import { useGlobalState } from '@src/store'; import { formatBalance, formatWalletAddress } from '@src/utils'; -import { forwardRef, useState } from 'react'; +import { forwardRef, ReactElement, useState } from 'react'; import Button, { ButtonType } from './Button'; import CopyButton from './CopyButton'; +import Placeholder from './Placeholder'; import Tooltip from './Tooltip'; import { ClockRewindIcon, @@ -14,17 +16,19 @@ import { WalletIcon, } from './icons'; import ConnectModal from './modals/ConnectModal'; -import useBalances from '@src/hooks/useBalances'; -import Placeholder from './Placeholder'; // eslint-disable-next-line react/display-name -const CustomPopoverButton = forwardRef((props, ref) => { +const CustomPopoverButton = forwardRef< + HTMLButtonElement, + { children?: ReactElement } +>((props, ref) => { return (
{/*
Observer Host:
@@ -59,11 +84,7 @@ const ReportHeader = ({
Observer Address:
{reportData ?
{reportData.observerAddress}
: }
Epoch Number:
- {reportData ? ( -
{reportData.epochIndex}
- ) : ( - - )} + {reportData ?
{reportData.epochIndex}
: }
Generated At:
{reportData ? (
{formatDateTime(new Date(reportData.epochStartTimestamp))}
diff --git a/src/pages/Reports/ReportsTable.tsx b/src/pages/Reports/ReportsTable.tsx index 54fa9254..b314a842 100644 --- a/src/pages/Reports/ReportsTable.tsx +++ b/src/pages/Reports/ReportsTable.tsx @@ -1,8 +1,13 @@ import { AoGateway } from '@ar.io/sdk/web'; +import Button, { ButtonType } from '@src/components/Button'; +import { DownloadIcon } from '@src/components/icons'; import TableView from '@src/components/TableView'; +import { downloadReport } from '@src/hooks/useReport'; import useReports, { ReportTransactionData } from '@src/hooks/useReports'; import { formatDateTime } from '@src/utils'; +import { showErrorToast } from '@src/utils/toast'; import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; +import { saveAs } from 'file-saver'; import { useNavigate } from 'react-router-dom'; const columnHelper = createColumnHelper(); @@ -52,6 +57,35 @@ const ReportsTable = ({ header: 'Failed Gateways', sortDescFirst: false, }), + + columnHelper.display({ + id: 'actions', + cell: ({ row }) => { + return ( +
+
+ ); + }, + }), ]; return ( diff --git a/yarn.lock b/yarn.lock index 49d99710..1c47cd0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,10 +35,10 @@ plimit-lit "^3.0.1" warp-contracts "1.4.45" -"@ar.io/sdk@2.5.0-alpha.8": - version "2.5.0-alpha.8" - resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.5.0-alpha.8.tgz#7057896b7858122237c69f11a2bbaeb59ce811af" - integrity sha512-+cE/jsfgQEwHb7rUNLvU77Tmhien+y3O9xOR+sE5Agj+RBwJthrl3JxZjAyd3nMaJ6OweLtZWhmHqCI+RQRAAg== +"@ar.io/sdk@2.5.0-alpha.10": + version "2.5.0-alpha.10" + resolved "https://registry.yarnpkg.com/@ar.io/sdk/-/sdk-2.5.0-alpha.10.tgz#feea7fa7ce2eba73bb24274132fcbf40eedac0cb" + integrity sha512-p4+018TEXHmByQW7QoAVNIkSie0h202plmaMoPFzTTbFQ7eygpNFhisrEQVNRaOpcR6xbkg070azjkBcr3DGzA== dependencies: "@dha-team/arbundles" "^1.0.1" "@permaweb/aoconnect" "^0.0.57" @@ -3431,6 +3431,11 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/file-saver@^2.0.7": + version "2.0.7" + resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.7.tgz#8dbb2f24bdc7486c54aa854eb414940bbd056f7d" + integrity sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A== + "@types/glob@*": version "8.1.0" resolved "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz" @@ -6413,6 +6418,11 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" From 688f0bf03a2c95794501729c197ba00949402d2a Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Fri, 22 Nov 2024 18:06:26 -0500 Subject: [PATCH 14/22] feat: added Epoch selector to Observers page --- CHANGELOG.md | 1 + package.json | 2 +- src/hooks/useObservations.ts | 14 ++++---- src/hooks/useObservers.ts | 13 ++++---- src/pages/Observers/Banner.tsx | 5 +-- src/pages/Observers/ObserversTable.tsx | 44 +++++++++++++++++++++----- yarn.lock | 8 ++--- 7 files changed, 57 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 082f3c07..2a26e274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added * Download buttons added to Reports page and individual Report page +* Observers: Added epoch selector to view prescribed observers for previous epochs ### Updated diff --git a/package.json b/package.json index c6c21ed9..93c7058d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "deploy": "yarn build && permaweb-deploy --ant-process ${DEPLOY_ANT_PROCESS_ID}" }, "dependencies": { - "@ar.io/sdk": "2.5.0-alpha.10", + "@ar.io/sdk": "2.5.0", "@fontsource/rubik": "^5.0.19", "@headlessui/react": "^2.2.0", "@radix-ui/react-tooltip": "^1.0.7", diff --git a/src/hooks/useObservations.ts b/src/hooks/useObservations.ts index 3367ac69..8cf03422 100644 --- a/src/hooks/useObservations.ts +++ b/src/hooks/useObservations.ts @@ -1,17 +1,15 @@ - +import { AoEpochData } from '@ar.io/sdk/web'; import { useGlobalState } from '@src/store'; import { useQuery } from '@tanstack/react-query'; -const useObservers = () => { +const useObservations = (epoch?: AoEpochData) => { const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); - const currentEpoch = useGlobalState((state) => state.currentEpoch); - const queryResults = useQuery({ - queryKey: ['observations', currentEpoch?.epochIndex || -1], + queryKey: ['observations', arIOReadSDK, epoch?.epochIndex || -1], queryFn: () => { - if (arIOReadSDK && currentEpoch) { - return arIOReadSDK.getObservations(currentEpoch); + if (arIOReadSDK && epoch) { + return arIOReadSDK.getObservations(epoch); } throw new Error('arIOReadSDK or currentEpoch not available'); }, @@ -20,4 +18,4 @@ const useObservers = () => { return queryResults; }; -export default useObservers; +export default useObservations; diff --git a/src/hooks/useObservers.ts b/src/hooks/useObservers.ts index e9b982d9..a873808d 100644 --- a/src/hooks/useObservers.ts +++ b/src/hooks/useObservers.ts @@ -1,18 +1,17 @@ +import { AoEpochData } from '@ar.io/sdk/web'; import { useGlobalState } from '@src/store'; import { useQuery } from '@tanstack/react-query'; -const useObservers = () => { +const useObservers = (epoch?: AoEpochData) => { const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); - const currentEpoch = useGlobalState((state) => state.currentEpoch); - const queryResults = useQuery({ - queryKey: ['prescribedObservers', currentEpoch?.epochIndex || -1], + queryKey: ['prescribedObservers', arIOReadSDK, epoch?.epochIndex || -1], queryFn: () => { - if (arIOReadSDK && currentEpoch) { - return arIOReadSDK.getPrescribedObservers(currentEpoch); + if (arIOReadSDK && epoch) { + return arIOReadSDK.getPrescribedObservers(epoch); } - throw new Error('arIOReadSDK or currentEpoch not available'); + throw new Error('arIOReadSDK or epoch not available'); }, }); diff --git a/src/pages/Observers/Banner.tsx b/src/pages/Observers/Banner.tsx index 1561f54f..d50bdf85 100644 --- a/src/pages/Observers/Banner.tsx +++ b/src/pages/Observers/Banner.tsx @@ -26,12 +26,13 @@ const InfoSection = ({ label, value }: { label: string; value: string }) => { const Banner = () => { const walletAddress = useGlobalState((state) => state.walletAddress); + const currentEpoch = useGlobalState((state) => state.currentEpoch); const [loginOpen, setLoginOpen] = useState(false); const [startGatewayOpen, setStartGatewayOpen] = useState(false); - const { data: observers } = useObservers(); - const { data: observations } = useObservations(); + const { data: observers } = useObservers(currentEpoch); + const { data: observations } = useObservations(currentEpoch); const { gateway, gatewayStatus } = useGatewayInfo(); diff --git a/src/pages/Observers/ObserversTable.tsx b/src/pages/Observers/ObserversTable.tsx index 44683a5d..17bed7cc 100644 --- a/src/pages/Observers/ObserversTable.tsx +++ b/src/pages/Observers/ObserversTable.tsx @@ -1,5 +1,7 @@ import AddressCell from '@src/components/AddressCell'; +import Dropdown from '@src/components/Dropdown'; import TableView from '@src/components/TableView'; +import useEpochs from '@src/hooks/useEpochs'; import useGateways from '@src/hooks/useGateways'; import useObservations from '@src/hooks/useObservations'; import useObservers from '@src/hooks/useObservers'; @@ -24,10 +26,16 @@ const columnHelper = createColumnHelper(); const ObserversTable = () => { const navigate = useNavigate(); - const { isLoading, data: observers } = useObservers(); - const { isLoading: gatewaysLoading, data: gateways } = useGateways(); + const { data: epochs } = useEpochs(); + const [selectedEpochIndex, setSelectedEpochIndex] = useState(0); + + const selectedEpoch = epochs?.[selectedEpochIndex]; + + const { isLoading, data: observers } = useObservers(selectedEpoch); const { isLoading: observationsLoading, data: observations } = - useObservations(); + useObservations(selectedEpoch); + const { isLoading: gatewaysLoading, data: gateways } = useGateways(); + const [observersTableData, setObserversTableData] = useState< Array >([]); @@ -42,7 +50,11 @@ const ObserversTable = () => { const gateway = gateways[observer.gatewayAddress]; const submitted = observations.reports[observer.observerAddress]; - const status = submitted ? 'Submitted' : 'Pending'; + const status = submitted + ? 'Submitted' + : selectedEpochIndex == 0 + ? 'Pending' + : 'Did not report'; const numFailedGatewaysFound = submitted ? Object.values(observations.failureSummaries).reduce( (acc, summary) => { @@ -129,7 +141,8 @@ const ObserversTable = () => { }), columnHelper.accessor('reportStatus', { id: 'reportStatus', - header: 'Current Report Status', + header: + selectedEpochIndex == 0 ? 'Current Report Status' : 'Report Status', sortDescFirst: true, }), @@ -137,14 +150,29 @@ const ObserversTable = () => { id: 'failedGateways', header: 'Failed Gateways', sortDescFirst: true, - cell: ({ row }) => row.original.failedGateways || 'Pending', + cell: ({ row }) => + row.original.failedGateways || + (selectedEpochIndex == 0 ? 'Pending' : 'N/A'), }), ]; return (
-
-
Observers
+
+
Observers
+ ({ + label: + index == 0 ? 'Current Epoch' : `Epoch ${epoch?.epochIndex}`, + value: index.toString(), + })) || [] + } + onChange={(e) => { + setSelectedEpochIndex(Number(e.target.value)); + }} + value={selectedEpochIndex.toString()} + />
Date: Sat, 23 Nov 2024 09:12:59 -0500 Subject: [PATCH 15/22] fix: missing dependency for useEffect --- src/pages/Observers/ObserversTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Observers/ObserversTable.tsx b/src/pages/Observers/ObserversTable.tsx index 17bed7cc..496d9184 100644 --- a/src/pages/Observers/ObserversTable.tsx +++ b/src/pages/Observers/ObserversTable.tsx @@ -84,7 +84,7 @@ const ObserversTable = () => { [] as Array, ); setObserversTableData(observersTableData); - }, [observers, gateways, observations]); + }, [observers, gateways, observations, selectedEpochIndex]); // Define columns for the table const columns: ColumnDef[] = [ From 54cfff2d56cbd663ca9831a33a461421a3765bd9 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 25 Nov 2024 13:44:39 -0500 Subject: [PATCH 16/22] feat: display primary name for user's wallet if available --- src/components/Profile.tsx | 38 ++++++++++++++++++++++++++++++++------ src/utils/index.ts | 21 ++++++++++++++++----- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index 9d7a8698..8684717a 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -1,9 +1,14 @@ /* eslint-disable tailwindcss/migration-from-tailwind-2 */ +import { AoPrimaryName } from '@ar.io/sdk/web'; import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'; import useBalances from '@src/hooks/useBalances'; import { useGlobalState } from '@src/store'; -import { formatBalance, formatWalletAddress } from '@src/utils'; -import { forwardRef, ReactElement, useState } from 'react'; +import { + formatBalance, + formatPrimaryName, + formatWalletAddress, +} from '@src/utils'; +import { forwardRef, ReactElement, useEffect, useState } from 'react'; import Button, { ButtonType } from './Button'; import CopyButton from './CopyButton'; import Placeholder from './Placeholder'; @@ -35,22 +40,43 @@ const CustomPopoverButton = forwardRef< }); const Profile = () => { - const [isModalOpen, setIsModalOpen] = useState(false); - const walletStateInitialized = useGlobalState( (state) => state.walletStateInitialized, ); - const wallet = useGlobalState((state) => state.wallet); + const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); const updateWallet = useGlobalState((state) => state.updateWallet); const walletAddress = useGlobalState((state) => state.walletAddress); const { data: balances } = useBalances(walletAddress); const ticker = useGlobalState((state) => state.ticker); + const [isModalOpen, setIsModalOpen] = useState(false); + const [primaryName, setPrimaryName] = useState(); + + useEffect(() => { + const update = async () => { + if (walletAddress && arIOReadSDK) { + try { + const primaryName = await arIOReadSDK.getPrimaryName({ + address: walletAddress.toString(), + }); + setPrimaryName(primaryName); + console.log('Primary name found:', primaryName); + } catch (e) { + console.log('name not found'); + setPrimaryName(undefined); + } + } + }; + update(); + }, [walletAddress, arIOReadSDK]); + return walletAddress ? ( - {formatWalletAddress(walletAddress.toString())} + {primaryName + ? formatPrimaryName(primaryName.name) + : formatWalletAddress(walletAddress.toString())} diff --git a/src/utils/index.ts b/src/utils/index.ts index a755c969..58bd72a5 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -56,6 +56,14 @@ export const formatAddress = (address: string) => { )}`; }; +/** Format primary names to truncate at end if length > than maxChars */ +export const formatPrimaryName = (name: string, maxChars = 20) => { + if (name.length < maxChars) { + return name; + } + return name.slice(0, maxChars) + '...'; +}; + /** Convert Date object to format of YYYY-MM-DD */ export function formatDate(date: Date): string { const year = date.getFullYear(); @@ -97,8 +105,11 @@ export const arrayBufferToBase64Url = (arrayBuffer: ArrayBuffer) => { return base64ToBase64url(base64); }; -export const fetchWithTimeout = async (resource:string, options?:RequestInit, timeout?:number) => { - +export const fetchWithTimeout = async ( + resource: string, + options?: RequestInit, + timeout?: number, +) => { const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout ?? 10_000); @@ -106,11 +117,11 @@ export const fetchWithTimeout = async (resource:string, options?:RequestInit, ti ...options, headers: { ...options?.headers, - 'Accept-Encoding': 'identity' + 'Accept-Encoding': 'identity', }, - signal: controller.signal + signal: controller.signal, }); clearTimeout(id); return response; -} +}; From e125cea541df7565e944ae99d35a33bf3c5400ff Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Mon, 25 Nov 2024 15:36:53 -0500 Subject: [PATCH 17/22] feat: added links to reports and made text link to gateway in Reported On By card --- CHANGELOG.md | 3 + src/components/Profile.tsx | 2 - src/pages/Gateway/SnitchRow.tsx | 98 ++++++++++++++++++++++++++------- 3 files changed, 81 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a26e274..e0fa740b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Download buttons added to Reports page and individual Report page * Observers: Added epoch selector to view prescribed observers for previous epochs +* Gateway Details Page + * Reported On By card: text links to gateway for observer, report button links to report + * Reported On card: Report button shows in header that links to that report's page ### Updated diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index 8684717a..c62c913a 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -61,9 +61,7 @@ const Profile = () => { address: walletAddress.toString(), }); setPrimaryName(primaryName); - console.log('Primary name found:', primaryName); } catch (e) { - console.log('name not found'); setPrimaryName(undefined); } } diff --git a/src/pages/Gateway/SnitchRow.tsx b/src/pages/Gateway/SnitchRow.tsx index ab3d85f3..ca0fd34c 100644 --- a/src/pages/Gateway/SnitchRow.tsx +++ b/src/pages/Gateway/SnitchRow.tsx @@ -1,17 +1,26 @@ import { AoGatewayWithAddress } from '@ar.io/sdk/web'; +import Button from '@src/components/Button'; import Dropdown from '@src/components/Dropdown'; -import { StatsArrowIcon } from '@src/components/icons'; +import { ReportsIcon, StatsArrowIcon } from '@src/components/icons'; import Placeholder from '@src/components/Placeholder'; import useEpochs from '@src/hooks/useEpochs'; import useObserverToGatewayMap from '@src/hooks/useObserverToGatewayMap'; import { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; + +type ReportedOnByEntry = { + observerId: string; + reportId?: string; +}; const ReportedOnByCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { const { data: epochs } = useEpochs(); const [selectedEpochIndex, setSelectedEpochIndex] = useState(0); - const [failureObservers, setFailureObservers] = useState([]); + const [failureObservers, setFailureObservers] = useState( + [], + ); const observerToGatewayMap = useObserverToGatewayMap(); + const navigate = useNavigate(); useEffect(() => { if (epochs) { @@ -22,7 +31,13 @@ const ReportedOnByCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { selectedEpoch?.observations.failureSummaries[ gateway.gatewayAddress ] || []; - setFailureObservers(observers); + const entries = observers.map((observerId) => { + return { + observerId, + reportId: selectedEpoch.observations.reports[observerId], + }; + }); + setFailureObservers(entries); } else { setFailureObservers([]); } @@ -68,19 +83,38 @@ const ReportedOnByCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { )}
- {failureObservers?.map((observer) => ( + {failureObservers?.map((entry) => (
-
+
{observerToGatewayMap && epochs ? ( - - {observer} - + <> + + {entry.observerId} + + + {entry.reportId && ( + + )} + ) : ( )} @@ -96,9 +130,12 @@ const ReportedOnCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { const { data: epochs } = useEpochs(); const [selectedEpochIndex, setSelectedEpochIndex] = useState(0); const [snitchedOn, setSnitchedOn] = useState([]); + const [reportId, setReportId] = useState(); const [selectedForObservation, setSelectedForObservation] = useState(); + const navigate = useNavigate(); + useEffect(() => { if (epochs) { const selectedEpoch = epochs[selectedEpochIndex]; @@ -106,6 +143,8 @@ const ReportedOnCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { if (gateway && selectedEpoch) { const address = gateway.observerAddress; + setReportId(selectedEpoch.observations.reports[address]); + setSelectedForObservation( selectedEpoch.prescribedObservers?.find( (obs) => obs.observerAddress == address, @@ -133,20 +172,39 @@ const ReportedOnCard = ({ gateway }: { gateway?: AoGatewayWithAddress }) => { return (
-
+
{epochs ? ( <> -
+
{selectedForObservation ? ( -
- Reported on{' '} - {snitchedOn.length}{' '} - gateways -
+ <> +
+ Reported on{' '} + {snitchedOn.length}{' '} + gateways +
+ ) : (
Not Selected for Observation
)}
+
+ {reportId && ( + + )} +
({ From ae6172a8bae2df17d0e0954fe409e00d5bc6bc07 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Tue, 26 Nov 2024 13:44:21 -0500 Subject: [PATCH 18/22] refactor: introduce hook for primary names --- src/components/Profile.tsx | 23 +++-------------------- src/hooks/usePrimaryName.ts | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 20 deletions(-) create mode 100644 src/hooks/usePrimaryName.ts diff --git a/src/components/Profile.tsx b/src/components/Profile.tsx index c62c913a..ae3c5cb6 100644 --- a/src/components/Profile.tsx +++ b/src/components/Profile.tsx @@ -1,14 +1,14 @@ /* eslint-disable tailwindcss/migration-from-tailwind-2 */ -import { AoPrimaryName } from '@ar.io/sdk/web'; import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'; import useBalances from '@src/hooks/useBalances'; +import usePrimaryName from '@src/hooks/usePrimaryName'; import { useGlobalState } from '@src/store'; import { formatBalance, formatPrimaryName, formatWalletAddress, } from '@src/utils'; -import { forwardRef, ReactElement, useEffect, useState } from 'react'; +import { forwardRef, ReactElement, useState } from 'react'; import Button, { ButtonType } from './Button'; import CopyButton from './CopyButton'; import Placeholder from './Placeholder'; @@ -44,30 +44,13 @@ const Profile = () => { (state) => state.walletStateInitialized, ); const wallet = useGlobalState((state) => state.wallet); - const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); const updateWallet = useGlobalState((state) => state.updateWallet); const walletAddress = useGlobalState((state) => state.walletAddress); const { data: balances } = useBalances(walletAddress); const ticker = useGlobalState((state) => state.ticker); const [isModalOpen, setIsModalOpen] = useState(false); - const [primaryName, setPrimaryName] = useState(); - - useEffect(() => { - const update = async () => { - if (walletAddress && arIOReadSDK) { - try { - const primaryName = await arIOReadSDK.getPrimaryName({ - address: walletAddress.toString(), - }); - setPrimaryName(primaryName); - } catch (e) { - setPrimaryName(undefined); - } - } - }; - update(); - }, [walletAddress, arIOReadSDK]); + const { data: primaryName } = usePrimaryName(walletAddress?.toString()); return walletAddress ? ( diff --git a/src/hooks/usePrimaryName.ts b/src/hooks/usePrimaryName.ts new file mode 100644 index 00000000..46f5a99c --- /dev/null +++ b/src/hooks/usePrimaryName.ts @@ -0,0 +1,23 @@ +import { useGlobalState } from '@src/store'; +import { useQuery } from '@tanstack/react-query'; + +const usePrimaryName = (walletAddress?: string) => { + const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); + + const res = useQuery({ + queryKey: ['primaryName', walletAddress], + queryFn: async () => { + if (!walletAddress || !arIOReadSDK) { + throw new Error('Wallet Address or SDK not available'); + } + const primaryName = await arIOReadSDK.getPrimaryName({ + address: walletAddress.toString(), + }); + return primaryName; + }, + }); + + return res; +}; + +export default usePrimaryName; From a8cd8d8a1930c6b1172f2878f47fc57f7c075226 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Wed, 4 Dec 2024 10:51:03 -0500 Subject: [PATCH 19/22] fix: reorder keys so that invalidateQuery will match elsewhere in code --- src/hooks/useGateway.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useGateway.ts b/src/hooks/useGateway.ts index a04979f2..5470c757 100644 --- a/src/hooks/useGateway.ts +++ b/src/hooks/useGateway.ts @@ -10,7 +10,7 @@ const useGateway = ({ const arIOReadSDK = useGlobalState((state) => state.arIOReadSDK); const queryResults = useQuery({ - queryKey: ['gateway', arIOReadSDK, ownerWalletAddress || ''], + queryKey: ['gateway', ownerWalletAddress || '', arIOReadSDK], queryFn: () => { if (ownerWalletAddress === undefined) { return Promise.reject( From 5be843a75fcdfe776b953735fa195f09bf2f3947 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Wed, 4 Dec 2024 10:51:37 -0500 Subject: [PATCH 20/22] feat: update to cap at 95% for reward share ratio when joining and modifying gateway settings --- src/components/modals/StartGatewayModal.tsx | 4 ++-- src/pages/Gateway/index.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/modals/StartGatewayModal.tsx b/src/components/modals/StartGatewayModal.tsx index 4e1be238..179d2d0d 100644 --- a/src/components/modals/StartGatewayModal.tsx +++ b/src/components/modals/StartGatewayModal.tsx @@ -112,9 +112,9 @@ const StartGatewayModal = ({ onClose }: { onClose: () => void }) => { label: 'Reward Share Ratio:', enabled: allowDelegatedStaking, placeholder: allowDelegatedStaking - ? 'Enter value 0-100' + ? 'Enter value 0-95' : 'Enable Delegated Staking to set this value.', - validateProperty: validateNumberRange('Reward Share Ratio', 0, 100), + validateProperty: validateNumberRange('Reward Share Ratio', 0, 95), }, { formPropertyName: 'note', diff --git a/src/pages/Gateway/index.tsx b/src/pages/Gateway/index.tsx index 027a172e..3052f157 100644 --- a/src/pages/Gateway/index.tsx +++ b/src/pages/Gateway/index.tsx @@ -257,9 +257,9 @@ const Gateway = () => { rowType: RowType.TOP, enabled: delegatedStakingEnabled, placeholder: delegatedStakingEnabled - ? 'Enter value 0-100' + ? 'Enter value 0-95' : 'Enable Delegated Staking to set this value.', - validateProperty: validateNumberRange('Reward Share Ratio', 0, 100), + validateProperty: validateNumberRange('Reward Share Ratio', 0, 95), }, { formPropertyName: 'minDelegatedStake', From 846037036e8f95b6a3ad2399057b62bd76bcf9c5 Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Wed, 4 Dec 2024 10:54:26 -0500 Subject: [PATCH 21/22] chore: bump version and update release notes --- CHANGELOG.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14d097f7..8ebdfbc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.5.0] - 2024-12-04 + ### Added * Download buttons added to Reports page and individual Report page @@ -20,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Staking and Withdrawal modals updated to show Review page for user to confirm operation before processing * Withdrawal Modal: Added option for Standard and Expedited Withdrawal * Modal dialog styles refreshed +* Reward Share Ratio capped to 95% when joining network and updating gateway settings ## [1.4.3] - 2024-11-27 diff --git a/package.json b/package.json index d51b7dcc..86866c17 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@ar-io/network-portal", "private": true, - "version": "1.4.3", + "version": "1.5.0", "type": "module", "scripts": { "build": "yarn clean && tsc --build tsconfig.build.json && NODE_OPTIONS=--max-old-space-size=32768 vite build", From 2e0bb45b0f048997c6e4354d9aaa0bc4c0fe15ed Mon Sep 17 00:00:00 2001 From: Steven Yi Date: Wed, 4 Dec 2024 10:58:07 -0500 Subject: [PATCH 22/22] chore: add note about primary name --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ebdfbc3..5429a1db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Profile button shows user's ArNS Primary Name (if available) or wallet address when logged in * Download buttons added to Reports page and individual Report page * Observers: Added epoch selector to view prescribed observers for previous epochs * Gateway Details Page