From c5afd9f895630a53474a504a815b1d5aa75763ec Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 27 Aug 2024 15:45:33 -0700 Subject: [PATCH] Allow selection of the account. This was surprisingly complex because I tried to use react-router and found no easily and reasonable way to do it, so instead I hooked the location change event myself and plumbed it through. This required me to switch to a class-style component rather than a functional one, which required translating a bunch of coding styles through React. Overall I'm happy, it works pretty well and simply. --- src/AccountList.tsx | 7 +- src/App.tsx | 153 +++++++++++++++++++++++++++++--------------- 2 files changed, 105 insertions(+), 55 deletions(-) diff --git a/src/AccountList.tsx b/src/AccountList.tsx index 25d6b53..50d045b 100644 --- a/src/AccountList.tsx +++ b/src/AccountList.tsx @@ -5,19 +5,20 @@ import { IAccount } from "jmap-client-ts/lib/types"; type AccountIdMap = { [accountId: string]: IAccount }; type AccountListProps = { + account: IAccount | null; accounts: AccountIdMap; }; -const AccountList: React.FC = ({ accounts }) => { +const AccountList: React.FC = ({ account, accounts }) => { return ( - Dropdown Button + {account ? account.name : "select account"} {Object.keys(accounts).map((key: keyof AccountIdMap) => ( - {accounts[key].name} + {accounts[key].name} ))} diff --git a/src/App.tsx b/src/App.tsx index 1ebf0de..5a073d4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,98 +2,147 @@ import "./App.css"; import "bootstrap/dist/css/bootstrap.min.css"; import * as base64 from "base-64"; import { Client } from "jmap-client-ts"; -import { ISession } from "jmap-client-ts/lib/types"; +import { IAccount, ISession } from "jmap-client-ts/lib/types"; import { FetchTransport } from "jmap-client-ts/lib/utils/fetch-transport"; import AccountList from "./AccountList"; import AuthModal from "./AuthModal"; -import React, { useEffect, useState } from "react"; +import React from "react"; interface IAuth { email: string; password: string; } -interface IAppState { - auth: IAuth; - client: Client | null; - session: ISession | null; +interface ILocation { + accountId: string; } -const App = () => { - const [state, setInternalState] = useState({ - auth: { email: "", password: "" }, - client: null, - session: null, - }); +type AppState = { + auth: IAuth; + location: ILocation; + session: ISession | null; +}; - // When the user provides credentials - const onLogin = (email: string, password: string) => { - // Store the provided credentials for now - state.auth.email = email; - state.auth.password = password; - state.client = null; - setInternalState(state); - localStorage.setItem("auth", JSON.stringify(state.auth)); - doLogin(state.auth); +type AppProps = {}; + +class App extends React.Component { + account(): IAccount | null { + if (!(this.state.session && this.state.session.accounts)) return null; + return this.state.session.accounts[this.state.location.accountId]; + } + client: Client | null = null; + state: AppState = { + auth: { email: "", password: "" }, + location: { accountId: "" }, + session: null, }; // Make the request to get system metadata - const doLogin = (auth: IAuth) => { + doLogin(auth: IAuth) { const domain = auth.email.split("@")[1]; const well_known_url = "https://" + domain + "/.well-known/jmap"; const basic_auth = "Basic " + base64.encode(auth.email + ":" + auth.password); - state.client = new Client({ + this.client = new Client({ accessToken: "fake token", httpHeaders: { Authorization: basic_auth }, sessionUrl: well_known_url, transport: new FetchTransport(fetch.bind(window)), }); - state.client + this.client .fetchSession() .then(() => { - console.log("Session recieved"); - if (state.client) { - state.session = state.client.getSession(); - setInternalState({ - ...state, - session: state.client.getSession(), - }); - } + console.log("Session received"); + + // For the type checker + if (!this.client) return; + + const session = this.client.getSession(); + this.setState({ + ...this.state, + session: session, + }); }) .catch((error) => console.error(error)); return; - }; + } - const loadAuth = () => { + onHashChange() { + console.log(window.location.hash); + const hash = window.location.hash.substring(1); + this.setState({ + ...this.state, + location: { accountId: hash }, + }); + } + // When the user provides credentials + onLogin(email: string, password: string) { + // Store the provided credentials for now + this.setState({ + ...this.state, + auth: { + email: email, + password: password, + }, + }); + localStorage.setItem("auth", JSON.stringify(this.state.auth)); + this.doLogin({ email, password }); + } + + loadAuth() { const data = localStorage.getItem("auth"); if (!data) return; const auth = JSON.parse(data); - state.auth = auth; - if (state.client == null) { - console.log("NULL STATE.client"); - doLogin(state.auth); + this.setState({ + ...this.state, + auth: auth, + }); + if (this.client == null) { + this.doLogin(auth); return; } - }; + } - useEffect(() => { - loadAuth(); - }, []); + componentDidMount() { + window.addEventListener( + "hashchange", + () => { + this.onHashChange(); + }, + false, + ); + this.loadAuth(); + this.onHashChange(); + } - return ( -
- {state && state.auth ? ( - - ) : ( - - )} -
- ); -}; + componentWillUnmount() { + window.removeEventListener( + "hashchange", + () => { + this.onHashChange(); + }, + false, + ); + } + + render() { + return ( +
+ {this.state && this.state.auth ? ( + + ) : ( + + )} +
+ ); + } +} export default App;