2025-07-07 13:42:11 +08:00

325 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Ionicons } from "@expo/vector-icons";
import { useLocalSearchParams, useRouter } from "expo-router";
import { useEffect, useState } from 'react';
import { useTranslation } from "react-i18next";
import { ActivityIndicator, TextInput, TouchableOpacity, View } from 'react-native';
import { useAuth } from "../../contexts/auth-context";
import { fetchApi } from "../../lib/server-api-util";
import { User } from "../../types/user";
import { ThemedText } from "../ThemedText";
interface LoginProps {
updateUrlParam: (status: string, value: string) => void;
setError: (error: string) => void;
setShowPassword: (showPassword: boolean) => void;
showPassword: boolean;
}
const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword }: LoginProps) => {
const { t } = useTranslation();
const { login } = useAuth();
const router = useRouter();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [passwordsMatch, setPasswordsMatch] = useState(true);
const [loading, setLoading] = useState(false);
const [checked, setChecked] = useState(false);
// 从 URL 参数中获取 task_id 和 steps
const params = useLocalSearchParams<{ task_id?: string; steps?: string }>();
const taskId = params.task_id;
const steps = params.steps;
const handlePasswordChange = (value: string) => {
setPassword(value);
// 当密码或确认密码变化时,检查是否匹配
if (confirmPassword && value !== confirmPassword) {
setPasswordsMatch(false);
} else {
setPasswordsMatch(true);
}
};
const handleConfirmPasswordChange = (value: string) => {
setConfirmPassword(value);
// 当密码或确认密码变化时,检查是否匹配
if (password && value !== password) {
setPasswordsMatch(false);
} else {
setPasswordsMatch(true);
}
};
const handleSubmit = async () => {
if (!email) {
setError(t('auth.signup.emailPlaceholder', { ns: 'login' }));
return;
}
if (!password) {
setError(t('auth.signup.passwordPlaceholder', { ns: 'login' }));
return;
}
// 验证两次密码是否一致
if (password !== confirmPassword) {
setPasswordsMatch(false);
setError(t('auth.signup.passwordNotMatch', { ns: 'login' }));
return;
}
if (!checked) {
setError(t('auth.signup.checkedRequired', { ns: 'login' }));
return;
}
if (email) {
// 校验是否符合邮箱规范
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
setError(t('auth.signup.emailAuth', { ns: 'login' }));
return;
}
}
if (password) {
// 校验密码是否符合规范
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
if (!passwordRegex.test(password)) {
setError(t('auth.signup.passwordAuth', { ns: 'login' }));
return;
}
}
setLoading(true);
try {
const body = {
email: email,
password: password
};
// 这里调用实际的注册API
const response = await fetchApi<User>('/iam/register/email', {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json'
}
}, true, false);
// 存储token
login(response as User, response.access_token || '');
// 如果有任务ID跳转到上传页面
if (taskId) {
// 使用字符串路径格式传递参数
// router.push(`/upload?steps=${encodeURIComponent(steps || '')}&task_id=${encodeURIComponent(taskId)}`);
} else {
// 注册成功后跳转到首页
router.replace('/user-message');
}
setLoading(false);
} catch (error) {
// console.error('Registration failed:', error);
// 这里可以添加错误处理逻辑
setLoading(false);
}
};
useEffect(() => {
if (!passwordsMatch) {
setError(t('auth.login.passwordNotMatch', { ns: 'login' }));
}
}, [passwordsMatch])
// 初始化
useEffect(() => {
setShowPassword(false)
}, [])
return <View className="w-full">
{/* 邮箱输入 */}
<View className="mb-4">
<ThemedText className="text-base !text-textPrimary mb-2 ml-2">
{t('auth.login.email', { ns: 'login' })}
</ThemedText>
<View className="border border-gray-300 rounded-2xl bg-inputBackground overflow-hidden">
<TextInput
className="p-3 text-base flex-1"
placeholder={t('auth.login.accountPlaceholder', { ns: 'login' })}
placeholderTextColor="#ccc"
value={email}
onChangeText={(value) => {
setEmail(value)
setError('123')
}}
keyboardType="email-address"
autoCapitalize="none"
/>
</View>
</View>
{/* 密码输入 */}
<View className="mb-4">
<ThemedText className="text-base !text-textPrimary mb-2 ml-2">
{t('auth.login.password', { ns: 'login' })}
</ThemedText>
<View className="border border-gray-300 rounded-2xl bg-inputBackground overflow-hidden flex-row items-center">
<TextInput
className="p-3 text-base flex-1"
placeholder={t('auth.login.passwordPlaceholder', { ns: 'login' })}
placeholderTextColor="#ccc"
value={password}
onChangeText={(value) => {
handlePasswordChange(value)
setError('123')
}}
secureTextEntry={!showPassword}
/>
<TouchableOpacity
onPress={() => setShowPassword(!showPassword)}
className="px-3 py-2"
>
<Ionicons
name={showPassword ? 'eye' : 'eye-off'}
size={20}
color="#666"
/>
</TouchableOpacity>
</View>
</View>
{/* 确认密码 */}
<View className="mb-6">
<ThemedText className="text-base !text-textPrimary mb-2 ml-2">
{t('auth.signup.confirmPassword', { ns: 'login' })}
</ThemedText>
<View className="border border-gray-300 rounded-2xl bg-inputBackground overflow-hidden flex-row items-center">
<TextInput
className="p-3 text-base flex-1"
placeholder={t('auth.signup.confirmPasswordPlaceholder', { ns: 'login' })}
placeholderTextColor="#ccc"
value={confirmPassword}
onChangeText={(value) => {
handleConfirmPasswordChange(value)
setError('123')
}}
secureTextEntry={!showPassword}
/>
<TouchableOpacity
onPress={() => setShowPassword(!showPassword)}
className="px-3 py-2"
>
<Ionicons
name={showPassword ? 'eye' : 'eye-off'}
size={20}
color="#666"
/>
</TouchableOpacity>
</View>
</View>
{/* 注册按钮 */}
<TouchableOpacity
className={`w-full bg-[#E2793F] rounded-full p-4 items-center ${loading ? 'opacity-70' : ''}`}
onPress={handleSubmit}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<ThemedText className="!text-white font-semibold">
{t("auth.signup.signupButton", { ns: 'login' })}
</ThemedText>
)}
</TouchableOpacity>
<View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 10 }}>
<TouchableOpacity
onPress={() => {
const newValue = !checked;
setChecked(newValue);
if (!newValue) {
setError(t('auth.signup.checkedRequired', { ns: 'login' }));
return
} else {
setError("123")
}
}}
style={{
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: checked ? '#E2793F' : '#ccc',
backgroundColor: checked ? '#E2793F' : 'transparent',
justifyContent: 'center',
alignItems: 'center',
marginRight: 8,
}}
>
{checked && (
<Ionicons name="checkmark" size={14} color="white" />
)}
</TouchableOpacity>
<View style={{ flexDirection: 'row', flexWrap: 'wrap', flex: 1 }}>
<ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.agree", { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => router.push({
pathname: '/agreement',
params: { type: 'service' }
} as any)}>
<ThemedText className="text-sm !text-[#E2793F]">
{t("auth.telLogin.terms", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.and", { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => router.push({
pathname: '/agreement',
params: { type: 'privacy' }
} as any)}>
<ThemedText className="text-sm !text-[#E2793F]">
{t("auth.telLogin.privacyPolicy", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.and", { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => router.push({
pathname: '/agreement',
params: { type: 'user' }
} as any)}>
<ThemedText className="text-sm !text-[#E2793F]">
{t("auth.telLogin.userAgreement", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.agreement", { ns: 'login' })}
</ThemedText>
<ThemedText className="text-sm !text-[#E2793F]">
{t("common.name")}
</ThemedText>
<ThemedText className="text-sm !text-textPrimary">
{t("auth.telLogin.getPhone", { ns: 'login' })}
</ThemedText>
</View>
</View>
{/* 已有账号 */}
<View className="flex-row justify-center mt-6">
<ThemedText className="text-sm !text-textPrimary">
{t("auth.signup.haveAccount", { ns: 'login' })}
</ThemedText>
<TouchableOpacity
onPress={() => {
updateUrlParam("status", "login");
}}
>
<ThemedText className="!text-[#E2793F] text-sm font-semibold ml-1">
{t("auth.signup.login", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
</View>
</View>
}
export default SignUp