EventBuzz_Checkin_App/src/screens/Login2FAScreen.tsx

129 lines
3.6 KiB
TypeScript

import React, { useState } from 'react';
import {
ActivityIndicator,
KeyboardAvoidingView,
Platform,
Pressable,
StyleSheet,
Text,
View,
} from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import { authApi } from '../api/auth';
import type { RootStackParamList } from '../navigation/types';
import { useAuth } from '../context/AuthContext';
import { colors, radii, spacing, typography } from '../theme';
import { Card, GradientHeader, PrimaryButton, TextField } from '../components/ui';
type Props = NativeStackScreenProps<RootStackParamList, 'Login2FA'>;
export function Login2FAScreen({ navigation, route }: Props) {
const { refreshUser } = useAuth();
const { twoFactorToken, email } = route.params;
const [code, setCode] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const submit = async () => {
setError(null);
if (!code.trim()) {
setError('Enter your authenticator code');
return;
}
setLoading(true);
const res = await authApi.login2fa(twoFactorToken, code.trim());
setLoading(false);
if (res.error) {
setError(res.error);
return;
}
await refreshUser();
navigation.replace('Events');
};
return (
<KeyboardAvoidingView
style={styles.wrap}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<View style={styles.inner}>
<GradientHeader
title="Two-factor"
subtitle={`Enter the code for ${email}`}
style={styles.hero}
/>
<Card style={styles.sheet} padded={false}>
<View style={styles.sheetInner}>
{error ? (
<View style={styles.errorBox}>
<Text style={styles.errorText}>{error}</Text>
</View>
) : null}
<TextField
label="Authenticator code"
value={code}
onChangeText={setCode}
keyboardType="number-pad"
autoComplete="one-time-code"
placeholder="6-digit code"
style={styles.codeInput}
/>
<View style={styles.actions}>
<PrimaryButton title={loading ? 'Verifying…' : 'Verify'} loading={loading} onPress={submit} />
<Pressable onPress={() => navigation.goBack()} style={styles.back}>
<Text style={styles.backText}>Back to login</Text>
</Pressable>
</View>
</View>
</Card>
</View>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
wrap: { flex: 1, backgroundColor: colors.background },
inner: { flex: 1 },
hero: {
paddingTop: 72,
paddingBottom: 44,
paddingHorizontal: spacing.xl,
},
sheet: {
flex: 1,
marginTop: -28,
borderTopLeftRadius: radii.xl,
borderTopRightRadius: radii.xl,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
borderWidth: 0,
},
sheetInner: {
flex: 1,
paddingHorizontal: spacing.xl,
paddingTop: spacing.xl,
paddingBottom: spacing.xl,
},
errorBox: {
backgroundColor: 'rgba(220,38,38,0.08)',
borderWidth: 1,
borderColor: 'rgba(220,38,38,0.18)',
borderRadius: radii.md,
paddingHorizontal: 14,
paddingVertical: 12,
marginBottom: 14,
},
errorText: { ...typography.body, color: colors.error },
codeInput: {
fontSize: 20,
letterSpacing: 4,
textAlign: 'center',
},
actions: { marginTop: 18, gap: 14 },
back: { alignItems: 'center' },
backText: { color: colors.primaryDark, ...typography.bodyEm },
});