Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CONST/SENTRY_LABEL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2777,6 +2777,7 @@ const SENTRY_LABEL = {
ADD_COPILOT_ACCESS_LEVEL: 'SettingsSecurity-AddCopilotAccessLevel',
ADD_COPILOT_CONFIRM: 'SettingsSecurity-AddCopilotConfirm',
DEVICE_REVOKE: 'SettingsSecurity-DeviceRevoke',
SIGN_OUT_ALL_DEVICES: 'SettingsSecurity-SignOutAllDevices',
LOCK_ACCOUNT_SUBMIT: 'SettingsSecurity-LockAccountSubmit',
MERGE_ACCOUNTS_NEXT: 'SettingsSecurity-MergeAccountsNext',
MERGE_RESULT_CONFIRM: 'SettingsSecurity-MergeResultConfirm',
Expand Down
2 changes: 2 additions & 0 deletions src/components/ConfirmContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ function ConfirmContent({
onPress={onConfirm}
size={CONST.BUTTON_SIZE.LARGE}
accessibilityLabel={confirmText || translate('common.yes')}
accessibilityState={{busy: !!isConfirmLoading}}
isDisabled={(isOffline && shouldDisableConfirmButtonWhenOffline) || isConfirmDisabled}
isLoading={isConfirmLoading}
sentryLabel={CONST.SENTRY_LABEL.CONFIRM_CONTENT.CONFIRM_BUTTON}
Expand Down Expand Up @@ -272,6 +273,7 @@ function ConfirmContent({
variant={confirmButtonVariant}
style={[styles.flex1]}
onPress={onConfirm}
accessibilityState={{busy: !!isConfirmLoading}}
isDisabled={(isOffline && shouldDisableConfirmButtonWhenOffline) || isConfirmDisabled}
isLoading={isConfirmLoading}
sentryLabel={CONST.SENTRY_LABEL.CONFIRM_CONTENT.CONFIRM_BUTTON}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@ import Animated, {useAnimatedStyle} from 'react-native-reanimated';

import type GrowlNotificationContainerProps from './types';

function GrowlNotificationContainer({children, progress, inactiveY}: GrowlNotificationContainerProps) {
function GrowlNotificationContainer({children, progress, inactiveY, useBottomPosition}: GrowlNotificationContainerProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const insets = useSafeAreaInsets();
// `progress` is a SharedValue read via .get() inside the worklet, so it's intentionally
// omitted from the deps array — only the plain JS value `inactiveY` needs to invalidate the style.
const animatedStyles = useAnimatedStyle(() => ({transform: [{translateY: inactiveY * (1 - progress.get())}]}), [inactiveY]);

return <Animated.View style={[StyleUtils.getPlatformSafeAreaPadding(insets), styles.growlNotificationContainer, animatedStyles]}>{children}</Animated.View>;
const containerStyle = useBottomPosition ? [styles.growlNotificationContainerBottomRight, styles.pAbsolute] : styles.growlNotificationContainer;

return (
<Animated.View
pointerEvents={useBottomPosition ? 'box-none' : undefined}
style={[StyleUtils.getPlatformSafeAreaPadding(insets), containerStyle, animatedStyles]}
>
{children}
</Animated.View>
);
}

export default GrowlNotificationContainer;
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,10 @@ function GrowlNotificationContent({bodyText, type, duration, action, position, n

return (
<GrowlNotificationPortal>
<View style={styles.growlNotificationWrapper}>
<View
style={[styles.growlNotificationWrapper, useBottomPosition && styles.fullScreen]}
pointerEvents={useBottomPosition ? 'box-none' : undefined}
>
<GrowlNotificationContainer
progress={progress}
inactiveY={inactiveY}
Expand Down

This file was deleted.

69 changes: 65 additions & 4 deletions src/components/Modal/Global/ConfirmModalWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import type {ConfirmModalProps} from '@components/ConfirmModal';
import ConfirmModal from '@components/ConfirmModal';
import FormHelpMessage from '@components/FormHelpMessage';
import Text from '@components/Text';

import useActiveElementRole from '@hooks/useActiveElementRole';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';

import Log from '@libs/Log';

import CONST from '@src/CONST';

import React, {useState} from 'react';
import React, {useRef, useState} from 'react';

import type {ModalProps} from './ModalContext';

Expand All @@ -17,21 +23,60 @@ type ConfirmModalWrapperProps = ModalProps &
Omit<ConfirmModalProps, 'onConfirm' | 'onCancel' | 'isVisible'> & {
/** Whether a backdrop tap should also hide the modal after running onBackdropPress. Leave it off when onBackdropPress is meant to keep the modal open */
shouldHideOnBackdropPress?: boolean;

/** Resolves without a value to close, or with a localized error to keep the modal open for retry. */
onConfirm?: () => Promise<string | void>;
};

// This wrapper bridges the ConfirmModal API with the global modal system, providing handlers for the onConfirm and onCancel callbacks to ConfirmModal.
// TODOS after migrating all ConfirmModal instances to use showConfirmModal:
// - handle closeModal inside ConfirmModal
// - remove ConfirmModalWrapper

function ConfirmModalWrapper({closeModal, onModalHide, resolveModal, onBackdropPress, shouldHideOnBackdropPress = false, ...props}: ConfirmModalWrapperProps) {
function ConfirmModalWrapper({closeModal, onModalHide, resolveModal, onBackdropPress, shouldHideOnBackdropPress = false, onConfirm, ...props}: ConfirmModalWrapperProps) {
const activeElementRole = useActiveElementRole();
const {isOffline} = useNetwork();
const {translate} = useLocalize();
const styles = useThemeStyles();
const [isVisible, setIsVisible] = useState(true);
const [closeAction, setCloseAction] = useState<typeof ModalActions.CONFIRM | typeof ModalActions.CLOSE>(ModalActions.CLOSE);
const [isConfirmLoading, setIsConfirmLoading] = useState(false);
const [confirmationError, setConfirmationError] = useState('');
const isSubmittingRef = useRef(false);

const isConfirmDisabledWhileOffline = !!props.shouldDisableConfirmButtonWhenOffline && isOffline;

const submitConfirmation = async () => {
if (!onConfirm) {
return;
}
isSubmittingRef.current = true;
setIsConfirmLoading(true);
setConfirmationError('');
try {
const error = await onConfirm();
if (error) {
setConfirmationError(error);
} else {
setCloseAction(ModalActions.CONFIRM);
setIsVisible(false);
}
} catch (error) {
Log.hmmm('Unable to complete confirmation', {error});
setConfirmationError(translate('common.genericErrorMessage'));
}
isSubmittingRef.current = false;
setIsConfirmLoading(false);
};

const handleConfirm = () => {
if (isSubmittingRef.current || isConfirmLoading || props.isConfirmLoading || props.isConfirmDisabled || isConfirmDisabledWhileOffline) {
return;
}
if (onConfirm) {
submitConfirmation();
return;
}
setCloseAction(ModalActions.CONFIRM);
// If isConfirmLoading is passed, don't close immediately - show loading state instead
// The caller should use closeModal() from useConfirmModal when the async operation completes
Expand All @@ -46,6 +91,9 @@ function ConfirmModalWrapper({closeModal, onModalHide, resolveModal, onBackdropP
};

const handleCancel = () => {
if (isSubmittingRef.current) {
return;
}
setCloseAction(ModalActions.CLOSE);
setIsVisible(false);
};
Expand All @@ -68,8 +116,6 @@ function ConfirmModalWrapper({closeModal, onModalHide, resolveModal, onBackdropP
};

// Mirror the confirm button's disabled state so the Enter shortcut can't bypass a button that is visually disabled.
const isConfirmDisabledWhileOffline = !!props.shouldDisableConfirmButtonWhenOffline && isOffline;

const shortcutConfig = {
isActive: activeElementRole !== CONST.ROLE.BUTTON && !isConfirmLoading && !isConfirmDisabledWhileOffline && !props.isConfirmDisabled,
shouldPreventDefault: false,
Expand All @@ -81,12 +127,27 @@ function ConfirmModalWrapper({closeModal, onModalHide, resolveModal, onBackdropP
return (
<ConfirmModal
{...props}
prompt={
onConfirm ? (
<>
{typeof props.prompt === 'string' ? <Text style={props.promptStyles}>{props.prompt}</Text> : props.prompt}
<FormHelpMessage
message={isConfirmDisabledWhileOffline ? translate('common.youAppearToBeOffline') : confirmationError}
isError={!isConfirmDisabledWhileOffline}
style={styles.mt4}
/>
</>
) : (
props.prompt
)
}
isVisible={isVisible}
onConfirm={handleConfirm}
onCancel={handleCancel}
onBackdropPress={handleBackdropPress}
onModalHide={handleModalHide}
isConfirmLoading={isConfirmLoading || props.isConfirmLoading}
shouldShowCancelButton={onConfirm && isConfirmLoading ? false : props.shouldShowCancelButton}
/>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function WebGenericPressable({focusable = true, ref, sentryLabel, ...props}: Pre
role={shouldRenderAsNonNativeButton ? undefined : role}
id={props.id}
aria-label={props.accessibilityLabel}
aria-busy={props['aria-busy'] ?? props.accessibilityState?.busy}
aria-labelledby={props.accessibilityLabelledBy}
aria-valuenow={props.accessibilityValue?.now}
aria-valuemin={props.accessibilityValue?.min}
Expand Down
4 changes: 4 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2675,6 +2675,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Verwalten Sie alle Geräte, bei denen Sie sich mit Ihrem Expensify Konto angemeldet haben. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Erfahren Sie mehr</a>.',
revoke: 'Widerrufen',
unknownDevice: 'Unbekanntes Gerät',
signOutAllDevices: 'Auf allen Geräten abmelden',
signOutAllDevicesConfirmation: 'Sie werden auf allen Geräten abgemeldet, einschließlich diesem. Sie müssen sich erneut anmelden, um Expensify zu nutzen.',
signOutAllDevicesError: 'Wir konnten Sie nicht auf allen Geräten abmelden. Bitte versuchen Sie es erneut.',
signOutAllDevicesSuccess: 'Sie wurden auf allen Geräten abgemeldet.',
},
twoFactorAuth: {
headerTitle: 'Zwei-Faktor-Authentifizierung',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2731,6 +2731,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Διαχειριστείτε όλες τις συσκευές στις οποίες έχετε συνδεθεί με τον λογαριασμό σας Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Μάθετε περισσότερα</a>.',
revoke: 'Ανακαλέστε',
unknownDevice: 'Άγνωστη συσκευή',
signOutAllDevices: 'Αποσύνδεση από όλες τις συσκευές',
signOutAllDevicesConfirmation: 'Θα αποσυνδεθείτε από όλες τις συσκευές, συμπεριλαμβανομένης αυτής. Θα χρειαστεί να συνδεθείτε ξανά για να χρησιμοποιήσετε το Expensify.',
signOutAllDevicesError: 'Δεν μπορέσαμε να σας αποσυνδέσουμε από όλες τις συσκευές. Δοκιμάστε ξανά.',
signOutAllDevicesSuccess: 'Αποσυνδεθήκατε από όλες τις συσκευές.',
},
twoFactorAuth: {
headerTitle: 'Έλεγχος ταυτότητας δύο παραγόντων',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2827,6 +2827,10 @@ const translations = {
'Manage all the devices that you have logged into with your Expensify Account. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Learn more</a>.',
revoke: 'Revoke',
unknownDevice: 'Unknown Device',
signOutAllDevices: 'Sign out on all devices',
signOutAllDevicesConfirmation: 'You’ll be signed out on all devices, including this one. You’ll need to sign in again to use Expensify.',
signOutAllDevicesError: 'We couldn’t sign you out on all devices. Please try again.',
signOutAllDevicesSuccess: 'You’ve been signed out on all devices.',
},
twoFactorAuth: {
headerTitle: 'Two-factor authentication',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2655,6 +2655,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Gestiona todos los dispositivos en los que has iniciado sesión con tu cuenta de Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Más información</a>.',
revoke: 'Revocar',
unknownDevice: 'Dispositivo Desconocido',
signOutAllDevices: 'Cerrar sesión en todos los dispositivos',
signOutAllDevicesConfirmation: 'Se cerrará tu sesión en todos los dispositivos, incluido este. Tendrás que volver a iniciar sesión para usar Expensify.',
signOutAllDevicesError: 'No pudimos cerrar tu sesión en todos los dispositivos. Inténtalo de nuevo.',
signOutAllDevicesSuccess: 'Se ha cerrado tu sesión en todos los dispositivos.',
},
twoFactorAuth: {
headerTitle: 'Autenticación de dos factores',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2683,6 +2683,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Gérez tous les appareils sur lesquels vous vous êtes connecté avec votre compte Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">En savoir plus</a>.',
revoke: 'Révoquer',
unknownDevice: 'Appareil Inconnu',
signOutAllDevices: 'Se déconnecter de tous les appareils',
signOutAllDevicesConfirmation: 'Vous serez déconnecté de tous les appareils, y compris celui-ci. Vous devrez vous reconnecter pour utiliser Expensify.',
signOutAllDevicesError: 'Nous n’avons pas pu vous déconnecter de tous les appareils. Veuillez réessayer.',
signOutAllDevicesSuccess: 'Vous avez été déconnecté de tous les appareils.',
},
twoFactorAuth: {
headerTitle: 'Authentification à deux facteurs',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2666,6 +2666,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Gestisci tutti i dispositivi su cui hai effettuato l\'accesso con il tuo account Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Scopri di più</a>.',
revoke: 'Revoca',
unknownDevice: 'Dispositivo Sconosciuto',
signOutAllDevices: 'Esci da tutti i dispositivi',
signOutAllDevicesConfirmation: 'Uscirai da tutti i dispositivi, incluso questo. Dovrai accedere di nuovo per usare Expensify.',
signOutAllDevicesError: 'Non è stato possibile disconnetterti da tutti i dispositivi. Riprova.',
signOutAllDevicesSuccess: 'Hai effettuato la disconnessione da tutti i dispositivi.',
},
twoFactorAuth: {
headerTitle: 'Autenticazione a due fattori',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2648,6 +2648,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Expensify アカウントでログインしているすべてのデバイスを管理できます。<a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">詳しくはこちら</a>.',
revoke: '取り消す',
unknownDevice: '不明なデバイス',
signOutAllDevices: 'すべてのデバイスからログアウト',
signOutAllDevicesConfirmation: 'このデバイスを含むすべてのデバイスからログアウトします。Expensifyを使用するには、再度ログインする必要があります。',
signOutAllDevicesError: 'すべてのデバイスからログアウトできませんでした。もう一度お試しください。',
signOutAllDevicesSuccess: 'すべてのデバイスからログアウトしました。',
},
twoFactorAuth: {
headerTitle: '2要素認証',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2664,6 +2664,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Beheer alle apparaten waarop je bent ingelogd met je Expensify-account. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Meer informatie</a>.',
revoke: 'Intrekken',
unknownDevice: 'Onbekend Apparaat',
signOutAllDevices: 'Uitloggen op alle apparaten',
signOutAllDevicesConfirmation: 'Je wordt uitgelogd op alle apparaten, inclusief dit apparaat. Je moet opnieuw inloggen om Expensify te gebruiken.',
signOutAllDevicesError: 'We konden je niet op alle apparaten uitloggen. Probeer het opnieuw.',
signOutAllDevicesSuccess: 'Je bent uitgelogd op alle apparaten.',
},
twoFactorAuth: {
headerTitle: 'Tweeledige verificatie',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2711,6 +2711,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Zarządzaj wszystkimi urządzeniami, na które zalogowałeś(-aś) się za pomocą konta Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Dowiedz się więcej</a>.',
revoke: 'Cofnąć',
unknownDevice: 'Nieznane Urządzenie',
signOutAllDevices: 'Wyloguj się ze wszystkich urządzeń',
signOutAllDevicesConfirmation: 'Wylogujesz się ze wszystkich urządzeń, w tym z tego. Aby korzystać z Expensify, trzeba będzie zalogować się ponownie.',
signOutAllDevicesError: 'Nie udało się wylogować Cię ze wszystkich urządzeń. Spróbuj ponownie.',
signOutAllDevicesSuccess: 'Wylogowano Cię ze wszystkich urządzeń.',
},
twoFactorAuth: {
headerTitle: 'Uwierzytelnianie dwuskładnikowe',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2669,6 +2669,10 @@ const translations: TranslationDeepObject<typeof en> = {
'Gerencie todos os dispositivos nos quais você fez login com sua conta Expensify. <a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">Saiba mais</a>.',
revoke: 'Revogar',
unknownDevice: 'Dispositivo Desconhecido',
signOutAllDevices: 'Sair em todos os dispositivos',
signOutAllDevicesConfirmation: 'Você sairá da sua conta em todos os dispositivos, incluindo este. Será necessário entrar novamente para usar o Expensify.',
signOutAllDevicesError: 'Não foi possível sair da sua conta em todos os dispositivos. Tente novamente.',
signOutAllDevicesSuccess: 'Você saiu da sua conta em todos os dispositivos.',
},
twoFactorAuth: {
headerTitle: 'Autenticação de dois fatores',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2569,6 +2569,10 @@ const translations: TranslationDeepObject<typeof en> = {
description: '管理所有已使用你的 Expensify 账户登录的设备。<a href="https://help.expensify.com/articles/new-expensify/settings/Manage-Logged-in-Devices">了解详情</a>.',
revoke: '撤销',
unknownDevice: '未知设备',
signOutAllDevices: '退出所有设备上的登录',
signOutAllDevicesConfirmation: '你将退出所有设备上的登录,包括此设备。你需要重新登录才能使用 Expensify。',
signOutAllDevicesError: '我们无法退出你在所有设备上的登录。请重试。',
signOutAllDevicesSuccess: '你已退出所有设备上的登录。',
},
twoFactorAuth: {
headerTitle: '双重身份验证',
Expand Down
Loading
Loading