diff --git a/components/nav/common.js b/components/nav/common.js
index 768afac4..89df6c7e 100644
--- a/components/nav/common.js
+++ b/components/nav/common.js
@@ -22,7 +22,7 @@ import classNames from 'classnames'
import SnIcon from '@/svgs/sn.svg'
import { useHasNewNotes } from '../use-has-new-notes'
import { useWallets } from '@/wallets/index'
-import { useWalletIndicator } from '@/components/wallet-indicator'
+import { useWalletIndicator } from '@/wallets/indicator'
import SwitchAccountList, { nextAccount, useAccounts } from '@/components/account'
import { useShowModal } from '@/components/modal'
import { numWithUnits } from '@/lib/format'
diff --git a/components/nav/mobile/offcanvas.js b/components/nav/mobile/offcanvas.js
index 65dba560..219e2917 100644
--- a/components/nav/mobile/offcanvas.js
+++ b/components/nav/mobile/offcanvas.js
@@ -7,7 +7,7 @@ import AnonIcon from '@/svgs/spy-fill.svg'
import styles from './footer.module.css'
import canvasStyles from './offcanvas.module.css'
import classNames from 'classnames'
-import { useWalletIndicator } from '@/components/wallet-indicator'
+import { useWalletIndicator } from '@/wallets/indicator'
export default function OffCanvas ({ me, dropNavKey }) {
const [show, setShow] = useState(false)
diff --git a/components/wallet-logger.js b/components/wallet-logger.js
deleted file mode 100644
index decd7451..00000000
--- a/components/wallet-logger.js
+++ /dev/null
@@ -1,323 +0,0 @@
-import LogMessage from './log-message'
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
-import styles from '@/styles/log.module.css'
-import { Button } from 'react-bootstrap'
-import { useToast } from './toast'
-import { useShowModal } from './modal'
-import { WALLET_LOGS } from '@/fragments/wallet'
-import { getWalletByType, walletTag } from '@/wallets/common'
-import { gql, useLazyQuery, useMutation } from '@apollo/client'
-import { useMe } from './me'
-import useIndexedDB, { getDbName } from './use-indexeddb'
-import { SSR } from '@/lib/constants'
-import { useRouter } from 'next/router'
-
-export function WalletLogs ({ wallet, embedded }) {
- const { logs, setLogs, hasMore, loadMore, loading } = useWalletLogs(wallet)
-
- const showModal = useShowModal()
-
- return (
- <>
-
- {
- showModal(onClose => )
- }}
- >clear logs
-
-
-
-
-
-
-
-
-
-
-
-
- {logs.map((log, i) => (
-
- ))}
-
-
- {loading
- ?
loading...
- : logs.length === 0 &&
empty
}
- {hasMore
- ?
- :
------ start of logs ------
}
-
- >
- )
-}
-
-function DeleteWalletLogsObstacle ({ wallet, setLogs, onClose }) {
- const { deleteLogs } = useWalletLogManager(setLogs)
- const toaster = useToast()
-
- let prompt = 'Do you really want to delete all wallet logs?'
- if (wallet) {
- prompt = 'Do you really want to delete all logs of this wallet?'
- }
-
- return (
-
- {prompt}
-
- cancel
-
-
-
- )
-}
-
-const INDICES = [
- { name: 'ts', keyPath: 'ts' },
- { name: 'wallet_ts', keyPath: ['wallet', 'ts'] }
-]
-
-function getWalletLogDbName (userId) {
- return getDbName(userId)
-}
-
-function useWalletLogDB () {
- const { me } = useMe()
- // memoize the idb config to avoid re-creating it on every render
- const idbConfig = useMemo(() =>
- ({ dbName: getWalletLogDbName(me?.id), storeName: 'wallet_logs', indices: INDICES }), [me?.id])
- const { add, getPage, clear, error, notSupported } = useIndexedDB(idbConfig)
-
- return { add, getPage, clear, error, notSupported }
-}
-
-export function useWalletLogManager (setLogs) {
- const { add, clear, notSupported } = useWalletLogDB()
-
- const appendLog = useCallback(async (wallet, level, message, context) => {
- const log = { wallet: walletTag(wallet.def), level, message, ts: +new Date(), context }
- try {
- if (notSupported) {
- console.log('cannot persist wallet log: indexeddb not supported')
- } else {
- await add(log)
- }
- setLogs?.(prevLogs => [log, ...prevLogs])
- } catch (error) {
- console.error('Failed to append wallet log:', error)
- }
- }, [add, notSupported])
-
- const [deleteServerWalletLogs] = useMutation(
- gql`
- mutation deleteWalletLogs($wallet: String) {
- deleteWalletLogs(wallet: $wallet)
- }
- `,
- {
- onCompleted: (_, { variables: { wallet: walletType } }) => {
- setLogs?.(logs => logs.filter(l => walletType ? l.wallet !== walletTag(getWalletByType(walletType)) : false))
- }
- }
- )
-
- const deleteLogs = useCallback(async (wallet, options) => {
- if ((!wallet || wallet.def.walletType) && !options?.clientOnly) {
- await deleteServerWalletLogs({ variables: { wallet: wallet?.def.walletType } })
- }
- if (!wallet || wallet.def.sendPayment) {
- try {
- const tag = wallet ? walletTag(wallet.def) : null
- if (notSupported) {
- console.log('cannot clear wallet logs: indexeddb not supported')
- } else {
- await clear('wallet_ts', tag ? window.IDBKeyRange.bound([tag, 0], [tag, Infinity]) : null)
- }
- setLogs?.(logs => logs.filter(l => wallet ? l.wallet !== tag : false))
- } catch (e) {
- console.error('failed to delete logs', e)
- }
- }
- }, [clear, deleteServerWalletLogs, setLogs, notSupported])
-
- return { appendLog, deleteLogs }
-}
-
-export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) {
- const [logs, _setLogs] = useState([])
- const [page, setPage] = useState(initialPage)
- const [hasMore, setHasMore] = useState(true)
- const [cursor, setCursor] = useState(null)
- const [loading, setLoading] = useState(true)
- const latestTimestamp = useRef()
- const { me } = useMe()
- const router = useRouter()
-
- const { getPage, error, notSupported } = useWalletLogDB()
- const [getWalletLogs] = useLazyQuery(WALLET_LOGS, SSR ? {} : { fetchPolicy: 'cache-and-network' })
-
- const setLogs = useCallback((action) => {
- _setLogs(action)
- // action can be a React state dispatch function
- const newLogs = typeof action === 'function' ? action(logs) : action
- // make sure 'more' button is removed if logs were deleted
- if (newLogs.length === 0) setHasMore(false)
- latestTimestamp.current = newLogs[0]?.ts
- }, [logs, _setLogs, setHasMore])
-
- const loadLogsPage = useCallback(async (page, pageSize, walletDef, variables = {}) => {
- try {
- let result = { data: [], hasMore: false }
- if (notSupported) {
- console.log('cannot get client wallet logs: indexeddb not supported')
- } else {
- const indexName = walletDef ? 'wallet_ts' : 'ts'
- const query = walletDef ? window.IDBKeyRange.bound([walletTag(walletDef), -Infinity], [walletTag(walletDef), Infinity]) : null
-
- result = await getPage(page, pageSize, indexName, query, 'prev')
- // if given wallet has no walletType it means logs are only stored in local IDB
- if (walletDef && !walletDef.walletType) {
- return result
- }
- }
-
- const oldestTs = result?.data[result.data.length - 1]?.ts // start of local logs
- const newestTs = result?.data[0]?.ts // end of local logs
-
- let from
- if (variables?.from !== undefined) {
- from = variables.from
- } else if (oldestTs && result.hasMore) {
- // fetch all missing, intertwined server logs since start of local logs
- from = String(oldestTs)
- } else {
- from = null
- }
-
- let to
- if (variables?.to !== undefined) {
- to = variables.to
- } else if (newestTs && cursor) {
- // fetch next old page of server logs
- // ( if cursor is available, we will use decoded time of cursor )
- to = String(newestTs)
- } else {
- to = null
- }
-
- const { data, error } = await getWalletLogs({
- variables: {
- type: walletDef?.walletType,
- from,
- to,
- cursor,
- ...variables
- }
- })
-
- if (error) {
- console.error('failed to query wallet logs:', error)
- return { data: [], hasMore: false }
- }
-
- const newLogs = data.walletLogs.entries.map(({ createdAt, wallet: walletType, ...log }) => ({
- ts: +new Date(createdAt),
- wallet: walletType ? walletTag(getWalletByType(walletType)) : 'system',
- ...log,
- // required to resolve recv status
- context: {
- recv: true,
- status: !!log.context?.bolt11 && ['warn', 'error', 'success'].includes(log.level.toLowerCase()),
- ...log.context
- }
- }))
- const combinedLogs = uniqueSort([...result.data, ...newLogs])
-
- setCursor(data.walletLogs.cursor)
- return {
- ...result,
- data: combinedLogs,
- hasMore: result.hasMore || !!data.walletLogs.cursor
- }
- } catch (error) {
- console.error('Error loading logs from IndexedDB:', error)
- return { data: [], hasMore: false }
- }
- }, [getPage, setCursor, cursor, notSupported])
-
- if (error) {
- console.error('IndexedDB error:', error)
- }
-
- const loadMore = useCallback(async () => {
- if (hasMore) {
- setLoading(true)
- const result = await loadLogsPage(page + 1, logsPerPage, wallet?.def)
- setLogs(prevLogs => uniqueSort([...prevLogs, ...result.data]))
- setHasMore(result.hasMore)
- setPage(prevPage => prevPage + 1)
- setLoading(false)
- }
- }, [setLogs, loadLogsPage, page, logsPerPage, wallet?.def, hasMore])
-
- const loadNew = useCallback(async () => {
- const latestTs = latestTimestamp.current
- const variables = { from: latestTs?.toString(), to: null }
- const result = await loadLogsPage(1, logsPerPage, wallet?.def, variables)
- setLoading(false)
- _setLogs(prevLogs => uniqueSort([...result.data, ...prevLogs]))
- if (!latestTs) {
- // we only want to update the more button if we didn't fetch new logs since it is about old logs.
- // we didn't fetch new logs if this is our first fetch (no newest timestamp available)
- setHasMore(result.hasMore)
- }
- }, [wallet?.def, loadLogsPage])
-
- useEffect(() => {
- // only fetch new logs if we are on a page that uses logs
- const needLogs = router.asPath.startsWith('/wallets')
- if (!me || !needLogs) return
-
- let timeout
- let stop = false
-
- const poll = async () => {
- await loadNew().catch(console.error)
- if (!stop) timeout = setTimeout(poll, 1_000)
- }
-
- timeout = setTimeout(poll, 1_000)
-
- return () => {
- stop = true
- clearTimeout(timeout)
- }
- }, [me?.id, router.pathname, loadNew])
-
- return { logs, hasMore: !loading && hasMore, loadMore, setLogs, loading }
-}
-
-function uniqueSort (logs) {
- return Array.from(new Set(logs.map(JSON.stringify))).map(JSON.parse).sort((a, b) => b.ts - a.ts)
-}
diff --git a/pages/wallets/[wallet].js b/pages/wallets/[wallet].js
index ff5830a6..5b8f7ae5 100644
--- a/pages/wallets/[wallet].js
+++ b/pages/wallets/[wallet].js
@@ -2,7 +2,7 @@ import { getGetServerSideProps } from '@/api/ssrApollo'
import { Form, ClientInput, PasswordInput, CheckboxGroup, Checkbox } from '@/components/form'
import { CenterLayout } from '@/components/layout'
import { WalletSecurityBanner } from '@/components/banners'
-import { WalletLogs } from '@/components/wallet-logger'
+import { WalletLogs } from '@/wallets/logger'
import { useToast } from '@/components/toast'
import { useRouter } from 'next/router'
import { useWallet } from '@/wallets/index'
@@ -11,14 +11,14 @@ import Text from '@/components/text'
import { autowithdrawInitial, AutowithdrawSettings } from '@/components/autowithdraw-shared'
import { canReceive, canSend, isConfigured } from '@/wallets/common'
import { SSR } from '@/lib/constants'
-import WalletButtonBar from '@/components/wallet-buttonbar'
+import WalletButtonBar from '@/wallets/buttonbar'
import { useWalletConfigurator } from '@/wallets/config'
import { useCallback, useMemo } from 'react'
import { useMe } from '@/components/me'
import validateWallet from '@/wallets/validate'
import { ValidationError } from 'yup'
import { useFormikContext } from 'formik'
-import { useWalletImage } from '@/components/wallet-image'
+import { useWalletImage } from '@/wallets/image'
import styles from '@/styles/wallet.module.css'
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
diff --git a/pages/wallets/index.js b/pages/wallets/index.js
index 55861f35..4ea53b90 100644
--- a/pages/wallets/index.js
+++ b/pages/wallets/index.js
@@ -5,14 +5,14 @@ import Link from 'next/link'
import { useWallets } from '@/wallets/index'
import { useCallback, useEffect, useState } from 'react'
import { useIsClient } from '@/components/use-client'
-import WalletCard from '@/components/wallet-card'
+import WalletCard from '@/wallets/card'
import { useToast } from '@/components/toast'
import BootstrapForm from 'react-bootstrap/Form'
import RecvIcon from '@/svgs/arrow-left-down-line.svg'
import SendIcon from '@/svgs/arrow-right-up-line.svg'
import { useRouter } from 'next/router'
import { supportsReceive, supportsSend } from '@/wallets/common'
-import { useWalletIndicator } from '@/components/wallet-indicator'
+import { useWalletIndicator } from '@/wallets/indicator'
import { Button } from 'react-bootstrap'
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
diff --git a/pages/wallets/logs.js b/pages/wallets/logs.js
index e207a32c..adecc877 100644
--- a/pages/wallets/logs.js
+++ b/pages/wallets/logs.js
@@ -1,6 +1,6 @@
import { CenterLayout } from '@/components/layout'
import { getGetServerSideProps } from '@/api/ssrApollo'
-import { WalletLogs } from '@/components/wallet-logger'
+import { WalletLogs } from '@/wallets/logger'
export const getServerSideProps = getGetServerSideProps({ query: null })
diff --git a/wallets/README.md b/wallets/README.md
index 41b0ab86..92a13f08 100644
--- a/wallets/README.md
+++ b/wallets/README.md
@@ -168,7 +168,7 @@ How this validation is implemented depends heavily on the wallet. For example, f
This function must throw an error if the configuration was found to be invalid.
-The `context` argument is an object. It makes the wallet logger for this wallet as returned by `useWalletLogger` available under `context.logger`. See [components/wallet-logger.js](../components/wallet-logger.js).
+The `context` argument is an object. It makes the wallet logger for this wallet as returned by `useWalletLogger` available under `context.logger`. See [wallets/logger.js](../wallets/logger.js).
- `sendPayment: async (bolt11: string, config, context) => Promise`
diff --git a/components/wallet-buttonbar.js b/wallets/buttonbar.js
similarity index 89%
rename from components/wallet-buttonbar.js
rename to wallets/buttonbar.js
index 46572919..04b8c1b8 100644
--- a/components/wallet-buttonbar.js
+++ b/wallets/buttonbar.js
@@ -1,6 +1,6 @@
import { Button } from 'react-bootstrap'
-import CancelButton from './cancel-button'
-import { SubmitButton } from './form'
+import CancelButton from '@/components/cancel-button'
+import { SubmitButton } from '@/components/form'
import { isConfigured } from '@/wallets/common'
export default function WalletButtonBar ({
diff --git a/components/wallet-card.js b/wallets/card.js
similarity index 91%
rename from components/wallet-card.js
rename to wallets/card.js
index 0c0fb7ea..051be961 100644
--- a/components/wallet-card.js
+++ b/wallets/card.js
@@ -7,9 +7,9 @@ import { Status, isConfigured } from '@/wallets/common'
import DraggableIcon from '@/svgs/draggable.svg'
import RecvIcon from '@/svgs/arrow-left-down-line.svg'
import SendIcon from '@/svgs/arrow-right-up-line.svg'
-import { useWalletImage } from '@/components/wallet-image'
-import { useWalletStatus, statusToClass } from '@/components/wallet-status'
-import { useWalletSupport } from '@/components/wallet-support'
+import { useWalletImage } from '@/wallets/image'
+import { useWalletStatus, statusToClass } from '@/wallets/status'
+import { useWalletSupport } from '@/wallets/support'
export default function WalletCard ({ wallet, draggable, onDragStart, onDragEnter, onDragEnd, onTouchStart, sourceIndex, targetIndex, index }) {
const image = useWalletImage(wallet)
diff --git a/components/wallet-image.js b/wallets/image.js
similarity index 100%
rename from components/wallet-image.js
rename to wallets/image.js
diff --git a/components/wallet-indicator.js b/wallets/indicator.js
similarity index 100%
rename from components/wallet-indicator.js
rename to wallets/indicator.js
diff --git a/wallets/logger.js b/wallets/logger.js
index 342cd664..8cf943f8 100644
--- a/wallets/logger.js
+++ b/wallets/logger.js
@@ -1,8 +1,23 @@
-import { useCallback } from 'react'
+import { useCallback, useMemo, useState, useEffect, useRef } from 'react'
import { decode as bolt11Decode } from 'bolt11'
import { formatMsats } from '@/lib/format'
-import { walletTag } from '@/wallets/common'
-import { useWalletLogManager } from '@/components/wallet-logger'
+import { walletTag, getWalletByType } from '@/wallets/common'
+import { useMe } from '@/components/me'
+import useIndexedDB, { getDbName } from '@/components/use-indexeddb'
+import { useShowModal } from '@/components/modal'
+import LogMessage from '@/components/log-message'
+import { useToast } from '@/components/toast'
+import { useMutation, useLazyQuery, gql } from '@apollo/client'
+import { useRouter } from 'next/router'
+import { WALLET_LOGS } from '@/fragments/wallet'
+import { SSR } from '@/lib/constants'
+import { Button } from 'react-bootstrap'
+import styles from '@/styles/log.module.css'
+
+const INDICES = [
+ { name: 'ts', keyPath: 'ts' },
+ { name: 'wallet_ts', keyPath: ['wallet', 'ts'] }
+]
export function useWalletLoggerFactory () {
const { appendLog } = useWalletLogManager()
@@ -43,3 +58,303 @@ export function useWalletLogger (wallet) {
const factory = useWalletLoggerFactory()
return factory(wallet)
}
+
+export function WalletLogs ({ wallet, embedded }) {
+ const { logs, setLogs, hasMore, loadMore, loading } = useWalletLogs(wallet)
+
+ const showModal = useShowModal()
+
+ return (
+ <>
+
+ {
+ showModal(onClose => )
+ }}
+ >clear logs
+
+
+
+
+
+
+
+
+
+
+
+
+ {logs.map((log, i) => (
+
+ ))}
+
+
+ {loading
+ ?
loading...
+ : logs.length === 0 &&
empty
}
+ {hasMore
+ ?
+ :
------ start of logs ------
}
+
+ >
+ )
+}
+
+function DeleteWalletLogsObstacle ({ wallet, setLogs, onClose }) {
+ const { deleteLogs } = useWalletLogManager(setLogs)
+ const toaster = useToast()
+
+ let prompt = 'Do you really want to delete all wallet logs?'
+ if (wallet) {
+ prompt = 'Do you really want to delete all logs of this wallet?'
+ }
+
+ return (
+
+ {prompt}
+
+ cancel
+
+
+
+ )
+}
+
+export function useWalletLogManager (setLogs) {
+ const { add, clear, notSupported } = useWalletLogDB()
+
+ const appendLog = useCallback(async (wallet, level, message, context) => {
+ const log = { wallet: walletTag(wallet.def), level, message, ts: +new Date(), context }
+ try {
+ if (notSupported) {
+ console.log('cannot persist wallet log: indexeddb not supported')
+ } else {
+ await add(log)
+ }
+ setLogs?.(prevLogs => [log, ...prevLogs])
+ } catch (error) {
+ console.error('Failed to append wallet log:', error)
+ }
+ }, [add, notSupported])
+
+ const [deleteServerWalletLogs] = useMutation(
+ gql`
+ mutation deleteWalletLogs($wallet: String) {
+ deleteWalletLogs(wallet: $wallet)
+ }
+ `,
+ {
+ onCompleted: (_, { variables: { wallet: walletType } }) => {
+ setLogs?.(logs => logs.filter(l => walletType ? l.wallet !== walletTag(getWalletByType(walletType)) : false))
+ }
+ }
+ )
+
+ const deleteLogs = useCallback(async (wallet, options) => {
+ if ((!wallet || wallet.def.walletType) && !options?.clientOnly) {
+ await deleteServerWalletLogs({ variables: { wallet: wallet?.def.walletType } })
+ }
+ if (!wallet || wallet.def.sendPayment) {
+ try {
+ const tag = wallet ? walletTag(wallet.def) : null
+ if (notSupported) {
+ console.log('cannot clear wallet logs: indexeddb not supported')
+ } else {
+ await clear('wallet_ts', tag ? window.IDBKeyRange.bound([tag, 0], [tag, Infinity]) : null)
+ }
+ setLogs?.(logs => logs.filter(l => wallet ? l.wallet !== tag : false))
+ } catch (e) {
+ console.error('failed to delete logs', e)
+ }
+ }
+ }, [clear, deleteServerWalletLogs, setLogs, notSupported])
+
+ return { appendLog, deleteLogs }
+}
+
+export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) {
+ const [logs, _setLogs] = useState([])
+ const [page, setPage] = useState(initialPage)
+ const [hasMore, setHasMore] = useState(true)
+ const [cursor, setCursor] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const latestTimestamp = useRef()
+ const { me } = useMe()
+ const router = useRouter()
+
+ const { getPage, error, notSupported } = useWalletLogDB()
+ const [getWalletLogs] = useLazyQuery(WALLET_LOGS, SSR ? {} : { fetchPolicy: 'cache-and-network' })
+
+ const setLogs = useCallback((action) => {
+ _setLogs(action)
+ // action can be a React state dispatch function
+ const newLogs = typeof action === 'function' ? action(logs) : action
+ // make sure 'more' button is removed if logs were deleted
+ if (newLogs.length === 0) setHasMore(false)
+ latestTimestamp.current = newLogs[0]?.ts
+ }, [logs, _setLogs, setHasMore])
+
+ const loadLogsPage = useCallback(async (page, pageSize, walletDef, variables = {}) => {
+ try {
+ let result = { data: [], hasMore: false }
+ if (notSupported) {
+ console.log('cannot get client wallet logs: indexeddb not supported')
+ } else {
+ const indexName = walletDef ? 'wallet_ts' : 'ts'
+ const query = walletDef ? window.IDBKeyRange.bound([walletTag(walletDef), -Infinity], [walletTag(walletDef), Infinity]) : null
+
+ result = await getPage(page, pageSize, indexName, query, 'prev')
+ // if given wallet has no walletType it means logs are only stored in local IDB
+ if (walletDef && !walletDef.walletType) {
+ return result
+ }
+ }
+
+ const oldestTs = result?.data[result.data.length - 1]?.ts // start of local logs
+ const newestTs = result?.data[0]?.ts // end of local logs
+
+ let from
+ if (variables?.from !== undefined) {
+ from = variables.from
+ } else if (oldestTs && result.hasMore) {
+ // fetch all missing, intertwined server logs since start of local logs
+ from = String(oldestTs)
+ } else {
+ from = null
+ }
+
+ let to
+ if (variables?.to !== undefined) {
+ to = variables.to
+ } else if (newestTs && cursor) {
+ // fetch next old page of server logs
+ // ( if cursor is available, we will use decoded time of cursor )
+ to = String(newestTs)
+ } else {
+ to = null
+ }
+
+ const { data } = await getWalletLogs({
+ variables: {
+ type: walletDef?.walletType,
+ from,
+ to,
+ cursor,
+ ...variables
+ }
+ })
+
+ const newLogs = data.walletLogs.entries.map(({ createdAt, wallet: walletType, ...log }) => ({
+ ts: +new Date(createdAt),
+ wallet: walletTag(getWalletByType(walletType)),
+ ...log,
+ // required to resolve recv status
+ context: {
+ recv: true,
+ status: !!log.context?.bolt11 && ['warn', 'error', 'success'].includes(log.level.toLowerCase()),
+ ...log.context
+ }
+ }))
+ const combinedLogs = uniqueSort([...result.data, ...newLogs])
+
+ setCursor(data.walletLogs.cursor)
+ return {
+ ...result,
+ data: combinedLogs,
+ hasMore: result.hasMore || !!data.walletLogs.cursor
+ }
+ } catch (error) {
+ console.error('Error loading logs from IndexedDB:', error)
+ return { data: [], hasMore: false }
+ }
+ }, [getPage, setCursor, cursor, notSupported])
+
+ if (error) {
+ console.error('IndexedDB error:', error)
+ }
+
+ const loadMore = useCallback(async () => {
+ if (hasMore) {
+ setLoading(true)
+ const result = await loadLogsPage(page + 1, logsPerPage, wallet?.def)
+ setLogs(prevLogs => uniqueSort([...prevLogs, ...result.data]))
+ setHasMore(result.hasMore)
+ setPage(prevPage => prevPage + 1)
+ setLoading(false)
+ }
+ }, [setLogs, loadLogsPage, page, logsPerPage, wallet?.def, hasMore])
+
+ const loadNew = useCallback(async () => {
+ const latestTs = latestTimestamp.current
+ const variables = { from: latestTs?.toString(), to: null }
+ const result = await loadLogsPage(1, logsPerPage, wallet?.def, variables)
+ setLoading(false)
+ _setLogs(prevLogs => uniqueSort([...result.data, ...prevLogs]))
+ if (!latestTs) {
+ // we only want to update the more button if we didn't fetch new logs since it is about old logs.
+ // we didn't fetch new logs if this is our first fetch (no newest timestamp available)
+ setHasMore(result.hasMore)
+ }
+ }, [wallet?.def, loadLogsPage])
+
+ useEffect(() => {
+ // only fetch new logs if we are on a page that uses logs
+ const needLogs = router.asPath.startsWith('/wallets')
+ if (!me || !needLogs) return
+
+ let timeout
+ let stop = false
+
+ const poll = async () => {
+ await loadNew().catch(console.error)
+ if (!stop) timeout = setTimeout(poll, 1_000)
+ }
+
+ timeout = setTimeout(poll, 1_000)
+
+ return () => {
+ stop = true
+ clearTimeout(timeout)
+ }
+ }, [me?.id, router.pathname, loadNew])
+
+ return { logs, hasMore: !loading && hasMore, loadMore, setLogs, loading }
+}
+
+function uniqueSort (logs) {
+ return Array.from(new Set(logs.map(JSON.stringify))).map(JSON.parse).sort((a, b) => b.ts - a.ts)
+}
+
+function getWalletLogDbName (userId) {
+ return getDbName(userId)
+}
+
+function useWalletLogDB () {
+ const { me } = useMe()
+ // memoize the idb config to avoid re-creating it on every render
+ const idbConfig = useMemo(() =>
+ ({ dbName: getWalletLogDbName(me?.id), storeName: 'wallet_logs', indices: INDICES }), [me?.id])
+ const { add, getPage, clear, error, notSupported } = useIndexedDB(idbConfig)
+
+ return { add, getPage, clear, error, notSupported }
+}
diff --git a/components/wallet-status.js b/wallets/status.js
similarity index 96%
rename from components/wallet-status.js
rename to wallets/status.js
index 9f3f2871..b77b7b08 100644
--- a/components/wallet-status.js
+++ b/wallets/status.js
@@ -1,5 +1,5 @@
import { canReceive, canSend, isConfigured, Status } from '@/wallets/common'
-import { useWalletLogs } from '@/components/wallet-logger'
+import { useWalletLogs } from '@/wallets/logger'
import styles from '@/styles/wallet.module.css'
export function useWalletStatus (wallet) {
diff --git a/components/wallet-support.js b/wallets/support.js
similarity index 100%
rename from components/wallet-support.js
rename to wallets/support.js