mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-04 16:06:41 +00:00
fix: ask before signing out
This commit is contained in:
@@ -1129,6 +1129,11 @@
|
|||||||
"remove_account_message": "Von {account} auf diesem Gerät abmelden. Ihre E-Mails bleiben auf dem Server.",
|
"remove_account_message": "Von {account} auf diesem Gerät abmelden. Ihre E-Mails bleiben auf dem Server.",
|
||||||
"remove_account_title": "Konto entfernen?",
|
"remove_account_title": "Konto entfernen?",
|
||||||
"show_unread": "Ungelesene anzeigen",
|
"show_unread": "Ungelesene anzeigen",
|
||||||
|
"sign_out_all_confirm_message": "Alle Konten werden von diesem Gerät entfernt. Sie können sich jederzeit wieder anmelden.",
|
||||||
|
"sign_out_all_confirm_title": "Von allen Konten abmelden?",
|
||||||
|
"sign_out_confirm_message": "Das Konto wird von diesem Gerät entfernt. Sie können sich jederzeit wieder anmelden.",
|
||||||
|
"sign_out_confirm_title": "Abmelden?",
|
||||||
|
"sign_out_confirm_title_account": "Von {account} abmelden?",
|
||||||
"tab_unavailable": "{name} (nicht verfügbar)"
|
"tab_unavailable": "{name} (nicht verfügbar)"
|
||||||
},
|
},
|
||||||
"unified_mailbox": {
|
"unified_mailbox": {
|
||||||
|
|||||||
@@ -1148,6 +1148,11 @@
|
|||||||
"remove_account_message": "Sign out of {account} on this device. Your mail stays on the server.",
|
"remove_account_message": "Sign out of {account} on this device. Your mail stays on the server.",
|
||||||
"remove_account_title": "Remove account?",
|
"remove_account_title": "Remove account?",
|
||||||
"show_unread": "Show unread",
|
"show_unread": "Show unread",
|
||||||
|
"sign_out_all_confirm_message": "Every account is removed from this device. You can sign in again at any time.",
|
||||||
|
"sign_out_all_confirm_title": "Sign out of all accounts?",
|
||||||
|
"sign_out_confirm_message": "The account is removed from this device. You can sign in again at any time.",
|
||||||
|
"sign_out_confirm_title": "Sign out?",
|
||||||
|
"sign_out_confirm_title_account": "Sign out of {account}?",
|
||||||
"tab_unavailable": "{name} (unavailable)"
|
"tab_unavailable": "{name} (unavailable)"
|
||||||
},
|
},
|
||||||
"unified_mailbox": {
|
"unified_mailbox": {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|||||||
import type { RootStackParamList } from '../navigation/types';
|
import type { RootStackParamList } from '../navigation/types';
|
||||||
import type { Mailbox } from '../api/types';
|
import type { Mailbox } from '../api/types';
|
||||||
import { MODAL_ORIENTATIONS } from '../lib/modal-orientations';
|
import { MODAL_ORIENTATIONS } from '../lib/modal-orientations';
|
||||||
|
import { confirmSignOut } from '../lib/sign-out-confirm';
|
||||||
|
|
||||||
const CHEVRON_SLOT = 20;
|
const CHEVRON_SLOT = 20;
|
||||||
const INDENT_STEP = 12;
|
const INDENT_STEP = 12;
|
||||||
@@ -919,7 +920,10 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side
|
|||||||
styles.accountMenuAction,
|
styles.accountMenuAction,
|
||||||
pressed && styles.accountMenuActionPressed,
|
pressed && styles.accountMenuActionPressed,
|
||||||
]}
|
]}
|
||||||
onPress={() => { setAccountMenuOpen(false); onClose(); void logout(); }}
|
onPress={() => {
|
||||||
|
setAccountMenuOpen(false);
|
||||||
|
confirmSignOut(t, { account: accountEmailFull || undefined }, () => { onClose(); void logout(); });
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<LogOut size={16} color={c.textSecondary} />
|
<LogOut size={16} color={c.textSecondary} />
|
||||||
<Text style={styles.accountMenuActionText} numberOfLines={1}>
|
<Text style={styles.accountMenuActionText} numberOfLines={1}>
|
||||||
@@ -932,7 +936,10 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side
|
|||||||
styles.accountMenuAction,
|
styles.accountMenuAction,
|
||||||
pressed && styles.accountMenuActionPressed,
|
pressed && styles.accountMenuActionPressed,
|
||||||
]}
|
]}
|
||||||
onPress={() => { setAccountMenuOpen(false); onClose(); void logoutAll(); }}
|
onPress={() => {
|
||||||
|
setAccountMenuOpen(false);
|
||||||
|
confirmSignOut(t, { all: true }, () => { onClose(); void logoutAll(); });
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<LogOut size={16} color={c.error} />
|
<LogOut size={16} color={c.error} />
|
||||||
<Text style={[styles.accountMenuActionText, { color: c.error }]}>
|
<Text style={[styles.accountMenuActionText, { color: c.error }]}>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
|
||||||
|
const alert = vi.fn();
|
||||||
|
vi.mock('react-native', () => ({ Alert: { alert: (...a: unknown[]) => alert(...a) } }));
|
||||||
|
|
||||||
|
import { confirmSignOut } from '../sign-out-confirm';
|
||||||
|
|
||||||
|
const t = (_key: string, fallback: string, params?: Record<string, string | number>) =>
|
||||||
|
fallback.replace(/\{(\w+)\}/g, (_, k) => String(params?.[k] ?? ''));
|
||||||
|
|
||||||
|
describe('confirmSignOut', () => {
|
||||||
|
it('asks first and signs out only from the destructive button', () => {
|
||||||
|
const onConfirm = vi.fn();
|
||||||
|
confirmSignOut(t, { account: 'usera@example.org' }, onConfirm);
|
||||||
|
expect(onConfirm).not.toHaveBeenCalled();
|
||||||
|
const [title, , buttons] = alert.mock.calls[0] as [string, string, Array<{ style?: string; onPress?: () => void }>];
|
||||||
|
expect(title).toBe('Sign out of usera@example.org?');
|
||||||
|
buttons.find((b) => b.style === 'cancel')?.onPress?.();
|
||||||
|
expect(onConfirm).not.toHaveBeenCalled();
|
||||||
|
buttons.find((b) => b.style === 'destructive')!.onPress!();
|
||||||
|
expect(onConfirm).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Alert } from 'react-native';
|
||||||
|
|
||||||
|
type Translate = (key: string, fallback: string, params?: Record<string, string | number>) => string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks before signing out: it drops the account's cached mail and push
|
||||||
|
* registration on this device, and a stray tap in the account menu or at the
|
||||||
|
* end of Settings did it straight away.
|
||||||
|
*/
|
||||||
|
export function confirmSignOut(
|
||||||
|
t: Translate,
|
||||||
|
{ account, all = false }: { account?: string; all?: boolean },
|
||||||
|
onConfirm: () => void,
|
||||||
|
): void {
|
||||||
|
const title = all
|
||||||
|
? t('sidebar.sign_out_all_confirm_title', 'Sign out of all accounts?')
|
||||||
|
: account
|
||||||
|
? t('sidebar.sign_out_confirm_title_account', 'Sign out of {account}?', { account })
|
||||||
|
: t('sidebar.sign_out_confirm_title', 'Sign out?');
|
||||||
|
const message = all
|
||||||
|
? t('sidebar.sign_out_all_confirm_message', 'Every account is removed from this device. You can sign in again at any time.')
|
||||||
|
: t('sidebar.sign_out_confirm_message', 'The account is removed from this device. You can sign in again at any time.');
|
||||||
|
Alert.alert(title, message, [
|
||||||
|
{ text: t('common.cancel', 'Cancel'), style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: all ? t('sidebar.sign_out_all', 'Sign out of all accounts') : t('sidebar.sign_out', 'Sign out'),
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: onConfirm,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { View, Text, StyleSheet, ScrollView, Pressable, TextInput, Keyboard } from 'react-native';
|
import { View, Text, StyleSheet, ScrollView, Pressable, TextInput, Keyboard } from 'react-native';
|
||||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||||
import { useLayout } from '../lib/use-layout';
|
import { useLayout } from '../lib/use-layout';
|
||||||
|
import { confirmSignOut } from '../lib/sign-out-confirm';
|
||||||
import {
|
import {
|
||||||
ArrowLeft, LogOut, Settings, ChevronRight, Search, X,
|
ArrowLeft, LogOut, Settings, ChevronRight, Search, X,
|
||||||
Palette, User, Shield, UserPen, Palmtree, Calendar,
|
Palette, User, Shield, UserPen, Palmtree, Calendar,
|
||||||
@@ -506,7 +507,8 @@ export default function SettingsScreen({ onLogout, onBack, onTabSelect }: Settin
|
|||||||
{/* Logout - matches webmail: border-t, destructive text, icon + label */}
|
{/* Logout - matches webmail: border-t, destructive text, icon + label */}
|
||||||
<View style={styles.logoutSection}>
|
<View style={styles.logoutSection}>
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={onLogout}
|
onPress={() => { if (onLogout) confirmSignOut(t, {}, onLogout); }}
|
||||||
|
accessibilityRole="button"
|
||||||
style={({ pressed }) => [styles.logoutBtn, pressed && styles.logoutBtnPressed]}
|
style={({ pressed }) => [styles.logoutBtn, pressed && styles.logoutBtnPressed]}
|
||||||
>
|
>
|
||||||
<LogOut size={16} color={c.error} />
|
<LogOut size={16} color={c.error} />
|
||||||
|
|||||||
Reference in New Issue
Block a user