Merge branch 'master' into amplitude

This commit is contained in:
Liam Cardenas 2017-12-05 17:12:47 -08:00
commit 4a31ed229b
45 changed files with 1302 additions and 124 deletions

View file

@ -8,7 +8,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
## [Unreleased]
### Added
*
* ShapeShift integration
*
### Changed
@ -21,6 +21,7 @@ Web UI version numbers should always match the corresponding version of LBRY App
* Fixed scriolling restore/reset/set (#729)
* Fixed sorting by title for published files (#614)
* App now uses the new balance_delta field in the txn list.
* Abandoning from the claim page now works.
*
### Deprecated

View file

@ -12,6 +12,9 @@ flow-typed
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
module.name_mapper='^constants\(.*\)$' -> '<PROJECT_ROOT>/js/constants\1'
module.name_mapper='^util\(.*\)$' -> '<PROJECT_ROOT>/js/util\1'
module.name_mapper='^redux\(.*\)$' -> '<PROJECT_ROOT>/js/redux\1'
module.name_mapper='^types\(.*\)$' -> '<PROJECT_ROOT>/js/types\1'
module.name_mapper='^component\(.*\)$' -> '<PROJECT_ROOT>/js/component\1'
[strict]

3
src/renderer/flow-typed/bluebird.js vendored Normal file
View file

@ -0,0 +1,3 @@
declare module 'bluebird' {
declare module.exports: any;
}

3
src/renderer/flow-typed/classnames.js vendored Normal file
View file

@ -0,0 +1,3 @@
declare module 'classnames' {
declare module.exports: any;
}

3
src/renderer/flow-typed/formik.js vendored Normal file
View file

@ -0,0 +1,3 @@
declare module 'formik' {
declare module.exports: any;
}

1
src/renderer/flow-typed/i18n.js vendored Normal file
View file

@ -0,0 +1 @@
declare function __(a: string): string;

View file

@ -0,0 +1,3 @@
declare module 'qrcode.react' {
declare module.exports: any;
}

View file

@ -0,0 +1,3 @@
declare module 'shapeshift.io' {
declare module.exports: any;
}

View file

@ -0,0 +1,7 @@
import { connect } from "react-redux";
import { doShowSnackBar } from "redux/actions/app";
import Address from "./view";
export default connect(null, {
doShowSnackBar,
})(Address);

View file

@ -0,0 +1,52 @@
import React from "react";
import PropTypes from "prop-types";
import { clipboard } from "electron";
import Link from "component/link";
import classnames from "classnames";
export default class Address extends React.PureComponent {
static propTypes = {
address: PropTypes.string,
};
constructor(props) {
super(props);
this._inputElem = null;
}
render() {
const { address, showCopyButton, doShowSnackBar } = this.props;
return (
<div className="form-field form-field--address">
<input
className={classnames("input-copyable", {
"input-copyable--with-copy-btn": showCopyButton,
})}
type="text"
ref={input => {
this._inputElem = input;
}}
onFocus={() => {
this._inputElem.select();
}}
readOnly="readonly"
value={address || ""}
/>
{showCopyButton && (
<span className="header__item">
<Link
button="alt button--flat"
icon="clipboard"
onClick={() => {
clipboard.writeText(address);
doShowSnackBar({ message: __("Address copied") });
}}
/>
</span>
)}
</div>
);
}
}

View file

@ -137,42 +137,6 @@ export class CreditAmount extends React.PureComponent {
}
}
let addressStyle = {
fontFamily:
'"Consolas", "Lucida Console", "Adobe Source Code Pro", monospace',
};
export class Address extends React.PureComponent {
static propTypes = {
address: PropTypes.string,
};
constructor(props) {
super(props);
this._inputElem = null;
}
render() {
return (
<div className="form-field form-field--address">
<input
className="input-copyable"
type="text"
ref={input => {
this._inputElem = input;
}}
onFocus={() => {
this._inputElem.select();
}}
style={addressStyle}
readOnly="readonly"
value={this.props.address || ""}
/>
</div>
);
}
}
export class Thumbnail extends React.PureComponent {
static propTypes = {
src: PropTypes.string,

View file

@ -0,0 +1,16 @@
import React from "react";
import classnames from "classnames";
export default ({ dark, className }) => {
return (
<div
className={classnames(
"spinner",
{
"spinner--dark": dark,
},
className
)}
/>
);
};

View file

@ -14,11 +14,12 @@ const Link = props => {
navigate,
navigateParams,
doNavigate,
className,
} = props;
const className =
(props.className || "") +
(!props.className && !button ? "button-text" : "") + // Non-button links get the same look as text buttons
const combinedClassName =
(className || "") +
(!className && !button ? "button-text" : "") + // Non-button links get the same look as text buttons
(button ? " button-block button-" + button + " button-set-item" : "") +
(disabled ? " disabled" : "");
@ -43,7 +44,7 @@ const Link = props => {
return (
<a
className={className}
className={combinedClassName}
href={href || "javascript:;"}
title={title}
onClick={onClick}

View file

@ -0,0 +1,26 @@
import { connect } from "react-redux";
import {
createShapeShift,
shapeShiftInit,
getCoinStats,
clearShapeShift,
getActiveShift,
} from "redux/actions/shape_shift";
import { doShowSnackBar } from "redux/actions/app";
import { selectReceiveAddress } from "redux/selectors/wallet";
import { selectShapeShift } from "redux/selectors/shape_shift";
import ShapeShift from "./view";
const select = state => ({
receiveAddress: selectReceiveAddress(state),
shapeShift: selectShapeShift(state),
});
export default connect(select, {
shapeShiftInit,
getCoinStats,
createShapeShift,
clearShapeShift,
getActiveShift,
doShowSnackBar,
})(ShapeShift);

View file

@ -0,0 +1,161 @@
// @flow
import * as React from "react";
import QRCode from "qrcode.react";
import * as statuses from "constants/shape_shift";
import Address from "component/address";
import Link from "component/link";
import type { Dispatch } from "redux/actions/shape_shift";
import ShiftMarketInfo from "./market_info";
type Props = {
shiftState: ?string,
shiftCoinType: ?string,
shiftDepositAddress: ?string,
shiftReturnAddress: ?string,
shiftOrderId: ?string,
originCoinDepositMax: ?number,
clearShapeShift: Dispatch,
doShowSnackBar: Dispatch,
getActiveShift: Dispatch,
shapeShiftRate: ?number,
originCoinDepositMax: ?number,
originCoinDepositFee: ?number,
originCoinDepositMin: ?string,
};
class ActiveShapeShift extends React.PureComponent<Props> {
continousFetch: ?number;
constructor() {
super();
this.continousFetch = undefined;
}
componentDidMount() {
const { getActiveShift, shiftDepositAddress } = this.props;
getActiveShift(shiftDepositAddress);
this.continousFetch = setInterval(() => {
getActiveShift(shiftDepositAddress);
}, 10000);
}
componentWillUpdate(nextProps: Props) {
const { shiftState } = nextProps;
if (shiftState === statuses.COMPLETE) {
this.clearContinuousFetch();
}
}
componentWillUnmount() {
this.clearContinuousFetch();
}
clearContinuousFetch() {
if (this.continousFetch) {
clearInterval(this.continousFetch);
this.continousFetch = null;
}
}
render() {
const {
shiftCoinType,
shiftDepositAddress,
shiftReturnAddress,
shiftOrderId,
shiftState,
originCoinDepositMax,
clearShapeShift,
doShowSnackBar,
shapeShiftRate,
originCoinDepositFee,
originCoinDepositMin,
} = this.props;
return (
<div>
{shiftState === statuses.NO_DEPOSITS && (
<div>
<p>
Send up to{" "}
<span className="credit-amount--bold">
{originCoinDepositMax} {shiftCoinType}
</span>{" "}
to the address below.
</p>
<ShiftMarketInfo
originCoin={shiftCoinType}
shapeShiftRate={shapeShiftRate}
originCoinDepositFee={originCoinDepositFee}
originCoinDepositMin={originCoinDepositMin}
originCoinDepositMax={originCoinDepositMax}
/>
<div className="shapeshift__deposit-address-wrapper">
<Address address={shiftDepositAddress} showCopyButton />
<div className="shapeshift__qrcode">
<QRCode value={shiftDepositAddress} />
</div>
</div>
</div>
)}
{shiftState === statuses.RECEIVED && (
<div className="card__content--extra-vertical-space">
<p>
{__(
"ShapeShift has received your payment! Sending the funds to your LBRY wallet."
)}
</p>
<span className="help">
{__("This can take a while, especially with BTC.")}
</span>
</div>
)}
{shiftState === statuses.COMPLETE && (
<div className="card__content--extra-vertical-space">
<p>
{__(
"Transaction complete! You should see the new LBC in your wallet."
)}
</p>
</div>
)}
<div className="card__actions card__actions--only-vertical">
<Link
button={shiftState === statuses.COMPLETE ? "primary" : "alt"}
onClick={clearShapeShift}
label={
shiftState === statuses.COMPLETE ||
shiftState === statuses.RECEIVED
? __("Done")
: __("Cancel")
}
/>
{shiftOrderId && (
<span className="shapeshift__link">
<Link
button="text"
label={__("View the status on Shapeshift.io")}
href={`https://shapeshift.io/#/status/${shiftOrderId}`}
/>
</span>
)}
{shiftState === statuses.NO_DEPOSITS &&
shiftReturnAddress && (
<div className="shapeshift__actions-help">
<span className="help">
If the transaction doesn't go through, ShapeShift will return
your {shiftCoinType} back to {shiftReturnAddress}
</span>
</div>
)}
</div>
</div>
);
}
}
export default ActiveShapeShift;

View file

@ -0,0 +1,109 @@
import React from "react";
import Link from "component/link";
import { getExampleAddress } from "util/shape_shift";
import { Submit, FormRow } from "component/form";
import type { ShapeShiftFormValues, Dispatch } from "redux/actions/shape_shift";
import ShiftMarketInfo from "./market_info";
type ShapeShiftFormErrors = {
returnAddress?: string,
};
type Props = {
values: ShapeShiftFormValues,
errors: ShapeShiftFormErrors,
touched: boolean,
handleChange: Event => any,
handleBlur: Event => any,
handleSubmit: Event => any,
isSubmitting: boolean,
shiftSupportedCoins: Array<string>,
originCoin: string,
updating: boolean,
getCoinStats: Dispatch,
receiveAddress: string,
originCoinDepositFee: number,
originCoinDepositMin: string,
originCoinDepositMax: number,
shapeShiftRate: number,
};
export default (props: Props) => {
const {
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
shiftSupportedCoins,
originCoin,
updating,
getCoinStats,
receiveAddress,
originCoinDepositMax,
originCoinDepositMin,
originCoinDepositFee,
shapeShiftRate,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="form-field">
<span>{__("Exchange")} </span>
<select
className="form-field__input form-field__input-select"
name="originCoin"
onChange={e => {
getCoinStats(e.target.value);
handleChange(e);
}}
>
{shiftSupportedCoins.map(coin => (
<option key={coin} value={coin}>
{coin}
</option>
))}
</select>
<span> {__("for LBC")}</span>
<div className="shapeshift__tx-info">
{!updating &&
originCoinDepositMax && (
<ShiftMarketInfo
originCoin={originCoin}
shapeShiftRate={shapeShiftRate}
originCoinDepositFee={originCoinDepositFee}
originCoinDepositMin={originCoinDepositMin}
originCoinDepositMax={originCoinDepositMax}
/>
)}
</div>
</div>
<FormRow
type="text"
name="returnAddress"
placeholder={getExampleAddress(originCoin)}
label={__("Return address")}
onChange={handleChange}
onBlur={handleBlur}
value={values.returnAddress}
errorMessage={errors.returnAddress}
hasError={touched.returnAddress && !!errors.returnAddress}
/>
<span className="help">
<span>
({__("optional but recommended")}) {__("We will return your")}{" "}
{originCoin}{" "}
{__("to this address if the transaction doesn't go through.")}
</span>
</span>
<div className="card__actions card__actions--only-vertical">
<Submit
label={__("Begin Conversion")}
disabled={isSubmitting || !!Object.keys(errors).length}
/>
</div>
</form>
);
};

View file

@ -0,0 +1,34 @@
// @flow
import React from "react";
type Props = {
shapeShiftRate: ?number,
originCoin: ?string,
originCoinDepositFee: ?number,
originCoinDepositMax: ?number,
originCoinDepositMin: ?string,
};
export default (props: Props) => {
const {
shapeShiftRate,
originCoin,
originCoinDepositFee,
originCoinDepositMax,
originCoinDepositMin,
} = props;
return (
<div>
<span className="help">
{__("Receive")} {shapeShiftRate} LBC
{" / "}
{"1"} {originCoin} {__("less")} {originCoinDepositFee} LBC {__("fee")}.
<br />
{__("Exchange max")}: {originCoinDepositMax} {originCoin}
<br />
{__("Exchange min")}: {originCoinDepositMin} {originCoin}
</span>
</div>
);
};

View file

@ -0,0 +1,150 @@
// @flow
import * as React from "react";
import { shell } from "electron";
import { Formik } from "formik";
import classnames from "classnames";
import * as statuses from "constants/shape_shift";
import { validateShapeShiftForm } from "util/shape_shift";
import Link from "component/link";
import Spinner from "component/common/spinner";
import { BusyMessage } from "component/common";
import ShapeShiftForm from "./internal/form";
import ActiveShapeShift from "./internal/active-shift";
import type { ShapeShiftState } from "redux/reducers/shape_shift";
import type { Dispatch, ShapeShiftFormValues } from "redux/actions/shape_shift";
type Props = {
shapeShift: ShapeShiftState,
getCoinStats: Dispatch,
createShapeShift: Dispatch,
clearShapeShift: Dispatch,
getActiveShift: Dispatch,
doShowSnackBar: Dispatch,
shapeShiftInit: Dispatch,
receiveAddress: string,
};
class ShapeShift extends React.PureComponent<Props> {
componentDidMount() {
const {
shapeShiftInit,
shapeShift: { hasActiveShift, shiftSupportedCoins },
} = this.props;
if (!hasActiveShift && !shiftSupportedCoins.length) {
// calls shapeshift to see list of supported coins for shifting
shapeShiftInit();
}
}
render() {
const {
getCoinStats,
receiveAddress,
createShapeShift,
shapeShift,
clearShapeShift,
getActiveShift,
doShowSnackBar,
} = this.props;
const {
loading,
updating,
error,
shiftSupportedCoins,
hasActiveShift,
originCoin,
// ShapeShift response values
originCoinDepositMax,
originCoinDepositMin,
originCoinDepositFee,
shiftDepositAddress,
shiftReturnAddress,
shiftCoinType,
shiftOrderId,
shiftState,
shapeShiftRate,
} = shapeShift;
const initialFormValues: ShapeShiftFormValues = {
receiveAddress,
originCoin: "BTC",
returnAddress: "",
};
return (
// add the "shapeshift__intital-wrapper class so we can avoid content jumping once everything loads"
// it just gives the section a min-height equal to the height of the content when the form is rendered
// if the markup below changes for the initial render (form.jsx) there will be content jumping
// the styling in shapeshift.scss will need to be updated to the correct min-height
<section
className={classnames("card shapeshift__wrapper", {
"shapeshift__initial-wrapper": loading,
})}
>
<div className="card__title-primary">
<h3>{__("Convert Crypto to LBC")}</h3>
<p className="help">
{__("Powered by ShapeShift. Read our FAQ")}{" "}
<Link href="https://lbry.io/faq/shapeshift">{__("here")}</Link>.
{hasActiveShift &&
shiftState !== "complete" && (
<span>{__("This will update automatically.")}</span>
)}
</p>
</div>
<div className="card__content shapeshift__content">
{error && <div className="form-field__error">{error}</div>}
{loading && <Spinner dark />}
{!loading &&
!hasActiveShift &&
!!shiftSupportedCoins.length && (
<Formik
onSubmit={createShapeShift}
validate={validateShapeShiftForm}
initialValues={initialFormValues}
render={formProps => (
<ShapeShiftForm
{...formProps}
updating={updating}
originCoin={originCoin}
shiftSupportedCoins={shiftSupportedCoins}
getCoinStats={getCoinStats}
receiveAddress={receiveAddress}
originCoinDepositMax={originCoinDepositMax}
originCoinDepositMin={originCoinDepositMin}
originCoinDepositFee={originCoinDepositFee}
shapeShiftRate={shapeShiftRate}
updating={updating}
/>
)}
/>
)}
{hasActiveShift && (
<ActiveShapeShift
getActiveShift={getActiveShift}
shiftCoinType={shiftCoinType}
shiftReturnAddress={shiftReturnAddress}
shiftDepositAddress={shiftDepositAddress}
originCoinDepositMax={originCoinDepositMax}
shiftOrderId={shiftOrderId}
shiftState={shiftState}
clearShapeShift={clearShapeShift}
doShowSnackBar={doShowSnackBar}
originCoinDepositMax={originCoinDepositMax}
originCoinDepositMin={originCoinDepositMin}
originCoinDepositFee={originCoinDepositFee}
shapeShiftRate={shapeShiftRate}
updating={updating}
/>
)}
</div>
</section>
);
}
}
export default ShapeShift;

View file

@ -1,9 +1,10 @@
import React from "react";
import Spinner from "component/common/spinner";
const LoadingScreen = ({ status, spinner = true }) => (
<div className="video__loading-screen">
<div>
{spinner && <div className="video__loading-spinner" />}
{spinner && <Spinner />}
<div className="video__loading-status">{status}</div>
</div>

View file

@ -1,6 +1,6 @@
import React from "react";
import Link from "component/link";
import { Address } from "component/common";
import Address from "component/address";
class WalletAddress extends React.PureComponent {
componentWillMount() {

View file

@ -144,3 +144,18 @@ export const FETCH_REWARD_CONTENT_COMPLETED = "FETCH_REWARD_CONTENT_COMPLETED";
//Language
export const DOWNLOAD_LANGUAGE_SUCCEEDED = "DOWNLOAD_LANGUAGE_SUCCEEDED";
export const DOWNLOAD_LANGUAGE_FAILED = "DOWNLOAD_LANGUAGE_FAILED";
// ShapeShift
export const GET_SUPPORTED_COINS_START = "GET_SUPPORTED_COINS_START";
export const GET_SUPPORTED_COINS_SUCCESS = "GET_SUPPORTED_COINS_SUCCESS";
export const GET_SUPPORTED_COINS_FAIL = "GET_SUPPORTED_COINS_FAIL";
export const GET_COIN_STATS_START = "GET_COIN_STATS_START";
export const GET_COIN_STATS_SUCCESS = "GET_COIN_STATS_SUCCESS";
export const GET_COIN_STATS_FAIL = "GET_COIN_STATS_FAIL";
export const PREPARE_SHAPE_SHIFT_START = "PREPARE_SHAPE_SHIFT_START";
export const PREPARE_SHAPE_SHIFT_SUCCESS = "PREPARE_SHAPE_SHIFT_SUCCESS";
export const PREPARE_SHAPE_SHIFT_FAIL = "PREPARE_SHAPE_SHIFT_FAIL";
export const GET_ACTIVE_SHIFT_START = "GET_ACTIVE_SHIFT_START";
export const GET_ACTIVE_SHIFT_SUCCESS = "GET_ACTIVE_SHIFT_SUCCESS";
export const GET_ACTIVE_SHIFT_FAIL = "GET_ACTIVE_SHIFT_FAIL";
export const CLEAR_SHAPE_SHIFT = "CLEAR_SHAPE_SHIFT";

View file

@ -0,0 +1,3 @@
export const NO_DEPOSITS = "no_deposits";
export const RECEIVED = "received";
export const COMPLETE = "complete";

View file

@ -2,15 +2,17 @@ import React from "react";
import SubHeader from "component/subHeader";
import Link from "component/link";
import WalletAddress from "component/walletAddress";
import ShapeShift from "component/shapeShift";
const ReceiveCreditsPage = props => {
return (
<main className="main--single-column">
<SubHeader />
<WalletAddress />
<ShapeShift />
<section className="card">
<div className="card__title-primary">
<h3>{__("Where To Find Credits")}</h3>
<h3>{__("More ways to get LBRY Credits")}</h3>
</div>
<div className="card__content">
<p>

View file

@ -102,8 +102,8 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
const fileInfo = byOutpoint[outpoint];
if (fileInfo) {
txid = fileInfo.outpoint.slice(0, -2);
nout = fileInfo.outpoint.slice(-1);
const txid = fileInfo.outpoint.slice(0, -2);
const nout = fileInfo.outpoint.slice(-1);
dispatch(doAbandonClaim(txid, nout));
}

View file

@ -0,0 +1,137 @@
// @flow
import Promise from "bluebird";
import * as types from "constants/action_types";
import { coinRegexPatterns } from "util/shape_shift";
import type {
GetSupportedCoinsSuccess,
GetCoinStatsStart,
GetCoinStatsSuccess,
GetCoinStatsFail,
PrepareShapeShiftSuccess,
PrepareShapeShiftFail,
GetActiveShiftSuccess,
GetActiveShiftFail,
} from "redux/reducers/shape_shift";
import type { FormikActions } from "types/common";
// use promise chains instead of callbacks for shapeshift api
const shapeShift = Promise.promisifyAll(require("shapeshift.io"));
// All ShapeShift actions
// Action types defined in the reducer will contain some payload
export type Action =
| { type: types.GET_SUPPORTED_COINS_START }
| { type: types.GET_SUPPORTED_COINS_FAIL }
| GetSupportedCoinsSuccess
| GetCoinStatsStart
| { type: types.GET_COIN_STATS_START }
| GetCoinStatsFail
| GetCoinStatsSuccess
| { type: types.PREPARE_SHAPE_SHIFT_START }
| PrepareShapeShiftFail
| PrepareShapeShiftSuccess
| { type: types.GET_ACTIVE_SHIFT_START }
| GetActiveShiftFail
| GetActiveShiftSuccess;
// Basic thunk types
// It would be nice to import these from types/common
// Not sure how that would work since they rely on the Action type
type PromiseAction = Promise<Action>;
type ThunkAction = (dispatch: Dispatch) => any;
export type Dispatch = (
action: Action | ThunkAction | PromiseAction | Array<Action>
) => any;
// ShapeShift form values
export type ShapeShiftFormValues = {
originCoin: string,
returnAddress: ?string,
receiveAddress: string,
};
export const shapeShiftInit = () => (dispatch: Dispatch): ThunkAction => {
dispatch({ type: types.GET_SUPPORTED_COINS_START });
return shapeShift
.coinsAsync()
.then(coinData => {
let supportedCoins = [];
Object.keys(coinData).forEach(symbol => {
if (coinData[symbol].status === "available") {
supportedCoins.push(coinData[symbol]);
}
});
// only use larger coins with client side validation
supportedCoins = supportedCoins
.filter(coin => coinRegexPatterns[coin.symbol])
.map(coin => coin.symbol);
dispatch({
type: types.GET_SUPPORTED_COINS_SUCCESS,
data: supportedCoins,
});
dispatch(getCoinStats(supportedCoins[0]));
})
.catch(err =>
dispatch({ type: types.GET_SUPPORTED_COINS_FAIL, data: err })
);
};
export const getCoinStats = (coin: string) => (
dispatch: Dispatch
): ThunkAction => {
const pair = `${coin.toLowerCase()}_lbc`;
dispatch({ type: types.GET_COIN_STATS_START, data: coin });
return shapeShift
.marketInfoAsync(pair)
.then(marketInfo =>
dispatch({ type: types.GET_COIN_STATS_SUCCESS, data: marketInfo })
)
.catch(err => dispatch({ type: types.GET_COIN_STATS_FAIL, data: err }));
};
export const createShapeShift = (
values: ShapeShiftFormValues,
actions: FormikActions
) => (dispatch: Dispatch): ThunkAction => {
const {
originCoin,
returnAddress,
receiveAddress: withdrawalAddress,
} = values;
const pair = `${originCoin.toLowerCase()}_lbc`;
const options = {
returnAddress: returnAddress,
};
dispatch({ type: types.PREPARE_SHAPE_SHIFT_START });
return shapeShift
.shiftAsync(withdrawalAddress, pair, options)
.then(res =>
dispatch({ type: types.PREPARE_SHAPE_SHIFT_SUCCESS, data: res })
)
.catch(err => {
dispatch({ type: types.PREPARE_SHAPE_SHIFT_FAIL, data: err });
// for formik to stop the submit
actions.setSubmitting(false);
});
};
export const getActiveShift = (depositAddress: string) => (
dispatch: Dispatch
): ThunkAction => {
dispatch({ type: types.GET_ACTIVE_SHIFT_START });
return shapeShift
.statusAsync(depositAddress)
.then(res => dispatch({ type: types.GET_ACTIVE_SHIFT_SUCCESS, data: res }))
.catch(err => dispatch({ type: types.GET_ACTIVE_SHIFT_FAIL, data: err }));
};
export const clearShapeShift = () => (dispatch: Dispatch): Action =>
dispatch({ type: types.CLEAR_SHAPE_SHIFT });

View file

@ -0,0 +1,245 @@
// @flow
import { handleActions } from "util/redux-utils";
import * as actions from "constants/action_types";
import * as statuses from "constants/shape_shift";
export type ShapeShiftState = {
loading: boolean,
updating: boolean,
shiftSupportedCoins: Array<string>,
hasActiveShift: boolean,
originCoin: ?string,
error: ?string,
shiftDepositAddress: ?string,
shiftReturnAddress: ?string,
shiftCoinType: ?string,
shiftOrderId: ?string,
shiftState: ?string,
originCoinDepositMax: ?number,
// originCoinDepositMin is a string because we need to convert it from scientifc notation
// it will usually be something like 0.00000001 coins
// using Number(x) or parseInt(x) will either change it back to scientific notation or round to zero
originCoinDepositMin: ?string,
originCoinDepositFee: ?number,
shapeShiftRate: ?number,
};
// All ShapeShift actions that will have some payload
export type GetSupportedCoinsSuccess = {
type: actions.GET_SUPPORTED_COINS_SUCCESS,
data: Array<string>,
};
export type GetCoinStatsStart = {
type: actions.GET_SUPPORTED_COINS_SUCCESS,
data: string,
};
export type GetCoinStatsSuccess = {
type: actions.GET_COIN_STATS_SUCCESS,
data: ShapeShiftMarketInfo,
};
export type GetCoinStatsFail = {
type: actions.GET_COIN_STATS_FAIL,
data: string,
};
export type PrepareShapeShiftSuccess = {
type: actions.PREPARE_SHAPE_SHIFT_SUCCESS,
data: ActiveShiftInfo,
};
export type PrepareShapeShiftFail = {
type: actions.PREPARE_SHAPE_SHIFT_FAIL,
data: ShapeShiftErrorResponse,
};
export type GetActiveShiftSuccess = {
type: actions.GET_ACTIVE_SHIFT_SUCCESS,
data: string,
};
export type GetActiveShiftFail = {
type: actions.GET_ACTIVE_SHIFT_FAIL,
data: ShapeShiftErrorResponse,
};
// ShapeShift sub-types
// Defined for actions that contain an object in the payload
type ShapeShiftMarketInfo = {
limit: number,
minimum: number,
minerFee: number,
rate: number,
};
type ActiveShiftInfo = {
deposit: string,
depositType: string,
returnAddress: string,
orderId: string,
};
type ShapeShiftErrorResponse = {
message: string,
};
const defaultState: ShapeShiftState = {
loading: true,
updating: false,
shiftSupportedCoins: [],
hasActiveShift: false,
originCoin: undefined,
error: undefined,
shiftDepositAddress: undefined, // shapeshift address to send your coins to
shiftReturnAddress: undefined,
shiftCoinType: undefined,
shiftOrderId: undefined,
shiftState: undefined,
originCoinDepositMax: undefined,
originCoinDepositMin: undefined,
originCoinDepositFee: undefined,
shapeShiftRate: undefined,
};
export default handleActions(
{
[actions.GET_SUPPORTED_COINS_START]: (
state: ShapeShiftState
): ShapeShiftState => ({
...state,
loading: true,
error: undefined,
}),
[actions.GET_SUPPORTED_COINS_SUCCESS]: (
state: ShapeShiftState,
action: GetSupportedCoinsSuccess
): ShapeShiftState => {
const shiftSupportedCoins = action.data;
return {
...state,
error: undefined,
shiftSupportedCoins,
};
},
[actions.GET_SUPPORTED_COINS_FAIL]: (
state: ShapeShiftState
): ShapeShiftState => ({
...state,
loading: false,
error: "Error getting available coins",
}),
[actions.GET_COIN_STATS_START]: (
state: ShapeShiftState,
action: GetCoinStatsStart
): ShapeShiftState => {
const coin = action.data;
return {
...state,
updating: true,
originCoin: coin,
};
},
[actions.GET_COIN_STATS_SUCCESS]: (
state: ShapeShiftState,
action: GetCoinStatsSuccess
): ShapeShiftState => {
const marketInfo: ShapeShiftMarketInfo = action.data;
return {
...state,
loading: false,
updating: false,
originCoinDepositMax: marketInfo.limit,
// this will come in scientific notation
// toFixed shows the real number, then regex to remove trailing zeros
originCoinDepositMin: marketInfo.minimum
.toFixed(10)
.replace(/\.?0+$/, ""),
originCoinDepositFee: marketInfo.minerFee,
shapeShiftRate: marketInfo.rate,
};
},
[actions.GET_COIN_STATS_FAIL]: (
state: ShapeShiftState,
action: GetCoinStatsFail
): ShapeShiftState => {
const error = action.data;
return {
...state,
loading: false,
error,
};
},
[actions.PREPARE_SHAPE_SHIFT_START]: (
state: ShapeShiftState
): ShapeShiftState => ({
...state,
error: undefined,
}),
[actions.PREPARE_SHAPE_SHIFT_SUCCESS]: (
state: ShapeShiftState,
action: PrepareShapeShiftSuccess
) => {
const activeShiftInfo: ActiveShiftInfo = action.data;
return {
...state,
hasActiveShift: true,
shiftDepositAddress: activeShiftInfo.deposit,
shiftCoinType: activeShiftInfo.depositType,
shiftReturnAddress: activeShiftInfo.returnAddress,
shiftOrderId: activeShiftInfo.orderId,
shiftState: statuses.NO_DEPOSITS,
};
},
[actions.PREPARE_SHAPE_SHIFT_FAIL]: (
state: ShapeShiftState,
action: PrepareShapeShiftFail
) => {
const error: ShapeShiftErrorResponse = action.data;
return {
...state,
error: error.message,
};
},
[actions.CLEAR_SHAPE_SHIFT]: (state: ShapeShiftState): ShapeShiftState => ({
...state,
loading: false,
updating: false,
hasActiveShift: false,
shiftDepositAddress: undefined,
shiftReturnAddress: undefined,
shiftCoinType: undefined,
shiftOrderId: undefined,
originCoin: state.shiftSupportedCoins[0],
}),
[actions.GET_ACTIVE_SHIFT_START]: (
state: ShapeShiftState
): ShapeShiftState => ({
...state,
error: undefined,
updating: true,
}),
[actions.GET_ACTIVE_SHIFT_SUCCESS]: (
state: ShapeShiftState,
action: GetActiveShiftSuccess
): ShapeShiftState => {
const status = action.data;
return {
...state,
updating: false,
shiftState: status,
};
},
[actions.GET_ACTIVE_SHIFT_FAIL]: (
state: ShapeShiftState,
action: GetActiveShiftFail
): ShapeShiftState => {
const error: ShapeShiftErrorResponse = action.data;
return {
...state,
updating: false,
error: error.message,
};
},
},
defaultState
);

View file

@ -44,8 +44,8 @@ export const selectHeaderLinks = createSelector(selectCurrentPage, page => {
return {
wallet: __("Overview"),
history: __("History"),
send: __("Send"),
receive: __("Receive"),
send: __("Send Credits"),
receive: __("Get Credits"),
rewards: __("Rewards"),
invite: __("Invites"),
};
@ -78,9 +78,9 @@ export const selectPageTitle = createSelector(
case "wallet":
return __("Wallet");
case "send":
return __("Send Credits");
return __("Send LBRY Credits");
case "receive":
return __("Wallet Address");
return __("Get LBRY Credits");
case "backup":
return __("Backup Your Wallet");
case "rewards":

View file

@ -65,7 +65,7 @@ export const selectWunderBarIcon = createSelector(
return "icon-envelope-open";
case "address":
case "receive":
return "icon-address-book";
return "icon-credit-card";
case "wallet":
case "backup":
return "icon-bank";

View file

@ -0,0 +1,7 @@
import { createSelector } from "reselect";
const _selectState = state => state.shapeShift;
export const selectShapeShift = createSelector(_selectState, state => ({
...state,
}));

View file

@ -11,6 +11,7 @@ import searchReducer from "redux/reducers/search";
import settingsReducer from "redux/reducers/settings";
import userReducer from "redux/reducers/user";
import walletReducer from "redux/reducers/wallet";
import shapeShiftReducer from "redux/reducers/shape_shift";
import { persistStore, autoRehydrate } from "redux-persist";
import createCompressor from "redux-persist-transform-compress";
import createFilter from "redux-persist-transform-filter";
@ -67,6 +68,7 @@ const reducers = redux.combineReducers({
settings: settingsReducer,
wallet: walletReducer,
user: userReducer,
shapeShift: shapeShiftReducer,
});
const bulkThunk = createBulkThunkMiddleware();

View file

@ -0,0 +1,3 @@
export type FormikActions = {
setSubmitting: boolean => mixed,
};

View file

@ -1,6 +1,7 @@
// util for creating reducers
// based off of redux-actions
// https://redux-actions.js.org/docs/api/handleAction.html#handleactions
export const handleActions = (actionMap, defaultState) => {
return (state = defaultState, action) => {
const handler = actionMap[action.type];

View file

@ -0,0 +1,50 @@
// these don't need to be exact
// shapeshift does a more thourough check on validity
// just general matches to prevent unneccesary api calls
export const coinRegexPatterns = {
BTC: /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/,
BCH: /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/,
ETH: /^(0x)?[0-9a-fA-F]{40}$/,
DASH: /^X([0-9a-zA-Z]){33}/,
LTC: /^L[a-zA-Z0-9]{26,33}$/,
XMR: /^4[0-9ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{94}$/,
};
const validateAddress = (coinType, address) => {
if (!coinType || !address) return false;
const coinRegex = coinRegexPatterns[coinType.toUpperCase()];
if (!coinRegex) return false;
return coinRegex.test(address);
};
export const validateShapeShiftForm = (vals, props) => {
let errors = {};
if (!vals.returnAddress) {
return errors;
}
const isValidAddress = validateAddress(vals.originCoin, vals.returnAddress);
if (!isValidAddress) {
errors.returnAddress = `Enter a valid ${vals.originCoin} address`;
}
return errors;
};
const exampleCoinAddresses = {
BTC: "1745oPaHeW7Fmpb1fUKTtasYfxr4zu9bwq",
BCH: "1745oPaHeW7Fmpb1fUKTtasYfxr4zu9bwq",
ETH: "0x8507cA6a274123fC8f80d929AF9D83602bC4e8cC",
DASH: "XedBP7vLPFXbS3URjrH2Z57Fg9SWftBmQ6",
LTC: "LgZivMvFMTDoqcA5weCQ2QrmRp7pa56bBk",
XMR:
"466XMeJEcowYGx7RzUJj3VDWBZgRWErVQQX6tHYbsacS5QF6v3tidE6LZZnTJgzeEh6bKEEJ6GC9jHirrUKvJwVKVj9e7jm",
};
export const getExampleAddress = coin => {
return exampleCoinAddresses[coin];
};

View file

@ -23,10 +23,14 @@
"homepage": "https://github.com/lbryio/lbry-app",
"dependencies": {
"amplitude-js": "^4.0.0",
"bluebird": "^3.5.1",
"classnames": "^2.2.5",
"formik": "^0.10.4",
"from2": "^2.3.0",
"jshashes": "^1.0.7",
"localforage": "^1.5.0",
"node-sass": "^4.5.3",
"qrcode.react": "^0.7.2",
"rc-progress": "^2.0.6",
"react": "^16.2.0",
"react-dom": "^16.2.0",
@ -44,6 +48,7 @@
"redux-thunk": "^2.2.0",
"render-media": "^2.10.0",
"reselect": "^3.0.0",
"shapeshift.io": "^1.3.1",
"y18n": "^3.2.1"
},
"devDependencies": {

View file

@ -34,6 +34,11 @@ body
color: var(--color-meta-light);
}
.credit-amount--bold
{
font-weight: 700;
}
#main-content
{
margin: auto;

View file

@ -26,4 +26,6 @@
@import "component/_divider.scss";
@import "component/_checkbox.scss";
@import "component/_radio.scss";
@import "component/_shapeshift.scss";
@import "component/_spinner.scss";
@import "page/_show.scss";

View file

@ -26,6 +26,7 @@
.card__actions {
padding: 0 var(--card-padding);
}
.card--small {
.card__title-primary,
.card__title-identity,
@ -72,6 +73,18 @@
margin-bottom: var(--card-margin);
}
}
.card__actions--only-vertical {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.card__content--extra-vertical-space {
margin: $spacing-vertical 0;
}
$font-size-subtext-multiple: 0.82;
.card__subtext {
color: var(--color-meta-light);

View file

@ -56,6 +56,11 @@
padding-left: 5px;
padding-right: 5px;
width: 100%;
font-family: "Consolas", "Lucida Console", "Adobe Source Code Pro", monospace;
&.input-copyable--with-copy-btn {
width: 85%;
}
}
input[readonly] {

View file

@ -43,6 +43,8 @@
.wunderbar--active .icon-search { color: var(--color-primary); }
// below styles should be inside the common input styling
// will come back to this with the redesign - sean
.wunderbar__input {
background: var(--search-bg);
width: 100%;

View file

@ -0,0 +1,40 @@
// Can't think of a better way to do this
// The initial shapeshift form is 311px tall
// the .shapeshift__initial-wrapper class is only added when the form is being loaded
// Once the form is rendered, there is a very smooth transition because the height doesn't change
.shapeshift__wrapper.shapeshift__initial-wrapper {
min-height: 346px;
}
.shapeshift__content {
.spinner {
margin-top: $spacing-vertical * 3;
}
}
.shapeshift__tx-info {
min-height: 55px;
}
.shapeshift__deposit-address-wrapper {
display: flex;
flex-direction: row;
* {
align-self: center;
}
}
// this should be pulled out into it's own styling when we add more qr codes
.shapeshift__qrcode {
// don't use a variable here. adds a white border for easier reading in dark mode
// needs to stay the same no matter what theme is present
background-color: #fff;
padding: 2px;
margin-left: 40px;
}
.shapeshift__link {
padding-left: 10px;
}

View file

@ -0,0 +1,58 @@
.spinner {
position: relative;
width: 11em;
height: 11em;
margin: 20px auto;
font-size: 3px;
border-radius: 50%;
background: linear-gradient(to right, #fff 10%, rgba(255, 255, 255, 0) 50%);
animation: spin 1.4s infinite linear;
transform: translateZ(0);
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
&:before,
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
}
&:before {
width: 50%;
height: 50%;
background: #fff;
border-radius: 100% 0 0 0;
}
&:after {
height: 75%;
width: 75%;
margin: auto;
bottom: 0;
right: 0;
background: #000;
border-radius: 50%;
}
}
.spinner.spinner--dark {
background: linear-gradient(to right, var(--button-primary-bg) 10%, var(--color-bg) 50%);
&:before {
background: var(--button-primary-bg);
}
&:after {
background: var(--color-bg);
}
}

View file

@ -46,53 +46,6 @@ video {
align-items: center;
}
.video__loading-spinner {
position: relative;
width: 11em;
height: 11em;
margin: 20px auto;
font-size: 3px;
border-radius: 50%;
background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 50%);
animation: spin 1.4s infinite linear;
transform: translateZ(0);
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
&:before,
&:after {
content: '';
position: absolute;
top: 0;
left: 0;
}
&:before {
width: 50%;
height: 50%;
background: #ffffff;
border-radius: 100% 0 0 0;
}
&:after {
height: 75%;
width: 75%;
margin: auto;
bottom: 0;
right: 0;
background: black;
border-radius: 50%;
}
}
.video__loading-status {
padding-top: 20px;
color: white;

View file

@ -18,7 +18,7 @@ module.exports = {
},
resolve: {
modules: [appPath, "node_modules"],
extensions: [".js", ".jsx", ".css"]
extensions: [".js", ".jsx", ".css", ".json"]
},
module: {
rules: [

View file

@ -64,8 +64,8 @@ ajv@^4.7.0, ajv@^4.9.1:
json-stable-stringify "^1.0.1"
ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5, ajv@^5.2.3:
version "5.5.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.0.tgz#eb2840746e9dc48bd5e063a36e3fd400c5eab5a9"
version "5.5.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.1.tgz#b38bb8876d9e86bee994956a04e721e88b248eb2"
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
@ -1018,6 +1018,10 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
blueimp-md5@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.10.0.tgz#02f0843921f90dca14f5b8920a38593201d6964d"
@ -1235,8 +1239,8 @@ caniuse-api@^1.5.2:
lodash.uniq "^4.5.0"
caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000777"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000777.tgz#2e19adba63bdd7c501df637a862adead7f4bc054"
version "1.0.30000778"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000778.tgz#167c60e9542a2aa60537c446fb3881d853a3072a"
cardinal@^1.0.0:
version "1.0.0"
@ -1954,6 +1958,10 @@ doctrine@^2.0.0:
dependencies:
esutils "^2.0.2"
dom-walk@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018"
domain-browser@^1.1.1:
version "1.1.7"
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
@ -1987,8 +1995,8 @@ electron-rebuild@^1.5.11:
yargs "^7.0.2"
electron-to-chromium@^1.2.7:
version "1.3.27"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d"
version "1.3.28"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.28.tgz#8dd4e6458086644e9f9f0a1cf32e2a1f9dffd9ee"
elegant-spinner@^1.0.1:
version "1.0.1"
@ -2619,6 +2627,12 @@ flow-typed@^2.2.3:
which "^1.2.14"
yargs "^4.2.0"
for-each@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.2.tgz#2c40450b9348e97f281322593ba96704b9abd4d4"
dependencies:
is-function "~1.0.0"
for-in@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -2653,6 +2667,14 @@ form-data@~2.3.1:
combined-stream "^1.0.5"
mime-types "^2.1.12"
formik@^0.10.4:
version "0.10.5"
resolved "https://registry.yarnpkg.com/formik/-/formik-0.10.5.tgz#6984d2f22e918c6d2264a3cb86b8582f7277faca"
dependencies:
lodash.isequal "4.5.0"
prop-types "^15.5.10"
warning "^3.0.0"
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@ -2677,8 +2699,8 @@ fs-extra@^3.0.1:
universalify "^0.1.0"
fs-extra@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b"
version "4.0.3"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
@ -2833,6 +2855,13 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
global@~4.3.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
dependencies:
min-document "^2.19.0"
process "~0.5.1"
globals@^9.14.0, globals@^9.18.0:
version "9.18.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
@ -3344,6 +3373,10 @@ is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
is-function@^1.0.1, is-function@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5"
is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
@ -3870,6 +3903,10 @@ lodash.isempty@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e"
lodash.isequal@4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
@ -3992,8 +4029,8 @@ marked-terminal@^1.6.2:
node-emoji "^1.4.1"
marked@*, marked@^0.3.6:
version "0.3.6"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.6.tgz#b2c6c618fccece4ef86c4fc6cb8a7cbf5aeda8d7"
version "0.3.7"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.7.tgz#80ef3bbf1bd00d1c9cfebe42ba1b8c85da258d0d"
"match-stream@>= 0.0.2 < 1":
version "0.0.2"
@ -4135,6 +4172,12 @@ mimic-response@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e"
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
dependencies:
dom-walk "^0.1.0"
minimalistic-assert@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
@ -4209,8 +4252,8 @@ nan@^2.3.0, nan@^2.3.2:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
natives@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.0.tgz#e9ff841418a6b2ec7a495e939984f78f163e6e31"
version "1.1.1"
resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.1.tgz#011acce1f7cbd87f7ba6b3093d6cd9392be1c574"
natural-compare@^1.4.0:
version "1.4.0"
@ -4676,6 +4719,13 @@ parse-glob@^3.0.4:
is-extglob "^1.0.0"
is-glob "^2.0.0"
parse-headers@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.1.tgz#6ae83a7aa25a9d9b700acc28698cd1f1ed7e9536"
dependencies:
for-each "^0.3.2"
trim "0.0.1"
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
@ -5061,8 +5111,8 @@ preserve@^0.2.0:
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@^1.4.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8"
version "1.9.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.1.tgz#41638a0d47c1efbd1b7d5a742aaa5548eab86d70"
private@^0.1.6, private@^0.1.7, private@~0.1.5:
version "0.1.8"
@ -5076,6 +5126,10 @@ process@^0.11.0, process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
process@~0.5.1:
version "0.5.2"
resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf"
progress@^1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
@ -5147,6 +5201,17 @@ q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
qr.js@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
qrcode.react@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-0.7.2.tgz#72a5718fd56baafe15c2c153fe436628d83aa286"
dependencies:
prop-types "^15.5.8"
qr.js "0.0.0"
qs@6.5.1, qs@~6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
@ -5264,8 +5329,8 @@ react-markdown@^2.5.0:
prop-types "^15.5.1"
react-modal@^3.1.5:
version "3.1.6"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.1.6.tgz#82e63f1ec86b80e242518250d066ee37fa035f8a"
version "3.1.7"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.1.7.tgz#21feb937c95cd722bf2d375cada751fdc8189c0e"
dependencies:
exenv "^1.2.0"
prop-types "^15.5.10"
@ -5561,7 +5626,7 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
request@2, request@^2.81.0:
request@2, request@^2.55.0, request@^2.81.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
dependencies:
@ -5735,10 +5800,10 @@ rx-lite@^3.1.2:
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
rxjs@^5.0.0-beta.11, rxjs@^5.1.1:
version "5.5.2"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.2.tgz#28d403f0071121967f18ad665563255d54236ac3"
version "5.5.4"
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.4.tgz#66a466acb21270b5f441645a1141f95fcae10ee6"
dependencies:
symbol-observable "^1.0.1"
symbol-observable "1.0.1"
safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
@ -5858,6 +5923,13 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
shapeshift.io@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/shapeshift.io/-/shapeshift.io-1.3.1.tgz#939f7d89e6a93fad4b556567d3fcdab45d5cc021"
dependencies:
request "^2.55.0"
xhr "^2.0.1"
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@ -6200,7 +6272,11 @@ svgo@^0.7.0:
sax "~1.2.1"
whet.extend "~0.9.9"
symbol-observable@^1.0.1, symbol-observable@^1.0.3:
symbol-observable@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4"
symbol-observable@^1.0.3:
version "1.1.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32"
@ -6314,6 +6390,10 @@ trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
trim@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd"
"true-case-path@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62"
@ -6686,8 +6766,8 @@ webpack@^1.12.0:
webpack-core "~0.6.9"
webpack@^3.0.0:
version "3.9.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.9.1.tgz#9a60aa544ed5d4d454c069e3f521aa007e02643c"
version "3.10.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.10.0.tgz#5291b875078cf2abf42bdd23afe3f8f96c17d725"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"
@ -6792,6 +6872,15 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
xhr@^2.0.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.4.0.tgz#e16e66a45f869861eeefab416d5eff722dc40993"
dependencies:
global "~4.3.0"
is-function "^1.0.1"
parse-headers "^2.0.0"
xtend "^4.0.0"
xss-filters@^1.2.6:
version "1.2.7"
resolved "https://registry.yarnpkg.com/xss-filters/-/xss-filters-1.2.7.tgz#59fa1de201f36f2f3470dcac5f58ccc2830b0a9a"