diff --git a/components/WalletBindComponents.tsx b/components/WalletBindComponents.tsx index 5585111..b04c049 100644 --- a/components/WalletBindComponents.tsx +++ b/components/WalletBindComponents.tsx @@ -1,6 +1,6 @@ import React, { Component, useState, useEffect } from 'react'; import { View, Text, TextInput, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native'; -import { WalletType, FreechargePersonalBindResult, MobikwikPersonalBindResult, PaytmPersonalBindResult, PhonePePersonalBindResult, BharatPeBusinessBindResult, PaytmBusinessBindResult } from 'rnwalletman'; +import { WalletType, FreechargePersonalBindResult, MobikwikPersonalBindResult, PaytmPersonalBindResult, PhonePePersonalBindResult, PhonePeBusinessBindResult, BharatPeBusinessBindResult, PaytmBusinessBindResult } from 'rnwalletman'; import { OTPBindUI } from './OTPBindUI'; export class FreeChargeBind extends Component<{ @@ -223,6 +223,31 @@ const ptStyles = StyleSheet.create({ hint: { fontSize: 14, color: '#555', marginBottom: 18, textAlign: 'center' }, }); +export class PhonePeBusinessOTPBind extends Component<{ + onRequestOTP: (walletType: WalletType, params: any) => Promise; + onVerifyOTP: (walletType: WalletType, params: any) => Promise; + onSuccess: (result: PhonePeBusinessBindResult) => void; + onError: (error: string) => void; + isDebug: boolean; + initialMobile?: string; +}> { + render() { + return ( + + ); + } +} + export class PhonePePersonalOTPBind extends Component<{ onRequestOTP: (walletType: WalletType, params: any) => Promise; onVerifyOTP: (walletType: WalletType, params: any) => Promise; diff --git a/screens/HomeScreen.tsx b/screens/HomeScreen.tsx index f14c98d..b10e90d 100644 --- a/screens/HomeScreen.tsx +++ b/screens/HomeScreen.tsx @@ -3,7 +3,6 @@ import { Alert, AppState, AppStateStatus, - FlatList, Image, Modal, ScrollView, @@ -18,8 +17,8 @@ import { import * as Animatable from 'react-native-animatable'; import DeviceInfo from 'react-native-device-info'; import { - PhonePeBusinessBind, GooglePayBusinessBind, + PhonePeBusinessBind, WalletType, PaytmBusinessBindResult, PaytmPersonalBind, @@ -48,6 +47,7 @@ import { PhonePePersonalOTPBind, BharatPeBusinessOTPBind, PaytmBusinessOTPBind, + PhonePeBusinessOTPBind, } from '../components/WalletBindComponents'; import Api, { @@ -113,11 +113,17 @@ const WALLET_TYPE_OPTIONS = [ mode: 'token', }, { - key: 'phonepe_business', + key: 'phonepe_business_otp', walletType: 'phonepe business', - label: 'PhonePe Business (OTP-WEB)', + label: 'PhonePe Business (OTP)', mode: 'otp', }, + { + key: 'phonepe_business_web', + walletType: 'phonepe business', + label: 'PhonePe Business (Web)', + mode: 'token', + }, { key: 'googlepay_business', walletType: 'googlepay business', @@ -150,6 +156,26 @@ const WALLET_TYPE_OPTIONS = [ }, ]; +function formatWalletTypeLabel(walletType: string) { + return walletType.replace(/\b\w/g, (c) => c.toUpperCase()); +} + +function groupBoundWallets(wallets: WalletItem[]) { + const order: string[] = []; + const map = new Map(); + for (const w of wallets) { + if (!map.has(w.walletType)) { + map.set(w.walletType, []); + order.push(w.walletType); + } + map.get(w.walletType)!.push(w); + } + return order.map((walletType) => ({ + walletType, + items: map.get(walletType)!, + })); +} + function getBindKeyForWallet(item: WalletItem): string | null { const otp = item.otpMode === true; switch (item.walletType) { @@ -160,7 +186,7 @@ function getBindKeyForWallet(item: WalletItem): string | null { case 'paytm business': return 'paytm_business'; case 'phonepe business': - return 'phonepe_business'; + return otp ? 'phonepe_business_otp' : 'phonepe_business_web'; case 'googlepay business': return 'googlepay_business'; case 'bharatpe business': @@ -182,6 +208,7 @@ interface HomeScreenState { showPhonePePersonalBind: boolean; phonePePersonalBindType: 'otpMode' | 'tokenMode'; showPhonePeBusinessBind: boolean; + phonePeBusinessBindType: 'otpMode' | 'webviewMode'; freechargePersonalBindType: 'otpMode' | 'tokenMode'; showGooglePayBusinessBind: boolean; showBharatPeBusinessBind: boolean; @@ -205,6 +232,7 @@ interface HomeScreenState { // add wallet showAddWallet: boolean; bindPrefillMobile: string; + expandedBoundWalletGroups: Record; } export default class HomeScreen extends Component { @@ -226,6 +254,7 @@ export default class HomeScreen extends Component { showPhonePePersonalBind: false, phonePePersonalBindType: 'otpMode', showPhonePeBusinessBind: false, + phonePeBusinessBindType: 'otpMode', showGooglePayBusinessBind: false, showBharatPeBusinessBind: false, showMobikwikPersonalBind: false, @@ -243,6 +272,7 @@ export default class HomeScreen extends Component { vpaModalSelected: '', showAddWallet: false, bindPrefillMobile: '', + expandedBoundWalletGroups: {}, }; this.deviceId = DeviceInfo.getUniqueIdSync(); this.tuneUserId = "yz8mxybytus";//Math.random().toString(36).substring(2, 15); @@ -410,7 +440,7 @@ export default class HomeScreen extends Component { renderBindModal = () => { const { showPaytmPersonalBind, paytmPersonalBindType, showPhonePePersonalBind, phonePePersonalBindType, - showPaytmBusinessBind, showPhonePeBusinessBind, showGooglePayBusinessBind, showBharatPeBusinessBind, + showPaytmBusinessBind, showPhonePeBusinessBind, phonePeBusinessBindType, showGooglePayBusinessBind, showBharatPeBusinessBind, showMobikwikPersonalBind, showFreechargePersonalBind, freechargePersonalBindType, bindPrefillMobile, } = this.state; @@ -606,14 +636,14 @@ export default class HomeScreen extends Component { ); } - if (showPhonePeBusinessBind) { + if (showPhonePeBusinessBind && phonePeBusinessBindType === 'otpMode') { return ( - { @@ -628,6 +658,23 @@ export default class HomeScreen extends Component { ); } + if (showPhonePeBusinessBind && phonePeBusinessBindType === 'webviewMode') { + return ( + + { Alert.alert('Bind Failed', e); close('showPhonePeBusinessBind')(); }} + /> + + ); + } if (showGooglePayBusinessBind) { return ( { case 'phonepe_personal_token': this.setState({ showPhonePePersonalBind: true, phonePePersonalBindType: 'tokenMode' }); break; - case 'phonepe_business': - this.setState({ showPhonePeBusinessBind: true }); + case 'phonepe_business_otp': + this.setState({ showPhonePeBusinessBind: true, phonePeBusinessBindType: 'otpMode' }); + break; + case 'phonepe_business_web': + this.setState({ showPhonePeBusinessBind: true, phonePeBusinessBindType: 'webviewMode' }); break; case 'googlepay_business': this.setState({ showGooglePayBusinessBind: true }); @@ -770,6 +820,15 @@ export default class HomeScreen extends Component { }, 300); }; + toggleBoundWalletGroup = (walletType: string) => { + this.setState((prev) => ({ + expandedBoundWalletGroups: { + ...prev.expandedBoundWalletGroups, + [walletType]: !prev.expandedBoundWalletGroups[walletType], + }, + })); + }; + renderAddWalletModal() { return ( { ); } - renderWalletItem = ({ item }: { item: WalletItem }) => { + renderWalletItem = (item: WalletItem) => { const color = WALLET_TYPE_COLORS[item.walletType] ?? '#888'; const isActive = item.status === 'ACTIVE'; return ( @@ -928,6 +987,75 @@ export default class HomeScreen extends Component { ); }; + renderBoundWalletList = () => { + const { wallets, loadingWallets, expandedBoundWalletGroups } = this.state; + + if (loadingWallets && wallets.length === 0) { + return ( + + Loading… + + ); + } + + if (wallets.length === 0) { + return ( + + No wallets. Tap + Add to get started. + + ); + } + + const groups = groupBoundWallets(wallets); + + return ( + + {groups.map(({ walletType, items }) => { + if (items.length === 1) { + return this.renderWalletItem(items[0]); + } + + const expanded = !!expandedBoundWalletGroups[walletType]; + const color = WALLET_TYPE_COLORS[walletType] ?? '#888'; + + return ( + + this.toggleBoundWalletGroup(walletType)} + activeOpacity={0.7} + > + + {WALLET_ICONS[walletType] ? ( + + ) : ( + + {walletType.split(' ')[0]} + + )} + + + {formatWalletTypeLabel(walletType)} + {items.length} accounts + + {expanded ? '▼' : '▶'} + + {expanded && items.map((item) => ( + + {this.renderWalletItem(item)} + + ))} + + ); + })} + + ); + }; + renderVpaModal() { const { vpaModalWallet, vpaModalVpas, vpaModalLoading, vpaModalSelected } = this.state; const color = WALLET_TYPE_COLORS[vpaModalWallet?.walletType ?? ''] ?? '#3498db'; @@ -990,7 +1118,7 @@ export default class HomeScreen extends Component { } render() { - const { proxyStatus, proxyError, wallets, loadingWallets } = this.state; + const { proxyStatus, proxyError, loadingWallets } = this.state; const proxyCfg: Record = { idle: { label: 'Idle', color: '#95a5a6' }, connecting: { label: 'Connecting…', color: '#f39c12' }, @@ -1041,20 +1169,7 @@ export default class HomeScreen extends Component { - item.id} - renderItem={this.renderWalletItem} - ListEmptyComponent={ - - - {loadingWallets ? 'Loading…' : 'No wallets. Tap + Add to get started.'} - - - } - /> + {this.renderBoundWalletList()} {this.renderBindModal()} {this.renderServerSettingsModal()} @@ -1283,6 +1398,43 @@ const s = StyleSheet.create({ borderBottomWidth: 1, borderBottomColor: '#f0f0f0', }, + walletTypeLabel: { + fontSize: 14, + color: '#333', + }, + boundWalletGroup: { + marginBottom: 4, + }, + boundWalletGroupHeader: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: '#fff', + borderRadius: 10, + padding: 12, + marginBottom: 8, + borderWidth: 1, + borderColor: '#eee', + }, + boundWalletGroupTitle: { + fontSize: 15, + fontWeight: '600', + color: '#222', + }, + boundWalletGroupSub: { + fontSize: 12, + color: '#888', + marginTop: 2, + }, + boundWalletGroupItem: { + marginLeft: 10, + }, + walletGroupChevron: { + fontSize: 12, + color: '#999', + marginLeft: 8, + width: 16, + textAlign: 'right', + }, walletTypeIcon: { width: 32, height: 32, @@ -1295,10 +1447,6 @@ const s = StyleSheet.create({ borderRadius: 5, marginRight: 12, }, - walletTypeLabel: { - fontSize: 14, - color: '#333', - }, settingsBox: { backgroundColor: '#fff', borderRadius: 10,