diff --git a/package-lock.json b/package-lock.json index de4aa37..98d19a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7076,8 +7076,8 @@ } }, "lbryinc": { - "version": "github:lbryio/lbryinc#053ca52f4f7f9bf8eb62a3581b183671a475ad1c", - "from": "github:lbryio/lbryinc#053ca52f4f7f9bf8eb62a3581b183671a475ad1c", + "version": "github:lbryio/lbryinc#138a053754ec8e3da8e9bf153d32f527c962f25c", + "from": "github:lbryio/lbryinc#138a053754ec8e3da8e9bf153d32f527c962f25c", "requires": { "reselect": "^3.0.0" } diff --git a/package.json b/package.json index 2192486..02b1403 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@expo/vector-icons": "^8.1.0", "gfycat-style-urls": "^1.0.3", "lbry-redux": "lbryio/lbry-redux#c910cd2b80b165843a81fdf6ce96094429b94ec8", - "lbryinc": "lbryio/lbryinc#053ca52f4f7f9bf8eb62a3581b183671a475ad1c", + "lbryinc": "lbryio/lbryinc#138a053754ec8e3da8e9bf153d32f527c962f25c", "lodash": ">=4.17.11", "merge": ">=1.2.1", "moment": "^2.22.1", diff --git a/src/component/AppNavigator.js b/src/component/AppNavigator.js index b6f64b9..db507e5 100644 --- a/src/component/AppNavigator.js +++ b/src/component/AppNavigator.js @@ -6,6 +6,7 @@ import DownloadsPage from 'page/downloads'; import DrawerContent from 'component/drawerContent'; import FilePage from 'page/file'; import FirstRunScreen from 'page/firstRun'; +import InvitesPage from 'page/invites'; import PublishPage from 'page/publish'; import PublishesPage from 'page/publishes'; import RewardsPage from 'page/rewards'; @@ -210,6 +211,12 @@ const drawer = createDrawerNavigator( drawerIcon: ({ tintColor }) => , }, }, + Invites: { + screen: InvitesPage, + navigationOptions: { + drawerIcon: ({ tintColor }) => , + }, + }, Downloads: { screen: DownloadsPage, navigationOptions: { @@ -530,7 +537,7 @@ class AppWithNavigationState extends React.Component { try { verification = JSON.parse(atob(evt.url.substring(15))); } catch (error) { - console.log(error); + // console.log(error); } if (verification.token && verification.recaptcha) { diff --git a/src/component/channelSelector/view.js b/src/component/channelSelector/view.js index d0ec6bb..952447d 100644 --- a/src/component/channelSelector/view.js +++ b/src/component/channelSelector/view.js @@ -36,10 +36,14 @@ export default class ChannelSelector extends React.PureComponent { } componentWillReceiveProps(nextProps) { - const { channels: prevChannels = [], channelName } = this.props; - const { channels = [] } = nextProps; + const { channels: prevChannels = [], channelName: prevChannelName } = this.props; + const { channels = [], channelName } = nextProps; if (channels && channels.length !== prevChannels.length && channelName !== this.state.currentSelectedValue) { + this.setState({ currentSelectedValue: prevChannelName }); + } + + if (channelName !== prevChannelName) { this.setState({ currentSelectedValue: channelName }); } } @@ -189,10 +193,11 @@ export default class ChannelSelector extends React.PureComponent { render() { const channel = this.state.addingChannel ? 'new' : this.props.channel; - const { balance, enabled, fetchingChannels, channels = [] } = this.props; - const pickerItems = [Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL].concat( - channels ? channels.map(ch => ch.name) : [], - ); + const { balance, enabled, fetchingChannels, channels = [], showAnonymous } = this.props; + const pickerItems = (showAnonymous + ? [Constants.ITEM_ANONYMOUS, Constants.ITEM_CREATE_A_CHANNEL] + : [Constants.ITEM_CREATE_A_CHANNEL] + ).concat(channels ? channels.map(ch => ch.name) : []); const { newChannelName, diff --git a/src/component/drawerContent/view.js b/src/component/drawerContent/view.js index f6b810a..cdc2815 100644 --- a/src/component/drawerContent/view.js +++ b/src/component/drawerContent/view.js @@ -22,6 +22,7 @@ const groupedMenuItems = { Wallet: [ { icon: 'wallet', label: 'Wallet', route: Constants.DRAWER_ROUTE_WALLET }, { icon: 'award', label: 'Rewards', route: Constants.DRAWER_ROUTE_REWARDS }, + { icon: 'user-friends', label: 'Invites', route: Constants.DRAWER_ROUTE_INVITES }, ], Settings: [ { icon: 'cog', label: 'Settings', route: Constants.DRAWER_ROUTE_SETTINGS }, diff --git a/src/constants.js b/src/constants.js index 20bf1d9..7c508d3 100644 --- a/src/constants.js +++ b/src/constants.js @@ -90,6 +90,7 @@ const Constants = { DRAWER_ROUTE_TAG: 'Tag', DRAWER_ROUTE_CHANNEL_CREATOR: 'ChannelCreator', DRAWER_ROUTE_CHANNEL_CREATOR_FORM: 'ChannnelCreatorForm', + DRAWER_ROUTE_INVITES: 'Invites', FULL_ROUTE_NAME_DISCOVER: 'DiscoverStack', FULL_ROUTE_NAME_WALLET: 'WalletStack', @@ -165,6 +166,7 @@ export const DrawerRoutes = [ Constants.DRAWER_ROUTE_SEARCH, Constants.DRAWER_ROUTE_TRANSACTION_HISTORY, Constants.DRAWER_ROUTE_CHANNEL_CREATOR, + Constants.DRAWER_ROUTE_INVITES, ]; // sub-pages for main routes diff --git a/src/page/invites/index.js b/src/page/invites/index.js new file mode 100644 index 0000000..230107a --- /dev/null +++ b/src/page/invites/index.js @@ -0,0 +1,46 @@ +import { connect } from 'react-redux'; +import { selectMyChannelClaims, selectFetchingMyChannels, doFetchChannelListMine, doToast } from 'lbry-redux'; +import { + selectReferralReward, + selectUserInvitesRemaining, + selectUserInviteNewIsPending, + selectUserInviteNewErrorMessage, + selectUserInviteReferralLink, + selectUserInviteReferralCode, + selectUserInvitees, + selectUserInviteStatusIsPending, + doFetchInviteStatus, + doUserInviteNew, +} from 'lbryinc'; +import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer'; +import { doUpdateChannelFormState, doClearChannelFormState } from 'redux/actions/form'; +import { selectDrawerStack } from 'redux/selectors/drawer'; +import { selectChannelFormState, selectHasChannelFormState } from 'redux/selectors/form'; +import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api +import InvitesPage from './view'; + +const select = state => ({ + channels: selectMyChannelClaims(state), + fetchingChannels: selectFetchingMyChannels(state), + fetchingInvitees: selectUserInviteStatusIsPending(state), + errorMessage: selectUserInviteNewErrorMessage(state), + invitesRemaining: selectUserInvitesRemaining(state), + referralCode: selectUserInviteReferralCode(state), + isPending: selectUserInviteNewIsPending(state), + invitees: selectUserInvitees(state), + referralReward: selectReferralReward(state), +}); + +const perform = dispatch => ({ + fetchChannelListMine: () => dispatch(doFetchChannelListMine()), + fetchInviteStatus: () => dispatch(doFetchInviteStatus()), + inviteNew: email => dispatch(doUserInviteNew(email)), + pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_INVITES)), + setPlayerVisible: () => dispatch(doSetPlayerVisible(false)), + notify: data => dispatch(doToast(data)), +}); + +export default connect( + select, + perform, +)(InvitesPage); diff --git a/src/page/invites/view.js b/src/page/invites/view.js new file mode 100644 index 0000000..efdf761 --- /dev/null +++ b/src/page/invites/view.js @@ -0,0 +1,228 @@ +import React from 'react'; +import { Lbry, parseURI } from 'lbry-redux'; +import { + ActivityIndicator, + Clipboard, + NativeModules, + ScrollView, + Text, + TextInput, + TouchableOpacity, + View, +} from 'react-native'; +import Colors from 'styles/colors'; +import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api +import Icon from 'react-native-vector-icons/FontAwesome5'; +import Link from 'component/link'; +import Button from 'component/button'; +import ChannelSelector from 'component/channelSelector'; +import PageHeader from 'component/pageHeader'; +import RewardCard from 'component/rewardCard'; +import RewardEnrolment from 'component/rewardEnrolment'; +import UriBar from 'component/uriBar'; +import invitesStyle from 'styles/invites'; + +class InvitesPage extends React.PureComponent { + state = { + channelName: null, + email: null, + inviteLink: null, + selectedChannel: null, + }; + + componentWillMount() { + const { navigation } = this.props; + // this.didFocusListener = navigation.addListener('didFocus', this.onComponentFocused); + } + + componentWillUnmount() { + if (this.didFocusListener) { + this.didFocusListener.remove(); + } + } + + onComponentFocused = () => { + const { fetchChannelListMine, fetchInviteStatus, pushDrawerStack, navigation, setPlayerVisible, user } = this.props; + + pushDrawerStack(); + setPlayerVisible(); + NativeModules.Firebase.setCurrentScreen('Invites').then(result => { + fetchChannelListMine(); + fetchInviteStatus(); + }); + }; + + componentDidMount() { + this.onComponentFocused(); + } + + handleChannelChange = channelName => { + const { channels = [] } = this.props; + if (channels && channels.length > 0) { + const filtered = channels.filter(c => c.name === channelName); + if (filtered.length > 0) { + const channel = filtered[0]; + this.setState({ channelName, inviteLink: this.getLinkForChannel(channel) }); + } + } + }; + + getLinkForChannel = channel => { + const { claimId, claimName } = parseURI(channel.permanent_url); + return `https://lbry.tv/$/invite/${claimName}:${claimId}`; + }; + + handleInviteEmailChange = text => { + this.setState({ email: text }); + }; + + handleInvitePress = () => { + const { inviteNew, notify } = this.props; + const { email } = this.state; + if (!email || email.indexOf('@') === -1) { + return notify({ + message: __('Please enter a valid email address to send an invite to.'), + isError: true, + }); + } + + inviteNew(email); + }; + + componentWillReceiveProps(nextProps) { + const { isPending: prevPending, notify } = this.props; + const { channels = [], isPending, errorMessage } = nextProps; + const { email } = this.state; + + if (!this.state.channelName && channels && channels.length > 0) { + const firstChannel = channels[0]; + this.setState({ channelName: firstChannel.name, inviteLink: this.getLinkForChannel(firstChannel) }); + } + + if (prevPending && !isPending) { + if (errorMessage && errorMessage.trim().length > 0) { + notify({ message: errorMessage, isError: true }); + } else { + notify({ message: __(`${email} was invited to the LBRY party!`) }); + this.setState({ email: null }); + } + } + } + + handleInviteLinkPress = () => { + const { notify } = this.props; + Clipboard.setString(this.state.inviteLink); + notify({ + message: __('Invite link copied'), + }); + }; + + render() { + const { fetchingInvitees, user, navigation, notify, isPending, invitees } = this.props; + const { email, inviteLink } = this.state; + const hasInvitees = invitees && invitees.length > 0; + + return ( + + + + + navigation.navigate('Rewards')}> + + {__('Earn rewards for inviting your friends.')} + + + + {__('Invite Link')} + + {__('Share this link with friends (or enemies) and get 20 LBC when they join lbry.tv')} + + + {__('Your invite link')} + + + {this.state.inviteLink} + +