feat: lots of stuff
This commit is contained in:
parent
aeeb56d605
commit
ec32e26cc0
30
components/AddressInput.tsx
Normal file
30
components/AddressInput.tsx
Normal 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
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
15
lib/showNotification.ts
Normal 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
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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"
|
||||||
]
|
]
|
||||||
|
|
Reference in a new issue