diff --git a/src/actions/alert.js b/src/actions/alert.js new file mode 100644 index 0000000..02e78ff --- /dev/null +++ b/src/actions/alert.js @@ -0,0 +1,18 @@ +import types from '../types/alert'; + +export const enablePriceAlertAction = (data) => ({ + type: types.ENABLE_PRICE_ALERT, + payload: data +}); +export const disablePriceAlertAction = (data) => ({ + type: types.DISABLE_PRICE_ALERT, + payload: data +}); +export const deletePriceAlertAction = (data) => ({ + type: types.DELETE_PRICE_ALERT, + payload: data +}); +export const fetchPriceAlertAction = (data) => ({ + type: types.FETCH_PRICE_ALERT, + payload: data +}); \ No newline at end of file diff --git a/src/components/PriceAlert/create.js b/src/components/PriceAlert/create.js new file mode 100644 index 0000000..c87dda0 --- /dev/null +++ b/src/components/PriceAlert/create.js @@ -0,0 +1,207 @@ +import React, { Component } from 'react'; +import { ScrollView, StyleSheet, View, Text, TextInput, Alert } from 'react-native'; +import { connect } from 'react-redux'; +import RadioForm, {RadioButton, RadioButtonInput, RadioButtonLabel} from 'react-native-simple-radio-button'; +import { baseColor, lossColor, brandColor } from '../../config' +import { Button, StyleProvider, } from 'native-base'; +import getTheme from '../../../native-base-theme/components'; +import _platform from '../../../native-base-theme/variables/platform'; +import { withDrawer } from '../../helpers/drawer'; +import { createPriceAlert } from '../../reducers/alert' + +const styles = StyleSheet.create({ + scrollContainer: { + backgroundColor: baseColor + }, + container: { + display: 'flex', + borderColor: '#fff', + justifyContent: 'space-between', + backgroundColor: baseColor, + marginBottom: 10, + flexDirection: 'row', + paddingLeft: 20 + }, + containerChild: { + marginBottom: 15, + flexGrow: .5, + flexBasis: 1, + display: 'flex', + justifyContent: 'space-around', + flexDirection: 'row' + }, + textInput: { + height: 30, + fontSize: 14, + width: '50%', + borderColor: 'gray', + borderBottomWidth: 1, + color: '#fff' + }, + horizontalLine: { + borderBottomColor: '#f5f5f5', + borderBottomWidth: 1, + marginTop: 15, + marginBottom: 15 + } +}) + +const radioProps = [ + {label: 'One Time', value: 0 }, + {label: 'Continuous', value: 1 } +] + +const priceLevel = [ + {label: 'More than', value: 0 }, + {label: 'Less than', value: 1 } +] + +class CreatePriceAlert extends Component { + state = { + radioValue: 0, + frequency: 0, + price: 0, + type: 0, + } + + submit = () => { + const {currencySymbol, currency, createPriceAlert } = this.props; + if(this.state.price <= 0) { + Alert.alert('Please enter a correct price value'); + return + } + + createPriceAlert({ + fsym : currencySymbol, + tsym: currency, + price: parseFloat(this.state.price), + type: parseInt(this.state.type), + frequency: parseInt(this.state.frequency) + }) + } + + render() { + const { tokenDetails, edit, price } = this.props + + return ( + + + + THRESHOLD + + + + PRICE + this.setState({price})} + keyboardType='numeric' + placeholder={'Enter numeric value'} + placeholderTextColor='#444' + /> + + + + Current + + + + + WHEN THE PRICE IS + + + {this.setState({type: value})}} + /> + + + + FREQUENCY + + + + {this.setState({frequency: value})}} + /> + + + + + + + + + ) + } +} + + +const mapStateToProps = (state, props) => ({ + tokenDetails: state.account.tokenDetails, + portfolio: state.account.portfolio, + currency: state.account.preference.currency, + ...props.navigation.state.params, +}) + +const mapDispatchToProps = (dispatch) => ({ + createPriceAlert: (data) => dispatch(createPriceAlert(data)) +}) + +export default connect(mapStateToProps, mapDispatchToProps)(withDrawer(CreatePriceAlert)); \ No newline at end of file diff --git a/src/components/PriceAlert/index.js b/src/components/PriceAlert/index.js index 806b652..bb9cbc4 100644 --- a/src/components/PriceAlert/index.js +++ b/src/components/PriceAlert/index.js @@ -1,10 +1,17 @@ import React, { Component } from 'react'; -import { ScrollView, StyleSheet, View, Text, TextInput } from 'react-native'; +import { ScrollView, StyleSheet, View, Text, TextInput, FlatList, Alert } from 'react-native'; import { connect } from 'react-redux'; import RadioForm, {RadioButton, RadioButtonInput, RadioButtonLabel} from 'react-native-simple-radio-button'; import { baseColor, lossColor, brandColor } from '../../config' - +import { Button, StyleProvider, List, ListItem, Body, Left, Right, Switch, Icon, Separator } from 'native-base'; +import getTheme from '../../../native-base-theme/components'; +import _platform from '../../../native-base-theme/variables/platform'; import { withDrawer } from '../../helpers/drawer'; +import { createPriceAlert } from '../../reducers/account' +import { SimpleLineIcons, Entypo } from '@expo/vector-icons'; +import { NavigationActions } from 'react-navigation'; +import { getPriceAlert,disablePriceAlert, enablePriceAlert, deletePriceAlert } from '../../reducers/alert' + const styles = StyleSheet.create({ scrollContainer: { @@ -13,10 +20,9 @@ const styles = StyleSheet.create({ container: { display: 'flex', borderColor: '#fff', - justifyContent: 'space-between', + justifyContent: 'center', backgroundColor: baseColor, marginBottom: 10, - flexDirection: 'row', paddingLeft: 20 }, containerChild: { @@ -48,95 +54,182 @@ const radioProps = [ {label: 'Continuous', value: 1 } ] +const ShowPriceAlerts = ({alert, goToCreatePriceAlertPage, currencyName, price, currencySymbol, deletePriceAlert, switchAlert}) => ( + + + {currencyName} Alerts + + { + return ( + deletePriceAlert(item.id)}> + + { + (item.type == 0)? + + : + + } + + + {item.tsym} {item.price} + + { + (item.type == 0)? + Greater Than + : + Less Than + } + + { + (item.frequency == 0)? + Once + : + Persistent + } + + + + {switchAlert(item)}} /> + + + ) + }} + data={alert} + keyExtractor={(item) => item.id} + /> + + + + + +) + +const ShowCreateAlerts = ({goToCreatePriceAlertPage, currencyName, price, currencySymbol}) => ( + + + You haven't created any Bitcoin price alerts + Create alerts to let you know when the price changes + + + + +) + class PriceAlert extends Component { state = { - radioValue: 0 + radioValue: 0, + gt: 0, + lt: 0, + frequency: 0, + alerts: [], } - render() { - const { tokenDetails } = this.props - return ( - - - THRESHOLD - + componentWillMount = async() => { + const { getPriceAlert, alert } = this.props; + getPriceAlert(); + } - - Above - this.setState({text})} - keyboardType='numeric' - placeholder={'Enter numeric value'} - placeholderTextColor='#444' - /> - + switchAlert = async(item) => { + const { enablePriceAlert,disablePriceAlert } = this.props; + if(item.status){ + disablePriceAlert(item.id) + return + } + enablePriceAlert(item.id) + } - - Current - this.setState({text})} - keyboardType='numeric' - placeholder={''} - placeholderTextColor='#444' - /> - + deletePriceAlert = (alertId) => { + const { deletePriceAlert } = this.props; + Alert.alert( + 'Delete Alert', + 'Will you like to delete this Price Alert ?', + [ + {text: 'Cancel', onPress: () => {}}, + {text: 'OK', onPress: () => { + deletePriceAlert(alertId) + }}, + ], + { cancelable: true } + ) + } - - Below - this.setState({text})} - keyboardType='numeric' - placeholder={'Enter Numeric value'} - placeholderTextColor='#444' - /> - + componentWillReceiveProps = (nextProps) => { + console.log('getDerivedStatefromprops') + console.log(nextProps) + const alerts = nextProps.alert.filter((item) => item.fsym == this.props.currencySymbol) + this.setState({alerts}) + } - - FREQUENCY - + render() { + let { goToCreatePriceAlertPage, currencyName, price, currencySymbol } = this.props - - {this.setState({radioValue: value})}} + return ( + + + + { + (this.state.alerts.length == 0 ) ? + - + : + + } + + + ) } } - const mapStateToProps = (state, props) => ({ tokenDetails: state.account.tokenDetails, portfolio: state.account.portfolio, + alert: state.alert, + ...props.navigation.state.params, +}) + +const mapDispatchToProps = (dispatch) => ({ + goToCreatePriceAlertPage: ({price, currencySymbol, currencyName, edit=false}) => dispatch( + NavigationActions.navigate({ routeName: 'Create Price Alert', + params: {price, currencySymbol, currencyName, edit} })), + getPriceAlert: () => dispatch(getPriceAlert()), + disablePriceAlert: (alertId)=>dispatch(disablePriceAlert(alertId)), + enablePriceAlert: (alertId)=>dispatch(enablePriceAlert(alertId)), + deletePriceAlert: (alertId) => dispatch(deletePriceAlert(alertId)), + }) -export default connect(mapStateToProps)(withDrawer(PriceAlert)); \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(withDrawer(PriceAlert)); \ No newline at end of file diff --git a/src/components/TokenDetails/index.js b/src/components/TokenDetails/index.js index 77b9146..82e245a 100644 --- a/src/components/TokenDetails/index.js +++ b/src/components/TokenDetails/index.js @@ -277,7 +277,7 @@ class TokenDetails extends Component { platform: 'ethereum', action: 'recieve', contractAddress: tokenDetails.address, - currencyName:tokenDetails.name, + currencyName: tokenDetails.name, currencySymbol: tokenDetails.symbol, image }, @@ -290,6 +290,19 @@ class TokenDetails extends Component { icon: 'eye', Component: SimpleLineIcons, onPress: () =>{console.log('onPress watch', isWatching); isWatching ? removeFromWatchList(symbol, token) : addToWatchlist(symbol, token) } + }, + { + name: "Price Alert", + icon: 'bell', + params: { + contractAddress: tokenDetails.address, + currencyName: tokenDetails.name, + currencySymbol: tokenDetails.symbol, + price: tokenDetails.price, + image + }, + Component: SimpleLineIcons, + route: "Price Alert" } ] diff --git a/src/config.js b/src/config.js index 34055cd..4f164f8 100644 --- a/src/config.js +++ b/src/config.js @@ -1,7 +1,9 @@ import { ENVIRONMENT } from 'react-native-dotenv'; import { Constants } from 'expo' -const devUrl = Constants.isDevice ? 'https://api.tokens.express' : 'http://dev.local:8888' +// const devUrl = Constants.isDevice ? 'https://api.tokens.express' : 'http://dev.local:8888' +const devUrl = Constants.isDevice ? 'https://api.tokens.express' : 'http://0.0.0.0:3000' + console.log({ENVIRONMENT}) export const baseURL = ENVIRONMENT !== 'development' ? 'https://api.tokens.express' : devUrl diff --git a/src/helpers/api.js b/src/helpers/api.js index 0236c63..67ebb0f 100644 --- a/src/helpers/api.js +++ b/src/helpers/api.js @@ -210,8 +210,30 @@ export const disableTwoFactorAuth = async (id) => { return res.data } +export const setPriceAlert = async({fsym, tsym, price , type, frequency}) => { + const res = await instance.post(`/Alerts`, {fsym, tsym, price , type, frequency}) + return res.data +} + +export const getPriceAlert = async() => { + const res = await instance.get(`/Alerts`) + return res.data +} + +export const enablePriceAlert = async(alertId) => { + const res = await instance.post(`/Alerts/enable`,{alertId}) + return res.data +} +export const disablePriceAlert = async(alertId) => { + const res = await instance.post(`/Alerts/disable`,{alertId}) + return res.data +} +export const deletePriceAlert = async(alertId) => { + const res = await instance.delete(`/Alerts/${alertId}`) + return res.data +} const log = (level) => (message, data) => { console.log({ message, data, level }) diff --git a/src/helpers/drawer.js b/src/helpers/drawer.js index 30b92a2..6bc2b28 100644 --- a/src/helpers/drawer.js +++ b/src/helpers/drawer.js @@ -172,13 +172,13 @@ export const withDrawer = (WrappedComponent) => { 'Token Details', 'Search', 'Price Alert', 'Add Address', 'ICO List', 'ICODetail', 'Education', 'Restore Wallet', 'New Wallet', 'Confirm Phrase', 'SetPin', 'SecuritySettings', 'Select Account', 'SendTransaction', 'Edit Profile', 'NewExchangeAccount', 'NewExchangeOrder', - 'Confirm 2FA', '2FA', 'Set Currency', 'SignUp' + 'Confirm 2FA', '2FA', 'Set Currency', 'SignUp', 'Create Price Alert', ].indexOf(navState.routeName) > -1 const noSearchButton = [ 'Restore Wallet', 'New Wallet', 'Confirm Phrase', 'SignUp', 'Login', 'SecuritySettings', 'Select Account', 'SendTransaction', 'Edit Profile', 'NewExchangeAccount', - 'NewExchangeOrder', 'Confirm 2FA', '2FA' + 'NewExchangeOrder', 'Confirm 2FA', '2FA', 'Price Alert', 'Create Price Alert', ].indexOf(navState.routeName) > -1 // add top padding for iphone X diff --git a/src/navigators/AppNavigator.js b/src/navigators/AppNavigator.js index c9041b3..e33ed23 100644 --- a/src/navigators/AppNavigator.js +++ b/src/navigators/AppNavigator.js @@ -22,6 +22,7 @@ import Register from '../components/Register'; import SignUp from '../components/Register/SignUp'; import Login from '../components/Register/Login'; import PriceAlert from '../components/PriceAlert'; +import CreatePriceAlert from '../components/PriceAlert/create'; import EditProfile from '../components/Profile/EditProfile'; import SetCurrency from '../components/Profile/SetCurrency'; import CardStackStyleInterpolator from 'react-navigation/src/views/CardStack/CardStackStyleInterpolator'; @@ -76,6 +77,7 @@ export const Routes = { 'NewExchangeAccount': { screen: NewExchangeAccount }, 'NewExchangeOrder': { screen: NewExchangeOrder }, 'Price Alert': { screen: PriceAlert }, + 'Create Price Alert': { screen: CreatePriceAlert }, 'Profile': Profile, 'Settings': {screen: Settings}, 'SecuritySettings': {screen: SecuritySettings}, diff --git a/src/reducers/account.js b/src/reducers/account.js index acacb3f..9a8d3d9 100644 --- a/src/reducers/account.js +++ b/src/reducers/account.js @@ -25,7 +25,8 @@ import { removeFromAccountWatchlist, logger, setCurrency, - verifyTwoFactorAuth + verifyTwoFactorAuth, + } from '../helpers/api' import { genericError, @@ -207,7 +208,7 @@ export const login = (params, supressToasts) => async (dispatch, getState) => { } catch(err) { const error = getError(err) if(error && error.statusCode === 401) { - error.message = 'Invalid cedentials'; + error.message = 'Invalid credentials'; } else { dispatch(NavigationActions.navigate({ routeName: 'Register' })) } diff --git a/src/reducers/alert.js b/src/reducers/alert.js new file mode 100644 index 0000000..d6780df --- /dev/null +++ b/src/reducers/alert.js @@ -0,0 +1,142 @@ +import { setLoading, showToast } from './ui' +import { NavigationActions } from 'react-navigation' +import types from '../types/alert'; +import {createReducer} from './common'; + +import { + genericError, + getErrorMsg, + registerForPushNotificationsAsync, + safeAlert, + removeArrItem +} from '../helpers/functions' +import { + getPriceAlert as getPriceAlertApi, + setPriceAlert as setPriceAlertApi, + enablePriceAlert as enablePriceAlertApi, + disablePriceAlert as disablePriceAlertApi, + deletePriceAlert as deletePriceAlertApi, + setAuthHeader, +} from '../helpers/api' +import { SecureStore } from 'expo' +import { fetchPriceAlertAction, disablePriceAlertAction, enablePriceAlertAction, deletePriceAlertAction } from '../actions/alert'; + +const initialState = { + alerts: [] +} + +export const getPriceAlert = () => async(dispatch, getState) => { + let err = null + + dispatch(setLoading(true, 'Fetching Price Alert')) + let token = await SecureStore.getItemAsync('token') + setAuthHeader('GnR32xxVUfaFX0GDYDG9mLYO1ARwncGPuHJRrLge9YSnWZsaDiaFyPCWGkmSxyPx') + const result = await getPriceAlertApi().catch(e=>err=e) + if(err){ + dispatch(showToast(getErrorMsg(err))) + return + } + + dispatch(fetchPriceAlertAction(result)) + dispatch(setLoading(false)) + dispatch(showToast('SuccessFul')) +} + +export const createPriceAlert = (data) => async(dispatch, getState)=> { + let err = null + dispatch(setLoading(true, 'Creating Price Alert')) + let token = await SecureStore.getItemAsync('token') + // setAuthHeader(token) + setAuthHeader('GnR32xxVUfaFX0GDYDG9mLYO1ARwncGPuHJRrLge9YSnWZsaDiaFyPCWGkmSxyPx') + const setResult = await setPriceAlertApi(data).catch(e=>err=e) + if(err){ + dispatch(setLoading(false)) + dispatch(showToast(getErrorMsg(err))) + return + } + const result = await getPriceAlertApi().catch(e=>err=e) + if(err){ + dispatch(showToast(getErrorMsg(err))) + return + } + + dispatch(fetchPriceAlertAction(result)) + dispatch(setLoading(false)) + dispatch(showToast('Price Alert Created Successfully')) + dispatch(NavigationActions.back()) +} + +export const enablePriceAlert = (alertId) => async(dispatch, getState) => { + let err = null + + let alerts = getState().alert; + const index = alerts.findIndex((item)=>item.id === alertId) + alerts[index].status = true + + dispatch(setLoading(true, 'Enabling Price Alert')) + let token = await SecureStore.getItemAsync('token') + setAuthHeader('GnR32xxVUfaFX0GDYDG9mLYO1ARwncGPuHJRrLge9YSnWZsaDiaFyPCWGkmSxyPx') + + const result = await enablePriceAlertApi(alertId).catch(e=>err=e) + if(err){ + dispatch(setLoading(false)) + dispatch(showToast(getErrorMsg(err))) + return + } + dispatch(setLoading(false)) + dispatch(enablePriceAlertAction(alerts)) +} + +export const disablePriceAlert = (alertId) => async(dispatch, getState) => { + let err = null + + dispatch(setLoading(true, 'Disabling Price Alert')) + + let alerts = getState().alert; + const index = alerts.findIndex((item)=>item.id === alertId) + alerts[index].status = false + + let token = await SecureStore.getItemAsync('token') + setAuthHeader('GnR32xxVUfaFX0GDYDG9mLYO1ARwncGPuHJRrLge9YSnWZsaDiaFyPCWGkmSxyPx') + + const result = await disablePriceAlertApi(alertId).catch(e=>err=e) + if(err){ + dispatch(setLoading(false)) + dispatch(showToast(getErrorMsg(err))) + return + } + dispatch(setLoading(false)) + dispatch(disablePriceAlertAction(alerts)) +} + +export const deletePriceAlert = (alertId) => async(dispatch, getState) => { + let err = null + + dispatch(setLoading(true, 'Deleting Price Alert')) + + let alerts = getState().alert; + const index = alerts.findIndex((item)=>item.id === alertId) + alerts = alerts.splice(index, 1) + + let token = await SecureStore.getItemAsync('token') + setAuthHeader('GnR32xxVUfaFX0GDYDG9mLYO1ARwncGPuHJRrLge9YSnWZsaDiaFyPCWGkmSxyPx') + + const result = await deletePriceAlertApi(alertId).catch(e=>err=e) + if(err){ + dispatch(setLoading(false)) + dispatch(showToast(getErrorMsg(err))) + return + } + dispatch(setLoading(false)) + dispatch(deletePriceAlertAction(alerts)) + +} + +export const actionHandlers = { + [types.FETCH_PRICE_ALERT]: (state, data) => { return [ ...data ] }, + [types.ENABLE_PRICE_ALERT]: (state, data) => {return [...data ] }, + [types.DISABLE_PRICE_ALERT]: (state, data) => { return [...data]}, + [types.DELETE_PRICE_ALERT]: (state, data) => { return [...data]}, +}; + +export default createReducer(initialState, actionHandlers); diff --git a/src/reducers/index.js b/src/reducers/index.js index b450bde..1a35a94 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -9,6 +9,7 @@ import ticker from './ticker' import security from './security' import blockchains from './blockchains' import exchanges from './exchanges' +import alert from './alert' import { AppNavigator } from '../navigators/AppNavigator'; @@ -58,7 +59,8 @@ const AppReducer = combineReducers({ ticker, security, blockchains, - exchanges + exchanges, + alert, }); export default AppReducer; diff --git a/src/types/alert.js b/src/types/alert.js new file mode 100644 index 0000000..6527735 --- /dev/null +++ b/src/types/alert.js @@ -0,0 +1,6 @@ +export default { + ENABLE_PRICE_ALERT: 'ENABLE_PRICE_ALERT', + DISABLE_PRICE_ALERT: 'DISABLE_PRICE_ALERT', + DELETE_PRICE_ALERT: 'DELETE_PRICE_ALERT', + FETCH_PRICE_ALERT: 'FETCH_PRICE_ALERT', +} \ No newline at end of file