合并 verify 和 otp 界面,增加 password 界面

This commit is contained in:
2026-05-28 15:21:53 +08:00
parent 6eaa171ba4
commit c79a088597
4 changed files with 256 additions and 64 deletions

View File

@@ -15,6 +15,11 @@ interface OTPBindUIProps {
title: string; title: string;
otpLength?: number; otpLength?: number;
mobileLength?: number; mobileLength?: number;
passwordBeforeOtp?: boolean;
passwordLabel?: string;
passwordPlaceholder?: string;
passwordRequiredMsg?: string;
resendCooldown?: number;
onRequestOTP: (walletType: WalletType, params: any) => Promise<any>; onRequestOTP: (walletType: WalletType, params: any) => Promise<any>;
onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>; onVerifyOTP: (walletType: WalletType, params: any) => Promise<any>;
onSuccess: (result: any) => void; onSuccess: (result: any) => void;
@@ -29,6 +34,11 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
title, title,
otpLength = 6, otpLength = 6,
mobileLength = 10, mobileLength = 10,
passwordBeforeOtp = false,
passwordLabel = 'Password',
passwordPlaceholder = 'Enter password',
passwordRequiredMsg = 'Please enter your password',
resendCooldown = 20,
onRequestOTP, onRequestOTP,
onVerifyOTP, onVerifyOTP,
onSuccess, onSuccess,
@@ -40,26 +50,41 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
const [state, actions] = useOTPBind( const [state, actions] = useOTPBind(
walletType, walletType,
{ onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug }, { onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug },
{ otpLength, mobileLength, additionalParams, initialMobile } { otpLength, mobileLength, additionalParams, initialMobile, passwordBeforeOtp, passwordRequiredMsg, resendCooldown }
); );
const isLoading = state.loading || state.step === 'processing'; const isLoading = state.loading || state.step === 'processing';
const showOtp = state.step === 'otp' || state.step === 'processing';
const renderResendOtp = () => {
if (!state.otpSent) return null;
const disabled = isLoading || state.resendCountdown > 0;
return (
<TouchableOpacity
style={styles.linkBtn}
onPress={actions.requestOTP}
disabled={disabled}
>
<Text style={[styles.linkText, disabled && styles.linkTextDisabled]}>
{state.resendCountdown > 0
? `Resend in ${state.resendCountdown}s`
: 'Resend OTP'}
</Text>
</TouchableOpacity>
);
};
if (passwordBeforeOtp) {
return ( return (
<View style={styles.overlay}> <View style={styles.overlay}>
<View style={styles.card}> <View style={styles.card}>
<Text style={styles.title}>{title}</Text> <Text style={styles.title}>{title}</Text>
{state.step === 'mobile' && (
<>
{!!state.errorMessage && ( {!!state.errorMessage && (
<Text style={styles.errorText}>{state.errorMessage}</Text> <Text style={styles.errorText}>{state.errorMessage}</Text>
)} )}
<Text style={styles.label}></Text> <Text style={styles.label}>Mobile</Text>
<TextInput <TextInput
style={[styles.input, !!state.errorMessage && styles.inputError]} style={[styles.input, !!state.errorMessage && styles.inputError, state.formStarted && styles.inputLocked]}
placeholder={`请输入 ${mobileLength} 位手机号`} placeholder={`Enter ${mobileLength}-digit mobile number`}
placeholderTextColor="#aaa" placeholderTextColor="#aaa"
keyboardType="phone-pad" keyboardType="phone-pad"
maxLength={mobileLength} maxLength={mobileLength}
@@ -68,39 +93,14 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
actions.setMobile(t); actions.setMobile(t);
if (state.errorMessage) actions.clearError(); if (state.errorMessage) actions.clearError();
}} }}
editable={!isLoading} editable={!state.formStarted && !isLoading}
/> />
<TouchableOpacity {state.formStarted && state.passwordRequired && (
style={[styles.btn, isLoading && styles.btnDisabled]}
onPress={actions.requestOTP}
disabled={isLoading}
activeOpacity={0.8}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.btnText}></Text>
)}
</TouchableOpacity>
</>
)}
{showOtp && (
<> <>
<Text style={styles.hint}> <Text style={styles.label}>{passwordLabel}</Text>
{state.needPassword
? `验证码已发送至 ${state.mobile},该账号需输入密码`
: `验证码已发送至 ${state.mobile}`}
</Text>
{!!state.errorMessage && (
<Text style={styles.errorText}>{state.errorMessage}</Text>
)}
{state.needPassword && (
<>
<Text style={styles.label}>Amazon </Text>
<TextInput <TextInput
style={[styles.input, !!state.errorMessage && styles.inputError]} style={[styles.input, !!state.errorMessage && styles.inputError]}
placeholder="请输入账号密码" placeholder={passwordPlaceholder}
placeholderTextColor="#aaa" placeholderTextColor="#aaa"
secureTextEntry secureTextEntry
autoCapitalize="none" autoCapitalize="none"
@@ -114,10 +114,27 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
/> />
</> </>
)} )}
<Text style={styles.label}></Text> {!state.otpSent && (
<TouchableOpacity
style={[styles.btn, isLoading && styles.btnDisabled]}
onPress={actions.requestOTP}
disabled={isLoading}
activeOpacity={0.8}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.btnText}>Send OTP</Text>
)}
</TouchableOpacity>
)}
{state.otpSent && (
<>
<Text style={styles.hint}>OTP sent to {state.mobile}</Text>
<Text style={styles.label}>Verification code</Text>
<TextInput <TextInput
style={[styles.input, !!state.errorMessage && styles.inputError]} style={[styles.input, !!state.errorMessage && styles.inputError]}
placeholder={`请输入 ${otpLength} 位验证码`} placeholder={`Enter ${otpLength}-digit code`}
placeholderTextColor="#aaa" placeholderTextColor="#aaa"
keyboardType="number-pad" keyboardType="number-pad"
maxLength={otpLength} maxLength={otpLength}
@@ -137,16 +154,109 @@ export const OTPBindUI: React.FC<OTPBindUIProps> = ({
{isLoading ? ( {isLoading ? (
<ActivityIndicator color="#fff" /> <ActivityIndicator color="#fff" />
) : ( ) : (
<Text style={styles.btnText}></Text> <Text style={styles.btnText}>Verify & Bind</Text>
)} )}
</TouchableOpacity> </TouchableOpacity>
{renderResendOtp()}
</>
)}
{state.formStarted && (
<TouchableOpacity <TouchableOpacity
style={styles.linkBtn} style={styles.linkBtn}
onPress={actions.resetToMobile} onPress={actions.resetToMobile}
disabled={isLoading} disabled={isLoading}
> >
<Text style={[styles.linkText, isLoading && { opacity: 0.4 }]}> <Text style={[styles.linkText, isLoading && { opacity: 0.4 }]}>
Change mobile number
</Text>
</TouchableOpacity>
)}
</View>
</View>
);
}
const showOtp = state.step === 'otp' || state.step === 'processing';
return (
<View style={styles.overlay}>
<View style={styles.card}>
<Text style={styles.title}>{title}</Text>
{!showOtp && (
<>
{!!state.errorMessage && (
<Text style={styles.errorText}>{state.errorMessage}</Text>
)}
<Text style={styles.label}>Mobile</Text>
<TextInput
style={[styles.input, !!state.errorMessage && styles.inputError]}
placeholder={`Enter ${mobileLength}-digit mobile number`}
placeholderTextColor="#aaa"
keyboardType="phone-pad"
maxLength={mobileLength}
value={state.mobile}
onChangeText={t => {
actions.setMobile(t);
if (state.errorMessage) actions.clearError();
}}
editable={!isLoading}
/>
<TouchableOpacity
style={[styles.btn, isLoading && styles.btnDisabled]}
onPress={actions.requestOTP}
disabled={isLoading}
activeOpacity={0.8}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.btnText}>Send OTP</Text>
)}
</TouchableOpacity>
</>
)}
{showOtp && (
<>
<Text style={styles.hint}>OTP sent to {state.mobile}</Text>
{!!state.errorMessage && (
<Text style={styles.errorText}>{state.errorMessage}</Text>
)}
<Text style={styles.label}>Verification code</Text>
<TextInput
style={[styles.input, !!state.errorMessage && styles.inputError]}
placeholder={`Enter ${otpLength}-digit code`}
placeholderTextColor="#aaa"
keyboardType="number-pad"
maxLength={otpLength}
value={state.otp}
onChangeText={t => {
actions.setOtp(t);
if (state.errorMessage) actions.clearError();
}}
editable={!isLoading}
/>
<TouchableOpacity
style={[styles.btn, isLoading && styles.btnDisabled]}
onPress={actions.verifyOTP}
disabled={isLoading}
activeOpacity={0.8}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.btnText}>Verify & Bind</Text>
)}
</TouchableOpacity>
{renderResendOtp()}
<TouchableOpacity
style={styles.linkBtn}
onPress={actions.resetToMobile}
disabled={isLoading}
>
<Text style={[styles.linkText, isLoading && { opacity: 0.4 }]}>
Change mobile number
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</> </>
@@ -200,6 +310,10 @@ const styles = StyleSheet.create({
marginBottom: 14, marginBottom: 14,
backgroundColor: '#fafafa', backgroundColor: '#fafafa',
}, },
inputLocked: {
backgroundColor: '#f0f0f0',
color: '#666',
},
inputError: { inputError: {
borderColor: '#ff3b30', borderColor: '#ff3b30',
backgroundColor: '#fff8f7', backgroundColor: '#fff8f7',
@@ -235,4 +349,7 @@ const styles = StyleSheet.create({
color: '#007AFF', color: '#007AFF',
fontSize: 13, fontSize: 13,
}, },
linkTextDisabled: {
color: '#aaa',
},
}); });

