41 Commits

Author SHA1 Message Date
1fbcbd864a working poc for editor 2023-05-09 18:31:55 +02:00
janis
b185c903dd testing of editor 2023-05-08 16:57:34 +02:00
janis
1f6fd6859a add lots of new files 2023-05-08 12:44:13 +02:00
2ff4f4a09c small changes 2023-05-08 08:41:10 +02:00
7edd663f4f progress on no seat plan view 2023-05-05 19:23:16 +02:00
eec30d8ed4 rework of purchase system 2023-04-30 11:47:50 +02:00
Janis Hutz
c7bc810e18 Update README.md 2023-04-28 11:15:00 +02:00
ab9b232433 removed old + some new code 2023-04-27 19:10:08 +02:00
64452abf6c readme + package.json 2023-04-27 17:43:01 +02:00
Janis Hutz
99ff1abc5d Fix 2023-04-26 15:10:17 +02:00
Janis Hutz
ac5c71559a Add repo structure info to readme 2023-04-26 15:08:14 +02:00
Janis Hutz
58ae853d71 Update README.md 2023-04-26 12:46:35 +00:00
Janis Hutz
736521834d Update README.md 2023-04-25 09:54:59 +00:00
Janis Hutz
5030532712 Update README.md 2023-04-25 09:54:34 +00:00
Janis Hutz
bf97068357 Update README.md 2023-04-25 09:50:10 +00:00
Janis Hutz
f19b9a472e Update README.md 2023-04-25 09:40:20 +00:00
Janis Hutz
f78a7c7dd6 Update README.md 2023-04-25 09:40:05 +00:00
Janis Hutz
1d7832a945 Update README.md 2023-04-25 09:39:52 +00:00
Janis Hutz
767d9a3d7a Update README.md 2023-04-25 09:38:13 +00:00
Janis Hutz
02af46d0f2 Update README.md 2023-04-25 09:36:19 +00:00
Janis Hutz
0e402bfba0 Update README.md 2023-04-25 09:34:55 +00:00
Janis Hutz
95b83d95bd Update README.md 2023-04-25 09:34:16 +00:00
Janis Hutz
66e4e448e5 Update README.md 2023-04-25 09:33:34 +00:00
Janis Hutz
c829bdcdb1 Update README.md 2023-04-25 09:24:33 +00:00
Janis Hutz
c89438854c Update README.md 2023-04-25 09:22:24 +00:00
Janis Hutz
c04ccd802b Update README.md
Fix badges
2023-04-25 09:21:19 +00:00
janis
55023dd46d readme 2023-04-24 17:05:40 +02:00
5e406e99d6 split up routes + setup 2023-04-23 15:50:52 +02:00
8d751026b6 lost of progress 2023-04-21 16:03:21 +02:00
13924e3abb Admin panel progress 2023-04-15 17:48:19 +02:00
7a81b94438 add login store 2023-04-13 14:26:28 +02:00
9fcdef4aac migrate to different cart store 2023-04-10 20:32:55 +02:00
a70c4cb1eb progress on adding to cart 2023-04-10 17:04:48 +02:00
c4abf91aeb dark mode support 2023-04-09 17:37:35 +02:00
5b8f1c03f7 progress on seat plan 2023-04-09 13:28:12 +02:00
ed63af624d design + admin panel 2023-04-08 21:57:28 +02:00
8b8172d9f8 tickets selection + login 2023-04-08 17:16:00 +02:00
f7e121708b server updates 2023-04-07 20:18:00 +02:00
c9e3648a0f first setting up of vue 2023-04-07 20:13:35 +02:00
d72ba805b9 initial setup of vue 2023-04-07 17:53:45 +02:00
5eccbb336a move to vue spa 2023-04-07 16:22:36 +02:00
114 changed files with 40471 additions and 1026 deletions

View File

@@ -1,24 +1,24 @@
version: 2.1
orbs:
node: circleci/node@5.1.0
orbs:
node: circleci/node@5.1.0
jobs:
build_and_test:
executor: node/default
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
command: npm run build
name: Build app
- run:
command: npm run test
name: Run tests
- run:
command: npm run build-website
name: Build documentation
- persist_to_workspace:
root: ~/project
paths:
- .
build_and_test:
executor: node/default
steps:
- checkout
- node/install-packages:
pkg-manager: npm
- run:
command: npm run build
name: Build app
- run:
command: npm run test
name: Run tests
- run:
command: npm run build-website
name: Build documentation
- persist_to_workspace:
root: ~/project
paths:
- .

5
.gitignore vendored
View File

@@ -11,4 +11,7 @@
*.log
# ignore node_modules (can be rebuilt with npm i --> shrinks repo size)
node_modules
node_modules
# ignore dist folder (this repo only contains source code!)
/dist

View File

