Merge pull request #207 from cosmos/feat/new-account-view
New account view
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.vscode
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { printableCoin } from "@/lib/displayHelpers";
|
||||
import { Coin } from "@cosmjs/amino";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "../../ui/avatar";
|
||||
import { Badge } from "../../ui/badge";
|
||||
|
||||
interface BalancePillProps {
|
||||
readonly coin: Coin;
|
||||
}
|
||||
|
||||
export default function BalancePill({ coin }: BalancePillProps) {
|
||||
const { chain } = useChains();
|
||||
|
||||
const foundAsset = chain.assets.find((asset) => asset.base === coin.denom);
|
||||
const logo = foundAsset?.logo_URIs?.svg || foundAsset?.logo_URIs?.png || "";
|
||||
const macroCoin = printableCoin(coin, chain);
|
||||
|
||||
return (
|
||||
<Badge key={coin.denom} className="px-1">
|
||||
<Avatar className="mr-2">
|
||||
<AvatarImage src={logo} alt={`${coin.denom} logo`} className="h-auto" />
|
||||
<AvatarFallback className="text-white">
|
||||
{coin.denom.slice(1, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{macroCoin}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { toastError } from "@/lib/utils";
|
||||
import { Coin } from "@cosmjs/amino";
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import BalancePill from "./BalancePill";
|
||||
|
||||
interface BalancesListProps {
|
||||
readonly walletAddress: string;
|
||||
}
|
||||
|
||||
export default function BalancesList({ walletAddress }: BalancesListProps) {
|
||||
const { chain } = useChains();
|
||||
const [balances, setBalances] = useState<readonly Coin[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
if (!walletAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await StargateClient.connect(chain.nodeAddress);
|
||||
const newBalances = await client.getAllBalances(walletAddress);
|
||||
setBalances(newBalances);
|
||||
} catch (e) {
|
||||
console.error("Failed to get balances:", e);
|
||||
toastError({
|
||||
description: "Failed to get balances",
|
||||
fullError: e instanceof Error ? e : undefined,
|
||||
});
|
||||
}
|
||||
})();
|
||||
}, [chain.nodeAddress, walletAddress]);
|
||||
|
||||
return balances.length ? (
|
||||
<Card className="bg-fuchsia-850 w-full max-w-md border-transparent">
|
||||
<CardHeader className="p-0">
|
||||
<CardTitle>Balances</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="mt-4 flex flex-wrap gap-2 p-0">
|
||||
{balances.map((coin) => (
|
||||
<BalancePill key={coin.denom} coin={coin} />
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table";
|
||||
import { printableCoin, thinSpace } from "@/lib/displayHelpers";
|
||||
import { toastError } from "@/lib/utils";
|
||||
import { Coin } from "@cosmjs/amino";
|
||||
import { StargateClient } from "@cosmjs/stargate";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "../../ui/avatar";
|
||||
|
||||
interface BalancesTableProps {
|
||||
readonly walletAddress: string;
|
||||
}
|
||||
|
||||
export default function BalancesTable({ walletAddress }: BalancesTableProps) {
|
||||
const { chain } = useChains();
|
||||
const [balances, setBalances] = useState<readonly Coin[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
if (!walletAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await StargateClient.connect(chain.nodeAddress);
|
||||
const newBalances = await client.getAllBalances(walletAddress);
|
||||
setBalances(newBalances);
|
||||
} catch (e) {
|
||||
console.error("Failed to get balances:", e);
|
||||
toastError({
|
||||
description: "Failed to get balances",
|
||||
fullError: e instanceof Error ? e : undefined,
|
||||
});
|
||||
}
|
||||
})();
|
||||
}, [chain.nodeAddress, walletAddress]);
|
||||
|
||||
return balances.length ? (
|
||||
<Table>
|
||||
<TableBody>
|
||||
{balances.map((coin) => {
|
||||
const foundAsset = chain.assets.find((asset) => asset.base === coin.denom);
|
||||
const logo = foundAsset?.logo_URIs?.svg || foundAsset?.logo_URIs?.png || "";
|
||||
const [macroAmount, macroDenom] = printableCoin(coin, chain).split(thinSpace);
|
||||
|
||||
return (
|
||||
<TableRow key={coin.denom}>
|
||||
<TableCell className="w-0 pr-0">
|
||||
<Avatar>
|
||||
<AvatarImage src={logo} alt={`${coin.denom} logo`} className="h-auto" />
|
||||
<AvatarFallback className="text-white">
|
||||
{coin.denom.slice(1, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</TableCell>
|
||||
<TableCell>{macroDenom}</TableCell>
|
||||
<TableCell className="text-right">{macroAmount}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
) : (
|
||||
"No tokens found"
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { toastError } from "@/lib/utils";
|
||||
import { cn, toastError } from "@/lib/utils";
|
||||
import { LoadingStates, WalletInfo, WalletType } from "@/types/signing";
|
||||
import { makeCosmoshubPath } from "@cosmjs/amino";
|
||||
import { toBase64 } from "@cosmjs/encoding";
|
||||
import { LedgerSigner } from "@cosmjs/ledger-amino";
|
||||
import TransportWebUSB from "@ledgerhq/hw-transport-webusb";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { Loader2, Unplug } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { Dispatch, SetStateAction, useCallback, useLayoutEffect, useState } from "react";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
@@ -108,17 +108,27 @@ export default function ButtonConnectWallet({
|
||||
const isLoading =
|
||||
(walletType === "Keplr" && loading.keplr) || (walletType === "Ledger" && loading.ledger);
|
||||
|
||||
return (
|
||||
<Button onClick={onClick} disabled={loading.keplr || loading.ledger}>
|
||||
return walletInfo?.type === walletType ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setWalletInfo(null);
|
||||
}}
|
||||
>
|
||||
<Unplug className="mr-2 h-auto w-5 text-destructive" />
|
||||
Disconnect {walletInfo.type}
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={onClick} disabled={loading.keplr || loading.ledger} variant="outline">
|
||||
{isLoading ? (
|
||||
<Loader2 className="mr-2 h-5 w-5 animate-spin" />
|
||||
) : (
|
||||
<Image
|
||||
alt=""
|
||||
src={`/assets/icons/${walletType.toLowerCase()}.svg`}
|
||||
width={20}
|
||||
height={20}
|
||||
className="mr-2"
|
||||
width={walletType === "Ledger" ? 23 : 20}
|
||||
height={walletType === "Ledger" ? 23 : 20}
|
||||
className={cn("mr-2", walletType === "Ledger" && "bg-white p-0.5")}
|
||||
/>
|
||||
)}
|
||||
Connect {walletType}
|
||||
|
||||
@@ -1,80 +1,105 @@
|
||||
import BadgeWithCopy from "@/components/BadgeWithCopy";
|
||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { explorerLinkAccount } from "@/lib/displayHelpers";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { WalletInfo } from "@/types/signing";
|
||||
import { Unplug } from "lucide-react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { ArrowUpRightSquare, Copy } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useChains } from "../../../context/ChainsContext";
|
||||
import { Button } from "../../ui/button";
|
||||
import BalancesList from "./BalancesList";
|
||||
import BalancesTable from "./BalancesTable";
|
||||
import ButtonConnectWallet from "./ButtonConnectWallet";
|
||||
|
||||
export default function AccountView() {
|
||||
const { chain } = useChains();
|
||||
|
||||
const walletInfoState = useState<WalletInfo | null>();
|
||||
const [walletInfo, setWalletInfo] = walletInfoState;
|
||||
const [walletInfo] = walletInfoState;
|
||||
|
||||
const explorerLink =
|
||||
explorerLinkAccount(chain.explorerLinks.account, walletInfo?.address || "") || "";
|
||||
|
||||
return (
|
||||
<div className="mt-6 flex flex-col gap-4">
|
||||
<Card className="bg-fuchsia-850 min-w-[400px] border-transparent">
|
||||
<CardHeader className="p-0">
|
||||
<CardTitle className="flex items-baseline gap-2">
|
||||
<Card>
|
||||
<CardHeader className="space-y-1">
|
||||
<CardTitle className="flex items-center text-2xl">
|
||||
{walletInfo?.type ? (
|
||||
<Image
|
||||
alt=""
|
||||
src={`/assets/icons/${walletInfo.type.toLowerCase()}.svg`}
|
||||
width={20}
|
||||
height={20}
|
||||
width={walletInfo.type === "Ledger" ? 30 : 27}
|
||||
height={walletInfo.type === "Ledger" ? 30 : 27}
|
||||
className={cn("mr-2", walletInfo.type === "Ledger" && "bg-white p-0.5")}
|
||||
/>
|
||||
) : null}
|
||||
{walletInfo?.type ? `${walletInfo.type} wallet connected` : "Connect wallet"}
|
||||
{walletInfo?.type ? `Connected to ${walletInfo.type}` : "Connect to a wallet"}
|
||||
</CardTitle>
|
||||
<CardDescription>Choose between Keplr or Ledger to show its account info</CardDescription>
|
||||
</CardHeader>
|
||||
{walletInfo ? (
|
||||
<CardContent className="mt-4 max-w-md p-0">
|
||||
<h2>Address</h2>
|
||||
<div className="flex flex-col gap-4">
|
||||
<BadgeWithCopy name="address" toCopy={walletInfo.address} />
|
||||
{explorerLink ? (
|
||||
<Button asChild variant="secondary" className="self-center">
|
||||
<a href={explorerLink} target="_blank">
|
||||
View in explorer
|
||||
</a>
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<h2>Public key</h2>
|
||||
<BadgeWithCopy name="pubKey" toCopy={walletInfo.pubKey} />
|
||||
</div>
|
||||
</CardContent>
|
||||
) : null}
|
||||
<CardFooter className="my-8 flex w-full flex-col gap-4 p-0">
|
||||
{walletInfo?.type ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setWalletInfo(null);
|
||||
}}
|
||||
>
|
||||
<Unplug className="mr-2 h-auto w-5 text-red-500" />
|
||||
Disconnect {walletInfo.type}
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<ButtonConnectWallet walletType="Keplr" walletInfoState={walletInfoState} />
|
||||
<ButtonConnectWallet walletType="Ledger" walletInfoState={walletInfoState} />
|
||||
</div>
|
||||
)}
|
||||
</CardFooter>
|
||||
<CardContent className="grid gap-4">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<ButtonConnectWallet walletType="Keplr" walletInfoState={walletInfoState} />
|
||||
<ButtonConnectWallet walletType="Ledger" walletInfoState={walletInfoState} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{walletInfo?.address ? (
|
||||
<BalancesList key={walletInfo.address} walletAddress={walletInfo.address} />
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Account info</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col gap-5">
|
||||
<div
|
||||
onClick={async () => {
|
||||
copy(walletInfo.address);
|
||||
toast(`Copied address to clipboard`, { description: walletInfo.address });
|
||||
}}
|
||||
className=" flex items-center space-x-4 rounded-md border p-4 transition-colors hover:cursor-pointer hover:bg-muted/50"
|
||||
>
|
||||
<Copy className="w-5" />
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Address</p>
|
||||
<p className="text-sm text-muted-foreground">{walletInfo.address}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
onClick={async () => {
|
||||
copy(walletInfo.pubKey);
|
||||
toast(`Copied public key to clipboard`, { description: walletInfo.pubKey });
|
||||
}}
|
||||
className=" flex items-center space-x-4 rounded-md border p-4 transition-colors hover:cursor-pointer hover:bg-muted/50"
|
||||
>
|
||||
<Copy className="w-5" />
|
||||
<div className="flex-1 space-y-1">
|
||||
<p className="text-sm font-medium leading-none">Public key</p>
|
||||
<p className="text-sm text-muted-foreground">{walletInfo.pubKey}</p>
|
||||
</div>
|
||||
</div>
|
||||
{explorerLink ? (
|
||||
<Button asChild variant="secondary">
|
||||
<a href={explorerLink} target="_blank">
|
||||
View in explorer <ArrowUpRightSquare className="ml-1" />
|
||||
</a>
|
||||
</Button>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
{walletInfo?.address ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Balances</CardTitle>
|
||||
<CardDescription>
|
||||
Your list of tokens on {chain.chainDisplayName || "Cosmos Hub"}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<BalancesTable walletAddress={walletInfo.address} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -59,7 +59,7 @@ const FindMultisigForm = ({ router }: FindMultisigFormProps) => {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Already have a {chain.chainDisplayName || "Cosmos Hub"} multisig?</CardTitle>
|
||||
<CardTitle>Already have a multisig on {chain.chainDisplayName || "Cosmos Hub"}?</CardTitle>
|
||||
<CardDescription>
|
||||
Enter its address below to view its transactions and create new ones.
|
||||
</CardDescription>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Breadcrumb = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<"nav"> & {
|
||||
separator?: React.ReactNode
|
||||
}
|
||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
||||
Breadcrumb.displayName = "Breadcrumb"
|
||||
|
||||
const BreadcrumbList = React.forwardRef<
|
||||
HTMLOListElement,
|
||||
React.ComponentPropsWithoutRef<"ol">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ol
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
BreadcrumbList.displayName = "BreadcrumbList"
|
||||
|
||||
const BreadcrumbItem = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentPropsWithoutRef<"li">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<li
|
||||
ref={ref}
|
||||
className={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
BreadcrumbItem.displayName = "BreadcrumbItem"
|
||||
|
||||
const BreadcrumbLink = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.ComponentPropsWithoutRef<"a"> & {
|
||||
asChild?: boolean
|
||||
}
|
||||
>(({ asChild, className, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "a"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn("transition-colors hover:text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
BreadcrumbLink.displayName = "BreadcrumbLink"
|
||||
|
||||
const BreadcrumbPage = React.forwardRef<
|
||||
HTMLSpanElement,
|
||||
React.ComponentPropsWithoutRef<"span">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<span
|
||||
ref={ref}
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn("font-normal text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
BreadcrumbPage.displayName = "BreadcrumbPage"
|
||||
|
||||
const BreadcrumbSeparator = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"li">) => (
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("[&>svg]:size-3.5", className)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
)
|
||||
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
||||
|
||||
const BreadcrumbEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
)
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
}
|
||||
Generated
+4
-4
@@ -18,7 +18,7 @@
|
||||
"@cosmjs/tendermint-rpc": "^0.32.2",
|
||||
"@cosmjs/utils": "^0.32.2",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@keplr-wallet/types": "^0.12.23",
|
||||
"@keplr-wallet/types": "^0.12.76",
|
||||
"@ledgerhq/hw-transport-webusb": "^6.27.19",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
@@ -1705,9 +1705,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@keplr-wallet/types": {
|
||||
"version": "0.12.23",
|
||||
"resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.23.tgz",
|
||||
"integrity": "sha512-PRnsoGswlWV5bwyqGw1o5dKCEY33s8Mgxd7HeqTX9L+Pj6TAapnBlyCxhXBp74W0QySIMe8it37VjhJqa9QWxA==",
|
||||
"version": "0.12.76",
|
||||
"resolved": "https://registry.npmjs.org/@keplr-wallet/types/-/types-0.12.76.tgz",
|
||||
"integrity": "sha512-N2KRh1xCJ3gqZUWEi803RwpeCVrFCxFTZyUgFgIUt/fg0qPyd/F6okjcpvu6dj9BNDppuJntqYUpOUdWA7mY9Q==",
|
||||
"dependencies": {
|
||||
"long": "^4.0.0"
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
"@cosmjs/tendermint-rpc": "^0.32.2",
|
||||
"@cosmjs/utils": "^0.32.2",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@keplr-wallet/types": "^0.12.23",
|
||||
"@keplr-wallet/types": "^0.12.76",
|
||||
"@ledgerhq/hw-transport-webusb": "^6.27.19",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
|
||||
@@ -1,17 +1,36 @@
|
||||
import AccountView from "@/components/dataViews/AccountView";
|
||||
import Page from "@/components/layout/Page";
|
||||
import Head from "@/components/head";
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import { useChains } from "@/context/ChainsContext";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function AccountPage() {
|
||||
const { chain } = useChains();
|
||||
|
||||
return (
|
||||
<Page
|
||||
goBack={
|
||||
chain.registryName ? { pathname: `/${chain.registryName}`, title: "home" } : undefined
|
||||
}
|
||||
>
|
||||
<div className="m-4 mt-8 flex max-w-xl flex-1 flex-col justify-center gap-4">
|
||||
<Head title={`${chain.chainDisplayName || "Cosmos Hub"} Multisig Manager`} />
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink>
|
||||
{chain.registryName ? <Link href={`/${chain.registryName}`}>Home</Link> : null}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Account</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<AccountView />
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import { Window as KeplrWindow } from "@keplr-wallet/types";
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface Window extends KeplrWindow {}
|
||||
}
|
||||
Reference in New Issue
Block a user