Compare commits

...

16 Commits

Author SHA1 Message Date
Icedream 93a86f8122
Completely restructure all files to use Lerna, Webpack and Babel.
gitea/icedream/vizon-countdown-website/feature%2Fwebpack This commit looks good Details
We're completely moving away from nwb with this commit and fully set up
on developing a backend and a frontend as two separate code bases.
2017-08-29 00:44:56 +02:00
Icedream 3c633d23ea
Rename .eslintrc.yaml to .eslintrc.yml. 2017-08-26 01:58:59 +02:00
Icedream 22587e4e2a
Tell eslint to allow usage of dev dependencies in frontend code. 2017-08-26 01:58:24 +02:00
Icedream 437ebf3e25
Remove unused props from Countdown component. 2017-08-26 01:58:08 +02:00
Icedream 7548ddcbc5
Alias react to preact in static builds. 2017-08-26 01:54:10 +02:00
Icedream fc0b06a1be
Move frontend babel configuration to webpack configuration file. 2017-08-26 01:53:56 +02:00
Icedream ac951aba32
Switch to rimraf for cleanup. 2017-08-26 01:50:45 +02:00
Icedream fb4e3c0e46
Fix webpack html title. 2017-08-26 01:50:32 +02:00
Icedream e9f19fbddf
Do not hide compile errors when webpack is running as middleware. 2017-08-26 01:50:15 +02:00
Icedream d0042f196e
Update ignore files. 2017-08-26 01:47:30 +02:00
Icedream c32db349d9
Only update upcoming date once the current upcoming date has ran out. 2017-08-26 00:43:21 +02:00
Icedream d7a42d03f7
Use toString.padStart instead of toLocaleString for dual digit display. 2017-08-26 00:34:00 +02:00
Icedream 392cda25ea
Upgrade packages. 2017-08-25 23:36:14 +02:00
Icedream 594c0f54a9
Fix babel transpiling. 2017-08-25 23:35:33 +02:00
Icedream c45be9a476
Set up webpack and webpack-dev-server. 2017-08-25 08:28:44 +02:00
Icedream 202c35b517
Start implementing MySQL data fetching.
gitea/icedream/vizon-countdown-website/develop There was a failure building this commit Details
- Create Drawing component to display drawing information.
- Add SQL file to create view to filter for amount of correctly matched numbers.
- Use pooled connections for MySQL connections.
- Implement API endpoint "status", not done yet, needs to query from above mentioned view.
- Add more npm scripts that interact with docker/docker-compose.
- Allow web container to write back to source folder.
2017-08-23 14:59:12 +02:00
86 changed files with 1412 additions and 12162 deletions

15
.babelrc Normal file
View File

@ -0,0 +1,15 @@
{
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
}
}]
],
"plugins": [
"babel-plugin-transform-class-properties",
"babel-plugin-transform-runtime",
"babel-plugin-dynamic-import-node"
]
}

View File

@ -22,10 +22,15 @@ build/Release
# Dependency directory # Dependency directory
# Deployed apps should consider commenting this line out: # Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules **/node_modules/**
!**/node_modules/.gitkeep
# Webpack output # Webpack output
/dist packages/*/dist
packages/*/lib
# Intermediate build files (cache, etc.)
/build
### ###

View File

@ -22,7 +22,12 @@ build/Release
# Dependency directory # Dependency directory
# Deployed apps should consider commenting this line out: # Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules **/node_modules/**
!**/node_modules/.gitkeep
# Webpack output # Webpack output
/dist packages/*/dist
packages/*/lib
# Intermediate build files (cache, etc.)
/build

View File

@ -12,8 +12,19 @@ env:
es6: true es6: true
rules: rules:
no-underscore-dangle: 'off' no-console: off
no-plusplus: no-plusplus:
- error - error
- allowForLoopAfterthoughts: true - allowForLoopAfterthoughts: true
no-underscore-dangle: 'off'
react/no-array-index-key: 0 react/no-array-index-key: 0
overrides:
- files:
- "config/**"
- "**/webpack.config*.js"
- "**/.babelrc*"
rules:
import/no-extraneous-dependencies:
- error
- devDependencies: true

12
.gitignore vendored
View File

@ -20,10 +20,14 @@ coverage
build/Release build/Release
# Dependency directory # Dependency directory
# Deployed apps should consider commenting these line out: # Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules/** **/node_modules/**
!node_modules/.gitkeep !**/node_modules/.gitkeep
# Webpack output # Webpack output
/dist packages/*/dist
packages/*/lib
# Intermediate build files (cache, etc.)
/build

1
.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

View File

@ -1,16 +1,16 @@
sudo: false # sudo: false
#
language: node_js # language: node_js
node_js: # node_js:
- 6 # - 6
#
before_install: # before_install:
- npm install codecov.io coveralls # - npm install codecov.io coveralls
#
after_success: # after_success:
- cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js # - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js # - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
#
branches: # branches:
only: # only:
- master # - master

View File

@ -1,4 +0,0 @@
FROM icedream/caddy
WORKDIR /data
COPY ./dist/ /data

58
Jenkinsfile vendored
View File

@ -1,29 +1,29 @@
node("docker && linux && amd64") { // node("docker && linux && amd64") {
checkout scm // checkout scm
//
docker.image("node:8.2.1").inside { // docker.image("node:8.2.1").inside {
// Install dependencies // // Install dependencies
sh "npm install" // sh "npm install"
//
// Build website with npm // // Build website with npm
sh "npm run build" // sh "npm run build"
archive "dist/**" // archive "dist/**"
} // }
//
// Build docker image to be deployed // // Build docker image to be deployed
def image = docker.build("docker.dreamnetwork.oss:5000/icedream/carl-kittelberger-website:${env.BRANCH_NAME ?: "latest"}", "-f Dockerfile.dist .") // def image = docker.build("docker.dreamnetwork.oss:5000/icedream/carl-kittelberger-website:${env.BRANCH_NAME ?: "latest"}", "-f Dockerfile.dist .")
//
// @NOTE - https://issues.jenkins-ci.org/browse/JENKINS-42152?focusedCommentId=307976&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-307976 // // @NOTE - https://issues.jenkins-ci.org/browse/JENKINS-42152?focusedCommentId=307976&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-307976
image.push() // image.push()
if (env.BRANCH_NAME) { // if (env.BRANCH_NAME) {
switch(env.BRANCH_NAME) { // switch(env.BRANCH_NAME) {
case "master": // case "master":
image.push("latest") // image.push("latest")
break // break
default: // default:
image.push() // image.push()
break // break
} // }
} // }
image.push("${sh(script: "git describe --tags --always", returnStdout: true).trim()}") // image.push("${sh(script: "git describe --tags --always", returnStdout: true).trim()}")
} // }