@@ -1,10 +1,62 @@
# myevent
<div id="title" align="center">
<img src="./assets/logo.png" width="300">
<h1>myevent</h1>
</div>
<div id="badges" align="center">
<img src="https://img.shields.io/github/release/simplePCBuilding/myevent.svg">
<img src="https://img.shields.io/github/license/simplePCBuilding/myevent.svg">
<img src="https://img.shields.io/github/repo-size/simplePCBuilding/myevent.svg">
<img src="https://img.shields.io/tokei/lines/github/simplePCBuilding/myevent">
<img src="https://img.shields.io/github/issues-pr-raw/simplePCBuilding/myevent">
<img src="https://img.shields.io/github/languages/top/simplePCBuilding/myevent">
<img src="https://img.shields.io/github/directory-file-count/simplePCBuilding/myevent.svg">
<img src="https://img.shields.io/github/package-json/v/simplePCBuilding/myevent.svg">
</div>
A fully featured, fully free and open source event management solution you can host yourself, to manage your event and sell tickets. All you need is a webserver that can run node.js!
Visit our [website](https://myevent.janishutz.com)
# System requirements
- node.js V16.0+
- npm
- (OPTIONAL) MySQL
- any CPU from the last 10 years
- Any operating system that can run node.js
# Download
You may download this project using the GitHub releases page or the direct links on the [myevent website](https://myevent.janishutz.com/download) as this only downloads the ready-to-distribute version, not the development version.
Alternatively, you may download the project directly from GitHub (by cloning it or downloading the code) but you'll have to compile and package the project [manually](https://myevent.janishutz.com/docs/contributing/packaging).
# Contributing
If you want to contribute to this project, please read more [here](https://myevent.janishutz.com/docs/contributing). Until the end of October 2023, no contributions can be accepted into master.
# Supporting the project
If you like this project and it helped you save money, please consider donating to help fund the continuous development. If you are a company, please contact me [here](https://myevent.janishutz.com/docs/sponsoring) if you want to sponsor the project and become an official partner.
# Repository structure
- [assets/](/assets/): contains the logo (as png and GIMP file), also iOS and Android marketing materials, just global assets (images / videos)
- [src/](/src/): contains all of the source code of the project:
- [src/apps](/src/apps/): contains the source code of the iOS and Android app.
- [src/server](/src/server/): contains the source code for the node.js application that runs on the server side.
- [src/webapp](/src/webapp/): contains the source code for the frontend, contains lots of vue files.
- [website/](/website/): contains all of the website files:
- [website/dist/](/website/dist/): contains all the ready to distribute website files
- [website/src/](/website/src/): contains all of the source files (markdown format) for the website. These files are converted into HTML by the build script.
- [package.js](/package.js): collects all of the files of the project and copies them into the [dist/](/dist/) folder. It also minifies the files in the process to reduce package size.
- [.eslintrc.js](/.eslintrc.js): ESlint config, the linter used for the project
- [.gitignore](/.gitignore): ignored files, currently is node_modules & log files.
You may notice some additional folders appearing after running
```
npm run package
```
This is to shrink the repository size. Distribution ready files can be found in the releases or on our [website](https://myevent.janishutz.com/download).
## This project is currently NOT ready to be used!
Development of this project is currently ongoing and no stable version is available yet.
## ROADMAP
The goal is to get this tool fully functioning by the End of September 2023.
The goal is to get this tool fully functioning by the End of September 2023.

13
package-lock.json generated Normal file
View File

@@ -0,0 +1,13 @@
{
"name": "myevent",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "myevent",
"version": "1.0.0",
"license": "GPL-3.0-or-later"
}
}
}

24
package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "myevent",
"version": "1.0.0",
"description": "A free and open source event management solution",
"main": "/dist/app.js",
"scripts": {
"test": "npm run test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/simplePCBuilding/myevent.git"
},
"keywords": [
"eventmanager",
"events",
"tickets"
],
"author": "Janis Hutz",
"license": "GPL-3.0-or-later",
"bugs": {
"url": "https://github.com/simplePCBuilding/myevent/issues"
},
"homepage": "https://myevent.janishutz.com"
}

View File

@@ -7,93 +7,39 @@
*
*/
const path = require( 'path' );
const pwdmanager = require( './pwdmanager.js' );
const fs = require( 'fs' );
module.exports = ( app, settings ) => {
/*
Static routes for files like login screen, css, js and assets. Js and assets require login
*/
app.get( '/admin/login', ( request, response ) => {
if ( request.session.loggedIn ) {
response.redirect( '/admin' );
} else {
response.sendFile( path.join( __dirname + '/ui/login.html' ) );
}
} );
app.get( '/admin/loginLangPack', ( request, response ) => {
response.sendFile( path.join( __dirname + '/ui/js/loginLangPack.js' ) );
} );
app.get( '/admin/js/:file', ( request, response ) => {
if ( request.session.loggedIn ) {
response.sendFile( path.join( __dirname + '/ui/js/' + request.params.file ) );
}
} );
app.get( '/admin/css/:file', ( request, response ) => {
response.sendFile( path.join( __dirname + '/ui/css/' + request.params.file ) );
} );
/*
Admin login route that checks the password and, if enabled in settings, redirects to 2fa page or directly to admin panel
Admin login route that checks the password
*/
app.post( '/admin/auth', ( request, response ) => {
pwdmanager.checkpassword( request.body.mail, request.body.pwd ).then( data => {
if ( data ) {
if ( settings.twoFA ) {
response.sendFile( path.join( __dirname + '../admin/ui/2fa.html' ) );
response.send( '2fa' );
} else {
request.session.loggedIn = true;
response.redirect( '/admin' );
request.session.loggedInAdmin = true;
response.send( 'ok' );
}
} else {
response.send( 'Password wrong' );
response.send( 'pwErr' );
}
} );
} );
app.get( '/test/login', ( request, response ) => {
request.session.loggedInAdmin = true;
response.send( 'Logged in' );
} );
app.get( '/admin/logout', ( request, response ) => {
request.session.loggedIn = false;
request.session.loggedInAdmin = false;
response.send( 'logged out' );
} );
/*
main admin panel access route. Will serve an html file
that uses vue.js in its SPA form to make the admin panel
more responsive. Same technology is also used in the purchasing
system itself.
*/
app.get( '/admin', ( request, response ) => {
if ( request.session.loggedIn ) {
if ( settings[ 'init' ] ) {
response.sendFile( path.join( __dirname + '/ui/panel.html' ) );
} else {
response.sendFile( path.join( __dirname + '/ui/welcome.html' ) );
}
} else {
response.redirect( '/admin/login' );
}
} );
/*
Send admin panel modules to UI as UI uses Vue.js Router
*/
app.get( '/admin/panel/modules', ( request, response ) => {
let panelModules = { 'home': fs.readFileSync( path.join( __dirname + '/ui/panel/home.html' ) ).toString() };
if ( request.session.loggedIn ) {
response.send( panelModules );
}
} );
app.get( '/admin/setup', ( request, response ) => {
if ( request.session.loggedIn ) {
response.sendFile( path.join( __dirname + '/ui/setup.html' ) );
} else {
response.redirect( '/admin/login' );
}
app.get( '/api/getAuth', ( request, response ) => {
response.send( { 'admin': request.session.loggedInAdmin ? true : false, 'user': request.session.loggedInUser ? true : false } );
} );
};

View File

@@ -1,101 +0,0 @@
:root, :root.light {
--background-color: rgb(202, 223, 255);
--secondary-background: white;
--primary-color: black;
--primary-inverse: white;
--secondary-color: blue;
--secondary-hover: darkblue;
}
:root.dark {
--background-color: rgb(42, 44, 56);
--secondary-background: rgb(19, 20, 32);
--primary-color: white;
--primary-inverse: black;
--secondary-color: rgb(94, 94, 226);
--secondary-hover: rgb(155, 155, 255);
}
@media ( prefers-color-scheme: dark ) {
:root {
--background-color: rgb(42, 44, 56);
--secondary-background: rgb(19, 20, 32);
--primary-color: white;
--secondary-color: rgb(94, 94, 226);
--secondary-hover: rgb(155, 155, 255);
}
}
body {
background-color: var(--background-color);
font-family: sans-serif;
font-size: calc(12pt + 0.35vw);
}
.content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.login-app {
border-radius: 50px;
margin-top: 2%;
background-color: var(--secondary-background);
color: var(--primary-color);
width: 60%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.form {
width: 80%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 4%;
margin-bottom: 4%;
}
.button {
text-decoration: none;
color: white;
background-color: var(--secondary-color);
padding: 15px;
border-radius: 30px;
transition: 1s;
cursor: pointer;
}
.button:hover {
transition: ease-in-out 0.2s;
color: var(--primary-inverse);
background-color: var(--secondary-hover);
border-radius: 5px;
}
.input {
padding: 1%;
width: 80%;
margin-bottom: 3%;
border-radius: 10px;
}
.top-bar {
width: 100%;
display: flex;
align-items: end;
justify-content: end;
}
.selector {
background-color: lightblue;
border-radius: 20px;
padding: 0.5%;
border-style: solid;
border-color: blue;
}

View File

@@ -1,24 +0,0 @@
.setup-app {
border-radius: 50px;
margin-top: 2%;
background-color: var(--secondary-background);
color: var(--primary-color);
height: 85vh;
width: 90%;
}
.setup-page {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.explanation {
padding-left: 5%;
padding-right: 5%;
text-align: center;
font-style: italic;
}

View File

@@ -1,72 +0,0 @@
:root, :root.light {
--background-color: rgb(202, 223, 255);
--secondary-background: white;
--primary-color: black;
--primary-inverse: white;
--secondary-color: blue;
--secondary-hover: darkblue;
}
:root.dark {
--background-color: rgb(42, 44, 56);
--secondary-background: rgb(19, 20, 32);
--primary-color: white;
--primary-inverse: black;
--secondary-color: rgb(94, 94, 226);
--secondary-hover: rgb(155, 155, 255);
}
@media ( prefers-color-scheme: dark ) {
:root {
--background-color: rgb(42, 44, 56);
--secondary-background: rgb(19, 20, 32);
--primary-color: white;
--secondary-color: rgb(94, 94, 226);
--secondary-hover: rgb(155, 155, 255);
}
}
body {
background-color: var(--background-color);
font-family: sans-serif;
font-size: calc(12pt + 0.35vw);
}
.content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.button {
text-decoration: none;
color: white;
background-color: var(--secondary-color);
padding: 15px;
border-radius: 30px;
transition: 1s;
cursor: pointer;
}
.button:hover {
transition: ease-in-out 0.2s;
color: var(--primary-inverse);
background-color: var(--secondary-hover);
border-radius: 5px;
}
.input {
padding: 1%;
width: 80%;
margin-bottom: 3%;
border-radius: 10px;
}
.selector {
background-color: lightblue;
border-radius: 20px;
padding: 0.5%;
border-style: solid;
border-color: blue;
}

View File

@@ -1,35 +0,0 @@
/* eslint-disable no-undef */
fetch( '/admin/panel/modules' ).then( res => {
res.json().then( data => {
const Home = { template: data[ 'home' ] };
const About = { template: '<div>About</div>' };
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
];
const router = VueRouter.createRouter( {
history: VueRouter.createWebHashHistory(),
routes,
} );
const app = Vue.createApp( {
data() {
return {
};
},
watch: {
$route ( to, from ) {
console.log( 'changing route' );
}
},
} );
app.use( router );
app.mount( '#app' );
} );
} );

View File

@@ -1,32 +0,0 @@
let themeSelector2 = document.getElementById( 'theme' );
let languageSelector = document.getElementById( 'lang' );
let mailLabel = document.getElementById( 'mailLabel' );
let pwdLabel = document.getElementById( 'pwdLabel' );
let login = document.getElementById( 'login' );
languageSelector.value = sessionStorage.getItem( 'lang' );
function langUpdate () {
if ( sessionStorage.getItem( 'lang' ) === 'de' ) {
themeSelector2.options[0].innerHTML = 'Automatisch';
themeSelector2.options[1].innerHTML = 'Hellmodus';
themeSelector2.options[2].innerHTML = 'Dunkelmodus';
mailLabel.innerHTML = 'Email - Adresse';
pwdLabel.innerHTML = 'Passwort';
login.value = 'Anmelden';
} else {
themeSelector2.options[0].innerHTML = 'System theme';
themeSelector2.options[1].innerHTML = 'Light';
themeSelector2.options[2].innerHTML = 'Dark';
mailLabel.innerHTML = 'Email address';
pwdLabel.innerHTML = 'Password';
login.value = 'Log in';
}
}
function changeLang () {
sessionStorage.setItem( 'lang', languageSelector.value );
langUpdate();
}
langUpdate();

View File

@@ -1,77 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login :: myevent - admin panel</title>
<link rel="stylesheet" href="/admin/css/loginstyle.css">
<script defer src="/admin/loginLangPack"></script>
</head>
<body>
<div class="content">
<div class="top-bar">
<select name="lang" id="lang" class="selector" onchange="changeLang();">
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
<select name="theme" id="theme" class="selector" onchange="toggleTheme();">
<option value="system">System theme</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
<a href="https://myevent.janishutz.com"><img src="/assets/logo.png" alt="myevent-logo" style="height: 35vh;"></a>
<div class="login-app">
<h1>Login</h1>
<footer>myevent - admin panel</footer>
<form action="/admin/auth" method="post" class="form">
<label for="mail" id="mailLabel">Email address</label><br>
<input type="email" id="mail" name="mail" required class="input"><br>
<label for="pwd" id="pwdLabel">Password</label><br>
<input type="password" id="pwd" name="pwd" required class="input"><br>
<input type="submit" value="Log in" class="button" id="login">
</form>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
let themeSelector = document.getElementById( 'theme' );
if ( sessionStorage.getItem( 'theme' ) ) {
themeSelector.value = sessionStorage.getItem( 'theme' );
}
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || themeSelector.value === 'dark' ) {
document.documentElement.classList.add( 'dark' );
} else {
document.documentElement.classList.add( 'light' );
};
setTimeout( activate, 500 );
function activate () {
$( 'body' ).css( 'transition', '0.5s' );
}
function toggleTheme () {
sessionStorage.setItem( 'theme', themeSelector.value );
if ( themeSelector.value === 'dark' ) {
document.documentElement.classList.remove( 'light' );
document.documentElement.classList.add( 'dark' );
} else if ( themeSelector.value === 'light' ) {
document.documentElement.classList.remove( 'dark' );
document.documentElement.classList.add( 'light' );
} else if ( themeSelector.value === 'system' ) {
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches ) {
document.documentElement.classList.remove( 'light' );
document.documentElement.classList.add( 'dark' );
} else {
document.documentElement.classList.remove( 'dark' );
document.documentElement.classList.add( 'light' );
}
}
}
</script>
</body>
</html>

View File

@@ -1,77 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/admin/css/style.css">
<title>Admin panel :: myevent</title>
</head>
<body>
<div id="top">
<select name="lang" id="lang" class="selector" onchange="changeLang();">
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
<select name="theme" id="theme" class="selector" onchange="toggleTheme();">
<option value="system">System theme</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</div>
<div id="content">
<router-view></router-view>
</div>
</div>
<!-- Load vue.js and app -->
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/vue-router@4"></script>
<script src="/admin/js/index.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
let themeSelector = document.getElementById( 'theme' );
if ( sessionStorage.getItem( 'theme' ) ) {
themeSelector.value = sessionStorage.getItem( 'theme' );
}
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || themeSelector.value === 'dark' ) {
document.documentElement.classList.add( 'dark' );
} else {
document.documentElement.classList.add( 'light' );
};
setTimeout( activate, 500 );
function activate () {
$( 'body' ).css( 'transition', '0.5s' );
}
function toggleTheme () {
sessionStorage.setItem( 'theme', themeSelector.value );
if ( themeSelector.value === 'dark' ) {
document.documentElement.classList.remove( 'light' );
document.documentElement.classList.add( 'dark' );
} else if ( themeSelector.value === 'light' ) {
document.documentElement.classList.remove( 'dark' );
document.documentElement.classList.add( 'light' );
} else if ( themeSelector.value === 'system' ) {
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches ) {
document.documentElement.classList.remove( 'light' );
document.documentElement.classList.add( 'dark' );
} else {
document.documentElement.classList.remove( 'dark' );
document.documentElement.classList.add( 'light' );
}
}
}
</script>
</body>
</html>

View File

@@ -1,3 +0,0 @@
<div>
Hello World!
</div>

View File

@@ -1,152 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Setup :: myevent - admin panel</title>
<link rel="stylesheet" href="/admin/css/loginstyle.css">
<link rel="stylesheet" href="/admin/css/setupstyle.css">
</head>
<body>
<div class="content">
<div class="top-bar">
<select name="lang" id="lang" class="selector" onchange="changeLang();">
<option value="en">English</option>
<option value="de">Deutsch</option>
</select>
<select name="theme" id="theme" class="selector" onchange="toggleTheme()">
<option value="system">System theme</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
<div class="setup-app">
<!-- TODO: In docs note that user has to set up the mail account via config file. -->
<!-- Root account setup -->
<div class="setup-page" id="1">
<h1>Setup</h1>
<p class="explanation">Now, let's create an admin account! Please choose a secure password, as this user has the highest amounts of privileges.</p>
<form class="form" style="margin-bottom: 0;">
<label for="mail">Email address *</label><br>
<input type="email" id="mail" name="mail" required class="input"><br>
<label for="pwd1">Password *</label><br>
<input type="password" id="pwd1" name="pwd1" required class="input"><br>
<label for="pwd2">Repeat password *</label><br>
<input type="password" id="pwd2" name="pwd2" required class="input"><br>
</form>
<button class="button" style="margin-bottom: 3%;">Continue</button>
</div>
<!-- Company name & offered language(s) & choice if homepage or not -->
<div class="setup-page" id="2">
<h1>Setup</h1>
<p class="explanation">Next off, we need to know your organisation's name, the language(s) you want to offer and you need to select if a simple homepage should be generated from templates for you. You may choose the template in the next step.</p>
<form class="form" style="margin-bottom: 0;">
<label for="name">Organisation name *</label><br>
<input type="text" id="name" name="name" required class="input"><br>
<p>Languages * (one required, if none selected, uses English only)</p>
<div style="margin-bottom: 1%;">
<label for="lang-en">English</label>
<input type="checkbox" id="lang-en" name="lang-en">
</div>
<div style="margin-bottom: 3%;">
<label for="lang-de">German</label>
<input type="checkbox" id="lang-de" name="lang-de">
</div>
<div style="margin-bottom: 3%;">
<label for="homepage">Homepage?</label>
<input type="checkbox" id="homepage" name="homepage">
</div>
</form>
<button class="button" style="margin-bottom: 3%;">Continue</button>
</div>
<!-- Setup of homepage (if activated previously) -->
<div class="setup-page" id="3">
<h1>Setup</h1>
<p class="explanation">Since you have activated the homepage on the previous step, let's set it up now. Please choose from a template which you can find <a href="https://myevent.janishutz.com/docs/homepage/templates">here</a></p>
<form class="form" style="margin-bottom: 0;">
<label for="template">Choose homepage template</label><br>
<select name="template" id="template" style="margin-bottom: 3%;">
<option value="1">Template 1</option>
<option value="2">Template 2</option>
<option value="3">Template 3</option>
<option value="4">Template 4</option>
</select>
<label for="pwd1">Promotional text *</label><br>
<textarea name="" id="" rows="10" class="input" minlength="100" placeholder="Your promotional text"></textarea>
</form>
<button class="button" style="margin-bottom: 1.5%; margin-top: 2%;">Preview</button>
<button class="button" style="margin-bottom: 3%;">Continue</button>
</div>
<!-- Payment options (one required) -->
<div class="setup-page" id="3">
<h1>Setup</h1>
<p class="explanation">Now, please choose a payment option. When entering the admin panel for the first time after completing the setup, you'll be prompted to enter tokens which you can find in the payment provider's dashboard. You there may also add more payment options that are provided by plugins. Please refer to the <a href="https://myevent.janishutz.com/docs/payments">docs</a> to see advantages and disadvantages of each payment provider.</p>
<form class="form" style="margin-bottom: 0;">
<label for="name">Organisation name *</label><br>
<input type="text" id="name" name="name" required class="input"><br>
<p>Languages * (one required, if none selected, uses English only)</p>
<div style="margin-bottom: 1%;">
<label for="lang-en">English</label>
<input type="checkbox" id="lang-en" name="lang-en">
</div>
<div style="margin-bottom: 3%;">
<label for="lang-de">German</label>
<input type="checkbox" id="lang-de" name="lang-de">
</div>
<div style="margin-bottom: 3%;">
<label for="homepage">Homepage?</label>
<input type="checkbox" id="homepage" name="homepage">
</div>
</form>
<button class="button" style="margin-bottom: 1.5%; margin-top: 2%;">Preview</button>
<button class="button" style="margin-bottom: 3%;">Continue</button>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
let themeSelector = document.getElementById( 'theme' );
if ( sessionStorage.getItem( 'theme' ) ) {
themeSelector.value = sessionStorage.getItem( 'theme' );
}
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || themeSelector.value === 'dark' ) {
document.documentElement.classList.add( 'dark' );
} else {
document.documentElement.classList.add( 'light' );
};
setTimeout( activate, 500 );
function activate () {
$( 'body' ).css( 'transition', '0.5s' );
}
function toggleTheme () {
sessionStorage.setItem( 'theme', themeSelector.value );
if ( themeSelector.value === 'dark' ) {
document.documentElement.classList.remove( 'light' );
document.documentElement.classList.add( 'dark' );
} else if ( themeSelector.value === 'light' ) {
document.documentElement.classList.remove( 'dark' );
document.documentElement.classList.add( 'light' );
} else if ( themeSelector.value === 'system' ) {
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches ) {
document.documentElement.classList.remove( 'light' );
document.documentElement.classList.add( 'dark' );
} else {
document.documentElement.classList.remove( 'dark' );
document.documentElement.classList.add( 'light' );
}
}
}
</script>
</body>
</html>

