mirror of
https://github.com/glenndehaan/unifi-voucher-site.git
synced 2026-03-31 06:24:00 -04:00
Updated dependencies. Introduced a styled 404 page. Implemented a new authorization system including separate login flow. Implemented flash message system to removal all query url parameters. Made SID more persistent between sessions. Implemented new vouchers overview. Improved color scheme for info and error flash messages. Updated README.md
This commit is contained in:
@@ -4,6 +4,8 @@ A small UniFi Voucher Site for simple voucher creation
|
||||
|
||||
[](https://hub.docker.com/r/glenndehaan/unifi-voucher-site)
|
||||
|
||||

|
||||
|
||||
## Structure
|
||||
- ES6 Javascript
|
||||
- ExpressJS
|
||||
|
||||
23
middlewares/authorization.js
Normal file
23
middlewares/authorization.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Verifies if a user is signed in
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param next
|
||||
*/
|
||||
module.exports = async (req, res, next) => {
|
||||
// Check if user has an existing authorization cookie
|
||||
if(!req.cookies.authorization) {
|
||||
res.redirect(302, '/login');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if password is correct
|
||||
const passwordCheck = req.cookies.authorization === (process.env.SECURITY_CODE || "0000");
|
||||
if(!passwordCheck) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Password Invalid!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, '/login');
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
20
middlewares/flashMessage.js
Normal file
20
middlewares/flashMessage.js
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Retrieves a flash message from a cookie if available
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param next
|
||||
*/
|
||||
module.exports = async (req, res, next) => {
|
||||
req.flashMessage = {
|
||||
type: '',
|
||||
message: ''
|
||||
};
|
||||
|
||||
if(req.cookies.flashMessage) {
|
||||
req.flashMessage = JSON.parse(req.cookies.flashMessage);
|
||||
res.cookie('flashMessage', '', {httpOnly: true, expires: new Date(0)})
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
28
middlewares/sid.js
Normal file
28
middlewares/sid.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Import base packages
|
||||
*/
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
/**
|
||||
* Attaches a session ID to a user session
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param next
|
||||
*/
|
||||
module.exports = async (req, res, next) => {
|
||||
req.sid = "";
|
||||
|
||||
// Check if user has an existing session id
|
||||
if(!req.cookies.sid) {
|
||||
const sid = uuidv4();
|
||||
res.cookie('sid', sid, {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)});
|
||||
req.sid = sid;
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
req.sid = req.cookies.sid;
|
||||
|
||||
next();
|
||||
}
|
||||
@@ -20,16 +20,23 @@ const config = {
|
||||
* Exports the UniFi voucher function
|
||||
*
|
||||
* @param type
|
||||
* @param create
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
module.exports = (type) => {
|
||||
return new Promise((resolve) => {
|
||||
module.exports = (type, create = true) => {
|
||||
if(create) {
|
||||
return new Promise((resolve, reject) => {
|
||||
/**
|
||||
* Create new UniFi controller object
|
||||
*
|
||||
* @type {Controller}
|
||||
*/
|
||||
const controller = new unifi.Controller({host: config.unifi.ip, port: config.unifi.port, site: config.unifi.siteID, sslverify: false});
|
||||
const controller = new unifi.Controller({
|
||||
host: config.unifi.ip,
|
||||
port: config.unifi.port,
|
||||
site: config.unifi.siteID,
|
||||
sslverify: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Login and create a voucher
|
||||
@@ -41,24 +48,62 @@ module.exports = (type) => {
|
||||
const voucher = `${[voucher_data_complete[0].code.slice(0, 5), '-', voucher_data_complete[0].code.slice(5)].join('')}`;
|
||||
resolve(voucher);
|
||||
}).catch((e) => {
|
||||
console.log('Error while getting voucher!');
|
||||
console.log('[UniFi] Error while getting voucher!');
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
reject('[UniFi] Error while getting voucher!');
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log('Error while creating voucher!');
|
||||
console.log('[UniFi] Error while creating voucher!');
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
reject('[UniFi] Error while creating voucher!');
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log('Error while getting site stats!');
|
||||
console.log('[UniFi] Error while getting site stats!');
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
reject('[UniFi] Error while getting site stats!');
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log('Error while logging in!');
|
||||
console.log('[UniFi] Error while logging in!');
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
reject('[UniFi] Error while logging in!');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
/**
|
||||
* Create new UniFi controller object
|
||||
*
|
||||
* @type {Controller}
|
||||
*/
|
||||
const controller = new unifi.Controller({
|
||||
host: config.unifi.ip,
|
||||
port: config.unifi.port,
|
||||
site: config.unifi.siteID,
|
||||
sslverify: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Login and create a voucher
|
||||
*/
|
||||
controller.login(config.unifi.username, config.unifi.password).then(() => {
|
||||
controller.getSitesStats().then(() => {
|
||||
controller.getVouchers().then((vouchers) => {
|
||||
resolve(vouchers);
|
||||
}).catch((e) => {
|
||||
console.log('[UniFi] Error while getting voucher!');
|
||||
console.log(e);
|
||||
reject('[UniFi] Error while getting voucher!');
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log('[UniFi] Error while getting site stats!');
|
||||
console.log(e);
|
||||
reject('[UniFi] Error while getting site stats!');
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log('[UniFi] Error while logging in!');
|
||||
console.log(e);
|
||||
reject('[UniFi] Error while logging in!');
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
51
package-lock.json
generated
51
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.4.6",
|
||||
"ejs": "^3.1.9",
|
||||
"express": "^4.18.2",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
@@ -18,7 +19,7 @@
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2"
|
||||
"nodemon": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
@@ -484,6 +485,26 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser/node_modules/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
@@ -1321,9 +1342,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nodemon": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.2.tgz",
|
||||
"integrity": "sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz",
|
||||
"integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.2",
|
||||
@@ -2887,6 +2908,22 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
||||
},
|
||||
"cookie-parser": {
|
||||
"version": "1.4.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||
"requires": {
|
||||
"cookie": "0.4.1",
|
||||
"cookie-signature": "1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
@@ -3481,9 +3518,9 @@
|
||||
}
|
||||
},
|
||||
"nodemon": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.2.tgz",
|
||||
"integrity": "sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz",
|
||||
"integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": "^3.5.2",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"author": "Glenn de Haan",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.4.6",
|
||||
"ejs": "^3.1.9",
|
||||
"express": "^4.18.2",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
@@ -24,6 +25,6 @@
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2"
|
||||
"nodemon": "^3.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
123
server.js
123
server.js
@@ -3,8 +3,7 @@
|
||||
*/
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const app = express();
|
||||
const cookieParser = require('cookie-parser');
|
||||
|
||||
/**
|
||||
* Import own modules
|
||||
@@ -14,6 +13,18 @@ const types = require('./modules/types');
|
||||
const time = require('./modules/time');
|
||||
const unifi = require('./modules/unifi');
|
||||
|
||||
/**
|
||||
* Import own middlewares
|
||||
*/
|
||||
const authorization = require('./middlewares/authorization');
|
||||
const flashMessage = require('./middlewares/flashMessage');
|
||||
const sid = require('./middlewares/sid');
|
||||
|
||||
/**
|
||||
* Setup Express app
|
||||
*/
|
||||
const app = express();
|
||||
|
||||
/**
|
||||
* Define global functions and variables
|
||||
*/
|
||||
@@ -54,11 +65,31 @@ app.set('views', `${__dirname}/template`);
|
||||
*/
|
||||
app.use(multer().none());
|
||||
|
||||
/**
|
||||
* Enable cookie-parser
|
||||
*/
|
||||
app.use(cookieParser());
|
||||
|
||||
/**
|
||||
* Enable flash-message
|
||||
*/
|
||||
app.use(flashMessage);
|
||||
|
||||
/**
|
||||
* Enable session id
|
||||
*/
|
||||
app.use(sid);
|
||||
|
||||
/**
|
||||
* Request logger
|
||||
*/
|
||||
app.use((req, res, next) => {
|
||||
if(req.originalUrl.includes('/images') || req.originalUrl.includes('/dist') || req.originalUrl.includes('/manifest')) {
|
||||
console.log(`[Web]: ${req.originalUrl}`);
|
||||
} else {
|
||||
console.log(`[Web][${req.sid}]: ${req.originalUrl}`);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -71,20 +102,21 @@ app.use(express.static(`${__dirname}/public`));
|
||||
* Configure routers
|
||||
*/
|
||||
app.get('/', (req, res) => {
|
||||
res.redirect(302, '/voucher');
|
||||
});
|
||||
app.get('/login', (req, res) => {
|
||||
const hour = new Date().getHours();
|
||||
const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||
|
||||
res.render('home', {
|
||||
error: typeof req.query.error === 'string' && req.query.error !== '',
|
||||
error_text: req.query.error || '',
|
||||
res.render('login', {
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
banner_image: process.env.BANNER_IMAGE || `/images/bg-${random(1, 10)}.jpg`,
|
||||
app_header: timeHeader,
|
||||
sid: uuidv4(),
|
||||
timeConvert: time,
|
||||
voucher_types: voucherTypes
|
||||
sid: req.sid
|
||||
});
|
||||
});
|
||||
app.post('/', async (req, res) => {
|
||||
app.post('/login', async (req, res) => {
|
||||
if(typeof req.body === "undefined") {
|
||||
res.status(400).send();
|
||||
return;
|
||||
@@ -93,44 +125,74 @@ app.post('/', async (req, res) => {
|
||||
const passwordCheck = req.body.password === (process.env.SECURITY_CODE || "0000");
|
||||
|
||||
if(!passwordCheck) {
|
||||
res.redirect(encodeURI(`/?error=Invalid password!`));
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Password Invalid!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, '/login');
|
||||
return;
|
||||
}
|
||||
|
||||
res.cookie('authorization', req.body.password, {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, '/voucher');
|
||||
});
|
||||
app.get('/voucher', [authorization], async (req, res) => {
|
||||
const hour = new Date().getHours();
|
||||
const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||
|
||||
res.render('voucher', {
|
||||
info: req.flashMessage.type === 'info',
|
||||
info_text: req.flashMessage.message || '',
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
banner_image: process.env.BANNER_IMAGE || `/images/bg-${random(1, 10)}.jpg`,
|
||||
app_header: timeHeader,
|
||||
sid: req.sid,
|
||||
timeConvert: time,
|
||||
voucher_types: voucherTypes,
|
||||
vouchers_popup: false
|
||||
});
|
||||
});
|
||||
app.post('/voucher', [authorization], async (req, res) => {
|
||||
if(typeof req.body === "undefined") {
|
||||
res.status(400).send();
|
||||
return;
|
||||
}
|
||||
|
||||
const typeCheck = (process.env.VOUCHER_TYPES || '480,0,,,;').split(';').includes(req.body['voucher-type']);
|
||||
|
||||
if(!typeCheck) {
|
||||
res.redirect(encodeURI(`/?error=Unknown type!`));
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: 'Unknown Type!'}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, '/voucher');
|
||||
return;
|
||||
}
|
||||
|
||||
res.redirect(encodeURI(`/voucher?code=${req.body.password}&type=${req.body['voucher-type']}`));
|
||||
// Create voucher code
|
||||
const voucherCode = await unifi(types(req.body['voucher-type'], true)).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, '/voucher');
|
||||
});
|
||||
app.get('/voucher', async (req, res) => {
|
||||
if(req.query.code !== (process.env.SECURITY_CODE || "0000")) {
|
||||
res.status(403).send();
|
||||
return;
|
||||
}
|
||||
|
||||
if(!(process.env.VOUCHER_TYPES || '480,0,,,;').split(';').includes(req.query.type)) {
|
||||
res.status(400).send();
|
||||
return;
|
||||
if(voucherCode) {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Voucher Created: ${voucherCode}`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, '/voucher');
|
||||
}
|
||||
|
||||
});
|
||||
app.get('/vouchers', [authorization], async (req, res) => {
|
||||
const hour = new Date().getHours();
|
||||
const timeHeader = hour < 12 ? 'Good Morning' : hour < 18 ? 'Good Afternoon' : 'Good Evening';
|
||||
const voucherCode = await unifi(types(req.query.type, true));
|
||||
|
||||
const vouchers = await unifi('', false).catch((e) => {
|
||||
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, '/voucher');
|
||||
});
|
||||
|
||||
if(vouchers) {
|
||||
res.render('voucher', {
|
||||
error: typeof req.query.error === 'string' && req.query.error !== '',
|
||||
error_text: req.query.error || '',
|
||||
info: req.flashMessage.type === 'info',
|
||||
info_text: req.flashMessage.message || '',
|
||||
error: req.flashMessage.type === 'error',
|
||||
error_text: req.flashMessage.message || '',
|
||||
banner_image: process.env.BANNER_IMAGE || `/images/bg-${random(1, 10)}.jpg`,
|
||||
app_header: timeHeader,
|
||||
code: req.query.code,
|
||||
type: req.query.type,
|
||||
voucher_code: voucherCode,
|
||||
sid: uuidv4()
|
||||
sid: req.sid,
|
||||
timeConvert: time,
|
||||
voucher_types: voucherTypes,
|
||||
vouchers_popup: true,
|
||||
vouchers
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -138,7 +200,10 @@ app.get('/voucher', async (req, res) => {
|
||||
*/
|
||||
app.use((req, res) => {
|
||||
res.status(404);
|
||||
res.send('Not Found!');
|
||||
res.render('404', {
|
||||
banner_image: process.env.BANNER_IMAGE || `/images/bg-${random(1, 10)}.jpg`,
|
||||
sid: req.sid
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
65
template/404.ejs
Normal file
65
template/404.ejs
Normal file
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Not Found | UniFi Voucher</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||
|
||||
<meta name="description" content="UniFi Voucher">
|
||||
<meta name="author" content="Glenn de Haan">
|
||||
|
||||
<meta property="og:title" content="Not Found | UniFi Voucher"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:description" content="UniFi Voucher"/>
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="shortcut icon" href="/images/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/images/icon/logo_256x256.png">
|
||||
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#1875b6">
|
||||
|
||||
<link rel="preload" href="<%= banner_image %>" as="image">
|
||||
<link rel="preload" href="/images/unifi-icon.png" as="image">
|
||||
<link rel="preload" href="/dist/style.css" as="style">
|
||||
<link href="/dist/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-white dark:bg-neutral-900 dark:text-gray-100 h-screen">
|
||||
<div class="w-full flex flex-wrap">
|
||||
<div class="w-full md:w-1/2 flex flex-col">
|
||||
<div class="flex justify-center md:justify-start pt-12 md:pl-12 md:-mb-24">
|
||||
<a href="/" title="Homepage">
|
||||
<img class="h-20 w-20" alt="UniFi Logo" src="/images/unifi-icon.png"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center md:justify-start my-auto pt-8 md:pt-0 px-8 md:px-24 lg:px-32">
|
||||
<p class="text-center text-3xl">Not Found</p>
|
||||
|
||||
<div class="text-center text-gray-400 pt-12">
|
||||
<p>
|
||||
It seems this page does not exist.<br/>
|
||||
<a href="/" class="underline font-semibold">Go back to the homepage</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-gray-400 text-sm italic pt-12 pb-12">
|
||||
<p>
|
||||
Powered by: <a href="https://glenndehaan.com" class="underline font-semibold">Glenn de Haan</a>.<br/>
|
||||
Want your own portal? Checkout the project on: <a href="https://github.com/glenndehaan/unifi-voucher-site" class="underline font-semibold">GitHub</a>
|
||||
</p>
|
||||
<p class="text-[10px] not-italic">
|
||||
SID: <%= sid %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-1/2 shadow-2xl">
|
||||
<img class="object-cover w-full h-screen hidden md:block" src="<%= banner_image %>">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,110 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Login | UniFi Voucher</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||
|
||||
<meta name="description" content="UniFi Voucher">
|
||||
<meta name="author" content="Glenn de Haan">
|
||||
|
||||
<meta property="og:title" content="Login | UniFi Voucher"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:description" content="UniFi Voucher"/>
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="shortcut icon" href="/images/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/images/icon/logo_256x256.png">
|
||||
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#1875b6">
|
||||
|
||||
<link rel="preload" href="<%= banner_image %>" as="image">
|
||||
<link rel="preload" href="/images/unifi-icon.png" as="image">
|
||||
<link rel="preload" href="/dist/style.css" as="style">
|
||||
<link href="/dist/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-white dark:bg-neutral-900 dark:text-gray-100 h-screen">
|
||||
<div class="w-full flex flex-wrap">
|
||||
<div class="w-full md:w-1/2 flex flex-col">
|
||||
<div class="flex justify-center md:justify-start pt-12 md:pl-12 md:-mb-24">
|
||||
<a href="/" title="Homepage">
|
||||
<img class="h-20 w-20" alt="UniFi Logo" src="/images/unifi-icon.png"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center md:justify-start my-auto pt-8 md:pt-0 px-8 md:px-24 lg:px-32">
|
||||
<p class="text-center text-3xl"><%= app_header %></p>
|
||||
<% if(error) { %>
|
||||
<div class="bg-red-500 text-white p-3 mt-8 rounded shadow-lg flex items-center">
|
||||
<svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<div><%= error_text %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<form id="voucher-forum" class="flex flex-col pt-3 md:pt-8" action="/" method="post" enctype="multipart/form-data">
|
||||
<div class="flex flex-col pt-4">
|
||||
<label for="password" class="text-lg">Password</label>
|
||||
<input type="password" id="password" name="password" placeholder="Password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mt-1 leading-tight focus:outline-none focus:shadow-outline dark:bg-neutral-800 dark:border-neutral-700 dark:text-gray-100" required>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col pt-4">
|
||||
<label for="voucher-type" class="text-lg">Voucher Type</label>
|
||||
<div class="relative inline-block w-full">
|
||||
<select id="voucher-type" name="voucher-type" class="shadow appearance-none border rounded w-full py-2 px-3 pr-8 text-gray-700 mt-1 leading-tight focus:outline-none focus:shadow-outline dark:bg-neutral-800 dark:border-neutral-700 dark:text-gray-100" required>
|
||||
<% voucher_types.forEach((type) => { %>
|
||||
<option value="<%= type.raw %>"><%= timeConvert(type.expiration) %>, <%= type.usage === '1' ? 'single-use' : 'multi-use' %><%= typeof type.upload === "undefined" && typeof type.download === "undefined" && typeof type.megabytes === "undefined" ? ', no limits' : '' %><%= typeof type.upload !== "undefined" ? `, upload bandwidth limit: ${type.upload} kb/s` : '' %><%= typeof type.download !== "undefined" ? `, download bandwidth limit: ${type.download} kb/s` : '' %><%= typeof type.megabytes !== "undefined" ? `, quota limit: ${type.megabytes} mb` : '' %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center px-2 mt-1 pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 fill-current" viewBox="0 0 20 20">
|
||||
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" fillRule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Create Voucher" class="bg-black text-white font-bold text-lg hover:bg-gray-700 p-2 mt-8 cursor-pointer transition-colors dark:text-black dark:bg-gray-200 dark:hover:bg-white">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-gray-400 text-sm italic pt-12 pb-12">
|
||||
<p>
|
||||
Powered by: <a href="https://glenndehaan.com" class="underline font-semibold">Glenn de Haan</a>.<br/>
|
||||
Want your own portal? Checkout the project on: <a href="https://github.com/glenndehaan/unifi-voucher-site" class="underline font-semibold">GitHub</a>
|
||||
</p>
|
||||
<p class="text-[10px] not-italic">
|
||||
SID: <%= sid %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-1/2 shadow-2xl">
|
||||
<img class="object-cover w-full h-screen hidden md:block" src="<%= banner_image %>">
|
||||
</div>
|
||||
|
||||
<div id="spinner" style="display: none;" class="fixed top-0 left-0 right-0 bottom-0 w-full h-screen z-50 overflow-hidden bg-gray-900 opacity-90 flex flex-col items-center justify-center">
|
||||
<div class="mb-4">
|
||||
<svg class="w-14 h-14 text-gray-200 animate-spin dark:text-gray-600 fill-cyan-400" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-center text-white text-xl font-semibold">Generating Voucher...</h2>
|
||||
<p class="w-1/3 text-center text-white">This may take a few seconds, please don't close this page.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript">
|
||||
var el = document.querySelector("#spinner");
|
||||
var forum = document.querySelector("#voucher-forum");
|
||||
|
||||
forum.addEventListener("submit", function () {
|
||||
el.style.display = "";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
74
template/login.ejs
Normal file
74
template/login.ejs
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Login | UniFi Voucher</title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||
|
||||
<meta name="description" content="UniFi Voucher">
|
||||
<meta name="author" content="Glenn de Haan">
|
||||
|
||||
<meta property="og:title" content="Login | UniFi Voucher"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:description" content="UniFi Voucher"/>
|
||||
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="shortcut icon" href="/images/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/images/icon/logo_256x256.png">
|
||||
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="theme-color" content="#1875b6">
|
||||
|
||||
<link rel="preload" href="<%= banner_image %>" as="image">
|
||||
<link rel="preload" href="/images/unifi-icon.png" as="image">
|
||||
<link rel="preload" href="/dist/style.css" as="style">
|
||||
<link href="/dist/style.css" rel="stylesheet">
|
||||
</head>
|
||||
<body class="bg-white dark:bg-neutral-900 dark:text-gray-100 h-screen">
|
||||
<div class="w-full flex flex-wrap">
|
||||
<div class="w-full md:w-1/2 flex flex-col">
|
||||
<div class="flex justify-center md:justify-start pt-12 md:pl-12 md:-mb-24">
|
||||
<a href="/" title="Homepage">
|
||||
<img class="h-20 w-20" alt="UniFi Logo" src="/images/unifi-icon.png"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center md:justify-start my-auto pt-8 md:pt-0 px-8 md:px-24 lg:px-32">
|
||||
<p class="text-center text-3xl"><%= app_header %></p>
|
||||
<% if(error) { %>
|
||||
<div class="bg-red-700 text-white p-3 mt-8 rounded shadow-lg flex items-center">
|
||||
<svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
<div><%= error_text %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<form id="login-forum" class="flex flex-col pt-3 md:pt-8" action="/login" method="post" enctype="multipart/form-data">
|
||||
<div class="flex flex-col pt-4">
|
||||
<label for="password" class="text-lg">Password</label>
|
||||
<input type="password" id="password" name="password" placeholder="Password" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mt-1 leading-tight focus:outline-none focus:shadow-outline dark:bg-neutral-800 dark:border-neutral-700 dark:text-gray-100" required>
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Login" class="bg-black text-white font-bold text-lg hover:bg-gray-700 p-2 mt-8 cursor-pointer transition-colors dark:text-black dark:bg-gray-200 dark:hover:bg-white">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-gray-400 text-sm italic pt-12 pb-12">
|
||||
<p>
|
||||
Powered by: <a href="https://glenndehaan.com" class="underline font-semibold">Glenn de Haan</a>.<br/>
|
||||
Want your own portal? Checkout the project on: <a href="https://github.com/glenndehaan/unifi-voucher-site" class="underline font-semibold">GitHub</a>
|
||||
</p>
|
||||
<p class="text-[10px] not-italic">
|
||||
SID: <%= sid %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-1/2 shadow-2xl">
|
||||
<img class="object-cover w-full h-screen hidden md:block" src="<%= banner_image %>">
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -37,19 +37,45 @@
|
||||
|
||||
<div class="flex flex-col justify-center md:justify-start my-auto pt-8 md:pt-0 px-8 md:px-24 lg:px-32">
|
||||
<p class="text-center text-3xl"><%= app_header %></p>
|
||||
<p class="mt-4 text-center">
|
||||
Voucher generated successfully!
|
||||
</p>
|
||||
<form id="voucher-forum" class="flex flex-col pt-3 md:pt-8" action="/" method="post" enctype="multipart/form-data">
|
||||
<% if(info) { %>
|
||||
<div class="bg-green-700 text-white p-3 mt-8 rounded shadow-lg flex items-center">
|
||||
<svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
<div><%= info_text %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if(error) { %>
|
||||
<div class="bg-red-700 text-white p-3 mt-8 rounded shadow-lg flex items-center">
|
||||
<svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
<div><%= error_text %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<form id="voucher-forum" class="flex flex-col pt-3 md:pt-8" action="/voucher" method="post" enctype="multipart/form-data">
|
||||
<div class="flex flex-col pt-4">
|
||||
<label for="voucher" class="text-lg">Voucher</label>
|
||||
<input type="text" id="voucher" name="voucher" disabled value="<%= voucher_code %>" class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 text-fill-gray-700 mt-1 leading-tight focus:outline-none focus:shadow-outline dark:bg-neutral-800 dark:border-neutral-700 dark:text-gray-100 dark:text-fill-gray-100">
|
||||
<label for="voucher-type" class="text-lg">Voucher Type</label>
|
||||
<div class="relative inline-block w-full">
|
||||
<select id="voucher-type" name="voucher-type" class="shadow appearance-none border rounded w-full py-2 px-3 pr-8 text-gray-700 mt-1 leading-tight focus:outline-none focus:shadow-outline dark:bg-neutral-800 dark:border-neutral-700 dark:text-gray-100" required>
|
||||
<% voucher_types.forEach((type) => { %>
|
||||
<option value="<%= type.raw %>"><%= timeConvert(type.expiration) %>, <%= type.usage === '1' ? 'single-use' : 'multi-use' %><%= typeof type.upload === "undefined" && typeof type.download === "undefined" && typeof type.megabytes === "undefined" ? ', no limits' : '' %><%= typeof type.upload !== "undefined" ? `, upload bandwidth limit: ${type.upload} kb/s` : '' %><%= typeof type.download !== "undefined" ? `, download bandwidth limit: ${type.download} kb/s` : '' %><%= typeof type.megabytes !== "undefined" ? `, quota limit: ${type.megabytes} mb` : '' %></option>
|
||||
<% }); %>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center px-2 mt-1 pointer-events-none">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 fill-current" viewBox="0 0 20 20">
|
||||
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clipRule="evenodd" fillRule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="password" name="password" value="<%= code %>"/>
|
||||
<input type="hidden" id="voucher-type" name="voucher-type" value="<%= type %>"/>
|
||||
<input type="submit" value="Create Another Voucher" class="bg-black text-white font-bold text-lg hover:bg-gray-700 p-2 mt-8 cursor-pointer transition-colors dark:text-black dark:bg-gray-200 dark:hover:bg-white">
|
||||
<input type="submit" value="Create Voucher" class="bg-black text-white font-bold text-lg hover:bg-gray-700 p-2 mt-8 cursor-pointer transition-colors dark:text-black dark:bg-gray-200 dark:hover:bg-white">
|
||||
</form>
|
||||
|
||||
<hr class="border-black dark:border-gray-200 my-4 mx-14"/>
|
||||
|
||||
<a id="voucher-list" href="/vouchers" class="bg-black text-white font-bold text-lg hover:bg-gray-700 p-2 cursor-pointer transition-colors text-center dark:text-black dark:bg-gray-200 dark:hover:bg-white">All Vouchers</a>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-gray-400 text-sm italic pt-12 pb-12">
|
||||
@@ -67,7 +93,66 @@
|
||||
<img class="object-cover w-full h-screen hidden md:block" src="<%= banner_image %>">
|
||||
</div>
|
||||
|
||||
<div id="spinner" style="display: none;" class="fixed top-0 left-0 right-0 bottom-0 w-full h-screen z-50 overflow-hidden bg-gray-900 opacity-90 flex flex-col items-center justify-center">
|
||||
<% if(vouchers_popup) { %>
|
||||
<div class="fixed top-0 left-0 right-0 bottom-0 w-full h-screen z-50 overflow-hidden bg-gray-900 opacity-95 flex flex-col items-center justify-center">
|
||||
<div class="px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-base font-semibold leading-6 text-gray-100">Vouchers</h1>
|
||||
<p class="mt-2 text-sm text-gray-100">
|
||||
A list of current available vouchers within UniFi.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
|
||||
<a href="/voucher" class="block bg-black text-white px-3 py-2 text-center text-sm font-semibold shadow-sm hover:bg-gray-700 cursor-pointer transition-colors dark:text-black dark:bg-gray-200 dark:hover:bg-white">
|
||||
Close
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 flow-root">
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<div class="h-96">
|
||||
<table class="min-w-full divide-y divide-gray-300">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">
|
||||
Code
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Used
|
||||
</th>
|
||||
<th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
||||
Duration
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 bg-white">
|
||||
<% vouchers.forEach((voucher) => { %>
|
||||
<tr>
|
||||
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 tabular-nums">
|
||||
<%= voucher.code.slice(0, 5) %>-<%= voucher.code.slice(5) %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
<%= voucher.used > 0 ? `Yes (${voucher.used}x)` : 'No' %>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
|
||||
<%= Math.floor(voucher.duration / 60) %>h
|
||||
</td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div id="spinner-create" style="display: none;" class="fixed top-0 left-0 right-0 bottom-0 w-full h-screen z-50 overflow-hidden bg-gray-900 opacity-90 flex flex-col items-center justify-center">
|
||||
<div class="mb-4">
|
||||
<svg class="w-14 h-14 text-gray-200 animate-spin dark:text-gray-600 fill-cyan-400" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||
@@ -75,16 +160,34 @@
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-center text-white text-xl font-semibold">Generating Voucher...</h2>
|
||||
<p class="w-1/3 text-center text-white">This may take a few seconds, please don't close this page.</p>
|
||||
<p class="w-1/2 text-center text-white">This may take a few seconds, please don't close this page.</p>
|
||||
</div>
|
||||
|
||||
<div id="spinner-list" style="display: none;" class="fixed top-0 left-0 right-0 bottom-0 w-full h-screen z-50 overflow-hidden bg-gray-900 opacity-90 flex flex-col items-center justify-center">
|
||||
<div class="mb-4">
|
||||
<svg class="w-14 h-14 text-gray-200 animate-spin dark:text-gray-600 fill-cyan-400" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h2 class="text-center text-white text-xl font-semibold">Requesting Vouchers Overview...</h2>
|
||||
<p class="w-1/2 text-center text-white">This may take a few seconds, please don't close this page.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/javascript">
|
||||
var el = document.querySelector("#spinner");
|
||||
var forum = document.querySelector("#voucher-forum");
|
||||
var spinnerCreate = document.querySelector("#spinner-create");
|
||||
var spinnerList = document.querySelector("#spinner-list");
|
||||
|
||||
forum.addEventListener("submit", function () {
|
||||
el.style.display = "";
|
||||
var createForum = document.querySelector("#voucher-forum");
|
||||
var listButton = document.querySelector("#voucher-list");
|
||||
|
||||
createForum.addEventListener("submit", function () {
|
||||
spinnerCreate.style.display = "";
|
||||
});
|
||||
|
||||
listButton.addEventListener("click", function () {
|
||||
spinnerList.style.display = "";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user