Add switch account UI from login and update accounts functionality

Signed-off-by: Marquis Kurt <software@marquiskurt.net>
This commit is contained in:
Marquis Kurt 2019-10-03 11:16:21 -04:00
parent 3229d0a337
commit e75a964a39
No known key found for this signature in database
GPG Key ID: 725636D259F5402D
5 changed files with 226 additions and 59 deletions

View File

@ -3,7 +3,7 @@ import { MuiThemeProvider, CssBaseline, withStyles } from "@material-ui/core";
import { setHyperspaceTheme, darkMode } from "./utilities/themes";
import AppLayout from "./components/AppLayout";
import { styles } from "./App.styles";
import { Route } from "react-router-dom";
import { Route, withRouter } from "react-router-dom";
import AboutPage from "./pages/About";
import Settings from "./pages/Settings";
import { getUserDefaultBool, getUserDefaultTheme } from "./utilities/settings";
@ -27,14 +27,22 @@ import { userLoggedIn } from "./utilities/accounts";
import { isDarwinApp } from "./utilities/desktop";
let theme = setHyperspaceTheme(getUserDefaultTheme());
class App extends Component<any, any> {
interface IAppState {
theme: any;
showLayout: boolean;
}
class App extends Component<any, IAppState> {
offline: any;
unlisten: any;
constructor(props: any) {
super(props);
this.state = {
theme: theme
theme: theme,
showLayout:
userLoggedIn() && !window.location.hash.includes("#/welcome")
};
}
@ -43,17 +51,34 @@ class App extends Component<any, any> {
this.state.theme,
getUserDefaultBool("darkModeEnabled")
);
this.setState({ theme: newTheme });
this.setState({
theme: newTheme,
showLayout:
userLoggedIn() && !window.location.hash.includes("#/welcome")
});
}
componentDidMount() {
this.removeBodyBackground();
this.unlisten = this.props.history.listen(
(location: Location, action: any) => {
console.log(location.pathname);
this.setState({
showLayout:
userLoggedIn() &&
!location.pathname.includes("/welcome")
});
}
);
}
componentDidUpdate() {
this.removeBodyBackground();
}
componentWillUnmount() {
this.unlisten();
}
removeBodyBackground() {
if (isDarwinApp()) {
@ -71,7 +96,7 @@ class App extends Component<any, any> {
<CssBaseline />
<Route path="/welcome" component={WelcomePage} />
<div>
{userLoggedIn() ? <AppLayout /> : null}
{this.state.showLayout ? <AppLayout /> : null}
<PrivateRoute exact path="/" component={HomePage} />
<PrivateRoute path="/home" component={HomePage} />
<PrivateRoute path="/local" component={LocalPage} />
@ -91,7 +116,7 @@ class App extends Component<any, any> {
/>
<PrivateRoute path="/search" component={SearchPage} />
<PrivateRoute path="/settings" component={Settings} />
<PrivateRoute path="/blocked" component={Blocked}/>
<PrivateRoute path="/blocked" component={Blocked} />
<PrivateRoute path="/you" component={You} />
<PrivateRoute path="/about" component={AboutPage} />
<PrivateRoute path="/compose" component={Composer} />
@ -105,4 +130,5 @@ class App extends Component<any, any> {
}
}
export default withStyles(styles)(withSnackbar(App));
// @ts-ignore
export default withStyles(styles)(withSnackbar(withRouter(App)));

View File

@ -39,7 +39,7 @@ import GroupIcon from "@material-ui/icons/Group";
import SettingsIcon from "@material-ui/icons/Settings";
import InfoIcon from "@material-ui/icons/Info";
import CreateIcon from "@material-ui/icons/Create";
//import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle';
import SupervisedUserCircleIcon from "@material-ui/icons/SupervisedUserCircle";
import ExitToAppIcon from "@material-ui/icons/ExitToApp";
import { styles } from "./AppLayout.styles";
import { UAccount } from "../../types/Account";
@ -55,6 +55,7 @@ import { withSnackbar } from "notistack";
import { getConfig, getUserDefaultBool } from "../../utilities/settings";
import { isDesktopApp, isDarwinApp } from "../../utilities/desktop";
import { Config } from "../../types/Config";
import { getAccountRegistry } from "../../utilities/accounts";
interface IAppLayoutState {
acctMenuOpen: boolean;
@ -92,23 +93,7 @@ export class AppLayout extends Component<any, IAppLayoutState> {
}
componentDidMount() {
let acct = localStorage.getItem("account");
if (acct) {
this.setState({ currentUser: JSON.parse(acct) });
} else {
this.client
.get("/accounts/verify_credentials")
.then((resp: any) => {
let data: UAccount = resp.data;
this.setState({ currentUser: data });
})
.catch((err: Error) => {
this.props.enqueueSnackbar(
"Couldn't find profile info: " + err.name
);
console.error(err.message);
});
}
this.getAccountData();
getConfig().then((result: any) => {
if (result !== undefined) {
@ -126,6 +111,23 @@ export class AppLayout extends Component<any, IAppLayoutState> {
this.streamNotifications();
}
private getAccountData() {
this.client
.get("/accounts/verify_credentials")
.then((resp: any) => {
let data: UAccount = resp.data;
this.setState({ currentUser: data });
})
.catch((err: Error) => {
this.props.enqueueSnackbar(
"Couldn't find profile info: " + err.name
);
console.error(err.message);
let acct = localStorage.getItem("account") as string;
this.setState({ currentUser: JSON.parse(acct) });
});
}
streamNotifications() {
this.streamListener = this.client.stream("/streaming/user");
@ -304,10 +306,22 @@ export class AppLayout extends Component<any, IAppLayoutState> {
}
/>
</LinkableListItem>
{/* <LinkableListItem button key="acctSwitch-module" to="/switchacct">
<ListItemIcon><SupervisedUserCircleIcon/></ListItemIcon>
<ListItemText primary="Switch account"/>
</LinkableListItem> */}
<LinkableListItem
button
key="acctSwitch-module"
to="/welcome"
>
<ListItemIcon>
<SupervisedUserCircleIcon />
</ListItemIcon>
<ListItemText
primary={
getAccountRegistry().length > 1
? "Switch account"
: "Add account"
}
/>
</LinkableListItem>
<ListItem
button
key="acctLogout-mobile"
@ -568,7 +582,14 @@ export class AppLayout extends Component<any, IAppLayoutState> {
Edit profile
</ListItemText>
</LinkableListItem>
{/* <MenuItem>Switch account</MenuItem> */}
<LinkableListItem to={"/welcome"}>
<ListItemText>
{getAccountRegistry()
.length > 1
? "Switch account"
: "Add account"}
</ListItemText>
</LinkableListItem>
<MenuItem
onClick={() =>
this.toggleLogOutDialog()

View File

@ -84,6 +84,10 @@ class Composer extends Component<any, IComposerState> {
componentDidMount() {
let state = this.getComposerParams(this.props);
let text = state.acct ? `@${state.acct}: ` : "";
this.client.get("/accounts/verify_credentials").then((resp: any) => {
let account: UAccount = resp.data;
this.setState({ account });
});
getConfig().then((config: any) => {
this.setState({
federated: config.federation.allowPublicPosts,
@ -439,7 +443,7 @@ class Composer extends Component<any, IComposerState> {
event.target.value
)
}
></TextField>
/>
</Fade>
) : null}
{this.state.visibility === "direct" ? (

View File

@ -12,7 +12,13 @@ import {
Dialog,
DialogTitle,
DialogActions,
DialogContent
DialogContent,
List,
ListItem,
ListItemText,
ListItemAvatar,
ListItemSecondaryAction,
IconButton
} from "@material-ui/core";
import { styles } from "./WelcomePage.styles";
import Mastodon from "megalodon";
@ -28,6 +34,16 @@ import { isDarwinApp } from "../utilities/desktop";
import axios from "axios";
import { withSnackbar, withSnackbarProps } from "notistack";
import { Config } from "../types/Config";
import {
addAccountToRegistry,
getAccountRegistry,
loginWithAccount,
removeAccountFromRegistry
} from "../utilities/accounts";
import { Account, MultiAccount } from "../types/Account";
import AccountCircleIcon from "@material-ui/icons/AccountCircle";
import CloseIcon from "@material-ui/icons/Close";
interface IWelcomeProps extends withSnackbarProps {
classes: any;
@ -39,7 +55,7 @@ interface IWelcomeState {
brandName?: string;
registerBase?: string;
federates?: boolean;
wantsToLogin: boolean;
proceedToGetCode: boolean;
user: string;
userInputError: boolean;
userInputErrorMessage: string;
@ -47,7 +63,7 @@ interface IWelcomeState {
clientSecret?: string;
authUrl?: string;
foundSavedLogin: boolean;
authority: boolean;
authorizing: boolean;
license?: string;
repo?: string;
defaultRedirectAddress: string;
@ -55,6 +71,7 @@ interface IWelcomeState {
authCode: string;
emergencyMode: boolean;
version: string;
willAddAccount: boolean;
}
class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
@ -64,17 +81,18 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
super(props);
this.state = {
wantsToLogin: false,
proceedToGetCode: false,
user: "",
userInputError: false,
foundSavedLogin: false,
authority: false,
authorizing: false,
userInputErrorMessage: "",
defaultRedirectAddress: "",
openAuthDialog: false,
authCode: "",
emergencyMode: false,
version: ""
version: "",
willAddAccount: false
};
getConfig()
@ -155,6 +173,11 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
clear() {
localStorage.removeItem("access_token");
localStorage.removeItem("baseurl");
}
getSavedSession() {
let loginData = localStorage.getItem("login");
if (loginData) {
@ -253,7 +276,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
clientId: resp.clientId,
clientSecret: resp.clientSecret,
authUrl: resp.url,
wantsToLogin: true
proceedToGetCode: true
});
});
} else {
@ -304,7 +327,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
clientSecret: session.clientSecret,
authUrl: session.authUrl,
emergencyMode: session.emergency,
wantsToLogin: true
proceedToGetCode: true
});
}
}
@ -332,8 +355,8 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
axios
.get(
"https://" +
baseUrl +
"/api/v1/timelines/public"
baseUrl +
"/api/v1/timelines/public"
)
.catch((err: Error) => {
let userInputError = true;
@ -375,7 +398,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
let location = window.location.href;
if (location.includes("?code=")) {
let code = parseUrl(location).query.code as string;
this.setState({ authority: true });
this.setState({ authorizing: true });
let loginData = localStorage.getItem("login");
if (loginData) {
let clientLoginSession: SaveClientSession = JSON.parse(
@ -389,12 +412,12 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
this.state.emergencyMode
? undefined
: clientLoginSession.authUrl.includes(
"urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"
)
"urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"
)
? undefined
: window.location.protocol === "hyperspace:"
? "hyperspace://hyperspace/app/"
: `https://${window.location.host}`
? "hyperspace://hyperspace/app/"
: `https://${window.location.host}`
)
.then((tokenData: any) => {
localStorage.setItem(
@ -436,6 +459,65 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
showMultiAccount() {
const { classes } = this.props;
return (
<div>
<Typography variant="h5">Select an account</Typography>
<Typography>from the list below or add a new one</Typography>
<List>
{getAccountRegistry().map(
(account: MultiAccount, index: number) => (
<ListItem
onClick={() => {
loginWithAccount(account);
window.location.href =
window.location.protocol ===
"hyperspace:"
? "hyperspace://hyperspace/app/"
: `https://${window.location.host}/#/`;
}}
button={true}
>
<ListItemAvatar>
<AccountCircleIcon color="action" />
</ListItemAvatar>
<ListItemText
primary={`@${account.username}`}
secondary={account.host}
/>
<ListItemSecondaryAction>
<IconButton
onClick={(e: any) => {
e.preventDefault();
removeAccountFromRegistry(index);
window.location.reload();
}}
>
<CloseIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
)
)}
</List>
<div className={classes.middlePadding} />
<Button
onClick={() => {
this.setState({ willAddAccount: true });
this.clear();
}}
color={"primary"}
variant={"contained"}
>
Add Account
</Button>
</div>
);
}
showLanding() {
const { classes } = this.props;
return (
@ -452,7 +534,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
onKeyDown={event => this.watchUsernameField(event)}
error={this.state.userInputError}
onBlur={() => this.checkForErrors()}
></TextField>
/>
{this.state.userInputError ? (
<Typography color="error">
{this.state.userInputErrorMessage}
@ -597,7 +679,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
this.updateAuthCode(event.target.value)
}
onKeyDown={event => this.watchAuthField(event)}
></TextField>
/>
</DialogContent>
<DialogActions>
<Button onClick={() => this.toggleAuthDialog()}>
@ -614,7 +696,7 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
);
}
showAuthority() {
showAuthorizationLoader() {
const { classes } = this.props;
return (
<div>
@ -659,11 +741,14 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
/>
<br />
<Fade in={true}>
{this.state.authority
? this.showAuthority()
: this.state.wantsToLogin
? this.showLoginAuth()
: this.showLanding()}
{this.state.authorizing
? this.showAuthorizationLoader()
: this.state.proceedToGetCode
? this.showLoginAuth()
: getAccountRegistry().length > 0 &&
!this.state.willAddAccount
? this.showMultiAccount()
: this.showLanding()}
</Fade>
<br />
<Typography variant="caption">
@ -741,4 +826,4 @@ class WelcomePage extends Component<IWelcomeProps, IWelcomeState> {
}
}
export default withStyles(styles)(withSnackbar(WelcomePage));
export default withStyles(styles)(withSnackbar(WelcomePage));

View File

@ -18,6 +18,7 @@ export function refreshUserAccountData() {
.then((resp: any) => {
let account: Account = resp.data;
localStorage.setItem("account", JSON.stringify(account));
addAccountToRegistry(host, token, account.acct);
})
.catch((err: Error) => {
@ -70,6 +71,7 @@ export function addAccountToRegistry(
access_token: string,
username: string
) {
console.log("Firing!");
const newAccount: MultiAccount = {
host: base_url,
username,
@ -78,10 +80,10 @@ export function addAccountToRegistry(
let accountRegistry = getAccountRegistry();
const stringifiedRegistry = accountRegistry.map(account =>
account.toString()
JSON.stringify(account)
);
if (stringifiedRegistry.indexOf(newAccount.toString()) === -1) {
if (stringifiedRegistry.indexOf(JSON.stringify(newAccount)) === -1) {
accountRegistry.push(newAccount);
}
@ -99,13 +101,42 @@ export function removeAccountFromRegistry(
if (typeof accountIdentifier === "number") {
if (accountRegistry.length > accountIdentifier) {
if (
localStorage.getItem("access_token") ===
accountRegistry[accountIdentifier].access_token
) {
localStorage.removeItem("baseurl");
localStorage.removeItem("access_token");
}
accountRegistry.splice(accountIdentifier);
} else {
console.log("Multi account index may be out of range");
}
} else {
if (accountRegistry.includes(accountIdentifier)) {
accountRegistry.splice(accountRegistry.indexOf(accountIdentifier));
const stringifiedRegistry = accountRegistry.map(account =>
JSON.stringify(account)
);
const stringifiedAccountId = JSON.stringify(accountIdentifier);
if (
stringifiedRegistry.indexOf(
JSON.stringify(stringifiedAccountId)
) !== -1
) {
if (
localStorage.getItem("access_token") ===
accountIdentifier.access_token
) {
localStorage.removeItem("baseurl");
localStorage.removeItem("access_token");
}
accountRegistry.splice(
stringifiedRegistry.indexOf(stringifiedAccountId)
);
}
}
localStorage.setItem("accountRegistry", JSON.stringify(accountRegistry));
}