2025-08-06 14:01:52 +08:00

442 lines
16 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, StyleSheet, 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";
import PrivacyModal from "../owner/qualification/privacy";
interface LoginProps {
updateUrlParam: (status: string, value: string) => void;
setError: (error: string) => void;
setShowPassword: (showPassword: boolean) => void;
showPassword: boolean;
setShowSecondPassword: (showSecondPassword: boolean) => void;
showSecondPassword: boolean;
}
const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword, setShowSecondPassword, showSecondPassword }: 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);
const [modalType, setModalType] = useState<'ai' | 'terms' | 'privacy' | 'user'>('ai');
// 协议弹窗
const [privacyModalVisible, setPrivacyModalVisible] = useState(false);
// 从 URL 参数中获取 task_id 和 steps
const params = useLocalSearchParams<{ task_id?: string; steps?: string }>();
const taskId = params.task_id;
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;
// }
if (password.length < 6) {
setError(t('auth.signup.pwdLengthError', { 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)
setShowSecondPassword(false)
}, [])
return (
<View style={styles.container}>
{/* 邮箱输入 */}
<View style={styles.inputContainer}>
<ThemedText style={styles.inputLabel}>
{t('auth.login.email', { ns: 'login' })}
</ThemedText>
<View style={styles.inputWrapper}>
<TextInput
style={styles.textInput}
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 style={styles.inputContainer}>
<ThemedText style={styles.inputLabel}>
{t('auth.login.password', { ns: 'login' })}
</ThemedText>
<View style={styles.passwordInputContainer}>
<TextInput
style={[styles.textInput, { 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)}
style={styles.eyeIcon}
>
<Ionicons
name={showPassword ? 'eye' : 'eye-off'}
size={20}
color="#666"
/>
</TouchableOpacity>
</View>
</View>
{/* 确认密码 */}
<View style={[styles.inputContainer, { marginBottom: 24 }]}>
<ThemedText style={styles.inputLabel}>
{t('auth.signup.confirmPassword', { ns: 'login' })}
</ThemedText>
<View style={styles.passwordInputContainer}>
<TextInput
style={[styles.textInput, { flex: 1 }]}
placeholder={t('auth.signup.confirmPasswordPlaceholder', { ns: 'login' })}
placeholderTextColor="#ccc"
value={confirmPassword}
onChangeText={(value) => {
handleConfirmPasswordChange(value)
setError('123')
}}
secureTextEntry={!showSecondPassword}
/>
<TouchableOpacity
onPress={() => setShowSecondPassword(!showSecondPassword)}
style={styles.eyeIcon}
>
<Ionicons
name={showSecondPassword ? 'eye' : 'eye-off'}
size={20}
color="#666"
/>
</TouchableOpacity>
</View>
</View>
{/* 注册按钮 */}
<TouchableOpacity
style={[styles.signupButton, loading && { opacity: 0.7 }]}
onPress={handleSubmit}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<ThemedText style={styles.signupButtonText}>
{t("auth.signup.signupButton", { ns: 'login' })}
</ThemedText>
)}
</TouchableOpacity>
<View style={styles.termsContainer}>
<TouchableOpacity
onPress={() => {
const newValue = !checked;
setChecked(newValue);
if (!newValue) {
setError(t('auth.signup.checkedRequired', { ns: 'login' }));
return
} else {
setError("123")
}
}}
style={[
styles.checkbox,
checked && styles.checkboxChecked
]}
>
{checked && (
<Ionicons name="checkmark" size={14} color="white" />
)}
</TouchableOpacity>
<View style={styles.termsTextContainer}>
<ThemedText style={styles.termsText}>
{t("auth.telLogin.agree", { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => {
setModalType('terms');
setPrivacyModalVisible(true);
}}>
<ThemedText style={styles.termsLink}>
{t("auth.telLogin.terms", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText style={styles.termsText}>
{t("auth.telLogin.and", { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => {
setModalType('privacy');
setPrivacyModalVisible(true);
}}>
<ThemedText style={styles.termsLink}>
{t("auth.telLogin.privacyPolicy", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText style={styles.termsText}>
{t("auth.telLogin.and", { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => {
setModalType('user');
setPrivacyModalVisible(true);
}}>
<ThemedText style={styles.termsLink}>
{t("auth.telLogin.userAgreement", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText style={styles.termsText}>
{t("auth.telLogin.and", { ns: 'login' })}
</ThemedText>
<TouchableOpacity onPress={() => {
setModalType('ai');
setPrivacyModalVisible(true);
}}>
<ThemedText style={styles.termsLink}>
{t("auth.telLogin.aiAgreement", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
<ThemedText style={styles.termsText}>
{t("auth.telLogin.agreement", { ns: 'login' })}
</ThemedText>
<ThemedText style={styles.termsLink}>
{t("common.name")}
</ThemedText>
<ThemedText style={styles.termsText}>
{t("auth.telLogin.getPhone", { ns: 'login' })}
</ThemedText>
</View>
</View>
{/* 已有账号 */}
<View style={styles.loginContainer}>
<ThemedText style={styles.loginText}>
{t("auth.signup.haveAccount", { ns: 'login' })}
</ThemedText>
<TouchableOpacity
onPress={() => {
updateUrlParam("status", "login");
}}
>
<ThemedText style={styles.loginLink}>
{t("auth.signup.login", { ns: 'login' })}
</ThemedText>
</TouchableOpacity>
</View>
{/* 协议弹窗 */}
<PrivacyModal modalVisible={privacyModalVisible} setModalVisible={setPrivacyModalVisible} type={modalType} />
</View>
);
};
const styles = StyleSheet.create({
container: {
width: '100%',
},
inputContainer: {
marginBottom: 16,
},
inputWrapper: {
borderRadius: 12,
backgroundColor: '#FFF8DE',
overflow: 'hidden',
},
inputLabel: {
fontSize: 14,
color: '#AC7E35',
fontWeight: '600',
marginBottom: 8,
marginLeft: 8,
},
textInput: {
padding: 12,
fontSize: 16,
color: '#1F2937',
},
passwordInputContainer: {
flexDirection: 'row',
alignItems: 'center',
borderRadius: 12,
backgroundColor: '#FFF8DE',
overflow: 'hidden',
},
eyeIcon: {
paddingHorizontal: 12,
paddingVertical: 8,
},
signupButton: {
width: '100%',
backgroundColor: '#E2793F',
borderRadius: 28,
padding: 16,
alignItems: 'center',
marginBottom: 16,
},
signupButtonText: {
color: '#FFFFFF',
fontWeight: '600',
},
termsContainer: {
flexDirection: 'row',
alignItems: 'flex-start',
marginVertical: 10,
},
checkbox: {
width: 20,
height: 20,
borderRadius: 10,
borderWidth: 2,
borderColor: '#E5E7EB',
justifyContent: 'center',
alignItems: 'center',
marginRight: 8,
marginTop: 2,
},
checkboxChecked: {
backgroundColor: '#E2793F',
borderColor: '#E2793F',
},
termsTextContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
flex: 1,
},
termsText: {
fontSize: 14,
color: '#1F2937',
lineHeight: 20,
},
termsLink: {
fontSize: 14,
color: '#E2793F',
lineHeight: 20,
},
loginContainer: {
flexDirection: 'row',
justifyContent: 'center',
marginTop: 24,
},
loginText: {
fontSize: 14,
color: '#1F2937',
},
loginLink: {
color: '#E2793F',
fontSize: 14,
fontWeight: '600',
marginLeft: 4,
},
});
export default SignUp;