Files
zkAuth/dapp/components/dashboard/ModalSetSocial.tsx
2022-09-23 19:40:20 +02:00

383 lines
15 KiB
TypeScript

import { Fragment, useEffect, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { MinusIcon, XMarkIcon } from '@heroicons/react/24/outline'
import { ethers } from 'ethers'
import { AlchemyProvider } from '@ethersproject/providers'
import { shortenAddress, useEthers, useResolveName } from '@usedapp/core'
import { CheckIcon } from '@heroicons/react/20/solid'
import { ThreeDots } from 'react-loader-spinner'
import { useTheme } from 'next-themes'
import { PlusIcon } from '@heroicons/react/24/solid'
import { PasswordTOTPBox, PasswordZKBox } from '.'
interface ModalSetSocialProps {}
const ModalSetSocial = (props: ModalSetSocialProps) => {
const [open, setOpen] = useState(false)
const { account } = useEthers()
// Setting Infura Provider TODO: Optimize
let providerIsSet = false
const [alchemyProvider, setInfuraProvider] = useState<
AlchemyProvider | undefined
>()
useEffect(() => {
if (!providerIsSet) {
const infura = new AlchemyProvider('homestead', process.env.API_KEY)
setInfuraProvider(infura)
providerIsSet = true
}
}, [providerIsSet])
const onSubmit = (e: React.UIEvent<HTMLButtonElement>) => {
e.preventDefault()
setOpen(true)
}
interface Accounts {
[key: string]: Account
}
interface Account {
[address: string]: string
ens: string
verified: string
value: string
}
const [accounts, setAccount] = useState<Accounts>({
0: { address: '', ens: '', verified: '', value: '' },
1: { address: '', ens: '', verified: '', value: '' },
2: { address: '', ens: '', verified: '', value: '' },
})
const [naccounts, setNAccounts] = useState(3)
// Sets the address when input changes value
const setAddress = (name: string, index: string) => {
//TODO: Extra fromatting
if (name.includes('.')) {
setAccount({
...accounts,
[index]: {
...accounts[index],
address: '',
ens: name,
verified: '',
value: name,
},
})
} else {
setAccount({
...accounts,
[index]: {
...accounts[index],
address: name,
ens: '',
verified: '',
value: name,
},
})
}
}
// Verifies if account address is correct. If normal address fetches ENS and viceversa. Formats input value.
const verifyAccount = async (index: string) => {
setAccount({
...accounts,
[index]: { ...accounts[index], verified: 'loading' },
})
if (alchemyProvider && account) {
// Get address from ENS if exists
const addressfromENS = await alchemyProvider.resolveName(
accounts[index]['ens']
)
const isValidAccount = ethers.utils.isAddress(accounts[index]['address'])
//Check that is not user's own account and not the same as others
const list = Array.from(Array(3).keys()).filter(
(item) => item !== parseInt(index)
)
let otherAccounts = list.map((i) => accounts[i]['address'])
otherAccounts.push(account)
// We verify if either ENS or address are correctly set and modify states
if (addressfromENS) {
if (otherAccounts.includes(addressfromENS)) {
setAccount({
...accounts,
[index]: { ...accounts[index], verified: 'error' },
})
return
} else {
setAccount({
...accounts,
[index]: {
...accounts[index],
address: addressfromENS,
verified: 'verified',
value: `${accounts[index]['ens']} (${shortenAddress(
addressfromENS
)})`,
},
})
}
} else if (isValidAccount) {
if (otherAccounts.includes(accounts[index]['address'])) {
setAccount({
...accounts,
[index]: { ...accounts[index], verified: 'error' },
})
return
} else {
const ensFromAdddress = await alchemyProvider.lookupAddress(
accounts[index]['address']
)
setAccount({
...accounts,
[index]: {
...accounts[index],
ens: ensFromAdddress ?? '',
verified: 'verified',
value: ensFromAdddress
? `${ensFromAdddress} (${shortenAddress(
accounts[index]['address']
)})`
: accounts[index]['address'],
},
})
}
} else {
setAccount({
...accounts,
[index]: { ...accounts[index], verified: 'error' },
})
}
}
}
const [allVerified, setAllVerified] = useState(false)
useEffect(() => {
const verified = []
for (let index in accounts) {
verified.push(accounts[index]['verified'] == 'verified')
}
setAllVerified(verified.every((v) => v === true))
})
const addAccount = (e: React.UIEvent<HTMLButtonElement>) => {
e.preventDefault()
setAccount({
...accounts,
[naccounts]: { address: '', ens: '', verified: '', value: '' },
})
setNAccounts(naccounts + 1)
}
const removeAccount = (e: React.UIEvent<HTMLButtonElement>) => {
e.preventDefault()
const newAccounts = accounts
delete accounts[naccounts]
setAccount(newAccounts)
setNAccounts(naccounts - 1)
}
// Manage theme hydration
const { theme } = useTheme()
const [loaded, setLoaded] = useState(false)
useEffect(() => {
if (theme) {
setLoaded(true)
}
})
if (!loaded) return null
return (
<>
<button
className="w-3/4 px-2 py-2 button-unsaturated"
onClick={(e) => onSubmit(e)}
>
Set Accounts
</button>
<Transition.Root show={open} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={setOpen}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 dark:bg-gray-900 dark:bg-opacity-80 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto ">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white dark:bg-gray-800 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6 ">
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button
type="button"
className="absolute top-3 right-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-800 dark:hover:text-white"
onClick={() => setOpen(false)}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="sm:flex sm:items-start w-full">
<div className="w-full mt-3 text-center px-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-2xl font-medium flex justify-center leading-6 text-gray-900 dark:text-gray-100 mb-8"
>
Set up recovery accounts
</Dialog.Title>
<form className="space-y-4" action="#">
<h5 className="font-medium text-gray-900 dark:text-white">
Accounts or ENS Domains
</h5>
{Array.from(Array(naccounts).keys()).map((index) => (
<div key={index}>
<label
htmlFor="text"
className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>
Account {index + 1}
</label>
<div
className={`w-full flex flex-row items-center h-14 p-2 rounded-lg
bg-gray-50 border border-gray-300 text-gray-900 dark:bg-gray-600 dark:border-gray-500 dark:text-white focus-within:outline focus-within:outline-2 ${
accounts[index]['verified'] == 'error'
? 'outline outline-2 outline-red-500 dark:outline-red-500 focus-within:outline-red-500 dark:focus-within:outline-red-500'
: 'focus-within:outline-blue-500 dark:focus-within:outline-blue-500'
}`}
>
<input
id={`account${index + 1}`}
name="account"
type="text"
className="w-5/6 border-none bg-transparent dark:bg-transparent outline-0 focus:ring-0 placeholder-gray-300 dark:placeholder-gray-500"
placeholder="0x000...0000 or vitalik.eth"
value={accounts[index]['value']}
onChange={(e) =>
setAddress(e.target.value, index.toString())
}
/>
{{
verified: (
<div className="mx-auto flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-green-200">
<CheckIcon
className="h-6 w-6 text-green-600"
aria-hidden="true"
/>
</div>
),
error: (
<div className="mx-auto flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-red-200">
<XMarkIcon
className="h-6 w-6 text-red-600"
aria-hidden="true"
/>
</div>
),
loading: (
<div className="mx-auto">
<ThreeDots
height="35"
width="35"
radius="9"
color={
theme == 'dark' ? '#ffffff' : '#3b83f6'
}
ariaLabel="three-dots-loading"
/>
</div>
),
}[accounts[index]['verified']] || (
<button
className="w-1/6 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 px-2 py-2 mx-auto"
onClick={() =>
verifyAccount(index.toString())
}
>
Verify
</button>
)}
</div>
</div>
))}
<div className="mt-3 flex flex-row gap-3 justify-center">
<button
className="dark:bg-gray-800 bg-white
dark:hover:bg-gray-700 hover:bg-gray-100
dark:border-gray-600 border-gray-300
rounded-full border w-9 h-9"
onClick={(e) => addAccount(e)}
>
<PlusIcon
height={'1.2rem'}
width={'1.2rem'}
className="mx-auto"
/>
</button>
{naccounts > 3 ? (
<button
className="dark:bg-gray-800 bg-white
dark:hover:bg-gray-700 hover:bg-gray-100
dark:border-gray-600 border-gray-300
rounded-full border w-9 h-9"
onClick={(e) => removeAccount(e)}
>
<MinusIcon
height={'1.2rem'}
width={'1.2rem'}
className="mx-auto"
/>
</button>
) : (
<></>
)}
</div>
<div
id="password"
className="w-full mt-3 flex flex-row gap-3 justify-center"
>
{false ? (
<PasswordTOTPBox
setOpen={setOpen}
allVerified={allVerified}
/>
) : (
<PasswordZKBox
setOpen={setOpen}
allVerified={allVerified}
/>
)}
</div>
</form>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</>
)
}
export default ModalSetSocial