View File

@@ -1,36 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome :: myevent - admin panel</title>
<link rel="stylesheet" href="/admin/css/loginstyle.css">
<style>
.myevent-logo-welcome {
height: 50vh;
}
</style>
</head>
<body>
<div class="content">
<h1>Welcome to myevent!</h1>
<a href="https://myevent.janishutz.com"><img src="/assets/logo.png" alt="myevent-logo" class="myevent-logo-welcome"></a>
<p>myevent, the new way to sell your tickets online!</p>
<p>Let's get started setting up your event!</p>
<p id="warranty">Note: As a project licensed under the GNU General Public License Version 3.0-or-later (GPL 3.0+), this software comes with ABSOLUTELY NO WARRANTY TO THE EXTENT PERMITTED BY APPLICABLE LAW</p>
<p>It is strongly advised to read through our <a href="https://myevent.janishutz.com/docs/setup/">setup guide</a> whilst doing the setup, as it contains vital information and shows you additional options which you can configure in the configuration file.</p>
<a href="/admin/setup">Getting Started!</a>
</div>
<!--Script for dark mode-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || sessionStorage.getItem( 'theme' ) === 'dark' ) {
document.documentElement.classList.add( 'dark' );
} else {
document.documentElement.classList.add( 'light' );
};
</script>
</body>
</html>

View File

@@ -11,20 +11,15 @@ const express = require( 'express' );
let app = express();
const path = require( 'path' );
const expressSession = require( 'express-session' );
const fs = require( 'fs' );
const bodyParser = require( 'body-parser' );
const cookieParser = require( 'cookie-parser' );
const favicon = require( 'serve-favicon' );
const http = require( 'http' );
const serveStatic = require( 'serve-static' );
const fs = require( 'fs' );
// const env = process.env.PROD || false;
const root = process.env.ROOT || '/order';
const settings = JSON.parse( fs.readFileSync( path.join( __dirname + '/config.json' ) ) );
// initialise express with middlewares
app.use( expressSession( {
secret: 'gaoevgoawefgo083tq2rfvöfaf0p8',
@@ -35,43 +30,15 @@ app.use( expressSession( {
app.use( bodyParser.urlencoded( { extended: false } ) );
app.use( bodyParser.json() );
app.use( cookieParser() );
app.use( favicon( path.join( __dirname + '/ui/assets/logo.png' ) ) );
app.use( serveStatic( __dirname + '/admin/ui/modules' ) );
// app.use( favicon( path.join( __dirname + '/ui/assets/logo.png' ) ) );
app.use( express.static( '../webapp/dist' ) );
require( './admin/routes.js' )( app, settings ); // admin route
if ( settings[ 'init' ] ) {
if ( root !== '/' ) {
app.get( '/', ( request, response ) => {
let lang = request.query.lang || 'en';
response.sendFile( path.join( __dirname + '/ui/html/' + lang + '/index.html' ) );
} );
}
} else {
app.get( '/', ( request, response ) => {
response.sendFile( path.join( __dirname + '/ui/html/index.html' ) );
} );
}
// Assets route for logo, etc
app.get( '/assets/:file', ( request, response ) => {
response.sendFile( path.join( __dirname + '/ui/assets/' + request.params.file ) );
} );
// CSS route for all user-facing CSS files
app.get( '/css/:file', ( request, response ) => {
response.sendFile( path.join( __dirname + '/ui/css/' + request.params.file ) );
} );
// create 404 handler
// eslint-disable-next-line no-unused-vars
app.use( ( request, response, next ) => {
response.sendFile( path.join( __dirname + '/ui/html/en/errorResponses/404.html' ) );
app.use( ( request, response ) => {
response.sendFile( path.join( __dirname + '/../webapp/dist/index.html' ) );
} );
const PORT = process.env.PORT || 8080;

View File

@@ -53,9 +53,9 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
@@ -85,9 +85,9 @@
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
"integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
@@ -95,21 +95,27 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
"version": "0.3.18",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz",
@@ -481,9 +487,9 @@
}
},
"node_modules/entities": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"engines": {
"node": ">=0.12"
@@ -816,14 +822,14 @@
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/html-minifier-terser": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.1.0.tgz",
"integrity": "sha512-BvPO2S7Ip0Q5qt+Y8j/27Vclj6uHC6av0TMoDn7/bJPhMWHI2UtR2e/zEgJn3/qYAmxumrGp9q4UHurL6mtW9Q==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
"integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
"dev": true,
"dependencies": {
"camel-case": "^4.1.2",
"clean-css": "5.2.0",
"commander": "^9.4.1",
"clean-css": "~5.3.2",
"commander": "^10.0.0",
"entities": "^4.4.0",
"param-case": "^3.0.4",
"relateurl": "^0.2.7",
@@ -836,16 +842,13 @@
"node": "^14.13.1 || >=16.0.0"
}
},
"node_modules/html-minifier-terser/node_modules/clean-css": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.0.tgz",
"integrity": "sha512-2639sWGa43EMmG7fn8mdVuBSs6HuWaSor+ZPoFWzenBc6oN+td8YhTfghWXZ25G1NiiSvz8bOFBS7PdSbTiqEA==",
"node_modules/html-minifier-terser/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true,
"dependencies": {
"source-map": "~0.6.0"
},
"engines": {
"node": ">= 10.0"
"node": ">=14"
}
},
"node_modules/http-errors": {
@@ -1066,9 +1069,9 @@
}
},
"node_modules/minipass": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz",
"integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==",
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
"integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
"engines": {
"node": ">=8"
}
@@ -1359,9 +1362,9 @@
}
},
"node_modules/readable-stream": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz",
"integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==",
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -1432,9 +1435,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
"integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -1637,9 +1640,9 @@
}
},
"node_modules/terser": {
"version": "5.16.5",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz",
"integrity": "sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==",
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz",
"integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
@@ -1796,9 +1799,9 @@
},
"dependencies": {
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"requires": {
"@jridgewell/set-array": "^1.0.1",
@@ -1819,9 +1822,9 @@
"dev": true
},
"@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz",
"integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==",
"dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
@@ -1829,19 +1832,27 @@
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.17",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
"integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
"version": "0.3.18",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
},
"dependencies": {
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
}
}
},
"@mapbox/node-pre-gyp": {
@@ -2133,9 +2144,9 @@
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
},
"entities": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true
},
"escape-html": {
@@ -2406,28 +2417,25 @@
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"html-minifier-terser": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.1.0.tgz",
"integrity": "sha512-BvPO2S7Ip0Q5qt+Y8j/27Vclj6uHC6av0TMoDn7/bJPhMWHI2UtR2e/zEgJn3/qYAmxumrGp9q4UHurL6mtW9Q==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
"integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
"dev": true,
"requires": {
"camel-case": "^4.1.2",
"clean-css": "5.2.0",
"commander": "^9.4.1",
"clean-css": "~5.3.2",
"commander": "^10.0.0",
"entities": "^4.4.0",
"param-case": "^3.0.4",
"relateurl": "^0.2.7",
"terser": "^5.15.1"
},
"dependencies": {
"clean-css": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.2.0.tgz",
"integrity": "sha512-2639sWGa43EMmG7fn8mdVuBSs6HuWaSor+ZPoFWzenBc6oN+td8YhTfghWXZ25G1NiiSvz8bOFBS7PdSbTiqEA==",
"dev": true,
"requires": {
"source-map": "~0.6.0"
}
"commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true
}
}
},
@@ -2591,9 +2599,9 @@
}
},
"minipass": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz",
"integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ=="
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
"integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="
},
"minizlib": {
"version": "2.1.2",
@@ -2800,9 +2808,9 @@
}
},
"readable-stream": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz",
"integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==",
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -2844,9 +2852,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
"integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
"requires": {
"lru-cache": "^6.0.0"
}
@@ -3019,9 +3027,9 @@
}
},
"terser": {
"version": "5.16.5",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz",
"integrity": "sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==",
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz",
"integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.2",

View File

@@ -1,54 +0,0 @@
/*
* myevent - errorstyle.css
*
* Created by Janis Hutz 03/11/2023, Licensed under the GPL V3 License
* https://janishutz.com, development@janishutz.com
*
*
*/
html {
height: 98%;
}
body {
background-color: lightgray;
font-family: monospace;
height: 100%;
}
.content {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
width: 100%;
}
.code {
font-size: 20vw;
margin: 0;
padding: 0;
}
.message {
font-size: 1.5vw;
}
.button {
text-decoration: none;
background-color: gray;
font-style: italic;
font-size: 1vw;
color: white;
padding: 20px;
border-radius: 30px;
transition: 1s;
}
.button:hover {
transition: ease-in-out 0.2s;
background-color: black;
border-radius: 5px;
}

View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>403 - Forbidden</title>
<link rel="stylesheet" href="/css/errorstyle.css">
</head>
<body>
<div class="content">
<h1 class="code">403</h1>
<h2 class="message">You are currently not allowed to visit this page!</h2>
<a href="/" class="button">Back to the homepage</a>
</div>
</body>
</html>

View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 - Not found</title>
<link rel="stylesheet" href="/css/errorstyle.css">
</head>
<body>
<div class="content">
<h1 class="code">404</h1>
<h2 class="message">The page you are looking for was not found on the server!</h2>
<a href="/" class="button">Back to the homepage</a>
</div>
</body>
</html>

View File

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>500 - Internal Server Error</title>
<link rel="stylesheet" href="/css/errorstyle.css">
</head>
<body>
<div class="content">
<h1 class="code">500</h1>
<h2 class="message">The server encountered a problem whilst processing your request!</h2>
<a href="/" class="button">Back to the homepage</a>
</div>
</body>
</html>

View File

@@ -1,91 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>myevent</title>
<script defer src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script defer src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- We are using inline styling to simplify the removal of this page -->
<style>
body {
background-color: rgb(202, 223, 255);
font-family: sans-serif;
}
.content, .lang {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.logo {
margin: 2%;
height: 50vh;
}
.lang {
display: none;
background-color: white;
padding: 2%;
margin: 3%;
margin-bottom: 0;
border-radius: 50px;
width: 40%;
}
.button {
cursor: pointer;
text-decoration: none;
color: white;
background-color: blue;
padding: 30px;
border-radius: 30px;
transition: 1s;
}
.button:hover {
transition: ease-in-out 0.2s;
background-color: darkblue;
border-radius: 5px;
}
.presented {
display: flex;
}
</style>
</head>
<body>
<div class="content">
<img src="/assets/logo.png" alt="myevent-logo" class="logo">
<div class="lang presented" id="en">
<h1>Welcome to myevent!</h1>
<!-- <p>Thank you for installing and using myevent! Let's get started by setting it up! First plan of action is to log in to the admin panel where you can replace this page here with your own landing page!</p> -->
<a href="/admin/login" class="button" onclick="sessionStorage.setItem( 'lang', 'en' )">To the admin panel</a>
</div>
<div class="lang" id="de">
<h1>Willkommen zu myevent!</h1>
<!-- <p>Vielen Dank für die Installation und Nutzung von myevent! Beginnen wir mit dem Aufsetzen! Der erste Schritt ist, sich ins Admin-Panel einzuloggen um die Seite hier mit deiner eigenen Seite zu ersetzen!</p> -->
<a href="/admin/login" class="button" onclick="sessionStorage.setItem( 'lang', 'de' )">Zum Admin-Panel</a>
</div>
</div>
<script>
let lang = [ 'en', 'de' ];
let pos = 0;
function switchPresented () {
console.log( 'updating' );
$( `#${ lang[ pos ] }` ).removeClass( 'presented' );
if ( pos + 2 > lang.length ) {
pos = 0;
} else {
pos += 1;
}
$( `#${ lang[ pos ] }` ).addClass( 'presented' );
}
setInterval( switchPresented, 5000 );
</script>
</body>
</html>

View File

@@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

23
src/webapp/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

19
src/webapp/README.md Normal file
View File

@@ -0,0 +1,19 @@
# myevent
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
};

19
src/webapp/jsconfig.json Normal file
View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

17465
src/webapp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
src/webapp/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "myevent",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"core-js": "^3.8.3",
"pinia": "^2.0.34",
"vue": "^3.2.13",
"vue-router": "^4.0.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200">
<script defer src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

170
src/webapp/src/App.vue Normal file
View File

@@ -0,0 +1,170 @@
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/tickets">Tickets</router-link> |
<router-link to="/cart">Cart</router-link> |
<router-link to="/login">Account</router-link> |
<button @click="changeTheme();" v-html="theme" id="themeSelector"></button>
</nav>
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</template>
<style>
:root, :root.light {
--primary-color: #2c3e50;
--accent-background: rgb(30, 30, 82);
--secondary-color: white;
--background-color: white;
--accent-color: #42b983;
--hover-color: rgb(165, 165, 165);
--accent-background-hover: #4380a8;
--overlay-color: rgba(37, 37, 37, 0.575);
--inactive-color: rgb(100, 100, 100);
--highlight-backdrop: rgb(143, 134, 192);
}
:root.dark {
--primary-color: white;
--accent-background: rgb(100, 100, 190);
--secondary-color: black;
--background-color: rgb(32, 32, 32);
--accent-color: #42b983;
--hover-color: rgb(83, 83, 83);
--accent-background-hover: #4380a8;
--overlay-color: rgba(104, 104, 104, 0.575);
--inactive-color: rgb(190, 190, 190);
--highlight-backdrop: rgb(85, 63, 207);
}
@media ( prefers-color-scheme: dark ) {
:root {
--primary-color: white;
--accent-background: rgb(100, 100, 190);
--secondary-color: black;
--background-color: rgb(32, 32, 32);
--accent-color: #42b983;
--hover-color: rgb(83, 83, 83);
--accent-background-hover: #4380a8;
--overlay-color: rgba(104, 104, 104, 0.575);
--inactive-color: rgb(190, 190, 190);
--highlight-backdrop: rgb(85, 63, 207);
}
}
::selection {
background-color: var( --highlight-backdrop );
}
#themeSelector {
background-color: rgba( 0, 0, 0, 0 );
color: var( --primary-color );
font-size: 130%;
padding: 0;
margin: 0;
border: none;
cursor: pointer;
}
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#app {
transition: 0.5s;
background-color: var( --background-color );
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: var( --primary-color );
height: 100%;
display: flex;
flex-direction: column;
}
nav {
padding: 30px;
}
nav a {
font-weight: bold;
color: var( --primary-color );
}
nav a.router-link-exact-active {
color: #42b983;
}
.scale-enter-active,
.scale-leave-active {
transition: all 0.5s ease;
}
.scale-enter-from,
.scale-leave-to {
opacity: 0;
transform: scale(0.9);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.4s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 48
}
</style>
<script>
export default {
name: 'app',
data () {
return {
theme: '',
}
},
methods: {
changeTheme () {
if ( this.theme === '&#9788;' ) {
document.documentElement.classList.remove( 'dark' );
document.documentElement.classList.add( 'light' );
sessionStorage.setItem( 'theme', '&#9789;' );
this.theme = '&#9789;';
} else if ( this.theme === '&#9789;' ) {
document.documentElement.classList.remove( 'light' );
document.documentElement.classList.add( 'dark' );
sessionStorage.setItem( 'theme', '&#9788;' );
this.theme = '&#9788;';
}
}
},
created () {
this.theme = sessionStorage.getItem( 'theme' ) ? sessionStorage.getItem( 'theme' ) : '';
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || this.theme === '&#9788;' ) {
document.documentElement.classList.add( 'dark' );
this.theme = '&#9788;';
} else {
document.documentElement.classList.add( 'light' );
this.theme = '&#9789;';
}
}
}
</script>

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,35 @@
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<div>
<h1>Welcome to myevent!</h1>
<p>Let's start the setup by entering the setup key below! You may define a setup key in the config file of myevent. See <a href="https://myevent.janishutz.com/docs/setup/setup" target="_blank">here</a> for more instructions</p>
<form>
<label for="key">Your setup key</label><br>
<input type="text" v-model="formData[ 'key' ]" required name="key" id="key">
</form>
<button @click="setup();" class="button">Start setup</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
formData: {},
}
},
methods: {
setup () {
this.$router.push( '/setup' );
}
},
}
</script>
<style scoped>
img {
width: 20%;
}
</style>

