Merge branch 'zero-ts-errors' into travis-ci

This commit is contained in:
Will O'Beirne 2018-09-11 16:03:35 -04:00
commit 420005727d
No known key found for this signature in database
GPG Key ID: 44C190DB5DEAF9F6
33 changed files with 187 additions and 360 deletions

View File

@ -1,5 +1,6 @@
import axios from './axios';
import { Proposal } from 'modules/proposals/reducers';
import { PROPOSAL_CATEGORY } from './constants';
export function getProposals(): Promise<{ data: Proposal[] }> {
return axios.get('/api/proposals/');
@ -18,11 +19,12 @@ export function getProposalUpdates(proposalId: number | string) {
}
export function postProposal(payload: {
accountAddress;
crowdFundContractAddress;
content;
title;
milestones;
accountAddress: string;
crowdFundContractAddress: string;
content: string;
title: string;
category: PROPOSAL_CATEGORY;
milestones: object[]; // TODO: Type me
}) {
return axios.post(`/api/proposals/create`, payload);
}

View File

@ -1,6 +1,6 @@
// TODO: Make each section its own page. Reduce size of this component!
import React from 'react';
import Web3Container from 'lib/Web3Container';
import Web3Container, { Web3RenderProps } from 'lib/Web3Container';
import { bindActionCreators, Dispatch } from 'redux';
import { connect } from 'react-redux';
import { compose } from 'recompose';
@ -17,6 +17,23 @@ import { getAmountError } from 'utils/validators';
import MarkdownEditor from 'components/MarkdownEditor';
import * as Styled from './styled';
interface StateProps {
crowdFundLoading: AppState['web3']['crowdFundLoading'];
crowdFundError: AppState['web3']['crowdFundError'];
crowdFundCreatedAddress: AppState['web3']['crowdFundCreatedAddress'];
}
interface DispatchProps {
createCrowdFund: typeof web3Actions['createCrowdFund'];
}
interface Web3Props {
web3: Web3RenderProps['web3'];
contract: Web3RenderProps['contracts'][0];
}
type Props = StateProps & DispatchProps & Web3Props;
interface Errors {
title?: string;
amountToRaise?: string;
@ -61,7 +78,7 @@ function milestoneToMilestoneAmount(milestone: Milestone, raiseGoal: number) {
return computePercentage(raiseGoal, milestone.payoutPercent);
}
class CreateProposal extends React.Component<any, State> {
class CreateProposal extends React.Component<Props, State> {
constructor(props: any) {
super(props);
this.state = { ...DEFAULT_STATE };
@ -172,7 +189,6 @@ class CreateProposal extends React.Component<any, State> {
durationInMinutes: deadline,
milestoneVotingPeriodInMinutes: milestoneDeadline,
immediateFirstMilestonePayout,
category,
};
createCrowdFund(contract, contractData, backendData);
@ -516,7 +532,7 @@ const withConnect = connect(
mapDispatchToProps,
);
const ConnectedCreateProposal = compose(withConnect)(CreateProposal);
const ConnectedCreateProposal = compose<Props, Web3Props>(withConnect)(CreateProposal);
export default () => (
<Web3Container

View File

@ -1,112 +0,0 @@
import React from 'react';
import {
Form,
Select,
InputNumber,
Switch,
Radio,
Slider,
Button,
Upload,
Icon,
Rate,
} from 'antd';
const FormItem = Form.Item;
const Option = Select.Option;
const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;
class Demo extends React.Component {
handleSubmit = e => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
normFile = e => {
console.log('Upload event:', e);
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
render() {
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol: { span: 6 },
wrapperCol: { span: 14 },
};
return (
<Form onSubmit={this.handleSubmit}>
<FormItem {...formItemLayout} label="InputNumber">
{getFieldDecorator('input-number', { initialValue: 3 })(
<InputNumber min={1} max={10} />,
)}
<span className="ant-form-text"> machines</span>
</FormItem>
<FormItem {...formItemLayout} label="Switch">
{getFieldDecorator('switch', { valuePropName: 'checked' })(<Switch />)}
</FormItem>
<FormItem {...formItemLayout} label="Slider">
{getFieldDecorator('slider')(
<Slider marks={{ 0: 'A', 20: 'B', 40: 'C', 60: 'D', 80: 'E', 100: 'F' }} />,
)}
</FormItem>
<FormItem {...formItemLayout} label="Radio.Group">
{getFieldDecorator('radio-group')(
<RadioGroup>
<Radio value="a">item 1</Radio>
<Radio value="b">item 2</Radio>
<Radio value="c">item 3</Radio>
</RadioGroup>,
)}
</FormItem>
<FormItem {...formItemLayout} label="Radio.Button">
{getFieldDecorator('radio-button')(
<RadioGroup>
<RadioButton value="a">item 1</RadioButton>
<RadioButton value="b">item 2</RadioButton>
<RadioButton value="c">item 3</RadioButton>
</RadioGroup>,
)}
</FormItem>
<FormItem {...formItemLayout} label="Dragger">
<div className="dropbox">
{getFieldDecorator('dragger', {
valuePropName: 'fileList',
getValueFromEvent: this.normFile,
})(
<Upload.Dragger name="files" action="/upload.do">
<p className="ant-upload-drag-icon">
<Icon type="inbox" />
</p>
<p className="ant-upload-text">
Click or drag file to this area to upload
</p>
<p className="ant-upload-hint">Support for a single or bulk upload.</p>
</Upload.Dragger>,
)}
</div>
</FormItem>
<FormItem wrapperCol={{ span: 12, offset: 6 }}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</FormItem>
</Form>
);
}
}
export default Form.create()(Demo);

View File

@ -1,70 +0,0 @@
import React from 'react';
import ReactMde, { ReactMdeTypes } from 'react-mde';
import Showdown from 'showdown';
import * as Styled from './styled';
import { Input } from 'antd';
import { Row, Col } from 'antd';
import { InputNumber } from 'antd';
import Form from './Form';
export interface AppState {
mdeState: ReactMdeTypes.MdeState;
}
export default class App extends React.Component<{}, AppState> {
converter: Showdown.Converter;
constructor(props: {}) {
super(props);
this.state = {
mdeState: null,
};
this.converter = new Showdown.Converter({
tables: true,
simplifiedAutoLink: true,
});
}
handleValueChange = (mdeState: ReactMdeTypes.MdeState) => {
this.setState({ mdeState });
};
onChange = () => {};
render() {
// https://github.com/andrerpena/react-mde
return (
<div>
<Row gutter={16}>
<Form />
<Col xs={32} sm={28} md={24} lg={20} xl={18}>
<Styled.Header>Create a new proposal! </Styled.Header>
<InputNumber
size="large"
min={1}
max={100000}
defaultValue={3}
onChange={this.onChange}
/>
<Input
size="large"
placeholder="My Awesome Proposal"
onChange={this.onChange}
/>
<ReactMde
onChange={this.handleValueChange}
editorState={this.state.mdeState}
generateMarkdownPreview={markdown =>
Promise.resolve(this.converter.makeHtml(markdown))
}
/>
</Col>
</Row>
</div>
);
}
}

View File

@ -1,5 +0,0 @@
import styled from 'styled-components';
export const Header = styled.h1`
font-size: 1.5rem;
`;

View File

@ -7,7 +7,7 @@ export const Placeholder = styled.div`
height: ${headerHeight};
`;
export const Header = styled.header`
export const Header = styled<{ isTransparent: boolean }, 'header'>('header')`
position: ${(p: any) => (p.isTransparent ? 'absolute' : 'relative')};
top: 0;
left: 0;

View File

@ -41,17 +41,17 @@ export const HeroButtons = styled.div`
}
`;
export const HeroButton = styled.a`
export const HeroButton = styled<{ isPrimary?: boolean }, 'a'>('a')`
height: 3.6rem;
line-height: 3.6rem;
width: 16rem;
padding: 0;
margin: 0 10px;
background: ${(p: any) =>
background: ${p =>
p.isPrimary
? 'linear-gradient(-180deg, #3498DB 0%, #2C8ACA 100%)'
: 'linear-gradient(-180deg, #FFFFFF 0%, #FAFAFA 98%)'};
color: ${(p: any) => (p.isPrimary ? '#FFF' : '#4C4C4C')};
color: ${p => (p.isPrimary ? '#FFF' : '#4C4C4C')};
text-align: center;
font-size: 1.4rem;
border-radius: 4px;
@ -61,7 +61,7 @@ export const HeroButton = styled.a`
&:hover,
&:focus {
transform: translateY(-2px);
color: ${(p: any) => (p.isPrimary ? '#FFF' : '#4C4C4C')};
color: ${p => (p.isPrimary ? '#FFF' : '#4C4C4C')};
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.25);
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import ReactMde, { ReactMdeTypes, DraftUtil } from 'react-mde';
import ReactMde, { ReactMdeTypes } from 'react-mde';
import Showdown from 'showdown';
interface Props {

View File

@ -16,7 +16,7 @@ export const Form = styled.form`
}
`;
export const Input = styled.input`
export const Input = styled<{ isSuccess?: boolean }, 'input'>('input')`
display: block;
height: ${inputHeight};
width: 100%;
@ -60,7 +60,9 @@ export const Input = styled.input`
}
`;
export const Button = styled.button`
export const Button = styled<{ isLoading?: boolean; isSuccess?: boolean }, 'button'>(
'button',
)`
display: block;
position: absolute;
top: 50%;

View File

@ -1,49 +0,0 @@
import React from 'react';
import { AppState } from 'store/reducers';
import { authActions } from 'modules/auth';
import { getEmail } from 'modules/auth/selectors';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { bindActionCreators, Dispatch } from 'redux';
import { Button } from 'antd';
interface StateProps {
email: string | null;
}
interface DispatchProps {
logoutAndRedirect: authActions.TLogoutAndRedirect;
}
type Props = DispatchProps & StateProps;
class Profile extends React.Component<Props> {
render() {
return (
<div>
<Button type="primary" onClick={() => this.props.logoutAndRedirect()}>
Logout
</Button>
<h1>hi profile. {this.props.email}</h1>
</div>
);
}
}
function mapStateToProps(state: AppState) {
return {
email: getEmail(state),
};
}
function mapDispatchToProps(dispatch: Dispatch) {
return bindActionCreators(authActions, dispatch);
}
const withConnect = connect(
mapStateToProps,
mapDispatchToProps,
);
export default compose(withConnect)(Profile);

View File

@ -203,7 +203,10 @@ const withConnect = connect(
{ fundCrowdFund: web3Actions.fundCrowdFund },
);
const ConnectedCampaignBlock = withRouter(compose(withConnect)(CampaignBlock));
const ConnectedCampaignBlock = compose<Props, OwnProps>(
withRouter,
withConnect,
)(CampaignBlock);
export default (props: OwnProps) => (
<Web3Container
@ -215,13 +218,6 @@ export default (props: OwnProps) => (
</ProposalStyled.Block>
</ProposalStyled.SideBlock>
)}
render={({ web3, accounts, contracts }) => (
<ConnectedCampaignBlock
web3={web3}
accounts={accounts}
contract={contracts[0]}
{...props}
/>
)}
render={() => <ConnectedCampaignBlock {...props} />}
/>
);

View File

@ -64,13 +64,13 @@ export const Button = styled.a`
}
`;
export const FundingOverMessage = styled.div`
export const FundingOverMessage = styled<{ isSuccess: boolean }, 'div'>('div')`
display: flex;
justify-content: center;
align-items: center;
margin: 0.5rem -1rem 0;
font-size: 1.15rem;
color: ${(p: any) => (p.isSuccess ? '#2ECC71' : '#E74C3C')};
color: ${p => (p.isSuccess ? '#2ECC71' : '#E74C3C')};
.anticon {
font-size: 1.5rem;

View File

@ -1,11 +1,7 @@
import React from 'react';
import moment from 'moment';
import { Timeline, Spin, Icon } from 'antd';
import {
ProposalWithCrowdFund,
Milestone,
MILESTONE_STATE,
} from 'modules/proposals/reducers';
import { ProposalWithCrowdFund, MILESTONE_STATE } from 'modules/proposals/reducers';
import Web3Container, { Web3RenderProps } from 'lib/Web3Container';
import * as Styled from './styled';
@ -14,7 +10,7 @@ interface OwnProps {
}
interface Web3Props {
web3: any;
web3: Web3RenderProps['web3'];
}
type Props = OwnProps & Web3Props;

View File

@ -126,12 +126,15 @@ function mapDispatchToProps(dispatch: Dispatch) {
return bindActionCreators({ ...proposalActions, ...web3Actions }, dispatch);
}
const withConnect = connect(
const withConnect = connect<StateProps, DispatchProps, OwnProps, AppState>(
mapStateToProps,
mapDispatchToProps,
);
const ConnectedProposal = withRouter(compose(withConnect)(ProposalDetail));
const ConnectedProposal = compose<Props, OwnProps>(
withRouter,
withConnect,
)(ProposalDetail);
export default (props: OwnProps) => (
<Web3Container
@ -144,13 +147,6 @@ export default (props: OwnProps) => (
</Styled.Top>
</Styled.Container>
)}
render={({ web3, accounts, contracts }) => (
<ConnectedProposal
web3={web3}
accounts={accounts}
contract={contracts[0]}
{...props}
/>
)}
render={() => <ConnectedProposal {...props} />}
/>
);

View File

@ -110,7 +110,7 @@ export const SideBlock = styled.div`
}
`;
export const BodyText = styled.div`
export const BodyText = styled<{ isExpanded: boolean }, 'div'>('div')`
max-height: ${(p: any) => (p.isExpanded ? 'none' : '27rem')};
overflow: hidden;
font-size: 1.1rem;

View File

@ -1,5 +1,6 @@
import React from 'react';
import { Select, Checkbox, Radio, Card, Divider, Affix } from 'antd';
import { RadioChangeEvent } from 'antd/lib/radio';
import {
PROPOSAL_SORT,
SORT_LABELS,
@ -73,7 +74,7 @@ export default class ProposalFilters extends React.Component<Props> {
);
}
private handleCategoryChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
private handleCategoryChange = (ev: RadioChangeEvent) => {
const { filters } = this.props;
const category = ev.target.value as PROPOSAL_CATEGORY;
const categories = ev.target.checked
@ -86,7 +87,7 @@ export default class ProposalFilters extends React.Component<Props> {
});
};
private handleStageChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
private handleStageChange = (ev: RadioChangeEvent) => {
this.props.handleChangeFilters({
...this.props.filters,
stage: ev.target.value as PROPOSAL_STAGE,

View File

@ -1 +0,0 @@
import styled from 'styled-components';

View File

@ -80,8 +80,8 @@ export const FundingRaised = styled.div`
}
`;
export const FundingPercent = styled.div`
color: ${(p: any) => (p.isFunded ? '#2ecc71' : 'inherit')};
export const FundingPercent = styled<{ isFunded: boolean }, 'div'>('div')`
color: ${p => (p.isFunded ? '#2ecc71' : 'inherit')};
font-size: 0.7rem;
padding-left: 0.25rem;
`;

View File

@ -49,6 +49,8 @@ const sortFunctions: { [key in PROPOSAL_SORT]: ProposalSortFn } = {
interface StateProps {
proposals: ReturnType<typeof getProposals>;
proposalsError: AppState['proposal']['proposalsError'];
isFetchingProposals: AppState['proposal']['isFetchingProposals'];
}
interface DispatchProps {
@ -185,16 +187,6 @@ const withConnect = connect(
const ConnectedProposals = compose(withConnect)(Proposals);
export default props => (
<Web3Container
renderLoading={() => <Spin />}
render={({ web3, accounts, contracts }) => (
<ConnectedProposals
web3={web3}
accounts={accounts}
contract={contracts[0]}
{...props}
/>
)}
/>
export default () => (
<Web3Container renderLoading={() => <Spin />} render={() => <ConnectedProposals />} />
);

View File

@ -1,9 +1,10 @@
import React from 'react';
const CrowdFundFactory = require('./contracts/CrowdFundFactory.json');
import { bindActionCreators, Dispatch } from 'redux';
import { connect } from 'react-redux';
import { AppState } from 'store/reducers';
import { web3Actions } from 'modules/web3';
/* tslint:disable no-var-requires --- TODO: find a better way to import json */
const CrowdFundFactory = require('./contracts/CrowdFundFactory.json');
export interface Web3RenderProps {
web3: any;
@ -28,9 +29,9 @@ interface StateProps {
}
interface ActionProps {
setContract(contract: any): void;
setAccounts(): void;
setWeb3(): void;
setContract: typeof web3Actions['setContract'];
setAccounts: typeof web3Actions['setAccounts'];
setWeb3: typeof web3Actions['setWeb3'];
}
type Props = OwnProps & StateProps & ActionProps;

View File

@ -1,7 +1,15 @@
import Web3 from 'web3';
const resolveWeb3 = (resolve, reject) => {
let { web3 } = window;
interface Web3Window extends Window {
web3?: Web3;
}
const resolveWeb3 = (resolve: (web3: Web3) => void, reject: (err: Error) => void) => {
if (typeof window === 'undefined') {
return reject(new Error('No global window variable'));
}
let { web3 } = window as Web3Window;
const alreadyInjected = typeof web3 !== 'undefined'; // i.e. Mist/Metamask
const localProvider = `http://localhost:8545`;

View File

@ -1,25 +1,31 @@
import React from 'react';
import { AppState } from 'store/reducers';
import { configureStore } from 'store/configure';
const isServer = typeof window === 'undefined';
const __NEXT_REDUX_STORE__ = '__NEXT_REDUX_STORE__';
function getOrCreateStore(initialState?: any) {
function getOrCreateStore(initialState?: Partial<AppState>) {
// Always make a new store if server, otherwise state is shared between requests
if (isServer) {
return configureStore(initialState);
}
// Create store if unavailable on the client and set it on the window object
if (!window[__NEXT_REDUX_STORE__]) {
window[__NEXT_REDUX_STORE__] = configureStore(initialState);
const anyWindow = window as any;
if (!anyWindow[__NEXT_REDUX_STORE__]) {
anyWindow[__NEXT_REDUX_STORE__] = configureStore(initialState);
}
return window[__NEXT_REDUX_STORE__];
return anyWindow[__NEXT_REDUX_STORE__];
}
export default App => {
return class AppWithRedux extends React.Component {
static async getInitialProps(appContext) {
interface Props {
initialReduxState: Partial<AppState>;
}
export default (App: any) => {
return class AppWithRedux extends React.Component<Props> {
static async getInitialProps(appContext: any) {
// Get or Create the store with `undefined` as INITIAL_STATE
// This allows you to set a custom default INITIAL_STATE
const store = getOrCreateStore();
@ -38,7 +44,8 @@ export default App => {
};
}
constructor(props) {
private store: any;
constructor(props: Props) {
super(props);
this.store = getOrCreateStore(props.initialReduxState);
}

View File

@ -16,14 +16,6 @@ export function getProposal(
);
}
export function getProposalForumURL(
state: AppState,
proposalId: ProposalWithCrowdFund['proposalId'],
): string | null {
const proposal = getProposal(state, proposalId);
return proposal ? proposal.forum_url : null;
}
export function getProposalComments(
state: AppState,
proposalId: ProposalWithCrowdFund['proposalId'],

View File

@ -5,6 +5,10 @@ import { postProposal } from 'api/api';
import getContract, { WrongNetworkError } from 'lib/getContract';
import { sleep } from 'utils/helpers';
import { fetchProposal, fetchProposals } from 'modules/proposals/actions';
import { PROPOSAL_CATEGORY } from 'api/constants';
import { AppState } from 'store/reducers';
type GetState = () => AppState;
function handleWrongNetworkError(dispatch: (action: any) => void) {
return (err: Error) => {
@ -27,11 +31,12 @@ export function setWeb3() {
}
export type TSetContract = typeof setContract;
export function setContract(json, deployedAddress?) {
return (dispatch: Dispatch<any>, getState: any) => {
export function setContract(json: any, deployedAddress?: string) {
return (dispatch: Dispatch<any>, getState: GetState) => {
const state = getState();
if (state.web3.web3) {
dispatch({
// TODO: Type me as promise dispatch
(dispatch as any)({
type: types.CONTRACT,
payload: getContract(state.web3.web3, json, deployedAddress),
}).catch(handleWrongNetworkError(dispatch));
@ -48,7 +53,7 @@ export function setContract(json, deployedAddress?) {
export type TSetAccounts = typeof setAccounts;
export function setAccounts() {
return (dispatch: Dispatch<any>, getState: any) => {
return (dispatch: Dispatch<any>, getState: GetState) => {
const state = getState();
if (state.web3.web3) {
dispatch({ type: types.ACCOUNTS_PENDING });
@ -82,9 +87,39 @@ export function setAccounts() {
};
}
// TODO: Move these to a better place?
interface MilestoneData {
title: string;
description: string;
date: string;
payoutPercent: number;
immediatePayout: boolean;
}
interface ProposalContractData {
ethAmount: number | string; // TODO: BigNumber
payOutAddress: string;
trusteesAddresses: string[];
milestoneAmounts: number[] | string[]; // TODO: BigNumber
milestones: MilestoneData[];
durationInMinutes: number;
milestoneVotingPeriodInMinutes: number;
immediateFirstMilestonePayout: boolean;
}
interface ProposalBackendData {
title: string;
content: string;
category: PROPOSAL_CATEGORY;
}
export type TCreateCrowdFund = typeof createCrowdFund;
export function createCrowdFund(CrowdFundFactoryContract, contractData, backendData) {
return async (dispatch: Dispatch<any>, getState) => {
export function createCrowdFund(
CrowdFundFactoryContract: any,
contractData: ProposalContractData,
backendData: ProposalBackendData,
) {
return async (dispatch: Dispatch<any>, getState: GetState) => {
dispatch({
type: types.CROWD_FUND_PENDING,
});
@ -117,7 +152,7 @@ export function createCrowdFund(CrowdFundFactoryContract, contractData, backendD
immediateFirstMilestonePayout,
)
.send({ from: accounts[0] })
.once('confirmation', async function(confNumber, receipt) {
.once('confirmation', async (_: any, receipt: any) => {
const crowdFundContractAddress =
receipt.events.ContractCreated.returnValues.newAddress;
await postProposal({
@ -132,7 +167,8 @@ export function createCrowdFund(CrowdFundFactoryContract, contractData, backendD
type: types.CROWD_FUND_CREATED,
payload: crowdFundContractAddress,
});
dispatch(fetchProposals()).catch(handleWrongNetworkError(dispatch));
// TODO: Type me as promise dispatch
(dispatch as any)(fetchProposals()).catch(handleWrongNetworkError(dispatch));
});
} catch (err) {
dispatch({
@ -145,8 +181,8 @@ export function createCrowdFund(CrowdFundFactoryContract, contractData, backendD
}
export type TRequestMilestonePayout = typeof requestMilestonePayout;
export function requestMilestonePayout(crowdFundContract, index) {
return async (dispatch: Dispatch<any>, getState) => {
export function requestMilestonePayout(crowdFundContract: any, index: number) {
return async (dispatch: Dispatch<any>, getState: GetState) => {
dispatch({
type: types.REQUEST_MILESTONE_PAYOUT_PENDING,
});
@ -156,8 +192,7 @@ export function requestMilestonePayout(crowdFundContract, index) {
await crowdFundContract.methods
.requestMilestonePayout(index)
.send({ from: account })
.once('confirmation', async function(confNumber, receipt) {
console.info('Milestone payout request confirmed', { confNumber, receipt });
.once('confirmation', async () => {
await sleep(5000);
await dispatch(fetchProposal(crowdFundContract._address));
dispatch({
@ -175,8 +210,8 @@ export function requestMilestonePayout(crowdFundContract, index) {
}
export type TPayMilestonePayout = typeof payMilestonePayout;
export function payMilestonePayout(crowdFundContract, index) {
return async (dispatch: Dispatch<any>, getState) => {
export function payMilestonePayout(crowdFundContract: any, index: number) {
return async (dispatch: Dispatch<any>, getState: GetState) => {
dispatch({
type: types.PAY_MILESTONE_PAYOUT_PENDING,
});
@ -186,7 +221,7 @@ export function payMilestonePayout(crowdFundContract, index) {
await crowdFundContract.methods
.payMilestonePayout(index)
.send({ from: account })
.once('confirmation', async function(confNumber, receipt) {
.once('confirmation', async () => {
await sleep(5000);
await dispatch(fetchProposal(crowdFundContract._address));
dispatch({
@ -204,9 +239,10 @@ export function payMilestonePayout(crowdFundContract, index) {
};
}
// TODO: BigNumber me
export type TSendTransaction = typeof fundCrowdFund;
export function fundCrowdFund(crowdFundContract, value) {
return async (dispatch: Dispatch<any>, getState) => {
export function fundCrowdFund(crowdFundContract: any, value: number | string) {
return async (dispatch: Dispatch<any>, getState: GetState) => {
dispatch({
type: types.SEND_PENDING,
});
@ -218,7 +254,7 @@ export function fundCrowdFund(crowdFundContract, value) {
await crowdFundContract.methods
.contribute()
.send({ from: account, value: web3.utils.toWei(String(value), 'ether') })
.once('confirmation', async function(confNumber, receipt) {
.once('confirmation', async () => {
await sleep(5000);
await dispatch(fetchProposal(crowdFundContract._address));
dispatch({
@ -241,7 +277,7 @@ export function voteMilestonePayout(
index: number,
vote: boolean,
) {
return async (dispatch: Dispatch<any>, getState: any) => {
return async (dispatch: Dispatch<any>, getState: GetState) => {
dispatch({ type: types.VOTE_AGAINST_MILESTONE_PAYOUT_PENDING });
const state = getState();
const account = state.web3.accounts[0];

View File

@ -1,8 +1,6 @@
import { AppState } from 'store/reducers';
export function findCrowdFund(state: AppState, contractAddress) {
const { crowdFunds } = state.web3;
return crowdFunds.find(crowdFund => {
return crowdFund.crowdFundContract._address === contractAddress;
});
export function findContract(state: AppState, contractAddress: string) {
const { contracts } = state.web3;
return contracts.find(contract => contract._address === contractAddress);
}

View File

@ -11,7 +11,7 @@ class ProposalPage extends Component<RouteProps> {
super(props);
}
render() {
const proposalId = this.props.router.query.id;
const proposalId = this.props.router.query.id as string;
return (
<Web3Page
title={`Proposal ${proposalId}`}

View File

@ -2,7 +2,7 @@ import { Store, createStore, applyMiddleware } from 'redux';
import createSagaMiddleware, { SagaMiddleware } from 'redux-saga';
import thunkMiddleware, { ThunkMiddleware } from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import rootReducer, { combineInitialState } from './reducers';
import rootReducer, { AppState, combineInitialState } from './reducers';
// import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
@ -22,8 +22,10 @@ const bindMiddleware = (middleware: MiddleWare[]) => {
return applyMiddleware(...middleware);
};
export function configureStore(initialState = combineInitialState): Store {
const store: any = createStore(
export function configureStore(
initialState: Partial<AppState> = combineInitialState,
): Store {
const store: Store<AppState> = createStore(
rootReducer,
initialState,
bindMiddleware([sagaMiddleware, thunkMiddleware, promiseMiddleware()]),

View File

@ -2,8 +2,9 @@
// https://github.com/carlos-peru/next-with-api/blob/master/lib/session.js
import cookie from 'js-cookie';
import { AxiosRequestConfig } from 'axios';
export const setCookie = (key, value) => {
export const setCookie = (key: string, value: string) => {
if (process.browser) {
cookie.set(key, value, {
expires: 1,
@ -12,7 +13,7 @@ export const setCookie = (key, value) => {
}
};
export const removeCookie = key => {
export const removeCookie = (key: string) => {
if (process.browser) {
cookie.remove(key, {
expires: 1,
@ -20,21 +21,21 @@ export const removeCookie = key => {
}
};
export const getCookie = (key, req) => {
export const getCookie = (key: string, req: AxiosRequestConfig) => {
return process.browser ? getCookieFromBrowser(key) : getCookieFromServer(key, req);
};
const getCookieFromBrowser = key => {
const getCookieFromBrowser = (key: string) => {
return cookie.get(key);
};
const getCookieFromServer = (key, req) => {
const getCookieFromServer = (key: string, req: AxiosRequestConfig) => {
if (!req.headers.cookie) {
return undefined;
}
const rawCookie = req.headers.cookie
.split(';')
.find(c => c.trim().startsWith(`${key}=`));
.find((c: string) => c.trim().startsWith(`${key}=`));
if (!rawCookie) {
return undefined;
}

View File

@ -1,5 +1,3 @@
import { Milestone } from 'modules/proposals/reducers';
export function isNumeric(n: any) {
return !isNaN(parseFloat(n)) && isFinite(n);
}

View File

@ -1,4 +1,11 @@
export async function collectArrayElements(method, account) {
import { TransactionObject } from 'web3/eth/types';
type Web3Method<T> = (index: number) => TransactionObject<T>;
export async function collectArrayElements<T>(
method: Web3Method<T>,
account: string,
): Promise<T[]> {
const arrayElements = [];
let noError = true;
let index = 0;

View File

@ -22,7 +22,7 @@ export async function getCrowdFundState(
: await web3.eth.getBalance(crowdFundContract._address);
const isFrozen = await crowdFundContract.methods.frozen().call({ from: account });
const trustees = await collectArrayElements(
const trustees = await collectArrayElements<string>(
crowdFundContract.methods.trustees,
account,
);

View File

@ -16,7 +16,8 @@
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
"pre-commit": "lint-staged",
"pre-push": "yarn run lint && yarn run tsc"
}
},
"lint-staged": {
@ -121,6 +122,7 @@
"devDependencies": {
"@types/bn.js": "4.11.1",
"@types/showdown": "1.7.5",
"@types/web3": "1.0.3",
"rimraf": "2.6.2"
}
}

View File

@ -747,7 +747,7 @@
dependencies:
any-observable "^0.3.0"
"@types/bn.js@4.11.1":
"@types/bn.js@*", "@types/bn.js@4.11.1":
version "4.11.1"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.1.tgz#6fd07b93490ecf0f3501a31ea9cfd330885b10fa"
dependencies:
@ -853,6 +853,17 @@
version "1.7.5"
resolved "https://registry.yarnpkg.com/@types/showdown/-/showdown-1.7.5.tgz#91061f2f16d5bdf66b186185999ed675a8908b6a"
"@types/underscore@*":
version "1.8.9"
resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.9.tgz#fef41f800cd23db1b4f262ddefe49cd952d82323"
"@types/web3@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@types/web3/-/web3-1.0.3.tgz#2c5f6905d46eb6e40da8eb7c1e553463862fa599"
dependencies:
"@types/bn.js" "*"
"@types/underscore" "*"
"@zeit/next-css@0.2.0", "@zeit/next-css@^0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@zeit/next-css/-/next-css-0.2.0.tgz#35da071256397b509b86ac7726ce0f7d3593e62b"