diff --git a/public/locales/en.json b/public/locales/en.json index 9f3e436..93dc79e 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -366,9 +366,7 @@ "resultInvalidTitle": "Invalid address", "resultInvalid": "That does not look like a valid Krist address.", "resultNotFoundTitle": "Address not found", - "resultNotFound": "That address does not exist.", - "resultUnknownTitle": "Unknown error", - "resultUnknown": "See console for details." + "resultNotFound": "That address does not exist." }, "transactionSummary": { @@ -409,8 +407,6 @@ "resultInvalidTitle": "Invalid address", "resultInvalid": "That does not look like a valid Krist address.", - "resultUnknownTitle": "Unknown error", - "resultUnknown": "See console for details.", "types": { "transferred": "Transferred", @@ -447,9 +443,7 @@ "tableTotal_plural": "{{count, number}} names", "resultInvalidTitle": "Invalid address", - "resultInvalid": "That does not look like a valid Krist address.", - "resultUnknownTitle": "Unknown error", - "resultUnknown": "See console for details." + "resultInvalid": "That does not look like a valid Krist address." }, "name": { @@ -478,9 +472,7 @@ "resultInvalidTitle": "Invalid name", "resultInvalid": "That does not look like a valid Krist name.", "resultNotFoundTitle": "Name not found", - "resultNotFound": "That name does not exist.", - "resultUnknownTitle": "Unknown error", - "resultUnknown": "See console for details." + "resultNotFound": "That name does not exist." }, "blocks": { @@ -497,10 +489,7 @@ "columnTime": "Time", "tableTotal": "{{count, number}} block", - "tableTotal_plural": "{{count, number}} blocks", - - "resultUnknownTitle": "Unknown error", - "resultUnknown": "See console for details." + "tableTotal_plural": "{{count, number}} blocks" }, "block": { @@ -526,9 +515,7 @@ "resultInvalidTitle": "Invalid block height", "resultInvalid": "That does not look like a valid block height.", "resultNotFoundTitle": "Block not found", - "resultNotFound": "That block does not exist.", - "resultUnknownTitle": "Unknown error", - "resultUnknown": "See console for details." + "resultNotFound": "That block does not exist." }, "transaction": { @@ -567,7 +554,10 @@ "resultInvalidTitle": "Invalid transaction ID", "resultInvalid": "That does not look like a valid transaction ID.", "resultNotFoundTitle": "Transaction not found", - "resultNotFound": "That transaction does not exist.", + "resultNotFound": "That transaction does not exist." + }, + + "apiErrorResult": { "resultUnknownTitle": "Unknown error", "resultUnknown": "See console for details." } diff --git a/src/components/ContextualAddress.less b/src/components/ContextualAddress.less deleted file mode 100644 index 1ce8da7..0000000 --- a/src/components/ContextualAddress.less +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -@import (reference) "../App.less"; - -.contextual-address { - &:not(.contextual-address-allow-wrap) { - .address-metaname, .address-name, .address-raw-metaname, - .address-wallet { - white-space: nowrap; - } - } - - .address-address, .address-original { - white-space: nowrap; - } - - .address-original { - opacity: 0.8; - } -} diff --git a/src/components/ContextualAddress.tsx b/src/components/ContextualAddress.tsx deleted file mode 100644 index 51f6a26..0000000 --- a/src/components/ContextualAddress.tsx +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import classNames from "classnames"; -import { Tooltip, Typography } from "antd"; - -import { useSelector } from "react-redux"; -import { RootState } from "../store"; -import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; - -import { KristAddress } from "../krist/api/types"; -import { Wallet, useWallets } from "../krist/wallets/Wallet"; -import { parseCommonMeta, CommonMeta } from "../utils/commonmeta"; -import { stripNameSuffix } from "../utils/currency"; -import { useBooleanSetting } from "../utils/settings"; - -import { KristNameLink } from "./KristNameLink"; - -import "./ContextualAddress.less"; - -const { Text } = Typography; - -interface Props { - address: KristAddress | string | null; - wallet?: Wallet | false; - metadata?: string; - source?: boolean; - hideNameAddress?: boolean; - allowWrap?: boolean; - neverCopyable?: boolean; - className?: string; -} - -interface AddressMetanameProps { - nameSuffix: string; - address: string; - commonMeta: CommonMeta; - source: boolean; - hideNameAddress: boolean; -} - -export function AddressMetaname({ nameSuffix, address, commonMeta, source, hideNameAddress }: AddressMetanameProps): JSX.Element { - const rawMetaname = (source ? commonMeta?.return : commonMeta?.recipient) || undefined; - const metaname = (source ? commonMeta?.returnMetaname : commonMeta?.metaname) || undefined; - const name = (source ? commonMeta?.returnName : commonMeta?.name) || undefined; - const nameWithoutSuffix = name ? stripNameSuffix(nameSuffix, name) : undefined; - - return name - ? <> - {/* Display the name/metaname (e.g. foo@bar.kst) */} - {metaname && <>{metaname}@} - - - {/* Display the original address too */} - {!hideNameAddress && <> -   - - ({address}) - - - } - - : ( - // Display the raw metaname, but link to the owner address - - {rawMetaname} - - ); -} - -export function ContextualAddress({ - address: origAddress, - wallet: origWallet, - metadata, - source, - hideNameAddress, - allowWrap, - neverCopyable, - className -}: Props): JSX.Element { - const { t } = useTranslation(); - const { walletAddressMap } = useWallets(); - const nameSuffix = useSelector((s: RootState) => s.node.currency.name_suffix); - const addressCopyButtons = useBooleanSetting("addressCopyButtons"); - - if (!origAddress) return ( - {t("contextualAddressUnknown")} - ); - - const address = typeof origAddress === "object" ? origAddress.address : origAddress; - - // If we were given a wallet, use it. Otherwise, look it up, unless it was - // explicitly excluded (e.g. the Wallets table) - const wallet = origWallet !== false - ? (origWallet || walletAddressMap[address]) - : undefined; - - const commonMeta = parseCommonMeta(nameSuffix, metadata); - const hasMetaname = source ? !!commonMeta?.returnRecipient : !!commonMeta?.recipient; - - const copyable = !neverCopyable && addressCopyButtons - ? { text: address } : undefined; - - const classes = classNames("contextual-address", className, { - "contextual-address-allow-wrap": allowWrap - }); - - return - - {commonMeta && hasMetaname - ? ( - // Display the metaname and link to the name if possible - - ) - : ( - // Display our wallet label if present, but link to the address - - {wallet && wallet.label - ? {wallet.label} - : {address}} - - ) - } - - ; -} diff --git a/src/components/KristNameLink.tsx b/src/components/KristNameLink.tsx deleted file mode 100644 index 7f0da1d..0000000 --- a/src/components/KristNameLink.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import classNames from "classnames"; -import { Typography } from "antd"; - -import { useSelector } from "react-redux"; -import { RootState } from "../store"; - -import { Link } from "react-router-dom"; - -import { useBooleanSetting } from "../utils/settings"; - -const { Text } = Typography; - -interface OwnProps { - name: string; - text?: string; - noLink?: boolean; - neverCopyable?: boolean; -} -type Props = React.HTMLProps & OwnProps; - -export function KristNameLink({ name, text, noLink, neverCopyable, ...props }: Props): JSX.Element | null { - const nameSuffix = useSelector((s: RootState) => s.node.currency.name_suffix); - const nameCopyButtons = useBooleanSetting("nameCopyButtons"); - const copyNameSuffixes = useBooleanSetting("copyNameSuffixes"); - - if (!name) return null; - const nameWithSuffix = `${name}.${nameSuffix}`; - const content = text || nameWithSuffix; - - const copyable = !neverCopyable && nameCopyButtons - ? { text: copyNameSuffixes ? nameWithSuffix : name } - : undefined; - - const classes = classNames("krist-name", props.className); - - return - {noLink - ? content - : ( - - {content} - - )} - ; -} diff --git a/src/components/KristSymbol.tsx b/src/components/KristSymbol.tsx deleted file mode 100644 index 732ca51..0000000 --- a/src/components/KristSymbol.tsx +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import Icon from "@ant-design/icons"; - -export const KristSymbolSvg = (): JSX.Element => ( - - - -); -export const KristSymbol = (props: any): JSX.Element => - ; diff --git a/src/components/KristValue.less b/src/components/KristValue.less deleted file mode 100644 index 0022693..0000000 --- a/src/components/KristValue.less +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -@import (reference) "../App.less"; - -.krist-value { - font-size: 100%; - - white-space: nowrap; - - .anticon svg { - /* Hack to make it consistent with Lato */ - position: relative; - bottom: 0.125em; - font-size: 0.75em; - color: @text-color-secondary; - } - - .krist-value-amount { - font-weight: bold; - } - - .krist-currency-long { - color: @text-color-secondary; - - &::before { - content: " "; - } - } - - &.krist-value-green { - color: @kw-green; - - .anticon svg, .krist-currency-long { - color: fade(@kw-green, 75%); - } - } - - &.krist-value-zero { - color: @text-color-secondary; - - .anticon svg, .krist-currency-long { - color: fade(@text-color-secondary, 60%); - } - } -} diff --git a/src/components/KristValue.tsx b/src/components/KristValue.tsx deleted file mode 100644 index bce8348..0000000 --- a/src/components/KristValue.tsx +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import classNames from "classnames"; - -import { useSelector } from "react-redux"; -import { RootState } from "../store"; - -import { KristSymbol } from "./KristSymbol"; - -import "./KristValue.less"; - -interface OwnProps { - value?: number; - long?: boolean; - hideNullish?: boolean; - green?: boolean; - highlightZero?: boolean; -} -type Props = React.HTMLProps & OwnProps; - -export const KristValue = ({ value, long, hideNullish, green, highlightZero, ...props }: Props): JSX.Element | null => { - const currencySymbol = useSelector((s: RootState) => s.node.currency.currency_symbol); - - if (hideNullish && (value === undefined || value === null)) return null; - - const classes = classNames("krist-value", props.className, { - "krist-value-green": green, - "krist-value-zero": highlightZero && value === 0 - }); - - return ( - - {(currencySymbol || "KST") === "KST" && } - {(value || 0).toLocaleString()} - {long && {currencySymbol || "KST"}} - - ); -}; diff --git a/src/components/NameARecordLink.less b/src/components/NameARecordLink.less deleted file mode 100644 index 593f5e1..0000000 --- a/src/components/NameARecordLink.less +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -@import (reference) "../App.less"; - -.name-a-record-link { - background: @kw-darker; - border-radius: @border-radius-base; - - display: inline-block; - margin-top: @padding-xs; - padding: 0.25rem @padding-xs; - - font-size: @font-size-base * 0.9; - font-family: monospace; -} diff --git a/src/components/NameARecordLink.tsx b/src/components/NameARecordLink.tsx deleted file mode 100644 index 009ffe5..0000000 --- a/src/components/NameARecordLink.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import classNames from "classnames"; - -import { useSelector } from "react-redux"; -import { RootState } from "../store"; -import { stripNameSuffix } from "../utils/currency"; - -import { KristNameLink } from "./KristNameLink"; - -import "./NameARecordLink.less"; - -function forceURL(link: string): string { - // TODO: this is rather crude - if (!link.startsWith("http")) return "https://" + link; - return link; -} - -interface Props { - a?: string; - className?: string; -} - -export function NameARecordLink({ a, className }: Props): JSX.Element | null { - const nameSuffix = useSelector((s: RootState) => s.node.currency.name_suffix); - - if (!a) return null; - - const classes = classNames("name-a-record-link", className); - - // I don't have a citation for this other than a vague memory, but there are - // (as of writing this) 45 names in the database whose A records begin with - // `$` and then point to another name. There is an additional 1 name that - // actually points to a domain, but still begins with `$` and ends with the - // name suffix. 40 of these names end in the `.kst` suffix. Since I cannot - // find any specification or documentation on it right now, I support both - // formats. The suffix is stripped if it is present. - if (a.startsWith("$")) { - // Probably a name redirect - const withoutPrefix = a.replace(/^\$/, ""); - const nameWithoutSuffix = stripNameSuffix(nameSuffix, withoutPrefix); - - return ; - } - - return - {a} - ; -} diff --git a/src/components/SmallResult.tsx b/src/components/SmallResult.tsx deleted file mode 100644 index 2247891..0000000 --- a/src/components/SmallResult.tsx +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt - -/** This is ant-design's Result component, but without importing 54 kB of - * images that we don't even use */ - -import React from "react"; -import classNames from "classnames"; - -import CheckCircleFilled from "@ant-design/icons/CheckCircleFilled"; -import CloseCircleFilled from "@ant-design/icons/CloseCircleFilled"; -import ExclamationCircleFilled from "@ant-design/icons/ExclamationCircleFilled"; -import WarningFilled from "@ant-design/icons/WarningFilled"; - -export const IconMap = { - success: CheckCircleFilled, - error: CloseCircleFilled, - info: ExclamationCircleFilled, - warning: WarningFilled, -}; -export type ResultStatusType = keyof typeof IconMap; - -export interface ResultProps { - icon?: React.ReactNode; - status?: ResultStatusType; - title?: React.ReactNode; - subTitle?: React.ReactNode; - extra?: React.ReactNode; - className?: string; - style?: React.CSSProperties; - fullPage?: boolean; -} - -/** - * Render icon if ExceptionStatus includes ,render svg image else render iconNode - */ -const renderIcon = ({ status, icon }: ResultProps) => { - const iconNode = React.createElement(IconMap[status as ResultStatusType],); - return
{icon || iconNode}
; -}; - -const renderExtra = ({ extra }: ResultProps) => - extra &&
{extra}
; - -export const SmallResult: React.FC = ({ - className: customizeClassName, - subTitle, - title, - style, - children, - status = "info", - icon, - extra, - fullPage, -}) => { - const classes = classNames("ant-result", "ant-result-" + status, customizeClassName, { - "full-page-result": fullPage - }); - - return ( -
- {renderIcon({ status, icon })} -
{title}
- {subTitle &&
{subTitle}
} - {renderExtra({ extra })} - {children &&
{children}
} -
- ); -}; diff --git a/src/components/addresses/ContextualAddress.less b/src/components/addresses/ContextualAddress.less new file mode 100644 index 0000000..5031814 --- /dev/null +++ b/src/components/addresses/ContextualAddress.less @@ -0,0 +1,21 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +@import (reference) "../../App.less"; + +.contextual-address { + &:not(.contextual-address-allow-wrap) { + .address-metaname, .address-name, .address-raw-metaname, + .address-wallet { + white-space: nowrap; + } + } + + .address-address, .address-original { + white-space: nowrap; + } + + .address-original { + opacity: 0.8; + } +} diff --git a/src/components/addresses/ContextualAddress.tsx b/src/components/addresses/ContextualAddress.tsx new file mode 100644 index 0000000..4419d54 --- /dev/null +++ b/src/components/addresses/ContextualAddress.tsx @@ -0,0 +1,134 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import React from "react"; +import classNames from "classnames"; +import { Tooltip, Typography } from "antd"; + +import { useSelector } from "react-redux"; +import { RootState } from "../../store"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; + +import { KristAddress } from "../../krist/api/types"; +import { Wallet, useWallets } from "../../krist/wallets/Wallet"; +import { parseCommonMeta, CommonMeta } from "../../utils/commonmeta"; +import { stripNameSuffix } from "../../utils/currency"; +import { useBooleanSetting } from "../../utils/settings"; + +import { KristNameLink } from "../names/KristNameLink"; + +import "./ContextualAddress.less"; + +const { Text } = Typography; + +interface Props { + address: KristAddress | string | null; + wallet?: Wallet | false; + metadata?: string; + source?: boolean; + hideNameAddress?: boolean; + allowWrap?: boolean; + neverCopyable?: boolean; + className?: string; +} + +interface AddressMetanameProps { + nameSuffix: string; + address: string; + commonMeta: CommonMeta; + source: boolean; + hideNameAddress: boolean; +} + +export function AddressMetaname({ nameSuffix, address, commonMeta, source, hideNameAddress }: AddressMetanameProps): JSX.Element { + const rawMetaname = (source ? commonMeta?.return : commonMeta?.recipient) || undefined; + const metaname = (source ? commonMeta?.returnMetaname : commonMeta?.metaname) || undefined; + const name = (source ? commonMeta?.returnName : commonMeta?.name) || undefined; + const nameWithoutSuffix = name ? stripNameSuffix(nameSuffix, name) : undefined; + + return name + ? <> + {/* Display the name/metaname (e.g. foo@bar.kst) */} + {metaname && <>{metaname}@} + + + {/* Display the original address too */} + {!hideNameAddress && <> +   + + ({address}) + + + } + + : ( + // Display the raw metaname, but link to the owner address + + {rawMetaname} + + ); +} + +export function ContextualAddress({ + address: origAddress, + wallet: origWallet, + metadata, + source, + hideNameAddress, + allowWrap, + neverCopyable, + className +}: Props): JSX.Element { + const { t } = useTranslation(); + const { walletAddressMap } = useWallets(); + const nameSuffix = useSelector((s: RootState) => s.node.currency.name_suffix); + const addressCopyButtons = useBooleanSetting("addressCopyButtons"); + + if (!origAddress) return ( + {t("contextualAddressUnknown")} + ); + + const address = typeof origAddress === "object" ? origAddress.address : origAddress; + + // If we were given a wallet, use it. Otherwise, look it up, unless it was + // explicitly excluded (e.g. the Wallets table) + const wallet = origWallet !== false + ? (origWallet || walletAddressMap[address]) + : undefined; + + const commonMeta = parseCommonMeta(nameSuffix, metadata); + const hasMetaname = source ? !!commonMeta?.returnRecipient : !!commonMeta?.recipient; + + const copyable = !neverCopyable && addressCopyButtons + ? { text: address } : undefined; + + const classes = classNames("contextual-address", className, { + "contextual-address-allow-wrap": allowWrap + }); + + return + + {commonMeta && hasMetaname + ? ( + // Display the metaname and link to the name if possible + + ) + : ( + // Display our wallet label if present, but link to the address + + {wallet && wallet.label + ? {wallet.label} + : {address}} + + ) + } + + ; +} diff --git a/src/components/krist/KristSymbol.tsx b/src/components/krist/KristSymbol.tsx new file mode 100644 index 0000000..732ca51 --- /dev/null +++ b/src/components/krist/KristSymbol.tsx @@ -0,0 +1,13 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import React from "react"; +import Icon from "@ant-design/icons"; + +export const KristSymbolSvg = (): JSX.Element => ( + + + +); +export const KristSymbol = (props: any): JSX.Element => + ; diff --git a/src/components/krist/KristValue.less b/src/components/krist/KristValue.less new file mode 100644 index 0000000..4a791be --- /dev/null +++ b/src/components/krist/KristValue.less @@ -0,0 +1,46 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +@import (reference) "../../App.less"; + +.krist-value { + font-size: 100%; + + white-space: nowrap; + + .anticon svg { + /* Hack to make it consistent with Lato */ + position: relative; + bottom: 0.125em; + font-size: 0.75em; + color: @text-color-secondary; + } + + .krist-value-amount { + font-weight: bold; + } + + .krist-currency-long { + color: @text-color-secondary; + + &::before { + content: " "; + } + } + + &.krist-value-green { + color: @kw-green; + + .anticon svg, .krist-currency-long { + color: fade(@kw-green, 75%); + } + } + + &.krist-value-zero { + color: @text-color-secondary; + + .anticon svg, .krist-currency-long { + color: fade(@text-color-secondary, 60%); + } + } +} diff --git a/src/components/krist/KristValue.tsx b/src/components/krist/KristValue.tsx new file mode 100644 index 0000000..1ccdc16 --- /dev/null +++ b/src/components/krist/KristValue.tsx @@ -0,0 +1,40 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import React from "react"; +import classNames from "classnames"; + +import { useSelector } from "react-redux"; +import { RootState } from "../../store"; + +import { KristSymbol } from "./KristSymbol"; + +import "./KristValue.less"; + +interface OwnProps { + value?: number; + long?: boolean; + hideNullish?: boolean; + green?: boolean; + highlightZero?: boolean; +} +type Props = React.HTMLProps & OwnProps; + +export const KristValue = ({ value, long, hideNullish, green, highlightZero, ...props }: Props): JSX.Element | null => { + const currencySymbol = useSelector((s: RootState) => s.node.currency.currency_symbol); + + if (hideNullish && (value === undefined || value === null)) return null; + + const classes = classNames("krist-value", props.className, { + "krist-value-green": green, + "krist-value-zero": highlightZero && value === 0 + }); + + return ( + + {(currencySymbol || "KST") === "KST" && } + {(value || 0).toLocaleString()} + {long && {currencySymbol || "KST"}} + + ); +}; diff --git a/src/components/names/KristNameLink.tsx b/src/components/names/KristNameLink.tsx new file mode 100644 index 0000000..6a8e080 --- /dev/null +++ b/src/components/names/KristNameLink.tsx @@ -0,0 +1,49 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import React from "react"; +import classNames from "classnames"; +import { Typography } from "antd"; + +import { useSelector } from "react-redux"; +import { RootState } from "../../store"; + +import { Link } from "react-router-dom"; + +import { useBooleanSetting } from "../../utils/settings"; + +const { Text } = Typography; + +interface OwnProps { + name: string; + text?: string; + noLink?: boolean; + neverCopyable?: boolean; +} +type Props = React.HTMLProps & OwnProps; + +export function KristNameLink({ name, text, noLink, neverCopyable, ...props }: Props): JSX.Element | null { + const nameSuffix = useSelector((s: RootState) => s.node.currency.name_suffix); + const nameCopyButtons = useBooleanSetting("nameCopyButtons"); + const copyNameSuffixes = useBooleanSetting("copyNameSuffixes"); + + if (!name) return null; + const nameWithSuffix = `${name}.${nameSuffix}`; + const content = text || nameWithSuffix; + + const copyable = !neverCopyable && nameCopyButtons + ? { text: copyNameSuffixes ? nameWithSuffix : name } + : undefined; + + const classes = classNames("krist-name", props.className); + + return + {noLink + ? content + : ( + + {content} + + )} + ; +} diff --git a/src/components/names/NameARecordLink.less b/src/components/names/NameARecordLink.less new file mode 100644 index 0000000..90c31c1 --- /dev/null +++ b/src/components/names/NameARecordLink.less @@ -0,0 +1,16 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +@import (reference) "../../App.less"; + +.name-a-record-link { + background: @kw-darker; + border-radius: @border-radius-base; + + display: inline-block; + margin-top: @padding-xs; + padding: 0.25rem @padding-xs; + + font-size: @font-size-base * 0.9; + font-family: monospace; +} diff --git a/src/components/names/NameARecordLink.tsx b/src/components/names/NameARecordLink.tsx new file mode 100644 index 0000000..1d56d03 --- /dev/null +++ b/src/components/names/NameARecordLink.tsx @@ -0,0 +1,61 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import React from "react"; +import classNames from "classnames"; + +import { useSelector } from "react-redux"; +import { RootState } from "../../store"; +import { stripNameSuffix } from "../../utils/currency"; + +import { KristNameLink } from "./KristNameLink"; + +import "./NameARecordLink.less"; + +function forceURL(link: string): string { + // TODO: this is rather crude + if (!link.startsWith("http")) return "https://" + link; + return link; +} + +interface Props { + a?: string; + className?: string; +} + +export function NameARecordLink({ a, className }: Props): JSX.Element | null { + const nameSuffix = useSelector((s: RootState) => s.node.currency.name_suffix); + + if (!a) return null; + + const classes = classNames("name-a-record-link", className); + + // I don't have a citation for this other than a vague memory, but there are + // (as of writing this) 45 names in the database whose A records begin with + // `$` and then point to another name. There is an additional 1 name that + // actually points to a domain, but still begins with `$` and ends with the + // name suffix. 40 of these names end in the `.kst` suffix. Since I cannot + // find any specification or documentation on it right now, I support both + // formats. The suffix is stripped if it is present. + if (a.startsWith("$")) { + // Probably a name redirect + const withoutPrefix = a.replace(/^\$/, ""); + const nameWithoutSuffix = stripNameSuffix(nameSuffix, withoutPrefix); + + return ; + } + + return + {a} + ; +} diff --git a/src/components/results/APIErrorResult.tsx b/src/components/results/APIErrorResult.tsx new file mode 100644 index 0000000..2545ab2 --- /dev/null +++ b/src/components/results/APIErrorResult.tsx @@ -0,0 +1,73 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt +import React from "react"; +import classNames from "classnames"; +import { FrownOutlined, ExclamationCircleOutlined, QuestionCircleOutlined } from "@ant-design/icons"; + +import { useTranslation } from "react-i18next"; + +import { SmallResult, ResultProps } from "./SmallResult"; +import { APIError } from "../../krist/api"; + +interface Props { + error: Error; + + // Handles `invalid_parameter` errors + invalidParameterTitleKey?: string; + invalidParameterSubTitleKey?: string; + + // Handles 'not found' errors (e.g. `name_not_found`) + notFoundMessage?: string; + notFoundTitleKey?: string; + notFoundSubTitleKey?: string; + + // Handles 'unknown' errors (completely optional) + unknownTitleKey?: string; + unknownSubTitleKey?: string; + + className?: string; +} + +export function APIErrorResult({ error, className, ...props }: Props): JSX.Element { + const { t } = useTranslation(); + + const classes = classNames("kw-api-error-result", className); + + // Props shared by all the errors + const errorProps: ResultProps = { + className: classes, + status: "error", + icon: , + fullPage: true + }; + + // Handle the most commonly expected errors from the API + if (error instanceof APIError) { + if (error.message === "invalid_parameter" && props.invalidParameterTitleKey) { + return } + title={props.invalidParameterTitleKey} + subTitle={props.invalidParameterSubTitleKey} + />; + } + + const { notFoundMessage } = props; + if (notFoundMessage && error.message === notFoundMessage && props.notFoundTitleKey) { + return } + title={props.notFoundTitleKey} + subTitle={props.notFoundSubTitleKey} + />; + } + } + + // Unknown error + return ; +} diff --git a/src/components/results/SmallResult.tsx b/src/components/results/SmallResult.tsx new file mode 100644 index 0000000..2247891 --- /dev/null +++ b/src/components/results/SmallResult.tsx @@ -0,0 +1,70 @@ +// Copyright (c) 2020-2021 Drew Lemmy +// This file is part of KristWeb 2 under GPL-3.0. +// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt + +/** This is ant-design's Result component, but without importing 54 kB of + * images that we don't even use */ + +import React from "react"; +import classNames from "classnames"; + +import CheckCircleFilled from "@ant-design/icons/CheckCircleFilled"; +import CloseCircleFilled from "@ant-design/icons/CloseCircleFilled"; +import ExclamationCircleFilled from "@ant-design/icons/ExclamationCircleFilled"; +import WarningFilled from "@ant-design/icons/WarningFilled"; + +export const IconMap = { + success: CheckCircleFilled, + error: CloseCircleFilled, + info: ExclamationCircleFilled, + warning: WarningFilled, +}; +export type ResultStatusType = keyof typeof IconMap; + +export interface ResultProps { + icon?: React.ReactNode; + status?: ResultStatusType; + title?: React.ReactNode; + subTitle?: React.ReactNode; + extra?: React.ReactNode; + className?: string; + style?: React.CSSProperties; + fullPage?: boolean; +} + +/** + * Render icon if ExceptionStatus includes ,render svg image else render iconNode + */ +const renderIcon = ({ status, icon }: ResultProps) => { + const iconNode = React.createElement(IconMap[status as ResultStatusType],); + return
{icon || iconNode}
; +}; + +const renderExtra = ({ extra }: ResultProps) => + extra &&
{extra}
; + +export const SmallResult: React.FC = ({ + className: customizeClassName, + subTitle, + title, + style, + children, + status = "info", + icon, + extra, + fullPage, +}) => { + const classes = classNames("ant-result", "ant-result-" + status, customizeClassName, { + "full-page-result": fullPage + }); + + return ( +
+ {renderIcon({ status, icon })} +
{title}
+ {subTitle &&
{subTitle}
} + {renderExtra({ extra })} + {children &&
{children}
} +
+ ); +}; diff --git a/src/components/transactions/TransactionItem.tsx b/src/components/transactions/TransactionItem.tsx index 590acad..f61c01f 100644 --- a/src/components/transactions/TransactionItem.tsx +++ b/src/components/transactions/TransactionItem.tsx @@ -11,9 +11,9 @@ import { KristTransaction } from "../../krist/api/types"; import { Wallet } from "../../krist/wallets/Wallet"; import { DateTime } from "../DateTime"; -import { KristValue } from "../KristValue"; -import { KristNameLink } from "../KristNameLink"; -import { ContextualAddress } from "../ContextualAddress"; +import { KristValue } from "../krist/KristValue"; +import { KristNameLink } from "../names/KristNameLink"; +import { ContextualAddress } from "../addresses/ContextualAddress"; import { getTransactionType, TransactionType, INTERNAL_TYPES_SHOW_VALUE } from "./TransactionType"; const MAX_A_LENGTH = 24; diff --git a/src/layout/nav/AppHeader.tsx b/src/layout/nav/AppHeader.tsx index 51542bd..09c9d44 100644 --- a/src/layout/nav/AppHeader.tsx +++ b/src/layout/nav/AppHeader.tsx @@ -58,7 +58,7 @@ {/* Settings button */} } title={t("nav.settings")}> - + ; diff --git a/src/layout/nav/Search.tsx b/src/layout/nav/Search.tsx index 141af72..cdef7af 100644 --- a/src/layout/nav/Search.tsx +++ b/src/layout/nav/Search.tsx @@ -392,6 +392,9 @@ ; diff --git a/src/layout/nav/SearchResults.tsx b/src/layout/nav/SearchResults.tsx index 3af9a2c..92c623f 100644 --- a/src/layout/nav/SearchResults.tsx +++ b/src/layout/nav/SearchResults.tsx @@ -8,8 +8,8 @@ import { Trans, useTranslation } from "react-i18next"; import { KristAddress, KristName, KristBlock, KristTransaction } from "../../krist/api/types"; -import { KristValue } from "../../components/KristValue"; -import { KristNameLink } from "../../components/KristNameLink"; +import { KristValue } from "../../components/krist/KristValue"; +import { KristNameLink } from "../../components/names/KristNameLink"; import { DateTime } from "../../components/DateTime"; import "./SearchResults.less"; diff --git a/src/layout/sidebar/ServiceWorkerCheck.tsx b/src/layout/sidebar/ServiceWorkerCheck.tsx index ee77f11..f727e5a 100644 --- a/src/layout/sidebar/ServiceWorkerCheck.tsx +++ b/src/layout/sidebar/ServiceWorkerCheck.tsx @@ -16,6 +16,7 @@ const [showReload, setShowReload] = useState(false); const [waitingWorker, setWaitingWorker] = useState(null); + const [loading, setLoading] = useState(false); function onUpdate(registration: ServiceWorkerRegistration) { setShowReload(true); @@ -25,10 +26,10 @@ /** Force the service worker to update, wait for it to become active, then * reload the page. */ function reloadPage() { + setLoading(true); debug("emitting skipWaiting now"); waitingWorker?.postMessage({ type: "SKIP_WAITING" }); - setShowReload(false); waitingWorker?.addEventListener("statechange", () => { debug("SW state changed to %s", waitingWorker?.state); @@ -43,7 +44,7 @@ // NOTE: The update checker is also responsible for registering the service // worker in the first place. useEffect(() => { - debug("Registering service worker"); + debug("registering service worker"); serviceWorker.register({ onUpdate }); }, []); @@ -52,7 +53,7 @@
{t("sidebar.updateTitle")}

{t("sidebar.updateDescription")}

- diff --git a/src/layout/sidebar/SidebarTotalBalance.tsx b/src/layout/sidebar/SidebarTotalBalance.tsx index 11499aa..9965b90 100644 --- a/src/layout/sidebar/SidebarTotalBalance.tsx +++ b/src/layout/sidebar/SidebarTotalBalance.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"; import { useWallets } from "../../krist/wallets/Wallet"; -import { KristValue } from "../../components/KristValue"; +import { KristValue } from "../../components/krist/KristValue"; export function SidebarTotalBalance(): JSX.Element { const { t } = useTranslation(); diff --git a/src/pages/NotFoundPage.tsx b/src/pages/NotFoundPage.tsx index d07066f..16fcc1b 100644 --- a/src/pages/NotFoundPage.tsx +++ b/src/pages/NotFoundPage.tsx @@ -8,7 +8,7 @@ import { useHistory } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { SmallResult } from "../components/SmallResult"; +import { SmallResult } from "../components/results/SmallResult"; export function NotFoundPage(): JSX.Element { const { t } = useTranslation(); diff --git a/src/pages/addresses/AddressNamesCard.tsx b/src/pages/addresses/AddressNamesCard.tsx index afafe27..86d9791 100644 --- a/src/pages/addresses/AddressNamesCard.tsx +++ b/src/pages/addresses/AddressNamesCard.tsx @@ -13,7 +13,7 @@ import { useSyncNode } from "../../krist/api"; -import { SmallResult } from "../../components/SmallResult"; +import { SmallResult } from "../../components/results/SmallResult"; import Debug from "debug"; const debug = Debug("kristweb:address-names-card"); diff --git a/src/pages/addresses/AddressPage.tsx b/src/pages/addresses/AddressPage.tsx index c49846a..2e6c08c 100644 --- a/src/pages/addresses/AddressPage.tsx +++ b/src/pages/addresses/AddressPage.tsx @@ -8,10 +8,10 @@ import { useParams } from "react-router-dom"; import { PageLayout } from "../../layout/PageLayout"; -import { AddressResult } from "./AddressResult"; +import { APIErrorResult } from "../../components/results/APIErrorResult"; import { Statistic } from "../../components/Statistic"; -import { KristValue } from "../../components/KristValue"; +import { KristValue } from "../../components/krist/KristValue"; import { DateTime } from "../../components/DateTime"; import * as api from "../../krist/api"; @@ -143,7 +143,18 @@ {...title} > {error - ? + ? ( + + ) : (kristAddress ? : )} diff --git a/src/pages/addresses/AddressResult.tsx b/src/pages/addresses/AddressResult.tsx deleted file mode 100644 index 4ebfcf0..0000000 --- a/src/pages/addresses/AddressResult.tsx +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import { FrownOutlined, ExclamationCircleOutlined, QuestionCircleOutlined } from "@ant-design/icons"; - -import { useTranslation } from "react-i18next"; - -import { SmallResult } from "../../components/SmallResult"; -import { APIError } from "../../krist/api"; - -interface Props { - error: Error; -} - -export function AddressResult({ error }: Props): JSX.Element { - const { t } = useTranslation(); - - // Handle the most commonly expected errors from the API - if (error instanceof APIError) { - // Invalid address - if (error.message === "invalid_parameter") { - return } - title={t("address.resultInvalidTitle")} - subTitle={t("address.resultInvalid")} - fullPage - />; - } - - // Address not found - if (error.message === "address_not_found") { - return } - title={t("address.resultNotFoundTitle")} - subTitle={t("address.resultNotFound")} - fullPage - />; - } - } - - // Unknown error - return } - title={t("address.resultUnknownTitle")} - subTitle={t("address.resultUnknown")} - fullPage - />; -} diff --git a/src/pages/addresses/AddressTransactionsCard.tsx b/src/pages/addresses/AddressTransactionsCard.tsx index 4a0c70b..54228a9 100644 --- a/src/pages/addresses/AddressTransactionsCard.tsx +++ b/src/pages/addresses/AddressTransactionsCard.tsx @@ -12,7 +12,7 @@ import { useSyncNode } from "../../krist/api"; -import { SmallResult } from "../../components/SmallResult"; +import { SmallResult } from "../../components/results/SmallResult"; import Debug from "debug"; const debug = Debug("kristweb:address-transactions-card"); diff --git a/src/pages/addresses/NameItem.tsx b/src/pages/addresses/NameItem.tsx index 6bc6df3..22be9c7 100644 --- a/src/pages/addresses/NameItem.tsx +++ b/src/pages/addresses/NameItem.tsx @@ -8,7 +8,7 @@ import { Link } from "react-router-dom"; import { KristName } from "../../krist/api/types"; -import { KristNameLink } from "../../components/KristNameLink"; +import { KristNameLink } from "../../components/names/KristNameLink"; import { DateTime } from "../../components/DateTime"; export function NameItem({ name }: { name: KristName }): JSX.Element { diff --git a/src/pages/blocks/BlockPage.tsx b/src/pages/blocks/BlockPage.tsx index 916bf99..68660b5 100644 --- a/src/pages/blocks/BlockPage.tsx +++ b/src/pages/blocks/BlockPage.tsx @@ -12,12 +12,12 @@ import { RootState } from "../../store"; import { PageLayout } from "../../layout/PageLayout"; -import { BlockResult } from "./BlockResult"; +import { APIErrorResult } from "../../components/results/APIErrorResult"; import { Statistic } from "../../components/Statistic"; -import { ContextualAddress } from "../../components/ContextualAddress"; +import { ContextualAddress } from "../../components/addresses/ContextualAddress"; import { BlockHash } from "./BlockHash"; -import { KristValue } from "../../components/KristValue"; +import { KristValue } from "../../components/krist/KristValue"; import { DateTime } from "../../components/DateTime"; import * as api from "../../krist/api"; @@ -180,7 +180,18 @@ extra={} > {error - ? + ? ( + + ) : (kristBlock ? : )} diff --git a/src/pages/blocks/BlockResult.tsx b/src/pages/blocks/BlockResult.tsx deleted file mode 100644 index ae2974e..0000000 --- a/src/pages/blocks/BlockResult.tsx +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import { FrownOutlined, ExclamationCircleOutlined, QuestionCircleOutlined } from "@ant-design/icons"; - -import { useTranslation } from "react-i18next"; - -import { SmallResult } from "../../components/SmallResult"; -import { APIError } from "../../krist/api"; - -interface Props { - error: Error; -} - -export function BlockResult({ error }: Props): JSX.Element { - const { t } = useTranslation(); - - // Handle the most commonly expected errors from the API - if (error instanceof APIError) { - // Invalid block height - if (error.message === "invalid_parameter") { - return } - title={t("block.resultInvalidTitle")} - subTitle={t("block.resultInvalid")} - fullPage - />; - } - - // Block not found - if (error.message === "block_not_found") { - return } - title={t("block.resultNotFoundTitle")} - subTitle={t("block.resultNotFound")} - fullPage - />; - } - } - - // Unknown error - return } - title={t("block.resultUnknownTitle")} - subTitle={t("block.resultUnknown")} - fullPage - />; -} diff --git a/src/pages/blocks/BlocksPage.tsx b/src/pages/blocks/BlocksPage.tsx index cf16a18..a9ffa40 100644 --- a/src/pages/blocks/BlocksPage.tsx +++ b/src/pages/blocks/BlocksPage.tsx @@ -7,7 +7,7 @@ import { RootState } from "../../store"; import { PageLayout } from "../../layout/PageLayout"; -import { BlocksResult } from "./BlocksResult"; +import { APIErrorResult } from "../../components/results/APIErrorResult"; import { BlocksTable } from "./BlocksTable"; import { useBooleanSetting } from "../../utils/settings"; @@ -45,7 +45,7 @@ siteTitleKey={lowest ? "blocks.siteTitleLowest" : "blocks.siteTitle"} > {error - ? + ? : memoTable} ; } diff --git a/src/pages/blocks/BlocksResult.tsx b/src/pages/blocks/BlocksResult.tsx deleted file mode 100644 index b6b92bf..0000000 --- a/src/pages/blocks/BlocksResult.tsx +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import { QuestionCircleOutlined } from "@ant-design/icons"; - -import { useTranslation } from "react-i18next"; - -import { SmallResult } from "../../components/SmallResult"; - -export function BlocksResult(): JSX.Element { - const { t } = useTranslation(); - - return } - title={t("blocks.resultUnknownTitle")} - subTitle={t("blocks.resultUnknown")} - fullPage - />; -} diff --git a/src/pages/blocks/BlocksTable.tsx b/src/pages/blocks/BlocksTable.tsx index 03bb67a..836bcaf 100644 --- a/src/pages/blocks/BlocksTable.tsx +++ b/src/pages/blocks/BlocksTable.tsx @@ -11,9 +11,9 @@ import { lookupBlocks, LookupBlocksOptions, LookupBlocksResponse } from "../../krist/api/lookup"; import { getTablePaginationSettings, handleLookupTableChange } from "../../utils/table"; -import { ContextualAddress } from "../../components/ContextualAddress"; +import { ContextualAddress } from "../../components/addresses/ContextualAddress"; import { BlockHash } from "./BlockHash"; -import { KristValue } from "../../components/KristValue"; +import { KristValue } from "../../components/krist/KristValue"; import { DateTime } from "../../components/DateTime"; import Debug from "debug"; diff --git a/src/pages/dashboard/BlockDifficultyCard.tsx b/src/pages/dashboard/BlockDifficultyCard.tsx index 771bc7a..254bebd 100644 --- a/src/pages/dashboard/BlockDifficultyCard.tsx +++ b/src/pages/dashboard/BlockDifficultyCard.tsx @@ -16,7 +16,7 @@ import { KristConstants } from "../../krist/api/types"; import { trailingThrottleState } from "../../utils/promiseThrottle"; -import { SmallResult } from "../../components/SmallResult"; +import { SmallResult } from "../../components/results/SmallResult"; import { Statistic } from "../../components/Statistic"; import Debug from "debug"; diff --git a/src/pages/dashboard/BlockValueCard.tsx b/src/pages/dashboard/BlockValueCard.tsx index 6abbc7a..9d65b71 100644 --- a/src/pages/dashboard/BlockValueCard.tsx +++ b/src/pages/dashboard/BlockValueCard.tsx @@ -10,7 +10,7 @@ import { useTranslation, Trans } from "react-i18next"; import { Link } from "react-router-dom"; -import { KristValue } from "../../components/KristValue"; +import { KristValue } from "../../components/krist/KristValue"; const { Text } = Typography; diff --git a/src/pages/dashboard/TransactionsCard.tsx b/src/pages/dashboard/TransactionsCard.tsx index 46be18e..12d5ce7 100644 --- a/src/pages/dashboard/TransactionsCard.tsx +++ b/src/pages/dashboard/TransactionsCard.tsx @@ -14,7 +14,7 @@ import { useWallets } from "../../krist/wallets/Wallet"; import { WalletMap } from "../../store/reducers/WalletsReducer"; -import { SmallResult } from "../../components/SmallResult"; +import { SmallResult } from "../../components/results/SmallResult"; import { trailingThrottleState } from "../../utils/promiseThrottle"; diff --git a/src/pages/dashboard/WalletItem.tsx b/src/pages/dashboard/WalletItem.tsx index 7fb8ca3..9aa06f4 100644 --- a/src/pages/dashboard/WalletItem.tsx +++ b/src/pages/dashboard/WalletItem.tsx @@ -6,7 +6,7 @@ import { Wallet } from "../../krist/wallets/Wallet"; -import { KristValue } from "../../components/KristValue"; +import { KristValue } from "../../components/krist/KristValue"; export function WalletItem({ wallet }: { wallet: Wallet }): JSX.Element { return diff --git a/src/pages/dashboard/WalletOverviewCard.tsx b/src/pages/dashboard/WalletOverviewCard.tsx index 09d7254..1cd90b4 100644 --- a/src/pages/dashboard/WalletOverviewCard.tsx +++ b/src/pages/dashboard/WalletOverviewCard.tsx @@ -9,7 +9,7 @@ import { Wallet, useWallets } from "../../krist/wallets/Wallet"; -import { KristValue } from "../../components/KristValue"; +import { KristValue } from "../../components/krist/KristValue"; import { Statistic } from "../../components/Statistic"; import { WalletItem } from "./WalletItem"; diff --git a/src/pages/names/NamePage.tsx b/src/pages/names/NamePage.tsx index a83589e..36dbdb5 100644 --- a/src/pages/names/NamePage.tsx +++ b/src/pages/names/NamePage.tsx @@ -12,12 +12,12 @@ import { RootState } from "../../store"; import { PageLayout } from "../../layout/PageLayout"; -import { NameResult } from "./NameResult"; +import { APIErrorResult } from "../../components/results/APIErrorResult"; import { Statistic } from "../../components/Statistic"; -import { ContextualAddress } from "../../components/ContextualAddress"; +import { ContextualAddress } from "../../components/addresses/ContextualAddress"; import { DateTime } from "../../components/DateTime"; -import { NameARecordLink } from "../../components/NameARecordLink"; +import { NameARecordLink } from "../../components/names/NameARecordLink"; import * as api from "../../krist/api"; import { KristName } from "../../krist/api/types"; @@ -175,7 +175,18 @@ {...title} > {error - ? + ? ( + + ) : (kristName ? : )} diff --git a/src/pages/names/NameResult.tsx b/src/pages/names/NameResult.tsx deleted file mode 100644 index de95284..0000000 --- a/src/pages/names/NameResult.tsx +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import { FrownOutlined, ExclamationCircleOutlined, QuestionCircleOutlined } from "@ant-design/icons"; - -import { useTranslation } from "react-i18next"; - -import { SmallResult } from "../../components/SmallResult"; -import { APIError } from "../../krist/api"; - -interface Props { - error: Error; -} - -export function NameResult({ error }: Props): JSX.Element { - const { t } = useTranslation(); - - // Handle the most commonly expected errors from the API - if (error instanceof APIError) { - // Invalid name - if (error.message === "invalid_parameter") { - return } - title={t("name.resultInvalidTitle")} - subTitle={t("name.resultInvalid")} - fullPage - />; - } - - // Name not found - if (error.message === "name_not_found") { - return } - title={t("name.resultNotFoundTitle")} - subTitle={t("name.resultNotFound")} - fullPage - />; - } - } - - // Unknown error - return } - title={t("name.resultUnknownTitle")} - subTitle={t("name.resultUnknown")} - fullPage - />; -} diff --git a/src/pages/names/NameTransactionsCard.tsx b/src/pages/names/NameTransactionsCard.tsx index 818ab57..50330a6 100644 --- a/src/pages/names/NameTransactionsCard.tsx +++ b/src/pages/names/NameTransactionsCard.tsx @@ -12,7 +12,7 @@ import { useSyncNode } from "../../krist/api"; -import { SmallResult } from "../../components/SmallResult"; +import { SmallResult } from "../../components/results/SmallResult"; import Debug from "debug"; const debug = Debug("kristweb:name-transactions-card"); diff --git a/src/pages/names/NamesPage.tsx b/src/pages/names/NamesPage.tsx index 788b39e..e982e99 100644 --- a/src/pages/names/NamesPage.tsx +++ b/src/pages/names/NamesPage.tsx @@ -10,7 +10,7 @@ import { RootState } from "../../store"; import { PageLayout } from "../../layout/PageLayout"; -import { NamesResult } from "./NamesResult"; +import { APIErrorResult } from "../../components/results/APIErrorResult"; import { NamesTable } from "./NamesTable"; import { useWallets } from "../../krist/wallets/Wallet"; @@ -108,7 +108,14 @@ subTitle={subTitle} > {error - ? + ? ( + + ) : memoTable} ; } diff --git a/src/pages/names/NamesResult.tsx b/src/pages/names/NamesResult.tsx deleted file mode 100644 index 9208add..0000000 --- a/src/pages/names/NamesResult.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import { ExclamationCircleOutlined, QuestionCircleOutlined } from "@ant-design/icons"; - -import { useTranslation } from "react-i18next"; - -import { SmallResult } from "../../components/SmallResult"; -import { APIError } from "../../krist/api"; - -interface Props { - error: Error; -} - -export function NamesResult({ error }: Props): JSX.Element { - const { t } = useTranslation(); - - // Handle the most commonly expected errors from the API - if (error instanceof APIError) { - // Invalid address list - if (error.message === "invalid_parameter") { - return } - title={t("names.resultInvalidTitle")} - subTitle={t("names.resultInvalid")} - fullPage - />; - } - } - - // Unknown error - return } - title={t("names.resultUnknownTitle")} - subTitle={t("names.resultUnknown")} - fullPage - />; -} diff --git a/src/pages/names/NamesTable.tsx b/src/pages/names/NamesTable.tsx index f84997a..3688fa8 100644 --- a/src/pages/names/NamesTable.tsx +++ b/src/pages/names/NamesTable.tsx @@ -10,8 +10,8 @@ import { lookupNames, LookupNamesOptions, LookupNamesResponse } from "../../krist/api/lookup"; import { getTablePaginationSettings, handleLookupTableChange } from "../../utils/table"; -import { KristNameLink } from "../../components/KristNameLink"; -import { ContextualAddress } from "../../components/ContextualAddress"; +import { KristNameLink } from "../../components/names/KristNameLink"; +import { ContextualAddress } from "../../components/addresses/ContextualAddress"; import { TransactionConciseMetadata } from "../../components/transactions/TransactionConciseMetadata"; import { DateTime } from "../../components/DateTime"; diff --git a/src/pages/settings/SettingsTranslations.tsx b/src/pages/settings/SettingsTranslations.tsx index da68e4c..8e4a4c2 100644 --- a/src/pages/settings/SettingsTranslations.tsx +++ b/src/pages/settings/SettingsTranslations.tsx @@ -13,7 +13,7 @@ import { saveAs } from "file-saver"; import { Flag } from "../../components/Flag"; -import { SmallResult } from "../../components/SmallResult"; +import { SmallResult } from "../../components/results/SmallResult"; import { SettingsPageLayout } from "./SettingsPage"; const { Text } = Typography; diff --git a/src/pages/transactions/TransactionPage.tsx b/src/pages/transactions/TransactionPage.tsx index 4a66da6..a85eb39 100644 --- a/src/pages/transactions/TransactionPage.tsx +++ b/src/pages/transactions/TransactionPage.tsx @@ -8,15 +8,15 @@ import { useParams } from "react-router-dom"; import { PageLayout } from "../../layout/PageLayout"; -import { TransactionResult } from "./TransactionResult"; +import { APIErrorResult } from "../../components/results/APIErrorResult"; import { Statistic } from "../../components/Statistic"; import { TransactionType, TYPES_SHOW_VALUE } from "../../components/transactions/TransactionType"; -import { ContextualAddress } from "../../components/ContextualAddress"; -import { KristNameLink } from "../../components/KristNameLink"; -import { KristValue } from "../../components/KristValue"; +import { ContextualAddress } from "../../components/addresses/ContextualAddress"; +import { KristNameLink } from "../../components/names/KristNameLink"; +import { KristValue } from "../../components/krist/KristValue"; import { DateTime } from "../../components/DateTime"; -import { NameARecordLink } from "../../components/NameARecordLink"; +import { NameARecordLink } from "../../components/names/NameARecordLink"; import * as api from "../../krist/api"; import { KristTransaction, KristTransactionType } from "../../krist/api/types"; @@ -161,7 +161,18 @@ {...titleData} > {error - ? + ? ( + + ) : (kristTransaction ? : )} diff --git a/src/pages/transactions/TransactionResult.tsx b/src/pages/transactions/TransactionResult.tsx deleted file mode 100644 index 978cf19..0000000 --- a/src/pages/transactions/TransactionResult.tsx +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import { FrownOutlined, ExclamationCircleOutlined, QuestionCircleOutlined } from "@ant-design/icons"; - -import { useTranslation } from "react-i18next"; - -import { SmallResult } from "../../components/SmallResult"; -import { APIError } from "../../krist/api"; - -interface Props { - error: Error; -} - -export function TransactionResult({ error }: Props): JSX.Element { - const { t } = useTranslation(); - - // Handle the most commonly expected errors from the API - if (error instanceof APIError) { - // Invalid transaction ID - if (error.message === "invalid_parameter") { - return } - title={t("transaction.resultInvalidTitle")} - subTitle={t("transaction.resultInvalid")} - fullPage - />; - } - - // Transaction not found - if (error.message === "transaction_not_found") { - return } - title={t("transaction.resultNotFoundTitle")} - subTitle={t("transaction.resultNotFound")} - fullPage - />; - } - } - - // Unknown error - return } - title={t("transaction.resultUnknownTitle")} - subTitle={t("transaction.resultUnknown")} - fullPage - />; -} diff --git a/src/pages/transactions/TransactionsPage.tsx b/src/pages/transactions/TransactionsPage.tsx index 08b0190..d4720bb 100644 --- a/src/pages/transactions/TransactionsPage.tsx +++ b/src/pages/transactions/TransactionsPage.tsx @@ -12,12 +12,12 @@ import { State as NodeState } from "../../store/reducers/NodeReducer"; import { PageLayout } from "../../layout/PageLayout"; -import { TransactionsResult } from "./TransactionsResult"; +import { APIErrorResult } from "../../components/results/APIErrorResult"; import { TransactionsTable } from "./TransactionsTable"; import { useWallets } from "../../krist/wallets/Wallet"; import { useBooleanSetting } from "../../utils/settings"; -import { KristNameLink } from "../../components/KristNameLink"; +import { KristNameLink } from "../../components/names/KristNameLink"; /** The type of transaction listing to search by. */ export enum ListingType { @@ -160,7 +160,14 @@ } > {error - ? + ? ( + + ) : memoTable} ; } diff --git a/src/pages/transactions/TransactionsResult.tsx b/src/pages/transactions/TransactionsResult.tsx deleted file mode 100644 index 1047c5d..0000000 --- a/src/pages/transactions/TransactionsResult.tsx +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2020-2021 Drew Lemmy -// This file is part of KristWeb 2 under GPL-3.0. -// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt -import React from "react"; -import { ExclamationCircleOutlined, QuestionCircleOutlined } from "@ant-design/icons"; - -import { useTranslation } from "react-i18next"; - -import { SmallResult } from "../../components/SmallResult"; -import { APIError } from "../../krist/api"; - -interface Props { - error: Error; -} - -export function TransactionsResult({ error }: Props): JSX.Element { - const { t } = useTranslation(); - - // Handle the most commonly expected errors from the API - if (error instanceof APIError) { - // Invalid address list - if (error.message === "invalid_parameter") { - return } - title={t("transactions.resultInvalidTitle")} - subTitle={t("transactions.resultInvalid")} - fullPage - />; - } - } - - // Unknown error - return } - title={t("transactions.resultUnknownTitle")} - subTitle={t("transactions.resultUnknown")} - fullPage - />; -} diff --git a/src/pages/transactions/TransactionsTable.tsx b/src/pages/transactions/TransactionsTable.tsx index 7f5d456..19098fe 100644 --- a/src/pages/transactions/TransactionsTable.tsx +++ b/src/pages/transactions/TransactionsTable.tsx @@ -14,9 +14,9 @@ import { ListingType } from "./TransactionsPage"; import { TransactionType, TYPES_SHOW_VALUE } from "../../components/transactions/TransactionType"; -import { ContextualAddress } from "../../components/ContextualAddress"; -import { KristValue } from "../../components/KristValue"; -import { KristNameLink } from "../../components/KristNameLink"; +import { ContextualAddress } from "../../components/addresses/ContextualAddress"; +import { KristValue } from "../../components/krist/KristValue"; +import { KristNameLink } from "../../components/names/KristNameLink"; import { TransactionConciseMetadata } from "../../components/transactions/TransactionConciseMetadata"; import { DateTime } from "../../components/DateTime"; diff --git a/src/pages/wallets/WalletsTable.tsx b/src/pages/wallets/WalletsTable.tsx index 50613b4..6f196b1 100644 --- a/src/pages/wallets/WalletsTable.tsx +++ b/src/pages/wallets/WalletsTable.tsx @@ -7,8 +7,8 @@ import { useTranslation } from "react-i18next"; -import { ContextualAddress } from "../../components/ContextualAddress"; -import { KristValue } from "../../components/KristValue"; +import { ContextualAddress } from "../../components/addresses/ContextualAddress"; +import { KristValue } from "../../components/krist/KristValue"; import { DateTime } from "../../components/DateTime"; import { WalletEditButton } from "./WalletEditButton"; import { AddWalletModal } from "./AddWalletModal"; diff --git a/src/style/theme.less b/src/style/theme.less index dca0f5e..f397238 100644 --- a/src/style/theme.less +++ b/src/style/theme.less @@ -5,7 +5,8 @@ // Colours // --- @kw-text: #eaf0fe; -@kw-text-secondary: #8991ab; +// @kw-text-secondary: #8991ab; +@kw-text-secondary: #969fbb; // Adjusted for a11y @kw-text-tertiary: mix(@kw-text, @kw-text-secondary, 30%); @kw-light: #343a56;