Added user sign up and login.

This commit is contained in:
DebaucheryLibrarian
2021-03-13 04:26:24 +01:00
parent 99cfd3dc3f
commit 816529b0ca
42 changed files with 741 additions and 8 deletions

View File

@@ -0,0 +1,116 @@
<template>
<form
class="login"
@submit.prevent="login"
>
<div
v-if="error"
class="feedback error"
>{{ error }}</div>
<div
v-if="success"
class="feedback success"
>Login successful, redirecting</div>
<template v-else>
<input
v-model="username"
placeholder="Username or e-mail"
type="text"
class="input"
required
>
<input
v-model="password"
placeholder="Password"
type="password"
class="input"
required
>
<button
type="submit"
class="button button-primary"
>Log in</button>
<router-link
to="/signup"
class="link link-external signup"
>Sign up</router-link>
</template>
</form>
</template>
<script>
async function login() {
this.error = null;
this.success = false;
try {
await this.$store.dispatch('login', {
username: this.username,
password: this.password,
});
this.success = true;
setTimeout(() => {
this.$router.replace(this.$route.query.ref || { name: 'home' });
}, 1000);
} catch (error) {
this.error = error.message;
}
}
export default {
data() {
return {
username: null,
password: null,
success: false,
error: null,
};
},
methods: {
login,
},
};
</script>
<style lang="scss" scoped>
.login {
width: 20rem;
display: flex;
flex-direction: column;
margin: auto;
}
.input {
margin: 0 0 .5rem 0;
}
.feedback {
padding: 1rem;
text-align: center;
font-weight: bold;
}
.error {
color: var(--error);
}
.success {
color: var(--shadow-strong);
}
.button {
margin: 0 0 .25rem 0;
}
.signup {
padding: .5rem;
text-align: center;
}
</style>

View File

@@ -0,0 +1,126 @@
<template>
<form
class="signup"
@submit.prevent="signup"
>
<div
v-if="error"
class="feedback error"
>{{ error }}</div>
<div
v-if="success"
class="feedback success"
>Signup successful, redirecting</div>
<template v-else>
<input
v-model="username"
placeholder="Username"
type="text"
class="input"
required
>
<input
v-model="email"
placeholder="E-mail"
type="email"
class="input"
required
>
<input
v-model="password"
placeholder="Password"
type="password"
class="input"
required
>
<button
type="submit"
class="button button-primary"
>Sign up</button>
<router-link
to="/login"
class="link link-external login"
>Log in</router-link>
</template>
</form>
</template>
<script>
async function signup() {
this.error = null;
this.success = false;
try {
await this.$store.dispatch('signup', {
username: this.username,
email: this.email,
password: this.password,
});
this.success = true;
setTimeout(() => {
this.$router.replace(this.$route.query.ref || { name: 'home' });
}, 1000);
} catch (error) {
this.error = error.message;
}
}
export default {
data() {
return {
username: null,
email: null,
password: null,
success: false,
error: null,
};
},
methods: {
signup,
},
};
</script>
<style lang="scss" scoped>
.signup {
width: 20rem;
display: flex;
flex-direction: column;
margin: auto;
}
.input {
margin: 0 0 .5rem 0;
}
.feedback {
padding: 1rem;
text-align: center;
font-weight: bold;
}
.error {
color: var(--error);
}
.success {
color: var(--shadow-strong);
}
.button {
margin: 0 0 .25rem 0;
}
.login {
padding: .5rem;
text-align: center;
}
</style>

View File