View File

@@ -0,0 +1,344 @@
<template>
<div class="seatingWrapper">
<div class="sidebar">
<h2>{{ eventInfo.name }}</h2>
<h3>{{ eventInfo.date }}</h3>
<h3>{{ eventInfo.location }}</h3>
<h3>Selected tickets</h3>
<table class="price-table" v-for="event in selectedSeats">
<tr v-if="Object.keys( event.selectedSeats ).length">
<h4>{{ event.name }}</h4>
</tr>
<tr v-for="ticket in event.selectedSeats">
<td>{{ ticket.name }} ({{ ticket.ageGroup }})</td>
<td>{{ eventInfo[ 'currency' ] }} {{ ticket[ 'price' ] }}</td>
</tr>
</table>
<h3>Total: {{ eventInfo[ 'currency' ] }} {{ total }}</h3>
<router-link to="/cart">To cart</router-link>
</div>
<div class="seatingPlan">
<h3>Available tickets</h3>
<div v-for="ticket in tickets">
{{ ticket.name }} ({{ eventInfo[ 'categories' ][ ticket.category ][ 'name' ] }}) - {{ eventInfo.currency }} {{ eventInfo.categories[ ticket.category ][ 'price' ][ '1' ] }} <span class="material-symbols-outlined">add</span> Selected <span class="material-symbols-outlined">remove</span>
</div>
</div>
<div class="overlay" id="placeNotAvailable">
<div class="popup">
<div class="popup-content">
<div class="close-container">
<span class="material-symbols-outlined close-button" @click="closePlaceNotAvailablePopup()">close</span>
</div>
<div class="popup-content-wrapper">
<h3>One or more seat(s) you have previously selected is/are no longer available!</h3>
<p>Please select another one!</p>
<button class="button" @click="closePlaceNotAvailablePopup()">Ok</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'noseatplan',
props: {
ticketID: String
},
data () {
return {
tickets: { 'ticket1': { 'name': 'Ticket 1', 'id': 'ticket1', 'category': 1 }, 'ticket2': { 'name': 'Ticket 2', 'id': 'ticket2', 'category': 2 } },
eventInfo: { 'name': 'TestEvent', 'location': 'TestLocation', 'date': 'TestDate', 'RoomName': 'TestRoom', 'currency': 'CHF', 'categories': { '1': { 'price': { '1':25, '2':35 }, 'bg': 'black', 'fg': 'white', 'name': 'Category 1' }, '2': { 'price': { '1':15, '2':20 }, 'bg': 'green', 'fg': 'white', 'name': 'Category 2' } }, 'ageGroups': { '1':{ 'id': 1, 'name':'Child', 'age':'0 - 15.99' }, '2':{ 'id': 2, 'name': 'Adult', 'age': null } }, 'ageGroupCount':2, 'stage': true },
selectedSeats: {},
pricingCurrentlySelected: {},
total: 0,
}
},
methods: {
loadPreviouslySelected () {
/*
This function is called whenever the data on the webpage is to be reloaded
*/
// load data from cart and set up cart if not available
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
cart[ this.ticketID ?? 'default' ] = cart[ this.ticketID ?? 'default' ] ? cart[ this.ticketID ?? 'default' ] : { 'name': this.eventInfo.name, 'date': this.eventInfo.date, 'location': this.eventInfo.location, 'currency': this.eventInfo.currency };
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let data = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let showError = false
for ( let i in data ) {
if ( this.seating[ data[ i ][ 'row' ] ][ 'content' ][ data[ i ][ 'seat' ] ][ 'available' ] ) {
this.seating[ data[ i ][ 'row' ] ][ 'content' ][ data[ i ][ 'seat' ] ][ 'selected' ] = true;
} else {
showError = true;
delete data[ i ];
}
}
if ( showError ) {
setTimeout( function () {
$( '#placeNotAvailable' ).show( 200 );
console.log( 'showing error message' );
}, 500 );
}
// check if no ticket selected and prevent writing if no ticket
// selected to not show too many events
let isEmpty = sessionStorage.getItem( 'selectedTicket' ) ? false : true;
if ( !isEmpty ) {
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = data;
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
}
this.selectedSeats = cart;
this.sumUp();
},
sumUp () {
// This function calculates the total price of the tickets for this event.
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
let price = 0;
for ( let i in cart ) {
for ( let entry in cart[ i ][ 'selectedSeats' ] ) {
price += parseInt( cart[ i ][ 'selectedSeats' ][ entry ][ 'price' ] );
}
}
let back = {};
back[ 'total' ] = price;
back[ 'currency' ] = this.eventInfo.currency;
this.total = price;
// check if no ticket selected and prevent writing if no ticket
// selected to not show too many events
let isEmpty = sessionStorage.getItem( 'selectedTicket' ) ? false : true;
if ( !isEmpty ) {
sessionStorage.setItem( 'backend', JSON.stringify( back ) );
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
}
},
closePlaceNotAvailablePopup () {
$( '#placeNotAvailable' ).hide( 300 );
},
selectSeat( placeID, rowID ) {
/*
This function allows the user to select a seat and deselect it, if it has previously
been selected.
*/
sessionStorage.setItem( 'tempStorage', JSON.stringify( { 1:[ placeID, rowID ] } ) );
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
cart[ this.ticketID ?? 'default' ] = cart[ this.ticketID ?? 'default' ] ? cart[ this.ticketID ?? 'default' ] : {};
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let data = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let isDeleting = false;
for ( let i in data ) {
if ( data[ i ][ 'seat' ] == placeID && data[ i ][ 'row' ] == rowID ) {
delete data[ i ];
isDeleting = true;
}
}
this.seating[ rowID ][ 'content' ][ placeID ][ 'selected' ] = !isDeleting;
if ( isDeleting ) {
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = data;
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
this.selectedSeats = cart;
this.sumUp();
} else {
if ( this.eventInfo.ageGroupCount > 1 ) {
$( '#overlay' ).show( 200 );
} else {
this.storeSeat( '1' );
}
}
this.pricingCurrentlySelected = this.eventInfo[ 'categories' ][ this.seating[ rowID ][ 'content' ][ placeID ][ 'category' ] ][ 'price' ];
},
closePopup () {
// This function closes the popup and sets the seat to not selected
$( '#overlay' ).hide( 200 );
let seat = JSON.parse( sessionStorage.getItem( 'tempStorage' ) );
this.seating[ seat[ 1 ][ 1 ] ][ 'content' ][ seat[ 1 ][ 0 ] ][ 'selected' ] = false;
},
storeSeat( ticketOption ) {
/*
This function stores a ticket into the event's selected seat sessionStorage.
*/
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
cart[ this.ticketID ?? 'default' ] = cart[ this.ticketID ?? 'default' ] ? cart[ this.ticketID ?? 'default' ] : { 'name': this.eventInfo.name, 'date': this.eventInfo.date, 'location': this.eventInfo.location, 'currency': this.eventInfo.currency };
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let data = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let seat = JSON.parse( sessionStorage.getItem( 'tempStorage' ) );
let ticket = this.seating[ seat[ 1 ][ 1 ] ][ 'content' ][ seat[ 1 ][ 0 ] ];
let ticketData = { 'name': ticket[ 'name' ], 'categoryID': ticketOption, 'category': this.eventInfo[ 'categories' ][ ticket[ 'category' ] ], 'price': this.eventInfo[ 'categories' ][ this.seating[ seat[ 1 ][ 1 ] ][ 'content' ][ seat[ 1 ][ 0 ] ][ 'category' ] ][ 'price' ][ ticketOption ], 'row':seat[ 1 ][ 1 ], 'seat':seat[ 1 ][ 0 ], 'ageGroup': this.eventInfo[ 'ageGroups' ][ ticketOption ][ 'name' ] };
data[ String( seat[ 1 ][ 1 ] ) + String( seat[ 1 ][ 0 ] ) ] = ticketData;
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = data;
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
$( '#overlay' ).hide( 200 );
this.selectedSeats = cart;
this.sumUp();
},
},
created() {
this.loadPreviouslySelected();
}
}
</script>
<style scoped>
.seatingWrapper {
display: grid;
grid-template-areas:
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar';
height: 100%;
}
.sidebar {
grid-area: sidebar;
background-color: var( --accent-background );
color: var( --secondary-color );
overflow: scroll;
}
.seatingPlan {
grid-area: main;
display: flex;
flex-direction: column;
align-items: center;
justify-content: justify;
overflow: scroll;
}
.active {
cursor: pointer;
}
.occupied {
background-color: var( --hover-color );
padding: 0.4%;
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var( --overlay-color );
height: 100%;
width: 100%;
}
.popup {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.popup-content {
background-color: var( --background-color );
height: 60%;
width: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
border-radius: 50px;
}
.popup-content-wrapper {
display: flex;
height: 90%;
width: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: scroll;
}
.close-container {
width: 100%;
display: flex;
justify-content: flex-end;
}
.close-button {
cursor: pointer;
margin-right: 3vh;
margin-top: 3vh;
}
.option {
list-style: none;
padding: 7px 15px;
border-radius: 10px;
border-color: var( --primary-color );
border-style: solid;
border-width: 1px;
margin: 3px 0px;
cursor: pointer;
}
.stage {
border-color: var( --primary-color );
border-style: solid;
width: 80%;
height: 7%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.button {
background-color: var( --accent-background );
color: var( --secondary-color );
font-weight: bold;
font-size: 110%;
border-radius: 20px;
border-style: none;
padding: 10px 40px;
transition: 0.6s;
}
.button:hover {
background-color: var( --accent-background-hover );
transition: 0.3s;
cursor: pointer;
}
.price-table {
width: 100%;
}
</style>

View File

@@ -0,0 +1,379 @@
<template>
<div class="seatingWrapper">
<div class="sidebar">
<h2>{{ eventInfo.name }}</h2>
<h3>{{ eventInfo.date }}</h3>
<h3>{{ eventInfo.location }}</h3>
<h3>Selected tickets</h3>
<table class="price-table" v-for="event in selectedSeats">
<tr v-if="Object.keys( event.selectedSeats ).length">
<h4>{{ event.name }}</h4>
</tr>
<tr v-for="ticket in event.selectedSeats">
<td>{{ ticket.name }} ({{ ticket.ageGroup }})</td>
<td>{{ eventInfo[ 'currency' ] }} {{ ticket[ 'price' ] }}</td>
</tr>
</table>
<h3>Total: {{ eventInfo[ 'currency' ] }} {{ total }}</h3>
<router-link to="/cart">To cart</router-link>
</div>
<div class="seatingPlan">
<h3>Seating plan</h3>
<p>{{ eventInfo.RoomName }}</p>
<p class="stage" v-if="eventInfo.stage">Stage</p>
<div class="seating">
<table>
<tr v-for="row in seating">
<td>
{{ row.name }}
</td>
<td v-for="place in row.content">
<div :class="place.category" class="active" v-if="place.available" @click="selectSeat( place.id, row.id )">
<div v-if="place.selected" :title="row.name + ', ' + place.name + ' is currently selected, click to deselect'">
<span class="material-symbols-outlined">done</span>
</div>
<div v-else :title="row.name + ', ' + place.name + ' is not currently selected, click to select'">
<span class="material-symbols-outlined">living</span>
</div>
</div>
<div v-else class="occupied">
<span class="material-symbols-outlined">close</span>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="overlay" id="overlay">
<div class="popup">
<div class="popup-content">
<div class="close-container">
<span class="material-symbols-outlined close-button" @click="closePopup()">close</span>
</div>
<div class="popup-content-wrapper">
<h3>Choose a ticket option</h3>
<ul v-for="group in eventInfo.ageGroups">
<li @click="storeSeat( group.id )" class="option">{{ group.name }} <i v-if="group.age">(0 - 15.99 years)</i> - {{ eventInfo.currency }} {{ pricingCurrentlySelected[ group.id ] }}</li>
</ul>
</div>
</div>
</div>
</div>
<div class="overlay" id="placeNotAvailable">
<div class="popup">
<div class="popup-content">
<div class="close-container">
<span class="material-symbols-outlined close-button" @click="closePlaceNotAvailablePopup()">close</span>
</div>
<div class="popup-content-wrapper">
<h3>One or more seat(s) you have previously selected is/are no longer available!</h3>
<p>Please select another one!</p>
<button class="button" @click="closePlaceNotAvailablePopup()">Ok</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'seatplan',
props: {
ticketID: String
},
data () {
return {
seating: { 'r1': { 'name': 'Row 1', 'id': 'r1', 'content':{ 'S1':{ 'name': 'Seat 1', 'id': 'S1', 'available': true, 'selected': false, 'category':'1' } } }, 'r2': { 'name': 'Row 2', 'id': 'r2', 'content':{ 'S1':{ 'name': 'S1', 'id': 'S1', 'available': true, 'selected': false, 'category':'2' } } } },
eventInfo: { 'name': 'TestEvent2', 'location': 'TestLocation2', 'date': 'TestDate2', 'RoomName': 'TestRoom2', 'currency': 'CHF', 'categories': { '1': { 'price': { '1':25, '2':35 }, 'bg': 'black', 'fg': 'white', 'name': 'Category 1' }, '2': { 'price': { '1':15, '2':20 }, 'bg': 'green', 'fg': 'white', 'name': 'Category 2' } }, 'ageGroups': { '1':{ 'id': 1, 'name':'Child', 'age':'0 - 15.99' }, '2':{ 'id': 2, 'name': 'Adult', 'age': null } }, 'ageGroupCount': 2, 'stage': true, 'maxTickets': 2 },
selectedSeats: {},
pricingCurrentlySelected: {},
total: 0,
}
},
methods: {
loadPreviouslySelected () {
/*
This function is called whenever the data on the webpage is to be reloaded
*/
// load data from cart and set up cart if not available
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
cart[ this.ticketID ?? 'default' ] = cart[ this.ticketID ?? 'default' ] ? cart[ this.ticketID ?? 'default' ] : { 'name': this.eventInfo.name, 'date': this.eventInfo.date, 'location': this.eventInfo.location, 'currency': this.eventInfo.currency };
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let data = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let showError = false
for ( let i in data ) {
if ( this.seating[ data[ i ][ 'row' ] ][ 'content' ][ data[ i ][ 'seat' ] ][ 'available' ] ) {
this.seating[ data[ i ][ 'row' ] ][ 'content' ][ data[ i ][ 'seat' ] ][ 'selected' ] = true;
} else {
showError = true;
delete data[ i ];
}
}
if ( showError ) {
setTimeout( function () {
$( '#placeNotAvailable' ).show( 200 );
console.log( 'showing error message' );
}, 500 );
}
// check if no ticket selected and prevent writing if no ticket
// selected to not show too many events
let isEmpty = sessionStorage.getItem( 'selectedTicket' ) ? false : true;
if ( !isEmpty ) {
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = data;
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
}
this.selectedSeats = cart;
this.sumUp();
},
sumUp () {
// This function calculates the total price of the tickets for this event.
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
let price = 0;
for ( let i in cart ) {
for ( let entry in cart[ i ][ 'selectedSeats' ] ) {
price += parseInt( cart[ i ][ 'selectedSeats' ][ entry ][ 'price' ] );
}
}
let back = {};
back[ 'total' ] = price;
back[ 'currency' ] = this.eventInfo.currency;
this.total = price;
// check if no ticket selected and prevent writing if no ticket
// selected to not show too many events
let isEmpty = sessionStorage.getItem( 'selectedTicket' ) ? false : true;
if ( !isEmpty ) {
sessionStorage.setItem( 'backend', JSON.stringify( back ) );
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
}
},
closePlaceNotAvailablePopup () {
$( '#placeNotAvailable' ).hide( 300 );
},
selectSeat( placeID, rowID ) {
/*
This function allows the user to select a seat and deselect it, if it has previously
been selected.
*/
sessionStorage.setItem( 'tempStorage', JSON.stringify( { 1:[ placeID, rowID ] } ) );
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
cart[ this.ticketID ?? 'default' ] = cart[ this.ticketID ?? 'default' ] ? cart[ this.ticketID ?? 'default' ] : {};
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let data = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let isDeleting = false;
for ( let i in data ) {
if ( data[ i ][ 'seat' ] == placeID && data[ i ][ 'row' ] == rowID ) {
delete data[ i ];
isDeleting = true;
}
}
this.seating[ rowID ][ 'content' ][ placeID ][ 'selected' ] = !isDeleting;
if ( isDeleting ) {
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = data;
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
this.selectedSeats = cart;
this.sumUp();
} else {
if ( this.eventInfo.ageGroupCount > 1 ) {
$( '#overlay' ).show( 200 );
} else {
this.storeSeat( '1' );
}
}
this.pricingCurrentlySelected = this.eventInfo[ 'categories' ][ this.seating[ rowID ][ 'content' ][ placeID ][ 'category' ] ][ 'price' ];
},
closePopup () {
// This function closes the popup and sets the seat to not selected
$( '#overlay' ).hide( 200 );
let seat = JSON.parse( sessionStorage.getItem( 'tempStorage' ) );
this.seating[ seat[ 1 ][ 1 ] ][ 'content' ][ seat[ 1 ][ 0 ] ][ 'selected' ] = false;
},
storeSeat( ticketOption ) {
/*
This function stores a ticket into the event's selected seat sessionStorage.
*/
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
cart[ this.ticketID ?? 'default' ] = cart[ this.ticketID ?? 'default' ] ? cart[ this.ticketID ?? 'default' ] : { 'name': this.eventInfo.name, 'date': this.eventInfo.date, 'location': this.eventInfo.location, 'currency': this.eventInfo.currency };
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let data = cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] ? cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] : {};
let seat = JSON.parse( sessionStorage.getItem( 'tempStorage' ) );
let ticket = this.seating[ seat[ 1 ][ 1 ] ][ 'content' ][ seat[ 1 ][ 0 ] ];
let ticketData = { 'name': ticket[ 'name' ], 'categoryID': ticketOption, 'category': this.eventInfo[ 'categories' ][ ticket[ 'category' ] ], 'price': this.eventInfo[ 'categories' ][ this.seating[ seat[ 1 ][ 1 ] ][ 'content' ][ seat[ 1 ][ 0 ] ][ 'category' ] ][ 'price' ][ ticketOption ], 'row':seat[ 1 ][ 1 ], 'seat':seat[ 1 ][ 0 ], 'ageGroup': this.eventInfo[ 'ageGroups' ][ ticketOption ][ 'name' ] };
data[ String( seat[ 1 ][ 1 ] ) + String( seat[ 1 ][ 0 ] ) ] = ticketData;
cart[ this.ticketID ?? 'default' ][ 'selectedSeats' ] = data;
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
$( '#overlay' ).hide( 200 );
this.selectedSeats = cart;
this.sumUp();
},
},
created() {
this.loadPreviouslySelected();
}
}
</script>
<style scoped>
.seatingWrapper {
display: grid;
grid-template-areas:
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar';
height: 100%;
}
.sidebar {
grid-area: sidebar;
background-color: var( --accent-background );
color: var( --secondary-color );
overflow: scroll;
}
.seatingPlan {
grid-area: main;
display: flex;
flex-direction: column;
align-items: center;
justify-content: justify;
overflow: scroll;
}
.active {
cursor: pointer;
}
.occupied {
background-color: var( --hover-color );
padding: 0.4%;
}
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var( --overlay-color );
height: 100%;
width: 100%;
}
.popup {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.popup-content {
background-color: var( --background-color );
height: 60%;
width: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
border-radius: 50px;
}
.popup-content-wrapper {
display: flex;
height: 90%;
width: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: scroll;
}
.close-container {
width: 100%;
display: flex;
justify-content: flex-end;
}
.close-button {
cursor: pointer;
margin-right: 3vh;
margin-top: 3vh;
}
.option {
list-style: none;
padding: 7px 15px;
border-radius: 10px;
border-color: var( --primary-color );
border-style: solid;
border-width: 1px;
margin: 3px 0px;
cursor: pointer;
}
.stage {
border-color: var( --primary-color );
border-style: solid;
width: 80%;
height: 7%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.button {
background-color: var( --accent-background );
color: var( --secondary-color );
font-weight: bold;
font-size: 110%;
border-radius: 20px;
border-style: none;
padding: 10px 40px;
transition: 0.6s;
}
.button:hover {
background-color: var( --accent-background-hover );
transition: 0.3s;
cursor: pointer;
}
.price-table {
width: 100%;
}
</style>

View File

@@ -0,0 +1,17 @@
<template>
<div>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

29
src/webapp/src/main.js Normal file
View File

@@ -0,0 +1,29 @@
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { createPinia } from 'pinia';
import { useUserStore } from '@/stores/userStore';
let app = createApp( App );
app.use( createPinia() );
let userStore = useUserStore();
let prod = false;
if ( prod ) {
fetch( '/api/getAuth' ).then( res => {
res.json().then( data => {
userStore.setUserAuth( data.user );
userStore.setAdminAuth( data.admin );
app.use( router );
app.mount( '#app' );
} );
} );
} else {
// userStore.setUserAuth( true );
app.use( router );
app.mount( '#app' );
}

View File

@@ -0,0 +1,66 @@
export default {
path: '/admin',
name: 'admin',
component: () => import( '../views/admin/AdminView.vue' ),
meta: {
title: 'Admin - myevent',
adminAuthRequired: true,
},
children: [
{
path: '',
name: 'adminHome',
component: () => import( '../views/admin/HomeView.vue' ),
meta: {
title: 'Home :: Admin - myevent',
adminAuthRequired: true,
}
},
{
path: 'admin-accounts',
name: 'adminAccounts',
component: () => import( '../views/admin/AccountView.vue' ),
meta: {
title: 'Accounts :: Admin - myevent',
adminAuthRequired: true,
permissions: 'root'
}
},
{
path: 'pages',
name: 'adminPages',
component: () => import( '../views/admin/PagesView.vue' ),
meta: {
title: 'Pages :: Admin - myevent',
adminAuthRequired: true,
}
},
{
path: 'events',
name: 'adminEvents',
component: () => import( '../views/admin/EventsView.vue' ),
meta: {
title: 'Events :: Admin - myevent',
adminAuthRequired: true,
}
},
{
path: 'plugins',
name: 'adminPlugins',
component: () => import( '../views/admin/PluginsView.vue' ),
meta: {
title: 'Plugins :: Admin - myevent',
adminAuthRequired: true,
}
},
{
path: 'settings',
name: 'adminSettings',
component: () => import( '../views/admin/SettingsView.vue' ),
meta: {
title: 'Admin - myevent',
adminAuthRequired: true,
}
},
]
}

View File

@@ -0,0 +1,64 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/userStore';
import { useBackendStore } from '@/stores/backendStore';
import adminRoutes from '@/router/adminRoutes';
import mainRoutes from '@/router/mainRoutes';
/*
This is the Vue.js router file. All valid routes get imported from other files to
improve code legibility. Only router logic and importing logic is defined here.
*/
const routes = mainRoutes;
routes.push( adminRoutes );
const router = createRouter( {
history: createWebHistory( process.env.BASE_URL ),
routes,
} );
router.afterEach( ( to, from ) => {
document.title = to.meta.title ? to.meta.title : 'myevent';
} );
let UserAccountPages = [ 'account' ];
let authRequired = false;
router.beforeEach( ( to, from ) => {
let userStore = useUserStore();
let backendStore = useBackendStore();
backendStore.loadVisitedSetupPages();
let isUserAuthenticated = userStore.getUserAuthenticated;
let isAdminAuthenticated = userStore.getAdminAuthenticated;
if ( to.meta.adminAuthRequired && !isAdminAuthenticated ) {
return { name: 'adminLogin' };
} else if ( to.name === 'adminLogin' && isAdminAuthenticated ) {
return { name: 'adminHome' };
} else if ( UserAccountPages.includes( to.name ) && !isUserAuthenticated ) {
return { name: 'login' };
} else if ( !isUserAuthenticated && to.name === 'purchase' && authRequired ) {
return { name: 'login' };
} else if ( !isUserAuthenticated && to.name === 'pay' ) {
return { name: 'purchase' };
} else if ( to.name.substring( 0, 5 ) === 'setup' && !backendStore.getVisitedSetupPages[ to.name.substring( 5 ).toLowerCase() ] && to.name.substring( 5 ).toLowerCase() !== 'start' ) {
return { name: 'setupStart' };
}
} );
let doSetup = true;
if ( doSetup ) {
import( '@/router/setupRoutes' ).then( data => {
router.addRoute( data.default );
setTimeout( function () {
router.replace( window.location.pathname );
}, 300 );
} );
}
export default router;

View File

@@ -0,0 +1,94 @@
import HomeView from '../views/HomeView.vue';
export default [
{
path: '/',
name: 'home',
component: HomeView,
meta: {
title: 'Home - myevent'
}
},
{
path: '/tickets',
name: 'tickets',
component: () => import( '../views/OrderView.vue' ),
meta: {
title: 'Order ticket - myevent'
}
},
{
path: '/login',
name: 'login',
component: () => import( '../views/LoginView.vue' ),
meta: {
title: 'Login - myevent'
}
},
{
path: '/admin/login',
name: 'adminLogin',
component: () => import( '../views/AdminLoginView.vue' ),
meta: {
title: 'Login :: Admin - myevent'
}
},
{
path: '/signup',
name: 'signup',
component: () => import( '../views/SignupView.vue' ),
meta: {
title: 'Signup - myevent'
}
},
{
path: '/tickets/details',
name: 'ticketDetails',
component: () => import( '../views/TicketsDetailsView.vue' ),
meta: {
title: 'Details - myevent',
transition: 'scale'
}
},
{
path: '/tickets/order',
name: 'ticketOrder',
component: () => import( '../views/TicketsOrderingView.vue' ),
meta: {
title: 'Order ticket - myevent',
transition: 'scale'
}
},
{
path: '/cart',
name: 'cart',
component: () => import( '../views/CartView.vue' ),
meta: {
title: 'Cart - myevent',
transition: 'scale'
}
},
{
path: '/purchase',
name: 'purchase',
component: () => import( '@/views/PurchaseView.vue' ),
meta: {
title: 'Pay - myevent',
transition: 'scale'
}
},
{
path: '/pay',
name: 'pay',
component: () => import( '@/views/PaymentView.vue' ),
meta: {
title: 'Pay - myevent',
transition: 'scale',
}
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import( '@/views/404.vue')
},
]

View File

@@ -0,0 +1,74 @@
export default {
path: '/setup',
name: 'setup',
component: () => import( '../views/SetupView.vue' ),
meta: {
title: 'Login :: Admin - myevent',
adminAuthRequired: true,
},
children: [
{
path: '',
name: 'setupStart',
component: () => import( '../views/setup/SetupStartView.vue' ),
meta: {
title: 'Start :: Setup - myevent',
adminAuthRequired: true,
}
},
{
path: 'root',
name: 'setupRoot',
component: () => import( '../views/setup/SetupRootView.vue' ),
meta: {
title: 'Root account :: Setup - myevent',
adminAuthRequired: true,
}
},
{
path: 'page',
name: 'setupPage',
component: () => import( '../views/setup/SetupPageView.vue' ),
meta: {
title: 'Landing page :: Setup - myevent',
adminAuthRequired: true,
}
},
{
path: 'payments',
name: 'setupPayments',
component: () => import( '../views/setup/SetupPaymentsView.vue' ),
meta: {
title: 'Payments :: Setup - myevent',
adminAuthRequired: true,
}
},
{
path: 'events',
name: 'setupEvents',
component: () => import( '../views/setup/SetupEventsView.vue' ),
meta: {
title: 'Events :: Setup - myevent',
adminAuthRequired: true,
}
},
{
path: 'tos',
name: 'setupTOS',
component: () => import( '../views/setup/SetupTOSView.vue' ),
meta: {
title: 'TOS (Optional) :: Setup - myevent',
adminAuthRequired: true,
}
},
{
path: 'complete',
name: 'setupComplete',
component: () => import( '../views/setup/SetupCompleteView.vue' ),
meta: {
title: 'Setup complete :: Setup - myevent',
adminAuthRequired: true,
}
},
]
}

View File

@@ -0,0 +1,19 @@
import { defineStore } from "pinia";
export const useBackendStore = defineStore ( 'backend', {
state: () => ( { 'visitedSetupPages': {}, 'guestPurchase': false, 'guestPurchaseAllowed': false } ),
getters: {
getVisitedSetupPages: ( state ) => state.visitedSetupPages,
getIsGuestPurchase: ( state ) => state.guestPurchase,
getIsGuestPurchaseAllowed: ( state ) => state.guestPurchaseAllowed,
},
actions: {
addVisitedSetupPages ( page, data ) {
this.visitedSetupPages[ page ] = data;
sessionStorage.setItem( 'visitedSetupPages', JSON.stringify( this.visitedSetupPages ) );
},
loadVisitedSetupPages () {
this.visitedSetupPages = sessionStorage.getItem( 'visitedSetupPages' ) ? JSON.parse( sessionStorage.getItem( 'visitedSetupPages' ) ) : {};
}
}
} );

View File

@@ -0,0 +1,17 @@
import { defineStore } from "pinia";
export const useUserStore = defineStore ( 'user', {
state: () => ( { 'isUserAuth': false, 'isAdminAuth': true, 'userPermissions': {} } ),
getters: {
getUserAuthenticated: ( state ) => state.isUserAuth,
getAdminAuthenticated: ( state ) => state.isAdminAuth,
},
actions: {
setUserAuth ( auth ) {
this.isUserAuth = auth;
},
setAdminAuth ( auth ) {
this.isAdminAuth = auth;
}
}
} );

View File

@@ -0,0 +1,35 @@
<template>
<div class="notFound">
<h1 class="code">404</h1>
<h2 class="message">The page you are looking for was not found on the server!</h2>
<router-link to="/">Return to home page</router-link>
<a href="https://myevent.janishutz.com/docs/errors#404">More information on this error</a>
</div>
</template>
<style scoped>
.notFound {
font-family: monospace;
color: var( --primary-color );
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
width: 100%;
}
.code {
font-size: 2500%;
margin: 0;
padding: 0;
}
.message {
font-size: 200%;
}
.small {
font-style: italic;
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<div class="login">
<div class="login-app">
<h1>Log into your admin account</h1>
<form>
<label for="mail">Email address</label><br>
<input type="email" v-model="formData[ 'mail' ]" name="mail" id="mail" required><br><br>
<label for="password">Password</label><br>
<input type="text" v-model="formData[ 'password' ]" name="password" id="password" required>
</form>
<button @click="login();" class="button">Log in</button>
</div>
</div>
</template>
<script>
export default {
data () {
return {
formData: {}
}
},
methods: {
login () {
this.$router.push( '/admin' );
}
},
}
</script>
<style scoped>
.login {
background-color: green;
width: 100%;
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 0 1 auto;
}
.login-app {
background-color: var( --background-color );
min-height: fit-content;
min-height: fit-content;
padding: 5% 20%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 50px;
}
.button {
padding: 5px 10px;
margin-top: 2%;
}
</style>
<style>
nav {
display: block;
}
</style>

View File

@@ -0,0 +1,126 @@
<template>
<div class="cart">
<h1>Cart</h1>
<div v-if="cartNotEmpty" class="cart-list">
<h3>Your tickets</h3>
<ul v-for="event in tickets" class="cart-list">
<li>{{ event.name }}
<ul v-for="ticket in event.selectedSeats">
<li>{{ ticket.name }} ({{ ticket.category.name }}, {{ ticket.ageGroup }}) {{ event.currency }} {{ ticket.price }} <span class="material-symbols-outlined deleteButton" @click="deleteEntry( ticket.name, event.name )" title="Delete ticket">delete</span></li>
</ul>
</li>
</ul>
<div class="tool-wrapper">
<h4>Total: {{ backend.currency }} {{ backend.total }}</h4>
<router-link to="/purchase">Purchase now</router-link>
</div>
</div>
<div v-else>
Cart is empty. Please add tickets <router-link to="/tickets">here</router-link>
</div>
</div>
</template>
<style scoped>
.cart {
text-align: justify;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.cart-list {
width: 50%;
}
ul {
list-style: none;
text-align: justify;
}
.tool-wrapper {
display: flex;
width: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
}
.deleteButton {
cursor: pointer;
font-size: 110%;
margin: 0;
}
</style>
<script>
export default {
data() {
return {
tickets: {},
backend: {},
cartNotEmpty: false,
}
},
methods: {
loadCart () {
this.cartNotEmpty = false;
let tickets = JSON.parse( sessionStorage.getItem( 'cart' ) );
for ( let event in tickets ) {
if ( Object.keys( tickets[ event ][ 'selectedSeats' ] ).length ) {
this.cartNotEmpty = true;
};
}
this.tickets = tickets;
this.backend = JSON.parse( sessionStorage.getItem( 'backend' ) );
},
deleteEntry( id, eventName ) {
if ( confirm( 'Do you really want to delete this ticket?' ) ) {
let tickets = JSON.parse( sessionStorage.getItem( 'cart' ) );
for ( let event in tickets ) {
let ev = tickets[ event ];
if ( ev.name == eventName ) {
for ( let ticket in ev[ 'selectedSeats' ] ) {
if ( ev[ 'selectedSeats' ][ ticket ].name ) {
delete tickets[ event ][ 'selectedSeats' ][ ticket ];
}
}
}
}
sessionStorage.setItem( 'cart', JSON.stringify( tickets ) );
this.sumUp();
this.loadCart();
};
},
sumUp () {
// This function calculates the total price of the tickets for this event.
let cart = sessionStorage.getItem( 'cart' ) ? JSON.parse( sessionStorage.getItem( 'cart' ) ) : {};
let price = 0;
for ( let i in cart ) {
for ( let entry in cart[ i ][ 'selectedSeats' ] ) {
price += parseInt( cart[ i ][ 'selectedSeats' ][ entry ][ 'price' ] );
}
}
let back = {};
back[ 'total' ] = price;
back[ 'currency' ] = this.backend.currency;
sessionStorage.setItem( 'backend', JSON.stringify( back ) );
this.total = price;
sessionStorage.setItem( 'cart', JSON.stringify( cart ) );
},
},
created () {
this.loadCart();
}
}
</script>

View File

@@ -0,0 +1,23 @@
<template>
<initial></initial>
</template>
<script>
import initial from '@/components/initial.vue';
export default {
data () {
return {
formData: {}
}
},
methods: {
setup () {
}
},
components: {
initial
}
};
</script>

View File

@@ -0,0 +1,71 @@
<template>
<div class="login">
<div class="login-app">
<h1>Log in</h1>
<form>
<label for="mail">Email</label><br>
<input type="email" v-model="formData[ 'mail' ]" name="mail" id="mail" required><br><br>
<label for="password">Password</label><br>
<input type="password" v-model="formData[ 'password' ]" name="password" id="password" required>
</form>
<button @click="login();" class="button">Log in</button>
<router-link to="/signup" class="button">Sign up instead</router-link>
</div>
</div>
</template>
<script>
import { useUserStore } from '@/stores/userStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
formData: {}
}
},
computed: {
...mapStores( useUserStore )
},
methods: {
login () {
this.userStore.setUserAuth( true );
this.$router.push( sessionStorage.getItem( 'redirect' ) ? sessionStorage.getItem( 'redirect' ) : '/account' );
sessionStorage.removeItem( 'redirect' );
}
},
}
</script>
<style scoped>
/* TODO: Update colour to image */
.login {
background-color: green;
width: 100%;
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 0 1 auto;
}
.login-app {
background-color: var( --background-color );
min-height: fit-content;
min-height: fit-content;
padding: 5% 20%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 50px;
}
.button {
padding: 5px 10px;
margin-top: 2%;
}
</style>

View File

@@ -0,0 +1,93 @@
<template>
<div class="order">
<h1>Order tickets</h1>
<div class="order-app" v-if="events">
<ul v-for="event in events">
<li>
<router-link to="/tickets/details" class="ticket" @click="setActiveTicket( event.eventID );">
<div class="ticket-name">
<h3>{{ event.name }}</h3>
<p>{{ event.description }}</p>
</div>
<div class="ticket-info">
<p>Free seats: {{ event.freeSeats }} / {{ event.maxSeats }}</p>
<p>{{ event.location }}, {{ event.date }}</p>
<h4>Starting at {{ event.currency }} {{ event.startingPrice }}</h4>
</div>
<img :src="require( '@/assets/' + event.logo )" alt="event logo" class="ticket-logo">
</router-link>
</li>
</ul>
</div>
<div class="order-app" v-else>
No future events are available!
</div>
</div>
</template>
<style scoped>
.order-app {
text-align: justify;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
ul {
list-style: none;
width: 80%;
}
.ticket {
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
color: var( --primary-color );
border-color: var( --primary-color );
border-width: 1px;
height: fit-content;
border-style: solid;
padding: 10px;
transition: 0.4s;
}
.ticket:hover {
background-color: var( --hover-color );
transition: 0.4s;
}
.ticket-logo {
height: 20vh;
width: auto;
margin-left: auto;
}
.ticket-name {
margin-right: auto;
max-width: 35%;
}
.ticket-info {
margin-left: auto;
margin-right: auto
}
</style>
<script>
export default {
name: 'OrderView',
methods: {
setActiveTicket ( id ) {
sessionStorage.setItem( 'selectedTicket', id );
}
},
data () {
return {
events: { 'test':{ 'name': 'TestEvent', 'description': 'This is a description for the TestEvent to test multiline support and proper positioning of the Fields', 'freeSeats': 2, 'maxSeats': 2, 'date':'TestDate', 'startingPrice':15, 'location': 'TestLocation', 'eventID': 'test', 'currency': 'CHF', 'logo': 'logo.png' }, 'test2':{ 'name': 'TestEvent2', 'description': 'This is a description for the TestEvent to test multiline support and proper positioning of the Fields', 'freeSeats': 2, 'maxSeats': 2, 'date':'TestDate', 'startingPrice':15, 'location': 'TestLocation', 'eventID': 'test2', 'currency': 'CHF', 'logo': 'logo.png' } }
}
}
};
</script>

View File

@@ -0,0 +1,55 @@
<template>
<div class="order">
<h1>Purchase</h1>
<h3>Please choose a payment option</h3>
</div>
</template>
<style scoped>
.order-app {
text-align: justify;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
ul {
list-style: none;
width: 80%;
}
.ticket {
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
color: var( --primary-color );
border-color: var( --primary-color );
border-width: 1px;
height: fit-content;
border-style: solid;
padding: 10px;
transition: 0.4s;
}
.ticket:hover {
background-color: var( --hover-color );
transition: 0.4s;
}
.ticket-logo {
height: 20vh;
width: auto;
margin-left: auto;
}
.ticket-name {
margin-right: auto;
}
.ticket-info {
margin-left: auto;
margin-right: auto
}
</style>

View File

@@ -0,0 +1,184 @@
<template>
<div class="purchase">
<h1>Purchase</h1>
<div class="purchase-app">
<div v-if="!isAuthenticated" class="wrapper-buttons">
<router-link to="/login" class="option-button" @click="setRedirect()">Log in with an existing account</router-link><br>
<router-link to="/signup" class="option-button" @click="setRedirect()">Create new account</router-link><br>
<router-link to="/guest" v-if="!settings.accountRequired" class="option-button" @click="setRedirect()">Purchase as guest</router-link>
</div>
<div v-else class="wrapper">
<div class="data">
<h2>Billing</h2>
<router-link to="/pay">Buy now</router-link>
</div>
<div class="cart">
<div class="cart-list">
<h2>Order summary</h2>
<h3>Your tickets</h3>
<ul v-for="event in tickets">
<li>{{ event.name }}
<ul v-for="ticket in event.selectedSeats">
<li>{{ ticket.name }} ({{ ticket.category.name }}, {{ ticket.ageGroup }}) {{ event.currency }} {{ ticket.price }}</li>
</ul>
</li>
</ul>
<div class="tool-wrapper wrapper-loggedIn">
<h4>Total: {{ backend.currency }} {{ backend.total }}</h4>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.purchase {
height: 100%;
display: flex;
flex-grow: 1;
flex-direction: column;
}
.purchase-app {
text-align: justify;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
}
.option-button {
border-style: solid;
border-color: var( --primary-color );
border-radius: 20px;
padding: 6% 7%;
display: block;
width: 100%;
text-align: center;
margin: 0.5%;
color: var( --primary-color );
text-decoration: none;
}
.data {
grid-area: main;
display: flex;
justify-content: justify;
align-items: center;
flex-direction: column;
flex-grow: 1;
}
.option-button:hover {
background-color: var( --hover-color );
color: var( --secondary-color )
}
.cart {
grid-area: sidebar;
background-color: var( --accent-background );
color: var( --secondary-color );
overflow: scroll;
}
.wrapper-buttons {
width: 40%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
height: 100%;
}
.wrapper {
width: 100%;
display: grid;
height: 100%;
grid-template-areas:
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar'
'main main main sidebar';
}
ul {
list-style: none;
text-align: justify;
}
.cart-list {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.tool-wrapper {
display: flex;
width: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
<script>
import { useUserStore } from '@/stores/userStore';
import { useBackendStore } from '@/stores/backendStore';
import { mapStores } from 'pinia';
export default {
name: 'PurchaseView',
data () {
return {
settings: { 'accountRequired': true, 'requiresAddress': true, 'requiresAge': true, 'requiresSpecialNumber': true, 'specialNumberDisplayName': { 'de': '', 'en': 'id number' } },
isAuthenticated: false,
tickets: {},
backend: {},
cartNotEmpty: false,
}
},
computed: {
...mapStores( useUserStore ),
...mapStores( useBackendStore )
},
methods: {
loadData () {
this.cartNotEmpty = false;
let tickets = JSON.parse( sessionStorage.getItem( 'cart' ) );
for ( let event in tickets ) {
if ( Object.keys( tickets[ event ][ 'selectedSeats' ] ).length ) {
this.cartNotEmpty = true;
};
}
if ( this.cartNotEmpty ) {
this.tickets = tickets;
this.backend = JSON.parse( sessionStorage.getItem( 'backend' ) );
this.isAuthenticated = this.userStore.getUserAuthenticated;
this.settings.accountRequired = !this.backendStore.getIsGuestPurchaseAllowed;
} else {
this.$router.push( '/tickets' );
}
},
setRedirect () {
sessionStorage.setItem( 'redirect', '/purchase' );
}
},
created () {
this.loadData();
}
};
</script>

View File

@@ -0,0 +1,64 @@
<template>
<div>
<nav class="setup-nav">
<router-link to="/setup">Start</router-link> |
<router-link to="/setup/root" v-if="backendStore.getVisitedSetupPages[ 'root' ]">Root account</router-link>
<a v-else class="inactive">Root account</a> |
<router-link to="/setup/page" v-if="backendStore.getVisitedSetupPages[ 'page' ]">Landing page</router-link>
<a v-else class="inactive">Landing page</a> |
<router-link to="/setup/payments" v-if="backendStore.getVisitedSetupPages[ 'payments' ]">Payments</router-link>
<a v-else class="inactive">Payments</a> |
<router-link to="/setup/events" v-if="backendStore.getVisitedSetupPages[ 'events' ]">Events</router-link>
<a v-else class="inactive">Events</a> |
<router-link to="/setup/tos" v-if="backendStore.getVisitedSetupPages[ 'tos' ]">TOS</router-link>
<a v-else class="inactive">TOS</a> |
<router-link to="/setup/complete" v-if="backendStore.getVisitedSetupPages[ 'complete' ]">Complete</router-link>
<a v-else class="inactive">Complete</a>
</nav>
<h1>Setup</h1>
<div class="main-view">
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'scale'" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</div>
</div>
</template>
<script>
import { useBackendStore } from '@/stores/backendStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
formData: {}
}
},
computed: {
...mapStores( useBackendStore )
},
created () {
this.backendStore.loadVisitedSetupPages();
},
};
</script>
<style scoped>
.inactive {
color: var( --inactive-color );
cursor: not-allowed;
}
.setup-nav {
display: block;
}
</style>
<style>
nav {
display: none;
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<div class="login">
<div class="login-app">
<h1>Sign up</h1>
<form>
<label for="mail">Email</label><br>
<input type="email" v-model="formData[ 'mail' ]" name="mail" id="mail" required><br><br>
<label for="name">Full name</label><br>
<input type="text" v-model="formData[ 'name' ]" name="name" id="name" required><br><br>
<label for="password">Password</label><br>
<input type="password" v-model="formData[ 'password' ]" name="password" id="password" required><br><br>
<label for="password2">Confirm password</label><br>
<input type="password" v-model="formData[ 'password2' ]" name="password2" id="password2" required>
</form>
<button @click="signup();" class="button">Sign up</button>
<router-link to="/login" class="button">Log in instead</router-link>
</div>
</div>
</template>
<script>
export default {
data () {
return {
formData: {}
}
},
methods: {
signup () {
}
},
}
</script>
<style scoped>
.login {
background-color: green;
width: 100%;
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 0 1 auto;
}
.login-app {
background-color: var( --background-color );
min-height: fit-content;
min-height: fit-content;
padding: 5% 20%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 50px;
}
.button {
padding: 5px 10px;
margin-top: 2%;
}
</style>

View File

@@ -0,0 +1,33 @@
<template>
<div class="details">
<h1>Details</h1>
<router-link to="/tickets"><span class="material-symbols-outlined" style="font-size: 100%;">arrow_back</span>Back</router-link>
<h3>{{ event.name }}</h3>
<p>{{ event.description }}</p>
<router-link to="/tickets/order">Order tickets</router-link>
</div>
</template>
<style scoped>
.details {
flex-grow: 1;
}
</style>
<script>
export default {
name: 'TicketsDetailsView',
created () {
if ( !sessionStorage.getItem( 'selectedTicket' ) ) {
this.$router.push( '/tickets' );
}
this.eventID = sessionStorage.getItem( 'selectedTicket' );
},
data() {
return {
event: { 'name': 'TestEvent', 'description': 'This is a description for the TestEvent to test multiline support and proper positioning of the Fields', 'freeSeats': 2, 'maxSeats': 2, 'date':'TestDate', 'startingPrice':15, 'location': 'TestLocation', 'eventID': 'test', 'currency': 'CHF', 'logo': 'logo.png' },
}
}
};
</script>

View File

@@ -0,0 +1,42 @@
<template>
<div class="details">
<!-- Load correct component depending on what the event's config is -->
<seatplan :ticketID="eventID" v-if="hasSeatplan"></seatplan>
<noseatplan :ticketID="eventID" v-else></noseatplan>
</div>
</template>
<style scoped>
.details {
flex-grow: 1;
}
</style>
<script>
import seatplan from '@/components/seatplan.vue';
import noseatplan from '@/components/noseatplan.vue';
export default {
name: 'TicketsDetailsView',
components: {
seatplan,
noseatplan
},
data() {
return {
hasSeatplan: true,
eventID: '',
}
},
created () {
if ( !sessionStorage.getItem( 'selectedTicket' ) ) {
this.$router.push( '/tickets' );
}
this.eventID = sessionStorage.getItem( 'selectedTicket' );
if ( this.eventID == 'test' ) {
this.hasSeatplan = false;
}
}
};
</script>

View File

@@ -0,0 +1,22 @@
<template>
<div>
<h2>Admin Accounts</h2>
<p>Here you can change everything regarding admin accounts. You may create, modify or delete admin accounts.</p>
<div class="bigButtons"></div>
</div>
</template>
<script>
export default {
data () {
return {
formData: {}
}
},
methods: {
setup () {
}
}
};
</script>

View File

@@ -0,0 +1,124 @@
<template>
<div class="admin-wrapper">
<div class="top-bar">
<h1>Admin panel</h1>
</div>
<nav class="side-nav">
<router-link to="/admin" class="admin-menu">Home</router-link>
<router-link to="/admin/admin-accounts" class="admin-menu">Admin Accounts</router-link>
<router-link to="/admin/pages" class="admin-menu">Pages</router-link>
<router-link to="/admin/events" class="admin-menu">Events</router-link>
<router-link to="/admin/plugins" class="admin-menu">Plugins</router-link>
<router-link to="/admin/settings" class="admin-menu">Settings</router-link>
<button to="/admin/login" class="admin-menu" @click="logout()">Logout</button>
</nav>
<div class="main-view">
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade'" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</div>
</div>
</template>
<style scoped>
.admin-wrapper {
display: grid;
grid-template-areas:
'sidebar top top top'
'sidebar main main main'
'sidebar main main main'
'sidebar main main main'
'sidebar main main main'
'sidebar main main main'
'sidebar main main main'
'sidebar main main main'
'sidebar main main main';
height: 100%;
}
.top-bar {
grid-area: top;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.main-view {
grid-area: main;
height: 100%;
min-height: 80vh;
overflow: scroll;
}
.side-nav {
grid-area: sidebar;
display: flex;
flex-direction: column;
height: 100%;
padding: 0;
margin: 0;
background-color: var( --accent-background );
justify-content: center;
overflow: scroll;
}
.admin-menu {
padding: 4% 0%;
width: 100%;
background-color: rgba( 0, 0, 0, 0 );
color: var( --secondary-color );
cursor: pointer;
font-family: Avenir, Helvetica, Arial, sans-serif;
font-weight: bold;
text-decoration: none;
transition: 1s;
font-size: 100%;
border-style: none;
}
nav a.router-link-exact-active {
background-color: var( --accent-background-hover );
}
.admin-menu:hover {
background-color: var( --accent-background-hover );
transition: 0.4s;
}
</style>
<style>
nav {
display: none;
}
</style>
<script>
import { useUserStore } from '@/stores/userStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
formData: {}
}
},
computed: {
...mapStores( useUserStore )
},
methods: {
logout () {
if ( confirm( 'Do you really want to log out?' ) ) {
fetch( '/admin/logout' ).then( _ => {
this.userStore.setAdminAuth( false );
this.$router.push( '/admin/login' );
} );
}
}
}
};
</script>

View File

@@ -0,0 +1,22 @@
<template>
<div>
<h2>Events</h2>
<p>Welcome to the admin panel!</p>
<div class="bigButtons"></div>
</div>
</template>
<script>
export default {
data () {
return {
formData: {}
}
},
methods: {
setup () {
}
}
};
</script>

View File

@@ -0,0 +1,43 @@
<template>
<div>
<h2>Home</h2>
<p>Welcome to the admin panel!</p>
<div class="bigButton-container">
<router-link to="/admin/admin-accounts" class="bigButton">Admin-accounts</router-link>
<router-link to="/admin/pages" class="bigButton">Pages</router-link>
<router-link to="/admin/events" class="bigButton">Events</router-link>
<router-link to="/admin/events" class="bigButton">Plugins</router-link>
<router-link to="/admin/events" class="bigButton">Settings</router-link>
<a href="https://myevent.janishutz.com/docs/admin-panel" class="bigButton" target="_blank">Documentation</a>
</div>
</div>
</template>
<style scoped>
.bigButton-container {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
}
.bigButton {
background-color: var( --accent-background );
width: 40%;
height: 30vh;
border-color: black;
margin: 0.02%;
border-style: inset;
color: var( --secondary-color );
text-decoration: none;
border-width: 2px;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
}
.bigButton:hover {
background-color: var( --accent-background-hover );
}
</style>

View File

@@ -0,0 +1,22 @@
<template>
<div>
<h2>Pages</h2>
<p>Welcome to the admin panel!</p>
<div class="bigButtons"></div>
</div>
</template>
<script>
export default {
data () {
return {
formData: {}
}
},
methods: {
setup () {
}
}
};
</script>

View File

@@ -0,0 +1,22 @@
<template>
<div>
<h2>Plugins</h2>
<p>Welcome to the admin panel!</p>
<div class="bigButtons"></div>
</div>
</template>
<script>
export default {
data () {
return {
formData: {}
}
},
methods: {
setup () {
}
}
};
</script>

View File

@@ -0,0 +1,22 @@
<template>
<div>
<h2>Settings</h2>
<p>Welcome to the admin panel!</p>
<div class="bigButtons"></div>
</div>
</template>
<script>
export default {
data () {
return {
formData: {}
}
},
methods: {
setup () {
}
}
};
</script>

View File

@@ -0,0 +1,6 @@
<template>
<div>
<h2>Setup was completed!</h2>
<router-link to="/admin">To the admin panel</router-link>
</div>
</template>

View File

@@ -0,0 +1,30 @@
<template>
<div>
<h3>Setting up Events</h3>
<p>You may choose all of the below payment methods, but we recommend to only select one payment gateway for simplicity. Recommended: Either Stripe or Adyen. See the comparison of the different payment methods <a href="https://myevent.janishutz.com/docs/payments">here</a></p>
<p>You may find more infos about this part <a href="https://myevent.janishutz.com/docs/setup/setup#payment-methods" target="_blank">here</a></p>
<button @click="submit()">Continue</button>
</div>
</template>
<script>
import { useBackendStore } from '@/stores/backendStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
formData: {}
}
},
computed: {
...mapStores( useBackendStore )
},
methods: {
submit () {
this.backendStore.addVisitedSetupPages( 'tos', true );
this.$router.push( '/setup/tos' );
}
},
};
</script>

