pages load *kazoo*
This commit is contained in:
parent
da020cf899
commit
48640cbed6
|
@ -54,10 +54,9 @@ export default {
|
|||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
txs.push(models.vaultEntry.upsert({
|
||||
where: { userId: me.id, key: entry.key },
|
||||
update: { key: entry.key, value: entry.value },
|
||||
create: { key: entry.key, value: entry.value, userId: me.id, walletId: entry.walletId }
|
||||
txs.push(models.vaultEntry.update({
|
||||
where: { id: entry.id },
|
||||
data: { key: entry.key, value: entry.value }
|
||||
}))
|
||||
}
|
||||
await models.prisma.$transaction(txs)
|
||||
|
|
|
@ -19,7 +19,8 @@ import assertApiKeyNotPermitted from './apiKey'
|
|||
import { bolt11Tags } from '@/lib/bolt11'
|
||||
import { finalizeHodlInvoice } from 'worker/wallet'
|
||||
import walletDefs from 'wallets/server'
|
||||
import { generateResolverName, generateTypeDefName, isConfigured } from '@/lib/wallet'
|
||||
import { generateResolverName, generateTypeDefName } from '@/wallets/graphql'
|
||||
import { isConfigured } from '@/wallets/common'
|
||||
import { lnAddrOptions } from '@/lib/lnurl'
|
||||
import { GqlAuthenticationError, GqlAuthorizationError, GqlInputError } from '@/lib/error'
|
||||
import { getNodeSockets, getOurPubkey } from '../lnd'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { gql } from 'graphql-tag'
|
||||
import { fieldToGqlArg, fieldToGqlArgOptional, generateResolverName, generateTypeDefName, isServerField } from '@/lib/wallet'
|
||||
|
||||
import { fieldToGqlArg, fieldToGqlArgOptional, generateResolverName, generateTypeDefName } from '@/wallets/graphql'
|
||||
import { isServerField } from '@/wallets/common'
|
||||
import walletDefs from 'wallets/server'
|
||||
|
||||
function injectTypeDefs (typeDefs) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'
|
|||
import { isNumber } from '@/lib/validate'
|
||||
import { useIsClient } from './use-client'
|
||||
import Link from 'next/link'
|
||||
import { isConfigured } from '@/wallets/common'
|
||||
|
||||
function autoWithdrawThreshold ({ me }) {
|
||||
return isNumber(me?.privates?.autoWithdrawThreshold) ? me?.privates?.autoWithdrawThreshold : 10000
|
||||
|
@ -33,7 +34,7 @@ export function AutowithdrawSettings ({ wallet }) {
|
|||
return (
|
||||
<>
|
||||
<Checkbox
|
||||
disabled={isClient && !wallet.isConfigured}
|
||||
disabled={isClient && !isConfigured(wallet)}
|
||||
label='enabled'
|
||||
id='enabled'
|
||||
name='enabled'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useMe } from './me'
|
||||
import { useShowModal } from './modal'
|
||||
import useVault, { useVaultConfigurator, useVaultMigration } from './vault/use-vault'
|
||||
import { useVaultConfigurator } from './vault/use-vault-configurator'
|
||||
import { Button, InputGroup } from 'react-bootstrap'
|
||||
import { Form, Input, PasswordInput, SubmitButton } from './form'
|
||||
import bip39Words from '@/lib/bip39-words'
|
||||
|
@ -21,9 +21,6 @@ export default function DeviceSync () {
|
|||
const enabled = !!me?.privates?.vaultKeyHash
|
||||
const connected = !!value?.key
|
||||
|
||||
const migrate = useVaultMigration()
|
||||
const [debugValue, setDebugValue, clearValue] = useVault(me, 'debug')
|
||||
|
||||
const manage = useCallback(async () => {
|
||||
if (enabled && connected) {
|
||||
showModal((onClose) => (
|
||||
|
@ -55,7 +52,7 @@ export default function DeviceSync () {
|
|||
<ConnectForm onClose={onClose} onConnect={onConnect} enabled={enabled} />
|
||||
))
|
||||
}
|
||||
}, [migrate, enabled, connected, value])
|
||||
}, [enabled, connected, value])
|
||||
|
||||
const reset = useCallback(async () => {
|
||||
const schema = yup.object().shape({
|
||||
|
@ -106,7 +103,6 @@ export default function DeviceSync () {
|
|||
if (values.passphrase) {
|
||||
try {
|
||||
await setVaultKey(values.passphrase)
|
||||
await migrate()
|
||||
apollo.cache.evict({ fieldName: 'BestWallets' })
|
||||
apollo.cache.gc()
|
||||
await apollo.refetchQueries({ include: ['BestWallets'] })
|
||||
|
@ -115,7 +111,7 @@ export default function DeviceSync () {
|
|||
throw e
|
||||
}
|
||||
}
|
||||
}, [setVaultKey, migrate])
|
||||
}, [setVaultKey])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -139,33 +135,6 @@ export default function DeviceSync () {
|
|||
</p>
|
||||
</Info>
|
||||
</div>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
try {
|
||||
const v = window.prompt('Enter debug value', debugValue)
|
||||
|
||||
await setDebugValue(v)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Set value
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
clearValue()
|
||||
}}
|
||||
>
|
||||
Clear value
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.alert(debugValue)
|
||||
}}
|
||||
>
|
||||
Show value
|
||||
</Button>
|
||||
{enabled && !connected && (
|
||||
<div className='mt-2 d-flex align-items-center'>
|
||||
<div>
|
||||
|
|
|
@ -13,7 +13,7 @@ import { usePaidMutation } from './use-paid-mutation'
|
|||
import { ACT_MUTATION } from '@/fragments/paidAction'
|
||||
import { meAnonSats } from '@/lib/apollo'
|
||||
import { BoostItemInput } from './adv-post-form'
|
||||
import { useWallet } from '../wallets/common'
|
||||
import { useWallet } from '@/wallets/index'
|
||||
|
||||
const defaultTips = [100, 1000, 10_000, 100_000]
|
||||
|
||||
|
|
|
@ -22,10 +22,9 @@ import SearchIcon from '../../svgs/search-line.svg'
|
|||
import classNames from 'classnames'
|
||||
import SnIcon from '@/svgs/sn.svg'
|
||||
import { useHasNewNotes } from '../use-has-new-notes'
|
||||
import { useWallets } from '@/wallets/common'
|
||||
import { useWallets } from '@/wallets/index'
|
||||
import SwitchAccountList, { useAccounts } from '@/components/account'
|
||||
import { useShowModal } from '@/components/modal'
|
||||
import { unsetLocalKey as resetVaultKey } from '@/components/use-vault'
|
||||
|
||||
export function Brand ({ className }) {
|
||||
return (
|
||||
|
@ -266,7 +265,6 @@ function LogoutObstacle ({ onClose }) {
|
|||
const { registration: swRegistration, togglePushSubscription } = useServiceWorker()
|
||||
const wallets = useWallets()
|
||||
const { multiAuthSignout } = useAccounts()
|
||||
const { me } = useMe()
|
||||
|
||||
return (
|
||||
<div className='d-flex m-auto flex-column w-fit-content'>
|
||||
|
@ -295,7 +293,6 @@ function LogoutObstacle ({ onClose }) {
|
|||
}
|
||||
|
||||
await wallets.resetClient().catch(console.error)
|
||||
await resetVaultKey(me?.id)
|
||||
|
||||
await signOut({ callbackUrl: '/' })
|
||||
}}
|
||||
|
|
|
@ -1,35 +1,13 @@
|
|||
import { useCallback, useMemo } from 'react'
|
||||
import { useMe } from './me'
|
||||
import { gql, useApolloClient, useMutation } from '@apollo/client'
|
||||
import { useWallet } from '@/wallets/common'
|
||||
import { useWallet } from '@/wallets/index'
|
||||
import { FAST_POLL_INTERVAL, JIT_INVOICE_TIMEOUT_MS } from '@/lib/constants'
|
||||
import { INVOICE } from '@/fragments/wallet'
|
||||
import Invoice from '@/components/invoice'
|
||||
import { useFeeButton } from './fee-button'
|
||||
import { useShowModal } from './modal'
|
||||
|
||||
export class InvoiceCanceledError extends Error {
|
||||
constructor (hash, actionError) {
|
||||
super(actionError ?? `invoice canceled: ${hash}`)
|
||||
this.name = 'InvoiceCanceledError'
|
||||
this.hash = hash
|
||||
this.actionError = actionError
|
||||
}
|
||||
}
|
||||
|
||||
export class NoAttachedWalletError extends Error {
|
||||
constructor () {
|
||||
super('no attached wallet found')
|
||||
this.name = 'NoAttachedWalletError'
|
||||
}
|
||||
}
|
||||
|
||||
export class InvoiceExpiredError extends Error {
|
||||
constructor (hash) {
|
||||
super(`invoice expired: ${hash}`)
|
||||
this.name = 'InvoiceExpiredError'
|
||||
}
|
||||
}
|
||||
import { InvoiceCanceledError, NoAttachedWalletError, InvoiceExpiredError } from '@/wallets/errors'
|
||||
|
||||
export const useInvoice = () => {
|
||||
const client = useApolloClient()
|
||||
|
|
|
@ -2,7 +2,7 @@ import { QRCodeSVG } from 'qrcode.react'
|
|||
import { CopyInput, InputSkeleton } from './form'
|
||||
import InvoiceStatus from './invoice-status'
|
||||
import { useEffect } from 'react'
|
||||
import { useWallet } from '@/wallets/common'
|
||||
import { useWallet } from '@/wallets/index'
|
||||
import Bolt11Info from './bolt11-info'
|
||||
|
||||
export default function Qr ({ asIs, value, useWallet: automated, statusVariant, description, status }) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||
|
||||
export function getDbName (userId) {
|
||||
return `app:storage${userId ? `:${userId}` : ''}`
|
||||
export function getDbName (userId, name) {
|
||||
return `app:storage:${userId ?? ''}${name ? `:${name}` : ''}`
|
||||
}
|
||||
|
||||
function useIndexedDB ({ dbName, storeName, options = { keyPath: 'id', autoIncrement: true }, indices = [], version = 1 }) {
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { UPDATE_VAULT_KEY } from '@/fragments/users'
|
||||
import { useMutation, useQuery } from '@apollo/client'
|
||||
import { useMe } from '../me'
|
||||
import { useToast } from '../toast'
|
||||
import useIndexedDB, { getDbName } from '../use-indexeddb'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { E_VAULT_KEY_EXISTS } from '@/lib/error'
|
||||
import { CLEAR_VAULT, GET_VAULT_ENTRIES } from '@/fragments/vault'
|
||||
import { CLEAR_VAULT, GET_VAULT_ENTRIES, UPDATE_VAULT_KEY } from '@/fragments/vault'
|
||||
import { toHex } from '@/lib/hex'
|
||||
import { decryptData, encryptData } from './use-vault'
|
||||
|
||||
|
@ -22,7 +21,7 @@ const useImperativeQuery = (query) => {
|
|||
export function useVaultConfigurator () {
|
||||
const { me } = useMe()
|
||||
const toaster = useToast()
|
||||
const { set, get, remove } = useIndexedDB({ dbName: getDbName(me?.id), storeName: 'vault' })
|
||||
const { set, get, remove } = useIndexedDB({ dbName: getDbName(me?.id, 'vault'), storeName: 'vault' })
|
||||
const [updateVaultKey] = useMutation(UPDATE_VAULT_KEY)
|
||||
const getVaultEntries = useImperativeQuery(GET_VAULT_ENTRIES)
|
||||
const [key, setKey] = useState(null)
|
||||
|
@ -44,10 +43,10 @@ export function useVaultConfigurator () {
|
|||
}
|
||||
setKey(localVaultKey)
|
||||
} catch (e) {
|
||||
toaster.danger('error loading vault configuration ' + e.message)
|
||||
// toaster?.danger('error loading vault configuration ' + e.message)
|
||||
}
|
||||
})()
|
||||
}, [me?.privates?.vaultKeyHash, keyHash, get, remove])
|
||||
}, [me?.privates?.vaultKeyHash, keyHash, get, remove, toaster])
|
||||
|
||||
// clear vault: remove everything and reset the key
|
||||
const [clearVault] = useMutation(CLEAR_VAULT, {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Button } from 'react-bootstrap'
|
||||
import CancelButton from './cancel-button'
|
||||
import { SubmitButton } from './form'
|
||||
import { isConfigured } from '@/wallets/common'
|
||||
|
||||
export default function WalletButtonBar ({
|
||||
wallet, disable,
|
||||
|
@ -10,12 +11,12 @@ export default function WalletButtonBar ({
|
|||
return (
|
||||
<div className={`mt-3 ${className}`}>
|
||||
<div className='d-flex justify-content-between'>
|
||||
{wallet.hasConfig && wallet.isConfigured &&
|
||||
{isConfigured(wallet) &&
|
||||
<Button onClick={onDelete} variant='grey-medium'>{deleteText}</Button>}
|
||||
{children}
|
||||
<div className='d-flex align-items-center ms-auto'>
|
||||
{hasCancel && <CancelButton onClick={onCancel} />}
|
||||
<SubmitButton variant='primary' disabled={disable}>{wallet.isConfigured ? editText : createText}</SubmitButton>
|
||||
<SubmitButton variant='primary' disabled={disable}>{isConfigured(wallet) ? editText : createText}</SubmitButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,26 +3,18 @@ import styles from '@/styles/wallet.module.css'
|
|||
import Plug from '@/svgs/plug.svg'
|
||||
import Gear from '@/svgs/settings-5-fill.svg'
|
||||
import Link from 'next/link'
|
||||
import { Status } from '@/wallets/common'
|
||||
import { Status, isConfigured } from '@/wallets/common'
|
||||
import DraggableIcon from '@/svgs/draggable.svg'
|
||||
|
||||
export default function WalletCard ({ wallet, draggable, onDragStart, onDragEnter, onDragEnd, onTouchStart, sourceIndex, targetIndex, index }) {
|
||||
const { card: { title, badges } } = wallet
|
||||
const { card: { title, badges } } = wallet.def
|
||||
|
||||
let indicator = styles.disabled
|
||||
switch (wallet.status) {
|
||||
case Status.Enabled:
|
||||
case true:
|
||||
indicator = styles.success
|
||||
break
|
||||
case Status.Locked:
|
||||
indicator = styles.warning
|
||||
break
|
||||
case Status.Error:
|
||||
indicator = styles.error
|
||||
break
|
||||
case Status.Initialized:
|
||||
case false:
|
||||
default:
|
||||
indicator = styles.disabled
|
||||
break
|
||||
}
|
||||
|
@ -57,9 +49,9 @@ export default function WalletCard ({ wallet, draggable, onDragStart, onDragEnte
|
|||
</Badge>)}
|
||||
</Card.Subtitle>
|
||||
</Card.Body>
|
||||
<Link href={`/settings/wallets/${wallet.name}`}>
|
||||
<Link href={`/settings/wallets/${wallet.def.name}`}>
|
||||
<Card.Footer className={styles.attach}>
|
||||
{wallet.isConfigured
|
||||
{isConfigured(wallet)
|
||||
? <>configure<Gear width={14} height={14} /></>
|
||||
: <>attach<Plug width={14} height={14} /></>}
|
||||
</Card.Footer>
|
||||
|
|
|
@ -86,10 +86,14 @@ const INDICES = [
|
|||
{ name: 'wallet_ts', keyPath: ['wallet', 'ts'] }
|
||||
]
|
||||
|
||||
function getWalletLogDbName (userId) {
|
||||
return getDbName(userId)
|
||||
}
|
||||
|
||||
function useWalletLogDB () {
|
||||
const { me } = useMe()
|
||||
const { add, getPage, clear, error, notSupported } = useIndexedDB({
|
||||
dbName: getDbName(me?.id),
|
||||
dbName: getWalletLogDbName(me?.id),
|
||||
storeName: 'wallet_logs',
|
||||
indices: INDICES
|
||||
})
|
||||
|
@ -127,7 +131,7 @@ export function useWalletLogger (wallet, setLogs) {
|
|||
)
|
||||
|
||||
const deleteLogs = useCallback(async (wallet, options) => {
|
||||
if ((!wallet || wallet.walletType) && !options?.clientOnly) {
|
||||
if ((!wallet || wallet.def.walletType) && !options?.clientOnly) {
|
||||
await deleteServerWalletLogs({ variables: { wallet: wallet?.walletType } })
|
||||
}
|
||||
if (!wallet || wallet.sendPayment) {
|
||||
|
@ -190,7 +194,7 @@ export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) {
|
|||
|
||||
result = await getPage(page, pageSize, indexName, query, 'prev')
|
||||
// no walletType means we're using the local IDB
|
||||
if (wallet && !wallet.walletType) {
|
||||
if (wallet && !wallet.def.walletType) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ import dynamic from 'next/dynamic'
|
|||
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
|
||||
import { WebLnProvider } from '@/wallets/webln/client'
|
||||
import { AccountProvider } from '@/components/account'
|
||||
import { WalletProvider } from '@/wallets/common'
|
||||
import { WalletsProvider } from '@/wallets/index'
|
||||
|
||||
const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false })
|
||||
|
||||
|
@ -105,7 +105,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
|||
<PlausibleProvider domain='stacker.news' trackOutboundLinks>
|
||||
<ApolloProvider client={client}>
|
||||
<MeProvider me={me}>
|
||||
<WalletProvider>
|
||||
<WalletsProvider>
|
||||
<HasNewNotesProvider>
|
||||
<LoggerProvider>
|
||||
<WebLnProvider>
|
||||
|
@ -132,7 +132,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
|
|||
</WebLnProvider>
|
||||
</LoggerProvider>
|
||||
</HasNewNotesProvider>
|
||||
</WalletProvider>
|
||||
</WalletsProvider>
|
||||
</MeProvider>
|
||||
</ApolloProvider>
|
||||
</PlausibleProvider>
|
||||
|
|
|
@ -5,14 +5,13 @@ import { WalletSecurityBanner } from '@/components/banners'
|
|||
import { WalletLogs } from '@/components/wallet-logger'
|
||||
import { useToast } from '@/components/toast'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useWallet } from '@/wallets/common'
|
||||
import { useWallet } from '@/wallets/index'
|
||||
import Info from '@/components/info'
|
||||
import Text from '@/components/text'
|
||||
import { AutowithdrawSettings } from '@/components/autowithdraw-shared'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useIsClient } from '@/components/use-client'
|
||||
|
||||
const WalletButtonBar = dynamic(() => import('@/components/wallet-buttonbar.js'), { ssr: false })
|
||||
import { isConfigured } from '@/wallets/common'
|
||||
import { SSR } from '@/lib/constants'
|
||||
import WalletButtonBar from '@/components/wallet-buttonbar'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||
|
||||
|
@ -22,7 +21,7 @@ export default function WalletSettings () {
|
|||
const { wallet: name } = router.query
|
||||
const wallet = useWallet(name)
|
||||
|
||||
const initial = wallet?.fields.reduce((acc, field) => {
|
||||
const initial = wallet?.def.fields.reduce((acc, field) => {
|
||||
// We still need to run over all wallet fields via reduce
|
||||
// even though we use wallet.config as the initial value
|
||||
// since wallet.config is empty when wallet is not configured.
|
||||
|
@ -41,8 +40,8 @@ export default function WalletSettings () {
|
|||
|
||||
return (
|
||||
<CenterLayout>
|
||||
<h2 className='pb-2'>{wallet?.card?.title}</h2>
|
||||
<h6 className='text-muted text-center pb-3'><Text>{wallet?.card?.subtitle}</Text></h6>
|
||||
<h2 className='pb-2'>{wallet?.def.card.title}</h2>
|
||||
<h6 className='text-muted text-center pb-3'><Text>{wallet?.def.card.subtitle}</Text></h6>
|
||||
{wallet?.canSend && wallet?.hasConfig > 0 && <WalletSecurityBanner />}
|
||||
<Form
|
||||
initial={initial}
|
||||
|
@ -50,7 +49,7 @@ export default function WalletSettings () {
|
|||
{...validateProps}
|
||||
onSubmit={async ({ amount, ...values }) => {
|
||||
try {
|
||||
const newConfig = !wallet?.isConfigured
|
||||
const newConfig = !isConfigured(wallet)
|
||||
|
||||
// enable wallet if wallet was just configured
|
||||
if (newConfig) {
|
||||
|
@ -68,11 +67,11 @@ export default function WalletSettings () {
|
|||
}}
|
||||
>
|
||||
{wallet && <WalletFields wallet={wallet} />}
|
||||
{wallet?.clientOnly
|
||||
{wallet?.def.clientOnly
|
||||
? (
|
||||
<CheckboxGroup name='enabled'>
|
||||
<Checkbox
|
||||
disabled={!wallet?.isConfigured}
|
||||
disabled={!isConfigured(wallet)}
|
||||
label='enabled'
|
||||
name='enabled'
|
||||
groupClassName='mb-0'
|
||||
|
@ -101,16 +100,16 @@ export default function WalletSettings () {
|
|||
)
|
||||
}
|
||||
|
||||
function WalletFields ({ wallet: { config, fields, isConfigured } }) {
|
||||
const isClient = useIsClient()
|
||||
function WalletFields ({ wallet }) {
|
||||
console.log('wallet', wallet)
|
||||
|
||||
return fields
|
||||
return wallet.def.fields
|
||||
.map(({ name, label = '', type, help, optional, editable, clientOnly, serverOnly, ...props }, i) => {
|
||||
const rawProps = {
|
||||
...props,
|
||||
name,
|
||||
initialValue: config?.[name],
|
||||
readOnly: isClient && isConfigured && editable === false && !!config?.[name],
|
||||
initialValue: wallet.config?.[name],
|
||||
readOnly: !SSR && isConfigured(wallet) && editable === false && !!wallet.config?.[name],
|
||||
groupClassName: props.hidden ? 'd-none' : undefined,
|
||||
label: label
|
||||
? (
|
||||
|
|
|
@ -2,12 +2,10 @@ import { getGetServerSideProps } from '@/api/ssrApollo'
|
|||
import Layout from '@/components/layout'
|
||||
import styles from '@/styles/wallet.module.css'
|
||||
import Link from 'next/link'
|
||||
import { useWallets, walletPrioritySort } from '@/wallets/common'
|
||||
import { useWallets } from '@/wallets/index'
|
||||
import { useState } from 'react'
|
||||
import dynamic from 'next/dynamic'
|
||||
import { useIsClient } from '@/components/use-client'
|
||||
|
||||
const WalletCard = dynamic(() => import('@/components/wallet-card'), { ssr: false })
|
||||
import WalletCard from '@/components/wallet-card'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||
|
||||
|
@ -28,7 +26,7 @@ async function reorder (wallets, sourceIndex, targetIndex) {
|
|||
}
|
||||
|
||||
export default function Wallet ({ ssrData }) {
|
||||
const { wallets } = useWallets()
|
||||
const wallets = useWallets()
|
||||
|
||||
const isClient = useIsClient()
|
||||
const [sourceIndex, setSourceIndex] = useState(null)
|
||||
|
@ -76,49 +74,33 @@ export default function Wallet ({ ssrData }) {
|
|||
</Link>
|
||||
</div>
|
||||
<div className={styles.walletGrid} onDragEnd={onDragEnd}>
|
||||
{wallets
|
||||
.sort((w1, w2) => {
|
||||
// enabled/configured wallets always come before disabled/unconfigured wallets
|
||||
if ((w1.enabled && !w2.enabled) || (w1.isConfigured && !w2.isConfigured)) {
|
||||
return -1
|
||||
} else if ((w2.enabled && !w1.enabled) || (w2.isConfigured && !w1.isConfigured)) {
|
||||
return 1
|
||||
}
|
||||
{wallets.map((w, i) => {
|
||||
const draggable = isClient && w.config?.enabled
|
||||
|
||||
return walletPrioritySort(w1, w2)
|
||||
})
|
||||
.map((w, i) => {
|
||||
const draggable = isClient && w.enabled
|
||||
|
||||
return (
|
||||
<div
|
||||
key={w?.name}
|
||||
draggable={draggable}
|
||||
style={{ cursor: draggable ? 'move' : 'default' }}
|
||||
onDragStart={draggable ? onDragStart(i) : undefined}
|
||||
onTouchStart={draggable ? onTouchStart(i) : undefined}
|
||||
onDragEnter={draggable ? onDragEnter(i) : undefined}
|
||||
className={
|
||||
return (
|
||||
<div
|
||||
key={w.def.name}
|
||||
className={
|
||||
!draggable
|
||||
? ''
|
||||
: (`${sourceIndex === i ? styles.drag : ''} ${draggable && targetIndex === i ? styles.drop : ''}`)
|
||||
}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<WalletCard
|
||||
wallet={w}
|
||||
draggable={draggable}
|
||||
onDragStart={draggable ? onDragStart(i) : undefined}
|
||||
onTouchStart={draggable ? onTouchStart(i) : undefined}
|
||||
onDragEnter={draggable ? onDragEnter(i) : undefined}
|
||||
sourceIndex={sourceIndex}
|
||||
targetIndex={targetIndex}
|
||||
index={i}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<WalletCard
|
||||
wallet={w}
|
||||
draggable={draggable}
|
||||
onDragStart={draggable ? onDragStart(i) : undefined}
|
||||
onTouchStart={draggable ? onTouchStart(i) : undefined}
|
||||
onDragEnter={draggable ? onDragEnter(i) : undefined}
|
||||
sourceIndex={sourceIndex}
|
||||
targetIndex={targetIndex}
|
||||
index={i}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import walletDefs from 'wallets/client'
|
||||
|
||||
export const Status = {
|
||||
Initialized: 'Initialized',
|
||||
Enabled: 'Enabled',
|
||||
Locked: 'Locked',
|
||||
Error: 'Error'
|
||||
Disabled: 'Disabled'
|
||||
}
|
||||
|
||||
export function getWalletByName (name) {
|
||||
|
@ -28,13 +26,20 @@ export function getStorageKey (name, me) {
|
|||
}
|
||||
|
||||
export function walletPrioritySort (w1, w2) {
|
||||
const delta = w1.priority - w2.priority
|
||||
// enabled/configured wallets always come before disabled/unconfigured wallets
|
||||
if ((w1.config?.enabled && !w2.config?.enabled) || (isConfigured(w1) && !isConfigured(w2))) {
|
||||
return -1
|
||||
} else if ((w2.config?.enabled && !w1.config?.enabled) || (isConfigured(w2) && !isConfigured(w1))) {
|
||||
return 1
|
||||
}
|
||||
|
||||
const delta = w1.config?.priority - w2.config?.priority
|
||||
// delta is NaN if either priority is undefined
|
||||
if (!Number.isNaN(delta) && delta !== 0) return delta
|
||||
|
||||
// if one wallet has a priority but the other one doesn't, the one with the priority comes first
|
||||
if (w1.priority !== undefined && w2.priority === undefined) return -1
|
||||
if (w1.priority === undefined && w2.priority !== undefined) return 1
|
||||
if (w1.config?.priority !== undefined && w2.config?.priority === undefined) return -1
|
||||
if (w1.config?.priority === undefined && w2.config?.priority !== undefined) return 1
|
||||
|
||||
// both wallets have no priority set, falling back to other methods
|
||||
|
||||
|
@ -43,5 +48,50 @@ export function walletPrioritySort (w1, w2) {
|
|||
if (w1.config?.id && w2.config?.id) return Number(w1.config.id) - Number(w2.config.id)
|
||||
|
||||
// else we will use the card title as tie breaker
|
||||
return w1.card.title < w2.card.title ? -1 : 1
|
||||
return w1.def.card.title < w2.def.card.title ? -1 : 1
|
||||
}
|
||||
|
||||
export function isServerField (f) {
|
||||
return f.serverOnly || !f.clientOnly
|
||||
}
|
||||
|
||||
export function isClientField (f) {
|
||||
return f.clientOnly || !f.serverOnly
|
||||
}
|
||||
|
||||
function checkFields ({ fields, config }) {
|
||||
// a wallet is configured if all of its required fields are set
|
||||
let val = fields.every(f => {
|
||||
return f.optional ? true : !!config?.[f.name]
|
||||
})
|
||||
|
||||
// however, a wallet is not configured if all fields are optional and none are set
|
||||
// since that usually means that one of them is required
|
||||
if (val && fields.length > 0) {
|
||||
val = !(fields.every(f => f.optional) && fields.every(f => !config?.[f.name]))
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
export function isConfigured (wallet) {
|
||||
return isSendConfigured(wallet) || isReceiveConfigured(wallet)
|
||||
}
|
||||
|
||||
function isSendConfigured (wallet) {
|
||||
const fields = wallet.def.fields.filter(isClientField)
|
||||
return checkFields({ fields, config: wallet.config })
|
||||
}
|
||||
|
||||
function isReceiveConfigured (wallet) {
|
||||
const fields = wallet.def.fields.filter(isServerField)
|
||||
return checkFields({ fields, config: wallet.config })
|
||||
}
|
||||
|
||||
export function canSend (wallet) {
|
||||
return !!wallet.def.sendPayment && isSendConfigured(wallet)
|
||||
}
|
||||
|
||||
export function canReceive (wallet) {
|
||||
return !wallet.def.clientOnly && isReceiveConfigured(wallet)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useMe } from '@/components/me'
|
||||
import useVault from '@/components/use-vault'
|
||||
import useVault from '@/components/vault/use-vault'
|
||||
import { useCallback } from 'react'
|
||||
import { getStorageKey } from './common'
|
||||
import { getStorageKey, isClientField, isServerField } from './common'
|
||||
import { useMutation } from '@apollo/client'
|
||||
import { generateMutation } from './graphql'
|
||||
import { REMOVE_WALLET } from '@/fragments/wallet'
|
||||
|
@ -11,8 +11,8 @@ import { useWalletLogger } from '@/components/wallet-logger'
|
|||
export function useWalletConfigurator (wallet) {
|
||||
const { me } = useMe()
|
||||
const { encrypt, isActive } = useVault()
|
||||
const { logger } = useWalletLogger(wallet.def)
|
||||
const [upsertWallet] = useMutation(generateMutation(wallet.def))
|
||||
const { logger } = useWalletLogger(wallet?.def)
|
||||
const [upsertWallet] = useMutation(generateMutation(wallet?.def))
|
||||
const [removeWallet] = useMutation(REMOVE_WALLET)
|
||||
|
||||
const _saveToServer = useCallback(async (serverConfig, clientConfig) => {
|
||||
|
@ -117,48 +117,3 @@ function extractClientConfig (fields, config) {
|
|||
function extractServerConfig (fields, config) {
|
||||
return extractConfig(fields, config, false, true)
|
||||
}
|
||||
|
||||
export function isServerField (f) {
|
||||
return f.serverOnly || !f.clientOnly
|
||||
}
|
||||
|
||||
export function isClientField (f) {
|
||||
return f.clientOnly || !f.serverOnly
|
||||
}
|
||||
|
||||
function checkFields ({ fields, config }) {
|
||||
// a wallet is configured if all of its required fields are set
|
||||
let val = fields.every(f => {
|
||||
return f.optional ? true : !!config?.[f.name]
|
||||
})
|
||||
|
||||
// however, a wallet is not configured if all fields are optional and none are set
|
||||
// since that usually means that one of them is required
|
||||
if (val && fields.length > 0) {
|
||||
val = !(fields.every(f => f.optional) && fields.every(f => !config?.[f.name]))
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
export function isConfigured (wallet) {
|
||||
return isSendConfigured(wallet) || isReceiveConfigured(wallet)
|
||||
}
|
||||
|
||||
function isSendConfigured (wallet) {
|
||||
const fields = wallet.def.fields.filter(isClientField)
|
||||
return checkFields({ fields, config: wallet.config })
|
||||
}
|
||||
|
||||
function isReceiveConfigured (wallet) {
|
||||
const fields = wallet.def.fields.filter(isServerField)
|
||||
return checkFields({ fields, config: wallet.config })
|
||||
}
|
||||
|
||||
export function canSend (wallet) {
|
||||
return !!wallet.def.sendPayment && isSendConfigured(wallet)
|
||||
}
|
||||
|
||||
export function canReceive (wallet) {
|
||||
return !wallet.def.clientOnly && isReceiveConfigured(wallet)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
export class InvoiceCanceledError extends Error {
|
||||
constructor (hash, actionError) {
|
||||
super(actionError ?? `invoice canceled: ${hash}`)
|
||||
this.name = 'InvoiceCanceledError'
|
||||
this.hash = hash
|
||||
this.actionError = actionError
|
||||
}
|
||||
}
|
||||
|
||||
export class NoAttachedWalletError extends Error {
|
||||
constructor () {
|
||||
super('no attached wallet found')
|
||||
this.name = 'NoAttachedWalletError'
|
||||
}
|
||||
}
|
||||
|
||||
export class InvoiceExpiredError extends Error {
|
||||
constructor (hash) {
|
||||
super(`invoice expired: ${hash}`)
|
||||
this.name = 'InvoiceExpiredError'
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import gql from 'graphql-tag'
|
||||
import { isServerField } from './config'
|
||||
import { isServerField } from './common'
|
||||
|
||||
export function fieldToGqlArg (field) {
|
||||
let arg = `${field.name}: String`
|
||||
|
|
|
@ -3,12 +3,11 @@ import { WALLETS } from '@/fragments/wallet'
|
|||
import { NORMAL_POLL_INTERVAL, SSR } from '@/lib/constants'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
import { getStorageKey, getWalletByType } from './common'
|
||||
import useVault from '@/components/use-vault'
|
||||
import { getStorageKey, getWalletByType, Status, walletPrioritySort, canSend } from './common'
|
||||
import useVault from '@/components/vault/use-vault'
|
||||
import { useWalletLogger } from '@/components/wallet-logger'
|
||||
import { bolt11Tags } from '@/lib/bolt11'
|
||||
import walletDefs from 'wallets/client'
|
||||
import { canSend } from './config'
|
||||
|
||||
const WalletsContext = createContext({
|
||||
wallets: []
|
||||
|
@ -47,6 +46,8 @@ function useLocalWallets () {
|
|||
return wallets
|
||||
}
|
||||
|
||||
const walletDefsOnly = walletDefs.map(w => ({ def: w, config: {} }))
|
||||
|
||||
export function WalletsProvider ({ children }) {
|
||||
const { me } = useMe()
|
||||
const { decrypt } = useVault()
|
||||
|
@ -70,16 +71,19 @@ export function WalletsProvider ({ children }) {
|
|||
}
|
||||
|
||||
return { config, def }
|
||||
})
|
||||
}) ?? []
|
||||
|
||||
// merge wallets on name
|
||||
const merged = {}
|
||||
for (const wallet of [...localWallets, ...wallets]) {
|
||||
for (const wallet of [...walletDefsOnly, ...localWallets, ...wallets]) {
|
||||
merged[wallet.def.name] = { ...merged[wallet.def.name], ...wallet }
|
||||
}
|
||||
return Object.values(merged)
|
||||
.sort(walletPrioritySort)
|
||||
.map(w => ({ ...w, status: w.config?.enabled ? Status.Enabled : Status.Disabled }))
|
||||
}, [data?.wallets, localWallets])
|
||||
|
||||
// provides priority sorted wallets to children
|
||||
return (
|
||||
<WalletsContext.Provider value={wallets}>
|
||||
{children}
|
||||
|
@ -101,10 +105,10 @@ export function useWallet (name) {
|
|||
|
||||
return wallets
|
||||
.filter(w => !w.def.isAvailable || w.def.isAvailable())
|
||||
.filter(w => w.config.enabled && canSend(w))[0]
|
||||
.filter(w => w.config?.enabled && canSend(w))[0]
|
||||
}, [wallets, name])
|
||||
|
||||
const { logger } = useWalletLogger(wallet.def)
|
||||
const { logger } = useWalletLogger(wallet?.def)
|
||||
|
||||
const sendPayment = useCallback(async (bolt11) => {
|
||||
const hash = bolt11Tags(bolt11).payment_hash
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { InvoiceCanceledError, InvoiceExpiredError } from '@/components/payment'
|
||||
import { InvoiceCanceledError, InvoiceExpiredError } from '@/wallets/errors'
|
||||
import { bolt11Tags } from '@/lib/bolt11'
|
||||
import { Mutex } from 'async-mutex'
|
||||
export * from 'wallets/lnc'
|
||||
|
|
Loading…
Reference in New Issue