View File

@ -0,0 +1,133 @@
import autoprefixer from 'autoprefixer';
export default class Environment {
constructor(options) {
this.development = true;
this.production = false;
this.server = false;
this.autoprefixerTargets = [
'> 1%',
'last 4 versions',
'Firefox ESR',
'ios >= 8',
];
this.locales = ['en'];
if (options !== undefined && options !== null) {
this.input(options);
}
}
input(options) {
if (options) {
switch (true) {
case typeof (options) === 'string': // string
this.inputString(options);
break;
case Array.isArray(options): // array
options.forEach((arg) => { this.input(arg); });
break;
default: // object
Object.keys(options).forEach((k) => {
this[k] = options[k] || this[k];
});
break;
}
} else if (process.env.NODE_ENV) {
this.inputString(process.env.NODE_ENV);
}
}
inputString(env) {
switch (env.toLowerCase()) {
case 'development':
this.development = true;
this.production = false;
break;
case 'production':
this.development = false;
this.production = true;
break;
case 'server':
this.server = true;
break;
default:
console.warn('Unknown environment:', env);
break;
}
}
styleLoaders(...preprocessingLoaders) {
const {
production,
autoprefixerTargets,
server,
ExtractTextPlugin, // @HACK
} = this;
if (!ExtractTextPlugin) {
throw new Error('Need a valid ExtractTextPlugin fed into the environment object.');
}
let cssLoaders = [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
sourceMap: true,
modules: true,
localIdentName: production
? '[name]__[local]--[hash:base64:5]'
: '[name]__[local]--[hash:base64:5]',
},
},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
autoprefixer({
browsers: autoprefixerTargets,
grid: false,
}),
],
sourceMap: true,
},
},
].filter(loader => loader !== false);
if (preprocessingLoaders && preprocessingLoaders.length > 0) {
cssLoaders.push(
{
loader: 'resolve-url-loader',
options: {
fail: true,
silent: false,
},
},
...preprocessingLoaders.map(loader => Object.assign({}, loader, {
options: Object.assign({}, loader.options || {}, {
sourceMap: true,
}),
})),
);
}
if (!server) {
const fallback = cssLoaders.shift();
cssLoaders = ExtractTextPlugin.extract({
fallback,
use: cssLoaders,
});
}
return cssLoaders;
}
}

View File

@ -0,0 +1,26 @@
/**
* Plugin for HtmlPlugin which inlines content for an extracted Webpack manifest
* into the HTML in a <script> tag before other emitted asssets are injected by
* HtmlPlugin itself.
*/
export default function injectManifestPlugin() {
this.plugin('compilation', (compilation) => {
compilation.plugin('html-webpack-plugin-before-html-processing', (data, cb) => {
Object.keys(compilation.assets).forEach((key) => {
if (!key.startsWith('manifest.')) { return; }
const { children } = compilation.assets[key];
if (children && children[0]) {
// eslint-disable-next-line no-param-reassign
data.html = data.html.replace(/^(\s*)<\/body>/m, `$1<script>${children[0]._value}</script>\n$1</body>`);
// Remove the manifest from HtmlPlugin's assets to prevent a <script>
// tag being created for it.
const manifestIndex = data.assets.js.indexOf(data.assets.publicPath + key);
data.assets.js.splice(manifestIndex, 1);
// eslint-disable-next-line no-param-reassign
delete data.assets.chunks.manifest;
}
});
cb(null, data);
});
});
}

View File

@ -28,13 +28,16 @@ services:
build: docker/node build: docker/node
command: sh ./docker/web.sh command: sh ./docker/web.sh
volumes: volumes:
- ".:/src:ro" - ".:/src"
- "web_npm_cache:/var/cache/npm" - "web_npm_cache:/var/cache/npm"
- "/src/node_modules" - "/src/node_modules"
- "/src/packages/frontend/node_modules"
- "/src/packages/backend/node_modules"
ports: ports:
- "3000:3000" - "3000:3000"
working_dir: /src working_dir: /src
environment: environment:
NODE_ENV: development
NPM_CONFIG_CACHE: /var/cache/npm NPM_CONFIG_CACHE: /var/cache/npm
PORT: "3000" PORT: "3000"
MYSQL_HOST: db MYSQL_HOST: db

View File

@ -1,4 +1,4 @@
FROM node:7-alpine FROM node:8.2-alpine
RUN \ RUN \
apk add --no-cache \ apk add --no-cache \
@ -11,7 +11,7 @@ RUN \
tar \ tar \
make make
RUN npm install npm@5.x \ # RUN npm install npm@5.x \
&& rm -rf /usr/local/lib/node_modules \ # && rm -rf /usr/local/lib/node_modules \
&& mv node_modules /usr/local/lib \ # && mv node_modules /usr/local/lib \
&& npm install -g npm@5.x # && npm install -g npm@5.x

View File

@ -1,3 +1,3 @@
#!/bin/sh -e #!/bin/sh -e
npm install npm install --unsafe-perm
npm start npm start

View File