View File

@@ -0,0 +1,36 @@
<template>
<div>
<h3>Setting up the landing page</h3>
<p>The landing page is the page your customers see when they visit your webpage. You may select a page template <a href="https://myevent.janishutz.com/docs/homepage/templates">here</a>.</p>
<p>You may find more infos about this part <a href="https://myevent.janishutz.com/docs/setup/setup#page-setup" target="_blank">here</a></p>
<label for="template">Choose a template</label><br>
<select name="template" id="template" v-for="option in options">
<option :value="option.id">{{ option.name }}</option>
</select><br>
<button @click="submit()">Continue</button>
</div>
</template>
<script>
import { useBackendStore } from '@/stores/backendStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
options: { 'default': { 'id': 'default', 'name': 'Default' }},
}
},
computed: {
...mapStores( useBackendStore )
},
methods: {
submit () {
this.backendStore.addVisitedSetupPages( 'payments', true );
this.$router.push( '/setup/payments' );
}
},
};
</script>

View File

@@ -0,0 +1,30 @@
<template>
<div>
<h3>Setting up payment methods</h3>
<p>You may choose all of the below payment methods, but we recommend to only select one payment gateway for simplicity. Recommended: Either Stripe or Adyen. See the comparison of the different payment methods <a href="https://myevent.janishutz.com/docs/payments">here</a></p>
<p>You may find more infos about this part <a href="https://myevent.janishutz.com/docs/setup/setup#payment-methods" target="_blank">here</a></p>
<button @click="submit()">Continue</button>
</div>
</template>
<script>
import { useBackendStore } from '@/stores/backendStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
formData: {}
}
},
computed: {
...mapStores( useBackendStore )
},
methods: {
submit () {
this.backendStore.addVisitedSetupPages( 'events', true );
this.$router.push( '/setup/events' );
}
},
};
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div>
<h3>Setting up the root account</h3>
<p>The root account is the most powerful account. Therefore, it should only be used if really necessary and should have a strong password. It also always requires Two Factor Authentication for added security. You may log into the root account by typing 'root' into the Email/Username field on the admin login screen.</p>
<p>You may find more infos about this part <a href="https://myevent.janishutz.com/docs/setup/setup#root-account" target="_blank">here</a></p>
<p>Password requirements:</p>
<ul style="list-style: none;">
<li>At least 15 characters long</li>
<li>At least 2 special characters</li>
<li>At least 2 numbers</li>
<li>At least 2 lower and 2 upper case letters</li>
</ul>
<form>
<label for="mail">Email address for 2FA</label><br>
<input type="email" name="mail" id="mail"><br>
<label for="password">Password</label><br>
<input type="email" name="password" id="password"><br>
<label for="password2">Confirm password</label><br>
<input type="email" name="password2" id="password2">
</form>
<button @click="submit()">Continue</button>
</div>
</template>
<script>
import { useBackendStore } from '@/stores/backendStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
formData: {}
}
},
computed: {
...mapStores( useBackendStore )
},
methods: {
submit () {
this.backendStore.addVisitedSetupPages( 'page', true );
this.$router.push( 'page' );
}
},
};
</script>

