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