chore: build
This commit is contained in:
@@ -61,7 +61,11 @@ const LoginClient = () => {
|
||||
formData.username,
|
||||
formData.password,
|
||||
);
|
||||
if (result.success && result.access_token) {
|
||||
if (result.requires_2fa && result.session_token) {
|
||||
navigation.navigate("clientTwoFA", {
|
||||
sessionToken: result.session_token,
|
||||
});
|
||||
} else if (result.success && result.access_token) {
|
||||
await loginClient(result.access_token, result.user?.must_change_password ?? false);
|
||||
} else {
|
||||
const errorMessage =
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
import React, { useState, useMemo } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
StyleSheet,
|
||||
KeyboardAvoidingView,
|
||||
ScrollView,
|
||||
} from "react-native";
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { verifyClient2FA } from "../../api/api";
|
||||
import { useAuth } from "../../auth/AuthContext";
|
||||
import { useNavigation, useRoute, type RouteProp } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
import type { RootStackParamList } from "../../navigation/types";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
|
||||
type TwoFANavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
"clientTwoFA"
|
||||
>;
|
||||
type TwoFARouteProp = RouteProp<RootStackParamList, "clientTwoFA">;
|
||||
|
||||
const ClientTwoFAScreen = () => {
|
||||
const navigation = useNavigation<TwoFANavigationProp>();
|
||||
const route = useRoute<TwoFARouteProp>();
|
||||
const { sessionToken } = route.params;
|
||||
const { loginClient } = useAuth();
|
||||
const { colors } = useTheme();
|
||||
|
||||
const [code, setCode] = useState("");
|
||||
const [apiError, setApiError] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (code.trim().length !== 6) {
|
||||
setApiError("Le code doit contenir 6 chiffres");
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
setApiError("");
|
||||
try {
|
||||
const result = await verifyClient2FA(sessionToken, code.trim());
|
||||
if (result.success && result.access_token) {
|
||||
await loginClient(result.access_token, result.user?.must_change_password ?? false);
|
||||
} else {
|
||||
setApiError(result.message || "Code invalide");
|
||||
}
|
||||
} catch (err) {
|
||||
const message =
|
||||
err instanceof Error ? err.message : "Erreur de connexion";
|
||||
setApiError(message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeCode = (value: string) => {
|
||||
const digitsOnly = value.replace(/[^0-9]/g, "").slice(0, 6);
|
||||
setCode(digitsOnly);
|
||||
if (apiError) setApiError("");
|
||||
};
|
||||
|
||||
const styles = useMemo(
|
||||
() =>
|
||||
StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: colors.bgSecondary },
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 16,
|
||||
},
|
||||
card: {
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
borderRadius: 16,
|
||||
padding: 24,
|
||||
backgroundColor: colors.bgPrimary,
|
||||
},
|
||||
header: { alignItems: "center", marginBottom: 24 },
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: "bold",
|
||||
marginTop: 8,
|
||||
color: colors.textPrimary,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 14,
|
||||
marginTop: 4,
|
||||
textAlign: "center",
|
||||
color: colors.textMuted,
|
||||
},
|
||||
apiError: {
|
||||
backgroundColor: "#fee2e2",
|
||||
color: "#991b1b",
|
||||
padding: 12,
|
||||
borderRadius: 6,
|
||||
marginBottom: 16,
|
||||
textAlign: "center",
|
||||
},
|
||||
inputGroup: { marginBottom: 16 },
|
||||
label: { marginBottom: 4, fontWeight: "500", color: colors.textSecondary },
|
||||
inputWrapper: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.bgInput,
|
||||
},
|
||||
icon: { marginRight: 8 },
|
||||
input: {
|
||||
flex: 1,
|
||||
height: 48,
|
||||
color: colors.textPrimary,
|
||||
fontSize: 20,
|
||||
letterSpacing: 8,
|
||||
textAlign: "center",
|
||||
},
|
||||
submitButton: {
|
||||
backgroundColor: colors.accent,
|
||||
borderRadius: 8,
|
||||
padding: 12,
|
||||
alignItems: "center",
|
||||
marginTop: 8,
|
||||
},
|
||||
submitText: { color: colors.white, fontWeight: "600", fontSize: 16 },
|
||||
backButton: { alignItems: "center", marginTop: 16 },
|
||||
backText: { color: colors.textMuted, fontSize: 14 },
|
||||
}),
|
||||
[colors],
|
||||
);
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView style={styles.container} behavior="padding">
|
||||
<ScrollView
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.card}>
|
||||
<View style={styles.header}>
|
||||
<Feather name="shield" size={48} color={colors.accent} />
|
||||
<Text style={styles.title}>Vérification en deux étapes</Text>
|
||||
<Text style={styles.subtitle}>
|
||||
Entrez le code à 6 chiffres envoyé sur Telegram
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{apiError ? (
|
||||
<Text style={styles.apiError}>⚠️ {apiError}</Text>
|
||||
) : null}
|
||||
|
||||
<View style={styles.inputGroup}>
|
||||
<Text style={styles.label}>Code de vérification</Text>
|
||||
<View style={styles.inputWrapper}>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="000000"
|
||||
placeholderTextColor={colors.textMuted}
|
||||
value={code}
|
||||
onChangeText={handleChangeCode}
|
||||
editable={!isLoading}
|
||||
keyboardType="number-pad"
|
||||
maxLength={6}
|
||||
autoFocus
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={[styles.submitButton, isLoading && { opacity: 0.6 }]}
|
||||
onPress={handleSubmit}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ActivityIndicator color={colors.white} />
|
||||
) : (
|
||||
<Text style={styles.submitText}>Vérifier</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity
|
||||
style={styles.backButton}
|
||||
onPress={() => navigation.goBack()}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<Text style={styles.backText}>Retour à la connexion</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClientTwoFAScreen;
|
||||
Reference in New Issue
Block a user