diff --git a/.eslintrc.js b/.eslintrc.js
index b93afd2..ea2e4f3 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -32,5 +32,8 @@ module.exports = {
},
},
},
+ "env": {
+ "browser": true
+ }
};
\ No newline at end of file
diff --git a/package.json b/package.json
index 2105ea5..c98eb34 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"start": "node src/index.js",
"build": "webpack --config webpack.conf.js",
"watch": "webpack-dev-server --config webpack.conf.js",
- "eslint.check": "eslint src/**/*.{js,jsx}",
+ "eslint.check": "eslint src/**/*.{js,jsx}",
"eslint.fix": "eslint src/**/*.{js,jsx} --fix",
"prettier.check": "prettier src/**/*.{js,jsx} --list-different",
"prettier.fix": "prettier src/**/*.{js,jsx} --write",
@@ -40,17 +40,26 @@
"dependencies": {
"@material-ui/core": "^4.2.1",
"axios": "0.19.0",
+ "cors": "^2.8.5",
"debug": "4.1.1",
"dotenv": "8.0.0",
"eslint-plugin-import": "^2.18.0",
"express": "4.17.1",
"glob": "7.1.4",
+ "js-cookie": "2.2.0",
"jsonwebtoken": "8.5.1",
+ "jwt-decode": "2.2.0",
"moment": "2.24.0",
"mongoose": "5.6.4",
"parse-dashboard": "1.3.3",
"parse-server": "https://github.com/brunoMaurice/parse-server/releases/download/3.6.0-bis/parse-server-v3.6.0-bis.tgz",
+ "prop-types": "^15.7.2",
+ "react": "16.8.6",
+ "react-dom": "16.8.6",
+ "react-router": "^5.0.1",
+ "react-router-dom": "^5.0.1",
"request-promise": "4.2.4",
+ "rxjs": "^6.5.2",
"swagger-ui-express": "4.0.7",
"uuid": "3.3.2",
"validate.js": "0.13.1",
@@ -73,14 +82,10 @@
"eslint-plugin-react": "^7.14.2",
"html-webpack-plugin": "3.2.0",
"jest": "24.8.0",
- "js-cookie": "2.2.0",
- "jwt-decode": "2.2.0",
"mini-css-extract-plugin": "0.7.0",
"node-sass": "4.12.0",
"nodemon": "1.19.1",
"prettier": "1.18.2",
- "react": "16.8.6",
- "react-dom": "16.8.6",
"sass-loader": "7.1.0",
"style-loader": "0.23.1",
"url-loader": "2.0.1",
diff --git a/src/front/component/App.jsx b/src/front/component/App.jsx
index f572402..ac741af 100644
--- a/src/front/component/App.jsx
+++ b/src/front/component/App.jsx
@@ -1,22 +1,32 @@
import * as React from 'react';
import { BrowserRouter as Router } from "react-router-dom";
-import Routes from "./Router";
+
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
-import { hasJwt, logout } from '../services/auth';
+import { logout, connectedState } from '../services/auth';
+import Routes from "./Router";
-class App extends React.Component {
+class App extends React.PureComponent {
- signout() {
- logout();
+ constructor() {
+ super();
+ this.state = {userConnected : false};
+ }
+
+ componentDidMount() {
+ connectedState.subscribe((userConnected) => {
+ this.setState({userConnected});
+ })
}
render() {
+ const { userConnected } = this.state;
+
return (
@@ -24,8 +34,9 @@ class App extends React.Component {
Connect
-
- {hasJwt() && }
+
+
+ {userConnected && }
diff --git a/src/front/component/Page.jsx b/src/front/component/Page.jsx
index 78a4005..4409f8e 100644
--- a/src/front/component/Page.jsx
+++ b/src/front/component/Page.jsx
@@ -1,13 +1,20 @@
import * as React from 'react';
import { Redirect } from 'react-router';
-import { ROUTES } from './Router';
+import PropTypes from 'prop-types'; // ES6
+
import { hasJwt } from '../services/auth';
-class Page extends React.Component {
- state = { redirectToReferrer: false };
+
+class Page extends React.PureComponent {
+
+ constructor() {
+ super();
+ this.state = { redirectToReferrer: false };
+ }
async componentDidMount() {
- if (!this.props.isPublic && !hasJwt()) {
+ const { isPublic } = this.props;
+ if (!isPublic && !hasJwt()) {
this.setState({ redirectToReferrer: true });
}
}
@@ -16,11 +23,19 @@ class Page extends React.Component {
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
- return
;
+ return
;
}
+ const { children } = this.props;
- return
{this.props.children}
;
+ return
{ children }
;
}
}
+Page.propTypes = {
+ isPublic: PropTypes.bool.isRequired,
+ children: PropTypes.element.isRequired
+};
+
+
+
export default Page;
diff --git a/src/front/component/Router.jsx b/src/front/component/Router.jsx
index 3478516..2a94226 100644
--- a/src/front/component/Router.jsx
+++ b/src/front/component/Router.jsx
@@ -1,50 +1,49 @@
import * as React from 'react';
import { Route } from 'react-router-dom';
+import LoginPage from '../pages/login/login';
+import Github from '../pages/login/github';
+import HomePage from '../pages/home/home';
+import DetailsPage from '../pages/details/details';
+
export const ROUTES = {
HOME: '/',
LOGIN_GITHUB: '/login/github',
CONNECTED_HOME: '/home'
};
-const routesConfig = [
- {
- component: '/',
- path: '/',
- exact: true,
- },
- {
- component: 'login/github',
- path: '/login/github',
- exact: true,
- },
- {
- component: 'home/home',
- path: '/home',
- exact: true,
- },
-
-];
-
-class Routes extends React.Component {
+class Routes extends React.PureComponent {
render() {
return (
<>
- {routesConfig.map((route, i) => {
- const path =
- route.component.charAt(0) === '/'
- ? route.component.substr(1)
- : route.component;
- const component = require(`./../pages/${path}`).default;
- return (
-
- );
- })}
+
+
+
+
+
+
+
+
+
+
+
+
>
);
}
diff --git a/src/front/index.css b/src/front/index.css
index 802c18b..d701104 100644
--- a/src/front/index.css
+++ b/src/front/index.css
@@ -6,4 +6,8 @@ body {
a {
text-decoration: none;
+}
+
+.spacer {
+ flex: 1;
}
\ No newline at end of file
diff --git a/src/front/pages/details/details.jsx b/src/front/pages/details/details.jsx
new file mode 100644
index 0000000..88dc339
--- /dev/null
+++ b/src/front/pages/details/details.jsx
@@ -0,0 +1,173 @@
+/* eslint-disable react/forbid-prop-types */
+import * as React from 'react';
+import { withRouter } from 'react-router-dom';
+import { withStyles } from '@material-ui/core/styles';
+import TextField from '@material-ui/core/TextField';
+import CircularProgress from '@material-ui/core/CircularProgress';
+
+import PropTypes from 'prop-types'; // ES6
+
+import { getApplication } from '../../services/api';
+
+
+const styles = {
+ root: {
+ width: '100%',
+ maxWidth: 720,
+ margin: "0 auto",
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ progress: {
+ margin: "0 auto",
+ "margin-top": 140
+ },
+ listContainer: {
+ width: "100%"
+ },
+ textField: {
+ margin: 16
+ }
+};
+
+
+class DetailsPage extends React.PureComponent {
+ constructor() {
+ super();
+ this.state = {
+ loading: true,
+ application: {
+ name: "Name",
+ description: "Description",
+ token: "token",
+ token_sandbox: "token sandbox",
+ apple_store_link: "http://apple",
+ google_market_link: "http://google",
+ create_at: new Date(),
+ updated_at: new Date(),
+ }
+ }
+ }
+
+ componentDidMount() {
+ const { match } = this.props;
+ getApplication(match.params.appId).then((res) => {
+ this.setState({
+ loading: false,
+ application: res
+ });
+ });
+ }
+
+ handleChange(name, event) {
+ const { application } = this.state;
+ this.setState({
+ application: {
+ ...application,
+ [name]: event.target.value
+ }
+ });
+ }
+
+ render() {
+ const { classes } = this.props;
+ const { application, loading } = this.state;
+ return (
+ <>
+
+ {loading && }
+
+ {!loading &&
+ (
+ <>
+ this.handleChange('name', event)}
+ margin="normal"
+ variant="outlined"
+ />
+
+
+ this.handleChange('description', event)}
+ margin="normal"
+ variant="outlined"
+ multiline
+ rows="4"
+ />
+
+ this.handleChange('token', event)}
+ margin="normal"
+ variant="outlined"
+ />
+
+
+ this.handleChange('token_sandbox', event)}
+ margin="normal"
+ variant="outlined"
+ />
+
+ this.handleChange('apple_store_link', event)}
+ margin="normal"
+ variant="outlined"
+ />
+
+
+ this.handleChange('google_market_link', event)}
+ margin="normal"
+ variant="outlined"
+ />
+
+ >
+ )
+ }
+
+
+ >
+ );
+ }
+}
+
+DetailsPage.propTypes = {
+ classes: PropTypes.object.isRequired,
+ // history: PropTypes.object.isRequired,
+ match: PropTypes.object.isRequired
+};
+
+
+export default withRouter(withStyles(styles)(DetailsPage));
diff --git a/src/front/pages/home/home.jsx b/src/front/pages/home/home.jsx
index 434fee7..bdc46be 100644
--- a/src/front/pages/home/home.jsx
+++ b/src/front/pages/home/home.jsx
@@ -1,35 +1,111 @@
+/* eslint-disable no-underscore-dangle */
+/* eslint-disable react/forbid-prop-types */
import * as React from 'react';
-import { hasJwt } from '../../services/auth';
-import Page from '../../component/Page';
-
import { withRouter } from 'react-router-dom';
import { withStyles } from '@material-ui/core/styles';
+import List from '@material-ui/core/List';
+import ListItem from '@material-ui/core/ListItem';
+import Divider from '@material-ui/core/Divider';
+import ListItemText from '@material-ui/core/ListItemText';
+import Typography from '@material-ui/core/Typography';
+import CircularProgress from '@material-ui/core/CircularProgress';
+
+import PropTypes from 'prop-types'; // ES6
+
+import { listOfApplications } from '../../services/api';
+
const styles = {
- card: {
- maxWidth: 345,
+ root: {
+ width: '100%',
+ maxWidth: 720,
margin: "0 auto",
- "margin-top": "10%",
+ display: "flex",
+ flexWrap: 'wrap',
},
- media: {
- height: 140,
+ progress: {
+ margin: "0 auto",
+ "margin-top": 140
},
- buttonContainer: {
- "justify-content": "center",
- }
+ listContainer: {
+ width: "100%"
+ },
+ inline: {
+ display: 'inline',
+ },
+
};
-class ConnectedHome extends React.Component {
- render() {
+class HomePage extends React.PureComponent {
+ constructor() {
+ super();
+ this.state = {
+ loading: true,
+ developerApplications: []
+ }
+ }
- const { classes } = this.props;
+ componentDidMount() {
+ listOfApplications().then((res) => {
+ this.setState({
+ loading: false,
+ developerApplications: res
+ });
+ });
+ }
+
+ rowClick(application) {
+ const { history } = this.props;
+ history.push(`/application/${application._id}`);
+ }
+
+ render() {
+ const { classes } = this.props;
+ const { developerApplications, loading } = this.state;
return (
-
- {hasJwt() && Your are authorize to see this
}
-
+ <>
+
+ {loading && }
+
+ {!loading &&
+ developerApplications.map(application => (
+
+
this.rowClick(application)}>
+
+ {application.name}
+
+ {` - ${application.updated_at}`}
+
+
+
+ )}
+ secondary={application.description}
+ />
+
+
+
+ ))
+ }
+
+
+ >
);
}
}
-export default withRouter(withStyles(styles)(ConnectedHome));
+HomePage.propTypes = {
+ classes: PropTypes.object.isRequired,
+ history: PropTypes.object.isRequired,
+};
+
+
+export default withRouter(withStyles(styles)(HomePage));
diff --git a/src/front/pages/index.jsx b/src/front/pages/index.jsx
deleted file mode 100644
index 32373d5..0000000
--- a/src/front/pages/index.jsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import * as React from 'react';
-import { hasJwt } from '../services/auth';
-import Page from '../component/Page';
-
-import { withRouter } from 'react-router-dom';
-import { withStyles } from '@material-ui/core/styles';
-import Card from '@material-ui/core/Card';
-import CardContent from '@material-ui/core/CardContent';
-import CardActions from '@material-ui/core/CardActions';
-import CardMedia from '@material-ui/core/CardMedia';
-import Button from '@material-ui/core/Button';
-import Typography from '@material-ui/core/Typography';
-
-const styles = {
- card: {
- maxWidth: 345,
- margin: "0 auto",
- "margin-top": "10%",
- },
- media: {
- height: 140,
- },
- buttonContainer: {
- "justify-content": "center",
- }
-};
-
-
-class Home extends React.Component {
- render() {
-
- if (hasJwt()) {
- return this.props.history.push('/home');
- }
-
- const { classes } = this.props;
- return (
-
-
-
-
-
-
- Welcome
-
-
- To start using Connect, please use your GitHub to login.
-
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-export default withRouter(withStyles(styles)(Home));
diff --git a/src/front/pages/login/github.jsx b/src/front/pages/login/github.jsx
index 9df1341..189e704 100644
--- a/src/front/pages/login/github.jsx
+++ b/src/front/pages/login/github.jsx
@@ -1,7 +1,8 @@
+/* eslint-disable react/forbid-prop-types */
import * as React from 'react';
+import PropTypes from 'prop-types'; // ES6
+
import { setAuthToken } from '../../services/auth';
-import { ROUTES } from '../../component/Router';
-import Page from '../../component/Page';
class Github extends React.Component {
constructor(props) {
@@ -11,9 +12,10 @@ class Github extends React.Component {
}
async componentDidMount() {
- let params = new URLSearchParams(this.props.location.search);
+ const { location, history } = this.props;
+ const params = new URLSearchParams(location.search);
- const responses = await fetch(`/api/auth`, {
+ const responses = await fetch(`${process.env.API_URL}/api/auth`, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
@@ -22,18 +24,24 @@ class Github extends React.Component {
body: JSON.stringify({ code: params.get('code') }),
});
- if ((await responses.code) !== 200) {
- this.props.history.push(ROUTES.HOME);
+ if (await responses.status !== 200) {
+ return history.push('/');
}
setAuthToken(await responses.text());
- this.props.history.push(ROUTES.HOME);
+ return history.push('/');
}
render() {
- return
Happy to see you back;
+ return
Redirection
;
}
}
+Github.propTypes = {
+ location: PropTypes.object.isRequired,
+ history: PropTypes.object.isRequired
+};
+
+
export default Github;
diff --git a/src/front/pages/login/login.jsx b/src/front/pages/login/login.jsx
new file mode 100644
index 0000000..e5c2f4e
--- /dev/null
+++ b/src/front/pages/login/login.jsx
@@ -0,0 +1,82 @@
+/* eslint-disable react/forbid-prop-types */
+
+import { withRouter } from 'react-router-dom';
+import { withStyles } from '@material-ui/core/styles';
+import Card from '@material-ui/core/Card';
+import CardContent from '@material-ui/core/CardContent';
+import CardActions from '@material-ui/core/CardActions';
+import CardMedia from '@material-ui/core/CardMedia';
+import Button from '@material-ui/core/Button';
+import Typography from '@material-ui/core/Typography';
+import PropTypes from 'prop-types'; // ES6
+
+import * as React from 'react';
+import { hasJwt } from '../../services/auth';
+
+
+const styles = {
+ card: {
+ maxWidth: 345,
+ margin: "0 auto",
+ "margin-top": "10%",
+ },
+ media: {
+ height: 140,
+ },
+ buttonContainer: {
+ "justify-content": "center",
+ }
+};
+
+
+class LoginPage extends React.PureComponent {
+ componentDidMount() {
+ if (hasJwt()) {
+ const { history } = this.props;
+ history.push('/home');
+ }
+ }
+
+
+ render() {
+ const { classes } = this.props;
+ return (
+
+
+
+
+
+ Welcome
+
+
+ To start using Connect, please use your GitHub to login.
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+LoginPage.propTypes = {
+ history: PropTypes.object.isRequired,
+ classes: PropTypes.object.isRequired,
+};
+
+
+
+export default withRouter(withStyles(styles)(LoginPage));
diff --git a/src/front/services/api.jsx b/src/front/services/api.jsx
new file mode 100644
index 0000000..53240da
--- /dev/null
+++ b/src/front/services/api.jsx
@@ -0,0 +1,43 @@
+import { getJwt } from './auth';
+
+
+export const listOfApplications = async () => {
+ const jwt = getJwt();
+ if (!jwt) {
+ return [];
+ }
+
+ const responses = await fetch(`${process.env.API_URL}/api/application`, {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${jwt}`
+ },
+ method: 'GET',
+ });
+
+
+ return responses.json();
+}
+
+
+export const getApplication = async (appId) => {
+ const jwt = getJwt();
+ if (!jwt) {
+ return {};
+ }
+
+ const responses = await fetch(`${process.env.API_URL}/api/application/${appId}`, {
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${jwt}`
+ },
+ method: 'GET',
+ });
+
+
+ return responses.json();
+}
+
+
diff --git a/src/front/services/auth.jsx b/src/front/services/auth.jsx
index 6278fd9..38b8a60 100644
--- a/src/front/services/auth.jsx
+++ b/src/front/services/auth.jsx
@@ -1,21 +1,14 @@
import * as Cookie from 'js-cookie';
import * as jwtDecode from 'jwt-decode';
+import { BehaviorSubject } from 'rxjs';
const COOKIENAME = 'jwt-connect';
+export const connectedState = new BehaviorSubject(false);
export const logout = () => {
Cookie.remove(COOKIENAME);
};
-export const setAuthToken = token => {
- if (!isJwtValid(token)) {
- return false;
- }
-
- Cookie.set(COOKIENAME, token);
- return true;
-};
-
const isJwtValid = jwt => {
if (!jwt) {
return false;
@@ -39,9 +32,26 @@ const isJwtValid = jwt => {
return now < exp;
};
+
+export const setAuthToken = token => {
+ if (!isJwtValid(token)) {
+ connectedState.next(false);
+ return false;
+ }
+
+ Cookie.set(COOKIENAME, token);
+ connectedState.next(true);
+ return true;
+};
+
+
export const getJwt = () => {
const jwt = Cookie.get(COOKIENAME);
- return jwt && isJwtValid(jwt) ? jwt : null;
+ const valid = jwt && isJwtValid(jwt) ? jwt : null;
+ connectedState.next(!!valid);
+ return valid;
};
export const hasJwt = () => !!getJwt();
+
+hasJwt();
diff --git a/src/index.js b/src/index.js
index d297517..44fd6f7 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,16 +1,26 @@
-const { APP_PORT } = require('./config');
const express = require('express');
+const path = require('path');
+const cors = require('cors');
+
+const api = require('./api');
+const logger = require('./logger');
+const { APP_PORT, FRONT_URL } = require('./config');
const parseApi = require('./middleware/parse');
const parseSandbox = require('./middleware/parseSandbox');
const parseDashboard = require('./middleware/parseDashboard');
const parseSwagger = require('./middleware/parseSwagger');
-const path = require('path');
-const api = require('./api');
-const logger = require('./logger');
const app = express();
+
app.use(express.json());
+
+const corsOptions = {
+ origin: FRONT_URL,
+ optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
+}
+
+app.use(cors());
app.use(express.urlencoded({ extended: true }));
// Serve the Parse API at /parse URL prefix
diff --git a/webpack.conf.js b/webpack.conf.js
index c54a1be..bf4d100 100644
--- a/webpack.conf.js
+++ b/webpack.conf.js
@@ -1,4 +1,4 @@
-require('dotenv').config();
+const dotenv = require('dotenv');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
@@ -23,81 +23,94 @@ const scssLoaders = cssOptions => [
'sass-loader',
];
-module.exports = {
- mode: process.env.NODE_ENV || 'development',
- devtool: process.env.NODE_ENV ? false : 'eval-source-map',
- entry: ['@babel/polyfill', sources],
- output: {
- path: dist,
- publicPath: '/',
- filename: '[name].[contenthash].js',
- },
- devServer: {
- inline:true,
- port: process.env.FRONT_PORT
- },
- module: {
- rules: [
- {
- test: /\.jsx?$/,
- use: 'babel-loader',
- },
- {
- test: [/\.scss$/, /\.css$/],
- exclude: /\.module\.scss$/,
- use: scssLoaders({}),
- },
- {
- test: /\.module\.scss$/,
- use: scssLoaders({
- modules: true,
- localIdentName: '[name]__[local]--[hash:base64:5]',
- }),
- },
- {
- test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
- loader: require.resolve('url-loader'),
- options: {
- limit: 10000,
- name: 'static/media/[name].[hash:8].[ext]',
+module.exports = () => {
+ const env = dotenv.config().parsed;
+
+ // reduce it to a nice object, the same as before
+ const envKeys = Object.keys(env).reduce((prev, next) => {
+ // eslint-disable-next-line no-param-reassign
+ prev[`process.env.${next}`] = JSON.stringify(env[next]);
+ return prev;
+ }, {});
+
+ return {
+ mode: process.env.NODE_ENV || 'development',
+ devtool: process.env.NODE_ENV ? false : 'eval-source-map',
+ entry: ['@babel/polyfill', sources],
+ output: {
+ path: dist,
+ publicPath: '/',
+ filename: '[name].[contenthash].js',
+ },
+ devServer: {
+ inline:true,
+ port: process.env.FRONT_PORT,
+ historyApiFallback: true
+ },
+ module: {
+ rules: [
+ {
+ test: /\.jsx?$/,
+ use: 'babel-loader',
},
- },
- ],
- },
- resolve: {
- modules: ['src/front', 'node_modules'],
- extensions: ['.jsx', '.js', '.json'],
- },
- plugins: [
- new MiniCssExtractPlugin({
- filename: EXTRACT_CSS ? '[name].[hash].css' : '[name].css',
- chunkFilename: EXTRACT_CSS ? '[id].[hash].css' : '[id].css',
- }),
- new HtmlWebpackPlugin({
- inject: true,
- template: indexTemplate,
- publicUrl: process.env.PUBLIC_URL,
- }),
- new webpack.DefinePlugin({}),
- new CopyWebpackPlugin([
- {
- from: getPath('src/front/public'),
- ignore: 'index.html',
- to: dist,
- },
- ]),
- new webpack.HashedModuleIdsPlugin(),
- ],
- watchOptions: {
- aggregateTimeout: 300,
- poll: 1000,
- },
- optimization: APPLY_OPTIMIZATIONS
- ? {
- runtimeChunk: 'single',
- splitChunks: {
- chunks: 'all',
+ {
+ test: [/\.scss$/, /\.css$/],
+ exclude: /\.module\.scss$/,
+ use: scssLoaders({}),
+ },
+ {
+ test: /\.module\.scss$/,
+ use: scssLoaders({
+ modules: true,
+ localIdentName: '[name]__[local]--[hash:base64:5]',
+ }),
+ },
+ {
+ test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
+ loader: require.resolve('url-loader'),
+ options: {
+ limit: 10000,
+ name: 'static/media/[name].[hash:8].[ext]',
+ },
},
- }
- : {},
+ ],
+ },
+ resolve: {
+ modules: ['src/front', 'node_modules'],
+ extensions: ['.jsx', '.js', '.json'],
+ },
+ plugins: [
+ new webpack.DefinePlugin(envKeys),
+ new MiniCssExtractPlugin({
+ filename: EXTRACT_CSS ? '[name].[hash].css' : '[name].css',
+ chunkFilename: EXTRACT_CSS ? '[id].[hash].css' : '[id].css',
+ }),
+ new HtmlWebpackPlugin({
+ inject: true,
+ template: indexTemplate,
+ publicUrl: process.env.PUBLIC_URL,
+ }),
+ new webpack.DefinePlugin({}),
+ new CopyWebpackPlugin([
+ {
+ from: getPath('src/front/public'),
+ ignore: 'index.html',
+ to: dist,
+ },
+ ]),
+ new webpack.HashedModuleIdsPlugin(),
+ ],
+ watchOptions: {
+ aggregateTimeout: 300,
+ poll: 1000,
+ },
+ optimization: APPLY_OPTIMIZATIONS
+ ? {
+ runtimeChunk: 'single',
+ splitChunks: {
+ chunks: 'all',
+ },
+ }
+ : {},
+ }
};
diff --git a/yarn.lock b/yarn.lock
index 41dde9a..4d7ec7e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3057,7 +3057,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-cors@2.8.5, cors@^2.8.4:
+cors@2.8.5, cors@^2.8.4, cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
@@ -8680,7 +8680,7 @@ react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
-react-router-dom@5.0.1:
+react-router-dom@5.0.1, react-router-dom@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.0.1.tgz#ee66f4a5d18b6089c361958e443489d6bab714be"
integrity sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==
@@ -8693,7 +8693,7 @@ react-router-dom@5.0.1:
tiny-invariant "^1.0.2"
tiny-warning "^1.0.0"
-react-router@5.0.1:
+react-router@5.0.1, react-router@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.0.1.tgz#04ee77df1d1ab6cb8939f9f01ad5702dbadb8b0f"
integrity sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==
@@ -9203,7 +9203,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
-rxjs@^6.4.0:
+rxjs@^6.4.0, rxjs@^6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==