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 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
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,13 +11,42 @@ class UserSerializer(serializers.ModelSerializer):
|
||||||
fields = ('id', 'username', 'email', 'first_name', 'last_name')
|
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):
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
authentication_classes = []
|
authentication_classes = [BasicAuthentication]
|
||||||
permission_classes = []
|
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 = routers.SimpleRouter()
|
||||||
router.register(r'users', UserViewSet, basename='users')
|
router.register(r'users', UserViewSet, basename='users')
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<AddItemModal v-if="addModalOpen" @close="closeAddModal()" isModal="true"/>
|
<AddItemModal v-if="addModalOpen && isLoggedIn" @close="closeAddModal()" isModal="true"/>
|
||||||
<Navbar @addClicked="openAddModal()"/>
|
<Navbar v-if="isLoggedIn" @addClicked="openAddModal()"/>
|
||||||
<router-view/>
|
<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"
|
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">
|
style="min-height: 200px; z-index: 100000; pointer-events: none">
|
||||||
<Toast v-for="toast in toasts" :key="toast" :title="toast.title" :message="toast.message"
|
<Toast v-for="toast in toasts" :key="toast" :title="toast.title" :message="toast.message"
|
||||||
|
@ -17,12 +17,15 @@
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
import AddItemModal from '@/components/AddItemModal';
|
import AddItemModal from '@/components/AddItemModal';
|
||||||
import Toast from './components/Toast';
|
import Toast from './components/Toast';
|
||||||
import {mapState, mapMutations, mapActions} from 'vuex';
|
import {mapState, mapMutations, mapActions, mapGetters} from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {Toast, Navbar, AddItemModal},
|
components: {Toast, Navbar, AddItemModal},
|
||||||
computed: mapState(['loadedItems', 'layout', 'toasts']),
|
computed: {
|
||||||
|
...mapState(['loadedItems', 'layout', 'toasts']),
|
||||||
|
...mapGetters(['isLoggedIn']),
|
||||||
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
addModalOpen: false,
|
addModalOpen: false,
|
||||||
notify_socket: null,
|
notify_socket: null,
|
||||||
|
@ -55,6 +58,7 @@ export default {
|
||||||
this.removeToast(this.socket_toast.key);
|
this.removeToast(this.socket_toast.key);
|
||||||
this.socket_toast = null;
|
this.socket_toast = null;
|
||||||
}
|
}
|
||||||
|
console.log(e);
|
||||||
this.socket_toast = this.createToast({
|
this.socket_toast = this.createToast({
|
||||||
title: "Connection established",
|
title: "Connection established",
|
||||||
message: JSON.stringify(e),
|
message: JSON.stringify(e),
|
||||||
|
@ -66,6 +70,7 @@ export default {
|
||||||
this.removeToast(this.socket_toast.key);
|
this.removeToast(this.socket_toast.key);
|
||||||
this.socket_toast = null;
|
this.socket_toast = null;
|
||||||
}
|
}
|
||||||
|
console.log(e);
|
||||||
this.socket_toast = this.createToast({
|
this.socket_toast = this.createToast({
|
||||||
title: "Connection closed",
|
title: "Connection closed",
|
||||||
message: JSON.stringify(e),
|
message: JSON.stringify(e),
|
||||||
|
@ -80,6 +85,7 @@ export default {
|
||||||
this.removeToast(this.socket_toast.key);
|
this.removeToast(this.socket_toast.key);
|
||||||
this.socket_toast = null;
|
this.socket_toast = null;
|
||||||
}
|
}
|
||||||
|
console.log(e);
|
||||||
this.socket_toast = this.createToast({
|
this.socket_toast = this.createToast({
|
||||||
title: "Connection error",
|
title: "Connection error",
|
||||||
message: JSON.stringify(e),
|
message: JSON.stringify(e),
|
||||||
|
@ -93,8 +99,6 @@ export default {
|
||||||
let data = JSON.parse(e.data);
|
let data = JSON.parse(e.data);
|
||||||
//this.loadItems()
|
//this.loadItems()
|
||||||
this.loadTickets()
|
this.loadTickets()
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,6 +6,8 @@ import Error from './views/Error';
|
||||||
import HowTo from './views/HowTo';
|
import HowTo from './views/HowTo';
|
||||||
import VueRouter from 'vue-router';
|
import VueRouter from 'vue-router';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import Login from '@/views/Login.vue';
|
||||||
|
import Register from '@/views/Register.vue';
|
||||||
import Debug from "@/views/admin/Debug.vue";
|
import Debug from "@/views/admin/Debug.vue";
|
||||||
import Tickets from "@/views/Tickets.vue";
|
import Tickets from "@/views/Tickets.vue";
|
||||||
import Ticket from "@/views/Ticket.vue";
|
import Ticket from "@/views/Ticket.vue";
|
||||||
|
@ -15,19 +17,21 @@ import store from "@/store";
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{path: '/', redirect: '/Camp23/items'},
|
{path: '/', redirect: '/Camp23/items', meta: {requiresAuth: false}},
|
||||||
{path: '/howto', name: 'howto', component: HowTo},
|
{path: '/login', name: 'login', component: Login, meta: {requiresAuth: false}},
|
||||||
{path: '/:event/boxes', name: 'boxes', component: Boxes},
|
{path: '/register', name: 'register', component: Register, meta: {requiresAuth: false}},
|
||||||
{path: '/:event/items', name: 'items', component: Items},
|
{path: '/howto', name: 'howto', component: HowTo, meta: {requiresAuth: true}},
|
||||||
{path: '/:event/box/:uid', name: 'box', component: Boxes},
|
{path: '/:event/boxes', name: 'boxes', component: Boxes, meta: {requiresAuth: true}},
|
||||||
{path: '/:event/item/:uid', name: 'item', component: Items},
|
{path: '/:event/items', name: 'items', component: Items, meta: {requiresAuth: true}},
|
||||||
{path: '/:event/tickets', name: 'tickets', component: Tickets},
|
{path: '/:event/box/:uid', name: 'box', component: Boxes, meta: {requiresAuth: true}},
|
||||||
{path: '/:event/ticket/:id', name: 'ticket', component: Ticket},
|
{path: '/:event/item/:uid', name: 'item', component: Items, meta: {requiresAuth: true}},
|
||||||
{path: '/admin', name: 'admin', component: Admin},
|
{path: '/:event/tickets', name: 'tickets', component: Tickets, meta: {requiresAuth: true}},
|
||||||
{path: '/admin/files', name: 'files', component: Files},
|
{path: '/:event/ticket/:id', name: 'ticket', component: Ticket, meta: {requiresAuth: true}},
|
||||||
{path: '/admin/events', name: 'events', component: Events},
|
{path: '/admin', name: 'admin', component: Admin, meta: {requiresAuth: true}},
|
||||||
{path: '/admin/debug', name: 'debug', component: Debug},
|
{path: '/admin/files', name: 'files', component: Files, meta: {requiresAuth: true}},
|
||||||
{path: '/admin/users', name: 'users', component: Events},
|
{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},
|
{path: '*', component: Error},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -36,7 +40,34 @@ const router = new VueRouter({
|
||||||
routes,
|
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) {
|
if (to.params.event) {
|
||||||
//console.log('update last event', to.params.event);
|
//console.log('update last event', to.params.event);
|
||||||
store.commit('updateLastEvent', to.params.event);
|
store.commit('updateLastEvent', to.params.event);
|
||||||
|
|
|
@ -47,6 +47,10 @@ const store = new Vuex.Store({
|
||||||
userRoles: ['admin', 'team', 'orga', 'user'],
|
userRoles: ['admin', 'team', 'orga', 'user'],
|
||||||
lastEvent: localStorage.getItem('lf_lastEvent') || '36C3',
|
lastEvent: localStorage.getItem('lf_lastEvent') || '36C3',
|
||||||
lastUsed: localStorage.getItem('lf_lastUsed') || {},
|
lastUsed: localStorage.getItem('lf_lastUsed') || {},
|
||||||
|
remember: false,
|
||||||
|
user: null,
|
||||||
|
token: null,
|
||||||
|
local_loaded: false,
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getEventSlug: state => state.route && state.route.params.event ? state.route.params.event : state.lastEvent,
|
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,
|
getFilters: state => state.route.query,
|
||||||
getBoxes: state => state.loadedBoxes,
|
getBoxes: state => state.loadedBoxes,
|
||||||
checkRole: state => role => state.userRoles.includes(role),
|
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: {
|
mutations: {
|
||||||
updateLastUsed(state, diff) {
|
updateLastUsed(state, diff) {
|
||||||
|
@ -104,53 +118,84 @@ const store = new Vuex.Store({
|
||||||
},
|
},
|
||||||
removeToast(state, key) {
|
removeToast(state, key) {
|
||||||
state.toasts = state.toasts.filter(toast => toast.key !== 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: {
|
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}) {
|
async loadEvents({commit}) {
|
||||||
const {data} = await axios.get('/1/events');
|
const {data} = await axios.get('/1/events/');
|
||||||
commit('replaceEvents', data);
|
commit('replaceEvents', data);
|
||||||
},
|
},
|
||||||
changeEvent({dispatch, getters}, eventName) {
|
changeEvent({dispatch, getters}, eventName) {
|
||||||
router.push({path: `/${eventName.slug}/${getters.getActiveView}`});
|
router.push({path: `/${eventName.slug}/${getters.getActiveView}/`});
|
||||||
dispatch('loadEventItems');
|
dispatch('loadEventItems');
|
||||||
},
|
},
|
||||||
changeView({getters}, link) {
|
changeView({getters}, link) {
|
||||||
router.push({path: `/${getters.getEventSlug}/${link.path}`});
|
router.push({path: `/${getters.getEventSlug}/${link.path}/`});
|
||||||
},
|
},
|
||||||
showBoxContent({getters}, box) {
|
showBoxContent({getters}, box) {
|
||||||
router.push({path: `/${getters.getEventSlug}/items`, query: {box}});
|
router.push({path: `/${getters.getEventSlug}/items/`, query: {box}});
|
||||||
},
|
},
|
||||||
async loadEventItems({commit, getters}) {
|
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);
|
commit('replaceLoadedItems', data);
|
||||||
},
|
},
|
||||||
async searchEventItems({commit, getters}, query) {
|
async searchEventItems({commit, getters}, query) {
|
||||||
const foo = utf8.encode(query);
|
const foo = utf8.encode(query);
|
||||||
const bar = base64.encode(foo);
|
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);
|
commit('replaceLoadedItems', data);
|
||||||
},
|
},
|
||||||
async loadBoxes({commit}) {
|
async loadBoxes({commit}) {
|
||||||
const {data} = await axios.get('/1/boxes');
|
const {data} = await axios.get('/2/boxes/');
|
||||||
commit('replaceBoxes', data);
|
commit('replaceBoxes', data);
|
||||||
},
|
},
|
||||||
async updateItem({commit, getters}, item) {
|
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);
|
commit('updateItem', data);
|
||||||
},
|
},
|
||||||
async markItemReturned({commit, getters}, item) {
|
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);
|
commit('removeItem', item);
|
||||||
},
|
},
|
||||||
async deleteItem({commit, getters}, 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);
|
commit('removeItem', item);
|
||||||
},
|
},
|
||||||
async postItem({commit, getters}, item) {
|
async postItem({commit, getters}, item) {
|
||||||
commit('updateLastUsed', {box: item.box, cid: item.cid});
|
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);
|
commit('appendItem', data);
|
||||||
},
|
},
|
||||||
async loadTickets({commit}) {
|
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