things
This commit is contained in:
parent
9fa142841b
commit
a78abbd950
2
.env
Normal file
2
.env
Normal file
|
@ -0,0 +1,2 @@
|
|||
NEXT_PUBLIC_DEFAULT_NANO_NODE_RPC=https://rainstorm.city/api
|
||||
NEXT_PUBLIC_DEFAULT_NANO_NODE_WS=wss://ws.mynano.ninja/
|
|
@ -66,7 +66,7 @@ const BottomMenu: FC<Props> = ({ className }) => {
|
|||
<div>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex gap-10 items-end',
|
||||
'flex gap-6 items-end',
|
||||
leftHanded ? 'flex-row-reverse' : 'flex-row'
|
||||
)}
|
||||
>
|
||||
|
@ -97,31 +97,12 @@ const BottomMenu: FC<Props> = ({ className }) => {
|
|||
</button>
|
||||
)}
|
||||
|
||||
<div className="relative h-16">
|
||||
<div className="flex h-16">
|
||||
<button
|
||||
disabled={isWelcoming}
|
||||
className={clsx(
|
||||
'bg-purple-500 absolute top-0 px-1 h-16 w-10 rounded-r hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-md disabled:cursor-default',
|
||||
leftHanded
|
||||
? 'left-0 -translate-x-2/3 rounded-l'
|
||||
: 'right-0 translate-x-2/3 rounded-r'
|
||||
)}
|
||||
onClick={() => push('/send/qr')}
|
||||
>
|
||||
<PaperAirplaneIcon className="h-full text-purple-50 dark:text-gray-900 w-full rotate-[30deg] translate-x-1" />
|
||||
</button>
|
||||
|
||||
<div className="border-purple-500 border-t-2 border-b-2 py-1 px-4 h-16 shadow-lg">
|
||||
<QrcodeIcon className="h-full text-gray-900 dark:text-purple-100" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
disabled={isWelcoming}
|
||||
className={clsx(
|
||||
'bg-purple-500 absolute top-0 px-1 h-16 w-10 hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-md disabled:cursor-default',
|
||||
leftHanded
|
||||
? 'right-0 translate-x-2/3 rounded-r'
|
||||
: 'left-0 -translate-x-2/3 rounded-l'
|
||||
'bg-purple-500 px-1 h-16 w-10 hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-md disabled:cursor-default',
|
||||
leftHanded ? 'rounded-r' : 'rounded-l'
|
||||
)}
|
||||
onClick={() => push('/receive/qr')}
|
||||
>
|
||||
|
@ -131,20 +112,18 @@ const BottomMenu: FC<Props> = ({ className }) => {
|
|||
}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col h-16 justify-between">
|
||||
<div className="border-purple-500 border-t-2 border-b-2 p-1 h-16 shadow-lg">
|
||||
<QrcodeIcon className="h-full text-gray-900 dark:text-purple-100" />
|
||||
</div>
|
||||
<button
|
||||
disabled={isWelcoming}
|
||||
className="bg-purple-500 p-1 h-7 rounded hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-md disabled:cursor-default"
|
||||
className={clsx(
|
||||
'bg-purple-500 px-1 h-16 w-10 rounded-r hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-md disabled:cursor-default',
|
||||
leftHanded ? 'rounded-l' : 'rounded-r'
|
||||
)}
|
||||
onClick={() => push('/send/qr')}
|
||||
>
|
||||
<PaperAirplaneIcon className="h-full text-purple-50 dark:text-gray-900 rotate-[30deg] translate-x-[2px]" />
|
||||
</button>
|
||||
<button
|
||||
disabled={isWelcoming}
|
||||
className="bg-purple-500 p-1 h-7 rounded hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-lg disabled:cursor-default"
|
||||
>
|
||||
<RssIcon className="h-full text-purple-50 dark:text-gray-900" />
|
||||
<PaperAirplaneIcon className="h-full text-purple-50 dark:text-gray-900 w-full rotate-[30deg] translate-x-1" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,9 +7,7 @@ import type { FC } from 'react'
|
|||
import { useCurrentAccount } from '../lib/context/accountContext'
|
||||
import useAccountHistory from '../lib/hooks/useAccountHistory'
|
||||
import useAccountReceivable from '../lib/hooks/useAccountReceivable'
|
||||
import useListenToConfirmations from '../lib/hooks/useListenToConfirmations'
|
||||
import useReceiveNano from '../lib/hooks/useReceiveNano'
|
||||
import { ConfirmationMessage } from '../lib/types'
|
||||
import rawToNanoDisplay from '../lib/xno/rawToNanoDisplay'
|
||||
|
||||
export interface Props {
|
||||
|
@ -17,90 +15,32 @@ export interface Props {
|
|||
}
|
||||
|
||||
const RecentTransactions: FC<Props> = ({ className }) => {
|
||||
const account = useCurrentAccount()
|
||||
const { receive } = useReceiveNano()
|
||||
|
||||
const {
|
||||
accountReceivable,
|
||||
blocksInfo: receivableBlocksInfo,
|
||||
mutate,
|
||||
} = useAccountReceivable()
|
||||
const { accountHistory } = useAccountHistory()
|
||||
|
||||
const account = useCurrentAccount()
|
||||
const { receivableBlocks, receivableBlocksInfo, onBlockReceived } =
|
||||
useAccountReceivable()
|
||||
const { data: accountHistory } = useAccountHistory()
|
||||
|
||||
const hasReceivable =
|
||||
accountReceivable !== undefined &&
|
||||
Object.values(accountReceivable.blocks).some(account => account !== '')
|
||||
receivableBlocks !== undefined &&
|
||||
receivableBlocks.blocks[account?.address ?? ''] !== '' &&
|
||||
receivableBlocksInfo !== undefined
|
||||
|
||||
const receivable = Object.entries(
|
||||
accountReceivable?.blocks[account?.address ?? ''] ?? {}
|
||||
receivableBlocks?.blocks[account?.address ?? ''] ?? {}
|
||||
).map(([hash, { amount, source }]) => ({
|
||||
hash,
|
||||
amount,
|
||||
from: source,
|
||||
}))
|
||||
|
||||
const [listenedReceivables, setListenedReceivables] = useState<
|
||||
ConfirmationMessage[]
|
||||
>([])
|
||||
|
||||
const onConfirmation = useCallback(
|
||||
(confirmation: ConfirmationMessage) => {
|
||||
const alreadyHaveConfirmation =
|
||||
accountHistory?.history !== '' &&
|
||||
accountHistory?.history.some(
|
||||
txn => txn.hash === confirmation.message.hash
|
||||
) &&
|
||||
receivable.some(txn => txn.hash === confirmation.message.hash)
|
||||
if (!alreadyHaveConfirmation)
|
||||
setListenedReceivables(prev => [...prev, confirmation])
|
||||
},
|
||||
[accountHistory, receivable]
|
||||
)
|
||||
|
||||
useListenToConfirmations(onConfirmation)
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col gap-6 w-full', className)}>
|
||||
{hasReceivable && (
|
||||
<section className="flex flex-col gap-3 w-full items-center">
|
||||
<h2 className="text-2xl font-semibold text-purple-50">receivable</h2>
|
||||
<ol className="flex flex-col gap-3 w-full">
|
||||
{listenedReceivables.map(({ message, time }) => (
|
||||
<li
|
||||
key={message.hash}
|
||||
className="bg-purple-50 shadow rounded px-3 py-3 flex items-center justify-between gap-2 text-black border-r-4 border-blue-500"
|
||||
>
|
||||
<button
|
||||
className="contents"
|
||||
onClick={() => receive(message.hash, message.amount)}
|
||||
>
|
||||
<ClockIcon className="w-6 flex-shrink-0 text-blue-500" />
|
||||
|
||||
<div className="overflow-hidden overflow-ellipsis text-left flex-1 whitespace-nowrap">
|
||||
{Intl.DateTimeFormat([], {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: '2-digit',
|
||||
}).format(Number(time) * 1000)}{' '}
|
||||
- {<span className="text-xs">{message.block.account}</span>}
|
||||
</div>
|
||||
<span className="flex-shrink-0 font-medium">
|
||||
Ӿ{' '}
|
||||
{rawToNanoDisplay(message.amount) === 'small' ? (
|
||||
'<0.01'
|
||||
) : rawToNanoDisplay(message.amount).startsWith('0.') ? (
|
||||
<>
|
||||
<span className="text-sm font-semibold">0</span>
|
||||
{rawToNanoDisplay(message.amount).substring(1)}
|
||||
</>
|
||||
) : (
|
||||
rawToNanoDisplay(message.amount)
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
{receivable.map(receivable => (
|
||||
<li
|
||||
key={receivable.hash}
|
||||
|
@ -108,7 +48,10 @@ const RecentTransactions: FC<Props> = ({ className }) => {
|
|||
>
|
||||
<button
|
||||
className="contents"
|
||||
onClick={() => receive(receivable.hash, receivable.amount)}
|
||||
onClick={async () => {
|
||||
await receive(receivable.hash, receivable.amount)
|
||||
onBlockReceived(receivable.hash)
|
||||
}}
|
||||
>
|
||||
<ClockIcon className="w-6 flex-shrink-0 text-blue-500" />
|
||||
|
||||
|
@ -119,7 +62,7 @@ const RecentTransactions: FC<Props> = ({ className }) => {
|
|||
year: '2-digit',
|
||||
}).format(
|
||||
Number(
|
||||
receivableBlocksInfo!.blocks[receivable.hash]
|
||||
receivableBlocksInfo.blocks[receivable.hash]
|
||||
.local_timestamp
|
||||
) * 1000
|
||||
)}{' '}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {
|
||||
CogIcon,
|
||||
DotsHorizontalIcon,
|
||||
FingerPrintIcon,
|
||||
HandIcon,
|
||||
KeyIcon,
|
||||
LibraryIcon,
|
||||
MoonIcon,
|
||||
PlusIcon,
|
||||
UsersIcon,
|
||||
} from '@heroicons/react/solid'
|
||||
import clsx from 'clsx'
|
||||
|
@ -59,7 +59,7 @@ const TopMenu: FC<Props> = () => {
|
|||
)}
|
||||
onClick={toggleAdvanced}
|
||||
>
|
||||
<PlusIcon className="w-full" />
|
||||
<DotsHorizontalIcon className="w-full" />
|
||||
</button>
|
||||
<ul
|
||||
role="menu"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { validateWork } from 'nanocurrency'
|
||||
import {
|
||||
FC,
|
||||
createContext,
|
||||
|
@ -9,12 +10,7 @@ import {
|
|||
} from 'react'
|
||||
|
||||
import computeWorkAsync from '../computeWorkAsync'
|
||||
import {
|
||||
addPrecomputedWork,
|
||||
consumePrecomputedWork,
|
||||
getAllAccounts,
|
||||
putAccount,
|
||||
} from '../db/accounts'
|
||||
import { addPrecomputedWork, getAllAccounts, putAccount } from '../db/accounts'
|
||||
import { AccountInfoCache } from '../types'
|
||||
import fetchAccountInfo from '../xno/fetchAccountInfo'
|
||||
|
||||
|
@ -37,7 +33,7 @@ const refreshAccountFromNetwork = async (account: AccountInfoCache) => {
|
|||
'error' in infoResponse ? null : infoResponse.confirmed_representative
|
||||
const balance =
|
||||
'error' in infoResponse ? null : infoResponse.confirmed_balance
|
||||
const freshAccountInfo = {
|
||||
const freshAccountInfo: AccountInfoCache = {
|
||||
...account,
|
||||
frontier,
|
||||
representative,
|
||||
|
@ -66,9 +62,13 @@ export const useAccounts = () => {
|
|||
*/
|
||||
export const useAccount = (index?: number) => {
|
||||
const contextValue = useAccounts()
|
||||
return index !== undefined
|
||||
? contextValue.accounts?.[index]
|
||||
: contextValue.currAccount
|
||||
return useMemo(
|
||||
() =>
|
||||
index !== undefined
|
||||
? contextValue.accounts?.[index]
|
||||
: contextValue.currAccount,
|
||||
[contextValue, index]
|
||||
)
|
||||
}
|
||||
|
||||
export const useCurrentAccount = () => useAccount()
|
||||
|
@ -98,7 +98,13 @@ export const AccountProvider: FC = ({ children }) => {
|
|||
const accounts: AccountContextValue['accounts'] = {}
|
||||
accountList.forEach(async account => {
|
||||
accounts[account.index] = account
|
||||
if (account.precomputedWork === null) {
|
||||
if (
|
||||
account.precomputedWork === null ||
|
||||
!validateWork({
|
||||
work: account.precomputedWork,
|
||||
blockHash: account.frontier ?? account.publicKey,
|
||||
})
|
||||
) {
|
||||
const work = await computeWorkAsync(
|
||||
account.frontier ?? account.publicKey,
|
||||
{
|
||||
|
|
|
@ -8,7 +8,11 @@ import {
|
|||
} from 'react'
|
||||
|
||||
import { getAllPreferences, putPreference } from '../db/preferences'
|
||||
import { PreferenceName, PreferenceTypes } from '../db/types'
|
||||
import {
|
||||
PreferenceName,
|
||||
PreferenceTypes,
|
||||
ShowCurrencyPreference,
|
||||
} from '../db/types'
|
||||
import useDarkMode from '../hooks/useDarkMode'
|
||||
|
||||
export interface PreferenceContextValue {
|
||||
|
@ -33,47 +37,49 @@ export const usePreferences = () => {
|
|||
}
|
||||
|
||||
const initialState: PreferenceContextValue['preferences'] = {
|
||||
darkMode: undefined,
|
||||
biometricsAuth: undefined,
|
||||
leftHanded: undefined,
|
||||
showCurrencyDash: undefined,
|
||||
darkMode: true,
|
||||
biometricsAuth: true,
|
||||
leftHanded: false,
|
||||
showCurrencyDash: ShowCurrencyPreference.None,
|
||||
}
|
||||
|
||||
export const PreferencesProvider: FC = ({ children }) => {
|
||||
const [preferences, setPreferences] = useState<PreferenceTypes>(initialState)
|
||||
useEffect(() => {
|
||||
const fetchPreferencesFromIdb = async () => {
|
||||
const preferenceList = await getAllPreferences()
|
||||
const preferences: PreferenceContextValue['preferences'] = initialState
|
||||
for (const preference of preferenceList) {
|
||||
// @ts-expect-error i should type this better but should be fine for now
|
||||
preferences[preference.name] = preference.value
|
||||
}
|
||||
setPreferences(preferences)
|
||||
}
|
||||
fetchPreferencesFromIdb()
|
||||
}, [])
|
||||
|
||||
const { setDarkMode, isDarkMode } = useDarkMode()
|
||||
|
||||
const setPreference = useCallback(
|
||||
<P extends PreferenceName>(
|
||||
name: P,
|
||||
value: Exclude<PreferenceTypes[P], undefined>
|
||||
value: Exclude<PreferenceTypes[P], undefined>,
|
||||
{ skipIdb = false } = { skipIdb: false }
|
||||
) => {
|
||||
setPreferences(prev => ({ ...prev, [name]: value }))
|
||||
putPreference(name, value)
|
||||
if (!skipIdb) putPreference(name, value)
|
||||
|
||||
// ? is there a better way to type this?
|
||||
if (name === 'darkMode') setDarkMode(value as boolean)
|
||||
if (name === 'darkMode' && isDarkMode !== value)
|
||||
setDarkMode(value as boolean)
|
||||
},
|
||||
[setDarkMode]
|
||||
[setDarkMode, isDarkMode]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setPreference('darkMode', isDarkMode)
|
||||
}, [isDarkMode, setPreference])
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPreferencesFromIdb = async () => {
|
||||
const preferenceList = await getAllPreferences()
|
||||
// todo i can change this to only change state once but i'm too sleepy now
|
||||
for (const preference of preferenceList) {
|
||||
// @ts-expect-error i should type this better but should be fine for now
|
||||
setPreference(preference.name, preference.value, { skipIdb: true })
|
||||
}
|
||||
}
|
||||
fetchPreferencesFromIdb()
|
||||
}, [setPreference])
|
||||
|
||||
return (
|
||||
<preferencesContext.Provider value={{ preferences, setPreference }}>
|
||||
{children}
|
||||
|
|
|
@ -54,7 +54,7 @@ export const openDb = async (version = 1) => {
|
|||
autoIncrement: true,
|
||||
})
|
||||
db.createObjectStore('preferences', {
|
||||
keyPath: 'id',
|
||||
keyPath: 'name',
|
||||
autoIncrement: true,
|
||||
})
|
||||
},
|
||||
|
|
|
@ -1,30 +1,15 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
import { useCurrentAccount } from '../context/accountContext'
|
||||
import type { AccountHistoryResponse } from '../types'
|
||||
import fetchAccountHistory from '../xno/fetchAccountHistory'
|
||||
|
||||
type ReturnValue =
|
||||
| {
|
||||
accountHistory: undefined
|
||||
loading: true
|
||||
}
|
||||
| { accountHistory: AccountHistoryResponse; loading: false }
|
||||
|
||||
const useAccountHistory = (): ReturnValue => {
|
||||
const [accountHistory, setAccountHistory] = useState<
|
||||
AccountHistoryResponse | undefined
|
||||
>(undefined)
|
||||
|
||||
const useAccountHistory = () => {
|
||||
const account = useCurrentAccount()
|
||||
|
||||
useEffect(() => {
|
||||
if (account !== undefined)
|
||||
fetchAccountHistory(account.address).then(setAccountHistory)
|
||||
}, [account])
|
||||
|
||||
const loading = accountHistory === undefined
|
||||
return { accountHistory, loading } as ReturnValue
|
||||
return useSWR<AccountHistoryResponse>(
|
||||
account !== undefined ? `history:${account.address}` : null,
|
||||
(key: string) => fetchAccountHistory(key.split(':')[1])
|
||||
)
|
||||
}
|
||||
|
||||
export default useAccountHistory
|
||||
|
|
|
@ -1,30 +1,15 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
import { useCurrentAccount } from '../context/accountContext'
|
||||
import { AccountInfoResponse } from '../types'
|
||||
import fetchAccountInfo from '../xno/fetchAccountInfo'
|
||||
|
||||
type ReturnValue =
|
||||
| {
|
||||
accountInfo: undefined
|
||||
loading: true
|
||||
}
|
||||
| { accountInfo: AccountInfoResponse; loading: false }
|
||||
|
||||
const useAccountInfo = (): ReturnValue => {
|
||||
const [accountInfo, setAccountInfo] = useState<
|
||||
AccountInfoResponse | undefined
|
||||
>(undefined)
|
||||
|
||||
const useAccountInfo = () => {
|
||||
const account = useCurrentAccount()
|
||||
|
||||
useEffect(() => {
|
||||
if (account !== undefined)
|
||||
fetchAccountInfo(account.address).then(setAccountInfo)
|
||||
}, [account])
|
||||
|
||||
const loading = accountInfo === undefined
|
||||
return { accountInfo, loading } as ReturnValue
|
||||
return useSWR<AccountInfoResponse>(
|
||||
account !== undefined ? `info:${account.address}` : null,
|
||||
(key: string) => fetchAccountInfo(key.split(':')[1])
|
||||
)
|
||||
}
|
||||
|
||||
export default useAccountInfo
|
||||
|
|
|
@ -1,66 +1,92 @@
|
|||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
import { useCurrentAccount } from '../context/accountContext'
|
||||
import type { AccountReceivableResponse, BlocksInfoResponse } from '../types'
|
||||
import type {
|
||||
AccountReceivableResponse,
|
||||
BlocksInfoResponse,
|
||||
ConfirmationMessage,
|
||||
} from '../types'
|
||||
import fetchAccountReceivable from '../xno/fetchAccountReceivable'
|
||||
import fetchBlocksInfo from '../xno/fetchBlocksInfo'
|
||||
import useListenToReceivables from './useListenToReceivables'
|
||||
|
||||
type ReturnValue = (
|
||||
| {
|
||||
accountReceivable: undefined
|
||||
blocksInfo: undefined
|
||||
loading: true
|
||||
}
|
||||
| {
|
||||
accountReceivable: AccountReceivableResponse
|
||||
blocksInfo: BlocksInfoResponse
|
||||
loading: false
|
||||
}
|
||||
) & {
|
||||
mutate: (prev: {
|
||||
accountReceivable: AccountReceivableResponse | undefined
|
||||
blocksInfo: BlocksInfoResponse | undefined
|
||||
}) => {
|
||||
accountReceivable: AccountReceivableResponse | undefined
|
||||
blocksInfo: BlocksInfoResponse | undefined
|
||||
}
|
||||
}
|
||||
|
||||
const useAccountReceivable = (): ReturnValue => {
|
||||
const [accountReceivableWithInfo, setAccountReceivableWithInfo] = useState<{
|
||||
accountReceivable: AccountReceivableResponse | undefined
|
||||
blocksInfo: BlocksInfoResponse | undefined
|
||||
}>({ accountReceivable: undefined, blocksInfo: undefined })
|
||||
|
||||
const useAccountReceivable = () => {
|
||||
const account = useCurrentAccount()
|
||||
|
||||
useEffect(() => {
|
||||
if (account !== undefined) {
|
||||
const fetchReceivable = async () => {
|
||||
const receivableBlocks = await fetchAccountReceivable(account.address)
|
||||
const blocksInfo = await fetchBlocksInfo(
|
||||
const { data: receivableBlocks, mutate: mutateReceivable } =
|
||||
useSWR<AccountReceivableResponse>(
|
||||
account !== undefined ? `receivable:${account.address}` : null,
|
||||
(key: string) => fetchAccountReceivable(key.split(':')[1])
|
||||
)
|
||||
|
||||
const { data: receivableBlocksInfo } = useSWR<BlocksInfoResponse>(
|
||||
receivableBlocks !== undefined
|
||||
? [
|
||||
Object.values(receivableBlocks.blocks)
|
||||
.map(blocks => Object.keys(blocks))
|
||||
.flat()
|
||||
)
|
||||
setAccountReceivableWithInfo({
|
||||
accountReceivable: receivableBlocks,
|
||||
blocksInfo,
|
||||
})
|
||||
}
|
||||
.join(','),
|
||||
]
|
||||
: null,
|
||||
(key: string) => fetchBlocksInfo(key.split(','))
|
||||
)
|
||||
|
||||
fetchReceivable()
|
||||
}
|
||||
}, [account])
|
||||
const onBlockReceived = useCallback(
|
||||
(hash: string) => {
|
||||
mutateReceivable(async current => {
|
||||
if (current === undefined || account === undefined) return current
|
||||
const { blocks } = current
|
||||
const currBlocks = blocks[account.address]
|
||||
if (currBlocks === '') return current
|
||||
const newBlocks = { ...currBlocks }
|
||||
delete newBlocks[hash]
|
||||
return { blocks: { [account.address]: newBlocks } }
|
||||
})
|
||||
},
|
||||
[mutateReceivable, account]
|
||||
)
|
||||
|
||||
const addReceivable = useCallback(
|
||||
({
|
||||
hash,
|
||||
amount,
|
||||
source,
|
||||
}: {
|
||||
hash: string
|
||||
amount: string
|
||||
source: string
|
||||
}) => {
|
||||
mutateReceivable(async current => {
|
||||
if (current === undefined || account === undefined) return current
|
||||
const { blocks } = current
|
||||
const currBlocks = blocks[account.address]
|
||||
if (currBlocks === '') return current
|
||||
const newBlocks = { ...currBlocks, [hash]: { amount, source } }
|
||||
return { blocks: { [account.address]: newBlocks } }
|
||||
})
|
||||
},
|
||||
[mutateReceivable, account]
|
||||
)
|
||||
|
||||
const onNewReceivable = useCallback(
|
||||
(confirmation: ConfirmationMessage) => {
|
||||
addReceivable({
|
||||
hash: confirmation.message.hash,
|
||||
amount: confirmation.message.amount,
|
||||
source: confirmation.message.account,
|
||||
})
|
||||
},
|
||||
[addReceivable]
|
||||
)
|
||||
|
||||
useListenToReceivables(onNewReceivable)
|
||||
|
||||
const loading =
|
||||
accountReceivableWithInfo.accountReceivable === undefined &&
|
||||
accountReceivableWithInfo.blocksInfo === undefined
|
||||
return {
|
||||
...accountReceivableWithInfo,
|
||||
loading,
|
||||
mutate: setAccountReceivableWithInfo,
|
||||
} as ReturnValue
|
||||
receivableBlocks,
|
||||
receivableBlocksInfo,
|
||||
onBlockReceived,
|
||||
}
|
||||
}
|
||||
|
||||
export default useAccountReceivable
|
||||
|
|
|
@ -2,12 +2,13 @@ import { useEffect, useRef } from 'react'
|
|||
|
||||
import { useCurrentAccount } from '../context/accountContext'
|
||||
import type { ConfirmationMessage } from '../types'
|
||||
import { defaultUrls } from '../xno/constants'
|
||||
|
||||
/**
|
||||
* _please don't forget to memo `onConfirmation`_ :)
|
||||
* @param onConfirmation the callback to call with the new confirmation
|
||||
*/
|
||||
const useListenToConfirmations = (
|
||||
const useListenToReceivables = (
|
||||
onConfirmation: (confirmation: ConfirmationMessage) => void
|
||||
) => {
|
||||
const account = useCurrentAccount()
|
||||
|
@ -15,7 +16,7 @@ const useListenToConfirmations = (
|
|||
const wsRef = useRef<WebSocket>()
|
||||
|
||||
useEffect(() => {
|
||||
wsRef.current = new WebSocket('wss://socket.nanos.cc/')
|
||||
wsRef.current = new WebSocket(defaultUrls.ws)
|
||||
return () => {
|
||||
if (
|
||||
wsRef.current?.readyState !== WebSocket.CLOSING &&
|
||||
|
@ -44,7 +45,8 @@ const useListenToConfirmations = (
|
|||
const parsed = JSON.parse(data) as ConfirmationMessage
|
||||
if (
|
||||
parsed.topic !== 'confirmation' ||
|
||||
parsed.message.block.subtype !== 'send'
|
||||
parsed.message.block.subtype !== 'send' ||
|
||||
parsed.message.account !== account.address
|
||||
)
|
||||
return
|
||||
onConfirmation(parsed)
|
||||
|
@ -61,4 +63,4 @@ const useListenToConfirmations = (
|
|||
}, [account, onConfirmation])
|
||||
}
|
||||
|
||||
export default useListenToConfirmations
|
||||
export default useListenToReceivables
|
|
@ -4,3 +4,8 @@ export const zeroString =
|
|||
export const receiveDiff = 'fffffe0000000000'
|
||||
|
||||
export const sendDiff = 'fffffff800000000'
|
||||
|
||||
export const defaultUrls = {
|
||||
rpc: process.env.NEXT_PUBLIC_DEFAULT_NANO_NODE_RPC!,
|
||||
ws: process.env.NEXT_PUBLIC_DEFAULT_NANO_NODE_WS!,
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import fetcher from '../fetcher'
|
||||
import { AccountHistoryResponse } from '../types'
|
||||
import { defaultUrls } from './constants'
|
||||
|
||||
const fetchAccountHistory = (address: string, count = 20, head = undefined) =>
|
||||
fetcher('https://proxy.powernode.cc/proxy', {
|
||||
fetcher(defaultUrls.rpc, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'account_history',
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import fetcher from '../fetcher'
|
||||
import { AccountInfoResponse } from '../types'
|
||||
import { defaultUrls } from './constants'
|
||||
|
||||
const fetchAccountInfo = (address: string) =>
|
||||
fetcher('https://proxy.powernode.cc/proxy', {
|
||||
fetcher(defaultUrls.rpc, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'account_info',
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import fetcher from '../fetcher'
|
||||
import type { AccountReceivableResponse } from '../types'
|
||||
import { defaultUrls } from './constants'
|
||||
|
||||
const _fetchAccountReceivable = (
|
||||
address: string,
|
||||
count = 20,
|
||||
version22 = false
|
||||
) =>
|
||||
fetcher('https://proxy.powernode.cc/proxy', {
|
||||
fetcher(defaultUrls.rpc, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: version22 ? 'accounts_pending' : 'accounts_receivable',
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import fetcher from '../fetcher'
|
||||
import type { BlocksInfoResponse } from '../types'
|
||||
import { defaultUrls } from './constants'
|
||||
|
||||
const fetchBlocksInfo = (hashes: string[]) =>
|
||||
fetcher('https://proxy.powernode.cc/proxy', {
|
||||
fetcher(defaultUrls.rpc, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'blocks_info',
|
||||
|
|
|
@ -7,6 +7,7 @@ import decryptSeed from '../decryptSeed'
|
|||
import fetcher from '../fetcher'
|
||||
import { ProcessResponse } from '../types'
|
||||
import accountAtIndex from './accountAtIndex'
|
||||
import { defaultUrls } from './constants'
|
||||
|
||||
const sendNano = async (
|
||||
blockData: Parameters<typeof block['receive']>[0],
|
||||
|
@ -18,18 +19,15 @@ const sendNano = async (
|
|||
)
|
||||
|
||||
const signedBlock = block.receive(blockData, privateKey)
|
||||
const processResponse = await fetcher<ProcessResponse>(
|
||||
'https://proxy.powernode.cc/proxy',
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'process',
|
||||
json_block: 'true',
|
||||
subtype: 'receive',
|
||||
block: signedBlock,
|
||||
},
|
||||
}
|
||||
)
|
||||
const processResponse = await fetcher<ProcessResponse>(defaultUrls.rpc, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'process',
|
||||
json_block: 'true',
|
||||
subtype: 'receive',
|
||||
block: signedBlock,
|
||||
},
|
||||
})
|
||||
|
||||
if ('error' in processResponse) throw new Error()
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import decryptSeed from '../decryptSeed'
|
|||
import fetcher from '../fetcher'
|
||||
import { ProcessResponse } from '../types'
|
||||
import accountAtIndex from './accountAtIndex'
|
||||
import { defaultUrls } from './constants'
|
||||
|
||||
const sendNano = async (
|
||||
blockData: Parameters<typeof block['send']>[0],
|
||||
|
@ -17,18 +18,15 @@ const sendNano = async (
|
|||
)
|
||||
|
||||
const signedBlock = block.send(blockData, privateKey)
|
||||
const processResponse = await fetcher<ProcessResponse>(
|
||||
'https://proxy.powernode.cc/proxy',
|
||||
{
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'process',
|
||||
json_block: 'true',
|
||||
subtype: 'send',
|
||||
block: signedBlock,
|
||||
},
|
||||
}
|
||||
)
|
||||
const processResponse = await fetcher<ProcessResponse>(defaultUrls.rpc, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
action: 'process',
|
||||
json_block: 'true',
|
||||
subtype: 'send',
|
||||
block: signedBlock,
|
||||
},
|
||||
})
|
||||
|
||||
if ('error' in processResponse) throw new Error()
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@
|
|||
"prettier": "^2.4.1",
|
||||
"qrcode": "^1.5.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"swr": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@trivago/prettier-plugin-sort-imports": "^3.1.1",
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import type { AppProps } from 'next/app'
|
||||
import { FC } from 'react'
|
||||
import { SWRConfig } from 'swr'
|
||||
|
||||
import Layout from '../components/Layout'
|
||||
import MemCacheProvider from '../lib/context/memCacheContextProvider'
|
||||
import fetcher from '../lib/fetcher'
|
||||
import useProtectedRoutes from '../lib/hooks/useProtectedRoutes'
|
||||
import useSetupDb from '../lib/hooks/useSetupDb'
|
||||
import useSetupServiceWorker from '../lib/hooks/useSetupServiceWorker'
|
||||
|
@ -10,17 +12,19 @@ import '../styles/global.css'
|
|||
|
||||
const MyApp: FC<AppProps> = ({ Component, pageProps }) => {
|
||||
useSetupServiceWorker()
|
||||
const ready = useSetupDb(10)
|
||||
const ready = useSetupDb()
|
||||
const validatingCredential = useProtectedRoutes(!ready)
|
||||
|
||||
if (validatingCredential) return null // todo
|
||||
|
||||
return (
|
||||
<MemCacheProvider>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</MemCacheProvider>
|
||||
<SWRConfig value={{ fetcher }}>
|
||||
<MemCacheProvider>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</MemCacheProvider>
|
||||
</SWRConfig>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,16 @@ const Done: NextPage = () => {
|
|||
<div className="flex flex-col h-full justify-start items-center text-center px-4 gap-2 text-purple-50">
|
||||
<h1 className="text-9xl font-extrabold">3</h1>
|
||||
<p className="text-3xl">you're done!</p>
|
||||
<p className="text-xl">
|
||||
<p className="text-xl mb-5">
|
||||
all the buttons are now enabled and you can start using <b>zep</b> and{' '}
|
||||
<b>nano</b>!
|
||||
</p>
|
||||
<button
|
||||
className="dark:bg-gray-900 dark:text-purple-100 py-2 px-5 rounded text-xl bg-purple-50 font-bold text-gray-900"
|
||||
onClick={() => push('/dashboard')}
|
||||
>
|
||||
go!
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
"expand target gospel goose oppose acquire genius hurdle trade size huge pact square silk canal bar curve shallow pistol push crowd glory slice news",
|
||||
"81631b9773ab0f4ff0d61a0ae092ec564acc01912aedbd849c420e10b2c38c7edc2c5494f778d3122a5933261c3a09b17f1b1be33d3b068d3228529a24bf94b3",
|
||||
"738b5cb3315dd267fc882f1388911162ee9a241c800bf583c07c1bbbc34c64a9c2099e01b5d5e3ab217012717e3ecfbe3f735af431c7e105e1719eeb03476af5",
|
||||
"75edd150e09bada0f5c68fab14c0b385239cc396ef6600029dea0f7b66be63fa6a7d8ebe30aaff66634f583c4e68067c0de136e27dddc96be76d2ac5bb14ef71"
|
||||
"75edd150e09bada0f5c68fab14c0b385239cc396ef6600029dea0f7b66be63fa6a7d8ebe30aaff66634f583c4e68067c0de136e27dddc96be76d2ac5bb14ef71",
|
||||
"b6203f57196d244f7eb6971cef41a4cd4ce961e81ae8046f4f8274a4057cb77b4ef97ad3c25e14b50598c1243fa3253644b44e89c8648fd9c18b3703d36372bb"
|
||||
]
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -1627,6 +1627,11 @@ depd@~1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
dequal@2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d"
|
||||
integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==
|
||||
|
||||
des.js@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843"
|
||||
|
@ -4671,6 +4676,13 @@ swap-case@^1.1.0:
|
|||
lower-case "^1.1.1"
|
||||
upper-case "^1.1.1"
|
||||
|
||||
swr@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/swr/-/swr-1.0.1.tgz#15f62846b87ee000e52fa07812bb65eb62d79483"
|
||||
integrity sha512-EPQAxSjoD4IaM49rpRHK0q+/NzcwoT8c0/Ylu/u3/6mFj/CWnQVjNJ0MV2Iuw/U+EJSd2TX5czdAwKPYZIG0YA==
|
||||
dependencies:
|
||||
dequal "2.0.2"
|
||||
|
||||
table@^6.0.9, table@^6.7.3:
|
||||
version "6.7.3"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-6.7.3.tgz#255388439715a738391bd2ee4cbca89a4d05a9b7"
|
||||
|
|
Reference in a new issue