From f0e7a8a1b18f1d4d9d0b67fe62f32c37dca5b402 Mon Sep 17 00:00:00 2001 From: George Lima Date: Wed, 30 Jan 2019 12:14:30 -0300 Subject: [PATCH 1/8] chore(assets): add plus icon --- app/assets/images/plus_icon.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 app/assets/images/plus_icon.svg diff --git a/app/assets/images/plus_icon.svg b/app/assets/images/plus_icon.svg new file mode 100644 index 0000000..32ba39b --- /dev/null +++ b/app/assets/images/plus_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file From 6f0660eccfd6b33c59c3d4d2ea9a9e9f3bbdb83b Mon Sep 17 00:00:00 2001 From: George Lima Date: Wed, 30 Jan 2019 12:16:10 -0300 Subject: [PATCH 2/8] feat(receive): add getnewaddress button --- app/containers/receive.js | 16 ++++++++++++--- app/redux/modules/receive.js | 28 ++++++++++++++++++++++++++ app/views/receive.js | 38 +++++++++++++++++++++++++++--------- 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/app/containers/receive.js b/app/containers/receive.js index 783d75a..7a56e14 100644 --- a/app/containers/receive.js +++ b/app/containers/receive.js @@ -7,6 +7,9 @@ import { ReceiveView } from '../views/receive'; import { loadAddressesSuccess, loadAddressesError, + getNewAddressSuccess, + getNewAddressError, + type addressType, } from '../redux/modules/receive'; import rpc from '../../services/api'; @@ -22,9 +25,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ loadAddresses: async () => { const [zAddressesErr, zAddresses] = await eres(rpc.z_listaddresses()); - const [tAddressesErr, transparentAddresses] = await eres( - rpc.getaddressesbyaccount(''), - ); + const [tAddressesErr, transparentAddresses] = await eres(rpc.getaddressesbyaccount('')); if (zAddressesErr || tAddressesErr) return dispatch(loadAddressesError({ error: 'Something went wrong!' })); @@ -34,6 +35,15 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ }), ); }, + getNewAddress: async ({ type }: { type: addressType }) => { + const [error, address] = await eres( + type === 'shielded' ? rpc.z_getnewaddress() : rpc.getnewaddress(''), + ); + + if (error || !address) return getNewAddressError({ error: 'Unable to generate a new address' }); + + getNewAddressSuccess({ address }); + }, }); // $FlowFixMe diff --git a/app/redux/modules/receive.js b/app/redux/modules/receive.js index 18a5714..1d651d8 100644 --- a/app/redux/modules/receive.js +++ b/app/redux/modules/receive.js @@ -4,6 +4,8 @@ import type { Action } from '../../types/redux'; // Actions export const LOAD_ADDRESSES_SUCCESS = 'LOAD_ADDRESSES_SUCCESS'; export const LOAD_ADDRESSES_ERROR = 'LOAD_ADDRESSES_ERROR'; +export const GET_NEW_ADDRESS_SUCCESS = 'GET_NEW_ADDRESS_SUCCESS'; +export const GET_NEW_ADDRESS_ERROR = 'GET_NEW_ADDRESS_ERROR'; export const loadAddressesSuccess = ({ addresses }: { addresses: string[] }) => ({ type: LOAD_ADDRESSES_SUCCESS, @@ -17,6 +19,22 @@ export const loadAddressesError = ({ error }: { error: string }) => ({ payload: { error }, }); +export const getNewAddressSuccess = ({ address }: { address: string }) => ({ + type: GET_NEW_ADDRESS_SUCCESS, + payload: { + address, + }, +}); + +export const getNewAddressError = ({ error }: { error: string }) => ({ + type: GET_NEW_ADDRESS_ERROR, + payload: { + error, + }, +}); + +export type addressType = 'transparent' | 'shielded'; + export type State = { addresses: string[], error: string | null, @@ -40,6 +58,16 @@ export default (state: State = initialState, action: Action) => { error: action.payload.error, addresses: [], }; + case GET_NEW_ADDRESS_SUCCESS: + return { + error: null, + addresses: [...state.addresses, action.payload.address], + }; + case GET_NEW_ADDRESS_ERROR: + return { + ...state, + error: action.payload.error, + }; default: return state; } diff --git a/app/views/receive.js b/app/views/receive.js index 866c91b..4ad232a 100644 --- a/app/views/receive.js +++ b/app/views/receive.js @@ -9,6 +9,9 @@ import { TextComponent } from '../components/text'; import { WalletAddress } from '../components/wallet-address'; import MenuIcon from '../assets/images/menu_icon.svg'; +import PlusIcon from '../assets/images/plus_icon.svg'; + +import type { addressType } from '../redux/modules/receive'; const Row = styled(RowComponent)` margin-bottom: 10px; @@ -22,7 +25,7 @@ const Label = styled(InputLabelComponent)` margin-bottom: 5px; `; -const ShowMoreButton = styled.button` +const ActionButton = styled.button` background: none; border: none; cursor: pointer; @@ -39,12 +42,13 @@ const ShowMoreButton = styled.button` } `; -const ShowMoreIcon = styled.img` +const ActionIcon = styled.img` width: 25px; height: 25px; border: 1px solid ${props => props.theme.colors.text}; border-radius: 100%; margin-right: 11.5px; + padding: 5px; `; const RevealsMain = styled.div` @@ -65,6 +69,7 @@ const RevealsMain = styled.div` type Props = { addresses: Array, loadAddresses: () => void, + getNewAddress: ({ type: addressType }) => void, }; type State = { @@ -97,10 +102,18 @@ export class ReceiveView extends PureComponent { - - + + - + + this.generateNewAddress('shielded')}> + + + + this.generateNewAddress('transparent')}> + + + ); @@ -109,6 +122,8 @@ export class ReceiveView extends PureComponent { renderTransparentAddresses = (address: string) => { const { showAdditionalOptions } = this.state; + console.log(address); + return ( { ); }; + generateNewAddress = (type: addressType) => { + const { getNewAddress } = this.props; + + getNewAddress({ type }); + }; + render() { const { addresses } = this.props; return (
- {(addresses || []).map((address, index) => { - if (index === 0) return this.renderShieldedAddresses(address); - return this.renderTransparentAddresses(address); - })} + {(addresses || []).map((address, index) => (index === 0 + ? this.renderShieldedAddresses(address) + : this.renderTransparentAddresses(address)))}
); } From 8e18d9285e1662d3b6eef5ef7ae694067556e9a0 Mon Sep 17 00:00:00 2001 From: Gabriel Rubens Date: Thu, 31 Jan 2019 12:32:55 -0200 Subject: [PATCH 3/8] chore(receive): remove log Co-Authored-By: georgelima --- app/views/receive.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/receive.js b/app/views/receive.js index 4ad232a..a70af65 100644 --- a/app/views/receive.js +++ b/app/views/receive.js @@ -122,7 +122,6 @@ export class ReceiveView extends PureComponent { renderTransparentAddresses = (address: string) => { const { showAdditionalOptions } = this.state; - console.log(address); return ( From 89ce3e429211aa6582275dcaf3f6db20fccc269e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eliabe=20J=C3=BAnior?= <8146889+eliabejr@users.noreply.github.com> Date: Thu, 31 Jan 2019 13:43:37 -0300 Subject: [PATCH 4/8] feat(test-redux-actions): create tests for redux actions (#62) --- __tests__/actions/receive.test.js | 49 +++++++++++ __tests__/actions/send.test.js | 116 +++++++++++++++++++++++++ __tests__/actions/transactions.test.js | 59 +++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 __tests__/actions/receive.test.js create mode 100644 __tests__/actions/send.test.js create mode 100644 __tests__/actions/transactions.test.js diff --git a/__tests__/actions/receive.test.js b/__tests__/actions/receive.test.js new file mode 100644 index 0000000..9ec0391 --- /dev/null +++ b/__tests__/actions/receive.test.js @@ -0,0 +1,49 @@ +// @flow + +import configureStore from 'redux-mock-store'; + +import { + LOAD_ADDRESSES_SUCCESS, + LOAD_ADDRESSES_ERROR, + loadAddressesSuccess, + loadAddressesError, +} from '../../app/redux/modules/receive'; + +const store = configureStore()(); + +describe('Receive Actions', () => { + beforeEach(() => store.clearActions()); + + test('should create an action to load addresses with success', () => { + const payload = { + addresses: [ + 'tm0a9si0ds09gj02jj', + 'smas098gk02jf0kskk' + ], + }; + + store.dispatch(loadAddressesSuccess(payload)); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: LOAD_ADDRESSES_SUCCESS, + payload, + }), + ); + }); + + test('should create an action to load addresses with error', () => { + const payload = { + error: 'Something went wrong!', + }; + + store.dispatch(loadAddressesError(payload)); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: LOAD_ADDRESSES_ERROR, + payload, + }), + ); + }); +}); diff --git a/__tests__/actions/send.test.js b/__tests__/actions/send.test.js new file mode 100644 index 0000000..4f82909 --- /dev/null +++ b/__tests__/actions/send.test.js @@ -0,0 +1,116 @@ +// @flow + +import configureStore from 'redux-mock-store'; + +import { + SEND_TRANSACTION, + SEND_TRANSACTION_SUCCESS, + SEND_TRANSACTION_ERROR, + RESET_SEND_TRANSACTION, + VALIDATE_ADDRESS_SUCCESS, + VALIDATE_ADDRESS_ERROR, + LOAD_ZEC_PRICE, + sendTransaction, + sendTransactionSuccess, + sendTransactionError, + resetSendTransaction, + validateAddressSuccess, + validateAddressError, + loadZECPrice, +} from '../../app/redux/modules/send'; + +const store = configureStore()(); + +describe('Send Actions', () => { + beforeEach(() => store.clearActions()); + + test('should create an action to send a transaction', () => { + store.dispatch(sendTransaction()); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: SEND_TRANSACTION, + }), + ); + }); + + test('should create an action to send transaction with success', () => { + const payload = { + operationId: '0b9ii4590ab-1d012klfo' + }; + + store.dispatch(sendTransactionSuccess(payload)); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: SEND_TRANSACTION_SUCCESS, + payload, + }), + ); + }); + + test('should create an action to send transaction with error', () => { + const payload = { + error: 'Something went wrong!', + }; + + store.dispatch(sendTransactionError(payload)); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: SEND_TRANSACTION_ERROR, + payload, + }), + ); + }); + + test('should reset a transaction', () => { + store.dispatch(resetSendTransaction()); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: RESET_SEND_TRANSACTION, + }), + ); + }); + + test('should validate a address with success', () => { + const payload = { + isValid: true + }; + + store.dispatch(validateAddressSuccess(payload)); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: VALIDATE_ADDRESS_SUCCESS, + payload, + }), + ); + }); + + test('should validate a address with error', () => { + store.dispatch(validateAddressError()); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: VALIDATE_ADDRESS_ERROR, + }), + ); + }); + + test('should load ZEC price', () => { + const payload = { + value: 1.35 + }; + + store.dispatch(loadZECPrice(payload)); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: LOAD_ZEC_PRICE, + payload + }), + ); + }); +}); diff --git a/__tests__/actions/transactions.test.js b/__tests__/actions/transactions.test.js new file mode 100644 index 0000000..836ad4a --- /dev/null +++ b/__tests__/actions/transactions.test.js @@ -0,0 +1,59 @@ +// @flow + +import configureStore from 'redux-mock-store'; + +import { + LOAD_TRANSACTIONS, + LOAD_TRANSACTIONS_SUCCESS, + LOAD_TRANSACTIONS_ERROR, + loadTransactions, + loadTransactionsSuccess, + loadTransactionsError, +} from '../../app/redux/modules/transactions'; + +const store = configureStore()(); + +describe('Transactions Actions', () => { + beforeEach(() => store.clearActions()); + + test('should create an action to load transactions', () => { + store.dispatch(loadTransactions()); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: LOAD_TRANSACTIONS, + }), + ); + }); + + test('should create an action to load transactions with success', () => { + const payload = { + list: [], + zecPrice: 0, + }; + + store.dispatch(loadTransactionsSuccess(payload)); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: LOAD_TRANSACTIONS_SUCCESS, + payload, + }), + ); + }); + + test('should create an action to load transactions with error', () => { + const payload = { + error: 'Something went wrong!', + }; + + store.dispatch(loadTransactionsError(payload)); + + expect(store.getActions()[0]).toEqual( + expect.objectContaining({ + type: LOAD_TRANSACTIONS_ERROR, + payload, + }), + ); + }); +}); From 4d4b0a3b3eca81c47abf5c96248ae2784bbe1484 Mon Sep 17 00:00:00 2001 From: George Lima Date: Thu, 31 Jan 2019 18:24:02 -0300 Subject: [PATCH 5/8] chore(wallet-address): remove flex wrapper component --- app/components/wallet-address.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/components/wallet-address.js b/app/components/wallet-address.js index f2ba388..5957f78 100644 --- a/app/components/wallet-address.js +++ b/app/components/wallet-address.js @@ -3,7 +3,6 @@ import React, { PureComponent } from 'react'; import styled from 'styled-components'; import { Transition, animated } from 'react-spring'; -import { ColumnComponent } from './column'; import { Button } from './button'; import { QRCode } from './qrcode'; @@ -18,6 +17,7 @@ const AddressWrapper = styled.div` border-radius: 6px; padding: 7px 13px; width: 100%; + margin-bottom: 5px; `; const Input = styled.input` @@ -56,7 +56,6 @@ const QRCodeWrapper = styled.div` background-color: #000; border-radius: 6px; padding: 20px; - margin-top: 10px; width: 100%; `; @@ -108,7 +107,7 @@ export class WalletAddress extends PureComponent { const buttonLabel = `${isVisible ? 'Hide' : 'Show'} details and QR Code`; return ( - +
{ } - +
); } } From 7a4584382fc779325c8f3fa34c40d052e5b2c4dc Mon Sep 17 00:00:00 2001 From: George Lima Date: Thu, 31 Jan 2019 18:26:08 -0300 Subject: [PATCH 6/8] chore(receive): add missing dispatch --- app/containers/receive.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/containers/receive.js b/app/containers/receive.js index 7a56e14..0318abc 100644 --- a/app/containers/receive.js +++ b/app/containers/receive.js @@ -40,9 +40,9 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ type === 'shielded' ? rpc.z_getnewaddress() : rpc.getnewaddress(''), ); - if (error || !address) return getNewAddressError({ error: 'Unable to generate a new address' }); + if (error || !address) return dispatch(getNewAddressError({ error: 'Unable to generate a new address' })); - getNewAddressSuccess({ address }); + dispatch(getNewAddressSuccess({ address })); }, }); From faa90b4a2f547630f82d3daea4216e7658eaa2da Mon Sep 17 00:00:00 2001 From: George Lima Date: Thu, 31 Jan 2019 18:28:06 -0300 Subject: [PATCH 7/8] chore(receive): show all addresses in receive page --- app/views/receive.js | 104 +++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 63 deletions(-) diff --git a/app/views/receive.js b/app/views/receive.js index a70af65..c38e191 100644 --- a/app/views/receive.js +++ b/app/views/receive.js @@ -1,5 +1,5 @@ // @flow -import React, { Fragment, PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import styled from 'styled-components'; import { Transition, animated } from 'react-spring'; @@ -58,12 +58,6 @@ const RevealsMain = styled.div` display: flex; align-items: flex-start; justify-content: flex-start; - - & > div { - top: 0; - right: 0; - left: 0; - } `; type Props = { @@ -91,16 +85,26 @@ export class ReceiveView extends PureComponent { showAdditionalOptions: !prevState.showAdditionalOptions, })); - renderShieldedAddresses = (address: string) => { + generateNewAddress = (type: addressType) => { + const { getNewAddress } = this.props; + + getNewAddress({ type }); + }; + + render() { + const { addresses } = this.props; const { showAdditionalOptions } = this.state; const buttonText = `${showAdditionalOptions ? 'Hide' : 'Show'} Other Address Types`; + const shieldedAddresses = addresses.filter(addr => addr.startsWith('z')); + const transparentAddresses = addresses.filter(addr => addr.startsWith('t')); + return ( - +