feat(auth): add user identity verification system with new screens
Add comprehensive identity verification flow including: - New verification guide screen at /verification-guide for selecting identity type - New verification form screen at /verification-form for submitting verification documents - Verification settings screen at /profile/verification for managing verification status - Move terms and privacy policy to root-level routes (/terms, /privacy) - Update login to require active terms agreement checkbox - Database: add in-memory fallback when OPFS is unavailable on web BREAKING CHANGE: Terms and privacy policy routes moved from /profile/terms and /profile/privacy to /terms and /privacy
This commit is contained in:
@@ -53,6 +53,7 @@ export const LoginScreen: React.FC = () => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [agreedToTerms, setAgreedToTerms] = useState(false);
|
||||
|
||||
// 动画值
|
||||
const fadeAnim = useRef(new Animated.Value(0)).current;
|
||||
@@ -84,6 +85,10 @@ export const LoginScreen: React.FC = () => {
|
||||
showPrompt({ title: '提示', message: '请输入密码', type: 'warning' });
|
||||
return false;
|
||||
}
|
||||
if (!agreedToTerms) {
|
||||
showPrompt({ title: '提示', message: '请同意用户协议和隐私政策', type: 'warning' });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -211,6 +216,37 @@ export const LoginScreen: React.FC = () => {
|
||||
<Text style={styles.forgotPasswordText}>忘记密码?</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* 服务条款勾选 */}
|
||||
<View style={styles.termsContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => setAgreedToTerms(!agreedToTerms)}
|
||||
activeOpacity={0.8}
|
||||
style={styles.checkboxWrapper}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name={agreedToTerms ? 'checkbox-marked' : 'checkbox-blank-outline'}
|
||||
size={22}
|
||||
color={agreedToTerms ? THEME_COLORS.primary : colors.text.hint}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.termsText}>
|
||||
我已阅读并同意
|
||||
<Text
|
||||
style={styles.termsLink}
|
||||
onPress={() => router.push(hrefs.hrefProfileTerms())}
|
||||
>
|
||||
《用户协议》
|
||||
</Text>
|
||||
和
|
||||
<Text
|
||||
style={styles.termsLink}
|
||||
onPress={() => router.push(hrefs.hrefProfilePrivacy())}
|
||||
>
|
||||
《隐私政策》
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* 登录按钮 */}
|
||||
<TouchableOpacity
|
||||
style={[styles.loginButton, loading && styles.loginButtonDisabled]}
|
||||
@@ -234,15 +270,7 @@ export const LoginScreen: React.FC = () => {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* 协议提示 */}
|
||||
<View style={styles.policyFooter}>
|
||||
<Text style={styles.policyText}>
|
||||
登录即表示您同意
|
||||
<Text style={styles.policyLink} onPress={() => router.push(hrefs.hrefProfileTerms())}>《用户协议》</Text>
|
||||
和
|
||||
<Text style={styles.policyLink} onPress={() => router.push(hrefs.hrefProfilePrivacy())}>《隐私政策》</Text>
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
@@ -394,6 +422,26 @@ function createLoginStyles(colors: AppColors) {
|
||||
color: THEME_COLORS.primary,
|
||||
fontWeight: '500',
|
||||
},
|
||||
termsContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
marginBottom: 20,
|
||||
marginTop: 4,
|
||||
},
|
||||
checkboxWrapper: {
|
||||
paddingTop: 2,
|
||||
},
|
||||
termsText: {
|
||||
fontSize: 14,
|
||||
color: colors.text.secondary,
|
||||
marginLeft: 10,
|
||||
flex: 1,
|
||||
lineHeight: 22,
|
||||
},
|
||||
termsLink: {
|
||||
color: THEME_COLORS.primary,
|
||||
fontWeight: '500',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export const RegisterStep3Profile: React.FC<RegisterStepProps> = ({
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const [agreedToTerms, setAgreedToTerms] = useState(true);
|
||||
const [agreedToTerms, setAgreedToTerms] = useState(false);
|
||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
|
||||
@@ -37,7 +37,7 @@ const IDENTITY_OPTIONS = [
|
||||
key: 'teacher',
|
||||
title: '教职工',
|
||||
description: '需要提交工作证或教师资格证',
|
||||
icon: 'chalkboard-teacher',
|
||||
icon: 'account-tie',
|
||||
},
|
||||
{
|
||||
key: 'staff',
|
||||
@@ -82,10 +82,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
// 导航到认证表单页面
|
||||
router.push({
|
||||
pathname: '/auth/verification-form',
|
||||
params: { identity: selectedIdentity },
|
||||
});
|
||||
router.push(hrefs.hrefVerificationForm(selectedIdentity));
|
||||
};
|
||||
|
||||
const renderIdentityCard = (option: typeof IDENTITY_OPTIONS[0]) => {
|
||||
@@ -148,7 +145,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
<View style={styles.benefitsContainer}>
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialCommunityIcons
|
||||
name="verified"
|
||||
name="check-decagram"
|
||||
size={20}
|
||||
color={THEME_COLORS.primary}
|
||||
/>
|
||||
@@ -164,7 +161,7 @@ export const VerificationGuideScreen: React.FC = () => {
|
||||
</View>
|
||||
<View style={styles.benefitItem}>
|
||||
<MaterialCommunityIcons
|
||||
name="account-group"
|
||||
name="account-group-outline"
|
||||
size={20}
|
||||
color={THEME_COLORS.primary}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,8 @@ export { LoginScreen } from './LoginScreen';
|
||||
export { RegisterScreen } from './RegisterScreen';
|
||||
export { ForgotPasswordScreen } from './ForgotPasswordScreen';
|
||||
export { WelcomeScreen } from './WelcomeScreen';
|
||||
export { VerificationGuideScreen } from './VerificationGuideScreen';
|
||||
export { VerificationFormScreen } from './VerificationFormScreen';
|
||||
|
||||
// 注册步骤组件
|
||||
export { RegisterStep1Email } from './RegisterStep1Email';
|
||||
|
||||
@@ -254,7 +254,7 @@ export const SettingsScreen: React.FC = () => {
|
||||
router.push(hrefs.hrefProfileEdit());
|
||||
break;
|
||||
case 'verification':
|
||||
router.push('/profile/verification');
|
||||
router.push(hrefs.hrefProfileVerification());
|
||||
break;
|
||||
case 'notification_settings':
|
||||
router.push(hrefs.hrefProfileNotifications());
|
||||
|
||||
@@ -84,7 +84,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
const handleStartVerification = () => {
|
||||
router.push('/auth/verification-guide');
|
||||
router.push(hrefs.hrefVerificationGuide());
|
||||
};
|
||||
|
||||
const handleViewRecords = () => {
|
||||
@@ -109,27 +109,10 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
const identityText = IDENTITY_TEXT[status?.identity || 'general'];
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<TouchableOpacity
|
||||
style={styles.backButton}
|
||||
onPress={() => router.back()}
|
||||
activeOpacity={0.8}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name="arrow-left"
|
||||
size={24}
|
||||
color={colors.text?.primary || '#333'}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>身份认证</Text>
|
||||
<View style={styles.headerRight} />
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{/* 状态卡片 */}
|
||||
<View style={styles.statusCard}>
|
||||
<View style={[styles.statusIconContainer, { backgroundColor: statusConfig.color + '20' }]}>
|
||||
@@ -167,7 +150,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
<View style={styles.benefitItem}>
|
||||
<View style={styles.benefitIconContainer}>
|
||||
<MaterialCommunityIcons
|
||||
name="verified"
|
||||
name="check-decagram"
|
||||
size={24}
|
||||
color={THEME_COLORS.primary}
|
||||
/>
|
||||
@@ -183,7 +166,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
<View style={styles.benefitItem}>
|
||||
<View style={styles.benefitIconContainer}>
|
||||
<MaterialCommunityIcons
|
||||
name="lock-open-variant"
|
||||
name="lock-open-outline"
|
||||
size={24}
|
||||
color={THEME_COLORS.primary}
|
||||
/>
|
||||
@@ -199,7 +182,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
<View style={styles.benefitItem}>
|
||||
<View style={styles.benefitIconContainer}>
|
||||
<MaterialCommunityIcons
|
||||
name="account-group"
|
||||
name="account-group-outline"
|
||||
size={24}
|
||||
color={THEME_COLORS.primary}
|
||||
/>
|
||||
@@ -278,7 +261,6 @@ export const VerificationSettingsScreen: React.FC = () => {
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export { AccountSecurityScreen } from './AccountSecurityScreen';
|
||||
export { AboutScreen } from './AboutScreen';
|
||||
export { TermsOfServiceScreen } from './TermsOfServiceScreen';
|
||||
export { PrivacyPolicyScreen } from './PrivacyPolicyScreen';
|
||||
export { VerificationSettingsScreen } from './VerificationSettingsScreen';
|
||||
|
||||
// 导出 Hook 供需要自定义的场景使用
|
||||
export { useUserProfile, createSharedProfileStyles, TABS, TAB_ICONS } from './useUserProfile';
|
||||
|
||||
Reference in New Issue
Block a user