255 lines
9.0 KiB
TypeScript
255 lines
9.0 KiB
TypeScript
import { Card, CardDescription, CardTitle } from "@/components/ui/card"
|
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Skeleton } from "@/components/ui/skeleton"
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectGroup,
|
|
SelectItem,
|
|
SelectLabel,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select"
|
|
import { ArrowRight, Trash2, KeyRound, GitFork } from "lucide-react"
|
|
import { useMemo, useState, useEffect } from "react"
|
|
import { Layout } from "@/components/Layout"
|
|
import { useParams, useNavigate, Link } from "react-router-dom"
|
|
import useSWR, { mutate } from "swr"
|
|
import { cn } from "@/lib/utils"
|
|
import { rpc } from "@/containers/WalletPopup/api"
|
|
import { ss58Address } from "@polkadot-labs/hdkd-helpers"
|
|
import {
|
|
CreateDeriveFn,
|
|
ecdsaCreateDerive,
|
|
ed25519CreateDerive,
|
|
sr25519CreateDerive,
|
|
} from "@polkadot-labs/hdkd"
|
|
import React from "react"
|
|
import { Header, BottomNavBar } from "../../components"
|
|
import { useAccounts, useChain } from "../../hooks"
|
|
import { networks } from "@/constants"
|
|
import { Pencil, Copy, Check } from 'lucide-react'
|
|
|
|
|
|
import { AccountTree, DerivationWithAddress } from "@/background/keystore/types"
|
|
|
|
type Scheme = "Sr25519" | "Ed25519" | "Ecdsa"
|
|
|
|
function truncate(address: string, startChars = 10, endChars = 10): string {
|
|
if (!address || address.length <= (startChars + endChars)) {
|
|
return address
|
|
}
|
|
|
|
const start = address.slice(0, startChars)
|
|
const end = address.slice(-endChars)
|
|
|
|
return `${start}...${end}`
|
|
}
|
|
|
|
type DerivationCardProps = React.ComponentProps<typeof Card> & {
|
|
derivation?: DerivationWithAddress | undefined
|
|
}
|
|
|
|
type AccountCardProps = React.ComponentProps<typeof Card> & {
|
|
accountTree?: AccountTree | undefined
|
|
}
|
|
|
|
const DerivationCard: React.FC<DerivationCardProps> = ({ derivation }) => {
|
|
const [copied, setCopied] = useState<boolean>(false)
|
|
|
|
const handleCopy = async (e: React.MouseEvent) => {
|
|
e.stopPropagation()
|
|
if (copied || !derivation) return
|
|
|
|
await navigator.clipboard.writeText(derivation.address)
|
|
setCopied(true)
|
|
setTimeout(() => setCopied(false), 2000)
|
|
}
|
|
|
|
return (
|
|
<Card
|
|
key={derivation?.address || ""}
|
|
className="p-3 pb-1 border-none rounded-lg text-card-foreground transition-all relative shadow-sm bg-card cursor-pointer"
|
|
>
|
|
<CardTitle className="font-semibold text-sm tracking-wide text-white/90">
|
|
{`${derivation?.path ?? ""} ● ${derivation?.name ?? ""}`}
|
|
</CardTitle>
|
|
<CardDescription
|
|
className={cn(
|
|
"h-[20px] text-[11px] flex items-center justify-start gap-2 font-mono",
|
|
copied
|
|
? "text-primary cursor-default"
|
|
: "text-white/60 cursor-pointer"
|
|
)}
|
|
onClick={(e) => handleCopy(e)}
|
|
>
|
|
<span>{copied
|
|
? "Address copied to clipboard"
|
|
: truncate(derivation?.address ?? "")
|
|
}</span>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className={cn(
|
|
"border-none hover:bg-transparent h-6 w-6 shrink-0",
|
|
copied ? "text-primary" : "text-white/60"
|
|
)}
|
|
>
|
|
{copied ? (
|
|
<Check className="w-3 h-3" />
|
|
) : (
|
|
<Copy className="w-3 h-3" />
|
|
)}
|
|
</Button>
|
|
</CardDescription>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
const AccountCard: React.FC<AccountCardProps> = ({ accountTree }) => {
|
|
const [copied, setCopied] = useState<boolean>(false)
|
|
|
|
const handleCopy = async (e: React.MouseEvent) => {
|
|
e.stopPropagation()
|
|
if (copied || !accountTree) return
|
|
|
|
await navigator.clipboard.writeText(accountTree.address)
|
|
setCopied(true)
|
|
setTimeout(() => setCopied(false), 2000)
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col space-y-2 w-full">
|
|
<Card
|
|
data-testid={accountTree?.name ?? ""}
|
|
className="p-3 pb-1 border-none rounded-lg text-card-foreground transition-all shadow-md bg-card cursor-pointer"
|
|
>
|
|
<CardTitle className="font-bold text-sm tracking-wide text-white">
|
|
{accountTree?.name ?? ""}
|
|
</CardTitle>
|
|
<CardDescription
|
|
className={cn(
|
|
"h-[20px] text-[11px] flex items-center justify-start gap-2 font-mono",
|
|
copied
|
|
? "text-primary cursor-default"
|
|
: "text-white/60 cursor-pointer"
|
|
)}
|
|
onClick={(e) => handleCopy(e)}
|
|
>
|
|
<span>{copied
|
|
? "Address copied to clipboard"
|
|
: truncate(accountTree?.address ?? "")
|
|
}</span>
|
|
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className={cn(
|
|
"border-none hover:bg-transparent h-6 w-6 shrink-0",
|
|
copied ? "text-primary" : "text-white/60"
|
|
)}
|
|
>
|
|
{copied ? (
|
|
<Check className="w-3 h-3" />
|
|
) : (
|
|
<Copy className="w-3 h-3" />
|
|
)}
|
|
</Button>
|
|
</CardDescription>
|
|
</Card>
|
|
|
|
{accountTree && accountTree.derivations && (
|
|
<div className="relative pl-6 flex flex-col space-y-2">
|
|
<div className="absolute left-[10px] top-0 bottom-4 w-[1px] bg-white/20" />
|
|
{accountTree.derivations.map((derivation, j) => {
|
|
return <DerivationCard key={j} derivation={derivation} />
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const AccountsSkeleton: React.FC = () => {
|
|
return new Array(4).fill(null).map((_, i) => (
|
|
<Card
|
|
key={`account-skeleton-card-${i}`}
|
|
className="p-2 mb-2 transition duration-300 border-none rounded bg-background"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="w-full">
|
|
<Skeleton className="w-5/12 h-4 mb-2" />
|
|
<Skeleton className="w-11/12 h-3" />
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
))
|
|
}
|
|
|
|
export const DeleteAccount = () => {
|
|
const navigate = useNavigate()
|
|
const { currentChain } = useChain()
|
|
const {
|
|
activeAccountName,
|
|
accountTree,
|
|
requestPassword,
|
|
accountsStorageMutate,
|
|
isFetchingAccountTree,
|
|
isFetchingAccounts,
|
|
isFetchingActiveAccounts,
|
|
} = useAccounts()
|
|
|
|
const { derivation } = useParams<{ derivation?: string }>()
|
|
const derivationPath = useMemo(() => {
|
|
return derivation ? decodeURIComponent(derivation) : undefined;
|
|
}, [derivation])
|
|
|
|
const isFetching = useMemo(() => {
|
|
return isFetchingAccountTree || isFetchingAccounts || isFetchingActiveAccounts
|
|
}, [isFetchingAccountTree, isFetchingAccounts, isFetchingActiveAccounts])
|
|
|
|
const deleteAccount = async () => {
|
|
await requestPassword(async (password) => {
|
|
await rpc.client.removeAccount({
|
|
publicKey: accountTree?.publicKey ?? "",
|
|
path: derivationPath,
|
|
password,
|
|
})
|
|
})
|
|
|
|
await accountsStorageMutate()
|
|
navigate("/accounts")
|
|
}
|
|
|
|
return (
|
|
<Layout accountName={activeAccountName} chainValue={currentChain?.value} innerClassName="bg-muted">
|
|
<div className="flex items-center justify-between px-6 my-4 sm:px-8">
|
|
<h2 className="text-xl font-semibold text-white">Delete {derivationPath ? "Derivation" : "Account Tree"}</h2>
|
|
</div>
|
|
|
|
<ScrollArea className="px-6 mb-4 grow sm:px-8">
|
|
<section>
|
|
{isFetching && <AccountsSkeleton />}
|
|
{!isFetching && (
|
|
<div className="space-y-2">
|
|
{derivationPath
|
|
? <DerivationCard derivation={accountTree?.derivations.find(d => d.path === derivationPath)} />
|
|
: <AccountCard accountTree={accountTree} />
|
|
}
|
|
</div>
|
|
)}
|
|
</section>
|
|
</ScrollArea>
|
|
|
|
<div className="flex w-full gap-x-4 mt-6 px-6 pb-6">
|
|
<Button onClick={() => navigate(-1)} size="full" variant="secondary">Back</Button>
|
|
<Button onClick={() => deleteAccount()} size="full" variant="primary">Delete Now</Button>
|
|
</div>
|
|
|
|
<BottomNavBar />
|
|
</Layout>
|
|
)
|
|
}
|