From e9921b77ec0638cb5aa0df2c9bcce3105fd746cb Mon Sep 17 00:00:00 2001 From: Rigidity Date: Sun, 8 Dec 2024 11:44:27 -0500 Subject: [PATCH 1/2] Switch to throw based global error handling --- src-tauri/src/lib.rs | 3 +- src/App.tsx | 33 +- src/bindings.ts | 603 ++++++------------------- src/components/ConfirmationDialog.tsx | 80 ++-- src/components/Header.tsx | 4 +- src/components/Layout.tsx | 2 +- src/components/MultiSelectActions.tsx | 47 +- src/components/NftCard.tsx | 67 +-- src/contexts/ErrorContext.tsx | 63 +++ src/contexts/PeerContext.tsx | 36 +- src/contexts/PriceContext.tsx | 15 +- src/contexts/WalletConnectContext.tsx | 35 +- src/hooks/useDids.ts | 22 +- src/hooks/useErrors.ts | 10 + src/hooks/useInitialization.ts | 21 +- src/hooks/usePeers.ts | 10 + src/hooks/usePrices.ts | 10 + src/hooks/useWallet.ts | 20 +- src/pages/Addresses.tsx | 25 +- src/pages/Collection.tsx | 22 +- src/pages/CreateProfile.tsx | 14 +- src/pages/CreateWallet.tsx | 42 +- src/pages/DidList.tsx | 50 +- src/pages/ImportWallet.tsx | 26 +- src/pages/IssueToken.tsx | 18 +- src/pages/Login.tsx | 79 ++-- src/pages/MakeOffer.tsx | 27 +- src/pages/MintNft.tsx | 16 +- src/pages/Nft.tsx | 22 +- src/pages/NftList.tsx | 23 +- src/pages/Offers.tsx | 36 +- src/pages/PeerList.tsx | 49 +- src/pages/Send.tsx | 41 +- src/pages/Settings.tsx | 90 ++-- src/pages/Token.tsx | 77 ++-- src/pages/TokenList.tsx | 23 +- src/pages/Transactions.tsx | 22 +- src/pages/ViewOffer.tsx | 57 +-- src/pages/ViewSavedOffer.tsx | 19 +- src/state.ts | 43 +- src/walletconnect/commands/chip0002.ts | 64 +-- src/walletconnect/commands/offers.ts | 21 +- 42 files changed, 757 insertions(+), 1230 deletions(-) create mode 100644 src/contexts/ErrorContext.tsx create mode 100644 src/hooks/useErrors.ts create mode 100644 src/hooks/usePeers.ts create mode 100644 src/hooks/usePrices.ts diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7113700f..2c590ec2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,7 +1,7 @@ use app_state::{AppState, AppStateInner}; use sage_api::SyncEvent; use tauri::Manager; -use tauri_specta::{collect_commands, collect_events, Builder}; +use tauri_specta::{collect_commands, collect_events, Builder, ErrorHandlingMode}; use tokio::sync::Mutex; mod app_state; @@ -14,6 +14,7 @@ use specta_typescript::{BigIntExportBehavior, Typescript}; #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let builder = Builder::::new() + .error_handling(ErrorHandlingMode::Throw) // Then register them (separated by a comma) .commands(collect_commands![ commands::initialize, diff --git a/src/App.tsx b/src/App.tsx index 555d792d..182ed678 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,11 +6,13 @@ import { RouterProvider, } from 'react-router-dom'; import { useLocalStorage } from 'usehooks-ts'; +import { ErrorProvider } from './contexts/ErrorContext'; import { PeerProvider } from './contexts/PeerContext'; import { PriceProvider } from './contexts/PriceContext'; import { WalletConnectProvider } from './contexts/WalletConnectContext'; import useInitialization from './hooks/useInitialization'; import { useWallet } from './hooks/useWallet'; +import Addresses from './pages/Addresses'; import Collection from './pages/Collection'; import CreateProfile from './pages/CreateProfile'; import CreateWallet from './pages/CreateWallet'; @@ -24,7 +26,6 @@ import Nft from './pages/Nft'; import { NftList } from './pages/NftList'; import { Offers } from './pages/Offers'; import PeerList from './pages/PeerList'; -import Addresses from './pages/Addresses'; import Send from './pages/Send'; import Settings from './pages/Settings'; import Token from './pages/Token'; @@ -105,6 +106,16 @@ export default function App() { root.classList.add(dark ? 'dark' : 'light'); }, [dark]); + return ( + + + + + + ); +} + +function AppInner() { const initialized = useInitialization(); const wallet = useWallet(initialized); @@ -115,16 +126,14 @@ export default function App() { }, [wallet]); return ( - - {initialized && ( - - - - - - - - )} - + initialized && ( + + + + + + + + ) ); } diff --git a/src/bindings.ts b/src/bindings.ts index 4853e942..5ecabfd8 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -5,541 +5,206 @@ export const commands = { -async initialize() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("initialize") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async initialize() : Promise { + return await TAURI_INVOKE("initialize"); }, -async login(req: Login) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("login", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async login(req: Login) : Promise { + return await TAURI_INVOKE("login", { req }); }, -async logout(req: Logout) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("logout", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async logout(req: Logout) : Promise { + return await TAURI_INVOKE("logout", { req }); }, -async resync(req: Resync) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("resync", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async resync(req: Resync) : Promise { + return await TAURI_INVOKE("resync", { req }); }, -async generateMnemonic(req: GenerateMnemonic) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("generate_mnemonic", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async generateMnemonic(req: GenerateMnemonic) : Promise { + return await TAURI_INVOKE("generate_mnemonic", { req }); }, -async importKey(req: ImportKey) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("import_key", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async importKey(req: ImportKey) : Promise { + return await TAURI_INVOKE("import_key", { req }); }, -async deleteKey(req: DeleteKey) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("delete_key", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async deleteKey(req: DeleteKey) : Promise { + return await TAURI_INVOKE("delete_key", { req }); }, -async renameKey(req: RenameKey) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("rename_key", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async renameKey(req: RenameKey) : Promise { + return await TAURI_INVOKE("rename_key", { req }); }, -async getKeys(req: GetKeys) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_keys", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getKeys(req: GetKeys) : Promise { + return await TAURI_INVOKE("get_keys", { req }); }, -async getKey(req: GetKey) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_key", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getKey(req: GetKey) : Promise { + return await TAURI_INVOKE("get_key", { req }); }, -async getSecretKey(req: GetSecretKey) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_secret_key", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getSecretKey(req: GetSecretKey) : Promise { + return await TAURI_INVOKE("get_secret_key", { req }); }, -async sendXch(req: SendXch) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("send_xch", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async sendXch(req: SendXch) : Promise { + return await TAURI_INVOKE("send_xch", { req }); }, -async combineXch(req: CombineXch) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("combine_xch", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async combineXch(req: CombineXch) : Promise { + return await TAURI_INVOKE("combine_xch", { req }); }, -async splitXch(req: SplitXch) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("split_xch", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async splitXch(req: SplitXch) : Promise { + return await TAURI_INVOKE("split_xch", { req }); }, -async sendCat(req: SendCat) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("send_cat", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async sendCat(req: SendCat) : Promise { + return await TAURI_INVOKE("send_cat", { req }); }, -async combineCat(req: CombineCat) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("combine_cat", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async combineCat(req: CombineCat) : Promise { + return await TAURI_INVOKE("combine_cat", { req }); }, -async splitCat(req: SplitCat) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("split_cat", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async splitCat(req: SplitCat) : Promise { + return await TAURI_INVOKE("split_cat", { req }); }, -async issueCat(req: IssueCat) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("issue_cat", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async issueCat(req: IssueCat) : Promise { + return await TAURI_INVOKE("issue_cat", { req }); }, -async createDid(req: CreateDid) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("create_did", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async createDid(req: CreateDid) : Promise { + return await TAURI_INVOKE("create_did", { req }); }, -async bulkMintNfts(req: BulkMintNfts) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("bulk_mint_nfts", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async bulkMintNfts(req: BulkMintNfts) : Promise { + return await TAURI_INVOKE("bulk_mint_nfts", { req }); }, -async transferNfts(req: TransferNfts) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("transfer_nfts", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async transferNfts(req: TransferNfts) : Promise { + return await TAURI_INVOKE("transfer_nfts", { req }); }, -async transferDids(req: TransferDids) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("transfer_dids", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async transferDids(req: TransferDids) : Promise { + return await TAURI_INVOKE("transfer_dids", { req }); }, -async addNftUri(req: AddNftUri) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("add_nft_uri", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async addNftUri(req: AddNftUri) : Promise { + return await TAURI_INVOKE("add_nft_uri", { req }); }, -async assignNftsToDid(req: AssignNftsToDid) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("assign_nfts_to_did", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async assignNftsToDid(req: AssignNftsToDid) : Promise { + return await TAURI_INVOKE("assign_nfts_to_did", { req }); }, -async signCoinSpends(req: SignCoinSpends) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("sign_coin_spends", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async signCoinSpends(req: SignCoinSpends) : Promise { + return await TAURI_INVOKE("sign_coin_spends", { req }); }, -async viewCoinSpends(req: ViewCoinSpends) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("view_coin_spends", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async viewCoinSpends(req: ViewCoinSpends) : Promise { + return await TAURI_INVOKE("view_coin_spends", { req }); }, -async submitTransaction(req: SubmitTransaction) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("submit_transaction", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async submitTransaction(req: SubmitTransaction) : Promise { + return await TAURI_INVOKE("submit_transaction", { req }); }, -async getSyncStatus(req: GetSyncStatus) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_sync_status", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getSyncStatus(req: GetSyncStatus) : Promise { + return await TAURI_INVOKE("get_sync_status", { req }); }, -async getDerivations(req: GetDerivations) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_derivations", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getDerivations(req: GetDerivations) : Promise { + return await TAURI_INVOKE("get_derivations", { req }); }, -async getXchCoins(req: GetXchCoins) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_xch_coins", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getXchCoins(req: GetXchCoins) : Promise { + return await TAURI_INVOKE("get_xch_coins", { req }); }, -async getCatCoins(req: GetCatCoins) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_cat_coins", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getCatCoins(req: GetCatCoins) : Promise { + return await TAURI_INVOKE("get_cat_coins", { req }); }, -async getCats(req: GetCats) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_cats", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getCats(req: GetCats) : Promise { + return await TAURI_INVOKE("get_cats", { req }); }, -async getCat(req: GetCat) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_cat", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getCat(req: GetCat) : Promise { + return await TAURI_INVOKE("get_cat", { req }); }, -async getDids(req: GetDids) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_dids", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getDids(req: GetDids) : Promise { + return await TAURI_INVOKE("get_dids", { req }); }, -async getNftStatus(req: GetNftStatus) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_nft_status", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getNftStatus(req: GetNftStatus) : Promise { + return await TAURI_INVOKE("get_nft_status", { req }); }, -async getNftCollections(req: GetNftCollections) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_nft_collections", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getNftCollections(req: GetNftCollections) : Promise { + return await TAURI_INVOKE("get_nft_collections", { req }); }, -async getNftCollection(req: GetNftCollection) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_nft_collection", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getNftCollection(req: GetNftCollection) : Promise { + return await TAURI_INVOKE("get_nft_collection", { req }); }, -async getNfts(req: GetNfts) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_nfts", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getNfts(req: GetNfts) : Promise { + return await TAURI_INVOKE("get_nfts", { req }); }, -async getNft(req: GetNft) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_nft", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getNft(req: GetNft) : Promise { + return await TAURI_INVOKE("get_nft", { req }); }, -async getPendingTransactions(req: GetPendingTransactions) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_pending_transactions", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getPendingTransactions(req: GetPendingTransactions) : Promise { + return await TAURI_INVOKE("get_pending_transactions", { req }); }, -async validateAddress(address: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("validate_address", { address }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async validateAddress(address: string) : Promise { + return await TAURI_INVOKE("validate_address", { address }); }, -async makeOffer(req: MakeOffer) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("make_offer", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async makeOffer(req: MakeOffer) : Promise { + return await TAURI_INVOKE("make_offer", { req }); }, -async takeOffer(req: TakeOffer) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("take_offer", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async takeOffer(req: TakeOffer) : Promise { + return await TAURI_INVOKE("take_offer", { req }); }, -async viewOffer(req: ViewOffer) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("view_offer", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async viewOffer(req: ViewOffer) : Promise { + return await TAURI_INVOKE("view_offer", { req }); }, -async importOffer(req: ImportOffer) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("import_offer", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async importOffer(req: ImportOffer) : Promise { + return await TAURI_INVOKE("import_offer", { req }); }, -async getOffers(req: GetOffers) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_offers", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getOffers(req: GetOffers) : Promise { + return await TAURI_INVOKE("get_offers", { req }); }, -async getOffer(req: GetOffer) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_offer", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getOffer(req: GetOffer) : Promise { + return await TAURI_INVOKE("get_offer", { req }); }, -async deleteOffer(req: DeleteOffer) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("delete_offer", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async deleteOffer(req: DeleteOffer) : Promise { + return await TAURI_INVOKE("delete_offer", { req }); }, -async networkConfig() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("network_config") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async networkConfig() : Promise { + return await TAURI_INVOKE("network_config"); }, -async setDiscoverPeers(req: SetDiscoverPeers) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("set_discover_peers", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async setDiscoverPeers(req: SetDiscoverPeers) : Promise { + return await TAURI_INVOKE("set_discover_peers", { req }); }, -async setTargetPeers(req: SetTargetPeers) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("set_target_peers", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async setTargetPeers(req: SetTargetPeers) : Promise { + return await TAURI_INVOKE("set_target_peers", { req }); }, -async setNetworkId(req: SetNetworkId) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("set_network_id", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async setNetworkId(req: SetNetworkId) : Promise { + return await TAURI_INVOKE("set_network_id", { req }); }, -async walletConfig(fingerprint: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("wallet_config", { fingerprint }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async walletConfig(fingerprint: number) : Promise { + return await TAURI_INVOKE("wallet_config", { fingerprint }); }, -async setDeriveAutomatically(req: SetDeriveAutomatically) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("set_derive_automatically", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async setDeriveAutomatically(req: SetDeriveAutomatically) : Promise { + return await TAURI_INVOKE("set_derive_automatically", { req }); }, -async setDerivationBatchSize(req: SetDerivationBatchSize) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("set_derivation_batch_size", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async setDerivationBatchSize(req: SetDerivationBatchSize) : Promise { + return await TAURI_INVOKE("set_derivation_batch_size", { req }); }, -async getNetworks(req: GetNetworks) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_networks", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getNetworks(req: GetNetworks) : Promise { + return await TAURI_INVOKE("get_networks", { req }); }, -async updateCat(req: UpdateCat) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_cat", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async updateCat(req: UpdateCat) : Promise { + return await TAURI_INVOKE("update_cat", { req }); }, -async removeCat(req: RemoveCat) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("remove_cat", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async removeCat(req: RemoveCat) : Promise { + return await TAURI_INVOKE("remove_cat", { req }); }, -async updateDid(req: UpdateDid) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_did", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async updateDid(req: UpdateDid) : Promise { + return await TAURI_INVOKE("update_did", { req }); }, -async updateNft(req: UpdateNft) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_nft", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async updateNft(req: UpdateNft) : Promise { + return await TAURI_INVOKE("update_nft", { req }); }, -async getPeers(req: GetPeers) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_peers", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getPeers(req: GetPeers) : Promise { + return await TAURI_INVOKE("get_peers", { req }); }, -async addPeer(req: AddPeer) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("add_peer", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async addPeer(req: AddPeer) : Promise { + return await TAURI_INVOKE("add_peer", { req }); }, -async removePeer(req: RemovePeer) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("remove_peer", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async removePeer(req: RemovePeer) : Promise { + return await TAURI_INVOKE("remove_peer", { req }); }, -async filterUnlockedCoins(req: FilterUnlockedCoins) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("filter_unlocked_coins", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async filterUnlockedCoins(req: FilterUnlockedCoins) : Promise { + return await TAURI_INVOKE("filter_unlocked_coins", { req }); }, -async getAssetCoins(req: GetAssetCoins) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_asset_coins", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async getAssetCoins(req: GetAssetCoins) : Promise { + return await TAURI_INVOKE("get_asset_coins", { req }); }, -async signMessageWithPublicKey(req: SignMessageWithPublicKey) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("sign_message_with_public_key", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async signMessageWithPublicKey(req: SignMessageWithPublicKey) : Promise { + return await TAURI_INVOKE("sign_message_with_public_key", { req }); }, -async sendTransactionImmediately(req: SendTransactionImmediately) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("send_transaction_immediately", { req }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} +async sendTransactionImmediately(req: SendTransactionImmediately) : Promise { + return await TAURI_INVOKE("send_transaction_immediately", { req }); } } diff --git a/src/components/ConfirmationDialog.tsx b/src/components/ConfirmationDialog.tsx index 6c03bec5..216ca9e7 100644 --- a/src/components/ConfirmationDialog.tsx +++ b/src/components/ConfirmationDialog.tsx @@ -7,6 +7,7 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; +import { useErrors } from '@/hooks/useErrors'; import { toDecimal } from '@/lib/utils'; import { useWalletState } from '@/state'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; @@ -24,7 +25,6 @@ import { import { PropsWithChildren, useEffect, useState } from 'react'; import { commands, - Error, TakeOfferResponse, TransactionResponse, TransactionSummary, @@ -38,7 +38,6 @@ export interface ConfirmationDialogProps { response: TransactionResponse | TakeOfferResponse | null; close: () => void; onConfirm?: () => void; - onError?: (error: Error) => void; } interface SpentCoin { @@ -59,16 +58,11 @@ export default function ConfirmationDialog({ response, close, onConfirm, - onError, }: ConfirmationDialogProps) { - if (!onError) { - onError = (error) => { - console.error(error); - }; - } - const walletState = useWalletState(); + const { addError } = useErrors(); + const [pending, setPending] = useState(false); const [signature, setSignature] = useState(null); @@ -184,15 +178,10 @@ export default function ConfirmationDialog({ ? response.coin_spends : response.spend_bundle.coin_spends, }) - .then((result) => { - if (result.status === 'error') { - onError?.(result.error); - } else { - setSignature( - result.data.spend_bundle.aggregated_signature, - ); - } - }); + .then((data) => + setSignature(data.spend_bundle.aggregated_signature), + ) + .catch(addError); }} disabled={!!signature} > @@ -236,37 +225,34 @@ export default function ConfirmationDialog({ response !== null && 'coin_spends' in response ) { - const result = await commands.signCoinSpends({ - coin_spends: response!.coin_spends, - }); - if (result.status === 'error') { - reset(); - onError?.(result.error); - return; - } - finalSignature = - result.data.spend_bundle.aggregated_signature; - } + const data = await commands + .signCoinSpends({ + coin_spends: response!.coin_spends, + }) + .catch(addError); + + if (!data) return reset(); - const result = await commands.submitTransaction({ - spend_bundle: { - coin_spends: - response === null - ? [] - : 'coin_spends' in response - ? response.coin_spends - : response.spend_bundle.coin_spends, - aggregated_signature: finalSignature!, - }, - }); - - reset(); - - if (result.status === 'error') { - onError?.(result.error); - } else { - onConfirm?.(); + finalSignature = data.spend_bundle.aggregated_signature; } + + const data = await commands + .submitTransaction({ + spend_bundle: { + coin_spends: + response === null + ? [] + : 'coin_spends' in response + ? response.coin_spends + : response.spend_bundle.coin_spends, + aggregated_signature: finalSignature!, + }, + }) + .catch(addError); + + if (!data) return reset(); + + onConfirm?.(); })().finally(() => setPending(false)); }} disabled={pending} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 32731068..f27a6324 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,4 +1,5 @@ -import { usePeers } from '@/contexts/PeerContext'; +import useInitialization from '@/hooks/useInitialization'; +import { usePeers } from '@/hooks/usePeers'; import { useWallet } from '@/hooks/useWallet'; import icon from '@/icon.png'; import { logoutAndUpdateState, useWalletState } from '@/state'; @@ -8,7 +9,6 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Nav } from './Nav'; import { Button } from './ui/button'; import { Sheet, SheetContent, SheetTrigger } from './ui/sheet'; -import useInitialization from '@/hooks/useInitialization'; export default function Header( props: PropsWithChildren<{ diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 55d4bde4..37226bcf 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -2,8 +2,8 @@ import { Cog, LogOut } from 'lucide-react'; import { PropsWithChildren, useMemo } from 'react'; import { Link, useNavigate } from 'react-router-dom'; -import { usePeers } from '@/contexts/PeerContext'; import useInitialization from '@/hooks/useInitialization'; +import { usePeers } from '@/hooks/usePeers'; import { useWallet } from '@/hooks/useWallet'; import icon from '@/icon.png'; import { logoutAndUpdateState, useWalletState } from '@/state'; diff --git a/src/components/MultiSelectActions.tsx b/src/components/MultiSelectActions.tsx index 5efafc40..23d2abae 100644 --- a/src/components/MultiSelectActions.tsx +++ b/src/components/MultiSelectActions.tsx @@ -1,4 +1,5 @@ import { commands, TransactionResponse } from '@/bindings'; +import { useErrors } from '@/hooks/useErrors'; import { useWalletState } from '@/state'; import { ChevronDown, @@ -32,6 +33,8 @@ export function MultiSelectActions({ }: MultiSelectActionsProps) { const walletState = useWalletState(); + const { addError } = useErrors(); + const [transferOpen, setTransferOpen] = useState(false); const [assignOpen, setAssignOpen] = useState(false); const [unassignOpen, setUnassignOpen] = useState(false); @@ -41,40 +44,25 @@ export function MultiSelectActions({ const onTransferSubmit = (address: string, fee: string) => { commands .transferNfts({ nft_ids: selected, address, fee }) - .then((result) => { - setTransferOpen(false); - if (result.status === 'error') { - console.error('Failed to transfer NFTs', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setTransferOpen(false)); }; const onAssignSubmit = (profile: string, fee: string) => { commands .assignNftsToDid({ nft_ids: selected, did_id: profile, fee }) - .then((result) => { - setAssignOpen(false); - if (result.status === 'error') { - console.error('Failed to assign NFT', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setAssignOpen(false)); }; const onUnassignSubmit = (fee: string) => { commands .assignNftsToDid({ nft_ids: selected, did_id: null, fee }) - .then((result) => { - setUnassignOpen(false); - if (result.status === 'error') { - console.error('Failed to unassign NFT', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setUnassignOpen(false)); }; const onBurnSubmit = (fee: string) => { @@ -84,14 +72,9 @@ export function MultiSelectActions({ address: walletState.sync.burn_address, fee, }) - .then((result) => { - setBurnOpen(false); - if (result.status === 'error') { - console.error('Failed to burn NFTs', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setBurnOpen(false)); }; return ( diff --git a/src/components/NftCard.tsx b/src/components/NftCard.tsx index 6a4bb3f6..a1f72250 100644 --- a/src/components/NftCard.tsx +++ b/src/components/NftCard.tsx @@ -4,6 +4,7 @@ import { NftUriKind, TransactionResponse, } from '@/bindings'; +import { useErrors } from '@/hooks/useErrors'; import { amount } from '@/lib/formTypes'; import { nftUri } from '@/lib/nftUri'; import { toMojos } from '@/lib/utils'; @@ -83,6 +84,8 @@ export function NftCard({ nft, updateNfts, selectionState }: NftProps) { const walletState = useWalletState(); const navigate = useNavigate(); + const { addError } = useErrors(); + const [transferOpen, setTransferOpen] = useState(false); const [assignOpen, setAssignOpen] = useState(false); const [unassignOpen, setUnassignOpen] = useState(false); @@ -93,13 +96,8 @@ export function NftCard({ nft, updateNfts, selectionState }: NftProps) { const toggleVisibility = () => { commands .updateNft({ nft_id: nft.launcher_id, visible: !nft.visible }) - .then((result) => { - if (result.status === 'ok') { - updateNfts(); - } else { - throw new Error('Failed to toggle visibility for NFT'); - } - }); + .then(updateNfts) + .catch(addError); }; const onTransferSubmit = (address: string, fee: string) => { @@ -109,14 +107,9 @@ export function NftCard({ nft, updateNfts, selectionState }: NftProps) { address, fee: toMojos(fee, walletState.sync.unit.decimals), }) - .then((result) => { - setTransferOpen(false); - if (result.status === 'error') { - console.error('Failed to transfer NFT', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setTransferOpen(false)); }; const onAssignSubmit = (profile: string, fee: string) => { @@ -126,14 +119,9 @@ export function NftCard({ nft, updateNfts, selectionState }: NftProps) { did_id: profile, fee: toMojos(fee, walletState.sync.unit.decimals), }) - .then((result) => { - setAssignOpen(false); - if (result.status === 'error') { - console.error('Failed to assign NFT', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setAssignOpen(false)); }; const onUnassignSubmit = (fee: string) => { @@ -143,14 +131,9 @@ export function NftCard({ nft, updateNfts, selectionState }: NftProps) { did_id: null, fee: toMojos(fee, walletState.sync.unit.decimals), }) - .then((result) => { - setUnassignOpen(false); - if (result.status === 'error') { - console.error('Failed to unassign NFT', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setUnassignOpen(false)); }; const addUrlFormSchema = z.object({ @@ -179,14 +162,9 @@ export function NftCard({ nft, updateNfts, selectionState }: NftProps) { kind: values.kind as NftUriKind, fee: toMojos(values.fee, walletState.sync.unit.decimals), }) - .then((result) => { - setAddUrlOpen(false); - if (result.status === 'error') { - console.error('Failed to add NFT URL', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setAddUrlOpen(false)); }; const onBurnSubmit = (fee: string) => { @@ -196,14 +174,9 @@ export function NftCard({ nft, updateNfts, selectionState }: NftProps) { address: walletState.sync.burn_address, fee: toMojos(fee, walletState.sync.unit.decimals), }) - .then((result) => { - setBurnOpen(false); - if (result.status === 'error') { - console.error('Failed to burn NFT', result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError) + .finally(() => setBurnOpen(false)); }; return ( diff --git a/src/contexts/ErrorContext.tsx b/src/contexts/ErrorContext.tsx new file mode 100644 index 00000000..19f65905 --- /dev/null +++ b/src/contexts/ErrorContext.tsx @@ -0,0 +1,63 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { + createContext, + ReactNode, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { type Error } from '../bindings'; + +export interface ErrorContextType { + errors: Error[]; + addError: (error: Error) => void; +} + +export const ErrorContext = createContext( + undefined, +); + +export function ErrorProvider({ children }: { children: ReactNode }) { + const [errors, setErrors] = useState([]); + const errorsRef = useRef(errors); + + useEffect(() => { + errorsRef.current = errors; + }, [errors]); + + const addError = useMemo( + () => (error: Error) => setErrors([...errorsRef.current, error]), + [], + ); + + return ( + + {children} + + 0} + onOpenChange={(open) => { + if (!open) { + setErrors([]); + } + }} + > + + + Errors + + There are {errors.length} errors. + + + + + + ); +} diff --git a/src/contexts/PeerContext.tsx b/src/contexts/PeerContext.tsx index 42e4974c..d8ad17f8 100644 --- a/src/contexts/PeerContext.tsx +++ b/src/contexts/PeerContext.tsx @@ -1,45 +1,33 @@ -import { - createContext, - ReactNode, - useContext, - useEffect, - useState, -} from 'react'; +import { createContext, ReactNode, useEffect, useState } from 'react'; import { commands, PeerRecord } from '../bindings'; +import { useErrors } from '../hooks/useErrors'; -interface PeerContextType { +export interface PeerContextType { peers: PeerRecord[] | null; } -const PeerContext = createContext(undefined); +export const PeerContext = createContext( + undefined, +); export function PeerProvider({ children }: { children: ReactNode }) { + const { addError } = useErrors(); const [peers, setPeers] = useState(null); useEffect(() => { const updatePeers = () => { - commands.getPeers({}).then((res) => { - if (res.status === 'ok') { - setPeers(res.data.peers); - } - }); + commands + .getPeers({}) + .then((data) => setPeers(data.peers)) + .catch(addError); }; updatePeers(); const interval = setInterval(updatePeers, 5000); - return () => clearInterval(interval); - }, []); + }, [addError]); return ( {children} ); } - -export function usePeers() { - const context = useContext(PeerContext); - if (context === undefined) { - throw new Error('usePeers must be used within a PeerProvider'); - } - return context; -} diff --git a/src/contexts/PriceContext.tsx b/src/contexts/PriceContext.tsx index 1a256b1d..1e7f904b 100644 --- a/src/contexts/PriceContext.tsx +++ b/src/contexts/PriceContext.tsx @@ -3,16 +3,17 @@ import { createContext, ReactNode, useCallback, - useContext, useEffect, useState, } from 'react'; -interface PriceContextType { +export interface PriceContextType { getBalanceInUsd: (assetId: string, balance: string) => string; } -const PriceContext = createContext(undefined); +export const PriceContext = createContext( + undefined, +); export function PriceProvider({ children }: { children: ReactNode }) { const walletState = useWalletState(); @@ -82,11 +83,3 @@ export function PriceProvider({ children }: { children: ReactNode }) { ); } - -export function usePrices() { - const context = useContext(PriceContext); - if (context === undefined) { - throw new Error('usePeers must be used within a PeerProvider'); - } - return context; -} diff --git a/src/contexts/WalletConnectContext.tsx b/src/contexts/WalletConnectContext.tsx index 653098fa..9ee4657e 100644 --- a/src/contexts/WalletConnectContext.tsx +++ b/src/contexts/WalletConnectContext.tsx @@ -11,6 +11,7 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; +import { useErrors } from '@/hooks/useErrors'; import useInitialization from '@/hooks/useInitialization'; import { useWallet } from '@/hooks/useWallet'; import { toDecimal } from '@/lib/utils'; @@ -47,6 +48,7 @@ type SessionRequest = SignClientTypes.EventArguments['session_request']; export function WalletConnectProvider({ children }: { children: ReactNode }) { const initialized = useInitialization(); const wallet = useWallet(initialized); + const { addError } = useErrors(); const [signClient, setSignClient] = useState @@ -148,12 +150,7 @@ export function WalletConnectProvider({ children }: { children: ReactNode }) { throw new Error('Chain not supported'); } - const networkConfig = await commands.networkConfig().then((network) => { - if (network.status === 'ok' && network.data) { - return network.data; - } - return null; - }); + const networkConfig = await commands.networkConfig().catch(addError); if (!networkConfig) { throw new Error('Network config not found'); @@ -218,7 +215,7 @@ export function WalletConnectProvider({ children }: { children: ReactNode }) { signClient.off('session_request', handleSessionRequest); signClient.off('session_delete', handleSessionDelete); }; - }, [signClient, wallet, handleAndRespond, setPendingRequests]); + }, [signClient, wallet, handleAndRespond, setPendingRequests, addError]); const pair = async (uri: string) => { if (!signClient) throw new Error('Sign client not initialized'); @@ -299,6 +296,7 @@ function SignCoinSpendsDialog({ params, }: CommandDialogProps<'chip0002_signCoinSpends'>) { const [summary, setSummary] = useState(null); + const { addError } = useErrors(); useEffect(() => { const coinSpends = params.coinSpends.map((coinSpend) => ({ @@ -311,12 +309,11 @@ function SignCoinSpendsDialog({ solution: coinSpend.solution, })); - commands.viewCoinSpends({ coin_spends: coinSpends }).then((res) => { - if (res.status === 'ok') { - setSummary(res.data.summary); - } - }); - }, [params]); + commands + .viewCoinSpends({ coin_spends: coinSpends }) + .then((data) => setSummary(data.summary)) + .catch(addError); + }, [params, addError]); return summary ? ( @@ -348,14 +345,14 @@ function SignMessageDialog({ function TakeOfferDialog({ params }: CommandDialogProps<'chia_takeOffer'>) { const [offer, setOffer] = useState(null); + const { addError } = useErrors(); useEffect(() => { - commands.viewOffer({ offer: params.offer }).then((res) => { - if (res.status === 'ok') { - setOffer(res.data.offer); - } - }); - }, [params]); + commands + .viewOffer({ offer: params.offer }) + .then((data) => setOffer(data.offer)) + .catch(addError); + }, [params, addError]); return offer ? ( diff --git a/src/hooks/useDids.ts b/src/hooks/useDids.ts index 964f93dc..528d497e 100644 --- a/src/hooks/useDids.ts +++ b/src/hooks/useDids.ts @@ -1,18 +1,18 @@ import { commands, DidRecord, events } from '@/bindings'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { useErrors } from './useErrors'; export function useDids() { + const { addError } = useErrors(); + const [dids, setDids] = useState([]); - const updateDids = async () => { - return await commands.getDids({}).then((result) => { - if (result.status === 'ok') { - setDids(result.data.dids); - } else { - throw new Error('Failed to get DIDs'); - } - }); - }; + const updateDids = useCallback(async () => { + return await commands + .getDids({}) + .then((data) => setDids(data.dids)) + .catch(addError); + }, [addError]); useEffect(() => { updateDids(); @@ -32,7 +32,7 @@ export function useDids() { return () => { unlisten.then((u) => u()); }; - }, []); + }, [updateDids]); return { dids: dids.sort((a, b) => { diff --git a/src/hooks/useErrors.ts b/src/hooks/useErrors.ts new file mode 100644 index 00000000..80841357 --- /dev/null +++ b/src/hooks/useErrors.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { ErrorContext } from '../contexts/ErrorContext'; + +export function useErrors() { + const context = useContext(ErrorContext); + if (context === undefined) { + throw new Error('useErrors must be used within a ErrorProvider'); + } + return context; +} diff --git a/src/hooks/useInitialization.ts b/src/hooks/useInitialization.ts index f8d63972..221a885c 100644 --- a/src/hooks/useInitialization.ts +++ b/src/hooks/useInitialization.ts @@ -1,23 +1,18 @@ import { commands } from '@/bindings'; import { useCallback, useEffect, useState } from 'react'; +import { useErrors } from './useErrors'; export default function useInitialization() { + const { addError } = useErrors(); + const [initialized, setInitialized] = useState(false); const onInitialize = useCallback(async () => { - try { - commands.initialize().then((result) => { - if (result.status === 'ok') { - setInitialized(true); - } else { - console.error(result.error); - } - }); - } catch (err: unknown) { - console.error('Initialization failed', err); - alert(err); - } - }, []); + commands + .initialize() + .then(() => setInitialized(true)) + .catch(addError); + }, [addError]); useEffect(() => { if (!initialized) { diff --git a/src/hooks/usePeers.ts b/src/hooks/usePeers.ts new file mode 100644 index 00000000..adf707bd --- /dev/null +++ b/src/hooks/usePeers.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { PeerContext } from '../contexts/PeerContext'; + +export function usePeers() { + const context = useContext(PeerContext); + if (context === undefined) { + throw new Error('usePeers must be used within a PeerProvider'); + } + return context; +} diff --git a/src/hooks/usePrices.ts b/src/hooks/usePrices.ts new file mode 100644 index 00000000..de83cf23 --- /dev/null +++ b/src/hooks/usePrices.ts @@ -0,0 +1,10 @@ +import { useContext } from 'react'; +import { PriceContext } from '../contexts/PriceContext'; + +export function usePrices() { + const context = useContext(PriceContext); + if (context === undefined) { + throw new Error('usePrices must be used within a PriceProvider'); + } + return context; +} diff --git a/src/hooks/useWallet.ts b/src/hooks/useWallet.ts index b48d5ad0..be9b56a9 100644 --- a/src/hooks/useWallet.ts +++ b/src/hooks/useWallet.ts @@ -1,20 +1,20 @@ import { useEffect, useState } from 'react'; import { commands, KeyInfo } from '../bindings'; +import { useErrors } from './useErrors'; export function useWallet(initialized: boolean) { + const { addError } = useErrors(); + const [wallet, setWallet] = useState(null); useEffect(() => { - if (initialized) { - commands.getKey({}).then((wallet) => { - if (wallet.status === 'ok' && wallet.data) { - setWallet(wallet.data.key); - } else { - setWallet(null); - } - }); - } - }, [initialized]); + if (!initialized) return; + + commands + .getKey({}) + .then((data) => setWallet(data.key)) + .catch(addError); + }, [initialized, addError]); return wallet; } diff --git a/src/pages/Addresses.tsx b/src/pages/Addresses.tsx index 0d664acc..89980149 100644 --- a/src/pages/Addresses.tsx +++ b/src/pages/Addresses.tsx @@ -8,26 +8,27 @@ import { CardHeader, CardTitle, } from '@/components/ui/card'; -import { useEffect, useState } from 'react'; +import { useErrors } from '@/hooks/useErrors'; +import { useCallback, useEffect, useState } from 'react'; import { commands, events } from '../bindings'; import AddressList from '../components/AddressList'; import { useWalletState } from '../state'; export default function Addresses() { + const { addError } = useErrors(); + const walletState = useWalletState(); const [addresses, setAddresses] = useState([]); - console.log(addresses); - - const updateAddresses = () => { - commands.getDerivations({ offset: 0, limit: 1000000 }).then((res) => { - if (res.status === 'error') return; - setAddresses( - res.data.derivations.map((derivation) => derivation.address), - ); - }); - }; + const updateAddresses = useCallback(() => { + commands + .getDerivations({ offset: 0, limit: 1000000 }) + .then((data) => + setAddresses(data.derivations.map((derivation) => derivation.address)), + ) + .catch(addError); + }, [addError]); useEffect(() => { updateAddresses(); @@ -41,7 +42,7 @@ export default function Addresses() { return () => { unlisten.then((u) => u()); }; - }, []); + }, [updateAddresses]); return ( <> diff --git a/src/pages/Collection.tsx b/src/pages/Collection.tsx index e7776709..8cc99d4d 100644 --- a/src/pages/Collection.tsx +++ b/src/pages/Collection.tsx @@ -5,11 +5,13 @@ import { MultiSelectActions } from '@/components/MultiSelectActions'; import { NftCard, NftCardList } from '@/components/NftCard'; import { NftOptions } from '@/components/NftOptions'; import { ReceiveAddress } from '@/components/ReceiveAddress'; +import { useErrors } from '@/hooks/useErrors'; import { NftView, useNftParams } from '@/hooks/useNftParams'; import { useCallback, useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; export default function Collection() { + const { addError } = useErrors(); const { collection_id: collectionId } = useParams(); const [params, setParams] = useNftParams(); @@ -35,28 +37,18 @@ export default function Collection() { sort_mode: view, include_hidden: showHidden, }) - .then((result) => { - if (result.status === 'ok') { - setNfts(result.data.nfts); - } else { - throw new Error('Failed to get NFTs'); - } - }); + .then((data) => setNfts(data.nfts)) + .catch(addError); await commands .getNftCollection({ collection_id: collectionId === 'No collection' ? null : (collectionId ?? null), }) - .then((result) => { - if (result.status === 'ok') { - setCollection(result.data.collection); - } else { - throw new Error('Failed to get collection'); - } - }); + .then((data) => setCollection(data.collection)) + .catch(addError); }, - [collectionId, pageSize, showHidden, view], + [collectionId, pageSize, showHidden, view, addError], ); useEffect(() => { diff --git a/src/pages/CreateProfile.tsx b/src/pages/CreateProfile.tsx index 7fe2fa95..769017ec 100644 --- a/src/pages/CreateProfile.tsx +++ b/src/pages/CreateProfile.tsx @@ -10,6 +10,7 @@ import { FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; +import { useErrors } from '@/hooks/useErrors'; import { amount } from '@/lib/formTypes'; import { toMojos } from '@/lib/utils'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -23,6 +24,8 @@ import ErrorDialog from '../components/ErrorDialog'; import { useWalletState } from '../state'; export default function CreateProfile() { + const { addError } = useErrors(); + const navigate = useNavigate(); const walletState = useWalletState(); @@ -47,15 +50,8 @@ export default function CreateProfile() { walletState.sync.unit.decimals, ), }) - .then((result) => { - if (result.status === 'error') { - console.error(result.error); - setError(result.error); - return; - } else { - setResponse(result.data); - } - }); + .then((data) => setResponse(data)) + .catch(addError); }; return ( diff --git a/src/pages/CreateWallet.tsx b/src/pages/CreateWallet.tsx index 5f7abc14..0aa15b58 100644 --- a/src/pages/CreateWallet.tsx +++ b/src/pages/CreateWallet.tsx @@ -1,5 +1,4 @@ import Header from '@/components/Header'; -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { @@ -22,9 +21,10 @@ import { import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; +import { useErrors } from '@/hooks/useErrors'; import { zodResolver } from '@hookform/resolvers/zod'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; -import { AlertCircle, CopyIcon, RefreshCwIcon } from 'lucide-react'; +import { CopyIcon, RefreshCwIcon } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; @@ -34,9 +34,9 @@ import Container from '../components/Container'; import { fetchState } from '../state'; export default function CreateWallet() { - const navigate = useNavigate(); + const { addError } = useErrors(); - const [error, setError] = useState(null); + const navigate = useNavigate(); const submit = (values: z.infer) => { commands @@ -45,14 +45,9 @@ export default function CreateWallet() { key: values.mnemonic, save_secrets: values.saveMnemonic, }) - .then((res) => { - if (res.status === 'ok') { - fetchState().then(() => { - navigate('/wallet'); - }); - } - }) - .catch(setError); + .catch(addError) + .then(fetchState) + .then(() => navigate('/wallet')); }; return ( @@ -60,14 +55,6 @@ export default function CreateWallet() {
navigate('/')} /> - - {error && ( - - - Error - {error} - - )} ); @@ -83,6 +70,8 @@ const formSchema = z.object({ function CreateForm(props: { onSubmit: (values: z.infer) => void; }) { + const { addError } = useErrors(); + const form = useForm>({ resolver: zodResolver(formSchema), }); @@ -90,12 +79,13 @@ function CreateForm(props: { const use24Words = form.watch('use24Words', true); const loadMnemonic = useCallback(() => { - commands.generateMnemonic({ use_24_words: use24Words }).then((res) => { - if (res.status === 'ok') { - form.setValue('mnemonic', res.data.mnemonic); - } - }); - }, [form, use24Words]); + commands + .generateMnemonic({ use_24_words: use24Words }) + .then((data) => { + form.setValue('mnemonic', data.mnemonic); + }) + .catch(addError); + }, [form, use24Words, addError]); useEffect(() => { loadMnemonic(); diff --git a/src/pages/DidList.tsx b/src/pages/DidList.tsx index 85eb9e10..86c57660 100644 --- a/src/pages/DidList.tsx +++ b/src/pages/DidList.tsx @@ -26,6 +26,7 @@ import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { useDids } from '@/hooks/useDids'; +import { useErrors } from '@/hooks/useErrors'; import { toMojos } from '@/lib/utils'; import { useWalletState } from '@/state'; import { @@ -46,10 +47,10 @@ import { commands, DidRecord, TransactionResponse } from '../bindings'; export function DidList() { const navigate = useNavigate(); - const [showHidden, setShowHidden] = useState(false); - const { dids, updateDids } = useDids(); + const [showHidden, setShowHidden] = useState(false); + const visibleDids = showHidden ? dids : dids.filter((did) => did.visible); const hasHiddenDids = dids.findIndex((did) => !did.visible) > -1; @@ -109,6 +110,8 @@ interface ProfileProps { function Profile({ did, updateDids }: ProfileProps) { const walletState = useWalletState(); + const { addError } = useErrors(); + const [name, setName] = useState(''); const [renameOpen, setRenameOpen] = useState(false); const [transferOpen, setTransferOpen] = useState(false); @@ -120,15 +123,11 @@ function Profile({ did, updateDids }: ProfileProps) { commands .updateDid({ did_id: did.launcher_id, name, visible: did.visible }) - .then((result) => { + .then(updateDids) + .catch(addError) + .finally(() => { setRenameOpen(false); - - if (result.status === 'ok') { - setName(''); - updateDids(); - } else { - throw new Error(`Failed to rename DID: ${result.error.reason}`); - } + setName(''); }); }; @@ -139,13 +138,8 @@ function Profile({ did, updateDids }: ProfileProps) { name: did.name, visible: !did.visible, }) - .then((result) => { - if (result.status === 'ok') { - updateDids(); - } else { - throw new Error('Failed to toggle visibility for DID'); - } - }); + .then(updateDids) + .catch(addError); }; const onTransferSubmit = (address: string, fee: string) => { @@ -155,14 +149,9 @@ function Profile({ did, updateDids }: ProfileProps) { address, fee: toMojos(fee, walletState.sync.unit.decimals), }) - .then((result) => { - setTransferOpen(false); - if (result.status === 'error') { - console.error('Failed to transfer DID', result.error); - } else { - setResponse(result.data); - } - }); + .then(updateDids) + .catch(addError) + .finally(() => setTransferOpen(false)); }; const onBurnSubmit = (fee: string) => { @@ -172,14 +161,9 @@ function Profile({ did, updateDids }: ProfileProps) { address: walletState.sync.burn_address, fee: toMojos(fee, walletState.sync.unit.decimals), }) - .then((result) => { - setBurnOpen(false); - if (result.status === 'error') { - console.error('Failed to burn DID', result.error); - } else { - setResponse(result.data); - } - }); + .then(updateDids) + .catch(addError) + .finally(() => setBurnOpen(false)); }; return ( diff --git a/src/pages/ImportWallet.tsx b/src/pages/ImportWallet.tsx index 9b44160a..163cf0dd 100644 --- a/src/pages/ImportWallet.tsx +++ b/src/pages/ImportWallet.tsx @@ -1,5 +1,4 @@ import Header from '@/components/Header'; -import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Form, @@ -12,9 +11,8 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; +import { useErrors } from '@/hooks/useErrors'; import { zodResolver } from '@hookform/resolvers/zod'; -import { AlertCircle } from 'lucide-react'; -import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import * as z from 'zod'; @@ -25,20 +23,14 @@ import { fetchState } from '../state'; export default function ImportWallet() { const navigate = useNavigate(); - const [error, setError] = useState(null); + const { addError } = useErrors(); const submit = (values: z.infer) => { commands .importKey({ name: values.walletName, key: values.walletKey }) - .then((res) => { - if (res.status === 'ok') { - fetchState().then(() => { - navigate('/wallet'); - }); - } else { - setError(res.error.reason); - } - }); + .then(fetchState) + .then(() => navigate('/wallet')) + .catch(addError); }; return ( @@ -47,14 +39,6 @@ export default function ImportWallet() { - - {error && ( - - - Error - {error} - - )} ); diff --git a/src/pages/IssueToken.tsx b/src/pages/IssueToken.tsx index c2639411..4d204be3 100644 --- a/src/pages/IssueToken.tsx +++ b/src/pages/IssueToken.tsx @@ -10,6 +10,7 @@ import { FormMessage, } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; +import { useErrors } from '@/hooks/useErrors'; import { amount, positiveAmount } from '@/lib/formTypes'; import { toMojos } from '@/lib/utils'; import { zodResolver } from '@hookform/resolvers/zod'; @@ -17,16 +18,16 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import * as z from 'zod'; -import { commands, Error, TransactionResponse } from '../bindings'; +import { commands, TransactionResponse } from '../bindings'; import Container from '../components/Container'; -import ErrorDialog from '../components/ErrorDialog'; import { useWalletState } from '../state'; export default function IssueToken() { const navigate = useNavigate(); const walletState = useWalletState(); - const [error, setError] = useState(null); + const { addError } = useErrors(); + const [response, setResponse] = useState(null); const formSchema = z.object({ @@ -51,14 +52,8 @@ export default function IssueToken() { walletState.sync.unit.decimals, ), }) - .then((result) => { - if (result.status === 'error') { - console.error(result.error); - setError(result.error); - } else { - setResponse(result.data); - } - }); + .then(setResponse) + .catch(addError); }; return ( @@ -155,7 +150,6 @@ export default function IssueToken() { - setResponse(null)} diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index a9b863e2..cd6221a9 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -18,6 +18,8 @@ import { import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Skeleton } from '@/components/ui/skeleton'; +import { Switch } from '@/components/ui/switch'; +import { useErrors } from '@/hooks/useErrors'; import { EraserIcon, EyeIcon, @@ -33,34 +35,33 @@ import { useNavigate } from 'react-router-dom'; import { commands, KeyInfo, SecretKeyInfo } from '../bindings'; import Container from '../components/Container'; import { loginAndUpdateState } from '../state'; -import { Switch } from '@/components/ui/switch'; export default function Login() { + const navigate = useNavigate(); + + const { addError } = useErrors(); + const [keys, setKeys] = useState(null); const [network, setNetwork] = useState(null); - const navigate = useNavigate(); - useEffect(() => { - commands.getKeys({}).then((res) => { - if (res.status === 'ok') { - setKeys(res.data.keys); - } - }); + commands + .getKeys({}) + .then((data) => setKeys(data.keys)) + .catch(addError); - commands.networkConfig().then((res) => { - if (res.status === 'ok') { - setNetwork(res.data.network_id); - } - }); - }, []); + commands + .networkConfig() + .then((data) => setNetwork(data.network_id)) + .catch(addError); + }, [addError]); useEffect(() => { - commands.getKey({}).then((res) => { - if (res.status === 'error' || !res.data.key) return; - navigate('/wallet'); - }); - }, [navigate]); + commands + .getKey({}) + .then((data) => data.key !== null && navigate('/wallet')) + .catch(addError); + }, [navigate, addError]); return (
@@ -122,6 +123,8 @@ interface WalletItemProps { function WalletItem({ network, info, keys, setKeys }: WalletItemProps) { const navigate = useNavigate(); + const { addError } = useErrors(); + const [anchorEl, _setAnchorEl] = useState(null); const isMenuOpen = Boolean(anchorEl); @@ -142,18 +145,18 @@ function WalletItem({ network, info, keys, setKeys }: WalletItemProps) { fingerprint: info.fingerprint, delete_offer_files: deleteOffers, }) - .then((res) => { - if (res.status === 'error') return; - setResyncOpen(false); - }); + .catch(addError) + .finally(() => setResyncOpen(false)); }; const deleteSelf = () => { - commands.deleteKey({ fingerprint: info.fingerprint }).then((res) => { - if (res.status === 'error') return; - setKeys(keys.filter((key) => key.fingerprint !== info.fingerprint)); - setDeleteOpen(false); - }); + commands + .deleteKey({ fingerprint: info.fingerprint }) + .then(() => + setKeys(keys.filter((key) => key.fingerprint !== info.fingerprint)), + ) + .catch(addError) + .finally(() => setDeleteOpen(false)); }; const renameSelf = () => { @@ -161,17 +164,17 @@ function WalletItem({ network, info, keys, setKeys }: WalletItemProps) { commands .renameKey({ fingerprint: info.fingerprint, name: newName }) - .then((res) => { - if (res.status === 'error') return; + .then(() => setKeys( keys.map((key) => key.fingerprint === info.fingerprint ? { ...key, name: newName } : key, ), - ); - setRenameOpen(false); - }); + ), + ) + .catch(addError) + .finally(() => setRenameOpen(false)); setNewName(''); }; @@ -190,11 +193,11 @@ function WalletItem({ network, info, keys, setKeys }: WalletItemProps) { return; } - commands.getSecretKey({ fingerprint: info.fingerprint }).then((res) => { - if (res.status === 'error') return; - setSecrets(res.data.secrets); - }); - }, [isDetailsOpen, info.fingerprint]); + commands + .getSecretKey({ fingerprint: info.fingerprint }) + .then((data) => data.secrets !== null && setSecrets(data.secrets)) + .catch(addError); + }, [isDetailsOpen, info.fingerprint, addError]); return ( <> diff --git a/src/pages/MakeOffer.tsx b/src/pages/MakeOffer.tsx index 74acf0b0..95b5cd6d 100644 --- a/src/pages/MakeOffer.tsx +++ b/src/pages/MakeOffer.tsx @@ -15,6 +15,7 @@ import { import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; +import { useErrors } from '@/hooks/useErrors'; import { toMojos } from '@/lib/utils'; import { clearOffer, useOfferState, useWalletState } from '@/state'; import { @@ -32,6 +33,8 @@ export function MakeOffer() { const walletState = useWalletState(); const navigate = useNavigate(); + const { addError } = useErrors(); + const [offer, setOffer] = useState(''); const make = () => { @@ -71,13 +74,8 @@ export function MakeOffer() { Number(state.expiration.hours || '0') * 60 * 60 + Number(state.expiration.minutes || '0') * 60, }) - .then((result) => { - if (result.status === 'error') { - console.error(result.error); - } else { - setOffer(result.data.offer); - } - }); + .then((data) => setOffer(data.offer)) + .catch(addError); }; const invalid = @@ -281,15 +279,14 @@ export function MakeOffer() { - - - - ); -} diff --git a/src/contexts/ErrorContext.tsx b/src/contexts/ErrorContext.tsx index 19f65905..0f02f9b7 100644 --- a/src/contexts/ErrorContext.tsx +++ b/src/contexts/ErrorContext.tsx @@ -1,7 +1,9 @@ +import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, + DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; @@ -41,23 +43,62 @@ export function ErrorProvider({ children }: { children: ReactNode }) { {children} - 0} - onOpenChange={(open) => { - if (!open) { - setErrors([]); - } - }} - > - - - Errors - - There are {errors.length} errors. - - - - + {errors.length > 0 && ( + setErrors(errors.slice(1))} + /> + )} ); } + +export interface ErrorDialogProps { + error: Error | null; + setError: (error: Error | null) => void; +} + +export default function ErrorDialog({ error, setError }: ErrorDialogProps) { + let kind: string | null; + + switch (error?.kind) { + case 'api': + kind = 'API'; + break; + + case 'internal': + kind = 'Internal'; + break; + + case 'not_found': + kind = 'Not Found'; + break; + + case 'unauthorized': + kind = 'Auth'; + break; + + case 'wallet': + kind = 'Wallet'; + break; + + default: + kind = null; + } + + return ( + setError(null)}> + + + {kind ? `${kind} ` : ''}Error + {error?.reason} + + + + + + + ); +} diff --git a/src/pages/CreateProfile.tsx b/src/pages/CreateProfile.tsx index 769017ec..c367b4c4 100644 --- a/src/pages/CreateProfile.tsx +++ b/src/pages/CreateProfile.tsx @@ -18,9 +18,8 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import * as z from 'zod'; -import { commands, Error, TransactionResponse } from '../bindings'; +import { commands, TransactionResponse } from '../bindings'; import Container from '../components/Container'; -import ErrorDialog from '../components/ErrorDialog'; import { useWalletState } from '../state'; export default function CreateProfile() { @@ -29,7 +28,6 @@ export default function CreateProfile() { const navigate = useNavigate(); const walletState = useWalletState(); - const [error, setError] = useState(null); const [response, setResponse] = useState(null); const formSchema = z.object({ @@ -108,7 +106,6 @@ export default function CreateProfile() { - setResponse(null)} diff --git a/src/pages/MintNft.tsx b/src/pages/MintNft.tsx index c71e22f0..829041b4 100644 --- a/src/pages/MintNft.tsx +++ b/src/pages/MintNft.tsx @@ -27,9 +27,8 @@ import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { useNavigate } from 'react-router-dom'; import * as z from 'zod'; -import { commands, Error, TransactionResponse } from '../bindings'; +import { commands, TransactionResponse } from '../bindings'; import Container from '../components/Container'; -import ErrorDialog from '../components/ErrorDialog'; import { useWalletState } from '../state'; export default function MintNft() { @@ -39,7 +38,6 @@ export default function MintNft() { const { dids } = useDids(); const { addError } = useErrors(); - const [error, setError] = useState(null); const [pending, setPending] = useState(false); const [response, setResponse] = useState(null); @@ -273,7 +271,6 @@ export default function MintNft() { - setResponse(null)} diff --git a/src/pages/Send.tsx b/src/pages/Send.tsx index fec5213e..9f3b6d8f 100644 --- a/src/pages/Send.tsx +++ b/src/pages/Send.tsx @@ -22,13 +22,11 @@ import * as z from 'zod'; import { CatRecord, commands, - Error, events, SendXch, TransactionResponse, } from '../bindings'; import Container from '../components/Container'; -import ErrorDialog from '../components/ErrorDialog'; export default function Send() { const { asset_id: assetId } = useParams(); @@ -42,7 +40,6 @@ export default function Send() { const [asset, setAsset] = useState<(CatRecord & { decimals: number }) | null>( null, ); - const [error, setError] = useState(null); const [response, setResponse] = useState(null); const updateCat = useCallback( @@ -226,7 +223,6 @@ export default function Send() { - setResponse(null)} diff --git a/src/pages/ViewOffer.tsx b/src/pages/ViewOffer.tsx index eb108d8a..964eedd1 100644 --- a/src/pages/ViewOffer.tsx +++ b/src/pages/ViewOffer.tsx @@ -1,7 +1,6 @@ -import { commands, Error, OfferSummary, TakeOfferResponse } from '@/bindings'; +import { commands, OfferSummary, TakeOfferResponse } from '@/bindings'; import ConfirmationDialog from '@/components/ConfirmationDialog'; import Container from '@/components/Container'; -import ErrorDialog from '@/components/ErrorDialog'; import Header from '@/components/Header'; import { OfferCard } from '@/components/OfferCard'; import { Button } from '@/components/ui/button'; @@ -23,7 +22,6 @@ export function ViewOffer() { const [summary, setSummary] = useState(null); const [response, setResponse] = useState(null); - const [error, setError] = useState(null); const [fee, setFee] = useState(''); useEffect(() => { @@ -97,14 +95,6 @@ export function ViewOffer() {
- - { - setError(error); - if (error === null) navigate('/offers'); - }} - /> (null); - const [error, setError] = useState(null); useEffect(() => { if (!offerId) return; @@ -29,17 +25,7 @@ export function ViewSavedOffer() { <>
- - {record && } - - { - setError(error); - if (error === null) navigate('/offers'); - }} - /> - + {record && } ); }