diff --git a/CHANGELOG.md b/CHANGELOG.md index e368ae700f8af41efda9fef2837e4f19e403611e..770681f77ccd345b338ea9fa54c2e17e2c8f2cdd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Ability to confirm users' emails and resend confirmation emails - Report notes - Ability to moderate users on the statuses page +- Ability to moderate user on the user's page ### Fixed diff --git a/src/api/users.js b/src/api/users.js index f3398c68da578ca44a2f108cc7ede9b64a2d1536..8298eeca047c6b890bbe5f84061401adca901fca 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -89,7 +89,7 @@ export async function getPasswordResetToken(nickname, authHost, token) { }) } -export async function requirePasswordReset(nicknames, authHost, token) { +export async function forcePasswordReset(nicknames, authHost, token) { return await request({ baseURL: baseName(authHost), url: `/api/pleroma/admin/users/force_password_reset`, diff --git a/src/lang/en.js b/src/lang/en.js index 1a8a8c9007c725227d3c000078b1427012caaec1..fa4d3209f5d3377e0c5e56d6d466316fff1d8b8f 100644 --- a/src/lang/en.js +++ b/src/lang/en.js @@ -202,6 +202,7 @@ export default { disableAnySubscriptionForMultiple: 'Disallow following users at all', requirePasswordReset: 'Require password reset on next login', selectUsers: 'Select users to apply actions to multiple users', + moderateUser: 'Moderate user', moderateUsers: 'Moderate multiple users', createAccount: 'Create new account', apply: 'apply', diff --git a/src/store/modules/users.js b/src/store/modules/users.js index dccbb1ca7284620f51f1465cd6150d8a085bfbd9..75b07402807d20ae9260cf72f09d87fb5a3ca710 100644 --- a/src/store/modules/users.js +++ b/src/store/modules/users.js @@ -12,7 +12,7 @@ import { searchUsers, tagUser, untagUser, - requirePasswordReset, + forcePasswordReset, confirmUserEmail, resendConfirmationEmail } from '@/api/users' @@ -79,53 +79,46 @@ const users = { } }, actions: { - async ActivateUsers({ commit, dispatch, getters, state }, users) { + async ActivateUsers({ dispatch, getters }, users) { const updatedUsers = users.map(user => { return { ...user, deactivated: false } }) + const nicknames = users.map(user => user.nickname) + const callApiFn = async() => await activateUsers(nicknames, getters.authHost, getters.token) + + dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: users[0].id }) + }, + async ApplyChanges({ commit, dispatch, state }, { updatedUsers, callApiFn, userId }) { commit('SWAP_USERS', updatedUsers) - const usersNicknames = users.map(user => user.nickname) try { - await activateUsers(usersNicknames, getters.authHost, getters.token) + await callApiFn() } catch (_e) { return } finally { dispatch('SearchUsers', { query: state.searchQuery, page: state.currentPage }) } + + dispatch('FetchUserProfile', { userId, godmode: false }) dispatch('SuccessMessage') }, - async AddRight({ commit, dispatch, getters, state }, { users, right }) { + async AddRight({ dispatch, getters }, { users, right }) { const updatedUsers = users.map(user => { return user.local ? { ...user, roles: { ...user.roles, [right]: true }} : user }) - commit('SWAP_USERS', updatedUsers) + const nicknames = users.map(user => user.nickname) + const callApiFn = async() => await addRight(nicknames, right, getters.authHost, getters.token) - const usersNicknames = users.map(user => user.nickname) - try { - await addRight(usersNicknames, right, getters.authHost, getters.token) - } catch (_e) { - return - } finally { - dispatch('SearchUsers', { query: state.searchQuery, page: state.currentPage }) - } - dispatch('SuccessMessage') + dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: users[0].id }) }, - async AddTag({ commit, dispatch, getters, state }, { users, tag }) { + async AddTag({ dispatch, getters }, { users, tag }) { const updatedUsers = users.map(user => { return { ...user, tags: [...user.tags, tag] } }) - commit('SWAP_USERS', updatedUsers) - const nicknames = users.map(user => user.nickname) - try { - await tagUser(nicknames, [tag], getters.authHost, getters.token) - } catch (_e) { - return - } finally { - dispatch('SearchUsers', { query: state.searchQuery, page: state.currentPage }) - } - dispatch('SuccessMessage') + const callApiFn = async() => await tagUser(nicknames, [tag], getters.authHost, getters.token) + + dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: users[0].id }) }, async ClearFilters({ commit, dispatch, state }) { commit('CLEAR_USERS_FILTERS') @@ -141,37 +134,23 @@ const users = { } dispatch('SuccessMessage') }, - async DeactivateUsers({ commit, dispatch, getters, state }, users) { + async DeactivateUsers({ dispatch, getters }, users) { const updatedUsers = users.map(user => { return { ...user, deactivated: true } }) - commit('SWAP_USERS', updatedUsers) + const nicknames = users.map(user => user.nickname) + const callApiFn = async() => await deactivateUsers(nicknames, getters.authHost, getters.token) - const usersNicknames = users.map(user => user.nickname) - try { - await deactivateUsers(usersNicknames, getters.authHost, getters.token) - } catch (_e) { - return - } finally { - dispatch('SearchUsers', { query: state.searchQuery, page: state.currentPage }) - } - dispatch('SuccessMessage') + dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: users[0].id }) }, - async ConfirmUsersEmail({ commit, dispatch, getters, state }, users) { + async ConfirmUsersEmail({ dispatch, getters }, users) { const updatedUsers = users.map(user => { return { ...user, confirmation_pending: false } }) - commit('SWAP_USERS', updatedUsers) + const nicknames = users.map(user => user.nickname) + const callApiFn = async() => await confirmUserEmail(nicknames, getters.authHost, getters.token) - const usersNicknames = users.map(user => user.nickname) - try { - await confirmUserEmail(usersNicknames, getters.authHost, getters.token) - } catch (_e) { - return - } finally { - dispatch('SearchUsers', { query: state.searchQuery, page: state.currentPage }) - } - dispatch('SuccessMessage') + dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: users[0].id }) }, async ResendConfirmationEmail({ dispatch, getters }, users) { const usersNicknames = users.map(user => user.nickname) @@ -182,21 +161,14 @@ const users = { } dispatch('SuccessMessage') }, - async DeleteRight({ commit, dispatch, getters, state }, { users, right }) { + async DeleteRight({ dispatch, getters }, { users, right }) { const updatedUsers = users.map(user => { return user.local ? { ...user, roles: { ...user.roles, [right]: false }} : user }) - commit('SWAP_USERS', updatedUsers) + const nicknames = users.map(user => user.nickname) + const callApiFn = async() => await deleteRight(nicknames, right, getters.authHost, getters.token) - const usersNicknames = users.map(user => user.nickname) - try { - await deleteRight(usersNicknames, right, getters.authHost, getters.token) - } catch (_e) { - return - } finally { - dispatch('SearchUsers', { query: state.searchQuery, page: state.currentPage }) - } - dispatch('SuccessMessage') + dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: users[0].id }) }, async DeleteUsers({ commit, dispatch, getters, state }, users) { const usersNicknames = users.map(user => user.nickname) @@ -208,6 +180,8 @@ const users = { const deletedUsersIds = users.map(deletedUser => deletedUser.id) const updatedUsers = state.fetchedUsers.filter(user => !deletedUsersIds.includes(user.id)) commit('SET_USERS', updatedUsers) + + dispatch('FetchUserProfile', { userId: users[0].id, godmode: false }) dispatch('SuccessMessage') }, async FetchUsers({ commit, dispatch, getters, state }, { page }) { @@ -224,25 +198,19 @@ const users = { RemovePasswordToken({ commit }) { commit('SET_PASSWORD_RESET_TOKEN', { link: '', token: '' }) }, - async RemoveTag({ commit, dispatch, getters, state }, { users, tag }) { + async RemoveTag({ dispatch, getters }, { users, tag }) { const updatedUsers = users.map(user => { return { ...user, tags: user.tags.filter(userTag => userTag !== tag) } }) - commit('SWAP_USERS', updatedUsers) - const nicknames = users.map(user => user.nickname) - try { - await untagUser(nicknames, [tag], getters.authHost, getters.token) - } catch (_e) { - return - } finally { - dispatch('SearchUsers', { query: state.searchQuery, page: state.currentPage }) - } - dispatch('SuccessMessage') + const callApiFn = async() => await untagUser(nicknames, [tag], getters.authHost, getters.token) + + dispatch('ApplyChanges', { updatedUsers, callApiFn, userId: users[0].id }) }, - async RequirePasswordReset({ dispatch, getters }, { nicknames }) { + async RequirePasswordReset({ dispatch, getters }, users) { + const nicknames = users.map(user => user.nickname) try { - await requirePasswordReset(nicknames, getters.authHost, getters.token) + await forcePasswordReset(nicknames, getters.authHost, getters.token) } catch (_e) { return } diff --git a/src/views/users/components/ModerationDropdown.vue b/src/views/users/components/ModerationDropdown.vue new file mode 100644 index 0000000000000000000000000000000000000000..d6e361ad4b52202f6bf00e487959917a2e07693c --- /dev/null +++ b/src/views/users/components/ModerationDropdown.vue @@ -0,0 +1,182 @@ +<template> + <el-dropdown :hide-on-click="false" size="small" trigger="click"> + <div> + <span v-if="page === 'users'" class="el-dropdown-link"> + {{ $t('users.moderation') }} + <i v-if="isDesktop" class="el-icon-arrow-down el-icon--right"/> + </span> + <el-button v-if="page === 'userPage'" class="moderate-user-button"> + <span class="moderate-user-button-container"> + <span> + <i class="el-icon-edit" /> + {{ $t('users.moderateUser') }} + </span> + <i class="el-icon-arrow-down el-icon--right"/> + </span> + </el-button> + </div> + <el-dropdown-menu slot="dropdown"> + <el-dropdown-item + v-if="showAdminAction(user)" + @click.native="toggleUserRight(user, 'admin')"> + {{ user.roles.admin ? $t('users.revokeAdmin') : $t('users.grantAdmin') }} + </el-dropdown-item> + <el-dropdown-item + v-if="showAdminAction(user)" + @click.native="toggleUserRight(user, 'moderator')"> + {{ user.roles.moderator ? $t('users.revokeModerator') : $t('users.grantModerator') }} + </el-dropdown-item> + <el-dropdown-item + v-if="showDeactivatedButton(user.id)" + :divided="showAdminAction(user)" + @click.native="toggleActivation(user)"> + {{ user.deactivated ? $t('users.activateAccount') : $t('users.deactivateAccount') }} + </el-dropdown-item> + <el-dropdown-item + v-if="showDeactivatedButton(user.id)" + @click.native="handleDeletion(user)"> + {{ $t('users.deleteAccount') }} + </el-dropdown-item> + <el-dropdown-item + v-if="user.local && user.confirmation_pending" + divided + @click.native="handleEmailConfirmation(user)"> + {{ $t('users.confirmAccount') }} + </el-dropdown-item> + <el-dropdown-item + v-if="user.local && user.confirmation_pending" + @click.native="handleConfirmationResend(user)"> + {{ $t('users.resendConfirmation') }} + </el-dropdown-item> + <el-dropdown-item + :divided="showAdminAction(user)" + :class="{ 'active-tag': user.tags.includes('force_nsfw') }" + @click.native="toggleTag(user, 'force_nsfw')"> + {{ $t('users.forceNsfw') }} + <i v-if="user.tags.includes('force_nsfw')" class="el-icon-check"/> + </el-dropdown-item> + <el-dropdown-item + :class="{ 'active-tag': user.tags.includes('strip_media') }" + @click.native="toggleTag(user, 'strip_media')"> + {{ $t('users.stripMedia') }} + <i v-if="user.tags.includes('strip_media')" class="el-icon-check"/> + </el-dropdown-item> + <el-dropdown-item + :class="{ 'active-tag': user.tags.includes('force_unlisted') }" + @click.native="toggleTag(user, 'force_unlisted')"> + {{ $t('users.forceUnlisted') }} + <i v-if="user.tags.includes('force_unlisted')" class="el-icon-check"/> + </el-dropdown-item> + <el-dropdown-item + :class="{ 'active-tag': user.tags.includes('sandbox') }" + @click.native="toggleTag(user, 'sandbox')"> + {{ $t('users.sandbox') }} + <i v-if="user.tags.includes('sandbox')" class="el-icon-check"/> + </el-dropdown-item> + <el-dropdown-item + v-if="user.local" + :class="{ 'active-tag': user.tags.includes('disable_remote_subscription') }" + @click.native="toggleTag(user, 'disable_remote_subscription')"> + {{ $t('users.disableRemoteSubscription') }} + <i v-if="user.tags.includes('disable_remote_subscription')" class="el-icon-check"/> + </el-dropdown-item> + <el-dropdown-item + v-if="user.local" + :class="{ 'active-tag': user.tags.includes('disable_any_subscription') }" + @click.native="toggleTag(user, 'disable_any_subscription')"> + {{ $t('users.disableAnySubscription') }} + <i v-if="user.tags.includes('disable_any_subscription')" class="el-icon-check"/> + </el-dropdown-item> + <el-dropdown-item + v-if="user.local" + divided + @click.native="getPasswordResetToken(user.nickname)"> + {{ $t('users.getPasswordResetToken') }} + </el-dropdown-item> + <el-dropdown-item + v-if="user.local" + @click.native="requirePasswordReset(user)"> + {{ $t('users.requirePasswordReset') }} + </el-dropdown-item> + </el-dropdown-menu> + </el-dropdown> +</template> + +<script> +export default { + name: 'ModerationDropdown', + props: { + user: { + type: Object, + default: function() { + return {} + } + }, + page: { + type: String, + default: 'users' + } + }, + computed: { + isDesktop() { + return this.$store.state.app.device === 'desktop' + } + }, + methods: { + getPasswordResetToken(nickname) { + this.$emit('open-reset-token-dialog') + this.$store.dispatch('GetPasswordResetToken', nickname) + }, + handleConfirmationResend(user) { + this.$store.dispatch('ResendConfirmationEmail', [user]) + }, + handleDeletion(user) { + this.$store.dispatch('DeleteUsers', [user]) + }, + handleEmailConfirmation(user) { + this.$store.dispatch('ConfirmUsersEmail', [user]) + }, + requirePasswordReset(user) { + const mailerEnabled = this.$store.state.user.nodeInfo.metadata.mailerEnabled + if (!mailerEnabled) { + this.$alert(this.$t('users.mailerMustBeEnabled'), 'Error', { type: 'error' }) + return + } + this.$store.dispatch('RequirePasswordReset', [user]) + }, + showAdminAction({ local, id }) { + return local && this.showDeactivatedButton(id) + }, + showDeactivatedButton(id) { + return this.$store.state.user.id !== id + }, + toggleActivation(user) { + user.deactivated + ? this.$store.dispatch('ActivateUsers', [user]) + : this.$store.dispatch('DeactivateUsers', [user]) + }, + toggleTag(user, tag) { + user.tags.includes(tag) + ? this.$store.dispatch('RemoveTag', { users: [user], tag }) + : this.$store.dispatch('AddTag', { users: [user], tag }) + }, + toggleUserRight(user, right) { + user.roles[right] + ? this.$store.dispatch('DeleteRight', { users: [user], right }) + : this.$store.dispatch('AddRight', { users: [user], right }) + } + } +} +</script> + +<style rel='stylesheet/scss' lang='scss'> + .moderate-user-button { + text-align: left; + width: 200px; + padding: 10px; + } + .moderate-user-button-container { + display: flex; + justify-content: space-between; + } +</style> diff --git a/src/views/users/components/MultipleUsersMenu.vue b/src/views/users/components/MultipleUsersMenu.vue index 6eb18c1c63b727dac27489ddd49857ec103e9bfc..de058ad8f58dd2c394ead0634f9bac6a8c3d127a 100644 --- a/src/views/users/components/MultipleUsersMenu.vue +++ b/src/views/users/components/MultipleUsersMenu.vue @@ -216,9 +216,9 @@ export default { }, requirePasswordReset: () => { const filtered = this.selectedUsers.filter(user => user.local) - const nicknames = filtered.map(u => u.nickname) - this.$store.dispatch('RequirePasswordReset', { nicknames }) - this.$emit('apply-action') + const requirePasswordResetFn = async(users) => await this.$store.dispatch('RequirePasswordReset', users) + + applyAction(filtered, requirePasswordResetFn) }, confirmAccounts: () => { const filtered = this.selectedUsers.filter(user => user.local && user.confirmation_pending) diff --git a/src/views/users/index.vue b/src/views/users/index.vue index fee152918e98c9f694818194976e2a84922eed82..2940f77634fadb6c142ed385351f847c751eb8e6 100644 --- a/src/views/users/index.vue +++ b/src/views/users/index.vue @@ -66,96 +66,10 @@ </el-table-column> <el-table-column :label="$t('users.actions')" fixed="right"> <template slot-scope="scope"> - <el-dropdown :hide-on-click="false" size="small" trigger="click"> - <span class="el-dropdown-link"> - {{ $t('users.moderation') }} - <i v-if="isDesktop" class="el-icon-arrow-down el-icon--right"/> - </span> - <el-dropdown-menu slot="dropdown"> - <el-dropdown-item - v-if="showAdminAction(scope.row)" - @click.native="toggleUserRight(scope.row, 'admin')"> - {{ scope.row.roles.admin ? $t('users.revokeAdmin') : $t('users.grantAdmin') }} - </el-dropdown-item> - <el-dropdown-item - v-if="showAdminAction(scope.row)" - @click.native="toggleUserRight(scope.row, 'moderator')"> - {{ scope.row.roles.moderator ? $t('users.revokeModerator') : $t('users.grantModerator') }} - </el-dropdown-item> - <el-dropdown-item - v-if="showDeactivatedButton(scope.row.id)" - :divided="showAdminAction(scope.row)" - @click.native="toggleActivation(scope.row)"> - {{ scope.row.deactivated ? $t('users.activateAccount') : $t('users.deactivateAccount') }} - </el-dropdown-item> - <el-dropdown-item - v-if="showDeactivatedButton(scope.row.id)" - @click.native="handleDeletion(scope.row)"> - {{ $t('users.deleteAccount') }} - </el-dropdown-item> - <el-dropdown-item - v-if="scope.row.local && scope.row.confirmation_pending" - divided - @click.native="handleEmailConfirmation(scope.row)"> - {{ $t('users.confirmAccount') }} - </el-dropdown-item> - <el-dropdown-item - v-if="scope.row.local && scope.row.confirmation_pending" - @click.native="handleConfirmationResend(scope.row)"> - {{ $t('users.resendConfirmation') }} - </el-dropdown-item> - <el-dropdown-item - :divided="showAdminAction(scope.row)" - :class="{ 'active-tag': scope.row.tags.includes('force_nsfw') }" - @click.native="toggleTag(scope.row, 'force_nsfw')"> - {{ $t('users.forceNsfw') }} - <i v-if="scope.row.tags.includes('force_nsfw')" class="el-icon-check"/> - </el-dropdown-item> - <el-dropdown-item - :class="{ 'active-tag': scope.row.tags.includes('strip_media') }" - @click.native="toggleTag(scope.row, 'strip_media')"> - {{ $t('users.stripMedia') }} - <i v-if="scope.row.tags.includes('strip_media')" class="el-icon-check"/> - </el-dropdown-item> - <el-dropdown-item - :class="{ 'active-tag': scope.row.tags.includes('force_unlisted') }" - @click.native="toggleTag(scope.row, 'force_unlisted')"> - {{ $t('users.forceUnlisted') }} - <i v-if="scope.row.tags.includes('force_unlisted')" class="el-icon-check"/> - </el-dropdown-item> - <el-dropdown-item - :class="{ 'active-tag': scope.row.tags.includes('sandbox') }" - @click.native="toggleTag(scope.row, 'sandbox')"> - {{ $t('users.sandbox') }} - <i v-if="scope.row.tags.includes('sandbox')" class="el-icon-check"/> - </el-dropdown-item> - <el-dropdown-item - v-if="scope.row.local" - :class="{ 'active-tag': scope.row.tags.includes('disable_remote_subscription') }" - @click.native="toggleTag(scope.row, 'disable_remote_subscription')"> - {{ $t('users.disableRemoteSubscription') }} - <i v-if="scope.row.tags.includes('disable_remote_subscription')" class="el-icon-check"/> - </el-dropdown-item> - <el-dropdown-item - v-if="scope.row.local" - :class="{ 'active-tag': scope.row.tags.includes('disable_any_subscription') }" - @click.native="toggleTag(scope.row, 'disable_any_subscription')"> - {{ $t('users.disableAnySubscription') }} - <i v-if="scope.row.tags.includes('disable_any_subscription')" class="el-icon-check"/> - </el-dropdown-item> - <el-dropdown-item - v-if="scope.row.local" - divided - @click.native="getPasswordResetToken(scope.row.nickname)"> - {{ $t('users.getPasswordResetToken') }} - </el-dropdown-item> - <el-dropdown-item - v-if="scope.row.local" - @click.native="requirePasswordReset(scope.row.nickname)"> - {{ $t('users.requirePasswordReset') }} - </el-dropdown-item> - </el-dropdown-menu> - </el-dropdown> + <moderation-dropdown + :user="scope.row" + :page="'users'" + @open-reset-token-dialog="openResetPasswordDialog"/> </template> </el-table-column> </el-table> @@ -191,13 +105,15 @@ import numeral from 'numeral' import UsersFilter from './components/UsersFilter' import MultipleUsersMenu from './components/MultipleUsersMenu' import NewAccountDialog from './components/NewAccountDialog' +import ModerationDropdown from './components/ModerationDropdown' export default { name: 'Users', components: { - UsersFilter, + NewAccountDialog, + ModerationDropdown, MultipleUsersMenu, - NewAccountDialog + UsersFilter }, data() { return { @@ -257,6 +173,10 @@ export default { clearSelection() { this.$refs.usersTable.clearSelection() }, + closeResetPasswordDialog() { + this.resetPasswordDialogOpen = false + this.$store.dispatch('RemovePasswordToken') + }, async createNewAccount(accountData) { await this.$store.dispatch('CreateNewAccount', accountData) this.createAccountDialogOpen = false @@ -264,29 +184,6 @@ export default { getFirstLetter(str) { return str.charAt(0).toUpperCase() }, - getPasswordResetToken(nickname) { - this.resetPasswordDialogOpen = true - this.$store.dispatch('GetPasswordResetToken', nickname) - }, - requirePasswordReset(nickname) { - const mailerEnabled = this.$store.state.user.nodeInfo.metadata.mailerEnabled - - if (!mailerEnabled) { - this.$alert(this.$t('users.mailerMustBeEnabled'), 'Error', { type: 'error' }) - - return - } - - this.$store.dispatch('RequirePasswordReset', { nicknames: [nickname] }) - }, - toggleActivation(user) { - user.deactivated - ? this.$store.dispatch('ActivateUsers', [user]) - : this.$store.dispatch('DeactivateUsers', [user]) - }, - handleDeletion(user) { - this.$store.dispatch('DeleteUsers', [user]) - }, handlePageChange(page) { const searchQuery = this.$store.state.users.searchQuery if (searchQuery === '') { @@ -298,31 +195,11 @@ export default { handleSelectionChange(value) { this.$data.selectedUsers = value }, - closeResetPasswordDialog() { - this.resetPasswordDialogOpen = false - this.$store.dispatch('RemovePasswordToken') - }, - showAdminAction({ local, id }) { - return local && this.showDeactivatedButton(id) + openResetPasswordDialog() { + this.resetPasswordDialogOpen = true }, showDeactivatedButton(id) { return this.$store.state.user.id !== id - }, - toggleTag(user, tag) { - user.tags.includes(tag) - ? this.$store.dispatch('RemoveTag', { users: [user], tag }) - : this.$store.dispatch('AddTag', { users: [user], tag }) - }, - toggleUserRight(user, right) { - user.roles[right] - ? this.$store.dispatch('DeleteRight', { users: [user], right }) - : this.$store.dispatch('AddRight', { users: [user], right }) - }, - handleEmailConfirmation(user) { - this.$store.dispatch('ConfirmUsersEmail', [user]) - }, - handleConfirmationResend(user) { - this.$store.dispatch('ResendConfirmationEmail', [user]) } } } diff --git a/src/views/users/show.vue b/src/views/users/show.vue index 2b1146de0e17a9db1e3aaf54f15f2b44953bdf47..aa826c07c715ed751aeafbb27c7397359e18e57c 100644 --- a/src/views/users/show.vue +++ b/src/views/users/show.vue @@ -1,9 +1,28 @@ <template> <main v-if="!userProfileLoading"> - <header> - <el-avatar :src="user.avatar" size="large" /> - <h1>{{ user.display_name }}</h1> + <header class="user-page-header"> + <div class="avatar-name-container"> + <el-avatar :src="user.avatar" size="large" /> + <h1>{{ user.display_name }}</h1> + </div> + <moderation-dropdown + :user="user" + :page="'userPage'" + @open-reset-token-dialog="openResetPasswordDialog"/> </header> + <el-dialog + v-loading="loading" + :visible.sync="resetPasswordDialogOpen" + :title="$t('users.passwordResetTokenCreated')" + custom-class="password-reset-token-dialog" + @close="closeResetPasswordDialog"> + <div> + <p class="password-reset-token">Password reset token was generated: {{ passwordResetToken }}</p> + <p>You can also use this link to reset password: + <a :href="passwordResetLink" target="_blank" class="reset-password-link">{{ passwordResetLink }}</a> + </p> + </div> + </el-dialog> <el-row> <el-col :span="8"> <el-card class="user-profile-card"> @@ -84,16 +103,27 @@ <script> import Status from '@/components/Status' +import ModerationDropdown from './components/ModerationDropdown' export default { name: 'UsersShow', - components: { Status }, + components: { ModerationDropdown, Status }, data() { return { - showPrivate: false + showPrivate: false, + resetPasswordDialogOpen: false } }, computed: { + loading() { + return this.$store.state.users.loading + }, + passwordResetLink() { + return this.$store.state.users.passwordResetToken.link + }, + passwordResetToken() { + return this.$store.state.users.passwordResetToken.token + }, statuses() { return this.$store.state.userProfile.statuses }, @@ -111,14 +141,25 @@ export default { this.$store.dispatch('FetchUserProfile', { userId: this.$route.params.id, godmode: false }) }, methods: { + closeResetPasswordDialog() { + this.resetPasswordDialogOpen = false + this.$store.dispatch('RemovePasswordToken') + }, onTogglePrivate() { this.$store.dispatch('FetchUserProfile', { userId: this.$route.params.id, godmode: this.showPrivate }) + }, + openResetPasswordDialog() { + this.resetPasswordDialogOpen = true } } } </script> <style rel='stylesheet/scss' lang='scss' scoped> +.avatar-name-container { + display: flex; + align-items: center; +} header { align-items: center; display: flex; @@ -151,7 +192,6 @@ table { .no-statuses { margin-left: 28px; color: #606266; - } .recent-statuses-header { margin-top: 10px; @@ -160,16 +200,24 @@ table { padding: 0 20px 0 0; } .show-private { - text-align: right; + width: 200px; + text-align: left; line-height: 67px; - padding-right: 20px; + margin-right: 20px; } .recent-statuses { margin-left: 28px; } +.user-page-header { + display: flex; + justify-content: space-between; + padding: 0 20px; + h1 { + display: inline + } +} .user-profile-card { - margin-left: 15px; - margin-right: 20px; + margin: 0 20px; } .user-profile-table { margin: 0;