stash
This commit is contained in:
parent
55577adde8
commit
916de9fd6b
7 changed files with 498 additions and 34 deletions
78
TODO.md
Normal file
78
TODO.md
Normal file
|
@ -0,0 +1,78 @@
|
|||
# Issues
|
||||
|
||||
* [ ] Frontend to add, edit and delete events
|
||||
* [ ] Backend to add, edit and delete events
|
||||
* [ ] api testcases for events
|
||||
* [ ] Frontend to add, edit and delete users
|
||||
* [ ] Backend to add, edit and delete users
|
||||
* [ ] api testcases for users
|
||||
* [ ] check permissions in all api endpoints
|
||||
* [ ] tickets
|
||||
* [ ] Frontend to add, edit and delete tickets
|
||||
* [ ] Backend to add, edit and delete tickets
|
||||
* [ ] api testcases for tickets
|
||||
* [ ] Frontend: change ticket status
|
||||
* [ ] Backend: change ticket status
|
||||
* [ ] api testcases for ticket status
|
||||
* [ ] Frontend: assign tickets to users
|
||||
* [ ] Backend: assign tickets to users
|
||||
* [ ] api testcases for ticket assignment
|
||||
* [ ] Frontend: ticket search
|
||||
* [ ] Backend: ticket search
|
||||
* [ ] api testcases for ticket search
|
||||
* [ ] Frontend: ticket comments
|
||||
* [ ] Backend: ticket comments
|
||||
* [ ] api testcases for ticket comments
|
||||
* [ ] Frontend: send replay mails
|
||||
* [ ] Backend: send replay mails
|
||||
* [ ] api testcases for replay mails
|
||||
* [ ] Frontend: manage auto mail triggers
|
||||
* [ ] Backend: manage auto mail triggers
|
||||
* [ ] api testcases for auto mail triggers
|
||||
* [ ] Frontend: manage mail templates
|
||||
* [ ] Backend: manage mail templates
|
||||
* [ ] api testcases for mail templates
|
||||
* [ ] Backend: send notification mails to users
|
||||
* [ ] testcases for notification mails
|
||||
* [ ] Frontend: notification settings
|
||||
* [ ] Backend: notification settings
|
||||
* [ ] api testcases for notification settings
|
||||
* [ ] Backend: Telegram bot
|
||||
* [ ] Backend: route mail to tickets bases on +tag
|
||||
* [ ] testcases for mail to tickets
|
||||
* [ ] Frontend: login, logout, register
|
||||
* [ ] Backend: login, logout, register
|
||||
* [ ] api testcases for login, logout, register
|
||||
* [ ] Frontend: item search
|
||||
* [ ] Backend: item search
|
||||
* [ ] api testcases for item search
|
||||
* [ ] Frontend: to math items to tickets
|
||||
* [ ] Backend: to math items to tickets
|
||||
* [ ] api testcases for item to tickets
|
||||
* [ ] Frontend: to show item history
|
||||
* [ ] Backend: to show item history
|
||||
* [ ] api testcases for item history
|
||||
* [ ] Frontend: to delegate permissions via qr code
|
||||
* [ ] testcases for qr code
|
||||
* [ ] Frontend to add, edit and delete boxes
|
||||
* [ ] Backend to add, edit and delete boxes
|
||||
* [ ] api testcases for boxes
|
||||
* [ ] Frontend: to show box history
|
||||
* [ ] Backend: to show box history
|
||||
* [ ] api testcases for box history
|
||||
* [ ] Frontend: clear, disband and move boxes
|
||||
* [ ] Backend: clear, disband and move boxes
|
||||
* [ ] api testcases for clear, disband and move boxes
|
||||
* [ ] testcases for receiving mails and auto reply
|
||||
* [ ] Frontend: merging tickets
|
||||
* [ ] Backend: merging tickets
|
||||
* [ ] api testcases for merging tickets
|
||||
* [ ] concept: create items from "found something" tickets
|
||||
* [ ] concept: purge old tickets
|
||||
* [ ] concept: purge old items
|
||||
* [ ] concept: auto email stale after x days
|
||||
|
||||
## Priority: TODO
|
||||
|
||||
* send mails from web frontend
|
||||
* login / user management
|
|
@ -1,4 +1,7 @@
|
|||
from rest_framework import routers, viewsets, serializers
|
||||
from rest_framework.decorators import api_view, permission_classes, authentication_classes
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.authentication import BasicAuthentication
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
|
@ -8,13 +11,42 @@ class UserSerializer(serializers.ModelSerializer):
|
|||
fields = ('id', 'username', 'email', 'first_name', 'last_name')
|
||||
|
||||
|
||||
class RegisterUserSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'password', 'email')
|
||||
extra_kwargs = {
|
||||
'password': {'write_only': True},
|
||||
}
|
||||
|
||||
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserSerializer
|
||||
authentication_classes = []
|
||||
authentication_classes = [BasicAuthentication]
|
||||
permission_classes = []
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@permission_classes([])
|
||||
@authentication_classes([BasicAuthentication])
|
||||
def token(request):
|
||||
return Response({
|
||||
'token': request.user.auth_token.key
|
||||
})
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([])
|
||||
@authentication_classes([])
|
||||
def registerUser(request):
|
||||
serializer = RegisterUserSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
user = serializer.save()
|
||||
return Response({'username': user.username, 'email': user.email}, status=201)
|
||||
return Response(serializer.errors, status=400)
|
||||
|
||||
|
||||
router = routers.SimpleRouter()
|
||||
router.register(r'users', UserViewSet, basename='users')
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<AddItemModal v-if="addModalOpen" @close="closeAddModal()" isModal="true"/>
|
||||
<Navbar @addClicked="openAddModal()"/>
|
||||
<AddItemModal v-if="addModalOpen && isLoggedIn" @close="closeAddModal()" isModal="true"/>
|
||||
<Navbar v-if="isLoggedIn" @addClicked="openAddModal()"/>
|
||||
<router-view/>
|
||||
<div aria-live="polite" aria-atomic="true"
|
||||
<div aria-live="polite" aria-atomic="true" v-if="isLoggedIn"
|
||||
class="d-flex justify-content-end align-items-start fixed-top mx-1 my-5 py-3"
|
||||
style="min-height: 200px; z-index: 100000; pointer-events: none">
|
||||
<Toast v-for="toast in toasts" :key="toast" :title="toast.title" :message="toast.message"
|
||||
|
@ -17,12 +17,15 @@
|
|||
import Navbar from '@/components/Navbar';
|
||||
import AddItemModal from '@/components/AddItemModal';
|
||||
import Toast from './components/Toast';
|
||||
import {mapState, mapMutations, mapActions} from 'vuex';
|
||||
import {mapState, mapMutations, mapActions, mapGetters} from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {Toast, Navbar, AddItemModal},
|
||||
computed: mapState(['loadedItems', 'layout', 'toasts']),
|
||||
computed: {
|
||||
...mapState(['loadedItems', 'layout', 'toasts']),
|
||||
...mapGetters(['isLoggedIn']),
|
||||
},
|
||||
data: () => ({
|
||||
addModalOpen: false,
|
||||
notify_socket: null,
|
||||
|
@ -55,6 +58,7 @@ export default {
|
|||
this.removeToast(this.socket_toast.key);
|
||||
this.socket_toast = null;
|
||||
}
|
||||
console.log(e);
|
||||
this.socket_toast = this.createToast({
|
||||
title: "Connection established",
|
||||
message: JSON.stringify(e),
|
||||
|
@ -66,6 +70,7 @@ export default {
|
|||
this.removeToast(this.socket_toast.key);
|
||||
this.socket_toast = null;
|
||||
}
|
||||
console.log(e);
|
||||
this.socket_toast = this.createToast({
|
||||
title: "Connection closed",
|
||||
message: JSON.stringify(e),
|
||||
|
@ -80,6 +85,7 @@ export default {
|
|||
this.removeToast(this.socket_toast.key);
|
||||
this.socket_toast = null;
|
||||
}
|
||||
console.log(e);
|
||||
this.socket_toast = this.createToast({
|
||||
title: "Connection error",
|
||||
message: JSON.stringify(e),
|
||||
|
@ -93,8 +99,6 @@ export default {
|
|||
let data = JSON.parse(e.data);
|
||||
//this.loadItems()
|
||||
this.loadTickets()
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -6,6 +6,8 @@ import Error from './views/Error';
|
|||
import HowTo from './views/HowTo';
|
||||
import VueRouter from 'vue-router';
|
||||
import Vue from 'vue';
|
||||
import Login from '@/views/Login.vue';
|
||||
import Register from '@/views/Register.vue';
|
||||
import Debug from "@/views/admin/Debug.vue";
|
||||
import Tickets from "@/views/Tickets.vue";
|
||||
import Ticket from "@/views/Ticket.vue";
|
||||
|
@ -15,19 +17,21 @@ import store from "@/store";
|
|||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{path: '/', redirect: '/Camp23/items'},
|
||||
{path: '/howto', name: 'howto', component: HowTo},
|
||||
{path: '/:event/boxes', name: 'boxes', component: Boxes},
|
||||
{path: '/:event/items', name: 'items', component: Items},
|
||||
{path: '/:event/box/:uid', name: 'box', component: Boxes},
|
||||
{path: '/:event/item/:uid', name: 'item', component: Items},
|
||||
{path: '/:event/tickets', name: 'tickets', component: Tickets},
|
||||
{path: '/:event/ticket/:id', name: 'ticket', component: Ticket},
|
||||
{path: '/admin', name: 'admin', component: Admin},
|
||||
{path: '/admin/files', name: 'files', component: Files},
|
||||
{path: '/admin/events', name: 'events', component: Events},
|
||||
{path: '/admin/debug', name: 'debug', component: Debug},
|
||||
{path: '/admin/users', name: 'users', component: Events},
|
||||
{path: '/', redirect: '/Camp23/items', meta: {requiresAuth: false}},
|
||||
{path: '/login', name: 'login', component: Login, meta: {requiresAuth: false}},
|
||||
{path: '/register', name: 'register', component: Register, meta: {requiresAuth: false}},
|
||||
{path: '/howto', name: 'howto', component: HowTo, meta: {requiresAuth: true}},
|
||||
{path: '/:event/boxes', name: 'boxes', component: Boxes, meta: {requiresAuth: true}},
|
||||
{path: '/:event/items', name: 'items', component: Items, meta: {requiresAuth: true}},
|
||||
{path: '/:event/box/:uid', name: 'box', component: Boxes, meta: {requiresAuth: true}},
|
||||
{path: '/:event/item/:uid', name: 'item', component: Items, meta: {requiresAuth: true}},
|
||||
{path: '/:event/tickets', name: 'tickets', component: Tickets, meta: {requiresAuth: true}},
|
||||
{path: '/:event/ticket/:id', name: 'ticket', component: Ticket, meta: {requiresAuth: true}},
|
||||
{path: '/admin', name: 'admin', component: Admin, meta: {requiresAuth: true}},
|
||||
{path: '/admin/files', name: 'files', component: Files, meta: {requiresAuth: true}},
|
||||
{path: '/admin/events', name: 'events', component: Events, meta: {requiresAuth: true}},
|
||||
{path: '/admin/debug', name: 'debug', component: Debug, meta: {requiresAuth: true}},
|
||||
{path: '/admin/users', name: 'users', component: Events, meta: {requiresAuth: true}},
|
||||
{path: '*', component: Error},
|
||||
];
|
||||
|
||||
|
@ -36,7 +40,34 @@ const router = new VueRouter({
|
|||
routes,
|
||||
});
|
||||
|
||||
router.afterEach((to, from) => {
|
||||
//router.beforeEach((to/*, from*/, next) => {
|
||||
// console.log("beforeEach", to);
|
||||
// if (to.meta.requiresAuth && !store.getters.isLoggedIn) {
|
||||
// console.log("Not logged in, redirecting to login page")
|
||||
// return {
|
||||
// name: 'login',
|
||||
// query: {redirect: to.fullPath},
|
||||
// }
|
||||
// }
|
||||
//});
|
||||
|
||||
//router.beforeResolve((to, from, next) => {
|
||||
// next()
|
||||
//});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta.requiresAuth && !store.getters.isLoggedIn) {
|
||||
//console.log("Not logged in, redirecting to login page")
|
||||
next({
|
||||
name: 'login',
|
||||
query: {redirect: to.fullPath},
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach((to/*, from*/) => {
|
||||
if (to.params.event) {
|
||||
//console.log('update last event', to.params.event);
|
||||
store.commit('updateLastEvent', to.params.event);
|
||||
|
|
|
@ -47,6 +47,10 @@ const store = new Vuex.Store({
|
|||
userRoles: ['admin', 'team', 'orga', 'user'],
|
||||
lastEvent: localStorage.getItem('lf_lastEvent') || '36C3',
|
||||
lastUsed: localStorage.getItem('lf_lastUsed') || {},
|
||||
remember: false,
|
||||
user: null,
|
||||
token: null,
|
||||
local_loaded: false,
|
||||
},
|
||||
getters: {
|
||||
getEventSlug: state => state.route && state.route.params.event ? state.route.params.event : state.lastEvent,
|
||||
|
@ -54,6 +58,16 @@ const store = new Vuex.Store({
|
|||
getFilters: state => state.route.query,
|
||||
getBoxes: state => state.loadedBoxes,
|
||||
checkRole: state => role => state.userRoles.includes(role),
|
||||
isLoggedIn(state) {
|
||||
if (!state.local_loaded) {
|
||||
state.remember = localStorage.getItem('remember') === 'true'
|
||||
state.user = localStorage.getItem('user')
|
||||
state.token = localStorage.getItem('token')
|
||||
state.local_loaded = true
|
||||
}
|
||||
|
||||
return state.user !== null && state.token !== null;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
updateLastUsed(state, diff) {
|
||||
|
@ -104,53 +118,84 @@ const store = new Vuex.Store({
|
|||
},
|
||||
removeToast(state, key) {
|
||||
state.toasts = state.toasts.filter(toast => toast.key !== key);
|
||||
}
|
||||
},
|
||||
setRemember(state, remember) {
|
||||
state.remember = remember;
|
||||
localStorage.setItem('remember', remember);
|
||||
},
|
||||
setUser(state, user) {
|
||||
state.user = user;
|
||||
localStorage.setItem('user', user);
|
||||
},
|
||||
logout(state) {
|
||||
state.user = null;
|
||||
state.token = null;
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('token');
|
||||
router.push('/login');
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async login({commit, dispatch, state}, {username, password, remember}) {
|
||||
commit('setRemember', remember);
|
||||
const data = await fetch('/api/2/auth/token/', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: username, password: password}),
|
||||
credentials: 'omit'
|
||||
}).then(r => r.json())
|
||||
if (data.token && data.key) {
|
||||
commit('setToken', data.token);
|
||||
commit('setUser', username);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
async loadEvents({commit}) {
|
||||
const {data} = await axios.get('/1/events');
|
||||
const {data} = await axios.get('/1/events/');
|
||||
commit('replaceEvents', data);
|
||||
},
|
||||
changeEvent({dispatch, getters}, eventName) {
|
||||
router.push({path: `/${eventName.slug}/${getters.getActiveView}`});
|
||||
router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
|
||||
dispatch('loadEventItems');
|
||||
},
|
||||
changeView({getters}, link) {
|
||||
router.push({path: `/${getters.getEventSlug}/${link.path}`});
|
||||
router.push({path: `/${getters.getEventSlug}/${link.path}/`});
|
||||
},
|
||||
showBoxContent({getters}, box) {
|
||||
router.push({path: `/${getters.getEventSlug}/items`, query: {box}});
|
||||
router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
|
||||
},
|
||||
async loadEventItems({commit, getters}) {
|
||||
const {data} = await axios.get(`/1/${getters.getEventSlug}/items`);
|
||||
const {data} = await axios.get(`/2/${getters.getEventSlug}/items/`);
|
||||
commit('replaceLoadedItems', data);
|
||||
},
|
||||
async searchEventItems({commit, getters}, query) {
|
||||
const foo = utf8.encode(query);
|
||||
const bar = base64.encode(foo);
|
||||
|
||||
const {data} = await axios.get(`/1/${getters.getEventSlug}/items/${bar}`);
|
||||
const {data} = await axios.get(`/2/${getters.getEventSlug}/items/${bar}/`);
|
||||
commit('replaceLoadedItems', data);
|
||||
},
|
||||
async loadBoxes({commit}) {
|
||||
const {data} = await axios.get('/1/boxes');
|
||||
const {data} = await axios.get('/2/boxes/');
|
||||
commit('replaceBoxes', data);
|
||||
},
|
||||
async updateItem({commit, getters}, item) {
|
||||
const {data} = await axios.put(`/1/${getters.getEventSlug}/item/${item.uid}`, item);
|
||||
const {data} = await axios.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
|
||||
commit('updateItem', data);
|
||||
},
|
||||
async markItemReturned({commit, getters}, item) {
|
||||
await axios.put(`/1/${getters.getEventSlug}/item/${item.uid}`, {returned: true});
|
||||
await axios.put(`/2/${getters.getEventSlug}/item/${item.uid}/`, {returned: true});
|
||||
commit('removeItem', item);
|
||||
},
|
||||
async deleteItem({commit, getters}, item) {
|
||||
await axios.delete(`/1/${getters.getEventSlug}/item/${item.uid}`, item);
|
||||
await axios.delete(`/2/${getters.getEventSlug}/item/${item.uid}/`, item);
|
||||
commit('removeItem', item);
|
||||
},
|
||||
async postItem({commit, getters}, item) {
|
||||
commit('updateLastUsed', {box: item.box, cid: item.cid});
|
||||
const {data} = await axios.post(`/1/${getters.getEventSlug}/item`, item);
|
||||
const {data} = await axios.post(`/2/${getters.getEventSlug}/item/`, item);
|
||||
commit('appendItem', data);
|
||||
},
|
||||
async loadTickets({commit}) {
|
||||
|
|
120
web/src/views/Login.vue
Normal file
120
web/src/views/Login.vue
Normal file
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<main class="d-flex w-100">
|
||||
<div class="container d-flex flex-column">
|
||||
<div class="row vh-100">
|
||||
<div class="col-sm-10 col-md-8 col-lg-6 mx-auto d-table h-100">
|
||||
<div class="d-table-cell align-middle">
|
||||
<div class="text-center mt-4">
|
||||
<h1 class="h2">
|
||||
C3LF System3
|
||||
</h1>
|
||||
<p class="lead" v-if="msg">
|
||||
{{ msg }}
|
||||
</p>
|
||||
<p class="lead" v-else>
|
||||
Sign in to your account to continue
|
||||
</p>
|
||||
</div>
|
||||
<div class="card bg-dark">
|
||||
<div class="card-body">
|
||||
<div class="m-sm-4">
|
||||
<form role="form" @submit.prevent="do_login">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Username</label>
|
||||
<input class="form-control" type="text"
|
||||
name="username" placeholder="Enter your username"
|
||||
v-model="username"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input class="form-control" type="password"
|
||||
name="password" placeholder="Enter your password"
|
||||
v-model="password"/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-check">
|
||||
<input class="form-check-input" type="checkbox" value="remember-me"
|
||||
name="remember-me" checked v-model="remember"
|
||||
@change="setRemember(remember)">
|
||||
<span class="form-check-label">
|
||||
Remember me next time
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="text-center mt-3">
|
||||
<button type="submit" name="login" class="btn btn-primary">Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<br/>
|
||||
<div class="text-center">
|
||||
<p class="mb-0 text-muted">
|
||||
Don’t have an account?
|
||||
<router-link to="/register">Sign up</router-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapMutations} from 'vuex';
|
||||
import router from "@/router";
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
data() {
|
||||
return {
|
||||
msg: 'Welcome to ' + window.location.hostname,
|
||||
username: '',
|
||||
password: '',
|
||||
remember: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['login']),
|
||||
...mapMutations(['setRemember']),
|
||||
async do_login(e) {
|
||||
e.preventDefault();
|
||||
if (await this.login({username: this.username, password: this.password, remember: this.remember})) {
|
||||
if (this.$route.query.redirect) {
|
||||
await router.push({path: this.$route.query.redirect});
|
||||
} else {
|
||||
await router.push({path: '/'});
|
||||
}
|
||||
} else {
|
||||
this.msg = 'Invalid username or password';
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
input{
|
||||
background-color: var(--dark);
|
||||
border: var(--gray) 1px solid;;
|
||||
|
||||
&:focus {
|
||||
background-color: var(--dark);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--dark);
|
||||
}
|
||||
|
||||
&[type="checkbox"] {
|
||||
background-color: var(--dark);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
154
web/src/views/Register.vue
Normal file
154
web/src/views/Register.vue
Normal file
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<main class="d-flex w-100">
|
||||
<div class="container d-flex flex-column">
|
||||
<div class="row vh-100">
|
||||
<div class="col-sm-10 col-md-8 col-lg-6 mx-auto d-table h-100">
|
||||
<div class="d-table-cell align-middle">
|
||||
<div class="text-center mt-4">
|
||||
<h1 class="h2">
|
||||
C3LF System3
|
||||
</h1>
|
||||
<p class="lead" v-if="msg">
|
||||
{{ msg }}
|
||||
</p>
|
||||
<p class="lead" v-else>
|
||||
Create an account to get started
|
||||
</p>
|
||||
</div>
|
||||
<div class="card bg-dark">
|
||||
<div class="card-body">
|
||||
<div class="m-sm-4">
|
||||
<form role="form" method="post" @submit.prevent="do_register">
|
||||
<div :class="errors.username?['mb-3','is-invalid']:['mb-3']">
|
||||
<label class="form-label">Username</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control"
|
||||
type="text" v-model="form.username" id="validationCustomUsername"
|
||||
placeholder="Enter your username" required/>
|
||||
</div>
|
||||
<div class="invalid-feedback">
|
||||
{{ errors.username }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="errors.password?['mb-3','is-invalid']:['mb-3']">
|
||||
<label class="form-label">Password</label>
|
||||
<input class="form-control" type="password"
|
||||
v-model="form.password" placeholder="Enter your password"/>
|
||||
<div class="invalid-feedback">{{ errors.password }}</div>
|
||||
</div>
|
||||
|
||||
<div :class="errors.password2?['mb-3','is-invalid']:['mb-3']">
|
||||
<label class="form-label">Password Check</label>
|
||||
<input class="form-control" type="password"
|
||||
v-model="password2" placeholder="Enter your password again"/>
|
||||
<div class="invalid-feedback">{{ errors.password2 }}</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-3">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<br/>
|
||||
<div class="text-center">
|
||||
<p class="mb-0 text-muted">
|
||||
Already have an account?
|
||||
<router-link to="/login">Login</router-link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Register',
|
||||
data() {
|
||||
return {
|
||||
msg: 'Register',
|
||||
password2: '',
|
||||
form: {
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
errors: {
|
||||
username: null,
|
||||
password: null,
|
||||
password2: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
do_register() {
|
||||
console.log('do_register');
|
||||
console.log(this.form);
|
||||
if (this.form.password !== this.password2) {
|
||||
this.errors.password2 = 'Passwords do not match';
|
||||
return;
|
||||
} else {
|
||||
this.errors.password2 = null;
|
||||
}
|
||||
fetch('/api/2/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;
|
||||
return;
|
||||
}
|
||||
console.log('Success:', data);
|
||||
this.msg = 'Success';
|
||||
this.$router.push('/login');
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
this.msg = 'Error';
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.is-invalid input, .is-invalid select {
|
||||
border: 1px solid var(--danger);
|
||||
}
|
||||
|
||||
.is-invalid .invalid-feedback {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input{
|
||||
background-color: var(--dark);
|
||||
border: var(--gray) 1px solid;;
|
||||
|
||||
&:focus {
|
||||
background-color: var(--dark);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--dark);
|
||||
}
|
||||
|
||||
&[type="checkbox"] {
|
||||
background-color: var(--dark);
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in a new issue