364 lines
12 KiB
TypeScript
364 lines
12 KiB
TypeScript
import { useState } from 'react'
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
Pressable,
|
|
Platform,
|
|
TextInput,
|
|
Keyboard,
|
|
} from 'react-native'
|
|
import { StatusBar } from 'expo-status-bar'
|
|
import { SafeAreaView } from 'react-native-safe-area-context'
|
|
import { useRouter } from 'expo-router'
|
|
import { StatusBar as RNStatusBar } from 'react-native'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { LeftArrowIcon } from '@/components/icon'
|
|
import { LinearGradient } from 'expo-linear-gradient'
|
|
import { useChangePassword } from '@/hooks/use-change-password'
|
|
|
|
export default function ChangePasswordScreen() {
|
|
const router = useRouter()
|
|
const { t } = useTranslation()
|
|
|
|
// 使用新的 hook
|
|
const { changePassword, loading, error: apiError } = useChangePassword()
|
|
|
|
const [currentPassword, setCurrentPassword] = useState('')
|
|
const [newPassword, setNewPassword] = useState('')
|
|
const [confirmPassword, setConfirmPassword] = useState('')
|
|
const [currentPasswordError, setCurrentPasswordError] = useState('')
|
|
const [newPasswordError, setNewPasswordError] = useState('')
|
|
const [confirmPasswordError, setConfirmPasswordError] = useState('')
|
|
const [successMessage, setSuccessMessage] = useState('')
|
|
|
|
const validateCurrentPassword = (value: string) => {
|
|
if (!value) {
|
|
setCurrentPasswordError(t('changePassword.currentPasswordRequired'))
|
|
return false
|
|
}
|
|
setCurrentPasswordError('')
|
|
return true
|
|
}
|
|
|
|
const validateNewPassword = (value: string) => {
|
|
if (!value) {
|
|
setNewPasswordError(t('changePassword.newPasswordRequired'))
|
|
return false
|
|
}
|
|
if (value.length < 6) {
|
|
setNewPasswordError(t('changePassword.newPasswordTooShort'))
|
|
return false
|
|
}
|
|
if (currentPassword && value === currentPassword) {
|
|
setNewPasswordError(t('changePassword.newPasswordSame'))
|
|
return false
|
|
}
|
|
setNewPasswordError('')
|
|
return true
|
|
}
|
|
|
|
const validateConfirmPassword = (value: string) => {
|
|
if (!value) {
|
|
setConfirmPasswordError(t('changePassword.confirmPasswordRequired'))
|
|
return false
|
|
}
|
|
if (newPassword && value !== newPassword) {
|
|
setConfirmPasswordError(t('changePassword.confirmPasswordMismatch'))
|
|
return false
|
|
}
|
|
setConfirmPasswordError('')
|
|
return true
|
|
}
|
|
|
|
const handleSubmit = async () => {
|
|
Keyboard.dismiss()
|
|
|
|
// 清除之前的成功消息
|
|
setSuccessMessage('')
|
|
|
|
// 按顺序验证每个字段
|
|
if (!validateCurrentPassword(currentPassword)) {
|
|
return
|
|
}
|
|
|
|
if (!validateNewPassword(newPassword)) {
|
|
return
|
|
}
|
|
|
|
if (!validateConfirmPassword(confirmPassword)) {
|
|
return
|
|
}
|
|
|
|
// 调用修改密码的API
|
|
await changePassword({
|
|
oldPassword: currentPassword,
|
|
newPassword: newPassword,
|
|
confirmPassword: confirmPassword,
|
|
})
|
|
|
|
// 处理结果
|
|
if (apiError) {
|
|
// 显示错误提示
|
|
if (apiError.message) {
|
|
// 根据 API 返回的错误消息显示相应的错误
|
|
if (apiError.message.includes('旧密码') || apiError.message.includes('当前密码')) {
|
|
setCurrentPasswordError(apiError.message)
|
|
} else {
|
|
setCurrentPasswordError(t('changePassword.apiError'))
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// 成功后显示提示并返回
|
|
setSuccessMessage(t('changePassword.success'))
|
|
setTimeout(() => {
|
|
router.back()
|
|
}, 1500)
|
|
}
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<StatusBar style="light" />
|
|
<RNStatusBar
|
|
barStyle="light-content"
|
|
backgroundColor="#090A0B"
|
|
translucent={Platform.OS === 'android'}
|
|
/>
|
|
<View style={styles.header}>
|
|
<Pressable
|
|
style={styles.backButton}
|
|
onPress={() => {
|
|
router.back()
|
|
}}
|
|
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
>
|
|
<LeftArrowIcon className="w-4 h-4" />
|
|
</Pressable>
|
|
<Text style={styles.headerTitle}>{t('changePassword.title')}</Text>
|
|
<View style={styles.headerSpacer} />
|
|
</View>
|
|
<ScrollView
|
|
style={styles.scrollView}
|
|
contentContainerStyle={styles.scrollContent}
|
|
showsVerticalScrollIndicator={false}
|
|
keyboardShouldPersistTaps="handled"
|
|
>
|
|
<View style={styles.form}>
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>{t('changePassword.currentPassword')}</Text>
|
|
<TextInput
|
|
style={[
|
|
styles.input,
|
|
currentPasswordError ? styles.inputError : null,
|
|
]}
|
|
value={currentPassword}
|
|
onChangeText={(text) => {
|
|
setCurrentPassword(text)
|
|
if (currentPasswordError) {
|
|
validateCurrentPassword(text)
|
|
}
|
|
}}
|
|
onBlur={() => {
|
|
validateCurrentPassword(currentPassword)
|
|
}}
|
|
placeholder={t('changePassword.currentPasswordPlaceholder')}
|
|
placeholderTextColor="#666666"
|
|
secureTextEntry
|
|
autoCapitalize="none"
|
|
/>
|
|
{currentPasswordError ? (
|
|
<Text style={styles.errorText}>{currentPasswordError}</Text>
|
|
) : null}
|
|
</View>
|
|
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>{t('changePassword.newPassword')}</Text>
|
|
<TextInput
|
|
style={[
|
|
styles.input,
|
|
newPasswordError ? styles.inputError : null,
|
|
]}
|
|
value={newPassword}
|
|
onChangeText={(text) => {
|
|
setNewPassword(text)
|
|
if (newPasswordError) {
|
|
validateNewPassword(text)
|
|
}
|
|
// 如果确认密码已填写,重新验证确认密码
|
|
if (confirmPassword) {
|
|
validateConfirmPassword(confirmPassword)
|
|
}
|
|
}}
|
|
onBlur={() => {
|
|
validateNewPassword(newPassword)
|
|
}}
|
|
placeholder={t('changePassword.newPasswordPlaceholder')}
|
|
placeholderTextColor="#666666"
|
|
secureTextEntry
|
|
autoCapitalize="none"
|
|
/>
|
|
{newPasswordError ? (
|
|
<Text style={styles.errorText}>{newPasswordError}</Text>
|
|
) : null}
|
|
</View>
|
|
|
|
<View style={styles.inputGroup}>
|
|
<Text style={styles.label}>{t('changePassword.confirmPassword')}</Text>
|
|
<TextInput
|
|
style={[
|
|
styles.input,
|
|
confirmPasswordError ? styles.inputError : null,
|
|
]}
|
|
value={confirmPassword}
|
|
onChangeText={(text) => {
|
|
setConfirmPassword(text)
|
|
if (confirmPasswordError) {
|
|
validateConfirmPassword(text)
|
|
}
|
|
}}
|
|
onBlur={() => {
|
|
validateConfirmPassword(confirmPassword)
|
|
}}
|
|
placeholder={t('changePassword.confirmPasswordPlaceholder')}
|
|
placeholderTextColor="#666666"
|
|
secureTextEntry
|
|
autoCapitalize="none"
|
|
returnKeyType="done"
|
|
onSubmitEditing={handleSubmit}
|
|
/>
|
|
{confirmPasswordError ? (
|
|
<Text style={styles.errorText}>{confirmPasswordError}</Text>
|
|
) : null}
|
|
</View>
|
|
|
|
{/* 成功提示 */}
|
|
{successMessage ? (
|
|
<Text style={styles.successText}>{successMessage}</Text>
|
|
) : null}
|
|
|
|
<Pressable
|
|
style={styles.submitButtonContainer}
|
|
onPress={handleSubmit}
|
|
disabled={loading}
|
|
android_ripple={{ color: 'rgba(255, 255, 255, 0.1)' }}
|
|
>
|
|
<LinearGradient
|
|
colors={['#9966FF', '#FF6699', '#FF9966']}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 0 }}
|
|
style={styles.submitButton}
|
|
>
|
|
<Text style={styles.submitButtonText}>
|
|
{loading ? t('changePassword.submitting') : t('changePassword.submit')}
|
|
</Text>
|
|
</LinearGradient>
|
|
</Pressable>
|
|
</View>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
paddingHorizontal: 16,
|
|
paddingTop: Platform.OS === 'ios' ? 8 : 16,
|
|
paddingBottom: 16,
|
|
backgroundColor: '#090A0B',
|
|
},
|
|
backButton: {
|
|
width: 32,
|
|
height: 32,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
headerTitle: {
|
|
color: '#FFFFFF',
|
|
fontSize: 18,
|
|
fontWeight: '600',
|
|
flex: 1,
|
|
textAlign: 'center',
|
|
},
|
|
headerSpacer: {
|
|
width: 32,
|
|
},
|
|
scrollView: {
|
|
flex: 1,
|
|
},
|
|
scrollContent: {
|
|
paddingHorizontal: 16,
|
|
paddingTop: 24,
|
|
paddingBottom: 32,
|
|
},
|
|
form: {
|
|
flex: 1,
|
|
},
|
|
inputGroup: {
|
|
marginBottom: 12,
|
|
},
|
|
label: {
|
|
color: '#F5F5F5',
|
|
fontSize: 14,
|
|
fontWeight: '500',
|
|
marginBottom: 8,
|
|
},
|
|
input: {
|
|
backgroundColor: '#1C1E22',
|
|
borderRadius: 12,
|
|
paddingHorizontal: 16,
|
|
paddingVertical: 14,
|
|
color: '#F5F5F5',
|
|
fontSize: 14,
|
|
height: 48,
|
|
},
|
|
inputError: {
|
|
borderWidth: 1,
|
|
borderColor: '#FF6B6B',
|
|
},
|
|
errorText: {
|
|
color: '#FF6B6B',
|
|
fontSize: 12,
|
|
marginTop: 8,
|
|
},
|
|
submitButtonContainer: {
|
|
width: '100%',
|
|
borderRadius: 12,
|
|
overflow: 'hidden',
|
|
height: 48,
|
|
marginTop: 32,
|
|
},
|
|
submitButton: {
|
|
width: '100%',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: 12,
|
|
height: 48,
|
|
},
|
|
submitButtonText: {
|
|
color: '#F5F5F5',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
},
|
|
successText: {
|
|
color: '#4ADE80',
|
|
fontSize: 14,
|
|
textAlign: 'center',
|
|
marginTop: 16,
|
|
marginBottom: 8,
|
|
},
|
|
submitButtonDisabled: {
|
|
opacity: 0.6,
|
|
},
|
|
})
|
|
|