View File

@@ -0,0 +1,29 @@
<template>
<div>
<h2>Welcome to myevent!</h2>
<i style="font-size: small;">All links during setup open in separate tabs</i>
<p>Let's start by setting it up. We strongly encourage you to also have a look at the extensive documentation of the setup process <a href="https://myevent.janishutz.com/docs/setup/setup" target="_blank">here</a></p>
<router-link to="/setup/root" @click="setup();">Start setup</router-link>
</div>
</template>
<script>
import { useBackendStore } from '@/stores/backendStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
formData: {}
}
},
computed: {
...mapStores( useBackendStore )
},
methods: {
setup () {
this.backendStore.addVisitedSetupPages( 'root', true );
}
},
};
</script>

View File

@@ -0,0 +1,30 @@
<template>
<div>
<h3>Setting up TOS (optional)</h3>
<p>You may choose all of the below payment methods, but we recommend to only select one payment gateway for simplicity. Recommended: Either Stripe or Adyen. See the comparison of the different payment methods <a href="https://myevent.janishutz.com/docs/payments" target="_blank">here</a></p>
<p>You may find more infos about this part <a href="https://myevent.janishutz.com/docs/setup/setup#payment-methods" target="_blank">here</a></p>
<button @click="submit()">Continue</button>
</div>
</template>
<script>
import { useBackendStore } from '@/stores/backendStore';
import { mapStores } from 'pinia';
export default {
data () {
return {
formData: {}
}
},
computed: {
...mapStores( useBackendStore )
},
methods: {
submit () {
this.backendStore.addVisitedSetupPages( 'complete', true );
this.$router.push( '/setup/complete' );
}
},
};
</script>

