feat: lots of stuff

This commit is contained in:
Filipe Medeiros 2021-12-01 21:09:28 +00:00
parent aeeb56d605
commit ec32e26cc0
11 changed files with 146 additions and 44 deletions

View file

@ -0,0 +1,30 @@
import { FC } from 'react'
// delete if not needed
export interface Props {
value: string
onChange: (value: string) => void
}
const AddressInput: FC<Props> = ({ value, onChange }) => {
return (
<div className="flex items-center w-full gap-3 text-2xl rounded transition-colors dark:bg-gray-800 bg-purple-50 focus-within:bg-purple-100 border-2 border-purple-200 dark:border-gray-700 py-2 px-4 overflow-hidden dark:focus-within:bg-gray-700">
<label htmlFor="xno-address">@</label>
<input
name="xno-address"
id="xno-address"
maxLength={65}
minLength={65}
className="bg-transparent focus:outline-none w-full"
value={value}
pattern="^(nano|xrb)_[13]{1}[13456789abcdefghijkmnopqrstuwxyz]{59}$"
onChange={({ target: { value, validity } }) => {
console.log(value, validity.patternMismatch)
if (!validity.patternMismatch) onChange(value)
}}
/>
</div>
)
}
export default AddressInput

View file

@ -41,22 +41,20 @@ const Balance: FC<Props> = ({ className }) => {
const xnoBalanceDisplay = rawToNanoDisplay(account?.balance ?? '0') const xnoBalanceDisplay = rawToNanoDisplay(account?.balance ?? '0')
return ( return (
<div <div className={clsx('dark:text-purple-50 text-gray-900 flex', className)}>
className={clsx(
'dark:text-purple-50 transition-colors text-gray-900 flex',
className
)}
>
<div <div
onClick={() => onClick={() =>
setPreference('showCurrencyDash', nextShowCurrency(showCurrencyDash)) setPreference('showCurrencyDash', nextShowCurrency(showCurrencyDash))
} }
className="hover:cursor-pointer active:translate-y-0.5" className="hover:cursor-pointer"
> >
<h3 className="text-4xl"> <h3 className="text-4xl">
Ӿ <span className="transition-colors">Ӿ</span>
<span <span
className={clsx(showXnoBalance ? 'font-medium' : 'font-semibold')} className={clsx(
'transition-colors',
showXnoBalance ? 'font-medium' : 'font-semibold'
)}
> >
{showXnoBalance ? ( {showXnoBalance ? (
account?.balance === null ? ( account?.balance === null ? (
@ -76,7 +74,7 @@ const Balance: FC<Props> = ({ className }) => {
</span> </span>
</h3> </h3>
{showFiatBalance && ( {showFiatBalance && (
<h4 className="text-xl"> <h4 className="text-xl transition-colors">
$ {(Number(xnoBalance) * xnoPrice).toFixed(2)} $ {(Number(xnoBalance) * xnoPrice).toFixed(2)}
</h4> </h4>
)} )}

View file

@ -117,7 +117,7 @@ const BottomMenu: FC<Props> = ({ className }) => {
<div className="h-16 p-1 border-t-2 border-b-2 border-purple-400 shadow-lg"> <div className="h-16 p-1 border-t-2 border-b-2 border-purple-400 shadow-lg">
<QrcodeIcon className="h-full text-gray-900 dark:text-purple-100 transition-colors" /> <QrcodeIcon className="h-full text-gray-900 dark:text-purple-100 transition-colors" />
</div> </div>
<Link href={isWelcoming ? '#' : '/send/qr'}> <Link href={isWelcoming ? '#' : '/send/qrOrAddress'}>
<a <a
className={clsx( className={clsx(
'bg-purple-400 transition-colors px-1 h-16 w-10 hover:bg-purple-400 disabled:hover:bg-purple-400 shadow-md disabled:cursor-default', 'bg-purple-400 transition-colors px-1 h-16 w-10 hover:bg-purple-400 disabled:hover:bg-purple-400 shadow-md disabled:cursor-default',

View file

@ -1,11 +1,13 @@
import { ChevronUpIcon, ClockIcon } from '@heroicons/react/outline' import { ChevronUpIcon, ClockIcon } from '@heroicons/react/outline'
import { DownloadIcon, UploadIcon } from '@heroicons/react/solid' import { DownloadIcon, UploadIcon } from '@heroicons/react/solid'
import clsx from 'clsx' import clsx from 'clsx'
import { FC, useState } from 'react' import { Unit, convert } from 'nanocurrency'
import { FC, useEffect, useState } from 'react'
import useAccountHistory from '../lib/hooks/useAccountHistory' import useAccountHistory from '../lib/hooks/useAccountHistory'
import useAccountReceivable from '../lib/hooks/useAccountReceivable' import useAccountReceivable from '../lib/hooks/useAccountReceivable'
import useReceiveNano from '../lib/hooks/useReceiveNano' import useReceiveNano from '../lib/hooks/useReceiveNano'
import showNotification from '../lib/showNotification'
import rawToNanoDisplay from '../lib/xno/rawToNanoDisplay' import rawToNanoDisplay from '../lib/xno/rawToNanoDisplay'
export interface Props {} export interface Props {}
@ -20,6 +22,7 @@ const RecentTransactions: FC<Props> = () => {
hasMore, hasMore,
loading: loadingHistory, loading: loadingHistory,
hasHistory, hasHistory,
revalidate: refetchHistory,
} = useAccountHistory() } = useAccountHistory()
const hasReceivable = const hasReceivable =
@ -33,11 +36,34 @@ const RecentTransactions: FC<Props> = () => {
const initialLoading = loadingHistory && accountHistory === undefined const initialLoading = loadingHistory && accountHistory === undefined
const onIncomingClick = async (receivable: {
hash: string
amount: string
from: string
timestamp: string
}) => {
await receive(receivable.hash, receivable.amount)
onBlockReceived(receivable.hash)
refetchHistory()
showNotification({
title: 'received!',
body: `received Ӿ${convert(receivable.amount, {
from: Unit.raw,
to: Unit.Nano,
})} from ${receivable.from}`,
tag: 'received',
})
}
return ( return (
<> <>
{hasReceivable && ( {hasReceivable && (
<section className="flex flex-col w-full gap-3"> <section className="flex flex-col w-full gap-3">
<div className="flex items-center justify-between gap-1"> <div
className="flex items-center justify-between gap-1"
onClick={() => setReceivablesExpanded(prev => !prev)}
>
<h2 className="flex-1 text-2xl font-semibold transition-colors text-gray-900 dark:text-purple-50"> <h2 className="flex-1 text-2xl font-semibold transition-colors text-gray-900 dark:text-purple-50">
incoming incoming
</h2> </h2>
@ -46,7 +72,6 @@ const RecentTransactions: FC<Props> = () => {
{receivableBlocks.length} {receivableBlocks.length}
</span> </span>
<ChevronUpIcon <ChevronUpIcon
onClick={() => setReceivablesExpanded(prev => !prev)}
className={clsx( className={clsx(
'h-8 transition-transform-child origin-center-child', 'h-8 transition-transform-child origin-center-child',
{ {
@ -68,10 +93,7 @@ const RecentTransactions: FC<Props> = () => {
> >
<button <button
className="contents" className="contents"
onClick={async () => { onClick={() => onIncomingClick(receivable)}
await receive(receivable.hash, receivable.amount)
onBlockReceived(receivable.hash)
}}
> >
<ClockIcon className="flex-shrink-0 w-6 text-yellow-400" /> <ClockIcon className="flex-shrink-0 w-6 text-yellow-400" />
@ -165,7 +187,7 @@ const RecentTransactions: FC<Props> = () => {
)} )}
</ol> </ol>
) : ( ) : (
<div className="pt-8 text-center text-gray-900 dark:text-purple-50"> <div className="pt-8 text-center text-xl text-gray-900 dark:text-purple-50">
<p className="pb-4">no transactions yet...</p> <p className="pb-4">no transactions yet...</p>
<p> <p>
get your first nano get your first nano

View file

@ -33,7 +33,7 @@ const XnoInput: FC<Props> = ({ value, onChange }) => {
return ( return (
<div className="flex items-center gap-3 text-2xl rounded transition-colors dark:bg-gray-800 bg-purple-50 focus-within:bg-purple-100 border-2 border-purple-200 dark:border-gray-700 py-2 px-4 w-48 overflow-hidden dark:focus-within:bg-gray-700"> <div className="flex items-center gap-3 text-2xl rounded transition-colors dark:bg-gray-800 bg-purple-50 focus-within:bg-purple-100 border-2 border-purple-200 dark:border-gray-700 py-2 px-4 w-48 overflow-hidden dark:focus-within:bg-gray-700">
<label htmlFor="xnoToSend">Ӿ</label> <label htmlFor="xno-amount">Ӿ</label>
<input <input
name="xno-amount" name="xno-amount"
id="xno-amount" id="xno-amount"

View file

@ -1,4 +1,3 @@
import { useCallback, useEffect } from 'react'
import useSWRInfinite from 'swr/infinite' import useSWRInfinite from 'swr/infinite'
import { useCurrentAccount } from '../context/accountContext' import { useCurrentAccount } from '../context/accountContext'
@ -23,6 +22,7 @@ const useAccountHistory = (pageSize = 20) => {
size, size,
setSize, setSize,
isValidating, isValidating,
mutate,
} = useSWRInfinite<AccountHistoryResponse>(getKey, (key: string) => } = useSWRInfinite<AccountHistoryResponse>(getKey, (key: string) =>
fetchAccountHistory( fetchAccountHistory(
account!.address, account!.address,
@ -44,6 +44,7 @@ const useAccountHistory = (pageSize = 20) => {
hasMore, hasMore,
loading: isValidating, loading: isValidating,
hasHistory, hasHistory,
revalidate: mutate,
} }
} }

View file

@ -1,7 +1,9 @@
import { Unit, convert } from 'nanocurrency'
import { useCallback } from 'react' import { useCallback } from 'react'
import useSWR from 'swr' import useSWR, { mutate } from 'swr'
import { useCurrentAccount } from '../context/accountContext' import { useCurrentAccount } from '../context/accountContext'
import showNotification from '../showNotification'
import type { ConfirmationMessage } from '../types' import type { ConfirmationMessage } from '../types'
import fetchAccountReceivable from '../xno/fetchAccountReceivable' import fetchAccountReceivable from '../xno/fetchAccountReceivable'
import fetchBlocksInfo from '../xno/fetchBlocksInfo' import fetchBlocksInfo from '../xno/fetchBlocksInfo'
@ -43,11 +45,12 @@ const useAccountReceivable = () => {
const onBlockReceived = useCallback( const onBlockReceived = useCallback(
(hash: string) => { (hash: string) => {
mutateReceivable(current => { mutateReceivable(current => {
if (current === undefined || account === undefined) return current if (current === undefined) return current
return current.filter(block => block.hash !== hash) return current.filter(block => block.hash !== hash)
}) })
mutate('history')
}, },
[mutateReceivable, account] [mutateReceivable]
) )
const addReceivable = useCallback( const addReceivable = useCallback(
@ -61,7 +64,7 @@ const useAccountReceivable = () => {
source: string source: string
}) => { }) => {
mutateReceivable(async current => { mutateReceivable(async current => {
if (current === undefined || account === undefined) return current if (current === undefined) return current
const blocksInfo = await fetchBlocksInfo([hash]) const blocksInfo = await fetchBlocksInfo([hash])
return [ return [
...current, ...current,
@ -74,16 +77,28 @@ const useAccountReceivable = () => {
] ]
}) })
}, },
[mutateReceivable, account] [mutateReceivable]
) )
const onNewReceivable = useCallback( const onNewReceivable = useCallback(
(confirmation: ConfirmationMessage) => (confirmation: ConfirmationMessage) => {
addReceivable({ addReceivable({
hash: confirmation.message.hash, hash: confirmation.message.hash,
amount: confirmation.message.amount, amount: confirmation.message.amount,
source: confirmation.message.account, source: confirmation.message.account,
}), })
showNotification({
title: 'incoming!',
body: `new incoming transaction: Ӿ${convert(
confirmation.message.amount,
{
from: Unit.raw,
to: Unit.Nano,
}
)} from ${confirmation.message.account}`,
tag: 'received',
})
},
[addReceivable] [addReceivable]
) )

15
lib/showNotification.ts Normal file
View file

@ -0,0 +1,15 @@
const showNotification = async (params: {
title: string
body: string
tag?: string
}) => {
const sw = await navigator.serviceWorker.getRegistration()
sw?.showNotification(params.title, {
body: params.body,
renotify: params.tag !== undefined,
tag: params.tag,
icon: '/images/icon-small.png',
})
}
export default showNotification

View file

@ -8,6 +8,7 @@ import { useCallback, useEffect, useState } from 'react'
import XnoInput from '../../components/XnoInput' import XnoInput from '../../components/XnoInput'
import useSendNano from '../../lib/hooks/useSendNano' import useSendNano from '../../lib/hooks/useSendNano'
import showNotification from '../../lib/showNotification'
const Send: NextPage = () => { const Send: NextPage = () => {
const { query, push } = useRouter() const { query, push } = useRouter()
@ -34,15 +35,17 @@ const Send: NextPage = () => {
useEffect(() => { useEffect(() => {
if (sliderPercentage === 1) { if (sliderPercentage === 1) {
const sendNano = async () => { const sendNano = async () => {
try { backToBase()
await send(address as string, amount as string) await send(address as string, amount as string)
navigator.serviceWorker showNotification({
.getRegistration() title: 'sent!',
.then(sw => sw?.showNotification('sent!')) body: `sent Ӿ${convert(amount as string, {
push('/dashboard') from: Unit.raw,
} catch { to: Unit.Nano,
backToBase() })} to ${address}`,
} tag: 'send',
})
push('/dashboard')
} }
sendNano() sendNano()
} }
@ -59,7 +62,7 @@ const Send: NextPage = () => {
return ( return (
<div className="flex flex-col h-full gap-8 pb-4"> <div className="flex flex-col h-full gap-8 pb-4">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<PaperAirplaneIcon className=" dark:text-purple-50 h-7 xs:h-10 text-gray-900 rotate-[30deg] translate-x-1" /> <PaperAirplaneIcon className="transition-colors dark:text-purple-50 h-7 xs:h-10 text-gray-900 rotate-[30deg] translate-x-1" />
<h1 className="text-3xl sm:text-5xl">send</h1> <h1 className="text-3xl sm:text-5xl">send</h1>
</span> </span>
<form <form

View file

@ -2,8 +2,10 @@ import { PaperAirplaneIcon } from '@heroicons/react/solid'
import clsx from 'clsx' import clsx from 'clsx'
import type { NextPage } from 'next' import type { NextPage } from 'next'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useCallback } from 'react' import { useCallback, useState } from 'react'
import AddressInput from '../../components/AddressInput'
import XnoInput from '../../components/XnoInput'
import useReadQrFromVideo from '../../lib/hooks/useReadQrFromVideo' import useReadQrFromVideo from '../../lib/hooks/useReadQrFromVideo'
import isTxnUrl from '../../lib/xno/isTxnUrl' import isTxnUrl from '../../lib/xno/isTxnUrl'
import txnUrlToParts from '../../lib/xno/txnUrlToParts' import txnUrlToParts from '../../lib/xno/txnUrlToParts'
@ -21,19 +23,34 @@ const ReadQrCode: NextPage = () => {
) )
const { videoLive, videoRef } = useReadQrFromVideo(onQrCodeRead) const { videoLive, videoRef } = useReadQrFromVideo(onQrCodeRead)
const [address, setAddress] = useState('')
return ( return (
<div className="flex flex-col h-full min-h-0 gap-8"> <div className="flex flex-col h-full min-h-0 gap-8 pb-4">
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<PaperAirplaneIcon className=" dark:text-purple-50 h-7 xs:h-10 text-gray-900 rotate-[30deg] translate-x-1" /> <PaperAirplaneIcon className=" dark:text-purple-50 h-7 xs:h-10 text-gray-900 rotate-[30deg] transition-colors translate-x-1" />
<h1 className="text-3xl sm:text-5xl">send</h1> <h1 className="text-3xl sm:text-5xl">send</h1>
</span> </span>
<video <video
className={clsx('rounded shadow-md min-h-0', { hidden: !videoLive })} className={clsx('rounded flex-1 shadow-md min-h-0', {
hidden: !videoLive,
})}
ref={videoRef} ref={videoRef}
/> />
{!videoLive && ( {!videoLive && (
<div className="w-full h-64 rounded dark:bg-gray-800 bg-purple-50 animate-pulse"></div> <div className="w-full flex-1 h-64 rounded dark:bg-gray-800 bg-purple-50 animate-pulse"></div>
)} )}
<div className="flex flex-col justify-self-end items-center gap-2 text-gray-900 dark:text-purple-50">
<span className="text-2xl">or</span>
<form
onSubmit={e => {
e.preventDefault()
if (address !== '') push(`/send?address=${address}`)
}}
>
<AddressInput value={address} onChange={setAddress} />
</form>
</div>
</div> </div>
) )
} }

View file

@ -5,5 +5,6 @@
"75edd150e09bada0f5c68fab14c0b385239cc396ef6600029dea0f7b66be63fa6a7d8ebe30aaff66634f583c4e68067c0de136e27dddc96be76d2ac5bb14ef71", "75edd150e09bada0f5c68fab14c0b385239cc396ef6600029dea0f7b66be63fa6a7d8ebe30aaff66634f583c4e68067c0de136e27dddc96be76d2ac5bb14ef71",
"b6203f57196d244f7eb6971cef41a4cd4ce961e81ae8046f4f8274a4057cb77b4ef97ad3c25e14b50598c1243fa3253644b44e89c8648fd9c18b3703d36372bb", "b6203f57196d244f7eb6971cef41a4cd4ce961e81ae8046f4f8274a4057cb77b4ef97ad3c25e14b50598c1243fa3253644b44e89c8648fd9c18b3703d36372bb",
"penalty parrot sure holiday target what human sadness crack picnic idle pluck mean salad frog usual grab aspect pottery faith staff crash umbrella lumber", "penalty parrot sure holiday target what human sadness crack picnic idle pluck mean salad frog usual grab aspect pottery faith staff crash umbrella lumber",
"6bfddd0bfd00bf13eb17bae35627a2dfdf49cb56027fc26988faff0ab8a20b31efb16d706ceac8e458d4d5c342f2459d24746521a6e6a62a0adb29b323455ed9" "6bfddd0bfd00bf13eb17bae35627a2dfdf49cb56027fc26988faff0ab8a20b31efb16d706ceac8e458d4d5c342f2459d24746521a6e6a62a0adb29b323455ed9",
"9bbaa2391a256c8d5c318790b86a142d4b13b1dc6d209567cb854e1b956f1c1385b863229a34e970d1f4c0c4a66f7ac7a17b5fd3a21e72716dddec842e0a6727"
] ]