162 lines
5.9 KiB
TypeScript
162 lines
5.9 KiB
TypeScript
import React, { useEffect, useState } from "react"
|
|
import { CirclePlus, Trash, Send } from "lucide-react"
|
|
import { useNavigate } from "react-router-dom"
|
|
import { ss58Decode } from "@polkadot-labs/hdkd-helpers"
|
|
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from "../components/ui/accordion"
|
|
import { Input } from "../components/ui/input"
|
|
import { Button } from "../components/ui/button"
|
|
|
|
type AddressBookRecord = {
|
|
name: string
|
|
address: string
|
|
}
|
|
|
|
interface AddressRecordProps {
|
|
name: string
|
|
address: string
|
|
removeRecord: ({ name }: { name: string}) => void
|
|
openTransfer: ({ address }: { address: string}) => void
|
|
}
|
|
|
|
const AddressRecord: React.FC<AddressRecordProps> = ({ name, address, removeRecord, openTransfer }) => {
|
|
return (
|
|
<AccordionItem className="bg-muted rounded px-4" value={name}>
|
|
<AccordionTrigger className="w-full hover:no-underline">
|
|
<div className="w-[90%] sm:text-base text-xs overflow-hidden whitespace-nowrap overflow-ellipsis text-left">
|
|
{name}
|
|
</div>
|
|
</AccordionTrigger>
|
|
<AccordionContent className="flex flex-row gap-4">
|
|
<Button
|
|
variant="secondary"
|
|
size="full"
|
|
className="flex-1 text-accent flex text-xs sm:flex hidden"
|
|
onClick={() => openTransfer({ address })}
|
|
>
|
|
<Send className="w-4 h-4 mr-1" />
|
|
</Button>
|
|
<Input
|
|
readOnly
|
|
value={address}
|
|
aria-label={name}
|
|
type="text"
|
|
className="sm:w-[300px] flex-6"
|
|
/>
|
|
<Button
|
|
variant="destructive"
|
|
size="full"
|
|
className="flex-2 text-accent text-xs sm:flex hidden"
|
|
onClick={() => removeRecord({ name })}
|
|
>
|
|
<Trash className="w-4 h-4 mr-2" />
|
|
Remove
|
|
</Button>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
)
|
|
}
|
|
|
|
export const AddressBook = () => {
|
|
const navigate = useNavigate()
|
|
|
|
const [name, setName] = useState<string>("")
|
|
const [address, setAddress] = useState<string>("")
|
|
const [error, setError] = useState<string | undefined>(undefined)
|
|
const [addressBook, setAddressBook] = useState<AddressBookRecord[]>(
|
|
JSON.parse(localStorage.getItem('addressBook') ?? '[]') || []
|
|
)
|
|
|
|
useEffect(() => {
|
|
localStorage.setItem('addressBook', JSON.stringify(addressBook))
|
|
setAddress("")
|
|
setName("")
|
|
setError(undefined)
|
|
}, [addressBook])
|
|
|
|
const addRecord = ({ name, address }: AddressBookRecord) => {
|
|
if (addressBook.find(record => record.name === name)) {
|
|
setError("Name already exist in the address book")
|
|
return
|
|
}
|
|
if (addressBook.find(record => record.address === address)) {
|
|
setError("Address already exist in the address book")
|
|
return
|
|
}
|
|
try {
|
|
const [, prefix] = ss58Decode(address)
|
|
if (prefix !== 1995 && prefix !== 1996) {
|
|
throw new Error("bad prefix")
|
|
}
|
|
} catch (e) {
|
|
setError("Incorrect Ghost or Casper address provided")
|
|
return
|
|
}
|
|
const newRecord = { name, address }
|
|
setAddressBook([...addressBook, newRecord])
|
|
}
|
|
|
|
const removeRecord = ({ name }: { name: string }) => {
|
|
const updatedAddressBook = addressBook.filter((record: AddressBookRecord) =>
|
|
record.name !== name
|
|
)
|
|
setAddressBook(updatedAddressBook)
|
|
}
|
|
|
|
const openTransfer = ({ address }: { address: string }) => {
|
|
const queryString = new URLSearchParams({ address }).toString()
|
|
navigate(`/transactions?${queryString}`)
|
|
}
|
|
|
|
return (
|
|
<Accordion type="multiple" className="sm:w-[500px] w-[85%] h-fit flex flex-col flex-1 gap-4 justify-center self-center py-2">
|
|
{addressBook.map(({ name, address }: AddressBookRecord, idx: number) => (
|
|
<AddressRecord
|
|
key={idx}
|
|
name={name}
|
|
address={address}
|
|
removeRecord={removeRecord}
|
|
openTransfer={openTransfer}
|
|
/>
|
|
))}
|
|
<div className="bg-muted flex flex-col sm:gap-2 gap-4 w-full rounded px-4 py-6">
|
|
<div className="flex sm:flex-row flex-col gap-4">
|
|
<Input
|
|
value={name}
|
|
onChange={e => setName(e.target.value)}
|
|
aria-label="New Name"
|
|
type="text"
|
|
className="w-full sm:text-base text-xs sm:placeholder:text-base placeholder:text-xs"
|
|
placeholder="Contact Name"
|
|
/>
|
|
<Input
|
|
value={address}
|
|
onChange={e => setAddress(e.target.value)}
|
|
aria-label="New Address"
|
|
type="text"
|
|
className="w-full sm:text-base text-xs sm:placeholder:text-base placeholder:text-xs"
|
|
placeholder="Contact Address"
|
|
/>
|
|
</div>
|
|
<Button
|
|
variant="secondary"
|
|
size="full"
|
|
className="flex flex-row justify-center items-center"
|
|
onClick={() => addRecord({ name, address })}
|
|
>
|
|
<CirclePlus className="w-4 h-4 mr-2" />
|
|
Add Contact
|
|
</Button>
|
|
{error && (
|
|
<div className="text-xs text-destructive">{error}</div>
|
|
)}
|
|
</div>
|
|
</Accordion>
|
|
)
|
|
}
|