@ -1,34 +0,0 @@
const path = require('path');
const express = require('express');
const fs = require('fs');
const bodyParser = require('body-parser');
const frontendDir = path.resolve(__dirname, 'dist');
const app = express();
app.set('port', (process.env.PORT || 3000));
fs.stat(frontendDir, (err) => {
if (!err) {
console.log(`Serving static build from ${frontendDir}`);
console.log('Run `npm run clean` to return to development mode');
app.use('/', express.static(frontendDir));
} else {
console.log('Serving development build with nwb middleware');
console.log('Run `npm run build` to create a production build');
// eslint-disable-next-line import/no-extraneous-dependencies
app.use(require('nwb/express')(express, {
entry: 'src/index.jsx',
reload: true,
}));
}
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(app.get('port'), () => {
console.log(`Server started: http://localhost:${app.get('port')}/`);
});

7
lerna.json Normal file
View File

@ -0,0 +1,7 @@
{
"lerna": "2.1.0",
"packages": [
"packages/*"
],
"version": "0.2.0"
}

2
node_modules/.gitkeep generated vendored
View File

@ -0,0 +1,2 @@
This file is placed here to avoid Docker from setting up a root-owned host
volume directory here.

View File

@ -1,105 +0,0 @@
const { ContextReplacementPlugin } = require('webpack');
module.exports = {
type: 'react-app',
babel: {
loose: false,
presets: [
'babel-preset-env',
],
plugins: [
'babel-plugin-transform-class-properties',
],
},
webpack: {
aliases: {
'moment-timezone': 'moment-timezone/builds/moment-timezone-with-data-2012-2022.js',
},
autoprefixer: '> 1%, last 4 versions, Firefox ESR, ios >= 8',
config(config) {
// Change config as you wish
// Select rules that match stylesheet files
const matchingLoaders = [
'sass-loader',
].map(require.resolve);
const newConfig = Object.assign({}, config);
newConfig.module.rules = config.module.rules.map((rule) => {
let enableSourceMap = false;
if (!rule.use) {
return rule;
}
return Object.assign({}, rule, {
use: rule.use.reduce((result, loaderDescriptor) => {
const resolvedLoader = require.resolve(loaderDescriptor.loader);
// Make sure resolve-url-loader is inserted right before compatible loaders
if (matchingLoaders.indexOf(resolvedLoader) >= 0) {
result.push({
loader: require.resolve('resolve-url-loader'),
options: {
fail: true,
silent: false,
},
});
enableSourceMap = true;
}
const finalLoaderDescriptor = Object.assign({}, loaderDescriptor);
// Avoid PostCSS discarding all source map information
if (enableSourceMap || resolvedLoader === require.resolve('postcss-loader')) {
finalLoaderDescriptor.options = finalLoaderDescriptor.options || {};
finalLoaderDescriptor.options.sourceMap = true;
}
result.push(finalLoaderDescriptor);
return result;
}, []),
});
});
return newConfig;
},
extra: {
plugins: [
new ContextReplacementPlugin(/moment[/\\]locale$/, /en\.js/),
],
resolve: {
extensions: [
'.jsx',
],
},
},
publicPath: '',
rules: {
babel: {
test: /\.jsx?/,
},
'sass-css': {
modules: true,
localIdentName: '[name]__[local]__[hash:base64:5]',
},
},
},
};
// @HACK - workaround for https://github.com/webpack/webpack/issues/1866
function batchresolve(arr) {
return arr.map(require.resolve);
}
module.exports.babel.presets = batchresolve(module.exports.babel.presets);
module.exports.babel.plugins = batchresolve(module.exports.babel.plugins);

11885
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,26 @@
{ {
"name": "vizon-countdown-website", "name": "@vizonweb/root",
"version": "0.2.0", "version": "0.2.0",
"description": "Website for a countdown to the next draw on VIzon",
"private": true, "private": true,
"main": "./index.js",
"scripts": { "scripts": {
"build": "nwb build-react-app ./src --preact", "build": "lerna run build",
"clean": "nwb clean-app", "clean": "lerna run clean",
"docker-compose": "docker-compose -f docker-compose.local.yml", "docker-compose": "docker-compose -f docker-compose.local.yml",
"docker:down:clean": "npm run -s docker:down -- --rmi all -v", "docker:down:clean": "npm run -s docker:down -- --rmi all -v",
"docker:down": "npm run -s docker-compose -- down", "docker:down": "npm run -s docker-compose -- down",
"docker:logs": "npm run -s docker-compose -- logs",
"docker:logs:follow": "npm run -s docker:logs -- -f",
"docker:restart": "npm run -s docker-compose -- restart",
"docker:up:daemon": "npm run -s docker:up -- -d", "docker:up:daemon": "npm run -s docker:up -- -d",
"docker:up": "npm run -s docker-compose -- up --build", "docker:up": "npm run -s docker-compose -- up --build",
"docker": "npm run -s docker:up", "docker": "npm run -s docker:up",
"lerna": "lerna",
"lint": "eslint .", "lint": "eslint .",
"prepare": "npm run -s build", "postinstall": "lerna bootstrap --hoist",
"start": "node .", "start": "cd packages/backend && npm run start",
"test:coverage": "nwb test-react --coverage", "test:coverage": "lerna run test:coverage",
"test:watch": "nwb test-react --server", "test:watch": "lerna run test:watch",
"test": "nwb test-react" "test": "lerna run test"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -31,43 +33,24 @@
"draw", "draw",
"countdown" "countdown"
], ],
"bin": "./index.js",
"files": [
"dist",
"index.js",
"README.md"
],
"author": "Carl Kittelberger <icedream@icedream.pw>", "author": "Carl Kittelberger <icedream@icedream.pw>",
"license": "UNLICENSED", "license": "UNLICENSED",
"devDependencies": { "devDependencies": {
"babel-eslint": "^7.2.3", "autoprefixer": "^7.1.2",
"babel-plugin-transform-class-properties": "^6.24.1", "chalk": "^2.1.0",
"babel-preset-env": "^1.6.0", "eslint": "^4.5.0",
"eslint-config-airbnb": "^15.1.0", "eslint-config-airbnb": "^15.1.0",
"eslint-plugin-babel": "^4.1.2", "eslint-plugin-babel": "^4.1.2",
"eslint-plugin-import": "^2.7.0", "eslint-plugin-import": "^2.7.0",
"eslint-plugin-json": "^1.2.0", "eslint-plugin-json": "^1.2.0",
"eslint-plugin-jsx-a11y": "^5.1.1", "eslint-plugin-jsx-a11y": "5.x",
"eslint-plugin-react": "^7.2.1", "eslint-plugin-react": "^7.3.0",
"eslint": "^4.5.0", "lerna": "^2.1.0",
"file-loader": "^0.11.2", "nodemon": "^1.11.0",
"moment-timezone": "^0.5.13", "rimraf": "^2.6.1",
"normalize-scss": "^7.0.0", "slash": "^1.0.0"
"nwb-sass": "^0.8.1",
"nwb": "^0.18.10",
"preact-compat": "^3.17.0",
"preact": "^8.2.1",
"prop-types": "^15.5.10",
"react-dom": "^15.6.1",
"react-fontawesome": "^1.6.1",
"react-helmet": "^5.1.3",
"react-router": "^4.1.2",
"react": "^15.6.1",
"resolve-url-loader": "^2.1.0",
"webfontloader": "^1.6.28"
}, },
"dependencies": { "dependencies": {
"body-parser": "^1.17.2", "moment-timezone": "^0.5.13"
"express": "^4.15.4"
} }
} }

34
packages/backend/.babelrc Normal file
View File

@ -0,0 +1,34 @@
{
"env": {
"es6": {
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"spec": true,
"debug": false,
"modules": false
}]
]
},
"node": {
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"spec": true,
"debug": false,
"modules": "commonjs"
}]
]
}
},
"plugins": [
"babel-plugin-transform-class-properties",
"babel-plugin-dynamic-import-node"
]
}

