Replace nextjs (#54)
* add new deps * remove .babelrc * add main files * package scripts + add missing typings * tslint ignore json * replace next/router * replace next/link * HMR + configureStore + fontawsome header link * Use Link instead of Redirect to solve same page redirect problem. * Home svg import. * hide filter button even if ant styles load first * Integrate Helmet * adjust style loading + fix font-face url format * import style higher in render tree for improved SSR * dev.js - nodemon only watch build/server dir * precedence order fixed * keep_fnames=true to keep uglifyjs from mangling BN * small cleanup
This commit is contained in:
parent
cf8e621528
commit
fe1e2a8df3
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"presets": ["next/babel", "@zeit/next-typescript/babel"],
|
|
||||||
"env": {
|
|
||||||
"development": {
|
|
||||||
"plugins": ["inline-dotenv"]
|
|
||||||
},
|
|
||||||
"production": {
|
|
||||||
"plugins": ["transform-inline-environment-variables"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
["import", { "libraryName": "antd", "style": false }],
|
|
||||||
[
|
|
||||||
"module-resolver",
|
|
||||||
{
|
|
||||||
"root": ["client"],
|
|
||||||
"extensions": [".js", ".tsx", ".ts"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"styled-components",
|
|
||||||
{
|
|
||||||
"ssr": true,
|
|
||||||
"displayName": true,
|
|
||||||
"preprocess": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const rimraf = require('rimraf');
|
||||||
|
|
||||||
|
const webpackConfig = require('../config/webpack.config.js')(
|
||||||
|
process.env.NODE_ENV || 'production',
|
||||||
|
);
|
||||||
|
const paths = require('../config/paths');
|
||||||
|
const { logMessage } = require('./utils');
|
||||||
|
|
||||||
|
const build = async () => {
|
||||||
|
rimraf.sync(paths.clientBuild);
|
||||||
|
rimraf.sync(paths.serverBuild);
|
||||||
|
|
||||||
|
logMessage('Compiling, please wait...');
|
||||||
|
|
||||||
|
const [clientConfig, serverConfig] = webpackConfig;
|
||||||
|
const multiCompiler = webpack([clientConfig, serverConfig]);
|
||||||
|
multiCompiler.run((error, stats) => {
|
||||||
|
if (stats) {
|
||||||
|
console.log(stats.toString(clientConfig.stats));
|
||||||
|
}
|
||||||
|
if (error) {
|
||||||
|
logMessage('Compile error', error);
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
build();
|
|
@ -0,0 +1,96 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const nodemon = require('nodemon');
|
||||||
|
const rimraf = require('rimraf');
|
||||||
|
const webpackConfig = require('../config/webpack.config.js')(
|
||||||
|
process.env.NODE_ENV || 'development',
|
||||||
|
);
|
||||||
|
const webpackDevMiddleware = require('webpack-dev-middleware');
|
||||||
|
const webpackHotMiddleware = require('webpack-hot-middleware');
|
||||||
|
const express = require('express');
|
||||||
|
const paths = require('../config/paths');
|
||||||
|
const truffleUtil = require('./truffle-util');
|
||||||
|
const { logMessage } = require('./utils');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
const WEBPACK_PORT =
|
||||||
|
process.env.WEBPACK_PORT ||
|
||||||
|
(!isNaN(Number(process.env.PORT)) ? Number(process.env.PORT) + 1 : 3001);
|
||||||
|
|
||||||
|
const start = async () => {
|
||||||
|
rimraf.sync(paths.clientBuild);
|
||||||
|
rimraf.sync(paths.serverBuild);
|
||||||
|
|
||||||
|
await truffleUtil.ethereumCheck();
|
||||||
|
|
||||||
|
const [clientConfig, serverConfig] = webpackConfig;
|
||||||
|
clientConfig.entry.bundle = [
|
||||||
|
`webpack-hot-middleware/client?path=http://localhost:${WEBPACK_PORT}/__webpack_hmr`,
|
||||||
|
...clientConfig.entry.bundle,
|
||||||
|
];
|
||||||
|
|
||||||
|
clientConfig.output.hotUpdateMainFilename = 'updates/[hash].hot-update.json';
|
||||||
|
clientConfig.output.hotUpdateChunkFilename = 'updates/[id].[hash].hot-update.js';
|
||||||
|
|
||||||
|
const publicPath = clientConfig.output.publicPath;
|
||||||
|
clientConfig.output.publicPath = `http://localhost:${WEBPACK_PORT}${publicPath}`;
|
||||||
|
serverConfig.output.publicPath = `http://localhost:${WEBPACK_PORT}${publicPath}`;
|
||||||
|
|
||||||
|
const multiCompiler = webpack([clientConfig, serverConfig]);
|
||||||
|
const clientCompiler = multiCompiler.compilers[0];
|
||||||
|
const serverCompiler = multiCompiler.compilers[1];
|
||||||
|
|
||||||
|
serverCompiler.hooks.compile.tap('_', () => logMessage('Server compiling...', 'info'));
|
||||||
|
clientCompiler.hooks.compile.tap('_', () => logMessage('Client compiling...', 'info'));
|
||||||
|
|
||||||
|
const watchOptions = {
|
||||||
|
ignored: /node_modules/,
|
||||||
|
stats: clientConfig.stats,
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
const devMiddleware = webpackDevMiddleware(multiCompiler, {
|
||||||
|
publicPath: clientConfig.output.publicPath,
|
||||||
|
stats: clientConfig.stats,
|
||||||
|
watchOptions,
|
||||||
|
});
|
||||||
|
app.use(devMiddleware);
|
||||||
|
app.use(webpackHotMiddleware(clientCompiler));
|
||||||
|
app.use('/static', express.static(paths.clientBuild));
|
||||||
|
app.listen(WEBPACK_PORT);
|
||||||
|
|
||||||
|
// await first build...
|
||||||
|
await new Promise((res, rej) => devMiddleware.waitUntilValid(() => res()));
|
||||||
|
|
||||||
|
const script = nodemon({
|
||||||
|
script: `${paths.serverBuild}/server.js`,
|
||||||
|
watch: [paths.serverBuild],
|
||||||
|
verbose: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// uncomment to see nodemon details
|
||||||
|
// script.on('log', x => console.log(`LOG `, x.colour));
|
||||||
|
|
||||||
|
script.on('crash', () =>
|
||||||
|
logMessage(
|
||||||
|
'Server crashed, will attempt to restart after changes. Waiting...',
|
||||||
|
'error',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
script.on('restart', () => {
|
||||||
|
logMessage('Server restarted.', 'warning');
|
||||||
|
});
|
||||||
|
|
||||||
|
script.on('error', () => {
|
||||||
|
logMessage('An error occured attempting to run the server. Exiting', 'error');
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
start();
|
|
@ -0,0 +1,140 @@
|
||||||
|
const rimraf = require('rimraf');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const childProcess = require('child_process');
|
||||||
|
const Web3 = require('web3');
|
||||||
|
|
||||||
|
const paths = require('../config/paths');
|
||||||
|
const truffleConfig = require('../truffle');
|
||||||
|
const { logMessage } = require('./utils');
|
||||||
|
|
||||||
|
require('../config/env');
|
||||||
|
|
||||||
|
module.exports = {};
|
||||||
|
|
||||||
|
const clean = (module.exports.clean = () => {
|
||||||
|
rimraf.sync(paths.contractsBuild);
|
||||||
|
});
|
||||||
|
|
||||||
|
const compile = (module.exports.compile = () => {
|
||||||
|
childProcess.execSync('yarn build', { cwd: paths.contractsBase });
|
||||||
|
});
|
||||||
|
|
||||||
|
const migrate = (module.exports.migrate = () => {
|
||||||
|
childProcess.execSync('truffle migrate', { cwd: paths.contractsBase });
|
||||||
|
});
|
||||||
|
|
||||||
|
const makeWeb3Conn = () => {
|
||||||
|
const { host, port } = truffleConfig.networks.development;
|
||||||
|
return `ws://${host}:${port}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createWeb3 = () => {
|
||||||
|
return new Web3(makeWeb3Conn());
|
||||||
|
};
|
||||||
|
|
||||||
|
const isGanacheUp = (module.exports.isGanacheUp = verbose =>
|
||||||
|
new Promise((res, rej) => {
|
||||||
|
verbose && logMessage(`Testing ganache @ ${makeWeb3Conn()}...`, 'info');
|
||||||
|
// console.log('curProv', web3.eth.currentProvider);
|
||||||
|
const web3 = createWeb3();
|
||||||
|
web3.eth.net
|
||||||
|
.isListening()
|
||||||
|
.then(() => {
|
||||||
|
verbose && logMessage('Ganache is UP!', 'info');
|
||||||
|
res(true);
|
||||||
|
web3.currentProvider.connection.close();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
logMessage('Ganache appears to be down, unable to connect.', 'error');
|
||||||
|
res(false);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
const getGanacheNetworkId = (module.exports.getGanacheNetworkId = () => {
|
||||||
|
const web3 = createWeb3();
|
||||||
|
return web3.eth.net
|
||||||
|
.getId()
|
||||||
|
.then(id => {
|
||||||
|
web3.currentProvider.connection.close();
|
||||||
|
return id;
|
||||||
|
})
|
||||||
|
.catch(() => -1);
|
||||||
|
});
|
||||||
|
|
||||||
|
const checkContractsNetworkIds = (module.exports.checkContractsNetworkIds = id =>
|
||||||
|
new Promise((res, rej) => {
|
||||||
|
const buildDir = paths.contractsBuild;
|
||||||
|
fs.readdir(buildDir, (err, names) => {
|
||||||
|
if (err) {
|
||||||
|
logMessage(`No contracts build directory @ ${buildDir}`, 'error');
|
||||||
|
res(false);
|
||||||
|
} else {
|
||||||
|
const allHaveId = names.reduce((ok, name) => {
|
||||||
|
const contract = require(path.join(buildDir, name));
|
||||||
|
if (Object.keys(contract.networks).length > 0 && !contract.networks[id]) {
|
||||||
|
const actual = Object.keys(contract.networks).join(', ');
|
||||||
|
logMessage(`${name} should have networks[${id}], it has ${actual}`, 'error');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true && ok;
|
||||||
|
}, true);
|
||||||
|
res(allHaveId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
const fundWeb3v1 = (module.exports.fundWeb3v1 = () => {
|
||||||
|
// Fund ETH accounts
|
||||||
|
const ethAccounts = process.env.FUND_ETH_ADDRESSES
|
||||||
|
? process.env.FUND_ETH_ADDRESSES.split(',').map(a => a.trim())
|
||||||
|
: [];
|
||||||
|
const web3 = createWeb3();
|
||||||
|
return web3.eth.getAccounts().then(accts => {
|
||||||
|
if (ethAccounts.length) {
|
||||||
|
logMessage('Sending 50% of ETH balance from accounts...', 'info');
|
||||||
|
const txs = ethAccounts.map((addr, i) => {
|
||||||
|
return web3.eth
|
||||||
|
.getBalance(accts[i])
|
||||||
|
.then(parseInt)
|
||||||
|
.then(bal => {
|
||||||
|
const amount = '' + Math.round(bal / 2);
|
||||||
|
const amountEth = web3.utils.fromWei(amount);
|
||||||
|
return web3.eth
|
||||||
|
.sendTransaction({
|
||||||
|
to: addr,
|
||||||
|
from: accts[i],
|
||||||
|
value: amount,
|
||||||
|
})
|
||||||
|
.then(() => logMessage(` ${addr} <- ${amountEth} from ${accts[i]}`))
|
||||||
|
.catch(e =>
|
||||||
|
logMessage(` Error sending funds to ${addr} : ${e}`, 'error'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return Promise.all(txs).then(() => web3.currentProvider.connection.close());
|
||||||
|
} else {
|
||||||
|
logMessage('No accounts specified for funding in .env file...', 'warning');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports.ethereumCheck = () =>
|
||||||
|
isGanacheUp(true)
|
||||||
|
.then(isUp => !isUp && Promise.reject('network down'))
|
||||||
|
.then(getGanacheNetworkId)
|
||||||
|
.then(checkContractsNetworkIds)
|
||||||
|
.then(allHaveId => {
|
||||||
|
if (!allHaveId) {
|
||||||
|
logMessage('Contract problems, will compile & migrate.', 'warning');
|
||||||
|
clean();
|
||||||
|
logMessage('truffle compile, please wait...', 'info');
|
||||||
|
compile();
|
||||||
|
logMessage('truffle migrate, please wait...', 'info');
|
||||||
|
migrate();
|
||||||
|
fundWeb3v1();
|
||||||
|
} else {
|
||||||
|
logMessage('OK, Contracts have correct network id.', 'info');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => logMessage('WARNING: ethereum setup has a problem: ' + e, 'error'));
|
|
@ -0,0 +1,27 @@
|
||||||
|
const chalk = require('chalk');
|
||||||
|
const net = require('net');
|
||||||
|
|
||||||
|
const logMessage = (message, level = 'info') => {
|
||||||
|
const colors = { error: 'red', warning: 'yellow', info: 'blue' };
|
||||||
|
colors[undefined] = 'white';
|
||||||
|
console.log(`${chalk[colors[level]](message)}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPortTaken = port =>
|
||||||
|
new Promise((res, rej) => {
|
||||||
|
const tester = net
|
||||||
|
.createServer()
|
||||||
|
.once('error', function(err) {
|
||||||
|
err.code != 'EADDRINUSE' && rej();
|
||||||
|
err.code == 'EADDRINUSE' && res(true);
|
||||||
|
})
|
||||||
|
.once('listening', () => {
|
||||||
|
tester.once('close', () => res(false)).close();
|
||||||
|
})
|
||||||
|
.listen(port, '127.0.0.1');
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
logMessage,
|
||||||
|
isPortTaken,
|
||||||
|
};
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { hot } from 'react-hot-loader';
|
||||||
|
import { Switch, Route, Redirect } from 'react-router';
|
||||||
|
import { injectGlobal } from 'styled-components';
|
||||||
|
import loadable from 'loadable-components';
|
||||||
|
|
||||||
|
// wrap components in loadable...import & they will be split
|
||||||
|
const Home = loadable(() => import('pages/index'));
|
||||||
|
const Create = loadable(() => import('pages/create'));
|
||||||
|
const Proposals = loadable(() => import('pages/proposals'));
|
||||||
|
const Proposal = loadable(() => import('pages/proposal'));
|
||||||
|
|
||||||
|
import 'styles/style.less';
|
||||||
|
|
||||||
|
// tslint:disable-next-line:no-unused-expression
|
||||||
|
injectGlobal`
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
class Routes extends React.Component<any> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/" component={Home} />
|
||||||
|
<Route path="/create" component={Create} />
|
||||||
|
<Route exact path="/proposals" component={Proposals} />
|
||||||
|
<Route path="/proposals/:id" component={Proposal} />
|
||||||
|
<Route path="/*" render={() => <Redirect to="/" />} />
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hot(module)(Routes);
|
|
@ -1,7 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Head from 'next/head';
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
import 'styles/style.less';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -12,20 +10,16 @@ export default class BasicHead extends React.Component<Props> {
|
||||||
const { children, title } = this.props;
|
const { children, title } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Head>
|
<Helmet>
|
||||||
<title>Grant.io - {title}</title>
|
<title>Grant.io - {title}</title>
|
||||||
{/*TODO - bundle*/}
|
<meta name={`${title} page`} content={`${title} page stuff`} />
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://use.fontawesome.com/releases/v5.2.0/css/all.css"
|
href="https://use.fontawesome.com/releases/v5.2.0/css/all.css"
|
||||||
integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ"
|
integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ"
|
||||||
crossOrigin="anonymous"
|
crossOrigin="anonymous"
|
||||||
/>
|
/>
|
||||||
|
</Helmet>
|
||||||
<link rel="stylesheet" href="/_next/static/style.css" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
</Head>
|
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'next/link';
|
import { Link } from 'react-router-dom';
|
||||||
import { Icon } from 'antd';
|
import { Icon } from 'antd';
|
||||||
import * as Styled from './styled';
|
import * as Styled from './styled';
|
||||||
|
|
||||||
|
@ -16,10 +16,8 @@ const CreateSuccess = ({ crowdFundCreatedAddress }: Props) => (
|
||||||
<h2>Contract was succesfully deployed!</h2>
|
<h2>Contract was succesfully deployed!</h2>
|
||||||
<div>
|
<div>
|
||||||
Your proposal is now live and on the blockchain!{' '}
|
Your proposal is now live and on the blockchain!{' '}
|
||||||
<Link href={`/proposals/${crowdFundCreatedAddress}`}>
|
<Link to={`/proposals/${crowdFundCreatedAddress}`}>Click here</Link> to check it
|
||||||
<a>Click here</a>
|
out.
|
||||||
</Link>{' '}
|
|
||||||
to check it out.
|
|
||||||
</div>
|
</div>
|
||||||
</Styled.SuccessText>
|
</Styled.SuccessText>
|
||||||
</Styled.Success>
|
</Styled.Success>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import React from 'react';
|
||||||
import { Input, DatePicker, Card, Icon, Alert, Checkbox } from 'antd';
|
import { Input, DatePicker, Card, Icon, Alert, Checkbox } from 'antd';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Link from 'next/link';
|
import { Link } from 'react-router-dom';
|
||||||
import * as Styled from './styled';
|
import * as Styled from './styled';
|
||||||
|
|
||||||
export default () => (
|
export default () => (
|
||||||
<Styled.Footer>
|
<Styled.Footer>
|
||||||
<Link href="/">
|
<Styled.Title>
|
||||||
<Styled.Title>Grant.io</Styled.Title>
|
<Link to="/">Grant.io</Link>
|
||||||
</Link>
|
</Styled.Title>
|
||||||
{/*<Styled.Links>
|
{/*<Styled.Links>
|
||||||
<Styled.Link>about</Styled.Link>
|
<Styled.Link>about</Styled.Link>
|
||||||
<Styled.Link>legal</Styled.Link>
|
<Styled.Link>legal</Styled.Link>
|
||||||
|
|
|
@ -10,18 +10,20 @@ export const Footer = styled.footer`
|
||||||
height: 140px;
|
height: 140px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Title = styled.a`
|
export const Title = styled.span`
|
||||||
font-size: 1.5rem;
|
a {
|
||||||
font-weight: 600;
|
font-size: 1.5rem;
|
||||||
margin-bottom: 0.5rem;
|
font-weight: 600;
|
||||||
color: #fff;
|
margin-bottom: 0.5rem;
|
||||||
transition: transform 100ms ease;
|
color: #fff;
|
||||||
|
transition: transform 100ms ease;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon } from 'antd';
|
import { Icon } from 'antd';
|
||||||
import Link from 'next/link';
|
import { Link } from 'react-router-dom';
|
||||||
import * as Styled from './styled';
|
import * as Styled from './styled';
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
|
@ -15,30 +15,28 @@ export default class Header extends React.Component<Props> {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Styled.Header isTransparent={isTransparent}>
|
<Styled.Header isTransparent={isTransparent}>
|
||||||
<div style={{ display: 'flex' }}>
|
<Styled.Button style={{ display: 'flex' }}>
|
||||||
<Link href="/proposals">
|
<Link to="/proposals">
|
||||||
<Styled.Button>
|
<Styled.ButtonIcon>
|
||||||
<Styled.ButtonIcon>
|
<Icon type="shop" />
|
||||||
<Icon type="shop" />
|
</Styled.ButtonIcon>
|
||||||
</Styled.ButtonIcon>
|
<Styled.ButtonText>Explore</Styled.ButtonText>
|
||||||
<Styled.ButtonText>Explore</Styled.ButtonText>
|
|
||||||
</Styled.Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</Styled.Button>
|
||||||
|
|
||||||
<Link href="/">
|
<Styled.Title>
|
||||||
<Styled.Title>Grant.io</Styled.Title>
|
<Link to="/">Grant.io</Link>
|
||||||
</Link>
|
</Styled.Title>
|
||||||
|
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Link href="/create">
|
<Styled.Button style={{ marginLeft: '1.5rem' }}>
|
||||||
<Styled.Button style={{ marginLeft: '1.5rem' }}>
|
<Link to="/create">
|
||||||
<Styled.ButtonIcon>
|
<Styled.ButtonIcon>
|
||||||
<Icon type="form" />
|
<Icon type="form" />
|
||||||
</Styled.ButtonIcon>
|
</Styled.ButtonIcon>
|
||||||
<Styled.ButtonText>Start a Proposal</Styled.ButtonText>
|
<Styled.ButtonText>Start a Proposal</Styled.ButtonText>
|
||||||
</Styled.Button>
|
</Link>
|
||||||
</Link>
|
</Styled.Button>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
||||||
{!isTransparent && <Styled.AlphaBanner>Alpha</Styled.AlphaBanner>}
|
{!isTransparent && <Styled.AlphaBanner>Alpha</Styled.AlphaBanner>}
|
||||||
|
|
|
@ -24,44 +24,48 @@ export const Header = styled<{ isTransparent: boolean }, 'header'>('header')`
|
||||||
box-shadow: ${(p: any) => (p.isTransparent ? 'none' : '0 1px 2px rgba(0, 0, 0, 0.3)')};
|
box-shadow: ${(p: any) => (p.isTransparent ? 'none' : '0 1px 2px rgba(0, 0, 0, 0.3)')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Title = styled.a`
|
export const Title = styled.span`
|
||||||
position: absolute;
|
a {
|
||||||
left: 50%;
|
position: absolute;
|
||||||
top: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
top: 50%;
|
||||||
font-size: 2.2rem;
|
transform: translate(-50%, -50%);
|
||||||
margin: 0;
|
font-size: 2.2rem;
|
||||||
color: inherit;
|
margin: 0;
|
||||||
letter-spacing: 0.08rem;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: transform 100ms ease;
|
|
||||||
flex-grow: 1;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
transform: translateY(-2px) translate(-50%, -50%);
|
letter-spacing: 0.08rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: transform 100ms ease;
|
||||||
|
flex-grow: 1;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
color: inherit;
|
||||||
|
transform: translateY(-2px) translate(-50%, -50%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Button = styled.a`
|
export const Button = styled.div`
|
||||||
display: block;
|
a {
|
||||||
background: none;
|
display: block;
|
||||||
padding: 0;
|
background: none;
|
||||||
font-size: 1.2rem;
|
padding: 0;
|
||||||
font-weight: 300;
|
font-size: 1.2rem;
|
||||||
color: inherit;
|
font-weight: 300;
|
||||||
letter-spacing: 0.05rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: transform 100ms ease;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
letter-spacing: 0.05rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 100ms ease;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,33 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import * as Styled from './styled';
|
import * as Styled from './styled';
|
||||||
import Link from 'next/link';
|
import { Redirect } from 'react-router-dom';
|
||||||
import { Icon } from 'antd';
|
import { Icon } from 'antd';
|
||||||
import AntWrap from 'components/AntWrap';
|
import AntWrap from 'components/AntWrap';
|
||||||
|
import TeamsSvg from 'static/images/intro-teams.svg';
|
||||||
|
import FundingSvg from 'static/images/intro-funding.svg';
|
||||||
|
import CommunitySvg from 'static/images/intro-community.svg';
|
||||||
|
|
||||||
const introBlobs = [
|
const introBlobs = [
|
||||||
{
|
{
|
||||||
image: 'static/images/intro-teams.svg',
|
Svg: TeamsSvg,
|
||||||
text: 'Developers and teams propose projects for improving the ecosystem',
|
text: 'Developers and teams propose projects for improving the ecosystem',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: 'static/images/intro-funding.svg',
|
Svg: FundingSvg,
|
||||||
text: 'Projects are funded by the community and paid as it’s built',
|
text: 'Projects are funded by the community and paid as it’s built',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: 'static/images/intro-community.svg',
|
Svg: CommunitySvg,
|
||||||
text: 'Open discussion and project updates bring devs and the community together',
|
text: 'Open discussion and project updates bring devs and the community together',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default class Home extends React.Component {
|
export default class Home extends React.Component {
|
||||||
|
state = { redirect: '' };
|
||||||
render() {
|
render() {
|
||||||
|
if (this.state.redirect) {
|
||||||
|
return <Redirect push to={this.state.redirect} />;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<AntWrap title="Home" isHeaderTransparent isFullScreen>
|
<AntWrap title="Home" isHeaderTransparent isFullScreen>
|
||||||
<Styled.Hero>
|
<Styled.Hero>
|
||||||
|
@ -29,12 +36,15 @@ export default class Home extends React.Component {
|
||||||
</Styled.HeroTitle>
|
</Styled.HeroTitle>
|
||||||
|
|
||||||
<Styled.HeroButtons>
|
<Styled.HeroButtons>
|
||||||
<Link href="/create">
|
<Styled.HeroButton
|
||||||
<Styled.HeroButton isPrimary>Propose a Project</Styled.HeroButton>
|
onClick={() => this.setState({ redirect: '/create' })}
|
||||||
</Link>
|
isPrimary
|
||||||
<Link href="/proposals">
|
>
|
||||||
<Styled.HeroButton>Explore Projects</Styled.HeroButton>
|
Propose a Project
|
||||||
</Link>
|
</Styled.HeroButton>
|
||||||
|
<Styled.HeroButton onClick={() => this.setState({ redirect: '/proposals' })}>
|
||||||
|
Explore Projects
|
||||||
|
</Styled.HeroButton>
|
||||||
</Styled.HeroButtons>
|
</Styled.HeroButtons>
|
||||||
|
|
||||||
<Styled.HeroScroll>
|
<Styled.HeroScroll>
|
||||||
|
@ -52,7 +62,7 @@ export default class Home extends React.Component {
|
||||||
<Styled.IntroBlobs>
|
<Styled.IntroBlobs>
|
||||||
{introBlobs.map((blob, i) => (
|
{introBlobs.map((blob, i) => (
|
||||||
<Styled.IntroBlob key={i}>
|
<Styled.IntroBlob key={i}>
|
||||||
<img src={blob.image} />
|
<blob.Svg />
|
||||||
<p>{blob.text}</p>
|
<p>{blob.text}</p>
|
||||||
</Styled.IntroBlob>
|
</Styled.IntroBlob>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -142,7 +142,7 @@ export const IntroBlob = styled.div`
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
svg {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { connect } from 'react-redux';
|
||||||
import { compose } from 'recompose';
|
import { compose } from 'recompose';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { web3Actions } from 'modules/web3';
|
import { web3Actions } from 'modules/web3';
|
||||||
import { withRouter } from 'next/router';
|
import { withRouter } from 'react-router';
|
||||||
import Web3Container, { Web3RenderProps } from 'lib/Web3Container';
|
import Web3Container, { Web3RenderProps } from 'lib/Web3Container';
|
||||||
import ShortAddress from 'components/ShortAddress';
|
import ShortAddress from 'components/ShortAddress';
|
||||||
import UnitDisplay from 'components/UnitDisplay';
|
import UnitDisplay from 'components/UnitDisplay';
|
||||||
|
|
|
@ -18,7 +18,7 @@ import GovernanceTab from './Governance';
|
||||||
import ContributorsTab from './Contributors';
|
import ContributorsTab from './Contributors';
|
||||||
// import CommunityTab from './Community';
|
// import CommunityTab from './Community';
|
||||||
import * as Styled from './styled';
|
import * as Styled from './styled';
|
||||||
import { withRouter } from 'next/router';
|
import { withRouter } from 'react-router';
|
||||||
import Web3Container from 'lib/Web3Container';
|
import Web3Container from 'lib/Web3Container';
|
||||||
import { web3Actions } from 'modules/web3';
|
import { web3Actions } from 'modules/web3';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Progress, Icon, Spin } from 'antd';
|
import { Progress, Icon, Spin } from 'antd';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Link from 'next/link';
|
import { Redirect } from 'react-router-dom';
|
||||||
import { CATEGORY_UI } from 'api/constants';
|
import { CATEGORY_UI } from 'api/constants';
|
||||||
import { ProposalWithCrowdFund } from 'modules/proposals/reducers';
|
import { ProposalWithCrowdFund } from 'modules/proposals/reducers';
|
||||||
import * as Styled from './styled';
|
import * as Styled from './styled';
|
||||||
|
@ -18,7 +18,11 @@ interface Props extends ProposalWithCrowdFund {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProposalCard extends React.Component<Props> {
|
class ProposalCard extends React.Component<Props> {
|
||||||
|
state = { redirect: '' };
|
||||||
render() {
|
render() {
|
||||||
|
if (this.state.redirect) {
|
||||||
|
return <Redirect push to={this.state.redirect} />;
|
||||||
|
}
|
||||||
const { title, proposalId, category, dateCreated, web3, crowdFund } = this.props;
|
const { title, proposalId, category, dateCreated, web3, crowdFund } = this.props;
|
||||||
const team = [...this.props.team].reverse();
|
const team = [...this.props.team].reverse();
|
||||||
|
|
||||||
|
@ -26,48 +30,47 @@ class ProposalCard extends React.Component<Props> {
|
||||||
return <Spin />;
|
return <Spin />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Link href={`/proposals/${proposalId}`}>
|
<Styled.Container
|
||||||
<Styled.Container>
|
onClick={() => this.setState({ redirect: `/proposals/${proposalId}` })}
|
||||||
<Styled.Title>{title}</Styled.Title>
|
>
|
||||||
<Styled.Funding>
|
<Styled.Title>{title}</Styled.Title>
|
||||||
<Styled.FundingRaised>
|
<Styled.Funding>
|
||||||
<UnitDisplay value={crowdFund.funded} symbol="ETH" />{' '}
|
<Styled.FundingRaised>
|
||||||
<small>raised</small> of{' '}
|
<UnitDisplay value={crowdFund.funded} symbol="ETH" /> <small>raised</small>{' '}
|
||||||
<UnitDisplay value={crowdFund.target} symbol="ETH" /> goal
|
of <UnitDisplay value={crowdFund.target} symbol="ETH" /> goal
|
||||||
</Styled.FundingRaised>
|
</Styled.FundingRaised>
|
||||||
<Styled.FundingPercent isFunded={crowdFund.percentFunded >= 100}>
|
<Styled.FundingPercent isFunded={crowdFund.percentFunded >= 100}>
|
||||||
{crowdFund.percentFunded}%
|
{crowdFund.percentFunded}%
|
||||||
</Styled.FundingPercent>
|
</Styled.FundingPercent>
|
||||||
</Styled.Funding>
|
</Styled.Funding>
|
||||||
<Progress
|
<Progress
|
||||||
percent={crowdFund.percentFunded}
|
percent={crowdFund.percentFunded}
|
||||||
status={crowdFund.percentFunded >= 100 ? 'success' : 'active'}
|
status={crowdFund.percentFunded >= 100 ? 'success' : 'active'}
|
||||||
showInfo={false}
|
showInfo={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Styled.Team>
|
<Styled.Team>
|
||||||
<Styled.TeamName>
|
<Styled.TeamName>
|
||||||
{team[0].accountAddress}{' '}
|
{team[0].accountAddress}{' '}
|
||||||
{team.length > 1 && <small>+{team.length - 1} other</small>}
|
{team.length > 1 && <small>+{team.length - 1} other</small>}
|
||||||
</Styled.TeamName>
|
</Styled.TeamName>
|
||||||
<Styled.TeamAvatars>
|
<Styled.TeamAvatars>
|
||||||
{team.reverse().map(u => (
|
{team.reverse().map(u => (
|
||||||
<Identicon key={u.userid} address={u.accountAddress} />
|
<Identicon key={u.userid} address={u.accountAddress} />
|
||||||
))}
|
))}
|
||||||
</Styled.TeamAvatars>
|
</Styled.TeamAvatars>
|
||||||
</Styled.Team>
|
</Styled.Team>
|
||||||
<Styled.ContractAddress>{proposalId}</Styled.ContractAddress>
|
<Styled.ContractAddress>{proposalId}</Styled.ContractAddress>
|
||||||
|
|
||||||
<Styled.Info>
|
<Styled.Info>
|
||||||
<Styled.InfoCategory style={{ color: CATEGORY_UI[category].color }}>
|
<Styled.InfoCategory style={{ color: CATEGORY_UI[category].color }}>
|
||||||
<Icon type={CATEGORY_UI[category].icon} /> {CATEGORY_UI[category].label}
|
<Icon type={CATEGORY_UI[category].icon} /> {CATEGORY_UI[category].label}
|
||||||
</Styled.InfoCategory>
|
</Styled.InfoCategory>
|
||||||
<Styled.InfoCreated>
|
<Styled.InfoCreated>
|
||||||
{moment(dateCreated * 1000).fromNow()}
|
{moment(dateCreated * 1000).fromNow()}
|
||||||
</Styled.InfoCreated>
|
</Styled.InfoCreated>
|
||||||
</Styled.Info>
|
</Styled.Info>
|
||||||
</Styled.Container>
|
</Styled.Container>
|
||||||
</Link>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import '@babel/polyfill';
|
||||||
|
import React from 'react';
|
||||||
|
import { hot } from 'react-hot-loader';
|
||||||
|
import { hydrate } from 'react-dom';
|
||||||
|
import { loadComponents } from 'loadable-components';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { configureStore } from 'store/configure';
|
||||||
|
import Routes from './Routes';
|
||||||
|
|
||||||
|
const initialState = window && (window as any).__PRELOADED_STATE__;
|
||||||
|
const store = configureStore(initialState);
|
||||||
|
|
||||||
|
const App = hot(module)(() => (
|
||||||
|
<Provider store={store}>
|
||||||
|
<Router>
|
||||||
|
<Routes />
|
||||||
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
));
|
||||||
|
|
||||||
|
loadComponents().then(() => {
|
||||||
|
hydrate(<App />, document.getElementById('app'));
|
||||||
|
});
|
|
@ -2,16 +2,16 @@ import React, { Component } from 'react';
|
||||||
import Web3Page from 'components/Web3Page';
|
import Web3Page from 'components/Web3Page';
|
||||||
import Proposal from 'components/Proposal';
|
import Proposal from 'components/Proposal';
|
||||||
|
|
||||||
import { WithRouterProps, withRouter } from 'next/router';
|
import { withRouter, RouteComponentProps } from 'react-router';
|
||||||
|
|
||||||
type RouteProps = WithRouterProps;
|
type RouteProps = RouteComponentProps<any>;
|
||||||
|
|
||||||
class ProposalPage extends Component<RouteProps> {
|
class ProposalPage extends Component<RouteProps> {
|
||||||
constructor(props: RouteProps) {
|
constructor(props: RouteProps) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
render() {
|
render() {
|
||||||
const proposalId = this.props.router.query.id as string;
|
const proposalId = this.props.match.params.id;
|
||||||
return (
|
return (
|
||||||
<Web3Page
|
<Web3Page
|
||||||
title={`Proposal ${proposalId}`}
|
title={`Proposal ${proposalId}`}
|
||||||
|
|
|
@ -35,5 +35,13 @@ export function configureStore(
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// store.runSagaTask();
|
// store.runSagaTask();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./reducers', () =>
|
||||||
|
store.replaceReducer(require('./reducers').default),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Nunito Sans';
|
font-family: 'Nunito Sans';
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url('/_next/static/fonts/NunitoSans-Regular.ttf') format('ttf');
|
src: url('../static/fonts/NunitoSans-Regular.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Nunito Sans';
|
font-family: 'Nunito Sans';
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
src: url('/_next/static/fonts/NunitoSans-SemiBold.ttf') format('ttf');
|
src: url('../static/fonts/NunitoSans-SemiBold.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Nunito Sans';
|
font-family: 'Nunito Sans';
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
src: url('/_next/static/fonts/NunitoSans-Light.ttf') format('ttf');
|
src: url('../static/fonts/NunitoSans-Light.ttf') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
declare module 'express-manifest-helpers';
|
|
@ -0,0 +1 @@
|
||||||
|
declare module 'loadable-components/server';
|
|
@ -0,0 +1,52 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const paths = require('./paths');
|
||||||
|
|
||||||
|
delete require.cache[require.resolve('./paths')];
|
||||||
|
|
||||||
|
if (!process.env.NODE_ENV) {
|
||||||
|
throw new Error(
|
||||||
|
'The process.env.NODE_ENV environment variable is required but was not specified.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
|
||||||
|
const dotenvFiles = [
|
||||||
|
`${paths.dotenv}.${process.env.NODE_ENV}.local`,
|
||||||
|
`${paths.dotenv}.${process.env.NODE_ENV}`,
|
||||||
|
process.env.NODE_ENV !== 'test' && `${paths.dotenv}.local`,
|
||||||
|
paths.dotenv,
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
dotenvFiles.forEach(dotenvFile => {
|
||||||
|
if (fs.existsSync(dotenvFile)) {
|
||||||
|
require('dotenv').config({
|
||||||
|
path: dotenvFile,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const appDirectory = fs.realpathSync(process.cwd());
|
||||||
|
process.env.NODE_PATH = (process.env.NODE_PATH || '')
|
||||||
|
.split(path.delimiter)
|
||||||
|
.filter(folder => folder && !path.isAbsolute(folder))
|
||||||
|
.map(folder => path.resolve(appDirectory, folder))
|
||||||
|
.join(path.delimiter);
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
const raw = {
|
||||||
|
PORT: process.env.PORT || 3000,
|
||||||
|
NODE_ENV: process.env.NODE_ENV || 'development',
|
||||||
|
BACKEND_URL: process.env.BACKEND_URL || 'http://localhost:5000',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stringify all values so we can feed into Webpack DefinePlugin
|
||||||
|
const stringified = {
|
||||||
|
'process.env': Object.keys(raw).reduce((env, key) => {
|
||||||
|
env[key] = JSON.stringify(raw[key]);
|
||||||
|
return env;
|
||||||
|
}, {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return { raw, stringified };
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const findRoot = require('find-root');
|
||||||
|
|
||||||
|
const appDirectory = fs.realpathSync(process.cwd());
|
||||||
|
// truffle exec cwd moves to called js, so make sure we are on root
|
||||||
|
const appRoot = findRoot(appDirectory);
|
||||||
|
const resolveApp = relativePath => path.resolve(appRoot, relativePath);
|
||||||
|
|
||||||
|
const paths = {
|
||||||
|
clientBuild: resolveApp('build/client'),
|
||||||
|
contractsBase: resolveApp('../contract'),
|
||||||
|
contractsBuild: resolveApp('../contract/build/contracts'),
|
||||||
|
dotenv: resolveApp('.env'),
|
||||||
|
logs: resolveApp('logs'),
|
||||||
|
publicPath: '/static/',
|
||||||
|
serverBuild: resolveApp('build/server'),
|
||||||
|
srcClient: resolveApp('client'),
|
||||||
|
srcServer: resolveApp('server'),
|
||||||
|
};
|
||||||
|
|
||||||
|
paths.resolveModules = [paths.srcClient, paths.srcServer, 'node_modules'];
|
||||||
|
|
||||||
|
module.exports = paths;
|
|
@ -0,0 +1,74 @@
|
||||||
|
const path = require('path');
|
||||||
|
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
const paths = require('../paths');
|
||||||
|
const { client: clientLoaders } = require('./loaders');
|
||||||
|
const resolvers = require('./resolvers');
|
||||||
|
const plugins = require('./plugins');
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'client',
|
||||||
|
target: 'web',
|
||||||
|
entry: {
|
||||||
|
bundle: [path.join(paths.srcClient, 'index.tsx')],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.join(paths.clientBuild, paths.publicPath),
|
||||||
|
filename: 'bundle.js',
|
||||||
|
publicPath: paths.publicPath,
|
||||||
|
chunkFilename: isDev ? '[name].chunk.js' : '[name].[chunkhash:8].chunk.js',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: clientLoaders,
|
||||||
|
},
|
||||||
|
resolve: { ...resolvers },
|
||||||
|
plugins: [...plugins.shared, ...plugins.client],
|
||||||
|
node: {
|
||||||
|
dgram: 'empty',
|
||||||
|
fs: 'empty',
|
||||||
|
net: 'empty',
|
||||||
|
tls: 'empty',
|
||||||
|
child_process: 'empty',
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimizer: [
|
||||||
|
new UglifyJsPlugin({
|
||||||
|
cache: true,
|
||||||
|
parallel: true,
|
||||||
|
sourceMap: true,
|
||||||
|
uglifyOptions: {
|
||||||
|
// otherwise BN typecheck gets mangled during minification
|
||||||
|
keep_fnames: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
namedModules: true,
|
||||||
|
noEmitOnErrors: false,
|
||||||
|
// concatenateModules: true,
|
||||||
|
// below settings bundle all vendor css in one file
|
||||||
|
// this allows SSR to render a reference to the hashed css
|
||||||
|
// if commons is split by module then flickering may occur on load
|
||||||
|
splitChunks: {
|
||||||
|
cacheGroups: {
|
||||||
|
commons: {
|
||||||
|
test: /[\\/]node_modules[\\/]/,
|
||||||
|
name: 'vendor',
|
||||||
|
chunks: 'all',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
cached: false,
|
||||||
|
cachedAssets: false,
|
||||||
|
chunks: false,
|
||||||
|
chunkModules: false,
|
||||||
|
colors: true,
|
||||||
|
hash: false,
|
||||||
|
modules: false,
|
||||||
|
reasons: false,
|
||||||
|
timings: true,
|
||||||
|
version: false,
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
const baseConfig = require('./client.base');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const WriteFileWebpackPlugin = require('write-file-webpack-plugin');
|
||||||
|
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
...baseConfig,
|
||||||
|
plugins: [
|
||||||
|
new WriteFileWebpackPlugin(),
|
||||||
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
...baseConfig.plugins,
|
||||||
|
new ForkTsCheckerWebpackPlugin(),
|
||||||
|
new BundleAnalyzerPlugin({
|
||||||
|
analyzerMode: 'static',
|
||||||
|
reportFilename: '../../dev-client-bundle-analysis.html',
|
||||||
|
defaultSizes: 'gzip',
|
||||||
|
openAnalyzer: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
mode: 'development',
|
||||||
|
devtool: 'cheap-module-inline-source-map',
|
||||||
|
performance: {
|
||||||
|
hints: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
|
@ -0,0 +1,21 @@
|
||||||
|
const baseConfig = require('./client.base');
|
||||||
|
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
...baseConfig,
|
||||||
|
plugins: [
|
||||||
|
...baseConfig.plugins,
|
||||||
|
new BundleAnalyzerPlugin({
|
||||||
|
analyzerMode: 'static',
|
||||||
|
reportFilename: '../../prod-client-bundle-analysis.html',
|
||||||
|
defaultSizes: 'gzip',
|
||||||
|
openAnalyzer: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
mode: 'production',
|
||||||
|
devtool: 'source-map',
|
||||||
|
};
|
||||||
|
|
||||||
|
config.output.filename = 'bundle.[hash:8].js';
|
||||||
|
|
||||||
|
module.exports = config;
|
|
@ -0,0 +1,8 @@
|
||||||
|
module.exports = (env = 'production') => {
|
||||||
|
if (env === 'development' || env === 'dev') {
|
||||||
|
process.env.NODE_ENV = 'development';
|
||||||
|
return [require('./client.dev'), require('./server.dev')];
|
||||||
|
}
|
||||||
|
process.env.NODE_ENV = 'production';
|
||||||
|
return [require('./client.prod'), require('./server.prod')];
|
||||||
|
};
|
|
@ -0,0 +1,229 @@
|
||||||
|
const _ = require('lodash');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
const babelPresets = [
|
||||||
|
'@babel/react',
|
||||||
|
// '@babel/typescript', (using ts-loader)
|
||||||
|
['@babel/env', { useBuiltIns: 'entry', modules: false }],
|
||||||
|
];
|
||||||
|
|
||||||
|
const lessLoader = {
|
||||||
|
loader: 'less-loader',
|
||||||
|
options: { javascriptEnabled: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
const tsBabelLoaderClient = {
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
plugins: [
|
||||||
|
['styled-components', { ssr: true, displayName: true }],
|
||||||
|
'dynamic-import-webpack', // for client
|
||||||
|
'loadable-components/babel',
|
||||||
|
'react-hot-loader/babel',
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-proposal-class-properties',
|
||||||
|
['import', { libraryName: 'antd', style: false }],
|
||||||
|
],
|
||||||
|
presets: ['@babel/react', ['@babel/env', { useBuiltIns: 'entry' }]],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: { transpileOnly: isDev },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const tsBabelLoaderServer = {
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
plugins: [
|
||||||
|
['styled-components', { ssr: true, displayName: true }],
|
||||||
|
'dynamic-import-node', // for server
|
||||||
|
'loadable-components/babel',
|
||||||
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
|
'@babel/plugin-proposal-class-properties',
|
||||||
|
['import', { libraryName: 'antd', style: false }],
|
||||||
|
],
|
||||||
|
presets: [
|
||||||
|
'@babel/react',
|
||||||
|
['@babel/env', { useBuiltIns: 'entry', targets: { node: 'current' } }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
loader: 'ts-loader',
|
||||||
|
options: { transpileOnly: isDev },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const cssLoaderClient = {
|
||||||
|
test: /\.css$/,
|
||||||
|
exclude: [/node_modules/],
|
||||||
|
use: [
|
||||||
|
isDev && 'style-loader',
|
||||||
|
!isDev && MiniCssExtractPlugin.loader,
|
||||||
|
{
|
||||||
|
loader: 'css-loader',
|
||||||
|
},
|
||||||
|
].filter(Boolean),
|
||||||
|
};
|
||||||
|
|
||||||
|
const lessLoaderClient = {
|
||||||
|
test: /\.less$/,
|
||||||
|
exclude: [/node_modules/],
|
||||||
|
use: [...cssLoaderClient.use, lessLoader],
|
||||||
|
};
|
||||||
|
|
||||||
|
const cssLoaderServer = {
|
||||||
|
test: /\.css$/,
|
||||||
|
exclude: [/node_modules/],
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'css-loader/locals',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const lessLoaderServer = {
|
||||||
|
test: /\.less$/,
|
||||||
|
exclude: [/node_modules/],
|
||||||
|
use: [...cssLoaderServer.use, lessLoader],
|
||||||
|
};
|
||||||
|
|
||||||
|
const urlLoaderClient = {
|
||||||
|
test: /\.(png|jpe?g|gif)$/,
|
||||||
|
loader: require.resolve('url-loader'),
|
||||||
|
options: {
|
||||||
|
limit: 2048,
|
||||||
|
name: 'assets/[name].[hash:8].[ext]',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const urlLoaderServer = {
|
||||||
|
...urlLoaderClient,
|
||||||
|
options: {
|
||||||
|
...urlLoaderClient.options,
|
||||||
|
emitFile: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileLoaderClient = {
|
||||||
|
// WARNING: this will catch all files except those below
|
||||||
|
exclude: [/\.(js|ts|tsx|css|less|mjs|html|json|ejs)$/],
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: {
|
||||||
|
name: 'assets/[name].[hash:8].[ext]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileLoaderServer = _.defaultsDeep(
|
||||||
|
{
|
||||||
|
use: [{ options: { emitFile: false } }],
|
||||||
|
},
|
||||||
|
fileLoaderClient,
|
||||||
|
);
|
||||||
|
|
||||||
|
const svgLoaderClient = {
|
||||||
|
test: /\.svg$/,
|
||||||
|
issuer: {
|
||||||
|
test: /\.tsx?$/,
|
||||||
|
},
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: '@svgr/webpack',
|
||||||
|
options: {
|
||||||
|
svgoConfig: {
|
||||||
|
plugins: [{ inlineStyles: { onlyMatchedOnce: false } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
], // svg -> react component
|
||||||
|
};
|
||||||
|
|
||||||
|
const svgLoaderServer = svgLoaderClient;
|
||||||
|
|
||||||
|
// Write css files from node_modules to its own vendor.css file
|
||||||
|
const externalCssLoaderClient = {
|
||||||
|
test: /\.css$/,
|
||||||
|
include: [/node_modules/],
|
||||||
|
use: [
|
||||||
|
isDev && 'style-loader',
|
||||||
|
!isDev && MiniCssExtractPlugin.loader,
|
||||||
|
'css-loader',
|
||||||
|
].filter(Boolean),
|
||||||
|
};
|
||||||
|
|
||||||
|
const externalLessLoaderClient = {
|
||||||
|
test: /\.less$/,
|
||||||
|
include: [/node_modules/],
|
||||||
|
use: [
|
||||||
|
isDev && 'style-loader',
|
||||||
|
!isDev && MiniCssExtractPlugin.loader,
|
||||||
|
'css-loader',
|
||||||
|
lessLoader,
|
||||||
|
].filter(Boolean),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Server build needs a loader to handle external .css files
|
||||||
|
const externalCssLoaderServer = {
|
||||||
|
test: /\.css$/,
|
||||||
|
include: [/node_modules/],
|
||||||
|
loader: 'css-loader/locals',
|
||||||
|
};
|
||||||
|
|
||||||
|
const externalLessLoaderServer = {
|
||||||
|
test: /\.less$/,
|
||||||
|
include: [/node_modules/],
|
||||||
|
use: ['css-loader/locals', lessLoader],
|
||||||
|
};
|
||||||
|
|
||||||
|
const client = [
|
||||||
|
{
|
||||||
|
// oneOf: first matching rule takes all
|
||||||
|
oneOf: [
|
||||||
|
tsBabelLoaderClient,
|
||||||
|
cssLoaderClient,
|
||||||
|
lessLoaderClient,
|
||||||
|
svgLoaderClient,
|
||||||
|
urlLoaderClient,
|
||||||
|
fileLoaderClient,
|
||||||
|
externalCssLoaderClient,
|
||||||
|
externalLessLoaderClient,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const server = [
|
||||||
|
{
|
||||||
|
// oneOf: first matching rule takes all
|
||||||
|
oneOf: [
|
||||||
|
tsBabelLoaderServer,
|
||||||
|
cssLoaderServer,
|
||||||
|
lessLoaderServer,
|
||||||
|
svgLoaderServer,
|
||||||
|
urlLoaderServer,
|
||||||
|
fileLoaderServer,
|
||||||
|
externalCssLoaderServer,
|
||||||
|
externalLessLoaderServer,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
client,
|
||||||
|
server,
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
const ModuleDependencyWarning = require('webpack/lib/ModuleDependencyWarning');
|
||||||
|
|
||||||
|
// supress unfortunate warnings due to transpileOnly=true and certain ts export patterns
|
||||||
|
// https://github.com/TypeStrong/ts-loader/issues/653#issuecomment-390889335
|
||||||
|
// https://github.com/TypeStrong/ts-loader/issues/751
|
||||||
|
|
||||||
|
module.exports = class IgnoreNotFoundExportPlugin {
|
||||||
|
apply(compiler) {
|
||||||
|
const messageRegExp = /export '.*'( \(reexported as '.*'\))? was not found in/;
|
||||||
|
function doneHook(stats) {
|
||||||
|
stats.compilation.warnings = stats.compilation.warnings.filter(function(warn) {
|
||||||
|
if (warn instanceof ModuleDependencyWarning && messageRegExp.test(warn.message)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (compiler.hooks) {
|
||||||
|
compiler.hooks.done.tap('IgnoreNotFoundExportPlugin', doneHook);
|
||||||
|
} else {
|
||||||
|
compiler.plugin('done', doneHook);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,60 @@
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||||
|
const { StatsWriterPlugin } = require('webpack-stats-plugin');
|
||||||
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
const ModuleDependencyWarning = require('./module-dependency-warning');
|
||||||
|
|
||||||
|
const env = require('../env')();
|
||||||
|
|
||||||
|
const shared = [new ModuleDependencyWarning()];
|
||||||
|
|
||||||
|
const client = [
|
||||||
|
new webpack.DefinePlugin(env.stringified),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
__SERVER__: 'false',
|
||||||
|
__CLIENT__: 'true',
|
||||||
|
}),
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename:
|
||||||
|
process.env.NODE_ENV === 'development' ? '[name].css' : '[name].[hash:8].css',
|
||||||
|
chunkFilename:
|
||||||
|
process.env.NODE_ENV === 'development'
|
||||||
|
? '[name].chunk.css'
|
||||||
|
: '[name].[chunkhash:8].chunk.css',
|
||||||
|
}),
|
||||||
|
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||||
|
new ManifestPlugin({
|
||||||
|
fileName: 'manifest.json',
|
||||||
|
writeToFileEmit: true, // fixes initial-only writing from WriteFileWebpackPlugin
|
||||||
|
}),
|
||||||
|
new StatsWriterPlugin({
|
||||||
|
fileName: 'stats.json',
|
||||||
|
fields: null,
|
||||||
|
transform(data) {
|
||||||
|
const trans = {};
|
||||||
|
trans.modules = data.modules.map(m => ({
|
||||||
|
id: m.id,
|
||||||
|
chunks: m.chunks,
|
||||||
|
reasons: m.reasons,
|
||||||
|
}));
|
||||||
|
trans.chunks = data.chunks.map(c => ({
|
||||||
|
id: c.id,
|
||||||
|
files: c.files,
|
||||||
|
}));
|
||||||
|
return JSON.stringify(trans, null, 2);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const server = [
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
__SERVER__: 'true',
|
||||||
|
__CLIENT__: 'false',
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
shared,
|
||||||
|
client,
|
||||||
|
server,
|
||||||
|
};
|
|
@ -0,0 +1,20 @@
|
||||||
|
const paths = require('../paths');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extensions: ['.ts', '.tsx', '.js', '.mjs', '.json'],
|
||||||
|
modules: paths.resolveModules,
|
||||||
|
// tsconfig.compilerOptions.paths should sync with these
|
||||||
|
alias: {
|
||||||
|
contracts: paths.contractsBuild, // truffle build contracts dir
|
||||||
|
api: `${paths.srcClient}/api`,
|
||||||
|
components: `${paths.srcClient}/components`,
|
||||||
|
lib: `${paths.srcClient}/lib`,
|
||||||
|
modules: `${paths.srcClient}/modules`,
|
||||||
|
pages: `${paths.srcClient}/pages`,
|
||||||
|
store: `${paths.srcClient}/store`,
|
||||||
|
styles: `${paths.srcClient}/styles`,
|
||||||
|
typings: `${paths.srcClient}/typings`,
|
||||||
|
utils: `${paths.srcClient}/utils`,
|
||||||
|
web3interact: `${paths.srcClient}/web3interact`,
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
const path = require('path');
|
||||||
|
const nodeExternals = require('webpack-node-externals');
|
||||||
|
|
||||||
|
const paths = require('../paths');
|
||||||
|
const { server: serverLoaders } = require('./loaders');
|
||||||
|
const resolvers = require('./resolvers');
|
||||||
|
const plugins = require('./plugins');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'server',
|
||||||
|
target: 'node',
|
||||||
|
entry: {
|
||||||
|
server: [path.join(paths.srcServer, 'index.tsx')],
|
||||||
|
},
|
||||||
|
externals: [
|
||||||
|
nodeExternals({
|
||||||
|
// we still want imported css from external files to be bundled otherwise 3rd party packages
|
||||||
|
// which require us to include their own css would not work properly
|
||||||
|
whitelist: [/\.css$/, /^antd.*style$/],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
path: paths.serverBuild,
|
||||||
|
filename: 'server.js',
|
||||||
|
publicPath: paths.publicPath,
|
||||||
|
// libraryTarget: 'commonjs2',
|
||||||
|
},
|
||||||
|
resolve: { ...resolvers },
|
||||||
|
module: {
|
||||||
|
rules: serverLoaders,
|
||||||
|
},
|
||||||
|
plugins: [...plugins.shared, ...plugins.server],
|
||||||
|
stats: {
|
||||||
|
colors: true,
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
const baseConfig = require('./server.base');
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const WriteFileWebpackPlugin = require('write-file-webpack-plugin');
|
||||||
|
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
...baseConfig,
|
||||||
|
plugins: [
|
||||||
|
new WriteFileWebpackPlugin(),
|
||||||
|
...baseConfig.plugins,
|
||||||
|
new ForkTsCheckerWebpackPlugin(),
|
||||||
|
],
|
||||||
|
mode: 'development',
|
||||||
|
performance: {
|
||||||
|
hints: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
|
@ -0,0 +1,6 @@
|
||||||
|
const config = require('./server.base');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...config,
|
||||||
|
mode: 'production',
|
||||||
|
};
|
|
@ -5,10 +5,10 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "NODE_ENV=production ANALYZE=true next build ./client",
|
"analyze": "NODE_ENV=production ANALYZE=true next build ./client",
|
||||||
"build": "NODE_ENV=production next build ./client",
|
"build": "cross-env NODE_ENV=production node bin/build.js",
|
||||||
"dev": "rm -rf ./node_modules/.cache && cross-env BACKEND_URL=http://localhost:5000 node server.js",
|
"dev": "rm -rf ./node_modules/.cache && cross-env NODE_ENV=development BACKEND_URL=http://localhost:5000 node bin/dev.js",
|
||||||
"lint": "tslint --project ./tsconfig.json --config ./tslint.json -e \"**/build/**\"",
|
"lint": "tslint --project ./tsconfig.json --config ./tslint.json -e \"**/build/**\"",
|
||||||
"start": "NODE_ENV=production node server.js",
|
"start": "NODE_ENV=production node build/server/server.js",
|
||||||
"tsc": "tsc",
|
"tsc": "tsc",
|
||||||
"link-contracts": "cd client/lib && ln -s ../../build/contracts contracts",
|
"link-contracts": "cd client/lib && ln -s ../../build/contracts contracts",
|
||||||
"ganache": "ganache-cli -b 5",
|
"ganache": "ganache-cli -b 5",
|
||||||
|
@ -27,7 +27,25 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/core": "^7.0.1",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||||
|
"@babel/plugin-proposal-object-rest-spread": "^7.0.0",
|
||||||
|
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||||
|
"@babel/plugin-transform-modules-commonjs": "^7.0.0",
|
||||||
|
"@babel/polyfill": "^7.0.0",
|
||||||
|
"@babel/preset-env": "^7.0.0",
|
||||||
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
"@babel/preset-typescript": "^7.0.0",
|
||||||
|
"@babel/register": "^7.0.0",
|
||||||
|
"@svgr/webpack": "^2.4.0",
|
||||||
|
"@types/classnames": "^2.2.6",
|
||||||
|
"@types/cors": "^2.8.4",
|
||||||
|
"@types/dotenv": "^4.0.3",
|
||||||
|
"@types/express": "^4.16.0",
|
||||||
|
"@types/express-winston": "^3.0.0",
|
||||||
"@types/headroom": "^0.7.31",
|
"@types/headroom": "^0.7.31",
|
||||||
|
"@types/history": "^4.7.0",
|
||||||
|
"@types/i18next": "^8.4.5",
|
||||||
"@types/js-cookie": "2.1.0",
|
"@types/js-cookie": "2.1.0",
|
||||||
"@types/jwt-decode": "^2.2.1",
|
"@types/jwt-decode": "^2.2.1",
|
||||||
"@types/lodash": "^4.14.112",
|
"@types/lodash": "^4.14.112",
|
||||||
|
@ -39,8 +57,17 @@
|
||||||
"@types/react": "16.3.16",
|
"@types/react": "16.3.16",
|
||||||
"@types/react-document-title": "^2.0.2",
|
"@types/react-document-title": "^2.0.2",
|
||||||
"@types/react-dom": "^16.0.6",
|
"@types/react-dom": "^16.0.6",
|
||||||
|
"@types/react-helmet": "^5.0.7",
|
||||||
|
"@types/react-i18next": "^7.8.2",
|
||||||
"@types/react-redux": "^6.0.2",
|
"@types/react-redux": "^6.0.2",
|
||||||
|
"@types/react-router": "^4.0.31",
|
||||||
|
"@types/react-router-dom": "^4.3.1",
|
||||||
"@types/recompose": "^0.26.1",
|
"@types/recompose": "^0.26.1",
|
||||||
|
"@types/redux-actions": "^2.3.0",
|
||||||
|
"@types/redux-logger": "^3.0.6",
|
||||||
|
"@types/webpack": "^4.4.11",
|
||||||
|
"@types/webpack-env": "^1.13.6",
|
||||||
|
"@types/winston": "^2.4.4",
|
||||||
"@zeit/next-css": "^0.2.0",
|
"@zeit/next-css": "^0.2.0",
|
||||||
"@zeit/next-less": "^0.3.0",
|
"@zeit/next-less": "^0.3.0",
|
||||||
"@zeit/next-sass": "^0.2.0",
|
"@zeit/next-sass": "^0.2.0",
|
||||||
|
@ -48,6 +75,10 @@
|
||||||
"ant-design-pro": "^2.0.0-beta.2",
|
"ant-design-pro": "^2.0.0-beta.2",
|
||||||
"antd": "^3.7.1",
|
"antd": "^3.7.1",
|
||||||
"axios": "^0.18.0",
|
"axios": "^0.18.0",
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
|
"babel-loader": "^8.0.2",
|
||||||
|
"babel-plugin-dynamic-import-node": "^2.1.0",
|
||||||
|
"babel-plugin-dynamic-import-webpack": "^1.0.2",
|
||||||
"babel-plugin-import": "^1.8.0",
|
"babel-plugin-import": "^1.8.0",
|
||||||
"babel-plugin-inline-dotenv": "^1.1.2",
|
"babel-plugin-inline-dotenv": "^1.1.2",
|
||||||
"babel-plugin-module-resolver": "^3.1.1",
|
"babel-plugin-module-resolver": "^3.1.1",
|
||||||
|
@ -56,26 +87,37 @@
|
||||||
"babel-register": "^6.26.0",
|
"babel-register": "^6.26.0",
|
||||||
"babel-runtime": "^6.26.0",
|
"babel-runtime": "^6.26.0",
|
||||||
"bn.js": "4.11.8",
|
"bn.js": "4.11.8",
|
||||||
|
"body-parser": "^1.18.3",
|
||||||
|
"chalk": "^2.4.1",
|
||||||
"cookie-parser": "^1.4.3",
|
"cookie-parser": "^1.4.3",
|
||||||
|
"cors": "^2.8.4",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"dotenv": "6.0.0",
|
"css-loader": "^1.0.0",
|
||||||
|
"dotenv": "^6.0.0",
|
||||||
"drizzle": "^1.2.2",
|
"drizzle": "^1.2.2",
|
||||||
"ethereum-blockies-base64": "1.0.2",
|
"ethereum-blockies-base64": "1.0.2",
|
||||||
"ethereumjs-util": "5.2.0",
|
"ethereumjs-util": "5.2.0",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.3",
|
||||||
"file-loader": "^1.1.11",
|
"express-manifest-helpers": "^0.5.0",
|
||||||
|
"express-winston": "^3.0.0",
|
||||||
|
"file-loader": "^2.0.0",
|
||||||
|
"find-root": "^1.1.0",
|
||||||
"font-awesome": "^4.7.0",
|
"font-awesome": "^4.7.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^0.4.2",
|
"fork-ts-checker-webpack-plugin": "^0.4.2",
|
||||||
"fs-extra": "^7.0.0",
|
"fs-extra": "^7.0.0",
|
||||||
"http-proxy-middleware": "^0.18.0",
|
"http-proxy-middleware": "^0.18.0",
|
||||||
"husky": "^1.0.0-rc.8",
|
"husky": "^1.0.0-rc.8",
|
||||||
|
"i18next": "^11.9.0",
|
||||||
"is-mobile": "^1.0.0",
|
"is-mobile": "^1.0.0",
|
||||||
"isomorphic-unfetch": "^2.0.0",
|
"isomorphic-unfetch": "^2.0.0",
|
||||||
"js-cookie": "^2.2.0",
|
"js-cookie": "^2.2.0",
|
||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"less": "^3.7.1",
|
"less": "^3.7.1",
|
||||||
"lint-staged": "^7.1.3",
|
"less-loader": "^4.1.0",
|
||||||
|
"lint-staged": "^7.2.2",
|
||||||
|
"loadable-components": "^2.2.3",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
|
"mini-css-extract-plugin": "^0.4.2",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"next": "^6.1.1",
|
"next": "^6.1.1",
|
||||||
"next-compose-plugins": "^2.1.1",
|
"next-compose-plugins": "^2.1.1",
|
||||||
|
@ -84,6 +126,7 @@
|
||||||
"next-redux-wrapper": "^2.0.0-beta.6",
|
"next-redux-wrapper": "^2.0.0-beta.6",
|
||||||
"next-routes": "^1.4.2",
|
"next-routes": "^1.4.2",
|
||||||
"node-sass": "^4.9.2",
|
"node-sass": "^4.9.2",
|
||||||
|
"nodemon": "^1.18.4",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"openzeppelin-solidity": "^1.12.0",
|
"openzeppelin-solidity": "^1.12.0",
|
||||||
"prettier": "^1.13.4",
|
"prettier": "^1.13.4",
|
||||||
|
@ -92,11 +135,17 @@
|
||||||
"rc-scroll-anim": "^2.0.2",
|
"rc-scroll-anim": "^2.0.2",
|
||||||
"rc-tween-one": "^1.5.5",
|
"rc-tween-one": "^1.5.5",
|
||||||
"react": "^16.4.0",
|
"react": "^16.4.0",
|
||||||
|
"react-dev-utils": "^5.0.2",
|
||||||
"react-document-title": "^2.0.3",
|
"react-document-title": "^2.0.3",
|
||||||
"react-dom": "^16.4.0",
|
"react-dom": "^16.4.0",
|
||||||
"react-headroom": "^2.2.2",
|
"react-headroom": "^2.2.2",
|
||||||
|
"react-helmet": "^5.2.0",
|
||||||
|
"react-hot-loader": "^4.3.8",
|
||||||
|
"react-i18next": "^7.12.0",
|
||||||
"react-mde": "^5.8.0",
|
"react-mde": "^5.8.0",
|
||||||
"react-redux": "^5.0.7",
|
"react-redux": "^5.0.7",
|
||||||
|
"react-router": "^4.3.1",
|
||||||
|
"react-router-dom": "^4.3.1",
|
||||||
"recompose": "^0.27.1",
|
"recompose": "^0.27.1",
|
||||||
"redux": "^4.0.0",
|
"redux": "^4.0.0",
|
||||||
"redux-devtools-extension": "^2.13.2",
|
"redux-devtools-extension": "^2.13.2",
|
||||||
|
@ -107,17 +156,30 @@
|
||||||
"showdown": "^1.8.6",
|
"showdown": "^1.8.6",
|
||||||
"slate": "^0.37.3",
|
"slate": "^0.37.3",
|
||||||
"slate-react": "^0.15.4",
|
"slate-react": "^0.15.4",
|
||||||
"styled-components": "^3.3.3",
|
"stats-webpack-plugin": "^0.7.0",
|
||||||
|
"style-loader": "^0.23.0",
|
||||||
|
"styled-components": "^3.4.6",
|
||||||
"truffle-hdwallet-provider": "0.0.6",
|
"truffle-hdwallet-provider": "0.0.6",
|
||||||
|
"ts-loader": "^5.1.1",
|
||||||
"tslint": "^5.10.0",
|
"tslint": "^5.10.0",
|
||||||
"tslint-config-airbnb": "^5.9.2",
|
"tslint-config-airbnb": "^5.9.2",
|
||||||
"tslint-config-prettier": "^1.13.0",
|
"tslint-config-prettier": "^1.13.0",
|
||||||
"tslint-eslint-rules": "^5.3.1",
|
"tslint-eslint-rules": "^5.3.1",
|
||||||
"tslint-react": "^3.6.0",
|
"tslint-react": "^3.6.0",
|
||||||
"typescript": "3.0.3",
|
"typescript": "3.0.3",
|
||||||
"url-loader": "^1.0.1",
|
"uglifyjs-webpack-plugin": "^2.0.0",
|
||||||
|
"url-loader": "^1.1.1",
|
||||||
"web3": "^1.0.0-beta.34",
|
"web3": "^1.0.0-beta.34",
|
||||||
"webpack-bundle-analyzer": "^2.13.1",
|
"webpack": "^4.19.0",
|
||||||
|
"webpack-bundle-analyzer": "^3.0.2",
|
||||||
|
"webpack-cli": "^3.1.0",
|
||||||
|
"webpack-dev-server": "^3.1.8",
|
||||||
|
"webpack-hot-middleware": "^2.24.0",
|
||||||
|
"webpack-manifest-plugin": "^2.0.4",
|
||||||
|
"webpack-node-externals": "^1.7.2",
|
||||||
|
"webpack-stats-plugin": "^0.2.1",
|
||||||
|
"winston": "^3.1.0",
|
||||||
|
"write-file-webpack-plugin": "^4.4.0",
|
||||||
"xss": "1.0.3"
|
"xss": "1.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import Helmet from 'react-helmet';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
children: any;
|
||||||
|
css: string[];
|
||||||
|
scripts: string[];
|
||||||
|
state: string;
|
||||||
|
loadableStateScript: string;
|
||||||
|
styleElements: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class HTML extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const head = Helmet.renderStatic();
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
scripts,
|
||||||
|
css,
|
||||||
|
state,
|
||||||
|
loadableStateScript,
|
||||||
|
styleElements,
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<html lang="">
|
||||||
|
<head>
|
||||||
|
<meta charSet="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
{/* TODO: import from @fortawesome */}
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://use.fontawesome.com/releases/v5.2.0/css/all.css"
|
||||||
|
integrity="sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ"
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
/>
|
||||||
|
{head.base.toComponent()}
|
||||||
|
{head.title.toComponent()}
|
||||||
|
{head.meta.toComponent()}
|
||||||
|
{head.link.toComponent()}
|
||||||
|
{head.script.toComponent()}
|
||||||
|
{css.map(href => {
|
||||||
|
return <link key={href} rel="stylesheet" href={href} />;
|
||||||
|
})}
|
||||||
|
{styleElements.map((styleEl: any) => styleEl)}
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `window.__PRELOADED_STATE__ = ${state}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app" dangerouslySetInnerHTML={{ __html: children }} />
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: loadableStateScript }} />
|
||||||
|
{scripts.map(src => {
|
||||||
|
return <script key={src} src={src} />;
|
||||||
|
})}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
import express from 'express';
|
||||||
|
import * as cors from 'cors';
|
||||||
|
import * as path from 'path';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import manifestHelpers from 'express-manifest-helpers';
|
||||||
|
import * as bodyParser from 'body-parser';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import expressWinston from 'express-winston';
|
||||||
|
|
||||||
|
import log from './log';
|
||||||
|
import serverRender from './render';
|
||||||
|
// @ts-ignore
|
||||||
|
import * as paths from '../config/paths';
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// log requests
|
||||||
|
app.use(expressWinston.logger({ winstonInstance: log }));
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
app.use(
|
||||||
|
paths.publicPath,
|
||||||
|
express.static(path.join(paths.clientBuild, paths.publicPath)),
|
||||||
|
);
|
||||||
|
// tslint:disable-next-line:variable-name
|
||||||
|
app.use('/favicon.ico', (_req, res) => {
|
||||||
|
res.send('');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.warn('PRODUCTION mode, serving static assets from node server.');
|
||||||
|
app.use(
|
||||||
|
paths.publicPath,
|
||||||
|
express.static(path.join(paths.clientBuild, paths.publicPath)),
|
||||||
|
);
|
||||||
|
// tslint:disable-next-line:variable-name
|
||||||
|
app.use('/favicon.ico', (_req, res) => {
|
||||||
|
res.send('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
const manifestPath = path.join(paths.clientBuild, paths.publicPath);
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
manifestHelpers({
|
||||||
|
manifestPath: `${manifestPath}/manifest.json`,
|
||||||
|
cache: process.env.NODE_ENV === 'production',
|
||||||
|
// prependPath: '//cdn.example/assets' // if statics are elsewhere
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(serverRender());
|
||||||
|
|
||||||
|
app.use(expressWinston.errorLogger({ winstonInstance: log }));
|
||||||
|
|
||||||
|
app.listen(process.env.PORT || 3000, () => {
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
if (isDev) {
|
||||||
|
console.log(chalk.blue(`App is running: 🌎 http://localhost:${port} `));
|
||||||
|
} else {
|
||||||
|
log.info(`Server started on port ${port}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { createLogger, format, transports } from 'winston';
|
||||||
|
// @ts-ignore
|
||||||
|
import * as paths from '../config/paths';
|
||||||
|
const { combine, timestamp, prettyPrint, printf, colorize } = format;
|
||||||
|
|
||||||
|
const custom = combine(
|
||||||
|
timestamp({ format: 'YY/MM/DD HH:mm:ss' }),
|
||||||
|
colorize(),
|
||||||
|
printf(info => `${info.timestamp}[${info.level}] ${info.message}`),
|
||||||
|
);
|
||||||
|
|
||||||
|
const enumerateErrorFormat = format((info: any) => {
|
||||||
|
if (info.message instanceof Error) {
|
||||||
|
info.message = Object.assign(
|
||||||
|
{
|
||||||
|
message: info.message.message,
|
||||||
|
stack: info.message.stack,
|
||||||
|
},
|
||||||
|
info.message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (info instanceof Error) {
|
||||||
|
return Object.assign(
|
||||||
|
{
|
||||||
|
message: info.message,
|
||||||
|
stack: info.stack,
|
||||||
|
},
|
||||||
|
info,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
});
|
||||||
|
|
||||||
|
// levels: error, warn, info, verbose, debug, silly
|
||||||
|
const log = createLogger({
|
||||||
|
level: 'verbose',
|
||||||
|
exitOnError: true,
|
||||||
|
format: combine(enumerateErrorFormat(), timestamp(), prettyPrint()),
|
||||||
|
transports: [
|
||||||
|
new transports.File({
|
||||||
|
filename: `${paths.logs}/app.log`,
|
||||||
|
level: 'info',
|
||||||
|
handleExceptions: true,
|
||||||
|
maxsize: 5242880, // 5MB
|
||||||
|
maxFiles: 5,
|
||||||
|
}),
|
||||||
|
new transports.Console({
|
||||||
|
level: 'verbose',
|
||||||
|
handleExceptions: true,
|
||||||
|
format: custom,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export default log;
|
|
@ -0,0 +1,141 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { renderToString } from 'react-dom/server';
|
||||||
|
import { ServerStyleSheet } from 'styled-components';
|
||||||
|
import { getLoadableState } from 'loadable-components/server';
|
||||||
|
import { StaticRouter as Router } from 'react-router-dom';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
// import IntlProvider from '../shared/i18n/IntlProvider';
|
||||||
|
|
||||||
|
import log from './log';
|
||||||
|
import { configureStore } from '../client/store/configure';
|
||||||
|
// import DrizzleContext from '../shared/DrizzleContext';
|
||||||
|
// import App from '../shared/App';
|
||||||
|
import Html from './components/HTML';
|
||||||
|
import Routes from '../client/Routes';
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
// @ts-ignore
|
||||||
|
import * as paths from '../config/paths';
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
let cachedStats: any;
|
||||||
|
const getStats = () =>
|
||||||
|
new Promise((res, rej) => {
|
||||||
|
if (!isDev && cachedStats) {
|
||||||
|
res(cachedStats);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const statsPath = path.join(paths.clientBuild, paths.publicPath, 'stats.json');
|
||||||
|
fs.readFile(statsPath, (e, d) => {
|
||||||
|
if (e) {
|
||||||
|
rej(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cachedStats = JSON.parse(d.toString());
|
||||||
|
res(cachedStats);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const extractLoadableIds = (tree: any): string[] => {
|
||||||
|
const ids = (tree.id && [tree.id]) || [];
|
||||||
|
if (tree.children) {
|
||||||
|
return tree.children
|
||||||
|
.reduce((a: string[], c: any) => a.concat(extractLoadableIds(c)), [])
|
||||||
|
.concat(ids);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
|
const chunkExtractFromLoadables = (loadableState: any) =>
|
||||||
|
getStats().then((stats: any) => {
|
||||||
|
const loadableIds = extractLoadableIds(loadableState.tree);
|
||||||
|
const mods = stats.modules.filter(
|
||||||
|
(m: any) =>
|
||||||
|
m.reasons.filter((r: any) => loadableIds.indexOf(r.userRequest) > -1).length > 0,
|
||||||
|
);
|
||||||
|
const chunks = mods.reduce((a: any[], m: any) => a.concat(m.chunks), []);
|
||||||
|
const files = stats.chunks
|
||||||
|
.filter((c: any) => chunks.indexOf(c.id) > -1)
|
||||||
|
.reduce((a: string[], c: any) => a.concat(c.files), []);
|
||||||
|
return {
|
||||||
|
css: files.filter((f: string) => /.css$/.test(f)),
|
||||||
|
js: files.filter((f: string) => /.js$/.test(f)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// react-router recommends agains redux - router integration, perhaps remove?
|
||||||
|
// https://reacttraining.com/react-router/web/guides/redux-integration
|
||||||
|
|
||||||
|
// <DrizzleContext.Provider store={store}>
|
||||||
|
// <IntlProvider>
|
||||||
|
// <App />
|
||||||
|
// </IntlProvider>
|
||||||
|
// </DrizzleContext.Provider>
|
||||||
|
|
||||||
|
const serverRenderer = () => async (req: Request, res: Response) => {
|
||||||
|
// const store = configureStore(req.url);
|
||||||
|
const store = configureStore();
|
||||||
|
const sheet = new ServerStyleSheet();
|
||||||
|
const reactApp = (
|
||||||
|
<Provider store={store}>
|
||||||
|
<Router location={req.url} context={{}}>
|
||||||
|
<Routes />
|
||||||
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
|
||||||
|
let loadableState;
|
||||||
|
let loadableFiles;
|
||||||
|
// 1. loadable state will render dynamic imports
|
||||||
|
try {
|
||||||
|
loadableState = await getLoadableState(reactApp);
|
||||||
|
loadableFiles = await chunkExtractFromLoadables(loadableState);
|
||||||
|
} catch (e) {
|
||||||
|
const disp = `Error getting loadable state for SSR`;
|
||||||
|
e.message = disp + ': ' + e.message;
|
||||||
|
log.error(e);
|
||||||
|
return res.status(500).send(disp + ' (more info in server logs)');
|
||||||
|
}
|
||||||
|
// 2. styled components will gather styles & wrap in provider
|
||||||
|
const styleConnectedApp = sheet.collectStyles(reactApp);
|
||||||
|
|
||||||
|
const styleElements = sheet.getStyleElement();
|
||||||
|
const content = renderToString(styleConnectedApp);
|
||||||
|
const state = JSON.stringify(store.getState());
|
||||||
|
|
||||||
|
// ! ensure manifest.json is available
|
||||||
|
try {
|
||||||
|
res.locals.getManifest();
|
||||||
|
} catch (e) {
|
||||||
|
const disp =
|
||||||
|
'ERROR: Could not load client manifest.json, there was probably a client build error.';
|
||||||
|
log.error(disp);
|
||||||
|
return res.status(500).send(disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssFiles = ['bundle.css', 'vendor.css', ...loadableFiles.css]
|
||||||
|
.map(f => res.locals.assetPath(f))
|
||||||
|
.filter(Boolean);
|
||||||
|
const jsFiles = [...loadableFiles.js, 'vendor.js', 'bundle.js']
|
||||||
|
.map(f => res.locals.assetPath(f))
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return res.send(
|
||||||
|
'<!doctype html>' +
|
||||||
|
renderToString(
|
||||||
|
<Html
|
||||||
|
css={cssFiles}
|
||||||
|
styleElements={styleElements}
|
||||||
|
scripts={jsFiles}
|
||||||
|
state={state}
|
||||||
|
loadableStateScript={loadableState.getScriptContent()}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</Html>,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default serverRenderer;
|
|
@ -15,9 +15,22 @@
|
||||||
"preserveConstEnums": true,
|
"preserveConstEnums": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"baseUrl": "./client",
|
"baseUrl": ".",
|
||||||
"lib": ["dom", "es2017"]
|
"lib": ["dom", "es2017"],
|
||||||
|
"paths": {
|
||||||
|
"contracts/*": ["../contract/build/contracts/*"],
|
||||||
|
"api/*": ["./client/api/*"],
|
||||||
|
"components/*": ["./client/components/*"],
|
||||||
|
"lib/*": ["./client/lib/*"],
|
||||||
|
"modules/*": ["./client/modules/*"],
|
||||||
|
"pages/*": ["./client/pages/*"],
|
||||||
|
"store/*": ["./client/store/*"],
|
||||||
|
"styles/*": ["./client/styles/*"],
|
||||||
|
"typings/*": ["./client/typings/*"],
|
||||||
|
"utils/*": ["./client/utils/*"],
|
||||||
|
"web3interact/*": ["./client/web3interact/*"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["./client", "node_modules/@types", "typings"],
|
"include": ["./client/**/*", "./server/**/*"],
|
||||||
"exclude": ["./client/build", "./client/static"]
|
"exclude": ["./client/static"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,5 +9,8 @@
|
||||||
"interface-name": [true, "never-prefix"],
|
"interface-name": [true, "never-prefix"],
|
||||||
"curly": [true, "ignore-same-line"],
|
"curly": [true, "ignore-same-line"],
|
||||||
"no-console": false
|
"no-console": false
|
||||||
|
},
|
||||||
|
"linterOptions": {
|
||||||
|
"exclude": ["client/lib/contracts/**/*.json"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
3005
frontend/yarn.lock
3005
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue