=> {
13 | const method = getMethod.method as Method;
14 | const headers = jsonHeader;
15 | const url = `${getLocationOrigin()}/${endpoint}`;
16 | const options = { ...defaultOptions };
17 |
18 | return axios
19 | .request({
20 | method,
21 | url,
22 | withCredentials: true,
23 | ...headers,
24 | ...options,
25 | })
26 | .then(data => data)
27 | .catch(error => Promise.reject(error));
28 | };
29 |
--------------------------------------------------------------------------------
/front/src/services/API/fetchTools.ts:
--------------------------------------------------------------------------------
1 | import { Base64 } from 'js-base64';
2 |
3 | // #region window.location.origin polyfill
4 | export const getLocationOrigin = (): string => {
5 | if (!window.location.origin) {
6 | // @ts-ignores
7 | window.location.origin = `${window.location.protocol}//${
8 | window.location.hostname
9 | }${window.location.port ? ':' + window.location.port : ''}`;
10 | }
11 | return window.location.origin;
12 | };
13 | // #endregion
14 |
15 | // #region query options:
16 | export const getMethod = {
17 | method: 'get',
18 | };
19 |
20 | export const postMethod = {
21 | method: 'post',
22 | };
23 |
24 | export const defaultOptions = {
25 | credentials: 'same-origin',
26 | };
27 |
28 | export const jsonHeader = {
29 | headers: {
30 | Accept: 'application/json',
31 | 'Content-Type': 'application/json',
32 | // 'Access-control-Allow-Origin': '*'
33 | },
34 | };
35 | // #endregion
36 |
37 | // #region general helpers
38 | export const encodeBase64 = (stringToEncode: string = ''): string => {
39 | return Base64.encode(stringToEncode);
40 | };
41 | // #endregion
42 |
--------------------------------------------------------------------------------
/front/src/services/auth/__tests__/auth.test.ts:
--------------------------------------------------------------------------------
1 | import auth from '../index';
2 |
3 |
4 | describe('auth service', () => {
5 | it('localStorage should exist', () => {
6 | expect(localStorage).toBeDefined();
7 | });
8 |
9 | it('should set token to localStorage (default store)', () => {
10 | const value = 'something to save';
11 | const tokenKey = 'test';
12 |
13 | // set token
14 | auth.setToken(value, 'localStorage', tokenKey);
15 | expect(localStorage.setItem).toHaveBeenLastCalledWith(tokenKey, value);
16 | });
17 |
18 | it('should get token from localStorage (default store)', () => {
19 | const tokenKey = 'test';
20 |
21 | // set token
22 | auth.getToken('localStorage', tokenKey);
23 | expect(localStorage.getItem).toHaveBeenLastCalledWith(tokenKey);
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/front/src/services/getLocationOrigin/getLocationOrigin.ts:
--------------------------------------------------------------------------------
1 | export const getLocationOrigin = (): string => {
2 | if (!window.location.origin) {
3 | // @ts-ignore
4 | window.location.origin = `${window.location.protocol}//${
5 | window.location.hostname
6 | }${window.location.port ? ':' + window.location.port : ''}`;
7 | }
8 | return window.location.origin;
9 | };
10 |
11 | export default getLocationOrigin;
12 |
--------------------------------------------------------------------------------
/front/src/services/sw/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | import appConfig from '../../config/appConfig';
2 |
3 | // #region constants
4 | const { path: swPath } = appConfig.sw;
5 | // #endregion
6 |
7 | function registerServiceWorker(): void {
8 | if (typeof window !== undefined) {
9 | if ('serviceWorker' in navigator) {
10 | window.addEventListener('load', () => {
11 | navigator.serviceWorker
12 | .register(swPath)
13 | .then(registration => {
14 | console.log('SW registered: ', registration);
15 | })
16 | .catch(registrationError => {
17 | console.log('SW registration failed: ', registrationError);
18 | });
19 | });
20 | }
21 | }
22 | }
23 |
24 | export default registerServiceWorker;
25 |
--------------------------------------------------------------------------------
/front/src/statics/index-raw.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ReactJS Bootstrap Starter
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/front/src/statics/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ReactJS Bootstrap Starter
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/front/src/style/GlobalStyles.ts:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const GlobalStyle = createGlobalStyle`
4 | html, body {
5 | margin: 0;
6 | height: 100%;
7 | -webkit-font-smoothing: antialiased;
8 | }
9 |
10 | * {
11 | box-sizing: border-box;
12 | }
13 |
14 | a {
15 | text-decoration: none;
16 | color: inherit;
17 | &:hover {
18 | text-decoration: none;
19 | }
20 | }
21 | `;
22 |
23 | export default GlobalStyle;
24 |
--------------------------------------------------------------------------------
/front/src/types/auth/index.d.ts:
--------------------------------------------------------------------------------
1 | declare type STORES_TYPES = 'localStorage' | 'sessionStorage';
2 | declare type TokenKey = string;
3 | declare type UserInfoKey = string;
4 |
--------------------------------------------------------------------------------
/front/src/types/user/index.d.ts:
--------------------------------------------------------------------------------
1 | declare type User = {
2 | id: string | number;
3 | login: string;
4 | firstname: string;
5 | lastname: string;
6 | token: string;
7 | isAuthenticated?: boolean;
8 | };
9 |
--------------------------------------------------------------------------------
/front/test/__mocks__/fileMock.ts:
--------------------------------------------------------------------------------
1 | export default 'test-file-stub';
--------------------------------------------------------------------------------
/front/test/mockedRouter.ts:
--------------------------------------------------------------------------------
1 | import { RouteComponentProps } from 'react-router';
2 | import { UnregisterCallback, Href } from 'history';
3 |
4 | export function getMockRouterProps(data: P) {
5 | const location = {
6 | hash: '',
7 | key: '',
8 | pathname: '',
9 | search: '',
10 | state: {},
11 | };
12 |
13 | const props: RouteComponentProps
= {
14 | match: {
15 | isExact: true,
16 | params: data,
17 | path: '',
18 | url: '',
19 | },
20 | location,
21 | history: {
22 | length: 2,
23 | action: 'POP',
24 | location,
25 | push: () => {},
26 | replace: () => {},
27 | go: num => {},
28 | goBack: () => {},
29 | goForward: () => {},
30 | block: t => {
31 | const temp: UnregisterCallback = () => {};
32 | return temp;
33 | },
34 | createHref: t => {
35 | const temp: Href = '';
36 | return temp;
37 | },
38 | listen: t => {
39 | const temp: UnregisterCallback = () => {};
40 | return temp;
41 | },
42 | },
43 | staticContext: {},
44 | ...data,
45 | };
46 |
47 | return props;
48 | }
49 |
--------------------------------------------------------------------------------
/front/test/setupTests.ts:
--------------------------------------------------------------------------------
1 | // to avoid: Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills
2 | // @ts-ignore
3 | require('raf/polyfill');
4 | // @ts-ignore
5 | require('jest-localstorage-mock');
6 | // @ts-ignore
7 | require('core-js/stable');
8 | // @ts-ignore
9 | require('regenerator-runtime/runtime');
10 | // @ts-ignore
11 | const Enzyme = require('enzyme');
12 | // @ts-ignore
13 | const EnzymeAdapter = require('enzyme-adapter-react-16');
14 |
15 | // Setup enzyme's react adapter
16 | Enzyme.configure({ adapter: new EnzymeAdapter() });
17 |
--------------------------------------------------------------------------------
/front/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "importHelpers": true,
4 | "target": "es5",
5 | "types": ["react", "jest"],
6 | "lib": ["dom", "es2015", "esnext"],
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "checkJs": false,
10 | "allowSyntheticDefaultImports": true,
11 | "esModuleInterop": true,
12 | "jsx": "react",
13 | "sourceMap": true,
14 | "removeComments": true,
15 | "strict": true,
16 | "noImplicitAny": true,
17 | "noImplicitThis": true,
18 | "resolveJsonModule": true,
19 | "outDir": "../dist/assets"
20 | },
21 | "include": ["./src"]
22 | }
23 |
--------------------------------------------------------------------------------
/front/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
5 | const workboxPlugin = require('workbox-webpack-plugin');
6 | const ModernizrWebpackPlugin = require('modernizr-webpack-plugin');
7 |
8 | // #region constants`
9 | const nodeModulesDir = path.join(__dirname, 'node_modules');
10 | const indexFile = path.join(__dirname, 'src/index.tsx');
11 | // #endregion
12 |
13 | const config = {
14 | target: 'web',
15 | mode: 'development',
16 | devtool: 'source-map',
17 | entry: {
18 | app: [indexFile],
19 | },
20 | output: {
21 | path: path.join(__dirname, '/../docs/assets'),
22 | publicPath: '/assets/',
23 | filename: '[name].[hash].js',
24 | chunkFilename: '[name].[hash].js',
25 | },
26 | resolve: {
27 | modules: ['node_modules'],
28 | extensions: ['.css', '.json', '.js', '.jsx', '.ts', '.tsx'],
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.ts(x)?$/,
34 | use: ['awesome-typescript-loader'],
35 | exclude: [nodeModulesDir],
36 | },
37 | {
38 | test: /\.css$/,
39 | use: ['style-loader', 'css-loader'],
40 | },
41 | {
42 | test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/,
43 | use: [
44 | {
45 | loader: 'url-loader',
46 | options: {
47 | limit: 100000,
48 | name: '[name].[ext]',
49 | },
50 | },
51 | ],
52 | },
53 | ],
54 | },
55 | optimization: {
56 | runtimeChunk: false,
57 | splitChunks: {
58 | cacheGroups: {
59 | commons: {
60 | test: /[\\/]node_modules[\\/]/,
61 | name: 'vendors',
62 | chunks: 'all',
63 | },
64 | styles: {
65 | name: 'styles',
66 | test: /\.css$/,
67 | chunks: 'all',
68 | enforce: true,
69 | },
70 | },
71 | },
72 | },
73 | plugins: [
74 | new HtmlWebpackPlugin({
75 | template: 'src/index.html',
76 | filename: '../index.html', // hack since outPut path would place in '/dist/assets/' in place of '/dist/'
77 | }),
78 | new webpack.DefinePlugin({
79 | 'process.env': {
80 | NODE_ENV: JSON.stringify('development'),
81 | },
82 | }),
83 | new ModernizrWebpackPlugin({
84 | htmlWebpackPlugin: true,
85 | }),
86 | new MiniCssExtractPlugin({
87 | filename: '[name].[hash].css',
88 | chunkFilename: '[id].[hash].css',
89 | }),
90 | new workboxPlugin.GenerateSW({
91 | swDest: 'sw.js',
92 | clientsClaim: true,
93 | skipWaiting: true,
94 | }),
95 | ],
96 | };
97 |
98 | module.exports = config;
99 |
--------------------------------------------------------------------------------
/front/webpack.hot.reload.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const ProgressBarPlugin = require('progress-bar-webpack-plugin');
4 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 |
7 | // #region constants
8 | const nodeModulesDir = path.join(__dirname, 'node_modules');
9 | const srcInclude = path.join(__dirname, 'src');
10 | const srcExclude = path.join(__dirname, 'test');
11 | const indexFile = path.join(__dirname, 'src/index.tsx');
12 | // #endregion
13 |
14 | const config = {
15 | mode: 'development',
16 | target: 'web',
17 | devtool: 'eval-source-map',
18 | entry: {
19 | app: [indexFile],
20 | },
21 | output: {
22 | path: path.join(__dirname, 'docs'),
23 | filename: '[name].js',
24 | chunkFilename: '[name].js',
25 | },
26 | resolve: {
27 | modules: ['node_modules'],
28 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.css', '.json'],
29 | },
30 | module: {
31 | rules: [
32 | {
33 | test: /\.ts(x)?$/,
34 | use: ['awesome-typescript-loader'],
35 | exclude: [nodeModulesDir],
36 | },
37 | {
38 | test: /\.css$/,
39 | use: ['style-loader', 'css-loader'],
40 | },
41 | {
42 | test: /\.(eot|woff|woff2|ttf|svg|png|jpe?g|gif)(\?\S*)?$/,
43 | use: [
44 | {
45 | loader: 'url-loader',
46 | options: {
47 | limit: 100000,
48 | name: '[name].[ext]',
49 | },
50 | },
51 | ],
52 | },
53 | ],
54 | },
55 | optimization: {
56 | runtimeChunk: false,
57 | splitChunks: {
58 | cacheGroups: {
59 | commons: {
60 | test: /[\\/]node_modules[\\/]/,
61 | name: 'vendors',
62 | chunks: 'all',
63 | },
64 | styles: {
65 | name: 'styles',
66 | test: /\.css$/,
67 | chunks: 'all',
68 | enforce: true,
69 | },
70 | },
71 | },
72 | },
73 | devServer: {
74 | host: 'localhost',
75 | port: 3001,
76 | historyApiFallback: true,
77 | contentBase: path.join(__dirname, 'temp'),
78 | headers: { 'Access-Control-Allow-Origin': '*' },
79 | },
80 | plugins: [
81 | new webpack.HotModuleReplacementPlugin(),
82 | new HtmlWebpackPlugin({
83 | template: 'index.html',
84 | }),
85 | new webpack.NamedModulesPlugin(),
86 | new webpack.DefinePlugin({
87 | 'process.env': {
88 | NODE_ENV: JSON.stringify('development'),
89 | },
90 | }),
91 | new ProgressBarPlugin({
92 | format: 'Build [:bar] :percent (:elapsed seconds)',
93 | clear: false,
94 | }),
95 | ],
96 | };
97 |
98 | module.exports = config;
99 |
--------------------------------------------------------------------------------
/preview/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MacKentoch/react-bootstrap-webpack-starter/aeca4d212ac312378b5b311b2c78ebc92a0a4000/preview/preview.png
--------------------------------------------------------------------------------
/scripts/prepareIndexHtml.js:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | // #region imports
4 | const { join } = require('path');
5 | const fs = require('fs');
6 | const chalk = require('chalk');
7 | // #endregion
8 |
9 | // #region constants
10 | const indexRaw = join(__dirname, '../', 'src/front/statics/index-raw.html');
11 | const destHtml = join(__dirname, '../', 'docs/index.html');
12 | // #endregion
13 |
14 | // #region utils
15 | function copyFile(sourceFilePath, destFilePath) {
16 | fs.createReadStream(sourceFilePath).pipe(fs.createWriteStream(destFilePath));
17 | }
18 | // #endregion
19 |
20 | // #region make production bundle
21 | function prepareIndexHtml() {
22 | if (fs.existsSync(indexRaw)) {
23 | copyFile(indexRaw, destHtml);
24 |
25 | return console.log(
26 | `${chalk.default.greenBright('==== index.html generated 🏋️ ====')}`,
27 | );
28 | }
29 |
30 | return console.log(
31 | `${chalk.default.red('==== index.html not found... 😢 ====')}`,
32 | );
33 | }
34 | // #endergion
35 |
36 | prepareIndexHtml();
37 |
--------------------------------------------------------------------------------
/server/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
--------------------------------------------------------------------------------
/server/.nvmrc:
--------------------------------------------------------------------------------
1 | 12
2 |
--------------------------------------------------------------------------------
/server/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "all",
4 | "bracketSpacing": true,
5 | "jsxBracketSameLine": false,
6 | "singleQuote": true,
7 | "overrides": [],
8 | "printWidth": 80,
9 | "useTabs": false,
10 | "tabWidth": 2,
11 | "parser": "typescript"
12 | }
13 |
--------------------------------------------------------------------------------
/server/.yarnclean:
--------------------------------------------------------------------------------
1 | @types/react-native
2 |
--------------------------------------------------------------------------------
/server/jest.config.js:
--------------------------------------------------------------------------------
1 | const { jsWithBabel: tsjPreset } = require('ts-jest/presets');
2 |
3 | module.exports = {
4 | preset: 'ts-jest',
5 | transform: { ...tsjPreset.transform },
6 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
7 | moduleFileExtensions: ['ts', 'tsx', 'json', 'node'],
8 | globals: {
9 | 'ts-jest': {
10 | babelConfig: false,
11 | },
12 | },
13 | testEnvironment: 'node',
14 | verbose: true,
15 | coverageDirectory: './coverage/',
16 | collectCoverage: true,
17 | };
18 |
--------------------------------------------------------------------------------
/server/out/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.__esModule = true;
3 | var express = require("express");
4 | var PrettyError = require("pretty-error");
5 | var expressServer_1 = require("./lib/expressServer");
6 | // #region constants
7 | var dev = process.env.NODE_ENV !== 'production';
8 | var pe = new PrettyError();
9 | // #endregion
10 | (function () {
11 | try {
12 | pe.start();
13 | var app = express();
14 | expressServer_1["default"](app, dev);
15 | }
16 | catch (error) {
17 | console.log('server error: ', error);
18 | }
19 | })();
20 |
--------------------------------------------------------------------------------
/server/out/lib/expressServer.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.__esModule = true;
3 | var express = require("express");
4 | var path = require("path");
5 | var chalk_1 = require("chalk");
6 | var errors_1 = require("../middleware/errors");
7 | // #region constants
8 | var DOCS_PATH = '../../../docs/';
9 | var port = process.env.PORT || 8082;
10 | var host = process.env.SERVER_HOST || 'localhost';
11 | // #endregion
12 | var expressServer = function (app, isDev) {
13 | if (isDev === void 0) { isDev = false; }
14 | if (!app) {
15 | console.log('Server application instance is undefined');
16 | throw new Error('Server application instance is undefined');
17 | }
18 | app.set('port', port);
19 | app.set('ipAdress', host);
20 | app.use('/assets', express.static(path.join(__dirname, DOCS_PATH, 'assets/')));
21 | app.get('/*', function (req, res) {
22 | return res.sendFile(path.join(__dirname, DOCS_PATH, 'index.html'));
23 | });
24 | app.use(errors_1.error404);
25 | app.use(errors_1.error500);
26 | /* eslint-disable no-console */
27 | // @ts-ignore
28 | app.listen(port, host, function () {
29 | return console.log("\n =====================================================\n -> Server (" + chalk_1["default"].bgBlue('SPA') + ") \uD83C\uDFC3 (running) on " + chalk_1["default"].green(host) + ":" + chalk_1["default"].green("" + port) + "\n =====================================================\n ");
30 | });
31 | /* eslint-enable no-console */
32 | return app;
33 | };
34 | exports["default"] = expressServer;
35 |
--------------------------------------------------------------------------------
/server/out/middleware/errors.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | exports.__esModule = true;
3 | exports.error404 = function (req, res, next) {
4 | console.log('req.url: ', req.url);
5 | var err = new Error('Not found');
6 | err.status = 404;
7 | next(err);
8 | };
9 | exports.error500 = function (err, req, res, next) {
10 | if (err.status === 404) {
11 | res.status(404).send('Sorry nothing here for now...');
12 | }
13 | console.error(err);
14 | res.status(500).send('internal server error');
15 | };
16 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-bootstrap-webpack-starter-server",
3 | "version": "1.0.1",
4 | "author": "Erwan DATIN (MacKentoch)",
5 | "license": "MIT",
6 | "description": "server side of react js + bootstrap + webpack starter",
7 | "main": "out/index.js",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/MacKentoch/react-bootstrap-webpack-starter.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/MacKentoch/react-bootstrap-webpack-starter/issues"
14 | },
15 | "engines": {
16 | "node": ">=10",
17 | "npm": ">=6.0.0",
18 | "yarn": ">=1.3.0"
19 | },
20 | "directories": {
21 | "test": "test"
22 | },
23 | "scripts": {
24 | "test": "cross-env NODE_ENV=test jest",
25 | "build-server": "tsc src/index.ts --outDir out",
26 | "run-server": "npm run build-server && ts-node src/index.ts"
27 | },
28 | "keywords": [
29 | "node",
30 | "TS",
31 | "express",
32 | "react",
33 | "react 16",
34 | "redux",
35 | "react-redux",
36 | "ES6",
37 | "ES7",
38 | "ES2015",
39 | "ES2016",
40 | "ES2017",
41 | "ES2018",
42 | "ES2019",
43 | "typescript",
44 | "bootstrap",
45 | "react-router4",
46 | "react-router",
47 | "starter",
48 | "webpack",
49 | "hot-reload",
50 | "redux-devtools-extension",
51 | "devtools",
52 | "webpack4"
53 | ],
54 | "dependencies": {
55 | "body-parser": "^1.19.0",
56 | "chalk": "^2.4.2",
57 | "compression": "^1.7.4",
58 | "convict": "^5.1.0",
59 | "date-fns": "^2.4.1",
60 | "express": "^4.17.1",
61 | "express-promise-router": "^3.0.3",
62 | "express-rate-limit": "^5.0.0",
63 | "pretty-error": "^2.1.1",
64 | "serialize-javascript": "^4.0.0",
65 | "serve-favicon": "^2.5.0"
66 | },
67 | "devDependencies": {
68 | "@types/body-parser": "^1.17.1",
69 | "@types/chalk": "^2.2.0",
70 | "@types/convict": "^4.2.1",
71 | "@types/express": "^4.17.1",
72 | "@types/express-promise-router": "^2.0.1",
73 | "@types/express-rate-limit": "^3.3.3",
74 | "@types/fetch-mock": "^7.3.1",
75 | "@types/jest": "^24.0.18",
76 | "@types/node": "^12.7.12",
77 | "fetch-mock": "^7.5.1",
78 | "jest": "^24.9.0",
79 | "jest-localstorage-mock": "^2.4.0",
80 | "prettier": "^1.18.2",
81 | "rimraf": "^3.0.0",
82 | "ts-jest": "^24.1.0",
83 | "ts-node": "^8.4.1",
84 | "tslib": "^1.10.0",
85 | "tslint-config-prettier": "^1.18.0",
86 | "typescript": "^3.6.4"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/server/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import * as PrettyError from 'pretty-error';
3 | import expressServer from './lib/expressServer';
4 |
5 | // #region constants
6 | const dev = process.env.NODE_ENV !== 'production';
7 | const pe = new PrettyError();
8 | // #endregion
9 |
10 | (() => {
11 | try {
12 | pe.start();
13 | const app = express();
14 | expressServer(app, dev);
15 | } catch (error) {
16 | console.log('server error: ', error);
17 | }
18 | })();
19 |
--------------------------------------------------------------------------------
/server/src/lib/asyncWrap.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 |
3 | // #region constants
4 | const shouldLogErrors = process.env.DEBUG || false;
5 | // #endregion
6 |
7 | /**
8 | * Returns a route handler for Express that calls the passed in function
9 | */
10 | export default function(fn: Function) {
11 | if (fn.length <= 3) {
12 | return function(
13 | req: express.Request,
14 | res: express.Response,
15 | next: express.NextFunction,
16 | ) {
17 | return fn(req, res, next).catch((error: any) => {
18 | if (shouldLogErrors) {
19 | console.log('middleware error: ', error);
20 | }
21 | next();
22 | });
23 | };
24 | } else {
25 | return function(
26 | err: express.Errback,
27 | req: express.Request,
28 | res: express.Response,
29 | next: express.NextFunction,
30 | ) {
31 | return fn(err, req, res, next).catch((error: any) => {
32 | if (shouldLogErrors) {
33 | console.log('middleware error: ', error);
34 | }
35 | next();
36 | });
37 | };
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/server/src/lib/expressServer.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 | import * as path from 'path';
3 | import chalk from 'chalk';
4 | import { error404, error500 } from '../middleware/errors';
5 |
6 | // #region constants
7 | const DOCS_PATH = '../../../docs/';
8 | const port = process.env.PORT || 8082;
9 | const host = process.env.SERVER_HOST || 'localhost';
10 | // #endregion
11 |
12 | const expressServer = (app: express.Application, isDev = false) => {
13 | if (!app) {
14 | console.log('Server application instance is undefined');
15 | throw new Error('Server application instance is undefined');
16 | }
17 |
18 | app.set('port', port);
19 | app.set('ipAdress', host);
20 |
21 | app.use(
22 | '/assets',
23 | express.static(path.join(__dirname, DOCS_PATH, 'assets/')),
24 | );
25 |
26 | app.get('/*', (req, res) =>
27 | res.sendFile(path.join(__dirname, DOCS_PATH, 'index.html')),
28 | );
29 |
30 | app.use(error404);
31 | app.use(error500);
32 |
33 | /* eslint-disable no-console */
34 | // @ts-ignore
35 | app.listen(port, host, () =>
36 | console.log(`
37 | =====================================================
38 | -> Server (${chalk.bgBlue('SPA')}) 🏃 (running) on ${chalk.green(
39 | host,
40 | )}:${chalk.green(`${port}`)}
41 | =====================================================
42 | `),
43 | );
44 | /* eslint-enable no-console */
45 |
46 | return app;
47 | };
48 |
49 | export default expressServer;
50 |
--------------------------------------------------------------------------------
/server/src/middleware/errors.ts:
--------------------------------------------------------------------------------
1 | import * as express from 'express';
2 |
3 | type ErrorWithStatus = {
4 | status?: number,
5 | message?: string,
6 | };
7 |
8 | export const error404 = (
9 | req: express.Request,
10 | res: express.Response,
11 | next: express.NextFunction,
12 | ) => {
13 | console.log('req.url: ', req.url);
14 |
15 | const err: ErrorWithStatus = new Error('Not found');
16 | err.status = 404;
17 | next(err);
18 | };
19 |
20 | export const error500 = (
21 | err: express.Errback & ErrorWithStatus,
22 | req: express.Request,
23 | res: express.Response,
24 | next: express.NextFunction,
25 | ) => {
26 | if (err.status === 404) {
27 | res.status(404).send('Sorry nothing here for now...');
28 | }
29 |
30 | console.error(err);
31 | res.status(500).send('internal server error');
32 | };
33 |
--------------------------------------------------------------------------------
/server/test/setupTests.ts:
--------------------------------------------------------------------------------
1 | require('jest-localstorage-mock');
2 |
--------------------------------------------------------------------------------
/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "noImplicitReturns": true,
5 | "noUnusedLocals": true,
6 | "sourceMap": true,
7 | "strict": true,
8 | "target": "es2017",
9 | "resolveJsonModule": true,
10 | "moduleResolution": "node",
11 | "baseUrl": ".",
12 | "outDir": "out"
13 | },
14 | "include": ["./src/**/*", "./test/**/*"]
15 | }
16 |
--------------------------------------------------------------------------------
/typings/globals/axios/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/e6215d4444ef44a38cd760817e955ce19b522032/axios/axios.d.ts",
5 | "raw": "registry:dt/axios#0.9.1+20161016142654",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/e6215d4444ef44a38cd760817e955ce19b522032/axios/axios.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/classnames/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/cf21303edea9abd919ecdab84698c6ab8513b1c2/classnames/index.d.ts
3 | declare type ClassValue = string | number | ClassDictionary | ClassArray | undefined | null | false;
4 |
5 | interface ClassDictionary {
6 | [id: string]: boolean | undefined | null;
7 | }
8 |
9 | interface ClassArray extends Array { }
10 |
11 | interface ClassNamesFn {
12 | (...classes: ClassValue[]): string;
13 | }
14 |
15 | declare var classNames: ClassNamesFn;
16 |
17 | declare module "classnames" {
18 | export = classNames
19 | }
20 |
--------------------------------------------------------------------------------
/typings/globals/classnames/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/cf21303edea9abd919ecdab84698c6ab8513b1c2/classnames/index.d.ts",
5 | "raw": "registry:dt/classnames#0.0.0+20161113184211",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/cf21303edea9abd919ecdab84698c6ab8513b1c2/classnames/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/jest/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/545f81a23a1c4f23ffb9707eeb40973bf5f053fe/jest/index.d.ts",
5 | "raw": "registry:dt/jest#19.2.0+20170312200138",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/545f81a23a1c4f23ffb9707eeb40973bf5f053fe/jest/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/js-base64/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62e37b17e1ec502ad642903d745d1b1ac8ff74d6/js-base64/index.d.ts
3 | declare module 'js-base64' {
4 | namespace JSBase64 {
5 | const Base64: Base64Static
6 | interface Base64Static {
7 | /**
8 | * .encode
9 | * @param {String} string
10 | * @return {String}
11 | */
12 | encode(base64: string): string;
13 |
14 | /**
15 | * .encodeURI
16 | * @param {String} string
17 | * @return {String}
18 | */
19 | encodeURI(base64: string): string
20 |
21 | /**
22 | * .decode
23 | * @param {String} string
24 | * @return {String}
25 | */
26 | decode(base64: string): string
27 |
28 | /**
29 | * Library version
30 | */
31 | VERSION:string
32 | }
33 | }
34 | export = JSBase64
35 | }
36 |
--------------------------------------------------------------------------------
/typings/globals/js-base64/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62e37b17e1ec502ad642903d745d1b1ac8ff74d6/js-base64/index.d.ts",
5 | "raw": "registry:dt/js-base64#2.1.9+20161025033459",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/62e37b17e1ec502ad642903d745d1b1ac8ff74d6/js-base64/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/modernizr/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/f7ab322332c2a6e3d923402cec1db0811ea753f2/modernizr/index.d.ts",
5 | "raw": "registry:dt/modernizr#3.3.0+20170302103631",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/f7ab322332c2a6e3d923402cec1db0811ea753f2/modernizr/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/node/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/a4a912a0cd1849fa7df0e5d909c8625fba04e49d/node/index.d.ts",
5 | "raw": "registry:dt/node#7.0.0+20170322231424",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/a4a912a0cd1849fa7df0e5d909c8625fba04e49d/node/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/popper.js/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/80060c94ef549c077a011977c2b5461bd0fd8947/popper.js/index.d.ts
3 | declare namespace Popper {
4 | export interface PopperOptions {
5 | placement?: string;
6 | gpuAcceleration?: boolean;
7 | offset?: number;
8 | boundariesElement?: string | Element;
9 | boundariesPadding?: number;
10 | preventOverflowOrder?: ("left" | "right" | "top" | "bottom")[];
11 | flipBehavior?: string | string[];
12 | modifiers?: string[];
13 | modifiersIgnored?: string[];
14 | removeOnDestroy?: boolean;
15 | arrowElement?: string | Element;
16 | }
17 | export class Modifiers {
18 | applyStyle(data: Object): Object;
19 | shift(data: Object): Object;
20 | preventOverflow(data: Object): Object;
21 | keepTogether(data: Object): Object;
22 | flip(data: Object): Object;
23 | offset(data: Object): Object;
24 | arrow(data: Object): Object;
25 | }
26 | export interface Data {
27 | placement: string;
28 | offsets: {
29 | popper: {
30 | position: string;
31 | top: number;
32 | left: number;
33 | };
34 | };
35 | }
36 | }
37 |
38 | declare class Popper {
39 | public modifiers: Popper.Modifiers;
40 | public placement: string;
41 |
42 | constructor(reference: Element, popper: Element | Object, options?: Popper.PopperOptions);
43 |
44 | destroy(): void;
45 | update(): void;
46 | onCreate(cb: (data: Popper.Data) => void): this;
47 | onUpdate(cb: (data: Popper.Data) => void): this;
48 | parse(config: Object): Element;
49 | runModifiers(data: Object, modifiers: string[], ends: Function): void;
50 | isModifierRequired(): boolean;
51 | }
52 |
53 | declare module "popper.js" {
54 | export = Popper;
55 | }
56 |
--------------------------------------------------------------------------------
/typings/globals/popper.js/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/80060c94ef549c077a011977c2b5461bd0fd8947/popper.js/index.d.ts",
5 | "raw": "registry:dt/popper.js#0.4.0+20161005184000",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/80060c94ef549c077a011977c2b5461bd0fd8947/popper.js/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/react-router-dom/index.d.ts:
--------------------------------------------------------------------------------
1 | // Generated by typings
2 | // Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/984933df7c89afcb97e42adbe37b6df5dd7b253c/react-router-dom/index.d.ts
3 | declare module 'react-router-dom' {
4 | import {
5 | Prompt,
6 | MemoryRouter,
7 | Redirect,
8 | RouteComponentProps,
9 | RouteProps,
10 | Route,
11 | Router,
12 | StaticRouter,
13 | Switch,
14 | match,
15 | matchPath,
16 | withRouter
17 | } from 'react-router';
18 | import * as React from 'react';
19 | import * as H from 'history';
20 |
21 |
22 | interface BrowserRouterProps {
23 | basename?: string;
24 | getUserConfirmation?: () => void;
25 | forceRefresh?: boolean;
26 | keyLength?: number;
27 | }
28 | class BrowserRouter extends React.Component {}
29 |
30 |
31 | interface HashRouterProps {
32 | basename?: string;
33 | getUserConfirmation?: () => void;
34 | hashType?: 'slash' | 'noslash' | 'hashbang';
35 | }
36 | class HashRouter extends React.Component {}
37 |
38 |
39 | interface LinkProps extends React.HTMLAttributes {
40 | to: H.LocationDescriptor;
41 | replace?: boolean;
42 | }
43 | class Link extends React.Component {}
44 |
45 |
46 | interface NavLinkProps extends LinkProps {
47 | activeClassName?: string;
48 | activeStyle?: React.CSSProperties;
49 | exact?: boolean;
50 | strict?: boolean;
51 | isActive?: (location: H.Location, props: any) => boolean;
52 | }
53 | class NavLink extends React.Component {}
54 |
55 |
56 | export {
57 | BrowserRouter,
58 | HashRouter,
59 | LinkProps, // TypeScript specific, not from React Router itself
60 | Link,
61 | NavLink,
62 | Prompt,
63 | MemoryRouter,
64 | Redirect,
65 | RouteComponentProps, // TypeScript specific, not from React Router itself
66 | RouteProps, // TypeScript specific, not from React Router itself
67 | Route,
68 | Router,
69 | StaticRouter,
70 | Switch,
71 | match, // TypeScript specific, not from React Router itself
72 | matchPath,
73 | withRouter
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/typings/globals/react-router-dom/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/984933df7c89afcb97e42adbe37b6df5dd7b253c/react-router-dom/index.d.ts",
5 | "raw": "registry:dt/react-router-dom#4.0.0+20170323201651",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/984933df7c89afcb97e42adbe37b6df5dd7b253c/react-router-dom/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/globals/recompose/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/66bd4290746e62eea9af862b46c22756c61e853a/recompose/index.d.ts",
5 | "raw": "registry:dt/recompose#0.22.0+20170306203754",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/66bd4290746e62eea9af862b46c22756c61e853a/recompose/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 | ///
5 | ///
6 | ///
7 | ///
8 | ///
9 | ///
10 | ///
11 | ///
12 | ///
13 | ///
14 |
--------------------------------------------------------------------------------
/typings/modules/enzyme/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/520e5694ccb26dfc7fe555abea584df9f969f412/enzyme/index.d.ts",
5 | "raw": "registry:dt/enzyme#2.7.0+20170320235509",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/520e5694ccb26dfc7fe555abea584df9f969f412/enzyme/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/modules/react-bootstrap/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/0f535159029908a2b5e068391b399e4d9a562d69/react-bootstrap/index.d.ts",
5 | "raw": "registry:dt/react-bootstrap#0.0.0+20170317163524",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/0f535159029908a2b5e068391b399e4d9a562d69/react-bootstrap/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/modules/react-motion/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/9b5329839558a78550bf078c3e5f323c2f0f3b86/react-motion/index.d.ts",
5 | "raw": "registry:dt/react-motion#0.0.0+20170123203653",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/9b5329839558a78550bf078c3e5f323c2f0f3b86/react-motion/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/typings/modules/react/typings.json:
--------------------------------------------------------------------------------
1 | {
2 | "resolution": "main",
3 | "tree": {
4 | "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/6ffca66c9d1edfdfa1f42f8d82db59e4297e302b/react/index.d.ts",
5 | "raw": "registry:dt/react#15.0.0+20170324160437",
6 | "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/6ffca66c9d1edfdfa1f42f8d82db59e4297e302b/react/index.d.ts"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------