mirror of
https://github.com/status-im/MyCrypto.git
synced 2025-02-10 10:07:00 +00:00
[FEATURE] Timestamp scheduling
* Scheduling: Basic date and time widget * Linting fixes * Moved the datetime field to new tab * Fixed push errors * Added missing specs * Undid unintentional UI change * Fixed some failing tests * Ignore datetime parameter when checking if a transaction is full * Added a date selector widget and renamed ScheduleTimestamp to ScheduleDate * Marked componentDidMount * Initialized Pikaday * Revert "Initialized Pikaday" This reverts commit 4e5bf5b2b882f236f5977400abf9b7092cbd1592. * Revert "Marked componentDidMount" This reverts commit 85d52192ac58f4b6ca9219e702f7390cd27e582f. * Revert "Added a date selector widget and renamed ScheduleTimestamp to ScheduleDate" This reverts commit aaad0ac9b565a78d1bfc631754160919fd38a59b. * Converted the date picker into a datetime picker * Added decent styling to the datetimepicker * Added validation to the datetime picker * Fixed prepush errors for scheduling timestamp * Adjusted validation logic scheduling timestamp
This commit is contained in:
parent
9a70d776b4
commit
c6ec79a31b
@ -13,7 +13,8 @@ import {
|
||||
ResetAction,
|
||||
SetGasPriceFieldAction,
|
||||
SetTimeBountyFieldAction,
|
||||
SetWindowStartFieldAction
|
||||
SetWindowStartFieldAction,
|
||||
SetScheduleTimestampFieldAction
|
||||
} from '../actionTypes';
|
||||
import { TypeKeys } from 'actions/transaction/constants';
|
||||
import { InputTimeBountyIntentAction } from 'actions/transaction';
|
||||
@ -112,6 +113,14 @@ const setWindowStartField = (
|
||||
payload
|
||||
});
|
||||
|
||||
type TSetScheduleTimestampField = typeof setScheduleTimestampField;
|
||||
const setScheduleTimestampField = (
|
||||
payload: SetScheduleTimestampFieldAction['payload']
|
||||
): SetScheduleTimestampFieldAction => ({
|
||||
type: TypeKeys.SCHEDULE_TIMESTAMP_FIELD_SET,
|
||||
payload
|
||||
});
|
||||
|
||||
type TReset = typeof reset;
|
||||
const reset = (payload: ResetAction['payload'] = { include: {}, exclude: {} }): ResetAction => ({
|
||||
type: TypeKeys.RESET,
|
||||
@ -134,6 +143,7 @@ export {
|
||||
TSetGasPriceField,
|
||||
TSetWindowStartField,
|
||||
TSetTimeBountyField,
|
||||
TSetScheduleTimestampField,
|
||||
TReset,
|
||||
inputGasLimit,
|
||||
inputGasPrice,
|
||||
@ -150,5 +160,6 @@ export {
|
||||
setValueField,
|
||||
setGasPriceField,
|
||||
setWindowStartField,
|
||||
setScheduleTimestampField,
|
||||
reset
|
||||
};
|
||||
|
@ -5,4 +5,5 @@ export * from './sign';
|
||||
export * from './broadcast';
|
||||
export * from './current';
|
||||
export * from './windowStart';
|
||||
export * from './scheduleTimestamp';
|
||||
export * from './sendEverything';
|
||||
|
@ -0,0 +1,12 @@
|
||||
import { SetCurrentScheduleTimestampAction } from '../actionTypes/scheduleTimestamp';
|
||||
import { TypeKeys } from '../';
|
||||
|
||||
type TSetCurrentScheduleTimestamp = typeof setCurrentScheduleTimestamp;
|
||||
const setCurrentScheduleTimestamp = (
|
||||
payload: SetCurrentScheduleTimestampAction['payload']
|
||||
): SetCurrentScheduleTimestampAction => ({
|
||||
type: TypeKeys.CURRENT_SCHEDULE_TIMESTAMP_SET,
|
||||
payload
|
||||
});
|
||||
|
||||
export { setCurrentScheduleTimestamp, TSetCurrentScheduleTimestamp };
|
@ -99,6 +99,14 @@ interface SetWindowStartFieldAction {
|
||||
};
|
||||
}
|
||||
|
||||
interface SetScheduleTimestampFieldAction {
|
||||
type: TypeKeys.SCHEDULE_TIMESTAMP_FIELD_SET;
|
||||
payload: {
|
||||
raw: string;
|
||||
value: Date | null;
|
||||
};
|
||||
}
|
||||
|
||||
type InputFieldAction = InputNonceAction | InputGasLimitAction | InputDataAction;
|
||||
|
||||
type FieldAction =
|
||||
@ -109,7 +117,8 @@ type FieldAction =
|
||||
| SetValueFieldAction
|
||||
| SetGasPriceFieldAction
|
||||
| SetTimeBountyFieldAction
|
||||
| SetWindowStartFieldAction;
|
||||
| SetWindowStartFieldAction
|
||||
| SetScheduleTimestampFieldAction;
|
||||
|
||||
export {
|
||||
InputGasLimitAction,
|
||||
@ -128,5 +137,6 @@ export {
|
||||
InputFieldAction,
|
||||
SetGasPriceFieldAction,
|
||||
SetTimeBountyFieldAction,
|
||||
SetWindowStartFieldAction
|
||||
SetWindowStartFieldAction,
|
||||
SetScheduleTimestampFieldAction
|
||||
};
|
||||
|
12
common/actions/transaction/actionTypes/scheduleTimestamp.ts
Normal file
12
common/actions/transaction/actionTypes/scheduleTimestamp.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { TypeKeys } from '../constants';
|
||||
|
||||
/* user input */
|
||||
|
||||
interface SetCurrentScheduleTimestampAction {
|
||||
type: TypeKeys.CURRENT_SCHEDULE_TIMESTAMP_SET;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
type CurrentAction = SetCurrentScheduleTimestampAction;
|
||||
|
||||
export { SetCurrentScheduleTimestampAction, CurrentAction };
|
@ -26,6 +26,7 @@ export enum TypeKeys {
|
||||
CURRENT_VALUE_SET = 'CURRENT_VALUE_SET',
|
||||
CURRENT_TO_SET = 'CURRENT_TO_SET',
|
||||
CURRENT_WINDOW_START_SET = 'CURRENT_WINDOW_START_SET',
|
||||
CURRENT_SCHEDULE_TIMESTAMP_SET = 'CURRENT_SCHEDULE_TIMESTAMP_SET',
|
||||
|
||||
DATA_FIELD_INPUT = 'DATA_FIELD_INPUT',
|
||||
GAS_LIMIT_INPUT = 'GAS_LIMIT_INPUT',
|
||||
@ -43,6 +44,7 @@ export enum TypeKeys {
|
||||
GAS_PRICE_FIELD_SET = 'GAS_PRICE_FIELD_SET',
|
||||
TIME_BOUNTY_FIELD_SET = 'TIME_BOUNTY_FIELD_SET',
|
||||
WINDOW_START_FIELD_SET = 'WINDOW_START_FIELD_SET',
|
||||
SCHEDULE_TIMESTAMP_FIELD_SET = 'SCHEDULE_TIMESTAMP_FIELD_SET',
|
||||
|
||||
TOKEN_TO_META_SET = 'TOKEN_TO_META_SET',
|
||||
UNIT_META_SET = 'UNIT_META_SET',
|
||||
|
28
common/components/ScheduleTimestampField.tsx
Normal file
28
common/components/ScheduleTimestampField.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import translate from 'translations';
|
||||
import { ScheduleTimestampFieldFactory } from './ScheduleTimestampFieldFactory';
|
||||
|
||||
interface Props {
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export const ScheduleTimestampField: React.SFC<Props> = ({ isReadOnly }) => (
|
||||
<ScheduleTimestampFieldFactory
|
||||
withProps={({ currentScheduleTimestamp, isValid, onChange, readOnly }) => (
|
||||
<div className="input-group-wrapper">
|
||||
<label className="input-group">
|
||||
<div className="input-group-header">{translate('SCHEDULE_timestamp')}</div>
|
||||
<input
|
||||
className={`input-group-input ${isValid ? '' : 'invalid'}`}
|
||||
type="text"
|
||||
value={currentScheduleTimestamp.raw}
|
||||
readOnly={!!(isReadOnly || readOnly)}
|
||||
spellCheck={false}
|
||||
onChange={onChange}
|
||||
id="datepicker"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
@ -0,0 +1,72 @@
|
||||
import { Query } from 'components/renderCbs';
|
||||
import { setCurrentScheduleTimestamp, TSetCurrentScheduleTimestamp } from 'actions/transaction';
|
||||
import { ScheduleTimestampInputFactory } from './ScheduleTimestampInputFactory';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ICurrentScheduleTimestamp } from 'selectors/transaction';
|
||||
import moment from 'moment';
|
||||
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
|
||||
|
||||
interface DispatchProps {
|
||||
setCurrentScheduleTimestamp: TSetCurrentScheduleTimestamp;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
scheduleTimestamp: string | null;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
export interface CallbackProps {
|
||||
isValid: boolean;
|
||||
readOnly: boolean;
|
||||
currentScheduleTimestamp: ICurrentScheduleTimestamp;
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
}
|
||||
|
||||
type Props = DispatchProps & OwnProps;
|
||||
|
||||
class ScheduleTimestampFieldFactoryClass extends React.Component<Props> {
|
||||
public componentDidMount() {
|
||||
const { scheduleTimestamp } = this.props;
|
||||
if (scheduleTimestamp) {
|
||||
this.props.setCurrentScheduleTimestamp(scheduleTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<ScheduleTimestampInputFactory
|
||||
onChange={this.setScheduleTimestamp}
|
||||
withProps={this.props.withProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private setScheduleTimestamp = (ev: any) => {
|
||||
const value = ev.currentTarget
|
||||
? ev.currentTarget.value
|
||||
: moment(ev).format(EAC_SCHEDULING_CONFIG.SCHEDULE_TIMESTAMP_FORMAT);
|
||||
this.props.setCurrentScheduleTimestamp(value);
|
||||
};
|
||||
}
|
||||
|
||||
const ScheduleTimestampFieldFactory = connect(null, { setCurrentScheduleTimestamp })(
|
||||
ScheduleTimestampFieldFactoryClass
|
||||
);
|
||||
|
||||
interface DefaultScheduleTimestampFieldProps {
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
const DefaultScheduleTimestampField: React.SFC<DefaultScheduleTimestampFieldProps> = ({
|
||||
withProps
|
||||
}) => (
|
||||
<Query
|
||||
params={['scheduleTimestamp']}
|
||||
withQuery={({ scheduleTimestamp }) => (
|
||||
<ScheduleTimestampFieldFactory scheduleTimestamp={scheduleTimestamp} withProps={withProps} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
export { DefaultScheduleTimestampField as ScheduleTimestampFieldFactory };
|
@ -0,0 +1,76 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Query } from 'components/renderCbs';
|
||||
import {
|
||||
getCurrentScheduleTimestamp,
|
||||
isValidCurrentScheduleTimestamp,
|
||||
ICurrentScheduleTimestamp
|
||||
} from 'selectors/transaction';
|
||||
import { connect } from 'react-redux';
|
||||
import { AppState } from 'reducers';
|
||||
import { CallbackProps } from 'components/ScheduleTimestampFieldFactory';
|
||||
import { getResolvingDomain } from 'selectors/ens';
|
||||
import Pikaday from 'pikaday-time';
|
||||
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
|
||||
|
||||
interface StateProps {
|
||||
currentScheduleTimestamp: ICurrentScheduleTimestamp;
|
||||
isValid: boolean;
|
||||
isResolving: boolean;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
onChange(ev: React.FormEvent<HTMLInputElement>): void;
|
||||
withProps(props: CallbackProps): React.ReactElement<any> | null;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps;
|
||||
|
||||
class ScheduleTimestampInputFactoryClass extends Component<Props> {
|
||||
public componentDidMount() {
|
||||
const now = new Date();
|
||||
|
||||
const picker = new Pikaday({
|
||||
field: document.getElementById('datepicker'),
|
||||
format: EAC_SCHEDULING_CONFIG.SCHEDULE_TIMESTAMP_FORMAT,
|
||||
minDate: now,
|
||||
defaultDate: now,
|
||||
setDefaultDate: true,
|
||||
yearRange: [2016, 2100],
|
||||
showTime: true,
|
||||
showMinutes: true,
|
||||
showSeconds: false,
|
||||
use24hour: false,
|
||||
incrementMinuteBy: 5,
|
||||
onSelect: this.props.onChange
|
||||
});
|
||||
picker.setDate(Date.now());
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { currentScheduleTimestamp, onChange, isValid, withProps } = this.props;
|
||||
|
||||
return (
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-11">
|
||||
<Query
|
||||
params={['readOnly']}
|
||||
withQuery={({ readOnly }) =>
|
||||
withProps({
|
||||
currentScheduleTimestamp,
|
||||
isValid,
|
||||
onChange,
|
||||
readOnly: !!readOnly || this.props.isResolving
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const ScheduleTimestampInputFactory = connect((state: AppState) => ({
|
||||
currentScheduleTimestamp: getCurrentScheduleTimestamp(state),
|
||||
isResolving: getResolvingDomain(state),
|
||||
isValid: isValidCurrentScheduleTimestamp(state)
|
||||
}))(ScheduleTimestampInputFactoryClass);
|
1
common/components/ScheduleTimestampFieldFactory/index.ts
Normal file
1
common/components/ScheduleTimestampFieldFactory/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './ScheduleTimestampFieldFactory';
|
@ -9,6 +9,7 @@ export * from './GenerateTransaction';
|
||||
export * from './SendButton';
|
||||
export * from './SigningStatus';
|
||||
export * from './WindowStartField';
|
||||
export * from './ScheduleTimestampField';
|
||||
export { default as NonceField } from './NonceField';
|
||||
export { default as Header } from './Header';
|
||||
export { default as Footer } from './Footer';
|
||||
|
@ -21,7 +21,8 @@ export type Param =
|
||||
| 'value'
|
||||
| 'gaslimit'
|
||||
| 'limit'
|
||||
| 'windowStart';
|
||||
| 'windowStart'
|
||||
| 'scheduleTimestamp';
|
||||
|
||||
interface Props extends RouteComponentProps<{}> {
|
||||
params: Param[];
|
||||
|
@ -39,7 +39,7 @@ class Schedule extends React.Component<Props> {
|
||||
{wallet && (
|
||||
<div className="SubTabs row">
|
||||
<div className="col-sm-8">
|
||||
{wallet.isReadOnly ? <Redirect to="schedule/info" /> : <ScheduleMain />};
|
||||
{wallet.isReadOnly ? <Redirect to="schedule/info" /> : <ScheduleMain />}
|
||||
</div>
|
||||
<SideBar />
|
||||
</div>
|
||||
|
@ -6,7 +6,8 @@ import {
|
||||
AmountField,
|
||||
TXMetaDataPanel,
|
||||
CurrentCustomMessage,
|
||||
WindowStartField
|
||||
WindowStartField,
|
||||
ScheduleTimestampField
|
||||
} from 'components';
|
||||
import { OnlyUnlocked, WhenQueryExists } from 'components/renderCbs';
|
||||
import translate from 'translations';
|
||||
@ -70,6 +71,12 @@ class SchedulingFieldsClass extends Component<StateProps> {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-12">
|
||||
<ScheduleTimestampField />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row form-group">
|
||||
<div className="col-xs-12">
|
||||
<TXMetaDataPanel scheduling={true} />
|
||||
|
@ -12,12 +12,14 @@ export const EAC_SCHEDULING_CONFIG = {
|
||||
TIME_BOUNTY_DEFAULT: 10, // $1
|
||||
TIME_BOUNTY_MAX: 100, // $10
|
||||
TIME_BOUNTY_TO_WEI_MULTIPLIER: new BN('100000000000000'),
|
||||
WINDOW_SIZE_IN_BLOCKS: 90
|
||||
WINDOW_SIZE_IN_BLOCKS: 90,
|
||||
SCHEDULE_TIMESTAMP_FORMAT: 'YYYY-MM-DD HH:mm:ss'
|
||||
};
|
||||
|
||||
export const EAC_ADDRESSES = {
|
||||
KOVAN: {
|
||||
blockScheduler: '0x1afc19a7e642761ba2b55d2a45b32c7ef08269d1'
|
||||
blockScheduler: '0x1afc19a7e642761ba2b55d2a45b32c7ef08269d1',
|
||||
timestampScheduler: '0xc6370807f0164bdf10a66c08d0dab1028dbe80a3'
|
||||
}
|
||||
};
|
||||
|
||||
@ -49,9 +51,10 @@ export const getScheduleData = (
|
||||
windowStart: any,
|
||||
gasPrice: BN | null,
|
||||
timeBounty: any,
|
||||
requiredDeposit: any
|
||||
requiredDeposit: any,
|
||||
scheduleTimestamp: Date | null
|
||||
) => {
|
||||
if (!callValue || !gasPrice || !windowStart) {
|
||||
if (!callValue || !gasPrice || !windowStart || !scheduleTimestamp) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -63,7 +66,8 @@ export const getScheduleData = (
|
||||
gasPrice,
|
||||
EAC_SCHEDULING_CONFIG.FEE,
|
||||
timeBounty,
|
||||
requiredDeposit
|
||||
requiredDeposit,
|
||||
scheduleTimestamp
|
||||
]);
|
||||
};
|
||||
|
||||
|
@ -20,6 +20,7 @@ const INITIAL_STATE: State = {
|
||||
nonce: { raw: '', value: null },
|
||||
value: { raw: '', value: null },
|
||||
windowStart: { raw: '', value: null },
|
||||
scheduleTimestamp: { raw: '', value: null },
|
||||
gasLimit: { raw: '21000', value: new BN(21000) },
|
||||
gasPrice: { raw: '20', value: gasPriceToBase(20) },
|
||||
timeBounty: {
|
||||
@ -79,6 +80,8 @@ export const fields = (
|
||||
return updateField('timeBounty')(state, action);
|
||||
case TK.WINDOW_START_FIELD_SET:
|
||||
return updateField('windowStart')(state, action);
|
||||
case TK.SCHEDULE_TIMESTAMP_FIELD_SET:
|
||||
return updateField('scheduleTimestamp')(state, action);
|
||||
case TK.TOKEN_TO_ETHER_SWAP:
|
||||
return tokenToEther(state, action);
|
||||
case TK.ETHER_TO_TOKEN_SWAP:
|
||||
|
@ -3,7 +3,8 @@ import {
|
||||
SetDataFieldAction,
|
||||
SetNonceFieldAction,
|
||||
SetGasLimitFieldAction,
|
||||
SetWindowStartFieldAction
|
||||
SetWindowStartFieldAction,
|
||||
SetScheduleTimestampFieldAction
|
||||
} from 'actions/transaction';
|
||||
import { Wei } from 'libs/units';
|
||||
|
||||
@ -16,4 +17,5 @@ export interface State {
|
||||
gasPrice: { raw: string; value: Wei };
|
||||
timeBounty: { raw: string; value: Wei };
|
||||
windowStart: SetWindowStartFieldAction['payload'];
|
||||
scheduleTimestamp: SetScheduleTimestampFieldAction['payload'];
|
||||
}
|
||||
|
25
common/sagas/transaction/current/currentScheduleTimestamp.ts
Normal file
25
common/sagas/transaction/current/currentScheduleTimestamp.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { setScheduleTimestampField } from 'actions/transaction/actionCreators/fields';
|
||||
import { call, put, takeLatest } from 'redux-saga/effects';
|
||||
import { SagaIterator } from 'redux-saga';
|
||||
import { TypeKeys } from 'actions/transaction/constants';
|
||||
import { SetScheduleTimestampFieldAction } from 'actions/transaction';
|
||||
import { SetCurrentScheduleTimestampAction } from 'actions/transaction/actionTypes/scheduleTimestamp';
|
||||
|
||||
export function* setCurrentScheduleTimestamp({
|
||||
payload: raw
|
||||
}: SetCurrentScheduleTimestampAction): SagaIterator {
|
||||
let value: Date | null = null;
|
||||
|
||||
value = new Date(raw);
|
||||
|
||||
yield call(setField, { value, raw });
|
||||
}
|
||||
|
||||
export function* setField(payload: SetScheduleTimestampFieldAction['payload']) {
|
||||
yield put(setScheduleTimestampField(payload));
|
||||
}
|
||||
|
||||
export const currentScheduleTimestamp = takeLatest(
|
||||
[TypeKeys.CURRENT_SCHEDULE_TIMESTAMP_SET],
|
||||
setCurrentScheduleTimestamp
|
||||
);
|
@ -1,5 +1,6 @@
|
||||
import { currentTo } from './currentTo';
|
||||
import { currentValue } from './currentValue';
|
||||
import { currentWindowStart } from './currentWindowStart';
|
||||
import { currentScheduleTimestamp } from './currentScheduleTimestamp';
|
||||
|
||||
export const current = [currentTo, ...currentValue, currentWindowStart];
|
||||
export const current = [currentTo, ...currentValue, currentWindowStart, currentScheduleTimestamp];
|
||||
|
@ -35,6 +35,7 @@
|
||||
@import './styles/tab';
|
||||
@import './styles/flexbox';
|
||||
@import './styles/helpers';
|
||||
@import './styles/datetimepicker';
|
||||
@import './styles/code';
|
||||
@import './fonts';
|
||||
|
||||
|
51
common/sass/styles/datetimepicker.scss
Normal file
51
common/sass/styles/datetimepicker.scss
Normal file
@ -0,0 +1,51 @@
|
||||
$border-color: #e5ecf3;
|
||||
$datetimepicker-border: 2px solid $border-color;
|
||||
|
||||
.pika-single {
|
||||
background-color: #fff;
|
||||
border: $datetimepicker-border;
|
||||
padding: 20px;
|
||||
}
|
||||
.pika-single.is-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pika-next {
|
||||
@extend .pull-right;
|
||||
}
|
||||
|
||||
.pika-button, .pika-prev, .pika-next {
|
||||
@extend .btn;
|
||||
@extend .btn-sm;
|
||||
}
|
||||
:not(.is-disabled) > .pika-button:hover, .pika-prev:hover, .pika-next:hover {
|
||||
@extend .btn-primary;
|
||||
}
|
||||
|
||||
.is-disabled > .pika-button:hover {
|
||||
color: $border-color;
|
||||
}
|
||||
|
||||
.pika-time-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.pika-label {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.pika-label select {
|
||||
@extend .pull-right;
|
||||
}
|
||||
|
||||
.pika-title {
|
||||
margin-bottom: 20px;
|
||||
border-bottom: $datetimepicker-border;
|
||||
}
|
||||
|
||||
.pika-lendar {
|
||||
border-bottom: $datetimepicker-border;
|
||||
}
|
||||
|
||||
tr.pika-row > td.is-disabled {
|
||||
color: $border-color;
|
||||
}
|
@ -12,6 +12,7 @@ const getValue = (state: AppState) => getFields(state).value;
|
||||
const getNonce = (state: AppState) => getFields(state).nonce;
|
||||
const getTimeBounty = (state: AppState) => getFields(state).timeBounty;
|
||||
const getWindowStart = (state: AppState) => getFields(state).windowStart;
|
||||
const getScheduleTimestamp = (state: AppState) => getFields(state).scheduleTimestamp;
|
||||
|
||||
const getDataExists = (state: AppState) => {
|
||||
const { value } = getData(state);
|
||||
@ -39,5 +40,6 @@ export {
|
||||
getDataExists,
|
||||
getValidGasCost,
|
||||
getTimeBounty,
|
||||
getWindowStart
|
||||
getWindowStart,
|
||||
getScheduleTimestamp
|
||||
};
|
||||
|
@ -30,6 +30,7 @@ export const isFullTx = (
|
||||
const partialParamsToCheck = { ...rest };
|
||||
|
||||
delete partialParamsToCheck.windowStart;
|
||||
delete partialParamsToCheck.scheduleTimestamp;
|
||||
|
||||
const validPartialParams = Object.values(partialParamsToCheck).reduce<boolean>(
|
||||
(isValid, v: AppState['transaction']['fields'] & ICurrentTo & ICurrentValue) =>
|
||||
@ -68,3 +69,10 @@ export const isWindowStartValid = (
|
||||
|
||||
return Boolean(windowStart && windowStart.value && windowStart.value > parseInt(latestBlock, 10));
|
||||
};
|
||||
|
||||
export const isScheduleTimestampValid = (transactionFields: AppState['transaction']['fields']) => {
|
||||
const { scheduleTimestamp } = transactionFields;
|
||||
const now = new Date();
|
||||
|
||||
return Boolean(scheduleTimestamp && scheduleTimestamp.value && scheduleTimestamp.value > now);
|
||||
};
|
||||
|
@ -5,4 +5,5 @@ export * from './meta';
|
||||
export * from './sign';
|
||||
export * from './current';
|
||||
export * from './windowStart';
|
||||
export * from './scheduleTimestamp';
|
||||
export * from './network';
|
||||
|
30
common/selectors/transaction/scheduleTimestamp.ts
Normal file
30
common/selectors/transaction/scheduleTimestamp.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { AppState } from 'reducers';
|
||||
import { getScheduleTimestamp } from './fields';
|
||||
import moment from 'moment';
|
||||
import { EAC_SCHEDULING_CONFIG } from 'libs/scheduling';
|
||||
|
||||
interface ICurrentScheduleTimestamp {
|
||||
raw: string;
|
||||
value: Date | null;
|
||||
}
|
||||
|
||||
const isValidCurrentScheduleTimestamp = (state: AppState) => {
|
||||
const currentScheduleTimestamp = getScheduleTimestamp(state);
|
||||
const raw = currentScheduleTimestamp.raw;
|
||||
const value = currentScheduleTimestamp.value;
|
||||
|
||||
const fiveMinFromNow = moment()
|
||||
.add(5, 'm')
|
||||
.toDate();
|
||||
|
||||
return value && value > fiveMinFromNow && isCorrectFormat(raw);
|
||||
};
|
||||
|
||||
const isCorrectFormat = (dateString: string) => {
|
||||
return moment(dateString, EAC_SCHEDULING_CONFIG.SCHEDULE_TIMESTAMP_FORMAT, true).isValid();
|
||||
};
|
||||
|
||||
const getCurrentScheduleTimestamp = (state: AppState): ICurrentScheduleTimestamp =>
|
||||
getScheduleTimestamp(state);
|
||||
|
||||
export { getCurrentScheduleTimestamp, ICurrentScheduleTimestamp, isValidCurrentScheduleTimestamp };
|
@ -1,10 +1,22 @@
|
||||
import { AppState } from 'reducers';
|
||||
import { getCurrentTo, getCurrentValue } from './current';
|
||||
import { getFields, getData, getWindowStart, getNonce, getTimeBounty } from './fields';
|
||||
import {
|
||||
getFields,
|
||||
getData,
|
||||
getWindowStart,
|
||||
getNonce,
|
||||
getTimeBounty,
|
||||
getScheduleTimestamp
|
||||
} from './fields';
|
||||
import { makeTransaction, IHexStrTransaction } from 'libs/transaction';
|
||||
import EthTx from 'ethereumjs-tx';
|
||||
import { getUnit } from 'selectors/transaction/meta';
|
||||
import { reduceToValues, isFullTx, isWindowStartValid } from 'selectors/transaction/helpers';
|
||||
import {
|
||||
reduceToValues,
|
||||
isFullTx,
|
||||
isWindowStartValid,
|
||||
isScheduleTimestampValid
|
||||
} from 'selectors/transaction/helpers';
|
||||
import {
|
||||
getGasPrice,
|
||||
getGasLimit,
|
||||
@ -62,15 +74,18 @@ const getSchedulingTransaction = (state: AppState): IGetTransaction => {
|
||||
const callData = getData(state);
|
||||
const validGasCost = getValidGasCost(state);
|
||||
const windowStart = getWindowStart(state);
|
||||
const scheduleTimestamp = getScheduleTimestamp(state);
|
||||
const gasLimit = getGasLimit(state);
|
||||
const nonce = getNonce(state);
|
||||
const gasPrice = getGasPrice(state);
|
||||
const timeBounty = getTimeBounty(state);
|
||||
const windowStartValid = isWindowStartValid(transactionFields, getLatestBlock(state));
|
||||
const scheduleTimestampValid = isScheduleTimestampValid(transactionFields);
|
||||
|
||||
const isFullTransaction =
|
||||
isFullTx(state, transactionFields, currentTo, currentValue, dataExists, validGasCost, unit) &&
|
||||
windowStartValid;
|
||||
windowStartValid &&
|
||||
scheduleTimestampValid;
|
||||
|
||||
const transactionData = getScheduleData(
|
||||
currentTo.raw,
|
||||
@ -81,7 +96,8 @@ const getSchedulingTransaction = (state: AppState): IGetTransaction => {
|
||||
windowStart.value,
|
||||
gasPrice.value,
|
||||
timeBounty.value,
|
||||
EAC_SCHEDULING_CONFIG.REQUIRED_DEPOSIT
|
||||
EAC_SCHEDULING_CONFIG.REQUIRED_DEPOSIT,
|
||||
scheduleTimestamp.value
|
||||
);
|
||||
|
||||
const endowment = calcEACEndowment(
|
||||
|
1
common/typescript/pikaday-time.d.ts
vendored
Normal file
1
common/typescript/pikaday-time.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
declare module 'pikaday-time';
|
@ -17,7 +17,8 @@ describe('fields reducer', () => {
|
||||
raw: EAC_SCHEDULING_CONFIG.TIME_BOUNTY_DEFAULT.toString(),
|
||||
value: timeBountyRawToValue(EAC_SCHEDULING_CONFIG.TIME_BOUNTY_DEFAULT)
|
||||
},
|
||||
windowStart: { raw: '', value: null }
|
||||
windowStart: { raw: '', value: null },
|
||||
scheduleTimestamp: { raw: '', value: null }
|
||||
};
|
||||
const testPayload = { raw: 'test', value: null };
|
||||
|
||||
|
@ -47,6 +47,10 @@ describe('fields selector', () => {
|
||||
windowStart: {
|
||||
raw: '',
|
||||
value: null
|
||||
},
|
||||
scheduleTimestamp: {
|
||||
raw: '',
|
||||
value: null
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -51,6 +51,10 @@ describe('helpers selector', () => {
|
||||
windowStart: {
|
||||
raw: '',
|
||||
value: null
|
||||
},
|
||||
scheduleTimestamp: {
|
||||
raw: '',
|
||||
value: null
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -64,7 +68,8 @@ describe('helpers selector', () => {
|
||||
to: new Buffer([0, 1, 2, 3]),
|
||||
value: Wei('1000000000'),
|
||||
timeBounty: Wei('1500'),
|
||||
windowStart: null
|
||||
windowStart: null,
|
||||
scheduleTimestamp: null
|
||||
};
|
||||
expect(reduceToValues(state.transaction.fields)).toEqual(values);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user