ghost-extension-wallet/src/containers/WalletPopup/pages/Accounts/DeleteAccount.tsx
Uncle Fatso d3274222f9
rewrite extension around new UX
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2026-10-08 18:25:56 +03:00

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>
)
}