diff --git a/package-lock.json b/package-lock.json index b985cf2..67f9004 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2957,6 +2957,15 @@ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=" }, + "connected-react-router": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/connected-react-router/-/connected-react-router-5.0.1.tgz", + "integrity": "sha512-0QwWYPRGZQ7f284lmqc5kwC4T3iW3zrAH3zzi6uUMzTOxbA+mn38tAgMOoVo9m3pbskvONFtXiajgVkCElE9EQ==", + "requires": { + "immutable": "^3.8.1", + "seamless-immutable": "^7.1.3" + } + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -6851,6 +6860,11 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-1.7.2.tgz", "integrity": "sha512-4Urocwu9+XLDJw4Tc6ZCg7APVjjLInCFvO4TwGsAYV5zT6YYSor14dsZR0+0tHlDIN92cFUOq+i7fC00G5vTxA==" }, + "immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=" + }, "import-cwd": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", @@ -12805,6 +12819,11 @@ "ajv-keywords": "^3.1.0" } }, + "seamless-immutable": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/seamless-immutable/-/seamless-immutable-7.1.4.tgz", + "integrity": "sha512-XiUO1QP4ki4E2PHegiGAlu6r82o5A+6tRh7IkGGTVg/h+UoeX4nFBeCGPOhb4CYjvkqsfm/TUtvOMYC1xmV30A==" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/package.json b/package.json index 0ca79b6..4dc6237 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "boltz-frontend", "version": "1.0.0", "dependencies": { + "connected-react-router": "^5.0.1", "prop-types": "^15.6.2", "react": "^16.6.3", "react-dom": "^16.6.3", diff --git a/src/components/input/index.js b/src/components/input/index.js index 65bbe65..2e00434 100644 --- a/src/components/input/index.js +++ b/src/components/input/index.js @@ -12,45 +12,56 @@ const styles = theme => ({ backgroundColor: theme.colors.lightGrey, width: '200px', height: '50px', - '&:focus': { - outline: 'none', - }, + outline: p => (p.error ? '1px solid red' : 'none'), }, }); class Input extends React.Component { constructor(props) { super(props); - this.state = { text: 0 }; + this.state = { value: 0 }; } onChange = e => { - if (e.target.value >= 0) { - this.setState({ text: e.target.value }); + if (e.target.value) { + this.setState({ value: e.target.value }); + this.props.onChange && this.props.onChange(e.target.value); } }; render() { - const { classes, isText, style, disable } = this.props; + const { classes, style, disable, min, step, value, max } = this.props; return ( this.onChange(e)} - value={this.state.text} - type={isText ? 'text' : 'number'} + value={value ? value : this.state.value} + type={'number'} /> ); } } +Input.defaultProps = { + min: 0, + step: 1, +}; + Input.propTypes = { classes: PropTypes.object.isRequired, - isText: PropTypes.bool.isRequired, + onChange: PropTypes.func, style: PropTypes.object, disable: PropTypes.bool, + error: PropTypes.bool, + min: PropTypes.number, + max: PropTypes.number, + value: PropTypes.number, + step: PropTypes.number, }; export default injectSheet(styles)(Input); diff --git a/src/views/landingPage/landingPage.js b/src/components/landingpage/index.js similarity index 84% rename from src/views/landingPage/landingPage.js rename to src/components/landingpage/index.js index a4c9ed9..18b4ba3 100644 --- a/src/views/landingPage/landingPage.js +++ b/src/components/landingpage/index.js @@ -33,7 +33,7 @@ const styles = theme => ({ }, }); -const LandingPage = ({ classes, toggleSwapMode }) => { +const LandingPage = ({ classes, toggleSwapMode, setSwapAmount }) => { return ( @@ -52,16 +52,22 @@ const LandingPage = ({ classes, toggleSwapMode }) => {

