WIP: feat: account switching #279
12 changed files with 195 additions and 19 deletions
|
@ -349,7 +349,7 @@ const checkOAuthToken = async ({ store }) => {
|
|||
return new Promise(async (resolve, reject) => {
|
||||
if (store.getters.getUserToken()) {
|
||||
try {
|
||||
await store.dispatch('loginUser', store.getters.getUserToken())
|
||||
await store.dispatch('loginUser', store.getters.getUserToken()[store.state.users.lastLoginName])
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
|
52
src/components/account_switcher/account_switcher.js
Normal file
52
src/components/account_switcher/account_switcher.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
import Popover from '../popover/popover.vue'
|
||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||
import RichContent from '../rich_content/rich_content.jsx'
|
||||
import { map } from 'lodash'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import {
|
||||
faRightToBracket,
|
||||
faUserPen
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
faRightToBracket,
|
||||
faUserPen
|
||||
)
|
||||
|
||||
const AccountSwitcher = {
|
||||
components: {
|
||||
Popover,
|
||||
UserAvatar,
|
||||
RichContent
|
||||
},
|
||||
computed: {
|
||||
currentUser () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
accounts () {
|
||||
return map(Object.keys(this.$store.state.oauth.userToken), username => (
|
||||
this.$store.getters.findUser(username)
|
||||
))
|
||||
},
|
||||
registrationOpen () {
|
||||
return this.$store.state.instance.registrationOpen
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
login () {
|
||||
this.$store.commit('beginAccountSwitch')
|
||||
this.$router.push({ name: 'login' })
|
||||
},
|
||||
switchAccount (username, token) {
|
||||
// don't switch to same user
|
||||
if (username !== this.currentUser.screen_name) {
|
||||
this.$store.commit('beginAccountSwitch')
|
||||
this.$store.dispatch('loginUser', this.$store.state.oauth.userToken[username]).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AccountSwitcher
|
90
src/components/account_switcher/account_switcher.vue
Normal file
90
src/components/account_switcher/account_switcher.vue
Normal file
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<Popover
|
||||
trigger="click"
|
||||
class="SwitchAccounts"
|
||||
:bound-to="{ x: 'container' }"
|
||||
:offset="{ x: -16 }"
|
||||
>
|
||||
<template #trigger>
|
||||
<button
|
||||
class="button-unstyled switch-account-button"
|
||||
>
|
||||
<FAIcon
|
||||
fixed-width
|
||||
class="icon"
|
||||
icon="user-plus"
|
||||
:title="$t('user_card.switch_accounts')"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="dropdown-menu">
|
||||
<button
|
||||
v-for="account in accounts"
|
||||
class="button-default dropdown-item account-button"
|
||||
:class="account.id === currentUser.id ? 'selected' : ''"
|
||||
:key="account.screen_name"
|
||||
@click="switchAccount(account.screen_name)"
|
||||
>
|
||||
<UserAvatar
|
||||
:compact="true"
|
||||
:user="account"
|
||||
/>
|
||||
<div class="right-side">
|
||||
<RichContent
|
||||
class="username"
|
||||
:title="'@'+account.screen_name"
|
||||
:html="account.name_html"
|
||||
:emoji="account.emoji"
|
||||
/>
|
||||
<a>@{{ account.screen_name }}</a>
|
||||
</div>
|
||||
</button>
|
||||
<div
|
||||
role="separator"
|
||||
class="dropdown-divider"
|
||||
/>
|
||||
<button
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click="login"
|
||||
>
|
||||
<FAIcon icon="right-to-bracket" />{{ $t('login.login') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="registrationOpen"
|
||||
class="button-default dropdown-item dropdown-item-icon"
|
||||
@click="register"
|
||||
>
|
||||
<FAIcon icon="user-pen" />{{ $t('login.register') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script src="./account_switcher.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.button-default.dropdown-item.account-button {
|
||||
display: flex;
|
||||
|
||||
&.selected {
|
||||
background-color: var(--selectedPost, $fallback--lightBg);
|
||||
}
|
||||
|
||||
.Avatar {
|
||||
margin-right: 0.75em;
|
||||
}
|
||||
|
||||
.right-side {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: bolder;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -75,7 +75,7 @@ const LoginForm = {
|
|||
}
|
||||
return
|
||||
}
|
||||
this.login(result).then(() => {
|
||||
this.login({ username: this.user.username, ...result }).then(() => {
|
||||
this.$router.push({ name: 'friends' })
|
||||
})
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ const oac = {
|
|||
if (this.code) {
|
||||
const { clientId, clientSecret } = this.$store.state.oauth
|
||||
|
||||
// XXX look at this later
|
||||
oauth.getToken({
|
||||
clientId,
|
||||
clientSecret,
|
||||
|
|
|
@ -69,9 +69,16 @@ const SettingsModal = {
|
|||
this.$store.dispatch('closeSettingsModal')
|
||||
},
|
||||
logout () {
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('closeSettingsModal')
|
||||
this.$router.replace('/main/public')
|
||||
this.$store.dispatch('logout')
|
||||
.then(() => {
|
||||
// check if logged in to other accounts
|
||||
const accounts = Object.keys(this.$store.state.oauth.userToken)
|
||||
if (accounts !== []) {
|
||||
this.$store.dispatch('loginUser', this.$store.state.oauth.userToken[accounts[0]])
|
||||
}
|
||||
})
|
||||
},
|
||||
peekModal () {
|
||||
this.$store.dispatch('togglePeekSettingsModal')
|
||||
|
|
|
@ -7,6 +7,7 @@ import AccountActions from '../account_actions/account_actions.vue'
|
|||
import Select from '../select/select.vue'
|
||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||
import AccountSwitcher from '../account_switcher/account_switcher.vue'
|
||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
|
@ -15,7 +16,8 @@ import {
|
|||
faRss,
|
||||
faSearchPlus,
|
||||
faExternalLinkAlt,
|
||||
faEdit
|
||||
faEdit,
|
||||
faUserPlus,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
library.add(
|
||||
|
@ -23,7 +25,8 @@ library.add(
|
|||
faBell,
|
||||
faSearchPlus,
|
||||
faExternalLinkAlt,
|
||||
faEdit
|
||||
faEdit,
|
||||
faUserPlus,
|
||||
)
|
||||
|
||||
export default {
|
||||
|
@ -128,7 +131,8 @@ export default {
|
|||
FollowButton,
|
||||
Select,
|
||||
RichContent,
|
||||
ConfirmModal
|
||||
ConfirmModal,
|
||||
AccountSwitcher
|
||||
},
|
||||
methods: {
|
||||
refetchRelationship () {
|
||||
|
|
|
@ -110,7 +110,7 @@
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.Avatar {
|
||||
a .Avatar {
|
||||
--_avatarShadowBox: var(--avatarShadow);
|
||||
--_avatarShadowFilter: var(--avatarShadowFilter);
|
||||
--_avatarShadowInset: var(--avatarShadowInset);
|
||||
|
@ -151,7 +151,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.external-link-button, .edit-profile-button {
|
||||
.external-link-button, .edit-profile-button, .switch-account-button {
|
||||
cursor: pointer;
|
||||
width: 2.5em;
|
||||
text-align: center;
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
:title="$t('user_card.edit_profile')"
|
||||
/>
|
||||
</button>
|
||||
<AccountSwitcher v-if="!isOtherUser && user.is_local" />
|
||||
<a
|
||||
v-if="isOtherUser && !user.is_local"
|
||||
:href="user.statusnet_profile_url"
|
||||
|
|
|
@ -68,8 +68,8 @@ const mutations = {
|
|||
// actions
|
||||
const actions = {
|
||||
// eslint-disable-next-line camelcase
|
||||
async login ({ state, dispatch, commit }, { access_token }) {
|
||||
commit('setToken', access_token, { root: true })
|
||||
async login ({ state, dispatch, commit }, { access_token, username }) {
|
||||
commit('setToken', { token: access_token, username }, { root: true })
|
||||
await dispatch('loginUser', access_token, { root: true })
|
||||
resetState(state)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ const oauth = {
|
|||
/* User token is authentication for app with user, this is for every calls
|
||||
* that need authorized user to be successful (i.e. posting, liking etc)
|
||||
*/
|
||||
userToken: false
|
||||
userToken: {}
|
||||
},
|
||||
mutations: {
|
||||
setClientData (state, { clientId, clientSecret }) {
|
||||
|
@ -20,11 +20,11 @@ const oauth = {
|
|||
setAppToken (state, token) {
|
||||
state.appToken = token
|
||||
},
|
||||
setToken (state, token) {
|
||||
state.userToken = token
|
||||
setToken (state, { token, username }) {
|
||||
state.userToken[username] = token
|
||||
},
|
||||
clearToken (state) {
|
||||
state.userToken = false
|
||||
clearToken (state, username) {
|
||||
delete state.userToken[username]
|
||||
// state.token is userToken with older name, coming from persistent state
|
||||
// let's clear it as well, since it is being used as a fallback of state.userToken
|
||||
delete state.token
|
||||
|
|
|
@ -149,6 +149,12 @@ export const mutations = {
|
|||
endLogin (state) {
|
||||
state.loggingIn = false
|
||||
},
|
||||
beginAccountSwitch (state) {
|
||||
state.switchingAccounts = true
|
||||
},
|
||||
endAccountSwitch (state) {
|
||||
state.switchingAccounts = false
|
||||
},
|
||||
saveFriendIds (state, { id, friendIds }) {
|
||||
const user = state.usersObject[id]
|
||||
user.friendIds = uniq(concat(user.friendIds || [], friendIds))
|
||||
|
@ -296,6 +302,7 @@ export const getters = {
|
|||
|
||||
export const defaultState = {
|
||||
loggingIn: false,
|
||||
switchingAccounts: false,
|
||||
lastLoginName: false,
|
||||
currentUser: false,
|
||||
users: [],
|
||||
|
@ -537,7 +544,7 @@ const users = {
|
|||
return data
|
||||
} else if (data.me !== undefined) {
|
||||
store.commit('signUpSuccess')
|
||||
store.commit('setToken', data.access_token)
|
||||
store.commit('setToken', { token: data.access_token, username: userInfo.nickname })
|
||||
store.dispatch('loginUser', data.access_token)
|
||||
return data
|
||||
} else {
|
||||
|
@ -554,12 +561,13 @@ const users = {
|
|||
},
|
||||
|
||||
logout (store) {
|
||||
const { oauth, instance } = store.rootState
|
||||
const { oauth, instance, users } = store.rootState
|
||||
|
||||
const data = {
|
||||
...oauth,
|
||||
commit: store.commit,
|
||||
instance: instance.server
|
||||
instance: instance.server,
|
||||
username: users.currentUser.screen_name
|
||||
}
|
||||
|
||||
return oauthApi.getOrCreateApp(data)
|
||||
|
@ -575,7 +583,7 @@ const users = {
|
|||
.then(() => {
|
||||
store.commit('clearCurrentUser')
|
||||
store.dispatch('disconnectFromSocket')
|
||||
store.commit('clearToken')
|
||||
store.commit('clearToken', data.username)
|
||||
store.dispatch('stopFetchingTimeline', 'friends')
|
||||
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
|
||||
store.dispatch('stopFetchingNotifications')
|
||||
|
@ -594,6 +602,19 @@ const users = {
|
|||
store.rootState.api.backendInteractor.verifyCredentials(accessToken)
|
||||
.then((data) => {
|
||||
if (!data.error) {
|
||||
// clear old user
|
||||
// TODO: maybe we don't need some of this
|
||||
if (store.state.loggingIn) {
|
||||
store.commit('clearCurrentUser')
|
||||
store.dispatch('disconnectFromSocket')
|
||||
store.dispatch('stopFetchingTimeline', 'friends')
|
||||
store.dispatch('stopFetchingNotifications')
|
||||
store.dispatch('stopFetchingConfig')
|
||||
store.commit('clearNotifications')
|
||||
store.commit('resetStatuses')
|
||||
store.commit('endAccountSwitch')
|
||||
}
|
||||
|
||||
const user = data
|
||||
// user.credentials = userCredentials
|
||||
user.credentials = accessToken
|
||||
|
|
Loading…
Reference in a new issue