4
src/webapp/vue.config.js Normal file
View File

@@ -0,0 +1,4 @@
const { defineConfig } = require( '@vue/cli-service' );
module.exports = defineConfig( {
transpileDependencies: true
} );

View File

@@ -0,0 +1,4 @@
> 1%
last 2 versions
not dead
not ie 11

23
visual-editor-test/testing/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,24 @@
# testing
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

19550
visual-editor-test/testing/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
{
"name": "testing",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"vue": "^3.2.13",
"vue-router": "^4.0.3",
"vue3-draggable-resizable": "^1.6.5"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -0,0 +1,30 @@
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/editor">Editor</router-link>
</nav>
<router-view/>
</template>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
}
nav a {
font-weight: bold;
color: #2c3e50;
}
nav a.router-link-exact-active {
color: #42b983;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,59 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<p>
For a guide and recipes on how to configure / customize this project,<br>
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
</p>
<h3>Installed CLI Plugins</h3>
<ul>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
</ul>
<h3>Essential Links</h3>
<ul>
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
</ul>
<h3>Ecosystem</h3>
<ul>
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
</ul>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@@ -0,0 +1,11 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Vue3DraggableResizable from 'vue3-draggable-resizable'
//default styles
import 'vue3-draggable-resizable/dist/Vue3DraggableResizable.css'
let app = createApp(App)
app.use(Vue3DraggableResizable)
app.use(router).mount('#app')

View File

@@ -0,0 +1,25 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/editor',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/EditorView.vue')
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router