1
packages/backend/.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=true

View File

@ -0,0 +1,86 @@
{
"name": "@vizonweb/backend",
"version": "0.2.0",
"description": "VIzon lottery website",
"private": true,
"main": "./dist/node/index.js",
"jsnext:main": "./dist/es6/index.js",
"module": "./dist/es6/index.js",
"bin": {
"vizonweb": "./dist/node/index.js"
},
"scripts": {
"build:es6": "bnr build:es6",
"build:node": "bnr build:node",
"build": "run-p build:*",
"clean": "rimraf build dist",
"start": "bnr start",
"lint": "eslint .",
"prepublishOnly": "npm run -s build"
},
"betterScripts": {
"start": {
"command": "babel-node ./src",
"env": {
"BABEL_ENV": "node"
}
},
"build:es6": {
"command": "babel --out-dir dist/es6 src/",
"env": {
"BABEL_ENV": "es6"
}
},
"build:node": {
"command": "babel --out-dir dist/node src/",
"env": {
"BABEL_ENV": "node"
}
}
},
"repository": {
"type": "git",
"url": "ssh://git@git.icedream.tech:2222/icedream/vizon-countdown-website.git"
},
"keywords": [
"website",
"vizon",
"rizon",
"draw",
"countdown",
"backend"
],
"files": [
"dist",
"README.md"
],
"author": "Carl Kittelberger <icedream@icedream.pw>",
"license": "UNLICENSED",
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
"better-npm-run": "^0.1.0",
"chalk": "^2.1.0",
"debug": "^3.0.1",
"eslint": "^4.5.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-jsx-a11y": "5.x",
"nodemon": "^1.11.0",
"npm-run-all": "^4.1.0",
"rimraf": "^2.6.1",
"slash": "^1.0.0"
},
"dependencies": {
"body-parser": "^1.17.2",
"express": "^4.15.4",
"moment-timezone": "^0.5.13",
"mysql": "^2.14.1",
"@vizonweb/frontend": "0.2.0"
}
}

View File

View File

@ -0,0 +1,127 @@
import path from 'path';
import mysql from 'mysql';
import express from 'express';
import moment from 'moment-timezone';
// const fs = require('fs');
import frontend from '@vizonweb/frontend';
import bodyParser from 'body-parser';
/* Database setup */
const dbPool = mysql.createPool({
host: process.env.MYSQL_HOST || 'localhost',
user: process.env.MYSQL_USER || 'root',
password: process.env.MYSQL_PASSWORD || undefined,
database: process.env.MYSQL_DATABASE || 'vizon',
socketPath: process.env.MYSQL_SOCKET_PATH || undefined,
timezone: process.env.TZ || 'local',
debug: process.env.NODE_ENV === 'development',
});
const tableNames = {
drawings: process.env.MYSQL_TABLE_DRAWINGS || 'vizon_drawings',
users: process.env.MYSQL_TABLE_USERS || 'vizon_users',
rankings: 'vizon_web_rankings',
};
/* Frontend server setup */
const app = express();
app.set('port', (process.env.PORT || 3000));
app.use('/', frontend());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Additional middleware which will set headers that we need on each request.
app.use((req, res, next) => {
// Disable caching so we'll always get the latest comments.
res.setHeader('Cache-Control', 'no-cache');
next();
});
/*
function calculateRankings(id) {
db.query('SELECT * FROM `vizon_web_rankings` WHERE `vizon_drawings_id`=?',
[id],
(err, results, fields) => {
if (err) {
console.error('Failed to query rankings from database:', err);
res.status(500).json({
error: 'Database query failed',
});
}
});
}
*/
app.post('/api/result/:id', (req, res) => {
res.status(501).json({});
});
app.get('/api/result/:id', (req, res) => {
res.status(501).json({});
});
app.get('/api/status', (req, res) => {
res.status(501).json({});
// dbPool.getConnection((err, db) => {
// if (err) {
// console.error('Failed to connect to database:', err);
// res.status(500).json({
// error: 'Database connection failed',
// });
// }
//
// db.query('SELECT * FROM ?? ORDER BY ?? DESC LIMIT 0, 1', [
// tableNames.drawings,
// 'id',
// ], (qerr, results, fields) => {
// if (qerr) {
// console.error('Failed to request drawings:', err);
// res.status(500).json({
// error: 'Database query failed',
// });
// return;
// }
//
// const row = results[0];
//
// const id = row.id;
//
// const date = moment(row.drawing_date);
//
// const numbers = [
// row.first,
// row.second,
// row.third,
// row.fourth,
// row.fifth,
// row.sixth,
// ].map(i => parseInt(i, 10));
//
// const ranking = [
// // @TODO - calculate ranking from mysql tables {drawings} and {bets}
// ];
//
// res.json({
// lastDrawing: {
// id,
// date,
// numbers,
// ranking,
// },
// });
// });
// });
});
app.listen(app.get('port'), () => {
console.log(`Server started: http://localhost:${app.get('port')}/`);
});

View File

@ -0,0 +1,16 @@
{
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"debug": true
}]
],
"plugins": [
"babel-plugin-transform-class-properties",
"babel-plugin-transform-runtime",
"babel-plugin-dynamic-import-node"
]
}

View File

@ -0,0 +1,8 @@
overrides:
- files:
- src/website/**/*.js
rules:
no-console: error
import/no-extraneous-dependencies:
- error
- devDependencies: true

1
packages/frontend/.npmrc Normal file
View File

@ -0,0 +1 @@
package-lock=false

2
packages/frontend/node_modules/.gitkeep generated vendored Normal file
View File

