Commit f8095bdf authored by Martin Santangelo's avatar Martin Santangelo

WIP: payment selector, btc payment, wire refactor

1 merge request!325WIP: [Sprint/ModestMonkey] wire epic 37
......@@ -557,6 +557,7 @@
},
"wire":{
"amountMonth":"{{amount}}/month",
"noBtcAddress":"This channel has not configured their Bitcoin address yet",
"amountMonthDescription":"THIS POST CAN ONLY BE SEEN BY SUPPORTERS WHO WIRE {{amount}}/MONTH TO @{{name}}",
"weHaveReceivedYourTransaction":"We've received your transaction",
"pleaseTryUnlockingMessage":"Please try unlocking this post after it gets processed. We estimate it may take around 5 minutes.",
......
......@@ -22,26 +22,26 @@ export const CommonStyle = StyleSheet.create({
alignContent: 'center',
},
centered: {
alignContent: 'center',
alignItems: 'center',
alignSelf: 'center',
justifyContent: 'center',
alignContent: 'center',
alignSelf: 'center'
},
columnAlignCenter: {
flexDirection: 'column',
alignItems: 'center',
flexDirection: 'column',
},
columnAlignStart: {
flexDirection: 'column',
alignItems: 'flex-start',
flexDirection: 'column',
},
columnAlignEnd: {
flexDirection: 'column',
alignItems: 'flex-end',
flexDirection: 'column',
},
columnStretch: {
flexDirection: 'column',
alignItems: 'stretch',
flexDirection: 'column',
},
rowJustifyEnd: {
flexDirection: 'row',
......@@ -74,12 +74,11 @@ export const CommonStyle = StyleSheet.create({
fillFlex: {
flexGrow: 1,
},
// color
colorWhite: {
color: 'white'
color: '#FFFFFF'
},
colorBlack: {
color: 'black'
color: '#000000'
},
colorLight: {
color: colors.light
......@@ -158,6 +157,9 @@ export const CommonStyle = StyleSheet.create({
borderLight: {
borderColor: colors.light
},
borderLightGreyed: {
borderColor: colors.lightGreyed
},
borderSecondary: {
borderColor: colors.secondary
},
......
......@@ -70,7 +70,7 @@ export const ComponentsStyle = StyleSheet.create({
margin: 4,
padding: 4,
alignItems: 'center',
borderRadius: 15,
borderRadius: 20,
borderWidth: 1,
},
bluebutton: {
......
This diff is collapsed.
// @flow
import api from './../common/services/api.service';
import i18n from '../common/services/i18n.service';
import BlockchainWireService from '../blockchain/services/BlockchainWireService';
......@@ -13,9 +14,9 @@ class WireService {
* Unlock an activity
* @param {string} guid
*/
unlock(guid) {
unlock(guid: string): Promise<any> {
return api.get(`api/v1/wire/threshold/${guid}`)
.then((response) => {
.then((response: any): any => {
if (response.hasOwnProperty('activity')) {
return response.activity;
} else if (response.hasOwnProperty('entity')) {
......@@ -29,7 +30,7 @@ class WireService {
* Get overview
* @param {string} guid
*/
overview(guid) {
overview(guid: string): Promise<any> {
return api.get(`api/v1/wire/sums/overview/${guid}?merchant=1`);
}
......@@ -37,7 +38,7 @@ class WireService {
* Get user rewards
* @param {string} guid
*/
userRewards(guid) {
userRewards(guid: string): Promise<any>{
return api.get(`api/v1/wire/rewards/${guid}/entity`);
}
......@@ -45,14 +46,14 @@ class WireService {
* Get rewards
* @param {string} guid
*/
rewards(guid) {
rewards(guid: string): Promise<any>{
return api.get(`api/v1/wire/rewards/${guid}`)
.then(rewards => {
.then((rewards: any): any=> {
rewards = (rewards.wire_rewards) ? rewards.wire_rewards.rewards : null
if (rewards) {
// map types
for (let type in rewards) {
rewards[type] = rewards[type].map((reward) => {
rewards[type] = rewards[type].map((reward): any => {
reward.type = type;
return reward;
});
......@@ -66,7 +67,7 @@ class WireService {
* Send wire
* @param {object} opts
*/
async send(opts) {
async send(opts): Promise<any> {
const payload = await this.getTransactionPayloads(opts);
if (!payload) {
......@@ -78,7 +79,7 @@ class WireService {
method: 'tokens',
amount: opts.amount,
recurring: !!opts.recurring
}).then(result => {
}).then((result: any): any => {
result.payload = payload;
return result;
});
......@@ -88,8 +89,12 @@ class WireService {
* Get transaction payloads
* @param {object} opts
*/
async getTransactionPayloads(opts) {
const payload = await BlockchainWalletService.selectCurrent(i18n.t('wire.selectWalletMessage'), { signable: true, offchain: true, buyable: true, confirmTokenExchange: opts.amount });
async getTransactionPayloads(opts: Object): any {
if (opts.currency == 'tokens') {
const payload = await BlockchainWalletService.selectCurrent(i18n.t('wire.selectWalletMessage'), { signable: true, offchain: true, buyable: true, confirmTokenExchange: opts.amount });
}
if (!payload || payload.cancelled) {
return;
......
// @flow
import {
observable,
action
} from 'mobx'
import { Alert } from 'react-native';
import wireService from './WireService';
import currency from '../common/helpers/currency';
import i18n from '../common/services/i18n.service';
export type PayloadType =
| 'onchain'
| 'offchain'
| 'usd'
| 'eth'
| 'erc20'
| 'btc';
/**
* Wire store
*/
class WireStore {
@observable currency = 'tokens';
@observable amount = 1;
@observable sending = false;
@observable.shallow owner = null;
@observable recurring = false;
@observable showBtc = false;
@observable loaded = false;
@observable errors = [];
guid = null;
setGuid(guid) {
this.guid = guid;
@action
setShowBtc = (value: boolean) => {
this.showBtc = value;
}
@action
setCurrency(value) {
setCurrency = (value: string) => {
this.currency = value;
// only tokens and usd can be recurring
if (this.currency !== 'tokens' && this.currency !== 'usd') {
this.recurring = false;
}
this.validate();
}
@action
setAmount(val) {
setAmount(val: number) {
this.amount = val;
this.validate();
}
@action
setTier = (tier) => {
setTier = (tier: any) => {
this.amount = tier.amount;
if (tier.currency) this.currency = tier.currency;
if (tier.currency) {
this.setCurrency(tier.currency);
} else {
this.validate();
}
}
@action
setOwner(owner) {
setOwner(owner: any) {
this.owner = owner;
}
loadUser(guid) {
return wireService.userRewards(guid)
.then(owner => {
this.setOwner(owner);
return owner;
});
async loadUserRewards(): Promise<any> {
const owner = await wireService.userRewards(this.owner.guid);
const { merchant, eth_wallet, wire_rewards, sums } = owner;
if (this.owner) {
this.owner.merchant = merchant;
this.owner.eth_wallet = eth_wallet;
this.owner.wire_rewards = wire_rewards;
this.owner.sums = sums;
}
this.setLoaded(true);
return owner;
}
round(number, precision) {
@action
setLoaded(value: boolean) {
this.loaded = value;
}
round(number: number, precision: number): number {
const factor = Math.pow(10, precision);
const tempNumber = number * factor;
const roundedTempNumber = Math.round(tempNumber);
......@@ -64,16 +96,32 @@ class WireStore {
/**
* Get formated amount
*/
formatAmount(amount) {
formatAmount(amount: number): string {
return amount.toLocaleString('en-US') + ' tokens';
}
/**
* Validate payment
*/
@action
validate() {
//TODO: implement wire validation
this.errors = [];
switch (this.currency) {
case 'btc':
if (this.owner && !this.owner.btc_address) {
this.errors.push(i18n.t('wire.noBtcAddress'));
}
break;
}
if (this.amount <= 0) {
this.errors.push(i18n.t('boosts.errorAmountSholdbePositive'));
}
}
@action
setRecurring(recurring) {
setRecurring(recurring: boolean) {
this.recurring = !!recurring;
}
......@@ -85,13 +133,19 @@ class WireStore {
/**
* Confirm and Send wire
*/
async send() {
@action
async send(): Promise<any> {
if (this.sending) {
return;
}
let done;
// for btc we only show the btc component
if (this.currency === 'btc') {
return this.setShowBtc(true);
}
try {
this.sending = true;
......@@ -99,7 +153,8 @@ class WireStore {
amount: this.amount,
guid: this.guid,
owner: this.owner,
recurring: this.recurring
recurring: this.recurring,
currency: this.currency
});
this.stopSending();
......@@ -119,10 +174,13 @@ class WireStore {
@action
reset() {
this.amount = 1;
this.showBtc = false;
this.currency = 'tokens';
this.sending = false;
this.owner = null;
this.recurring = false;
this.guid = null;
this.loaded = false;
this.errors = [];
}
}
......
// @flow
import * as React from 'react'
import { View, Text, Linking } from 'react-native';
import QRCode from 'react-native-qrcode-svg';
import { CommonStyle as CS } from '../../styles/Common';
import viewportPercentage from '../../common/helpers/viewportPercentage';
import Button from '../../common/components/Button';
import i18nService from '../../common/services/i18n.service';
type PropsType = {
address: string,
amount: number,
onCancel: ?Function
};
/**
* Btc Payment
*/
export default class BtcPayment extends React.PureComponent<PropsType> {
url = '';
/**
* Open bitcoin link
*/
openLink = () => {
Linking.openURL(this.url);
}
cancel = () => {
if (this.props.onCancel) {
this.props.onCancel();
}
}
/**
* Render
*/
render(): React.Node {
this.url = `bitcoin:${this.props.address}?amount=${this.props.amount}`;
return (
<View style={[CS.flexContainer, CS.marginTop3x]}>
<Text style={[CS.fontXL, CS.textCenter]}>Tap to send <Text style={CS.colorPrimary}>{ this.props.amount } BTC</Text> to</Text>
<Text style={[CS.colorPrimary, CS.fontL]} numberOfLines={1}>{ this.props.address }</Text>
<View style={CS.rowJustifyCenter}>
<Button
text={i18nService.t('goback')}
onPress={this.cancel}
containerStyle={CS.padding}
textStyle={CS.fontL}
/>
<Button
inverted
text={i18nService.t('send').toUpperCase()}
onPress={this.openLink}
containerStyle={CS.padding}
textStyle={CS.fontL}
/>
</View>
<Text style={[CS.fontXL, CS.textCenter, CS.marginTop4x]}>Or scan the following QR code</Text>
<View style={[CS.centered, CS.marginTop3x]}>
<QRCode
value={this.url}
size={viewportPercentage(70).value}
/>
</View>
</View>
);
}
}
\ No newline at end of file
// @flow
import * as React from 'react'
import Icon from 'react-native-vector-icons/FontAwesome5';
type PropsType = {
value: string
};
/**
* Payment method selector
*/
export default class PaymentMethodIcon extends React.PureComponent<PropsType> {
render(): React.Node {
const { value, ...other } = this.props;
let icon: string = '';
switch (value.toLowerCase()) {
case 'tokens':
icon = 'lightbulb';
break;
case 'usd':
icon = 'dollar-sign';
break;
case 'btc':
icon = 'bitcoin';
break;
case 'eth':
icon = 'ethereum';
break;
}
return <Icon name={icon} {...other} />
}
}
\ No newline at end of file
// @flow
import * as React from 'react'
import { Text, StyleSheet } from 'react-native';
import Menu, { MenuItem } from 'react-native-material-menu';
import featuresService from '../../common/services/features.service';
import testID from '../../common/helpers/testID';
import Colors from '../../styles/Colors';
import PaymentMethodIcon from './PaymentMethodIcon';
import { CommonStyle as CS } from '../../styles/Common';
type PropsType = {
button: React.Node,
value: string,
onSelect: Function
};
/**
* Payment method selector
*/
export default class PaymentMethodSelector extends React.PureComponent<PropsType> {
methods: Array<any>;
menuRef: ?React.ElementRef<Menu>;
/**
* @param {PropsType} props
*/
constructor(props: PropsType) {
super(props);
if (featuresService.has('wire-multi-currency')) {
this.methods = [
{label: 'Tokens', handle: (): any => this.onSelect('tokens')},
{label: 'USD', handle: (): any => this.onSelect('usd')},
{label: 'BTC', handle: (): any => this.onSelect('btc')},
{label: 'ETH', handle: (): any => this.onSelect('eth')},
];
} else {
this.methods = [{label: 'Tokens', handle: (): any => this.onSelect('tokens')}];
}
this.menuRef = React.createRef();
}
/**
* On method selected
* @param {*} method
*/
onSelect(method: any) {
if (this.props.onSelect) {
this.props.onSelect(method);
}
if (this.menuRef && this.menuRef.current) {
this.menuRef.current.hide();
}
}
/**
* Show menu
*/
show() {
if (this.menuRef && this.menuRef.current) {
this.menuRef.current.show();
}
}
/**
* Render
*/
render(): React.Node {
return (
<Menu ref={this.menuRef} button={this.props.button}>
{this.methods.map((method: any, i: number): MenuItem => (
<MenuItem
key={i}
onPress={method.handle}
textStyle={CS.fontXL}
{...testID(`PAYMENT METHOD ${method.label}`)}
>
<Text style={(method.label.toLowerCase() === this.props.value) ? styles.selected : null}><PaymentMethodIcon value={method.label} size={15} /> {method.label}</Text>
</MenuItem>
))}
</Menu>
);
}
}
const styles: any = StyleSheet.create({
selected: {
color: Colors.primary
}
})
\ No newline at end of file
import React, {PureComponent, Fragment} from 'react';
import { Text, Dimensions, View, StyleSheet } from 'react-native';
import React, { PureComponent } from 'react';
import { Text, View } from 'react-native';
import Carousel from 'react-native-snap-carousel';
import featuresService from '../../common/services/features.service';
import i18n from '../../common/services/i18n.service';
import ModalPicker from '../../common/components/ModalPicker';
import RewardsStateDecreaseView from '../../notifications/notification/view/RewardsStateDecreaseView';
import { CommonStyle as CS } from '../../styles/Common';
import viewportPercentage from '../../common/helpers/viewportPercentage';
......@@ -32,6 +29,10 @@ export default class SubscriptionTierCarousel extends PureComponent {
return amount > 1 ? 'Tokens' : 'Token';
case 'usd':
return 'USD';
case 'eth':
return 'ETH';
case 'btc':
return 'BTC';
}
}
......@@ -82,7 +83,7 @@ export default class SubscriptionTierCarousel extends PureComponent {
const amount = row.item.amount || this.props.amount;
const currency = row.item.currency || this.props.currency;
return (
<View key={`rewards${row.item.amount}`} style={[CS.rowJustifyCenter, CS.backgroundLightGreyed, CS.borderRadius5x, CS.shadow, CS.padding2x, CS.border, CS.borderGreyed]}>
<View key={`rewards${row.item.amount}`} style={[CS.rowJustifyCenter, CS.backgroundLightGreyed, CS.borderRadius5x, CS.padding2x, CS.border, CS.borderGreyed]}>
<View style={CS.columnAlignCenter}>
<Text style={[CS.fontXXL, CS.fontMedium, CS.colorDark]}>{amount} {this.getPluralizedCurrency(currency, row.item.amount)} / month</Text>
<Text numberOfLines={5} style={[CS.fontL, CS.fontHairline, CS.colorDark]}>{row.item.description}</Text>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment