diff --git a/src/components/ConditionalLink.tsx b/src/components/ConditionalLink.tsx
index c512d46..906f8ec 100644
--- a/src/components/ConditionalLink.tsx
+++ b/src/components/ConditionalLink.tsx
@@ -6,7 +6,7 @@
import { Link, useRouteMatch } from "react-router-dom";
interface Props {
- to: string;
+ to?: string;
condition?: boolean;
replace?: boolean;
diff --git a/src/components/addresses/ContextualAddress.tsx b/src/components/addresses/ContextualAddress.tsx
index e52cee1..64e0070 100644
--- a/src/components/addresses/ContextualAddress.tsx
+++ b/src/components/addresses/ContextualAddress.tsx
@@ -6,7 +6,6 @@
import { Tooltip } from "antd";
import { useTranslation } from "react-i18next";
-import { Link } from "react-router-dom";
import { KristAddress } from "@api/types";
import { Wallet, useWallets } from "@wallets";
@@ -33,6 +32,7 @@
allowWrap?: boolean;
neverCopyable?: boolean;
nonExistent?: boolean;
+ noLink?: boolean;
className?: string;
}
@@ -46,6 +46,7 @@
allowWrap,
neverCopyable,
nonExistent,
+ noLink,
className
}: Props): JSX.Element {
const { t } = useTranslation();
@@ -113,6 +114,7 @@
address={address}
source={!!source}
hideNameAddress={!!hideNameAddress}
+ noLink={!!noLink}
name={cmName}
recipient={cmRecipient}
@@ -122,14 +124,18 @@
)
: (verified
// Display the verified address if possible
- ?
+ ?
: (
// Display the regular address or label
)
), [
- hideNameAddress, nonExistent, source, nameSuffix,
+ hideNameAddress, nonExistent, noLink, source, nameSuffix,
address, walletLabel, contactLabel, verified,
cmName, cmRecipient, cmReturn, cmReturnName, hasMetaname,
]);
@@ -187,6 +193,7 @@
address: string;
source: boolean;
hideNameAddress: boolean;
+ noLink: boolean;
name?: string;
recipient?: string;
@@ -199,6 +206,7 @@
address,
source,
hideNameAddress,
+ noLink,
name: cmName,
recipient: cmRecipient,
@@ -215,7 +223,12 @@
return verified
? (
// Verified address
-
+
)
: (
// Regular address
@@ -224,6 +237,7 @@
to={"/network/addresses/" + encodeURIComponent(address)}
matchTo
matchExact
+ condition={!noLink}
>
({address})
@@ -238,6 +252,7 @@
className="address-name"
name={nameWithoutSuffix!}
text={rawMetaname}
+ noLink={noLink}
/>
{/* Display the original address too */}
@@ -247,8 +262,13 @@
>
: (
// Display the raw metaname, but link to the owner address
-
+
{rawMetaname}
-
+
);
}
diff --git a/src/components/addresses/VerifiedAddress.tsx b/src/components/addresses/VerifiedAddress.tsx
index 452fa53..7621c01 100644
--- a/src/components/addresses/VerifiedAddress.tsx
+++ b/src/components/addresses/VerifiedAddress.tsx
@@ -37,6 +37,7 @@
address: string;
verified: VerifiedAddress;
parens?: boolean;
+ noLink?: boolean;
className?: string;
}
@@ -44,6 +45,7 @@
address,
verified,
parens,
+ noLink,
className
}: Props): JSX.Element {
const classes = classNames("address-verified", className, {
@@ -56,6 +58,7 @@
to={"/network/addresses/" + encodeURIComponent(address)}
matchTo
matchExact
+ condition={!noLink}
>
{parens && <>(>}
diff --git a/src/components/names/KristNameLink.tsx b/src/components/names/KristNameLink.tsx
index 0d842f5..b147992 100644
--- a/src/components/names/KristNameLink.tsx
+++ b/src/components/names/KristNameLink.tsx
@@ -4,7 +4,7 @@
import classNames from "classnames";
import { Typography } from "antd";
-import { Link } from "react-router-dom";
+import { ConditionalLink } from "@comp/ConditionalLink";
import { useNameSuffix } from "@utils/currency";
import { useBooleanSetting } from "@utils/settings";
@@ -35,12 +35,13 @@
const classes = classNames("krist-name", props.className);
return
- {noLink
- ? content
- : (
-
- {content}
-
- )}
+
+ {content}
+
;
}
diff --git a/src/components/transactions/TransactionItem.tsx b/src/components/transactions/TransactionItem.tsx
index dbbbeeb..7bf2354 100644
--- a/src/components/transactions/TransactionItem.tsx
+++ b/src/components/transactions/TransactionItem.tsx
@@ -1,21 +1,23 @@
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of KristWeb 2 under AGPL-3.0.
// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
-import classNames from "classnames";
import { Row, Col, Tooltip, Grid } from "antd";
+import { RightOutlined } from "@ant-design/icons";
-import { useTranslation, Trans } from "react-i18next";
+import { useTFns } from "@utils/i18n";
+
import { Link } from "react-router-dom";
import { KristTransaction } from "@api/types";
-import { WalletAddressMap } from "@wallets";
-import { DateTime } from "../DateTime";
-import { KristValue } from "../krist/KristValue";
-import { KristNameLink } from "../names/KristNameLink";
-import { ContextualAddress } from "../addresses/ContextualAddress";
-import { getTransactionType, TransactionType, INTERNAL_TYPES_SHOW_VALUE } from "./TransactionType";
+import { WalletAddressMap, Wallet } from "@wallets";
-const MAX_A_LENGTH = 24;
+import { DateTime } from "../DateTime";
+
+import * as Parts from "./TransactionItemParts";
+
+import {
+ getTransactionType, TransactionType, InternalTransactionType
+} from "./TransactionType";
interface Props {
transaction: KristTransaction;
@@ -24,26 +26,23 @@
wallets: WalletAddressMap;
}
-export function TransactionARecord({ metadata }: { metadata: string | undefined | null }): JSX.Element {
- const { t } = useTranslation();
+interface ItemProps {
+ type: InternalTransactionType;
- return metadata
- ?
-
- {metadata.length > MAX_A_LENGTH
- ? <>{metadata.substring(0, MAX_A_LENGTH)}…>
- : metadata}
-
-
- : (
-
- {t("transactionSummary.itemARecordRemoved")}
-
- );
+ tx: KristTransaction;
+ txTime: Date;
+ txLink: string;
+
+ fromWallet?: Wallet;
+ toWallet?: Wallet;
+
+ hideNameAddress: boolean;
}
-export function TransactionItem({ transaction: tx, wallets }: Props): JSX.Element {
- const { t } = useTranslation();
+export function TransactionItem({
+ transaction: tx,
+ wallets
+}: Props): JSX.Element {
const bps = Grid.useBreakpoint();
// Whether or not the from/to addresses are a wallet we own
@@ -53,20 +52,39 @@
const type = getTransactionType(tx, fromWallet, toWallet);
const txTime = new Date(tx.time);
- const isNew = (new Date().getTime() - txTime.getTime()) < 360000;
-
const txLink = "/network/transactions/" + encodeURIComponent(tx.id);
const hideNameAddress = !bps.xl;
- const classes = classNames("card-list-item", "transaction-summary-item", {
- "new": isNew
- });
+ // Return a different element (same data, different layout) depending on
+ // whether this is mobile or desktop
+ return bps.sm
+ ?
+ : ;
+}
- return
+function TransactionItemDesktop({
+ type,
+ tx, txTime, txLink,
+ fromWallet, toWallet,
+ hideNameAddress
+}: ItemProps): JSX.Element {
+ const { t, tKey } = useTFns("transactionSummary.");
+
+ return
{/* Transaction type and link to transaction */}
-
+
@@ -77,69 +95,75 @@
- {/* Transaction name */}
- {(type === "name_a_record" || type === "name_purchased") && (
-
- Name:
-
-
- )}
+ {/* Name and A record */}
+
+
- {/* Transaction A record */}
- {type === "name_a_record" && (
-
- A record:
-
-
- )}
+ {/* To */}
+
- {/* Transaction to */}
- {type !== "name_a_record" && (
-
- To:
- {type === "name_purchased"
- ?
- : }
-
- )}
-
- {/* Transaction from */}
- {type !== "name_a_record" && type !== "name_purchased" && type !== "mined" && (
-
- From:
-
-
- )}
+ {/* From */}
+
- {INTERNAL_TYPES_SHOW_VALUE.includes(type)
- ? (
- // Transaction value
-
- )
- : tx.type === "name_transfer" && (
- // Transaction name
-
- )}
+ {/* Value / name */}
+
;
}
+
+function TransactionItemMobile({
+ type,
+ tx, txTime, txLink,
+ fromWallet, toWallet,
+ hideNameAddress
+}: ItemProps): JSX.Element {
+ const { tKey } = useTFns("transactionSummary.");
+
+ return
+ {/* Type and primary value */}
+
+
+ {/* Name and A record */}
+
+
+
+ {/* To */}
+
+
+ {/* From */}
+
+
+ {/* Time */}
+
+
+ {/* Right chevron */}
+
+ ;
+}
diff --git a/src/components/transactions/TransactionItemParts.tsx b/src/components/transactions/TransactionItemParts.tsx
new file mode 100644
index 0000000..7650892
--- /dev/null
+++ b/src/components/transactions/TransactionItemParts.tsx
@@ -0,0 +1,177 @@
+// Copyright (c) 2020-2021 Drew Lemmy
+// This file is part of KristWeb 2 under AGPL-3.0.
+// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
+import { Tooltip } from "antd";
+
+import { Trans } from "react-i18next";
+import { useTFns, TKeyFn } from "@utils/i18n";
+
+import { KristTransaction } from "@api/types";
+import { Wallet } from "@wallets";
+
+import { KristNameLink } from "../names/KristNameLink";
+import { ContextualAddress } from "../addresses/ContextualAddress";
+import { KristValue } from "../krist/KristValue";
+
+import {
+ InternalTransactionType, INTERNAL_TYPES_SHOW_VALUE
+} from "./TransactionType";
+
+const MAX_A_LENGTH = 24;
+
+interface PartBaseProps {
+ tKey: TKeyFn;
+ type: InternalTransactionType;
+ noLink?: boolean;
+}
+
+interface PartTxProps extends PartBaseProps {
+ tx: KristTransaction;
+}
+
+interface PartAddressProps extends PartTxProps {
+ fromWallet?: Wallet;
+ toWallet?: Wallet;
+ hideNameAddress: boolean;
+}
+
+// -----------------------------------------------------------------------------
+// NAME
+// -----------------------------------------------------------------------------
+export function TransactionName({ tKey, type, name, noLink }: PartBaseProps & {
+ name?: string;
+}): JSX.Element | null {
+ if (type !== "name_a_record" && type !== "name_purchased") return null;
+
+ return
+
+ Name:
+
+
+ ;
+}
+
+// -----------------------------------------------------------------------------
+// A RECORD
+// -----------------------------------------------------------------------------
+export function TransactionARecordContent({ metadata }: {
+ metadata: string | undefined | null;
+}): JSX.Element {
+ const { tStr } = useTFns("transactionSummary.");
+
+ return metadata
+ ?
+
+ {metadata.length > MAX_A_LENGTH
+ ? <>{metadata.substring(0, MAX_A_LENGTH)}…>
+ : metadata}
+
+
+ : (
+
+ {tStr("itemARecordRemoved")}
+
+ );
+}
+
+export function TransactionARecord({ tKey, type, metadata }: PartBaseProps & {
+ metadata: string | undefined | null;
+}): JSX.Element | null {
+ if (type !== "name_a_record") return null;
+
+ return
+
+ A record:
+
+
+ ;
+}
+
+// -----------------------------------------------------------------------------
+// TO
+// -----------------------------------------------------------------------------
+export function TransactionTo({
+ tKey,
+ type, tx,
+ fromWallet, toWallet,
+ hideNameAddress, noLink
+}: PartAddressProps): JSX.Element | null {
+ if (type === "name_a_record") return null;
+
+ return
+
+ To:
+ {type === "name_purchased"
+ ?
+ : }
+
+ ;
+}
+
+// -----------------------------------------------------------------------------
+// FROM
+// -----------------------------------------------------------------------------
+export function TransactionFrom({
+ tKey,
+ type, tx,
+ fromWallet,
+ hideNameAddress, noLink
+}: Omit): JSX.Element | null {
+ if (type === "name_a_record" || type === "name_purchased" || type === "mined")
+ return null;
+
+ return
+
+ From:
+
+
+ ;
+}
+
+// -----------------------------------------------------------------------------
+// VALUE / NAME
+// -----------------------------------------------------------------------------
+export function TransactionPrimaryValue({
+ type,
+ tx
+}: Omit): JSX.Element | null {
+ return
+ {INTERNAL_TYPES_SHOW_VALUE.includes(type)
+ ? (
+ // Transaction value
+
+ )
+ : (tx.type === "name_transfer"
+ ? (
+ // Transaction name
+
+ )
+ : null
+ )}
+ ;
+}
diff --git a/src/components/transactions/TransactionSummary.less b/src/components/transactions/TransactionSummary.less
index 7224972..cce55c3 100644
--- a/src/components/transactions/TransactionSummary.less
+++ b/src/components/transactions/TransactionSummary.less
@@ -6,19 +6,44 @@
.transaction-summary-item {
flex-flow: nowrap;
+ .date-time {
+ color: @text-color-secondary;
+ font-size: 90%;
+
+ @media (max-width: @screen-xl) {
+ font-size: 85%;
+ }
+ }
+
+ .transaction-field {
+ font-weight: bold;
+ white-space: nowrap;
+ color: @text-color-secondary;
+ }
+
+ .transaction-a-record-value {
+ font-family: monospace;
+ font-size: 90%;
+ color: @text-color-secondary;
+ }
+
+ .transaction-a-record-removed {
+ font-style: italic;
+ font-size: 90%;
+ color: @text-color-secondary;
+
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .transaction-name {
+ font-weight: bold;
+ }
+
.transaction-left {
display: flex;
flex-direction: column;
justify-content: center;
-
- .transaction-time {
- color: @text-color-secondary;
- font-size: 90%;
-
- @media (max-width: @screen-xl) {
- font-size: 85%;
- }
- }
}
.transaction-middle {
@@ -29,27 +54,6 @@
justify-content: center;
overflow: hidden;
-
- .transaction-field {
- font-weight: bold;
- white-space: nowrap;
- color: @text-color-secondary;
- }
-
- .transaction-a-record-value {
- font-family: monospace;
- font-size: 90%;
- color: @text-color-secondary;
- }
-
- .transaction-a-record-removed {
- font-style: italic;
- font-size: 90%;
- color: @text-color-secondary;
-
- white-space: nowrap;
- text-overflow: ellipsis;
- }
}
.transaction-right {
@@ -59,9 +63,50 @@
display: flex;
justify-content: center;
align-items: center;
+ }
- .transaction-name {
- font-weight: bold;
+ &.transaction-summary-item-mobile {
+ display: block;
+ margin-bottom: @padding-xs;
+
+ color: @text-color;
+ font-size: @font-size-sm;
+
+ position: relative;
+
+ .transaction-mobile-top {
+ font-size: @font-size-base;
+ margin-bottom: @padding-xss;
+
+ .transaction-type {
+ font-size: @font-size-base;
+ }
+
+ .transaction-primary-value {
+ display: inline-block;
+ margin-left: @padding-xs;
+ }
+ }
+
+ .transaction-to, .transaction-from {
+ display: block;
+ }
+
+ .date-time {
+ font-size: @font-size-sm;
+ }
+
+ .transaction-mobile-right {
+ display: flex;
+ align-items: center;
+
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: @padding-md;
+
+ font-size: 32px;
+ color: fade(@text-color-secondary, 50%);
}
}
}
diff --git a/src/components/transactions/TransactionType.tsx b/src/components/transactions/TransactionType.tsx
index 1a50597..3cf11e7 100644
--- a/src/components/transactions/TransactionType.tsx
+++ b/src/components/transactions/TransactionType.tsx
@@ -4,7 +4,8 @@
import classNames from "classnames";
import { useTranslation } from "react-i18next";
-import { Link } from "react-router-dom";
+
+import { ConditionalLink } from "@comp/ConditionalLink";
import { KristTransaction, KristTransactionType } from "@api/types";
import { Wallet, useWallets } from "@wallets";
@@ -22,7 +23,11 @@
"transfer", "mined", "name_purchase"
];
-export function getTransactionType(tx: KristTransaction, from?: Wallet, to?: Wallet): InternalTransactionType {
+export function getTransactionType(
+ tx: KristTransaction,
+ from?: Wallet,
+ to?: Wallet
+): InternalTransactionType {
switch (tx.type) {
case "transfer":
if (tx.from && tx.to && tx.from === tx.to) return "bumped";
@@ -55,7 +60,14 @@
}
type Props = React.HTMLProps & OwnProps;
-export function TransactionType({ type, transaction, from, to, link, className }: Props): JSX.Element {
+export function TransactionType({
+ type,
+ transaction,
+ from,
+ to,
+ link,
+ className
+}: Props): JSX.Element {
const { t } = useTranslation();
const { walletAddressMap } = useWallets();
@@ -73,8 +85,13 @@
});
return
- {link
- ? {contents}
- : {contents}}
+
+ {contents}
+
;
}
diff --git a/src/layout/PageLayout.less b/src/layout/PageLayout.less
index 688c03f..9cf68c2 100644
--- a/src/layout/PageLayout.less
+++ b/src/layout/PageLayout.less
@@ -33,6 +33,8 @@
// Make tables full-width on mobile (though most should be replaced with
// custom views)
@media (max-width: @screen-md) {
+ padding: @padding-md;
+
>.ant-table-wrapper {
.ant-table {
margin: 0 -@padding-lg;
@@ -55,6 +57,10 @@
}
}
}
+
+ @media (max-width: @screen-sm) {
+ padding: @padding-sm;
+ }
}
&.page-layout-no-top-padding {
diff --git a/src/style/card.less b/src/style/card.less
index cfa6007..8f49a16 100644
--- a/src/style/card.less
+++ b/src/style/card.less
@@ -123,4 +123,30 @@
}
}
}
+
+ @media (max-width: @screen-sm) {
+ .ant-card-head {
+ padding: 0 @padding-md;
+
+ .ant-card-head-title {
+ padding-top: @padding-md;
+ }
+ }
+
+ .ant-card-body {
+ padding: @padding-md;
+ }
+
+ .card-more {
+ height: 32px;
+ margin-bottom: -6px;
+
+ a {
+ display: inline;
+ line-height: 32px;
+ vertical-align: middle;
+ margin-bottom: 0;
+ }
+ }
+ }
}