feat: 手机号
This commit is contained in:
parent
727ebcb483
commit
e2cd78f6a0
@ -115,13 +115,13 @@ const Code = ({ phone }: CodeProps) => {
|
||||
<View style={styles.container}>
|
||||
<View style={styles.contentContainer}>
|
||||
<View style={styles.headerContainer}>
|
||||
<ThemedText style={styles.title}>
|
||||
<ThemedText style={styles.title} color="textSecondary" size="xl" weight="bold">
|
||||
{t("auth.telLogin.codeTitle", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.subtitle}>
|
||||
<ThemedText style={styles.subtitle} type="sfPro" color="textPrimary" size="sm">
|
||||
{t("auth.telLogin.secondTitle", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.phoneNumber}>
|
||||
<ThemedText color="bgSecondary" size="sm" weight="bold">
|
||||
{phone}
|
||||
</ThemedText>
|
||||
</View>
|
||||
@ -144,13 +144,13 @@ const Code = ({ phone }: CodeProps) => {
|
||||
/>
|
||||
<View style={[styles.errorContainer, { opacity: error ? 1 : 0 }]}>
|
||||
<Error />
|
||||
<ThemedText style={styles.errorText}>
|
||||
<ThemedText style={styles.errorText} size="xxs" color="bgSecondary" type="inter">
|
||||
{error}
|
||||
</ThemedText>
|
||||
</View>
|
||||
|
||||
<View style={styles.footerContainer}>
|
||||
<ThemedText style={styles.footerText}>
|
||||
<ThemedText size="sm" color="textPrimary" type="sfPro">
|
||||
{t("auth.telLogin.sendAgain", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<TouchableOpacity onPress={() => {
|
||||
@ -158,10 +158,16 @@ const Code = ({ phone }: CodeProps) => {
|
||||
sendVerificationCode()
|
||||
}
|
||||
}}>
|
||||
<ThemedText style={[
|
||||
styles.resendText,
|
||||
countdown > 0 && styles.disabledResendText
|
||||
]}>
|
||||
<ThemedText
|
||||
style={[
|
||||
styles.resendText,
|
||||
countdown > 0 && styles.disabledResendText
|
||||
]}
|
||||
size="sm"
|
||||
color="bgSecondary"
|
||||
type="inter"
|
||||
weight="bold"
|
||||
>
|
||||
{countdown > 0 ? `${countdown}s${t("auth.telLogin.resend", { ns: 'login' })}` : t("auth.telLogin.resend", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
@ -185,23 +191,13 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 16,
|
||||
},
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: '600',
|
||||
marginBottom: 8,
|
||||
paddingTop: 4,
|
||||
color: '#111827',
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
color: '#4B5563',
|
||||
textAlign: 'center',
|
||||
marginBottom: 4,
|
||||
},
|
||||
phoneNumber: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
color: '#E2793F',
|
||||
},
|
||||
otpContainer: {
|
||||
width: '100%',
|
||||
height: 80,
|
||||
@ -228,9 +224,6 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 16,
|
||||
fontWeight: '500',
|
||||
color: '#E2793F',
|
||||
marginLeft: 8,
|
||||
},
|
||||
footerContainer: {
|
||||
@ -238,12 +231,7 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'center',
|
||||
marginTop: 8,
|
||||
},
|
||||
footerText: {
|
||||
color: '#6B7280',
|
||||
},
|
||||
resendText: {
|
||||
color: '#E2793F',
|
||||
fontWeight: '500',
|
||||
marginLeft: 4,
|
||||
},
|
||||
disabledResendText: {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { fetchApi } from "@/lib/server-api-util";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ActivityIndicator, TextInput, TouchableOpacity, View } from "react-native";
|
||||
import { ThemedText } from "../ThemedText";
|
||||
import { View } from "react-native";
|
||||
import { Steps } from "./phoneLogin";
|
||||
import Button from "./ui/Button";
|
||||
import TextInput from "./ui/TextInput";
|
||||
|
||||
interface LoginProps {
|
||||
setSteps: (steps: Steps) => void;
|
||||
@ -18,67 +18,30 @@ const Phone = ({ setSteps, setPhone, phone, updateUrlParam }: LoginProps) => {
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
const sendVerificationCode = async () => {
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
setError(t("auth.telLogin.phoneInvalid", { ns: 'login' }));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
await fetchApi(`/iam/veritification-code`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ phone: phone }),
|
||||
})
|
||||
setSteps('code')
|
||||
updateUrlParam("status", "code");
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setPhone("")
|
||||
setIsLoading(false);
|
||||
// console.error(t("auth.telLogin.sendCodeError", { ns: 'login' }), error);
|
||||
}
|
||||
setSteps('code')
|
||||
updateUrlParam("status", "code");
|
||||
return
|
||||
};
|
||||
|
||||
return <View>
|
||||
{/* 手机号输入框 */}
|
||||
<View className="mb-5">
|
||||
<View className="w-full flex flex-row justify-between">
|
||||
<ThemedText className="text-base !text-textPrimary mb-2 ml-2">
|
||||
{t('auth.telLogin.title', { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<ThemedText className="text-sm !text-textPrimary mb-2 ml-2">
|
||||
{error}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<TextInput
|
||||
className="border border-gray-300 rounded-2xl p-3 text-base bg-inputBackground"
|
||||
label={t('auth.telLogin.title', { ns: 'login' })}
|
||||
placeholder={t('auth.telLogin.phoneRequired', { ns: 'login' })}
|
||||
placeholderTextColor="#ccc"
|
||||
value={phone}
|
||||
onChangeText={(text) => {
|
||||
setPhone(text);
|
||||
setError('');
|
||||
}}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
value={phone}
|
||||
error={error}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* 发送验证码 */}
|
||||
<TouchableOpacity
|
||||
className={`w-full bg-[#E2793F] rounded-full text-[#fff] p-4 items-center mb-6 ${isLoading ? 'opacity-70' : ''}`}
|
||||
onPress={sendVerificationCode}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : (
|
||||
<ThemedText className="!text-white font-semibold">
|
||||
{t('auth.telLogin.sendCode', { ns: 'login' })}
|
||||
</ThemedText>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<Button isLoading={isLoading} handleLogin={sendVerificationCode} text={t('auth.telLogin.sendCode', { ns: 'login' })} />
|
||||
</View>
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,9 @@ const PhoneLogin = ({ updateUrlParam }: LoginProps) => {
|
||||
|
||||
return <View>
|
||||
{
|
||||
steps === "phone" ? <Phone setSteps={setSteps} setPhone={setPhone} phone={phone} updateUrlParam={updateUrlParam} /> : <Code phone={phone} />
|
||||
steps === "phone"
|
||||
? <Phone setSteps={setSteps} setPhone={setPhone} phone={phone} updateUrlParam={updateUrlParam} />
|
||||
: <Code phone={phone} />
|
||||
}
|
||||
</View>
|
||||
}
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import { Fonts } from "@/constants/Fonts";
|
||||
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 { StyleSheet, 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";
|
||||
import Button from "./ui/Button";
|
||||
import TextInput from "./ui/TextInput";
|
||||
|
||||
interface LoginProps {
|
||||
updateUrlParam: (status: string, value: string) => void;
|
||||
@ -146,100 +149,53 @@ const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword, setSh
|
||||
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>
|
||||
<TextInput
|
||||
label={t('auth.login.email', { ns: 'login' })}
|
||||
placeholder={t('auth.login.accountPlaceholder', { ns: 'login' })}
|
||||
onChangeText={(text) => {
|
||||
setEmail(text);
|
||||
setError('123');
|
||||
}}
|
||||
autoCapitalize="none"
|
||||
keyboardType="email-address"
|
||||
value={email}
|
||||
/>
|
||||
|
||||
{/* 密码输入 */}
|
||||
<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>
|
||||
<TextInput
|
||||
label={t('auth.login.password', { ns: 'login' })}
|
||||
placeholder={t('auth.login.passwordPlaceholder', { ns: 'login' })}
|
||||
autoCapitalize="none"
|
||||
value={password}
|
||||
onChangeText={(value) => {
|
||||
handlePasswordChange(value)
|
||||
setError('123')
|
||||
}}
|
||||
secureTextEntry={!showPassword}
|
||||
type="password"
|
||||
setShowPassword={setShowPassword}
|
||||
showPassword={showPassword}
|
||||
/>
|
||||
|
||||
{/* 确认密码 */}
|
||||
<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>
|
||||
<TextInput
|
||||
label={t('auth.signup.confirmPassword', { ns: 'login' })}
|
||||
placeholder={t('auth.signup.confirmPasswordPlaceholder', { ns: 'login' })}
|
||||
autoCapitalize="none"
|
||||
value={confirmPassword}
|
||||
onChangeText={(value) => {
|
||||
handleConfirmPasswordChange(value)
|
||||
setError('123')
|
||||
}}
|
||||
secureTextEntry={!showSecondPassword}
|
||||
type="password"
|
||||
setShowPassword={setShowSecondPassword}
|
||||
showPassword={showSecondPassword}
|
||||
/>
|
||||
|
||||
{/* 注册按钮 */}
|
||||
<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>
|
||||
<Button isLoading={loading} handleLogin={handleSubmit} text={t("auth.signup.signupButton", { ns: 'login' })} />
|
||||
|
||||
<View style={styles.termsContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
@ -259,68 +215,68 @@ const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword, setSh
|
||||
]}
|
||||
>
|
||||
{checked && (
|
||||
<Ionicons name="checkmark" size={14} color="white" />
|
||||
<Ionicons name="checkmark" size={14} color={Fonts['textSecondary']} />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<View style={styles.termsTextContainer}>
|
||||
<ThemedText style={styles.termsText}>
|
||||
<ThemedText style={styles.termsText} type="sfPro">
|
||||
{t("auth.telLogin.agree", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<TouchableOpacity onPress={() => {
|
||||
setModalType('terms');
|
||||
setPrivacyModalVisible(true);
|
||||
}}>
|
||||
<ThemedText style={styles.termsLink}>
|
||||
<ThemedText style={styles.termsLink} type="sfPro">
|
||||
{t("auth.telLogin.terms", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
<ThemedText style={styles.termsText}>
|
||||
<ThemedText style={styles.termsText} type="sfPro">
|
||||
{t("auth.telLogin.and", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<TouchableOpacity onPress={() => {
|
||||
setModalType('privacy');
|
||||
setPrivacyModalVisible(true);
|
||||
}}>
|
||||
<ThemedText style={styles.termsLink}>
|
||||
<ThemedText style={styles.termsLink} type="sfPro">
|
||||
{t("auth.telLogin.privacyPolicy", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
<ThemedText style={styles.termsText}>
|
||||
<ThemedText style={styles.termsText} type="sfPro">
|
||||
{t("auth.telLogin.and", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<TouchableOpacity onPress={() => {
|
||||
setModalType('user');
|
||||
setPrivacyModalVisible(true);
|
||||
}}>
|
||||
<ThemedText style={styles.termsLink}>
|
||||
<ThemedText style={styles.termsLink} type="sfPro">
|
||||
{t("auth.telLogin.userAgreement", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
<ThemedText style={styles.termsText}>
|
||||
<ThemedText style={styles.termsText} type="sfPro">
|
||||
{t("auth.telLogin.and", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<TouchableOpacity onPress={() => {
|
||||
setModalType('ai');
|
||||
setPrivacyModalVisible(true);
|
||||
}}>
|
||||
<ThemedText style={styles.termsLink}>
|
||||
<ThemedText style={styles.termsLink} type="sfPro">
|
||||
{t("auth.telLogin.aiAgreement", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
<ThemedText style={styles.termsText}>
|
||||
<ThemedText style={styles.termsText} type="sfPro">
|
||||
{t("auth.telLogin.agreement", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.termsLink}>
|
||||
<ThemedText style={styles.termsLink} type="sfPro">
|
||||
{t("common.name")}
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.termsText}>
|
||||
<ThemedText style={styles.termsText} type="sfPro">
|
||||
{t("auth.telLogin.getPhone", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
{/* 已有账号 */}
|
||||
<View style={styles.loginContainer}>
|
||||
<ThemedText style={styles.loginText}>
|
||||
<View style={styles.loginContainer} >
|
||||
<ThemedText type="sfPro" color="textPrimary" size="sm">
|
||||
{t("auth.signup.haveAccount", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
<TouchableOpacity
|
||||
@ -328,7 +284,7 @@ const SignUp = ({ updateUrlParam, setError, setShowPassword, showPassword, setSh
|
||||
updateUrlParam("status", "login");
|
||||
}}
|
||||
>
|
||||
<ThemedText style={styles.loginLink}>
|
||||
<ThemedText type="sfPro" color="bgSecondary" weight="bold" size="sm">
|
||||
{t("auth.signup.login", { ns: 'login' })}
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
@ -395,17 +351,17 @@ const styles = StyleSheet.create({
|
||||
checkbox: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
borderColor: '#E5E7EB',
|
||||
borderRadius: 6,
|
||||
borderWidth: 1,
|
||||
borderColor: Fonts['textPrimary'],
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 8,
|
||||
marginTop: 2,
|
||||
},
|
||||
checkboxChecked: {
|
||||
backgroundColor: '#E2793F',
|
||||
borderColor: '#E2793F',
|
||||
backgroundColor: Fonts["bgCheck"],
|
||||
borderColor: Fonts['bgCheck'],
|
||||
},
|
||||
termsTextContainer: {
|
||||
flexDirection: 'row',
|
||||
@ -414,29 +370,20 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
termsText: {
|
||||
fontSize: 14,
|
||||
color: '#1F2937',
|
||||
color: Fonts["textPrimary"],
|
||||
lineHeight: 20,
|
||||
},
|
||||
termsLink: {
|
||||
fontSize: 14,
|
||||
color: '#E2793F',
|
||||
lineHeight: 20,
|
||||
color: Fonts['bgSecondary'],
|
||||
lineHeight: 20
|
||||
},
|
||||
loginContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
marginTop: 24,
|
||||
},
|
||||
loginText: {
|
||||
fontSize: 14,
|
||||
color: '#1F2937',
|
||||
},
|
||||
loginLink: {
|
||||
color: '#E2793F',
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
marginLeft: 4,
|
||||
},
|
||||
gap: 4,
|
||||
}
|
||||
});
|
||||
|
||||
export default SignUp;
|
||||
@ -14,6 +14,7 @@ interface CustomTextInputProps {
|
||||
type?: 'default' | 'password';
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
style?: StyleProp<TextStyle>;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
type TextInputProps = RNTextInputProps & CustomTextInputProps;
|
||||
@ -27,6 +28,7 @@ const TextInput = ({
|
||||
setError,
|
||||
showPassword,
|
||||
setShowPassword,
|
||||
error,
|
||||
style,
|
||||
containerStyle,
|
||||
...props
|
||||
@ -34,9 +36,18 @@ const TextInput = ({
|
||||
|
||||
return (
|
||||
<View style={[styles.inputContainer, containerStyle]}>
|
||||
<ThemedText style={styles.inputLabel}>
|
||||
{label}
|
||||
</ThemedText>
|
||||
<View className="w-full flex flex-row justify-between">
|
||||
<ThemedText style={styles.inputLabel}>
|
||||
{label}
|
||||
</ThemedText>
|
||||
{
|
||||
error &&
|
||||
<ThemedText color="bgSecondary" size="xxs">
|
||||
{error}
|
||||
</ThemedText>
|
||||
}
|
||||
</View>
|
||||
|
||||
<View style={styles.inputTextContainer}>
|
||||
<RNTextInput
|
||||
style={[styles.textInput, style]}
|
||||
|
||||
@ -26,6 +26,7 @@ export const Fonts = {
|
||||
// color
|
||||
bgPrimary: '#FFB645',
|
||||
bgSecondary: '#E2793F',
|
||||
bgCheck: "#FADBA1",
|
||||
bgInput: '#FFF8DE',
|
||||
textPrimary: '#AC7E35',
|
||||
textSecondary: '#4C320C',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user