View File

@@ -0,0 +1,70 @@
<template>
<div id="app">
<div class="parent">
<Vue3DraggableResizable :initW="110" :initH="120" v-model:x="draggables[1].x" v-model:y="draggables[1].y" v-model:w="draggables[1].w" v-model:h="draggables[1].h"
v-model:active="draggables[1].active" :draggable="draggables[1].draggable" :resizable="draggables[1].resizable" @activated="print('activated')"
@deactivated="disableEditMode()" @drag-start="print('drag-start')" @resize-start="print('resize-start')"
@dragging="print('dragging')" @resizing="print('resizing')" @drag-end="print('drag-end')"
@resize-end="print('resize-end')" @contextmenu="(e) => { e.preventDefault(); print('context') }" @dblclick="enableEditMode( 1 )">
<p v-if="draggables[1].draggable">This is a test example</p>
<input @keydown.esc="disableEditMode()" value="This is a test example" v-else/>
</Vue3DraggableResizable>
<Vue3DraggableResizable :initW="110" :initH="120" v-model:x="draggables[2].x" v-model:y="draggables[2].y" v-model:w="draggables[2].w" v-model:h="draggables[2].h"
v-model:active="draggables[2].active" :draggable="draggables[2].draggable" :resizable="draggables[2].resizable" @activated="print('activated')"
@deactivated="disableEditMode()" @drag-start="print('drag-start')" @resize-start="print('resize-start')"
@dragging="print('dragging')" @resizing="print('resizing')" @drag-end="print('drag-end')"
@resize-end="print('resize-end')" @contextmenu="(e) => { e.preventDefault(); print('context') }" @dblclick="enableEditMode( 2 )">
<p v-if="draggables[2].draggable">This is a test example (2)</p>
<input @keydown.esc="disableEditMode()" value="This is a test example (2)" v-else/>
</Vue3DraggableResizable>
</div>
</div>
</template>
<script>
import Vue3DraggableResizable from 'vue3-draggable-resizable';
//default styles
import 'vue3-draggable-resizable/dist/Vue3DraggableResizable.css';
export default {
components: { Vue3DraggableResizable },
data() {
return {
draggables: { 1: { 'x': 100, 'y':100, 'h': 100, 'w': 100, 'active': false, 'draggable': true, 'resizable': true }, 2: { 'x': 100, 'y':100, 'h': 100, 'w': 100, 'active': false, 'draggable': true, 'resizable': true } },
x: 100,
y: 100,
h: 100,
w: 100,
active: false
}
},
methods: {
print(val) {
console.log(val)
},
enableEditMode ( obj ) {
this.draggables[ obj ].draggable = false;
this.draggables[ obj ].resizable = false;
},
handleKeyboard ( key ) {
console.log( key );
},
disableEditMode () {
for ( let draggable in this.draggables ) {
this.draggables[ draggable ].draggable = true;
this.draggables[ draggable ].resizable = true;
}
},
}
}
</script>
<style>
.parent {
width: 200px;
height: 200px;
position: absolute;
top: 100px;
left: 100px;
border: 1px solid #000;
user-select: none;
}
</style>

Some files were not shown because too many files have changed in this diff Show More