diff --git a/front/src/components/auth/Login.vue b/front/src/components/auth/Login.vue deleted file mode 100644 index 20aff3236bd797e0c03a50ce9ec36b29287e20c9..0000000000000000000000000000000000000000 --- a/front/src/components/auth/Login.vue +++ /dev/null @@ -1,122 +0,0 @@ -<template> - <main class="main pusher" v-title="labels.title"> - <section class="ui vertical stripe segment"> - <div class="ui small text container"> - <h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2> - <form class="ui form" @submit.prevent="submit()"> - <div v-if="error" class="ui negative message"> - <div class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></div> - <ul class="list"> - <li v-if="error == 'invalid_credentials'"><translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check your username/password couple is correct</translate></li> - <li v-else><translate translate-context="Content/Login/Error message/List item">An unknown error happend, this can mean the server is down or cannot be reached</translate></li> - </ul> - </div> - <div class="field"> - <label> - <translate translate-context="Content/Login/Input.Label/Noun">Username or email</translate> | - <router-link :to="{path: '/signup'}"> - <translate translate-context="*/Signup/Link/Verb">Create an account</translate> - </router-link> - </label> - <input - ref="username" - tabindex="1" - required - name="username" - type="text" - autofocus - :placeholder="labels.usernamePlaceholder" - v-model="credentials.username" - > - </div> - <div class="field"> - <label> - <translate translate-context="Content/*/Input.Label">Password</translate> | - <router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}"> - <translate translate-context="*/Login/*/Verb">Reset your password</translate> - </router-link> - </label> - <password-input :index="2" required v-model="credentials.password" /> - - </div> - <button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> - <translate translate-context="*/Login/*/Verb">Login</translate> - </button> - </form> - </div> - </section> - </main> -</template> - -<script> -import PasswordInput from "@/components/forms/PasswordInput" - -export default { - props: { - next: { type: String, default: "/library" } - }, - components: { - PasswordInput - }, - data() { - return { - // We need to initialize the component with any - // properties that will be used in it - credentials: { - username: "", - password: "" - }, - error: "", - isLoading: false - } - }, - created () { - if (this.$store.state.auth.authenticated) { - this.$router.push(this.next) - } - }, - mounted() { - this.$refs.username.focus() - }, - computed: { - labels() { - let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or email") - let title = this.$pgettext('Head/Login/Title', "Log In") - return { - usernamePlaceholder, - title - } - } - }, - methods: { - submit() { - var self = this - self.isLoading = true - this.error = "" - var credentials = { - username: this.credentials.username, - password: this.credentials.password - } - this.$store - .dispatch("auth/login", { - credentials, - next: this.next, - onError: error => { - if (error.response.status === 400) { - self.error = "invalid_credentials" - } else { - self.error = "unknown_error" - } - } - }) - .then(e => { - self.isLoading = false - }) - } - } -} -</script> - -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped> -</style> diff --git a/front/src/components/auth/LoginForm.vue b/front/src/components/auth/LoginForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..e21b33d041432d8e49bdc69070d46645020ff9b1 --- /dev/null +++ b/front/src/components/auth/LoginForm.vue @@ -0,0 +1,114 @@ +<template> + <form class="ui form" @submit.prevent="submit()"> + <slot name="title"></slot> + <div v-if="error" class="ui negative message"> + <div class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></div> + <ul class="list"> + <li v-if="error == 'invalid_credentials'"><translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check your username/password couple is correct</translate></li> + <li v-else><translate translate-context="Content/Login/Error message/List item">An unknown error happend, this can mean the server is down or cannot be reached</translate></li> + </ul> + </div> + <div class="field"> + <label> + <translate translate-context="Content/Login/Input.Label/Noun">Username or email</translate> | + <router-link :to="{path: '/signup'}"> + <translate translate-context="*/Signup/Link/Verb">Create an account</translate> + </router-link> + </label> + <input + ref="username" + tabindex="1" + required + name="username" + type="text" + autofocus + :placeholder="labels.usernamePlaceholder" + v-model="credentials.username" + > + </div> + <div class="field"> + <label> + <translate translate-context="Content/*/Input.Label">Password</translate> | + <router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}"> + <translate translate-context="*/Login/*/Verb">Reset your password</translate> + </router-link> + </label> + <password-input :index="2" required v-model="credentials.password" /> + + </div> + <button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit"> + <translate translate-context="*/Login/*/Verb">Login</translate> + </button> + </form> +</template> + +<script> +import PasswordInput from "@/components/forms/PasswordInput" + +export default { + props: { + next: { type: String, default: "/library" } + }, + components: { + PasswordInput + }, + data() { + return { + // We need to initialize the component with any + // properties that will be used in it + credentials: { + username: "", + password: "" + }, + error: "", + isLoading: false + } + }, + created () { + if (this.$store.state.auth.authenticated) { + this.$router.push(this.next) + } + }, + mounted() { + this.$refs.username.focus() + }, + computed: { + labels() { + let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or email") + return { + usernamePlaceholder, + } + } + }, + methods: { + submit() { + var self = this + self.isLoading = true + this.error = "" + var credentials = { + username: this.credentials.username, + password: this.credentials.password + } + this.$store + .dispatch("auth/login", { + credentials, + next: this.next, + onError: error => { + if (error.response.status === 400) { + self.error = "invalid_credentials" + } else { + self.error = "unknown_error" + } + } + }) + .then(e => { + self.isLoading = false + }) + } + } +} +</script> + +<!-- Add "scoped" attribute to limit CSS to this component only --> +<style scoped> +</style> diff --git a/front/src/components/auth/Signup.vue b/front/src/components/auth/Signup.vue deleted file mode 100644 index 974d082bda98153009cc2e9f35cd4c0e67ab16fd..0000000000000000000000000000000000000000 --- a/front/src/components/auth/Signup.vue +++ /dev/null @@ -1,146 +0,0 @@ -<template> - <main class="main pusher" v-title="labels.title"> - <section class="ui vertical stripe segment"> - <div class="ui small text container"> - <h2><translate translate-context="Content/Signup/Title">Create a funkwhale account</translate></h2> - <form - :class="['ui', {'loading': isLoadingInstanceSetting}, 'form']" - @submit.prevent="submit()"> - <p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value"> - <translate translate-context="Content/Signup/Form/Paragraph">Registration are closed on this instance, you will need an invitation code to signup.</translate> - </p> - - <div v-if="errors.length > 0" class="ui negative message"> - <div class="header"><translate translate-context="Content/Signup/Form/Paragraph">Your account cannot be created.</translate></div> - <ul class="list"> - <li v-for="error in errors">{{ error }}</li> - </ul> - </div> - <div class="field"> - <label><translate translate-context="Content/*/*">Username</translate></label> - <input - ref="username" - name="username" - required - type="text" - autofocus - :placeholder="labels.usernamePlaceholder" - v-model="username"> - </div> - <div class="field"> - <label><translate translate-context="Content/*/*/Noun">Email</translate></label> - <input - ref="email" - name="email" - required - type="email" - :placeholder="labels.emailPlaceholder" - v-model="email"> - </div> - <div class="field"> - <label><translate translate-context="Content/*/Input.Label">Password</translate></label> - <password-input v-model="password" /> - </div> - <div class="field" v-if="!$store.state.instance.settings.users.registration_enabled.value"> - <label><translate translate-context="Content/*/Input.Label">Invitation code</translate></label> - <input - required - type="text" - name="invitation" - :placeholder="labels.placeholder" - v-model="invitation"> - </div> - <button :class="['ui', 'green', {'loading': isLoading}, 'button']" type="submit"> - <translate translate-context="Content/Signup/Button.Label">Create my account</translate> - </button> - </form> - </div> - </section> - </main> -</template> - -<script> -import axios from "axios" -import logger from "@/logging" - -import PasswordInput from "@/components/forms/PasswordInput" - -export default { - props: { - defaultInvitation: { type: String, required: false, default: null }, - next: { type: String, default: "/" } - }, - components: { - PasswordInput - }, - data() { - return { - username: "", - email: "", - password: "", - isLoadingInstanceSetting: true, - errors: [], - isLoading: false, - invitation: this.defaultInvitation - } - }, - created() { - let self = this - this.$store.dispatch("instance/fetchSettings", { - callback: function() { - self.isLoadingInstanceSetting = false - } - }) - }, - computed: { - labels() { - let title = this.$pgettext("*/Signup/Title", "Sign Up") - let placeholder = this.$pgettext( - "Content/Signup/Form/Placeholder", - "Enter your invitation code (case insensitive)" - ) - let usernamePlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your username") - let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email") - return { - title, - usernamePlaceholder, - emailPlaceholder, - placeholder - } - } - }, - methods: { - submit() { - var self = this - self.isLoading = true - this.errors = [] - var payload = { - username: this.username, - password1: this.password, - password2: this.password, - email: this.email, - invitation: this.invitation - } - return axios.post("auth/registration/", payload).then( - response => { - logger.default.info("Successfully created account") - self.$router.push({ - name: "profile", - params: { - username: this.username - } - }) - }, - error => { - self.errors = error.backendErrors - self.isLoading = false - } - ) - } - } -} -</script> - -<!-- Add "scoped" attribute to limit CSS to this component only --> -<style scoped> -</style> diff --git a/front/src/components/auth/SignupForm.vue b/front/src/components/auth/SignupForm.vue new file mode 100644 index 0000000000000000000000000000000000000000..57c8a0c8de3479b5c3b61e6dbba501a198caa43a --- /dev/null +++ b/front/src/components/auth/SignupForm.vue @@ -0,0 +1,134 @@ +<template> + <form + :class="['ui', {'loading': isLoadingInstanceSetting}, 'form']" + @submit.prevent="submit()"> + <slot name="title"></slot> + <p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value"> + <translate translate-context="Content/Signup/Form/Paragraph">Registration are closed on this instance, you will need an invitation code to signup.</translate> + </p> + + <div v-if="errors.length > 0" class="ui negative message"> + <div class="header"><translate translate-context="Content/Signup/Form/Paragraph">Your account cannot be created.</translate></div> + <ul class="list"> + <li v-for="error in errors">{{ error }}</li> + </ul> + </div> + <div class="field"> + <label><translate translate-context="Content/*/*">Username</translate></label> + <input + ref="username" + name="username" + required + type="text" + autofocus + :placeholder="labels.usernamePlaceholder" + v-model="username"> + </div> + <div class="field"> + <label><translate translate-context="Content/*/*/Noun">Email</translate></label> + <input + ref="email" + name="email" + required + type="email" + :placeholder="labels.emailPlaceholder" + v-model="email"> + </div> + <div class="field"> + <label><translate translate-context="Content/*/Input.Label">Password</translate></label> + <password-input v-model="password" /> + </div> + <div class="field" v-if="!$store.state.instance.settings.users.registration_enabled.value"> + <label><translate translate-context="Content/*/Input.Label">Invitation code</translate></label> + <input + required + type="text" + name="invitation" + :placeholder="labels.placeholder" + v-model="invitation"> + </div> + <button :class="['ui', 'green', {'loading': isLoading}, 'button']" type="submit"> + <translate translate-context="Content/Signup/Button.Label">Create my account</translate> + </button> + </form> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" + +import PasswordInput from "@/components/forms/PasswordInput" + +export default { + props: { + defaultInvitation: { type: String, required: false, default: null }, + next: { type: String, default: "/" } + }, + components: { + PasswordInput + }, + data() { + return { + username: "", + email: "", + password: "", + isLoadingInstanceSetting: true, + errors: [], + isLoading: false, + invitation: this.defaultInvitation + } + }, + created() { + let self = this + this.$store.dispatch("instance/fetchSettings", { + callback: function() { + self.isLoadingInstanceSetting = false + } + }) + }, + computed: { + labels() { + let placeholder = this.$pgettext( + "Content/Signup/Form/Placeholder", + "Enter your invitation code (case insensitive)" + ) + let usernamePlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your username") + let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email") + return { + usernamePlaceholder, + emailPlaceholder, + placeholder + } + } + }, + methods: { + submit() { + var self = this + self.isLoading = true + this.errors = [] + var payload = { + username: this.username, + password1: this.password, + password2: this.password, + email: this.email, + invitation: this.invitation + } + return axios.post("auth/registration/", payload).then( + response => { + logger.default.info("Successfully created account") + self.$router.push({ + name: "profile", + params: { + username: this.username + } + }) + }, + error => { + self.errors = error.backendErrors + self.isLoading = false + } + ) + } + } +} +</script> diff --git a/front/src/router/index.js b/front/src/router/index.js index 6a9ba6112f7680766b22945bfd41097db3f143aa..ed46cc5944bcd7069763cb210c5e15b039ab6540 100644 --- a/front/src/router/index.js +++ b/front/src/router/index.js @@ -30,7 +30,7 @@ export default new Router({ path: '/login', name: 'login', component: () => - import(/* webpackChunkName: "core" */ "@/components/auth/Login"), + import(/* webpackChunkName: "core" */ "@/views/auth/Login"), props: (route) => ({ next: route.query.next || '/library' }) }, { @@ -85,7 +85,7 @@ export default new Router({ path: '/signup', name: 'signup', component: () => - import(/* webpackChunkName: "core" */ "@/components/auth/Signup"), + import(/* webpackChunkName: "core" */ "@/views/auth/Signup"), props: (route) => ({ defaultInvitation: route.query.invitation }) diff --git a/front/src/views/auth/Login.vue b/front/src/views/auth/Login.vue new file mode 100644 index 0000000000000000000000000000000000000000..07d796131d63b1202c3a7ba502b8cb55a4e47eb5 --- /dev/null +++ b/front/src/views/auth/Login.vue @@ -0,0 +1,32 @@ +<template> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical stripe segment"> + <div class="ui small text container"> + <h2 slot="title"><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2> + <login-form :next="next"></login-form> + </div> + </section> + </main> +</template> + +<script> +import LoginForm from "@/components/auth/LoginForm" +import PasswordInput from "@/components/forms/PasswordInput" + +export default { + components: { + LoginForm + }, + props: { + next: { type: String, default: "/library" } + }, + computed: { + labels() { + let title = this.$pgettext('Head/Login/Title', "Log In") + return { + title + } + } + }, +} +</script> diff --git a/front/src/views/auth/Signup.vue b/front/src/views/auth/Signup.vue new file mode 100644 index 0000000000000000000000000000000000000000..51849030a4f4dfb790333698a83e7bd568821c0a --- /dev/null +++ b/front/src/views/auth/Signup.vue @@ -0,0 +1,36 @@ +<template> + <main class="main pusher" v-title="labels.title"> + <section class="ui vertical stripe segment"> + <div class="ui small text container"> + <signup-form :default-invitation="defaultInvitation" :next="next"> + <h2 slot="title"><translate translate-context="Content/Signup/Title">Create a funkwhale account</translate></h2> + </signup-form> + </div> + </section> + </main> +</template> + +<script> +import axios from "axios" +import logger from "@/logging" + +import SignupForm from "@/components/auth/SignupForm" + +export default { + components: { + SignupForm + }, + props: { + defaultInvitation: { type: String, required: false, default: null }, + next: { type: String, default: "/" } + }, + computed: { + labels() { + let title = this.$pgettext("*/Signup/Title", "Sign Up") + return { + title, + } + } + } +} +</script>