diff --git a/.vscode/settings.json b/.vscode/settings.json
index 30fbe00..95c0d10 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -31,7 +31,8 @@
"singleline",
"textbox",
"tsdoc",
+ "typeahead",
"typesafe",
"unstyled"
],
-}
\ No newline at end of file
+}
diff --git a/package-lock.json b/package-lock.json
index 64444cc..86ecd9e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1765,6 +1765,15 @@
"csstype": "^3.0.2"
}
},
+ "@types/react-bootstrap-typeahead": {
+ "version": "3.4.6",
+ "resolved": "https://registry.npmjs.org/@types/react-bootstrap-typeahead/-/react-bootstrap-typeahead-3.4.6.tgz",
+ "integrity": "sha512-3vrWrPNb2G+ieyWB1VtEPZH6E4NBcd2cVsacb6oUhBNU/D/ZYnUqnbmeTIKzIVXqEkdPtgARG45W1x7tUV9DGA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
"@types/react-dom": {
"version": "16.9.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
@@ -3832,6 +3841,11 @@
}
}
},
+ "compute-scroll-into-view": {
+ "version": "1.0.16",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.16.tgz",
+ "integrity": "sha512-a85LHKY81oQnikatZYA90pufpZ6sQx++BoCxOEMsjpZx+ZnaKGQnCyCehTRr/1p9GBIAHTjcU9k71kSYWloLiQ=="
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -4041,6 +4055,15 @@
"sha.js": "^2.4.8"
}
},
+ "create-react-context": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz",
+ "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==",
+ "requires": {
+ "gud": "^1.0.0",
+ "warning": "^4.0.3"
+ }
+ },
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -6492,6 +6515,11 @@
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
"integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE="
},
+ "gud": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz",
+ "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw=="
+ },
"gzip-size": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz",
@@ -8348,6 +8376,11 @@
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
"integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
},
+ "lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
+ },
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -9924,6 +9957,11 @@
"ts-pnp": "^1.1.6"
}
},
+ "popper.js": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
+ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
+ },
"portfinder": {
"version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -11203,6 +11241,40 @@
"warning": "^4.0.3"
}
},
+ "react-bootstrap-typeahead": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/react-bootstrap-typeahead/-/react-bootstrap-typeahead-5.1.1.tgz",
+ "integrity": "sha512-fjbeF9xenZ7AqH+yQr9ad7PvntA4SSmHMDD56F25B/EmN0NAJsqobXMW8/Y2jllMpcziGG2H4A7GyAdQfyVRFA==",
+ "requires": {
+ "@babel/runtime": "^7.3.4",
+ "@restart/hooks": "^0.3.22",
+ "classnames": "^2.2.0",
+ "fast-deep-equal": "^3.1.1",
+ "invariant": "^2.2.1",
+ "lodash.debounce": "^4.0.8",
+ "prop-types": "^15.5.8",
+ "react-overlays": "^2.0.0",
+ "react-popper": "^1.0.0",
+ "scroll-into-view-if-needed": "^2.2.20",
+ "warning": "^4.0.1"
+ },
+ "dependencies": {
+ "react-overlays": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-2.1.1.tgz",
+ "integrity": "sha512-gaQJwmb8Ij2IGVt4D1HmLtl4A0mDVYxlsv/8i0dHWK7Mw0kNat6ORelbbEWzaXTK1TqMeQtJw/jraL3WOADz3w==",
+ "requires": {
+ "@babel/runtime": "^7.4.5",
+ "@restart/hooks": "^0.3.12",
+ "dom-helpers": "^5.1.0",
+ "popper.js": "^1.15.0",
+ "prop-types": "^15.7.2",
+ "uncontrollable": "^7.0.0",
+ "warning": "^4.0.3"
+ }
+ }
+ }
+ },
"react-dev-utils": {
"version": "10.2.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz",
@@ -11459,6 +11531,20 @@
"warning": "^4.0.3"
}
},
+ "react-popper": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
+ "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
+ "requires": {
+ "@babel/runtime": "^7.1.2",
+ "create-react-context": "^0.3.0",
+ "deep-equal": "^1.1.1",
+ "popper.js": "^1.14.4",
+ "prop-types": "^15.6.1",
+ "typed-styles": "^0.0.7",
+ "warning": "^4.0.2"
+ }
+ },
"react-redux": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz",
@@ -12304,6 +12390,14 @@
"ajv-keywords": "^3.4.1"
}
},
+ "scroll-into-view-if-needed": {
+ "version": "2.2.26",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.26.tgz",
+ "integrity": "sha512-SQ6AOKfABaSchokAmmaxVnL9IArxEnLEX9j4wAZw+x4iUTb40q7irtHG3z4GtAWz5veVZcCnubXDBRyLVQaohw==",
+ "requires": {
+ "compute-scroll-into-view": "^1.0.16"
+ }
+ },
"scss-tokenizer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
@@ -13706,6 +13800,11 @@
"mime-types": "~2.1.24"
}
},
+ "typed-styles": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz",
+ "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q=="
+ },
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
diff --git a/package.json b/package.json
index 1eeee27..dad5120 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"i18next-http-backend": "^1.0.20",
"react": "^16.13.1",
"react-bootstrap": "^1.3.0",
+ "react-bootstrap-typeahead": "^5.1.1",
"react-dom": "^16.13.1",
"react-i18next": "^11.7.2",
"react-redux": "^7.2.1",
@@ -67,6 +68,7 @@
"@types/node": "^12.12.55",
"@types/prop-types": "^15.7.3",
"@types/react": "^16.9.49",
+ "@types/react-bootstrap-typeahead": "^3.4.6",
"@types/react-dom": "^16.9.8",
"@types/react-redux": "^7.1.9",
"@types/react-router-bootstrap": "^0.24.5",
diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
index f56ae50..a56e707 100644
--- a/public/locales/en/translation.json
+++ b/public/locales/en/translation.json
@@ -94,7 +94,11 @@
"addWallet": {
"dialogTitle": "Add wallet",
- "dialogTitleCreate": "Create wallet"
+ "dialogTitleCreate": "Create wallet",
+ "walletLabel": "Wallet label",
+ "walletLabelPlaceholder": "Wallet label (optional)",
+ "walletCategory": "Wallet category",
+ "walletCategoryDropdownNone": "No category"
},
"credits": {
diff --git a/src/index.scss b/src/index.scss
index be2feef..ef9333b 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -2,4 +2,5 @@
@import "~scss/font";
@import "~scss/variables";
@import "~bootstrap/scss/bootstrap";
+@import "~react-bootstrap-typeahead/css/Typeahead";
@import "~scss/modal";
diff --git a/src/layouts/dialogs/AddWalletDialog.tsx b/src/layouts/dialogs/AddWalletDialog.tsx
index aec3480..348d588 100644
--- a/src/layouts/dialogs/AddWalletDialog.tsx
+++ b/src/layouts/dialogs/AddWalletDialog.tsx
@@ -1,11 +1,18 @@
-import React from "react";
+import React, { Component } from "react";
-import { useTranslation } from "react-i18next";
+import { withTranslation, WithTranslation } from "react-i18next";
import { IconButton } from "@components/icon-button/IconButton";
+import Form from "react-bootstrap/Form";
+import { FormikHelpers } from "formik";
+import { Highlighter, Typeahead } from "react-bootstrap-typeahead";
import { ModalDialog, withDialogLink } from "./ModalDialog";
+import { WalletFormatName } from "@krist/wallets/formats/WalletFormat";
+
+import { noop } from "@utils";
+
export const AddWalletButton = withDialogLink(
(show, handleClose) =>
@@ -16,7 +23,7 @@
)(IconButton);
-interface Props {
+interface OwnProps {
show: boolean;
handleClose: () => void;
@@ -24,15 +31,75 @@
* dialog. */
create?: boolean;
}
+type Props = OwnProps & WithTranslation;
-export const AddWalletDialog: React.FC = ({ show, handleClose, create }: Props) => {
- const { t } = useTranslation();
+interface FormValues {
+ label?: string;
+ category: string;
+ password: string;
+ format: WalletFormatName;
+}
- return
- Placeholder
- ;
+class AddWalletDialogComponent extends Component {
+ async onSubmit(values: FormValues, helpers: FormikHelpers): Promise {
+ }
+
+ render() {
+ const { show, handleClose, create, t } = this.props;
+
+ return
+ show={show}
+ handleClose={handleClose}
+ hasCloseButton={true}
+ hasFooterCloseButton={true}
+ title={t(create ? "addWallet.dialogTitleCreate" : "addWallet.dialogTitle")}
+ initialValues={{
+ label: "",
+ category: "",
+ password: "",
+ format: "kristwallet"
+ }}
+ onSubmit={this.onSubmit.bind(this)}
+ >
+ {({ handleChange, values, errors, setFieldValue }) => <>
+ {/* Wallet label */}
+
+ {t("addWallet.walletLabel")}
+
+
+ {errors.label}
+
+ {/* Category dropdown */}
+
+ {t("addWallet.walletCategory")}
+ setFieldValue("category", selected[0] || "")}
+ onInputChange={text => setFieldValue("category", text)}
+ isInvalid={!!errors.category}
+ renderMenuItemChildren={(option, { text }) =>
+ option === "" // Show the "No category" text
+ ? {t("addWallet.walletCategoryDropdownNone")}
+ : {option}
+ }
+ />
+
+ {errors.category}
+ >}
+ ;
+ }
};
+
+export const AddWalletDialog = withTranslation()(AddWalletDialogComponent);
diff --git a/src/layouts/dialogs/ModalDialog.tsx b/src/layouts/dialogs/ModalDialog.tsx
index f7704f0..6e7e1da 100644
--- a/src/layouts/dialogs/ModalDialog.tsx
+++ b/src/layouts/dialogs/ModalDialog.tsx
@@ -34,7 +34,10 @@
} = this.props;
if ((hasCloseButton || hasFooterCloseButton) && !handleClose)
- throw new Error("FormDialog has close button but no close handler");
+ throw new Error("ModalDialog has close button but no close handler");
+
+ if (initialValues && !onSubmit)
+ throw new Error("ModalDialog has initialValues but no onSubmit");
// The contents of the modal (header, body, footer), which may or may not
// be wrapped in a form.
diff --git a/src/shared-components/list-view/ListMobile.tsx b/src/shared-components/list-view/ListMobile.tsx
index 23329ab..18ec4be 100644
--- a/src/shared-components/list-view/ListMobile.tsx
+++ b/src/shared-components/list-view/ListMobile.tsx
@@ -1,6 +1,6 @@
import React, { Component, ReactNode } from "react";
-import { ListGroup } from "react-bootstrap";
+import ListGroup from "react-bootstrap/ListGroup";
import { QueryStateBase, DataStateBase } from "./DataProvider";