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