Skip to content
Commits on Source (6)
......@@ -557,6 +557,7 @@
},
"wire":{
"amountMonth":"{{amount}}/month",
"noAddress":"This channel has not configured their {{type}} 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.",
......
......@@ -7,6 +7,12 @@ class BlockchainWireService {
return await Web3Service.getContract('wire');
}
/**
* Create an onchain tokens wireß
* @param {string} receiver
* @param {string} tokensAmount
* @param {string} message
*/
async create(receiver, tokensAmount, message = '') {
const token = await BlockchainTokenService.getContract(),
wireAddress = (await this.getContract()).options.address;
......@@ -24,6 +30,21 @@ class BlockchainWireService {
return result.transactionHash;
}
/**
* Create a eth wire
* @param {string} receiver
* @param {string} tokensAmount
*/
async createEth(receiver, tokensAmount) {
const result = await Web3Service.sendEth(
receiver,
tokensAmount
);
return result.transactionHash;
}
}
export default new BlockchainWireService();
......@@ -126,6 +126,36 @@ class Web3Service {
throw new Error('E_CANCELLED');
}
}
/**
* Send ETH from the selected wallet to the destination address
* @param {string} to destination ETH address
* @param {number} amount eth amount
*/
async sendEth(to, amount) {
const toHex = this.web3.utils.toHex;
const baseOptions = await this.getTransactionOptions();
const privateKey = await BlockchainWalletService.unlock(baseOptions.from);
const nonce = await this.web3.eth.getTransactionCount(baseOptions.from);
const tx = {
nonce,
to,
from: baseOptions.from,
value: toHex( this.web3.utils.toWei(amount, 'ether') ),
gas: toHex(21000),
gasPrice: toHex( this.web3.utils.toWei('2', 'Gwei') ), // converts the gwei price to wei
}
const signedTx = sign(tx, privateKey);
return await new Promise((resolve, reject) => {
this.web3.eth.sendSignedTransaction(signedTx)
.once('transactionHash', hash => resolve({ transactionHash: hash }))
.once('error', e => reject(e));
});
}
}
export default new Web3Service();
......@@ -2,7 +2,6 @@ import React, { Component } from 'react';
import {
View,
Text,
TextInput,
Alert,
StyleSheet,
} from 'react-native';
......@@ -16,6 +15,8 @@ import Web3Service from '../../services/Web3Service';
import { ComponentsStyle } from '../../../styles/Components';
import i18n from '../../../common/services/i18n.service';
import TextInput from '../../../common/components/TextInput';
function addressExcerpt(address) {
return `0×${address.substr(2, 5)}...${address.substr(-5)}`;
}
......
......@@ -66,8 +66,21 @@ export default class BlockchainWalletModalScreen extends Component {
return;
}
let type;
switch(opts.currency) {
case 'tokens':
type = 'onchain';
break;
case 'eth':
type = 'eth';
break;
default:
throw new Error('BlockchainWalletModal: currency not supported '+ opts.currency);
}
payload = {
type: 'onchain',
type,
wallet: toJS(wallet)
}
}
......
......@@ -26,7 +26,7 @@ class StorageService {
return null;
}
value = await this._decryptIfNeeded(value);
value = await this._decryptIfNeeded(value, key);
return JSON.parse(value);
}
......@@ -36,7 +36,7 @@ class StorageService {
*
* @param {any} value
*/
async _decryptIfNeeded(value) {
async _decryptIfNeeded(value, key) {
if (value.startsWith(CRYPTO_AES_PREFIX)) {
const keychain = await AsyncStorage.getItem(`${STORAGE_KEY_KEYCHAIN_PREFIX}${key}`);
......
......@@ -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: {
......
import React, {
Component
Component, Fragment
} from 'react';
import {
......@@ -20,13 +20,11 @@ import {
inject
} from 'mobx-react/native'
import { Button } from 'react-native-elements';
import Icon from 'react-native-vector-icons/Ionicons';
import McIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import colors from '../styles/Colors';
import { CommonStyle } from '../styles/Common';
import { CommonStyle as CS } from '../styles/Common';
import CenteredLoading from '../common/components/CenteredLoading';
import RewardsCarousel from '../channel/carousel/RewardsCarousel';
......@@ -38,6 +36,11 @@ import addressExcerpt from '../common/helpers/address-excerpt';
import i18n from '../common/services/i18n.service';
import logService from '../common/services/log.service';
import SubscriptionTierCarousel from './tiers/SubscriptionTierCarousel';
import PaymentMethodSelector from './methods/PaymentMethodSelector';
import BtcPayment from './methods/BtcPayment';
import PaymentMethodIcon from './methods/PaymentMethodIcon';
import Button from '../common/components/Button';
import numberFromat from '../common/helpers/number';
/**
* Wire Fab Screen
......@@ -49,24 +52,40 @@ export default class FabScreen extends Component {
componentWillMount() {
this.paymethodRef = React.createRef();
if (!featuresService.has('crypto')) {
featuresService.showAlert();
return this.props.navigation.goBack();
}
const owner = this.getOwner();
this.props.wire.setGuid(owner.guid);
this.props.wire.loadUser(owner.guid)
.then(() => this.setDefaults());
this.loadUserAndSetDefaults();
this.props.wallet.refresh();
}
}
componentWillUnmount() {
this.props.wire.setOwner(null);
}
async loadUserAndSetDefaults() {
const params = this.props.navigation.state.params;
// if there is no default data we reset the store
if (!params || !params.default) {
this.props.wire.reset();
}
const owner = this.getOwner();
this.props.wire.setOwner(owner);
await this.props.wire.loadUserRewards();
this.setDefaults();
}
setDefaults() {
const params = this.props.navigation.state.params;
const wire = this.props.wire;
......@@ -94,74 +113,88 @@ export default class FabScreen extends Component {
*/
static navigationOptions = ({ navigation }) => ({
header: (
<View style={styles.header}>
<Icon size={36} name="ios-close" onPress={() => navigation.goBack()} style={styles.iconclose}/>
<View style={[CS.backgroundLight, CS.rowJustifyEnd]}>
<Icon size={40} name="ios-close" onPress={() => navigation.goBack()} style={[CS.marginRight3x, CS.marginTop3x]}/>
</View>
),
transitionConfig: {
isModal: true
}
})
});
/**
* Render screen
*/
render() {
if (!this.props.wire.owner) {
return <CenteredLoading/>
selectMethod = () => {
if (this.paymethodRef.current) {
this.paymethodRef.current.show();
}
}
// get the owner passed as a parameter in navigation
const owner = this.getOwner();
const txtAmount = this.getTextAmount();
onCancelBtc = () => {
this.props.wire.setShowBtc(false);
}
// sending?
let icon;
if (this.props.wire.sending) {
icon = <ActivityIndicator size={'large'} color={selectedcolor}/>
} else {
icon = <Icon size={64} name="ios-flash" style={styles.icon} />
getBody() {
if (this.props.wire.showBtc) {
return (
<BtcPayment amount={this.props.wire.amount} address={this.props.wire.owner.btc_address} onCancel={this.onCancelBtc}/>
)
}
const owner = this.getOwner();
const txtAmount = this.getTextAmount();
const amount = this.props.wire.amount.toString();
const currency = this.props.wire.currency;
const buttonDisabled = this.props.wire.sending || this.props.wire.errors.length > 0;
const currencySelector = (
<View style={CS.alignCenter}>
<PaymentMethodIcon value={this.props.wire.currency} size={30} style={CS.colorPrimary} onPress={this.selectMethod}/>
<Text style={[CS.fontL, CS.colorPrimary]} onPress={this.selectMethod}>
{this.props.wire.currency.toUpperCase()}
</Text>
</View>
)
return (
<ScrollView contentContainerStyle={styles.body}>
{icon}
<Text style={styles.subtext}>{i18n.to('wire.supportMessage', {payments: featuresService.has('wire-multi-currency') ? 'tokens , ETH, BTC or USD' : 'tokens' }, {
name: <Text style={styles.bold}>@{ owner.username }</Text>
<Fragment>
<Text style={[CS.fontM, CS.textCenter]}>{i18n.to('wire.supportMessage', {payments: featuresService.has('wire-multi-currency') ? 'tokens , ETH, BTC or USD' : 'tokens' }, {
name: <Text style={CS.bold}>@{ owner.username }</Text>
})}</Text>
<View style={{height: 180, paddingTop: 20}}>
<SubscriptionTierCarousel
<View style={[styles.carouselContainer, CS.paddingTop2x]}>
{this.props.wire.owner.wire_rewards.rewards && <SubscriptionTierCarousel
amount={amount}
rewards={this.props.wire.owner.wire_rewards.rewards}
currency={this.props.wire.currency}
onTierSelected={this.props.wire.setTier}
/>
/>}
</View>
<View style={{ flexDirection: 'row', marginTop: 15, marginBottom: 32, }}>
<View>
{this.props.wire.errors.map(e => <Text style={[CS.colorDanger, CS.fontM, CS.textCenter]}>{e}</Text>)}
</View>
{this.props.wire.currency === 'btc' && <Text style={[CS.fontM, CS.textCenter]}>You can send BTC to this user, however it will not recur.</Text>}
<View style={[CS.rowJustifySpaceEvenly, CS.marginBottom3x, CS.marginTop3x, CS.alignJustifyCenter, CS.alignCenter]}>
<View style={[CS.flexContainer, CS.centered]}>
<PaymentMethodSelector
button={currencySelector}
ref={this.paymethodRef}
value={this.props.wire.currency}
onSelect={this.props.wire.setCurrency}
/>
</View>
<TextInput
ref="input"
onChangeText={this.changeInput}
style={[CommonStyle.field, styles.input]}
style={[CS.field, CS.fontXXXL, CS.backgroundLightGreyed, CS.padding3x, CS.textRight, CS.flexContainer, CS.borderRadius5x]}
underlineColorAndroid="transparent"
value={amount}
keyboardType="numeric"
/>
<View style={{ flexDirection: 'column', alignItems: 'center', justifyContent: 'center', flex: 1 }}>
<Text style={{ fontSize: 24, fontWeight: '600', fontFamily: 'Roboto', padding: 16, color: '#555' }}>
{currency.toUpperCase()}
</Text>
</View>
/>
</View>
<View style={{ width: '100%', alignSelf: 'flex-start', }}>
<View>
<CheckBox
title={i18n.t('wire.repeatMessage')}
checked={this.props.wire.recurring}
......@@ -171,7 +204,7 @@ export default class FabScreen extends Component {
checkedColor={ colors.primary }
uncheckedIcon="circle-o"
uncheckedColor={ colors.greyed }
containerStyle={{ margin: 0 }}
containerStyle={[CS.backgroundLight]}
/>
</View>
......@@ -181,44 +214,40 @@ export default class FabScreen extends Component {
</View> }
<Button
title={(this.props.wire.amount == 0) ? i18n.t('ok') : i18n.t('send')}
buttonStyle={styles.send}
disabled={this.props.wire.sending}
text={(this.props.wire.amount == 0) ? i18n.t('ok').toUpperCase() : i18n.t('send').toUpperCase()}
disabled={buttonDisabled}
onPress={this.confirmSend}
backgroundColor={selectedcolor}
textStyle={[CS.fontL, CS.padding]}
inverted
/>
{!!this.props.wallet.addresses && false && <View style={styles.addressViewWrapper}>
{this.props.wallet.addresses.map((address, i) => (
<View style={styles.addressView} key={address.address}>
<View style={styles.addressMetaView}>
<Text style={styles.addressLabel}>{i18n.t('wire.addressLabel', {label:address.label})}</Text>
<Text style={styles.addressAddress} ellipsizeMode='tail' numberOfLines={1}>{addressExcerpt(address.address)}</Text>
</View>
<View style={styles.addressBalanceView}>
<Text style={styles.addressBalanceText}>{number(token(address.balance, 18), 3)}</Text>
</View>
</View>
))}
</View>}
</ScrollView>
);
</Fragment>
)
}
validate() {
try {
this.props.wire.validate();
return true;
} catch(e) {
Alert.alert(
'Atention',
(e && e.message) || 'Unknown internal error',
[{ text: 'OK' }],
{ cancelable: false }
)
return false;
/**
* Render screen
*/
render() {
if (!this.props.wire.loaded) {
return <CenteredLoading/>
}
// sending?
let icon;
if (this.props.wire.sending) {
icon = <ActivityIndicator size={'large'} color={colors.primary}/>
} else {
icon = <Icon size={64} name="ios-flash" style={CS.colorPrimary} />
}
const body = this.getBody();
return (
<ScrollView contentContainerStyle={[CS.backgroundLight, CS.paddingLeft2x, CS.paddingRight2x, CS.flexContainer, CS.alignCenter]}>
{icon}
{body}
</ScrollView>
);
}
confirmSend = () => {
......@@ -230,7 +259,9 @@ export default class FabScreen extends Component {
return;
}
if (!this.validate()) return;
if (this.props.wire.currency === 'btc') {
return this.send();
}
Alert.alert(
i18n.t('confirmMessage'),
......@@ -279,6 +310,11 @@ export default class FabScreen extends Component {
}
changeInput = (val) => {
if (val !== '') {
val = val.replace(',','.');
val = val.replace('..','.');
val = val.replace("/(?<=\w)\.(?=\w+\.)|\G\w+\K\./g", '');
}
this.props.wire.setAmount(val);
}
}
......@@ -287,117 +323,7 @@ const selectedcolor = '#4690D6';
const color = '#444'
const styles = {
body: {
paddingLeft: 10,
paddingRight: 10,
alignItems: 'center',
backgroundColor: '#F8F8F8',
opacity: 0.97,
flex:1
},
send: {
marginTop: 20
},
input: {
fontSize: 50,
paddingRight: 16,
backgroundColor: '#eee',
borderRadius: 3,
color: '#666',
flex: 1,
textAlign: 'right',
},
bold: {
fontWeight: 'bold'
},
subtext: {
fontWeight: '200',
textAlign: 'left',
fontSize: 12,
color: '#666666',
marginTop: 12,
},
icon: {
color: '#4690D6'
},
header: {
backgroundColor: '#F8F8F8',
},
iconclose: {
alignSelf: 'flex-end',
padding: 10
},
rewards: {
fontSize: 18,
fontWeight: '300',
marginTop: 32
},
lastmonth: {
color: '#666',
fontSize: 13,
fontWeight: '200',
marginTop: 5,
marginBottom: 10
},
// options styles
container: {
display: 'flex',
flexDirection: 'row',
paddingTop: 12,
paddingLeft: 16,
paddingRight: 16,
},
topbar: {
flex: 1,
justifyContent: 'space-between',
flexDirection: 'row',
},
buttontext: {
paddingTop: 5,
fontSize: 16
},
button: {
flex: 1,
flexDirection: 'column',
alignItems: 'center',
padding: 3,
},
selected: {
color: selectedcolor
},
carousel: {
paddingTop: 20
},
addressViewWrapper: {
marginTop: 30,
borderWidth: 1,
borderColor: colors.greyed,
borderRadius: 4,
width: '100%',
},
addressView: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
margin: 10
},
addressMetaView: {
flexGrow: 1,
},
addressLabel: {
fontWeight: '700',
},
addressAddress: {
color: colors.darkGreyed,
fontSize: 10
},
addressBalanceView: {
paddingLeft: 10,
},
addressBalanceText: {
color: 'green',
fontSize: 18,
fontWeight: '700'
carouselContainer: {
height: 180
}
}
// @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) {
......@@ -75,10 +76,10 @@ class WireService {
return await api.post(`api/v1/wire/${opts.guid}`, {
payload,
method: 'tokens',
method: payload.method,
amount: opts.amount,
recurring: !!opts.recurring
}).then(result => {
}).then((result: any): any => {
result.payload = payload;
return result;
});
......@@ -88,8 +89,14 @@ 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 {
let payload
if (opts.currency == 'tokens' || opts.currency == 'eth') {
payload = await BlockchainWalletService.selectCurrent(i18n.t('wire.selectWalletMessage'), { signable: true, offchain: opts.currency === 'tokens', buyable: opts.currency === 'tokens', confirmTokenExchange: opts.amount, currency: opts.currency });
}
if (!payload || payload.cancelled) {
return;
......@@ -128,6 +135,18 @@ class WireService {
receiver: opts.owner.eth_wallet,
txHash: await BlockchainWireService.create(opts.owner.eth_wallet, opts.amount)
};
case 'eth':
if (!opts.owner.eth_wallet) {
throw new Error(i18n.t('boosts.errorCantReceiveTokens'));
}
return {
method: payload.type,
address: payload.wallet.address,
receiver: opts.owner.eth_wallet,
txHash: await BlockchainWireService.createEth(opts.owner.eth_wallet, opts.amount)
};
}
throw new Error('Unknown type');
......
// @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,37 @@ class WireStore {
/**
* Get formated amount
*/
formatAmount(amount) {
return amount.toLocaleString('en-US') + ' tokens';
formatAmount(amount: number): string {
return amount.toLocaleString('en-US') + ' ' + this.currency;
}
/**
* 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.noAddress', {type: 'Bitcoin'}));
}
break;
case 'eth':
if (this.owner && !this.owner.eth_wallet) {
this.errors.push(i18n.t('wire.noAddress', {type: 'ETH'}));
}
break;
}
if (this.amount <= 0) {
this.errors.push(i18n.t('boosts.errorAmountSholdbePositive'));
}
}
@action
setRecurring(recurring) {
setRecurring(recurring: boolean) {
this.recurring = !!recurring;
}
......@@ -85,13 +138,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 +158,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 +179,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>
......