Add switch account UI from login and update accounts functionality
Signed-off-by: Marquis Kurt <software@marquiskurt.net>
This commit is contained in:
parent
3229d0a337
commit
e75a964a39
40
src/App.tsx
40
src/App.tsx
|
@ -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)));
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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" ? (
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue