feat: 重置密码

This commit is contained in:
jinyaqiu 2025-08-04 11:49:57 +08:00
parent 3fa9acc15d
commit c406312e0a
3 changed files with 130 additions and 47 deletions

View File

@ -1,4 +1,5 @@
import { HapticTab } from '@/components/HapticTab';
import AskNavbar from '@/components/layout/ask';
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
import { requestNotificationPermission } from '@/components/owner/utils';
import TabBarBackground from '@/components/ui/TabBarBackground';
@ -7,14 +8,13 @@ import { useColorScheme } from '@/hooks/useColorScheme';
import { prefetchChats } from '@/lib/prefetch';
import { fetchApi } from '@/lib/server-api-util';
import { webSocketManager, WebSocketStatus } from '@/lib/websocket-util';
import { TransitionPresets } from '@react-navigation/bottom-tabs';
import * as Notifications from 'expo-notifications';
import { Tabs } from 'expo-router';
import * as SecureStore from 'expo-secure-store';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Platform } from 'react-native';
import { TransitionPresets } from '@react-navigation/bottom-tabs';
import AskNavbar from '@/components/layout/ask';
interface PollingData {
title: string;

View File

@ -7,19 +7,19 @@ import { Ionicons } from '@expo/vector-icons';
import { useLocalSearchParams, useRouter } from 'expo-router';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ActivityIndicator, KeyboardAvoidingView, Platform, ScrollView, TextInput, TouchableOpacity, View } from 'react-native';
import { ActivityIndicator, KeyboardAvoidingView, Platform, ScrollView, StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
const resetPassword = () => {
const ResetPassword = () => {
const { t } = useTranslation();
const router = useRouter();
const { session_id: resetPasswordSessionId, token } = useLocalSearchParams<{ session_id: string; token: string }>();
// 使用 auth context 登录
const { login } = useAuth();
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [showSecondPassword, setShowSecondPassword] = useState(false);
const [error, setError] = useState('');
const validatePassword = (pwd: string) => {
@ -38,8 +38,8 @@ const resetPassword = () => {
return;
}
if (!validatePassword(password)) {
setError(t('auth.signup.passwordAuth', { ns: 'login' }));
if (password?.length < 6) {
setError(t('auth.signup.pwdLengthError', { ns: 'login' }));
return;
}
@ -64,6 +64,7 @@ const resetPassword = () => {
if (login) {
login(response, response.access_token || '');
}
router.push('/ask');
} catch (error) {
console.error('Reset password error:', error);
setError(t('auth.resetPwd.error', { ns: 'login' }) || 'Failed to reset password');
@ -75,80 +76,75 @@ const resetPassword = () => {
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
className="flex-1 bg-white"
style={styles.container}
>
<ScrollView contentContainerClassName="flex-grow justify-center p-5">
<ThemedView className="w-full max-w-[400px] self-center p-5 rounded-xl bg-white">
<ThemedText className="text-2xl font-bold mb-6 text-center text-gray-800">
<ScrollView contentContainerStyle={styles.scrollContainer}>
<ThemedView style={styles.formContainer}>
<ThemedText style={styles.title}>
{t('auth.resetPwd.title', { ns: 'login' })}
</ThemedText>
{error ? (
<ThemedText className="text-red-500 mb-4 text-center">
<ThemedText style={styles.errorText}>
{error}
</ThemedText>
) : null}
<View className="mb-6">
<View className="flex-row items-center border border-gray-200 rounded-lg px-3">
<View style={styles.inputContainer}>
<View style={styles.passwordInputContainer}>
<TextInput
className="flex-1 h-12 text-gray-800"
placeholder={t('auth.login.passwordPlaceholder', { ns: 'login' })}
placeholderTextColor="#999"
value={password}
onChangeText={setPassword}
secureTextEntry={!showPassword}
autoCapitalize="none"
autoCorrect={false}
/>
<TouchableOpacity
onPress={() => setShowPassword(!showPassword)}
className="p-2"
>
<Ionicons
name={showPassword ? 'eye-off' : 'eye'}
size={20}
color="#666"
/>
</TouchableOpacity>
</View>
<View className="flex-row items-center border border-gray-200 rounded-lg px-3 mt-4">
<TextInput
className="flex-1 h-12 text-gray-800"
style={[styles.input, { flex: 1 }]}
placeholder={t('auth.signup.confirmPasswordPlaceholder', { ns: 'login' })}
placeholderTextColor="#999"
value={confirmPassword}
onChangeText={setConfirmPassword}
placeholderTextColor="#ccc"
value={password}
onChangeText={(value) => {
setPassword(value)
}}
secureTextEntry={!showPassword}
autoCapitalize="none"
autoCorrect={false}
returnKeyType="done"
onSubmitEditing={handleSubmit}
/>
<TouchableOpacity
onPress={() => setShowPassword(!showPassword)}
className="p-2"
style={styles.eyeIcon}
>
<Ionicons
name={showPassword ? 'eye-off' : 'eye'}
name={showPassword ? 'eye' : 'eye-off'}
size={20}
color="#666"
/>
</TouchableOpacity>
</View>
<View style={styles.passwordInputContainer}>
<TextInput
style={[styles.input, { flex: 1 }]}
placeholder={t('auth.signup.confirmPasswordPlaceholder', { ns: 'login' })}
placeholderTextColor="#ccc"
value={confirmPassword}
onChangeText={(value) => {
setConfirmPassword(value)
}}
secureTextEntry={!showSecondPassword}
/>
<TouchableOpacity
onPress={() => setShowSecondPassword(!showSecondPassword)}
style={styles.eyeIcon}
>
<Ionicons
name={showSecondPassword ? 'eye' : 'eye-off'}
size={20}
color="#666"
/>
</TouchableOpacity>
</View>
</View>
<TouchableOpacity
className={`w-full py-4 rounded-lg items-center justify-center ${loading ? 'bg-orange-400' : 'bg-[#E2793F]'}`}
style={[styles.submitButton, loading && styles.submitButtonDisabled]}
onPress={handleSubmit}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<ThemedText className="text-white text-base font-semibold">
<ThemedText style={styles.submitButtonText}>
{t('auth.resetPwd.resetButton', { ns: 'login' })}
</ThemedText>
)}
@ -157,6 +153,87 @@ const resetPassword = () => {
</ScrollView>
</KeyboardAvoidingView>
);
}
};
export default resetPassword
const styles = StyleSheet.create({
passwordInputContainer: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: 12,
backgroundColor: '#FFF8DE',
overflow: 'hidden',
},
container: {
flex: 1,
backgroundColor: '#fff',
},
scrollContainer: {
flexGrow: 1,
justifyContent: 'center',
padding: 20,
},
formContainer: {
width: '100%',
maxWidth: 400,
alignSelf: 'center',
padding: 20,
borderRadius: 12,
backgroundColor: '#fff',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 24,
textAlign: 'center',
color: '#1f2937',
},
errorText: {
color: '#ef4444',
marginBottom: 16,
textAlign: 'center',
},
inputContainer: {
marginBottom: 24,
gap: 16
},
inputWrapper: {
flexDirection: 'row',
alignItems: 'center',
borderWidth: 1,
borderColor: '#e5e7eb',
borderRadius: 8,
paddingHorizontal: 12,
},
confirmInput: {
marginTop: 16,
},
input: {
borderRadius: 12,
paddingHorizontal: 16,
paddingVertical: 12,
fontSize: 16,
textAlignVertical: 'center',
backgroundColor: '#FFF8DE'
},
eyeIcon: {
padding: 8,
},
submitButton: {
width: '100%',
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#E2793F',
},
submitButtonDisabled: {
backgroundColor: '#f59e0b',
},
submitButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '600',
},
});
export default ResetPassword;

View File

@ -12,12 +12,11 @@ import {
View
} from 'react-native';
import { webSocketManager, WsMessage } from '@/lib/websocket-util';
import { Message } from '@/types/ask';
import { useTranslation } from 'react-i18next';
import { ThemedText } from '../ThemedText';
import { createNewConversation } from './utils';
import { WsMessage } from '@/lib/websocket-util';
import { webSocketManager } from '@/lib/websocket-util';
interface Props {
setIsHello: Dispatch<SetStateAction<boolean>>,
@ -194,6 +193,13 @@ export default function SendMessage(props: Props) {
if (!currentSessionId) {
currentSessionId = await createNewConversation(text);
setConversationId(currentSessionId);
webSocketManager.send({
type: 'Chat',
session_id: currentSessionId,
message: text,
image_material_ids: selectedImages.length > 0 ? selectedImages : undefined,
});
setSelectedImages([]);
}
// 通过 WebSocket 发送消息