From b00ed0462e7c847423a38fff9c8e92cb509c3e1b Mon Sep 17 00:00:00 2001 From: "kody.low" Date: Mon, 2 Dec 2024 18:18:09 -0800 Subject: [PATCH] feat: split out gateway payments modal --- .gitignore | 2 + .../components/walletCard/BalancesSummary.tsx | 89 ++++++++++ .../components/walletCard/EcashCard.tsx | 59 +++++++ .../components/walletCard/LightningCard.tsx | 59 +++++++ .../components/walletCard/OnchainCard.tsx | 92 ++++++++++ .../components/walletCard/WalletCard.tsx | 160 ++---------------- .../components/walletModal/WalletModal.tsx | 96 +++++------ apps/router/src/hooks/gateway/useGateway.tsx | 17 +- apps/router/src/languages/en.json | 1 + apps/router/src/types/gateway.tsx | 2 +- 10 files changed, 362 insertions(+), 215 deletions(-) create mode 100644 apps/router/src/gateway-ui/components/walletCard/BalancesSummary.tsx create mode 100644 apps/router/src/gateway-ui/components/walletCard/EcashCard.tsx create mode 100644 apps/router/src/gateway-ui/components/walletCard/LightningCard.tsx create mode 100644 apps/router/src/gateway-ui/components/walletCard/OnchainCard.tsx diff --git a/.gitignore b/.gitignore index ab164e7c8..5446ef317 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ yarn-error.log* fm_* misc/test/data misc/test/tls.key + +todo.md diff --git a/apps/router/src/gateway-ui/components/walletCard/BalancesSummary.tsx b/apps/router/src/gateway-ui/components/walletCard/BalancesSummary.tsx new file mode 100644 index 000000000..88e0eeb31 --- /dev/null +++ b/apps/router/src/gateway-ui/components/walletCard/BalancesSummary.tsx @@ -0,0 +1,89 @@ +import React, { useMemo } from 'react'; +import { Box, Flex, Text, Square } from '@chakra-ui/react'; +import { formatValue, useTranslation } from '@fedimint/utils'; +import { MSats } from '@fedimint/types'; +import { PieChart } from 'react-minimal-pie-chart'; +import { useGatewayContext } from '../../../hooks'; +import { useBalanceCalculations } from '../../hooks/useBalanceCalculations'; + +export const BalancesSummary = React.memo( + function BalancesSummary(): JSX.Element { + const { t } = useTranslation(); + const { state } = useGatewayContext(); + const balanceAmounts = useBalanceCalculations(state.balances ?? undefined); + + const balanceData = useMemo( + () => [ + { + title: 'Ecash', + value: balanceAmounts.ecash, + formattedValue: formatValue( + balanceAmounts.ecash as MSats, + state.unit, + true + ), + color: '#FF6384', + }, + { + title: 'Lightning', + value: balanceAmounts.lightning, + formattedValue: formatValue( + balanceAmounts.lightning as MSats, + state.unit, + true + ), + color: '#36A2EB', + }, + { + title: 'Onchain', + value: balanceAmounts.onchain, + formattedValue: formatValue( + balanceAmounts.onchain as MSats, + state.unit, + true + ), + color: '#FFCE56', + }, + ], + [balanceAmounts, state.unit] + ); + + return ( + + + {t('wallet.balances-summary')} + + + + {balanceData.map((item) => ( + + + + {item.title}: + + {item.formattedValue} + + ))} + + + Total: + + + {formatValue(balanceAmounts.total as MSats, state.unit, true)} + + + + + + + + + ); + } +); diff --git a/apps/router/src/gateway-ui/components/walletCard/EcashCard.tsx b/apps/router/src/gateway-ui/components/walletCard/EcashCard.tsx new file mode 100644 index 000000000..07490fed5 --- /dev/null +++ b/apps/router/src/gateway-ui/components/walletCard/EcashCard.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Box, Button, Flex, Text } from '@chakra-ui/react'; +import { useTranslation } from '@fedimint/utils'; +import { FaArrowDown, FaArrowUp } from 'react-icons/fa'; +import { useGatewayContext } from '../../../hooks'; +import { + GATEWAY_APP_ACTION_TYPE, + WalletModalAction, + WalletModalType, +} from '../../../types/gateway'; + +export const EcashCard = React.memo(function EcashCard(): JSX.Element { + const { t } = useTranslation(); + const { state, dispatch } = useGatewayContext(); + + const handleModalOpen = (action: WalletModalAction) => { + const federations = state.gatewayInfo?.federations; + if (!federations?.length) return; + + dispatch({ + type: GATEWAY_APP_ACTION_TYPE.SET_WALLET_MODAL_STATE, + payload: { + action, + type: WalletModalType.Ecash, + selectedFederation: federations[0], + showSelector: true, + isOpen: true, + }, + }); + }; + + return ( + + + {t('wallet.ecash')} + + + + + + + Send and receive federated ecash between federation members + + + ); +}); diff --git a/apps/router/src/gateway-ui/components/walletCard/LightningCard.tsx b/apps/router/src/gateway-ui/components/walletCard/LightningCard.tsx new file mode 100644 index 000000000..11937f04a --- /dev/null +++ b/apps/router/src/gateway-ui/components/walletCard/LightningCard.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Box, Button, Flex, Text } from '@chakra-ui/react'; +import { useTranslation } from '@fedimint/utils'; +import { FaArrowDown, FaArrowUp } from 'react-icons/fa'; +import { useGatewayContext } from '../../../hooks'; +import { + GATEWAY_APP_ACTION_TYPE, + WalletModalAction, + WalletModalType, +} from '../../../types/gateway'; + +export const LightningCard = React.memo(function LightningCard(): JSX.Element { + const { t } = useTranslation(); + const { state, dispatch } = useGatewayContext(); + + const handleModalOpen = (action: WalletModalAction) => { + const federations = state.gatewayInfo?.federations; + if (!federations?.length) return; + + dispatch({ + type: GATEWAY_APP_ACTION_TYPE.SET_WALLET_MODAL_STATE, + payload: { + action, + type: WalletModalType.Lightning, + selectedFederation: federations[0], + showSelector: true, + isOpen: true, + }, + }); + }; + + return ( + + + {t('wallet.lightning-payments')} + + + + + + + Send and receive Lightning Network payments through the federation + + + ); +}); diff --git a/apps/router/src/gateway-ui/components/walletCard/OnchainCard.tsx b/apps/router/src/gateway-ui/components/walletCard/OnchainCard.tsx new file mode 100644 index 000000000..030d152ea --- /dev/null +++ b/apps/router/src/gateway-ui/components/walletCard/OnchainCard.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { Box, Button, Flex, Text } from '@chakra-ui/react'; +import { useTranslation } from '@fedimint/utils'; +import { FaArrowDown, FaArrowUp } from 'react-icons/fa'; +import { useGatewayContext } from '../../../hooks'; +import { + GATEWAY_APP_ACTION_TYPE, + WalletModalAction, + WalletModalType, +} from '../../../types/gateway'; + +export const OnchainCard = React.memo(function OnchainCard(): JSX.Element { + const { t } = useTranslation(); + const { state, dispatch } = useGatewayContext(); + + const handleModalOpen = (action: WalletModalAction) => { + const federations = state.gatewayInfo?.federations; + if (!federations?.length) return; + + dispatch({ + type: GATEWAY_APP_ACTION_TYPE.SET_WALLET_MODAL_STATE, + payload: { + action, + type: WalletModalType.Onchain, + selectedFederation: federations[0], + showSelector: true, + isOpen: true, + }, + }); + }; + + return ( + + + Onchain + + + {/* Federation Balances Section */} + + + + {t('wallet.federation-balances')} + + + + + + + + Deposit and withdraw bitcoin to/from the federation + + + + {/* Node's Onchain Wallet Section */} + + + + {t('wallet.node-onchain-wallet')} + + + + + + + + Manage bitcoin in the gateway's onchain wallet + + + + ); +}); diff --git a/apps/router/src/gateway-ui/components/walletCard/WalletCard.tsx b/apps/router/src/gateway-ui/components/walletCard/WalletCard.tsx index 9ae465889..cfcf942bc 100644 --- a/apps/router/src/gateway-ui/components/walletCard/WalletCard.tsx +++ b/apps/router/src/gateway-ui/components/walletCard/WalletCard.tsx @@ -1,155 +1,17 @@ -import React, { useMemo } from 'react'; -import { Box, Button, Flex, Text, Square } from '@chakra-ui/react'; -import { formatValue, useTranslation } from '@fedimint/utils'; -import { MSats } from '@fedimint/types'; -import { PieChart } from 'react-minimal-pie-chart'; -import { FaArrowDown, FaArrowUp } from 'react-icons/fa'; -import { - GATEWAY_APP_ACTION_TYPE, - WalletModalAction, - WalletModalType, -} from '../../../types/gateway'; -import { useGatewayContext } from '../../../hooks'; -import { useBalanceCalculations } from '../../hooks/useBalanceCalculations'; +import React from 'react'; +import { Flex } from '@chakra-ui/react'; +import { BalancesSummary } from './BalancesSummary'; +import { OnchainCard } from './OnchainCard'; +import { EcashCard } from './EcashCard'; +import { LightningCard } from './LightningCard'; export const WalletCard = React.memo(function WalletCard(): JSX.Element { - const { t } = useTranslation(); - const { state, dispatch } = useGatewayContext(); - const balanceAmounts = useBalanceCalculations(state.balances ?? undefined); - - const balanceData = useMemo( - () => [ - { - title: t('wallet.ecash'), - value: balanceAmounts.ecash, - formattedValue: formatValue( - balanceAmounts.ecash as MSats, - state.unit, - true - ), - color: '#FF6384', - }, - { - title: t('wallet.lightning'), - value: balanceAmounts.lightning, - formattedValue: formatValue( - balanceAmounts.lightning as MSats, - state.unit, - true - ), - color: '#36A2EB', - }, - { - title: t('wallet.onchain'), - value: balanceAmounts.onchain, - formattedValue: formatValue( - balanceAmounts.onchain as MSats, - state.unit, - true - ), - color: '#FFCE56', - }, - ], - [balanceAmounts, state.unit, t] - ); - - const totalBalance = useMemo( - () => formatValue(balanceAmounts.total as MSats, state.unit, true), - [balanceAmounts.total, state.unit] - ); - - const handleModalOpen = (action: WalletModalAction) => { - const federations = state.gatewayInfo?.federations; - if (!federations?.length) { - console.error('No federations available'); - return; - } - dispatch({ - type: GATEWAY_APP_ACTION_TYPE.SET_WALLET_MODAL_STATE, - payload: { - action, - type: WalletModalType.Onchain, - selectedFederation: federations[0], - showSelector: true, - isOpen: true, - }, - }); - }; - return ( - - {/* Balance section */} - - {balanceData.map((item) => ( - - - - - {item.title}: - - - - {item.formattedValue} - - - ))} - - - - {t('wallet.total')}: - - - {totalBalance} - - - - - - {/* Pie chart section */} - - - - - {/* Deposit/Withdraw section */} - - - - + + + + + ); }); diff --git a/apps/router/src/gateway-ui/components/walletModal/WalletModal.tsx b/apps/router/src/gateway-ui/components/walletModal/WalletModal.tsx index 4024538e5..6c1cac4b2 100644 --- a/apps/router/src/gateway-ui/components/walletModal/WalletModal.tsx +++ b/apps/router/src/gateway-ui/components/walletModal/WalletModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Modal, ModalOverlay, @@ -10,55 +10,29 @@ import { import { useTranslation } from '@fedimint/utils'; import { capitalizeFirstLetters } from '../../utils'; import ReceiveEcash from './receive/ReceiveEcash'; -// import ReceiveLightning from './receive/ReceiveLightning'; +import ReceiveLightning from './receive/ReceiveLightning'; import ReceiveOnchain from './receive/ReceiveOnchain'; import SendEcash from './send/SendEcash'; -// import SendLightning from './send/SendLightning'; +import SendLightning from './send/SendLightning'; import SendOnchain from './send/SendOnchain'; -import { WalletActionSelector } from './WalletActionSelector'; -import { - GATEWAY_APP_ACTION_TYPE, - WalletModalAction, - WalletModalType, -} from '../../../types/gateway'; import { useGatewayContext } from '../../../hooks'; +import { GATEWAY_APP_ACTION_TYPE } from '../../../types/gateway'; export const WalletModal: React.FC = () => { const { t } = useTranslation(); const { state, dispatch } = useGatewayContext(); - const [showSelector, setShowSelector] = useState(true); - - const handleActionChange = (action: WalletModalAction) => { - dispatch({ - type: GATEWAY_APP_ACTION_TYPE.SET_WALLET_MODAL_STATE, - payload: { - ...state.walletModalState, - action, - }, - }); - }; - - const handleTypeChange = (type: WalletModalType) => { - dispatch({ - type: GATEWAY_APP_ACTION_TYPE.SET_WALLET_MODAL_STATE, - payload: { - ...state.walletModalState, - type, - }, - }); - }; const renderActionComponent = () => { const components = { - [WalletModalAction.Receive]: { - [WalletModalType.Ecash]: ReceiveEcash, - // [WalletModalType.Lightning]: ReceiveLightning, - [WalletModalType.Onchain]: ReceiveOnchain, + receive: { + ecash: ReceiveEcash, + lightning: ReceiveLightning, + onchain: ReceiveOnchain, }, - [WalletModalAction.Send]: { - [WalletModalType.Ecash]: SendEcash, - // [WalletModalType.Lightning]: SendLightning, - [WalletModalType.Onchain]: SendOnchain, + send: { + ecash: SendEcash, + lightning: SendLightning, + onchain: SendOnchain, }, }; @@ -67,39 +41,45 @@ export const WalletModal: React.FC = () => { return Component ? : null; }; + const getModalTitle = () => { + const action = t(`wallet.${state.walletModalState.action}`); + const type = t(`wallet.${state.walletModalState.type}`); + + // Special cases for peg-in/peg-out + if (state.walletModalState.type === 'onchain') { + if (state.walletModalState.action === 'receive') { + return t('wallet.peg-in'); + } + if (state.walletModalState.action === 'send') { + return t('wallet.peg-out'); + } + } + + return capitalizeFirstLetters(`${action} ${type}`); + }; + return ( { dispatch({ type: GATEWAY_APP_ACTION_TYPE.SET_WALLET_MODAL_STATE, - payload: { ...state.walletModalState, isOpen: false }, + payload: { + ...state.walletModalState, + isOpen: false, + }, }); - setShowSelector(true); }} size='md' + isCentered > - + - - {capitalizeFirstLetters( - t(`wallet.${state.walletModalState.action}`) + - ' ' + - t(`wallet.${state.walletModalState.type}`) - )} + + {getModalTitle()} - - {showSelector && ( - - )} - {renderActionComponent()} - + {renderActionComponent()} ); diff --git a/apps/router/src/hooks/gateway/useGateway.tsx b/apps/router/src/hooks/gateway/useGateway.tsx index ec7118b48..f3bc9e4ed 100644 --- a/apps/router/src/hooks/gateway/useGateway.tsx +++ b/apps/router/src/hooks/gateway/useGateway.tsx @@ -54,19 +54,21 @@ export const useGatewayInfo = (): GatewayInfo => { export const useLoadGateway = () => { const { state, dispatch, api, id } = useGatewayContext(); - if (sessionStorage.getItem(id)) { - dispatch({ - type: GATEWAY_APP_ACTION_TYPE.SET_NEEDS_AUTH, - payload: false, - }); - } + + useEffect(() => { + if (sessionStorage.getItem(id)) { + dispatch({ + type: GATEWAY_APP_ACTION_TYPE.SET_NEEDS_AUTH, + payload: false, + }); + } + }, [id, dispatch]); useEffect(() => { if (!state.needsAuth) { const fetchInfoAndConfigs = async () => { try { const gatewayInfo = await api.fetchInfo(); - const configs = await api.fetchConfigs(); const updatedFederations = gatewayInfo.federations.map( @@ -119,6 +121,7 @@ export const useLoadGateway = () => { fetchInfoAndConfigs(); fetchBalances(); }, 5000); + return () => clearInterval(interval); } }, [state.needsAuth, api, dispatch]); diff --git a/apps/router/src/languages/en.json b/apps/router/src/languages/en.json index 12b182937..889d83f35 100644 --- a/apps/router/src/languages/en.json +++ b/apps/router/src/languages/en.json @@ -425,6 +425,7 @@ "error-message": "Error connecting to federation: {{error}}" }, "wallet": { + "balances-summary": "Balances Summary", "title": "Wallet", "ecash": "Ecash", "lightning": "Lightning", diff --git a/apps/router/src/types/gateway.tsx b/apps/router/src/types/gateway.tsx index c75356e39..cf744af3e 100644 --- a/apps/router/src/types/gateway.tsx +++ b/apps/router/src/types/gateway.tsx @@ -21,7 +21,7 @@ export enum WalletModalAction { } export enum WalletModalType { Ecash = 'ecash', - // Lightning = 'lightning', + Lightning = 'lightning', Onchain = 'onchain', }