<template> <main v-if="!userProfileLoading"> <header v-if="isDesktop || isTablet" class="user-page-header"> <div class="avatar-name-container"> <el-avatar v-if="propertyExists(user, 'avatar')" :src="user.avatar" size="large" /> <h1 v-if="propertyExists(user, 'nickname')">{{ user.nickname }}</h1> <h1 v-else class="invalid">({{ $t('users.invalidNickname') }})</h1> <a v-if="propertyExists(user, 'url')" :href="user.url" target="_blank"> <i :title="$t('userProfile.openAccountInInstance')" class="el-icon-top-right"/> </a> </div> <div class="left-header-container"> <moderation-dropdown v-if="propertyExists(user, 'nickname')" :user="user" :page="'userPage'" @open-reset-token-dialog="openResetPasswordDialog"/> <reboot-button/> </div> </header> <div v-if="isMobile" class="user-page-header-container"> <header class="user-page-header"> <div class="avatar-name-container"> <el-avatar v-if="propertyExists(user, 'avatar')" :src="user.avatar" size="large" /> <h1 v-if="propertyExists(user, 'nickname')">{{ user.nickname }}</h1> <h1 v-else class="invalid">({{ $t('users.invalidNickname') }})</h1> </div> <reboot-button/> </header> <moderation-dropdown v-if="propertyExists(user, 'nickname')" :user="user" :page="'userPage'" @open-reset-token-dialog="openResetPasswordDialog"/> </div> <reset-password-dialog :reset-password-dialog-open="resetPasswordDialogOpen" @close-reset-token-dialog="closeResetPasswordDialog"/> <div class="user-profile-container"> <div class="user-cards-container"> <el-card class="user-profile-card"> <div class="el-table el-table--fit el-table--enable-row-hover el-table--enable-row-transition el-table--medium"> <el-tag v-if="!propertyExists(user, 'nickname')" type="info" class="invalid-user-tag"> {{ $t('users.invalidAccount') }} </el-tag> <table class="user-profile-table"> <tbody> <tr class="el-table__row"> <td class="name-col">ID</td> <td> {{ user.id }} </td> </tr> <tr class="el-table__row"> <td>{{ $t('userProfile.actorType') }}</td> <td> <el-tag :type="userCredentials.actor_type === 'Person' ? 'success' : 'warning'"> {{ userCredentials.actor_type }} </el-tag> </td> </tr> <tr class="el-table__row"> <td>{{ $t('userProfile.tags') }}</td> <td> <span v-if="user.tags.length === 0 || !propertyExists(user, 'tags')">—</span> <el-tag v-for="tag in user.tags" v-else :key="tag" class="user-profile-tag">{{ humanizeTag(tag) }}</el-tag> </td> </tr> <tr class="el-table__row"> <td>{{ $t('userProfile.roles') }}</td> <td> <el-tag v-if="user.roles.admin" class="user-profile-tag"> {{ $t('users.admin') }} </el-tag> <el-tag v-if="user.roles.moderator" class="user-profile-tag"> {{ $t('users.moderator') }} </el-tag> <span v-if="!propertyExists(user, 'roles') || (!user.roles.moderator && !user.roles.admin)">—</span> </td> </tr> <tr class="el-table__row"> <td>{{ $t('userProfile.accountType') }}</td> <td> <el-tag v-if="user.local" type="info">{{ $t('userProfile.local') }}</el-tag> <el-tag v-if="!user.local" type="info">{{ $t('userProfile.external') }}</el-tag> </td> </tr> <tr class="el-table__row"> <td>{{ $t('userProfile.status') }}</td> <td> <el-tag v-if="user.approval_pending" type="info">{{ $t('userProfile.pending') }}</el-tag> <el-tag v-if="!user.deactivated & !user.approval_pending" type="success">{{ $t('userProfile.active') }}</el-tag> <el-tag v-if="user.deactivated" type="danger">{{ $t('userProfile.deactivated') }}</el-tag> </td> </tr> </tbody> </table> <div v-if="user.registration_reason"> <div class="reason-label">{{ $t('userProfile.reason') }}</div> "{{ user.registration_reason }}" </div> </div> <el-button v-if="propertyExists(user, 'nickname')" icon="el-icon-lock" class="security-setting-button" @click="securitySettingsModalVisible = true"> {{ $t('userProfile.securitySettings.securitySettings') }} </el-button> <SecuritySettingsModal v-if="propertyExists(user, 'nickname')" :user="user" :visible="securitySettingsModalVisible" @close="securitySettingsModalVisible = false" /> </el-card> <el-card class="user-chats-card"> <h2 class="chats">{{ $t('userProfile.chats') }}</h2> <div class="el-table el-table--fit el-table--enable-row-hover el-table--enable-row-transition el-table--medium"> <table class="user-chats-table"> <tbody v-if="!chatsLoading" class="chats"> <tr v-if="chats.length === 0" class="no-statuses"> {{ $t('userProfile.noChats') }} </tr> <tr v-for="chat in chats" :key="chat.id" class="el-table__row chat-item"> <td> <router-link v-if="propertyExists(chat, 'id')" :to="{ name: 'ChatsShow', params: { id: chat.id }}" class="router-link"> <div class="chat-card-header"> <img v-if="propertyExists(chat.receiver, 'avatar')" :src="chat.receiver.avatar" class="chat-avatar-img"> <span v-if="propertyExists(chat.receiver, 'username')" class="chat-account-name">{{ chat.receiver.username }}</span> <span v-else> <span v-if="propertyExists(chat.receiver, 'username')" class="chat-account-name"> {{ chat.receiver.username }} </span> <span v-else class="chat-account-name deactivated">({{ $t('users.invalidNickname') }})</span> </span> </div> <div class="chat-card-preview"> <span v-if="propertyExists(chat, 'last_message')" class="chat-preview">{{ chat.last_message.content }}</span> </div> </router-link> </td> </tr> </tbody> </table> </div> </el-card> </div> <div class="recent-statuses-container"> <h2 class="recent-statuses">{{ $t('userProfile.recentStatuses') }}</h2> <el-checkbox v-model="showPrivate" class="show-private-statuses" @change="onTogglePrivate"> {{ $t('statuses.showPrivateStatuses') }} </el-checkbox> <el-timeline v-if="!statusesLoading" class="statuses"> <el-timeline-item v-for="status in statuses" :key="status.id"> <status :status="status" :account="status.account" :show-checkbox="false" :user-id="user.id" :godmode="showPrivate"/> </el-timeline-item> <p v-if="statuses.length === 0" class="no-statuses">{{ $t('userProfile.noStatuses') }}</p> </el-timeline> </div> </div> </main> </template> <script> import Status from '@/components/Status' import ModerationDropdown from './components/ModerationDropdown' import SecuritySettingsModal from './components/SecuritySettingsModal' import RebootButton from '@/components/RebootButton' import ResetPasswordDialog from './components/ResetPasswordDialog' export default { name: 'UsersShow', components: { ModerationDropdown, RebootButton, ResetPasswordDialog, Status, SecuritySettingsModal }, data() { return { showPrivate: false, resetPasswordDialogOpen: false, securitySettingsModalVisible: false } }, computed: { isDesktop() { return this.$store.state.app.device === 'desktop' }, isMobile() { return this.$store.state.app.device === 'mobile' }, isTablet() { return this.$store.state.app.device === 'tablet' }, loading() { return this.$store.state.users.loading }, statuses() { return this.$store.state.userProfile.statuses }, statusesLoading() { return this.$store.state.userProfile.statusesLoading }, chats() { return this.$store.state.userProfile.chats }, chatsLoading() { return this.$store.state.userProfile.chatsLoading }, user() { return this.$store.state.userProfile.user }, userProfileLoading() { return this.$store.state.userProfile.userProfileLoading }, userCredentials() { return this.$store.state.userProfile.userCredentials } }, mounted: function() { this.$store.dispatch('NeedReboot') this.$store.dispatch('GetNodeInfo') this.$store.dispatch('FetchUserProfile', { userId: this.$route.params.id, godmode: false }) }, methods: { closeResetPasswordDialog() { this.resetPasswordDialogOpen = false this.$store.dispatch('RemovePasswordToken') }, humanizeTag(tag) { const mapTags = { 'mrf_tag:media-force-nsfw': 'Force NSFW', 'mrf_tag:media-strip': 'Strip Media', 'mrf_tag:force-unlisted': 'Force Unlisted', 'mrf_tag:sandbox': 'Sandbox', 'mrf_tag:disable-remote-subscription': 'Disable remote subscription', 'mrf_tag:disable-any-subscription': 'Disable any subscription' } return mapTags[tag] }, onTogglePrivate() { this.$store.dispatch('FetchUserProfile', { userId: this.$route.params.id, godmode: this.showPrivate }) }, openResetPasswordDialog() { this.resetPasswordDialogOpen = true }, propertyExists(account, property) { return account[property] } } } </script> <style rel='stylesheet/scss' lang='scss'> header { align-items: center; display: flex; margin: 22px 0; padding-left: 15px; h1 { margin: 0 0 0 10px; } } table { margin: 10px 0 0 15px; .name-col { width: 150px; } } .avatar-name-container { display: flex; align-items: center; .el-icon-top-right { font-size: 2em; line-height: 36px; color: #606266; } } .invalid { color: gray; } .el-table--border::after, .el-table--group::after, .el-table::before { background-color: transparent; } .image { width: 20%; img { width: 100%; } } .invalid-user-tag { font-size: 14px; width: inherit; height: auto; text-align: center; word-wrap: break-word; white-space: normal; } .left-header-container { align-items: center; display: flex; justify-content: space-between; } .no-statuses { margin-left: 28px; color: #606266; } .password-reset-token { margin: 0 0 14px 0; } .password-reset-token-dialog { width: 50% } .poll ul { list-style-type: none; padding: 0; width: 30%; } .reboot-button { padding: 10px; margin-left: 10px; } .recent-statuses-container { display: flex; flex-direction: column; width: 67%; } .recent-statuses-header { margin-top: 10px; } .reset-password-link { text-decoration: underline; } .security-setting-button { margin-top: 20px; width: 100%; } .statuses { padding: 0 20px 0 0; } .show-private { width: 200px; text-align: left; line-height: 67px; margin-right: 20px; } .show-private-statuses { margin-left: 28px; margin-bottom: 20px; } .recent-statuses { margin-left: 28px; } .user-page-header { display: flex; justify-content: space-between; margin: 22px 15px 22px 20px; padding: 0; align-items: center; h1 { display: inline } } .user-cards-container { display: flex; flex-direction: column; width: 30%; min-width: 300px; margin: 0 20px; } .user-profile-card { height: fit-content; width: auto; margin-bottom: 20px; } .user-chats-card { width: auto; height: fit-content; margin-bottom: 20px; } .user-profile-container { display: flex; } .user-profile-table { margin: 0; width: inherit; } .user-chats-table { width: 100%; } .user-profile-tag { margin: 0 4px 4px 0; } .reason-label { color: #878d99; font-weight: bold; margin: 5px 0; } .chat-card-header { display: flex; align-items: center; } .chat-avatar-img { display: inline-block; width: 15px; height: 15px; margin-right: 5px; } .chat-account-name { display: inline-block; margin: 0; font-size: 15px; font-weight: 500; } .chat-card-preview { color: gray; font-style: italic; margin: 5px 0 0 20px; } @media only screen and (max-width:480px) { .avatar-name-container { margin-bottom: 10px; } .el-timeline-item__wrapper { padding-left: 18px; } .password-reset-token-dialog { width: 85% } .recent-statuses { margin: 20px 10px 15px 10px; } .recent-statuses-container { width: 100%; margin: 0; } .show-private-statuses { margin: 0 10px 20px 10px; } .status-container { margin: 0 10px; } .statuses { padding-right: 10px; margin-left: 8px; } .user-page-header { padding: 0; margin: 7px 15px 15px 10px; } .user-page-header-container { .el-dropdown { width: 95%; margin: 0 15px 15px 10px; } } .user-profile-card, .user-chats-card { margin: 0 10px 20px; width: 95%; td { width: 80px; } } .user-profile-container { flex-direction: column; } .user-cards-container { width: 100%; margin: 0; } } @media only screen and (max-width:801px) and (min-width: 481px) { .recent-statuses { margin: 20px 10px 15px 0; } .recent-statuses-container { width: 97%; margin: 0 20px; } .show-private-statuses { margin: 0 10px 20px 0; } .user-page-header { padding: 0; margin: 7px 15px 20px 20px; } .user-profile-container { flex-direction: column; } .user-cards-container { width: 66%; padding-left: 28px; } } </style>