expo-popcore-app/app/changePassword.tsx

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,
},
})