Newer
Older
Eliot Berriot
committed
<template>
<main class="main pusher" v-title="labels.title">
Eliot Berriot
committed
<div class="ui vertical stripe segment">
<section class="ui small text container">
<translate :translate-context="'Content/Settings/Title'">Account settings</translate>
<form class="ui form" @submit.prevent="submitSettings()">
<div v-if="settings.success" class="ui positive message">
<translate :translate-context="'Content/Settings/Message'">Settings updated</translate>
</div>
<div v-if="settings.errors.length > 0" class="ui negative message">
<div class="header"><translate :translate-context="'Content/Settings/Error message.Title'">Your settings can't be updateds</translate></div>
<ul class="list">
<li v-for="error in settings.errors">{{ error }}</li>
</ul>
</div>
<div class="field" v-for="f in orderedSettingsFields">
<label>{{ sharedLabels.fields[f.id].label }}</label>
<p v-if="f.help">{{ sharedLabels.fields[f.id].help }}</p>
<select v-if="f.type === 'dropdown'" class="ui dropdown" v-model="f.value">
<option :value="c" v-for="c in f.choices">{{ sharedLabels.fields[f.id].choices[c] }}</option>
<button :class="['ui', {'loading': isLoading}, 'button']" type="submit">
<translate :translate-context="'Content/Settings/Button.Label/Verb'">Update settings</translate>
</section>
<section class="ui small text container">
<translate :translate-context="'Content/Settings/Title'">Avatar</translate>
</h2>
<div class="ui form">
<div v-if="avatarErrors.length > 0" class="ui negative message">
<div class="header"><translate :translate-context="'Content/Settings/Error message.Title'">Your avatar cannot be saved</translate></div>
<ul class="list">
<li v-for="error in avatarErrors">{{ error }}</li>
</ul>
</div>
<div class="ui stackable grid">
<div class="ui ten wide column">
<h3 class="ui header"><translate :translate-context="'Content/Settings/Title/Verb'">Upload a new avatar</translate></h3>
<p><translate :translate-context="'Content/Settings/Paragraph'">PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px.</translate></p>
<input class="ui input" ref="avatar" type="file" />
<div class="ui hidden divider"></div>
<button @click="submitAvatar" :class="['ui', {'loading': isLoadingAvatar}, 'button']">
<translate :translate-context="'Content/Settings/Button.Label/Verb'">Update avatar</translate>
</button>
</div>
<div class="ui six wide column">
<h3 class="ui header"><translate :translate-context="'Content/Settings/Title/Noun'">Current avatar</translate></h3>
<img class="ui circular image" v-if="currentAvatar && currentAvatar.square_crop" v-lazy="$store.getters['instance/absoluteUrl'](currentAvatar.medium_square_crop)" />
<div class="ui hidden divider"></div>
<button @click="removeAvatar" v-if="currentAvatar && currentAvatar.square_crop" :class="['ui', {'loading': isLoadingAvatar}, ,'yellow', 'button']">
<translate :translate-context="'Content/Settings/Button.Label/Verb'">Remove avatar</translate>
</section>
<section class="ui small text container">
<translate :translate-context="'Content/Settings/Title/Verb'">Change my password</translate>
Eliot Berriot
committed
<div class="ui message">
<translate :translate-context="'Content/Settings/Paragraph'">Changing your password will also change your Subsonic API password if you have requested one.</translate> <translate :translate-context="'Content/Settings/Paragraph'">You will have to update your password on your clients that use this password.</translate>
Eliot Berriot
committed
</div>
<form class="ui form" @submit.prevent="submitPassword()">
<div v-if="passwordError" class="ui negative message">
<translate :translate-context="'Content/Settings/Error message.Title'">Your password cannot be changed</translate>
Eliot Berriot
committed
<ul class="list">
<li v-if="passwordError == 'invalid_credentials'"><translate :translate-context="'Content/Settings/Error message.List item/Call to action'">Please double-check your password is correct</translate></li>
Eliot Berriot
committed
</ul>
</div>
<div class="field">
<label><translate :translate-context="'Content/Settings/Input.Label'">Old password</translate></label>
<password-input required v-model="old_password" />
Eliot Berriot
committed
</div>
<div class="field">
<label><translate :translate-context="'Content/Settings/Input.Label'">New password</translate></label>
<password-input required v-model="new_password" />
Eliot Berriot
committed
</div>
Eliot Berriot
committed
<dangerous-button
color="yellow"
:class="['ui', {'loading': isLoading}, 'button']"
:action="submitPassword">
<translate :translate-context="'Content/Settings/Button.Label'">Change password</translate>
<p slot="modal-header"><translate :translate-context="'Popup/Settings/Title'">Change your password?</translate></p>
Eliot Berriot
committed
<div slot="modal-content">
<p><translate :translate-context="'Popup/Settings/Paragraph'">Changing your password will have the following consequences:</translate></p>
Eliot Berriot
committed
<ul>
<li><translate :translate-context="'Popup/Settings/List item'">You will be logged out from this session and have to log in with the new one</translate></li>
<li><translate :translate-context="'Popup/Settings/List item'">Your Subsonic password will be changed to a new, random one, logging you out from devices that used the old Subsonic password</translate></li>
Eliot Berriot
committed
</ul>
</div>
<p slot="modal-confirm"><translate :translate-context="'Popup/Setting/Button.Label'">Disable access</translate></p>
Eliot Berriot
committed
</dangerous-button>
Eliot Berriot
committed
</form>
Eliot Berriot
committed
<div class="ui hidden divider" />
<subsonic-token-form />
</section>
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<section class="ui small text container" id="content-filters">
<div class="ui divider"></div>
<h2 class="ui header">
<i class="eye slash outline icon"></i>
<div class="content">
<translate>Content filters</translate>
</div>
</h2>
<p><translate>Content filters help you hide content you don't want to see on the service.</translate></p>
<button
@click="$store.dispatch('moderation/fetchContentFilters')"
class="ui basic icon button">
<i class="refresh icon"></i>
<translate :translate-context="'Content/*/Button.Label'">Refresh</translate>
</button>
<h3 class="ui header">
<translate>Hidden artists</translate>
</h3>
<table class="ui compact very basic fixed single line unstackable table">
<thead>
<tr>
<th><translate :translate-context="'Content/*/Table.Label'">Name</translate></th>
<th><translate :translate-context="'Content/*/Table.Label'">Creation date</translate></th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="filter in $store.getters['moderation/artistFilters']()" :key='filter.uuid'>
<td>
<router-link :to="{name: 'library.artists.detail', params: {id: filter.target.id }}">
{{ filter.target.name }}
</router-link>
</td>
<td>
<human-date :date="filter.creation_date"></human-date>
</td>
<td>
<button @click="$store.dispatch('moderation/deleteContentFilter', filter.uuid)" class="ui basic tiny button">
<translate :translate-context="'Content/*/Button.Label'">Delete</translate>
</button>
</td>
</tr>
</tbody>
</table>
</section>
Eliot Berriot
committed
</div>
</main>
Eliot Berriot
committed
</template>
<script>
import $ from "jquery"
import axios from "axios"
import logger from "@/logging"
import PasswordInput from "@/components/forms/PasswordInput"
import SubsonicTokenForm from "@/components/auth/SubsonicTokenForm"
import TranslationsMixin from "@/components/mixins/Translations"
Eliot Berriot
committed
export default {
mixins: [TranslationsMixin],
Eliot Berriot
committed
PasswordInput,
SubsonicTokenForm
data() {
Eliot Berriot
committed
// We need to initialize the component with any
// properties that will be used in it
old_password: "",
new_password: "",
currentAvatar: this.$store.state.auth.profile.avatar,
passwordError: "",
isLoadingAvatar: false,
avatarErrors: [],
avatar: null,
settings: {
success: false,
errors: [],
order: ["privacy_level"],
privacy_level: {
type: "dropdown",
initial: this.$store.state.auth.profile.privacy_level,
choices: ["me", "instance"]
Eliot Berriot
committed
}
d.settings.order.forEach(id => {
d.settings.fields[id].value = d.settings.fields[id].initial
d.settings.fields[id].id = id
mounted() {
$("select.dropdown").dropdown()
Eliot Berriot
committed
},
methods: {
submitSettings() {
this.settings.success = false
this.settings.errors = []
let self = this
let payload = this.settingsValues
let url = `users/users/${this.$store.state.auth.username}/`
return axios.patch(url, payload).then(
response => {
logger.default.info("Updated settings successfully")
self.settings.success = true
return axios.get("users/users/me/").then(response => {
self.$store.dispatch("auth/updateProfile", response.data)
})
},
error => {
logger.default.error("Error while updating settings")
self.isLoading = false
self.settings.errors = error.backendErrors
}
)
submitAvatar() {
this.isLoadingAvatar = true
this.avatarErrors = []
let self = this
this.avatar = this.$refs.avatar.files[0]
let formData = new FormData()
formData.append("avatar", this.avatar)
axios
.patch(`users/users/${this.$store.state.auth.username}/`, formData, {
"Content-Type": "multipart/form-data"
})
.then(
response => {
this.isLoadingAvatar = false
self.currentAvatar = response.data.avatar
self.$store.commit("auth/avatar", self.currentAvatar)
},
error => {
self.isLoadingAvatar = false
self.avatarErrors = error.backendErrors
}
)
removeAvatar() {
this.isLoadingAvatar = true
let self = this
this.avatar = null
axios
.patch(`users/users/${this.$store.state.auth.username}/`, {
avatar: null
})
.then(
response => {
this.isLoadingAvatar = false
self.currentAvatar = {}
self.$store.commit("auth/avatar", self.currentAvatar)
},
error => {
self.isLoadingAvatar = false
self.avatarErrors = error.backendErrors
}
)
submitPassword() {
Eliot Berriot
committed
var self = this
self.isLoading = true
this.error = ""
Eliot Berriot
committed
var credentials = {
old_password: this.old_password,
new_password1: this.new_password,
new_password2: this.new_password
}
let url = "auth/registration/change-password/"
return axios.post(url, credentials).then(
response => {
logger.default.info("Password successfully changed")
self.$router.push({
name: "profile",
params: {
username: self.$store.state.auth.username
}
})
},
error => {
if (error.response.status === 400) {
self.passwordError = "invalid_credentials"
} else {
self.passwordError = "unknown_error"
}
self.isLoading = false
Eliot Berriot
committed
}
)
Eliot Berriot
committed
}
labels() {
title: this.$pgettext('Head/Settings/Title', "Account Settings")
orderedSettingsFields() {
let self = this
return this.settings.order.map(id => {
return self.settings.fields[id]
})
},
settingsValues() {
let self = this
let s = {}
this.settings.order.forEach(setting => {
let conf = self.settings.fields[setting]
s[setting] = conf.value
})
return s
}
Eliot Berriot
committed
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>