diff --git a/src/components/ConfirmModal/index.tsx b/src/components/ConfirmModal/index.tsx new file mode 100644 index 0000000..862abab --- /dev/null +++ b/src/components/ConfirmModal/index.tsx @@ -0,0 +1,79 @@ +import React from 'react' + +export interface ConfirmModalProps { + title: string; + body: string; + confirmText: string; + cancelText: string; + onConfirm: () => void; + onCancel: () => void; + onClose: () => void; +} + +const ConfirmModal:React.FC = ({ title, body, confirmText, cancelText, onConfirm, onCancel, onClose }) => { + return ( +
+
+
+
+
+ +
+
+
+

{title}

+ +
+
+

{body}

+
+
+ + +
+
+
+
+
+
+
+
+ //
+ //
+ //
+ //
+ //

{title}

+ // + //
+ //
+ //

{body}

+ //
+ //
+ // + // + //
+ //
+ //
+ //
+ ) +} + +export default ConfirmModal \ No newline at end of file diff --git a/src/components/Content/index.tsx b/src/components/Content/index.tsx index 2c6b8d3..72632cd 100644 --- a/src/components/Content/index.tsx +++ b/src/components/Content/index.tsx @@ -8,6 +8,16 @@ import GLOW_IMAGE from 'assets/images/glow-bottom.svg' const glowImage = GLOW_IMAGE +import { + useShow, + useShowWithdrawModal, + useShowClaimModal, + useHideModal, + useContent, + useIsClaim, + //useContent, +} from 'state/confirm/hooks' + import { useRegisterUser, useClaimRewards, @@ -24,6 +34,7 @@ import { useUserRegistrationStatus, useUserType, useUserCollateralAmount, + useTotalSeconds, } from 'state/user/hooks' import { @@ -37,6 +48,8 @@ import { WITHDRAWAL_DELAY } from '../../constants' import StatsTile, { StatsTileProps } from './StatsTile' import { ParticleAnimation } from 'utils/particles' +import ConfirmModal from 'components/ConfirmModal' +import CountdownTimer from 'components/CountdownTimer' const Content = () => { const { pending: pendingRegisterUser, registerUser } = useRegisterUser() @@ -48,6 +61,7 @@ const Content = () => { const totalCollateralAmount = useTotalCollateralAmount() const totalBlockShares = useTotalBlockShares() const totalRegistrations = useTotalRegistrations() + const totalSeconds = useTotalSeconds() const userType = useUserType() const userBalance = useUserBalance() @@ -58,22 +72,41 @@ const Content = () => { const userRegistrationStatus = useUserRegistrationStatus() const userCollateralAmount = useUserCollateralAmount() + //const userConfirmed = useConfirmed() + const showModal = useShow() + const confirmMessage = useContent() + const isClaim = useIsClaim() + + const hideModal = useHideModal() + + const showClaimConfirmation = useShowClaimModal() + + const showWithdrawConfirmation = useShowWithdrawModal() + const renderAction = useCallback(() => { if (userRegistrationStatus === RegistrationStatus.UNREGISTERED) { const isDisabled = pendingRegisterUser || userBalance.lt(userCollateralAmount) return ( - + <> + + + ) } else if (userRegistrationStatus === RegistrationStatus.REGISTERED) { const isClaimDisabled = userRewards.eq(0) || pendingClaimRewards || pendingStartWithdrawal @@ -88,7 +121,7 @@ const Content = () => { : 'cursor-pointer bg-purple-800 text-white' }`} disabled={isClaimDisabled} - onClick={isClaimDisabled ? undefined : claimRewards} + onClick={isClaimDisabled ? undefined : showClaimConfirmation} > Claim rewards @@ -99,7 +132,7 @@ const Content = () => { : 'cursor-pointer bg-purple-800 text-white' }`} disabled={isStartDisabled} - onClick={isStartDisabled ? undefined : startWithdrawal} + onClick={isStartDisabled ? undefined : showWithdrawConfirmation} > Start withdrawal @@ -108,9 +141,22 @@ const Content = () => { } else if (userRegistrationStatus === RegistrationStatus.WITHDRAWING) { const disabled = pendingCompleteWithdrawal || userSinceLastClaim < WITHDRAWAL_DELAY return ( - + <> + +
+ {totalSeconds === 0 ? Calculating time... : } +
+ ) } @@ -253,8 +299,32 @@ const Content = () => { + {showModal && ( + { + hideModal().then(() => { + if (isClaim) { + claimRewards() + } else { + startWithdrawal() + } + }) + }} + onCancel={() => { + hideModal() + }} + onClose={() => { + hideModal() + }} + /> + )} ) } export default Content + diff --git a/src/components/CountdownTimer/index.tsx b/src/components/CountdownTimer/index.tsx new file mode 100644 index 0000000..8ad4d6a --- /dev/null +++ b/src/components/CountdownTimer/index.tsx @@ -0,0 +1,36 @@ +import { useState, useEffect } from 'react' + +const CountdownTimer = ({ totalSeconds }: { totalSeconds: number }) => { + const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(totalSeconds)) + + useEffect(() => { + let timer: NodeJS.Timeout + + if (totalSeconds >= 0) { + timer = + setInterval(() => { + setTimeLeft(calculateTimeLeft(totalSeconds - 1)) + totalSeconds-- + }, 1000) + } + + return () => clearInterval(timer) + }, [totalSeconds]) + + function calculateTimeLeft(seconds: number) { + return { + days: Math.floor(seconds / (60 * 60 * 24)), + hours: Math.floor((seconds / (60 * 60)) % 24), + minutes: Math.floor((seconds / 60) % 60), + seconds: seconds % 60, + } + } + + return ( +
+ Time to completion: {timeLeft.days}d {timeLeft.hours}h {timeLeft.minutes}m {timeLeft.seconds}s +
+ ) +} + +export default CountdownTimer diff --git a/src/components/Footer/index.tsx b/src/components/Footer/index.tsx index 9884c50..ac3d3fd 100644 --- a/src/components/Footer/index.tsx +++ b/src/components/Footer/index.tsx @@ -23,34 +23,25 @@ const Footer = () => ( diff --git a/src/state/confirm/hooks.ts b/src/state/confirm/hooks.ts new file mode 100644 index 0000000..8852560 --- /dev/null +++ b/src/state/confirm/hooks.ts @@ -0,0 +1,51 @@ +import { formatEther } from 'ethers/lib/utils' +import { useAppSelector, useAppDispatch } from 'state' +import { useCallback } from 'react' +import { setHide, setShow } from './reducer' +import { useUserRewards } from 'state/user/hooks' + +export function useShow() { + return useAppSelector(state => state.confirm.showModal) +} + +export function useConfirmed() { + return useAppSelector(state => state.confirm.confirmed) +} + +export function useContent() { + return useAppSelector(state => state.confirm.text) +} + +export function useIsClaim() { + return useAppSelector(state => state.confirm.isClaim) +} + +export function useShowWithdrawModal() { + const dispatch = useAppDispatch() + + return useCallback(async () => { + dispatch(setShow({ isClaim: false, text: 'You are attempting to withdraw your collateral. This will have a mandatory colling off period of approximately 2 weeks. Are you sure you want to continue?' })) + }, []) +} + +export function useShowClaimModal() { + const dispatch = useAppDispatch() + const rewards = useUserRewards() + const strax = financial(formatEther(rewards)) + return useCallback(async () => { + dispatch(setShow({ isClaim: true, text: `You are attempting to claim ${strax} STRAX. Once confirmed, you will receive a prompt in your MetaMask wallet. Would you plike to proceed?` })) + }, []) +} + +export function useHideModal() { + const dispatch = useAppDispatch() + + return useCallback(async () => { + dispatch(setHide()) + }, []) +} + +const financial = (x: string) => { + return Number.parseFloat(x).toFixed(5) +} + diff --git a/src/state/confirm/reducer.ts b/src/state/confirm/reducer.ts new file mode 100644 index 0000000..a24fd77 --- /dev/null +++ b/src/state/confirm/reducer.ts @@ -0,0 +1,52 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { persistReducer } from 'redux-persist' +import storage from 'redux-persist/lib/storage' + +export interface ConfirmState { + showModal: boolean + confirmed: boolean, + text: string, + isClaim: boolean, +} + +export const initialState: ConfirmState = { + showModal: false, + confirmed: false, + text: '', + isClaim: false, +} + +const confirmSlice = createSlice({ + name: 'confirm', + initialState, + reducers: { + setShow(state, action: PayloadAction<{ isClaim: boolean, text: string}>) { + state.showModal = true + state.text = action.payload.text + state.isClaim = action.payload.isClaim + }, + setHide(state) { + state.showModal = false + }, + setContent(state, action: PayloadAction) { + state.text = action.payload + }, + setResponse(state, action: PayloadAction) { + state.showModal = false + state.confirmed = action.payload + }, + }, +}) + +export const { + setShow, + setResponse, + setContent, + setHide, +} = confirmSlice.actions + +export default persistReducer({ + key: 'confirm', + storage, +}, confirmSlice.reducer) + diff --git a/src/state/index.ts b/src/state/index.ts index f6af086..914e6df 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -14,6 +14,7 @@ import wallet from './wallet/reducer' import network from './network/reducer' import stats from './stats/reducer' import user from './user/reducer' +import confirm from './confirm/reducer' const store = configureStore({ reducer: { @@ -21,6 +22,7 @@ const store = configureStore({ network, stats, user, + confirm, }, middleware: getDefaultMiddleware => getDefaultMiddleware({ diff --git a/src/state/user/hooks.ts b/src/state/user/hooks.ts index fef0d4d..69d2f8a 100644 --- a/src/state/user/hooks.ts +++ b/src/state/user/hooks.ts @@ -19,6 +19,7 @@ import { setLastClaimedBlock, setSinceLastClaim, setBlockShares, + setTotalSeconds, } from './reducer' export function useUpdateBalance() { @@ -114,6 +115,35 @@ export function useUpdateBlockShares() { }, [account, contract]) } +export function useUpdateTotalSeconds() { + const { account } = useWeb3React() + const contract = useMasterNodeContract() + const dispatch = useAppDispatch() + const offset = 100800 + const blockTime = 16 + + return useCallback(async () => { + + if (!contract || !account) { + dispatch(setTotalSeconds(0)) + return + } + + const registrationStatus = await contract.registrationStatus(account) + + if (registrationStatus !== RegistrationStatus.WITHDRAWING) { + dispatch(setTotalSeconds(0)) + return + } + + const lastClaimedBlock = await contract.lastClaimedBlock(account) + const blockNumber = await contract.provider.getBlockNumber() + const totalSeconds = (blockNumber + offset - lastClaimedBlock.toNumber()) * blockTime + + dispatch(setTotalSeconds(totalSeconds)) + }, [contract, dispatch]) +} + export function useUpdateType() { const { account } = useWeb3React() const contract = useMasterNodeContract() @@ -169,6 +199,10 @@ export function useUserType() { return useAppSelector(state => state.user.type) } +export function useTotalSeconds() { + return useAppSelector(state => state.user.totalSeconds) +} + export function useUserCollateralAmount() { const type = useUserType() if (type === UserType.LEGACY_10K) { diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts index 1161a95..dda1b94 100644 --- a/src/state/user/reducer.ts +++ b/src/state/user/reducer.ts @@ -9,6 +9,7 @@ export interface UserState { lastClaimedBlock: number blockShares: number sinceLastClaim: number + totalSeconds: number } export const initialState: UserState = { @@ -19,6 +20,7 @@ export const initialState: UserState = { lastClaimedBlock: 0, blockShares: 0, sinceLastClaim: 0, + totalSeconds: 0, } const userSlice = createSlice({ @@ -46,6 +48,9 @@ const userSlice = createSlice({ setBlockShares(state, action: PayloadAction) { state.blockShares = action.payload }, + setTotalSeconds(state, action: PayloadAction) { + state.totalSeconds = action.payload + }, }, }) @@ -57,6 +62,7 @@ export const { setBlockShares, setLastClaimedBlock, setSinceLastClaim, + setTotalSeconds, } = userSlice.actions export default userSlice.reducer diff --git a/src/state/user/updater.tsx b/src/state/user/updater.tsx index 19af5bc..2683cb8 100644 --- a/src/state/user/updater.tsx +++ b/src/state/user/updater.tsx @@ -9,6 +9,7 @@ import { useUpdateLastClaimedBlock, useUpdateRegistrationStatus, useUpdateType, + useUpdateTotalSeconds, } from './hooks' export default function Updater() { @@ -18,6 +19,7 @@ export default function Updater() { const updateLastClaimedBlock = useUpdateLastClaimedBlock() const updateRegistrationStatus = useUpdateRegistrationStatus() const updateType = useUpdateType() + const updateTotalSeconds = useUpdateTotalSeconds() const updateData = useCallback(async () => { updateBalance() @@ -26,6 +28,7 @@ export default function Updater() { updateLastClaimedBlock() updateRegistrationStatus() updateType() + updateTotalSeconds() }, [ updateBalance, updateRewards, @@ -33,6 +36,7 @@ export default function Updater() { updateLastClaimedBlock, updateRegistrationStatus, updateType, + updateTotalSeconds, ]) useInterval(updateData, 3000)