Skip to content

Commit

Permalink
Limit NFTL multi-claim to only Degens with claimable balance
Browse files Browse the repository at this point in the history
  • Loading branch information
NiftyAndy committed Oct 2, 2024
1 parent b2e44e4 commit 690eb41
Show file tree
Hide file tree
Showing 11 changed files with 191 additions and 156 deletions.
Original file line number Diff line number Diff line change
@@ -1,64 +1,37 @@
'use client';

import { useState, useCallback, useEffect } from 'react';
import Image from 'next/image';
import { Button, IconButton } from '@mui/material';
import { useTheme } from '@nl/theme';
import Image from 'next/image';

import { NFTL_CONTRACT } from '@/constants/contracts';
import { DEBUG } from '@/constants/index';
import { formatNumberToDisplay } from '@/utils/numbers';
import useNFTsBalances from '@/hooks/balances/useNFTsBalances';
import useTokensBalances from '@/hooks/balances/useTokensBalances';
import useNetworkContext from '@/hooks/useNetworkContext';
import HoverDataCard from '@/components/cards/HoverDataCard';
import useClaimNFTL from '@/hooks/writeContracts/useClaimNFTL';
import useNetworkContext from '@/hooks/useNetworkContext';

const DegenBalance = (): JSX.Element => {
const theme = useTheme();
const { writeContracts, tx } = useNetworkContext();
const { degenTokenIndices } = useNFTsBalances();
const { loadingNFTLAccrued, refreshClaimableNFTL, totalAccruedNFTL } = useTokensBalances();
const [mockAccrued, setMockAccrued] = useState(0);

useEffect(() => {
if (totalAccruedNFTL) setMockAccrued(totalAccruedNFTL);
}, [totalAccruedNFTL]);

const handleClaimNFTL = useCallback(async () => {
// eslint-disable-next-line no-console
if (DEBUG) console.log('claim', degenTokenIndices, totalAccruedNFTL);
const nftl = writeContracts[NFTL_CONTRACT];
const res = await tx(nftl.claim(degenTokenIndices));
if (res) {
setMockAccrued(0);
setTimeout(refreshClaimableNFTL, 5000);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [degenTokenIndices, totalAccruedNFTL, tx, writeContracts]);
const { isConnected } = useNetworkContext();
const { balance, claimCallback, loading } = useClaimNFTL();

return (
<HoverDataCard
title="DEGEN Balance"
primary={`${mockAccrued ? formatNumberToDisplay(mockAccrued) : '0.00'} NFTL`}
primary={`${balance ? formatNumberToDisplay(balance) : '0.00'} NFTL`}
customStyle={{
backgroundColor: theme.palette.background.default,
border: '1px solid',
borderColor: theme.palette.border,
position: 'relative',
}}
secondary="Available to Claim"
isLoading={loadingNFTLAccrued}
isLoading={loading}
actions={
<>
<IconButton disabled color="primary" component="span" sx={{ position: 'absolute', top: -2, right: -2 }}>
<Image src="/icons/eth.svg" alt="Ethereum" width={22} height={22} />
</IconButton>
<Button
fullWidth
variant="contained"
disabled={!(mockAccrued > 0.0 && writeContracts[NFTL_CONTRACT])}
onClick={handleClaimNFTL}
>
<Button fullWidth variant="contained" disabled={!(balance > 0.0 && isConnected)} onClick={claimCallback}>
Claim NFTL
</Button>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import { useTheme } from '@nl/theme';

import { formatNumberToDisplay } from '@/utils/numbers';
import useUserUnclaimedAmount from '@/hooks/merkleDistributor/useUserUnclaimedAmount';
import WithdrawButtonDialog from '@/components/dialog/WithdrawButtonDialog';
import HoverDataCard from '@/components/cards/HoverDataCard';
import WithdrawButtonDialog from './dialogs/WithdrawButtonDialog';

const GameBalance: React.FC = memo(() => {
const theme = useTheme();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { useTheme } from '@nl/theme';

import { formatNumberToDisplay } from '@/utils/numbers';
import useTokensBalances from '@/hooks/balances/useTokensBalances';
import BridgeButtonDialog from '@/components/dialog/BridgeButtonDialog';
import HoverDataCard from '@/components/cards/HoverDataCard';
import { GOVERNANCE_PORTAL_URL, SNAPSHOT_PORTAL_URL } from '@/constants/url';
import BridgeButtonDialog from './dialogs/BridgeButtonDialog';

const WalletBalances = (): JSX.Element => {
const theme = useTheme();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,36 @@
'use client';

import { useCallback, useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useEnsAvatar, useEnsName } from 'wagmi';
import { normalize } from 'viem/ens';
import { Avatar, Box, Button, Skeleton, Stack, Typography } from '@mui/material';
import { useTheme } from '@nl/theme';

import { DEBUG } from '@/constants/index';
import { formatNumberToDisplay } from '@/utils/numbers';
import { NFTL_CONTRACT } from '@/constants/contracts';
import { sendUserId } from '@/utils/google-analytics';
import { useGamerProfile } from '@/hooks/useGamerProfile';
import type { ProfileAvatar } from '@/types/account';
import useNFTsBalances from '@/hooks/balances/useNFTsBalances';
import useTokensBalances from '@/hooks/balances/useTokensBalances';
import ConnectWrapper from '@/components/wrapper/ConnectWrapper';
import useNetworkContext from '@/hooks/useNetworkContext';
import useClaimNFTL from '@/hooks/writeContracts/useClaimNFTL';
import useAuth from '@/hooks/useAuth';

const ClaimNFTLView = () => {
const { writeContracts, tx } = useNetworkContext();
const { degenTokenIndices } = useNFTsBalances();
const { totalAccruedNFTL, loadingNFTLAccrued, refreshClaimableNFTL } = useTokensBalances();
const [mockAccumulated, setMockAccumulated] = useState(0);

useEffect(() => {
if (totalAccruedNFTL) setMockAccumulated(totalAccruedNFTL);
}, [totalAccruedNFTL]);

const handleClaimNFTL = useCallback(async () => {
// eslint-disable-next-line no-console
if (DEBUG) console.log('CLAIM NFTL', degenTokenIndices, totalAccruedNFTL);
await tx(writeContracts[NFTL_CONTRACT].claim(degenTokenIndices));
setMockAccumulated(0);
setTimeout(refreshClaimableNFTL, 5000);
}, [refreshClaimableNFTL, degenTokenIndices, totalAccruedNFTL, tx, writeContracts]);
const { isConnected } = useNetworkContext();
const { balance, claimCallback, loading } = useClaimNFTL();

return (
<>
<Stack direction="column" marginY={2} sx={{ alignItems: 'center' }}>
{loadingNFTLAccrued ? (
{loading ? (
<Skeleton variant="text" animation="wave" width={80} />
) : (
<Typography fontWeight="bold">
{mockAccumulated ? formatNumberToDisplay(mockAccumulated) : '0.00'} NFTL
</Typography>
<Typography fontWeight="bold">{balance ? formatNumberToDisplay(balance) : '0.00'} NFTL</Typography>
)}
<Typography>Available to Claim</Typography>
</Stack>

<Button
variant="contained"
fullWidth
disabled={!(mockAccumulated > 0.0 && writeContracts[NFTL_CONTRACT])}
onClick={handleClaimNFTL}
>
<Button variant="contained" fullWidth disabled={!(balance > 0.0 && isConnected)} onClick={claimCallback}>
Claim NFTL
</Button>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { parseEther } from 'ethers6';
import {
Alert,
Box,
Button,
Checkbox,
FormControlLabel,
FormGroup,
Expand All @@ -32,9 +31,8 @@ import useLocalStorageContext from '@/hooks/useLocalStorageContext';
import useNetworkContext from '@/hooks/useNetworkContext';
import useNFTLAllowance from '@/hooks/useNFTLAllowance';

import { Dialog, DialogContent, DialogContext, DialogTrigger } from '@/components/dialog';
import { DialogContext } from '@/components/dialog';
import TermsOfServiceDialog from '@/components/dialog/TermsOfServiceDialog';
import BridgeSuccess from './BridgeSuccess';

type BridgeFormProps = { balance: number; onBridgeSuccess: () => void };
type IFormInput = { amountSelected: number; amountInput: string; isCheckedTerm: boolean };
Expand Down Expand Up @@ -300,37 +298,4 @@ export const BridgeForm = ({ balance, onBridgeSuccess }: BridgeFormProps): JSX.E
);
};

type BridgeButtonDialogProps = { balance: number; loading: boolean };

const BridgeButtonDialog = ({ balance, loading }: BridgeButtonDialogProps) => {
const [successDialogOpen, setSuccessDialogOpen] = useState(false);

const onCloseBridgeDialog = () => {}; // handle actions if needed

const onBridgeSuccess = () => setSuccessDialogOpen(true);

return (
<>
<Dialog onClose={onCloseBridgeDialog}>
<DialogTrigger>
<Button fullWidth variant="contained" disabled={loading || balance < 0.5}>
Bridge
</Button>
</DialogTrigger>
<DialogContent
aria-labelledby="bridge-nftl-dialog"
dialogTitle="Bridge NFTL to Immutable"
sx={{
'& h2': { textAlign: 'center' },
'& .MuiDialogContent-root': { textAlign: 'center' },
}}
>
<BridgeForm balance={balance} onBridgeSuccess={onBridgeSuccess} />
</DialogContent>
</Dialog>
<BridgeSuccess successDialogOpen={successDialogOpen} setSuccessDialogOpen={setSuccessDialogOpen} />
</>
);
};

export default BridgeButtonDialog;
export default BridgeForm;
42 changes: 42 additions & 0 deletions apps/app/src/components/dialog/BridgeButtonDialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client';

import { useState } from 'react';
import { Button } from '@mui/material';
import { Dialog, DialogContent, DialogTrigger } from '@/components/dialog';
import BridgeForm from './BridgeForm';
import BridgeSuccess from './BridgeSuccess';

type BridgeButtonDialogProps = { balance: number; loading: boolean };

const BridgeButtonDialog = ({ balance, loading }: BridgeButtonDialogProps) => {
const [successDialogOpen, setSuccessDialogOpen] = useState(false);

const onCloseBridgeDialog = () => {}; // handle actions if needed

const onBridgeSuccess = () => setSuccessDialogOpen(true);

return (
<>
<Dialog onClose={onCloseBridgeDialog}>
<DialogTrigger>
<Button fullWidth variant="contained" disabled={loading || balance < 0.5}>
Bridge
</Button>
</DialogTrigger>
<DialogContent
aria-labelledby="bridge-nftl-dialog"
dialogTitle="Bridge NFTL to Immutable"
sx={{
'& h2': { textAlign: 'center' },
'& .MuiDialogContent-root': { textAlign: 'center' },
}}
>
<BridgeForm balance={balance} onBridgeSuccess={onBridgeSuccess} />
</DialogContent>
</Dialog>
<BridgeSuccess successDialogOpen={successDialogOpen} setSuccessDialogOpen={setSuccessDialogOpen} />
</>
);
};

export default BridgeButtonDialog;
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useCallback, useContext, useState } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { Alert, Badge, Button, Stack, Typography } from '@mui/material';
import { Alert, Stack, Typography } from '@mui/material';
import LoadingButton from '@mui/lab/LoadingButton';
import type { TransactionResponse } from 'ethers6';
import { useSwitchChain } from 'wagmi';
Expand All @@ -12,15 +12,13 @@ import { formatNumberToDisplay } from '@/utils/numbers';
import { useConnectedToIMXCheck } from '@/hooks/useImxProvider';
import useClaimCallback from '@/hooks/merkleDistributor/useClaimCallback';
import useIMXContext from '@/hooks/useIMXContext';
import { TARGET_NETWORK } from '@/constants/networks';

import { Dialog, DialogContent, DialogContext, DialogTrigger } from '@/components/dialog';
import WithdrawSuccess from './WithdrawSuccess';
import { DialogContext } from '@/components/dialog';

type WithdrawFormProps = { balance: number; onWithdrawSuccess: () => void };
type IFormInput = { withdrawal: string };

export const WithdrawForm = ({ balance, onWithdrawSuccess }: WithdrawFormProps): JSX.Element => {
const WithdrawForm = ({ balance, onWithdrawSuccess }: WithdrawFormProps): JSX.Element => {
const { imxChainId } = useIMXContext();
const isConnectedToIMX = useConnectedToIMXCheck();
const { switchChain } = useSwitchChain();
Expand Down Expand Up @@ -99,49 +97,4 @@ export const WithdrawForm = ({ balance, onWithdrawSuccess }: WithdrawFormProps):
</form>
);
};

type WithdrawButtonDialogProps = { balance: number; loading: boolean };

const WithdrawButtonDialog = ({ balance, loading }: WithdrawButtonDialogProps) => {
const { switchChain } = useSwitchChain();
const [successDialogOpen, setSuccessDialogOpen] = useState(false);

const onCloseWithdrawDialog = () => {
switchChain?.({ chainId: TARGET_NETWORK.chainId });
};

const onWithdrawSuccess = () => setSuccessDialogOpen(true);

return (
<>
<Dialog onClose={onCloseWithdrawDialog}>
<DialogTrigger>
<Badge
color="error"
variant="standard"
badgeContent=" "
invisible={loading || balance === 0}
sx={{ width: '100%' }}
>
<Button fullWidth variant="contained" disabled={loading || balance === 0}>
Withdraw
</Button>
</Badge>
</DialogTrigger>
<DialogContent
aria-labelledby="withdraw-earnings-dialog"
dialogTitle="Withdraw Earnings"
sx={{
'& h2': { textAlign: 'center' },
'& .MuiDialogContent-root': { textAlign: 'center' },
}}
>
<WithdrawForm balance={balance} onWithdrawSuccess={onWithdrawSuccess} />
</DialogContent>
</Dialog>
<WithdrawSuccess successDialogOpen={successDialogOpen} setSuccessDialogOpen={setSuccessDialogOpen} />
</>
);
};

export default WithdrawButtonDialog;
export default WithdrawForm;
56 changes: 56 additions & 0 deletions apps/app/src/components/dialog/WithdrawButtonDialog/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import { useState } from 'react';
import { Badge, Button } from '@mui/material';
import { useSwitchChain } from 'wagmi';

import { TARGET_NETWORK } from '@/constants/networks';
import { Dialog, DialogContent, DialogTrigger } from '@/components/dialog';
import WithdrawForm from './WithdrawForm';
import WithdrawSuccess from './WithdrawSuccess';

type WithdrawButtonDialogProps = { balance: number; loading: boolean };

const WithdrawButtonDialog = ({ balance, loading }: WithdrawButtonDialogProps) => {
const { switchChain } = useSwitchChain();
const [successDialogOpen, setSuccessDialogOpen] = useState(false);

const onCloseWithdrawDialog = () => {
switchChain?.({ chainId: TARGET_NETWORK.chainId });
};

const onWithdrawSuccess = () => setSuccessDialogOpen(true);

return (
<>
<Dialog onClose={onCloseWithdrawDialog}>
<DialogTrigger>
<Badge
color="error"
variant="standard"
badgeContent=" "
invisible={loading || balance === 0}
sx={{ width: '100%' }}
>
<Button fullWidth variant="contained" disabled={loading || balance === 0}>
Withdraw
</Button>
</Badge>
</DialogTrigger>
<DialogContent
aria-labelledby="withdraw-earnings-dialog"
dialogTitle="Withdraw Earnings"
sx={{
'& h2': { textAlign: 'center' },
'& .MuiDialogContent-root': { textAlign: 'center' },
}}
>
<WithdrawForm balance={balance} onWithdrawSuccess={onWithdrawSuccess} />
</DialogContent>
</Dialog>
<WithdrawSuccess successDialogOpen={successDialogOpen} setSuccessDialogOpen={setSuccessDialogOpen} />
</>
);
};

export default WithdrawButtonDialog;
Loading

0 comments on commit 690eb41

Please sign in to comment.