Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Page/Tx-details #226

Merged
merged 19 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- [#262](https://github.com/alleslabs/celatone-frontend/pull/262) Add amplitude tracking for tx page components
- [#224](https://github.com/alleslabs/celatone-frontend/pull/224) Support search by tx and internal tx link
- [#226](https://github.com/alleslabs/celatone-frontend/pull/226) Add fully functional transaction details page
- [#254](https://github.com/alleslabs/celatone-frontend/pull/254) Add GiHub link to public code and contract detail pages
- [#230](https://github.com/alleslabs/celatone-frontend/pull/230) Add cw2info to code table

Expand Down
22 changes: 11 additions & 11 deletions src/lib/app-fns/explorer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,49 @@ export const explorerMap: Record<string, string> = {
osmosistestnet: "https://testnet.mintscan.io/osmosis-testnet",
};

export const getExplorerTxUrl = (chainName: string) => {
export const getExplorerBlockUrl = (chainName: string) => {
let pathSuffix = "";
switch (chainName) {
case "osmosis":
case "osmosistestnet":
pathSuffix = "txs";
break;
case "terra2":
case "terra2testnet":
pathSuffix = "tx";
pathSuffix = "blocks";
break;
default:
break;
}
return `${explorerMap[chainName]}/${pathSuffix}`;
};

export const getExplorerBlockUrl = (chainName: string) => {
export const getProposalUrl = (chainName: string) => {
let pathSuffix = "";
switch (chainName) {
case "osmosis":
case "osmosistestnet":
pathSuffix = "proposals";
break;
case "terra2":
return "https://station.terra.money/proposal/phoenix-1";
case "terra2testnet":
pathSuffix = "blocks";
break;
return "https://station.terra.money/proposal/pisco-1";
default:
break;
}
return `${explorerMap[chainName]}/${pathSuffix}`;
};

export const getProposalUrl = (chainName: string) => {
export const getExplorerValidatorUrl = (chainName: string) => {
let pathSuffix = "";
switch (chainName) {
case "osmosis":
case "osmosistestnet":
pathSuffix = "proposals";
pathSuffix = "validators";
break;
case "terra2":
return `https://station.terra.money/proposal/phoenix-1`;
case "terra2testnet":
return `https://station.terra.money/proposal/pisco-1`;
pathSuffix = "validator";
break;
default:
break;
}
Expand Down
51 changes: 40 additions & 11 deletions src/lib/app-provider/hooks/useAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,50 @@ import type { ChainRecord } from "@cosmos-kit/core";
import { useWallet } from "@cosmos-kit/react";
import { useCallback } from "react";

import type { Option } from "lib/types";

export type AddressReturnType =
| "user_address"
| "contract_address"
| "validator_address"
| "invalid_address";

const addressLengthMap: {
[key: string]: { [length: number]: AddressReturnType };
} = {
osmosis: {
43: "user_address",
50: "validator_address",
63: "contract_address",
},
osmosistestnet: {
43: "user_address",
50: "validator_address",
63: "contract_address",
},
terra2: {
44: "user_address",
51: "validator_address",
64: "contract_address",
},
terra2testnet: {
44: "user_address",
51: "validator_address",
64: "contract_address",
},
};

export const getAddressTypeByLength = (
chainName: string,
address: string
address: Option<string>
): AddressReturnType =>
addressLengthMap[chainName]?.[address.length] ?? "invalid_address";
address
? addressLengthMap[chainName]?.[address.length] ?? "invalid_address"
: "invalid_address";

export const useGetAddressType = () => {
const { currentChainName } = useWallet();
return useCallback(
(address: string): AddressReturnType =>
getAddressTypeByLength(currentChainName, address),
[currentChainName]
);
const getPrefix = (basePrefix: string, addressType: AddressReturnType) => {
if (addressType === "validator_address") return `${basePrefix}valoper`;
return basePrefix;
};

const validateAddress = (
Expand All @@ -51,8 +56,10 @@ const validateAddress = (
) => {
if (!currentChainRecord) return "Invalid network";

if (!address.startsWith(currentChainRecord.chain.bech32_prefix))
return `Invalid prefix (expected "${currentChainRecord.chain.bech32_prefix}")`;
const prefix = getPrefix(currentChainRecord.chain.bech32_prefix, addressType);

if (!address.startsWith(prefix))
return `Invalid prefix (expected "${prefix}")`;

if (getAddressTypeByLength(currentChainRecord.name, address) !== addressType)
return "Invalid address length";
Expand All @@ -65,6 +72,23 @@ const validateAddress = (
return null;
};

export const useGetAddressType = () => {
const { currentChainName, currentChainRecord } = useWallet();
return useCallback(
(address: Option<string>): AddressReturnType => {
const addressType = getAddressTypeByLength(currentChainName, address);
if (
!address ||
addressType === "invalid_address" ||
validateAddress(currentChainRecord, address, addressType)
)
return "invalid_address";
return addressType;
},
[currentChainName, currentChainRecord]
);
};

// TODO: refactor
export const useValidateAddress = () => {
const { currentChainRecord } = useWallet();
Expand All @@ -80,5 +104,10 @@ export const useValidateAddress = () => {
validateAddress(currentChainRecord, address, "user_address"),
[currentChainRecord]
),
validateValidatorAddress: useCallback(
(address: string) =>
validateAddress(currentChainRecord, address, "validator_address"),
[currentChainRecord]
),
};
};
24 changes: 18 additions & 6 deletions src/lib/components/ExplorerLink.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { BoxProps } from "@chakra-ui/react";
import type { BoxProps, TextProps } from "@chakra-ui/react";
import { Box, Text } from "@chakra-ui/react";
import { useWallet } from "@cosmos-kit/react";

import {
getExplorerBlockUrl,
getExplorerTxUrl,
getProposalUrl,
getExplorerValidatorUrl,
} from "lib/app-fns/explorer";
import type { AddressReturnType } from "lib/app-provider";
import { AmpTrackMintscan } from "lib/services/amplitude";
Expand All @@ -29,6 +29,8 @@ interface ExplorerLinkProps extends BoxProps {
isReadOnly?: boolean;
textFormat?: "truncate" | "ellipsis" | "normal";
maxWidth?: string;
textVariant?: TextProps["variant"];
ampCopierSection?: string;
}

const getNavigationUrl = (
Expand All @@ -39,14 +41,17 @@ const getNavigationUrl = (
let url = "";
switch (type) {
case "tx_hash":
url = getExplorerTxUrl(currentChainName);
url = "/tx";
break;
case "contract_address":
url = "/contract";
break;
case "user_address":
url = "/account";
break;
case "validator_address":
url = getExplorerValidatorUrl(currentChainName);
break;
case "code_id":
url = "/code";
break;
Expand Down Expand Up @@ -82,17 +87,19 @@ const LinkRender = ({
textValue,
isEllipsis,
maxWidth,
textVariant,
}: {
type: string;
isInternal: boolean;
hrefLink: string;
textValue: string;
isEllipsis: boolean;
maxWidth: ExplorerLinkProps["maxWidth"];
textVariant: TextProps["variant"];
}) => {
const textElement = (
<Text
variant="body2"
variant={textVariant}
color="lilac.main"
transition="all .25s ease-in-out"
_hover={{ color: "lilac.light" }}
Expand Down Expand Up @@ -131,14 +138,17 @@ export const ExplorerLink = ({
showCopyOnHover = false,
isReadOnly = false,
textFormat = "truncate",
maxWidth = "150px",
maxWidth = "160px",
textVariant = "body2",
ampCopierSection,
...componentProps
}: ExplorerLinkProps) => {
const { address, currentChainName } = useWallet();
const isInternal =
type === "code_id" ||
type === "contract_address" ||
type === "user_address";
type === "user_address" ||
type === "tx_hash";

const [hrefLink, textValue] = [
getNavigationUrl(type, currentChainName, copyValue || value),
Expand Down Expand Up @@ -172,12 +182,14 @@ export const ExplorerLink = ({
textValue={textValue}
isEllipsis={textFormat === "ellipsis"}
maxWidth={maxWidth}
textVariant={textVariant}
/>
<Copier
type={type}
value={copyValue || value}
display={showCopyOnHover ? "none" : "block"}
ml="8px"
amptrackSection={ampCopierSection}
/>
</>
)}
Expand Down
12 changes: 10 additions & 2 deletions src/lib/components/TokenCard.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { FlexProps } from "@chakra-ui/react";
import { Badge, Flex, Image, Text, Tooltip } from "@chakra-ui/react";
import { useState } from "react";

Expand All @@ -11,11 +12,16 @@ import {

import { Copier } from "./copy";

interface TokenCardProps {
interface TokenCardProps extends FlexProps {
userBalance: BalanceWithAssetInfo;
amptrackSection?: string;
}

export const TokenCard = ({ userBalance }: TokenCardProps) => {
export const TokenCard = ({
userBalance,
amptrackSection,
...cardProps
}: TokenCardProps) => {
const [logoError, setLogoError] = useState(false);
const { symbol, price, amount, precision, id } = userBalance.balance;

Expand All @@ -37,6 +43,7 @@ export const TokenCard = ({ userBalance }: TokenCardProps) => {
p={3}
background="pebble.900"
borderRadius="8px"
{...cardProps}
>
<Flex
gap={1}
Expand Down Expand Up @@ -72,6 +79,7 @@ export const TokenCard = ({ userBalance }: TokenCardProps) => {
copyLabel="Token ID Copied!"
display="none"
ml="1px"
amptrackSection={amptrackSection}
/>
</Flex>

Expand Down
63 changes: 63 additions & 0 deletions src/lib/components/ViewPermissionAddresses.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Button } from "@chakra-ui/react";
import { useState } from "react";

import { useGetAddressType } from "lib/app-provider";
import { AmpTrackExpand } from "lib/services/amplitude";
import type { PermissionAddresses } from "lib/types";

import { ExplorerLink } from "./ExplorerLink";
import { CustomIcon } from "./icon";

export const ViewPermissionAddresses = ({
permissionAddresses,
amptrackSection,
}: {
permissionAddresses: PermissionAddresses;
amptrackSection: string;
}) => {
const [viewAll, setViewAll] = useState(false);
const getAddressType = useGetAddressType();
const showAddressses =
viewAll ||
(typeof permissionAddresses === "object" &&
permissionAddresses.length === 1);

return (
<>
{showAddressses &&
permissionAddresses.map((addr) => (
<ExplorerLink
key={addr}
type={getAddressType(addr)}
value={addr}
showCopyOnHover
/>
))}
{permissionAddresses.length > 1 && (
<Button
variant="ghost-primary"
onClick={() => {
AmpTrackExpand(
viewAll ? "collapse" : "expand",
"permission_address",
amptrackSection
);
setViewAll((prev) => !prev);
}}
size="sm"
px="2 !important"
w="fit-content"
rightIcon={
<CustomIcon
name={viewAll ? "chevron-up" : "chevron-down"}
color="lilac.main"
boxSize="3"
/>
}
>
{viewAll ? "See Less" : "View All Addresses"}
</Button>
)}
</>
);
};
Loading