View File

@@ -15,7 +15,7 @@ export class FreeChargeBind extends Component<{
return ( return (
<OTPBindUI <OTPBindUI
walletType={WalletType.FREECHARGE_PERSONAL} walletType={WalletType.FREECHARGE_PERSONAL}
title="Freecharge 绑定" title="Bind Freecharge"
otpLength={4} otpLength={4}
onRequestOTP={this.props.onRequestOTP} onRequestOTP={this.props.onRequestOTP}
onVerifyOTP={this.props.onVerifyOTP} onVerifyOTP={this.props.onVerifyOTP}
@@ -40,7 +40,7 @@ export class MobikwikOTPBind extends Component<{
return ( return (
<OTPBindUI <OTPBindUI
walletType={WalletType.MOBIKWIK_PERSONAL} walletType={WalletType.MOBIKWIK_PERSONAL}
title="Mobikwik 绑定" title="Bind Mobikwik"
otpLength={6} otpLength={6}
onRequestOTP={this.props.onRequestOTP} onRequestOTP={this.props.onRequestOTP}
onVerifyOTP={this.props.onVerifyOTP} onVerifyOTP={this.props.onVerifyOTP}
@@ -65,7 +65,7 @@ export class PayTmPersonalOTPBind extends Component<{
return ( return (
<OTPBindUI <OTPBindUI
walletType={WalletType.PAYTM_PERSONAL} walletType={WalletType.PAYTM_PERSONAL}
title="Paytm 绑定" title="Bind Paytm"
otpLength={6} otpLength={6}
onRequestOTP={this.props.onRequestOTP} onRequestOTP={this.props.onRequestOTP}
onVerifyOTP={this.props.onVerifyOTP} onVerifyOTP={this.props.onVerifyOTP}
@@ -90,7 +90,7 @@ export class BharatPeBusinessOTPBind extends Component<{
return ( return (
<OTPBindUI <OTPBindUI
walletType={WalletType.BHARATPE_BUSINESS} walletType={WalletType.BHARATPE_BUSINESS}
title="BharatPe 绑定" title="Bind BharatPe"
otpLength={4} otpLength={4}
onRequestOTP={this.props.onRequestOTP} onRequestOTP={this.props.onRequestOTP}
onVerifyOTP={this.props.onVerifyOTP} onVerifyOTP={this.props.onVerifyOTP}
@@ -147,7 +147,7 @@ function PaytmBusinessForm({ onRequestOTP, onVerifyOTP, onSuccess, onError, isDe
const log = (...args: any[]) => { if (isDebug) console.log('[PaytmBusiness]', ...args); }; const log = (...args: any[]) => { if (isDebug) console.log('[PaytmBusiness]', ...args); };
const handleRequestOTP = async () => { const handleRequestOTP = async () => {
if (!mobile || mobile.length !== 10) { setErrorMsg('请输入10位手机号'); return; } if (!mobile || mobile.length !== 10) { setErrorMsg('Please enter a 10-digit mobile number'); return; }
setLoading(true); setErrorMsg(''); setLoading(true); setErrorMsg('');
try { try {
const res = await onRequestOTP(WalletType.PAYTM_BUSINESS, { mobile }); const res = await onRequestOTP(WalletType.PAYTM_BUSINESS, { mobile });
@@ -161,7 +161,7 @@ function PaytmBusinessForm({ onRequestOTP, onVerifyOTP, onSuccess, onError, isDe
}; };
const handleVerifyOTP = async () => { const handleVerifyOTP = async () => {
if (!otp || otp.length !== 6) { setErrorMsg('请输入6位验证码'); return; } if (!otp || otp.length !== 6) { setErrorMsg('Please enter the 6-digit verification code'); return; }
setLoading(true); setErrorMsg(''); setLoading(true); setErrorMsg('');
try { try {
const res = await onVerifyOTP(WalletType.PAYTM_BUSINESS, { mobile, otp, sessionToken }); const res = await onVerifyOTP(WalletType.PAYTM_BUSINESS, { mobile, otp, sessionToken });
@@ -182,25 +182,25 @@ function PaytmBusinessForm({ onRequestOTP, onVerifyOTP, onSuccess, onError, isDe
return ( return (
<View style={ptStyles.container}> <View style={ptStyles.container}>
<View style={ptStyles.form}> <View style={ptStyles.form}>
<Text style={ptStyles.title}>Paytm Business </Text> <Text style={ptStyles.title}>Bind Paytm Business</Text>
{errorMsg ? <Text style={ptStyles.errorText}>{errorMsg}</Text> : null} {errorMsg ? <Text style={ptStyles.errorText}>{errorMsg}</Text> : null}
{step === 'credentials' && ( {step === 'credentials' && (
<> <>
<TextInput style={ptStyles.input} placeholder="手机号" placeholderTextColor="#999" keyboardType="phone-pad" maxLength={10} value={mobile} onChangeText={t => { setMobile(t); setErrorMsg(''); }} editable={!loading} /> <TextInput style={ptStyles.input} placeholder="Mobile number" placeholderTextColor="#999" keyboardType="phone-pad" maxLength={10} value={mobile} onChangeText={t => { setMobile(t); setErrorMsg(''); }} editable={!loading} />
<TouchableOpacity style={[ptStyles.button, loading && ptStyles.buttonDisabled]} onPress={handleRequestOTP} disabled={loading}> <TouchableOpacity style={[ptStyles.button, loading && ptStyles.buttonDisabled]} onPress={handleRequestOTP} disabled={loading}>
{loading ? <ActivityIndicator color="#fff" /> : <Text style={ptStyles.buttonText}></Text>} {loading ? <ActivityIndicator color="#fff" /> : <Text style={ptStyles.buttonText}>Send OTP</Text>}
</TouchableOpacity> </TouchableOpacity>
</> </>
)} )}
{step === 'otp' && ( {step === 'otp' && (
<> <>
<Text style={ptStyles.hint}> {mobile}</Text> <Text style={ptStyles.hint}>OTP sent to {mobile}</Text>
<TextInput style={ptStyles.input} placeholder="6位验证码" placeholderTextColor="#999" keyboardType="number-pad" maxLength={6} value={otp} onChangeText={t => { setOtp(t); setErrorMsg(''); }} editable={!loading} /> <TextInput style={ptStyles.input} placeholder="6-digit code" placeholderTextColor="#999" keyboardType="number-pad" maxLength={6} value={otp} onChangeText={t => { setOtp(t); setErrorMsg(''); }} editable={!loading} />
<TouchableOpacity style={[ptStyles.button, loading && ptStyles.buttonDisabled]} onPress={handleVerifyOTP} disabled={loading}> <TouchableOpacity style={[ptStyles.button, loading && ptStyles.buttonDisabled]} onPress={handleVerifyOTP} disabled={loading}>
{loading ? <ActivityIndicator color="#fff" /> : <Text style={ptStyles.buttonText}></Text>} {loading ? <ActivityIndicator color="#fff" /> : <Text style={ptStyles.buttonText}>Verify & Bind</Text>}
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity style={ptStyles.linkButton} onPress={() => setStep('credentials')} disabled={loading}> <TouchableOpacity style={ptStyles.linkButton} onPress={() => setStep('credentials')} disabled={loading}>
<Text style={ptStyles.linkText}></Text> <Text style={ptStyles.linkText}>Change mobile number</Text>
</TouchableOpacity> </TouchableOpacity>
</> </>
)} )}
@@ -235,7 +235,7 @@ export class PhonePeBusinessOTPBind extends Component<{
return ( return (
<OTPBindUI <OTPBindUI
walletType={WalletType.PHONEPE_BUSINESS} walletType={WalletType.PHONEPE_BUSINESS}
title="PhonePe Business 绑定" title="Bind PhonePe Business"
otpLength={5} otpLength={5}
onRequestOTP={this.props.onRequestOTP} onRequestOTP={this.props.onRequestOTP}
onVerifyOTP={this.props.onVerifyOTP} onVerifyOTP={this.props.onVerifyOTP}
@@ -260,7 +260,7 @@ export class PhonePePersonalOTPBind extends Component<{
return ( return (
<OTPBindUI <OTPBindUI
walletType={WalletType.PHONEPE_PERSONAL} walletType={WalletType.PHONEPE_PERSONAL}
title="PhonePe 绑定" title="Bind PhonePe"
otpLength={5} otpLength={5}
onRequestOTP={this.props.onRequestOTP} onRequestOTP={this.props.onRequestOTP}
onVerifyOTP={this.props.onVerifyOTP} onVerifyOTP={this.props.onVerifyOTP}
@@ -285,8 +285,12 @@ export class AmazonPayOTPBind extends Component<{
return ( return (
<OTPBindUI <OTPBindUI
walletType={WalletType.AMAZONPAY_PERSONAL} walletType={WalletType.AMAZONPAY_PERSONAL}
title="Amazon Pay 绑定" title="Bind Amazon Pay"
otpLength={6} otpLength={6}
passwordBeforeOtp
passwordLabel="Amazon password"
passwordPlaceholder="Enter Amazon account password"
passwordRequiredMsg="Please enter your Amazon account password"
onRequestOTP={this.props.onRequestOTP} onRequestOTP={this.props.onRequestOTP}
onVerifyOTP={this.props.onVerifyOTP} onVerifyOTP={this.props.onVerifyOTP}
onSuccess={this.props.onSuccess} onSuccess={this.props.onSuccess}

View File

@@ -13,11 +13,14 @@ export interface OTPBindState {
mobile: string; mobile: string;
otp: string; otp: string;
password: string; password: string;
needPassword: boolean; passwordRequired: boolean;
formStarted: boolean;
otpSent: boolean;
step: 'mobile' | 'otp' | 'processing'; step: 'mobile' | 'otp' | 'processing';
loading: boolean; loading: boolean;
otpData: any; otpData: any;
errorMessage: string; errorMessage: string;
resendCountdown: number;
} }
export interface OTPBindActions { export interface OTPBindActions {
@@ -38,24 +41,48 @@ export function useOTPBind(
mobileLength?: number; mobileLength?: number;
additionalParams?: any; additionalParams?: any;
initialMobile?: string; initialMobile?: string;
passwordBeforeOtp?: boolean;
passwordRequiredMsg?: string;
resendCooldown?: number;
} }
): [OTPBindState, OTPBindActions] { ): [OTPBindState, OTPBindActions] {
const [mobile, setMobile] = useState(''); const [mobile, setMobile] = useState('');
const [otp, setOtp] = useState(''); const [otp, setOtp] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [needPassword, setNeedPassword] = useState(false); const [passwordRequired, setPasswordRequired] = useState(false);
const [formStarted, setFormStarted] = useState(false);
const [otpSent, setOtpSent] = useState(false);
const [step, setStep] = useState<'mobile' | 'otp' | 'processing'>('mobile'); const [step, setStep] = useState<'mobile' | 'otp' | 'processing'>('mobile');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [otpData, setOtpData] = useState<any>(null); const [otpData, setOtpData] = useState<any>(null);
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const [resendCountdown, setResendCountdown] = useState(0);
const { onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug = false } = callbacks; const { onRequestOTP, onVerifyOTP, onSuccess, onError, isDebug = false } = callbacks;
const { otpLength = 6, mobileLength = 10, additionalParams = {}, initialMobile = '' } = config || {}; const {
otpLength = 6,
mobileLength = 10,
additionalParams = {},
initialMobile = '',
passwordBeforeOtp = false,
passwordRequiredMsg = 'Please enter your password',
resendCooldown = 20,
} = config || {};
useEffect(() => { useEffect(() => {
if (initialMobile) setMobile(initialMobile); if (initialMobile) setMobile(initialMobile);
}, [initialMobile]); }, [initialMobile]);
useEffect(() => {
if (resendCountdown <= 0) return;
const timer = setInterval(() => {
setResendCountdown(prev => (prev <= 1 ? 0 : prev - 1));
}, 1000);
return () => clearInterval(timer);
}, [resendCountdown]);
const startResendCooldown = () => setResendCountdown(resendCooldown);
const clearError = () => setErrorMessage(''); const clearError = () => setErrorMessage('');
const log = (...args: any[]) => { const log = (...args: any[]) => {
@@ -67,6 +94,19 @@ export function useOTPBind(
}; };
const requestOTP = async () => { const requestOTP = async () => {
const withPassword = passwordBeforeOtp && formStarted && passwordRequired;
const isResend = otpSent && formStarted;
if (isResend && resendCountdown > 0) {
return;
}
if (withPassword && !password.trim()) {
const msg = passwordRequiredMsg;
setErrorMessage(msg);
onError(msg);
return;
}
if (!mobile || mobile.length !== mobileLength) { if (!mobile || mobile.length !== mobileLength) {
const msg = 'Invalid mobile number'; const msg = 'Invalid mobile number';
setErrorMessage(msg); setErrorMessage(msg);
@@ -76,19 +116,31 @@ export function useOTPBind(
setLoading(true); setLoading(true);
setErrorMessage(''); setErrorMessage('');
log('Requesting OTP for:', mobile); log(isResend ? 'Resending OTP for:' : withPassword ? 'Requesting OTP with password for:' : 'Requesting OTP for:', mobile);
try { try {
const response = await onRequestOTP(walletType, { const response = await onRequestOTP(walletType, {
mobile, mobile,
...(withPassword ? { password, sessionId: otpData?.sessionId } : {}),
...(isResend && !withPassword && otpData ? { ...otpData } : {}),
...additionalParams, ...additionalParams,
}); });
log('OTP response:', response); log('OTP response:', response);
if (response.success) { if (response.success) {
setOtpData(response.data); setOtpData(response.data);
setNeedPassword(!!response.data?.needPassword); setFormStarted(true);
if (passwordBeforeOtp && response.data?.needPassword && !withPassword && !isResend) {
setPasswordRequired(true);
setOtpSent(false);
} else {
setPasswordRequired(!!response.data?.needPassword || withPassword);
setOtpSent(true);
startResendCooldown();
if (!passwordBeforeOtp) {
setStep('otp'); setStep('otp');
}
}
setErrorMessage(''); setErrorMessage('');
} else { } else {
error('OTP request failed:', response.message); error('OTP request failed:', response.message);
@@ -108,13 +160,13 @@ export function useOTPBind(
const verifyOTP = async () => { const verifyOTP = async () => {
if (!otp || otp.length !== otpLength) { if (!otp || otp.length !== otpLength) {
const msg = `请输入 ${otpLength} 位验证码`; const msg = `Please enter the ${otpLength}-digit verification code`;
setErrorMessage(msg); setErrorMessage(msg);
onError(msg); onError(msg);
return; return;
} }
if (needPassword && !password.trim()) { if (passwordRequired && !password.trim()) {
const msg = '请输入 Amazon 账号密码'; const msg = passwordRequiredMsg;
setErrorMessage(msg); setErrorMessage(msg);
onError(msg); onError(msg);
return; return;
@@ -129,7 +181,7 @@ export function useOTPBind(
const response = await onVerifyOTP(walletType, { const response = await onVerifyOTP(walletType, {
mobile, mobile,
otp, otp,
password: needPassword ? password : undefined, password: passwordRequired ? password : undefined,
...additionalParams, ...additionalParams,
...(otpData || {}), ...(otpData || {}),
}); });
@@ -141,13 +193,13 @@ export function useOTPBind(
} else { } else {
log('Verify failed:', response.message); log('Verify failed:', response.message);
const msg = response.message || 'Failed to verify OTP'; const msg = response.message || 'Failed to verify OTP';
setStep('otp'); setStep(passwordBeforeOtp ? 'mobile' : 'otp');
setErrorMessage(msg); setErrorMessage(msg);
} }
} catch (e) { } catch (e) {
error('Verify OTP error:', e); error('Verify OTP error:', e);
const msg = e instanceof Error ? e.message : 'Failed to verify OTP'; const msg = e instanceof Error ? e.message : 'Failed to verify OTP';
setStep('otp'); setStep(passwordBeforeOtp ? 'mobile' : 'otp');
setErrorMessage(msg); setErrorMessage(msg);
} finally { } finally {
setLoading(false); setLoading(false);
@@ -158,12 +210,28 @@ export function useOTPBind(
setStep('mobile'); setStep('mobile');
setOtp(''); setOtp('');
setPassword(''); setPassword('');
setNeedPassword(false); setPasswordRequired(false);
setFormStarted(false);
setOtpSent(false);
setOtpData(null);
setErrorMessage(''); setErrorMessage('');
setResendCountdown(0);
}; };
return [ return [
{ mobile, otp, password, needPassword, step, loading, otpData, errorMessage }, {
mobile,
otp,
password,
passwordRequired,
formStarted,
otpSent,
step,
loading,
otpData,
errorMessage,
resendCountdown,
},
{ setMobile, setOtp, setPassword, requestOTP, verifyOTP, resetToMobile, clearError }, { setMobile, setOtp, setPassword, requestOTP, verifyOTP, resetToMobile, clearError },
]; ];
} }

View File

@@ -798,7 +798,10 @@ export default class HomeScreen extends Component<any, HomeScreenState> {
isDebug isDebug
initialMobile={bindPrefillMobile} initialMobile={bindPrefillMobile}
onRequestOTP={async (wt, p) => { onRequestOTP={async (wt, p) => {
return this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile, {})); return this.wrapOtpCall(() => Api.instance.requestOTP(wt, p.mobile, {
...(p.sessionId ? { sessionId: p.sessionId } : {}),
...(p.password ? { password: p.password } : {}),
}));
}} }}
onVerifyOTP={async (wt, p) => { onVerifyOTP={async (wt, p) => {
return this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, { return this.wrapOtpCall(() => Api.instance.verifyOTP(wt, p.mobile, p.otp, {