mirror of
https://github.com/glenndehaan/unifi-voucher-site.git
synced 2026-03-31 06:24:00 -04:00
Initial commit
This commit is contained in:
24
.editorconfig
Normal file
24
.editorconfig
Normal file
@@ -0,0 +1,24 @@
|
||||
# Top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Set default charset with unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# PHP PSR2 overrides
|
||||
[*.php]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# JS overrides
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# SCSS and JSON overrides
|
||||
[*.{scss,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# PhpStorm
|
||||
.idea
|
||||
|
||||
# General files
|
||||
*~
|
||||
*.DS_STORE
|
||||
|
||||
# Dependency managers
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
|
||||
# Build files
|
||||
dist/
|
||||
21
LICENCE
Normal file
21
LICENCE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Glenn de Haan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
13
README.md
Normal file
13
README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# UniFi Voucher Site
|
||||
|
||||
## Usage
|
||||
- Install NodeJS 8.0 or higher.
|
||||
- Run `npm install` in the root folder
|
||||
- Run `npm start` or `npm run dev` in the root folder
|
||||
|
||||
Then open up your favorite web browser and go to: http://localhost:3001
|
||||
That's it. Easy right?
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
111
_scripts/webpack.config.js
Normal file
111
_scripts/webpack.config.js
Normal file
@@ -0,0 +1,111 @@
|
||||
const webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const browserslist = require('../package.json').browserslist;
|
||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||
|
||||
//Get path so every environment works
|
||||
const projectPath = path.resolve(__dirname);
|
||||
|
||||
//Environment check depending on call from
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const isProd = env === 'production';
|
||||
|
||||
//Define all the global config for both production & dev
|
||||
const config = {
|
||||
entry: {
|
||||
main: [
|
||||
projectPath + '/../public/js/main.js',
|
||||
projectPath + '/../public/scss/style.scss'
|
||||
],
|
||||
},
|
||||
output: {
|
||||
path: projectPath + '/../public/dist/',
|
||||
filename: '[name].[hash:6].js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
presets: ['babel-preset-es2015'].map(require.resolve),
|
||||
sourceMaps: 'inline'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(css|scss)$/,
|
||||
loader: ExtractTextPlugin.extract({
|
||||
use: [
|
||||
'raw-loader?url=false', {
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
ident: 'postcss',
|
||||
context: path.resolve(__dirname, '../public'),
|
||||
sourceMap: 'inline',
|
||||
plugins: [
|
||||
autoprefixer
|
||||
],
|
||||
}
|
||||
},
|
||||
'sass-loader?sourceMap=true'
|
||||
]
|
||||
})
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(env)
|
||||
}
|
||||
}),
|
||||
new ExtractTextPlugin({filename: 'main.[hash:6].css', allChunks: true}),
|
||||
new ManifestPlugin({
|
||||
fileName: 'rev-manifest.json',
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js'],
|
||||
modules: [path.join(__dirname, '../node_modules')]
|
||||
},
|
||||
devServer: {
|
||||
compress: true,
|
||||
disableHostCheck: true,
|
||||
}
|
||||
};
|
||||
|
||||
//Extra options depending on environment
|
||||
if (isProd) {
|
||||
Object.assign(config, {
|
||||
plugins: config.plugins.concat([
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true,
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
drop_console: true,
|
||||
output: {
|
||||
comments: false,
|
||||
},
|
||||
mangle: {
|
||||
screw_ie8: true,
|
||||
},
|
||||
compressor: {
|
||||
screw_ie8: true,
|
||||
warnings: false,
|
||||
}
|
||||
})
|
||||
])
|
||||
});
|
||||
} else {
|
||||
Object.assign(config, {
|
||||
devtool: 'cheap-module-eval-source-map',
|
||||
plugins: config.plugins.concat([
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
])
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = config;
|
||||
24
app/config/config.js
Normal file
24
app/config/config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* General config
|
||||
*/
|
||||
const config = {
|
||||
application: {
|
||||
name: "NodeExpress",
|
||||
env: " (local)",
|
||||
basePath: "/",
|
||||
port: 3001,
|
||||
bind: "0.0.0.0",
|
||||
supportedBrowsers: [
|
||||
"Chrome >= 52",
|
||||
"Firefox >= 47",
|
||||
"Safari >= 10",
|
||||
"Edge == All",
|
||||
"IE == 11"
|
||||
]
|
||||
},
|
||||
session: {
|
||||
secret: "averysecretstring"
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
21
app/controllers/Api/BaseController.js
Normal file
21
app/controllers/Api/BaseController.js
Normal file
@@ -0,0 +1,21 @@
|
||||
class BaseController {
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a response to express
|
||||
*
|
||||
* @param response
|
||||
* @param status
|
||||
* @param data
|
||||
* @param contentType
|
||||
*/
|
||||
jsonResponse(response, status, data, contentType = 'application/json') {
|
||||
response.type(contentType);
|
||||
response.status(status);
|
||||
response.json(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseController;
|
||||
19
app/controllers/Api/IndexController.js
Normal file
19
app/controllers/Api/IndexController.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const baseController = require('./BaseController');
|
||||
|
||||
class IndexController extends baseController {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Action for the default api route
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
indexAction(req, res) {
|
||||
this.jsonResponse(res, 200, { 'message': 'Default API route!' });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new IndexController();
|
||||
26
app/controllers/Web/BaseController.js
Normal file
26
app/controllers/Web/BaseController.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const config = require("../../config/config");
|
||||
|
||||
class BaseController {
|
||||
constructor() {
|
||||
this.baseConfig = {
|
||||
config: config,
|
||||
hostname: '',
|
||||
baseUrl: ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the complete config base + page specific
|
||||
*
|
||||
* @param request
|
||||
* @param pageSpecificConfig
|
||||
*/
|
||||
mergePageConfig(request, pageSpecificConfig) {
|
||||
this.baseConfig.hostname = request.hostname;
|
||||
this.baseConfig.baseUrl = `${request.protocol}://${request.hostname}${config.application.basePath}`;
|
||||
|
||||
return Object.assign(this.baseConfig, pageSpecificConfig);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseController;
|
||||
74
app/controllers/Web/IndexController.js
Normal file
74
app/controllers/Web/IndexController.js
Normal file
@@ -0,0 +1,74 @@
|
||||
const baseController = require('./BaseController');
|
||||
|
||||
class IndexController extends baseController {
|
||||
/**
|
||||
* Renders the Home page
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
indexAction(req, res) {
|
||||
res.render('index', this.mergePageConfig(req, {
|
||||
template: 'index/index',
|
||||
pageTitle: 'Home'
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the 404 page
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
notFoundAction(req, res) {
|
||||
res.render('index', this.mergePageConfig(req, {
|
||||
template: 'general/notfound',
|
||||
pageTitle: 'Not Found'
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the old browser page (Fallback page)
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
oldBrowserAction(req, res) {
|
||||
res.render('index', this.mergePageConfig(req, {
|
||||
template: 'general/oldbrowser',
|
||||
pageTitle: 'Old Browser'
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the sitemap.xml
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param routes
|
||||
*/
|
||||
siteMapAction(req, res, routes) {
|
||||
res.type("application/xml");
|
||||
res.render('general/sitemap', this.mergePageConfig(req, {
|
||||
template: false,
|
||||
pageTitle: false,
|
||||
routes: routes
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the robots.txt
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
robotsAction(req, res) {
|
||||
res.type("text/plain");
|
||||
res.render('general/robots', this.mergePageConfig(req, {
|
||||
template: false,
|
||||
pageTitle: false
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new IndexController();
|
||||
26
app/helpers/modules/Router.js
Normal file
26
app/helpers/modules/Router.js
Normal file
@@ -0,0 +1,26 @@
|
||||
class Router {
|
||||
|
||||
/**
|
||||
* An easy to use function to add multiple routes to the Express router
|
||||
*
|
||||
* @param router
|
||||
* @param routes
|
||||
* @param type
|
||||
*/
|
||||
routesToRouter(router, routes, type) {
|
||||
for (let item = 0; item < routes.length; item += 1) {
|
||||
const route = routes[item];
|
||||
const controller = route.controller.charAt(0).toUpperCase() + route.controller.slice(1);
|
||||
eval(
|
||||
`
|
||||
const ${route.controller}Controller = require('../../controllers/${type}/${controller}Controller');
|
||||
router.${route.method}('${route.route}', (req, res) => {
|
||||
${route.controller}Controller.${route.action}Action(req, res);
|
||||
});
|
||||
`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Router();
|
||||
22
app/routers/Api.js
Normal file
22
app/routers/Api.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Import base packages
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const routerUtils = require('../helpers/modules/Router');
|
||||
|
||||
/**
|
||||
* Define routes
|
||||
*/
|
||||
const routes = [
|
||||
{
|
||||
route: '/',
|
||||
method: 'get',
|
||||
controller: 'Index',
|
||||
action: 'index'
|
||||
}
|
||||
];
|
||||
|
||||
routerUtils.routesToRouter(router, routes, 'Api');
|
||||
|
||||
module.exports = {router, routes};
|
||||
22
app/routers/Web.js
Normal file
22
app/routers/Web.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Import base packages
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const routerUtils = require('../helpers/modules/Router');
|
||||
|
||||
/**
|
||||
* Define routes
|
||||
*/
|
||||
const routes = [
|
||||
{
|
||||
route: '/',
|
||||
method: 'get',
|
||||
controller: 'Index',
|
||||
action: 'index'
|
||||
}
|
||||
];
|
||||
|
||||
routerUtils.routesToRouter(router, routes, 'Web');
|
||||
|
||||
module.exports = {router, routes};
|
||||
110
app/server.js
Normal file
110
app/server.js
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Import base packages
|
||||
*/
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const bodyParser = require('body-parser');
|
||||
const session = require('express-session');
|
||||
const browsersupport = require('express-browsersupport');
|
||||
|
||||
/**
|
||||
* Import own packages
|
||||
*/
|
||||
const config = require('./config/config');
|
||||
const webRouter = require('./routers/Web');
|
||||
const apiRouter = require('./routers/Api');
|
||||
const indexController = require('./controllers/Web/IndexController');
|
||||
|
||||
/**
|
||||
* Set template engine
|
||||
*/
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', `${__dirname}/views`);
|
||||
|
||||
/**
|
||||
* Serve static public dir
|
||||
*/
|
||||
app.use(express.static(`${__dirname}/../public`));
|
||||
|
||||
/**
|
||||
* Configure app to use bodyParser()
|
||||
*/
|
||||
app.use(bodyParser.urlencoded({extended: true}));
|
||||
app.use(bodyParser.json());
|
||||
|
||||
/**
|
||||
* Configure sessions in app
|
||||
*/
|
||||
app.use(session({secret: config.session.secret, resave: true, saveUninitialized: true}));
|
||||
|
||||
/**
|
||||
* Configure app to use Browser Support
|
||||
*/
|
||||
app.use(browsersupport({
|
||||
redirectUrl: "/oldbrowser",
|
||||
supportedBrowsers: config.application.supportedBrowsers
|
||||
}));
|
||||
|
||||
/**
|
||||
* Configure routers
|
||||
*/
|
||||
app.use('/', webRouter.router);
|
||||
app.use('/api', apiRouter.router);
|
||||
|
||||
/**
|
||||
* Render sitemap.xml and robots.txt
|
||||
*/
|
||||
app.get('/sitemap.xml', (req, res) => {
|
||||
indexController.siteMapAction(req, res, webRouter.routes);
|
||||
});
|
||||
app.get('/robots.txt', (req, res) => {
|
||||
indexController.robotsAction(req, res);
|
||||
});
|
||||
|
||||
/**
|
||||
* Render old browser page
|
||||
*/
|
||||
app.get('/oldbrowser', (req, res) => {
|
||||
indexController.oldBrowserAction(req, res);
|
||||
});
|
||||
|
||||
/**
|
||||
* Setup default 404 message
|
||||
*/
|
||||
app.use((req, res) => {
|
||||
res.status(404);
|
||||
|
||||
// respond with json
|
||||
if (req.originalUrl.split('/')[1] === 'api') {
|
||||
|
||||
/**
|
||||
* API 404 not found
|
||||
*/
|
||||
res.send({error: 'This API route is not implemented yet'});
|
||||
return;
|
||||
}
|
||||
|
||||
indexController.notFoundAction(req, res);
|
||||
});
|
||||
|
||||
/**
|
||||
* Disable powered by header for security reasons
|
||||
*/
|
||||
app.disable('x-powered-by');
|
||||
|
||||
/**
|
||||
* Start listening on port
|
||||
*/
|
||||
const server = app.listen(config.application.port, config.application.bind, () => {
|
||||
console.log(`[NODE] App is running on: ${config.application.bind}:${config.application.port}`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle nodemon shutdown
|
||||
*/
|
||||
process.once('SIGUSR2', () => {
|
||||
server.close(() => {
|
||||
console.log(`[NODE] Express exited! Port ${config.application.port} is now free!`);
|
||||
process.kill(process.pid, 'SIGUSR2');
|
||||
});
|
||||
});
|
||||
1
app/views/general/notfound.ejs
Normal file
1
app/views/general/notfound.ejs
Normal file
@@ -0,0 +1 @@
|
||||
<h2>404 Not Found !</h2>
|
||||
4
app/views/general/oldbrowser.ejs
Normal file
4
app/views/general/oldbrowser.ejs
Normal file
@@ -0,0 +1,4 @@
|
||||
<h2>Old Browser</h2>
|
||||
<p>
|
||||
Please update your browser to make sure our website works properly
|
||||
</p>
|
||||
4
app/views/general/robots.ejs
Normal file
4
app/views/general/robots.ejs
Normal file
@@ -0,0 +1,4 @@
|
||||
User-agent: *
|
||||
Disallow: /fonts/
|
||||
Disallow: /scss/
|
||||
Sitemap: <%= baseUrl %>sitemap.xml
|
||||
17
app/views/general/sitemap.ejs
Normal file
17
app/views/general/sitemap.ejs
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
<url>
|
||||
<loc><%= baseUrl %></loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<% routes.forEach((route) => { %>
|
||||
<% if(route.route !== "/") { %>
|
||||
<url>
|
||||
<loc><%= baseUrl %><%= route.route.substring(1) %></loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.5</priority>
|
||||
</url>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</urlset>
|
||||
15
app/views/index.ejs
Normal file
15
app/views/index.ejs
Normal file
@@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<% include ./partials/head.ejs %>
|
||||
</head>
|
||||
<body>
|
||||
<% include ./partials/header.ejs %>
|
||||
|
||||
<main>
|
||||
<%- include(template) %>
|
||||
</main>
|
||||
|
||||
<% include ./partials/footer.ejs %>
|
||||
</body>
|
||||
</html>
|
||||
1
app/views/index/index.ejs
Normal file
1
app/views/index/index.ejs
Normal file
@@ -0,0 +1 @@
|
||||
Some text here<br/>
|
||||
4
app/views/partials/footer.ejs
Normal file
4
app/views/partials/footer.ejs
Normal file
@@ -0,0 +1,4 @@
|
||||
<footer>
|
||||
<hr/>
|
||||
<p>© Copyright The Awesome People</p>
|
||||
</footer>
|
||||
24
app/views/partials/head.ejs
Normal file
24
app/views/partials/head.ejs
Normal file
@@ -0,0 +1,24 @@
|
||||
<title><%= config.application.name %> <%= config.application.env %> | <%= pageTitle %></title>
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta name="keywords" content="">
|
||||
|
||||
<meta property="og:title" content="<%= config.application.name %>"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:url" content="<%= baseUrl %>"/>
|
||||
<meta property="og:description" content=""/>
|
||||
|
||||
<link rel="alternate" href="<%= baseUrl %>" hreflang="en" />
|
||||
<link rel="canonical" href="<%= baseUrl %>" />
|
||||
<script type='application/ld+json'>
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "<%= config.application.name %>",
|
||||
"url": "<%= baseUrl %>"
|
||||
}
|
||||
</script>
|
||||
4
app/views/partials/header.ejs
Normal file
4
app/views/partials/header.ejs
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>
|
||||
<h2><%= config.application.name %></h2>
|
||||
<hr/>
|
||||
</header>
|
||||
8340
package-lock.json
generated
Normal file
8340
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
85
package.json
Normal file
85
package.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "unifi-voucher-site",
|
||||
"version": "0.0.0",
|
||||
"description": "NPM packages for unifi-voucher-site",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "concurrently --kill-others 'npm run frontend' 'npm run backend:dev'",
|
||||
"backend:start": "node ./app/server.js",
|
||||
"backend:dev": "nodemon -L --watch ./app ./app/server.js",
|
||||
"prebuild": "rimraf ./public/dist",
|
||||
"build": "cross-env NODE_ENV=production webpack --config ./_scripts/webpack.config.js --progress",
|
||||
"prefrontend": "rimraf ./public/dist",
|
||||
"frontend": "cross-env NODE_ENV=development webpack --config ./_scripts/webpack.config.js --watch --watch-poll -d --progress",
|
||||
"lint": "eslint -c ./package.json ./app ./public/js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
"no-console": 0,
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"indent": [
|
||||
"error",
|
||||
4
|
||||
]
|
||||
},
|
||||
"globals": {
|
||||
"site": false,
|
||||
"expressConfig": false
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended"
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
"Chrome >= 52",
|
||||
"Firefox >= 47",
|
||||
"ie 11",
|
||||
"last 2 Edge versions",
|
||||
"Safari >= 8",
|
||||
"ios_saf >= 9",
|
||||
"last 2 and_chr versions"
|
||||
],
|
||||
"author": "Glenn de Haan",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"autoprefixer": "^8.0.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.3",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"body-parser": "^1.17.2",
|
||||
"cross-env": "^5.1.3",
|
||||
"ejs": "^2.5.6",
|
||||
"express": "^4.15.3",
|
||||
"express-browsersupport": "^1.3.3",
|
||||
"express-session": "^1.15.6",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"node-sass": "^4.7.2",
|
||||
"postcss": "^6.0.19",
|
||||
"postcss-loader": "^2.1.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"rimraf": "^2.6.2",
|
||||
"sass-loader": "^6.0.6",
|
||||
"socket.io-client": "^2.0.4",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-manifest-plugin": "^1.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^3.5.1",
|
||||
"eslint": "^4.18.1",
|
||||
"nodemon": "^1.15.1"
|
||||
}
|
||||
}
|
||||
34
public/js/main.js
Normal file
34
public/js/main.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import settings from './settings';
|
||||
|
||||
// Create global
|
||||
window.site = {};
|
||||
site.settings = settings;
|
||||
|
||||
function initialize() {
|
||||
site.html = document.querySelector('html');
|
||||
site.body = document.querySelector('body');
|
||||
|
||||
console.log('JS Start');
|
||||
|
||||
initializeModules();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the modules with all the components
|
||||
*/
|
||||
function initializeModules() {
|
||||
for(let modulesItem = 0; modulesItem < settings.modules.length; modulesItem++) {
|
||||
const module = settings.modules[modulesItem];
|
||||
|
||||
const moduleClass = require(`./modules/${module.group}/${module.module}`);
|
||||
const domElements = document.querySelectorAll(module.el);
|
||||
|
||||
if (typeof moduleClass !== 'undefined' && domElements.length > 0) {
|
||||
new moduleClass({
|
||||
el: domElements
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", initialize);
|
||||
32
public/js/modules/default/Socket.js
Normal file
32
public/js/modules/default/Socket.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import io from 'socket.io-client';
|
||||
|
||||
class Socket {
|
||||
constructor({el}) {
|
||||
this.el = el;
|
||||
this.socket = false;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.socket = io.connect(`http://${expressConfig.hostname}:${expressConfig.port}`);
|
||||
|
||||
this.socket.on('connect', this.connect);
|
||||
this.socket.on('disconnect', this.disconnect);
|
||||
this.socket.on('error', this.error);
|
||||
}
|
||||
|
||||
connect() {
|
||||
console.log('[SOCKET] Connected!');
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
console.log('[SOCKET] Disconnected!');
|
||||
}
|
||||
|
||||
error() {
|
||||
console.log('[SOCKET] Error!');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Socket;
|
||||
7
public/js/modules/index/Index.js
Normal file
7
public/js/modules/index/Index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
class Index {
|
||||
constructor({el}) {
|
||||
this.el = el;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Index;
|
||||
14
public/js/settings.js
Normal file
14
public/js/settings.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
modules: [
|
||||
{
|
||||
module: "Socket",
|
||||
group: "default",
|
||||
el: "main"
|
||||
},
|
||||
{
|
||||
module: "Index",
|
||||
group: "index",
|
||||
el: "#home"
|
||||
}
|
||||
]
|
||||
}
|
||||
33
public/js/utils/Api.js
Normal file
33
public/js/utils/Api.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Function to post data to the API
|
||||
*
|
||||
* @param method
|
||||
* @param data
|
||||
* @param callback
|
||||
* @param auth
|
||||
*/
|
||||
export default (method, data, callback, auth = "") => {
|
||||
const xmlhttp = new XMLHttpRequest();
|
||||
let route = "/api";
|
||||
|
||||
xmlhttp.open("POST", route);
|
||||
xmlhttp.setRequestHeader("Content-Type", "application/json");
|
||||
xmlhttp.setRequestHeader("Bearer", auth);
|
||||
xmlhttp.send(JSON.stringify(data));
|
||||
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
if (xmlhttp.readyState === XMLHttpRequest.DONE) {
|
||||
const responseCode = xmlhttp.status;
|
||||
const response = JSON.parse(xmlhttp.responseText);
|
||||
console.log('response', response);
|
||||
|
||||
console.log('responseCode', responseCode);
|
||||
|
||||
if (responseCode === 200) {
|
||||
callback({error: false});
|
||||
} else {
|
||||
callback({error: true, fields: response.fields, raw_error: response.error});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
public/scss/components/_form.scss
Normal file
5
public/scss/components/_form.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
label.invalid {
|
||||
span {
|
||||
color: #F44336;
|
||||
}
|
||||
}
|
||||
259
public/scss/global/_normalize.scss
Normal file
259
public/scss/global/_normalize.scss
Normal file
@@ -0,0 +1,259 @@
|
||||
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
|
||||
|
||||
/**
|
||||
* 1. Set default font family to sans-serif.
|
||||
* 2. Prevent iOS text size adjust after orientation change, without disabling
|
||||
* user zoom.
|
||||
*/
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default margin.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* HTML5 display definitions
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
||||
* Correct `block` display not defined for `details` or `summary` in IE 10/11
|
||||
* and Firefox.
|
||||
* Correct `block` display not defined for `main` in IE 11.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
main,
|
||||
menu,
|
||||
nav,
|
||||
section,
|
||||
summary {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct `inline-block` display not defined in IE 8/9.
|
||||
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
progress,
|
||||
video {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent modern browsers from displaying `audio` without controls.
|
||||
* Remove excess height in iOS 5 devices.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `[hidden]` styling not present in IE 8/9/10.
|
||||
* Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
|
||||
*/
|
||||
|
||||
[hidden],
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Links
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background color from active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Improve readability when focused and also mouse hovered in all browsers.
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove border when inside `a` element in IE 8/9/10.
|
||||
*/
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct overflow not hidden in IE 9/10/11.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
||||
* styling of `select`, unless a `border` property is set.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 1. Correct color not being inherited.
|
||||
* Known issue: affects color of disabled elements.
|
||||
* 2. Correct font properties not being inherited.
|
||||
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
color: inherit; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
margin: 0; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
button {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||
* All other form control elements do not inherit `text-transform` values.
|
||||
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
||||
* Correct `select` style inheritance in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||
* and `video` controls.
|
||||
* 2. Correct inability to style clickable `input` types in iOS.
|
||||
* 3. Improve usability and consistency of cursor style between image-type
|
||||
* `input` and others.
|
||||
*/
|
||||
|
||||
button,
|
||||
html input[type="button"], /* 1 */
|
||||
input[type="reset"],
|
||||
input[type="submit"] {
|
||||
appearance: none; /* 2 */
|
||||
border: none;
|
||||
cursor: pointer; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-set default cursor for disabled elements.
|
||||
*/
|
||||
|
||||
button[disabled],
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and border in Firefox 4+.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
input::-moz-focus-inner {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||
* the UA stylesheet.
|
||||
*/
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
||||
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome
|
||||
* (include `-moz` to future-proof).
|
||||
*/
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
-moz-box-sizing: content-box;
|
||||
-webkit-box-sizing: content-box; /* 2 */
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
||||
* Safari (but not Chrome) clips the cancel button when the search input has
|
||||
* padding (and `textfield` appearance).
|
||||
*/
|
||||
|
||||
input[type="search"]::-webkit-search-cancel-button,
|
||||
input[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove default vertical scrollbar in IE 8/9/10/11.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* Tables
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove most spacing between table cells.
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
22
public/scss/global/_utils.scss
Normal file
22
public/scss/global/_utils.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
// Visibility
|
||||
.hidden {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessibility: Positioning content offscreen
|
||||
* @see https://www.paciellogroup.com/blog/2012/05/html5-accessibility-chops-hidden-and-aria-hidden/
|
||||
* @see http://alistapart.com/article/now-you-see-me
|
||||
* @see http://accessibilitytips.com/2008/03/04/positioning-content-offscreen/
|
||||
*/
|
||||
.off-screen,
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
// never use the top property!
|
||||
left: -999em;
|
||||
margin: 0; // fix uncontrolled whitespace still being displayed
|
||||
}
|
||||
19
public/scss/style.scss
Normal file
19
public/scss/style.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
//
|
||||
// General
|
||||
//
|
||||
|
||||
// Variables
|
||||
@import "variables/colors";
|
||||
|
||||
// Global
|
||||
@import "global/normalize";
|
||||
@import "global/utils";
|
||||
|
||||
//
|
||||
// Local App
|
||||
//
|
||||
|
||||
// Components
|
||||
@import "components/form";
|
||||
4
public/scss/variables/_colors.scss
Normal file
4
public/scss/variables/_colors.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
// colors
|
||||
$black: #000000;
|
||||
$white: #ffffff;
|
||||
$orange: #ff9800;
|
||||
Reference in New Issue
Block a user