diff --git a/public/locales/en.json b/public/locales/en.json index bf133f9..a7e1db6 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -615,6 +615,7 @@ "noWalletsResult": { "title": "No wallets yet", "subTitle": "You currently have no wallets saved in KristWeb, so there is nothing to see here yet. Would you like to add a wallet?", + "subTitleSendTransaction": "You currently have no wallets saved in KristWeb, so you can't make a transaction yet. Would you like to add a wallet?", "button": "Add wallets", "buttonNetworkTransactions": "Network transactions", "buttonNetworkNames": "Network names" diff --git a/src/components/addresses/picker/AddressPicker.tsx b/src/components/addresses/picker/AddressPicker.tsx index faf8928..413c0aa 100644 --- a/src/components/addresses/picker/AddressPicker.tsx +++ b/src/components/addresses/picker/AddressPicker.tsx @@ -1,10 +1,11 @@ // 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 { useMemo } from "react"; +import { useMemo, Ref } from "react"; import classNames from "classnames"; import { AutoComplete, Form } from "antd"; import { Rule } from "antd/lib/form"; +import { RefSelectProps } from "antd/lib/select"; import { useTranslation } from "react-i18next"; @@ -33,6 +34,8 @@ nameHint?: boolean; className?: string; + tabIndex?: number; + inputRef?: Ref; } export function AddressPicker({ @@ -46,6 +49,8 @@ nameHint, className, + tabIndex, + inputRef, ...props }: Props): JSX.Element { const { t } = useTranslation(); @@ -168,6 +173,8 @@ {...props} > diff --git a/src/components/results/NoWalletsResult.tsx b/src/components/results/NoWalletsResult.tsx index 9cf6fa3..f8c5514 100644 --- a/src/components/results/NoWalletsResult.tsx +++ b/src/components/results/NoWalletsResult.tsx @@ -1,16 +1,17 @@ // 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 { Dispatch, SetStateAction} from "react"; import classNames from "classnames"; -import { Button } from "antd"; +import { Button, Modal } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; import { useTranslation } from "react-i18next"; -import { Link } from "react-router-dom"; +import { Link, useHistory } from "react-router-dom"; import { SmallResult } from "./SmallResult"; -export type ResultType = "transactions" | "names"; +export type ResultType = "transactions" | "names" | "sendTransaction"; interface Props { type?: ResultType; @@ -39,6 +40,15 @@ } } +function getSubTitleKey(type?: ResultType): string { + switch (type) { + case "sendTransaction": + return "noWalletsResult.subTitleSendTransaction"; + default: + return "noWalletsResult.subTitle"; + } +} + export function NoWalletsResult({ type, className }: Props): JSX.Element { const { t } = useTranslation(); @@ -51,7 +61,7 @@ icon={} title={t("noWalletsResult.title")} - subTitle={t("noWalletsResult.subTitle")} + subTitle={t(getSubTitleKey(type))} extra={<> {/* Other helpful buttons (e.g. 'Network transactions') */} {} @@ -65,3 +75,38 @@ fullPage />; } + +interface ModalProps extends Props { + visible?: boolean; + setVisible?: Dispatch>; +} + +export function NoWalletsModal({ + type, + className, + visible, + setVisible +}: ModalProps): JSX.Element { + const { t } = useTranslation(); + const history = useHistory(); + + const classes = classNames("kw-no-wallets-modal", className); + + return { + setVisible?.(false); + history.push("/wallets"); + }} + okText={t("noWalletsResult.button")} + + onCancel={() => setVisible?.(false)} + cancelText={t("dialog.cancel")} + > + {t(getSubTitleKey(type))} + ; +} diff --git a/src/pages/transactions/send/AmountInput.tsx b/src/pages/transactions/send/AmountInput.tsx index 53df896..9ad1f26 100644 --- a/src/pages/transactions/send/AmountInput.tsx +++ b/src/pages/transactions/send/AmountInput.tsx @@ -13,9 +13,15 @@ interface Props { from: string; setValue: (value: number) => void; + tabIndex?: number; } -export function AmountInput({ from, setValue, ...props }: Props): JSX.Element { +export function AmountInput({ + from, + setValue, + tabIndex, + ...props +}: Props): JSX.Element { const { t } = useTranslation(); // Used to populate 'Max' @@ -72,6 +78,7 @@ type="number" min={1} style={{ width: "100%", height: 32 }} + tabIndex={tabIndex} /> diff --git a/src/pages/transactions/send/SendTransactionForm.tsx b/src/pages/transactions/send/SendTransactionForm.tsx index ebc460d..48cc80a 100644 --- a/src/pages/transactions/send/SendTransactionForm.tsx +++ b/src/pages/transactions/send/SendTransactionForm.tsx @@ -1,12 +1,14 @@ // 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 { useState } from "react"; +import { useState, useRef } from "react"; import { Row, Col, Form, FormInstance, Input } from "antd"; +import { RefSelectProps } from "antd/lib/select"; import { useTranslation } from "react-i18next"; import { useWallets } from "@wallets"; +import { useMountEffect } from "@utils"; import { AddressPicker } from "@comp/addresses/picker/AddressPicker"; import { AmountInput } from "./AmountInput"; @@ -46,6 +48,12 @@ const [from, setFrom] = useState(initialFrom); const [to, setTo] = useState(""); + // Focus the 'to' input on initial render + const toRef = useRef(null); + useMountEffect(() => { + toRef?.current?.focus(); + }); + function onValuesChange(_: unknown, values: Partial) { setFrom(values.from || ""); setTo(values.to || ""); @@ -80,6 +88,7 @@ name="from" label={t("sendTransaction.labelFrom")} value={from} + tabIndex={1} /> @@ -90,6 +99,8 @@ label={t("sendTransaction.labelTo")} value={to} otherPickerValue={from === undefined ? initialFrom : from} + tabIndex={2} + inputRef={toRef} /> @@ -98,6 +109,7 @@ form.setFieldsValue({ value })} + tabIndex={3} /> {/* Metadata */} @@ -114,6 +126,7 @@ className="input-monospace" rows={3} placeholder={t("sendTransaction.placeholderMetadata")} + tabIndex={4} /> ; diff --git a/src/pages/transactions/send/SendTransactionModal.tsx b/src/pages/transactions/send/SendTransactionModal.tsx index 4b29b05..6faca36 100644 --- a/src/pages/transactions/send/SendTransactionModal.tsx +++ b/src/pages/transactions/send/SendTransactionModal.tsx @@ -6,6 +6,9 @@ import { useTranslation } from "react-i18next"; +import { useWallets } from "@wallets"; +import { NoWalletsModal } from "@comp/results/NoWalletsResult"; + import { useTransactionForm } from "./SendTransactionForm"; interface Props { @@ -19,24 +22,38 @@ const { t } = useTranslation(); const { form, isSubmitting, triggerSubmit, txForm } = useTransactionForm(); + // Don't open the modal if there are no wallets. + const { addressList } = useWallets(); + const hasWallets = addressList?.length > 0; + function closeModal() { form.resetFields(); setVisible(false); } - return - {txForm} - ; + onCancel={closeModal} + cancelText={t("dialog.cancel")} + destroyOnClose + > + {txForm} + + ) + : ( + + ); }