- toggleSwapMode()} /> + { + setSwapAmount(sent, received); + toggleSwapMode(); + }} + />
); }; LandingPage.propTypes = { - classes: PropTypes.object, + classes: PropTypes.object.isRequired, inSwapMode: PropTypes.bool, toggleSwapMode: PropTypes.func, + setSwapAmount: PropTypes.func, }; export default injectSheet(styles)(LandingPage); diff --git a/src/components/swaptab/index.js b/src/components/swaptab/index.js index 33573a9..f19e0a7 100644 --- a/src/components/swaptab/index.js +++ b/src/components/swaptab/index.js @@ -5,6 +5,7 @@ import View from '../view'; import Input from '../input'; import DropDown from '../dropdown'; import Text, { InfoText } from '../text'; +import { MIN, MAX, FEE } from '../../constants/fees'; import { FaArrowRight } from 'react-icons/fa'; const types = ['BTC', 'T-BTC']; @@ -67,32 +68,71 @@ const styles = theme => ({ }, }); -const SwapTab = ({ classes, onClick }) => ( - - - - - - - - - - - - - - - - - +class SwapTab extends React.Component { + state = { + sent: 0, + received: 0, + error: false, + }; + + setSwapData = sent => { + if (sent > MAX || sent < MIN) { + this.setState({ + error: true, + }); + } else { + this.setState({ + sent, + received: Number.parseFloat(sent - FEE).toFixed(4), + error: false, + }); + } + }; + + shouldSubmit = () => { + const { error, sent, received } = this.state; + if (!error && sent !== 0) { + this.props.onClick(sent, received); + } + }; + + render() { + const { classes } = this.props; + return ( + + + + + + + + + + + this.setSwapData(e)} + /> + + + + + + + + + this.shouldSubmit()}> + + + - - onClick()}> - - - - -); + ); + } +} + SwapTab.propTypes = { classes: PropTypes.object, onClick: PropTypes.func, diff --git a/src/components/taskbar/index.js b/src/components/taskbar/index.js index b6ea371..3ab9916 100644 --- a/src/components/taskbar/index.js +++ b/src/components/taskbar/index.js @@ -28,7 +28,7 @@ const TaskBar = ({ classes }) => ( alt="logo" /> - + + combineReducers({ + router: connectRouter(history), + swapReducer, + refundReducer, + }); -export default rootReducer; +export default createRootReducer; diff --git a/src/views/index.js b/src/views/index.js index d698586..eb51eca 100644 --- a/src/views/index.js +++ b/src/views/index.js @@ -1,12 +1,12 @@ import React, { Component } from 'react'; import { Provider } from 'react-redux'; import { ThemeProvider, preset, jss } from 'react-jss'; -import { BrowserRouter as Router, Route } from 'react-router-dom'; -import store from '../state'; +import { Route, Switch } from 'react-router-dom'; +import { ConnectedRouter } from 'connected-react-router'; +import store, { history } from '../state'; import theme from '../constants/theme'; import Container from '../components/container'; -import LandingPage from '../views/landingPage'; import Swap from '../views/swap'; import Refund from '../views/refund'; @@ -16,16 +16,17 @@ class App extends Component { render() { return ( - + - - - - + + + + + - + ); } diff --git a/src/views/landingPage/index.js b/src/views/landingPage/index.js deleted file mode 100644 index cc66d70..0000000 --- a/src/views/landingPage/index.js +++ /dev/null @@ -1,16 +0,0 @@ -import { connect } from 'react-redux'; -import * as actions from './landingPageActions'; -import LandingPage from './landingPage'; - -const mapStateToProps = state => ({ - inSwapMode: state.swapReducer.inSwapMode, -}); - -const mapDispatchToProps = dispatch => ({ - toggleSwapMode: () => dispatch(actions.toggleSwapMode()), -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(LandingPage); diff --git a/src/views/landingPage/landingPageActions.js b/src/views/landingPage/landingPageActions.js deleted file mode 100644 index 734b44e..0000000 --- a/src/views/landingPage/landingPageActions.js +++ /dev/null @@ -1,5 +0,0 @@ -import * as actionTypes from '../../constants/actions'; - -export const toggleSwapMode = () => ({ - type: actionTypes.ENTER_SWAP_MODE, -}); diff --git a/src/views/refund/index.js b/src/views/refund/index.js index fcf8bc3..4e2d9d0 100644 --- a/src/views/refund/index.js +++ b/src/views/refund/index.js @@ -1,4 +1,5 @@ import { connect } from 'react-redux'; +import { push } from 'connected-react-router'; import Refund from './refund'; import * as actions from './refundActions'; @@ -8,6 +9,7 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ toggleRefundMode: () => dispatch(actions.toggleRefundMode()), + push: path => dispatch(push(path)), }); export default connect( diff --git a/src/views/refund/refund.js b/src/views/refund/refund.js index caff6f7..f8ff2fb 100644 --- a/src/views/refund/refund.js +++ b/src/views/refund/refund.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import injectSheet from 'react-jss'; import { FaArrowRight } from 'react-icons/fa'; import Background from '../../components/background'; -import TaskBar from '../../components/taskbar'; import StepsWizard from '../../components/stepswizard'; import View from '../../components/view'; import { StepOne, StepTwo, StepFour } from './steps'; @@ -29,49 +28,64 @@ Controls.propTypes = { text: PropTypes.string, }; -const Refund = ({ classes, inRefundMode, toggleRefundMode }) => ( - - - - toggleRefundMode()} - message={'Are you sure?'} - > - - } /> - } /> - } /> - } /> - - - } - /> - } - /> - } - /> - } - /> - - - - -); +class Refund extends React.Component { + UNSAFE_componentWillMount() { + this.props.toggleRefundMode(); + } + + componentWillUnmount() { + this.props.toggleRefundMode(); + } + + render() { + const { classes, inRefundMode, push } = this.props; + return ( + + + { + push('/'); + }} + message={'Are you sure?'} + > + + } /> + } /> + } /> + } /> + + + } + /> + } + /> + } + /> + } + /> + + + + + ); + } +} Refund.propTypes = { classes: PropTypes.object, + push: PropTypes.func, inRefundMode: PropTypes.bool, toggleRefundMode: PropTypes.func, }; diff --git a/src/views/swap/index.js b/src/views/swap/index.js index ece981b..103da8b 100644 --- a/src/views/swap/index.js +++ b/src/views/swap/index.js @@ -1,13 +1,18 @@ import { connect } from 'react-redux'; import { compose } from 'redux'; import { withRouter } from 'react-router-dom'; +import { toggleSwapMode, setSwapAmount } from './swapActions'; import Swap from './swap'; const mapStateToProps = state => ({ inSwapMode: state.swapReducer.inSwapMode, + swapInfo: state.swapReducer.swapInfo, }); -const mapDispatchToProps = () => {}; +const mapDispatchToProps = dispatch => ({ + toggleSwapMode: () => dispatch(toggleSwapMode()), + setSwapAmount: (sent, received) => dispatch(setSwapAmount(sent, received)), +}); export default compose( connect( diff --git a/src/views/swap/steps.js b/src/views/swap/steps.js index 6372e70..c8641dc 100644 --- a/src/views/swap/steps.js +++ b/src/views/swap/steps.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import injectSheet from 'react-jss'; +import { Link } from 'react-router-dom'; import qr from '../../asset/icons/qr_code.png'; import { FaCheckCircle, FaBolt } from 'react-icons/fa'; import View from '../../components/view'; @@ -29,12 +30,12 @@ const stepOneStyles = () => ({ }, }); -const StyledStepOne = ({ classes }) => ( +const StyledStepOne = ({ classes, value }) => (

Paste a Bitcoin lightning {' '} invoice of
- 0.0049 T-BTC to recieve it. + {value} T-BTC to recieve it.

lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5 @@ -46,6 +47,7 @@ const StyledStepOne = ({ classes }) => ( StyledStepOne.propTypes = { classes: PropTypes.object.isRequired, + value: PropTypes.number.isRequired, }; export const StepOne = injectSheet(stepOneStyles)(StyledStepOne); @@ -83,7 +85,7 @@ const stepTwoStyles = () => ({ }, }); -const StyledStepTwo = ({ classes }) => ( +const StyledStepTwo = ({ classes, value }) => ( {'qr @@ -94,7 +96,7 @@ const StyledStepTwo = ({ classes }) => ( fontSize: '30px', }} > - Send 0.005 T-BTC
+ Send {value} T-BTC
on Bitcoin
blockchain address:

@@ -110,6 +112,7 @@ const StyledStepTwo = ({ classes }) => ( StyledStepTwo.propTypes = { classes: PropTypes.object.isRequired, + value: PropTypes.number.isRequired, }; export const StepTwo = injectSheet(stepTwoStyles)(StyledStepTwo); @@ -185,3 +188,58 @@ StyledStepFour.propTypes = { }; export const StepFour = injectSheet(stepFourStyles)(StyledStepFour); + +const errorStyles = theme => ({ + wrapper: { + width: '700px', + height: '400px', + boxShadow: '0px 0px 30px -6px rgba(0,0,0,0.52)', + backgroundColor: theme.colors.white, + flexDirection: 'column', + }, + content: { + width: '100%', + height: '80%', + justifyContent: 'center', + alignItems: 'center', + }, + button: { + width: '100%', + height: '20%', + backgroundColor: theme.colors.matisseBlue, + '&:hover': { + cursor: 'pointer', + }, + }, + info: { + fontSize: '30px', + }, + link: { + flex: 1, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + textDecoration: 'none', + color: theme.colors.white, + }, +}); + +const StyledError = ({ classes }) => ( + + +

You cannot begin a swap.

+
+ + +

Back

+ +
+
+); + +StyledError.propTypes = { + classes: PropTypes.object.isRequired, + text: PropTypes.string, +}; + +export const Error = injectSheet(errorStyles)(StyledError); diff --git a/src/views/swap/swap.js b/src/views/swap/swap.js index 5dbd50f..66794f0 100644 --- a/src/views/swap/swap.js +++ b/src/views/swap/swap.js @@ -5,6 +5,7 @@ import { FaArrowRight } from 'react-icons/fa'; import View from '../../components/view'; import BackGround from '../../components/background'; import StepsWizard from '../../components/stepswizard'; +import LandingPage from '../../components/landingpage'; import { StepOne, StepTwo, StepThree, StepFour } from './steps'; const styles = () => ({ @@ -28,46 +29,67 @@ Controls.propTypes = { text: PropTypes.string, }; -const Swap = ({ classes, history, inSwapMode, toggleSwapMode }) => { - if (!inSwapMode) { - history.replace('/'); - } +const Swap = ({ + classes, + inSwapMode, + toggleSwapMode, + setSwapAmount, + swapInfo, +}) => { return ( - toggleSwapMode()} - alertOnExit={inSwapMode} - // TODO: change state isSwapMode - message={'Are you sure?'} - > - - } /> - } /> - } /> - } /> - - - } - /> - } - /> - } - /> - } - /> - - + {inSwapMode ? ( + { + const x = window.confirm('Sure you want to exit'); + if (x) { + setSwapAmount(null, null); + toggleSwapMode(); + } + }} + alertOnExit={inSwapMode} + message={'Are you sure?'} + > + + } + /> + } + /> + } /> + } /> + + + } + /> + } + /> + } + /> + } + /> + + + ) : ( + + )} ); @@ -77,7 +99,9 @@ Swap.propTypes = { classes: PropTypes.object.isRequired, history: PropTypes.object.isRequired, inSwapMode: PropTypes.bool.isRequired, + swapInfo: PropTypes.object, toggleSwapMode: PropTypes.func, + setSwapAmount: PropTypes.func, }; export default injectSheet(styles)(Swap); diff --git a/src/views/swap/swapActions.js b/src/views/swap/swapActions.js new file mode 100644 index 0000000..ce7211c --- /dev/null +++ b/src/views/swap/swapActions.js @@ -0,0 +1,17 @@ +import * as actionTypes from '../../constants/actions'; + +export const toggleSwapMode = () => ({ + type: actionTypes.ENTER_SWAP_MODE, +}); + +/** + * @param sent amount sent + * @param received amount received + */ +export const setSwapAmount = (sent, received) => ({ + type: actionTypes.SET_SWAP_AMOUNT, + payload: { + sent, + received, + }, +}); diff --git a/src/views/landingPage/landingPageReducer.js b/src/views/swap/swapReducer.js similarity index 59% rename from src/views/landingPage/landingPageReducer.js rename to src/views/swap/swapReducer.js index b850334..8e3f102 100644 --- a/src/views/landingPage/landingPageReducer.js +++ b/src/views/swap/swapReducer.js @@ -2,6 +2,10 @@ import * as actionTypes from '../../constants/actions'; const initalState = { inSwapMode: false, + swapInfo: { + sent: null, + received: null, + }, }; const reducer = (state = initalState, action) => { @@ -11,6 +15,14 @@ const reducer = (state = initalState, action) => { ...state, inSwapMode: !state.inSwapMode, }; + case actionTypes.SET_SWAP_AMOUNT: + return { + ...state, + swapInfo: { + sent: action.payload.sent, + received: action.payload.received, + }, + }; default: return state; }