diff --git a/public/locales/en.json b/public/locales/en.json
index 2cf6c5f..d1aaf39 100644
--- a/public/locales/en.json
+++ b/public/locales/en.json
@@ -150,11 +150,13 @@
"nameCount_plural": "{{count, number}} names",
"nameCountEmpty": "No names",
"firstSeen": "First seen {{date}}",
+ "firstSeenMobile": "First seen: <1 />",
"walletCount": "{{count, number}} wallet",
"walletCount_plural": "{{count, number}} wallets",
"walletCountEmpty": "No wallets",
+ "actionsViewAddress": "View address",
"actionsEditTooltip": "Edit wallet",
"actionsSendTransaction": "Send Krist",
"actionsWalletInfo": "Wallet info",
diff --git a/src/components/addresses/ContextualAddress.less b/src/components/addresses/ContextualAddress.less
index d030790..5d0c09e 100644
--- a/src/components/addresses/ContextualAddress.less
+++ b/src/components/addresses/ContextualAddress.less
@@ -20,7 +20,7 @@
}
&.contextual-address-non-existent {
- &, a {
+ &, span, a {
color: @text-color-secondary;
cursor: not-allowed;
diff --git a/src/components/addresses/ContextualAddress.tsx b/src/components/addresses/ContextualAddress.tsx
index 294b835..ae7f156 100644
--- a/src/components/addresses/ContextualAddress.tsx
+++ b/src/components/addresses/ContextualAddress.tsx
@@ -32,6 +32,7 @@
neverCopyable?: boolean;
nonExistent?: boolean;
noLink?: boolean;
+ noTooltip?: boolean;
className?: string;
}
@@ -46,6 +47,7 @@
neverCopyable,
nonExistent,
noLink,
+ noTooltip,
className
}: Props): JSX.Element {
const { t } = useTranslation();
@@ -90,7 +92,7 @@
// If the address definitely doesn't exist, show the 'not yet initialised'
// tooltip on hover instead.
- const showTooltip = !verified &&
+ const showTooltip = !noTooltip && !verified &&
((hideNameAddress && !!hasMetaname) || !!walletLabel || !!contactLabel);
const tooltipTitle = nonExistent
? t("contextualAddressNonExistentTooltip")
diff --git a/src/pages/wallets/WalletActions.tsx b/src/pages/wallets/WalletActions.tsx
index 031dce6..a457b41 100644
--- a/src/pages/wallets/WalletActions.tsx
+++ b/src/pages/wallets/WalletActions.tsx
@@ -8,7 +8,8 @@
SendOutlined
} from "@ant-design/icons";
-import { useTFns } from "@utils/i18n";
+import { TFunction } from "react-i18next";
+import { useTFns, TStrFn } from "@utils/i18n";
import { useAuth } from "@comp/auth";
import { OpenEditWalletFn } from "./WalletEditButton";
@@ -33,19 +34,10 @@
const { t, tStr } = useTFns("myWallets.");
const promptAuth = useAuth();
- const showWalletDeleteConfirm = useCallback((): void => {
- Modal.confirm({
- icon: ,
-
- title: tStr("actionsDeleteConfirm"),
- content: tStr("actionsDeleteConfirmDescription"),
-
- onOk: () => deleteWallet(wallet),
- okText: t("dialog.yes"),
- okType: "danger",
- cancelText: t("dialog.no")
- });
- }, [t, tStr, wallet]);
+ const showWalletDeleteConfirm = useCallback(
+ () => showWalletDeleteConfirmModal(t, tStr, wallet),
+ [t, tStr, wallet]
+ );
const memoDropdown = useMemo(() => ,
+
+ title: tStr("actionsDeleteConfirm"),
+ content: tStr("actionsDeleteConfirmDescription"),
+
+ onOk: () => deleteWallet(wallet),
+ okText: t("dialog.yes"),
+ okType: "danger",
+ cancelText: t("dialog.no")
+ });
+}
diff --git a/src/pages/wallets/WalletMobileItem.tsx b/src/pages/wallets/WalletMobileItem.tsx
new file mode 100644
index 0000000..f3174b5
--- /dev/null
+++ b/src/pages/wallets/WalletMobileItem.tsx
@@ -0,0 +1,170 @@
+// 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 { useCallback, useMemo } from "react";
+import { Tag, Collapse, Menu } from "antd";
+import {
+ ProfileOutlined, SendOutlined, EditOutlined, InfoCircleOutlined,
+ DeleteOutlined
+} from "@ant-design/icons";
+
+import { Trans } from "react-i18next";
+import { useTFns } from "@utils/i18n";
+
+import { useHistory } from "react-router-dom";
+
+import { Wallet } from "@wallets";
+
+import { ContextualAddress } from "@comp/addresses/ContextualAddress";
+import { KristValue } from "@comp/krist/KristValue";
+import { DateTime } from "@comp/DateTime";
+
+import { useAuth } from "@comp/auth";
+import { OpenEditWalletFn } from "./WalletEditButton";
+import { OpenSendTxFn } from "@comp/transactions/SendTransactionModalLink";
+import { OpenWalletInfoFn } from "./info/WalletInfoModal";
+import { showWalletDeleteConfirmModal } from "./WalletActions";
+
+interface Props {
+ wallet: Wallet;
+
+ openEditWallet: OpenEditWalletFn;
+ openSendTx: OpenSendTxFn;
+ openWalletInfo: OpenWalletInfoFn;
+}
+
+export function WalletMobileItem({
+ wallet,
+ openEditWallet,
+ openSendTx,
+ openWalletInfo
+}: Props): JSX.Element {
+ const { t, tStr, tKey } = useTFns("myWallets.");
+
+ const itemHead = useMemo(() => (
+
+ {/* Wallet balance */}
+
+
+
+
+ {/* Label, if possible */}
+ {wallet.label &&
+ {wallet.label}
+ {/* Don't save tag */}
+ {wallet.dontSave && (
+
+ {tStr("tagDontSave")}
+
+ )}
+ }
+
+
+ {/* Address */}
+
+
+ {/* Category */}
+ {wallet.category && <>
+
+
+
+ {wallet.category}
+
+ >}
+
+ {/* Names */}
+ {(wallet.names || 0) > 0 && <>
+
+
+
+ {t(tKey("nameCount"), { count: wallet.names })}
+
+ >}
+
+
+ {/* First seen */}
+ {wallet.firstSeen &&
+
+ First seen
+
+
}
+
+ ), [
+ t, tKey, tStr,
+ wallet.address, wallet.label, wallet.category, wallet.dontSave,
+ wallet.firstSeen, wallet.balance, wallet.names
+ ]);
+
+ return
+
+
+
+ ;
+}
+
+function WalletMobileItemActions({
+ wallet,
+ openEditWallet,
+ openSendTx,
+ openWalletInfo
+}: Props): JSX.Element {
+ const { t, tStr } = useTFns("myWallets.");
+
+ const history = useHistory();
+ const promptAuth = useAuth();
+
+ const showWalletDeleteConfirm = useCallback(
+ () => showWalletDeleteConfirmModal(t, tStr, wallet),
+ [t, tStr, wallet]
+ );
+
+ const addressLink = `/network/addresses/${encodeURIComponent(wallet.address)}`;
+
+ return ;
+}
diff --git a/src/pages/wallets/WalletsPage.less b/src/pages/wallets/WalletsPage.less
new file mode 100644
index 0000000..015026a
--- /dev/null
+++ b/src/pages/wallets/WalletsPage.less
@@ -0,0 +1,63 @@
+// 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 (reference) "../../App.less";
+@import "../../style/table.less";
+
+.wallets-page .table-mobile-list-view {
+ .wallet-mobile-item {
+ background: transparent;
+ transition: background @animation-duration-base @ease-in-out;
+
+ // Move the padding from the mobile-item to the collapse header, to make the
+ // clickable area the full size
+ &.card-list-item { padding: 0; }
+ .ant-collapse-header {
+ padding: @padding-sm @padding-md;
+
+ // Darken the background when expanded
+ &[aria-expanded=true] {
+ background: @kw-darker;
+ }
+ }
+
+ .wallet-label {
+ display: block;
+ font-size: 120%;
+ }
+
+ .wallet-value {
+ float: right;
+ font-size: 120%;
+ }
+
+ .wallet-first-seen {
+ display: block;
+ font-size: @font-size-sm;
+ color: @text-color-secondary;
+ }
+
+ .wallet-names {
+ white-space: nowrap;
+ }
+
+ .sep:before {
+ content: "\2013";
+
+ display: inline-block;
+ margin: 0 @padding-xs;
+ color: @text-color-secondary;
+ }
+
+ .ant-collapse-content-box {
+ // Make the actions menu flush
+ padding: 0;
+
+ .ant-menu {
+ .ant-menu-item {
+ margin-bottom: 0;
+ }
+ }
+ }
+ }
+}
diff --git a/src/pages/wallets/WalletsPage.tsx b/src/pages/wallets/WalletsPage.tsx
index e211a43..b3d817c 100644
--- a/src/pages/wallets/WalletsPage.tsx
+++ b/src/pages/wallets/WalletsPage.tsx
@@ -14,6 +14,8 @@
import { useSendTransactionModal } from "@comp/transactions/SendTransactionModalLink";
import { useWalletInfoModal } from "./info/WalletInfoModal";
+import "./WalletsPage.less";
+
/** Extract the subtitle into its own component to avoid re-rendering the
* entire page when a wallet is added. */
function WalletsPageSubtitle(): JSX.Element {
@@ -39,6 +41,7 @@
siteTitleKey="myWallets.title" titleKey="myWallets.title"
subTitle={}
extra={extra}
+ className="wallets-page"
>
= useCallback(wallet => (
+
+ ), [openEditWallet, openSendTx, openWalletInfo]);
+
+ const { isMobile, list } = useSimpleMobileList(
+ false, walletValues, "id", "balance", true, renderMobileItem
+ );
+
+ return isMobile && list
+ ? list
+ : ;
+}
+
+interface DesktopViewProps extends Props {
+ wallets: Wallet[];
+}
+
+function DesktopView({
+ wallets,
+ openEditWallet,
+ openSendTx,
+ openWalletInfo
+}: DesktopViewProps): JSX.Element {
const { tStr } = useTFns("myWallets.");
- const { wallets } = useWallets();
const { categories, joinedCategoryList } = useWalletCategories();
const dateColumnWidth = useDateColumnWidth();
@@ -151,7 +190,7 @@
size="small"
scroll={{ x: true }}
- dataSource={Object.values(wallets)}
+ dataSource={wallets}
rowKey="id"
pagination={{
diff --git a/src/utils/table/mobileList.tsx b/src/utils/table/mobileList.tsx
index fd4c3ef..a14c8a0 100644
--- a/src/utils/table/mobileList.tsx
+++ b/src/utils/table/mobileList.tsx
@@ -8,6 +8,7 @@
import { PaginationChangeFn, LookupFilterOptionsBase } from "@utils/table/table";
import { useSortModal, SetOpenSortModalFn, SortOptions } from "./SortModal";
+import { keyedNullSort } from "@utils";
import { useBreakpoint } from "@utils/hooks";
interface MobileListHookRes {
@@ -39,9 +40,6 @@
const bps = useBreakpoint();
const isMobile = !bps.md;
- console.log(paginationConfig);
- console.log(options);
-
const sortModal = useSortModal(
sortOptions, defaultOrderBy, defaultOrder,
options, setOptions, setOpenSortModal
@@ -76,3 +74,46 @@
return { isMobile, list };
}
+
+/** Alternative for useMobileList that doesn't require the lookup API.
+ * Has limited functionality. */
+export function useSimpleMobileList(
+ loading: boolean,
+ values: T[],
+ rowKey: string,
+
+ sortBy: keyof T,
+ sortDesc: boolean,
+
+ renderItem: (item: T, index: number) => ReactNode
+): MobileListHookRes {
+ const bps = useBreakpoint();
+ const isMobile = !bps.md;
+
+ const sortFn = useMemo(() => keyedNullSort(sortBy, true), [sortBy]);
+
+ const sortedValues = useMemo(() => {
+ const sorted = values.sort((a, b) => sortFn(a, b, sortDesc ? "descend" : "ascend"));
+ if (sortDesc) sorted.reverse();
+ return sorted;
+ }, [values, sortFn, sortDesc]);
+
+ const list = useMemo(() => {
+ if (!isMobile) return null;
+
+ return
;
+ }, [isMobile, loading, sortedValues, rowKey, renderItem]);
+
+ return { isMobile, list };
+}