diff --git a/frontend/src/components/BaseLayout.vue b/frontend/src/components/BaseLayout.vue index 21b3d75..6fb817c 100644 --- a/frontend/src/components/BaseLayout.vue +++ b/frontend/src/components/BaseLayout.vue @@ -6,6 +6,11 @@ <a class="sidebar-toggle d-flex" @click="toggleSidebar"> <i class="hamburger align-self-center"></i> </a> + <div class="navbar-collapse collapse"> + <ul class="navbar-nav navbar-align"> + <UserDropdown/> + </ul> + </div> </nav> <slot></slot> <Footer/> @@ -14,12 +19,15 @@ </template> <script> +/* global closeAllDropdowns */ import Footer from "@/components/Footer.vue"; import Sidebar from "@/components/Sidebar.vue"; +import UserDropdown from "@/components/UserDropdown.vue"; export default { name: 'BaseLayout', components: { + UserDropdown, Footer, Sidebar }, @@ -68,6 +76,10 @@ export default { box-shadow: 0 0 2rem var(--bs-shadow) } +.navbar-align { + margin-left: auto; +} + .navbar-bg { background: var(--bs-white); } diff --git a/frontend/src/components/UserDropdown.vue b/frontend/src/components/UserDropdown.vue new file mode 100644 index 0000000..92bfdc2 --- /dev/null +++ b/frontend/src/components/UserDropdown.vue @@ -0,0 +1,47 @@ +<template> + <li class="nav-item dropdown"> + <a class="nav-icon dropdown-toggle d-inline-block d-sm-none" href="#" @click="toggleDropdown"> + <i class="align-middle" data-feather="settings"></i> + <b-icon-person class="bi-valign-middle"></b-icon-person> + </a> + + <a class="nav-link dropdown-toggle d-none d-sm-inline-block" href="#" @click="toggleDropdown"> + <span class="text-dark"> + {{ username }} + </span> + </a> + + <div class="dropdown-menu dropdown-menu-right" id="userDropdown"> + <a class="dropdown-item" href="#" @click="logout"> Log out</a> + </div> + </li> +</template> + +<script> +import * as BIcons from 'bootstrap-icons-vue'; +import {mapMutations, mapState} from "vuex"; + +export default { + name: "UserDropdown", + components: { + ...BIcons + }, + methods: { + ...mapMutations(['logout']), + toggleDropdown() { + closeAllDropdowns(); + document.getElementById("userDropdown").classList.toggle("show"); + } + }, + computed: { + ...mapState(['user']), + username() { + return this.user; + }, + } +} +</script> + +<style scoped> + +</style> \ No newline at end of file diff --git a/frontend/src/router.js b/frontend/src/router.js index 1602093..9d8f707 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -2,6 +2,7 @@ import {createRouter, createWebHistory} from 'vue-router' import Index from '@/views/Index.vue'; import Login from '@/views/Login.vue'; import Register from '@/views/Register.vue'; +import store from '@/store'; const routes = [ @@ -20,7 +21,7 @@ const router = createRouter({ router.beforeEach((to/*, from*/) => { // instead of having to check every route record with // to.matched.some(record => record.meta.requiresAuth) - if (to.meta.requiresAuth && false) { + if (to.meta.requiresAuth && !store.getters.isLoggedIn) { // this route requires auth, check if logged in // if not, redirect to login page. console.log("Not logged in, redirecting to login page") diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 7a2097b..b53ec8e 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -24,7 +24,9 @@ <label class="form-label">Username</label> <input class="form-control form-control-lg" type="text" name="username" placeholder="Enter your username" + :class="errors.username?['is-invalid']:[]" v-model="username"/> + <div class="invalid-feedback">{{ errors.username }}</div> </div> <div class="mb-3"> <label class="form-label">Password</label> @@ -65,6 +67,7 @@ </template> <script> +import {mapActions, mapMutations} from 'vuex'; import router from "@/router"; export default { @@ -74,15 +77,15 @@ export default { msg: 'Welcome to ' + location.hostname, username: '', password: '', - remember: false + remember: false, + errors: { + username: '', + } } }, methods: { - setRemember(remember) { - }, - login(data) { - return true; - }, + ...mapActions(['login']), + ...mapMutations(['setRemember']), async do_login(e) { e.preventDefault(); if (await this.login({username: this.username, password: this.password, remember: this.remember})) { @@ -92,11 +95,24 @@ export default { await router.push({path: '/'}); } } else { + if (!this.username.includes('@')) { + this.errors.username = 'Username should be in the form of user@domain'; + } this.msg = 'Invalid username or password'; } }, + }, + mounted() { + if (this.$route.query.redirect) { + this.msg = 'You must be logged in to access that page'; + } + const user = localStorage.getItem('user'); + if (user) { + this.username = user; + } + } } </script> diff --git a/frontend/src/views/Register.vue b/frontend/src/views/Register.vue index f0091d0..4f0d05a 100644 --- a/frontend/src/views/Register.vue +++ b/frontend/src/views/Register.vue @@ -44,6 +44,7 @@ <div :class="errors.email?['mb-3','is-invalid']:['mb-3']"> <label class="form-label">Email</label> <input class="form-control form-control-lg" type="email" + :class="errors.email?['is-invalid']:[]" v-model="form.email" placeholder="Enter your email"/> <div class="invalid-feedback">{{ errors.email }}</div> </div> @@ -51,6 +52,7 @@ <div :class="errors.password?['mb-3','is-invalid']:['mb-3']"> <label class="form-label">Password</label> <input class="form-control form-control-lg" type="password" + :class="errors.password?['is-invalid']:[]" v-model="form.password" placeholder="Enter your password"/> <div class="invalid-feedback">{{ errors.password }}</div> </div> @@ -58,6 +60,7 @@ <div :class="errors.password2?['mb-3','is-invalid']:['mb-3']"> <label class="form-label">Password Check</label> <input class="form-control form-control-lg" type="password" + :class="errors.password2?['is-invalid']:[]" v-model="password2" placeholder="Enter your password again"/> <div class="invalid-feedback">{{ errors.password2 }}</div> </div> @@ -86,6 +89,10 @@ </template> <script> +import {mapActions} from 'vuex'; +import {ServerSet, createNullAuth} from '../federation'; +import NeighborsCache from '../neigbors'; + export default { name: 'Register', data() { @@ -109,6 +116,7 @@ export default { } }, methods: { + ...mapActions(['lookupServer']), do_register() { console.log('do_register'); console.log(this.form); @@ -118,36 +126,34 @@ export default { } else { this.errors.password2 = null; } - fetch('/auth/register/', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(this.form) - }) - .then(response => response.json()) - .then(data => { - if (data.errors) { - console.error('Error:', data.errors); - this.errors = data.errors; + const user = this.form.username + '@' + this.form.domain; + var unreachable = new NeighborsCache(); + var nullAuth = createNullAuth({}) + this.lookupServer({username: user}).then(servers => new ServerSet(servers, unreachable)) + .then(set => set.post(nullAuth, '/auth/register/', this.form)) + .then(response => { + if (response.errors) { + console.error('Error:', response.errors); + this.errors = response.errors; return; } - console.log('Success:', data); + console.log('Success:', response); this.msg = 'Success'; + localStorage.setItem('user', user); this.$router.push('/login'); }) - .catch((error) => { - console.error('Error:', error); - this.msg = 'Error'; - }); } }, mounted() { - fetch('/api/domains/') - .then(response => response.json()) - .then(data => { - this.domains = data; - }); + try { + fetch('/local/domains') + .then(response => response.json()) + .then(data => { + this.domains = data; + }); + } catch (e) { + console.error('Error:', e); + } } } </script>