@ -0,0 +1,2 @@
This file is placed here to avoid Docker from setting up a root-owned host
volume directory here.

View File

@ -0,0 +1,127 @@
{
"name": "@vizonweb/frontend",
"version": "0.2.0",
"description": "VIzon lottery website",
"private": true,
"browser": "./browser/index.js",
"main": "./lib/middleware.node/index.js",
"jsnext:main": "./lib/middleware.es6/index.js",
"module": "./lib/middleware.es6/index.js",
"scripts": {
"build:website:development": "npm run -s build:website -- --env development",
"build:website:production": "npm run -s build:website -- --env production",
"build:website:watch": "run-p build:website -- --watch",
"build:website": "webpack",
"build:middleware:es6": "bnr build:middleware:es6",
"build:middleware:node": "bnr build:middleware:node",
"build:middleware": "run-p build:middleware:*",
"build:production": "bnr build:production",
"build": "run-p build:website build:middleware",
"clean": "rimraf build lib",
"lint": "eslint .",
"prepublish": "npm run -s build:middleware",
"prepublishOnly": "npm run -s build:production"
},
"betterScripts": {
"build:middleware:es6": {
"command": "babel --out-dir lib/middleware.es6 src/middleware",
"env": {
"BABEL_ENV": "es6"
}
},
"build:middleware:node": {
"command": "babel --out-dir lib/middleware.node src/middleware",
"env": {
"BABEL_ENV": "node"
}
},
"build:production": {
"command": "npm run -s build",
"env": {
"NODE_ENV": "production"
}
}
},
"repository": {
"type": "git",
"url": "ssh://git@git.icedream.tech:2222/icedream/vizon-countdown-website.git"
},
"keywords": [
"website",
"vizon",
"rizon",
"draw",
"countdown",
"frontend"
],
"files": [
"lib",
"webpack.config.babel.js",
"README.md"
],
"author": "Carl Kittelberger <icedream@icedream.pw>",
"license": "UNLICENSED",
"devDependencies": {
"autoprefixer": "^7.1.2",
"babel-cli": "^6.26.0",
"babel-eslint": "^7.2.3",
"babel-loader": "^7.1.2",
"babel-plugin-dynamic-import-node": "^1.0.2",
"babel-plugin-dynamic-import-webpack": "^1.0.1",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-react-constant-elements": "^6.23.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1",
"better-npm-run": "^0.1.0",
"case-sensitive-paths-webpack-plugin": "^2.1.1",
"chalk": "^2.1.0",
"css-loader": "^0.28.5",
"debug": "^3.0.1",
"eslint": "^4.5.0",
"eslint-config-airbnb": "^15.1.0",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-jsx-a11y": "5.x",
"eslint-plugin-react": "^7.3.0",
"eventsource-polyfill": "^0.9.6",
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^0.11.2",
"html-webpack-plugin": "^2.30.1",
"moment-timezone": "^0.5.13",
"node-sass": "^4.5.3",
"nodemon": "^1.11.0",
"normalize-scss": "^7.0.0",
"npm-run-all": "^4.1.0",
"postcss-loader": "^2.0.6",
"preact": "^8.2.4",
"preact-compat": "^3.17.0",
"progress-bar-webpack-plugin": "^1.10.0",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-fontawesome": "^1.6.1",
"react-helmet": "^5.1.3",
"react-hot-loader": "^3.0.0-beta.7",
"react-router": "^4.2.0",
"resolve-url-loader": "^2.1.0",
"rimraf": "^2.6.1",
"sass-loader": "^6.0.6",
"slash": "^1.0.0",
"style-loader": "^0.18.2",
"url-loader": "^0.5.9",
"webfontloader": "^1.6.28",
"webpack": "^3.5.5",
"webpack-dev-middleware": "^1.12.0",
"webpack-hot-middleware": "^2.18.2",
"webpack-merge": "^4.1.0"
},
"dependencies": {
"body-parser": "^1.17.2"
},
"peerDependencies": {
"express": "^4.0.0"
}
}

View File

@ -0,0 +1,34 @@
{
"env": {
"es6": {
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"spec": true,
"debug": false,
"modules": false
}]
]
},
"node": {
"presets": [
["babel-preset-env", {
"targets": {
"node": true,
"uglify": false
},
"spec": true,
"debug": false,
"modules": "commonjs"
}]
]
}
},
"plugins": [
"babel-plugin-transform-class-properties",
"babel-plugin-dynamic-import-node"
]
}

View File

@ -0,0 +1,97 @@
/*
* Implements the express middleware to serve the frontend.
*
* - Should use webpack-dev-server middleware with hot reloading if running in
* development mode.
* - Should just serve the files from the htdocs folder if running in production
* mode.
*/
import { resolve } from 'path';
import { Router, static as staticMiddleware } from 'express';
const websitePath = resolve(__dirname, '..', 'website');
export default function frontendRouter() {
const app = new Router();
switch ((process.env.NODE_ENV || 'production').toString().toLowerCase()) {
case 'development':
console.log('Serving development build with webpack dev middleware');
console.log('Loading development packages...');
Promise.all([
import('babel-register'),
import('babel-polyfill'),
import('webpack'),
import('webpack-dev-middleware'),
import('webpack-hot-middleware'),
import('util'),
import('body-parser'),
])
.then((loadedModules) => {
const babelRegister = loadedModules[0];
// const babelPolyfill = loadedModules[1];
const webpack = loadedModules[2];
const webpackDevMiddleware = loadedModules[3];
const webpackHotMiddleware = loadedModules[4];
const { inspect } = loadedModules[5];
const bodyParser = loadedModules[6];
console.log('Setting up babel-register...');
babelRegister({
babelrc: true,
extensions: ['.es6', '.es', '.babel.js', '.jsx', '.js'],
});
console.log('Loading webpack configuration...');
return import('../../webpack.config.babel')
.then((generateWebpackConfig) => {
let webpackConfig = generateWebpackConfig;
if (webpackConfig.default) {
// ES6 module imported in CJS, default export.
webpackConfig = webpackConfig.default;
}
if (typeof (webpackConfig) === 'function') {
webpackConfig = webpackConfig([
process.env.NODE_ENV,
{
server: true,
},
].filter(v => !!v));
}
console.log('Webpack configuration loaded.');
const compiler = webpack(webpackConfig);
app.use(webpackDevMiddleware(compiler, webpackConfig.devServer));
app.use(webpackHotMiddleware(compiler, {
log: false,
}));
app.use(bodyParser.json());
app.get('/webpackConfig', (req, res) => {
res.setHeader('content-type', 'text/plain');
res.send(inspect(webpackConfig, { depth: 7, colors: false }));
});
});
})
.catch((err) => {
console.error(err);
console.warn('The frontend router will not work due to the error above.');
});
break;
default: // production mode
console.log(`Serving static build from ${websitePath}`);
app.use('/', staticMiddleware(websitePath));
break;
}
return app;
}

