feat: good now? :)

This commit is contained in:
Filipe Medeiros 2021-11-28 01:32:25 +00:00
parent 18eef39275
commit a9afee477e
16 changed files with 363 additions and 248 deletions

View file

@ -1,25 +1,18 @@
import { LoginIcon, PaperAirplaneIcon } from '@heroicons/react/outline'
import {
CheckIcon,
DocumentDuplicateIcon,
DownloadIcon,
HomeIcon,
KeyIcon,
LibraryIcon,
QrcodeIcon,
RssIcon,
UploadIcon,
ShareIcon,
} from '@heroicons/react/solid'
import clsx from 'clsx'
import { AES, enc } from 'crypto-js'
import { useRouter } from 'next/router'
import { FC, useState } from 'react'
import { checkBiometrics } from '../lib/biometrics'
import computeWorkAsync from '../lib/computeWorkAsync'
import { useAccount, useAccounts } from '../lib/context/accountContext'
import { useAccount } from '../lib/context/accountContext'
import { usePreferences } from '../lib/context/preferencesContext'
import { getEncryptedSeed } from '../lib/db/encryptedSeeds'
import decryptSeed from '../lib/decryptSeed'
import useIsWelcoming from '../lib/hooks/useIsWelcoming'
export interface Props {
@ -34,15 +27,6 @@ const BottomMenu: FC<Props> = ({ className }) => {
const account = useAccount()
const [confirmCopyAddress, setConfirmCopyAddress] = useState(false)
const [confirmCopySeed, setConfirmCopySeed] = useState(false)
const onCopySeed = async () => {
const seed = await decryptSeed('os')
navigator.clipboard.writeText(seed)
setConfirmCopySeed(true)
setTimeout(() => setConfirmCopySeed(false), 1500)
}
const onCopyAddress = () => {
if (account !== undefined) {
navigator.clipboard.writeText(account.address)
@ -51,6 +35,13 @@ const BottomMenu: FC<Props> = ({ className }) => {
}
}
const onShare = () => {
navigator.share({
title: 'This is my nano address!',
text: account?.address,
})
}
const isWelcoming = useIsWelcoming()
return (
@ -75,94 +66,66 @@ const BottomMenu: FC<Props> = ({ className }) => {
<div>
<div
className={clsx(
'flex gap-8 items-end',
'flex gap-10 items-end',
leftHanded ? 'flex-row-reverse' : 'flex-row'
)}
>
<div
className={clsx('flex gap-6 items-end', {
'flex-row-reverse': leftHanded,
})}
>
<div className="flex flex-col h-16 justify-between">
<button
disabled={isWelcoming || confirmCopySeed}
className={clsx(
'p-1 h-7 rounded shadow-lg',
confirmCopySeed
? 'bg-purple-50'
: 'bg-purple-500 hover:bg-purple-400 disabled:hover:bg-purple-500 disabled:cursor-default'
)}
onClick={onCopySeed}
>
{confirmCopySeed ? (
<CheckIcon className="h-full text-purple-500" />
) : (
<KeyIcon className="h-full text-purple-50 dark:text-gray-900" />
)}
</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"
>
<LibraryIcon className="h-full text-purple-50 dark:text-gray-900" />
</button>
</div>
<div className="flex flex-col h-16 justify-between">
<button
disabled={isWelcoming || confirmCopyAddress}
className={clsx(
'p-1 h-7 rounded shadow-lg',
confirmCopyAddress
? 'bg-purple-50'
: 'bg-purple-500 hover:bg-purple-400 disabled:hover:bg-purple-500 disabled:cursor-default'
)}
onClick={onCopyAddress}
>
{confirmCopyAddress ? (
<CheckIcon className="h-full text-purple-500" />
) : (
<DocumentDuplicateIcon className="h-full text-purple-50 dark:text-gray-900" />
)}
</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"
>
<DownloadIcon className="h-full text-purple-50 dark:text-gray-900" />
</button>
</div>
</div>
{'share' in navigator ? (
<button
disabled={isWelcoming}
className="p-1 h-16 w-7 rounded shadow-lg bg-purple-500 hover:bg-purple-400 disabled:hover:bg-purple-500 disabled:cursor-default"
onClick={onShare}
>
<ShareIcon className="h-full text-purple-50 dark:text-gray-900" />
</button>
) : (
<button
disabled={isWelcoming || confirmCopyAddress}
className={clsx(
'p-1 h-16 w-10 rounded shadow-lg',
confirmCopyAddress
? 'bg-purple-50'
: 'bg-purple-500 hover:bg-purple-400 disabled:hover:bg-purple-500 disabled:cursor-default'
)}
onClick={onCopyAddress}
>
{confirmCopyAddress ? (
<CheckIcon className="text-purple-500" />
) : (
<DocumentDuplicateIcon className="text-purple-50 dark:text-gray-900" />
)}
</button>
)}
<div className="relative h-16">
<button
disabled={isWelcoming}
className={clsx(
'bg-purple-500 absolute top-0 h-16 w-7 rounded-r hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-md disabled:cursor-default',
'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')}
>
<UploadIcon className="h-full text-purple-50 dark:text-gray-900 w-full" />
<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-3 h-16 shadow-lg">
<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 h-16 w-7 hover:bg-purple-400 disabled:hover:bg-purple-500 shadow-md disabled:cursor-default',
'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'
)}
onClick={() => push('/receive/qr')}
>
<DownloadIcon className="h-full text-purple-50 dark:text-gray-900 w-full" />
<LoginIcon className="h-full text-purple-50 dark:text-gray-900 w-full -rotate-90" />
</button>
</div>
@ -171,7 +134,7 @@ const BottomMenu: FC<Props> = ({ className }) => {
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"
>
<UploadIcon className="h-full text-purple-50 dark:text-gray-900" />
<PaperAirplaneIcon className="h-full text-purple-50 dark:text-gray-900 rotate-[30deg] translate-x-[2px]" />
</button>
<button
disabled={isWelcoming}

View file

@ -5,11 +5,9 @@ import { FC } from 'react'
import { usePreferences } from '../lib/context/preferencesContext'
import useListenToColorMedia from '../lib/hooks/useListenToColorMedia'
import useProtectedRoutes from '../lib/hooks/useProtectedRoutes'
import useSetupDb from '../lib/hooks/useSetupDb'
import Balance from './Balance'
import BottomMenu from './BottomMenu'
import PreferencesMenu from './PreferencesMenu'
import TopMenu from './TopMenu'
export interface Props {}
@ -41,7 +39,7 @@ const Layout: FC<Props> = ({ children }) => {
</h1>
<LightningBoltIcon className="text-gray-900 dark:text-purple-100 h-4" />
</div>
<PreferencesMenu />
<TopMenu />
</header>
{pathname !== '/' ? (
<>

View file

@ -1,111 +0,0 @@
import {
CogIcon,
FingerPrintIcon,
HandIcon,
MoonIcon,
} from '@heroicons/react/solid'
import clsx from 'clsx'
import { FC, useRef, useState } from 'react'
import colors from 'tailwindcss/colors'
import { usePreferences } from '../lib/context/preferencesContext'
import useClickAway from '../lib/hooks/useClickAway'
import useIsiOS from '../lib/hooks/useIsiOS'
export interface Props {}
const PreferencesMenu: FC<Props> = () => {
const [showMenu, setShowMenu] = useState(false)
const toggleMenu = () => setShowMenu(prev => !prev)
const menuRef = useRef<HTMLDivElement>(null)
useClickAway(menuRef, () => setShowMenu(false))
const {
preferences: { darkMode, biometricsAuth, leftHanded },
setPreference,
} = usePreferences()
const isiOS = useIsiOS()
return (
<div className="relative z-20 justify-center" ref={menuRef}>
<button
className={clsx(
'w-10 p-1 rounded bg-purple-500 shadow-md text-purple-50 dark:text-gray-900 hover:cursor-pointer hover:bg-purple-400 transition-colors dark text-purple-50:dark:text-gray-900'
)}
onClick={toggleMenu}
>
<CogIcon className="w-full" />
</button>
<ul
role="menu"
className={clsx(
'transition-all flex flex-col gap-2 absolute w-10 bg-purple-400 p-1 rounded mt-3',
showMenu ? 'opacity-100' : 'opacity-0 -translate-y-2'
)}
style={{
boxShadow: `${colors.coolGray[900]} 0px 2px 15px`,
}}
>
{!isiOS && (
<li role="menuitem">
<button
disabled={!showMenu}
className={clsx(
'p-1 rounded transition-colors duration-100 w-full',
biometricsAuth
? 'dark:text-purple-400 dark:bg-gray-900 dark:hover:text-purple-400 shadow-md hover:bg-purple-400 bg-purple-50 text-purple-400'
: 'hover:text-purple-400 bg-purple-400 dark:text-gray-900 text-purple-50 dark:hover:text-purple-300',
showMenu ? 'hover:cursor-pointer' : 'cursor-default'
)}
onClick={() => {
setPreference('biometricsAuth', !biometricsAuth)
setShowMenu(false)
}}
>
<FingerPrintIcon className="h-full" />
</button>
</li>
)}
<li role="menuitem">
<button
disabled={!showMenu}
className={clsx(
'p-1 rounded transition-colors duration-100 w-full',
darkMode
? 'dark:text-purple-400 dark:bg-gray-900 dark:hover:text-purple-400 shadow-md hover:bg-purple-400 bg-purple-50 text-purple-400'
: 'hover:text-purple-400 bg-purple-400 dark:text-gray-900 text-purple-50 dark:hover:text-purple-300',
showMenu ? 'hover:cursor-pointer' : 'cursor-default'
)}
onClick={() => {
setPreference('darkMode', !darkMode)
setShowMenu(false)
}}
>
<MoonIcon className="h-full" />
</button>
</li>
<li role="menuitem">
<button
disabled={!showMenu}
className={clsx(
'p-1 rounded transition-colors duration-100 w-full',
leftHanded
? 'dark:text-purple-400 dark:bg-gray-900 dark:hover:text-purple-400 shadow-md hover:bg-purple-400 bg-purple-50 text-purple-400'
: 'hover:text-purple-400 bg-purple-400 dark:text-gray-900 dark:hover:text-purple-300 text-purple-50',
showMenu ? 'hover:cursor-pointer' : 'cursor-default'
)}
onClick={() => {
setPreference('leftHanded', !leftHanded)
setShowMenu(false)
}}
>
<HandIcon className="h-full" />
</button>
</li>
</ul>
</div>
)
}
export default PreferencesMenu

View file

@ -1,16 +1,9 @@
import { DownloadIcon, UploadIcon } from '@heroicons/react/solid'
import clsx from 'clsx'
import { tools } from 'nanocurrency-web'
import { FC, useCallback, useMemo } from 'react'
import { FC } from 'react'
import { useAccount } from '../lib/context/accountContext'
import fetcher from '../lib/fetcher'
import useReceiveNano from '../lib/hooks/useReceiveNano'
import {
AccountHistoryResponse,
AccountPendingResponse,
BlocksInfoResponse,
} from '../lib/types'
const rawToNanoDisplay = (raw: string) =>
Number(tools.convert(raw, 'RAW', 'NANO').slice(0, 20))

185
components/TopMenu.tsx Normal file
View file

@ -0,0 +1,185 @@
import {
CogIcon,
FingerPrintIcon,
HandIcon,
KeyIcon,
LibraryIcon,
MoonIcon,
PlusIcon,
UsersIcon,
} from '@heroicons/react/solid'
import clsx from 'clsx'
import { FC, useRef, useState } from 'react'
import colors from 'tailwindcss/colors'
import { usePreferences } from '../lib/context/preferencesContext'
import decryptSeed from '../lib/decryptSeed'
import useClickAway from '../lib/hooks/useClickAway'
import useIsWelcoming from '../lib/hooks/useIsWelcoming'
import useIsiOS from '../lib/hooks/useIsiOS'
export interface Props {}
const TopMenu: FC<Props> = () => {
const [showPreferences, setShowPreferences] = useState(false)
const togglePreferences = () => setShowPreferences(prev => !prev)
const preferencesRef = useRef<HTMLDivElement>(null)
useClickAway(preferencesRef, () => setShowPreferences(false))
const {
preferences: { darkMode, biometricsAuth, leftHanded },
setPreference,
} = usePreferences()
const [showAdvanced, setShowAdvanced] = useState(false)
const toggleAdvanced = () => setShowAdvanced(prev => !prev)
const advancedRef = useRef<HTMLDivElement>(null)
useClickAway(advancedRef, () => setShowAdvanced(false))
const isiOS = useIsiOS()
const isWelcoming = useIsWelcoming()
const onCopySeed = async () => {
const seed = await decryptSeed('os')
navigator.clipboard.writeText(seed)
}
return (
<div
className={clsx('flex gap-3 justify-center', {
'flex-row-reverse': leftHanded,
})}
>
{!isWelcoming && (
<div className="relative z-20" ref={advancedRef}>
<button
className={clsx(
'w-10 p-1 rounded bg-purple-500 shadow-md text-purple-50 dark:text-gray-900 hover:cursor-pointer hover:bg-purple-400 transition-colors dark:hover:text-purple-50 dark:text-gray-900'
)}
onClick={toggleAdvanced}
>
<PlusIcon className="w-full" />
</button>
<ul
role="menu"
className={clsx(
'transition-all flex flex-col gap-2 absolute w-10 bg-purple-400 p-1 rounded mt-3 hover:text-white',
showAdvanced ? 'opacity-100' : 'opacity-0 -translate-y-2'
)}
style={{
boxShadow: `${colors.coolGray[900]} 0px 2px 15px`,
}}
>
<button
className={clsx(
'p-1 rounded transition-colors duration-100 w-full hover:text-purple-400 bg-purple-400 dark:text-gray-900 text-purple-50 dark:hover:text-purple-50'
)}
onClick={onCopySeed}
>
<KeyIcon className="h-full" />
</button>
<button
className={clsx(
'p-1 rounded transition-colors duration-100 w-full hover:text-purple-400 bg-purple-400 dark:text-gray-900 text-purple-50 dark:hover:text-purple-50'
)}
onClick={() => {
// todo
}}
>
<LibraryIcon className="h-full" />
</button>
<button
className={clsx(
'p-1 rounded transition-colors duration-100 w-full hover:text-purple-400 bg-purple-400 dark:text-gray-900 text-purple-50 dark:hover:text-purple-50'
)}
onClick={onCopySeed}
>
<UsersIcon className="h-full" />
</button>
</ul>
</div>
)}
<div className="relative z-20" ref={preferencesRef}>
<button
className={clsx(
'w-10 p-1 rounded bg-purple-500 shadow-md text-purple-50 hover:cursor-pointer hover:bg-purple-400 transition-colors dark:hover:text-purple-50 dark:text-gray-900'
)}
onClick={togglePreferences}
>
<CogIcon className="w-full" />
</button>
<ul
role="menu"
className={clsx(
'transition-all flex flex-col gap-2 absolute w-10 bg-purple-400 p-1 rounded mt-3',
showPreferences ? 'opacity-100' : 'opacity-0 -translate-y-2'
)}
style={{
boxShadow: `${colors.coolGray[900]} 0px 2px 15px`,
}}
>
{!isiOS && (
<li role="menuitem">
<button
disabled={!showPreferences}
className={clsx(
'p-1 rounded transition-colors duration-100 w-full dark:hover:text-purple-50',
biometricsAuth
? 'dark:text-purple-400 dark:bg-gray-900 shadow-md hover:bg-purple-400 bg-purple-50 text-purple-400'
: 'hover:text-purple-400 bg-purple-400 dark:text-gray-900 text-purple-50',
showPreferences ? 'hover:cursor-pointer' : 'cursor-default'
)}
onClick={() => {
setPreference('biometricsAuth', !biometricsAuth)
setShowPreferences(false)
}}
>
<FingerPrintIcon className="h-full" />
</button>
</li>
)}
<li role="menuitem">
<button
disabled={!showPreferences}
className={clsx(
'p-1 rounded transition-colors duration-100 w-full dark:hover:text-purple-50',
darkMode
? 'dark:text-purple-400 dark:bg-gray-900 shadow-md hover:bg-purple-400 bg-purple-50 text-purple-400'
: 'hover:text-purple-400 bg-purple-400 dark:text-gray-900 text-purple-50',
showPreferences ? 'hover:cursor-pointer' : 'cursor-default'
)}
onClick={() => {
setPreference('darkMode', !darkMode)
setShowPreferences(false)
}}
>
<MoonIcon className="h-full" />
</button>
</li>
<li role="menuitem">
<button
disabled={!showPreferences}
className={clsx(
'p-1 rounded transition-colors duration-100 w-full dark:hover:text-purple-50',
leftHanded
? 'dark:text-purple-400 dark:bg-gray-900 shadow-md hover:bg-purple-400 bg-purple-50 text-purple-400'
: 'hover:text-purple-400 bg-purple-400 dark:text-gray-900 text-purple-50',
showPreferences ? 'hover:cursor-pointer' : 'cursor-default'
)}
onClick={() => {
setPreference('leftHanded', !leftHanded)
setShowPreferences(false)
}}
>
<HandIcon className="h-full" />
</button>
</li>
</ul>
</div>
</div>
)
}
export default TopMenu

View file

@ -15,7 +15,7 @@ export interface PreferenceContextValue {
preferences: PreferenceTypes
setPreference: <P extends PreferenceName>(
preference: P,
value: PreferenceTypes[P]
value: Exclude<PreferenceTypes[P], undefined>
) => void
}
@ -53,12 +53,21 @@ export const PreferencesProvider: FC = ({ children }) => {
}
fetchPreferencesFromIdb()
}, [])
const setDarkMode = useDarkMode()
const setPreference = useCallback(
<P extends PreferenceName>(name: P, value: PreferenceTypes[P]) => {
<P extends PreferenceName>(
name: P,
value: Exclude<PreferenceTypes[P], undefined>
) => {
setPreferences(prev => ({ ...prev, [name]: value }))
putPreference(name, value)
// ? is there a better way to type this?
if (name === 'darkMode') setDarkMode(value as boolean)
},
[]
[setDarkMode]
)
return (

View file

@ -1,5 +1,18 @@
const fetcher = <T>(...args: Parameters<typeof fetch>) =>
fetch(...args).then(res => {
const fetcher = <T>(
input: Parameters<typeof fetch>[0],
init?: Omit<Exclude<Parameters<typeof fetch>[1], undefined>, 'body'> & {
method: 'POST' | 'PUT' | 'PATCH'
body?: any
}
) =>
fetch(input, {
...init,
headers:
(init?.method ?? 'GET') === 'GET'
? []
: [['Content-Type', 'application/json']],
...(init?.body !== undefined ? { body: JSON.stringify(init?.body) } : {}),
}).then(res => {
if (!res.ok) throw new Error() // todo improve this error
return res.json() as Promise<T>
})

View file

@ -1,20 +1,25 @@
import { useEffect } from 'react'
import { useCallback, useEffect } from 'react'
import { getPreference } from '../db/preferences'
const useDarkMode = (darkMode?: boolean) => {
const useDarkMode = () => {
const setDarkMode = useCallback(async (darkMode: boolean) => {
const htmlClasses = document.querySelector('html')?.classList
if (darkMode) htmlClasses?.add('dark')
else htmlClasses?.remove('dark')
}, [])
useEffect(() => {
const setDarkModeClass = async () => {
const isDark =
darkMode ??
const darkModeOnStarup = async () => {
const darkMode =
(await getPreference('darkMode')) ??
window.matchMedia('(prefers-color-scheme: dark)').matches
const htmlClasses = document.querySelector('html')?.classList
if (isDark) htmlClasses?.add('dark')
else htmlClasses?.remove('dark')
setDarkMode(darkMode)
}
setDarkModeClass()
}, [darkMode])
darkModeOnStarup()
}, [setDarkMode])
return setDarkMode
}
export default useDarkMode

View file

@ -1,8 +0,0 @@
import { useState } from 'react'
const useImperativeRender = () => {
const [, setValue] = useState(0)
return () => setValue(value => value + 1)
}
export default useImperativeRender

View file

@ -1,16 +1,17 @@
import { useEffect, useState } from 'react'
import { useEffect } from 'react'
import { useAccounts } from '../context/accountContext'
import { ConfirmationMessage } from '../types'
const useListenToTxn = () => {
const useListenToTxn = (
onConfirmation: (confirmation: ConfirmationMessage) => void
) => {
const { accounts } = useAccounts()
const [mostRecentTxn, setMostRecentTxn] = useState<any | undefined>(undefined)
useEffect(() => {
if (accounts !== undefined) {
const ws = new WebSocket('wss://ws.mynano.ninja/')
ws.onopen = () => {
console.log('subscribed')
ws.send(
JSON.stringify({
action: 'subscribe',
@ -21,16 +22,14 @@ const useListenToTxn = () => {
})
)
ws.addEventListener('message', ({ data }) => {
const parsed = JSON.parse(data)
console.log(parsed)
setMostRecentTxn(parsed)
const parsed = JSON.parse(data) as ConfirmationMessage
onConfirmation(parsed)
})
}
return () => ws.close()
}
}, [accounts])
return mostRecentTxn
}, [accounts, onConfirmation])
}
export default useListenToTxn

View file

@ -2,7 +2,7 @@ import { useCallback } from 'react'
import useSetup from './useSetup'
const useSetupSw = (skip?: boolean) =>
const useSetupServiceWorker = (skip?: boolean) =>
useSetup(
useCallback(() => {
if ('serviceWorker' in navigator) {
@ -16,4 +16,4 @@ const useSetupSw = (skip?: boolean) =>
skip
)
export default useSetupSw
export default useSetupServiceWorker

View file

@ -1,3 +1,6 @@
export type BooleanString = 'true' | 'false'
export type TransactionSubtype = 'send' | 'receive' | 'change'
export interface AccountHistoryResponse {
account: string
history:
@ -27,7 +30,7 @@ export interface BlocksInfoResponse {
balance: string
height: string
local_timestamp: string
confirmed: 'true' | 'false'
confirmed: BooleanString
contents: {
type: 'state'
account: string
@ -39,7 +42,7 @@ export interface BlocksInfoResponse {
signature: string
work: string
}
subtype: 'send' | 'receive' | 'change'
subtype: TransactionSubtype
}
}
}
@ -55,13 +58,13 @@ export type AccountInfoResponse =
account_version: string
confirmation_height: string
confirmation_height_frontier: string
confirmed_balance: '11999999999999999918751838129509869131'
confirmed_height: '22966'
confirmed_frontier: '80A6745762493FA21A22718ABFA4F635656A707B48B3324198AC7F3938DE6D4F'
representative: 'nano_1gyeqc6u5j3oaxbe5qy1hyz3q745a318kh8h9ocnpan7fuxnq85cxqboapu5'
confirmed_representative: 'nano_1gyeqc6u5j3oaxbe5qy1hyz3q745a318kh8h9ocnpan7fuxnq85cxqboapu5'
confirmed_balance: string
confirmed_height: string
confirmed_frontier: string
representative: string
confirmed_representative: string
}
| { error: 'Account not found' }
| { error: string }
export interface AccountInfoCache {
address: string
@ -85,3 +88,26 @@ export type ProcessResponse =
hash: string
}
| { error: string }
export interface ConfirmationMessage {
topic: 'confirmation'
time: string
message: {
account: string
amount: string
hash: string
confirmation_type: string
block: {
type: 'state'
account: string
previous: string
representative: string
balance: string
link: string
link_as_account: string
signature: string
work: string
subtype: TransactionSubtype
}
}
}

View file

@ -0,0 +1,15 @@
import fetcher from '../fetcher'
import { AccountHistoryResponse } from '../types'
const fetchAccountHistory = (address: string, count = 20, head = undefined) =>
fetcher('https://mynano.ninja/api/node', {
method: 'POST',
body: {
action: 'account_history',
account: address,
head,
count,
},
}) as Promise<AccountHistoryResponse>
export default fetchAccountHistory

View file

@ -4,13 +4,12 @@ import { AccountInfoResponse } from '../types'
const fetchAccountInfo = (address: string) =>
fetcher('https://mynano.ninja/api/node', {
method: 'POST',
headers: [['Content-Type', 'application/json']],
body: JSON.stringify({
body: {
action: 'account_info',
account: address,
representative: 'true',
include_confirmed: 'true',
}),
},
}) as Promise<AccountInfoResponse>
export default fetchAccountInfo

View file

@ -0,0 +1,27 @@
import fetcher from '../fetcher'
import { AccountHistoryResponse } from '../types'
const fetchAccountReceivable = (
address: string,
count = 20,
head = undefined,
version22 = false
) =>
fetcher('https://mynano.ninja/api/node', {
method: 'POST',
body: {
action: version22 ? 'account_pending' : 'account_receivable',
account: address,
head,
count,
},
}) as Promise<AccountHistoryResponse>
// most nodes haven't upgraded yet https://docs.nano.org/commands/rpc-protocol/#accounts_pending
// this will be the future api for this function
const withVersionFallback = (address: string, count = 20, head = undefined) =>
fetchAccountReceivable(address, count, head).catch(() =>
fetchAccountReceivable(address, count, head, true)
)
export default withVersionFallback

View file

@ -6,9 +6,11 @@ import Layout from '../components/Layout'
import MemCacheProvider from '../lib/context/memCacheContextProvider'
import useProtectedRoutes from '../lib/hooks/useProtectedRoutes'
import useSetupDb from '../lib/hooks/useSetupDb'
import useSetupServiceWorker from '../lib/hooks/useSetupServiceWorker'
import '../styles/global.css'
const MyApp: FC<AppProps> = ({ Component, pageProps }) => {
useSetupServiceWorker()
const ready = useSetupDb(10)
const validatingCredential = useProtectedRoutes(!ready)