From 843b957fb6e3f9347e44c60c5a78d372aec2bb26 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:38:27 -0700 Subject: [PATCH 01/62] ui: basic transaction layout/style --- .../UnconfirmedTransaction.tsx | 32 +++++++++++++++++++ .../UnconfirmedTransactions.styles.ts | 32 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.tsx create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.tsx new file mode 100644 index 0000000..c40171f --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import * as S from './UnconfirmedTransaction.styles' + +interface UnconfirmedTransactionProps { + tx_id: string; + date_created: string; + amount: string; + feeAmount?: string; +} + +const UnconfirmedTransaction: React.FC = ({ tx_id, date_created, amount, feeAmount }) => { + // Implement your component logic here + + return ( + + + Transaction ID + {tx_id} + + + Date Created + {date_created} + + + Amount + {amount} + + + ); +}; + +export default UnconfirmedTransaction; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts new file mode 100644 index 0000000..2f3c8c7 --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts @@ -0,0 +1,32 @@ +import styled, { css } from 'styled-components'; + +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; +`; +export const PanelHeaderText = styled.span` + font-size: 1.5rem; + font-weight: bold; + padding-bottom: 1rem; +`; +export const PanelContent = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; +`; + +export const TransactionWrapper = styled.div` + display: flex; + width: 80%; +`; +export const ButtonWrapper = styled.div` + disply: flex; + width: 20%; +`; + +export const RowWrapper = styled.div` + display: flex; + flex-direction: row; + gap: 1rem; +`; From 8e0f31075bb37ced6d02f71f30dc7bee42115074 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:38:55 -0700 Subject: [PATCH 02/62] ui: basic ui/style (unconf tx) --- .../UnconfirmedTransaction.styles.ts | 24 +++++++++++++++++ .../UnconfirmedTransactions.tsx | 26 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts new file mode 100644 index 0000000..84ddd6e --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts @@ -0,0 +1,24 @@ +import styled from 'styled-components'; + +export const TransactionWrapper = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 1rem; +`; + +export const DataWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 0.8rem; +`; + +export const Value = styled.span` + font-size: 1.5rem; + color: var(--text-main-color); + font-weight: semibold; +`; +export const Label = styled.span` + font-size: 1rem; + color: var(--text-nft-light-color); +`; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx new file mode 100644 index 0000000..d22a0c6 --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import * as S from './UnconfirmedTransactions.styles'; +import UnconfirmedTransaction from './UnconfirmedTransaction/UnconfirmedTransaction'; + +import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; +interface UnconfirmedTransactionsProps { + // Define your component props here +} + +const UnconfirmedTransactions: React.FC = () => { + // Add your component logic here + + return ( + + Unconfirmed Transactions + + + + Replace + + + + ); +}; + +export default UnconfirmedTransactions; \ No newline at end of file From abcd7fd4a6981304dc0f223fbabe7000df54cd69 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 13:51:12 -0700 Subject: [PATCH 03/62] refac: reorganize struc --- .../UnconfirmedTransaction/UnconfirmedTransaction.styles.ts | 0 .../UnconfirmedTransaction/UnconfirmedTransaction.tsx | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/components/nft-dashboard/unconfirmed-transactions/{ => components}/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts (100%) rename src/components/nft-dashboard/unconfirmed-transactions/{ => components}/UnconfirmedTransaction/UnconfirmedTransaction.tsx (100%) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts rename to src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransaction/UnconfirmedTransaction.tsx rename to src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx From 11f94adfcf1fdf470c76291bc63de3833dbe0e0a Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:01:53 -0700 Subject: [PATCH 04/62] ui: basic button/modal setup --- .../ButtonTrigger/ButtonTrigger.tsx | 28 +++++++++++++++++++ .../components/Modal/UnconfirmedTxModal.tsx | 18 ++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx new file mode 100644 index 0000000..003f750 --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx @@ -0,0 +1,28 @@ +import React, { useState } from 'react'; +import UnconfirmedTxModal from '../Modal/UnconfirmedTxModal'; +import { useAppSelector } from '@app/hooks/reduxHooks'; +import * as S from '../../../Balance/components/TopUpBalanceButton/TopUpBalanceButton.styles'; + +const ButtonTrigger: React.FC = () => { + const { theme } = useAppSelector((state) => state.theme); + const [isModalOpen, setIsModalOpen] = useState(false); + + const handleButtonClick = () => { + setIsModalOpen(true); + }; + + const handleModalClose = () => { + setIsModalOpen(false); + }; + + return ( + <> + + Unconfirmed Transactions + + + + ); +}; + +export default ButtonTrigger; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx new file mode 100644 index 0000000..64d060a --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import * as S from './UnconfirmedTxModal.styles'; +import UnconfirmedTransactions from '../../UnconfirmedTransactions'; +interface UnconfirmedTxModalProps { + isOpen: boolean; + onOpenChange: () => void; +} + +const UnconfirmedTxModal: React.FC = ({isOpen, onOpenChange}) => { + + return ( + + + + ); +}; + +export default UnconfirmedTxModal; \ No newline at end of file From 56b1cc5a32dcd09027c9f299ad590a9c0915b4bc Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:19:38 -0700 Subject: [PATCH 05/62] integrate modal --- .../activityStory/ActivityStory.tsx | 7 ++-- .../UnconfirmedTransactions.styles.ts | 3 +- .../UnconfirmedTransactions.tsx | 36 ++++++++++--------- .../ButtonTrigger/ButtonTrigger.tsx | 6 ++-- .../Modal/UnconfirmedTxModal.styles.ts | 14 ++++++++ .../components/Modal/UnconfirmedTxModal.tsx | 19 +++++----- .../UnconfirmedTransaction.styles.ts | 1 + 7 files changed, 54 insertions(+), 32 deletions(-) create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts diff --git a/src/components/nft-dashboard/activityStory/ActivityStory.tsx b/src/components/nft-dashboard/activityStory/ActivityStory.tsx index 43f45fd..e824b4b 100644 --- a/src/components/nft-dashboard/activityStory/ActivityStory.tsx +++ b/src/components/nft-dashboard/activityStory/ActivityStory.tsx @@ -22,6 +22,7 @@ import { Filler, } from 'chart.js'; import { TransactionCard } from './ActivityStoryItem/ActivityStoryItem.styles'; +import ButtonTrigger from '../unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger'; ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler); @@ -192,14 +193,14 @@ export const ActivityStory: React.FC = () => { {t('nft.viewTransactions')} - +
- {isLoading ? : {activityContent}} + {isLoading ? : {activityContent}}
- {isLoading ? : {activityContent}} + {isLoading ? : {activityContent}} ); }; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts index 2f3c8c7..c0f8613 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts @@ -28,5 +28,6 @@ export const ButtonWrapper = styled.div` export const RowWrapper = styled.div` display: flex; flex-direction: row; - gap: 1rem; + gap: 2rem; + align-items: center; `; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index d22a0c6..672fd98 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -1,26 +1,30 @@ import React from 'react'; -import * as S from './UnconfirmedTransactions.styles'; -import UnconfirmedTransaction from './UnconfirmedTransaction/UnconfirmedTransaction'; +import * as S from './UnconfirmedTransactions.styles'; +import UnconfirmedTransaction from './components/UnconfirmedTransaction/UnconfirmedTransaction'; import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; interface UnconfirmedTransactionsProps { - // Define your component props here + // Define your component props here } const UnconfirmedTransactions: React.FC = () => { - // Add your component logic here + // Add your component logic here - return ( - - Unconfirmed Transactions - - - - Replace - - - - ); + return ( + + Unconfirmed Transactions + + + + + + + Replace + + + + + ); }; -export default UnconfirmedTransactions; \ No newline at end of file +export default UnconfirmedTransactions; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx index 003f750..6a91fe1 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx @@ -2,8 +2,10 @@ import React, { useState } from 'react'; import UnconfirmedTxModal from '../Modal/UnconfirmedTxModal'; import { useAppSelector } from '@app/hooks/reduxHooks'; import * as S from '../../../Balance/components/TopUpBalanceButton/TopUpBalanceButton.styles'; - -const ButtonTrigger: React.FC = () => { +interface ButtonTriggerProps { +amount: number; +} +const ButtonTrigger: React.FC = ({amount}) => { const { theme } = useAppSelector((state) => state.theme); const [isModalOpen, setIsModalOpen] = useState(false); diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts new file mode 100644 index 0000000..8097a92 --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts @@ -0,0 +1,14 @@ +import styled from 'styled-components'; +import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; + +export const Modal = styled(BaseModal)` + max-width: fit-content !important; + width: fit-content !important; + min-width: 50vw; + + .ant-modal-content { + width: 100%; + min-width: 50vw; + padding: 2rem; + } +`; \ No newline at end of file diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx index 64d060a..71d0c9f 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx @@ -2,17 +2,16 @@ import React from 'react'; import * as S from './UnconfirmedTxModal.styles'; import UnconfirmedTransactions from '../../UnconfirmedTransactions'; interface UnconfirmedTxModalProps { - isOpen: boolean; - onOpenChange: () => void; + isOpen: boolean; + onOpenChange: () => void; } -const UnconfirmedTxModal: React.FC = ({isOpen, onOpenChange}) => { - - return ( - - - - ); +const UnconfirmedTxModal: React.FC = ({ isOpen, onOpenChange }) => { + return ( + + + + ); }; -export default UnconfirmedTxModal; \ No newline at end of file +export default UnconfirmedTxModal; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts index 84ddd6e..f76aa06 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts @@ -1,6 +1,7 @@ import styled from 'styled-components'; export const TransactionWrapper = styled.div` + width: 100%; display: flex; flex-direction: row; justify-content: space-between; From 7dfe12af0b7aa78fa82eee752fb26175ba2ff0cf Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:03:07 -0700 Subject: [PATCH 06/62] update styling --- .../UnconfirmedTransactions.styles.ts | 5 ++++- .../UnconfirmedTransaction.styles.ts | 8 +++++--- .../UnconfirmedTransaction/UnconfirmedTransaction.tsx | 11 ++++++++--- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts index c0f8613..d87d73a 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts @@ -22,10 +22,13 @@ export const TransactionWrapper = styled.div` `; export const ButtonWrapper = styled.div` disply: flex; - width: 20%; `; export const RowWrapper = styled.div` +padding: .5rem 1.5rem .5rem .5rem; + border-radius: 0.5rem; + box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.12), 0px 4px 5px rgba(0, 0, 0, 0.14), 0px 2px 4px -1px rgba(0, 0, 0, 0.2); + background-color: var(--additional-background-color); display: flex; flex-direction: row; gap: 2rem; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts index f76aa06..e827e76 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts @@ -1,7 +1,9 @@ import styled from 'styled-components'; export const TransactionWrapper = styled.div` + width: 100%; + padding: 1rem; display: flex; flex-direction: row; justify-content: space-between; @@ -11,15 +13,15 @@ export const TransactionWrapper = styled.div` export const DataWrapper = styled.div` display: flex; flex-direction: column; - gap: 0.8rem; + gap: 0.3rem; `; export const Value = styled.span` - font-size: 1.5rem; + font-size: 1rem; color: var(--text-main-color); font-weight: semibold; `; export const Label = styled.span` - font-size: 1rem; + font-size: 0.8rem; color: var(--text-nft-light-color); `; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx index c40171f..9a0e46f 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx @@ -1,5 +1,6 @@ import React from 'react'; import * as S from './UnconfirmedTransaction.styles' +import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; interface UnconfirmedTransactionProps { tx_id: string; @@ -12,18 +13,22 @@ const UnconfirmedTransaction: React.FC = ({ tx_id, // Implement your component logic here return ( + + {tx_id} Transaction ID - {tx_id} + + {date_created} Date Created - {date_created} + + {amount} Amount - {amount} + ); From 84a2aaba72f31a7e5baa977c20581de90cdedc4d Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 15:11:06 -0700 Subject: [PATCH 07/62] fix: window can now be closed --- .../components/Modal/UnconfirmedTxModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx index 71d0c9f..6b01709 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx @@ -8,7 +8,7 @@ interface UnconfirmedTxModalProps { const UnconfirmedTxModal: React.FC = ({ isOpen, onOpenChange }) => { return ( - + ); From da27478108075dcc6c00e2e9ac8f6404e1ebc015 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:07:43 -0700 Subject: [PATCH 08/62] basic layout for replace tx panel --- .../UnconfirmedTransactions.tsx | 49 ++++++++++++------ .../components/Modal/UnconfirmedTxModal.tsx | 4 +- .../ReplaceTransaction.styles.ts | 50 +++++++++++++++++++ .../ReplaceTransaction/ReplaceTransaction.tsx | 32 ++++++++++++ 4 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts create mode 100644 src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index 672fd98..daf961b 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -1,28 +1,45 @@ -import React from 'react'; +import React, { useState } from 'react'; import * as S from './UnconfirmedTransactions.styles'; import UnconfirmedTransaction from './components/UnconfirmedTransaction/UnconfirmedTransaction'; - +import ReplaceTransaction from './components/ReplaceTransaction/ReplaceTransaction'; import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; interface UnconfirmedTransactionsProps { - // Define your component props here + transactions: any[]; //TODO: update the type } -const UnconfirmedTransactions: React.FC = () => { - // Add your component logic here +const UnconfirmedTransactions: React.FC = ({}) => { + const [isReplacingTransaction, setIsReplacingTransaction] = useState(false); + + const handleOpenReplaceTransaction = () => { + setIsReplacingTransaction(true); + }; + const handleCancelReplaceTransaction = () => { + setIsReplacingTransaction(false); + }; + const onReplaceTransaction = () => {}; return ( - Unconfirmed Transactions - - - - - - - Replace - - - + {isReplacingTransaction ? ( + <> + Replace Transaction + + + ) : ( + <> + Unconfirmed Transactions + + + + + + + Replace + + + + + )} ); }; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx index 6b01709..51cc485 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx @@ -5,11 +5,13 @@ interface UnconfirmedTxModalProps { isOpen: boolean; onOpenChange: () => void; } +const transactions:any = [ +] const UnconfirmedTxModal: React.FC = ({ isOpen, onOpenChange }) => { return ( - + ); }; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts new file mode 100644 index 0000000..c7a10ac --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts @@ -0,0 +1,50 @@ +import styled, { css } from 'styled-components'; +import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; + +interface ResponsiveProps { + isMobile: boolean; +} +export const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 1.5rem; +`; + +export const FieldDisplay = styled.div` + display: flex; + flex-direction: column; + gap: 0.5rem; +`; + +export const ValueWrapper = styled.div` + ${(props) => + props.isMobile && + css` + width: 100%; + `} + width: 50%; + padding: 1rem; + background-color: var(--additional-background-color); + border-radius: 0.5rem; +`; +export const FieldLabel = styled.span` + font-size: 1.2rem; + color: var(--text-main-color); + font-weight: semibold; +`; +export const FieldValue = styled.span` + font-size: 1rem; + color: var(--text-main-color); +`; + +export const ButtonRow = styled.div` + display: flex; + flex-direction: row; + gap: 1rem; + justify-content: center; + pading: .5rem 2rem; + `; + +export const Button = styled(BaseButton)` + +`; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx new file mode 100644 index 0000000..60ebadb --- /dev/null +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import * as S from './ReplaceTransaction.styles'; +import { useResponsive } from '@app/hooks/useResponsive'; +interface ReplaceTransactionProps { + onCancel: () => void; + onReplace: () => void; +} + +const ReplaceTransaction: React.FC = ({onCancel, onReplace}) => { + const { isDesktop } = useResponsive(); + return ( + + + Transaction ID + + {' '} + 123456 + + + + Amount + 123456 + + + Cancel + Replace + + + ); +}; + +export default ReplaceTransaction; From 864919e90b09e55753bfc87611b7c5d291c2e52c Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:11:51 -0700 Subject: [PATCH 09/62] refac: move result screen files to own folder --- .../components/{ => ResultScreen}/ResultScreen.styles.ts | 0 .../SendForm/components/{ => ResultScreen}/ResultScreen.tsx | 0 .../nft-dashboard/Balance/components/SendModal/SendModal.tsx | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename src/components/nft-dashboard/Balance/components/SendForm/components/{ => ResultScreen}/ResultScreen.styles.ts (100%) rename src/components/nft-dashboard/Balance/components/SendForm/components/{ => ResultScreen}/ResultScreen.tsx (100%) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen.styles.ts b/src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.styles.ts similarity index 100% rename from src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen.styles.ts rename to src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.styles.ts diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen.tsx b/src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.tsx similarity index 100% rename from src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen.tsx rename to src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.tsx diff --git a/src/components/nft-dashboard/Balance/components/SendModal/SendModal.tsx b/src/components/nft-dashboard/Balance/components/SendModal/SendModal.tsx index 573d5a8..c6d2edf 100644 --- a/src/components/nft-dashboard/Balance/components/SendModal/SendModal.tsx +++ b/src/components/nft-dashboard/Balance/components/SendModal/SendModal.tsx @@ -3,7 +3,7 @@ import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; import SendForm from '../SendForm/SendForm'; import * as S from './SendModal.styles'; -import ResultScreen from '../SendForm/components/ResultScreen'; +import ResultScreen from '../SendForm/components/ResultScreen/ResultScreen'; interface SendModalProps { isOpen: boolean; From 21feb990e202524635183b42492638504f98d8e2 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 16:57:50 -0700 Subject: [PATCH 10/62] refac: tiered fees component --- .../components/SendForm/SendForm.styles.ts | 71 ++-------- .../Balance/components/SendForm/SendForm.tsx | 134 ++++-------------- .../TieredFees/TieredFees.styles.ts | 48 +++++++ .../components/TieredFees/TieredFees.tsx | 114 +++++++++++++++ 4 files changed, 201 insertions(+), 166 deletions(-) create mode 100644 src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts create mode 100644 src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.styles.ts b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.styles.ts index 8161ed6..cf19c1c 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.styles.ts +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.styles.ts @@ -13,20 +13,6 @@ export const TextRow = styled.div` gap: 1rem; `; -export const SubCard = styled(BaseCard)<{ $isMobile?: boolean }>` - width: 30%; - ${(props) => - props.$isMobile && - css` - width: 100%; - `} - background-color: var(--additional-background-color); - cursor: pointer; - box-shadow: 0px 0px 10px 0px var(--shadow-color); - .ant-card-body { - padding: 1rem 2rem; - } -`; export const SendBody = styled(BaseRow)` padding-bottom: 1rem; `; @@ -45,28 +31,16 @@ export const FormHeader = styled.span` padding-bottom: 1rem; `; -export const SubCardHeader = styled.span` - font-size: 1.5rem; -`; - export const InputHeader = styled.span` font-size: 1.5rem; `; -export const SubCardAmount = styled.span` - font-size: 1.5rem; -`; -export const SubCardContent = styled.div` - font-size: 1.3rem; - height: 100%; +export const RBFWrapper = styled.div` display: flex; - justify-content: space-around; - flex-direction: column; + flex-direction: row; + gap: 1rem; align-items: center; - text-align: center; - gap: 3rem; - padding-top: 1rem; - padding-bottom: 1rem; + justify-content: center; `; export const InputWrapper = styled.div` @@ -75,22 +49,6 @@ export const InputWrapper = styled.div` flex-direction: column; gap: 0.5rem; `; -export const TiersRow = styled.div` - display: flex; - flex-direction: row; - gap: 1rem; - justify-content: space-around; -`; - -export const TiersCol = styled.div` - display: flex; - flex-direction: column; - gap: 1rem; - justify-content: space-around; -`; -export const SendFormButton = styled(BaseButton)` - width: 100%; -`; export const TiersContainer = styled.div` display: flex; flex-direction: column; @@ -107,6 +65,10 @@ export const TiersContainer = styled.div` border: 1px solid var(--error-color); } `; +export const SendFormButton = styled(BaseButton)` + width: 100%; +`; + export const BalanceInfo = styled.small` color: var(--subtext-color); `; @@ -125,19 +87,16 @@ export const AddressText = styled.span` text-decoration: underline; color: var(--text-main-color); `; - -export const RateValueWrapper = styled.div` +export const TiersRow = styled.div` display: flex; - flex-direction: column; + flex-direction: row; gap: 1rem; + justify-content: space-around; `; -export const RateValue = styled.span` - color: green; -`; -export const RBFWrapper = styled.div` + +export const TiersCol = styled.div` display: flex; - flex-direction: row; + flex-direction: column; gap: 1rem; - align-items: center; - justify-content: center; + justify-content: space-around; `; diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 0337738..c4f0e2a 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -9,36 +9,26 @@ import { truncateString } from '@app/utils/utils'; import useBalanceData from '@app/hooks/useBalanceData'; import { BaseCheckbox } from '@app/components/common/BaseCheckbox/BaseCheckbox'; import config from '@app/config/config'; +import TieredFees from './components/TieredFees/TieredFees'; interface SendFormProps { onSend: (status: boolean, address: string, amount: number, txid?: string, message?: string) => void; } -interface FeeRecommendation { - fastestFee: number; - halfHourFee: number; - hourFee: number; - economyFee: number; - minimumFee: number; -} - interface PendingTransaction { txid: string; feeRate: number; timestamp: string; // ISO format string } -type tiers = 'low' | 'med' | 'high'; -type Fees = { - [key in tiers]: number; -}; +export type tiers = 'low' | 'med' | 'high'; const SendForm: React.FC = ({ onSend }) => { const { balanceData, isLoading } = useBalanceData(); const { isTablet, isDesktop } = useResponsive(); const [loading, setLoading] = useState(false); - const [selectedTier, setSelectedTier] = useState('low'); + const [isDetailsOpen, setIsDetailsOpen] = useState(false); const [inValidAmount, setInvalidAmount] = useState(false); @@ -46,15 +36,22 @@ const SendForm: React.FC = ({ onSend }) => { const [amountWithFee, setAmountWithFee] = useState(null); + const [fee, setFee] = useState(0); const [formData, setFormData] = useState({ address: '', amount: '1', }); - const [fees, setFees] = useState({ low: 0, med: 0, high: 0 }); + useEffect(() => { + + console.log(fee) + setAmountWithFee(parseInt(formData.amount) + fee); + }, [fee, formData.amount]); - const handleTierChange = (tier: any) => { - setSelectedTier(tier.id); + + + const handleFeeChange = (fee: number) => { + setFee(fee); }; const isValidAddress = (address: string) => { @@ -83,7 +80,7 @@ const SendForm: React.FC = ({ onSend }) => { setLoading(true); - const selectedFee = selectedTier ? fees[selectedTier] : fees.low; // Default to low if not selected + const selectedFee = fee; // Default to low if not selected const transactionRequest = { choice: 1, // Default to choice 1 for new transactions @@ -133,31 +130,6 @@ const SendForm: React.FC = ({ onSend }) => { } }; - useEffect(() => { - const fetchFees = async () => { - try { - const response = await fetch('https://mempool.space/api/v1/fees/recommended'); - const data: FeeRecommendation = await response.json(); - - setFees({ - low: data.economyFee, - med: data.halfHourFee, - high: data.fastestFee, - }); - } catch (error) { - console.error('Failed to fetch fees:', error); - } - }; - - fetchFees(); - }, []); - - useEffect(() => { - if (selectedTier) { - setAmountWithFee(parseInt(formData.amount) + fees[selectedTier]); - } - }, [fees]); - useEffect(() => { if (formData.amount.length <= 0 || (balanceData && parseInt(formData.amount) > balanceData.latest_balance)) { setInvalidAmount(true); @@ -176,77 +148,13 @@ const SendForm: React.FC = ({ onSend }) => { ); - const TieredFees = () => ( - <> - handleTierChange({ id: 'low', rate: fees.low })} - className={`tier-hover ${selectedTier === 'low' ? 'selected' : ''} ${ - selectedTier === 'low' && inValidAmount ? 'invalidAmount' : '' - } `} - > - - - {' '} - {`Low`} -
- {`Priority`} -
- - {`${fees.low} sat/vB`} - {`${fees.low} Sats`} - -
-
- - handleTierChange({ id: 'med', rate: fees.med })} - className={`tier-hover ${selectedTier === 'med' ? 'selected' : ''} ${ - selectedTier === 'med' && inValidAmount ? 'invalidAmount' : '' - } `} - > - - {`Medium`} -
- {`Priority`} - - {`${fees.med} sat/vB`} - {`${fees.med} Sats`} - -
-
- - handleTierChange({ id: 'high', rate: fees.high })} - className={`tier-hover ${selectedTier === 'high' ? 'selected' : ''} ${ - selectedTier === 'high' && inValidAmount ? 'invalidAmount' : '' - } `} - > - - - {' '} - {`High`} -
- {`Priority`} -
- - {`${fees.high} sat/vB`} - {`${fees.high} Sats`} - -
-
- - ); - const detailsPanel = () => ( - {`Amount = ${selectedTier && amountWithFee ? amountWithFee : ''}`} + {`Amount = ${amountWithFee ? amountWithFee : ''}`} - {inValidAmount && selectedTier && Invalid Amount} + {inValidAmount && Invalid Amount}
@@ -262,11 +170,17 @@ const SendForm: React.FC = ({ onSend }) => { {isDesktop || isTablet ? ( - + ) : ( - + )} diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts new file mode 100644 index 0000000..1095fb7 --- /dev/null +++ b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts @@ -0,0 +1,48 @@ +import styled, {css} from 'styled-components'; +import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; + + +export const TierCard = styled(BaseCard)<{ $isMobile?: boolean }>` + width: 30%; + ${(props) => + props.$isMobile && + css` + width: 100%; + `} + background-color: var(--additional-background-color); + cursor: pointer; + box-shadow: 0px 0px 10px 0px var(--shadow-color); + .ant-card-body { + padding: 1rem 2rem; + } +`; + +export const TierCardHeader = styled.span` + font-size: 1.5rem; +`; +export const TierCardAmount = styled.span` + font-size: 1.5rem; +`; +export const TierCardContent = styled.div` + font-size: 1.3rem; + height: 100%; + display: flex; + justify-content: space-around; + flex-direction: column; + align-items: center; + text-align: center; + gap: 3rem; + padding-top: 1rem; + padding-bottom: 1rem; +`; + + + +export const RateValueWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; +`; +export const RateValue = styled.span` + color: green; +`; diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx new file mode 100644 index 0000000..a8f9ba4 --- /dev/null +++ b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx @@ -0,0 +1,114 @@ +import React, { useEffect, useState } from 'react'; +import * as S from './TieredFees.styles'; +import { useResponsive } from '@app/hooks/useResponsive'; +import { tiers } from '../../SendForm'; + +interface FeeRecommendation { + fastestFee: number; + halfHourFee: number; + hourFee: number; + economyFee: number; + minimumFee: number; +} +type Fees = { + [key in tiers]: number; +}; +interface TieredFeesProps { + // Define the props for your component here + handleFeeChange: (fee: number) => void; + inValidAmount: boolean; +} + +const TieredFees: React.FC = ({ inValidAmount, handleFeeChange }) => { + const { isDesktop } = useResponsive(); + const [fees, setFees] = useState({ low: 0, med: 0, high: 0 }); + const [selectedTier, setSelectedTier] = useState('low'); + + useEffect(() => { + const fetchFees = async () => { + try { + const response = await fetch('https://mempool.space/api/v1/fees/recommended'); + const data: FeeRecommendation = await response.json(); + console.log(data); + setFees({ + low: data.economyFee, + med: data.halfHourFee, + high: data.fastestFee, + }); + } catch (error) { + console.error('Failed to fetch fees:', error); + } + }; + + fetchFees(); + }, []); + const handleTierChange = (tier: any) => { + setSelectedTier(tier.id); + }; + + useEffect(() => { + handleFeeChange(fees[selectedTier as tiers]); + }, [selectedTier]); + return ( + <> + handleTierChange({ id: 'low', rate: fees.med })} + className={`tier-hover ${selectedTier === 'low' ? 'selected' : ''} ${ + selectedTier === 'low' && inValidAmount ? 'invalidAmount' : '' + } `} + > + + {`Low`} +
+ {`Priority`} + + {`${fees.low} sat/vB`} + {`${fees.low} Sats`} + +
+
+ + handleTierChange({ id: 'med', rate: fees.med })} + className={`tier-hover ${selectedTier === 'med' ? 'selected' : ''} ${ + selectedTier === 'med' && inValidAmount ? 'invalidAmount' : '' + } `} + > + + {`Medium`} +
+ {`Priority`} + + {`${fees.med} sat/vB`} + {`${fees.med} Sats`} + +
+
+ + handleTierChange({ id: 'high', rate: fees.high })} + className={`tier-hover ${selectedTier === 'high' ? 'selected' : ''} ${ + selectedTier === 'high' && inValidAmount ? 'invalidAmount' : '' + } `} + > + + + {' '} + {`High`} +
+ {`Priority`} +
+ + {`${fees.high} sat/vB`} + {`${fees.high} Sats`} + +
+
+ + ); +}; + +export default TieredFees; From 99a99c670c0b42553cc10ccc81bb2a646ac72023 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 17:25:33 -0700 Subject: [PATCH 11/62] refactor: interactive styling --- .../components/SendForm/SendForm.styles.ts | 11 ------ .../Balance/components/SendForm/SendForm.tsx | 23 ++---------- .../TieredFees/TieredFees.styles.ts | 35 ++++++++++++++++--- .../components/TieredFees/TieredFees.tsx | 8 ++--- .../ReplaceTransaction.styles.ts | 15 +++++++- .../ReplaceTransaction/ReplaceTransaction.tsx | 31 +++++++++++----- 6 files changed, 74 insertions(+), 49 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.styles.ts b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.styles.ts index cf19c1c..cb19afe 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.styles.ts +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.styles.ts @@ -53,17 +53,6 @@ export const TiersContainer = styled.div` display: flex; flex-direction: column; gap: 1rem; - transition: all 0.5s ease; - padding: 1rem; - .tier-hover:hover { - background-color: var(--primary-color); - } - .selected { - border: 1px solid var(--primary-color); - } - .invalidAmount { - border: 1px solid var(--error-color); - } `; export const SendFormButton = styled(BaseButton)` width: 100%; diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index c4f0e2a..bdfb536 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -28,7 +28,6 @@ const SendForm: React.FC = ({ onSend }) => { const { isTablet, isDesktop } = useResponsive(); const [loading, setLoading] = useState(false); - const [isDetailsOpen, setIsDetailsOpen] = useState(false); const [inValidAmount, setInvalidAmount] = useState(false); @@ -43,13 +42,9 @@ const SendForm: React.FC = ({ onSend }) => { }); useEffect(() => { - - console.log(fee) - setAmountWithFee(parseInt(formData.amount) + fee); + setAmountWithFee(parseInt(formData.amount) + fee); }, [fee, formData.amount]); - - const handleFeeChange = (fee: number) => { setFee(fee); }; @@ -168,21 +163,7 @@ const SendForm: React.FC = ({ onSend }) => { RBF Opt In - {isDesktop || isTablet ? ( - - - - ) : ( - - - - )} + ` width: 30%; ${(props) => @@ -12,6 +11,7 @@ export const TierCard = styled(BaseCard)<{ $isMobile?: boolean }>` background-color: var(--additional-background-color); cursor: pointer; box-shadow: 0px 0px 10px 0px var(--shadow-color); + .ant-card-body { padding: 1rem 2rem; } @@ -36,8 +36,6 @@ export const TierCardContent = styled.div` padding-bottom: 1rem; `; - - export const RateValueWrapper = styled.div` display: flex; flex-direction: column; @@ -46,3 +44,32 @@ export const RateValueWrapper = styled.div` export const RateValue = styled.span` color: green; `; +interface ResponsiveProps { + isMobile: boolean; +} +export const TiersWrapper = styled.div` + ${(props) => + props.isMobile && + css` + display: flex; + flex-direction: column; + gap: 1rem; + `} + display: flex; + flex-direction: row; + gap: 1rem; + width: 100%; + justify-content: space-around; + + transition: all 0.5s ease; + padding: 1rem; + .tier-hover:hover { + background-color: var(--primary-color); + } + .selected { + border: 1px solid var(--primary-color); + } + .invalidAmount { + border: 1px solid var(--error-color); + } +`; diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx index a8f9ba4..d8d6b6c 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx @@ -20,7 +20,7 @@ interface TieredFeesProps { } const TieredFees: React.FC = ({ inValidAmount, handleFeeChange }) => { - const { isDesktop } = useResponsive(); + const { isDesktop, isMobile } = useResponsive(); const [fees, setFees] = useState({ low: 0, med: 0, high: 0 }); const [selectedTier, setSelectedTier] = useState('low'); @@ -29,7 +29,6 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange try { const response = await fetch('https://mempool.space/api/v1/fees/recommended'); const data: FeeRecommendation = await response.json(); - console.log(data); setFees({ low: data.economyFee, med: data.halfHourFee, @@ -43,6 +42,7 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange fetchFees(); }, []); const handleTierChange = (tier: any) => { + console.log(tier); setSelectedTier(tier.id); }; @@ -50,7 +50,7 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange handleFeeChange(fees[selectedTier as tiers]); }, [selectedTier]); return ( - <> + handleTierChange({ id: 'low', rate: fees.med })} @@ -107,7 +107,7 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange - + ); }; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts index c7a10ac..21fcdb2 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts @@ -22,7 +22,7 @@ export const ValueWrapper = styled.div` css` width: 100%; `} - width: 50%; + width: 70%; padding: 1rem; background-color: var(--additional-background-color); border-radius: 0.5rem; @@ -48,3 +48,16 @@ export const ButtonRow = styled.div` export const Button = styled(BaseButton)` `; +export const TiersRow = styled.div` + display: flex; + flex-direction: row; + gap: 1rem; + justify-content: space-around; +`; + +export const TiersCol = styled.div` + display: flex; + flex-direction: column; + gap: 1rem; + justify-content: space-around; +`; \ No newline at end of file diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 60ebadb..d67c91d 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -1,29 +1,44 @@ -import React from 'react'; +import React, { useState } from 'react'; import * as S from './ReplaceTransaction.styles'; import { useResponsive } from '@app/hooks/useResponsive'; +import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; interface ReplaceTransactionProps { onCancel: () => void; onReplace: () => void; } -const ReplaceTransaction: React.FC = ({onCancel, onReplace}) => { - const { isDesktop } = useResponsive(); +const ReplaceTransaction: React.FC = ({ onCancel, onReplace }) => { + const { isDesktop, isTablet } = useResponsive(); + const [inValidAmount, setInvalidAmount] = React.useState(false); + + const handleFeeChange = (fee: number) => {}; + return ( Transaction ID - - {' '} + 123456 Amount - 123456 + + 123456 + + {isDesktop || isTablet ? ( + + + + ) : ( + + + + )} - Cancel - Replace + Cancel + Replace ); From 969147f09031971f5a4f5b797a46e580cf26133e Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 22:53:02 -0700 Subject: [PATCH 12/62] fix: styling --- .../Balance/components/SendForm/SendForm.tsx | 2 +- .../TieredFees/TieredFees.styles.ts | 18 ++++++++------- .../components/TieredFees/TieredFees.tsx | 4 ++-- .../ReplaceTransaction.styles.ts | 4 +++- .../ReplaceTransaction/ReplaceTransaction.tsx | 22 +++++++++++-------- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index bdfb536..7c89f57 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -25,7 +25,7 @@ export type tiers = 'low' | 'med' | 'high'; const SendForm: React.FC = ({ onSend }) => { const { balanceData, isLoading } = useBalanceData(); - const { isTablet, isDesktop } = useResponsive(); + const [loading, setLoading] = useState(false); const [isDetailsOpen, setIsDetailsOpen] = useState(false); diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts index e58f200..4f4951c 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts +++ b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts @@ -45,16 +45,11 @@ export const RateValue = styled.span` color: green; `; interface ResponsiveProps { - isMobile: boolean; + $isMobile: boolean; } export const TiersWrapper = styled.div` - ${(props) => - props.isMobile && - css` - display: flex; - flex-direction: column; - gap: 1rem; - `} + + display: flex; flex-direction: row; gap: 1rem; @@ -72,4 +67,11 @@ export const TiersWrapper = styled.div` .invalidAmount { border: 1px solid var(--error-color); } + ${(props) => + props.$isMobile && + css` + display: flex; + flex-direction: column; + gap: 1rem; + `} `; diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx index d8d6b6c..54df409 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx @@ -20,7 +20,7 @@ interface TieredFeesProps { } const TieredFees: React.FC = ({ inValidAmount, handleFeeChange }) => { - const { isDesktop, isMobile } = useResponsive(); + const { isDesktop, isTablet } = useResponsive(); const [fees, setFees] = useState({ low: 0, med: 0, high: 0 }); const [selectedTier, setSelectedTier] = useState('low'); @@ -50,7 +50,7 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange handleFeeChange(fees[selectedTier as tiers]); }, [selectedTier]); return ( - + handleTierChange({ id: 'low', rate: fees.med })} diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts index 21fcdb2..8c9279f 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts @@ -22,8 +22,9 @@ export const ValueWrapper = styled.div` css` width: 100%; `} - width: 70%; + width: 90%; padding: 1rem; + margin-left:1rem; background-color: var(--additional-background-color); border-radius: 0.5rem; `; @@ -38,6 +39,7 @@ export const FieldValue = styled.span` `; export const ButtonRow = styled.div` + padding-top: 1rem; display: flex; flex-direction: row; gap: 1rem; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index d67c91d..525140f 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -27,15 +27,19 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep 123456 - {isDesktop || isTablet ? ( - - - - ) : ( - - - - )} + + + New Fee + + 123456 + + + + Total + + 123456 + + Cancel Replace From 2f01e31413fc135a51dd7eec50b757b2d2f1f113 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Mon, 2 Sep 2024 22:55:48 -0700 Subject: [PATCH 13/62] added placeholder functions --- .../UnconfirmedTransactions.tsx | 2 +- .../ReplaceTransaction/ReplaceTransaction.tsx | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index daf961b..338cd64 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -17,7 +17,7 @@ const UnconfirmedTransactions: React.FC = ({}) => setIsReplacingTransaction(false); }; - const onReplaceTransaction = () => {}; + const onReplaceTransaction = () => {}; //define any behavior after replacing transaction return ( {isReplacingTransaction ? ( diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 525140f..11245ab 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -4,14 +4,19 @@ import { useResponsive } from '@app/hooks/useResponsive'; import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; interface ReplaceTransactionProps { onCancel: () => void; - onReplace: () => void; + onReplace: () => void; } const ReplaceTransaction: React.FC = ({ onCancel, onReplace }) => { const { isDesktop, isTablet } = useResponsive(); const [inValidAmount, setInvalidAmount] = React.useState(false); - const handleFeeChange = (fee: number) => {}; + const handleFeeChange = (fee: number) => {}; //use this for calculating new total + + const handleReplace = () => { + //function to replace transaction + onReplace(); + }; return ( @@ -42,7 +47,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep Cancel - Replace + Replace ); From 8592508ed7ccc168ad751189c9acd87670ea0699 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Tue, 3 Sep 2024 17:02:32 +0200 Subject: [PATCH 14/62] Adding replacement transactions --- .../Balance/components/SendForm/SendForm.tsx | 2 + .../UnconfirmedTransactions.styles.ts | 7 + .../UnconfirmedTransactions.tsx | 56 +++++-- .../components/Modal/UnconfirmedTxModal.tsx | 8 +- .../ReplaceTransaction/ReplaceTransaction.tsx | 140 +++++++++++++----- src/hooks/usePendingTransactions.ts | 19 ++- 6 files changed, 167 insertions(+), 65 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 7c89f57..d7b54b3 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -19,6 +19,7 @@ interface PendingTransaction { txid: string; feeRate: number; timestamp: string; // ISO format string + amount: string; } export type tiers = 'low' | 'med' | 'high'; @@ -102,6 +103,7 @@ const SendForm: React.FC = ({ onSend }) => { txid: result.txid, feeRate: selectedFee, timestamp: new Date().toISOString(), // Capture the current time in ISO format + amount: formData.amount }; // Send the transaction details to the pending-transactions endpoint diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts index d87d73a..0b215e5 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts @@ -22,6 +22,13 @@ export const TransactionWrapper = styled.div` `; export const ButtonWrapper = styled.div` disply: flex; + margin: .5rem; +`; + +export const NoTransactionsText = styled.p` + text-align: center; + color: #888; + font-size: 16px; `; export const RowWrapper = styled.div` diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index 338cd64..6fde9f3 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -3,40 +3,60 @@ import * as S from './UnconfirmedTransactions.styles'; import UnconfirmedTransaction from './components/UnconfirmedTransaction/UnconfirmedTransaction'; import ReplaceTransaction from './components/ReplaceTransaction/ReplaceTransaction'; import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; -interface UnconfirmedTransactionsProps { - transactions: any[]; //TODO: update the type -} +import usePendingTransactions, { PendingTransaction } from '@app/hooks/usePendingTransactions'; -const UnconfirmedTransactions: React.FC = ({}) => { +const UnconfirmedTransactions: React.FC = () => { const [isReplacingTransaction, setIsReplacingTransaction] = useState(false); + const [selectedTransaction, setSelectedTransaction] = useState(null); + const { pendingTransactions } = usePendingTransactions(); - const handleOpenReplaceTransaction = () => { + const handleOpenReplaceTransaction = (transaction: PendingTransaction) => { + setSelectedTransaction(transaction); setIsReplacingTransaction(true); }; + const handleCancelReplaceTransaction = () => { + setSelectedTransaction(null); setIsReplacingTransaction(false); }; - const onReplaceTransaction = () => {}; //define any behavior after replacing transaction + const onReplaceTransaction = () => { + // Define any behavior after replacing a transaction + }; + return ( - {isReplacingTransaction ? ( + {isReplacingTransaction && selectedTransaction ? ( <> Replace Transaction - + ) : ( <> Unconfirmed Transactions - - - - - - Replace - - + {pendingTransactions.length === 0 ? ( + No unconfirmed transactions available. + ) : ( + pendingTransactions.map((transaction) => ( + + + + + + handleOpenReplaceTransaction(transaction)}>Replace + + + )) + )} )} @@ -45,3 +65,7 @@ const UnconfirmedTransactions: React.FC = ({}) => }; export default UnconfirmedTransactions; + + + + diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx index 51cc485..29be838 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx @@ -1,19 +1,19 @@ import React from 'react'; import * as S from './UnconfirmedTxModal.styles'; import UnconfirmedTransactions from '../../UnconfirmedTransactions'; + interface UnconfirmedTxModalProps { isOpen: boolean; onOpenChange: () => void; } -const transactions:any = [ -] const UnconfirmedTxModal: React.FC = ({ isOpen, onOpenChange }) => { return ( - - + + ); }; export default UnconfirmedTxModal; + diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 11245ab..bb24cba 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -2,54 +2,118 @@ import React, { useState } from 'react'; import * as S from './ReplaceTransaction.styles'; import { useResponsive } from '@app/hooks/useResponsive'; import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; +import { PendingTransaction } from '@app/hooks/usePendingTransactions'; +import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; +import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; +import config from '@app/config/config'; + interface ReplaceTransactionProps { onCancel: () => void; - onReplace: () => void; + onReplace: () => void; + transaction: PendingTransaction; } -const ReplaceTransaction: React.FC = ({ onCancel, onReplace }) => { +const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { const { isDesktop, isTablet } = useResponsive(); - const [inValidAmount, setInvalidAmount] = React.useState(false); + const [inValidAmount, setInvalidAmount] = useState(false); + const [newFee, setNewFee] = useState(transaction.FeeRate); + const [loading, setLoading] = useState(false); // Add loading state + const [isFinished, setIsFinished] = useState(false); // Add finished state + const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ + isSuccess: false, + message: '', + txid: '', + }); + + const handleFeeChange = (fee: number) => { + setNewFee(fee); // Update the new fee when it changes + }; + + const handleReplace = async (e: React.MouseEvent) => { + e.stopPropagation(); + setLoading(true); // Start loading + console.log('Replace button clicked'); - const handleFeeChange = (fee: number) => {}; //use this for calculating new total + try { + const replaceRequest = { + choice: 2, // Replace transaction option + original_tx_id: transaction.TxID, // Send the original transaction ID + new_fee_rate: newFee, // Send the updated fee rate + }; - const handleReplace = () => { - //function to replace transaction - onReplace(); + const response = await fetch('http://localhost:9003/transaction', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(replaceRequest), + }); + + const result = await response.json(); + setLoading(false); // Stop loading + + if (result.status === 'success') { + setResult({ isSuccess: true, message: result.message, txid: result.txid }); + setIsFinished(true); + onReplace(); // Notify the parent that the replace was successful + } else { + setResult({ isSuccess: false, message: result.message, txid: '' }); + setIsFinished(true); + } + } catch (error) { + console.error('RBF transaction failed:', error); + setLoading(false); // Stop loading + setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); + setIsFinished(true); + } }; + if (isFinished) { + return ( + + ); + } + return ( - - - Transaction ID - - 123456 - - - - Amount - - 123456 - - - - - New Fee - - 123456 - - - - Total - - 123456 - - - - Cancel - Replace - - + + + + Transaction ID + + {transaction.TxID} + + + + Amount + + {transaction.Amount} + + + + + New Fee + + {newFee} + + + + Total + + {Number(transaction.Amount) + newFee} + + + + Cancel + Replace + + + ); }; diff --git a/src/hooks/usePendingTransactions.ts b/src/hooks/usePendingTransactions.ts index 99dac64..5000cc9 100644 --- a/src/hooks/usePendingTransactions.ts +++ b/src/hooks/usePendingTransactions.ts @@ -1,11 +1,12 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; -interface PendingTransaction { - txid: string; - feeRate: number; - timestamp: string; -} +export interface PendingTransaction { + TxID: string; + FeeRate: number; + Timestamp: string; + Amount: number; + } const usePendingTransactions = () => { const [pendingTransactions, setPendingTransactions] = useState([]); @@ -24,10 +25,12 @@ const usePendingTransactions = () => { if (!response.ok) { throw new Error(`Network response was not ok (status: ${response.status})`); } - const data: PendingTransaction[] = await response.json(); - setPendingTransactions(data); + const data: PendingTransaction[] | null = await response.json(); + console.log('Fetched Pending Transactions:', data); + setPendingTransactions(data || []); // Ensuring it is always an array } catch (error) { console.error('Error fetching pending transactions:', error); + setPendingTransactions([]); // Setting an empty array on error } finally { setIsLoading(false); } @@ -40,3 +43,5 @@ const usePendingTransactions = () => { }; export default usePendingTransactions; + + From da7d945e511afa3a2ba6975a39841b7bb32f315c Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:44:10 -0700 Subject: [PATCH 15/62] add scroll to panel content --- .../UnconfirmedTransactions.styles.ts | 19 ++++- .../UnconfirmedTransactions.tsx | 71 ++++++++++++++----- .../Modal/UnconfirmedTxModal.styles.ts | 9 ++- 3 files changed, 76 insertions(+), 23 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts index 0b215e5..c667448 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts @@ -1,6 +1,7 @@ import styled, { css } from 'styled-components'; export const ContentWrapper = styled.div` + overflow-y: hidden; display: flex; flex-direction: column; gap: 1rem; @@ -11,7 +12,12 @@ export const PanelHeaderText = styled.span` padding-bottom: 1rem; `; export const PanelContent = styled.div` + + max-height: 50vh; + display: flex; + min-height: 20vh; + height: 100%; flex-direction: column; gap: 1rem; `; @@ -22,7 +28,7 @@ export const TransactionWrapper = styled.div` `; export const ButtonWrapper = styled.div` disply: flex; - margin: .5rem; + margin: 0.5rem; `; export const NoTransactionsText = styled.p` @@ -32,12 +38,19 @@ export const NoTransactionsText = styled.p` `; export const RowWrapper = styled.div` -padding: .5rem 1.5rem .5rem .5rem; + padding: 0.5rem 1.5rem 0.5rem 0.5rem; border-radius: 0.5rem; - box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.12), 0px 4px 5px rgba(0, 0, 0, 0.14), 0px 2px 4px -1px rgba(0, 0, 0, 0.2); + box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.12), 0px 4px 5px rgba(0, 0, 0, 0.14), 0px 2px 4px -1px rgba(0, 0, 0, 0.2); background-color: var(--additional-background-color); display: flex; flex-direction: row; gap: 2rem; align-items: center; `; +export const ScrollPanel = styled.div` + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 1rem; + height: 100%; +`; \ No newline at end of file diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index 6fde9f3..e351576 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -4,6 +4,39 @@ import UnconfirmedTransaction from './components/UnconfirmedTransaction/Unconfir import ReplaceTransaction from './components/ReplaceTransaction/ReplaceTransaction'; import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; import usePendingTransactions, { PendingTransaction } from '@app/hooks/usePendingTransactions'; +const dummyTransactions: PendingTransaction[] = [ + //for testing purposes + { + TxID: '1', + Timestamp: '2022-01-01T00:00:00Z', + Amount: 10, + FeeRate: 4, + }, + { + TxID: '2', + Timestamp: '2022-01-02T00:00:00Z', + Amount: 20, + FeeRate: 3, + }, + { + TxID: '3', + Timestamp: '2022-01-03T00:00:00Z', + Amount: 30, + FeeRate: 2, + }, + { + TxID: '4', + Timestamp: '2022-01-03T00:00:00Z', + Amount: 30, + FeeRate: 2, + }, + { + TxID: '5', + Timestamp: '2022-01-03T00:00:00Z', + Amount: 30, + FeeRate: 2, + }, +]; const UnconfirmedTransactions: React.FC = () => { const [isReplacingTransaction, setIsReplacingTransaction] = useState(false); @@ -42,20 +75,26 @@ const UnconfirmedTransactions: React.FC = () => { {pendingTransactions.length === 0 ? ( No unconfirmed transactions available. ) : ( - pendingTransactions.map((transaction) => ( - - - - - - handleOpenReplaceTransaction(transaction)}>Replace - - - )) + + {pendingTransactions.map((transaction) => ( + + + + + + handleOpenReplaceTransaction(transaction)}>Replace + + + ))} + )} @@ -65,7 +104,3 @@ const UnconfirmedTransactions: React.FC = () => { }; export default UnconfirmedTransactions; - - - - diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts index 8097a92..008f499 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts @@ -4,11 +4,16 @@ import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; export const Modal = styled(BaseModal)` max-width: fit-content !important; width: fit-content !important; - min-width: 50vw; + min-width: 50vw; + .ant-modal-body { + height: 100%; + padding-bottom: 0; + } .ant-modal-content { + min-height: 50vh; width: 100%; min-width: 50vw; padding: 2rem; } -`; \ No newline at end of file +`; From 1e6f9b949ccc28e4587ca5e7dece6b18bed9cb32 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:47:28 -0700 Subject: [PATCH 16/62] fix text alignment --- .../UnconfirmedTransactions.styles.ts | 4 ++-- .../unconfirmed-transactions/UnconfirmedTransactions.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts index c667448..aae0a2a 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts @@ -14,9 +14,9 @@ export const PanelHeaderText = styled.span` export const PanelContent = styled.div` max-height: 50vh; - + justify-content: center; display: flex; - min-height: 20vh; + min-height: 30vh; height: 100%; flex-direction: column; gap: 1rem; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index e351576..1044aea 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -72,11 +72,11 @@ const UnconfirmedTransactions: React.FC = () => { <> Unconfirmed Transactions - {pendingTransactions.length === 0 ? ( + {dummyTransactions.length === 0 ? ( No unconfirmed transactions available. ) : ( - {pendingTransactions.map((transaction) => ( + {dummyTransactions.map((transaction) => ( Date: Tue, 3 Sep 2024 14:09:44 -0700 Subject: [PATCH 17/62] made usable on mobile --- .../UnconfirmedTransactions.styles.ts | 21 ++++++++++++------- .../UnconfirmedTransactions.tsx | 9 ++++---- .../Modal/UnconfirmedTxModal.styles.ts | 7 +++++-- .../UnconfirmedTransaction.tsx | 6 ++++-- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts index aae0a2a..8c2c282 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts @@ -1,5 +1,5 @@ import styled, { css } from 'styled-components'; - +import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; export const ContentWrapper = styled.div` overflow-y: hidden; display: flex; @@ -12,7 +12,6 @@ export const PanelHeaderText = styled.span` padding-bottom: 1rem; `; export const PanelContent = styled.div` - max-height: 50vh; justify-content: center; display: flex; @@ -24,12 +23,15 @@ export const PanelContent = styled.div` export const TransactionWrapper = styled.div` display: flex; - width: 80%; + width: 100%; `; export const ButtonWrapper = styled.div` disply: flex; margin: 0.5rem; `; +export const ReplaceButton = styled(BaseButton)` + font-size: 0.8rem; +`; export const NoTransactionsText = styled.p` text-align: center; @@ -37,15 +39,20 @@ export const NoTransactionsText = styled.p` font-size: 16px; `; -export const RowWrapper = styled.div` - padding: 0.5rem 1.5rem 0.5rem 0.5rem; +export const RowWrapper = styled.div<{ $isMobile: boolean }>` + padding: .5rem; border-radius: 0.5rem; box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.12), 0px 4px 5px rgba(0, 0, 0, 0.14), 0px 2px 4px -1px rgba(0, 0, 0, 0.2); background-color: var(--additional-background-color); display: flex; flex-direction: row; - gap: 2rem; + gap: 1rem; align-items: center; + ${({ $isMobile }) => + $isMobile && + css` + flex-direction: column; + `} `; export const ScrollPanel = styled.div` overflow-y: auto; @@ -53,4 +60,4 @@ export const ScrollPanel = styled.div` flex-direction: column; gap: 1rem; height: 100%; -`; \ No newline at end of file +`; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index 1044aea..62a0bac 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -2,12 +2,12 @@ import React, { useState } from 'react'; import * as S from './UnconfirmedTransactions.styles'; import UnconfirmedTransaction from './components/UnconfirmedTransaction/UnconfirmedTransaction'; import ReplaceTransaction from './components/ReplaceTransaction/ReplaceTransaction'; -import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; import usePendingTransactions, { PendingTransaction } from '@app/hooks/usePendingTransactions'; +import { useResponsive } from '@app/hooks/useResponsive'; const dummyTransactions: PendingTransaction[] = [ //for testing purposes { - TxID: '1', + TxID: '1123u1293u123u139u12321312312', Timestamp: '2022-01-01T00:00:00Z', Amount: 10, FeeRate: 4, @@ -42,6 +42,7 @@ const UnconfirmedTransactions: React.FC = () => { const [isReplacingTransaction, setIsReplacingTransaction] = useState(false); const [selectedTransaction, setSelectedTransaction] = useState(null); const { pendingTransactions } = usePendingTransactions(); + const {isTablet, isDesktop} = useResponsive(); const handleOpenReplaceTransaction = (transaction: PendingTransaction) => { setSelectedTransaction(transaction); @@ -77,7 +78,7 @@ const UnconfirmedTransactions: React.FC = () => { ) : ( {dummyTransactions.map((transaction) => ( - + { /> - handleOpenReplaceTransaction(transaction)}>Replace + handleOpenReplaceTransaction(transaction)}>Replace ))} diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts index 008f499..2974f45 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts @@ -2,13 +2,16 @@ import styled from 'styled-components'; import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; export const Modal = styled(BaseModal)` - max-width: fit-content !important; width: fit-content !important; min-width: 50vw; - + .ant-modal{ + width: + } .ant-modal-body { height: 100%; padding-bottom: 0; + padding-left: 0; + padding-right: 0; } .ant-modal-content { min-height: 50vh; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx index 9a0e46f..8fce5b5 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx @@ -1,6 +1,8 @@ import React from 'react'; import * as S from './UnconfirmedTransaction.styles' import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; +import { useResponsive } from '@app/hooks/useResponsive'; +import { truncateString } from '@app/utils/utils'; interface UnconfirmedTransactionProps { tx_id: string; @@ -10,13 +12,13 @@ interface UnconfirmedTransactionProps { } const UnconfirmedTransaction: React.FC = ({ tx_id, date_created, amount, feeAmount }) => { - // Implement your component logic here + const {isTablet} = useResponsive(); return ( - {tx_id} + {!isTablet ? truncateString(tx_id, 10) : truncateString(tx_id, 30)} Transaction ID From 783587a9f1a19bfc0d4abdf52a7f3d7958a64bdd Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 4 Sep 2024 11:51:57 +0200 Subject: [PATCH 18/62] adding the ability to dynamically show what the transaction fee is going to be based on the fee rate selected by the user --- .../Balance/components/SendForm/SendForm.tsx | 59 ++++- .../components/TieredFees/TieredFees.tsx | 158 +++++++++++-- .../UnconfirmedTransactions.tsx | 70 +++--- .../ReplaceTransaction/ReplaceTransaction.tsx | 214 ++++++++++++++++-- src/hooks/usePendingTransactions.ts | 7 +- 5 files changed, 429 insertions(+), 79 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index d7b54b3..48263a9 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -20,6 +20,7 @@ interface PendingTransaction { feeRate: number; timestamp: string; // ISO format string amount: string; + recipient_address: string; } export type tiers = 'low' | 'med' | 'high'; @@ -42,9 +43,51 @@ const SendForm: React.FC = ({ onSend }) => { amount: '1', }); + const [txSize, setTxSize] = useState(null); + + // Debounced effect to calculate transaction size when the amount changes + useEffect(() => { + const debounceTimeout = setTimeout(() => { + if (formData.amount.length > 0) { + // Call backend to calculate transaction size + const fetchTransactionSize = async () => { + try { + const response = await fetch('http://localhost:9003/calculate-tx-size', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + recipient_address: formData.address, + spend_amount: parseInt(formData.amount), + priority_rate: fee, // Pass the fee rate + }), + }); + + const result = await response.json(); + setTxSize(result.txSize); // Set the transaction size + } catch (error) { + console.error('Error fetching transaction size:', error); + setTxSize(null); + } + }; + + fetchTransactionSize(); + } + }, 500); // Debounce for 500ms + + return () => clearTimeout(debounceTimeout); // Clear the timeout if the amount changes before 500ms + }, [formData.amount, fee]); + + // Calculate the fee based on the transaction size useEffect(() => { - setAmountWithFee(parseInt(formData.amount) + fee); - }, [fee, formData.amount]); + if (txSize && fee) { + const estimatedFee = txSize * fee; + setAmountWithFee(parseInt(formData.amount) + estimatedFee); + } + }, [txSize, fee]); + + // useEffect(() => { + // setAmountWithFee(parseInt(formData.amount) + fee); + // }, [fee, formData.amount]); const handleFeeChange = (fee: number) => { setFee(fee); @@ -103,9 +146,10 @@ const SendForm: React.FC = ({ onSend }) => { txid: result.txid, feeRate: selectedFee, timestamp: new Date().toISOString(), // Capture the current time in ISO format - amount: formData.amount + amount: formData.amount, + recipient_address: formData.address, // Send the recipient address }; - + // Send the transaction details to the pending-transactions endpoint await fetch(`${config.baseURL}/pending-transactions`, { method: 'POST', @@ -114,10 +158,11 @@ const SendForm: React.FC = ({ onSend }) => { }, body: JSON.stringify(pendingTransaction), }); - + // Call the onSend callback with the result onSend(true, formData.address, transactionRequest.spend_amount, result.txid, result.message); - } else { + } + else { onSend(false, formData.address, 0, '', result.message); } } catch (error) { @@ -165,7 +210,7 @@ const SendForm: React.FC = ({ onSend }) => { RBF Opt In - + void; inValidAmount: boolean; + txSize: number | null; // Transaction size passed down from SendForm } -const TieredFees: React.FC = ({ inValidAmount, handleFeeChange }) => { +const TieredFees: React.FC = ({ inValidAmount, handleFeeChange, txSize }) => { const { isDesktop, isTablet } = useResponsive(); const [fees, setFees] = useState({ low: 0, med: 0, high: 0 }); const [selectedTier, setSelectedTier] = useState('low'); + const [estimatedFee, setEstimatedFee] = useState({ low: 0, med: 0, high: 0 }); useEffect(() => { const fetchFees = async () => { @@ -41,19 +42,31 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange fetchFees(); }, []); + + // Update estimated fees whenever the fees or transaction size change + useEffect(() => { + if (txSize) { + setEstimatedFee({ + low: txSize * fees.low, + med: txSize * fees.med, + high: txSize * fees.high, + }); + } + }, [fees, txSize]); + const handleTierChange = (tier: any) => { - console.log(tier); setSelectedTier(tier.id); }; useEffect(() => { handleFeeChange(fees[selectedTier as tiers]); }, [selectedTier]); + return ( - + handleTierChange({ id: 'low', rate: fees.med })} + onClick={() => handleTierChange({ id: 'low' })} className={`tier-hover ${selectedTier === 'low' ? 'selected' : ''} ${ selectedTier === 'low' && inValidAmount ? 'invalidAmount' : '' } `} @@ -64,14 +77,14 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange {`Priority`} {`${fees.low} sat/vB`} - {`${fees.low} Sats`} + {`${estimatedFee.low} Sats`} {/* Show estimated fee */} handleTierChange({ id: 'med', rate: fees.med })} + onClick={() => handleTierChange({ id: 'med' })} className={`tier-hover ${selectedTier === 'med' ? 'selected' : ''} ${ selectedTier === 'med' && inValidAmount ? 'invalidAmount' : '' } `} @@ -82,28 +95,25 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange {`Priority`} {`${fees.med} sat/vB`} - {`${fees.med} Sats`} + {`${estimatedFee.med} Sats`} {/* Show estimated fee */} handleTierChange({ id: 'high', rate: fees.high })} + onClick={() => handleTierChange({ id: 'high' })} className={`tier-hover ${selectedTier === 'high' ? 'selected' : ''} ${ selectedTier === 'high' && inValidAmount ? 'invalidAmount' : '' } `} > - - {' '} - {`High`} -
- {`Priority`} -
+ {`High`} +
+ {`Priority`} {`${fees.high} sat/vB`} - {`${fees.high} Sats`} + {`${estimatedFee.high} Sats`} {/* Show estimated fee */}
@@ -112,3 +122,119 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange }; export default TieredFees; + + +// import React, { useEffect, useState } from 'react'; +// import * as S from './TieredFees.styles'; +// import { useResponsive } from '@app/hooks/useResponsive'; +// import { tiers } from '../../SendForm'; + +// interface FeeRecommendation { +// fastestFee: number; +// halfHourFee: number; +// hourFee: number; +// economyFee: number; +// minimumFee: number; +// } +// type Fees = { +// [key in tiers]: number; +// }; +// interface TieredFeesProps { +// // Define the props for your component here +// handleFeeChange: (fee: number) => void; +// inValidAmount: boolean; +// } + +// const TieredFees: React.FC = ({ inValidAmount, handleFeeChange }) => { +// const { isDesktop, isTablet } = useResponsive(); +// const [fees, setFees] = useState({ low: 0, med: 0, high: 0 }); +// const [selectedTier, setSelectedTier] = useState('low'); + +// useEffect(() => { +// const fetchFees = async () => { +// try { +// const response = await fetch('https://mempool.space/api/v1/fees/recommended'); +// const data: FeeRecommendation = await response.json(); +// setFees({ +// low: data.economyFee, +// med: data.halfHourFee, +// high: data.fastestFee, +// }); +// } catch (error) { +// console.error('Failed to fetch fees:', error); +// } +// }; + +// fetchFees(); +// }, []); +// const handleTierChange = (tier: any) => { +// console.log(tier); +// setSelectedTier(tier.id); +// }; + +// useEffect(() => { +// handleFeeChange(fees[selectedTier as tiers]); +// }, [selectedTier]); +// return ( +// +// handleTierChange({ id: 'low', rate: fees.med })} +// className={`tier-hover ${selectedTier === 'low' ? 'selected' : ''} ${ +// selectedTier === 'low' && inValidAmount ? 'invalidAmount' : '' +// } `} +// > +// +// {`Low`} +//
+// {`Priority`} +// +// {`${fees.low} sat/vB`} +// {`${fees.low} Sats`} +// +//
+//
+ +// handleTierChange({ id: 'med', rate: fees.med })} +// className={`tier-hover ${selectedTier === 'med' ? 'selected' : ''} ${ +// selectedTier === 'med' && inValidAmount ? 'invalidAmount' : '' +// } `} +// > +// +// {`Medium`} +//
+// {`Priority`} +// +// {`${fees.med} sat/vB`} +// {`${fees.med} Sats`} +// +//
+//
+ +// handleTierChange({ id: 'high', rate: fees.high })} +// className={`tier-hover ${selectedTier === 'high' ? 'selected' : ''} ${ +// selectedTier === 'high' && inValidAmount ? 'invalidAmount' : '' +// } `} +// > +// +// +// {' '} +// {`High`} +//
+// {`Priority`} +//
+// +// {`${fees.high} sat/vB`} +// {`${fees.high} Sats`} +// +//
+//
+//
+// ); +// }; + +// export default TieredFees; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index 62a0bac..03da35d 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -4,39 +4,39 @@ import UnconfirmedTransaction from './components/UnconfirmedTransaction/Unconfir import ReplaceTransaction from './components/ReplaceTransaction/ReplaceTransaction'; import usePendingTransactions, { PendingTransaction } from '@app/hooks/usePendingTransactions'; import { useResponsive } from '@app/hooks/useResponsive'; -const dummyTransactions: PendingTransaction[] = [ - //for testing purposes - { - TxID: '1123u1293u123u139u12321312312', - Timestamp: '2022-01-01T00:00:00Z', - Amount: 10, - FeeRate: 4, - }, - { - TxID: '2', - Timestamp: '2022-01-02T00:00:00Z', - Amount: 20, - FeeRate: 3, - }, - { - TxID: '3', - Timestamp: '2022-01-03T00:00:00Z', - Amount: 30, - FeeRate: 2, - }, - { - TxID: '4', - Timestamp: '2022-01-03T00:00:00Z', - Amount: 30, - FeeRate: 2, - }, - { - TxID: '5', - Timestamp: '2022-01-03T00:00:00Z', - Amount: 30, - FeeRate: 2, - }, -]; +// const dummyTransactions: PendingTransaction[] = [ +// //for testing purposes +// { +// TxID: '1123u1293u123u139u12321312312', +// Timestamp: '2022-01-01T00:00:00Z', +// Amount: 10, +// FeeRate: 4, +// }, +// { +// TxID: '2', +// Timestamp: '2022-01-02T00:00:00Z', +// Amount: 20, +// FeeRate: 3, +// }, +// { +// TxID: '3', +// Timestamp: '2022-01-03T00:00:00Z', +// Amount: 30, +// FeeRate: 2, +// }, +// { +// TxID: '4', +// Timestamp: '2022-01-03T00:00:00Z', +// Amount: 30, +// FeeRate: 2, +// }, +// { +// TxID: '5', +// Timestamp: '2022-01-03T00:00:00Z', +// Amount: 30, +// FeeRate: 2, +// }, +// ]; const UnconfirmedTransactions: React.FC = () => { const [isReplacingTransaction, setIsReplacingTransaction] = useState(false); @@ -73,11 +73,11 @@ const UnconfirmedTransactions: React.FC = () => { <> Unconfirmed Transactions - {dummyTransactions.length === 0 ? ( + {pendingTransactions.length === 0 ? ( No unconfirmed transactions available. ) : ( - {dummyTransactions.map((transaction) => ( + {pendingTransactions.map((transaction) => ( void; @@ -16,53 +15,78 @@ interface ReplaceTransactionProps { const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { const { isDesktop, isTablet } = useResponsive(); const [inValidAmount, setInvalidAmount] = useState(false); - const [newFee, setNewFee] = useState(transaction.FeeRate); - const [loading, setLoading] = useState(false); // Add loading state - const [isFinished, setIsFinished] = useState(false); // Add finished state + const [newFee, setNewFee] = useState(transaction.FeeRate); // Fee rate in sat/vB + const [txSize, setTxSize] = useState(null); // State to store transaction size + const [loading, setLoading] = useState(false); // Add loading state + const [isFinished, setIsFinished] = useState(false); // Add finished state const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ isSuccess: false, message: '', txid: '', }); + // Fetch the transaction size when the component mounts + useEffect(() => { + const fetchTransactionSize = async () => { + try { + const response = await fetch('http://localhost:9003/calculate-tx-size', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + recipient_address: transaction.RecipientAddress, // Since this is a replace, we can use the original transaction ID as the address placeholder + spend_amount: parseInt(transaction.Amount.toString()), // The original amount + priority_rate: newFee, // The current fee rate + }), + }); + + const result = await response.json(); + setTxSize(result.txSize); // Set the transaction size + } catch (error) { + console.error('Error fetching transaction size:', error); + setTxSize(null); + } + }; + + fetchTransactionSize(); + }, [transaction.TxID, transaction.Amount, newFee]); + + // Update fee when selected from the TieredFees component const handleFeeChange = (fee: number) => { - setNewFee(fee); // Update the new fee when it changes + setNewFee(fee); // Update the new fee when it changes }; const handleReplace = async (e: React.MouseEvent) => { e.stopPropagation(); - setLoading(true); // Start loading + setLoading(true); // Start loading console.log('Replace button clicked'); try { const replaceRequest = { - choice: 2, // Replace transaction option - original_tx_id: transaction.TxID, // Send the original transaction ID - new_fee_rate: newFee, // Send the updated fee rate + choice: 2, // Replace transaction option + original_tx_id: transaction.TxID, // Send the original transaction ID + new_fee_rate: newFee, // Send the updated fee rate }; const response = await fetch('http://localhost:9003/transaction', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(replaceRequest), }); const result = await response.json(); - setLoading(false); // Stop loading + setLoading(false); // Stop loading if (result.status === 'success') { setResult({ isSuccess: true, message: result.message, txid: result.txid }); setIsFinished(true); - onReplace(); // Notify the parent that the replace was successful + onReplace(); // Notify the parent that the replace was successful } else { setResult({ isSuccess: false, message: result.message, txid: '' }); setIsFinished(true); } } catch (error) { console.error('RBF transaction failed:', error); - setLoading(false); // Stop loading + setLoading(false); // Stop loading setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); setIsFinished(true); } @@ -80,6 +104,9 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep ); } + // Calculate the total transaction cost (Amount + Calculated Fee) + const totalCost = txSize && newFee ? Number(transaction.Amount) + newFee * txSize : Number(transaction.Amount); + return ( @@ -95,7 +122,8 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep {transaction.Amount} - + {/* Pass the transaction size to TieredFees to dynamically calculate the fee */} + New Fee @@ -105,7 +133,8 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep Total - {Number(transaction.Amount) + newFee} + {/* Calculate total amount (Amount + Fee based on transaction size) */} + {totalCost} @@ -118,3 +147,152 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep }; export default ReplaceTransaction; + + +// import React, { useEffect, useState } from 'react'; +// import * as S from './ReplaceTransaction.styles'; +// import { useResponsive } from '@app/hooks/useResponsive'; +// import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; +// import { PendingTransaction } from '@app/hooks/usePendingTransactions'; +// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; +// import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; +// import config from '@app/config/config'; + +// interface ReplaceTransactionProps { +// onCancel: () => void; +// onReplace: () => void; +// transaction: PendingTransaction; +// } + +// const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { +// const { isDesktop, isTablet } = useResponsive(); +// const [inValidAmount, setInvalidAmount] = useState(false); +// const [newFee, setNewFee] = useState(transaction.FeeRate); +// const [txSize, setTxSize] = useState(null); // State to store transaction size +// const [loading, setLoading] = useState(false); // Add loading state +// const [isFinished, setIsFinished] = useState(false); // Add finished state +// const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ +// isSuccess: false, +// message: '', +// txid: '', +// }); + +// // Fetch the transaction size when the component mounts +// useEffect(() => { +// const fetchTransactionSize = async () => { +// try { +// const response = await fetch('http://localhost:9003/calculate-tx-size', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ +// recipient_address: transaction.RecipientAddress, // Since this is a replace, we can use the original transaction ID as the address placeholder +// spend_amount: parseInt(transaction.Amount.toString()), // The original amount +// priority_rate: newFee, // The current fee rate +// }), +// }); + +// const result = await response.json(); +// setTxSize(result.txSize); // Set the transaction size +// } catch (error) { +// console.error('Error fetching transaction size:', error); +// setTxSize(null); +// } +// }; + +// fetchTransactionSize(); +// }, [transaction.TxID, transaction.Amount, newFee]); + +// // Update fee when selected from the TieredFees component +// const handleFeeChange = (fee: number) => { +// setNewFee(fee); // Update the new fee when it changes +// }; + +// const handleReplace = async (e: React.MouseEvent) => { +// e.stopPropagation(); +// setLoading(true); // Start loading +// console.log('Replace button clicked'); + +// try { +// const replaceRequest = { +// choice: 2, // Replace transaction option +// original_tx_id: transaction.TxID, // Send the original transaction ID +// new_fee_rate: newFee, // Send the updated fee rate +// }; + +// const response = await fetch('http://localhost:9003/transaction', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(replaceRequest), +// }); + +// const result = await response.json(); +// setLoading(false); // Stop loading + +// if (result.status === 'success') { +// setResult({ isSuccess: true, message: result.message, txid: result.txid }); +// setIsFinished(true); +// onReplace(); // Notify the parent that the replace was successful +// } else { +// setResult({ isSuccess: false, message: result.message, txid: '' }); +// setIsFinished(true); +// } +// } catch (error) { +// console.error('RBF transaction failed:', error); +// setLoading(false); // Stop loading +// setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); +// setIsFinished(true); +// } +// }; + +// if (isFinished) { +// return ( +// +// ); +// } + +// return ( +// +// +// +// Transaction ID +// +// {transaction.TxID} +// +// +// +// Amount +// +// {transaction.Amount} +// +// +// {/* Pass the transaction size to TieredFees to dynamically calculate the fee */} +// +// +// New Fee +// +// {newFee} +// +// +// +// Total +// +// {/* Calculate total amount (Amount + Fee) */} +// {Number(transaction.Amount) + newFee} +// +// +// +// Cancel +// Replace +// +// +// +// ); +// }; + +// export default ReplaceTransaction; diff --git a/src/hooks/usePendingTransactions.ts b/src/hooks/usePendingTransactions.ts index 5000cc9..c268419 100644 --- a/src/hooks/usePendingTransactions.ts +++ b/src/hooks/usePendingTransactions.ts @@ -4,9 +4,10 @@ import config from '@app/config/config'; export interface PendingTransaction { TxID: string; FeeRate: number; - Timestamp: string; - Amount: number; - } + Timestamp: string; // ISO format string + Amount: string; + RecipientAddress: string; // Add recipient address + } const usePendingTransactions = () => { const [pendingTransactions, setPendingTransactions] = useState([]); From 18e95afdab4403f4fe35fa3fed289a3154f134c5 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 4 Sep 2024 13:08:05 +0200 Subject: [PATCH 19/62] adding balance checking to transactions before processing them --- .../Balance/components/SendForm/SendForm.tsx | 285 +++++++++++++++++- 1 file changed, 274 insertions(+), 11 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 48263a9..250627e 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -36,6 +36,7 @@ const SendForm: React.FC = ({ onSend }) => { const [addressError, setAddressError] = useState(false); const [amountWithFee, setAmountWithFee] = useState(null); + const [totalCost, setTotalCost] = useState(null); // To store the total cost (amount + fee) const [fee, setFee] = useState(0); const [formData, setFormData] = useState({ @@ -44,6 +45,7 @@ const SendForm: React.FC = ({ onSend }) => { }); const [txSize, setTxSize] = useState(null); + const [errorMessage, setErrorMessage] = useState(null); // For error messaging // Debounced effect to calculate transaction size when the amount changes useEffect(() => { @@ -81,13 +83,20 @@ const SendForm: React.FC = ({ onSend }) => { useEffect(() => { if (txSize && fee) { const estimatedFee = txSize * fee; - setAmountWithFee(parseInt(formData.amount) + estimatedFee); + const total = parseInt(formData.amount) + estimatedFee; + setAmountWithFee(total); + setTotalCost(total); } - }, [txSize, fee]); + }, [txSize, fee, formData.amount]); - // useEffect(() => { - // setAmountWithFee(parseInt(formData.amount) + fee); - // }, [fee, formData.amount]); + // Check if the total cost exceeds the user's balance + useEffect(() => { + if (totalCost !== null && balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance) { + setErrorMessage('Insufficient balance to complete the transaction.'); + } else { + setErrorMessage(null); // Reset error if the total cost is within balance + } + }, [totalCost, balanceData]); const handleFeeChange = (fee: number) => { setFee(fee); @@ -149,7 +158,7 @@ const SendForm: React.FC = ({ onSend }) => { amount: formData.amount, recipient_address: formData.address, // Send the recipient address }; - + // Send the transaction details to the pending-transactions endpoint await fetch(`${config.baseURL}/pending-transactions`, { method: 'POST', @@ -158,11 +167,10 @@ const SendForm: React.FC = ({ onSend }) => { }, body: JSON.stringify(pendingTransaction), }); - + // Call the onSend callback with the result onSend(true, formData.address, transactionRequest.spend_amount, result.txid, result.message); - } - else { + } else { onSend(false, formData.address, 0, '', result.message); } } catch (error) { @@ -178,7 +186,7 @@ const SendForm: React.FC = ({ onSend }) => { } else { setInvalidAmount(false); } - }, [formData.amount]); + }, [formData.amount, balanceData]); const receiverPanel = () => ( <> @@ -212,9 +220,11 @@ const SendForm: React.FC = ({ onSend }) => { + {/* Display an error if the total exceeds the balance */} + {errorMessage && {errorMessage}} = ({ onSend }) => { }; export default SendForm; + + +// import React, { useEffect, useState } from 'react'; +// import { BaseInput } from '@app/components/common/inputs/BaseInput/BaseInput'; +// import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; +// import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; +// import { useResponsive } from '@app/hooks/useResponsive'; +// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; +// import * as S from './SendForm.styles'; +// import { truncateString } from '@app/utils/utils'; +// import useBalanceData from '@app/hooks/useBalanceData'; +// import { BaseCheckbox } from '@app/components/common/BaseCheckbox/BaseCheckbox'; +// import config from '@app/config/config'; +// import TieredFees from './components/TieredFees/TieredFees'; + +// interface SendFormProps { +// onSend: (status: boolean, address: string, amount: number, txid?: string, message?: string) => void; +// } + +// interface PendingTransaction { +// txid: string; +// feeRate: number; +// timestamp: string; // ISO format string +// amount: string; +// recipient_address: string; +// } + +// export type tiers = 'low' | 'med' | 'high'; + +// const SendForm: React.FC = ({ onSend }) => { +// const { balanceData, isLoading } = useBalanceData(); + +// const [loading, setLoading] = useState(false); + +// const [isDetailsOpen, setIsDetailsOpen] = useState(false); + +// const [inValidAmount, setInvalidAmount] = useState(false); +// const [addressError, setAddressError] = useState(false); + +// const [amountWithFee, setAmountWithFee] = useState(null); + +// const [fee, setFee] = useState(0); +// const [formData, setFormData] = useState({ +// address: '', +// amount: '1', +// }); + +// const [txSize, setTxSize] = useState(null); + +// // Debounced effect to calculate transaction size when the amount changes +// useEffect(() => { +// const debounceTimeout = setTimeout(() => { +// if (formData.amount.length > 0) { +// // Call backend to calculate transaction size +// const fetchTransactionSize = async () => { +// try { +// const response = await fetch('http://localhost:9003/calculate-tx-size', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ +// recipient_address: formData.address, +// spend_amount: parseInt(formData.amount), +// priority_rate: fee, // Pass the fee rate +// }), +// }); + +// const result = await response.json(); +// setTxSize(result.txSize); // Set the transaction size +// } catch (error) { +// console.error('Error fetching transaction size:', error); +// setTxSize(null); +// } +// }; + +// fetchTransactionSize(); +// } +// }, 500); // Debounce for 500ms + +// return () => clearTimeout(debounceTimeout); // Clear the timeout if the amount changes before 500ms +// }, [formData.amount, fee]); + +// // Calculate the fee based on the transaction size +// useEffect(() => { +// if (txSize && fee) { +// const estimatedFee = txSize * fee; +// setAmountWithFee(parseInt(formData.amount) + estimatedFee); +// } +// }, [txSize, fee]); + +// // useEffect(() => { +// // setAmountWithFee(parseInt(formData.amount) + fee); +// // }, [fee, formData.amount]); + +// const handleFeeChange = (fee: number) => { +// setFee(fee); +// }; + +// const isValidAddress = (address: string) => { +// return address.length > 0; +// }; + +// const handleAddressSubmit = () => { +// const isValid = isValidAddress(formData.address); + +// if (isValid) { +// setAddressError(false); +// setIsDetailsOpen(true); +// } else { +// setAddressError(true); +// } +// }; + +// const handleInputChange = (e: React.ChangeEvent) => { +// e.preventDefault(); +// const { name, value } = e.target; +// setFormData({ ...formData, [name]: value }); +// }; + +// const handleSend = async () => { +// if (loading || inValidAmount) return; + +// setLoading(true); + +// const selectedFee = fee; // Default to low if not selected + +// const transactionRequest = { +// choice: 1, // Default to choice 1 for new transactions +// recipient_address: formData.address, +// spend_amount: parseInt(formData.amount), +// priority_rate: selectedFee, +// }; + +// try { +// const response = await fetch('http://localhost:9003/transaction', { +// method: 'POST', +// headers: { +// 'Content-Type': 'application/json', +// }, +// body: JSON.stringify(transactionRequest), +// }); + +// const result = await response.json(); +// setLoading(false); + +// if (result.status === 'success') { +// // Prepare the transaction details to send to the pending-transactions endpoint +// const pendingTransaction: PendingTransaction = { +// txid: result.txid, +// feeRate: selectedFee, +// timestamp: new Date().toISOString(), // Capture the current time in ISO format +// amount: formData.amount, +// recipient_address: formData.address, // Send the recipient address +// }; + +// // Send the transaction details to the pending-transactions endpoint +// await fetch(`${config.baseURL}/pending-transactions`, { +// method: 'POST', +// headers: { +// 'Content-Type': 'application/json', +// }, +// body: JSON.stringify(pendingTransaction), +// }); + +// // Call the onSend callback with the result +// onSend(true, formData.address, transactionRequest.spend_amount, result.txid, result.message); +// } +// else { +// onSend(false, formData.address, 0, '', result.message); +// } +// } catch (error) { +// console.error('Transaction failed:', error); +// setLoading(false); +// onSend(false, formData.address, 0, '', 'Transaction failed due to a network error.'); +// } +// }; + +// useEffect(() => { +// if (formData.amount.length <= 0 || (balanceData && parseInt(formData.amount) > balanceData.latest_balance)) { +// setInvalidAmount(true); +// } else { +// setInvalidAmount(false); +// } +// }, [formData.amount]); + +// const receiverPanel = () => ( +// <> +// +// Address +// +// +// Continue +// +// ); + +// const detailsPanel = () => ( +// +// +// +// {`Amount = ${amountWithFee ? amountWithFee : ''}`} + +// {inValidAmount && Invalid Amount} +// + +//
+// +// {`Balance: ${balanceData ? balanceData.latest_balance : 0}`} +//
+//
+// +// Tiered Fees +// +// +// RBF Opt In +// +// +// +// +// +// Send +// +// +//
+// ); + +// return ( +// +// +// +// Send +// {isDetailsOpen ? ( +// <> +// +// To: +//
+// {truncateString(formData.address, 65)} +//
+// {detailsPanel()} +// +// ) : ( +// receiverPanel() +// )} +//
+//
+//
+// ); +// }; + +// export default SendForm; From 4d6db3c584a14a45ca32e717fe08bdbd8d2ac4ef Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 4 Sep 2024 13:35:19 +0200 Subject: [PATCH 20/62] Adding balance checking to replacement transactions so that we can prevent actioning trsactions that would not be successful --- .../ReplaceTransaction.styles.ts | 7 ++++ .../ReplaceTransaction/ReplaceTransaction.tsx | 40 +++++++++++++------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts index 8c9279f..fd74c2a 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts @@ -62,4 +62,11 @@ export const TiersCol = styled.div` flex-direction: column; gap: 1rem; justify-content: space-around; +`; + +export const ErrorMessage = styled.div` + color: red; + font-size: 14px; + margin-top: 10px; + text-align: center; `; \ No newline at end of file diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index d3477ae..87c34fd 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -5,6 +5,7 @@ import TieredFees from '@app/components/nft-dashboard/Balance/components/SendFor import { PendingTransaction } from '@app/hooks/usePendingTransactions'; import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; +import useBalanceData from '@app/hooks/useBalanceData'; interface ReplaceTransactionProps { onCancel: () => void; @@ -14,6 +15,8 @@ interface ReplaceTransactionProps { const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { const { isDesktop, isTablet } = useResponsive(); + const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); // Fetch balance data using the hook + const [inValidAmount, setInvalidAmount] = useState(false); const [newFee, setNewFee] = useState(transaction.FeeRate); // Fee rate in sat/vB const [txSize, setTxSize] = useState(null); // State to store transaction size @@ -33,7 +36,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - recipient_address: transaction.RecipientAddress, // Since this is a replace, we can use the original transaction ID as the address placeholder + recipient_address: transaction.RecipientAddress, // Use the original recipient address spend_amount: parseInt(transaction.Amount.toString()), // The original amount priority_rate: newFee, // The current fee rate }), @@ -50,7 +53,12 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep fetchTransactionSize(); }, [transaction.TxID, transaction.Amount, newFee]); - // Update fee when selected from the TieredFees component + // Calculate the total transaction cost (Amount + Calculated Fee) + const totalCost = txSize && newFee ? Number(transaction.Amount) + newFee * txSize : Number(transaction.Amount); + + // Check if the total cost exceeds the user's balance + const isBalanceInsufficient = balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance; + const handleFeeChange = (fee: number) => { setNewFee(fee); // Update the new fee when it changes }; @@ -58,7 +66,6 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep const handleReplace = async (e: React.MouseEvent) => { e.stopPropagation(); setLoading(true); // Start loading - console.log('Replace button clicked'); try { const replaceRequest = { @@ -104,11 +111,8 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep ); } - // Calculate the total transaction cost (Amount + Calculated Fee) - const totalCost = txSize && newFee ? Number(transaction.Amount) + newFee * txSize : Number(transaction.Amount); - return ( - + Transaction ID @@ -137,9 +141,18 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep {totalCost}
+ + {/* Show error message if balance is insufficient */} + {isBalanceInsufficient && ( + Insufficient balance to complete the transaction. + )} + Cancel - Replace + {/* Disable replace button if total cost exceeds balance */} + + Replace +
@@ -149,6 +162,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep export default ReplaceTransaction; + // import React, { useEffect, useState } from 'react'; // import * as S from './ReplaceTransaction.styles'; // import { useResponsive } from '@app/hooks/useResponsive'; @@ -156,7 +170,6 @@ export default ReplaceTransaction; // import { PendingTransaction } from '@app/hooks/usePendingTransactions'; // import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; // import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; -// import config from '@app/config/config'; // interface ReplaceTransactionProps { // onCancel: () => void; @@ -167,7 +180,7 @@ export default ReplaceTransaction; // const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { // const { isDesktop, isTablet } = useResponsive(); // const [inValidAmount, setInvalidAmount] = useState(false); -// const [newFee, setNewFee] = useState(transaction.FeeRate); +// const [newFee, setNewFee] = useState(transaction.FeeRate); // Fee rate in sat/vB // const [txSize, setTxSize] = useState(null); // State to store transaction size // const [loading, setLoading] = useState(false); // Add loading state // const [isFinished, setIsFinished] = useState(false); // Add finished state @@ -256,6 +269,9 @@ export default ReplaceTransaction; // ); // } +// // Calculate the total transaction cost (Amount + Calculated Fee) +// const totalCost = txSize && newFee ? Number(transaction.Amount) + newFee * txSize : Number(transaction.Amount); + // return ( // // @@ -282,8 +298,8 @@ export default ReplaceTransaction; // // Total // -// {/* Calculate total amount (Amount + Fee) */} -// {Number(transaction.Amount) + newFee} +// {/* Calculate total amount (Amount + Fee based on transaction size) */} +// {totalCost} // // // From 327125774c4ce0ba02e072a69f2826c50aa28a09 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 4 Sep 2024 13:36:48 +0200 Subject: [PATCH 21/62] Removing redundant code from replace transactions code file --- .../ReplaceTransaction/ReplaceTransaction.tsx | 152 ------------------ 1 file changed, 152 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 87c34fd..dca51d5 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -160,155 +160,3 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep }; export default ReplaceTransaction; - - - -// import React, { useEffect, useState } from 'react'; -// import * as S from './ReplaceTransaction.styles'; -// import { useResponsive } from '@app/hooks/useResponsive'; -// import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; -// import { PendingTransaction } from '@app/hooks/usePendingTransactions'; -// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; -// import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; - -// interface ReplaceTransactionProps { -// onCancel: () => void; -// onReplace: () => void; -// transaction: PendingTransaction; -// } - -// const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { -// const { isDesktop, isTablet } = useResponsive(); -// const [inValidAmount, setInvalidAmount] = useState(false); -// const [newFee, setNewFee] = useState(transaction.FeeRate); // Fee rate in sat/vB -// const [txSize, setTxSize] = useState(null); // State to store transaction size -// const [loading, setLoading] = useState(false); // Add loading state -// const [isFinished, setIsFinished] = useState(false); // Add finished state -// const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ -// isSuccess: false, -// message: '', -// txid: '', -// }); - -// // Fetch the transaction size when the component mounts -// useEffect(() => { -// const fetchTransactionSize = async () => { -// try { -// const response = await fetch('http://localhost:9003/calculate-tx-size', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ -// recipient_address: transaction.RecipientAddress, // Since this is a replace, we can use the original transaction ID as the address placeholder -// spend_amount: parseInt(transaction.Amount.toString()), // The original amount -// priority_rate: newFee, // The current fee rate -// }), -// }); - -// const result = await response.json(); -// setTxSize(result.txSize); // Set the transaction size -// } catch (error) { -// console.error('Error fetching transaction size:', error); -// setTxSize(null); -// } -// }; - -// fetchTransactionSize(); -// }, [transaction.TxID, transaction.Amount, newFee]); - -// // Update fee when selected from the TieredFees component -// const handleFeeChange = (fee: number) => { -// setNewFee(fee); // Update the new fee when it changes -// }; - -// const handleReplace = async (e: React.MouseEvent) => { -// e.stopPropagation(); -// setLoading(true); // Start loading -// console.log('Replace button clicked'); - -// try { -// const replaceRequest = { -// choice: 2, // Replace transaction option -// original_tx_id: transaction.TxID, // Send the original transaction ID -// new_fee_rate: newFee, // Send the updated fee rate -// }; - -// const response = await fetch('http://localhost:9003/transaction', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(replaceRequest), -// }); - -// const result = await response.json(); -// setLoading(false); // Stop loading - -// if (result.status === 'success') { -// setResult({ isSuccess: true, message: result.message, txid: result.txid }); -// setIsFinished(true); -// onReplace(); // Notify the parent that the replace was successful -// } else { -// setResult({ isSuccess: false, message: result.message, txid: '' }); -// setIsFinished(true); -// } -// } catch (error) { -// console.error('RBF transaction failed:', error); -// setLoading(false); // Stop loading -// setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); -// setIsFinished(true); -// } -// }; - -// if (isFinished) { -// return ( -// -// ); -// } - -// // Calculate the total transaction cost (Amount + Calculated Fee) -// const totalCost = txSize && newFee ? Number(transaction.Amount) + newFee * txSize : Number(transaction.Amount); - -// return ( -// -// -// -// Transaction ID -// -// {transaction.TxID} -// -// -// -// Amount -// -// {transaction.Amount} -// -// -// {/* Pass the transaction size to TieredFees to dynamically calculate the fee */} -// -// -// New Fee -// -// {newFee} -// -// -// -// Total -// -// {/* Calculate total amount (Amount + Fee based on transaction size) */} -// {totalCost} -// -// -// -// Cancel -// Replace -// -// -// -// ); -// }; - -// export default ReplaceTransaction; From 08ef8aecc600c1e653cd04691fce49a3be834d01 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 4 Sep 2024 18:11:45 +0200 Subject: [PATCH 22/62] changing to use https for transaction size calculation --- .../components/ReplaceTransaction/ReplaceTransaction.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index dca51d5..7469647 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -32,7 +32,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep useEffect(() => { const fetchTransactionSize = async () => { try { - const response = await fetch('http://localhost:9003/calculate-tx-size', { + const response = await fetch('https://localhost:443/calculate-tx-size', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ From 108ca5c8bf5ee3121bc673d5f3fb0cd434ac76fa Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:49:57 -0700 Subject: [PATCH 23/62] fix address overflow --- .../UnconfirmedTransaction.styles.ts | 12 +++++++++++- .../UnconfirmedTransaction.tsx | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts index e827e76..d377a03 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts @@ -1,7 +1,6 @@ import styled from 'styled-components'; export const TransactionWrapper = styled.div` - width: 100%; padding: 1rem; display: flex; @@ -10,8 +9,16 @@ export const TransactionWrapper = styled.div` gap: 1rem; `; +export const IDWrapper = styled.div` + display: flex; + width: 40%; + flex-direction: column; + gap: 0.3rem; +`; + export const DataWrapper = styled.div` display: flex; + width: 22%; flex-direction: column; gap: 0.3rem; `; @@ -20,6 +27,9 @@ export const Value = styled.span` font-size: 1rem; color: var(--text-main-color); font-weight: semibold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; `; export const Label = styled.span` font-size: 0.8rem; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx index 8fce5b5..be966c8 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx @@ -17,11 +17,11 @@ const UnconfirmedTransaction: React.FC = ({ tx_id, return ( - + {!isTablet ? truncateString(tx_id, 10) : truncateString(tx_id, 30)} Transaction ID - + {date_created} Date Created From 09e09b9ffbc23be64a605c57d2845dc17ef13b8e Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:58:12 -0700 Subject: [PATCH 24/62] fix mobile replace button --- .../UnconfirmedTransactions.styles.ts | 16 +++++++++++++--- .../UnconfirmedTransactions.tsx | 9 +++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts index 8c2c282..80b86ce 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts @@ -25,12 +25,22 @@ export const TransactionWrapper = styled.div` display: flex; width: 100%; `; -export const ButtonWrapper = styled.div` +export const ButtonWrapper = styled.div<{ $isMobile: boolean }>` disply: flex; margin: 0.5rem; + ${({ $isMobile }) => + $isMobile && + css` + width: 100%; + `} `; -export const ReplaceButton = styled(BaseButton)` +export const ReplaceButton = styled(BaseButton)<{ $isMobile: boolean }>` font-size: 0.8rem; + ${({ $isMobile }) => + $isMobile && + css` + width: 100%; + `} `; export const NoTransactionsText = styled.p` @@ -40,7 +50,7 @@ export const NoTransactionsText = styled.p` `; export const RowWrapper = styled.div<{ $isMobile: boolean }>` - padding: .5rem; + padding: 0.5rem; border-radius: 0.5rem; box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.12), 0px 4px 5px rgba(0, 0, 0, 0.14), 0px 2px 4px -1px rgba(0, 0, 0, 0.2); background-color: var(--additional-background-color); diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx index 03da35d..9dbf1b3 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx @@ -90,8 +90,13 @@ const UnconfirmedTransactions: React.FC = () => { } /> - - handleOpenReplaceTransaction(transaction)}>Replace + + handleOpenReplaceTransaction(transaction)} + > + Replace +
))} From b8d12bd6ec4c0260f71379829f5147aa4d1742db Mon Sep 17 00:00:00 2001 From: Maphikza Date: Mon, 9 Sep 2024 13:17:35 +0200 Subject: [PATCH 25/62] Working transaction broadcasting --- .../Balance/components/SendForm/SendForm.tsx | 231 ++++++----- .../UnconfirmedTransactions.tsx | 20 +- .../ReplaceTransaction.styles.ts | 20 + .../ReplaceTransaction/ReplaceTransaction.tsx | 362 ++++++++++++++++-- src/hooks/usePendingTransactions.ts | 11 +- src/utils/utils.ts | 19 +- 6 files changed, 529 insertions(+), 134 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 250627e..f47131a 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -19,8 +19,9 @@ interface PendingTransaction { txid: string; feeRate: number; timestamp: string; // ISO format string - amount: string; + amount: number; recipient_address: string; + enable_rbf: boolean } export type tiers = 'low' | 'med' | 'high'; @@ -36,7 +37,6 @@ const SendForm: React.FC = ({ onSend }) => { const [addressError, setAddressError] = useState(false); const [amountWithFee, setAmountWithFee] = useState(null); - const [totalCost, setTotalCost] = useState(null); // To store the total cost (amount + fee) const [fee, setFee] = useState(0); const [formData, setFormData] = useState({ @@ -45,7 +45,8 @@ const SendForm: React.FC = ({ onSend }) => { }); const [txSize, setTxSize] = useState(null); - const [errorMessage, setErrorMessage] = useState(null); // For error messaging + + const [enableRBF, setEnableRBF] = useState(false); // Default to false // Debounced effect to calculate transaction size when the amount changes useEffect(() => { @@ -83,20 +84,13 @@ const SendForm: React.FC = ({ onSend }) => { useEffect(() => { if (txSize && fee) { const estimatedFee = txSize * fee; - const total = parseInt(formData.amount) + estimatedFee; - setAmountWithFee(total); - setTotalCost(total); + setAmountWithFee(parseInt(formData.amount) + estimatedFee); } - }, [txSize, fee, formData.amount]); + }, [txSize, fee]); - // Check if the total cost exceeds the user's balance - useEffect(() => { - if (totalCost !== null && balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance) { - setErrorMessage('Insufficient balance to complete the transaction.'); - } else { - setErrorMessage(null); // Reset error if the total cost is within balance - } - }, [totalCost, balanceData]); + // useEffect(() => { + // setAmountWithFee(parseInt(formData.amount) + fee); + // }, [fee, formData.amount]); const handleFeeChange = (fee: number) => { setFee(fee); @@ -125,60 +119,72 @@ const SendForm: React.FC = ({ onSend }) => { const handleSend = async () => { if (loading || inValidAmount) return; - + setLoading(true); - - const selectedFee = fee; // Default to low if not selected - + + const selectedFee = fee; // The user-selected fee rate + const transactionRequest = { - choice: 1, // Default to choice 1 for new transactions + choice: 1, // New transaction option recipient_address: formData.address, spend_amount: parseInt(formData.amount), priority_rate: selectedFee, + enable_rbf: enableRBF, }; - + try { + // Step 1: Initiate the new transaction const response = await fetch('http://localhost:9003/transaction', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(transactionRequest), }); - + const result = await response.json(); - setLoading(false); - - if (result.status === 'success') { - // Prepare the transaction details to send to the pending-transactions endpoint + + // Check the status from the wallet's response + if (result.status === 'success' || result.status === 'pending') { + // Step 2: If the transaction succeeds or is pending, update the pending transactions const pendingTransaction: PendingTransaction = { txid: result.txid, - feeRate: selectedFee, - timestamp: new Date().toISOString(), // Capture the current time in ISO format - amount: formData.amount, - recipient_address: formData.address, // Send the recipient address + feeRate: Math.round(selectedFee), // Ensure feeRate is an integer + timestamp: new Date().toISOString(), // Already in correct ISO format expected by Go's time.Time + amount: parseInt(formData.amount, 10), // Parse amount as an integer + recipient_address: formData.address, + enable_rbf: enableRBF, // Already boolean and correct }; - - // Send the transaction details to the pending-transactions endpoint - await fetch(`${config.baseURL}/pending-transactions`, { + + const pendingResponse = await fetch(`${config.baseURL}/pending-transactions`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(pendingTransaction), }); - - // Call the onSend callback with the result - onSend(true, formData.address, transactionRequest.spend_amount, result.txid, result.message); + + const pendingResult = await pendingResponse.json(); + + // Step 3: Handle the final result from updating pending transactions + if (pendingResponse.ok) { + setLoading(false); + onSend(true, formData.address, transactionRequest.spend_amount, result.txid, pendingResult.message); // Notify parent + } else { + setLoading(false); + onSend(false, formData.address, 0, '', pendingResult.error || 'Failed to save pending transaction.'); + } } else { - onSend(false, formData.address, 0, '', result.message); + // Handle error in the wallet's transaction response + setLoading(false); + onSend(false, formData.address, 0, '', result.message || 'Transaction failed.'); } } catch (error) { console.error('Transaction failed:', error); setLoading(false); onSend(false, formData.address, 0, '', 'Transaction failed due to a network error.'); + } finally { + setLoading(false); // Ensure loading stops in all cases } }; + + useEffect(() => { if (formData.amount.length <= 0 || (balanceData && parseInt(formData.amount) > balanceData.latest_balance)) { @@ -186,7 +192,7 @@ const SendForm: React.FC = ({ onSend }) => { } else { setInvalidAmount(false); } - }, [formData.amount, balanceData]); + }, [formData.amount]); const receiverPanel = () => ( <> @@ -215,16 +221,17 @@ const SendForm: React.FC = ({ onSend }) => { Tiered Fees - + setEnableRBF(e.target.checked)} // Update the state when the checkbox is toggled + /> RBF Opt In - {/* Display an error if the total exceeds the balance */} - {errorMessage && {errorMessage}} (null); // Store the transaction ID +// const [verificationInProgress, setVerificationInProgress] = useState(false); // const [isDetailsOpen, setIsDetailsOpen] = useState(false); - // const [inValidAmount, setInvalidAmount] = useState(false); // const [addressError, setAddressError] = useState(false); - // const [amountWithFee, setAmountWithFee] = useState(null); - +// const [totalCost, setTotalCost] = useState(null); // const [fee, setFee] = useState(0); // const [formData, setFormData] = useState({ // address: '', // amount: '1', // }); - // const [txSize, setTxSize] = useState(null); +// const [errorMessage, setErrorMessage] = useState(null); // For error messaging + +// // Function to fetch with a timeout +// const fetchWithTimeout = async (url: string, options: RequestInit, timeout: number = 10000) => { +// const controller = new AbortController(); +// const timeoutId = setTimeout(() => controller.abort(), timeout); + +// try { +// const response = await fetch(url, { +// ...options, +// signal: controller.signal, +// }); +// clearTimeout(timeoutId); // Clear the timeout once the request is completed + +// return response; +// } catch (error) { +// if (error instanceof Error) { +// console.error('Network request failed:', error.message); +// throw new Error(error.message); +// } +// throw error; // Handle other types of network errors +// } +// }; // // Debounced effect to calculate transaction size when the amount changes // useEffect(() => { @@ -315,7 +343,7 @@ export default SendForm; // // Call backend to calculate transaction size // const fetchTransactionSize = async () => { // try { -// const response = await fetch('http://localhost:9003/calculate-tx-size', { +// const response = await fetch('https://localhost:443/calculate-tx-size', { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify({ @@ -344,13 +372,20 @@ export default SendForm; // useEffect(() => { // if (txSize && fee) { // const estimatedFee = txSize * fee; -// setAmountWithFee(parseInt(formData.amount) + estimatedFee); +// const total = parseInt(formData.amount) + estimatedFee; +// setAmountWithFee(total); +// setTotalCost(total); // } -// }, [txSize, fee]); +// }, [txSize, fee, formData.amount]); -// // useEffect(() => { -// // setAmountWithFee(parseInt(formData.amount) + fee); -// // }, [fee, formData.amount]); +// // Check if the total cost exceeds the user's balance +// useEffect(() => { +// if (totalCost !== null && balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance) { +// setErrorMessage('Insufficient balance to complete the transaction.'); +// } else { +// setErrorMessage(null); // Reset error if the total cost is within balance +// } +// }, [totalCost, balanceData]); // const handleFeeChange = (fee: number) => { // setFee(fee); @@ -377,71 +412,85 @@ export default SendForm; // setFormData({ ...formData, [name]: value }); // }; +// // Transaction send and verification process // const handleSend = async () => { // if (loading || inValidAmount) return; // setLoading(true); -// const selectedFee = fee; // Default to low if not selected +// const selectedFee = fee; // Default to the selected fee rate // const transactionRequest = { -// choice: 1, // Default to choice 1 for new transactions +// choice: 1, // Choice 1 for initiating a new transaction // recipient_address: formData.address, // spend_amount: parseInt(formData.amount), // priority_rate: selectedFee, // }; // try { -// const response = await fetch('http://localhost:9003/transaction', { +// // Step 1: Initiate the transaction request +// const response = await fetchWithTimeout('https://localhost:443/transaction', { // method: 'POST', // headers: { // 'Content-Type': 'application/json', // }, // body: JSON.stringify(transactionRequest), -// }); +// }, 100000); // Setting a 30-second timeout for the transaction request // const result = await response.json(); -// setLoading(false); -// if (result.status === 'success') { -// // Prepare the transaction details to send to the pending-transactions endpoint -// const pendingTransaction: PendingTransaction = { -// txid: result.txid, -// feeRate: selectedFee, -// timestamp: new Date().toISOString(), // Capture the current time in ISO format -// amount: formData.amount, -// recipient_address: formData.address, // Send the recipient address +// if (result.status === 'pending') { +// // Transaction broadcasted but pending verification +// console.log('Transaction broadcasted. Verifying in mempool...'); +// setTxID(result.txid); +// setVerificationInProgress(true); + +// // Step 2: Start mempool verification +// const verifyTransaction = async () => { +// const verifyRequest = { txID: result.txid }; +// const verifyResponse = await fetchWithTimeout('https://localhost:443/verify-transaction', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(verifyRequest), +// }, 30000); + +// const verifyResult = await verifyResponse.json(); + +// if (verifyResult.status === 'success') { +// console.log('Transaction verified in the mempool'); +// onSend(true, formData.address, parseInt(formData.amount), verifyResult.txid, verifyResult.message); +// } else if (verifyResult.status === 'pending') { +// console.log('Transaction not found in mempool yet. Will retry.'); +// setTimeout(verifyTransaction, 10000); // Retry after 10 seconds +// } else { +// console.error('Verification failed:', verifyResult.message); +// onSend(false, formData.address, 0, '', verifyResult.message); +// } // }; - -// // Send the transaction details to the pending-transactions endpoint -// await fetch(`${config.baseURL}/pending-transactions`, { -// method: 'POST', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify(pendingTransaction), -// }); - -// // Call the onSend callback with the result -// onSend(true, formData.address, transactionRequest.spend_amount, result.txid, result.message); -// } -// else { + +// verifyTransaction(); // Start verification process + +// } else { +// console.error('Transaction failed:', result.message); // onSend(false, formData.address, 0, '', result.message); +// setLoading(false); // } // } catch (error) { // console.error('Transaction failed:', error); -// setLoading(false); // onSend(false, formData.address, 0, '', 'Transaction failed due to a network error.'); +// } finally { +// setLoading(false); // } // }; +// // Handle invalid amounts // useEffect(() => { // if (formData.amount.length <= 0 || (balanceData && parseInt(formData.amount) > balanceData.latest_balance)) { // setInvalidAmount(true); // } else { // setInvalidAmount(false); // } -// }, [formData.amount]); +// }, [formData.amount, balanceData]); // const receiverPanel = () => ( // <> @@ -475,9 +524,11 @@ export default SendForm; // // // +// {/* Display an error if the total exceeds the balance */} +// {errorMessage && {errorMessage}} // // { const [isReplacingTransaction, setIsReplacingTransaction] = useState(false); const [selectedTransaction, setSelectedTransaction] = useState(null); const { pendingTransactions } = usePendingTransactions(); - const {isTablet, isDesktop} = useResponsive(); + const { isTablet, isDesktop } = useResponsive(); const handleOpenReplaceTransaction = (transaction: PendingTransaction) => { setSelectedTransaction(transaction); @@ -78,20 +78,26 @@ const UnconfirmedTransactions: React.FC = () => { ) : ( {pendingTransactions.map((transaction) => ( - + - handleOpenReplaceTransaction(transaction)}>Replace + {/* Disable the Replace button if enable_rbf is false */} + handleOpenReplaceTransaction(transaction)} + > + Replace + ))} diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts index fd74c2a..cc6f889 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts @@ -69,4 +69,24 @@ export const ErrorMessage = styled.div` font-size: 14px; margin-top: 10px; text-align: center; +`; + +export const CustomFeeInput = styled.input` + width: 100%; + padding: 0.5rem; + border: 1px solid #ccc; + border-radius: 4px; +`; + +export const ToggleCustomFee = styled.button` + margin-top: 1rem; + padding: 0.5rem 1rem; + background-color: #f0f0f0; + border: none; + border-radius: 4px; + cursor: pointer; + + &:hover { + background-color: #e0e0e0; + } `; \ No newline at end of file diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 7469647..927ff1b 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -5,6 +5,7 @@ import TieredFees from '@app/components/nft-dashboard/Balance/components/SendFor import { PendingTransaction } from '@app/hooks/usePendingTransactions'; import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; +import config from '@app/config/config'; import useBalanceData from '@app/hooks/useBalanceData'; interface ReplaceTransactionProps { @@ -16,9 +17,8 @@ interface ReplaceTransactionProps { const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { const { isDesktop, isTablet } = useResponsive(); const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); // Fetch balance data using the hook - const [inValidAmount, setInvalidAmount] = useState(false); - const [newFee, setNewFee] = useState(transaction.FeeRate); // Fee rate in sat/vB + const [newFee, setNewFee] = useState(transaction.feeRate); // Fee rate in sat/vB const [txSize, setTxSize] = useState(null); // State to store transaction size const [loading, setLoading] = useState(false); // Add loading state const [isFinished, setIsFinished] = useState(false); // Add finished state @@ -32,12 +32,12 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep useEffect(() => { const fetchTransactionSize = async () => { try { - const response = await fetch('https://localhost:443/calculate-tx-size', { + const response = await fetch('http://localhost:9003/calculate-tx-size', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - recipient_address: transaction.RecipientAddress, // Use the original recipient address - spend_amount: parseInt(transaction.Amount.toString()), // The original amount + recipient_address: transaction.recipient_address, // Use the original recipient address + spend_amount: parseInt(transaction.amount.toString()), // The original amount priority_rate: newFee, // The current fee rate }), }); @@ -51,10 +51,10 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep }; fetchTransactionSize(); - }, [transaction.TxID, transaction.Amount, newFee]); + }, [transaction.txid, transaction.amount, newFee]); // Calculate the total transaction cost (Amount + Calculated Fee) - const totalCost = txSize && newFee ? Number(transaction.Amount) + newFee * txSize : Number(transaction.Amount); + const totalCost = txSize && newFee ? Number(transaction.amount) + newFee * txSize : Number(transaction.amount); // Check if the total cost exceeds the user's balance const isBalanceInsufficient = balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance; @@ -66,45 +66,75 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep const handleReplace = async (e: React.MouseEvent) => { e.stopPropagation(); setLoading(true); // Start loading - + try { + // Step 1: Initiate the replacement transaction const replaceRequest = { choice: 2, // Replace transaction option - original_tx_id: transaction.TxID, // Send the original transaction ID + original_tx_id: transaction.txid, // Send the original transaction ID new_fee_rate: newFee, // Send the updated fee rate }; - + const response = await fetch('http://localhost:9003/transaction', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(replaceRequest), }); - + const result = await response.json(); - setLoading(false); // Stop loading - - if (result.status === 'success') { - setResult({ isSuccess: true, message: result.message, txid: result.txid }); - setIsFinished(true); - onReplace(); // Notify the parent that the replace was successful + + // Handle different statuses from the wallet (success, pending, or failed) + if (result.status === 'success' || result.status === 'pending') { + + // Step 2: If the replacement transaction succeeds or is pending, update the pending transactions + const updatePendingRequest = { + original_tx_id: transaction.txid, // Original transaction ID to replace + new_tx_id: result.txid, // New transaction ID from the replacement + new_fee_rate: Math.round(newFee), // Updated fee rate + amount: parseInt(transaction.amount, 10), // Same amount + recipient_address: transaction.recipient_address, // Same recipient address + enable_rbf: true, // RBF status + }; + + const pendingResponse = await fetch(`${config.baseURL}/replacement-transactions`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(updatePendingRequest), + }); + + const pendingResult = await pendingResponse.json(); + + // Step 3: Handle the final result from updating pending transactions + if (pendingResponse.ok) { + setResult({ isSuccess: true, message: pendingResult.message, txid: result.txid }); + setIsFinished(true); + onReplace(); // Notify parent that the replacement and update were successful + } else { + setResult({ isSuccess: false, message: pendingResult.error || 'Failed to update pending transaction.', txid: '' }); + setIsFinished(true); + } } else { - setResult({ isSuccess: false, message: result.message, txid: '' }); + // Handle error in the replacement transaction + setResult({ isSuccess: false, message: result.message || 'Replacement transaction failed.', txid: '' }); setIsFinished(true); } } catch (error) { console.error('RBF transaction failed:', error); - setLoading(false); // Stop loading setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); setIsFinished(true); + } finally { + setLoading(false); // Ensure loading stops in all cases } }; + + if (isFinished) { return ( @@ -117,23 +147,35 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep Transaction ID - {transaction.TxID} + {transaction.txid} Amount - {transaction.Amount} + {transaction.amount} {/* Pass the transaction size to TieredFees to dynamically calculate the fee */} - New Fee + New Fee - {newFee} + { + const fee = parseFloat(e.target.value); + if (!isNaN(fee)) { // Ensure the value is a number + setNewFee(fee); // Update the newFee state + } + }} + min={0} // You can add a minimum value of 0 for the fee to avoid negative values + step={1} // Optional: define the increment step for the fee input + /> + Total @@ -160,3 +202,273 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep }; export default ReplaceTransaction; + +// import React, { useEffect, useState } from 'react'; +// import * as S from './ReplaceTransaction.styles'; +// import { useResponsive } from '@app/hooks/useResponsive'; +// import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; +// import { PendingTransaction } from '@app/hooks/usePendingTransactions'; +// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; +// import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; +// import config from '@app/config/config'; +// import useBalanceData from '@app/hooks/useBalanceData'; + +// interface ReplaceTransactionProps { +// onCancel: () => void; +// onReplace: () => void; +// transaction: PendingTransaction; +// } + +// const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { +// const { isDesktop, isTablet } = useResponsive(); +// const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); + +// const [inValidAmount, setInvalidAmount] = useState(false); +// const [newFee, setNewFee] = useState(transaction.feeRate); +// const [txSize, setTxSize] = useState(null); +// const [loading, setLoading] = useState(false); +// const [isFinished, setIsFinished] = useState(false); +// const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ +// isSuccess: false, +// message: '', +// txid: '', +// }); +// const [isCustomFee, setIsCustomFee] = useState(false); +// const [verificationInProgress, setVerificationInProgress] = useState(false); + +// // Fetch the transaction size when the fee changes or the component loads +// useEffect(() => { +// const fetchTransactionSize = async () => { +// try { +// const response = await fetch('https://localhost:443/calculate-tx-size', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ +// recipient_address: transaction.recipient_address, +// spend_amount: parseInt(transaction.amount.toString()), +// priority_rate: newFee, +// }), +// }); + +// const result = await response.json(); +// setTxSize(result.txSize); +// } catch (error) { +// console.error('Error fetching transaction size:', error); +// setTxSize(null); +// } +// }; + +// fetchTransactionSize(); +// }, [transaction.txid, transaction.amount, newFee]); + +// // Calculate total cost +// const totalCost = txSize && newFee ? Number(transaction.amount) + newFee * txSize : Number(transaction.amount); +// const isBalanceInsufficient = balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance; + +// // Handle fee changes +// const handleFeeChange = (fee: number) => { +// setNewFee(fee); +// setIsCustomFee(false); +// }; + +// const handleCustomFeeChange = (e: React.ChangeEvent) => { +// const value = parseFloat(e.target.value); +// if (!isNaN(value) && value >= 0) { +// setNewFee(value); +// setIsCustomFee(true); +// } +// }; + +// const fetchWithTimeout = async (url: string, options: RequestInit, timeout: number = 10000) => { +// const controller = new AbortController(); +// const timeoutId = setTimeout(() => controller.abort(), timeout); + +// try { +// const response = await fetch(url, { +// ...options, +// signal: controller.signal, +// }); +// clearTimeout(timeoutId); +// return response; +// } catch (error) { +// if (error instanceof Error) { +// console.error('RBF transaction failed:', error.message); +// throw new Error(error.message); +// } +// throw error; +// } +// }; + +// // Toggle between custom and preset fees +// const toggleCustomFee = () => { +// setIsCustomFee(!isCustomFee); +// }; + +// // Handle Replace transaction and verification +// const handleReplace = async (e: React.MouseEvent) => { +// e.stopPropagation(); +// setLoading(true); + +// try { +// const replaceRequest = { +// choice: 2, +// original_tx_id: transaction.txid, +// new_fee_rate: newFee, +// }; + +// // Step 1: Broadcast the transaction +// const response = await fetchWithTimeout('https://localhost:443/transaction', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(replaceRequest), +// }, 30000); + +// const result = await response.json(); + +// if (result.status === 'pending' || result.status === 'success') { +// console.log('Transaction broadcasted. Updating pending transaction...'); + +// const updatePendingRequest = { +// original_tx_id: transaction.txid, +// new_tx_id: result.txid, +// new_fee_rate: newFee, +// amount: transaction.amount, +// recipient_address: transaction.recipient_address, +// }; + +// // Step 2: Update the pending transaction +// const pendingResponse = await fetchWithTimeout(`${config.baseURL}/replacement-transactions`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(updatePendingRequest), +// }, 100000); + +// const pendingResult = await pendingResponse.json(); + +// if (pendingResult.status === 'success') { +// setResult({ isSuccess: true, message: pendingResult.message, txid: pendingResult.txid }); +// setVerificationInProgress(false); +// } else { +// setResult({ isSuccess: false, message: pendingResult.message, txid: '' }); +// } + +// // Step 3: Verify the transaction in the mempool +// if (result.status === 'pending') { +// console.log('Transaction broadcasted but pending verification. Verifying in mempool...'); +// setVerificationInProgress(true); + +// const verifyTransaction = async () => { +// const verifyRequest = { txID: result.txid }; +// const verifyResponse = await fetchWithTimeout('https://localhost:443/verify-transaction', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(verifyRequest), +// }, 30000); + +// const verifyResult = await verifyResponse.json(); + +// if (verifyResult.status === 'success') { +// console.log('Transaction verified in the mempool'); +// setResult({ isSuccess: true, message: verifyResult.message, txid: verifyResult.txid }); +// } else if (verifyResult.status === 'pending') { +// console.log('Transaction not found in mempool yet. Retrying in 10 seconds.'); +// setTimeout(verifyTransaction, 10000); // Retry after 10 seconds +// } else { +// console.error('Verification failed:', verifyResult.message); +// setResult({ isSuccess: false, message: verifyResult.message, txid: '' }); +// } +// }; + +// verifyTransaction(); // Start the verification process +// } + +// } else { +// setResult({ isSuccess: false, message: result.message, txid: '' }); +// } +// } catch (error) { +// console.error('RBF transaction failed:', error); +// setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); +// } finally { +// setIsFinished(true); +// setLoading(false); +// } +// }; + +// // If the process is finished, display the result +// if (isFinished) { +// return ( +// +// ); +// } + +// // Render the form UI +// return ( +// +// +// +// Transaction ID +// +// {transaction.txid} +// +// +// +// Amount +// +// {transaction.amount} +// +// + +// {!isCustomFee && ( +// +// )} + +// +// New Fee +// +// {isCustomFee ? ( +// +// ) : ( +// {newFee} +// )} +// +// + +// +// {isCustomFee ? "Use preset fees" : "Enter custom fee"} +// + +// +// Total +// +// {totalCost} +// +// + +// {isBalanceInsufficient && ( +// Insufficient balance to complete the transaction. +// )} + +// +// Cancel +// +// Replace +// +// +// +// +// ); +// }; + +// export default ReplaceTransaction; diff --git a/src/hooks/usePendingTransactions.ts b/src/hooks/usePendingTransactions.ts index c268419..783889c 100644 --- a/src/hooks/usePendingTransactions.ts +++ b/src/hooks/usePendingTransactions.ts @@ -2,11 +2,12 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; export interface PendingTransaction { - TxID: string; - FeeRate: number; - Timestamp: string; // ISO format string - Amount: string; - RecipientAddress: string; // Add recipient address + txid: string; + feeRate: number; + timestamp: string; // ISO format string + amount: string; + recipient_address: string; // Add recipient address + enable_rbf: boolean } const usePendingTransactions = () => { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 701abff..2fe980d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -158,13 +158,18 @@ export const formatNumberWithCommas = (value: number): string => { export const msToH = (ms: number): number => Math.floor(ms / 3600000); export const hToMS = (h: number): number => h * 3600000; -export const truncateString = (str: string, num: number): string => { -if (str.length <= num) { -return str; -}else{ -return str.slice(0, num) + '...'; -} -} +export const truncateString = (str: string | null | undefined, num: number): string => { + if (!str) { // Check if str is undefined or null + return ''; // Return an empty string if str is not defined + } + + if (str.length <= num) { + return str; + } else { + return str.slice(0, num) + '...'; + } +}; + export const getPaymentCardTypeIcon = (type: string): string | null => { switch (type) { case 'visa': From 3958e7a2d56c2eae6b85a6a1f6824c7bdf1d8efe Mon Sep 17 00:00:00 2001 From: Maphikza Date: Mon, 9 Sep 2024 13:39:11 +0200 Subject: [PATCH 26/62] Fixing signing window loading at login page. --- src/components/auth/LoginForm/LoginForm.tsx | 33 +++++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/components/auth/LoginForm/LoginForm.tsx b/src/components/auth/LoginForm/LoginForm.tsx index 74eecfa..be4949d 100644 --- a/src/components/auth/LoginForm/LoginForm.tsx +++ b/src/components/auth/LoginForm/LoginForm.tsx @@ -45,7 +45,6 @@ export const LoginForm: React.FC = () => { const dispatch = useDispatch(); const [form] = Form.useForm(); - const [event, setEvent] = useState(null); useEffect(() => { const fetchPublicKey = async () => { @@ -60,37 +59,44 @@ export const LoginForm: React.FC = () => { console.error('Failed to get public key:', error); } }; - - fetchPublicKey(); - - const isFirstLoad = localStorage.getItem('isFirstLoad'); - if (!isFirstLoad) { - window.location.reload(); - localStorage.setItem('isFirstLoad', 'true'); - } + + const intervalId = setInterval(() => { + if (window.nostr) { + fetchPublicKey(); + clearInterval(intervalId); + } + }, 1000); // Retry every 1 second + + return () => clearInterval(intervalId); // Clear the interval on component unmount }, [form]); + + + const [event, setEvent] = useState(null); const handleSubmit = async (values: LoginFormData) => { try { + if (!window.nostr) { + notificationController.error({ message: 'Nostr extension is not available' }); + return; + } + const { success, event } = await login(values); if (success && event) { setEvent(event); - // Automatically proceed to verification const signedEvent = await window.nostr.signEvent(event); console.log('Signed event:', signedEvent); - + const response = await verifyChallenge({ challenge: signedEvent.content, signature: signedEvent.sig, messageHash: signedEvent.id, event: signedEvent, }); - + if (response.success) { if (response.token && response.user) { persistToken(response.token); dispatch(setUser(response.user)); - // Set the token in the API instance notificationController.success({ message: 'Login successful', description: 'You have successfully logged in!', @@ -105,6 +111,7 @@ export const LoginForm: React.FC = () => { notificationController.error({ message: error.message }); } }; + return ( From aae0f2faba52e139c2ba213d13bb1a1654c0bf0e Mon Sep 17 00:00:00 2001 From: Maphikza Date: Mon, 9 Sep 2024 21:40:42 +0200 Subject: [PATCH 27/62] Adding jwt to manage session length for transaction context --- .../Balance/components/SendForm/SendForm.tsx | 355 ++++++++++++++++-- .../ReplaceTransaction/ReplaceTransaction.tsx | 320 ++++++++++++++-- src/services/localStorage.service.ts | 13 + 3 files changed, 626 insertions(+), 62 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index f47131a..0f12ed0 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -10,6 +10,10 @@ import useBalanceData from '@app/hooks/useBalanceData'; import { BaseCheckbox } from '@app/components/common/BaseCheckbox/BaseCheckbox'; import config from '@app/config/config'; import TieredFees from './components/TieredFees/TieredFees'; +import useWalletAuth from '@app/hooks/useWalletAuth'; // Import the auth hook +import { notificationController } from '@app/controllers/notificationController'; +import { deleteWalletToken } from '@app/services/localStorage.service'; // Assuming this is where deleteWalletToken is defined + interface SendFormProps { onSend: (status: boolean, address: string, amount: number, txid?: string, message?: string) => void; @@ -21,13 +25,14 @@ interface PendingTransaction { timestamp: string; // ISO format string amount: number; recipient_address: string; - enable_rbf: boolean + enable_rbf: boolean; } export type tiers = 'low' | 'med' | 'high'; const SendForm: React.FC = ({ onSend }) => { const { balanceData, isLoading } = useBalanceData(); + const { isAuthenticated, login, token, loading: authLoading } = useWalletAuth(); // Use the auth hook const [loading, setLoading] = useState(false); @@ -48,37 +53,56 @@ const SendForm: React.FC = ({ onSend }) => { const [enableRBF, setEnableRBF] = useState(false); // Default to false - // Debounced effect to calculate transaction size when the amount changes + // Debounced effect to calculate transaction size when the amount changes, with JWT useEffect(() => { const debounceTimeout = setTimeout(() => { - if (formData.amount.length > 0) { - // Call backend to calculate transaction size - const fetchTransactionSize = async () => { + const fetchTransactionSize = async () => { + if (formData.amount.length > 0) { try { + // Ensure user is authenticated + if (!isAuthenticated) { + await login(); // Perform login if not authenticated + } + const response = await fetch('http://localhost:9003/calculate-tx-size', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Include JWT token in headers + }, body: JSON.stringify({ recipient_address: formData.address, spend_amount: parseInt(formData.amount), - priority_rate: fee, // Pass the fee rate + priority_rate: fee, }), }); - + + if (response.status === 401) { + const errorText = await response.text(); + if (errorText.includes("Token expired")) { + // Token has expired, trigger a re-login + notificationController.error({ message: 'Session expired. Please log in again.' }); + deleteWalletToken(); // Clear the old token + await login(); // Re-initiate login + } + throw new Error(errorText); + } + const result = await response.json(); - setTxSize(result.txSize); // Set the transaction size + setTxSize(result.txSize); } catch (error) { console.error('Error fetching transaction size:', error); setTxSize(null); } - }; + } + }; + - fetchTransactionSize(); - } + fetchTransactionSize(); }, 500); // Debounce for 500ms return () => clearTimeout(debounceTimeout); // Clear the timeout if the amount changes before 500ms - }, [formData.amount, fee]); + }, [formData.amount, fee, isAuthenticated, login, token]); // Calculate the fee based on the transaction size useEffect(() => { @@ -88,10 +112,6 @@ const SendForm: React.FC = ({ onSend }) => { } }, [txSize, fee]); - // useEffect(() => { - // setAmountWithFee(parseInt(formData.amount) + fee); - // }, [fee, formData.amount]); - const handleFeeChange = (fee: number) => { setFee(fee); }; @@ -129,17 +149,36 @@ const SendForm: React.FC = ({ onSend }) => { recipient_address: formData.address, spend_amount: parseInt(formData.amount), priority_rate: selectedFee, - enable_rbf: enableRBF, + enable_rbf: enableRBF, }; try { - // Step 1: Initiate the new transaction + // Step 1: Ensure the user is authenticated + if (!isAuthenticated) { + await login(); // Perform login if not authenticated + } + + // Step 2: Initiate the new transaction with the JWT token const response = await fetch('http://localhost:9003/transaction', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Include JWT token in headers + }, body: JSON.stringify(transactionRequest), }); + if (response.status === 401) { + const errorText = await response.text(); + if (errorText.includes("Token expired")) { + // Token has expired, trigger a re-login + notificationController.error({ message: 'Session expired. Please log in again.' }); + deleteWalletToken(); // Clear the old token + await login(); // Re-initiate login + } + throw new Error(errorText); + } + const result = await response.json(); // Check the status from the wallet's response @@ -185,7 +224,6 @@ const SendForm: React.FC = ({ onSend }) => { }; - useEffect(() => { if (formData.amount.length <= 0 || (balanceData && parseInt(formData.amount) > balanceData.latest_balance)) { setInvalidAmount(true); @@ -231,7 +269,7 @@ const SendForm: React.FC = ({ onSend }) => { = ({ onSend }) => { ); return ( - + Send @@ -268,6 +306,277 @@ const SendForm: React.FC = ({ onSend }) => { export default SendForm; + +// import React, { useEffect, useState } from 'react'; +// import { BaseInput } from '@app/components/common/inputs/BaseInput/BaseInput'; +// import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; +// import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; +// import { useResponsive } from '@app/hooks/useResponsive'; +// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; +// import * as S from './SendForm.styles'; +// import { truncateString } from '@app/utils/utils'; +// import useBalanceData from '@app/hooks/useBalanceData'; +// import { BaseCheckbox } from '@app/components/common/BaseCheckbox/BaseCheckbox'; +// import config from '@app/config/config'; +// import TieredFees from './components/TieredFees/TieredFees'; + +// interface SendFormProps { +// onSend: (status: boolean, address: string, amount: number, txid?: string, message?: string) => void; +// } + +// interface PendingTransaction { +// txid: string; +// feeRate: number; +// timestamp: string; // ISO format string +// amount: number; +// recipient_address: string; +// enable_rbf: boolean +// } + +// export type tiers = 'low' | 'med' | 'high'; + +// const SendForm: React.FC = ({ onSend }) => { +// const { balanceData, isLoading } = useBalanceData(); + +// const [loading, setLoading] = useState(false); + +// const [isDetailsOpen, setIsDetailsOpen] = useState(false); + +// const [inValidAmount, setInvalidAmount] = useState(false); +// const [addressError, setAddressError] = useState(false); + +// const [amountWithFee, setAmountWithFee] = useState(null); + +// const [fee, setFee] = useState(0); +// const [formData, setFormData] = useState({ +// address: '', +// amount: '1', +// }); + +// const [txSize, setTxSize] = useState(null); + +// const [enableRBF, setEnableRBF] = useState(false); // Default to false + +// // Debounced effect to calculate transaction size when the amount changes +// useEffect(() => { +// const debounceTimeout = setTimeout(() => { +// if (formData.amount.length > 0) { +// // Call backend to calculate transaction size +// const fetchTransactionSize = async () => { +// try { +// const response = await fetch('http://localhost:9003/calculate-tx-size', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ +// recipient_address: formData.address, +// spend_amount: parseInt(formData.amount), +// priority_rate: fee, // Pass the fee rate +// }), +// }); + +// const result = await response.json(); +// setTxSize(result.txSize); // Set the transaction size +// } catch (error) { +// console.error('Error fetching transaction size:', error); +// setTxSize(null); +// } +// }; + +// fetchTransactionSize(); +// } +// }, 500); // Debounce for 500ms + +// return () => clearTimeout(debounceTimeout); // Clear the timeout if the amount changes before 500ms +// }, [formData.amount, fee]); + +// // Calculate the fee based on the transaction size +// useEffect(() => { +// if (txSize && fee) { +// const estimatedFee = txSize * fee; +// setAmountWithFee(parseInt(formData.amount) + estimatedFee); +// } +// }, [txSize, fee]); + +// // useEffect(() => { +// // setAmountWithFee(parseInt(formData.amount) + fee); +// // }, [fee, formData.amount]); + +// const handleFeeChange = (fee: number) => { +// setFee(fee); +// }; + +// const isValidAddress = (address: string) => { +// return address.length > 0; +// }; + +// const handleAddressSubmit = () => { +// const isValid = isValidAddress(formData.address); + +// if (isValid) { +// setAddressError(false); +// setIsDetailsOpen(true); +// } else { +// setAddressError(true); +// } +// }; + +// const handleInputChange = (e: React.ChangeEvent) => { +// e.preventDefault(); +// const { name, value } = e.target; +// setFormData({ ...formData, [name]: value }); +// }; + +// const handleSend = async () => { +// if (loading || inValidAmount) return; + +// setLoading(true); + +// const selectedFee = fee; // The user-selected fee rate + +// const transactionRequest = { +// choice: 1, // New transaction option +// recipient_address: formData.address, +// spend_amount: parseInt(formData.amount), +// priority_rate: selectedFee, +// enable_rbf: enableRBF, +// }; + +// try { +// // Step 1: Initiate the new transaction +// const response = await fetch('http://localhost:9003/transaction', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(transactionRequest), +// }); + +// const result = await response.json(); + +// // Check the status from the wallet's response +// if (result.status === 'success' || result.status === 'pending') { +// // Step 2: If the transaction succeeds or is pending, update the pending transactions +// const pendingTransaction: PendingTransaction = { +// txid: result.txid, +// feeRate: Math.round(selectedFee), // Ensure feeRate is an integer +// timestamp: new Date().toISOString(), // Already in correct ISO format expected by Go's time.Time +// amount: parseInt(formData.amount, 10), // Parse amount as an integer +// recipient_address: formData.address, +// enable_rbf: enableRBF, // Already boolean and correct +// }; + +// const pendingResponse = await fetch(`${config.baseURL}/pending-transactions`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(pendingTransaction), +// }); + +// const pendingResult = await pendingResponse.json(); + +// // Step 3: Handle the final result from updating pending transactions +// if (pendingResponse.ok) { +// setLoading(false); +// onSend(true, formData.address, transactionRequest.spend_amount, result.txid, pendingResult.message); // Notify parent +// } else { +// setLoading(false); +// onSend(false, formData.address, 0, '', pendingResult.error || 'Failed to save pending transaction.'); +// } +// } else { +// // Handle error in the wallet's transaction response +// setLoading(false); +// onSend(false, formData.address, 0, '', result.message || 'Transaction failed.'); +// } +// } catch (error) { +// console.error('Transaction failed:', error); +// setLoading(false); +// onSend(false, formData.address, 0, '', 'Transaction failed due to a network error.'); +// } finally { +// setLoading(false); // Ensure loading stops in all cases +// } +// }; + + + +// useEffect(() => { +// if (formData.amount.length <= 0 || (balanceData && parseInt(formData.amount) > balanceData.latest_balance)) { +// setInvalidAmount(true); +// } else { +// setInvalidAmount(false); +// } +// }, [formData.amount]); + +// const receiverPanel = () => ( +// <> +// +// Address +// +// +// Continue +// +// ); + +// const detailsPanel = () => ( +// +// +// +// {`Amount = ${amountWithFee ? amountWithFee : ''}`} + +// {inValidAmount && Invalid Amount} +// + +//
+// +// {`Balance: ${balanceData ? balanceData.latest_balance : 0}`} +//
+//
+// +// Tiered Fees +// +// setEnableRBF(e.target.checked)} // Update the state when the checkbox is toggled +// /> +// RBF Opt In +// +// +// +// +// +// Send +// +// +//
+// ); + +// return ( +// +// +// +// Send +// {isDetailsOpen ? ( +// <> +// +// To: +//
+// {truncateString(formData.address, 65)} +//
+// {detailsPanel()} +// +// ) : ( +// receiverPanel() +// )} +//
+//
+//
+// ); +// }; + +// export default SendForm; + + // import React, { useEffect, useState } from 'react'; // import { BaseInput } from '@app/components/common/inputs/BaseInput/BaseInput'; // import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 927ff1b..72987c1 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -7,6 +7,9 @@ import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; import config from '@app/config/config'; import useBalanceData from '@app/hooks/useBalanceData'; +import useWalletAuth from '@app/hooks/useWalletAuth'; // Import authentication hook +import { notificationController } from '@app/controllers/notificationController'; // Handle notifications +import { deleteWalletToken } from '@app/services/localStorage.service'; // Delete wallet token if expired interface ReplaceTransactionProps { onCancel: () => void; @@ -17,11 +20,12 @@ interface ReplaceTransactionProps { const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { const { isDesktop, isTablet } = useResponsive(); const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); // Fetch balance data using the hook + const { isAuthenticated, login, token } = useWalletAuth(); // Use wallet authentication const [inValidAmount, setInvalidAmount] = useState(false); const [newFee, setNewFee] = useState(transaction.feeRate); // Fee rate in sat/vB - const [txSize, setTxSize] = useState(null); // State to store transaction size - const [loading, setLoading] = useState(false); // Add loading state - const [isFinished, setIsFinished] = useState(false); // Add finished state + const [txSize, setTxSize] = useState(null); // State to store transaction size + const [loading, setLoading] = useState(false); // Add loading state + const [isFinished, setIsFinished] = useState(false); // Add finished state const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ isSuccess: false, message: '', @@ -32,9 +36,16 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep useEffect(() => { const fetchTransactionSize = async () => { try { + if (!isAuthenticated) { + await login(); // Ensure user is logged in + } + const response = await fetch('http://localhost:9003/calculate-tx-size', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Include JWT token + }, body: JSON.stringify({ recipient_address: transaction.recipient_address, // Use the original recipient address spend_amount: parseInt(transaction.amount.toString()), // The original amount @@ -42,8 +53,18 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep }), }); + if (response.status === 401) { + const errorText = await response.text(); + if (errorText.includes('Token expired')) { + notificationController.error({ message: 'Session expired. Please log in again.' }); + deleteWalletToken(); // Clear the old token + await login(); // Re-initiate login + } + throw new Error(errorText); + } + const result = await response.json(); - setTxSize(result.txSize); // Set the transaction size + setTxSize(result.txSize); // Set the transaction size } catch (error) { console.error('Error fetching transaction size:', error); setTxSize(null); @@ -51,7 +72,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep }; fetchTransactionSize(); - }, [transaction.txid, transaction.amount, newFee]); + }, [transaction.txid, transaction.amount, newFee, isAuthenticated, login, token]); // Calculate the total transaction cost (Amount + Calculated Fee) const totalCost = txSize && newFee ? Number(transaction.amount) + newFee * txSize : Number(transaction.amount); @@ -60,55 +81,72 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep const isBalanceInsufficient = balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance; const handleFeeChange = (fee: number) => { - setNewFee(fee); // Update the new fee when it changes + setNewFee(fee); // Update the new fee when it changes }; const handleReplace = async (e: React.MouseEvent) => { e.stopPropagation(); - setLoading(true); // Start loading - + setLoading(true); // Start loading + try { - // Step 1: Initiate the replacement transaction + // Step 1: Ensure user is authenticated + if (!isAuthenticated) { + await login(); // Perform login if not authenticated + } + + // Step 2: Initiate the replacement transaction const replaceRequest = { - choice: 2, // Replace transaction option - original_tx_id: transaction.txid, // Send the original transaction ID - new_fee_rate: newFee, // Send the updated fee rate + choice: 2, // Replace transaction option + original_tx_id: transaction.txid, // Send the original transaction ID + new_fee_rate: newFee, // Send the updated fee rate }; - + const response = await fetch('http://localhost:9003/transaction', { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Include JWT token + }, body: JSON.stringify(replaceRequest), }); - + + if (response.status === 401) { + const errorText = await response.text(); + if (errorText.includes('Token expired')) { + notificationController.error({ message: 'Session expired. Please log in again.' }); + deleteWalletToken(); // Clear the old token + await login(); // Re-initiate login + } + throw new Error(errorText); + } + const result = await response.json(); - + // Handle different statuses from the wallet (success, pending, or failed) if (result.status === 'success' || result.status === 'pending') { - // Step 2: If the replacement transaction succeeds or is pending, update the pending transactions const updatePendingRequest = { - original_tx_id: transaction.txid, // Original transaction ID to replace - new_tx_id: result.txid, // New transaction ID from the replacement - new_fee_rate: Math.round(newFee), // Updated fee rate - amount: parseInt(transaction.amount, 10), // Same amount - recipient_address: transaction.recipient_address, // Same recipient address - enable_rbf: true, // RBF status + original_tx_id: transaction.txid, // Original transaction ID to replace + new_tx_id: result.txid, // New transaction ID from the replacement + new_fee_rate: Math.round(newFee), // Updated fee rate + amount: parseInt(transaction.amount, 10), // Same amount + recipient_address: transaction.recipient_address, // Same recipient address + enable_rbf: true, // RBF status }; - + const pendingResponse = await fetch(`${config.baseURL}/replacement-transactions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updatePendingRequest), }); - + const pendingResult = await pendingResponse.json(); - + // Step 3: Handle the final result from updating pending transactions if (pendingResponse.ok) { setResult({ isSuccess: true, message: pendingResult.message, txid: result.txid }); setIsFinished(true); - onReplace(); // Notify parent that the replacement and update were successful + onReplace(); // Notify parent that the replacement and update were successful } else { setResult({ isSuccess: false, message: pendingResult.error || 'Failed to update pending transaction.', txid: '' }); setIsFinished(true); @@ -123,17 +161,15 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); setIsFinished(true); } finally { - setLoading(false); // Ensure loading stops in all cases + setLoading(false); // Ensure loading stops in all cases } }; - - if (isFinished) { return ( = ({ onCancel, onRep {/* Pass the transaction size to TieredFees to dynamically calculate the fee */} - New Fee + New Fee { const fee = parseFloat(e.target.value); - if (!isNaN(fee)) { // Ensure the value is a number - setNewFee(fee); // Update the newFee state + if (!isNaN(fee)) { + setNewFee(fee); // Update the newFee state } }} - min={0} // You can add a minimum value of 0 for the fee to avoid negative values - step={1} // Optional: define the increment step for the fee input + min={0} // Minimum value of 0 for the fee + step={1} // Optional: define the increment step for the fee input /> @@ -203,6 +239,212 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep export default ReplaceTransaction; + +// import React, { useEffect, useState } from 'react'; +// import * as S from './ReplaceTransaction.styles'; +// import { useResponsive } from '@app/hooks/useResponsive'; +// import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; +// import { PendingTransaction } from '@app/hooks/usePendingTransactions'; +// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; +// import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; +// import config from '@app/config/config'; +// import useBalanceData from '@app/hooks/useBalanceData'; + +// interface ReplaceTransactionProps { +// onCancel: () => void; +// onReplace: () => void; +// transaction: PendingTransaction; +// } + +// const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { +// const { isDesktop, isTablet } = useResponsive(); +// const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); // Fetch balance data using the hook +// const [inValidAmount, setInvalidAmount] = useState(false); +// const [newFee, setNewFee] = useState(transaction.feeRate); // Fee rate in sat/vB +// const [txSize, setTxSize] = useState(null); // State to store transaction size +// const [loading, setLoading] = useState(false); // Add loading state +// const [isFinished, setIsFinished] = useState(false); // Add finished state +// const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ +// isSuccess: false, +// message: '', +// txid: '', +// }); + +// // Fetch the transaction size when the component mounts +// useEffect(() => { +// const fetchTransactionSize = async () => { +// try { +// const response = await fetch('http://localhost:9003/calculate-tx-size', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ +// recipient_address: transaction.recipient_address, // Use the original recipient address +// spend_amount: parseInt(transaction.amount.toString()), // The original amount +// priority_rate: newFee, // The current fee rate +// }), +// }); + +// const result = await response.json(); +// setTxSize(result.txSize); // Set the transaction size +// } catch (error) { +// console.error('Error fetching transaction size:', error); +// setTxSize(null); +// } +// }; + +// fetchTransactionSize(); +// }, [transaction.txid, transaction.amount, newFee]); + +// // Calculate the total transaction cost (Amount + Calculated Fee) +// const totalCost = txSize && newFee ? Number(transaction.amount) + newFee * txSize : Number(transaction.amount); + +// // Check if the total cost exceeds the user's balance +// const isBalanceInsufficient = balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance; + +// const handleFeeChange = (fee: number) => { +// setNewFee(fee); // Update the new fee when it changes +// }; + +// const handleReplace = async (e: React.MouseEvent) => { +// e.stopPropagation(); +// setLoading(true); // Start loading + +// try { +// // Step 1: Initiate the replacement transaction +// const replaceRequest = { +// choice: 2, // Replace transaction option +// original_tx_id: transaction.txid, // Send the original transaction ID +// new_fee_rate: newFee, // Send the updated fee rate +// }; + +// const response = await fetch('http://localhost:9003/transaction', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(replaceRequest), +// }); + +// const result = await response.json(); + +// // Handle different statuses from the wallet (success, pending, or failed) +// if (result.status === 'success' || result.status === 'pending') { + +// // Step 2: If the replacement transaction succeeds or is pending, update the pending transactions +// const updatePendingRequest = { +// original_tx_id: transaction.txid, // Original transaction ID to replace +// new_tx_id: result.txid, // New transaction ID from the replacement +// new_fee_rate: Math.round(newFee), // Updated fee rate +// amount: parseInt(transaction.amount, 10), // Same amount +// recipient_address: transaction.recipient_address, // Same recipient address +// enable_rbf: true, // RBF status +// }; + +// const pendingResponse = await fetch(`${config.baseURL}/replacement-transactions`, { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify(updatePendingRequest), +// }); + +// const pendingResult = await pendingResponse.json(); + +// // Step 3: Handle the final result from updating pending transactions +// if (pendingResponse.ok) { +// setResult({ isSuccess: true, message: pendingResult.message, txid: result.txid }); +// setIsFinished(true); +// onReplace(); // Notify parent that the replacement and update were successful +// } else { +// setResult({ isSuccess: false, message: pendingResult.error || 'Failed to update pending transaction.', txid: '' }); +// setIsFinished(true); +// } +// } else { +// // Handle error in the replacement transaction +// setResult({ isSuccess: false, message: result.message || 'Replacement transaction failed.', txid: '' }); +// setIsFinished(true); +// } +// } catch (error) { +// console.error('RBF transaction failed:', error); +// setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); +// setIsFinished(true); +// } finally { +// setLoading(false); // Ensure loading stops in all cases +// } +// }; + + + +// if (isFinished) { +// return ( +// +// ); +// } + +// return ( +// +// +// +// Transaction ID +// +// {transaction.txid} +// +// +// +// Amount +// +// {transaction.amount} +// +// +// {/* Pass the transaction size to TieredFees to dynamically calculate the fee */} +// +// +// New Fee +// +// { +// const fee = parseFloat(e.target.value); +// if (!isNaN(fee)) { // Ensure the value is a number +// setNewFee(fee); // Update the newFee state +// } +// }} +// min={0} // You can add a minimum value of 0 for the fee to avoid negative values +// step={1} // Optional: define the increment step for the fee input +// /> +// +// + +// +// Total +// +// {/* Calculate total amount (Amount + Fee based on transaction size) */} +// {totalCost} +// +// + +// {/* Show error message if balance is insufficient */} +// {isBalanceInsufficient && ( +// Insufficient balance to complete the transaction. +// )} + +// +// Cancel +// {/* Disable replace button if total cost exceeds balance */} +// +// Replace +// +// +// +// +// ); +// }; + +// export default ReplaceTransaction; + // import React, { useEffect, useState } from 'react'; // import * as S from './ReplaceTransaction.styles'; // import { useResponsive } from '@app/hooks/useResponsive'; diff --git a/src/services/localStorage.service.ts b/src/services/localStorage.service.ts index 2aa56a0..0635d62 100644 --- a/src/services/localStorage.service.ts +++ b/src/services/localStorage.service.ts @@ -43,3 +43,16 @@ export const readRelayMode = (): 'unlimited' | 'smart' => { export const deleteToken = (): void => localStorage.removeItem('accessToken'); export const deleteUser = (): void => localStorage.removeItem('user'); + +// This is the new token management for wallet auth +export const persistWalletToken = (token: string): void => { + localStorage.setItem('walletToken', token); // Use a different key for the wallet token +}; + +export const readWalletToken = (): string => { + return localStorage.getItem('walletToken') || ''; // Read the wallet token, default is empty if not present +}; + +export const deleteWalletToken = (): void => { + localStorage.removeItem('walletToken'); // Remove the wallet token when logging out +}; \ No newline at end of file From 00c39717e500de8d95a68919d4d3fa56ed99b592 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Mon, 9 Sep 2024 21:42:05 +0200 Subject: [PATCH 28/62] Adding jwt hook code and removing redundant code --- .../Balance/components/SendForm/SendForm.tsx | 568 ------------------ .../ReplaceTransaction/ReplaceTransaction.tsx | 476 --------------- src/hooks/useWalletAuth.ts | 188 ++++++ 3 files changed, 188 insertions(+), 1044 deletions(-) create mode 100644 src/hooks/useWalletAuth.ts diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 0f12ed0..97ee0b7 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -304,571 +304,3 @@ const SendForm: React.FC = ({ onSend }) => { }; export default SendForm; - - - -// import React, { useEffect, useState } from 'react'; -// import { BaseInput } from '@app/components/common/inputs/BaseInput/BaseInput'; -// import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; -// import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; -// import { useResponsive } from '@app/hooks/useResponsive'; -// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; -// import * as S from './SendForm.styles'; -// import { truncateString } from '@app/utils/utils'; -// import useBalanceData from '@app/hooks/useBalanceData'; -// import { BaseCheckbox } from '@app/components/common/BaseCheckbox/BaseCheckbox'; -// import config from '@app/config/config'; -// import TieredFees from './components/TieredFees/TieredFees'; - -// interface SendFormProps { -// onSend: (status: boolean, address: string, amount: number, txid?: string, message?: string) => void; -// } - -// interface PendingTransaction { -// txid: string; -// feeRate: number; -// timestamp: string; // ISO format string -// amount: number; -// recipient_address: string; -// enable_rbf: boolean -// } - -// export type tiers = 'low' | 'med' | 'high'; - -// const SendForm: React.FC = ({ onSend }) => { -// const { balanceData, isLoading } = useBalanceData(); - -// const [loading, setLoading] = useState(false); - -// const [isDetailsOpen, setIsDetailsOpen] = useState(false); - -// const [inValidAmount, setInvalidAmount] = useState(false); -// const [addressError, setAddressError] = useState(false); - -// const [amountWithFee, setAmountWithFee] = useState(null); - -// const [fee, setFee] = useState(0); -// const [formData, setFormData] = useState({ -// address: '', -// amount: '1', -// }); - -// const [txSize, setTxSize] = useState(null); - -// const [enableRBF, setEnableRBF] = useState(false); // Default to false - -// // Debounced effect to calculate transaction size when the amount changes -// useEffect(() => { -// const debounceTimeout = setTimeout(() => { -// if (formData.amount.length > 0) { -// // Call backend to calculate transaction size -// const fetchTransactionSize = async () => { -// try { -// const response = await fetch('http://localhost:9003/calculate-tx-size', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ -// recipient_address: formData.address, -// spend_amount: parseInt(formData.amount), -// priority_rate: fee, // Pass the fee rate -// }), -// }); - -// const result = await response.json(); -// setTxSize(result.txSize); // Set the transaction size -// } catch (error) { -// console.error('Error fetching transaction size:', error); -// setTxSize(null); -// } -// }; - -// fetchTransactionSize(); -// } -// }, 500); // Debounce for 500ms - -// return () => clearTimeout(debounceTimeout); // Clear the timeout if the amount changes before 500ms -// }, [formData.amount, fee]); - -// // Calculate the fee based on the transaction size -// useEffect(() => { -// if (txSize && fee) { -// const estimatedFee = txSize * fee; -// setAmountWithFee(parseInt(formData.amount) + estimatedFee); -// } -// }, [txSize, fee]); - -// // useEffect(() => { -// // setAmountWithFee(parseInt(formData.amount) + fee); -// // }, [fee, formData.amount]); - -// const handleFeeChange = (fee: number) => { -// setFee(fee); -// }; - -// const isValidAddress = (address: string) => { -// return address.length > 0; -// }; - -// const handleAddressSubmit = () => { -// const isValid = isValidAddress(formData.address); - -// if (isValid) { -// setAddressError(false); -// setIsDetailsOpen(true); -// } else { -// setAddressError(true); -// } -// }; - -// const handleInputChange = (e: React.ChangeEvent) => { -// e.preventDefault(); -// const { name, value } = e.target; -// setFormData({ ...formData, [name]: value }); -// }; - -// const handleSend = async () => { -// if (loading || inValidAmount) return; - -// setLoading(true); - -// const selectedFee = fee; // The user-selected fee rate - -// const transactionRequest = { -// choice: 1, // New transaction option -// recipient_address: formData.address, -// spend_amount: parseInt(formData.amount), -// priority_rate: selectedFee, -// enable_rbf: enableRBF, -// }; - -// try { -// // Step 1: Initiate the new transaction -// const response = await fetch('http://localhost:9003/transaction', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(transactionRequest), -// }); - -// const result = await response.json(); - -// // Check the status from the wallet's response -// if (result.status === 'success' || result.status === 'pending') { -// // Step 2: If the transaction succeeds or is pending, update the pending transactions -// const pendingTransaction: PendingTransaction = { -// txid: result.txid, -// feeRate: Math.round(selectedFee), // Ensure feeRate is an integer -// timestamp: new Date().toISOString(), // Already in correct ISO format expected by Go's time.Time -// amount: parseInt(formData.amount, 10), // Parse amount as an integer -// recipient_address: formData.address, -// enable_rbf: enableRBF, // Already boolean and correct -// }; - -// const pendingResponse = await fetch(`${config.baseURL}/pending-transactions`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(pendingTransaction), -// }); - -// const pendingResult = await pendingResponse.json(); - -// // Step 3: Handle the final result from updating pending transactions -// if (pendingResponse.ok) { -// setLoading(false); -// onSend(true, formData.address, transactionRequest.spend_amount, result.txid, pendingResult.message); // Notify parent -// } else { -// setLoading(false); -// onSend(false, formData.address, 0, '', pendingResult.error || 'Failed to save pending transaction.'); -// } -// } else { -// // Handle error in the wallet's transaction response -// setLoading(false); -// onSend(false, formData.address, 0, '', result.message || 'Transaction failed.'); -// } -// } catch (error) { -// console.error('Transaction failed:', error); -// setLoading(false); -// onSend(false, formData.address, 0, '', 'Transaction failed due to a network error.'); -// } finally { -// setLoading(false); // Ensure loading stops in all cases -// } -// }; - - - -// useEffect(() => { -// if (formData.amount.length <= 0 || (balanceData && parseInt(formData.amount) > balanceData.latest_balance)) { -// setInvalidAmount(true); -// } else { -// setInvalidAmount(false); -// } -// }, [formData.amount]); - -// const receiverPanel = () => ( -// <> -// -// Address -// -// -// Continue -// -// ); - -// const detailsPanel = () => ( -// -// -// -// {`Amount = ${amountWithFee ? amountWithFee : ''}`} - -// {inValidAmount && Invalid Amount} -// - -//
-// -// {`Balance: ${balanceData ? balanceData.latest_balance : 0}`} -//
-//
-// -// Tiered Fees -// -// setEnableRBF(e.target.checked)} // Update the state when the checkbox is toggled -// /> -// RBF Opt In -// -// -// -// -// -// Send -// -// -//
-// ); - -// return ( -// -// -// -// Send -// {isDetailsOpen ? ( -// <> -// -// To: -//
-// {truncateString(formData.address, 65)} -//
-// {detailsPanel()} -// -// ) : ( -// receiverPanel() -// )} -//
-//
-//
-// ); -// }; - -// export default SendForm; - - -// import React, { useEffect, useState } from 'react'; -// import { BaseInput } from '@app/components/common/inputs/BaseInput/BaseInput'; -// import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; -// import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; -// import { useResponsive } from '@app/hooks/useResponsive'; -// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; -// import * as S from './SendForm.styles'; -// import { truncateString } from '@app/utils/utils'; -// import useBalanceData from '@app/hooks/useBalanceData'; -// import { BaseCheckbox } from '@app/components/common/BaseCheckbox/BaseCheckbox'; -// import config from '@app/config/config'; -// import TieredFees from './components/TieredFees/TieredFees'; - -// interface SendFormProps { -// onSend: (status: boolean, address: string, amount: number, txid?: string, message?: string) => void; -// } - -// interface PendingTransaction { -// txid: string; -// feeRate: number; -// timestamp: string; // ISO format string -// amount: string; -// recipient_address: string; -// } - -// export type tiers = 'low' | 'med' | 'high'; - -// const SendForm: React.FC = ({ onSend }) => { -// const { balanceData, isLoading } = useBalanceData(); - -// const [loading, setLoading] = useState(false); -// const [txID, setTxID] = useState(null); // Store the transaction ID -// const [verificationInProgress, setVerificationInProgress] = useState(false); -// const [isDetailsOpen, setIsDetailsOpen] = useState(false); -// const [inValidAmount, setInvalidAmount] = useState(false); -// const [addressError, setAddressError] = useState(false); -// const [amountWithFee, setAmountWithFee] = useState(null); -// const [totalCost, setTotalCost] = useState(null); -// const [fee, setFee] = useState(0); -// const [formData, setFormData] = useState({ -// address: '', -// amount: '1', -// }); -// const [txSize, setTxSize] = useState(null); -// const [errorMessage, setErrorMessage] = useState(null); // For error messaging - -// // Function to fetch with a timeout -// const fetchWithTimeout = async (url: string, options: RequestInit, timeout: number = 10000) => { -// const controller = new AbortController(); -// const timeoutId = setTimeout(() => controller.abort(), timeout); - -// try { -// const response = await fetch(url, { -// ...options, -// signal: controller.signal, -// }); -// clearTimeout(timeoutId); // Clear the timeout once the request is completed - -// return response; -// } catch (error) { -// if (error instanceof Error) { -// console.error('Network request failed:', error.message); -// throw new Error(error.message); -// } -// throw error; // Handle other types of network errors -// } -// }; - -// // Debounced effect to calculate transaction size when the amount changes -// useEffect(() => { -// const debounceTimeout = setTimeout(() => { -// if (formData.amount.length > 0) { -// // Call backend to calculate transaction size -// const fetchTransactionSize = async () => { -// try { -// const response = await fetch('https://localhost:443/calculate-tx-size', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ -// recipient_address: formData.address, -// spend_amount: parseInt(formData.amount), -// priority_rate: fee, // Pass the fee rate -// }), -// }); - -// const result = await response.json(); -// setTxSize(result.txSize); // Set the transaction size -// } catch (error) { -// console.error('Error fetching transaction size:', error); -// setTxSize(null); -// } -// }; - -// fetchTransactionSize(); -// } -// }, 500); // Debounce for 500ms - -// return () => clearTimeout(debounceTimeout); // Clear the timeout if the amount changes before 500ms -// }, [formData.amount, fee]); - -// // Calculate the fee based on the transaction size -// useEffect(() => { -// if (txSize && fee) { -// const estimatedFee = txSize * fee; -// const total = parseInt(formData.amount) + estimatedFee; -// setAmountWithFee(total); -// setTotalCost(total); -// } -// }, [txSize, fee, formData.amount]); - -// // Check if the total cost exceeds the user's balance -// useEffect(() => { -// if (totalCost !== null && balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance) { -// setErrorMessage('Insufficient balance to complete the transaction.'); -// } else { -// setErrorMessage(null); // Reset error if the total cost is within balance -// } -// }, [totalCost, balanceData]); - -// const handleFeeChange = (fee: number) => { -// setFee(fee); -// }; - -// const isValidAddress = (address: string) => { -// return address.length > 0; -// }; - -// const handleAddressSubmit = () => { -// const isValid = isValidAddress(formData.address); - -// if (isValid) { -// setAddressError(false); -// setIsDetailsOpen(true); -// } else { -// setAddressError(true); -// } -// }; - -// const handleInputChange = (e: React.ChangeEvent) => { -// e.preventDefault(); -// const { name, value } = e.target; -// setFormData({ ...formData, [name]: value }); -// }; - -// // Transaction send and verification process -// const handleSend = async () => { -// if (loading || inValidAmount) return; - -// setLoading(true); - -// const selectedFee = fee; // Default to the selected fee rate - -// const transactionRequest = { -// choice: 1, // Choice 1 for initiating a new transaction -// recipient_address: formData.address, -// spend_amount: parseInt(formData.amount), -// priority_rate: selectedFee, -// }; - -// try { -// // Step 1: Initiate the transaction request -// const response = await fetchWithTimeout('https://localhost:443/transaction', { -// method: 'POST', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify(transactionRequest), -// }, 100000); // Setting a 30-second timeout for the transaction request - -// const result = await response.json(); - -// if (result.status === 'pending') { -// // Transaction broadcasted but pending verification -// console.log('Transaction broadcasted. Verifying in mempool...'); -// setTxID(result.txid); -// setVerificationInProgress(true); - -// // Step 2: Start mempool verification -// const verifyTransaction = async () => { -// const verifyRequest = { txID: result.txid }; -// const verifyResponse = await fetchWithTimeout('https://localhost:443/verify-transaction', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(verifyRequest), -// }, 30000); - -// const verifyResult = await verifyResponse.json(); - -// if (verifyResult.status === 'success') { -// console.log('Transaction verified in the mempool'); -// onSend(true, formData.address, parseInt(formData.amount), verifyResult.txid, verifyResult.message); -// } else if (verifyResult.status === 'pending') { -// console.log('Transaction not found in mempool yet. Will retry.'); -// setTimeout(verifyTransaction, 10000); // Retry after 10 seconds -// } else { -// console.error('Verification failed:', verifyResult.message); -// onSend(false, formData.address, 0, '', verifyResult.message); -// } -// }; - -// verifyTransaction(); // Start verification process - -// } else { -// console.error('Transaction failed:', result.message); -// onSend(false, formData.address, 0, '', result.message); -// setLoading(false); -// } -// } catch (error) { -// console.error('Transaction failed:', error); -// onSend(false, formData.address, 0, '', 'Transaction failed due to a network error.'); -// } finally { -// setLoading(false); -// } -// }; - -// // Handle invalid amounts -// useEffect(() => { -// if (formData.amount.length <= 0 || (balanceData && parseInt(formData.amount) > balanceData.latest_balance)) { -// setInvalidAmount(true); -// } else { -// setInvalidAmount(false); -// } -// }, [formData.amount, balanceData]); - -// const receiverPanel = () => ( -// <> -// -// Address -// -// -// Continue -// -// ); - -// const detailsPanel = () => ( -// -// -// -// {`Amount = ${amountWithFee ? amountWithFee : ''}`} - -// {inValidAmount && Invalid Amount} -// - -//
-// -// {`Balance: ${balanceData ? balanceData.latest_balance : 0}`} -//
-//
-// -// Tiered Fees -// -// -// RBF Opt In -// -// -// -// {/* Display an error if the total exceeds the balance */} -// {errorMessage && {errorMessage}} -// -// -// Send -// -// -//
-// ); - -// return ( -// -// -// -// Send -// {isDetailsOpen ? ( -// <> -// -// To: -//
-// {truncateString(formData.address, 65)} -//
-// {detailsPanel()} -// -// ) : ( -// receiverPanel() -// )} -//
-//
-//
-// ); -// }; - -// export default SendForm; \ No newline at end of file diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 72987c1..c7a1588 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -238,479 +238,3 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep }; export default ReplaceTransaction; - - -// import React, { useEffect, useState } from 'react'; -// import * as S from './ReplaceTransaction.styles'; -// import { useResponsive } from '@app/hooks/useResponsive'; -// import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; -// import { PendingTransaction } from '@app/hooks/usePendingTransactions'; -// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; -// import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; -// import config from '@app/config/config'; -// import useBalanceData from '@app/hooks/useBalanceData'; - -// interface ReplaceTransactionProps { -// onCancel: () => void; -// onReplace: () => void; -// transaction: PendingTransaction; -// } - -// const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { -// const { isDesktop, isTablet } = useResponsive(); -// const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); // Fetch balance data using the hook -// const [inValidAmount, setInvalidAmount] = useState(false); -// const [newFee, setNewFee] = useState(transaction.feeRate); // Fee rate in sat/vB -// const [txSize, setTxSize] = useState(null); // State to store transaction size -// const [loading, setLoading] = useState(false); // Add loading state -// const [isFinished, setIsFinished] = useState(false); // Add finished state -// const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ -// isSuccess: false, -// message: '', -// txid: '', -// }); - -// // Fetch the transaction size when the component mounts -// useEffect(() => { -// const fetchTransactionSize = async () => { -// try { -// const response = await fetch('http://localhost:9003/calculate-tx-size', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ -// recipient_address: transaction.recipient_address, // Use the original recipient address -// spend_amount: parseInt(transaction.amount.toString()), // The original amount -// priority_rate: newFee, // The current fee rate -// }), -// }); - -// const result = await response.json(); -// setTxSize(result.txSize); // Set the transaction size -// } catch (error) { -// console.error('Error fetching transaction size:', error); -// setTxSize(null); -// } -// }; - -// fetchTransactionSize(); -// }, [transaction.txid, transaction.amount, newFee]); - -// // Calculate the total transaction cost (Amount + Calculated Fee) -// const totalCost = txSize && newFee ? Number(transaction.amount) + newFee * txSize : Number(transaction.amount); - -// // Check if the total cost exceeds the user's balance -// const isBalanceInsufficient = balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance; - -// const handleFeeChange = (fee: number) => { -// setNewFee(fee); // Update the new fee when it changes -// }; - -// const handleReplace = async (e: React.MouseEvent) => { -// e.stopPropagation(); -// setLoading(true); // Start loading - -// try { -// // Step 1: Initiate the replacement transaction -// const replaceRequest = { -// choice: 2, // Replace transaction option -// original_tx_id: transaction.txid, // Send the original transaction ID -// new_fee_rate: newFee, // Send the updated fee rate -// }; - -// const response = await fetch('http://localhost:9003/transaction', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(replaceRequest), -// }); - -// const result = await response.json(); - -// // Handle different statuses from the wallet (success, pending, or failed) -// if (result.status === 'success' || result.status === 'pending') { - -// // Step 2: If the replacement transaction succeeds or is pending, update the pending transactions -// const updatePendingRequest = { -// original_tx_id: transaction.txid, // Original transaction ID to replace -// new_tx_id: result.txid, // New transaction ID from the replacement -// new_fee_rate: Math.round(newFee), // Updated fee rate -// amount: parseInt(transaction.amount, 10), // Same amount -// recipient_address: transaction.recipient_address, // Same recipient address -// enable_rbf: true, // RBF status -// }; - -// const pendingResponse = await fetch(`${config.baseURL}/replacement-transactions`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(updatePendingRequest), -// }); - -// const pendingResult = await pendingResponse.json(); - -// // Step 3: Handle the final result from updating pending transactions -// if (pendingResponse.ok) { -// setResult({ isSuccess: true, message: pendingResult.message, txid: result.txid }); -// setIsFinished(true); -// onReplace(); // Notify parent that the replacement and update were successful -// } else { -// setResult({ isSuccess: false, message: pendingResult.error || 'Failed to update pending transaction.', txid: '' }); -// setIsFinished(true); -// } -// } else { -// // Handle error in the replacement transaction -// setResult({ isSuccess: false, message: result.message || 'Replacement transaction failed.', txid: '' }); -// setIsFinished(true); -// } -// } catch (error) { -// console.error('RBF transaction failed:', error); -// setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); -// setIsFinished(true); -// } finally { -// setLoading(false); // Ensure loading stops in all cases -// } -// }; - - - -// if (isFinished) { -// return ( -// -// ); -// } - -// return ( -// -// -// -// Transaction ID -// -// {transaction.txid} -// -// -// -// Amount -// -// {transaction.amount} -// -// -// {/* Pass the transaction size to TieredFees to dynamically calculate the fee */} -// -// -// New Fee -// -// { -// const fee = parseFloat(e.target.value); -// if (!isNaN(fee)) { // Ensure the value is a number -// setNewFee(fee); // Update the newFee state -// } -// }} -// min={0} // You can add a minimum value of 0 for the fee to avoid negative values -// step={1} // Optional: define the increment step for the fee input -// /> -// -// - -// -// Total -// -// {/* Calculate total amount (Amount + Fee based on transaction size) */} -// {totalCost} -// -// - -// {/* Show error message if balance is insufficient */} -// {isBalanceInsufficient && ( -// Insufficient balance to complete the transaction. -// )} - -// -// Cancel -// {/* Disable replace button if total cost exceeds balance */} -// -// Replace -// -// -// -// -// ); -// }; - -// export default ReplaceTransaction; - -// import React, { useEffect, useState } from 'react'; -// import * as S from './ReplaceTransaction.styles'; -// import { useResponsive } from '@app/hooks/useResponsive'; -// import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; -// import { PendingTransaction } from '@app/hooks/usePendingTransactions'; -// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; -// import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; -// import config from '@app/config/config'; -// import useBalanceData from '@app/hooks/useBalanceData'; - -// interface ReplaceTransactionProps { -// onCancel: () => void; -// onReplace: () => void; -// transaction: PendingTransaction; -// } - -// const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { -// const { isDesktop, isTablet } = useResponsive(); -// const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); - -// const [inValidAmount, setInvalidAmount] = useState(false); -// const [newFee, setNewFee] = useState(transaction.feeRate); -// const [txSize, setTxSize] = useState(null); -// const [loading, setLoading] = useState(false); -// const [isFinished, setIsFinished] = useState(false); -// const [result, setResult] = useState<{ isSuccess: boolean; message: string; txid: string }>({ -// isSuccess: false, -// message: '', -// txid: '', -// }); -// const [isCustomFee, setIsCustomFee] = useState(false); -// const [verificationInProgress, setVerificationInProgress] = useState(false); - -// // Fetch the transaction size when the fee changes or the component loads -// useEffect(() => { -// const fetchTransactionSize = async () => { -// try { -// const response = await fetch('https://localhost:443/calculate-tx-size', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ -// recipient_address: transaction.recipient_address, -// spend_amount: parseInt(transaction.amount.toString()), -// priority_rate: newFee, -// }), -// }); - -// const result = await response.json(); -// setTxSize(result.txSize); -// } catch (error) { -// console.error('Error fetching transaction size:', error); -// setTxSize(null); -// } -// }; - -// fetchTransactionSize(); -// }, [transaction.txid, transaction.amount, newFee]); - -// // Calculate total cost -// const totalCost = txSize && newFee ? Number(transaction.amount) + newFee * txSize : Number(transaction.amount); -// const isBalanceInsufficient = balanceData?.latest_balance !== undefined && totalCost > balanceData.latest_balance; - -// // Handle fee changes -// const handleFeeChange = (fee: number) => { -// setNewFee(fee); -// setIsCustomFee(false); -// }; - -// const handleCustomFeeChange = (e: React.ChangeEvent) => { -// const value = parseFloat(e.target.value); -// if (!isNaN(value) && value >= 0) { -// setNewFee(value); -// setIsCustomFee(true); -// } -// }; - -// const fetchWithTimeout = async (url: string, options: RequestInit, timeout: number = 10000) => { -// const controller = new AbortController(); -// const timeoutId = setTimeout(() => controller.abort(), timeout); - -// try { -// const response = await fetch(url, { -// ...options, -// signal: controller.signal, -// }); -// clearTimeout(timeoutId); -// return response; -// } catch (error) { -// if (error instanceof Error) { -// console.error('RBF transaction failed:', error.message); -// throw new Error(error.message); -// } -// throw error; -// } -// }; - -// // Toggle between custom and preset fees -// const toggleCustomFee = () => { -// setIsCustomFee(!isCustomFee); -// }; - -// // Handle Replace transaction and verification -// const handleReplace = async (e: React.MouseEvent) => { -// e.stopPropagation(); -// setLoading(true); - -// try { -// const replaceRequest = { -// choice: 2, -// original_tx_id: transaction.txid, -// new_fee_rate: newFee, -// }; - -// // Step 1: Broadcast the transaction -// const response = await fetchWithTimeout('https://localhost:443/transaction', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(replaceRequest), -// }, 30000); - -// const result = await response.json(); - -// if (result.status === 'pending' || result.status === 'success') { -// console.log('Transaction broadcasted. Updating pending transaction...'); - -// const updatePendingRequest = { -// original_tx_id: transaction.txid, -// new_tx_id: result.txid, -// new_fee_rate: newFee, -// amount: transaction.amount, -// recipient_address: transaction.recipient_address, -// }; - -// // Step 2: Update the pending transaction -// const pendingResponse = await fetchWithTimeout(`${config.baseURL}/replacement-transactions`, { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(updatePendingRequest), -// }, 100000); - -// const pendingResult = await pendingResponse.json(); - -// if (pendingResult.status === 'success') { -// setResult({ isSuccess: true, message: pendingResult.message, txid: pendingResult.txid }); -// setVerificationInProgress(false); -// } else { -// setResult({ isSuccess: false, message: pendingResult.message, txid: '' }); -// } - -// // Step 3: Verify the transaction in the mempool -// if (result.status === 'pending') { -// console.log('Transaction broadcasted but pending verification. Verifying in mempool...'); -// setVerificationInProgress(true); - -// const verifyTransaction = async () => { -// const verifyRequest = { txID: result.txid }; -// const verifyResponse = await fetchWithTimeout('https://localhost:443/verify-transaction', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify(verifyRequest), -// }, 30000); - -// const verifyResult = await verifyResponse.json(); - -// if (verifyResult.status === 'success') { -// console.log('Transaction verified in the mempool'); -// setResult({ isSuccess: true, message: verifyResult.message, txid: verifyResult.txid }); -// } else if (verifyResult.status === 'pending') { -// console.log('Transaction not found in mempool yet. Retrying in 10 seconds.'); -// setTimeout(verifyTransaction, 10000); // Retry after 10 seconds -// } else { -// console.error('Verification failed:', verifyResult.message); -// setResult({ isSuccess: false, message: verifyResult.message, txid: '' }); -// } -// }; - -// verifyTransaction(); // Start the verification process -// } - -// } else { -// setResult({ isSuccess: false, message: result.message, txid: '' }); -// } -// } catch (error) { -// console.error('RBF transaction failed:', error); -// setResult({ isSuccess: false, message: 'RBF transaction failed due to a network error.', txid: '' }); -// } finally { -// setIsFinished(true); -// setLoading(false); -// } -// }; - -// // If the process is finished, display the result -// if (isFinished) { -// return ( -// -// ); -// } - -// // Render the form UI -// return ( -// -// -// -// Transaction ID -// -// {transaction.txid} -// -// -// -// Amount -// -// {transaction.amount} -// -// - -// {!isCustomFee && ( -// -// )} - -// -// New Fee -// -// {isCustomFee ? ( -// -// ) : ( -// {newFee} -// )} -// -// - -// -// {isCustomFee ? "Use preset fees" : "Enter custom fee"} -// - -// -// Total -// -// {totalCost} -// -// - -// {isBalanceInsufficient && ( -// Insufficient balance to complete the transaction. -// )} - -// -// Cancel -// -// Replace -// -// -// -// -// ); -// }; - -// export default ReplaceTransaction; diff --git a/src/hooks/useWalletAuth.ts b/src/hooks/useWalletAuth.ts new file mode 100644 index 0000000..4456f06 --- /dev/null +++ b/src/hooks/useWalletAuth.ts @@ -0,0 +1,188 @@ +import { useEffect, useState } from 'react'; +import { persistWalletToken, readWalletToken, deleteWalletToken } from '@app/services/localStorage.service'; // Import the wallet-specific functions +import { notificationController } from '@app/controllers/notificationController'; + +const useWalletAuth = () => { + const [token, setToken] = useState(null); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [loading, setLoading] = useState(false); + + // Fetch the wallet token from localStorage on mount + useEffect(() => { + const storedToken = readWalletToken(); // Use the wallet-specific token reader + if (storedToken) { + setToken(storedToken); + setIsAuthenticated(true); + } + }, []); + + // Function to handle login with Nostr public key and challenge verification + const login = async () => { + setLoading(true); + try { + if (!window.nostr) { + notificationController.error({ message: 'Nostr extension is not available' }); + return; + } + + // Fetch the Nostr public key + const npub = await window.nostr.getPublicKey(); + + // Fetch the challenge from the server + const challengeResponse = await fetch('http://localhost:9003/challenge', { method: 'GET' }); + + // Check if the response is valid JSON + if (!challengeResponse.ok) { + throw new Error('Network response was not ok'); + } + + const { content: challenge } = await challengeResponse.json(); + + console.log(challenge) + + // Sign the challenge using Nostr + const signedEvent = await window.nostr.signEvent({ + pubkey: npub, + content: challenge, + created_at: Math.floor(Date.now() / 1000), + kind: 1, + tags: [], + }); + + // Send the signed challenge to the backend for verification + const verifyResponse = await fetch('http://localhost:9003/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + challenge, + signature: signedEvent.sig, + messageHash: signedEvent.id, + event: signedEvent, + }), + }); + + const { token } = await verifyResponse.json(); + + // Store the wallet token and mark the user as authenticated + persistWalletToken(token); // Persist the wallet-specific token + setToken(token); + setIsAuthenticated(true); + + notificationController.success({ message: 'Wallet login successful!' }); + } catch (error) { + console.error('Error during wallet login:', error); + notificationController.error({ message: 'Wallet authentication failed' }); + } finally { + setLoading(false); + } + }; + + // Logout and clear wallet token + const logout = () => { + deleteWalletToken(); // Use the wallet-specific token deletion + setToken(null); + setIsAuthenticated(false); + }; + + return { + token, + isAuthenticated, + login, + logout, + loading, + }; +}; + +export default useWalletAuth; + + + +// import { useEffect, useState } from 'react'; +// import { persistToken, readToken, deleteToken } from '@app/services/localStorage.service'; +// import { notificationController } from '@app/controllers/notificationController'; + +// const useWalletAuth = () => { +// const [token, setToken] = useState(null); +// const [isAuthenticated, setIsAuthenticated] = useState(false); +// const [loading, setLoading] = useState(false); + +// // Fetch token from local storage on mount +// useEffect(() => { +// const storedToken = readToken(); // Use readToken to fetch from localStorage +// if (storedToken) { +// setToken(storedToken); +// setIsAuthenticated(true); +// } +// }, []); + +// // Function to handle login with Nostr public key and challenge verification +// const login = async () => { +// setLoading(true); +// try { +// if (!window.nostr) { +// notificationController.error({ message: 'Nostr extension is not available' }); +// return; +// } + +// // Fetch the Nostr public key +// const npub = await window.nostr.getPublicKey(); + +// // Fetch the challenge from the server +// const challengeResponse = await fetch('http://localhost:9003/challenge'); +// const { content: challenge } = await challengeResponse.json(); + +// // Sign the challenge using Nostr +// const signedEvent = await window.nostr.signEvent({ +// pubkey: npub, +// content: challenge, +// created_at: Math.floor(Date.now() / 1000), +// kind: 1, +// tags: [], +// }); + +// // Send the signed challenge to the backend for verification +// const verifyResponse = await fetch('http://localhost:9003/verify', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ +// challenge, +// signature: signedEvent.sig, +// messageHash: signedEvent.id, +// event: signedEvent, +// }), +// }); + +// const { token } = await verifyResponse.json(); + +// // Store the token and mark the user as authenticated +// persistToken(token); +// setToken(token); +// setIsAuthenticated(true); + +// notificationController.success({ message: 'Login successful!' }); +// } catch (error) { +// console.error('Error during login:', error); +// notificationController.error({ message: 'Authentication failed' }); +// } finally { +// setLoading(false); +// } +// }; + +// // Logout and clear token +// const logout = () => { +// deleteToken(); +// setToken(null); +// setIsAuthenticated(false); +// }; + +// return { +// token, +// isAuthenticated, +// login, +// logout, +// loading, +// }; +// }; + +// export default useWalletAuth; + From a0b2f716f19a8044634086b8faac89056280db2c Mon Sep 17 00:00:00 2001 From: Maphikza Date: Tue, 10 Sep 2024 19:38:02 +0200 Subject: [PATCH 29/62] Login process working with new wallet --- .../Balance/components/SendForm/SendForm.tsx | 9 +++++---- .../components/ReplaceTransaction/ReplaceTransaction.tsx | 4 ++-- src/hooks/useWalletAuth.ts | 4 +++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 97ee0b7..ae0b9e7 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -61,6 +61,7 @@ const SendForm: React.FC = ({ onSend }) => { try { // Ensure user is authenticated if (!isAuthenticated) { + console.log("Not Authenticated.") await login(); // Perform login if not authenticated } @@ -79,9 +80,9 @@ const SendForm: React.FC = ({ onSend }) => { if (response.status === 401) { const errorText = await response.text(); - if (errorText.includes("Token expired")) { + if (errorText.includes("Token expired") || errorText.includes("Unauthorized: Invalid token")) { // Token has expired, trigger a re-login - notificationController.error({ message: 'Session expired. Please log in again.' }); + console.log('Session expired. Please log in again.'); deleteWalletToken(); // Clear the old token await login(); // Re-initiate login } @@ -170,9 +171,9 @@ const SendForm: React.FC = ({ onSend }) => { if (response.status === 401) { const errorText = await response.text(); - if (errorText.includes("Token expired")) { + if (errorText.includes("Token expired") || errorText.includes("Unauthorized: Invalid token")) { // Token has expired, trigger a re-login - notificationController.error({ message: 'Session expired. Please log in again.' }); + console.log('Session expired. Please log in again.'); deleteWalletToken(); // Clear the old token await login(); // Re-initiate login } diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index c7a1588..034c6f6 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -112,8 +112,8 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep if (response.status === 401) { const errorText = await response.text(); - if (errorText.includes('Token expired')) { - notificationController.error({ message: 'Session expired. Please log in again.' }); + if (errorText.includes("Token expired") || errorText.includes("Unauthorized: Invalid token")) { + console.log('Session expired. Please log in again.'); deleteWalletToken(); // Clear the old token await login(); // Re-initiate login } diff --git a/src/hooks/useWalletAuth.ts b/src/hooks/useWalletAuth.ts index 4456f06..e3c90c5 100644 --- a/src/hooks/useWalletAuth.ts +++ b/src/hooks/useWalletAuth.ts @@ -25,6 +25,8 @@ const useWalletAuth = () => { return; } + console.log("getting challenge.") + // Fetch the Nostr public key const npub = await window.nostr.getPublicKey(); @@ -68,7 +70,7 @@ const useWalletAuth = () => { setToken(token); setIsAuthenticated(true); - notificationController.success({ message: 'Wallet login successful!' }); + console.log('Wallet login successful!') } catch (error) { console.error('Error during wallet login:', error); notificationController.error({ message: 'Wallet authentication failed' }); From e95883337ef7e8ee51beeb24474dc1415eb8311a Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 11 Sep 2024 10:56:38 +0200 Subject: [PATCH 30/62] Adding token auth for relay endpoints, updating doLogout to logout of relay --- src/api/activity.api.ts | 18 +- .../Balance/components/SendForm/SendForm.tsx | 17 +- .../TopUpBalanceModal/TopUpBalanceModal.tsx | 109 ++---- .../ReplaceTransaction/ReplaceTransaction.tsx | 9 +- src/hooks/useActivityData.ts | 66 +++- src/hooks/useBalanceData.ts | 97 +++--- src/hooks/useBarChartData.ts | 69 +++- src/hooks/useBitcoinRates.ts | 73 ++++- src/hooks/useChartData.ts | 310 ++++++++++-------- src/hooks/useKindData.ts | 42 ++- src/hooks/useKindTrendData.ts | 68 +++- src/hooks/useLineChartData.ts | 70 +++- src/hooks/usePendingTransactions.ts | 4 + src/store/slices/authSlice.ts | 35 +- 14 files changed, 695 insertions(+), 292 deletions(-) diff --git a/src/api/activity.api.ts b/src/api/activity.api.ts index 5a7aa3c..34af9c1 100644 --- a/src/api/activity.api.ts +++ b/src/api/activity.api.ts @@ -1,7 +1,9 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars import { ActivityStatusType } from '@app/interfaces/interfaces'; +import { readToken } from '@app/services/localStorage.service'; import config from '@app/config/config'; +import { useHandleLogout } from '@app/utils/authUtils'; export interface WalletTransaction { id: number; @@ -12,9 +14,22 @@ export interface WalletTransaction { } export const getUserActivities = (): Promise => { - return fetch(`${config.baseURL}/transactions/latest`) + const token = readToken(); // Read the JWT token from local storage + + const handleLogout = useHandleLogout(); + + return fetch(`${config.baseURL}/transactions/latest`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Add JWT to Authorization header + }, + }) .then((response) => { if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Log out the user if the token is invalid or expired + } throw new Error('Network response was not ok'); } return response.json(); @@ -25,7 +40,6 @@ export const getUserActivities = (): Promise => { return []; } // Assuming your backend response matches the WalletTransaction interface - // eslint-disable-next-line return data.map((item: any) => ({ id: item.ID, witness_tx_id: item.WitnessTxId, diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index ae0b9e7..71cf8ba 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -11,8 +11,7 @@ import { BaseCheckbox } from '@app/components/common/BaseCheckbox/BaseCheckbox'; import config from '@app/config/config'; import TieredFees from './components/TieredFees/TieredFees'; import useWalletAuth from '@app/hooks/useWalletAuth'; // Import the auth hook -import { notificationController } from '@app/controllers/notificationController'; -import { deleteWalletToken } from '@app/services/localStorage.service'; // Assuming this is where deleteWalletToken is defined +import { deleteWalletToken, readToken } from '@app/services/localStorage.service'; // Assuming this is where deleteWalletToken is defined interface SendFormProps { @@ -193,14 +192,20 @@ const SendForm: React.FC = ({ onSend }) => { recipient_address: formData.address, enable_rbf: enableRBF, // Already boolean and correct }; - + + // Fetch the JWT token using readToken() + const pendingToken = readToken(); + const pendingResponse = await fetch(`${config.baseURL}/pending-transactions`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${pendingToken}`, // Use the token from readToken() + }, body: JSON.stringify(pendingTransaction), }); - - const pendingResult = await pendingResponse.json(); + + const pendingResult = await pendingResponse.json(); // Step 3: Handle the final result from updating pending transactions if (pendingResponse.ok) { diff --git a/src/components/nft-dashboard/Balance/components/TopUpBalanceModal/TopUpBalanceModal.tsx b/src/components/nft-dashboard/Balance/components/TopUpBalanceModal/TopUpBalanceModal.tsx index d23bfbb..89b290f 100644 --- a/src/components/nft-dashboard/Balance/components/TopUpBalanceModal/TopUpBalanceModal.tsx +++ b/src/components/nft-dashboard/Balance/components/TopUpBalanceModal/TopUpBalanceModal.tsx @@ -5,6 +5,9 @@ import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; import { AddressList } from '../AddressList/AddressList'; import axios from 'axios'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist +import { useDispatch } from 'react-redux'; +import { useHandleLogout } from '@app/utils/authUtils'; interface TopUpBalanceModalProps extends TopUpDataProps { isOpen: boolean; @@ -25,22 +28,39 @@ export const TopUpBalanceModal: React.FC = ({ }) => { const [addresses, setAddresses] = useState([]); const [isLoading, setIsLoading] = useState(true); + const dispatch = useDispatch(); + + const handleLogout = useHandleLogout(); useEffect(() => { if (isOpen) { setIsLoading(true); + const token = readToken(); // Read the JWT token from local storage + if (!token) { + handleLogout(); + return; + } + axios - .get(`${config.baseURL}/addresses`) + .get(`${config.baseURL}/addresses`, { + headers: { + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request + }, + }) .then((response) => { setAddresses(response.data); setIsLoading(false); }) .catch((error) => { - console.error('Error fetching addresses:', error); + if (error.response && error.response.status === 401) { + handleLogout(); // Log out if token is invalid or expired + } else { + console.error('Error fetching addresses:', error); + } setIsLoading(false); }); } - }, [isOpen]); + }, [isOpen, dispatch]); return ( @@ -51,12 +71,14 @@ export const TopUpBalanceModal: React.FC = ({ ); }; + // import React, { useEffect, useState } from 'react'; // import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; // import { TopUpDataProps } from '../../interfaces/interfaces'; // import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; // import { AddressList } from '../AddressList/AddressList'; // import axios from 'axios'; +// import config from '@app/config/config'; // interface TopUpBalanceModalProps extends TopUpDataProps { // isOpen: boolean; @@ -81,57 +103,13 @@ export const TopUpBalanceModal: React.FC = ({ // useEffect(() => { // if (isOpen) { // setIsLoading(true); -// axios.get('http://localhost:5000/addresses') -// .then(response => { -// setAddresses(response.data); -// setIsLoading(false); -// }) -// .catch(error => { -// console.error('Error fetching addresses:', error); -// setIsLoading(false); -// }); -// } -// }, [isOpen]); - -// return ( -// -// -// -// -// -// ); -// }; - -// import React, { useEffect, useState } from 'react'; -// import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; -// import { TopUpDataProps } from '../../interfaces/interfaces'; -// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; -// import { AddressList } from '../AddressList/AddressList'; -// import axios from 'axios'; - -// interface TopUpBalanceModalProps extends TopUpDataProps { -// isOpen: boolean; -// onOpenChange: () => void; -// } - -// export const TopUpBalanceModal: React.FC = ({ -// cards, -// loading, -// isOpen, -// onOpenChange, -// onFinish, -// }) => { -// const [addresses, setAddresses] = useState([]); -// const [isLoading, setIsLoading] = useState(true); - -// useEffect(() => { -// if (isOpen) { -// axios.get('http://localhost:5000/addresses') -// .then(response => { +// axios +// .get(`${config.baseURL}/addresses`) +// .then((response) => { // setAddresses(response.data); // setIsLoading(false); // }) -// .catch(error => { +// .catch((error) => { // console.error('Error fetching addresses:', error); // setIsLoading(false); // }); @@ -139,37 +117,10 @@ export const TopUpBalanceModal: React.FC = ({ // }, [isOpen]); // return ( -// +// // // // // // ); // }; - -// import React from 'react'; -// import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; -// import { TopUpDataProps } from '../../interfaces/interfaces'; -// import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; -// import { TopUpBalanceForm } from '../TopUpBalanceForm/TopUpBalanceForm'; - -// interface TopUpBalanceModalProps extends TopUpDataProps { -// isOpen: boolean; -// onOpenChange: () => void; -// } - -// export const TopUpBalanceModal: React.FC = ({ -// cards, -// loading, -// isOpen, -// onOpenChange, -// onFinish, -// }) => { -// return ( -// -// -// -// -// -// ); -// }; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 034c6f6..f70e18a 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -9,7 +9,7 @@ import config from '@app/config/config'; import useBalanceData from '@app/hooks/useBalanceData'; import useWalletAuth from '@app/hooks/useWalletAuth'; // Import authentication hook import { notificationController } from '@app/controllers/notificationController'; // Handle notifications -import { deleteWalletToken } from '@app/services/localStorage.service'; // Delete wallet token if expired +import { deleteWalletToken, readToken } from '@app/services/localStorage.service'; // Delete wallet token if expired interface ReplaceTransactionProps { onCancel: () => void; @@ -122,6 +122,8 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep const result = await response.json(); + const pendingToken = readToken(); + // Handle different statuses from the wallet (success, pending, or failed) if (result.status === 'success' || result.status === 'pending') { // Step 2: If the replacement transaction succeeds or is pending, update the pending transactions @@ -136,7 +138,10 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep const pendingResponse = await fetch(`${config.baseURL}/replacement-transactions`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${pendingToken}`, // Include the JWT token in the Authorization header + }, body: JSON.stringify(updatePendingRequest), }); diff --git a/src/hooks/useActivityData.ts b/src/hooks/useActivityData.ts index 837441b..e7ad1e3 100644 --- a/src/hooks/useActivityData.ts +++ b/src/hooks/useActivityData.ts @@ -1,5 +1,9 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; // Assuming you have these services for token management +import { useDispatch } from 'react-redux'; +import { message } from 'antd'; +import { useHandleLogout } from '@app/utils/authUtils'; interface ActivityDataItem { month: string; @@ -9,33 +13,93 @@ interface ActivityDataItem { const useActivityData = () => { const [data, setData] = useState([]); const [isLoading, setIsLoading] = useState(true); + const dispatch = useDispatch(); + + const handleLogout = useHandleLogout(); useEffect(() => { const fetchData = async () => { setIsLoading(true); try { + const token = readToken(); // Read JWT from localStorage + if (!token) { + throw new Error('No authentication token found'); + } + const response = await fetch(`${config.baseURL}/activitydata`, { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request }, }); + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Log out the user if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } throw new Error(`Network response was not ok (status: ${response.status})`); } + const data = await response.json(); setData(data); } catch (error) { console.error('Error:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); + setData([]); } finally { setIsLoading(false); } }; fetchData(); - }, []); + }, [dispatch]); return { data, isLoading }; }; export default useActivityData; + + +// import { useState, useEffect } from 'react'; +// import config from '@app/config/config'; + +// interface ActivityDataItem { +// month: string; +// total_gb: number; +// } + +// const useActivityData = () => { +// const [data, setData] = useState([]); +// const [isLoading, setIsLoading] = useState(true); + +// useEffect(() => { +// const fetchData = async () => { +// setIsLoading(true); +// try { +// const response = await fetch(`${config.baseURL}/activitydata`, { +// method: 'POST', +// headers: { +// 'Content-Type': 'application/json', +// }, +// }); +// if (!response.ok) { +// throw new Error(`Network response was not ok (status: ${response.status})`); +// } +// const data = await response.json(); +// setData(data); +// } catch (error) { +// console.error('Error:', error); +// } finally { +// setIsLoading(false); +// } +// }; + +// fetchData(); +// }, []); + +// return { data, isLoading }; +// }; + +// export default useActivityData; diff --git a/src/hooks/useBalanceData.ts b/src/hooks/useBalanceData.ts index bfd8cc9..1de2ceb 100644 --- a/src/hooks/useBalanceData.ts +++ b/src/hooks/useBalanceData.ts @@ -1,5 +1,9 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist +import { useDispatch } from 'react-redux'; +import { message } from 'antd'; +import { useHandleLogout } from '@app/utils/authUtils'; interface Transaction { id: number; @@ -18,91 +22,72 @@ const useBalanceData = () => { const [balanceData, setBalanceData] = useState(null); const [transactions, setTransactions] = useState([]); const [isLoading, setIsLoading] = useState(true); + const dispatch = useDispatch(); + + const handleLogout = useHandleLogout(); useEffect(() => { const fetchData = async () => { setIsLoading(true); try { + const token = readToken(); // Read JWT from localStorage + if (!token) { + throw new Error('No authentication token found'); + } + // Fetch balance data - const balanceResponse = await fetch(`${config.baseURL}/balance/usd`); + const balanceResponse = await fetch(`${config.baseURL}/balance/usd`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request + }, + }); + if (!balanceResponse.ok) { + if (balanceResponse.status === 401) { + handleLogout(); // Log out the user if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } throw new Error(`Network response was not ok (status: ${balanceResponse.status})`); } const balanceData: BalanceData = await balanceResponse.json(); setBalanceData(balanceData); // Fetch transaction data - const transactionsResponse = await fetch(`${config.baseURL}/transactions/latest`); + const transactionsResponse = await fetch(`${config.baseURL}/transactions/latest`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request + }, + }); + if (!transactionsResponse.ok) { + if (transactionsResponse.status === 401) { + handleLogout(); // Log out the user if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } throw new Error(`Network response was not ok (status: ${transactionsResponse.status})`); } const transactionsData: Transaction[] = await transactionsResponse.json(); setTransactions(transactionsData); console.log('Transactions data:', transactionsData); + } catch (error) { console.error('Error fetching data:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); + setBalanceData(null); + setTransactions([]); } finally { setIsLoading(false); } }; fetchData(); - }, []); + }, [dispatch]); return { balanceData, transactions, isLoading }; }; export default useBalanceData; - -// import { useState, useEffect } from 'react'; - -// interface Transaction { -// id: number; -// address: string; -// date: string; -// output: string; -// value: number; -// } - -// interface BalanceData { -// balance_usd: number; -// } - -// const useBalanceData = () => { -// const [balanceData, setBalanceData] = useState(null); -// const [transactions, setTransactions] = useState([]); -// const [isLoading, setIsLoading] = useState(true); - -// useEffect(() => { -// const fetchData = async () => { -// setIsLoading(true); -// try { -// // Fetch balance data -// const balanceResponse = await fetch('http://localhost:5000/balance/usd'); -// if (!balanceResponse.ok) { -// throw new Error(`Network response was not ok (status: ${balanceResponse.status})`); -// } -// const balanceData = await balanceResponse.json(); -// setBalanceData(balanceData); - -// // Fetch transaction data -// const transactionsResponse = await fetch('http://localhost:5000/transactions/latest'); -// if (!transactionsResponse.ok) { -// throw new Error(`Network response was not ok (status: ${transactionsResponse.status})`); -// } -// const transactionsData = await transactionsResponse.json(); -// setTransactions(transactionsData); -// } catch (error) { -// console.error('Error fetching data:', error); -// } finally { -// setIsLoading(false); -// } -// }; - -// fetchData(); -// }, []); - -// return { balanceData, transactions, isLoading }; -// }; - -// export default useBalanceData; diff --git a/src/hooks/useBarChartData.ts b/src/hooks/useBarChartData.ts index 44a78a7..83f0a72 100644 --- a/src/hooks/useBarChartData.ts +++ b/src/hooks/useBarChartData.ts @@ -1,5 +1,10 @@ + import { useState, useEffect } from 'react'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist +import { useDispatch } from 'react-redux'; +import { message } from 'antd'; +import { useHandleLogout } from '@app/utils/authUtils'; interface BarChartDataItem { month: string; @@ -10,34 +15,96 @@ interface BarChartDataItem { const useBarChartData = () => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); + const dispatch = useDispatch(); + + const handleLogout = useHandleLogout(); useEffect(() => { const fetchData = async () => { setIsLoading(true); try { + const token = readToken(); // Read JWT from localStorage + if (!token) { + throw new Error('No authentication token found'); + } + const response = await fetch(`${config.baseURL}/barchartdata`, { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request }, body: JSON.stringify({}), // If needed, include a payload here }); + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Log out the user if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } throw new Error(`Network response was not ok (status: ${response.status})`); } + const data = await response.json(); setData(data); } catch (error) { console.error('Error:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); + setData(null); } finally { setIsLoading(false); } }; fetchData(); - }, []); + }, [dispatch]); return { data, isLoading }; }; export default useBarChartData; + + +// import { useState, useEffect } from 'react'; +// import config from '@app/config/config'; + +// interface BarChartDataItem { +// month: string; +// notes_gb: number; +// media_gb: number; +// } + +// const useBarChartData = () => { +// const [data, setData] = useState(null); +// const [isLoading, setIsLoading] = useState(true); + +// useEffect(() => { +// const fetchData = async () => { +// setIsLoading(true); +// try { +// const response = await fetch(`${config.baseURL}/barchartdata`, { +// method: 'POST', +// headers: { +// 'Content-Type': 'application/json', +// }, +// body: JSON.stringify({}), // If needed, include a payload here +// }); +// if (!response.ok) { +// throw new Error(`Network response was not ok (status: ${response.status})`); +// } +// const data = await response.json(); +// setData(data); +// } catch (error) { +// console.error('Error:', error); +// } finally { +// setIsLoading(false); +// } +// }; + +// fetchData(); +// }, []); + +// return { data, isLoading }; +// }; + +// export default useBarChartData; diff --git a/src/hooks/useBitcoinRates.ts b/src/hooks/useBitcoinRates.ts index 2d66002..11dc402 100644 --- a/src/hooks/useBitcoinRates.ts +++ b/src/hooks/useBitcoinRates.ts @@ -1,5 +1,8 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist +import { useDispatch } from 'react-redux'; +import { useHandleLogout } from '@app/utils/authUtils'; interface Earning { date: number; @@ -10,14 +13,37 @@ export const useBitcoinRates = () => { const [rates, setRates] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); + const dispatch = useDispatch(); + + const handleLogout = useHandleLogout(); useEffect(() => { const fetchBitcoinRates = async () => { + setIsLoading(true); + setError(null); // Reset error state before fetching + try { - const response = await fetch(`${config.baseURL}/bitcoin-rates/last-30-days`); + const token = readToken(); // Read JWT from localStorage + if (!token) { + throw new Error('No authentication token found'); + } + + const response = await fetch(`${config.baseURL}/bitcoin-rates/last-30-days`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request + }, + }); + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Log out the user if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } throw new Error(`Network response was not ok (status: ${response.status})`); } + const data = await response.json(); setRates( data.map((item: { Rate: number; Timestamp: string }) => ({ @@ -25,15 +51,56 @@ export const useBitcoinRates = () => { usd_value: item.Rate, })), ); - setIsLoading(false); } catch (err: any) { setError(err.message); + } finally { setIsLoading(false); } }; fetchBitcoinRates(); - }, []); + }, [dispatch]); return { rates, isLoading, error }; }; + + +// import { useState, useEffect } from 'react'; +// import config from '@app/config/config'; + +// interface Earning { +// date: number; +// usd_value: number; +// } + +// export const useBitcoinRates = () => { +// const [rates, setRates] = useState([]); +// const [isLoading, setIsLoading] = useState(true); +// const [error, setError] = useState(null); + +// useEffect(() => { +// const fetchBitcoinRates = async () => { +// try { +// const response = await fetch(`${config.baseURL}/bitcoin-rates/last-30-days`); +// if (!response.ok) { +// throw new Error(`Network response was not ok (status: ${response.status})`); +// } +// const data = await response.json(); +// setRates( +// data.map((item: { Rate: number; Timestamp: string }) => ({ +// date: new Date(item.Timestamp).getTime(), +// usd_value: item.Rate, +// })), +// ); +// setIsLoading(false); +// } catch (err: any) { +// setError(err.message); +// setIsLoading(false); +// } +// }; + +// fetchBitcoinRates(); +// }, []); + +// return { rates, isLoading, error }; +// }; diff --git a/src/hooks/useChartData.ts b/src/hooks/useChartData.ts index 304268e..4adae17 100644 --- a/src/hooks/useChartData.ts +++ b/src/hooks/useChartData.ts @@ -1,73 +1,172 @@ -import { useState, useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useState, useEffect, useCallback } from 'react'; import config from '@app/config/config'; - -interface ChartDataItem { - value: number; - name: string; +import { readToken } from '@app/services/localStorage.service'; // Assuming you have these services +import { useDispatch } from 'react-redux'; +import { message } from 'antd'; +import { useHandleLogout } from '@app/utils/authUtils'; + +interface RelaySettings { + mode: string; + protocol: string[]; + chunked: string[]; + chunksize: string; + maxFileSize: number; + maxFileSizeUnit: string; + kinds: string[]; + dynamicKinds: string[]; + photos: string[]; + videos: string[]; + gitNestr: string[]; + audio: string[]; + appBuckets: string[]; + dynamicAppBuckets: string[]; + isKindsActive: boolean; + isPhotosActive: boolean; + isVideosActive: boolean; + isGitNestrActive: boolean; + isAudioActive: boolean; } -const useChartData = () => { - const [chartData, setChartData] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const { t } = useTranslation(); +const getInitialSettings = (): RelaySettings => { + const savedSettings = localStorage.getItem('relaySettings'); + return savedSettings + ? JSON.parse(savedSettings) + : { + mode: 'smart', + protocol: ['WebSocket'], + chunked: ['unchunked'], + chunksize: '2', + maxFileSize: 100, + maxFileSizeUnit: 'MB', + dynamicKinds: [], + kinds: [], + photos: [], + videos: [], + gitNestr: [], + audio: [], + appBuckets: [], + dynamicAppBuckets: [], + isKindsActive: true, + isPhotosActive: true, + isVideosActive: true, + isGitNestrActive: true, + isAudioActive: true, + }; +}; + +const useRelaySettings = () => { + const [relaySettings, setRelaySettings] = useState(getInitialSettings()); + const dispatch = useDispatch(); useEffect(() => { - console.log('Component mounted, starting data fetch...'); - - const fetchData = async () => { - console.log('Preparing to fetch data...'); - setIsLoading(true); - - try { - console.log('Sending request to server...'); - const response = await fetch(`${config.baseURL}/relaycount`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ relaycount: [] }), // No need to send kinds, server knows the categories - }); - if (!response.ok) { - throw new Error(`Network response was not ok (status: ${response.status})`); + localStorage.setItem('relaySettings', JSON.stringify(relaySettings)); + }, [relaySettings]); + + const handleLogout = useHandleLogout(); + + const fetchSettings = useCallback(async () => { + try { + const token = readToken(); // Read JWT from localStorage + if (!token) { + throw new Error('No authentication token found'); + } + + const response = await fetch(`${config.baseURL}/relay-settings`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Add JWT to Authorization header + }, + }); + + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Logout on invalid token + throw new Error('Authentication failed. Please log in again.'); } - const data = await response.json(); // Expecting a response like { "kinds": 10, "photos": 5, "videos": 3, "gitNestr": 2, "audio": 6, "misc": 4 } - console.log('Response Data:', data); - - // Process the data into chartDataItems using translated names - const newChartData: ChartDataItem[] = [ - { value: data.kinds, name: t('categories.kinds') }, - { value: data.photos, name: t('categories.photos') }, - { value: data.videos, name: t('categories.videos') }, - // { value: data.gitNestr, name: t('categories.gitNestr') }, // Not yet ready - { value: data.audio, name: t('categories.audio') }, // Add the audio slice - { value: data.misc, name: t('categories.misc') }, // Add the misc slice - ]; - - setChartData(newChartData); - } catch (error) { - console.error('Error:', error); - } finally { - console.log('Fetching process complete.'); - setIsLoading(false); + throw new Error(`Network response was not ok (status: ${response.status})`); + } + + const data = await response.json(); + + const storedAppBuckets = JSON.parse(localStorage.getItem('appBuckets') || '[]'); + const storedDynamicKinds = JSON.parse(localStorage.getItem('dynamicKinds') || '[]'); + + const newAppBuckets = data.relay_settings.dynamicAppBuckets?.filter((bucket: string) => !storedAppBuckets.includes(bucket)) || []; + const newDynamicKinds = data.relay_settings.dynamicKinds?.filter((kind: string) => !storedDynamicKinds.includes(kind)) || []; + + if (newAppBuckets.length > 0) { + localStorage.setItem('appBuckets', JSON.stringify([...storedAppBuckets, ...newAppBuckets])); + } + if (newDynamicKinds.length > 0) { + localStorage.setItem('dynamicKinds', JSON.stringify([...storedDynamicKinds, ...newDynamicKinds])); } - }; - fetchData(); + setRelaySettings({ + ...data.relay_settings, + protocol: Array.isArray(data.relay_settings.protocol) ? data.relay_settings.protocol : [data.relay_settings.protocol], + chunked: Array.isArray(data.relay_settings.chunked) ? data.relay_settings.chunked : [data.relay_settings.chunked], + }); + + } catch (error) { + console.error('Error fetching settings:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); + } + }, []); + + const updateSettings = useCallback((category: keyof RelaySettings, value: string | string[] | boolean | number) => { + setRelaySettings((prevSettings) => ({ + ...prevSettings, + [category]: value, + })); + }, []); + + const saveSettings = useCallback(async () => { + try { + const token = readToken(); + if (!token) { + throw new Error('No authentication token found'); + } - return () => { - console.log('Cleanup called; Component unmounting...'); - }; - }, [t]); + const response = await fetch(`${config.baseURL}/relay-settings`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Add JWT to Authorization header + }, + body: JSON.stringify({ relay_settings: relaySettings }), + }); + + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Logout on invalid token + throw new Error('Authentication failed. Please log in again.'); + } + throw new Error(`Network response was not ok (status: ${response.status})`); + } - return { chartData, isLoading }; + localStorage.setItem('settingsCache', JSON.stringify(relaySettings)); + message.success('Settings saved successfully!'); + } catch (error) { + console.error('Error saving settings:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); + } + }, [relaySettings]); + + return { relaySettings, fetchSettings, updateSettings, saveSettings }; }; -export default useChartData; +export default useRelaySettings; + + // import { useState, useEffect } from 'react'; // import { useTranslation } from 'react-i18next'; +// import { useDispatch } from 'react-redux'; // import config from '@app/config/config'; +// import { readToken, deleteToken, deleteUser } from '@app/services/localStorage.service'; +// import { setUser } from '@app/store/slices/userSlice'; +// import { message } from 'antd'; // interface ChartDataItem { // value: number; @@ -78,41 +177,56 @@ export default useChartData; // const [chartData, setChartData] = useState(null); // const [isLoading, setIsLoading] = useState(true); // const { t } = useTranslation(); +// const dispatch = useDispatch(); + +// const handleLogout = () => { +// deleteToken(); +// deleteUser(); +// dispatch(setUser(null)); +// console.log('Token deleted, user logged out'); +// message.info('You have been logged out. Please login again.'); +// }; // useEffect(() => { // console.log('Component mounted, starting data fetch...'); - // const fetchData = async () => { // console.log('Preparing to fetch data...'); // setIsLoading(true); - // try { +// const token = readToken(); +// if (!token) { +// throw new Error('No authentication token found'); +// } // console.log('Sending request to server...'); -// const response = await fetch(`${config.baseURL}/relaycount`, { -// method: 'POST', +// const response = await fetch(`${config.baseURL}/relay-count`, { +// method: 'GET', // headers: { // 'Content-Type': 'application/json', +// 'Authorization': `Bearer ${token}`, // }, -// body: JSON.stringify({ relaycount: [] }), // No need to send kinds, server knows the categories // }); // if (!response.ok) { +// if (response.status === 401) { +// handleLogout(); +// throw new Error('Authentication failed. You have been logged out.'); +// } // throw new Error(`Network response was not ok (status: ${response.status})`); // } -// const data = await response.json(); // Expecting a response like { "kinds": 10, "photos": 5, "videos": 3, "gitNestr": 2, "misc": 4 } +// const data = await response.json(); // console.log('Response Data:', data); - // // Process the data into chartDataItems using translated names // const newChartData: ChartDataItem[] = [ // { value: data.kinds, name: t('categories.kinds') }, // { value: data.photos, name: t('categories.photos') }, // { value: data.videos, name: t('categories.videos') }, -// { value: data.gitNestr, name: t('categories.gitNestr') }, -// { value: data.misc, name: t('categories.misc') }, // Add the misc slice +// { value: data.audio, name: t('categories.audio') }, +// { value: data.misc, name: t('categories.misc') }, // ]; - // setChartData(newChartData); // } catch (error) { // console.error('Error:', error); +// message.error(error instanceof Error ? error.message : 'An error occurred'); +// setChartData(null); // } finally { // console.log('Fetching process complete.'); // setIsLoading(false); @@ -124,73 +238,9 @@ export default useChartData; // return () => { // console.log('Cleanup called; Component unmounting...'); // }; -// }, [t]); +// }, [t, dispatch]); // return { chartData, isLoading }; // }; -// export default useChartData; - -// import { useState, useEffect } from 'react'; -// import { useTranslation } from 'react-i18next'; // Import useTranslation hook - -// interface ChartDataItem { -// value: number; -// name: string; -// } - -// const useChartData = () => { -// const [chartData, setChartData] = useState(null); -// const [isLoading, setIsLoading] = useState(true); -// const { t } = useTranslation(); // Initialize the translation hook - -// useEffect(() => { -// console.log("Component mounted, starting data fetch..."); - -// const fetchData = async () => { -// console.log("Preparing to fetch data..."); -// setIsLoading(true); -// const kinds = [0, 1, 3, 5, 6, 10000, 1984, 30000, 30008, 30009, 30023, 36810, 7, 8, 9372, 9373, 9735, 9802] -// ; // The kinds you want to count - -// try { -// console.log("Sending request to server..."); -// const response = await fetch('http://localhost:5000/relaycount', { -// method: 'POST', -// headers: { -// 'Content-Type': 'application/json' -// }, -// body: JSON.stringify({ relaycount: kinds }) -// }); -// if (!response.ok) { -// throw new Error(`Network response was not ok (status: ${response.status})`); -// } -// const data = await response.json(); // Expecting a response like { "0": 2, "1": 3, ... } -// console.log("Response Data:", data); - -// // Process the data into chartDataItems using translated names -// const newChartData = kinds.map(kind => ({ -// value: data[kind.toString()], // Access the response by converting kind to string key -// name: t(`checkboxes.kind${kind}`) // Use the translated name for each kind -// })).filter(item => item.value !== undefined); // Filter out any undefined values if kind was not in response - -// setChartData(newChartData); -// } catch (error) { -// console.error('Error:', error); -// } finally { -// console.log("Fetching process complete."); -// setIsLoading(false); -// } -// }; - -// fetchData(); - -// return () => { -// console.log("Cleanup called; Component unmounting..."); -// }; -// }, [t]); // Add t to the dependency array to re-run the effect when the translation changes - -// return { chartData, isLoading }; -// }; - -// export default useChartData; +// export default useChartData; \ No newline at end of file diff --git a/src/hooks/useKindData.ts b/src/hooks/useKindData.ts index 0a8f61b..8de37b2 100644 --- a/src/hooks/useKindData.ts +++ b/src/hooks/useKindData.ts @@ -1,6 +1,10 @@ import { useState, useEffect } from 'react'; import kindMapping from '../constants/kindMapping'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist +import { useDispatch } from 'react-redux'; +import { message } from 'antd'; +import { useHandleLogout } from '@app/utils/authUtils'; interface KindData { kindNumber: number; @@ -14,20 +18,35 @@ interface KindData { const useKindData = () => { const [kindData, setKindData] = useState([]); const [isLoading, setIsLoading] = useState(true); + const dispatch = useDispatch(); + + const handleLogout = useHandleLogout(); useEffect(() => { const fetchKindData = async () => { setIsLoading(true); try { + const token = readToken(); // Read JWT from local storage + if (!token) { + throw new Error('No authentication token found'); + } + const response = await fetch(`${config.baseURL}/api/kinds`, { method: 'GET', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request }, }); + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Log out if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } throw new Error(`Network response was not ok (status: ${response.status})`); } + const data = await response.json(); const enrichedData = data.map((item: any) => { const mapping = kindMapping[item.kindNumber] || { description: 'Unknown', nip: 'Unknown' }; @@ -41,26 +60,32 @@ const useKindData = () => { setKindData(enrichedData); } catch (error) { console.error('Error fetching kind data:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); } finally { setIsLoading(false); } }; fetchKindData(); - }, []); + }, [dispatch]); return { kindData, isLoading }; }; export default useKindData; + // import { useState, useEffect } from 'react'; +// import kindMapping from '../constants/kindMapping'; // import config from '@app/config/config'; // interface KindData { -// kindName: string; -// kindCount: number; +// kindNumber: number; // totalSize: number; +// kindCount: number; +// kindName: string; +// description: string; +// nip: string; // } // const useKindData = () => { @@ -81,7 +106,16 @@ export default useKindData; // throw new Error(`Network response was not ok (status: ${response.status})`); // } // const data = await response.json(); -// setKindData(data); +// const enrichedData = data.map((item: any) => { +// const mapping = kindMapping[item.kindNumber] || { description: 'Unknown', nip: 'Unknown' }; +// return { +// ...item, +// kindName: `Kind ${item.kindNumber}`, +// description: mapping.description, +// nip: mapping.nip, +// }; +// }); +// setKindData(enrichedData); // } catch (error) { // console.error('Error fetching kind data:', error); // } finally { diff --git a/src/hooks/useKindTrendData.ts b/src/hooks/useKindTrendData.ts index 04b095e..380f1f8 100644 --- a/src/hooks/useKindTrendData.ts +++ b/src/hooks/useKindTrendData.ts @@ -1,5 +1,9 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist +import { useDispatch } from 'react-redux'; +import { message } from 'antd'; +import { useHandleLogout } from '@app/utils/authUtils'; interface MonthlyKindData { month: string; @@ -9,29 +13,89 @@ interface MonthlyKindData { const useKindTrendData = (kindNumber: number) => { const [trendData, setTrendData] = useState([]); const [isLoading, setIsLoading] = useState(true); + const dispatch = useDispatch(); + + const handleLogout = useHandleLogout(); useEffect(() => { const fetchTrendData = async () => { setIsLoading(true); try { - const response = await fetch(`${config.baseURL}/api/kind-trend/${kindNumber}`); + const token = readToken(); // Read JWT from localStorage + if (!token) { + throw new Error('No authentication token found'); + } + + const response = await fetch(`${config.baseURL}/api/kind-trend/${kindNumber}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request + }, + }); + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Log out if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } throw new Error('Failed to fetch kind trend data'); } + const data = await response.json(); console.log('Trend data response:', data); setTrendData(data); } catch (error) { console.error('Error fetching kind trend data:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); } finally { setIsLoading(false); } }; fetchTrendData(); - }, [kindNumber]); + }, [kindNumber, dispatch]); return { trendData, isLoading }; }; export default useKindTrendData; + + +// import { useState, useEffect } from 'react'; +// import config from '@app/config/config'; + +// interface MonthlyKindData { +// month: string; +// totalSize: number; +// } + +// const useKindTrendData = (kindNumber: number) => { +// const [trendData, setTrendData] = useState([]); +// const [isLoading, setIsLoading] = useState(true); + +// useEffect(() => { +// const fetchTrendData = async () => { +// setIsLoading(true); +// try { +// const response = await fetch(`${config.baseURL}/api/kind-trend/${kindNumber}`); +// if (!response.ok) { +// throw new Error('Failed to fetch kind trend data'); +// } +// const data = await response.json(); +// console.log('Trend data response:', data); +// setTrendData(data); +// } catch (error) { +// console.error('Error fetching kind trend data:', error); +// } finally { +// setIsLoading(false); +// } +// }; + +// fetchTrendData(); +// }, [kindNumber]); + +// return { trendData, isLoading }; +// }; + +// export default useKindTrendData; diff --git a/src/hooks/useLineChartData.ts b/src/hooks/useLineChartData.ts index 2a76b25..65e9071 100644 --- a/src/hooks/useLineChartData.ts +++ b/src/hooks/useLineChartData.ts @@ -1,5 +1,9 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; // Assuming you have these services for token management +import { useDispatch } from 'react-redux'; +import { message } from 'antd'; +import { useHandleLogout } from '@app/utils/authUtils'; interface TimeSeriesData { month: string; @@ -12,35 +16,99 @@ interface TimeSeriesData { const useLineChartData = () => { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); + const dispatch = useDispatch(); + + const handleLogout = useHandleLogout(); useEffect(() => { const fetchData = async () => { setIsLoading(true); try { + const token = readToken(); // Read JWT from localStorage + if (!token) { + throw new Error('No authentication token found'); + } + const response = await fetch(`${config.baseURL}/timeseries`, { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request }, body: JSON.stringify({}), // If needed, include a payload here }); + if (!response.ok) { + if (response.status === 401) { + handleLogout(); // Log out the user if token is invalid or expired + throw new Error('Authentication failed. You have been logged out.'); + } throw new Error(`Network response was not ok (status: ${response.status})`); } + const data = await response.json(); console.log('Data:', data); setData(data); } catch (error) { console.error('Error:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); + setData(null); } finally { setIsLoading(false); } }; fetchData(); - }, []); + }, [dispatch]); return { data, isLoading }; }; export default useLineChartData; + +// import { useState, useEffect } from 'react'; +// import config from '@app/config/config'; + +// interface TimeSeriesData { +// month: string; +// profiles: number; +// lightning_addr: number; +// dht_key: number; +// lightning_and_dht: number; +// } + +// const useLineChartData = () => { +// const [data, setData] = useState(null); +// const [isLoading, setIsLoading] = useState(true); + +// useEffect(() => { +// const fetchData = async () => { +// setIsLoading(true); +// try { +// const response = await fetch(`${config.baseURL}/timeseries`, { +// method: 'POST', +// headers: { +// 'Content-Type': 'application/json', +// }, +// body: JSON.stringify({}), // If needed, include a payload here +// }); +// if (!response.ok) { +// throw new Error(`Network response was not ok (status: ${response.status})`); +// } +// const data = await response.json(); +// console.log('Data:', data); +// setData(data); +// } catch (error) { +// console.error('Error:', error); +// } finally { +// setIsLoading(false); +// } +// }; + +// fetchData(); +// }, []); + +// return { data, isLoading }; +// }; + +// export default useLineChartData; diff --git a/src/hooks/usePendingTransactions.ts b/src/hooks/usePendingTransactions.ts index 783889c..429f116 100644 --- a/src/hooks/usePendingTransactions.ts +++ b/src/hooks/usePendingTransactions.ts @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; export interface PendingTransaction { txid: string; @@ -17,11 +18,14 @@ const usePendingTransactions = () => { useEffect(() => { const fetchPendingTransactions = async () => { setIsLoading(true); + // Fetch the JWT token using readToken() + const pendingToken = readToken(); try { const response = await fetch(`${config.baseURL}/pending-transactions`, { method: 'GET', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${pendingToken}`, // Use the token from readToken() }, }); if (!response.ok) { diff --git a/src/store/slices/authSlice.ts b/src/store/slices/authSlice.ts index a8efd40..9e3ce66 100644 --- a/src/store/slices/authSlice.ts +++ b/src/store/slices/authSlice.ts @@ -15,6 +15,8 @@ import { setUser } from '@app/store/slices/userSlice'; import { deleteToken, deleteUser, persistToken, readToken } from '@app/services/localStorage.service'; import { notificationController } from '@app/controllers/notificationController'; import { createSignedMessage } from '@app/hooks/useSigner'; +import config from '@app/config/config'; +import { message } from 'antd'; // Define the initial state for the auth slice export interface AuthSlice { @@ -87,11 +89,34 @@ export const doSetNewPassword = createAsyncThunk('auth/doSetNewPassword', async ); // Define async thunk for logout -export const doLogout = createAsyncThunk('auth/doLogout', (payload, { dispatch }) => { - deleteToken(); - deleteUser(); - dispatch(setUser(null)); - console.log('Token deleted'); +export const doLogout = createAsyncThunk('auth/doLogout', async (_, { dispatch }) => { + try { + const token = readToken(); + if (token) { + const response = await fetch(`${config.baseURL}/logout`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + throw new Error('Logout failed on server'); + } + } + + // Clean up local storage + deleteToken(); + deleteUser(); + + // Clear the user state + dispatch(setUser(null)); + + console.log('Token deleted and user logged out'); + } catch (error) { + console.error('Logout error:', error); + message.error('An error occurred during logout'); + } }); // Create the auth slice From b668afb9a06b225321480b6b556510174f5d968b Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 11 Sep 2024 13:01:12 +0200 Subject: [PATCH 31/62] Fixing endpoint bugs as a result of jwt. --- src/api/activity.api.ts | 63 +++--- src/api/earnings.api.ts | 2 +- .../Balance/components/SendForm/SendForm.tsx | 2 +- .../TopUpBalanceModal/TopUpBalanceModal.tsx | 6 +- .../activityStory/ActivityStory.tsx | 17 +- .../ReplaceTransaction/ReplaceTransaction.tsx | 2 +- src/hooks/useActivityData.ts | 8 +- src/hooks/useBalanceData.ts | 6 +- src/hooks/useBarChartData.ts | 9 +- src/hooks/useBitcoinRates.ts | 6 +- src/hooks/useChartData.ts | 205 +++++------------- src/hooks/useKindData.ts | 2 +- src/hooks/useKindTrendData.ts | 2 +- src/hooks/useLineChartData.ts | 9 +- src/hooks/usePendingTransactions.ts | 2 +- src/hooks/useRelaySettings.ts | 109 +--------- 16 files changed, 133 insertions(+), 317 deletions(-) diff --git a/src/api/activity.api.ts b/src/api/activity.api.ts index 34af9c1..11e9dcb 100644 --- a/src/api/activity.api.ts +++ b/src/api/activity.api.ts @@ -3,7 +3,7 @@ import { ActivityStatusType } from '@app/interfaces/interfaces'; import { readToken } from '@app/services/localStorage.service'; import config from '@app/config/config'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { message } from 'antd'; export interface WalletTransaction { id: number; @@ -13,45 +13,46 @@ export interface WalletTransaction { value: string; } -export const getUserActivities = (): Promise => { +export const getUserActivities = (handleLogout: () => void): Promise => { const token = readToken(); // Read the JWT token from local storage - const handleLogout = useHandleLogout(); + if (!token) { + handleLogout(); // Call handleLogout if no token + return Promise.reject('No token found'); + } - return fetch(`${config.baseURL}/transactions/latest`, { + return fetch(`${config.baseURL}/api/transactions/latest`, { method: 'GET', headers: { + 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, // Add JWT to Authorization header }, }) - .then((response) => { - if (!response.ok) { - if (response.status === 401) { - handleLogout(); // Log out the user if the token is invalid or expired - } - throw new Error('Network response was not ok'); - } - return response.json(); - }) - .then((data) => { - if (Array.isArray(data) && data.length === 0) { - // Handle the case where the response is an empty array - return []; - } - // Assuming your backend response matches the WalletTransaction interface - return data.map((item: any) => ({ - id: item.ID, - witness_tx_id: item.WitnessTxId, - date: new Date(item.Date).getTime(), - output: item.Output, - value: item.Value, - })); - }) - .catch((error) => { - console.error('Error fetching user activities:', error); + .then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => { + if (Array.isArray(data) && data.length === 0) { + // Handle the case where the response is an empty array return []; - }); + } + // Assuming your backend response matches the WalletTransaction interface + // eslint-disable-next-line + return data.map((item: any) => ({ + id: item.ID, + witness_tx_id: item.WitnessTxId, + date: new Date(item.Date).getTime(), + output: item.Output, + value: item.Value, + })); + }) + .catch((error) => { + console.error('Error fetching user activities:', error); + return []; + }); }; export interface Activity { diff --git a/src/api/earnings.api.ts b/src/api/earnings.api.ts index 91ea08a..e3a27d4 100644 --- a/src/api/earnings.api.ts +++ b/src/api/earnings.api.ts @@ -75,7 +75,7 @@ export const getTotalEarning = (id: number, currency: CurrencyTypeEnum): Promise export const getBitcoinRatesForLast30Days = (): Promise => { console.log('Fetching bitcoin rate data.'); - return fetch('http://localhost:5000/bitcoin-rates/last-30-days') + return fetch('http://localhost:5000/api/bitcoin-rates/last-30-days') .then((response) => response.json()) .then((data) => { console.log('Received data:', data); // Add log statement to see the data diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 71cf8ba..af04cb1 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -196,7 +196,7 @@ const SendForm: React.FC = ({ onSend }) => { // Fetch the JWT token using readToken() const pendingToken = readToken(); - const pendingResponse = await fetch(`${config.baseURL}/pending-transactions`, { + const pendingResponse = await fetch(`${config.baseURL}/api/pending-transactions`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/nft-dashboard/Balance/components/TopUpBalanceModal/TopUpBalanceModal.tsx b/src/components/nft-dashboard/Balance/components/TopUpBalanceModal/TopUpBalanceModal.tsx index 89b290f..7333b5b 100644 --- a/src/components/nft-dashboard/Balance/components/TopUpBalanceModal/TopUpBalanceModal.tsx +++ b/src/components/nft-dashboard/Balance/components/TopUpBalanceModal/TopUpBalanceModal.tsx @@ -7,7 +7,7 @@ import axios from 'axios'; import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist import { useDispatch } from 'react-redux'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { useHandleLogout } from '@app/hooks/authUtils'; interface TopUpBalanceModalProps extends TopUpDataProps { isOpen: boolean; @@ -42,7 +42,7 @@ export const TopUpBalanceModal: React.FC = ({ } axios - .get(`${config.baseURL}/addresses`, { + .get(`${config.baseURL}/api/addresses`, { headers: { 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request }, @@ -104,7 +104,7 @@ export const TopUpBalanceModal: React.FC = ({ // if (isOpen) { // setIsLoading(true); // axios -// .get(`${config.baseURL}/addresses`) +// .get(`${config.baseURL}/api/addresses`) // .then((response) => { // setAddresses(response.data); // setIsLoading(false); diff --git a/src/components/nft-dashboard/activityStory/ActivityStory.tsx b/src/components/nft-dashboard/activityStory/ActivityStory.tsx index e824b4b..7a2574f 100644 --- a/src/components/nft-dashboard/activityStory/ActivityStory.tsx +++ b/src/components/nft-dashboard/activityStory/ActivityStory.tsx @@ -23,6 +23,7 @@ import { } from 'chart.js'; import { TransactionCard } from './ActivityStoryItem/ActivityStoryItem.styles'; import ButtonTrigger from '../unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger'; +import { useHandleLogout } from '@app/hooks/authUtils'; ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler); @@ -37,15 +38,21 @@ export const ActivityStory: React.FC = () => { const [story, setStory] = useState([]); const [isModalVisible, setIsModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(true); + const handleLogout = useHandleLogout(); const { t } = useTranslation(); useEffect(() => { - getUserActivities().then((res) => { - setStory(res); - setIsLoading(false); - }); - }, []); + getUserActivities(handleLogout) + .then((res) => { + setStory(res); + setIsLoading(false); + }) + .catch((error) => { + console.error('Failed to load user activities:', error); + setIsLoading(false); + }); + }, [handleLogout]); const activityContent = story.length > 0 ? ( diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index f70e18a..d8e6953 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -136,7 +136,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep enable_rbf: true, // RBF status }; - const pendingResponse = await fetch(`${config.baseURL}/replacement-transactions`, { + const pendingResponse = await fetch(`${config.baseURL}/api/replacement-transactions`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/hooks/useActivityData.ts b/src/hooks/useActivityData.ts index e7ad1e3..e28309a 100644 --- a/src/hooks/useActivityData.ts +++ b/src/hooks/useActivityData.ts @@ -3,7 +3,7 @@ import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; // Assuming you have these services for token management import { useDispatch } from 'react-redux'; import { message } from 'antd'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { useHandleLogout } from './authUtils'; interface ActivityDataItem { month: string; @@ -26,8 +26,8 @@ const useActivityData = () => { throw new Error('No authentication token found'); } - const response = await fetch(`${config.baseURL}/activitydata`, { - method: 'POST', + const response = await fetch(`${config.baseURL}/api/activitydata`, { + method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request @@ -78,7 +78,7 @@ export default useActivityData; // const fetchData = async () => { // setIsLoading(true); // try { -// const response = await fetch(`${config.baseURL}/activitydata`, { +// const response = await fetch(`${config.baseURL}/api/activitydata`, { // method: 'POST', // headers: { // 'Content-Type': 'application/json', diff --git a/src/hooks/useBalanceData.ts b/src/hooks/useBalanceData.ts index 1de2ceb..f28c786 100644 --- a/src/hooks/useBalanceData.ts +++ b/src/hooks/useBalanceData.ts @@ -3,7 +3,7 @@ import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist import { useDispatch } from 'react-redux'; import { message } from 'antd'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { useHandleLogout } from './authUtils'; interface Transaction { id: number; @@ -36,7 +36,7 @@ const useBalanceData = () => { } // Fetch balance data - const balanceResponse = await fetch(`${config.baseURL}/balance/usd`, { + const balanceResponse = await fetch(`${config.baseURL}/api/balance/usd`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -55,7 +55,7 @@ const useBalanceData = () => { setBalanceData(balanceData); // Fetch transaction data - const transactionsResponse = await fetch(`${config.baseURL}/transactions/latest`, { + const transactionsResponse = await fetch(`${config.baseURL}/api/transactions/latest`, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/src/hooks/useBarChartData.ts b/src/hooks/useBarChartData.ts index 83f0a72..eac387e 100644 --- a/src/hooks/useBarChartData.ts +++ b/src/hooks/useBarChartData.ts @@ -4,7 +4,7 @@ import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist import { useDispatch } from 'react-redux'; import { message } from 'antd'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { useHandleLogout } from './authUtils'; interface BarChartDataItem { month: string; @@ -28,13 +28,12 @@ const useBarChartData = () => { throw new Error('No authentication token found'); } - const response = await fetch(`${config.baseURL}/barchartdata`, { - method: 'POST', + const response = await fetch(`${config.baseURL}/api/barchartdata`, { + method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request }, - body: JSON.stringify({}), // If needed, include a payload here }); if (!response.ok) { @@ -82,7 +81,7 @@ export default useBarChartData; // const fetchData = async () => { // setIsLoading(true); // try { -// const response = await fetch(`${config.baseURL}/barchartdata`, { +// const response = await fetch(`${config.baseURL}/api/barchartdata`, { // method: 'POST', // headers: { // 'Content-Type': 'application/json', diff --git a/src/hooks/useBitcoinRates.ts b/src/hooks/useBitcoinRates.ts index 11dc402..8930a88 100644 --- a/src/hooks/useBitcoinRates.ts +++ b/src/hooks/useBitcoinRates.ts @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'; import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist import { useDispatch } from 'react-redux'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { useHandleLogout } from './authUtils'; interface Earning { date: number; @@ -28,7 +28,7 @@ export const useBitcoinRates = () => { throw new Error('No authentication token found'); } - const response = await fetch(`${config.baseURL}/bitcoin-rates/last-30-days`, { + const response = await fetch(`${config.baseURL}/api/bitcoin-rates/last-30-days`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -81,7 +81,7 @@ export const useBitcoinRates = () => { // useEffect(() => { // const fetchBitcoinRates = async () => { // try { -// const response = await fetch(`${config.baseURL}/bitcoin-rates/last-30-days`); +// const response = await fetch(`${config.baseURL}/api/bitcoin-rates/last-30-days`); // if (!response.ok) { // throw new Error(`Network response was not ok (status: ${response.status})`); // } diff --git a/src/hooks/useChartData.ts b/src/hooks/useChartData.ts index 4adae17..4771fa9 100644 --- a/src/hooks/useChartData.ts +++ b/src/hooks/useChartData.ts @@ -1,163 +1,78 @@ -import { useState, useEffect, useCallback } from 'react'; -import config from '@app/config/config'; -import { readToken } from '@app/services/localStorage.service'; // Assuming you have these services +// src/hooks/useChartData.ts +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; +import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; import { message } from 'antd'; -import { useHandleLogout } from '@app/utils/authUtils'; - -interface RelaySettings { - mode: string; - protocol: string[]; - chunked: string[]; - chunksize: string; - maxFileSize: number; - maxFileSizeUnit: string; - kinds: string[]; - dynamicKinds: string[]; - photos: string[]; - videos: string[]; - gitNestr: string[]; - audio: string[]; - appBuckets: string[]; - dynamicAppBuckets: string[]; - isKindsActive: boolean; - isPhotosActive: boolean; - isVideosActive: boolean; - isGitNestrActive: boolean; - isAudioActive: boolean; -} +import { useHandleLogout } from './authUtils'; -const getInitialSettings = (): RelaySettings => { - const savedSettings = localStorage.getItem('relaySettings'); - return savedSettings - ? JSON.parse(savedSettings) - : { - mode: 'smart', - protocol: ['WebSocket'], - chunked: ['unchunked'], - chunksize: '2', - maxFileSize: 100, - maxFileSizeUnit: 'MB', - dynamicKinds: [], - kinds: [], - photos: [], - videos: [], - gitNestr: [], - audio: [], - appBuckets: [], - dynamicAppBuckets: [], - isKindsActive: true, - isPhotosActive: true, - isVideosActive: true, - isGitNestrActive: true, - isAudioActive: true, - }; -}; +interface ChartDataItem { + value: number; + name: string; +} -const useRelaySettings = () => { - const [relaySettings, setRelaySettings] = useState(getInitialSettings()); +const useChartData = () => { + const [chartData, setChartData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const { t } = useTranslation(); const dispatch = useDispatch(); - useEffect(() => { - localStorage.setItem('relaySettings', JSON.stringify(relaySettings)); - }, [relaySettings]); - const handleLogout = useHandleLogout(); - const fetchSettings = useCallback(async () => { - try { - const token = readToken(); // Read JWT from localStorage - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetch(`${config.baseURL}/relay-settings`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, // Add JWT to Authorization header - }, - }); - - if (!response.ok) { - if (response.status === 401) { - handleLogout(); // Logout on invalid token - throw new Error('Authentication failed. Please log in again.'); + useEffect(() => { + const fetchData = async () => { + setIsLoading(true); + try { + const token = readToken(); + if (!token) { + throw new Error('No authentication token found'); } - throw new Error(`Network response was not ok (status: ${response.status})`); - } - const data = await response.json(); - - const storedAppBuckets = JSON.parse(localStorage.getItem('appBuckets') || '[]'); - const storedDynamicKinds = JSON.parse(localStorage.getItem('dynamicKinds') || '[]'); - - const newAppBuckets = data.relay_settings.dynamicAppBuckets?.filter((bucket: string) => !storedAppBuckets.includes(bucket)) || []; - const newDynamicKinds = data.relay_settings.dynamicKinds?.filter((kind: string) => !storedDynamicKinds.includes(kind)) || []; - - if (newAppBuckets.length > 0) { - localStorage.setItem('appBuckets', JSON.stringify([...storedAppBuckets, ...newAppBuckets])); - } - if (newDynamicKinds.length > 0) { - localStorage.setItem('dynamicKinds', JSON.stringify([...storedDynamicKinds, ...newDynamicKinds])); - } - - setRelaySettings({ - ...data.relay_settings, - protocol: Array.isArray(data.relay_settings.protocol) ? data.relay_settings.protocol : [data.relay_settings.protocol], - chunked: Array.isArray(data.relay_settings.chunked) ? data.relay_settings.chunked : [data.relay_settings.chunked], - }); - - } catch (error) { - console.error('Error fetching settings:', error); - message.error(error instanceof Error ? error.message : 'An error occurred'); - } - }, []); - - const updateSettings = useCallback((category: keyof RelaySettings, value: string | string[] | boolean | number) => { - setRelaySettings((prevSettings) => ({ - ...prevSettings, - [category]: value, - })); - }, []); - - const saveSettings = useCallback(async () => { - try { - const token = readToken(); - if (!token) { - throw new Error('No authentication token found'); - } - - const response = await fetch(`${config.baseURL}/relay-settings`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, // Add JWT to Authorization header - }, - body: JSON.stringify({ relay_settings: relaySettings }), - }); - - if (!response.ok) { - if (response.status === 401) { - handleLogout(); // Logout on invalid token - throw new Error('Authentication failed. Please log in again.'); + const response = await fetch(`${config.baseURL}/api/relaycount`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + }); + + if (!response.ok) { + if (response.status === 401) { + handleLogout(); + throw new Error('Authentication failed. You have been logged out.'); + } + throw new Error(`Network response was not ok (status: ${response.status})`); } - throw new Error(`Network response was not ok (status: ${response.status})`); + + const data = await response.json(); + + // Process the data into chartDataItems using translated names + const newChartData: ChartDataItem[] = [ + { value: data.kinds, name: t('categories.kinds') }, + { value: data.photos, name: t('categories.photos') }, + { value: data.videos, name: t('categories.videos') }, + { value: data.audio, name: t('categories.audio') }, + { value: data.misc, name: t('categories.misc') }, + ]; + + setChartData(newChartData); + } catch (error) { + console.error('Error:', error); + message.error(error instanceof Error ? error.message : 'An error occurred'); + setChartData(null); + } finally { + setIsLoading(false); } + }; - localStorage.setItem('settingsCache', JSON.stringify(relaySettings)); - message.success('Settings saved successfully!'); - } catch (error) { - console.error('Error saving settings:', error); - message.error(error instanceof Error ? error.message : 'An error occurred'); - } - }, [relaySettings]); + fetchData(); + }, [t, dispatch]); - return { relaySettings, fetchSettings, updateSettings, saveSettings }; + return { chartData, isLoading }; }; -export default useRelaySettings; - +export default useChartData; // import { useState, useEffect } from 'react'; @@ -198,7 +113,7 @@ export default useRelaySettings; // throw new Error('No authentication token found'); // } // console.log('Sending request to server...'); -// const response = await fetch(`${config.baseURL}/relay-count`, { +// const response = await fetch(`${config.baseURL}/api/relaycount`, { // method: 'GET', // headers: { // 'Content-Type': 'application/json', diff --git a/src/hooks/useKindData.ts b/src/hooks/useKindData.ts index 8de37b2..78d516e 100644 --- a/src/hooks/useKindData.ts +++ b/src/hooks/useKindData.ts @@ -4,7 +4,7 @@ import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist import { useDispatch } from 'react-redux'; import { message } from 'antd'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { useHandleLogout } from './authUtils'; interface KindData { kindNumber: number; diff --git a/src/hooks/useKindTrendData.ts b/src/hooks/useKindTrendData.ts index 380f1f8..81ae5a4 100644 --- a/src/hooks/useKindTrendData.ts +++ b/src/hooks/useKindTrendData.ts @@ -3,7 +3,7 @@ import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; // Assuming these services exist import { useDispatch } from 'react-redux'; import { message } from 'antd'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { useHandleLogout } from './authUtils'; interface MonthlyKindData { month: string; diff --git a/src/hooks/useLineChartData.ts b/src/hooks/useLineChartData.ts index 65e9071..b5cec5b 100644 --- a/src/hooks/useLineChartData.ts +++ b/src/hooks/useLineChartData.ts @@ -3,7 +3,7 @@ import config from '@app/config/config'; import { readToken } from '@app/services/localStorage.service'; // Assuming you have these services for token management import { useDispatch } from 'react-redux'; import { message } from 'antd'; -import { useHandleLogout } from '@app/utils/authUtils'; +import { useHandleLogout } from './authUtils'; interface TimeSeriesData { month: string; @@ -29,13 +29,12 @@ const useLineChartData = () => { throw new Error('No authentication token found'); } - const response = await fetch(`${config.baseURL}/timeseries`, { - method: 'POST', + const response = await fetch(`${config.baseURL}/api/timeseries`, { + method: 'GET', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, // Attach the JWT token to the request }, - body: JSON.stringify({}), // If needed, include a payload here }); if (!response.ok) { @@ -85,7 +84,7 @@ export default useLineChartData; // const fetchData = async () => { // setIsLoading(true); // try { -// const response = await fetch(`${config.baseURL}/timeseries`, { +// const response = await fetch(`${config.baseURL}/api/timeseries`, { // method: 'POST', // headers: { // 'Content-Type': 'application/json', diff --git a/src/hooks/usePendingTransactions.ts b/src/hooks/usePendingTransactions.ts index 429f116..22a6184 100644 --- a/src/hooks/usePendingTransactions.ts +++ b/src/hooks/usePendingTransactions.ts @@ -21,7 +21,7 @@ const usePendingTransactions = () => { // Fetch the JWT token using readToken() const pendingToken = readToken(); try { - const response = await fetch(`${config.baseURL}/pending-transactions`, { + const response = await fetch(`${config.baseURL}/api/pending-transactions`, { method: 'GET', headers: { 'Content-Type': 'application/json', diff --git a/src/hooks/useRelaySettings.ts b/src/hooks/useRelaySettings.ts index 560246a..ab478de 100644 --- a/src/hooks/useRelaySettings.ts +++ b/src/hooks/useRelaySettings.ts @@ -59,7 +59,7 @@ const useRelaySettings = () => { const fetchSettings = useCallback(async () => { try { - const response = await fetch(`${config.baseURL}/relay-settings`, { + const response = await fetch(`${config.baseURL}/api/relay-settings`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -113,7 +113,7 @@ const useRelaySettings = () => { const saveSettings = useCallback(async () => { try { - const response = await fetch(`${config.baseURL}/relay-settings`, { + const response = await fetch(`${config.baseURL}/api/relay-settings`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -134,108 +134,3 @@ const useRelaySettings = () => { }; export default useRelaySettings; - -// import { useState, useEffect, useCallback } from 'react'; -// import config from '@app/config/config'; - -// interface RelaySettings { -// mode: string; -// protocol: string[]; -// chunked: string[]; -// chunksize: string; -// kinds: string[]; -// dynamicKinds: string[]; -// photos: string[]; -// videos: string[]; -// gitNestr: string[]; -// isKindsActive: boolean; -// isPhotosActive: boolean; -// isVideosActive: boolean; -// isGitNestrActive: boolean; -// } - -// const getInitialSettings = (): RelaySettings => { -// const savedSettings = localStorage.getItem('relaySettings'); -// return savedSettings -// ? JSON.parse(savedSettings) -// : { -// mode: 'smart', -// protocol: ['WebSocket'], -// chunked: ['unchunked'], -// chunksize: '2', -// dynamicKinds: [], -// kinds: [], -// photos: [], -// videos: [], -// gitNestr: [], -// isKindsActive: true, -// isPhotosActive: true, -// isVideosActive: true, -// isGitNestrActive: true, -// }; -// }; - -// const useRelaySettings = () => { -// const [relaySettings, setRelaySettings] = useState(getInitialSettings()); - -// useEffect(() => { -// localStorage.setItem('relaySettings', JSON.stringify(relaySettings)); -// }, [relaySettings]); - -// const fetchSettings = useCallback(async () => { -// try { -// const response = await fetch(`${config.baseURL}/relay-settings`, { -// method: 'GET', -// headers: { -// 'Content-Type': 'application/json', -// }, -// }); -// if (!response.ok) { -// throw new Error(`Network response was not ok (status: ${response.status})`); -// } -// const data = await response.json(); -// console.log('Fetched settings:', data.relay_settings); -// setRelaySettings({ -// ...data.relay_settings, -// protocol: Array.isArray(data.relay_settings.protocol) -// ? data.relay_settings.protocol -// : [data.relay_settings.protocol], -// chunked: Array.isArray(data.relay_settings.chunked) -// ? data.relay_settings.chunked -// : [data.relay_settings.chunked], -// }); -// localStorage.setItem('relaySettings', JSON.stringify(data.relay_settings)); -// } catch (error) { -// console.error('Error fetching settings:', error); -// } -// }, []); - -// const updateSettings = useCallback((category: keyof RelaySettings, value: string | string[] | boolean) => { -// setRelaySettings((prevSettings) => ({ -// ...prevSettings, -// [category]: value, -// })); -// }, []); - -// const saveSettings = useCallback(async () => { -// try { -// const response = await fetch(`${config.baseURL}/relay-settings`, { -// method: 'POST', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify({ relay_settings: relaySettings }), -// }); -// if (!response.ok) { -// throw new Error(`Network response was not ok (status: ${response.status})`); -// } -// console.log('Settings saved successfully!'); -// } catch (error) { -// console.error('Error saving settings:', error); -// } -// }, [relaySettings]); - -// return { relaySettings, fetchSettings, updateSettings, saveSettings }; -// }; - -// export default useRelaySettings; From 938db2e04a0c2803385afb2ecb89b7f3149ea916 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 11 Sep 2024 13:23:18 +0200 Subject: [PATCH 32/62] Handling null on kind trend data hook. --- .../tables/editableTable/EditableTable.tsx | 44 +++++++++++-------- src/hooks/useKindTrendData.ts | 9 +++- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/components/tables/editableTable/EditableTable.tsx b/src/components/tables/editableTable/EditableTable.tsx index 3e7424b..cbadbbc 100644 --- a/src/components/tables/editableTable/EditableTable.tsx +++ b/src/components/tables/editableTable/EditableTable.tsx @@ -44,7 +44,8 @@ const EditableTable: React.FC = () => { const [sortField, setSortField] = useState('totalSize'); const { isMobile, isDesktop, isTablet } = useResponsive(); - const { trendData, isLoading: isTrendLoading } = useKindTrendData(currentKindNumber || 0); + const { trendData, isLoading: isTrendLoading } = useKindTrendData(currentKindNumber); + useEffect(() => { if (initialKindData && initialKindData.length > 0) { @@ -66,10 +67,14 @@ const EditableTable: React.FC = () => { }, [initialKindData, sortOrder, sortField]); const showModal = (kindNumber: number) => { + if (kindNumber === null || kindNumber === undefined) { + return; // Don't open the modal if the kindNumber is not valid + } setCurrentKindNumber(kindNumber); setIsModalVisible(true); }; + const handleCancel = () => { setIsModalVisible(false); setCurrentKindNumber(null); @@ -228,26 +233,26 @@ const EditableTable: React.FC = () => { return (
- {isLoading ? : - sortedData.length > 0 ? ( - - ) : ( -
{'No Data Available'}
- ) - } + {isLoading ? : + sortedData.length > 0 ? ( + + ) : ( +
{'No Data Available'}
+ ) + }
{
{t('common.noTrendDataAvailable')}
)}
+
); }; diff --git a/src/hooks/useKindTrendData.ts b/src/hooks/useKindTrendData.ts index 81ae5a4..9e535fb 100644 --- a/src/hooks/useKindTrendData.ts +++ b/src/hooks/useKindTrendData.ts @@ -10,7 +10,7 @@ interface MonthlyKindData { totalSize: number; } -const useKindTrendData = (kindNumber: number) => { +const useKindTrendData = (kindNumber: number | null) => { const [trendData, setTrendData] = useState([]); const [isLoading, setIsLoading] = useState(true); const dispatch = useDispatch(); @@ -18,6 +18,11 @@ const useKindTrendData = (kindNumber: number) => { const handleLogout = useHandleLogout(); useEffect(() => { + if (kindNumber === null) { + // Don't fetch data if kindNumber is null + return; + } + const fetchTrendData = async () => { setIsLoading(true); try { @@ -43,7 +48,6 @@ const useKindTrendData = (kindNumber: number) => { } const data = await response.json(); - console.log('Trend data response:', data); setTrendData(data); } catch (error) { console.error('Error fetching kind trend data:', error); @@ -62,6 +66,7 @@ const useKindTrendData = (kindNumber: number) => { export default useKindTrendData; + // import { useState, useEffect } from 'react'; // import config from '@app/config/config'; From f029722e5e267c043011db6562efea245591081e Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 11 Sep 2024 13:45:59 +0200 Subject: [PATCH 33/62] Demo mode is going to be problematic so I have switched it to off. --- .env.development | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.development b/.env.development index a9ee309..5e5aec8 100644 --- a/.env.development +++ b/.env.development @@ -1,7 +1,7 @@ REACT_APP_BASE_URL=http://localhost:9002 # REACT_APP_BASE_URL=https://11561ba2d27f.ngrok.app REACT_APP_ASSETS_BUCKET=http://localhost -REACT_APP_DEMO_MODE=true +REACT_APP_DEMO_MODE=false # more info https://create-react-app.dev/docs/advanced-configuration ESLINT_NO_DEV_ERRORS=true From fc6a7b3ee2e5eabad233256274cfa9214f3bfca5 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 11 Sep 2024 22:24:59 +0200 Subject: [PATCH 34/62] Adding Auth hook --- src/hooks/authUtils.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/hooks/authUtils.ts diff --git a/src/hooks/authUtils.ts b/src/hooks/authUtils.ts new file mode 100644 index 0000000..ac67cbd --- /dev/null +++ b/src/hooks/authUtils.ts @@ -0,0 +1,21 @@ +import { useAppDispatch } from '@app/hooks/reduxHooks'; // Make sure your hook path is correct +import { doLogout } from '@app/store/slices/authSlice'; // Adjust the path as necessary +import { message } from 'antd'; + +export const useHandleLogout = () => { + const dispatch = useAppDispatch(); + + const handleLogout = () => { + dispatch(doLogout()) + .unwrap() + .then(() => { + message.info('You have been logged out. Please login again.'); + }) + .catch((error) => { + console.error('Logout error:', error); + message.error('An error occurred during logout.'); + }); + }; + + return handleLogout; +}; \ No newline at end of file From 106c2c28c2e162de36229d7e44f8d4d392cf9cda Mon Sep 17 00:00:00 2001 From: Maphikza Date: Thu, 12 Sep 2024 11:40:24 +0200 Subject: [PATCH 35/62] Adding token to settings hook --- src/hooks/useRelaySettings.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/hooks/useRelaySettings.ts b/src/hooks/useRelaySettings.ts index ab478de..fe93227 100644 --- a/src/hooks/useRelaySettings.ts +++ b/src/hooks/useRelaySettings.ts @@ -1,5 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import config from '@app/config/config'; +import { readToken } from '@app/services/localStorage.service'; +import { useHandleLogout } from './authUtils'; interface RelaySettings { mode: string; @@ -57,14 +59,23 @@ const useRelaySettings = () => { localStorage.setItem('relaySettings', JSON.stringify(relaySettings)); }, [relaySettings]); + const handleLogout = useHandleLogout(); + + const token = readToken(); + const fetchSettings = useCallback(async () => { try { const response = await fetch(`${config.baseURL}/api/relay-settings`, { method: 'GET', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, }, }); + if (response.status === 401 ) { + console.error('Unauthorized: Invalid or expired token'); + handleLogout(); + } if (!response.ok) { throw new Error(`Network response was not ok (status: ${response.status})`); } @@ -117,6 +128,7 @@ const useRelaySettings = () => { method: 'POST', headers: { 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ relay_settings: relaySettings }), }); From f7da6910435c1d8d3114b50bafe10013f090cd92 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Thu, 12 Sep 2024 21:48:32 +0200 Subject: [PATCH 36/62] Adding reroute to login to doLogout --- .env.development | 2 +- src/store/slices/authSlice.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.env.development b/.env.development index 5e5aec8..4bf857c 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,4 @@ -REACT_APP_BASE_URL=http://localhost:9002 +REACT_APP_BASE_URL=https://c85d415bbf8fcf3f3b68fb396039cc3e.serveo.net # REACT_APP_BASE_URL=https://11561ba2d27f.ngrok.app REACT_APP_ASSETS_BUCKET=http://localhost REACT_APP_DEMO_MODE=false diff --git a/src/store/slices/authSlice.ts b/src/store/slices/authSlice.ts index 9e3ce66..dd08176 100644 --- a/src/store/slices/authSlice.ts +++ b/src/store/slices/authSlice.ts @@ -116,6 +116,9 @@ export const doLogout = createAsyncThunk('auth/doLogout', async (_, { dispatch } } catch (error) { console.error('Logout error:', error); message.error('An error occurred during logout'); + } finally { + // Ensure user is redirected to login page after logout attempt + window.location.href = '/login'; // Redirect to the login page } }); From b73d1771a536466d58dab375fd84da48a261e880 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Fri, 13 Sep 2024 21:46:21 +0200 Subject: [PATCH 37/62] Updating transaction display and transactions chart. --- .env.development | 2 +- .../activityStory/ActivityStory.tsx | 11 +- .../ActivityStoryItem/ActivityStoryItem.tsx | 142 ++++++++++++++++-- 3 files changed, 141 insertions(+), 14 deletions(-) diff --git a/.env.development b/.env.development index 4bf857c..5e5aec8 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,4 @@ -REACT_APP_BASE_URL=https://c85d415bbf8fcf3f3b68fb396039cc3e.serveo.net +REACT_APP_BASE_URL=http://localhost:9002 # REACT_APP_BASE_URL=https://11561ba2d27f.ngrok.app REACT_APP_ASSETS_BUCKET=http://localhost REACT_APP_DEMO_MODE=false diff --git a/src/components/nft-dashboard/activityStory/ActivityStory.tsx b/src/components/nft-dashboard/activityStory/ActivityStory.tsx index 7a2574f..a227dcd 100644 --- a/src/components/nft-dashboard/activityStory/ActivityStory.tsx +++ b/src/components/nft-dashboard/activityStory/ActivityStory.tsx @@ -91,12 +91,16 @@ export const ActivityStory: React.FC = () => { }; const prepareChartData = () => { const sortedStory = [...story].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); - const labels = sortedStory.map((item) => new Date(item.date).toLocaleDateString()); - const amounts = sortedStory.map((item) => { + + // Filter out negative values and their corresponding labels + const positiveStory = sortedStory.filter((item) => parseFloat(item.value) > 0); + + const labels = positiveStory.map((item) => new Date(item.date).toLocaleDateString()); + const amounts = positiveStory.map((item) => { const amount = parseFloat(item.value); return isNaN(amount) ? 0 : amount; }); - + return { labels, datasets: [ @@ -121,6 +125,7 @@ export const ActivityStory: React.FC = () => { ], }; }; + const chartOptions: ChartOptions<'line'> = { responsive: true, diff --git a/src/components/nft-dashboard/activityStory/ActivityStoryItem/ActivityStoryItem.tsx b/src/components/nft-dashboard/activityStory/ActivityStoryItem/ActivityStoryItem.tsx index 2c09cd1..3c54460 100644 --- a/src/components/nft-dashboard/activityStory/ActivityStoryItem/ActivityStoryItem.tsx +++ b/src/components/nft-dashboard/activityStory/ActivityStoryItem/ActivityStoryItem.tsx @@ -1,12 +1,14 @@ -import React, {useEffect, useState} from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { WalletTransaction } from '@app/api/activity.api'; import { Dates } from '@app/constants/Dates'; -import { formatNumberWithCommas, getCurrencyPrice } from '@app/utils/utils'; +import { getCurrencyPrice } from '@app/utils/utils'; import { CurrencyTypeEnum } from '@app/interfaces/interfaces'; import * as S from './ActivityStoryItem.styles'; import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; +import { useBitcoinRates } from '@app/hooks/useBitcoinRates' + function makeHexId(length: number): string { const characters = 'abcdef0123456789'; let result = ''; @@ -16,10 +18,17 @@ function makeHexId(length: number): string { return result; } -export const ActivityStoryItem: React.FC = ({ witness_tx_id, date, output, value }) => { +export const ActivityStoryItem: React.FC = ({ + witness_tx_id, + date, + output, + value, +}) => { const { t } = useTranslation(); const [transactionId, setTransactionId] = useState(null); + const { rates, isLoading, error } = useBitcoinRates(); + // Effect to initialize the transaction ID when the component mounts useEffect(() => { if (!witness_tx_id) { @@ -27,28 +36,73 @@ export const ActivityStoryItem: React.FC = ({ witness_tx_id, } }, [witness_tx_id]); - const numericValue = parseFloat(value); + // Parse 'value' as BTC amount + const btcAmount = parseFloat(value); + const [usdValue, setUsdValue] = useState(null); + + useEffect(() => { + if (!isLoading && rates.length > 0 && btcAmount) { + // Convert the transaction date to a timestamp + const transactionDate = new Date(date).getTime(); + // Find the rate closest to the transaction date + let closestRate = rates[0]; + let minDiff = Math.abs(rates[0].date - transactionDate); + + for (let i = 1; i < rates.length; i++) { + const diff = Math.abs(rates[i].date - transactionDate); + if (diff < minDiff) { + minDiff = diff; + closestRate = rates[i]; + } + } + + const rate = closestRate.usd_value; + + // Compute the USD value + const usdAmount = btcAmount * rate; + setUsdValue(usdAmount); + } + }, [isLoading, rates, btcAmount, date]); + + // Handle potential errors and loading states + if (error) { + return
Error loading exchange rates: {error}
; + } + + if (isLoading || usdValue === null) { + return
Loading...
; + } return ( - {t('Witness Transaction ID')}: - {witness_tx_id ? witness_tx_id : transactionId} + {'Transaction ID'}: + + {witness_tx_id ? witness_tx_id : transactionId} + - {t('Output')}: + {'Address'}: {output} - {t('Date')}: + {'Date'}: {Dates.getDate(date).format('L LTS')} - {t('Value')}: - {getCurrencyPrice(formatNumberWithCommas(numericValue), CurrencyTypeEnum.USD)} + {'Value'}: + + {getCurrencyPrice( + usdValue.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }), + CurrencyTypeEnum.USD + )} + @@ -56,3 +110,71 @@ export const ActivityStoryItem: React.FC = ({ witness_tx_id, }; export default ActivityStoryItem; + + + + + +// import React, {useEffect, useState} from 'react'; +// import { useTranslation } from 'react-i18next'; +// import { WalletTransaction } from '@app/api/activity.api'; +// import { Dates } from '@app/constants/Dates'; +// import { formatNumberWithCommas, getCurrencyPrice } from '@app/utils/utils'; +// import { CurrencyTypeEnum } from '@app/interfaces/interfaces'; +// import * as S from './ActivityStoryItem.styles'; +// import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; +// import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; +// import { useBitcoinRates } from '@app/hooks/useBitcoinRates' + +// function makeHexId(length: number): string { +// const characters = 'abcdef0123456789'; +// let result = ''; +// for (let i = 0; i < length; i++) { +// result += characters.charAt(Math.floor(Math.random() * characters.length)); +// } +// return result; +// } + +// // Use the useBitcoinRates hook +// const { rates, isLoading, error } = useBitcoinRates(); + +// export const ActivityStoryItem: React.FC = ({ witness_tx_id, date, output, value }) => { +// const { t } = useTranslation(); +// const [transactionId, setTransactionId] = useState(null); + +// // Effect to initialize the transaction ID when the component mounts +// useEffect(() => { +// if (!witness_tx_id) { +// setTransactionId(makeHexId(64)); +// } +// }, [witness_tx_id]); + +// const numericValue = parseFloat(value); + + + +// return ( +// +// +// +// {t('Witness Transaction ID')}: +// {witness_tx_id ? witness_tx_id : transactionId} +// +// +// {t('Output')}: +// {output} +// +// +// {t('Date')}: +// {Dates.getDate(date).format('L LTS')} +// +// +// {t('Value')}: +// {getCurrencyPrice(formatNumberWithCommas(numericValue), CurrencyTypeEnum.USD)} +// +// +// +// ); +// }; + +// export default ActivityStoryItem; From 0a96662b198ede9688001bf7480468293f56cd7b Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Sun, 15 Sep 2024 15:03:40 -0700 Subject: [PATCH 38/62] added copy transaction button --- .../Modal/UnconfirmedTxModal.styles.ts | 2 +- .../UnconfirmedTransaction.styles.ts | 18 +++++++--- .../UnconfirmedTransaction.tsx | 34 ++++++++++++------- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts index 2974f45..b03908f 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts @@ -3,7 +3,7 @@ import { BaseModal } from '@app/components/common/BaseModal/BaseModal'; export const Modal = styled(BaseModal)` width: fit-content !important; - min-width: 50vw; + min-width: 75vw; .ant-modal{ width: } diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts index d377a03..5da3916 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts @@ -1,4 +1,4 @@ -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; export const TransactionWrapper = styled.div` width: 100%; @@ -6,19 +6,23 @@ export const TransactionWrapper = styled.div` display: flex; flex-direction: row; justify-content: space-between; + flex-wrap: wrap; gap: 1rem; `; -export const IDWrapper = styled.div` +export const IDWrapper = styled.div<{ $isMobile: boolean }>` display: flex; - width: 40%; flex-direction: column; gap: 0.3rem; + ${({ $isMobile }) => + $isMobile && + css` + width: 100%; + `} `; export const DataWrapper = styled.div` display: flex; - width: 22%; flex-direction: column; gap: 0.3rem; `; @@ -26,7 +30,7 @@ export const DataWrapper = styled.div` export const Value = styled.span` font-size: 1rem; color: var(--text-main-color); - font-weight: semibold; + font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -35,3 +39,7 @@ export const Label = styled.span` font-size: 0.8rem; color: var(--text-nft-light-color); `; + +export const CopyWrapper = styled.span` + padding-left: 1rem; +`; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx index be966c8..d66a544 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx @@ -1,9 +1,11 @@ -import React from 'react'; -import * as S from './UnconfirmedTransaction.styles' +import React, { useState } from 'react'; +import * as S from './UnconfirmedTransaction.styles'; import { BaseCard } from '@app/components/common/BaseCard/BaseCard'; import { useResponsive } from '@app/hooks/useResponsive'; import { truncateString } from '@app/utils/utils'; - +import CopyToClipboard from 'react-copy-to-clipboard'; +import { Button } from 'antd'; +import {CopyOutlined } from '@ant-design/icons'; interface UnconfirmedTransactionProps { tx_id: string; date_created: string; @@ -12,25 +14,31 @@ interface UnconfirmedTransactionProps { } const UnconfirmedTransaction: React.FC = ({ tx_id, date_created, amount, feeAmount }) => { - - const {isTablet} = useResponsive(); + const { isTablet } = useResponsive(); + const onCopy = () => { + //display Copied to clipboard + } return ( - - - {!isTablet ? truncateString(tx_id, 10) : truncateString(tx_id, 30)} + + + {!isTablet ? truncateString(tx_id, 20) : truncateString(tx_id, 35)} + + +
- {t('common.chunkedSetting')}{' '} - handleChunkedChange(checkedValues as string[])} - style={{ - display: 'grid', - gridTemplateColumns: '10rem auto', // Adjust the width as needed - }} - /> + {t('File Storage')}{' '} + handleSwitchChange('isFileStorageActive', e.target.checked)} + style={{ fontSize: '.85rem' }} + > + Enable/Disable +
- {settings.chunked.includes('unchunked') && ( - <> -
- Max Unchunked File Size: - -
-
- handleMaxFileSizeChange(Number(e.target.value))} - style={{ width: 100 }} - /> - -
- - )} - {settings.chunked.includes('chunked') && ( - <> -
- Max Chunked File Size: - -
-
- handleMaxFileSizeChange(Number(e.target.value))} - style={{ width: 100 }} - /> - -
- - )} @@ -949,83 +865,14 @@ const RelaySettingsPage: React.FC = () => {
- {t('common.chunkedSetting')} - handleChunkedChange(checkedValues as string[])} - style={{ - display: 'grid', - gridTemplateColumns: '9rem auto', - paddingRight: '0', - justifyContent: 'start', - fontSize: 'small', - }} - /> - {settings.chunked.includes('unchunked') && ( - <> -
- Max Unchunked File Size: - -
-
- handleMaxFileSizeChange(Number(e.target.value))} - style={{ width: 100 }} - /> - -
- - )} - {settings.chunked.includes('chunked') && ( - <> -
- Max Chunked File Size: - -
-
- handleMaxFileSizeChange(Number(e.target.value))} - style={{ width: 100 }} - /> - -
- - )} + {t('File Storage')} + handleSwitchChange('isFileStorageActive', e.target.checked)} + style={{ fontSize: '.8rem' }} + > + Enable/Disable +
From cf154d419e0d69953a2769fc1f01e9548a887598 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Tue, 15 Oct 2024 12:34:19 +0200 Subject: [PATCH 59/62] Updating tiered fees to dynamically adjust recommended fee rates for replacement transactions so that we do not present fee rates that are lower than the initial fee rate. --- .../Balance/components/SendForm/SendForm.tsx | 12 +- .../components/TieredFees/TieredFees.tsx | 232 ++++++------------ .../ReplaceTransaction/ReplaceTransaction.tsx | 16 +- 3 files changed, 94 insertions(+), 166 deletions(-) diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 6daaa43..7d68437 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -53,7 +53,7 @@ const SendForm: React.FC = ({ onSend }) => { // Debounced effect to calculate transaction size when the amount changes, with JWT useEffect(() => { - + const debounceTimeout = setTimeout(() => { const fetchTransactionSize = async () => { if (isValidAddress(formData.address) && isDetailsOpen) { @@ -185,7 +185,7 @@ const SendForm: React.FC = ({ onSend }) => { await login(); // Perform login if not authenticated } - + // Step 2: Initiate the new transaction with the JWT token const response = await fetch('http://localhost:9003/transaction', { method: 'POST', @@ -301,7 +301,13 @@ const SendForm: React.FC = ({ onSend }) => { /> RBF Opt In - + + void; - inValidAmount: boolean; - txSize: number | null; // Transaction size passed down from SendForm + invalidAmount: boolean; + transactionSize: number | null; // Transaction size passed down from parent + originalFeeRate?: number; // Optional original fee rate (used for replacements) } -const TieredFees: React.FC = ({ inValidAmount, handleFeeChange, txSize }) => { +const DEFAULT_FEES: Fees = { low: 3, med: 4, high: 5 }; + +const TieredFees: React.FC = ({ + handleFeeChange, + invalidAmount, + transactionSize, + originalFeeRate = 0, +}) => { const { isDesktop, isTablet } = useResponsive(); + const [selectedTier, setSelectedTier] = useState("low"); + const [fees, setFees] = useState(DEFAULT_FEES); + const [estimatedFee, setEstimatedFee] = useState({ low: 0, med: 0, high: 0 }); const [loadingRecommendation, setLoadingRecommendation] = useState(false); - const [fetchedrecommendation, setFetchedRecommendation] = useState(false); - const [fees, setFees] = useState({ low: 0, med: 0, high: 0 }); - const [selectedTier, setSelectedTier] = useState('low'); - const [estimatedFee, setEstimatedFee] = useState({ low: 0, med: 0, high: 0 }); + const [fetchedRecommendation, setFetchedRecommendation] = useState(false); + + // Adjust fees for replacement transactions if originalFeeRate > 0 + const adjustFees = (fetchedFees: Fees) => { + if (originalFeeRate > 0) { + const adjustedFees = { ...fetchedFees }; + adjustedFees.low = Math.max(originalFeeRate + 1, fetchedFees.low); + adjustedFees.med = Math.max(adjustedFees.low + 1, fetchedFees.med); + adjustedFees.high = Math.max(adjustedFees.med + 1, fetchedFees.high); + return adjustedFees; + } + return fetchedFees; + }; useEffect(() => { - if (loadingRecommendation || fetchedrecommendation) return; + if (loadingRecommendation || fetchedRecommendation) return; const fetchFees = async () => { setLoadingRecommendation(true); try { - const response = await fetch('https://mempool.space/api/v1/fees/recommended'); + const response = await fetch("https://mempool.space/api/v1/fees/recommended"); const data: FeeRecommendation = await response.json(); - setFees({ + const fetchedFees: Fees = { low: data.economyFee, med: data.halfHourFee, high: data.fastestFee, - }); + }; + const adjustedFees = adjustFees(fetchedFees); + setFees(adjustedFees); setFetchedRecommendation(true); } catch (error) { - console.error('Failed to fetch fees:', error); + console.error("Failed to fetch fees:", error); + setFees(adjustFees(DEFAULT_FEES)); } setLoadingRecommendation(false); }; - fetchFees(); - }, []); + }, [originalFeeRate]); // Update estimated fees whenever the fees or transaction size change useEffect(() => { - if (txSize) { + if (transactionSize) { setEstimatedFee({ - low: txSize * fees.low, - med: txSize * fees.med, - high: txSize * fees.high, + low: Math.ceil(transactionSize * fees.low), + med: Math.ceil(transactionSize * fees.med), + high: Math.ceil(transactionSize * fees.high), }); } - }, [fees, txSize]); + }, [fees, transactionSize]); - const handleTierChange = (tier: any) => { - setSelectedTier(tier.id); + const handleTierChange = (tier: Tier) => { + setSelectedTier(tier); }; useEffect(() => { - handleFeeChange(fees[selectedTier as tiers]); - }, [selectedTier, fees]); + handleFeeChange(fees[selectedTier]); + }, [selectedTier, fees, handleFeeChange]); return ( handleTierChange({ id: 'low' })} - className={`tier-hover ${selectedTier === 'low' ? 'selected' : ''} ${ - selectedTier === 'low' && inValidAmount ? 'invalidAmount' : '' + onClick={() => handleTierChange("low")} + className={`tier-hover ${selectedTier === "low" ? "selected" : ""} ${ + selectedTier === "low" && invalidAmount ? "invalidAmount" : "" } `} > @@ -83,7 +114,7 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange,
{`Priority`} - {`${fees.low}`}
{`sat/vB`} + {`${fees.low}`}
{`sat/vB`} {`${estimatedFee.low} Sats`} {/* Show estimated fee */}
@@ -91,9 +122,9 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange, handleTierChange({ id: 'med' })} - className={`tier-hover ${selectedTier === 'med' ? 'selected' : ''} ${ - selectedTier === 'med' && inValidAmount ? 'invalidAmount' : '' + onClick={() => handleTierChange("med")} + className={`tier-hover ${selectedTier === "med" ? "selected" : ""} ${ + selectedTier === "med" && invalidAmount ? "invalidAmount" : "" } `} > @@ -101,7 +132,7 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange,
{`Priority`} - {`${fees.med}`}
{`sat/vB`} + {`${fees.med}`}
{`sat/vB`} {`${estimatedFee.med} Sats`} {/* Show estimated fee */}
@@ -109,9 +140,9 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange, handleTierChange({ id: 'high' })} - className={`tier-hover ${selectedTier === 'high' ? 'selected' : ''} ${ - selectedTier === 'high' && inValidAmount ? 'invalidAmount' : '' + onClick={() => handleTierChange("high")} + className={`tier-hover ${selectedTier === "high" ? "selected" : ""} ${ + selectedTier === "high" && invalidAmount ? "invalidAmount" : "" } `} > @@ -130,119 +161,4 @@ const TieredFees: React.FC = ({ inValidAmount, handleFeeChange, ); }; -export default TieredFees; - -// import React, { useEffect, useState } from 'react'; -// import * as S from './TieredFees.styles'; -// import { useResponsive } from '@app/hooks/useResponsive'; -// import { tiers } from '../../SendForm'; - -// interface FeeRecommendation { -// fastestFee: number; -// halfHourFee: number; -// hourFee: number; -// economyFee: number; -// minimumFee: number; -// } -// type Fees = { -// [key in tiers]: number; -// }; -// interface TieredFeesProps { -// // Define the props for your component here -// handleFeeChange: (fee: number) => void; -// inValidAmount: boolean; -// } - -// const TieredFees: React.FC = ({ inValidAmount, handleFeeChange }) => { -// const { isDesktop, isTablet } = useResponsive(); -// const [fees, setFees] = useState({ low: 0, med: 0, high: 0 }); -// const [selectedTier, setSelectedTier] = useState('low'); - -// useEffect(() => { -// const fetchFees = async () => { -// try { -// const response = await fetch('https://mempool.space/api/v1/fees/recommended'); -// const data: FeeRecommendation = await response.json(); -// setFees({ -// low: data.economyFee, -// med: data.halfHourFee, -// high: data.fastestFee, -// }); -// } catch (error) { -// console.error('Failed to fetch fees:', error); -// } -// }; - -// fetchFees(); -// }, []); -// const handleTierChange = (tier: any) => { -// console.log(tier); -// setSelectedTier(tier.id); -// }; - -// useEffect(() => { -// handleFeeChange(fees[selectedTier as tiers]); -// }, [selectedTier]); -// return ( -// -// handleTierChange({ id: 'low', rate: fees.med })} -// className={`tier-hover ${selectedTier === 'low' ? 'selected' : ''} ${ -// selectedTier === 'low' && inValidAmount ? 'invalidAmount' : '' -// } `} -// > -// -// {`Low`} -//
-// {`Priority`} -// -// {`${fees.low} sat/vB`} -// {`${fees.low} Sats`} -// -//
-//
- -// handleTierChange({ id: 'med', rate: fees.med })} -// className={`tier-hover ${selectedTier === 'med' ? 'selected' : ''} ${ -// selectedTier === 'med' && inValidAmount ? 'invalidAmount' : '' -// } `} -// > -// -// {`Medium`} -//
-// {`Priority`} -// -// {`${fees.med} sat/vB`} -// {`${fees.med} Sats`} -// -//
-//
- -// handleTierChange({ id: 'high', rate: fees.high })} -// className={`tier-hover ${selectedTier === 'high' ? 'selected' : ''} ${ -// selectedTier === 'high' && inValidAmount ? 'invalidAmount' : '' -// } `} -// > -// -// -// {' '} -// {`High`} -//
-// {`Priority`} -//
-// -// {`${fees.high} sat/vB`} -// {`${fees.high} Sats`} -// -//
-//
-//
-// ); -// }; - -// export default TieredFees; +export default TieredFees; \ No newline at end of file diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index b53de7d..6c8f2af 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -21,7 +21,7 @@ interface ReplaceTransactionProps { const ReplaceTransaction: React.FC = ({ onCancel, onReplace, transaction }) => { const { isDesktop, isTablet } = useResponsive(); const { balanceData, isLoading: isBalanceLoading } = useBalanceData(); // Fetch balance data using the hook - const { isAuthenticated, login, token} = useWalletAuth(); // Use wallet authentication + const { isAuthenticated, login, token } = useWalletAuth(); // Use wallet authentication const [isLoadingSize, setIsLoadingSize] = useState(false); //tx size fetching states @@ -223,8 +223,8 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep Transaction ID {transaction.txid} - {" "} - + {" "} + @@ -242,7 +242,13 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep /> RBF Opt In */} - + + New Fee Rate @@ -257,7 +263,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep } }} min={0} // Minimum value of 0 for the fee - step={0.25} // Optional: define the increment step for the fee input + step={1} // Optional: define the increment step for the fee input /> From b0103afc429ee023ce00732f36b8a6ef5fcae031 Mon Sep 17 00:00:00 2001 From: Maphikza Date: Tue, 15 Oct 2024 12:49:59 +0200 Subject: [PATCH 60/62] Updating configs so that we can use config to get wallet base url from the .env --- .env.development | 4 ++-- .../nft-dashboard/Balance/components/SendForm/SendForm.tsx | 4 ++-- .../components/ReplaceTransaction/ReplaceTransaction.tsx | 4 ++-- src/config/config.ts | 7 +++++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.env.development b/.env.development index 5e5aec8..608d56f 100644 --- a/.env.development +++ b/.env.development @@ -1,8 +1,8 @@ REACT_APP_BASE_URL=http://localhost:9002 -# REACT_APP_BASE_URL=https://11561ba2d27f.ngrok.app +REACT_APP_WALLET_BASE_URL=http://localhost:9003 # New wallet base URL REACT_APP_ASSETS_BUCKET=http://localhost REACT_APP_DEMO_MODE=false -# more info https://create-react-app.dev/docs/advanced-configuration +# More info https://create-react-app.dev/docs/advanced-configuration ESLINT_NO_DEV_ERRORS=true TSC_COMPILE_ON_ERROR=true diff --git a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx index 7d68437..1e1318f 100644 --- a/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx +++ b/src/components/nft-dashboard/Balance/components/SendForm/SendForm.tsx @@ -65,7 +65,7 @@ const SendForm: React.FC = ({ onSend }) => { await login(); // Perform login if not authenticated } - const response = await fetch('http://localhost:9003/calculate-tx-size', { + const response = await fetch(`${config.walletBaseURL}/calculate-tx-size`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -187,7 +187,7 @@ const SendForm: React.FC = ({ onSend }) => { // Step 2: Initiate the new transaction with the JWT token - const response = await fetch('http://localhost:9003/transaction', { + const response = await fetch(`${config.walletBaseURL}/transaction`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 6c8f2af..29c3dce 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -56,7 +56,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep return; } - const response = await fetch('http://localhost:9003/calculate-tx-size', { + const response = await fetch(`${config.walletBaseURL}/calculate-tx-size`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -131,7 +131,7 @@ const ReplaceTransaction: React.FC = ({ onCancel, onRep new_fee_rate: newFeeRate, // Send the updated fee rate }; - const response = await fetch('http://localhost:9003/transaction', { + const response = await fetch(`${config.walletBaseURL}/transaction`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/config/config.ts b/src/config/config.ts index 4be5a16..4e8eca2 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -5,9 +5,12 @@ const config = { // eslint-disable-next-line @typescript-eslint/ban-ts-comment /*@ts-ignore*/ baseURL: process.env.NODE_ENV === 'production' ? window.location.origin || 'http://localhost:9002' : process.env.REACT_APP_BASE_URL || 'http://localhost:9002', // Fallback in case the environment variable is not set - + // Add this line to enable demo mode isDemoMode: process.env.REACT_APP_DEMO_MODE === 'true', + + // Add this line for wallet base URL + walletBaseURL: process.env.REACT_APP_WALLET_BASE_URL || 'http://localhost:9003', // Fallback in case the environment variable is not set }; -export default config; \ No newline at end of file +export default config; From bf7e8d89eee34726c8589454a5fb183ceb67d33b Mon Sep 17 00:00:00 2001 From: Maphikza Date: Wed, 16 Oct 2024 13:14:30 +0200 Subject: [PATCH 61/62] Enabling App buckets as the infrastructure is already set. --- src/hooks/useRelaySettings.ts | 69 +++++++------ src/pages/RelaySettingsPage.tsx | 167 ++++++++++++++++---------------- 2 files changed, 116 insertions(+), 120 deletions(-) diff --git a/src/hooks/useRelaySettings.ts b/src/hooks/useRelaySettings.ts index 6ffbf30..31da9f0 100644 --- a/src/hooks/useRelaySettings.ts +++ b/src/hooks/useRelaySettings.ts @@ -27,23 +27,23 @@ const getInitialSettings = (): RelaySettings => { return savedSettings ? JSON.parse(savedSettings) : { - mode: 'smart', - protocol: ['WebSocket'], - dynamicKinds: [], - kinds: [], - photos: [], - videos: [], - gitNestr: [], - audio: [], - appBuckets: [], - dynamicAppBuckets: [], - isKindsActive: true, - isPhotosActive: true, - isVideosActive: true, - isGitNestrActive: true, - isAudioActive: true, - isFileStorageActive: false, - }; + mode: 'smart', + protocol: ['WebSocket'], + dynamicKinds: [], + kinds: [], + photos: [], + videos: [], + gitNestr: [], + audio: [], + appBuckets: [], + dynamicAppBuckets: [], + isKindsActive: true, + isPhotosActive: true, + isVideosActive: true, + isGitNestrActive: true, + isAudioActive: true, + isFileStorageActive: false, + }; }; const useRelaySettings = () => { @@ -76,37 +76,34 @@ const useRelaySettings = () => { const data = await response.json(); - const storedAppBuckets = JSON.parse(localStorage.getItem('appBuckets') || '[]'); - const storedDynamicKinds = JSON.parse(localStorage.getItem('dynamicKinds') || '[]'); + // Handle app buckets + const backendAppBuckets = data.relay_settings.appBuckets || []; + const backendDynamicAppBuckets = data.relay_settings.dynamicAppBuckets || []; - const newAppBuckets = - data.relay_settings.dynamicAppBuckets == undefined - ? [] - : data.relay_settings.dynamicAppBuckets.filter((bucket: string) => !storedAppBuckets.includes(bucket)); - const newDynamicKinds = - data.relay_settings.dynamicKinds == undefined - ? [] - : data.relay_settings.dynamicKinds.filter((kind: string) => !storedDynamicKinds.includes(kind)); - - if (newAppBuckets.length > 0) { - localStorage.setItem('appBuckets', JSON.stringify([...storedAppBuckets, ...newAppBuckets])); - } - if (newDynamicKinds.length > 0) { - localStorage.setItem('dynamicKinds', JSON.stringify([...storedDynamicKinds, ...newDynamicKinds])); - } setRelaySettings({ ...data.relay_settings, protocol: Array.isArray(data.relay_settings.protocol) ? data.relay_settings.protocol : [data.relay_settings.protocol], + appBuckets: backendAppBuckets, + dynamicAppBuckets: backendDynamicAppBuckets, }); - localStorage.setItem('relaySettings', JSON.stringify(data.relay_settings)); + + localStorage.setItem('relaySettings', JSON.stringify({ + ...data.relay_settings, + appBuckets: backendAppBuckets, + dynamicAppBuckets: backendDynamicAppBuckets, + })); + + // Update localStorage for dynamicAppBuckets only + localStorage.setItem('dynamicAppBuckets', JSON.stringify(backendDynamicAppBuckets)); } catch (error) { console.error('Error fetching settings:', error); } }, []); - const updateSettings = useCallback((category: keyof RelaySettings, value: string | string[] | boolean | number) => { + + const updateSettings = useCallback((category: keyof RelaySettings, value: any) => { setRelaySettings((prevSettings) => ({ ...prevSettings, [category]: value, diff --git a/src/pages/RelaySettingsPage.tsx b/src/pages/RelaySettingsPage.tsx index e5a21ab..707d187 100644 --- a/src/pages/RelaySettingsPage.tsx +++ b/src/pages/RelaySettingsPage.tsx @@ -17,7 +17,7 @@ import { useResponsive } from '@app/hooks/useResponsive'; import useRelaySettings from '@app/hooks/useRelaySettings'; import * as S from '@app/pages/uiComponentsPages/UIComponentsPage.styles'; import { themeObject } from '@app/styles/themes/themeVariables'; -import { categories, noteOptions, appBuckets, Settings, Category } from '@app/constants/relaySettings'; +import { categories, noteOptions, appBuckets as defaultAppBuckets, Settings, Category } from '@app/constants/relaySettings'; const { Panel } = Collapse; const StyledPanel = styled(Panel)``; const { Option } = Select; @@ -33,10 +33,15 @@ const RelaySettingsPage: React.FC = () => { const [storedDynamicKinds, setStoredDynamicKinds] = useState( JSON.parse(localStorage.getItem('dynamicKinds') || '[]'), ); + const [storedAppBuckets, setStoredAppBuckets] = useState( JSON.parse(localStorage.getItem('appBuckets') || '[]'), ); + const [dynamicAppBuckets, setDynamicAppBuckets] = useState( + JSON.parse(localStorage.getItem('dynamicAppBuckets') || '[]'), + ); + const [loadings, setLoadings] = useState([]); const [newKind, setNewKind] = useState(''); const [newBucket, setNewBucket] = useState(''); @@ -75,7 +80,7 @@ const RelaySettingsPage: React.FC = () => { useEffect(() => { console.log(settings); console.log(blacklist) - }, [settings,blacklist]); + }, [settings, blacklist]); const enterLoading = (index: number) => { setLoadings((loadings) => { const newLoadings = [...loadings]; @@ -114,8 +119,8 @@ const RelaySettingsPage: React.FC = () => { settings.mode !== 'smart' ? themeObject[theme].textMain : relaySettings.isPhotosActive - ? themeObject[theme].textMain - : themeObject[theme].textLight, + ? themeObject[theme].textMain + : themeObject[theme].textLight, }} isActive={settings.mode !== 'smart' || '' ? true : settings.isPhotosActive} > @@ -125,6 +130,17 @@ const RelaySettingsPage: React.FC = () => { value: format, })); + useEffect(() => { + if (relaySettings) { + setSettings({ + ...relaySettings, + protocol: Array.isArray(relaySettings.protocol) ? relaySettings.protocol : [relaySettings.protocol], + }); + setDynamicAppBuckets(relaySettings.dynamicAppBuckets); + } + }, [relaySettings]); + + const videoFormatOptions = ['avi', 'mp4', 'mov', 'wmv', 'mkv', 'flv', 'mpeg', '3gp', 'webm', 'ogg'].map((format) => ({ label: ( { settings.mode !== 'smart' ? themeObject[theme].textMain : relaySettings.isVideosActive - ? themeObject[theme].textMain - : themeObject[theme].textLight, + ? themeObject[theme].textMain + : themeObject[theme].textLight, }} isActive={settings.mode !== 'smart' ? true : settings.isVideosActive} > @@ -144,7 +160,10 @@ const RelaySettingsPage: React.FC = () => { value: format, })); - const appBucketOptions = appBuckets.map((bucket) => ({ + const appBucketOptions = [ + ...defaultAppBuckets.map(bucket => ({ id: bucket.id, label: bucket.label })), + ...dynamicAppBuckets.map(bucket => ({ id: bucket, label: bucket })) + ].map((bucket) => ({ label: ( { settings.mode !== 'smart' ? themeObject[theme].textMain : relaySettings.isKindsActive - ? themeObject[theme].textMain - : themeObject[theme].textLight, + ? themeObject[theme].textMain + : themeObject[theme].textLight, }} - isActive={settings.mode !== 'smart' ? true : settings.isKindsActive} //TODO: isAppBucketActive + isActive={settings.mode !== 'smart' ? true : settings.isKindsActive} > {bucket.label} @@ -185,8 +204,8 @@ const RelaySettingsPage: React.FC = () => { settings.mode !== 'smart' ? themeObject[theme].textMain : relaySettings.isAudioActive - ? themeObject[theme].textMain - : themeObject[theme].textLight, + ? themeObject[theme].textMain + : themeObject[theme].textLight, }} isActive={settings.mode !== 'smart' ? true : settings.isAudioActive} > @@ -209,8 +228,8 @@ const RelaySettingsPage: React.FC = () => { settings.mode !== 'smart' ? themeObject[theme].textMain : relaySettings.isGitNestrActive - ? themeObject[theme].textMain - : themeObject[theme].textLight, + ? themeObject[theme].textMain + : themeObject[theme].textLight, }} isActive={settings.mode !== 'smart' ? true : settings.isGitNestrActive} > @@ -276,19 +295,8 @@ const RelaySettingsPage: React.FC = () => { // }; const handleSettingsChange = (category: Category, checkedValues: string[]) => { - console.log("changing settings", category, checkedValues) - if (settings.mode === 'unlimited') { - setBlacklist((prevBlacklist) => ({ - ...prevBlacklist, - [category]: checkedValues, - })); - } else { - setSettings((prevSettings) => { - const updatedSettings = { ...prevSettings, [category]: checkedValues }; - updateSettings(category, checkedValues); - return updatedSettings; - }); - } + console.log("changing settings", category, checkedValues); + updateSettings(category, checkedValues); }; const handleSwitchChange = (category: keyof Settings, value: boolean) => { @@ -300,20 +308,20 @@ const RelaySettingsPage: React.FC = () => { }; const handleNewBucket = (bucket: string) => { - const currentBuckets = settings.appBuckets.concat(storedAppBuckets); - if (currentBuckets.includes(bucket)) { - return; - } - setStoredAppBuckets((prevBuckets) => [...prevBuckets, bucket]); - handleSettingsChange('dynamicAppBuckets', [...settings.dynamicAppBuckets, bucket]); + if (!bucket) return; + if (dynamicAppBuckets.includes(bucket)) return; + + const updatedDynamicAppBuckets = [...dynamicAppBuckets, bucket]; + setDynamicAppBuckets(updatedDynamicAppBuckets); + updateSettings('dynamicAppBuckets', updatedDynamicAppBuckets); + localStorage.setItem('dynamicAppBuckets', JSON.stringify(updatedDynamicAppBuckets)); }; const handleRemovedBucket = (bucket: string) => { - setStoredAppBuckets((prevBuckets) => prevBuckets.filter((b) => b !== bucket)); - handleSettingsChange( - 'appBuckets', - settings.appBuckets.filter((b) => b !== bucket), - ); + const updatedDynamicAppBuckets = dynamicAppBuckets.filter((b) => b !== bucket); + setDynamicAppBuckets(updatedDynamicAppBuckets); + updateSettings('dynamicAppBuckets', updatedDynamicAppBuckets); + localStorage.setItem('dynamicAppBuckets', JSON.stringify(updatedDynamicAppBuckets)); }; const handleNewDynamicKind = (kind: string) => { @@ -342,8 +350,8 @@ const RelaySettingsPage: React.FC = () => { updateSettings('audio', settings.isAudioActive ? settings.audio : []), updateSettings('protocol', settings.protocol), updateSettings('isFileStorageActive', settings.isFileStorageActive), - // updateSettings('appBuckets', settings.appBuckets), - //updateSettings('dynamicAppBuckets', settings.dynamicAppBuckets), + updateSettings('appBuckets', settings.appBuckets), + updateSettings('dynamicAppBuckets', settings.dynamicAppBuckets), ]); await saveSettings(); @@ -390,7 +398,7 @@ const RelaySettingsPage: React.FC = () => { }, [relaySettings]); useEffect(() => { - if(settings.mode === 'unlimited') return + if (settings.mode === 'unlimited') return console.log("resetting blacklist changing") setBlacklist({ kinds: [], @@ -401,12 +409,12 @@ const RelaySettingsPage: React.FC = () => { }); }, [settings.mode]); - useEffect(() => { - localStorage.setItem('appBuckets', JSON.stringify(storedAppBuckets)); - }, [storedAppBuckets]); - useEffect(() => { - localStorage.setItem('dynamicKinds', JSON.stringify(storedDynamicKinds)); - }, [storedDynamicKinds]); + // useEffect(() => { + // localStorage.setItem('appBuckets', JSON.stringify(storedAppBuckets)); + // }, [storedAppBuckets]); + // useEffect(() => { + // localStorage.setItem('dynamicKinds', JSON.stringify(storedDynamicKinds)); + // }, [storedDynamicKinds]); useEffect(() => { const updateDynamicKinds = async () => { @@ -468,8 +476,9 @@ const RelaySettingsPage: React.FC = () => { handleSettingsChange('appBuckets', checkedValues as string[])} - options={appBucketOptions} + options={appBucketOptions.filter(option => defaultAppBuckets.some(bucket => bucket.id === option.value))} /> @@ -506,15 +515,14 @@ const RelaySettingsPage: React.FC = () => { handleSettingsChange('dynamicAppBuckets', checkedValues as string[]) } > - {(storedAppBuckets || []).map((bucket) => ( + {dynamicAppBuckets.map((bucket) => (
{ settings.mode !== 'smart' ? themeObject[theme].textMain : relaySettings.isKindsActive - ? themeObject[theme].textMain - : themeObject[theme].textLight, + ? themeObject[theme].textMain + : themeObject[theme].textLight, }} > {t(`kind${note.kind}`)} -{' '} @@ -655,9 +663,8 @@ const RelaySettingsPage: React.FC = () => {
handleSettingsChange('dynamicKinds', checkedValues as string[])} > @@ -712,9 +719,8 @@ const RelaySettingsPage: React.FC = () => { )} handleSettingsChange('photos', checkedValues as string[])} @@ -741,9 +747,8 @@ const RelaySettingsPage: React.FC = () => { )} handleSettingsChange('videos', checkedValues as string[])} @@ -797,9 +802,8 @@ const RelaySettingsPage: React.FC = () => { )} handleSettingsChange('audio', checkedValues as string[])} @@ -921,9 +925,8 @@ const RelaySettingsPage: React.FC = () => { handleSettingsChange('dynamicAppBuckets', checkedValues as string[])} > @@ -1017,8 +1020,8 @@ const RelaySettingsPage: React.FC = () => { settings.mode !== 'smart' ? themeObject[theme].textMain : relaySettings.isKindsActive - ? themeObject[theme].textMain - : themeObject[theme].textLight, + ? themeObject[theme].textMain + : themeObject[theme].textLight, }} > {t(`kind${note.kind}`)} - {note.description} @@ -1048,9 +1051,8 @@ const RelaySettingsPage: React.FC = () => { handleSettingsChange('dynamicKinds', checkedValues as string[])} > @@ -1106,9 +1108,8 @@ const RelaySettingsPage: React.FC = () => { handleSettingsChange('photos', checkedValues as string[])} @@ -1133,9 +1134,8 @@ const RelaySettingsPage: React.FC = () => { handleSettingsChange('videos', checkedValues as string[])} @@ -1188,9 +1188,8 @@ const RelaySettingsPage: React.FC = () => { )} handleSettingsChange('audio', checkedValues as string[])} From d0a1d879f98bae8a147b1fa8a591526aaa8fd4c9 Mon Sep 17 00:00:00 2001 From: TONE-E <90010906+AnthonyMarin@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:48:59 -0800 Subject: [PATCH 62/62] fix remaining issues from squashing --- .../activityStory/ActivityStory.tsx | 529 ------------------ .../ActivityStoryItem/ActivityStoryItem.tsx | 180 ------ .../ResultScreen/ResultScreen.styles.ts | 0 .../components/ResultScreen/ResultScreen.tsx | 0 .../TieredFees/TieredFees.styles.ts | 0 .../components/TieredFees/TieredFees.tsx | 0 .../TransactionItem/TransactionItem.tsx | 90 ++- .../transactions/Transactions.tsx | 53 +- .../UnconfirmedTransactions.styles.ts | 0 .../UnconfirmedTransactions.tsx | 0 .../ButtonTrigger/ButtonTrigger.tsx | 0 .../Modal/UnconfirmedTxModal.styles.ts | 0 .../components/Modal/UnconfirmedTxModal.tsx | 0 .../ReplaceTransaction.styles.ts | 0 .../ReplaceTransaction/ReplaceTransaction.tsx | 4 +- .../UnconfirmedTransaction.styles.ts | 0 .../UnconfirmedTransaction.tsx | 0 17 files changed, 87 insertions(+), 769 deletions(-) delete mode 100644 src/components/nft-dashboard/activityStory/ActivityStory.tsx delete mode 100644 src/components/nft-dashboard/activityStory/ActivityStoryItem/ActivityStoryItem.tsx rename src/components/{nft-dashboard => relay-dashboard}/Balance/components/SendForm/components/ResultScreen/ResultScreen.styles.ts (100%) rename src/components/{nft-dashboard => relay-dashboard}/Balance/components/SendForm/components/ResultScreen/ResultScreen.tsx (100%) rename src/components/{nft-dashboard => relay-dashboard}/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts (100%) rename src/components/{nft-dashboard => relay-dashboard}/Balance/components/SendForm/components/TieredFees/TieredFees.tsx (100%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/UnconfirmedTransactions.styles.ts (100%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/UnconfirmedTransactions.tsx (100%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx (100%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts (100%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx (100%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts (100%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx (98%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts (100%) rename src/components/{nft-dashboard => relay-dashboard}/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx (100%) diff --git a/src/components/nft-dashboard/activityStory/ActivityStory.tsx b/src/components/nft-dashboard/activityStory/ActivityStory.tsx deleted file mode 100644 index a227dcd..0000000 --- a/src/components/nft-dashboard/activityStory/ActivityStory.tsx +++ /dev/null @@ -1,529 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import ActivityStoryItem from './ActivityStoryItem/ActivityStoryItem'; -import { getUserActivities, WalletTransaction } from '@app/api/activity.api'; -import * as S from './ActivityStory.styles'; -import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; -import { Modal } from 'antd'; -import { ViewTransactions } from '@app/components/nft-dashboard/common/ViewAll/ViewTransactions'; -import styled from 'styled-components'; -import { Line } from 'react-chartjs-2'; -import { BaseSkeleton } from '@app/components/common/BaseSkeleton/BaseSkeleton'; -import { ChartOptions } from 'chart.js'; -import { - Chart as ChartJS, - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, - Filler, -} from 'chart.js'; -import { TransactionCard } from './ActivityStoryItem/ActivityStoryItem.styles'; -import ButtonTrigger from '../unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger'; -import { useHandleLogout } from '@app/hooks/authUtils'; - -ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler); - -const TitleContainer = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; -`; - -export const ActivityStory: React.FC = () => { - const [story, setStory] = useState([]); - const [isModalVisible, setIsModalVisible] = useState(false); - const [isLoading, setIsLoading] = useState(true); - const handleLogout = useHandleLogout(); - - const { t } = useTranslation(); - - useEffect(() => { - getUserActivities(handleLogout) - .then((res) => { - setStory(res); - setIsLoading(false); - }) - .catch((error) => { - console.error('Failed to load user activities:', error); - setIsLoading(false); - }); - }, [handleLogout]); - - const activityContent = - story.length > 0 ? ( - story.map((item) => ( - - - - )) - ) : ( - {t('No transaction data')} - ); - - const showModal = () => { - setIsModalVisible(true); - }; - - const handleCancel = () => { - setIsModalVisible(false); - }; - - const TransactionSkeletons = () => { - return ( - <> - - - - - - - - - - - - ); - }; - const prepareChartData = () => { - const sortedStory = [...story].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); - - // Filter out negative values and their corresponding labels - const positiveStory = sortedStory.filter((item) => parseFloat(item.value) > 0); - - const labels = positiveStory.map((item) => new Date(item.date).toLocaleDateString()); - const amounts = positiveStory.map((item) => { - const amount = parseFloat(item.value); - return isNaN(amount) ? 0 : amount; - }); - - return { - labels, - datasets: [ - { - label: 'Transaction Amount', - data: amounts, - fill: true, - backgroundColor: (context: any) => { - const ctx = context.chart.ctx; - const gradient = ctx.createLinearGradient(0, 0, 0, 400); - gradient.addColorStop(0, 'rgba(75, 192, 192, 0.6)'); - gradient.addColorStop(1, 'rgba(75, 192, 192, 0.1)'); - return gradient; - }, - borderColor: 'rgba(75, 192, 192, 1)', - pointBackgroundColor: 'rgba(75, 192, 192, 1)', - pointBorderColor: '#fff', - pointHoverBackgroundColor: '#fff', - pointHoverBorderColor: 'rgba(75, 192, 192, 1)', - tension: 0.4, - }, - ], - }; - }; - - - const chartOptions: ChartOptions<'line'> = { - responsive: true, - maintainAspectRatio: false, - scales: { - y: { - beginAtZero: true, - title: { - display: true, - text: 'Amount', - font: { - size: 14, - weight: 'bold', - }, - color: 'rgba(255, 255, 255, 0.8)', // Lighter color for y-axis title - }, - ticks: { - font: { - size: 12, - }, - color: 'rgba(255, 255, 255, 0.6)', // Lighter color for y-axis ticks - }, - grid: { - color: 'rgba(255, 255, 255, 0.1)', // Lighter color for y-axis grid lines - }, - }, - x: { - title: { - display: true, - text: 'Date', - font: { - size: 14, - weight: 'bold', - }, - color: 'rgba(255, 255, 255, 0.8)', // Lighter color for x-axis title - }, - ticks: { - font: { - size: 12, - }, - color: 'rgba(255, 255, 255, 0.6)', // Lighter color for x-axis ticks - }, - grid: { - color: 'rgba(255, 255, 255, 0.1)', // Lighter color for x-axis grid lines - }, - }, - }, - plugins: { - legend: { - position: 'top' as const, - labels: { - font: { - size: 14, - }, - color: 'rgba(255, 255, 255, 0.8)', // Lighter color for legend labels - }, - }, - tooltip: { - // ... (keep the existing tooltip configuration) - }, - }, - animation: { - duration: 1000, - easing: 'easeInOutQuart', - }, - hover: { - mode: 'nearest' as const, - intersect: true, - }, - }; - - return ( - - - {t('nft.yourTransactions')} - - {t('nft.viewTransactions')} - - - - -
- -
- {isLoading ? : {activityContent}} -
- {isLoading ? : {activityContent}} -
- ); -}; - -// import React, { useEffect, useState } from 'react'; -// import { useTranslation } from 'react-i18next'; -// import ActivityStoryItem from './ActivityStoryItem/ActivityStoryItem'; -// import { getUserActivities, WalletTransaction } from '@app/api/activity.api'; -// import * as S from './ActivityStory.styles'; -// import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; -// import { Modal } from 'antd'; -// import { ViewTransactions } from '@app/components/nft-dashboard/common/ViewAll/ViewTransactions'; -// import styled from 'styled-components'; -// import { Line } from 'react-chartjs-2'; -// import { -// Chart as ChartJS, -// CategoryScale, -// LinearScale, -// PointElement, -// LineElement, -// Title, -// Tooltip, -// Legend, -// Filler, -// } from 'chart.js'; - -// ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler); - -// const TitleContainer = styled.div` -// display: flex; -// align-items: center; -// justify-content: space-between; -// width: 100%; -// `; - -// export const ActivityStory: React.FC = () => { -// const [story, setStory] = useState([]); -// const [isModalVisible, setIsModalVisible] = useState(false); - -// const { t } = useTranslation(); - -// useEffect(() => { -// getUserActivities().then((res) => setStory(res)); -// }, []); - -// const activityContent = -// story.length > 0 ? ( -// story.map((item) => ( -// -// -// -// )) -// ) : ( -// {t('No transaction data')} -// ); - -// const showModal = () => { -// setIsModalVisible(true); -// }; - -// const handleCancel = () => { -// setIsModalVisible(false); -// }; - -// const prepareChartData = () => { -// const labels = story.map(item => new Date(item.date).toLocaleDateString()); -// const amounts = story.map(item => { -// const amount = parseFloat(item.value); -// return isNaN(amount) ? 0 : amount; -// }); - -// return { -// labels, -// datasets: [ -// { -// label: 'Transaction Amount', -// data: amounts, -// fill: true, -// backgroundColor: 'rgba(75, 192, 192, 0.2)', -// borderColor: 'rgba(75, 192, 192, 1)', -// pointBackgroundColor: 'rgba(75, 192, 192, 1)', -// pointBorderColor: '#fff', -// pointHoverBackgroundColor: '#fff', -// pointHoverBorderColor: 'rgba(75, 192, 192, 1)', -// }, -// ], -// }; -// }; - -// const chartOptions = { -// responsive: true, -// maintainAspectRatio: false, -// scales: { -// y: { -// beginAtZero: true, -// title: { -// display: true, -// text: 'Amount', -// }, -// }, -// x: { -// title: { -// display: true, -// text: 'Date', -// }, -// }, -// }, -// plugins: { -// legend: { -// position: 'top' as const, -// }, -// tooltip: { -// callbacks: { -// label: (context: any) => { -// const value = context.raw; -// if (typeof value === 'number') { -// return value.toFixed(2); -// } else if (typeof value === 'string') { -// return value; -// } else { -// return 'N/A'; -// } -// }, -// }, -// }, -// }, -// }; - -// return ( -// -// -// {t('nft.yourTransactions')} -// -// {t('nft.viewTransactions')} -// -// - -// -//
-// -//
-// {activityContent} -//
-// {activityContent} -//
-// ); -// }; - -// import React, { useEffect, useMemo, useState } from 'react'; -// import { useTranslation } from 'react-i18next'; -// import ActivityStoryItem from './ActivityStoryItem/ActivityStoryItem'; -// import { WalletTransaction, getUserActivities } from '@app/api/activity.api'; -// import * as S from './ActivityStory.styles'; -// import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; -// import { Modal } from 'antd'; -// import { ViewTransactions } from '@app/components/nft-dashboard/common/ViewAll/ViewTransactions'; -// import styled from 'styled-components'; - -// const TitleContainer = styled.div` -// display: flex; -// align-items: center; -// justify-content: space-between; -// width: 100%; -// `; - -// export const ActivityStory: React.FC = () => { -// const [story, setStory] = useState([]); -// const [isModalVisible, setIsModalVisible] = useState(false); - -// const { t } = useTranslation(); - -// useEffect(() => { -// getUserActivities().then((res) => setStory(res)); -// }, []); - -// const activityStory = useMemo( -// () => -// story.map((item) => ( -// -// -// -// )), -// [story], -// ); - -// const showModal = () => { -// setIsModalVisible(true); -// }; - -// const handleCancel = () => { -// setIsModalVisible(false); -// }; - -// return ( -// -// -// {t('nft.yourTransactions')} -// -// {t('nft.viewTransactions')} -// -// - -// -// {activityStory} -// -// {activityStory} -// -// ); -// }; - -// import React, { useEffect, useMemo, useState } from 'react'; -// import { useTranslation } from 'react-i18next'; -// import ActivityStoryItem from './ActivityStoryItem/ActivityStoryItem'; -// import { WalletTransaction, getUserActivities } from '@app/api/activity.api'; -// import * as S from './ActivityStory.styles'; -// import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; -// import { Modal } from 'antd'; -// import { BaseButton } from '@app/components/common/BaseButton/BaseButton'; -// import styled, { css } from 'styled-components'; - -// // Define TopUpButton with the desired styling -// export const TopUpButton = styled(BaseButton)` -// ${(props) => -// props.type === 'ghost' && -// css` -// color: var(--text-secondary-color); -// `}; -// `; - -// const TitleContainer = styled.div` -// display: flex; -// align-items: center; -// justify-content: space-between; -// width: 100%; -// `; - -// export const ActivityStory: React.FC = () => { -// const [story, setStory] = useState([]); -// const [isModalVisible, setIsModalVisible] = useState(false); - -// const { t } = useTranslation(); - -// useEffect(() => { -// getUserActivities().then((res) => setStory(res)); -// }, []); - -// const activityStory = useMemo( -// () => -// story.map((item) => ( -// -// -// -// )), -// [story], -// ); - -// const showModal = () => { -// setIsModalVisible(true); -// }; - -// const handleCancel = () => { -// setIsModalVisible(false); -// }; - -// return ( -// -// -// {t('nft.yourTransactions')} -// -// View Transactions -// -// - -// -// {activityStory} -// -// {activityStory} -// -// ); -// }; - -// import React, { useEffect, useMemo, useState } from 'react'; -// import { useTranslation } from 'react-i18next'; -// import ActivityStoryItem from './ActivityStoryItem/ActivityStoryItem'; -// import { WalletTransaction, getUserActivities } from '@app/api/activity.api'; -// import * as S from './ActivityStory.styles'; -// import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; - -// export const ActivityStory: React.FC = () => { -// const [story, setStory] = useState([]); - -// const { t } = useTranslation(); - -// useEffect(() => { -// getUserActivities().then((res) => setStory(res)); -// }, []); - -// const activityStory = useMemo( -// () => -// story.map((item) => ( -// -// -// -// )), -// [story], -// ); - -// return ( -// -// {t('nft.yourTransactions')} -// {activityStory} -// -// ); -// }; diff --git a/src/components/nft-dashboard/activityStory/ActivityStoryItem/ActivityStoryItem.tsx b/src/components/nft-dashboard/activityStory/ActivityStoryItem/ActivityStoryItem.tsx deleted file mode 100644 index 3c54460..0000000 --- a/src/components/nft-dashboard/activityStory/ActivityStoryItem/ActivityStoryItem.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { WalletTransaction } from '@app/api/activity.api'; -import { Dates } from '@app/constants/Dates'; -import { getCurrencyPrice } from '@app/utils/utils'; -import { CurrencyTypeEnum } from '@app/interfaces/interfaces'; -import * as S from './ActivityStoryItem.styles'; -import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; -import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; -import { useBitcoinRates } from '@app/hooks/useBitcoinRates' - -function makeHexId(length: number): string { - const characters = 'abcdef0123456789'; - let result = ''; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * characters.length)); - } - return result; -} - -export const ActivityStoryItem: React.FC = ({ - witness_tx_id, - date, - output, - value, -}) => { - const { t } = useTranslation(); - const [transactionId, setTransactionId] = useState(null); - - const { rates, isLoading, error } = useBitcoinRates(); - - // Effect to initialize the transaction ID when the component mounts - useEffect(() => { - if (!witness_tx_id) { - setTransactionId(makeHexId(64)); - } - }, [witness_tx_id]); - - // Parse 'value' as BTC amount - const btcAmount = parseFloat(value); - - const [usdValue, setUsdValue] = useState(null); - - useEffect(() => { - if (!isLoading && rates.length > 0 && btcAmount) { - // Convert the transaction date to a timestamp - const transactionDate = new Date(date).getTime(); - - // Find the rate closest to the transaction date - let closestRate = rates[0]; - let minDiff = Math.abs(rates[0].date - transactionDate); - - for (let i = 1; i < rates.length; i++) { - const diff = Math.abs(rates[i].date - transactionDate); - if (diff < minDiff) { - minDiff = diff; - closestRate = rates[i]; - } - } - - const rate = closestRate.usd_value; - - // Compute the USD value - const usdAmount = btcAmount * rate; - setUsdValue(usdAmount); - } - }, [isLoading, rates, btcAmount, date]); - - // Handle potential errors and loading states - if (error) { - return
Error loading exchange rates: {error}
; - } - - if (isLoading || usdValue === null) { - return
Loading...
; - } - - return ( - - - - {'Transaction ID'}: - - {witness_tx_id ? witness_tx_id : transactionId} - - - - {'Address'}: - {output} - - - {'Date'}: - {Dates.getDate(date).format('L LTS')} - - - {'Value'}: - - {getCurrencyPrice( - usdValue.toLocaleString(undefined, { - minimumFractionDigits: 2, - maximumFractionDigits: 2, - }), - CurrencyTypeEnum.USD - )} - - - - - ); -}; - -export default ActivityStoryItem; - - - - - -// import React, {useEffect, useState} from 'react'; -// import { useTranslation } from 'react-i18next'; -// import { WalletTransaction } from '@app/api/activity.api'; -// import { Dates } from '@app/constants/Dates'; -// import { formatNumberWithCommas, getCurrencyPrice } from '@app/utils/utils'; -// import { CurrencyTypeEnum } from '@app/interfaces/interfaces'; -// import * as S from './ActivityStoryItem.styles'; -// import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; -// import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; -// import { useBitcoinRates } from '@app/hooks/useBitcoinRates' - -// function makeHexId(length: number): string { -// const characters = 'abcdef0123456789'; -// let result = ''; -// for (let i = 0; i < length; i++) { -// result += characters.charAt(Math.floor(Math.random() * characters.length)); -// } -// return result; -// } - -// // Use the useBitcoinRates hook -// const { rates, isLoading, error } = useBitcoinRates(); - -// export const ActivityStoryItem: React.FC = ({ witness_tx_id, date, output, value }) => { -// const { t } = useTranslation(); -// const [transactionId, setTransactionId] = useState(null); - -// // Effect to initialize the transaction ID when the component mounts -// useEffect(() => { -// if (!witness_tx_id) { -// setTransactionId(makeHexId(64)); -// } -// }, [witness_tx_id]); - -// const numericValue = parseFloat(value); - - - -// return ( -// -// -// -// {t('Witness Transaction ID')}: -// {witness_tx_id ? witness_tx_id : transactionId} -// -// -// {t('Output')}: -// {output} -// -// -// {t('Date')}: -// {Dates.getDate(date).format('L LTS')} -// -// -// {t('Value')}: -// {getCurrencyPrice(formatNumberWithCommas(numericValue), CurrencyTypeEnum.USD)} -// -// -// -// ); -// }; - -// export default ActivityStoryItem; diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.styles.ts b/src/components/relay-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.styles.ts similarity index 100% rename from src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.styles.ts rename to src/components/relay-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.styles.ts diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.tsx b/src/components/relay-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.tsx similarity index 100% rename from src/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.tsx rename to src/components/relay-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen.tsx diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts b/src/components/relay-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts similarity index 100% rename from src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts rename to src/components/relay-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.styles.ts diff --git a/src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx b/src/components/relay-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx similarity index 100% rename from src/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx rename to src/components/relay-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees.tsx diff --git a/src/components/relay-dashboard/transactions/TransactionItem/TransactionItem.tsx b/src/components/relay-dashboard/transactions/TransactionItem/TransactionItem.tsx index e558273..47b1e8f 100644 --- a/src/components/relay-dashboard/transactions/TransactionItem/TransactionItem.tsx +++ b/src/components/relay-dashboard/transactions/TransactionItem/TransactionItem.tsx @@ -6,6 +6,8 @@ import * as S from './TransactionItem.styles'; import { BaseRow } from '@app/components/common/BaseRow/BaseRow'; import { BaseCol } from '@app/components/common/BaseCol/BaseCol'; import { useBitcoinRates } from '@app/hooks/useBitcoinRates'; +import { getCurrencyPrice } from '@app/utils/utils'; +import { CurrencyTypeEnum } from '@app/interfaces/interfaces'; function makeHexId(length: number): string { const characters = 'abcdef0123456789'; @@ -16,63 +18,91 @@ function makeHexId(length: number): string { return result; } -function convertBtcToUsd(btcValue: string, btcPriceInUsd: number): string { - const btcAmount = parseFloat(btcValue); - if (btcAmount < 1) { - const satoshis = Math.round(btcAmount * 100000000); - const usdValue = (satoshis / 100000000) * btcPriceInUsd; - return usdValue.toFixed(2); - } else { - const wholeBtc = Math.floor(btcAmount); - const satoshis = Math.round((btcAmount - wholeBtc) * 100000000); - const usdValue = (wholeBtc * btcPriceInUsd) + (satoshis / 100000000 * btcPriceInUsd); - return usdValue.toFixed(2); - } -} - -export const TransactionItem: React.FC = ({ witness_tx_id, date, output, value }) => { +export const TransactionItem: React.FC = ({ + witness_tx_id, + date, + output, + value, +}) => { const { t } = useTranslation(); const [transactionId, setTransactionId] = useState(null); - const [usdValue, setUsdValue] = useState(''); + const { rates, isLoading, error } = useBitcoinRates(); + // Effect to initialize the transaction ID when the component mounts useEffect(() => { if (!witness_tx_id) { setTransactionId(makeHexId(64)); } }, [witness_tx_id]); + // Parse 'value' as BTC amount + const btcAmount = parseFloat(value) / 100000000; + + const [usdValue, setUsdValue] = useState(null); + useEffect(() => { - if (!isLoading && !error && rates.length > 0) { - const btcPrice = rates[rates.length - 1].usd_value; // Get the most recent BTC price - const usdValueCalculated = convertBtcToUsd(value, btcPrice); - setUsdValue(usdValueCalculated); + if (!isLoading && rates.length > 0 && btcAmount) { + // Convert the transaction date to a timestamp + const transactionDate = new Date(date).getTime(); + + // Find the rate closest to the transaction date + let closestRate = rates[0]; + let minDiff = Math.abs(rates[0].date - transactionDate); + + for (let i = 1; i < rates.length; i++) { + const diff = Math.abs(rates[i].date - transactionDate); + if (diff < minDiff) { + minDiff = diff; + closestRate = rates[i]; + } + } + + const rate = closestRate.usd_value; + + // Compute the USD value + const usdAmount = btcAmount * rate; + setUsdValue(usdAmount); } - }, [value, rates, isLoading, error]); + }, [isLoading, rates, btcAmount, date]); + + // Handle potential errors and loading states + if (error) { + return
Error loading exchange rates: {error}
; + } - // Skip rendering if the value is zero - if (parseFloat(value) === 0) { - return null; + if (isLoading || usdValue === null) { + return
Loading...
; } return ( - {t('Transaction ID')}: - {witness_tx_id ? witness_tx_id : transactionId} + {'Transaction ID'}: + + {witness_tx_id ? witness_tx_id : transactionId} + - {t('Address')}: + {'Address'}: {output} - {t('Date')}: + {'Date'}: {Dates.getDate(date).format('L LTS')} - {t('Value')}: - {usdValue && ${usdValue}} + {'Value'}: + + {getCurrencyPrice( + usdValue.toLocaleString(undefined, { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }), + CurrencyTypeEnum.USD + )} + diff --git a/src/components/relay-dashboard/transactions/Transactions.tsx b/src/components/relay-dashboard/transactions/Transactions.tsx index 9123c18..660e80b 100644 --- a/src/components/relay-dashboard/transactions/Transactions.tsx +++ b/src/components/relay-dashboard/transactions/Transactions.tsx @@ -22,6 +22,8 @@ import { Filler, } from 'chart.js'; import { TransactionCard } from './TransactionItem/TransactionItem.styles'; +import ButtonTrigger from '../unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger'; +import { useHandleLogout } from '@app/hooks/authUtils'; ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, Filler); @@ -36,15 +38,21 @@ export const ActivityStory: React.FC = () => { const [story, setStory] = useState([]); const [isModalVisible, setIsModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(true); + const handleLogout = useHandleLogout(); const { t } = useTranslation(); useEffect(() => { - getUserActivities().then((res) => { - setStory(res); - setIsLoading(false); - }); - }, []); + getUserActivities(handleLogout) + .then((res) => { + setStory(res); + setIsLoading(false); + }) + .catch((error) => { + console.error('Failed to load user activities:', error); + setIsLoading(false); + }); + }, [handleLogout]); const activityContent = story.length > 0 ? ( @@ -82,29 +90,23 @@ export const ActivityStory: React.FC = () => { ); }; const prepareChartData = () => { - const sortedStory = [...story] - .filter((item) => { - const amount = parseFloat(item.value); - console.log(`Parsed amount: ${amount} for transaction ID: ${item.id}`); - return amount > 0; // Filter only positive values - }) - .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + const sortedStory = [...story].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); + + // Filter out negative values and their corresponding labels + const positiveStory = sortedStory.filter((item) => parseFloat(item.value) > 0); - const labels = sortedStory.map((item) => new Date(item.date).toLocaleDateString()); - const amounts = sortedStory.map((item) => { + const labels = positiveStory.map((item) => new Date(item.date).toLocaleDateString()); + const amounts = positiveStory.map((item) => { const amount = parseFloat(item.value); return isNaN(amount) ? 0 : amount; }); - // Additional log to verify amounts array - console.log('Chart Data Amounts:', amounts); - return { labels, datasets: [ { label: 'Transaction Amount', - data: amounts, // Ensure this is correctly linked + data: amounts, fill: true, backgroundColor: (context: any) => { const ctx = context.chart.ctx; @@ -124,6 +126,7 @@ export const ActivityStory: React.FC = () => { }; }; + const chartOptions: ChartOptions<'line'> = { responsive: true, maintainAspectRatio: false, @@ -181,12 +184,7 @@ export const ActivityStory: React.FC = () => { }, }, tooltip: { - callbacks: { - label: function (tooltipItem) { - // Customize tooltip display - return `Amount: ${tooltipItem.raw}`; // Assuming that `tooltipItem.raw` holds the amount value - }, - }, + // ... (keep the existing tooltip configuration) }, }, animation: { @@ -198,7 +196,6 @@ export const ActivityStory: React.FC = () => { intersect: true, }, }; - return ( @@ -208,14 +205,14 @@ export const ActivityStory: React.FC = () => { {t('nft.viewTransactions')} - +
- {isLoading ? : {activityContent}} + {isLoading ? : {activityContent}}
- {isLoading ? : {activityContent}} + {isLoading ? : {activityContent}}
); }; diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts b/src/components/relay-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts rename to src/components/relay-dashboard/unconfirmed-transactions/UnconfirmedTransactions.styles.ts diff --git a/src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx b/src/components/relay-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx rename to src/components/relay-dashboard/unconfirmed-transactions/UnconfirmedTransactions.tsx diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx b/src/components/relay-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx rename to src/components/relay-dashboard/unconfirmed-transactions/components/ButtonTrigger/ButtonTrigger.tsx diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts b/src/components/relay-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts rename to src/components/relay-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.styles.ts diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx b/src/components/relay-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx rename to src/components/relay-dashboard/unconfirmed-transactions/components/Modal/UnconfirmedTxModal.tsx diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts b/src/components/relay-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts rename to src/components/relay-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.styles.ts diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx b/src/components/relay-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx similarity index 98% rename from src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx rename to src/components/relay-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx index 29c3dce..a6eab7b 100644 --- a/src/components/nft-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx +++ b/src/components/relay-dashboard/unconfirmed-transactions/components/ReplaceTransaction/ReplaceTransaction.tsx @@ -1,10 +1,10 @@ import React, { useEffect, useState, useCallback } from 'react'; import * as S from './ReplaceTransaction.styles'; import { useResponsive } from '@app/hooks/useResponsive'; -import TieredFees from '@app/components/nft-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; +import TieredFees from '@app/components/relay-dashboard/Balance/components/SendForm/components/TieredFees/TieredFees'; import { PendingTransaction } from '@app/hooks/usePendingTransactions'; import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin'; -import ResultScreen from '@app/components/nft-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; +import ResultScreen from '@app/components/relay-dashboard/Balance/components/SendForm/components/ResultScreen/ResultScreen'; import config from '@app/config/config'; import useBalanceData from '@app/hooks/useBalanceData'; import useWalletAuth from '@app/hooks/useWalletAuth'; // Import authentication hook diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts b/src/components/relay-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts rename to src/components/relay-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.styles.ts diff --git a/src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx b/src/components/relay-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx similarity index 100% rename from src/components/nft-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx rename to src/components/relay-dashboard/unconfirmed-transactions/components/UnconfirmedTransaction/UnconfirmedTransaction.tsx