mirror of
https://github.com/janishutz/libreevent.git
synced 2025-11-25 13:24:24 +00:00
Compare commits
41 Commits
dev-V1
...
visual-edi
| Author | SHA1 | Date | |
|---|---|---|---|
| 1fbcbd864a | |||
|
|
b185c903dd | ||
|
|
1f6fd6859a | ||
| 2ff4f4a09c | |||
| 7edd663f4f | |||
| eec30d8ed4 | |||
|
|
c7bc810e18 | ||
| ab9b232433 | |||
| 64452abf6c | |||
|
|
99ff1abc5d | ||
|
|
ac5c71559a | ||
|
|
58ae853d71 | ||
|
|
736521834d | ||
|
|
5030532712 | ||
|
|
bf97068357 | ||
|
|
f19b9a472e | ||
|
|
f78a7c7dd6 | ||
|
|
1d7832a945 | ||
|
|
767d9a3d7a | ||
|
|
02af46d0f2 | ||
|
|
0e402bfba0 | ||
|
|
95b83d95bd | ||
|
|
66e4e448e5 | ||
|
|
c829bdcdb1 | ||
|
|
c89438854c | ||
|
|
c04ccd802b | ||
|
|
55023dd46d | ||
| 5e406e99d6 | |||
| 8d751026b6 | |||
| 13924e3abb | |||
| 7a81b94438 | |||
| 9fcdef4aac | |||
| a70c4cb1eb | |||
| c4abf91aeb | |||
| 5b8f1c03f7 | |||
| ed63af624d | |||
| 8b8172d9f8 | |||
| f7e121708b | |||
| c9e3648a0f | |||
| d72ba805b9 | |||
| 5eccbb336a |
@@ -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
5
.gitignore
vendored
@@ -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
|
||||
56
README.md
56
README.md
@@ -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
13
package-lock.json
generated
Normal 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
24
package.json
Normal 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"
|
||||
}
|
||||
@@ -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 } );
|
||||
} );
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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' );
|
||||
} );
|
||||
} );
|
||||
@@ -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();
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,3 +0,0 @@
|
||||
<div>
|
||||
Hello World!
|
||||
</div>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
168
src/server/package-lock.json
generated
168
src/server/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
4
src/webapp/.browserslistrc
Normal file
4
src/webapp/.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
23
src/webapp/.gitignore
vendored
Normal file
23
src/webapp/.gitignore
vendored
Normal 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
19
src/webapp/README.md
Normal 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/).
|
||||
5
src/webapp/babel.config.js
Normal file
5
src/webapp/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
};
|
||||
19
src/webapp/jsconfig.json
Normal file
19
src/webapp/jsconfig.json
Normal 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
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
20
src/webapp/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
src/webapp/public/favicon.ico
Normal file
BIN
src/webapp/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
25
src/webapp/public/index.html
Normal file
25
src/webapp/public/index.html
Normal 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
170
src/webapp/src/App.vue
Normal 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 === '☼' ) {
|
||||
document.documentElement.classList.remove( 'dark' );
|
||||
document.documentElement.classList.add( 'light' );
|
||||
sessionStorage.setItem( 'theme', '☽' );
|
||||
this.theme = '☽';
|
||||
} else if ( this.theme === '☽' ) {
|
||||
document.documentElement.classList.remove( 'light' );
|
||||
document.documentElement.classList.add( 'dark' );
|
||||
sessionStorage.setItem( 'theme', '☼' );
|
||||
this.theme = '☼';
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.theme = sessionStorage.getItem( 'theme' ) ? sessionStorage.getItem( 'theme' ) : '';
|
||||
if ( window.matchMedia( '(prefers-color-scheme: dark)' ).matches || this.theme === '☼' ) {
|
||||
document.documentElement.classList.add( 'dark' );
|
||||
this.theme = '☼';
|
||||
} else {
|
||||
document.documentElement.classList.add( 'light' );
|
||||
this.theme = '☽';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
35
src/webapp/src/components/initial.vue
Normal file
35
src/webapp/src/components/initial.vue
Normal 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>
|
||||
344
src/webapp/src/components/noseatplan.vue
Normal file
344
src/webapp/src/components/noseatplan.vue
Normal 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>
|
||||
379
src/webapp/src/components/seatplan.vue
Normal file
379
src/webapp/src/components/seatplan.vue
Normal 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>
|
||||
17
src/webapp/src/components/seatplan/newSeatplan.vue
Normal file
17
src/webapp/src/components/seatplan/newSeatplan.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
29
src/webapp/src/main.js
Normal file
29
src/webapp/src/main.js
Normal 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' );
|
||||
}
|
||||
66
src/webapp/src/router/adminRoutes.js
Normal file
66
src/webapp/src/router/adminRoutes.js
Normal 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,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
64
src/webapp/src/router/index.js
Normal file
64
src/webapp/src/router/index.js
Normal 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;
|
||||
94
src/webapp/src/router/mainRoutes.js
Normal file
94
src/webapp/src/router/mainRoutes.js
Normal 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')
|
||||
},
|
||||
]
|
||||
74
src/webapp/src/router/setupRoutes.js
Normal file
74
src/webapp/src/router/setupRoutes.js
Normal 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,
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
19
src/webapp/src/stores/backendStore.js
Normal file
19
src/webapp/src/stores/backendStore.js
Normal 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' ) ) : {};
|
||||
}
|
||||
}
|
||||
} );
|
||||
17
src/webapp/src/stores/userStore.js
Normal file
17
src/webapp/src/stores/userStore.js
Normal 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;
|
||||
}
|
||||
}
|
||||
} );
|
||||
35
src/webapp/src/views/404.vue
Normal file
35
src/webapp/src/views/404.vue
Normal 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>
|
||||
66
src/webapp/src/views/AdminLoginView.vue
Normal file
66
src/webapp/src/views/AdminLoginView.vue
Normal 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>
|
||||
126
src/webapp/src/views/CartView.vue
Normal file
126
src/webapp/src/views/CartView.vue
Normal 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>
|
||||
23
src/webapp/src/views/HomeView.vue
Normal file
23
src/webapp/src/views/HomeView.vue
Normal 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>
|
||||
71
src/webapp/src/views/LoginView.vue
Normal file
71
src/webapp/src/views/LoginView.vue
Normal 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>
|
||||
93
src/webapp/src/views/OrderView.vue
Normal file
93
src/webapp/src/views/OrderView.vue
Normal 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>
|
||||
55
src/webapp/src/views/PaymentView.vue
Normal file
55
src/webapp/src/views/PaymentView.vue
Normal 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>
|
||||
184
src/webapp/src/views/PurchaseView.vue
Normal file
184
src/webapp/src/views/PurchaseView.vue
Normal 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>
|
||||
64
src/webapp/src/views/SetupView.vue
Normal file
64
src/webapp/src/views/SetupView.vue
Normal 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>
|
||||
65
src/webapp/src/views/SignupView.vue
Normal file
65
src/webapp/src/views/SignupView.vue
Normal 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>
|
||||
33
src/webapp/src/views/TicketsDetailsView.vue
Normal file
33
src/webapp/src/views/TicketsDetailsView.vue
Normal 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>
|
||||
|
||||
42
src/webapp/src/views/TicketsOrderingView.vue
Normal file
42
src/webapp/src/views/TicketsOrderingView.vue
Normal 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>
|
||||
|
||||
22
src/webapp/src/views/admin/AccountView.vue
Normal file
22
src/webapp/src/views/admin/AccountView.vue
Normal 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>
|
||||
124
src/webapp/src/views/admin/AdminView.vue
Normal file
124
src/webapp/src/views/admin/AdminView.vue
Normal 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>
|
||||
|
||||
22
src/webapp/src/views/admin/EventsView.vue
Normal file
22
src/webapp/src/views/admin/EventsView.vue
Normal 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>
|
||||
43
src/webapp/src/views/admin/HomeView.vue
Normal file
43
src/webapp/src/views/admin/HomeView.vue
Normal 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>
|
||||
22
src/webapp/src/views/admin/PagesView.vue
Normal file
22
src/webapp/src/views/admin/PagesView.vue
Normal 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>
|
||||
22
src/webapp/src/views/admin/PluginsView.vue
Normal file
22
src/webapp/src/views/admin/PluginsView.vue
Normal 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>
|
||||
22
src/webapp/src/views/admin/SettingsView.vue
Normal file
22
src/webapp/src/views/admin/SettingsView.vue
Normal 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>
|
||||
6
src/webapp/src/views/setup/SetupCompleteView.vue
Normal file
6
src/webapp/src/views/setup/SetupCompleteView.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Setup was completed!</h2>
|
||||
<router-link to="/admin">To the admin panel</router-link>
|
||||
</div>
|
||||
</template>
|
||||
30
src/webapp/src/views/setup/SetupEventsView.vue
Normal file
30
src/webapp/src/views/setup/SetupEventsView.vue
Normal 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>
|
||||
36
src/webapp/src/views/setup/SetupPageView.vue
Normal file
36
src/webapp/src/views/setup/SetupPageView.vue
Normal 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>
|
||||
30
src/webapp/src/views/setup/SetupPaymentsView.vue
Normal file
30
src/webapp/src/views/setup/SetupPaymentsView.vue
Normal 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>
|
||||
45
src/webapp/src/views/setup/SetupRootView.vue
Normal file
45
src/webapp/src/views/setup/SetupRootView.vue
Normal 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>
|
||||
29
src/webapp/src/views/setup/SetupStartView.vue
Normal file
29
src/webapp/src/views/setup/SetupStartView.vue
Normal 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>
|
||||
30
src/webapp/src/views/setup/SetupTOSView.vue
Normal file
30
src/webapp/src/views/setup/SetupTOSView.vue
Normal 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
4
src/webapp/vue.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
const { defineConfig } = require( '@vue/cli-service' );
|
||||
module.exports = defineConfig( {
|
||||
transpileDependencies: true
|
||||
} );
|
||||
4
visual-editor-test/testing/.browserslistrc
Normal file
4
visual-editor-test/testing/.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
23
visual-editor-test/testing/.gitignore
vendored
Normal file
23
visual-editor-test/testing/.gitignore
vendored
Normal 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?
|
||||
24
visual-editor-test/testing/README.md
Normal file
24
visual-editor-test/testing/README.md
Normal 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/).
|
||||
5
visual-editor-test/testing/babel.config.js
Normal file
5
visual-editor-test/testing/babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
19
visual-editor-test/testing/jsconfig.json
Normal file
19
visual-editor-test/testing/jsconfig.json
Normal 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
19550
visual-editor-test/testing/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
visual-editor-test/testing/package.json
Normal file
26
visual-editor-test/testing/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
visual-editor-test/testing/public/favicon.ico
Normal file
BIN
visual-editor-test/testing/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
17
visual-editor-test/testing/public/index.html
Normal file
17
visual-editor-test/testing/public/index.html
Normal 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>
|
||||
30
visual-editor-test/testing/src/App.vue
Normal file
30
visual-editor-test/testing/src/App.vue
Normal 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>
|
||||
BIN
visual-editor-test/testing/src/assets/logo.png
Normal file
BIN
visual-editor-test/testing/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
59
visual-editor-test/testing/src/components/HelloWorld.vue
Normal file
59
visual-editor-test/testing/src/components/HelloWorld.vue
Normal 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>
|
||||
11
visual-editor-test/testing/src/main.js
Normal file
11
visual-editor-test/testing/src/main.js
Normal 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')
|
||||
25
visual-editor-test/testing/src/router/index.js
Normal file
25
visual-editor-test/testing/src/router/index.js
Normal 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
|
||||
70
visual-editor-test/testing/src/views/EditorView.vue
Normal file
70
visual-editor-test/testing/src/views/EditorView.vue
Normal 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
Reference in New Issue
Block a user