@@ -95,11 +95,26 @@
<template v-slot:tooltip>
<div class="menu">
<ul class="menu-items noselect">
<li
class="menu-item disabled"
<router-link
v-if="!me"
to="/login"
class="menu-item"
@click.stop
>
<Icon icon="enter2" />Sign in
<Icon icon="enter2" />Log in
</router-link>
<li
v-if="me"
class="menu-username"
>{{ me.username }}</li>
<li
v-if="me"
class="menu-item"
@click.stop="$store.dispatch('logout')"
>
<Icon icon="enter2" />Log out
</li>
<li
@@ -200,6 +215,10 @@ function theme(state) {
return state.ui.theme;
}
function me(state) {
return state.auth.user;
}
function setTheme(newTheme) {
this.$store.dispatch('setTheme', newTheme);
}
@@ -224,6 +243,7 @@ export default {
...mapState({
sfw,
theme,
me,
}),
},
methods: {
@@ -403,6 +423,8 @@ export default {
.menu-item {
display: flex;
padding: .75rem 1rem .75rem .75rem;
color: inherit;
text-decoration: none;
.icon {
fill: var(--darken);
@@ -428,6 +450,15 @@ export default {
}
}
.menu-username {
font-weight: bold;
color: var(--shadow-strong);
font-size: .9rem;
padding: .75rem 1rem;
border-bottom: solid 1px var(--shadow-hint);
text-align: center;
}
.search-compact {
display: none;
height: 100%;

View File

@@ -1,11 +1,15 @@
.input {
box-sizing: border-box;
padding: .5rem;
border: solid 1px var(--shadow-weak);
border: solid 1px var(--shadow-hint);
color: var(--shadow-strong);
background: var(--background);
font-size: 1rem;
&:focus {
border: solid 1px var(--primary);
}
&::-webkit-calendar-picker-indicator {
opacity: .5;
}
@@ -20,6 +24,35 @@
cursor: pointer;
}
.button {
border: none;
background: none;
padding: .5rem;
font-weight: bold;
&:hover {
cursor: pointer;
}
}
.button-primary {
color: var(--text-light);
background: var(--primary);
&:hover {
background: var(--primary-strong);
}
}
.button-secondary {
color: var(--primary);
&:hover {
color: var(--highlight-strong);
background: var(--primary);
}
}
.album-toggle {
height: fit-content;
display: inline-flex;

View File

@@ -36,6 +36,7 @@ $breakpoint4: 1500px;
--female: #f0a;
--alert: #f00;
--error: #f00;
--warn: #fa0;
--success: #5c2;
@@ -58,6 +59,7 @@ $breakpoint4: 1500px;
--tile: #2a2a2a;
--link: #dd6688;
--link-external: #48f;
--empty: #333;
--crease: #eaeaea;

View File

@@ -44,3 +44,12 @@ body {
fill: var(--primary);
}
}
.link {
color: var(--link);
text-decoration: none;
}
.link-external {
color: var(--link-external);
}

View File

@@ -39,6 +39,22 @@ async function post(endpoint, data) {
throw new Error(errorMsg);
}
async function del(endpoint) {
const res = await fetch(`${config.api.url}${endpoint}`, {
method: 'DELETE',
mode: 'cors',
credentials: 'same-origin',
});
if (res.ok) {
return true;
}
const errorMsg = await res.text();
throw new Error(errorMsg);
}
async function graphql(query, variables = null) {
const res = await fetch('/graphql', {
method: 'POST',
@@ -67,5 +83,6 @@ async function graphql(query, variables = null) {
export {
get,
post,
del,
graphql,
};

View File

@@ -1,3 +1,42 @@
function initAuthActions(_store, _router) {}
import { get, post, del } from '../api';
function initAuthActions(_store, _router) {
async function fetchMe({ commit }) {
const user = await get('/session');
commit('setUser', user);
return user;
}
async function login({ commit }, credentials) {
const user = await post('/session', credentials);
commit('setUser', user);
return user;
}
async function signup({ commit }, credentials) {
const user = await post('/users', credentials);
commit('setUser', user);
return user;
}
async function logout({ commit }) {
await del('/session');
commit('setUser', null);
}
return {
fetchMe,
login,
logout,
signup,
};
}
export default initAuthActions;

View File

@@ -1 +1,7 @@
export default {};
function setUser(state, user) {
state.user = user;
}
export default {
setUser,
};

View File

@@ -0,0 +1,5 @@
async function initAuthObserver(store, _router) {
await store.dispatch('fetchMe');
}
export default initAuthObserver;

View File

@@ -1,4 +1,3 @@
export default {
authenticated: false,
user: null,
};

View File

@@ -6,6 +6,7 @@ import mitt from 'mitt';
import router from './router';
import initStore from './store';
import initUiObservers from './ui/observers';
import initAuthObservers from './auth/observers';
import { formatDate, formatDuration } from './format';
@@ -64,6 +65,7 @@ async function init() {
}
initUiObservers(store, router);
initAuthObservers(store, router);
if (window.env.sfw) {
store.dispatch('setSfw', true);

View File

@@ -1,6 +1,8 @@
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../components/home/home.vue';
import Login from '../components/auth/login.vue';
import Signup from '../components/auth/signup.vue';
import Release from '../components/releases/release.vue';
import Entity from '../components/entities/entity.vue';
import Networks from '../components/networks/networks.vue';
@@ -16,6 +18,7 @@ import NotFound from '../components/errors/404.vue';
const routes = [
{
path: '/',
name: 'home',
redirect: {
name: 'updates',
params: {
@@ -25,6 +28,16 @@ const routes = [
},
},
},
{
path: '/login',
name: 'login',
component: Login,
},
{
path: '/signup',
name: 'singup',
component: Signup,
},
{
path: '/updates',
redirect: {