View File

@ -0,0 +1,22 @@
{
"presets": [
["babel-preset-env", {
"targets": {
"browsers": ">1%",
"uglify": false
},
"modules": false
}],
"babel-preset-react"
],
"plugins": [
"babel-plugin-transform-react-constant-elements",
"babel-plugin-transform-class-properties",
["babel-plugin-transform-runtime", {
"helpers": false,
"polyfill": false,
"regenerator": true
}],
"babel-plugin-syntax-dynamic-import"
]
}

View File

@ -4,6 +4,7 @@ import moment from 'moment-timezone';
import 'react-fontawesome'; import 'react-fontawesome';
import WebFont from 'webfontloader'; import WebFont from 'webfontloader';
import Countdown from './Countdown'; import Countdown from './Countdown';
import Drawing from './Drawing';
import Header from './Header'; import Header from './Header';
import Footer from './Footer'; import Footer from './Footer';
import getUpcomingDate from './getUpcomingDate'; import getUpcomingDate from './getUpcomingDate';
@ -44,6 +45,7 @@ class App extends React.Component {
componentWillUnmount() { componentWillUnmount() {
clearInterval(this.interval); clearInterval(this.interval);
this.interval = null;
} }
calculateUpcomingDate() { calculateUpcomingDate() {
@ -55,7 +57,19 @@ class App extends React.Component {
} }
updateUpcomingDate() { updateUpcomingDate() {
this.setState({ nextUpcomingDate: this.calculateUpcomingDate() }); const { getNow } = this.props;
const nextUpcomingDate = this.calculateUpcomingDate();
if (this.interval) {
const interval = moment.duration(nextUpcomingDate.diff(getNow()))
.asMilliseconds();
clearInterval(this.interval);
this.interval = setInterval(this.updateUpcomingDate.bind(this), interval);
}
this.setState({ nextUpcomingDate });
} }
render() { render() {
@ -79,6 +93,19 @@ class App extends React.Component {
The next VIzon draw is on {nextUpcomingDate.format('dddd')}, {nextUpcomingDate.format('L LT z')}. The next VIzon draw is on {nextUpcomingDate.format('dddd')}, {nextUpcomingDate.format('L LT z')}.
</p> </p>
<Countdown date={nextUpcomingDate} /> <Countdown date={nextUpcomingDate} />
<p>
Last Drawing:
</p>
<Drawing
numbers={[1, 2, 3, 4, 5, 6]}
ranking={[
{ rank: '1st', winners: 0 },
{ rank: '2nd', winners: 0 },
{ rank: '3rd', winners: 0 },
{ rank: 'Consolation', winners: 4 },
]}
/>
</div> </div>
<Footer> <Footer>

View File

@ -9,9 +9,6 @@ import style from './Countdown.sass';
export default class Countdown extends React.Component { export default class Countdown extends React.Component {
static propTypes = { static propTypes = {
date: PropTypes.instanceOf(moment).isRequired, date: PropTypes.instanceOf(moment).isRequired,
locale: PropTypes.string,
minimumIntegerDigits: PropTypes.number,
useGrouping: PropTypes.bool,
getNow: PropTypes.func, getNow: PropTypes.func,
}; };
@ -57,39 +54,26 @@ export default class Countdown extends React.Component {
const size = 160; // @HACK const size = 160; // @HACK
const { locale, minimumIntegerDigits, useGrouping } = this.props;
return ( return (
<div className={style.countdown}> <div className={style.countdown}>
<ProgressCircle size={size} max={3} progress={days} stroke="rgb(76,114,163)"> <ProgressCircle size={size} max={3} progress={days} stroke="rgb(76,114,163)">
<DigitBlock suffix="d"> <DigitBlock suffix="d">
{Math.floor(days).toLocaleString(locale, { {Math.floor(days).toString().padStart(2, '0')}
minimumIntegerDigits,
useGrouping,
})}
</DigitBlock> </DigitBlock>
</ProgressCircle> </ProgressCircle>
<ProgressCircle size={size} max={24} progress={hours} stroke="rgb(60, 144, 60)"> <ProgressCircle size={size} max={24} progress={hours} stroke="rgb(60, 144, 60)">
<DigitBlock suffix="h"> <DigitBlock suffix="h">
{Math.floor(hours).toLocaleString(locale, { {Math.floor(hours).toString().padStart(2, '0')}
minimumIntegerDigits,
useGrouping,
})}
</DigitBlock> </DigitBlock>
</ProgressCircle> </ProgressCircle>
<ProgressCircle size={size} max={60} progress={minutes} stroke="rgb(160, 154, 40)"> <ProgressCircle size={size} max={60} progress={minutes} stroke="rgb(160, 154, 40)">
<DigitBlock suffix="m"> <DigitBlock suffix="m">
{Math.floor(minutes).toLocaleString(locale, { {Math.floor(minutes).toString().padStart(2, '0')}
minimumIntegerDigits,
useGrouping,
})}
</DigitBlock> </DigitBlock>
</ProgressCircle> </ProgressCircle>
<ProgressCircle size={size} max={60} progress={seconds} stroke="rgb(193,63,88)"> <ProgressCircle size={size} max={60} progress={seconds} stroke="rgb(193,63,88)">
<DigitBlock suffix="s"> <DigitBlock suffix="s">
{Math.floor(seconds).toLocaleString(locale, { {Math.floor(seconds).toString().padStart(2, '0')}
minimumIntegerDigits,
useGrouping,
})}
</DigitBlock> </DigitBlock>
</ProgressCircle> </ProgressCircle>
</div> </div>

View File

@ -0,0 +1,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import style from './Drawing.sass';
const Numbers = ({ numbers }) => (
<div className={style.numbers}>
{
numbers.map((number, index) => (
<div key={index} className={style.number}>
{number}
</div>
))
}
</div>
);
Numbers.propTypes = {
numbers: PropTypes.arrayOf(PropTypes.number).isRequired,
};
export default class Drawing extends React.Component {
static propTypes = {
ranking: PropTypes.arrayOf(PropTypes.shape({
rank: PropTypes.string,
winners: PropTypes.int,
})).isRequired,
numbers: PropTypes.arrayOf(PropTypes.number).isRequired,
}
static defaultProps = {
}
render() {
const { ranking, numbers } = this.props;
return (
<div className={style.drawing}>
<Numbers numbers={numbers} />
<div className={style.ranking}>
{
ranking.map(({ rank, winners }) => (
<div key={rank} className={style.rank}>
<div className={style.rankName}>{rank}</div>
<div className={style.winners}>{winners > 0 ? winners : 'No'} winners</div>
</div>
))
}
</div>
</div>
);
}
}

View File

@ -0,0 +1,32 @@
.drawing
.numbers
display: flex
flex-direction: row
width: 100%
.number
flex: 1 0 auto
text-align: center
font-size: 2.5em
font-weight: bold
.ranking
display: table
width: 100%
.rank
display: table-row
.rankName, .winners
display: table-cell
.rankName
text-align: right
width: 50%
padding-right: .25em
.winners
text-align: left
width: 50%
padding-left: .25em

View File

@ -16,7 +16,6 @@ Footer.propTypes = {
className: PropTypes.string, className: PropTypes.string,
}; };
Footer.defaultProps = { Footer.defaultProps = {
children: null, children: null,
className: null, className: null,

View File

@ -0,0 +1,21 @@
$debug: false
@if ($debug)
*
background-color: rgba(255, 0, 255, 0.1)
border: red 1px solid
box-sizing: border-box
position: relative
&:after
content: attr(class)
z-index: 99999
box-shadow: 0 0 10px black
position: absolute
left: 0
top: 0
background: white
border: black 1px solid
padding: 4px
font-size: 12px
font-weight: bold
font-family: monospace

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -0,0 +1,23 @@
import React from 'react';
import { render } from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import './index.sass';
import App from './App';
// Create div node to render app in
const rootContainer = document.createElement('div');
document.body.appendChild(rootContainer);
const runRender = (Component) => {
console.log('Rendering app...');
render(<AppContainer>
<Component />
</AppContainer>, rootContainer);
};
runRender(App);
if (module.hot) {
module.hot.accept('./App', () => { runRender(App); });
}

View File

@ -1,5 +1,6 @@
@import ~normalize-scss/sass/normalize @import ~normalize-scss/sass/normalize
@import fonts/colaborate @import fonts/colaborate
@import debug
body body
font-family: sans-serif font-family: sans-serif

View File

@ -0,0 +1,309 @@
import {
DefinePlugin,
NamedModulesPlugin,
HashedModuleIdsPlugin,
LoaderOptionsPlugin,
ContextReplacementPlugin,
HotModuleReplacementPlugin,
NoEmitOnErrorsPlugin,
optimize,
} from 'webpack';
import path from 'path';
import { isatty } from 'tty';
import chalk from 'chalk';
import _debug from 'debug';
import slash from 'slash';
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';
import ExtractTextPlugin from 'extract-text-webpack-plugin';
import HtmlPlugin from 'html-webpack-plugin';
import ProgressBarPlugin from 'progress-bar-webpack-plugin';
import Environment from '../../config/webpack/environment';
import injectManifestPlugin from '../../config/webpack/injectManifestPlugin';
const debug = _debug('webpack:config');
debug.generated = _debug('webpack:config:generated');
debug.generated('filename:', __filename);
debug.generated('dirname:', __dirname);
const {
CommonsChunkPlugin,
UglifyJsPlugin,
ModuleConcatenationPlugin,
} = optimize;
const websiteSubfolder = 'website';
export default (options) => {
const environment = new Environment({
ExtractTextPlugin, // @HACK
});
environment.input(options);
debug.generated(environment);
const {
development,
production,
server,
locales,
autoprefixerTargets,
} = environment;
const baseOutputFilename = development
? 'assets/dev/[name].dev.[ext]'
// Always use a hash (in production) to prevent files with the same name causing issues
: 'assets/prod/[chunkhash:2]/[name].[chunkhash:8].[ext]';
const webpackChunkFilename = baseOutputFilename
.replace(/\[ext(.*?)\]/g, 'js');
const webpackOutputFilename = webpackChunkFilename;
const assetOutputFilename = baseOutputFilename
.replace(/\[chunkhash(.*?)\]/g, '[hash$1]');
const cssOutputFileName = baseOutputFilename
.replace(/\[ext(.*?)\]/g, 'css')
.replace(/\[chunkhash(.*?)\]/g, '[contenthash$1]');
const cssOutputRebasePath = `${slash(path.relative(path.dirname(cssOutputFileName), ''))}/`;
// Default options for url-loader
const urlLoaderOptions = {
limit: 1, // Don't inline anything (but empty files) by default
name: assetOutputFilename,
publicPath: cssOutputRebasePath,
};
const config = {
name: 'frontend',
target: 'web',
devServer: {
// inline: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
historyApiFallback: true,
hot: true,
noInfo: true,
overlay: true,
publicPath: '',
quiet: false,
watchOptions: {
ignored: /node_modules/,
},
},
module: {
rules: [
{
test: /\.jsx?/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
// Look for babel configuration in project directory
babelrc: false,
// Cache transformations to the filesystem (in default temp dir)
cacheDirectory: true,
presets: [
['babel-preset-env', {
targets: {
browsers: autoprefixerTargets,
uglify: false,
},
// spec: true,
// debug: development,
modules: false, // do not transpile modules, webpack 2+ does that
}],
'babel-preset-react',
],
plugins: [
'babel-plugin-transform-react-constant-elements',
'babel-plugin-transform-class-properties',
['babel-plugin-transform-runtime', {
helpers: false,
polyfill: false,
regenerator: true,
}],
'babel-plugin-dynamic-import-webpack',
],
},
},
...[
/\.(gif|png|webp)$/i, // graphics
/\.svg$/i, // svg
/\.jpe?g$/i, // jpeg
/\.(mp4|ogg|webm)$/i, // video
/\.(eot|otf|ttf|woff|woff2)$/i, // fonts
/\.(wav|mp3|m4a|aac|oga)$/i, // audio
].map(test => ({
test,
loader: 'url-loader',
options: urlLoaderOptions,
})),
{
test: /\.css$/,
use: environment.styleLoaders(),
},
{
test: /\.s[ac]ss$/,
use: environment.styleLoaders(
{
loader: 'sass-loader',
},
),
},
],
strictExportPresence: true,
},
output: {
filename: webpackOutputFilename,
chunkFilename: webpackChunkFilename,
path: path.join(__dirname, 'lib', websiteSubfolder),
publicPath: '',
},
plugins: [
// Show progress as a bar during build
isatty(1) && new ProgressBarPlugin({
complete: chalk.white('\u2588'),
incomplete: chalk.grey('\u2591'),
format: `:bar ${chalk.cyan.bold(':percent')} Webpack build: ${chalk.grey(':msg')}`,
}),
// Enforce case-sensitive import paths
new CaseSensitivePathsPlugin(),
// Replace specified expressions with values
new DefinePlugin({
'process.env.__DEV__': JSON.stringify(development),
'process.env.__PROD__': JSON.stringify(production),
'process.env.__SERVER__': JSON.stringify(server),
'process.env.NODE_ENV': JSON.stringify(production ? 'production' : 'development'),
}),
// Dev server build
...[
// Hot module reloading
new HotModuleReplacementPlugin(),
new NoEmitOnErrorsPlugin(),
// Use paths as names when serving
new NamedModulesPlugin(),
].filter(() => server),
// If we're not serving, we're creating a static build
...[// Extract imported stylesheets out into .css files
new ExtractTextPlugin({
allChunks: true,
filename: cssOutputFileName,
}),
].filter(() => !server),
// Move modules imported from node_modules/ into a vendor chunk when enabled
new CommonsChunkPlugin({
name: 'vendor',
minChunks(module) {
return (module.resource && module.resource.includes('node_modules'));
},
}),
// If we're generating an HTML file, we must be building a web app, so
// configure deterministic hashing for long-term caching.
// Generate stable module ids instead of having Webpack assign integers.
// NamedModulesPlugin allows for easier debugging and
// HashedModuleIdsPlugin does this without adding too much to bundle
// size.
development
? new NamedModulesPlugin()
: new HashedModuleIdsPlugin(),
// The Webpack manifest is normally folded into the last chunk, changing
// its hash - prevent this by extracting the manifest into its own
// chunk - also essential for deterministic hashing.
new CommonsChunkPlugin({ name: 'manifest' }),
// Inject the Webpack manifest into the generated HTML as a <script>
injectManifestPlugin,
// Production builds
...[
// JavaScript minification
new LoaderOptionsPlugin({ debug: false, minimize: true }),
new UglifyJsPlugin({
compress: {
warnings: false,
},
output: {
comments: false,
},
sourceMap: true,
}),
// Hoisting
new ModuleConcatenationPlugin(),
].filter(() => production),
new HtmlPlugin({
template: path.join(__dirname, 'src', websiteSubfolder, 'index.html'),
filename: 'index.html',
hash: false,
inject: true,
compile: true,
favicon: false,
minify: false,
cache: true,
showErrors: true,
chunks: 'all',
excludeChunks: [],
title: 'VIzon Countdown',
xhtml: false,
chunksSortMode: 'dependency',
}),
// Only include selected locales for moment.js
new ContextReplacementPlugin(/moment[/\\]locale$/, new RegExp(`^\\.\\/(${locales.join('|')})$`)),
].filter(plugin => plugin !== false),
resolve: {
extensions: [
'.js', '.json', '.jsx',
],
alias: {
'moment-timezone': 'moment-timezone/builds/moment-timezone-with-data-2012-2022.js',
},
},
resolveLoader: {
modules: ['node_modules', 'nwb'],
},
devtool: server ? 'cheap-module-source-map' : 'source-map',
entry: {
app: [
...[
'eventsource-polyfill',
'react-hot-loader/patch',
'webpack-hot-middleware/client',
].filter(() => server).map(require.resolve),
path.join(__dirname, 'src', websiteSubfolder),
],
},
};
if (!server) {
// Use preact instead of react (only on static build)
Object.assign(config.resolve.alias, {
react: !server ? 'preact-compat\\dist\\preact-compat' : undefined,
'react-dom': !server ? 'preact-compat\\dist\\preact-compat' : undefined,
'create-react-class': !server ? 'preact-compat/lib/create-react-class' : undefined,
});
}
debug.generated(config);
return config;
};

View File

@ -0,0 +1,91 @@
CREATE OR REPLACE VIEW `vizon_web_rankings` AS
SELECT
bets.id,
vizon_drawings_id,
COUNT(*) AS richtige
FROM
(
SELECT
vizon_users_id AS id,
vizon_drawings_id,
FIRST AS NUMBER
FROM
vizon_bets
UNION ALL
SELECT
vizon_users_id,
vizon_drawings_id,
SECOND
FROM
vizon_bets
UNION ALL
SELECT
vizon_users_id,
vizon_drawings_id,
third
FROM
vizon_bets
UNION ALL
SELECT
vizon_users_id,
vizon_drawings_id,
fourth
FROM
vizon_bets
UNION ALL
SELECT
vizon_users_id,
vizon_drawings_id,
fifth
FROM
vizon_bets
UNION ALL
SELECT
vizon_users_id,
vizon_drawings_id,
sixth
FROM
vizon_bets
) bets,
(
SELECT
id,
FIRST AS NUMBER
FROM
vizon_drawings
UNION ALL
SELECT
id,
SECOND
FROM
vizon_drawings
UNION ALL
SELECT
id,
third
FROM
vizon_drawings
UNION ALL
SELECT
id,
fourth
FROM
vizon_drawings
UNION ALL
SELECT
id,
fifth
FROM
vizon_drawings
UNION ALL
SELECT
id,
sixth
FROM
vizon_drawings
) drawings
WHERE
bets.vizon_drawings_id = drawings.id AND bets.number = drawings.number
GROUP BY
bets.id,
vizon_drawings_id

View File

@ -1,11 +0,0 @@
import React from 'react';
import { render } from 'react-dom';
import './index.sass';
import App from './App';
// Create div node to render app in
const rootContainer = document.createElement('div');
document.body.appendChild(rootContainer);
render(<App />, rootContainer);