Skip to content

Commit

Permalink
Merge pull request #392 from WatchItDev/app/refactor/finance
Browse files Browse the repository at this point in the history
fix: some feedback
geolffreym authored Jan 8, 2025
2 parents db84332 + 2886c38 commit 8856cf1
Showing 16 changed files with 424 additions and 399 deletions.
32 changes: 32 additions & 0 deletions src/hooks/use-metamask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useEffect, useState } from 'react';
import { Address } from 'viem';
import { connectToMetaMask } from '@src/utils/metamask';
import { notifyError } from '@notifications/internal-notifications.ts';
import { ERRORS } from '@notifications/errors.ts';

export const useMetaMask = () => {
const [address, setAddress] = useState<Address | undefined>();
const [connecting, setConnecting] = useState(false);

useEffect(() => {
const walletConnected = localStorage.getItem('walletConnected');
if (walletConnected === 'true') {
connect();
}
}, []);

const connect = async () => {
setConnecting(true);
try {
const walletAddress = await connectToMetaMask();
setAddress(walletAddress);
localStorage.setItem('walletConnected', 'true');
} catch (err) {
notifyError(ERRORS.METAMASK_CONNECTING_ERROR);
} finally {
setConnecting(false);
}
};

return { address, connecting, connect };
};
1 change: 1 addition & 0 deletions src/sections/finance/components/box-row.tsx
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ export const BoxRow: FC<PropsWithChildren> = ({ children }) => (
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
my: 0.5
}}
>
{children}
3 changes: 2 additions & 1 deletion src/sections/finance/components/finance-contacts.tsx
Original file line number Diff line number Diff line change
@@ -102,10 +102,11 @@ export default function FinanceContactsCarousel({
title={title}
subheader={subheader}
action={<NavigationArrows next={carousel.onNext} prev={carousel.onPrev} />}
sx={{ px: 0 }}
/>

{/* Main carousel container */}
<Box sx={{ p: 3 }}>
<Box sx={{ py: 3 }}>
<Carousel ref={carousel.carouselRef} {...carousel.carouselSettings}>
{slidesData.map((chunk, index) => (
<SlideContacts
83 changes: 13 additions & 70 deletions src/sections/finance/components/finance-deposit-from-metamask.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,29 @@
// React and libraries imports
import { FC, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { Address } from 'viem';
// REACT IMPORTS
import { FC } from 'react';

// @MUI Imports
import Button from '@mui/material/Button';
// REDUX IMPORTS
import { useSelector } from 'react-redux';

// Project Imports
import Iconify from '@src/components/iconify';
import { connectToMetaMask } from '@src/utils/metamask';
// LOCAL IMPORTS
import { useMetaMask } from '@src/hooks/use-metamask';
import { useDepositMetamask } from '@src/hooks/use-deposit-metamask';
import { LoadingScreen } from '@src/components/loading-screen';
import FinanceDeposit from './finance-deposit';
import { ERRORS } from '@notifications/errors.ts';
import { notifyError } from '@notifications/internal-notifications.ts';
import { Box } from '@mui/system';
import FinanceDeposit from '@src/sections/finance/components/finance-deposit';
import FinanceMetamaskLoader from '@src/sections/finance/components/finance-metamask-loader.tsx';
import FinanceMetamaskButton from '@src/sections/finance/components/finance-metamask-button.tsx';

interface FinanceDepositFromMetamaskProps {
onClose: () => void;
}

const FinanceDepositFromMetamask: FC<FinanceDepositFromMetamaskProps> = ({ onClose }) => {
const sessionData = useSelector((state: any) => state.auth.session);

const [address, setAddress] = useState<Address | undefined>();
const [connecting, setConnecting] = useState(false);

// Specific hook for Metamask
const depositHook = useDepositMetamask();
const { address, connecting, connect } = useMetaMask();

// Try reconnecting if the user connected MetaMask before
useEffect(() => {
const walletConnected = localStorage.getItem('walletConnected');
if (walletConnected === 'true') {
handleConnectMetaMask();
}
}, []);

const handleConnectMetaMask = async () => {
setConnecting(true);
try {
const walletAddress = await connectToMetaMask();
setAddress(walletAddress);
localStorage.setItem('walletConnected', 'true');
} catch (err) {
notifyError(ERRORS.METAMASK_CONNECTING_ERROR);
} finally {
setConnecting(false);
}
};

if (connecting) {
return (
<Box sx={{ m: 2 }}>
<LoadingScreen />
</Box>
);
}

// If the wallet is NOT connected, show a button
if (!address) {
return (
<Button
sx={{ m: 4, p: 1.5 }}
startIcon={<Iconify icon="logos:metamask-icon" />}
variant="outlined"
onClick={handleConnectMetaMask}
>
Connect MetaMask
</Button>
);
}
if (connecting) return <FinanceMetamaskLoader />;
if (!address) return <FinanceMetamaskButton connect={connect} />;

// If the wallet IS connected, render the deposit component
return (
<FinanceDeposit
address={address}
recipient={sessionData?.address}
depositHook={depositHook}
onClose={onClose}
/>
);
return <FinanceDeposit address={address} recipient={sessionData?.address} depositHook={depositHook} onClose={onClose} />;
};

export default FinanceDepositFromMetamask;
39 changes: 20 additions & 19 deletions src/sections/finance/components/finance-deposit-from-stripe.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @mui
// MUI IMPORTS
import Box from '@mui/material/Box';

// Project components
// LOCAL IMPORTS
import TextMaxLine from '@src/components/text-max-line';
import { ComingSoonIllustration } from '@src/assets/illustrations';

@@ -17,25 +17,26 @@ const FinanceDepositFromStripe = () => {
p: 2,
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 2,
textAlign: 'center',
}}
>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
<ComingSoonIllustration sx={{ mt: 0, mb: 1, height: 120 }} />
<TextMaxLine variant={'h5'} line={1}>
This feature is coming soon.
</TextMaxLine>
<TextMaxLine color={'text.secondary'} variant={'body2'} line={2} sx={{ mb: 2 }}>
We’re working hard to bring this feature to life.
<br /> Check back soon!
</TextMaxLine>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 1,
textAlign: 'center',
}}
>
<TextMaxLine variant={'h5'} line={1}>
This feature is coming soon.
</TextMaxLine>
<TextMaxLine color={'text.secondary'} variant={'body2'} line={2} sx={{ mb: 2 }}>
We’re working hard to bring this feature to life.
<br /> Check back soon!
</TextMaxLine>
</Box>
</Box>
<Box></Box>
</Box>
);
};
95 changes: 32 additions & 63 deletions src/sections/finance/components/finance-deposit-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,49 @@
// REACT IMPORTS
import { FC, useState } from 'react';

// MUI IMPORTS
import Tab from '@mui/material/Tab';
import DialogTitle from '@mui/material/DialogTitle';
import Tabs, { tabsClasses } from '@mui/material/Tabs';
import Dialog, { DialogProps } from '@mui/material/Dialog';
import { FC } from 'react';

// LOCAL IMPORTS
import Iconify from '@src/components/iconify';
import FinanceDepositFromStripe from '@src/sections/finance/components/finance-deposit-from-stripe.tsx';
import FinanceDepositFromMetamask from '@src/sections/finance/components/finance-deposit-from-metamask.tsx';
import FinanceDepositFromSmartAccount from '@src/sections/finance/components/finance-deposit-from-smart-account.tsx';

// ----------------------------------------------------------------------
import FinanceModal from '@src/sections/finance/components/finance-modal';
import FinanceDepositFromStripe from '@src/sections/finance/components/finance-deposit-from-stripe';
import FinanceDepositFromMetamask from '@src/sections/finance/components/finance-deposit-from-metamask';
import FinanceDepositFromSmartAccount from '@src/sections/finance/components/finance-deposit-from-smart-account';

interface FinanceDepositModalProps extends DialogProps {
interface FinanceDepositModalProps {
open: boolean;
onClose: VoidFunction;
}

// ----------------------------------------------------------------------

const TABS = [
const depositTabs = [
{ value: 'fiat', label: 'Stripe', disabled: false, icon: <Iconify icon={'logos:stripe'} /> },
{
value: 'metamask',
label: 'Metamask',
disabled: false,
icon: <Iconify icon={'logos:metamask-icon'} />,
},
{
value: 'smartAccount',
label: 'Smart Account',
disabled: false,
icon: <Iconify icon={'logos:ethereum-color'} />,
},
{ value: 'metamask', label: 'Metamask', disabled: false, icon: <Iconify icon={'logos:metamask-icon'} /> },
{ value: 'smartAccount', label: 'Smart Account', disabled: false, icon: <Iconify icon={'logos:ethereum-color'} /> },
];

// ----------------------------------------------------------------------

export const FinanceDepositModal: FC<FinanceDepositModalProps> = ({ open, onClose }) => {
const [currentTab, setCurrentTab] = useState('metamask');

const handleChangeTab = (_event: any, newValue: any) => {
setCurrentTab(newValue);
const renderContent = (currentTab: string) => {
switch (currentTab) {
case 'fiat':
return <FinanceDepositFromStripe />;
case 'metamask':
return <FinanceDepositFromMetamask onClose={onClose} />;
case 'smartAccount':
return <FinanceDepositFromSmartAccount onClose={onClose} />;
default:
return null;
}
};

return (
<Dialog open={open} fullWidth maxWidth="xs" onClose={onClose}>
<DialogTitle>Deposit</DialogTitle>

<Tabs
key={`tabs-deposit`}
value={currentTab}
onChange={handleChangeTab}
sx={{
width: 1,
zIndex: 9,
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
[`& .${tabsClasses.flexContainer}`]: { justifyContent: { xs: 'left', md: 'center' } },
}}
>
{TABS.map((tab) => (
<Tab
icon={tab.icon}
disabled={tab.disabled}
key={tab.value}
value={tab.value}
label={tab.label}
/>
))}
</Tabs>

{currentTab === 'fiat' && <FinanceDepositFromStripe />}
{currentTab === 'metamask' && <FinanceDepositFromMetamask onClose={onClose} />}
{currentTab === 'smartAccount' && <FinanceDepositFromSmartAccount onClose={onClose} />}
</Dialog>
<FinanceModal
open={open}
onClose={onClose}
title="Deposit"
tabs={depositTabs}
renderContent={renderContent}
maxWidth="xs"
fullWidth
/>
);
};

export default FinanceDepositModal;
86 changes: 49 additions & 37 deletions src/sections/finance/components/finance-deposit.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
// React and libraries imports
// REACT IMPORTS
import { FC, useCallback, useEffect, useState } from 'react';

// VIEM IMPORTS
import { Address } from 'viem';

// @mui components
// MUI IMPORTS
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Divider from '@mui/material/Divider';

// Project components
// LOCAL IMPORTS
import NeonPaper from '@src/sections/publication/NeonPaperContainer';
import FinanceDialogsActions from '@src/sections/finance/components/finance-dialogs-actions';
import TextMaxLine from '@src/components/text-max-line';
import { InputAmount } from '@src/components/input-amount';
import { formatBalanceNumber } from '@src/utils/format-number';
import { useResponsive } from '@src/hooks/use-responsive';
import { useGetMmcContractBalance } from '@src/hooks/use-get-mmc-contract-balance';
import BoxRow from '@src/sections/finance/components/box-row.tsx';
import { UseDepositHook } from '@src/hooks/use-deposit';
import { truncateAddress } from '@src/utils/wallet';

// Notifications
// NOTIFICATIONS IMPORTS
import { notifyError, notifySuccess, notifyWarning } from '@notifications/internal-notifications';
import { WARNING } from '@notifications/warnings';
import { SUCCESS } from '@notifications/success';
import { ERRORS } from '@notifications/errors.ts';
import TextField from '@mui/material/TextField';

interface FinanceDepositProps {
/**
@@ -58,13 +58,14 @@ interface FinanceDepositProps {
* - `onClose`
*/
const FinanceDeposit: FC<FinanceDepositProps> = ({ address, recipient, depositHook, onClose }) => {
const [amount, setAmount] = useState<number>(0);
const [amount, setAmount] = useState<number>();
const [localLoading, setLocalLoading] = useState(false);

const [amountError, setAmountError] = useState(false);
const [helperText, setHelperText] = useState<string>("");
const { deposit, loading: depositLoading, error } = depositHook;

// Retrieve the balance using the "address" (the connected one)
const { balance } = useGetMmcContractBalance(address);
const isBusy = localLoading || depositLoading;
const RainbowEffect = isBusy ? NeonPaper : Box;

useEffect(() => {
if (error) {
@@ -74,6 +75,8 @@ const FinanceDeposit: FC<FinanceDepositProps> = ({ address, recipient, depositHo

// Validation and deposit
const handleConfirmDeposit = useCallback(async () => {
if (!amount) return;

if (!address) {
// If there's no connected address, we can't deposit
notifyWarning(WARNING.NO_WALLET_CONNECTED);
@@ -96,64 +99,73 @@ const FinanceDeposit: FC<FinanceDepositProps> = ({ address, recipient, depositHo
}
}, [address, recipient, amount, balance, deposit, onClose]);

// NeonPaper effect if currently loading
const isBusy = localLoading || depositLoading;
const RainbowEffect = isBusy ? NeonPaper : Box;
const mdUp = useResponsive('up', 'md');
const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = Number(event.target.value);

setAmount(value ?? undefined);

// Set appropriate error message
const errorMessage =
value <= 0
? "No amount entered"
: value > (balance ?? 0)
? "Amount cannot be greater than balance"
: "";

setAmountError(!!errorMessage); // Set error state
setHelperText(errorMessage); // Update helper text with the error message
};


// If there's no address, we could show a fallback,
// but here we'll show the same UI (balance: 0, etc.)
// or a warning when confirming. It's up to you.

return (
<>
<Stack
sx={{ mt: 2, p: 2, gap: 1 }}
sx={{ mt: 1, py: 2, px: 3, gap: 1 }}
direction="column"
display="flex"
alignItems="center"
justifyContent="space-between"
>
<BoxRow>
<TextMaxLine line={1}>Connected Wallet</TextMaxLine>
<TextMaxLine line={1} fontWeight={"bold"}>Connected Wallet</TextMaxLine>
<TextMaxLine
line={1}
sx={{ fontWeight: 'bold', fontSize: '1em', color: 'text.secondary' }}
sx={{ fontWeight: '400', fontSize: '1em', color: 'text.secondary' }}
>
{address ? truncateAddress(address) : 'No wallet connected'}
</TextMaxLine>
</BoxRow>

<Divider sx={{ width: '100%' }} />

<BoxRow>
<TextMaxLine line={1}>Balance</TextMaxLine>
<TextMaxLine line={1} fontWeight={"bold"}>Balance</TextMaxLine>
<TextMaxLine
line={1}
sx={{ fontWeight: 'bold', fontSize: '1em', color: 'text.secondary' }}
sx={{ fontWeight: '400', fontSize: '1em', color: 'text.secondary' }}
>
{formatBalanceNumber(balance ?? 0)} MMC
</TextMaxLine>
</BoxRow>

<Divider sx={{ width: '100%' }} />

<BoxRow>
<TextMaxLine line={1}>Amount to deposit</TextMaxLine>
<InputAmount
autoFocus
max={balance ?? 0}
amount={amount}
onChange={(e) => setAmount(Number(e.target.value))}
/>
</BoxRow>
<TextField
sx={{ mt: 1 }}
fullWidth
autoFocus
label="Amount to withdraw"
type="number"
value={amount}
onChange={handleAmountChange}
placeholder="Enter an amount"
error={amountError}
helperText={helperText}
/>
</Stack>

<FinanceDialogsActions
rainbowComponent={RainbowEffect}
loading={isBusy}
actionLoading={depositLoading}
amount={amount}
amount={amount ?? 0}
balance={balance ?? 0}
label={'Confirm'}
onConfirmAction={handleConfirmDeposit}
21 changes: 21 additions & 0 deletions src/sections/finance/components/finance-metamask-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// REACT IMPORTS
import { FC } from 'react';

// MUI IMPORTS
import Button from '@mui/material/Button';

// LOCAL IMPORTS
import Iconify from '@src/components/iconify';

const FinanceMetamaskButton: FC<{ connect: () => void }> = ({ connect }) => {
return <Button
sx={{ m: 4, p: 1.5 }}
startIcon={<Iconify icon="logos:metamask-icon" />}
variant="outlined"
onClick={connect}
>
Connect MetaMask
</Button>;
};

export default FinanceMetamaskButton;
16 changes: 16 additions & 0 deletions src/sections/finance/components/finance-metamask-loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// REACT IMPORTS
import { FC } from 'react';

// MUI IMPORTS
import { Box } from '@mui/system';

// LOCAL IMPORTS
import { LoadingScreen } from '@src/components/loading-screen';

const FinanceMetamaskLoader: FC = () => {
return <Box sx={{ mx: 4, my: 8 }}>
<LoadingScreen />
</Box>;
};

export default FinanceMetamaskLoader;
65 changes: 65 additions & 0 deletions src/sections/finance/components/finance-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { FC, useState, ReactNode } from 'react';

// MUI IMPORTS
import Tab from '@mui/material/Tab';
import DialogTitle from '@mui/material/DialogTitle';
import Tabs, { tabsClasses } from '@mui/material/Tabs';
import Dialog, { DialogProps } from '@mui/material/Dialog';

interface TabConfig {
value: string;
label: string;
disabled?: boolean;
icon: any;
}

interface FinanceModalProps extends DialogProps {
onClose: VoidFunction;
title: string;
tabs: TabConfig[];
renderContent: (currentTab: string) => ReactNode;
}

const FinanceModal: FC<FinanceModalProps> = ({ open, onClose, title, tabs, renderContent, ...dialogProps }) => {
const [currentTab, setCurrentTab] = useState('smartAccount');

const handleChangeTab = (_event: React.SyntheticEvent, newValue: string) => {
setCurrentTab(newValue);
};

return (
<Dialog open={open} fullWidth maxWidth="xs" onClose={onClose} {...dialogProps}>
<DialogTitle sx={{ pb: 1 }}>{title}</DialogTitle>

<Tabs
key={`tabs-${title.toLowerCase()}`}
value={currentTab}
variant="scrollable"
scrollButtons="auto"
onChange={handleChangeTab}
allowScrollButtonsMobile
sx={{
width: 1,
zIndex: 9,
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
[`& .${tabsClasses.flexContainer}`]: { justifyContent: 'center', px: 1 },
[`& .${tabsClasses.scroller}`]: { display: 'flex', justifyContent: 'center' },
}}
>
{tabs.map((tab) => (
<Tab
icon={tab.icon}
disabled={tab.disabled}
key={tab.value}
value={tab.value}
label={tab.label}
/>
))}
</Tabs>

{renderContent(currentTab)}
</Dialog>
);
};

export default FinanceModal;
10 changes: 6 additions & 4 deletions src/sections/finance/components/finance-quick-transfer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// React and libraries imports
// REACT IMPORTS
import React, { useCallback, useEffect, useState } from 'react';
// Redux

// REDUX IMPORTS
import { useDispatch, useSelector } from 'react-redux';
// Redux
import { storeAddress, toggleRainbow } from '@redux/address';

// API Lens and ethers
// LENS IMPORTS
import { Profile } from '@lens-protocol/api-bindings';

// ETHERS IMPORTS
import { ethers } from 'ethers';

// MUI
80 changes: 12 additions & 68 deletions src/sections/finance/components/finance-withdraw-from-metamask.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,27 @@
// React and libraries imports
import { FC, useEffect, useState } from 'react';
import { Address } from 'viem';
// REACT IMPORTS
import { FC } from 'react';

// @MUI Imports
import Button from '@mui/material/Button';

// Import Components
import Iconify from '@src/components/iconify';
import FinanceWithdraw from './finance-withdraw.tsx';
// LOCAL IMPORTS
import { useMetaMask } from '@src/hooks/use-metamask';
import { useWithdrawMetamask } from '@src/hooks/use-withdraw-metamask';
import { useGetVaultBalance } from '@src/hooks/use-get-vault-balance';
import { connectToMetaMask } from '@src/utils/metamask';
import { LoadingScreen } from '@src/components/loading-screen';

// Notifications
import { notifyError } from '@src/utils/notifications/internal-notifications';
import { ERRORS } from '@src/utils/notifications/errors';
import { Box } from '@mui/system';
import FinanceWithdraw from '@src/sections/finance/components/finance-withdraw';
import FinanceMetamaskLoader from '@src/sections/finance/components/finance-metamask-loader.tsx';
import FinanceMetamaskButton from '@src/sections/finance/components/finance-metamask-button.tsx';

interface FinanceWithdrawFromMetamaskProps {
onClose: () => void;
}

const FinanceWithdrawFromMetamask: FC<FinanceWithdrawFromMetamaskProps> = ({ onClose }) => {
const [address, setAddress] = useState<Address | undefined>();
const [connecting, setConnecting] = useState(false);
const { balance } = useGetVaultBalance(address);
const withdrawHook = useWithdrawMetamask();
const { address, connecting, connect } = useMetaMask();
const { balance } = useGetVaultBalance(address);

// Auto-reconnect if MetaMask was previously connected
useEffect(() => {
const walletConnected = localStorage.getItem('walletConnected');
if (walletConnected === 'true') {
handleConnectMetaMask();
}
}, []);

const handleConnectMetaMask = async () => {
setConnecting(true);
try {
const walletAddress = await connectToMetaMask();
setAddress(walletAddress);
localStorage.setItem('walletConnected', 'true');
} catch (err) {
notifyError(ERRORS.METAMASK_CONNECTING_ERROR);
} finally {
setConnecting(false);
}
};

if (connecting) {
return (
<Box sx={{ m: 2 }}>
<LoadingScreen />
</Box>
);
}

if (!address) {
return (
<Button
sx={{ m: 4, p: 1.5 }}
startIcon={<Iconify icon="logos:metamask-icon" />}
variant="outlined"
onClick={handleConnectMetaMask}
>
Connect MetaMask
</Button>
);
}
if (connecting) return <FinanceMetamaskLoader />;
if (!address) return <FinanceMetamaskButton connect={connect} />;

return (
<FinanceWithdraw
address={address}
withdrawHook={withdrawHook}
balance={balance}
onClose={onClose}
/>
);
return <FinanceWithdraw address={address} withdrawHook={withdrawHook} balance={balance} onClose={onClose} />;
};

export default FinanceWithdrawFromMetamask;
94 changes: 31 additions & 63 deletions src/sections/finance/components/finance-withdraw-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,46 @@
// REACT IMPORTS
import { FC, useState } from 'react';
// src/sections/finance/components/FinanceWithdrawModal.tsx

// MUI IMPORTS

import DialogTitle from '@mui/material/DialogTitle';

import Dialog, { DialogProps } from '@mui/material/Dialog';
import { FC } from 'react';

// LOCAL IMPORTS

import FinanceWithdrawFromSmartAccount from '@src/sections/finance/components/finance-withdraw-from-smart-account';
import Iconify from '@src/components/iconify';
import Tabs, { tabsClasses } from '@mui/material/Tabs';
import Tab from '@mui/material/Tab';
import FinanceWithdrawFromMetamask from '@src/sections/finance/components/finance-withdraw-from-metamask.tsx';

const TABS = [
{
value: 'metamask',
label: 'Metamask',
disabled: false,
icon: <Iconify icon={'logos:metamask-icon'} />,
},
{
value: 'smartAccount',
label: 'Smart Account',
disabled: false,
icon: <Iconify icon={'logos:ethereum-color'} />,
},
];

// ----------------------------------------------------------------------
import FinanceModal from '@src/sections/finance/components/finance-modal';
import FinanceWithdrawFromMetamask from '@src/sections/finance/components/finance-withdraw-from-metamask';
import FinanceWithdrawFromSmartAccount from '@src/sections/finance/components/finance-withdraw-from-smart-account';

interface FinanceWithdrawModalProps extends DialogProps {
interface FinanceWithdrawModalProps {
open: boolean;
onClose: VoidFunction;
}

// ----------------------------------------------------------------------
const withdrawTabs = [
{ value: 'metamask', label: 'Metamask', disabled: false, icon: <Iconify icon={'logos:metamask-icon'} /> },
{ value: 'smartAccount', label: 'Smart Account', disabled: false, icon: <Iconify icon={'logos:ethereum-color'} /> },
];

export const FinanceWithdrawModal: FC<FinanceWithdrawModalProps> = ({ open, onClose }) => {
const [currentTab, setCurrentTab] = useState('metamask');

const handleChangeTab = (_event: any, newValue: any) => {
setCurrentTab(newValue);
const renderContent = (currentTab: string) => {
switch (currentTab) {
case 'metamask':
return <FinanceWithdrawFromMetamask onClose={onClose} />;
case 'smartAccount':
return <FinanceWithdrawFromSmartAccount onClose={onClose} />;
default:
return null;
}
};

return (
<Dialog open={open} fullWidth maxWidth="xs" onClose={onClose}>
<DialogTitle>Withdraw</DialogTitle>

<Tabs
key={`tabs-deposit`}
value={currentTab}
onChange={handleChangeTab}
sx={{
width: 1,
zIndex: 9,
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
[`& .${tabsClasses.flexContainer}`]: { justifyContent: { xs: 'left', md: 'center' } },
}}
>
{TABS.map((tab) => (
<Tab
icon={tab.icon}
disabled={tab.disabled}
key={tab.value}
value={tab.value}
label={tab.label}
/>
))}
</Tabs>

{currentTab === 'metamask' && <FinanceWithdrawFromMetamask onClose={onClose} />}
{currentTab === 'smartAccount' && <FinanceWithdrawFromSmartAccount onClose={onClose} />}
</Dialog>
<FinanceModal
open={open}
onClose={onClose}
title="Withdraw"
tabs={withdrawTabs}
renderContent={renderContent}
maxWidth="xs"
fullWidth
/>
);
};

export default FinanceWithdrawModal;
104 changes: 74 additions & 30 deletions src/sections/finance/components/finance-withdraw.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,48 @@
import { FC, useCallback, useEffect, useState } from 'react';

// VIEM IMPORTS
import { Address } from 'viem';

// @mui
// MUI IMPORTS
import Box from '@mui/material/Box';
import Stack from '@mui/material/Stack';
import Divider from '@mui/material/Divider';
import TextField from '@mui/material/TextField';

// Project components
// LOCAL IMPORTS
import NeonPaper from '@src/sections/publication/NeonPaperContainer';
import FinanceDialogsActions from '@src/sections/finance/components/finance-dialogs-actions';
import TextMaxLine from '@src/components/text-max-line';
import { InputAmount } from '@src/components/input-amount';
import { formatBalanceNumber } from '@src/utils/format-number';
import { useResponsive } from '@src/hooks/use-responsive';
import BoxRow from '@src/sections/finance/components/box-row';
import { isValidAddress } from '@src/sections/finance/components/finance-quick-transfer';
import { UseWithdrawHook } from '@src/hooks/use-withdraw.ts';
import { truncateAddress } from '@src/utils/wallet.ts';

// Notifications
// NOTIFICATIONS IMPORTS
import { notifyError, notifySuccess, notifyWarning } from '@notifications/internal-notifications';
import { ERRORS } from '@notifications/errors';
import { WARNING } from '@notifications/warnings';
import { SUCCESS } from '@notifications/success';

// ----------------------------------------------------------------------

interface FinanceWithdrawProps {
address?: Address; // The connected wallet address
withdrawHook: UseWithdrawHook; // Generic withdraw hook
balance: number | null; // Current user balance
onClose: () => void; // Callback to close the modal/dialog
}

// ----------------------------------------------------------------------

const FinanceWithdraw: FC<FinanceWithdrawProps> = ({ address, withdrawHook, balance, onClose }) => {
const [amount, setAmount] = useState<number>(0);
const [amount, setAmount] = useState<number>();
const [destinationAddress, setDestinationAddress] = useState('');
const [addressError, setAddressError] = useState(false);
const [amountError, setAmountError] = useState(false);
const [amountHelperText, setAmountHelperText] = useState(''); // Dynamic helper text for amount
const [localLoading, setLocalLoading] = useState(false);

const { withdraw, loading: withdrawLoading, error } = withdrawHook;

const mdUp = useResponsive('up', 'md');
const RainbowEffect = localLoading || withdrawLoading ? NeonPaper : Box;

useEffect(() => {
@@ -49,6 +52,8 @@ const FinanceWithdraw: FC<FinanceWithdrawProps> = ({ address, withdrawHook, bala
}, [error]);

const handleConfirmWithdraw = useCallback(async () => {
if (!amount) return;

if (!destinationAddress || addressError) {
notifyWarning(WARNING.INVALID_WALLET_ADDRESS);
return;
@@ -69,6 +74,22 @@ const FinanceWithdraw: FC<FinanceWithdrawProps> = ({ address, withdrawHook, bala
}
}, [amount, destinationAddress, balance, withdraw, addressError, onClose]);

const handleAmountChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = Number(event.target.value);
setAmount(value);

// Update error state and helper text dynamically
const errorMessage =
value <= 0
? "Amount must be greater than zero"
: value > (balance ?? 0)
? "Amount cannot be greater than balance"
: "";

setAmountError(!!errorMessage);
setAmountHelperText(errorMessage);
};

const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
setDestinationAddress(value);
@@ -78,48 +99,71 @@ const FinanceWithdraw: FC<FinanceWithdrawProps> = ({ address, withdrawHook, bala
return (
<>
<Stack
sx={{ p: 2, gap: 1 }}
sx={{ mt: 1, py: 2, px: 3, gap: 1 }}
direction="column"
display="flex"
alignItems="center"
justifyContent="space-between"
>
<BoxRow>
<TextMaxLine line={1}>Connected Wallet</TextMaxLine>
<TextMaxLine line={1} fontWeight={"bold"}>Wallet</TextMaxLine>
<TextMaxLine
line={1}
sx={{ fontWeight: 'bold', fontSize: '1em', color: 'text.secondary' }}
sx={{ fontWeight: '400', fontSize: '1em', color: 'text.secondary' }}
>
{address ? address : 'No wallet connected'}
{address ? truncateAddress(address) : 'No wallet connected'}
</TextMaxLine>
</BoxRow>

<Divider sx={{ width: '100%' }} />

<BoxRow>
<TextMaxLine line={1}>Balance</TextMaxLine>
<TextMaxLine line={1} fontWeight={"bold"}>Balance</TextMaxLine>
<TextMaxLine
line={1}
sx={{ fontWeight: 'bold', fontSize: '1em', color: 'text.secondary' }}
sx={{ fontWeight: '400', fontSize: '1em', color: 'text.secondary' }}
>
{formatBalanceNumber(balance ?? 0)} MMC
</TextMaxLine>
</BoxRow>

<Divider sx={{ width: '100%' }} />
<TextField
sx={{ mt: 1 }}
fullWidth
autoFocus
label="Amount to withdraw"
type="number"
value={amount}
onChange={handleAmountChange}
placeholder="Enter an amount"
error={amountError}
helperText={amountHelperText}
/>

<BoxRow>
<TextMaxLine line={1}>Amount to withdraw</TextMaxLine>
<InputAmount
autoFocus
max={balance ?? 0}
amount={amount}
onChange={(e) => setAmount(Number(e.target.value))}
/>
</BoxRow>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
width: '100%',
my: 2
}}
>
<Box sx={{ background: 'rgba(255,255,255,0.1)', height: '1px', width: '100%' }} />
<Box sx={{
position: 'absolute',
left: 'calc(50% - 1.5rem)',
top: '-22px',
width: '3rem',
padding: '8px',
background: '#212b36',
textAlign: 'center',
color: 'text.secondary'
}}>
to
</Box>
</Box>

<TextField
sx={{ mt: 1 }}
fullWidth
label="Wallet Address"
value={destinationAddress}
@@ -134,7 +178,7 @@ const FinanceWithdraw: FC<FinanceWithdrawProps> = ({ address, withdrawHook, bala
rainbowComponent={RainbowEffect}
loading={localLoading}
actionLoading={withdrawLoading}
amount={amount}
amount={amount ?? 0}
balance={balance ?? 0}
label={'Confirm'}
onConfirmAction={handleConfirmWithdraw}
8 changes: 7 additions & 1 deletion src/sections/user/profile-cover.tsx
Original file line number Diff line number Diff line change
@@ -4,14 +4,19 @@ import { alpha } from '@mui/material/styles';
import { bgGradient } from '@src/theme/css';
import { Profile } from '@lens-protocol/api-bindings';
import Image from '../../components/image';
import { SxProps } from '@mui/system';
import { Theme } from '@mui/material';

// ----------------------------------------------------------------------

interface ProfileCoverProps {
profile?: Profile;
sx?: SxProps<Theme>;
}

export default function ProfileCover({ profile }: ProfileCoverProps) {
// ----------------------------------------------------------------------

export default function ProfileCover({ profile, sx }: ProfileCoverProps) {
const coverImage = profile?.metadata?.coverPicture?.optimized?.uri;
return (
<Image
@@ -28,6 +33,7 @@ export default function ProfileCover({ profile }: ProfileCoverProps) {
justifyContent: 'center',
borderRadius: 2,
overflow: 'hidden',
...sx
}}
/>
);
86 changes: 43 additions & 43 deletions src/sections/user/profile-header.tsx
Original file line number Diff line number Diff line change
@@ -234,7 +234,7 @@ const ProfileHeader = ({
return (
<>
<Box sx={{ my: 3, position: 'relative' }}>
<ProfileCover profile={profile} />
<ProfileCover profile={profile} sx={{ height: { xs: 200, md: 300 }}} />

{sessionData?.authenticated ? (
<Button
@@ -309,7 +309,7 @@ const ProfileHeader = ({
width: '100%',
marginTop: { xs: '-48px', md: '-64px' },
ml: {
xs: 0,
xs: 4,
md: 4,
},
}}
@@ -318,8 +318,8 @@ const ProfileHeader = ({
direction="row"
sx={{
ml: {
xs: 4,
sm: 4,
xs: 0,
sm: 0,
md: 0,
},
}}
@@ -377,10 +377,47 @@ const ProfileHeader = ({
</Button>
)
)}
<Button
onMouseEnter={handleOpenShare}
onMouseLeave={handleCloseShare}
ref={navRefSocial}
size="medium"
variant="outlined"
sx={{ p: 1, minWidth: '44px' }}
onClick={handlePopoverOpen}
>
<Iconify icon="ion:share-social-outline" width={20} />
</Button>

<Popover
open={openTooltipShare}
anchorEl={navRefSocial.current}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ vertical: 'bottom', horizontal: 'center' }}
slotProps={{
paper: {
onMouseEnter: handleOpenShare,
onMouseLeave: handleCloseShare,
sx: {
mt: 6,
backgroundColor: 'rgba(0,0,0,0.6)',
padding: '8px 20px',
...(open && {
pointerEvents: 'auto',
}),
},
},
}}
sx={{
pointerEvents: 'none',
}}
>
<Typography>Share Watchit on your social</Typography>
</Popover>
</Stack>
</Stack>

<Stack direction="column" sx={{ width: '100%' }}>
<Stack direction="column" sx={{ width: '100%', maxWidth: { xs: 'calc(100% - 2rem)', md: '100%' } }}>
<Box sx={{ mt: 2, width: '80%' }}>
<Box
sx={{
@@ -413,7 +450,7 @@ const ProfileHeader = ({
mt: 0,
mb: 2,
opacity: 0.7,
minWidth: '400px',
minWidth: { xs: 'auto', md: '400px' },
}}
>
{profile?.metadata?.bio ?? ''}
@@ -497,43 +534,6 @@ const ProfileHeader = ({
{profile?.id !== sessionData?.profile?.id && (
<FollowUnfollowButton profileId={profile?.id} />
)}
<Button
onMouseEnter={handleOpenShare}
onMouseLeave={handleCloseShare}
ref={navRefSocial}
size="medium"
variant="outlined"
sx={{ p: 1, minWidth: '44px' }}
onClick={handlePopoverOpen}
>
<Iconify icon="ion:share-social-outline" width={20} />
</Button>

<Popover
open={openTooltipShare}
anchorEl={navRefSocial.current}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
transformOrigin={{ vertical: 'bottom', horizontal: 'center' }}
slotProps={{
paper: {
onMouseEnter: handleOpenShare,
onMouseLeave: handleCloseShare,
sx: {
mt: 6,
backgroundColor: 'rgba(0,0,0,0.6)',
padding: '8px 20px',
...(open && {
pointerEvents: 'auto',
}),
},
},
}}
sx={{
pointerEvents: 'none',
}}
>
<Typography>Share Watchit on your social</Typography>
</Popover>

{sessionData?.profile && profile?.id === sessionData?.profile?.id && (
<>

0 comments on commit 8856cf1

Please sign in to comment.