Merge pull request #23 from grant-project/zero-ts-errors
"Fix" all typescript errors
This commit is contained in:
commit
149420bbb1
|
@ -0,0 +1,31 @@
|
|||
matrix:
|
||||
include:
|
||||
# Frontend
|
||||
- language: node_js
|
||||
node_js: 8.11.4
|
||||
before_install:
|
||||
- cd frontend/
|
||||
install: yarn
|
||||
script:
|
||||
- yarn run lint
|
||||
- yarn run tsc
|
||||
# Backend
|
||||
- language: python
|
||||
python: 3.6
|
||||
before_install:
|
||||
- cd backend/
|
||||
- cp .env.example .env
|
||||
install: pip install -r requirements/dev.txt
|
||||
script:
|
||||
- flask test
|
||||
# Contracts
|
||||
- language: node_js
|
||||
node_js: 8.11.4
|
||||
before_install:
|
||||
- cd contract/
|
||||
install: yarn && yarn add global truffle ganache-cli
|
||||
before_script:
|
||||
- ganache-cli > /dev/null &
|
||||
- sleep 10
|
||||
script:
|
||||
- yarn run test
|
|
@ -0,0 +1,4 @@
|
|||
"""Sample test for CI"""
|
||||
|
||||
def test_runs():
|
||||
assert True
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
export const Header = styled.h1`
|
||||
font-size: 1.5rem;
|
||||
`;
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -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);
|
|
@ -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} />}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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} />}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
import styled from 'styled-components';
|
|
@ -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;
|
||||
`;
|
||||
|
|
|
@ -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 />} />
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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`;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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}`}
|
||||
|
|
|
@ -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()]),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { Milestone } from 'modules/proposals/reducers';
|
||||
|
||||
export function isNumeric(n: any) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue