轻松聘
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

217 lines
7.0 KiB

import { Ionicons } from '@expo/vector-icons';
import { useRouter } from 'expo-router';
import { useState } from 'react';
import {
ActivityIndicator,
Pressable,
ScrollView,
StyleSheet,
Text,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useIdentityOptions } from '../src/hooks';
import { useRole } from '../src/context/RoleContext';
import { useAuth } from '../src/context/AuthContext';
import { colors } from '../src/theme';
import type { IdentityOption } from '../src/types';
/* ===================== 子组件:顶部 Logo ===================== */
function LogoHeader() {
return (
<View style={styles.logoHeader}>
<View style={styles.logoCircle}>
<Text style={styles.logoText}>职</Text>
</View>
<Text style={styles.brandName}>职来</Text>
<Text style={styles.brandSlogan}>智能求职 · 企业招聘一站式服务平台</Text>
</View>
);
}
/* ===================== 子组件:身份卡片 ===================== */
function IdentityCard({
option,
selected,
onSelect,
}: {
option: IdentityOption;
selected: boolean;
onSelect: () => void;
}) {
const isCandidate = option.role === 'candidate';
return (
<Pressable
style={[styles.card, selected && styles.cardSelected]}
onPress={onSelect}
hitSlop={4}
>
<View
style={[
styles.iconWrap,
{ backgroundColor: isCandidate ? colors.primaryLight : colors.successLight },
selected && { backgroundColor: isCandidate ? colors.primary : colors.success },
]}
>
<Ionicons
name={option.icon as keyof typeof Ionicons.glyphMap}
size={28}
color={selected ? colors.textInverse : isCandidate ? colors.primary : colors.success}
/>
</View>
<Text style={[styles.cardTitle, selected && styles.cardTitleSelected]}>{option.title}</Text>
<Text style={styles.cardDesc}>{option.desc}</Text>
{selected && (
<View style={styles.check}>
<Ionicons name="checkmark-circle" size={22} color={colors.primary} />
</View>
)}
</Pressable>
);
}
/* ===================== 子组件:四态 ===================== */
function Loading() {
return (
<View style={styles.center}>
<ActivityIndicator size="large" color={colors.primary} />
<Text style={styles.tip}>正在加载…</Text>
</View>
);
}
function ErrorState({ onRetry }: { onRetry: () => void }) {
return (
<View style={styles.center}>
<Ionicons name="swap-horizontal-outline" size={40} color={colors.textHint} />
<Text style={styles.tip}>加载失败</Text>
<Pressable style={styles.retryBtn} onPress={onRetry} hitSlop={6}>
<Text style={styles.retryText}>点击重试</Text>
</Pressable>
</View>
);
}
/* ===================== 页面 ===================== */
export default function IdentitySelectScreen() {
const router = useRouter();
const { setRole } = useRole();
const { candidateSetupDone, enterpriseVerified } = useAuth();
const { options, loading, error, refresh } = useIdentityOptions();
const [selectedId, setSelectedId] = useState<string | null>(null);
/**
* 选中身份即进入:先写 role,再进入该身份的下一步(避免点到卡片无反应)。
* - 求职者:未设置求职意向 → 求职意向页;已设置 → 求职端
* - 招聘者:未完成企业认证 → 企业认证页;已完成 → 企业端
* 注意:两个路由组均无 index,故一律用显式落地路由。
*/
const handleSelect = (option: IdentityOption) => {
setSelectedId(option.id);
if (option.role === 'candidate') {
setRole('candidate');
router.replace(candidateSetupDone ? '/(tabs)/jobs' : '/job-preference');
} else {
setRole('enterprise');
router.replace(
enterpriseVerified ? '/(enterprise)/job-management' : '/enterprise-certification',
);
}
};
return (
<SafeAreaView style={styles.container} edges={['top']}>
<ScrollView contentContainerStyle={styles.body} showsVerticalScrollIndicator={false}>
<LogoHeader />
{loading ? (
<Loading />
) : error ? (
<ErrorState onRetry={refresh} />
) : (
<>
<View style={styles.cards}>
{options.map((opt) => (
<IdentityCard
key={opt.id}
option={opt}
selected={opt.id === selectedId}
onSelect={() => handleSelect(opt)}
/>
))}
</View>
<Text style={styles.hint}>点击上方身份卡片,立即进入对应端</Text>
</>
)}
</ScrollView>
<Pressable style={styles.manageLink} onPress={() => router.push('/operation-rules')}>
<Ionicons name="settings-outline" size={14} color={colors.textHint} />
<Text style={styles.manageText}>管理规则 / 风控审计</Text>
</Pressable>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: colors.pageBg },
body: { flexGrow: 1, padding: 20, paddingTop: 40 },
logoHeader: { alignItems: 'center', marginBottom: 32 },
logoCircle: {
width: 72,
height: 72,
borderRadius: 36,
backgroundColor: colors.primary,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 12,
},
logoText: { fontSize: 32, fontWeight: '800', color: colors.textInverse },
brandName: { fontSize: 24, fontWeight: '800', color: colors.textPrimary },
brandSlogan: { fontSize: 13, color: colors.textSecondary, marginTop: 6 },
cards: { flexDirection: 'row', gap: 12, marginBottom: 24 },
card: {
flex: 1,
position: 'relative',
backgroundColor: colors.white,
borderRadius: 16,
padding: 16,
borderWidth: 1.5,
borderColor: colors.borderLight,
},
cardSelected: { borderColor: colors.primary, backgroundColor: colors.primaryLight },
iconWrap: {
width: 52,
height: 52,
borderRadius: 14,
alignItems: 'center',
justifyContent: 'center',
marginBottom: 12,
},
cardTitle: { fontSize: 16, fontWeight: '700', color: colors.textPrimary },
cardTitleSelected: { color: colors.primaryText },
cardDesc: { fontSize: 12, color: colors.textSecondary, marginTop: 6, lineHeight: 18 },
check: { position: 'absolute', top: 12, right: 12 },
hint: {
marginTop: 8,
fontSize: 13,
color: colors.textHint,
textAlign: 'center',
},
manageLink: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 16,
},
manageText: { marginLeft: 6, fontSize: 13, color: colors.textHint },
center: { flex: 1, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 40, gap: 12 },
tip: { fontSize: 14, color: colors.textSecondary },
retryBtn: { paddingHorizontal: 18, paddingVertical: 8, borderRadius: 16, backgroundColor: colors.primaryLight },
retryText: { fontSize: 13, fontWeight: '600', color: colors.primaryText },
});