Fix wallets error handling order (#2286)

This commit is contained in:
ekzyis 2025-07-16 21:50:08 +02:00 committed by GitHub
parent 2ee685d5a9
commit 5502d29d7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 59 additions and 64 deletions

View File

@ -1,6 +1,6 @@
import { getGetServerSideProps } from '@/api/ssrApollo' import { getGetServerSideProps } from '@/api/ssrApollo'
import { Button } from 'react-bootstrap' import { Button } from 'react-bootstrap'
import { useWallets, useTemplates, DndProvider, Status, useStatus } from '@/wallets/client/context' import { useWallets, useTemplates, DndProvider, KeyStatus, useWalletsLoading, useKeyError, useWalletsError } from '@/wallets/client/context'
import { WalletCard, WalletLayout, WalletLayoutHeader, WalletLayoutLink, WalletLayoutSubHeader } from '@/wallets/client/components' import { WalletCard, WalletLayout, WalletLayoutHeader, WalletLayoutLink, WalletLayoutSubHeader } from '@/wallets/client/components'
import styles from '@/styles/wallet.module.css' import styles from '@/styles/wallet.module.css'
import { usePassphrasePrompt, useShowPassphrase, useSetWalletPriorities } from '@/wallets/client/hooks' import { usePassphrasePrompt, useShowPassphrase, useSetWalletPriorities } from '@/wallets/client/hooks'
@ -13,7 +13,9 @@ export const getServerSideProps = getGetServerSideProps({ authRequired: true })
export default function Wallet () { export default function Wallet () {
const wallets = useWallets() const wallets = useWallets()
const status = useStatus() const walletsLoading = useWalletsLoading()
const walletsError = useWalletsError()
const keyError = useKeyError()
const [showWallets, setShowWallets] = useState(false) const [showWallets, setShowWallets] = useState(false)
const templates = useTemplates() const templates = useTemplates()
const showPassphrase = useShowPassphrase() const showPassphrase = useShowPassphrase()
@ -29,18 +31,20 @@ export default function Wallet () {
} }
}, [wallets, templates, searchFilter]) }, [wallets, templates, searchFilter])
if (status === Status.LOADING_WALLETS) { if (keyError === KeyStatus.KEY_STORAGE_UNAVAILABLE) {
return ( return (
<WalletLayout> <WalletLayout>
<div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1 text-muted'> <div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1'>
<Moon className='spin fill-grey' height={28} width={28} /> <span className='text-muted fw-bold my-1'>wallets unavailable</span>
<small className='d-block mt-3 text-muted'>loading wallets</small> <small className='d-block text-muted'>
this device does not support storage of cryptographic keys via IndexedDB
</small>
</div> </div>
</WalletLayout> </WalletLayout>
) )
} }
if (status === Status.PASSPHRASE_REQUIRED) { if (keyError === KeyStatus.WRONG_KEY) {
return ( return (
<WalletLayout> <WalletLayout>
<div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1'> <div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1'>
@ -55,33 +59,31 @@ export default function Wallet () {
) )
} }
if (status === Status.WALLETS_UNAVAILABLE) { if (walletsError) {
return (
<WalletLayout>
<div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1'>
<span className='text-muted fw-bold my-1'>wallets unavailable</span>
<small className='d-block text-muted'>
this device does not support storage of cryptographic keys via IndexedDB
</small>
</div>
</WalletLayout>
)
}
if (status instanceof Error) {
return ( return (
<WalletLayout> <WalletLayout>
<div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1'> <div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1'>
<span className='text-muted fw-bold my-1'>failed to load wallets</span> <span className='text-muted fw-bold my-1'>failed to load wallets</span>
<small className='d-block text-muted'> <small className='d-block text-muted'>
{status.message} {walletsError.message}
</small> </small>
</div> </div>
</WalletLayout> </WalletLayout>
) )
} }
if (status === Status.NO_WALLETS && !showWallets) { if (walletsLoading) {
return (
<WalletLayout>
<div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1 text-muted'>
<Moon className='spin fill-grey' height={28} width={28} />
<small className='d-block mt-3 text-muted'>loading wallets</small>
</div>
</WalletLayout>
)
}
if (wallets.length === 0 && !showWallets) {
return ( return (
<WalletLayout> <WalletLayout>
<div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1'> <div className='py-5 text-center d-flex flex-column align-items-center justify-content-center flex-grow-1'>

View File

@ -9,7 +9,7 @@ import {
useWalletMigrationMutation, CryptoKeyRequiredError, useIsWrongKey useWalletMigrationMutation, CryptoKeyRequiredError, useIsWrongKey
} from '@/wallets/client/hooks' } from '@/wallets/client/hooks'
import { WalletConfigurationError } from '@/wallets/client/errors' import { WalletConfigurationError } from '@/wallets/client/errors'
import { SET_WALLETS, WRONG_KEY, KEY_MATCH, NO_KEY, useWalletsDispatch, WALLETS_QUERY_ERROR } from '@/wallets/client/context' import { SET_WALLETS, WRONG_KEY, KEY_MATCH, useWalletsDispatch, WALLETS_QUERY_ERROR, KEY_STORAGE_UNAVAILABLE } from '@/wallets/client/context'
import { useIndexedDB } from '@/components/use-indexeddb' import { useIndexedDB } from '@/components/use-indexeddb'
export function useServerWallets () { export function useServerWallets () {
@ -110,7 +110,7 @@ export function useKeyInit () {
useEffect(() => { useEffect(() => {
if (typeof window.indexedDB === 'undefined') { if (typeof window.indexedDB === 'undefined') {
dispatch({ type: NO_KEY }) dispatch({ type: KEY_STORAGE_UNAVAILABLE })
} else if (wrongKey) { } else if (wrongKey) {
dispatch({ type: WRONG_KEY }) dispatch({ type: WRONG_KEY })
} else { } else {

View File

@ -1,5 +1,5 @@
import { createContext, useContext, useReducer } from 'react' import { createContext, useContext, useReducer } from 'react'
import walletsReducer, { Status } from './reducer' import walletsReducer from './reducer'
import { useServerWallets, useKeyCheck, useAutomatedRetries, useKeyInit, useWalletMigration } from './hooks' import { useServerWallets, useKeyCheck, useAutomatedRetries, useKeyInit, useWalletMigration } from './hooks'
import { WebLnProvider } from '@/wallets/lib/protocols/webln' import { WebLnProvider } from '@/wallets/lib/protocols/webln'
@ -17,14 +17,14 @@ export function useTemplates () {
return templates return templates
} }
export function useLoading () { export function useWalletsLoading () {
const { status } = useContext(WalletsContext) const { walletsLoading } = useContext(WalletsContext)
return status === Status.LOADING_WALLETS return walletsLoading
} }
export function useStatus () { export function useWalletsError () {
const { status } = useContext(WalletsContext) const { walletsError } = useContext(WalletsContext)
return status return walletsError
} }
export function useWalletsDispatch () { export function useWalletsDispatch () {
@ -41,13 +41,20 @@ export function useKeyHash () {
return keyHash return keyHash
} }
export function useKeyError () {
const { keyError } = useContext(WalletsContext)
return keyError
}
export default function WalletsProvider ({ children }) { export default function WalletsProvider ({ children }) {
const [state, dispatch] = useReducer(walletsReducer, { const [state, dispatch] = useReducer(walletsReducer, {
status: Status.LOADING_WALLETS,
wallets: [], wallets: [],
walletsLoading: true,
walletsError: null,
templates: [], templates: [],
key: null, key: null,
keyHash: null keyHash: null,
keyError: null
}) })
return ( return (

View File

@ -1,12 +1,9 @@
import { isTemplate, isWallet } from '@/wallets/lib/util' import { isTemplate, isWallet } from '@/wallets/lib/util'
// states that dictate if we show a button or wallets on the wallets page export const KeyStatus = {
export const Status = { KEY_MATCH: 'KEY_MATCH',
LOADING_WALLETS: 'LOADING_WALLETS', NO_KEY: 'NO_KEY',
NO_WALLETS: 'NO_WALLETS', WRONG_KEY: 'WRONG_KEY'
HAS_WALLETS: 'HAS_WALLETS',
PASSPHRASE_REQUIRED: 'PASSPHRASE_REQUIRED',
WALLETS_UNAVAILABLE: 'WALLETS_UNAVAILABLE'
} }
// wallet actions // wallet actions
@ -14,7 +11,7 @@ export const SET_WALLETS = 'SET_WALLETS'
export const SET_KEY = 'SET_KEY' export const SET_KEY = 'SET_KEY'
export const WRONG_KEY = 'WRONG_KEY' export const WRONG_KEY = 'WRONG_KEY'
export const KEY_MATCH = 'KEY_MATCH' export const KEY_MATCH = 'KEY_MATCH'
export const NO_KEY = 'KEY_UNAVAILABLE' export const KEY_STORAGE_UNAVAILABLE = 'KEY_STORAGE_UNAVAILABLE'
export const WALLETS_QUERY_ERROR = 'WALLETS_QUERY_ERROR' export const WALLETS_QUERY_ERROR = 'WALLETS_QUERY_ERROR'
export default function reducer (state, action) { export default function reducer (state, action) {
@ -28,7 +25,8 @@ export default function reducer (state, action) {
.sort((a, b) => a.name.localeCompare(b.name)) .sort((a, b) => a.name.localeCompare(b.name))
return { return {
...state, ...state,
status: transitionStatus(action, state, wallets.length > 0 ? Status.HAS_WALLETS : Status.NO_WALLETS), walletsLoading: false,
walletsError: null,
wallets, wallets,
templates templates
} }
@ -36,7 +34,8 @@ export default function reducer (state, action) {
case WALLETS_QUERY_ERROR: case WALLETS_QUERY_ERROR:
return { return {
...state, ...state,
status: transitionStatus(action, state, action.error) walletsLoading: false,
walletsError: action.error
} }
case SET_KEY: case SET_KEY:
return { return {
@ -47,32 +46,19 @@ export default function reducer (state, action) {
case WRONG_KEY: case WRONG_KEY:
return { return {
...state, ...state,
status: transitionStatus(action, state, Status.PASSPHRASE_REQUIRED) keyError: KeyStatus.WRONG_KEY
} }
case KEY_MATCH: case KEY_MATCH:
return { return {
...state, ...state,
status: transitionStatus(action, state, state.wallets.length > 0 ? Status.HAS_WALLETS : Status.NO_WALLETS) keyError: null
} }
case NO_KEY: case KEY_STORAGE_UNAVAILABLE:
return { return {
...state, ...state,
status: transitionStatus(action, state, Status.WALLETS_UNAVAILABLE) keyError: KeyStatus.KEY_STORAGE_UNAVAILABLE
} }
default: default:
return state return state
} }
} }
function transitionStatus ({ type }, { status: from }, to) {
switch (type) {
case SET_WALLETS: {
return (from instanceof Error || [Status.PASSPHRASE_REQUIRED, Status.WALLETS_UNAVAILABLE].includes(from)) ? from : to
}
case KEY_MATCH: {
return (from instanceof Error || from === Status.LOADING_WALLETS) ? from : to
}
default:
return to
}
}

View File

@ -1,7 +1,7 @@
import { useWallets, useLoading } from '@/wallets/client/context' import { useWallets, useWalletsLoading } from '@/wallets/client/context'
export function useWalletIndicator () { export function useWalletIndicator () {
const wallets = useWallets() const wallets = useWallets()
const loading = useLoading() const loading = useWalletsLoading()
return !loading && wallets.length === 0 return !loading && wallets.length === 0
} }

View File

@ -32,7 +32,7 @@ import { timeoutSignal } from '@/lib/time'
import { WALLET_SEND_PAYMENT_TIMEOUT_MS } from '@/lib/constants' import { WALLET_SEND_PAYMENT_TIMEOUT_MS } from '@/lib/constants'
import { useToast } from '@/components/toast' import { useToast } from '@/components/toast'
import { useMe } from '@/components/me' import { useMe } from '@/components/me'
import { useWallets, useLoading as useWalletsLoading } from '@/wallets/client/context' import { useWallets, useWalletsLoading } from '@/wallets/client/context'
export function useWalletsQuery () { export function useWalletsQuery () {
const { me } = useMe() const { me } = useMe()