diff --git a/package.json b/package.json index e626a0a..644d8ae 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "bootstrap": "^4.5.2", "debug": "^4.1.1", "formik": "^2.1.5", - "prop-types": "^15.7.2", "react": "^16.13.1", "react-bootstrap": "^1.3.0", "react-dom": "^16.13.1", diff --git a/src/app/App.tsx b/src/app/App.tsx index dc42f6f..d87434c 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,9 +1,11 @@ -import React from "react"; +import React, { Component } from "react"; import "./App.scss"; import { MainLayout } from "../layouts/main"; import { WalletManager } from "./WalletManager"; +import { WalletManagerView } from "./WalletManagerView"; + import { kristService } from "@krist/KristConnectionService"; import packageJson from "@/package.json"; @@ -11,9 +13,27 @@ kristService().connect(packageJson.defaultSyncNode) // TODO .catch(console.error); -export const App = (): JSX.Element => ( - <> - - - -); +type AppState = { + walletManager: WalletManager +} + +export class App extends Component { + constructor(props: unknown) { + super(props); + + this.state = { + walletManager: new WalletManager((walletManager: WalletManager) => { + this.setState({ walletManager }); + }) + }; + } + + render(): JSX.Element { + const { walletManager } = this.state; + + return <> + + + ; + } +} diff --git a/src/app/MasterPasswordDialog.tsx b/src/app/MasterPasswordDialog.tsx index 5bb6030..53cf7e6 100644 --- a/src/app/MasterPasswordDialog.tsx +++ b/src/app/MasterPasswordDialog.tsx @@ -36,6 +36,10 @@ } } + browseAsGuest(): void { + this.props.walletManager.browseAsGuest(); + } + render(): JSX.Element { const { hasMasterPassword } = this.props; const body = hasMasterPassword @@ -54,7 +58,10 @@ /* TODO: Animation is disabled for now, because react-bootstrap (or more specifically, react-transition-group) has an incompatibility with strict mode. */ - + {({ handleSubmit, handleChange, values, errors, isSubmitting }) => (
@@ -83,8 +90,15 @@ {errors.password} - {/* Left side */} - + {/* Left side, "Browse as guest" button */} + {/* Right side */} {hasMasterPassword diff --git a/src/app/WalletManager.tsx b/src/app/WalletManager.tsx index 5ac2b2c..5d61854 100644 --- a/src/app/WalletManager.tsx +++ b/src/app/WalletManager.tsx @@ -1,20 +1,16 @@ -import React, { Component } from "react"; - import { toHex } from "@utils"; import { aesGcmEncrypt, aesGcmDecrypt } from "@utils/crypto"; -import { MasterPasswordDialog } from "./MasterPasswordDialog"; - import Debug from "debug"; const debug = Debug("kristweb:walletManager"); -type WalletManagerData = { +export class WalletManager { /** Whether or not the user has logged in, either as a guest, or with a * master password. */ - isLoggedIn: boolean; + isLoggedIn = false; /** Whether or not the user is browsing KristWeb as a guest. */ - isGuest: boolean; + isGuest = true; /** The master password used to encrypt and decrypt local storage data. */ masterPassword?: string; @@ -28,28 +24,20 @@ /** Whether or not the user has configured and saved a master password * before (whether or not salt+tester are present in local storage). */ - hasMasterPassword: boolean; -} + hasMasterPassword = false; -export class WalletManager extends Component { - constructor(props: unknown) { - super(props); + constructor(private stateChangeListener: (walletManager: WalletManager) => void) { + this.isLoggedIn = false; + this.isGuest = true; - // Check current data stored in local storage. - const salt = localStorage.getItem("salt") || undefined; - const tester = localStorage.getItem("tester") || undefined; + // Salt and tester from local storage (or undefined) + this.salt = localStorage.getItem("salt") || undefined; + this.tester = localStorage.getItem("tester") || undefined; - this.state = { - isLoggedIn: false, - isGuest: true, + // There is a master password configured if both `salt` and `tester` exist + this.hasMasterPassword = !!this.salt && !!this.tester; - // Salt and tester from local storage (or undefined) - salt, tester, - // There is a master password configured if both `salt` and `tester` exist - hasMasterPassword: !!salt && !!tester - }; - - debug("hasMasterPassword: %b", this.state.hasMasterPassword); + debug("hasMasterPassword: %b", this.hasMasterPassword); } async setMasterPassword(password: string): Promise { @@ -69,18 +57,19 @@ localStorage.setItem("tester", tester); // Set the logged in state - this.setState({ - isLoggedIn: true, - isGuest: false, - masterPassword: password - }); + this.isLoggedIn = true; + this.isGuest = false; + this.masterPassword = password; + + // Delegate to the App's listener + this.stateChangeListener(this); } async testMasterPassword(password: string): Promise { if (!password) throw new Error("Password is required."); // Get the salt and tester from local storage and ensure they exist - const { salt, tester } = this.state; + const { salt, tester } = this; if (!salt || !tester) throw new Error("Master password has not been set up."); try { @@ -97,23 +86,20 @@ } // Set the logged in state and don't return any errors (login successful) - this.setState({ - isLoggedIn: true, - isGuest: false, - masterPassword: password - }); + this.isLoggedIn = true; + this.isGuest = false; + this.masterPassword = password; + + // Delegate to the App's listener + this.stateChangeListener(this); } - /** Render the master password login/setup dialog */ - render(): JSX.Element | null { - const { isLoggedIn, hasMasterPassword } = this.state; - if (isLoggedIn) return null; // Don't show the dialog again + browseAsGuest(): void { + // Set the logged in state as a guest + this.isLoggedIn = true; + this.isGuest = true; - return ( - - ); + // Delegate to the App's listener + this.stateChangeListener(this); } -} +}; diff --git a/src/app/WalletManagerView.tsx b/src/app/WalletManagerView.tsx new file mode 100644 index 0000000..4ffcef3 --- /dev/null +++ b/src/app/WalletManagerView.tsx @@ -0,0 +1,24 @@ +import React, { Component } from "react"; + +import { MasterPasswordDialog } from "./MasterPasswordDialog"; +import { WalletManager } from "./WalletManager"; + +interface Props { + walletManager: WalletManager; +} + +export class WalletManagerView extends Component { + /** Render the master password login/setup dialog */ + render(): JSX.Element | null { + const { walletManager } = this.props; + const { isLoggedIn, hasMasterPassword } = walletManager; + if (isLoggedIn) return null; // Don't show the dialog again + + return ( + + ); + } +} diff --git a/src/layouts/main/components/sidebar/SidebarItem.tsx b/src/layouts/main/components/sidebar/SidebarItem.tsx index cd03bcb..d5562ef 100644 --- a/src/layouts/main/components/sidebar/SidebarItem.tsx +++ b/src/layouts/main/components/sidebar/SidebarItem.tsx @@ -1,34 +1,23 @@ -import React, { Component, ReactNode } from "react"; -import PropTypes from "prop-types"; +import React from "react"; import "./SidebarItem.scss"; import Nav from "react-bootstrap/Nav"; import { LinkContainer } from "react-router-bootstrap"; -type SidebarItemProps = { +type Props = { url: string, text: string, icon: string }; -export class SidebarItem extends Component { - public static propTypes = { - url: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired - } - - render(): ReactNode { - const { url, text, icon } = this.props; - - return - - - - {text} - - - ; - } -} +export const SidebarItem: React.FC = ({ url, text, icon }: Props) => ( + + + + + {text} + + + +); diff --git a/src/layouts/main/components/sidebar/TotalBalance.scss b/src/layouts/main/components/sidebar/TotalBalance.scss index 53d14c5..c587897 100644 --- a/src/layouts/main/components/sidebar/TotalBalance.scss +++ b/src/layouts/main/components/sidebar/TotalBalance.scss @@ -1,6 +1,6 @@ @import "~scss/variables"; -#nav-total-balance { +.nav-total-balance { background-color: $gray-200; padding: 0.5rem 1rem; @@ -19,4 +19,4 @@ .krist-value { font-size: 120%; } -} \ No newline at end of file +} diff --git a/src/layouts/main/components/sidebar/TotalBalance.tsx b/src/layouts/main/components/sidebar/TotalBalance.tsx index 83e4b42..5600c9c 100644 --- a/src/layouts/main/components/sidebar/TotalBalance.tsx +++ b/src/layouts/main/components/sidebar/TotalBalance.tsx @@ -1,25 +1,15 @@ -import React, { Component, ReactNode } from "react"; -import PropTypes from "prop-types"; - +import React from "react"; import { KristValue } from "@components/krist-value"; import "./TotalBalance.scss"; -type TotalBalanceProps = { +type Props = { balance: number }; -export class TotalBalance extends Component { - public static propTypes = { - balance: PropTypes.number.isRequired - } - - render(): ReactNode { - const { balance } = this.props; - - return ; - } -} +export const TotalBalance: React.FC = ({ balance }: Props) => ( +
+
Total Balance
+ +
+); diff --git a/src/layouts/main/components/sidebar/index.tsx b/src/layouts/main/components/sidebar/index.tsx index c776f6a..ab03eac 100644 --- a/src/layouts/main/components/sidebar/index.tsx +++ b/src/layouts/main/components/sidebar/index.tsx @@ -1,15 +1,22 @@ import React from "react"; -import "./index.scss"; - import Nav from "react-bootstrap/Nav"; + +import { WalletManager } from "@app/WalletManager"; + import { TotalBalance } from "./TotalBalance"; import { SidebarItem } from "./SidebarItem"; import { Footer } from "./Footer"; -export const MainSidebar = (): JSX.Element => ( +import "./index.scss"; + +interface Props { + walletManager: WalletManager; +} + +export const MainSidebar: React.FC = (props: Props): JSX.Element => (