From 8a0fe3b4e88b89e05827b89f4fb9a66b13b01368 Mon Sep 17 00:00:00 2001 From: Don Kackman Date: Sun, 5 Apr 2026 15:07:34 -0500 Subject: [PATCH 1/4] read only mode for cold wallets --- src/components/ClawbackCoinsCard.tsx | 8 ++++-- src/components/Layout.tsx | 8 +++++- src/components/NftCard.tsx | 11 ++++--- src/components/OwnedCoinsCard.tsx | 6 ++-- src/components/ReadOnlyBanner.tsx | 24 ++++++++++++++++ src/components/ReadOnlyButton.tsx | 43 ++++++++++++++++++++++++++++ src/components/TokenCard.tsx | 19 +++++++----- src/contexts/WalletContext.tsx | 5 +++- src/pages/DidList.tsx | 13 +++++---- src/pages/NftList.tsx | 6 ++-- src/pages/Offers.tsx | 13 +++++---- src/pages/OptionList.tsx | 5 ++-- src/pages/Swap.tsx | 3 ++ src/pages/TokenList.tsx | 6 ++-- 14 files changed, 134 insertions(+), 36 deletions(-) create mode 100644 src/components/ReadOnlyBanner.tsx create mode 100644 src/components/ReadOnlyButton.tsx diff --git a/src/components/ClawbackCoinsCard.tsx b/src/components/ClawbackCoinsCard.tsx index 4bfdfd269..058e49751 100644 --- a/src/components/ClawbackCoinsCard.tsx +++ b/src/components/ClawbackCoinsCard.tsx @@ -20,6 +20,7 @@ import { import { useErrors } from '@/hooks/useErrors'; import { amount } from '@/lib/formTypes'; import { toMojos } from '@/lib/utils'; +import { useWallet } from '@/contexts/WalletContext'; import { useWalletState } from '@/state'; import { zodResolver } from '@hookform/resolvers/zod'; import { t } from '@lingui/core/macro'; @@ -61,6 +62,7 @@ export function ClawbackCoinsCard({ setSelectedCoins, }: ClawbackCoinsCardProps) { const walletState = useWalletState(); + const { isReadOnly } = useWallet(); const { addError } = useErrors(); @@ -315,7 +317,7 @@ export function ClawbackCoinsCard({ <> + + + + Not available for read-only wallets + + + ); + } + + return ( + + ); +} diff --git a/src/components/TokenCard.tsx b/src/components/TokenCard.tsx index ca530b8d6..9e6bd0690 100644 --- a/src/components/TokenCard.tsx +++ b/src/components/TokenCard.tsx @@ -1,5 +1,6 @@ import { NumberFormat } from '@/components/NumberFormat'; import { QRCodeDialog } from '@/components/QRCodeDialog'; +import { ReadOnlyButton } from '@/components/ReadOnlyButton'; import { ReceiveAddress } from '@/components/ReceiveAddress'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader } from '@/components/ui/card'; @@ -37,7 +38,7 @@ import { Send, } from 'lucide-react'; import { useState } from 'react'; -import { Link } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; import { TokenRecord } from '../bindings'; import { AssetIcon } from './AssetIcon'; @@ -60,6 +61,7 @@ export function TokenCard({ onUpdate, }: TokenCardProps) { const walletState = useWalletState(); + const navigate = useNavigate(); const [isEditOpen, setIsEditOpen] = useState(false); const [isReceiveOpen, setIsReceiveOpen] = useState(false); @@ -134,12 +136,15 @@ export function TokenCard({
- - - + + navigate(`/wallet/send/${asset.asset_id ?? 'xch'}`) + } + > + + {hasHiddenDids && (
@@ -118,6 +120,7 @@ interface ProfileProps { function Profile({ did, updateDids }: ProfileProps) { const { addError } = useErrors(); + const { isReadOnly } = useWallet(); const walletState = useWalletState(); @@ -232,7 +235,7 @@ function Profile({ did, updateDids }: ProfileProps) { e.stopPropagation(); setTransferOpen(true); }} - disabled={did.created_height === null} + disabled={did.created_height === null || isReadOnly} > @@ -247,7 +250,7 @@ function Profile({ did, updateDids }: ProfileProps) { e.stopPropagation(); setNormalizeOpen(true); }} - disabled={did.created_height === null} + disabled={did.created_height === null || isReadOnly} > @@ -262,7 +265,7 @@ function Profile({ did, updateDids }: ProfileProps) { e.stopPropagation(); setBurnOpen(true); }} - disabled={did.created_height === null} + disabled={did.created_height === null || isReadOnly} > diff --git a/src/pages/NftList.tsx b/src/pages/NftList.tsx index 509ec5b72..74eaccd85 100644 --- a/src/pages/NftList.tsx +++ b/src/pages/NftList.tsx @@ -6,7 +6,7 @@ import { NftOptions } from '@/components/NftOptions'; import { NftPageTitle } from '@/components/NftPageTitle'; import { Pagination } from '@/components/Pagination'; import { ReceiveAddress } from '@/components/ReceiveAddress'; -import { Button } from '@/components/ui/button'; +import { ReadOnlyButton } from '@/components/ReadOnlyButton'; import { useErrors } from '@/hooks/useErrors'; import { useIntersectionObserver } from '@/hooks/useIntersectionObserver'; import { useNftData } from '@/hooks/useNftData'; @@ -131,13 +131,13 @@ export function NftList() { - +
- - - + navigate('/offers/make', { replace: true })} + > + Create Offer +
diff --git a/src/pages/OptionList.tsx b/src/pages/OptionList.tsx index 45a152fd0..0ae2ffc1e 100644 --- a/src/pages/OptionList.tsx +++ b/src/pages/OptionList.tsx @@ -6,6 +6,7 @@ import { OptionListView } from '@/components/OptionListView'; import { OptionOptions } from '@/components/OptionOptions'; import { ReceiveAddress } from '@/components/ReceiveAddress'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { ReadOnlyButton } from '@/components/ReadOnlyButton'; import { Button } from '@/components/ui/button'; import { CustomError } from '@/contexts/ErrorContext'; import { useErrors } from '@/hooks/useErrors'; @@ -84,14 +85,14 @@ export function OptionList() {
- + + Date: Sun, 5 Apr 2026 17:18:35 -0500 Subject: [PATCH 2/4] make banner generic --- src/components/Banner.tsx | 32 +++++++++++++++++++++++++++++++ src/components/Layout.tsx | 20 ++++++++++++++----- src/components/ReadOnlyBanner.tsx | 24 ----------------------- 3 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 src/components/Banner.tsx delete mode 100644 src/components/ReadOnlyBanner.tsx diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx new file mode 100644 index 000000000..0590168a6 --- /dev/null +++ b/src/components/Banner.tsx @@ -0,0 +1,32 @@ +import { ReactNode } from 'react'; +import { cn } from '@/lib/utils'; + +interface BannerProps { + message: string; + icon?: ReactNode; + 'aria-label'?: string; + className?: string; +} + +export function Banner({ + message, + icon, + className, + 'aria-label': ariaLabel, +}: BannerProps) { + return ( +
+ {icon ? icon : null} + {message} +
+ ); +} diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 8606b0941..d80346b81 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -8,14 +8,14 @@ import { import { useInsets } from '@/contexts/SafeAreaContext'; import { useWallet } from '@/contexts/WalletContext'; import { t } from '@lingui/core/macro'; -import { PanelLeft, PanelLeftClose } from 'lucide-react'; +import { PanelLeft, PanelLeftClose, Snowflake } from 'lucide-react'; import { PropsWithChildren } from 'react'; import { useLocation } from 'react-router-dom'; import { Insets } from 'tauri-plugin-safe-area-insets'; import { useTheme } from 'theme-o-rama'; import { useLocalStorage } from 'usehooks-ts'; import { BottomNav, TopNav } from './Nav'; -import { ReadOnlyBanner } from './ReadOnlyBanner'; +import { Banner } from './Banner'; import { WalletSwitcher } from './WalletSwitcher'; function WalletTransitionWrapper({ @@ -23,7 +23,7 @@ function WalletTransitionWrapper({ props, insets, }: PropsWithChildren<{ props: LayoutProps; insets: Insets }>) { - const { isSwitching, wallet } = useWallet(); + const { isSwitching, wallet, isReadOnly } = useWallet(); // Only show content if we have a wallet or we're not switching // This prevents old wallet data from showing during transition @@ -46,7 +46,18 @@ function WalletTransitionWrapper({ > {shouldShow ? ( <> - + {isReadOnly && ( +