feat: add 2FA and change title
This commit is contained in:
@@ -787,6 +787,7 @@ export interface PublicSettings {
|
||||
crypto_only: boolean;
|
||||
nowpayments_currencies: string[];
|
||||
telegram_notifications_enabled: boolean;
|
||||
two_fa_enabled: boolean;
|
||||
}
|
||||
|
||||
export const getPublicSettings = async (): Promise<PublicSettings> => {
|
||||
@@ -801,6 +802,7 @@ export const getPublicSettings = async (): Promise<PublicSettings> => {
|
||||
crypto_only: false,
|
||||
nowpayments_currencies: [],
|
||||
telegram_notifications_enabled: false,
|
||||
two_fa_enabled: false,
|
||||
};
|
||||
try {
|
||||
const { data } = await apiClient.get(`${V1}/app-settings`);
|
||||
@@ -821,6 +823,7 @@ export const getPublicSettings = async (): Promise<PublicSettings> => {
|
||||
: [],
|
||||
telegram_notifications_enabled:
|
||||
data.telegram_notifications_enabled ?? false,
|
||||
two_fa_enabled: data.two_fa_enabled ?? false,
|
||||
};
|
||||
} catch {
|
||||
return defaults;
|
||||
@@ -889,6 +892,30 @@ export const unlinkTelegram = async (): Promise<void> => {
|
||||
}
|
||||
};
|
||||
|
||||
export const get2FAStatus = async (): Promise<{
|
||||
two_fa_enabled: boolean;
|
||||
telegram_linked: boolean;
|
||||
admin_2fa_enabled: boolean;
|
||||
}> => {
|
||||
try {
|
||||
const { data } = await apiClient.get(`${V1}/two-fa/status`);
|
||||
return data;
|
||||
} catch {
|
||||
return { two_fa_enabled: false, telegram_linked: false, admin_2fa_enabled: false };
|
||||
}
|
||||
};
|
||||
|
||||
export const toggle2FA = async (
|
||||
enabled: boolean,
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
try {
|
||||
const { data } = await apiClient.post(`${V1}/two-fa/toggle`, { enabled });
|
||||
return { success: data.success ?? true };
|
||||
} catch (e: any) {
|
||||
return { success: false, error: e?.response?.data?.error || "Erreur" };
|
||||
}
|
||||
};
|
||||
|
||||
export const calculateOrderTotal = (order: any): number => {
|
||||
if (typeof order.total === "number" && order.total > 0) return order.total;
|
||||
if (typeof order.total_prix === "number" && order.total_prix > 0)
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function OrderHistoryScreen() {
|
||||
const [orders, setOrders] = useState<CompletedOrder[]>([]);
|
||||
const [stats, setStats] = useState<ClientStats | null>(null);
|
||||
const [penalties, setPenalties] = useState<PenaltyInfo | null>(null);
|
||||
const [appSettings, setAppSettings] = useState<PublicSettings>({ penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true, referral_enabled: false, pool_names: ['Pool 1', 'Pool 2'], crypto_payment_enabled: false, nowpayments_currencies: [], crypto_only: false, telegram_notifications_enabled: false });
|
||||
const [appSettings, setAppSettings] = useState<PublicSettings>({ penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true, referral_enabled: false, pool_names: ['Pool 1', 'Pool 2'], crypto_payment_enabled: false, nowpayments_currencies: [], crypto_only: false, telegram_notifications_enabled: false, two_fa_enabled: false });
|
||||
const [referralBalance, setReferralBalance] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
Switch,
|
||||
Alert,
|
||||
Modal,
|
||||
KeyboardAvoidingView,
|
||||
@@ -17,7 +18,7 @@ import { Feather } from "@expo/vector-icons";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { getMyProfile, updateMyProfile, getTelegramStatus, generateTelegramLinkToken, unlinkTelegram, changePassword } from "../../api/api";
|
||||
import { getMyProfile, updateMyProfile, getTelegramStatus, generateTelegramLinkToken, unlinkTelegram, changePassword, get2FAStatus, toggle2FA, getPublicSettings } from "../../api/api";
|
||||
import TextInput from "../../components/ui/TextInput";
|
||||
import Button from "../../components/ui/Button";
|
||||
import { useTheme } from "../../context/ThemeContext";
|
||||
@@ -49,6 +50,11 @@ export default function ProfileScreen() {
|
||||
const [telegramEnabled, setTelegramEnabled] = useState(false);
|
||||
const [telegramLoading, setTelegramLoading] = useState(false);
|
||||
|
||||
// 2FA
|
||||
const [twoFAEnabled, setTwoFAEnabled] = useState(false);
|
||||
const [twoFAAdminEnabled, setTwoFAAdminEnabled] = useState(false);
|
||||
const [twoFALoading, setTwoFALoading] = useState(false);
|
||||
|
||||
// Modal confirmation infos par défaut
|
||||
const [showSaveModal, setShowSaveModal] = useState(false);
|
||||
|
||||
@@ -64,15 +70,19 @@ export default function ProfileScreen() {
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoadingProfile(true);
|
||||
const [savedAddress, savedPhone, savedSignal, profileRes, tgStatus] = await Promise.all([
|
||||
const [savedAddress, savedPhone, savedSignal, profileRes, tgStatus, twoFAStatus, pubSettings] = await Promise.all([
|
||||
AsyncStorage.getItem(STORAGE_ADDRESS),
|
||||
AsyncStorage.getItem(STORAGE_PHONE),
|
||||
AsyncStorage.getItem(STORAGE_SIGNAL),
|
||||
getMyProfile(),
|
||||
getTelegramStatus(),
|
||||
get2FAStatus(),
|
||||
getPublicSettings(),
|
||||
]);
|
||||
setTelegramLinked(tgStatus.linked);
|
||||
setTelegramEnabled(tgStatus.enabled);
|
||||
setTwoFAEnabled(twoFAStatus.two_fa_enabled);
|
||||
setTwoFAAdminEnabled(pubSettings.two_fa_enabled);
|
||||
|
||||
if (savedAddress !== null) setDefaultAddress(savedAddress);
|
||||
if (savedPhone !== null) setDefaultPhone(savedPhone);
|
||||
@@ -136,6 +146,7 @@ export default function ProfileScreen() {
|
||||
onPress: async () => {
|
||||
await unlinkTelegram();
|
||||
setTelegramLinked(false);
|
||||
setTwoFAEnabled(false);
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -170,6 +181,17 @@ export default function ProfileScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggle2FA = async (value: boolean) => {
|
||||
setTwoFALoading(true);
|
||||
const res = await toggle2FA(value);
|
||||
setTwoFALoading(false);
|
||||
if (res.success) {
|
||||
setTwoFAEnabled(value);
|
||||
} else {
|
||||
Alert.alert("Erreur", res.error || "Impossible de modifier la 2FA");
|
||||
}
|
||||
};
|
||||
|
||||
const saveContact = async () => {
|
||||
setSavingContact(true);
|
||||
const res = await updateMyProfile({ nom: nom.trim(), prenom: prenom.trim(), telephone: telephone.trim() });
|
||||
@@ -327,6 +349,17 @@ export default function ProfileScreen() {
|
||||
},
|
||||
pwdInputIcon: { marginRight: spacing.s },
|
||||
pwdInput: { flex: 1, fontSize: fontSize.sm },
|
||||
twoFARow: {
|
||||
flexDirection: "row" as const,
|
||||
alignItems: "center" as const,
|
||||
justifyContent: "space-between" as const,
|
||||
paddingTop: spacing.xs,
|
||||
},
|
||||
twoFALabel: {
|
||||
color: colors.textPrimary,
|
||||
fontSize: fontSize.sm,
|
||||
fontWeight: fontWeight.semibold,
|
||||
},
|
||||
}), [colors]);
|
||||
|
||||
if (loadingProfile) {
|
||||
@@ -505,6 +538,42 @@ export default function ProfileScreen() {
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Carte 2FA — visible uniquement si l'admin l'a activé ET Telegram est lié */}
|
||||
{twoFAAdminEnabled && telegramLinked && (
|
||||
<View style={styles.card}>
|
||||
<View style={styles.cardTitle}>
|
||||
<Ionicons name="shield-checkmark-outline" size={18} color="#6366f1" />
|
||||
<Text style={styles.cardTitleText}>Double authentification (2FA)</Text>
|
||||
</View>
|
||||
<Text style={styles.hint}>
|
||||
À chaque connexion, un code à 6 chiffres vous sera envoyé sur Telegram avant d'accéder à votre compte.
|
||||
</Text>
|
||||
<View style={styles.twoFARow}>
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={styles.twoFALabel}>
|
||||
{twoFAEnabled ? "Activée" : "Désactivée"}
|
||||
</Text>
|
||||
{twoFAEnabled && (
|
||||
<View style={{ flexDirection: "row", alignItems: "center", gap: spacing.xs, marginTop: 2 }}>
|
||||
<Ionicons name="checkmark-circle" size={13} color="#10b981" />
|
||||
<Text style={{ color: "#10b981", fontSize: fontSize.xs }}>Protection activée</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
{twoFALoading ? (
|
||||
<ActivityIndicator size="small" color="#6366f1" />
|
||||
) : (
|
||||
<Switch
|
||||
value={twoFAEnabled}
|
||||
onValueChange={handleToggle2FA}
|
||||
trackColor={{ false: colors.border, true: "#6366f155" }}
|
||||
thumbColor={twoFAEnabled ? "#6366f1" : colors.textMuted}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<Modal
|
||||
|
||||
Reference in New Issue
Block a user