mirror of
https://akkoma.dev/AkkomaGang/akkoma-fe
synced 2025-04-30 19:19:29 +08:00
feat: account switching
This commit is contained in:
parent
9e04e4fd80
commit
22fdb50d88
12 changed files with 195 additions and 19 deletions
|
@ -349,7 +349,7 @@ const checkOAuthToken = async ({ store }) => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
if (store.getters.getUserToken()) {
|
if (store.getters.getUserToken()) {
|
||||||
try {
|
try {
|
||||||
await store.dispatch('loginUser', store.getters.getUserToken())
|
await store.dispatch('loginUser', store.getters.getUserToken()[store.state.users.lastLoginName])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(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
|
return
|
||||||
}
|
}
|
||||||
this.login(result).then(() => {
|
this.login({ username: this.user.username, ...result }).then(() => {
|
||||||
this.$router.push({ name: 'friends' })
|
this.$router.push({ name: 'friends' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@ const oac = {
|
||||||
if (this.code) {
|
if (this.code) {
|
||||||
const { clientId, clientSecret } = this.$store.state.oauth
|
const { clientId, clientSecret } = this.$store.state.oauth
|
||||||
|
|
||||||
|
// XXX look at this later
|
||||||
oauth.getToken({
|
oauth.getToken({
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
|
|
|
@ -69,9 +69,16 @@ const SettingsModal = {
|
||||||
this.$store.dispatch('closeSettingsModal')
|
this.$store.dispatch('closeSettingsModal')
|
||||||
},
|
},
|
||||||
logout () {
|
logout () {
|
||||||
this.$router.replace('/main/public')
|
|
||||||
this.$store.dispatch('closeSettingsModal')
|
this.$store.dispatch('closeSettingsModal')
|
||||||
|
this.$router.replace('/main/public')
|
||||||
this.$store.dispatch('logout')
|
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 () {
|
peekModal () {
|
||||||
this.$store.dispatch('togglePeekSettingsModal')
|
this.$store.dispatch('togglePeekSettingsModal')
|
||||||
|
|
|
@ -7,6 +7,7 @@ import AccountActions from '../account_actions/account_actions.vue'
|
||||||
import Select from '../select/select.vue'
|
import Select from '../select/select.vue'
|
||||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
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 generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
@ -15,7 +16,8 @@ import {
|
||||||
faRss,
|
faRss,
|
||||||
faSearchPlus,
|
faSearchPlus,
|
||||||
faExternalLinkAlt,
|
faExternalLinkAlt,
|
||||||
faEdit
|
faEdit,
|
||||||
|
faUserPlus,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
@ -23,7 +25,8 @@ library.add(
|
||||||
faBell,
|
faBell,
|
||||||
faSearchPlus,
|
faSearchPlus,
|
||||||
faExternalLinkAlt,
|
faExternalLinkAlt,
|
||||||
faEdit
|
faEdit,
|
||||||
|
faUserPlus,
|
||||||
)
|
)
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -128,7 +131,8 @@ export default {
|
||||||
FollowButton,
|
FollowButton,
|
||||||
Select,
|
Select,
|
||||||
RichContent,
|
RichContent,
|
||||||
ConfirmModal
|
ConfirmModal,
|
||||||
|
AccountSwitcher
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
refetchRelationship () {
|
refetchRelationship () {
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Avatar {
|
a .Avatar {
|
||||||
--_avatarShadowBox: var(--avatarShadow);
|
--_avatarShadowBox: var(--avatarShadow);
|
||||||
--_avatarShadowFilter: var(--avatarShadowFilter);
|
--_avatarShadowFilter: var(--avatarShadowFilter);
|
||||||
--_avatarShadowInset: var(--avatarShadowInset);
|
--_avatarShadowInset: var(--avatarShadowInset);
|
||||||
|
@ -151,7 +151,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.external-link-button, .edit-profile-button {
|
.external-link-button, .edit-profile-button, .switch-account-button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 2.5em;
|
width: 2.5em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
:title="$t('user_card.edit_profile')"
|
:title="$t('user_card.edit_profile')"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
<AccountSwitcher v-if="!isOtherUser && user.is_local" />
|
||||||
<a
|
<a
|
||||||
v-if="isOtherUser && !user.is_local"
|
v-if="isOtherUser && !user.is_local"
|
||||||
:href="user.statusnet_profile_url"
|
:href="user.statusnet_profile_url"
|
||||||
|
|
|
@ -68,8 +68,8 @@ const mutations = {
|
||||||
// actions
|
// actions
|
||||||
const actions = {
|
const actions = {
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
async login ({ state, dispatch, commit }, { access_token }) {
|
async login ({ state, dispatch, commit }, { access_token, username }) {
|
||||||
commit('setToken', access_token, { root: true })
|
commit('setToken', { token: access_token, username }, { root: true })
|
||||||
await dispatch('loginUser', access_token, { root: true })
|
await dispatch('loginUser', access_token, { root: true })
|
||||||
resetState(state)
|
resetState(state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ const oauth = {
|
||||||
/* User token is authentication for app with user, this is for every calls
|
/* 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)
|
* that need authorized user to be successful (i.e. posting, liking etc)
|
||||||
*/
|
*/
|
||||||
userToken: false
|
userToken: {}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setClientData (state, { clientId, clientSecret }) {
|
setClientData (state, { clientId, clientSecret }) {
|
||||||
|
@ -20,11 +20,11 @@ const oauth = {
|
||||||
setAppToken (state, token) {
|
setAppToken (state, token) {
|
||||||
state.appToken = token
|
state.appToken = token
|
||||||
},
|
},
|
||||||
setToken (state, token) {
|
setToken (state, { token, username }) {
|
||||||
state.userToken = token
|
state.userToken[username] = token
|
||||||
},
|
},
|
||||||
clearToken (state) {
|
clearToken (state, username) {
|
||||||
state.userToken = false
|
delete state.userToken[username]
|
||||||
// state.token is userToken with older name, coming from persistent state
|
// 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
|
// let's clear it as well, since it is being used as a fallback of state.userToken
|
||||||
delete state.token
|
delete state.token
|
||||||
|
|
|
@ -149,6 +149,12 @@ export const mutations = {
|
||||||
endLogin (state) {
|
endLogin (state) {
|
||||||
state.loggingIn = false
|
state.loggingIn = false
|
||||||
},
|
},
|
||||||
|
beginAccountSwitch (state) {
|
||||||
|
state.switchingAccounts = true
|
||||||
|
},
|
||||||
|
endAccountSwitch (state) {
|
||||||
|
state.switchingAccounts = false
|
||||||
|
},
|
||||||
saveFriendIds (state, { id, friendIds }) {
|
saveFriendIds (state, { id, friendIds }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
user.friendIds = uniq(concat(user.friendIds || [], friendIds))
|
user.friendIds = uniq(concat(user.friendIds || [], friendIds))
|
||||||
|
@ -296,6 +302,7 @@ export const getters = {
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
loggingIn: false,
|
loggingIn: false,
|
||||||
|
switchingAccounts: false,
|
||||||
lastLoginName: false,
|
lastLoginName: false,
|
||||||
currentUser: false,
|
currentUser: false,
|
||||||
users: [],
|
users: [],
|
||||||
|
@ -537,7 +544,7 @@ const users = {
|
||||||
return data
|
return data
|
||||||
} else if (data.me !== undefined) {
|
} else if (data.me !== undefined) {
|
||||||
store.commit('signUpSuccess')
|
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)
|
store.dispatch('loginUser', data.access_token)
|
||||||
return data
|
return data
|
||||||
} else {
|
} else {
|
||||||
|
@ -554,12 +561,13 @@ const users = {
|
||||||
},
|
},
|
||||||
|
|
||||||
logout (store) {
|
logout (store) {
|
||||||
const { oauth, instance } = store.rootState
|
const { oauth, instance, users } = store.rootState
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
...oauth,
|
...oauth,
|
||||||
commit: store.commit,
|
commit: store.commit,
|
||||||
instance: instance.server
|
instance: instance.server,
|
||||||
|
username: users.currentUser.screen_name
|
||||||
}
|
}
|
||||||
|
|
||||||
return oauthApi.getOrCreateApp(data)
|
return oauthApi.getOrCreateApp(data)
|
||||||
|
@ -575,7 +583,7 @@ const users = {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
store.commit('clearCurrentUser')
|
store.commit('clearCurrentUser')
|
||||||
store.dispatch('disconnectFromSocket')
|
store.dispatch('disconnectFromSocket')
|
||||||
store.commit('clearToken')
|
store.commit('clearToken', data.username)
|
||||||
store.dispatch('stopFetchingTimeline', 'friends')
|
store.dispatch('stopFetchingTimeline', 'friends')
|
||||||
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
|
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
|
||||||
store.dispatch('stopFetchingNotifications')
|
store.dispatch('stopFetchingNotifications')
|
||||||
|
@ -594,6 +602,19 @@ const users = {
|
||||||
store.rootState.api.backendInteractor.verifyCredentials(accessToken)
|
store.rootState.api.backendInteractor.verifyCredentials(accessToken)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (!data.error) {
|
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
|
const user = data
|
||||||
// user.credentials = userCredentials
|
// user.credentials = userCredentials
|
||||||
user.credentials = accessToken
|
user.credentials = accessToken
|
||||||
|
|
Loading…
Reference in a new issue