convert to TypeScript and add bucket actions
This commit is contained in:
parent
88f019e6c6
commit
0937a83f93
|
@ -0,0 +1,159 @@
|
|||
import { RootState } from '../reducers';
|
||||
import GiftBucket from '../../../embarkArtifacts/contracts/GiftBucket';
|
||||
import IERC20Detailed from '../../../embarkArtifacts/contracts/IERC20Detailed';
|
||||
import { config } from "../config";
|
||||
import { Contract } from 'web3-eth-contract';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
export const ERROR_GIFT_NOT_FOUND = "ERROR_GIFT_NOT_FOUND";
|
||||
export interface ErrGiftNotFound {
|
||||
type: typeof ERROR_GIFT_NOT_FOUND
|
||||
}
|
||||
|
||||
export const ERROR_LOADING_GIFT = "ERROR_LOADING_GIFT";
|
||||
export interface ErrLoadingGift {
|
||||
type: typeof ERROR_LOADING_GIFT
|
||||
message: string
|
||||
}
|
||||
|
||||
export type BucketError =
|
||||
ErrGiftNotFound;
|
||||
|
||||
const errGiftNotFound = () => ({
|
||||
type: ERROR_GIFT_NOT_FOUND,
|
||||
});
|
||||
|
||||
const errLoadingGift = (message: string) => ({
|
||||
type: ERROR_LOADING_GIFT,
|
||||
message,
|
||||
});
|
||||
|
||||
export const BUCKET_GIFT_LOADING = "BUCKET_GIFT_LOADING";
|
||||
export interface BucketGiftLoadingAction {
|
||||
type: typeof BUCKET_GIFT_LOADING
|
||||
address: string
|
||||
}
|
||||
|
||||
export const BUCKET_GIFT_LOADING_ERROR = "BUCKET_GIFT_LOADING_ERROR";
|
||||
export interface BucketGiftLoadingErrorAction {
|
||||
type: typeof BUCKET_GIFT_LOADING_ERROR
|
||||
error: BucketError
|
||||
}
|
||||
|
||||
export const BUCKET_GIFT_LOADED = "BUCKET_GIFT_LOADED";
|
||||
export interface BucketGiftLoadedAction {
|
||||
type: typeof BUCKET_GIFT_LOADED
|
||||
recipient: string
|
||||
amount: string
|
||||
codeHash: string
|
||||
}
|
||||
|
||||
export const BUCKET_GIFT_NOT_FOUND = "BUCKET_GIFT_NOT_FOUND";
|
||||
export interface BucketGiftNotFoundAction {
|
||||
type: typeof BUCKET_GIFT_NOT_FOUND
|
||||
error: BucketError
|
||||
}
|
||||
|
||||
export const BUCKET_TOKEN_LOADING = "BUCKET_TOKEN_LOADING";
|
||||
export interface BucketTokenLoadingAction {
|
||||
type: typeof BUCKET_TOKEN_LOADING
|
||||
address: string
|
||||
}
|
||||
|
||||
export const BUCKET_TOKEN_LOADED = "BUCKET_TOKEN_LOADED";
|
||||
export interface BucketTokenLoadedAction {
|
||||
type: typeof BUCKET_TOKEN_LOADED
|
||||
symbol: string
|
||||
decimal: number
|
||||
}
|
||||
|
||||
export type BucketActions =
|
||||
BucketGiftLoadingAction |
|
||||
BucketGiftLoadingErrorAction |
|
||||
BucketGiftLoadedAction |
|
||||
BucketGiftNotFoundAction |
|
||||
BucketTokenLoadingAction |
|
||||
BucketTokenLoadedAction;
|
||||
|
||||
export const loadingGift = (address: string): BucketLoadingAction => ({
|
||||
type: BUCKET_GIFT_LOADING,
|
||||
address,
|
||||
});
|
||||
|
||||
export const giftLoaded = (recipient: string, amount: string, codeHash: string): BucketGiftLoadedAction => ({
|
||||
type: BUCKET_GIFT_LOADED,
|
||||
recipient,
|
||||
amount,
|
||||
codeHash,
|
||||
});
|
||||
|
||||
export const giftNotFound = (recipient: string, amount: string, codeHash: string): BucketGiftNotFoundAction => ({
|
||||
type: BUCKET_GIFT_NOT_FOUND,
|
||||
error: errGiftNotFound(),
|
||||
});
|
||||
|
||||
export const errorLoadingGift = (errorMessage: string): BucketGiftNotFoundAction => ({
|
||||
type: BUCKET_GIFT_NOT_FOUND,
|
||||
error: errLoadingGift(errorMessage),
|
||||
});
|
||||
|
||||
export const loadingToken = (address: string): BucketTokenLoadingAction => ({
|
||||
type: BUCKET_TOKEN_LOADING,
|
||||
address,
|
||||
});
|
||||
|
||||
export const tokenLoaded = (symbol: string, decimals: number): BucketTokenLoadedAction => ({
|
||||
type: BUCKET_TOKEN_LOADED,
|
||||
symbol,
|
||||
decimals,
|
||||
});
|
||||
|
||||
const newBucketContract = (address: string) => {
|
||||
const bucketAbi = GiftBucket.options.jsonInterface;
|
||||
const bucket = new config.web3!.eth.Contract(bucketAbi, address);
|
||||
return bucket;
|
||||
}
|
||||
|
||||
const newERC20Contract = (address: string) => {
|
||||
const erc20Abi = IERC20Detailed.options.jsonInterface;
|
||||
const erc20 = new config.web3!.eth.Contract(erc20Abi, address);
|
||||
return erc20;
|
||||
}
|
||||
|
||||
export const loadGift = (bucketAddress: string, recipientAddress: string) => {
|
||||
return async (dispatch: Dispatch, getState: () => RootState) => {
|
||||
dispatch(loadingGift(bucketAddress, recipientAddress));
|
||||
const bucket = newBucketContract(bucketAddress);
|
||||
|
||||
bucket.methods.gifts(recipientAddress).call().then((result: Any) => {
|
||||
const { recipient, amount, code } = result;
|
||||
if (amount === "0") {
|
||||
dispatch(giftNotFound())
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(giftLoaded(recipient, amount, code));
|
||||
dispatch(loadToken(bucket))
|
||||
}).catch(err => {
|
||||
dispatch(errorLoadingGift(err))
|
||||
console.error("err: ", err)
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
export const loadToken = (bucket: Contract) => {
|
||||
return (dispatch: Dispatch, getState: () => RootState) => {
|
||||
bucket.methods.tokenContract().call().then(async (address: string) => {
|
||||
const erc20Abi = IERC20Detailed.options.jsonInterface;
|
||||
const erc20 = new config.web3!.eth.Contract(erc20Abi, address);
|
||||
dispatch(loadingToken(address));
|
||||
|
||||
const symbol = await erc20.methods.symbol().call();
|
||||
const decimals = await erc20.methods.decimals().call();
|
||||
dispatch(tokenLoaded(symbol, decimals));
|
||||
}).catch((err: string) => {
|
||||
//FIXME: manage error
|
||||
console.error("ERROR: ", err);
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
import Web3 from 'web3';
|
||||
import { Dispatch } from 'redux';
|
||||
import { config } from '../config';
|
||||
|
||||
export const VALID_NETWORK_NAME = "Ropsten";
|
||||
export const VALID_NETWORK_ID = 3;
|
||||
// export const VALID_NETWORK_NAME = "Goerli";
|
||||
// export const VALID_NETWORK_ID = 5;
|
||||
export const LOCAL_NETWORK_ID = 1337;
|
||||
|
||||
export const WEB3_INITIALIZED = "WEB3_INITIALIZED";
|
||||
export const WEB3_ERROR = "WEB3_ERROR";
|
||||
export const WEB3_NETWORK_ID_LOADED = "WEB3_NETWORK_ID_LOADED";
|
||||
export const WEB3_ACCOUNT_LOADED = "WEB3_ACCOUNT_LOADED";
|
||||
|
||||
export const web3Initialized = () => ({
|
||||
type: WEB3_INITIALIZED,
|
||||
})
|
||||
|
||||
export const web3NetworkIDLoaded = networkID => ({
|
||||
type: WEB3_NETWORK_ID_LOADED,
|
||||
networkID,
|
||||
});
|
||||
|
||||
export const web3Error = error => ({
|
||||
type: WEB3_ERROR,
|
||||
error,
|
||||
});
|
||||
|
||||
export const web3AccoutLoaded = account => ({
|
||||
type: WEB3_ACCOUNT_LOADED,
|
||||
account,
|
||||
});
|
||||
|
||||
export const initWeb3 = () => {
|
||||
if (window.ethereum) {
|
||||
config.web3 = new Web3(window.ethereum);
|
||||
return (dispatch, getState) => {
|
||||
window.ethereum.enable()
|
||||
.then(() => {
|
||||
dispatch(web3Initialized());
|
||||
dispatch(loadNetwordId());
|
||||
})
|
||||
.catch((err) => {
|
||||
dispatch(web3Error(err));
|
||||
});
|
||||
}
|
||||
} else if (window.web3) {
|
||||
config.web3 = window.web3;
|
||||
return (dispatch, getState) => {
|
||||
dispatch(web3Initialized());
|
||||
dispatch(loadNetwordId());
|
||||
}
|
||||
} else {
|
||||
//FIXME: move to config
|
||||
// const web3 = new Web3('https://ropsten.infura.io/v3/f315575765b14720b32382a61a89341a');
|
||||
// const web3 = new Web3(new Web3.providers.HttpProvider('https://ropsten.infura.io/v3/f315575765b14720b32382a61a89341a'));
|
||||
config.web3 = new Web3(new Web3.providers.WebsocketProvider('wss://ropsten.infura.io/ws/v3/f315575765b14720b32382a61a89341a'));
|
||||
return (dispatch, getState) => {
|
||||
dispatch(web3Initialized());
|
||||
dispatch(loadNetwordId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loadNetwordId = () => {
|
||||
return (dispatch, getState) => {
|
||||
config.web3.eth.net.getId().then((id) => {
|
||||
dispatch(web3NetworkIDLoaded(id))
|
||||
if (id !== VALID_NETWORK_ID && id !== LOCAL_NETWORK_ID) {
|
||||
dispatch(web3Error(`wrong network, please connect to ${VALID_NETWORK_NAME}`));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(web3NetworkIDLoaded(id))
|
||||
dispatch(loadMainAccount());
|
||||
})
|
||||
.catch((err) => {
|
||||
dispatch(web3Error(err));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const loadMainAccount = () => {
|
||||
return (dispatch, getState) => {
|
||||
web3.eth.getAccounts()
|
||||
.then(accounts => {
|
||||
dispatch(web3AccoutLoaded(accounts[0]));
|
||||
})
|
||||
.catch((err) => {
|
||||
dispatch(web3Error(err));
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
import Web3 from 'web3';
|
||||
import { config } from '../config';
|
||||
import {
|
||||
Dispatch,
|
||||
} from 'redux';
|
||||
import { RootState } from '../reducers';
|
||||
|
||||
export const VALID_NETWORK_NAME = "Ropsten";
|
||||
export const VALID_NETWORK_ID = 3;
|
||||
// export const VALID_NETWORK_NAME = "Goerli";
|
||||
// export const VALID_NETWORK_ID = 5;
|
||||
export const LOCAL_NETWORK_ID = 1337;
|
||||
|
||||
enum Web3Type {
|
||||
Generic,
|
||||
Remote,
|
||||
Status,
|
||||
}
|
||||
|
||||
export const WEB3_INITIALIZED = "WEB3_INITIALIZED";
|
||||
export interface Web3InitializedAction {
|
||||
type: typeof WEB3_INITIALIZED
|
||||
web3Type: Web3Type
|
||||
}
|
||||
|
||||
export const WEB3_ERROR = "WEB3_ERROR";
|
||||
export interface Web3ErrorAction {
|
||||
type: typeof WEB3_ERROR
|
||||
error: string
|
||||
}
|
||||
|
||||
export const WEB3_NETWORK_ID_LOADED = "WEB3_NETWORK_ID_LOADED";
|
||||
export interface Web3NetworkIDLoadedAction {
|
||||
type: typeof WEB3_NETWORK_ID_LOADED
|
||||
networkID: number
|
||||
}
|
||||
|
||||
export const WEB3_ACCOUNT_LOADED = "WEB3_ACCOUNT_LOADED";
|
||||
export interface Web3AccountLoadedAction {
|
||||
type: typeof WEB3_ACCOUNT_LOADED
|
||||
account: string
|
||||
}
|
||||
|
||||
export type Web3Actions =
|
||||
Web3InitializedAction |
|
||||
Web3ErrorAction |
|
||||
Web3NetworkIDLoadedAction |
|
||||
Web3AccountLoadedAction;
|
||||
|
||||
|
||||
export const web3Initialized = (t: Web3Type): Web3Actions => ({
|
||||
type: WEB3_INITIALIZED,
|
||||
web3Type: t,
|
||||
})
|
||||
|
||||
export const web3NetworkIDLoaded = (id: number): Web3Actions => ({
|
||||
type: WEB3_NETWORK_ID_LOADED,
|
||||
networkID: id,
|
||||
});
|
||||
|
||||
export const web3Error = (error: string): Web3Actions => ({
|
||||
type: WEB3_ERROR,
|
||||
error: error,
|
||||
});
|
||||
|
||||
export const accountLoaded = (account: string): Web3Actions => ({
|
||||
type: WEB3_ACCOUNT_LOADED,
|
||||
account
|
||||
});
|
||||
|
||||
export const initializeWeb3 = () => {
|
||||
const w = window as any;
|
||||
return (dispatch: Dispatch, getState: () => RootState) => {
|
||||
if (w.ethereum) {
|
||||
config.web3 = new Web3(w.ethereum);
|
||||
w.ethereum.enable()
|
||||
.then(() => {
|
||||
const t: Web3Type = w.ethereum.isStatus ? Web3Type.Status : Web3Type.Generic;
|
||||
dispatch(web3Initialized(t));
|
||||
config.web3!.eth.net.getId().then((id: number) => {
|
||||
if (id !== VALID_NETWORK_ID && id !== LOCAL_NETWORK_ID) {
|
||||
dispatch(web3Error(`wrong network, please connect to ${VALID_NETWORK_NAME}`));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(web3NetworkIDLoaded(id))
|
||||
dispatch(loadAddress());
|
||||
});
|
||||
})
|
||||
.catch((err: string) => {
|
||||
//FIXME: handle error
|
||||
console.log("error", err)
|
||||
});
|
||||
} else if (config.web3) {
|
||||
const t: Web3Type = w.ethereum.isStatus ? Web3Type.Status : Web3Type.Generic;
|
||||
dispatch(web3Initialized(t));
|
||||
config.web3!.eth.net.getId().then((id: number) => {
|
||||
dispatch(web3NetworkIDLoaded(id))
|
||||
dispatch(loadAddress());
|
||||
})
|
||||
.catch((err: string) => {
|
||||
//FIXME: handle error
|
||||
console.log("error", err)
|
||||
});
|
||||
} else {
|
||||
dispatch(web3Error("web3 not supported"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const loadAddress = () => {
|
||||
return (dispatch: Dispatch, getState: () => RootState) => {
|
||||
web3.eth.getAccounts().then((accounts: string[]) => {
|
||||
dispatch(accountLoaded(accounts[0]));
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
export default function App(props) {
|
||||
if (!props.initialized) {
|
||||
return "initializing...";
|
||||
}
|
||||
|
||||
if (props.error) {
|
||||
return <>
|
||||
<p>Error: {props.error}</p>
|
||||
</>;
|
||||
}
|
||||
|
||||
return <>
|
||||
<p>Network ID {props.networkID}</p>
|
||||
<p>Hello {props.account}</p>
|
||||
</>;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
shallowEqual,
|
||||
useSelector,
|
||||
useDispatch,
|
||||
} from 'react-redux';
|
||||
|
||||
export default function(ownProps: any) {
|
||||
const props = useSelector(state => {
|
||||
return {
|
||||
initialized: state.web3.networkID,
|
||||
networkID: state.web3.networkID,
|
||||
error: state.web3.error,
|
||||
}
|
||||
}, shallowEqual);
|
||||
|
||||
if (props.error) {
|
||||
return `Error: ${props.error}`;
|
||||
}
|
||||
|
||||
if (!props.initialized) {
|
||||
return "initializing...";
|
||||
}
|
||||
|
||||
return ownProps.children;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return <div>
|
||||
<p>Something went wrong.</p>
|
||||
</div>;
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
|
@ -0,0 +1,103 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useRouteMatch } from 'react-router-dom';
|
||||
import {
|
||||
shallowEqual,
|
||||
useSelector,
|
||||
useDispatch,
|
||||
} from 'react-redux';
|
||||
import { redeemPath } from '../config';
|
||||
import {
|
||||
loadGift,
|
||||
BucketError,
|
||||
ERROR_LOADING_GIFT,
|
||||
ERROR_GIFT_NOT_FOUND,
|
||||
} from '../actions/bucket';
|
||||
import Web3Utils from "web3-utils";
|
||||
|
||||
const BN = Web3Utils.BN;
|
||||
|
||||
const errorMessage = (error: BucketError): string => {
|
||||
switch (error.type) {
|
||||
case ERROR_LOADING_GIFT:
|
||||
return "couldn't load gift.";
|
||||
|
||||
case ERROR_GIFT_NOT_FOUND:
|
||||
return "gift not found";
|
||||
|
||||
default:
|
||||
return "something went wrong";
|
||||
}
|
||||
}
|
||||
|
||||
const toBaseUnit = (fullAmount: string, decimalsSize: number, roundDecimals: number) => {
|
||||
const amount = new BN(fullAmount);
|
||||
const base = new BN(10).pow(new BN(decimalsSize));
|
||||
const whole = amount.div(base).toString();
|
||||
let decimals = amount.mod(base).toString();
|
||||
for (let i = decimals.length; i < decimalsSize; i++) {
|
||||
decimals = `0${decimals}`;
|
||||
}
|
||||
|
||||
const full = `${whole}.${decimals}`;
|
||||
const rounded = `${whole}.${decimals.slice(0, roundDecimals)}`;
|
||||
|
||||
return [full, rounded];
|
||||
}
|
||||
|
||||
export default function(ownProps: any) {
|
||||
const dispatch = useDispatch()
|
||||
const match = useRouteMatch({
|
||||
path: redeemPath,
|
||||
exact: true,
|
||||
});
|
||||
|
||||
const bucketAddress = match.params.bucketAddress;
|
||||
const recipientAddress = match.params.recipientAddress;
|
||||
|
||||
const props = useSelector(state => {
|
||||
return {
|
||||
bucketAddress: state.bucket.address,
|
||||
loading: state.bucket.loading,
|
||||
found: state.bucket.found,
|
||||
error: state.bucket.error,
|
||||
recipient: state.bucket.recipient,
|
||||
amount: state.bucket.amount,
|
||||
codeHash: state.bucket.codeHash,
|
||||
tokenAddress: state.bucket.tokenAddress,
|
||||
tokenSymbol: state.bucket.tokenSymbol,
|
||||
tokenDecimals: state.bucket.tokenDecimals,
|
||||
receiver: state.web3.account,
|
||||
}
|
||||
}, shallowEqual);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(loadGift(bucketAddress, recipientAddress));
|
||||
}, [bucketAddress, recipientAddress]);
|
||||
|
||||
if (props.error) {
|
||||
return `Error: ${errorMessage(props.error)}`;
|
||||
}
|
||||
|
||||
if (props.loading) {
|
||||
return "loading bucket...";
|
||||
}
|
||||
|
||||
if (props.tokenSymbol === undefined || props.tokenDecimals === undefined) {
|
||||
return "loading token info...";
|
||||
}
|
||||
|
||||
const [displayAmount, roundedDisplayAmount] = toBaseUnit(props.amount, props.tokenDecimals, 2);
|
||||
|
||||
return <>
|
||||
Bucket Address: {props.bucketAddress}<br />
|
||||
Recipient: {props.recipient}<br />
|
||||
Amount: {props.amount}<br />
|
||||
Code Hash: {props.codeHash}<br />
|
||||
Token Address: {props.tokenAddress}<br />
|
||||
Token Symbol: {props.tokenSymbol}<br />
|
||||
Token Decimals: {props.tokenDecimals}<br />
|
||||
Display Amount: {displayAmount} <br />
|
||||
Rounded Display Amount: {roundedDisplayAmount} <br />
|
||||
Receiver: {props.receiver} <br />
|
||||
</>;
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export const config = {
|
||||
web3: undefined,
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
import Web3 from "web3";
|
||||
|
||||
export const config = {
|
||||
web3: Web3 | undefined
|
||||
};
|
||||
|
||||
export const redeemPath = "/redeem/:bucketAddress/:recipientAddress";
|
|
@ -1,17 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import App from '../components/App';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
initialized: state.web3.networkID,
|
||||
networkID: state.web3.networkID,
|
||||
account: state.web3.account,
|
||||
error: state.web3.error,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(App);
|
|
@ -1,47 +0,0 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import createRootReducer from './reducers';
|
||||
import { initWeb3 } from './actions/web3';
|
||||
import App from './containers/App';
|
||||
|
||||
const logger = (store) => {
|
||||
return (next) => {
|
||||
return (action) => {
|
||||
console.log('dispatching\n', action);
|
||||
const result = next(action);
|
||||
console.log('next state\n', store.getState());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let middlewares = [
|
||||
thunkMiddleware,
|
||||
];
|
||||
|
||||
if (true || process.env.NODE_ENV !== 'production') {
|
||||
middlewares = [
|
||||
...middlewares,
|
||||
logger
|
||||
];
|
||||
}
|
||||
|
||||
const store = createStore(
|
||||
createRootReducer(),
|
||||
applyMiddleware(...middlewares),
|
||||
);
|
||||
|
||||
EmbarkJS.onReady((err) => {
|
||||
store.dispatch(initWeb3());
|
||||
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
});
|
|
@ -0,0 +1,63 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import { createStore, applyMiddleware, Middleware, MiddlewareAPI, Dispatch } from 'redux';
|
||||
import createRootReducer from './reducers';
|
||||
import { initializeWeb3 } from './actions/web3';
|
||||
import { routerMiddleware, ConnectedRouter } from 'connected-react-router';
|
||||
import { Route, Switch } from 'react-router';
|
||||
import { createHashHistory } from 'history';
|
||||
import ErrorBoundary from './components/ErrorBoundary';
|
||||
import App from './components/App';
|
||||
import Home from './components/Home';
|
||||
import Redeem from './components/Redeem';
|
||||
import { redeemPath } from './config';
|
||||
|
||||
const logger: Middleware = ({ getState }: MiddlewareAPI) => (next: Dispatch) => action => {
|
||||
console.log('will dispatch', action);
|
||||
const returnValue = next(action);
|
||||
console.log('state after dispatch', getState());
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
const history = createHashHistory();
|
||||
|
||||
let middlewares: Middleware[] = [
|
||||
routerMiddleware(history),
|
||||
thunkMiddleware,
|
||||
];
|
||||
|
||||
if (true || process.env.NODE_ENV !== 'production') {
|
||||
middlewares = [
|
||||
...middlewares,
|
||||
logger
|
||||
];
|
||||
}
|
||||
|
||||
const store = createStore(
|
||||
createRootReducer(history),
|
||||
applyMiddleware(...middlewares),
|
||||
);
|
||||
|
||||
EmbarkJS.onReady(err => {
|
||||
store.dispatch<any>(initializeWeb3());
|
||||
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<Provider store={store}>
|
||||
<App>
|
||||
<ConnectedRouter history={history}>
|
||||
<Switch>
|
||||
<Route exact path="/"><Home /></Route>
|
||||
<Route exact path={redeemPath}><Redeem /></Route>
|
||||
<Route render={() => "page not found"} />
|
||||
</Switch>
|
||||
</ConnectedRouter>
|
||||
</App>
|
||||
</Provider>
|
||||
</ErrorBoundary>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
import {
|
||||
BucketActions,
|
||||
BucketError,
|
||||
BUCKET_GIFT_LOADING,
|
||||
BUCKET_GIFT_NOT_FOUND,
|
||||
BUCKET_GIFT_LOADED,
|
||||
BUCKET_TOKEN_LOADING,
|
||||
BUCKET_TOKEN_LOADED,
|
||||
} from "../actions/bucket";
|
||||
|
||||
export interface BucketState {
|
||||
loading: boolean
|
||||
address: string | undefined
|
||||
tokenAddress: string | undefined
|
||||
tokenDecimals: number | undefined
|
||||
error: BucketState | undefined
|
||||
recipient: string | undefined
|
||||
amount: string | undefined
|
||||
codeHash: string | undefined
|
||||
}
|
||||
|
||||
const initialState: BucketState = {
|
||||
loading: false,
|
||||
address: undefined,
|
||||
tokenAddress: undefined,
|
||||
tokenDecimals: undefined,
|
||||
error: undefined,
|
||||
recipient: undefined,
|
||||
amount: undefined,
|
||||
codeHash: undefined,
|
||||
}
|
||||
|
||||
export const bucketReducer = (state: BucketState = initialState, action: BucketActions): BucketState => {
|
||||
switch (action.type) {
|
||||
case BUCKET_GIFT_LOADING: {
|
||||
return {
|
||||
...initialState,
|
||||
loading: true,
|
||||
address: action.address,
|
||||
}
|
||||
}
|
||||
|
||||
case BUCKET_GIFT_NOT_FOUND: {
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
error: action.error,
|
||||
}
|
||||
}
|
||||
|
||||
case BUCKET_GIFT_LOADED: {
|
||||
return {
|
||||
...state,
|
||||
loading: false,
|
||||
recipient: action.recipient,
|
||||
amount: action.amount,
|
||||
codeHash: action.codeHash,
|
||||
}
|
||||
}
|
||||
|
||||
case BUCKET_TOKEN_LOADING: {
|
||||
return {
|
||||
...state,
|
||||
tokenAddress: action.address,
|
||||
}
|
||||
}
|
||||
|
||||
case BUCKET_TOKEN_LOADED: {
|
||||
return {
|
||||
...state,
|
||||
tokenSymbol: action.symbol,
|
||||
tokenDecimals: action.decimals,
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import { web3Reducer } from './web3';
|
||||
|
||||
export default function() {
|
||||
return combineReducers({
|
||||
web3: web3Reducer,
|
||||
});
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import { connectRouter } from 'connected-react-router';
|
||||
import {
|
||||
Web3State,
|
||||
web3Reducer,
|
||||
} from './web3';
|
||||
import {
|
||||
BucketState,
|
||||
bucketReducer,
|
||||
} from './bucket';
|
||||
|
||||
export interface RootState {
|
||||
web3: Web3State,
|
||||
bucket: BucketState,
|
||||
}
|
||||
|
||||
|
||||
export default function(history) {
|
||||
return combineReducers({
|
||||
web3: web3Reducer,
|
||||
router: connectRouter(history),
|
||||
bucket: bucketReducer,
|
||||
});
|
||||
}
|
|
@ -1,10 +1,18 @@
|
|||
import {
|
||||
Web3Actions,
|
||||
WEB3_INITIALIZED,
|
||||
WEB3_ERROR,
|
||||
WEB3_NETWORK_ID_LOADED,
|
||||
WEB3_ACCOUNT_LOADED,
|
||||
} from '../actions/web3';
|
||||
|
||||
export interface Web3State {
|
||||
initialized: boolean
|
||||
networkID: number | undefined
|
||||
error: string | undefined
|
||||
account: string | undefined
|
||||
}
|
||||
|
||||
const initialState: Web3State = {
|
||||
initialized: false,
|
||||
networkID: undefined,
|
||||
|
@ -12,7 +20,7 @@ const initialState: Web3State = {
|
|||
account: undefined,
|
||||
};
|
||||
|
||||
export const web3Reducer = (state = initialState, action) => {
|
||||
export const web3Reducer = (state: Web3State = initialState, action: Web3Actions): Web3State => {
|
||||
switch (action.type) {
|
||||
case WEB3_INITIALIZED: {
|
||||
return {
|
||||
|
@ -41,7 +49,8 @@ export const web3Reducer = (state = initialState, action) => {
|
|||
account: action.account,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ module.exports = {
|
|||
default: {
|
||||
// order of connections the dapp should connect to
|
||||
dappConnection: [
|
||||
"$EMBARK",
|
||||
"$WEB3", // uses pre existing web3 object if available (e.g in Mist)
|
||||
"$EMBARK",
|
||||
"ws://localhost:8546",
|
||||
"http://localhost:8545"
|
||||
],
|
||||
|
@ -22,7 +22,7 @@ module.exports = {
|
|||
// when not specified
|
||||
// - explicit will only attempt to deploy the contracts that are explicitly specified inside the
|
||||
// contracts section.
|
||||
// strategy: 'implicit',
|
||||
strategy: 'explicit',
|
||||
|
||||
// minimalContractSize, when set to true, tells Embark to generate contract files without the heavy bytecodes
|
||||
// Using filteredFields lets you customize which field you want to filter out of the contract file (requires minimalContractSize: true)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"contracts": ["contracts/**"],
|
||||
"app": {
|
||||
"css/app.css": ["app/css/**"],
|
||||
"js/app.js": ["app/js/index.js"],
|
||||
"js/app.js": ["app/js/index.tsx"],
|
||||
"images/": ["app/images/**"],
|
||||
"index.html": "app/index.html"
|
||||
},
|
||||
|
@ -12,6 +12,13 @@
|
|||
"solc": "0.6.1"
|
||||
},
|
||||
"plugins": {
|
||||
"embark-ipfs": {},
|
||||
"embark-swarm": {},
|
||||
"embark-whisper-geth": {},
|
||||
"embark-geth": {},
|
||||
"embark-parity": {},
|
||||
"embark-profiler": {},
|
||||
"embark-graph": {}
|
||||
},
|
||||
"options": {
|
||||
"solc": {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"react-dom": "^16.12.0",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"web3": "^1.2.6"
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
// some packages, plugins, and presets referenced/required in this webpack
|
||||
// config are deps of embark and will be transitive dapp deps unless specified
|
||||
// in the dapp's own package.json
|
||||
|
||||
// embark modifies process.env.NODE_PATH so that when running dapp scripts in
|
||||
// embark's child processes, embark's own node_modules directory will be
|
||||
// searched by node's require(); however, webpack and babel do not directly
|
||||
// support NODE_PATH, so modules such as babel plugins and presets must be
|
||||
// resolved with require.resolve(); that is only necessary if a plugin/preset
|
||||
// is in embark's node_modules vs. the dapp's node_modules
|
||||
|
||||
const cloneDeep = require('lodash.clonedeep');
|
||||
// const CompressionPlugin = require('compression-webpack-plugin');
|
||||
const glob = require('glob');
|
||||
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
const dappPath = process.env.DAPP_PATH;
|
||||
const embarkPath = process.env.EMBARK_PATH;
|
||||
|
||||
const embarkAliases = require(path.join(dappPath, '.embark/embark-aliases.json'));
|
||||
const embarkAssets = require(path.join(dappPath, '.embark/embark-assets.json'));
|
||||
const embarkNodeModules = path.join(embarkPath, 'node_modules');
|
||||
const embarkJson = require(path.join(dappPath, 'embark.json'));
|
||||
|
||||
const buildDir = path.join(dappPath, embarkJson.buildDir);
|
||||
|
||||
// it's important to `embark reset` if a pkg version is specified in
|
||||
// embark.json and changed/removed later, otherwise pkg resolution may behave
|
||||
// unexpectedly
|
||||
let versions;
|
||||
try {
|
||||
versions = glob.sync(path.join(dappPath, '.embark/versions/*/*'));
|
||||
} catch (e) {
|
||||
versions = [];
|
||||
}
|
||||
|
||||
const entry = Object.keys(embarkAssets)
|
||||
.filter(key => key.match(/\.js$/))
|
||||
.reduce((obj, key) => {
|
||||
// webpack entry paths should start with './' if they're relative to the
|
||||
// webpack context; embark.json "app" keys correspond to lists of .js
|
||||
// source paths relative to the top-level dapp dir and may be missing the
|
||||
// leading './'
|
||||
obj[key] = embarkAssets[key]
|
||||
.map(file => {
|
||||
let file_path = file.path;
|
||||
if (!file.path.match(/^\.\//)) {
|
||||
file_path = './' + file_path;
|
||||
}
|
||||
return file_path;
|
||||
});
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
function resolve(pkgName) {
|
||||
if (Array.isArray(pkgName)) {
|
||||
const _pkgName = pkgName[0];
|
||||
pkgName[0] = require.resolve(_pkgName);
|
||||
return pkgName;
|
||||
}
|
||||
return require.resolve(pkgName);
|
||||
}
|
||||
|
||||
// base config
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const base = {
|
||||
context: dappPath,
|
||||
entry: entry,
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [{loader: 'style-loader'}, {loader: 'css-loader'}]
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [{loader: 'style-loader'}, {loader: 'css-loader'}]
|
||||
},
|
||||
{
|
||||
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
|
||||
loader: 'url-loader?limit=100000'
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx|tsx|ts)$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /(node_modules|bower_components|\.embark[\\/]versions)/,
|
||||
options: {
|
||||
plugins: [
|
||||
[
|
||||
'babel-plugin-module-resolver', {
|
||||
'alias': embarkAliases
|
||||
}
|
||||
],
|
||||
[
|
||||
'@babel/plugin-transform-runtime', {
|
||||
corejs: 2,
|
||||
useESModules: true
|
||||
}
|
||||
]
|
||||
].map(resolve),
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env', {
|
||||
modules: false,
|
||||
targets: {
|
||||
browsers: ['last 1 version', 'not dead', '> 0.2%']
|
||||
}
|
||||
}
|
||||
],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript'
|
||||
].map(resolve)
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: (chunkData) => chunkData.chunk.name,
|
||||
// globalObject workaround for node-compatible UMD builds with webpack 4
|
||||
// see: https://github.com/webpack/webpack/issues/6522#issuecomment-371120689
|
||||
globalObject: 'typeof self !== \'undefined\' ? self : this',
|
||||
libraryTarget: 'umd',
|
||||
path: buildDir
|
||||
},
|
||||
plugins: [new HardSourceWebpackPlugin()],
|
||||
// profiling and generating verbose stats increases build time; if stats
|
||||
// are generated embark will write the output to:
|
||||
// path.join(dappPath, '.embark/stats.[json,report]')
|
||||
// to visualize the stats info in a browser run:
|
||||
// npx webpack-bundle-analyzer .embark/stats.json <buildDir>
|
||||
profile: true, stats: 'verbose',
|
||||
resolve: {
|
||||
alias: embarkAliases,
|
||||
modules: [
|
||||
...versions,
|
||||
'node_modules',
|
||||
embarkNodeModules
|
||||
]
|
||||
},
|
||||
resolveLoader: {
|
||||
modules: [
|
||||
'node_modules',
|
||||
embarkNodeModules
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// typescript mods
|
||||
// -----------------------------------------------------------------------------
|
||||
base.resolve.extensions = [
|
||||
// webpack defaults
|
||||
// see: https://webpack.js.org/configuration/resolve/#resolve-extensions
|
||||
'.wasm', '.mjs', '.js', '.json',
|
||||
// typescript extensions
|
||||
'.ts', '.tsx'
|
||||
];
|
||||
|
||||
// development config
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const development = cloneDeep(base);
|
||||
// full source maps increase build time but are useful during dapp development
|
||||
development.devtool = 'source-map';
|
||||
development.mode = 'development';
|
||||
// alternatively:
|
||||
// development.mode = 'none';
|
||||
development.name = 'development';
|
||||
const devBabelLoader = development.module.rules[3];
|
||||
devBabelLoader.options.compact = false;
|
||||
|
||||
// production config
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const production = cloneDeep(base);
|
||||
production.mode = 'production';
|
||||
production.name = 'production';
|
||||
// compression of webpack's JS output not enabled by default
|
||||
// production.plugins.push(new CompressionPlugin());
|
||||
|
||||
// export a list of named configs
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
module.exports = [
|
||||
development,
|
||||
production
|
||||
];
|
15
yarn.lock
15
yarn.lock
|
@ -9623,7 +9623,20 @@ react-redux@^7.2.0:
|
|||
prop-types "^15.7.2"
|
||||
react-is "^16.9.0"
|
||||
|
||||
react-router@^5.1.2:
|
||||
react-router-dom@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18"
|
||||
integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
history "^4.9.0"
|
||||
loose-envify "^1.3.1"
|
||||
prop-types "^15.6.2"
|
||||
react-router "5.1.2"
|
||||
tiny-invariant "^1.0.2"
|
||||
tiny-warning "^1.0.0"
|
||||
|
||||
react-router@5.1.2, react-router@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418"
|
||||
integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==
|
||||
|
|
Loading…
Reference in New Issue