diff --git a/__mocks__/urbanairship-react-native.js b/__mocks__/urbanairship-react-native.js
index efbb5d87f92b..ba380b6d4a72 100644
--- a/__mocks__/urbanairship-react-native.js
+++ b/__mocks__/urbanairship-react-native.js
@@ -3,6 +3,14 @@ const EventType = {
PushReceived: 'pushReceived',
};
+const iOS = {
+ ForegroundPresentationOption: {
+ Alert: 'alert',
+ Sound: 'sound',
+ Badge: 'badge',
+ },
+};
+
const UrbanAirship = {
setUserNotificationsEnabled: jest.fn(),
clearNotifications: jest.fn(),
@@ -12,11 +20,13 @@ const UrbanAirship = {
setNamedUser: jest.fn(),
removeAllListeners: jest.fn(),
setBadgeNumber: jest.fn(),
+ setForegroundPresentationOptions: jest.fn(),
};
export default UrbanAirship;
export {
EventType,
+ iOS,
UrbanAirship,
};
diff --git a/config/webpack/webpack.desktop.js b/config/webpack/webpack.desktop.js
index 298741eed498..86012e4bf738 100644
--- a/config/webpack/webpack.desktop.js
+++ b/config/webpack/webpack.desktop.js
@@ -50,6 +50,20 @@ module.exports = (env) => {
__dirname: false,
__filename: false,
},
+ module: {
+ rules: [
+ {
+ test: /react-native-onyx/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-react'],
+
+ },
+ },
+ },
+ ],
+ },
};
return [mainProcessConfig, rendererConfig];
diff --git a/desktop/ELECTRON_EVENTS.js b/desktop/ELECTRON_EVENTS.js
index 34643447d7da..f0d9b865b01d 100644
--- a/desktop/ELECTRON_EVENTS.js
+++ b/desktop/ELECTRON_EVENTS.js
@@ -7,6 +7,7 @@ const ELECTRON_EVENTS = {
UPDATE_DOWNLOADED: 'update-downloaded',
FOCUS: 'focus',
BLUR: 'blur',
+ LOCALE_UPDATED: 'locale-updated',
};
module.exports = ELECTRON_EVENTS;
diff --git a/desktop/contextBridge.js b/desktop/contextBridge.js
index c49aa5d9e9fb..f065c4caac21 100644
--- a/desktop/contextBridge.js
+++ b/desktop/contextBridge.js
@@ -10,6 +10,7 @@ const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [
ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT,
ELECTRON_EVENTS.REQUEST_VISIBILITY,
ELECTRON_EVENTS.START_UPDATE,
+ ELECTRON_EVENTS.LOCALE_UPDATED,
];
const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [
diff --git a/desktop/main.js b/desktop/main.js
index da5e13e91e22..3ca61aa318b4 100644
--- a/desktop/main.js
+++ b/desktop/main.js
@@ -15,6 +15,8 @@ const log = require('electron-log');
const ELECTRON_EVENTS = require('./ELECTRON_EVENTS');
const checkForUpdates = require('../src/libs/checkForUpdates');
const CONFIG = require('../src/CONFIG').default;
+const CONST = require('../src/CONST');
+const Localize = require('../src/libs/Localize');
const port = process.env.PORT || 8080;
@@ -73,6 +75,11 @@ for (let i = 0; i < process.argv.length; i++) {
let hasUpdate = false;
let downloadedVersion;
+// Note that we have to subscribe to this separately and cannot use Localize.translateLocal,
+// because the only way code can be shared between the main and renderer processes at runtime is via the context bridge
+// So we track preferredLocale separately via ELECTRON_EVENTS.LOCALE_UPDATED
+let preferredLocale = CONST.DEFAULT_LOCALE;
+
const quitAndInstallWithUpdate = () => {
if (!downloadedVersion) {
return;
@@ -100,23 +107,23 @@ const manuallyCheckForUpdates = (menuItem, browserWindow) => {
if (downloadPromise) {
dialog.showMessageBox(browserWindow, {
type: 'info',
- message: 'Update Available',
- detail: 'The new version will be available shortly. We’ll notify you when we’re ready to update.',
- buttons: ['Sounds good'],
+ message: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.title'),
+ detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.available.message'),
+ buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.available.soundsGood')],
});
} else if (result && result.error) {
dialog.showMessageBox(browserWindow, {
type: 'error',
- message: 'Update Check Failed',
- detail: 'We couldn’t look for an update. Please check again in a bit!',
- buttons: ['Okay'],
+ message: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.title'),
+ detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.error.message'),
+ buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')],
});
} else {
dialog.showMessageBox(browserWindow, {
type: 'info',
- message: 'Update Not Available',
- detail: 'There is no update available as of now! Check again at a later time.',
- buttons: ['Okay'],
+ message: Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.title'),
+ detail: Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.message'),
+ buttons: [Localize.translate(preferredLocale, 'checkForUpdatesModal.notAvailable.okay')],
cancelId: 2,
});
}
@@ -141,34 +148,14 @@ const showKeyboardShortcutsModal = (browserWindow) => {
browserWindow.webContents.send(ELECTRON_EVENTS.SHOW_KEYBOARD_SHORTCUTS_MODAL);
};
-// Defines the system-level menu item to manually apply an update
-// This menu item should become visible after an update is downloaded and ready to be applied
-const updateAppMenuItem = new MenuItem({
- label: 'Update New Expensify',
- visible: false,
- click: quitAndInstallWithUpdate,
-});
-
-// System-level menu item to manually check for App updates
-const checkForUpdateMenuItem = new MenuItem({
- label: 'Check For Updates',
- visible: true,
- click: manuallyCheckForUpdates,
-});
-
-// Defines the system-level menu item for opening keyboard shortcuts modal
-const keyboardShortcutsMenu = new MenuItem({
- label: 'View Keyboard Shortcuts',
- accelerator: 'CmdOrCtrl+I',
-});
-
// Actual auto-update listeners
const electronUpdater = browserWindow => ({
init: () => {
autoUpdater.on(ELECTRON_EVENTS.UPDATE_DOWNLOADED, (info) => {
+ const systemMenu = Menu.getApplicationMenu();
downloadedVersion = info.version;
- updateAppMenuItem.visible = true;
- checkForUpdateMenuItem.visible = false;
+ systemMenu.getMenuItemById(`updateAppMenuItem-${preferredLocale}`).visible = true;
+ systemMenu.getMenuItemById(`checkForUpdateMenuItem-${preferredLocale}`).visible = false;
if (browserWindow.isVisible()) {
browserWindow.webContents.send(ELECTRON_EVENTS.UPDATE_DOWNLOADED, info.version);
} else {
@@ -184,6 +171,61 @@ const electronUpdater = browserWindow => ({
},
});
+/*
+* @param {Menu} systemMenu
+*/
+const localizeMenuItems = (browserWindow, systemMenu) => {
+ // List the Expensify Chat instance under the Window menu, even when it's hidden
+ systemMenu.insert(4, new MenuItem({
+ id: `historyMenuItem-${preferredLocale}`,
+ label: Localize.translate(preferredLocale, 'desktopApplicationMenu.history'),
+ submenu: [{
+ id: `backMenuItem-${preferredLocale}`,
+ label: Localize.translate(preferredLocale, 'historyMenu.back'),
+ accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Shift+[',
+ click: () => { browserWindow.webContents.goBack(); },
+ },
+ {
+ id: `forwardMenuItem-${preferredLocale}`,
+ label: Localize.translate(preferredLocale, 'historyMenu.forward'),
+ accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Shift+]',
+ click: () => { browserWindow.webContents.goForward(); },
+ }],
+ }));
+
+ // Defines the system-level menu item to manually apply an update
+ // This menu item should become visible after an update is downloaded and ready to be applied
+ const updateAppMenuItem = new MenuItem({
+ id: `updateAppMenuItem-${preferredLocale}`,
+ label: Localize.translate(preferredLocale, 'desktopApplicationMenu.updateExpensify'),
+ visible: false,
+ click: quitAndInstallWithUpdate,
+ });
+
+ // System-level menu item to manually check for App updates
+ const checkForUpdateMenuItem = new MenuItem({
+ id: `checkForUpdateMenuItem-${preferredLocale}`,
+ label: Localize.translate(preferredLocale, 'desktopApplicationMenu.checkForUpdates'),
+ visible: true,
+ click: manuallyCheckForUpdates,
+ });
+
+ // Defines the system-level menu item for opening keyboard shortcuts modal
+ const keyboardShortcutsMenuItem = new MenuItem({
+ id: `keyboardShortcutsMenuItem-${preferredLocale}`,
+ label: Localize.translate(preferredLocale, 'initialSettingsPage.aboutPage.viewKeyboardShortcuts'),
+ accelerator: 'CmdOrCtrl+I',
+ click: () => {
+ showKeyboardShortcutsModal(browserWindow);
+ },
+ });
+
+ const appMenu = _.find(systemMenu.items, item => item.role === 'appmenu');
+ appMenu.submenu.insert(1, updateAppMenuItem);
+ appMenu.submenu.insert(2, checkForUpdateMenuItem);
+ appMenu.submenu.insert(3, keyboardShortcutsMenuItem);
+};
+
const mainWindow = (() => {
const loadURL = __DEV__
? win => win.loadURL(`http://localhost:${port}`)
@@ -256,27 +298,7 @@ const mainWindow = (() => {
browserWindow.setTitle('New Expensify');
}
- keyboardShortcutsMenu.click = () => {
- showKeyboardShortcutsModal(browserWindow);
- };
-
- // List the Expensify Chat instance under the Window menu, even when it's hidden
const systemMenu = Menu.getApplicationMenu();
- systemMenu.insert(4, new MenuItem({
- label: 'History',
- submenu: [{
- role: 'back',
- label: 'Back',
- accelerator: process.platform === 'darwin' ? 'Cmd+[' : 'Shift+[',
- click: () => { browserWindow.webContents.goBack(); },
- },
- {
- role: 'forward',
- label: 'Forward',
- accelerator: process.platform === 'darwin' ? 'Cmd+]' : 'Shift+]',
- click: () => { browserWindow.webContents.goForward(); },
- }],
- }));
// Register the custom Paste and Match Style command and place it near the default shortcut of the same role.
const editMenu = _.find(systemMenu.items, item => item.role === 'editmenu');
@@ -285,10 +307,8 @@ const mainWindow = (() => {
accelerator: 'CmdOrCtrl+Shift+V',
}));
- const appMenu = _.find(systemMenu.items, item => item.role === 'appmenu');
- appMenu.submenu.insert(1, updateAppMenuItem);
- appMenu.submenu.insert(2, checkForUpdateMenuItem);
- appMenu.submenu.insert(3, keyboardShortcutsMenu);
+ // Append custom context menu items using the preferred language of the user.
+ localizeMenuItems(browserWindow, systemMenu);
// On mac, pressing cmd++ actually sends a cmd+=. cmd++ is generally the zoom in shortcut, but this is
// not properly listened for by electron. Adding in an invisible cmd+= listener fixes this.
@@ -374,6 +394,37 @@ const mainWindow = (() => {
app.hide();
}
+ ipcMain.on(ELECTRON_EVENTS.LOCALE_UPDATED, (event, updatedLocale) => {
+ // Store the old locale so we can hide/remove these items after adding/showing the new ones.
+ const outdatedLocale = preferredLocale;
+ preferredLocale = updatedLocale;
+
+ const currentHistoryMenuItem = systemMenu.getMenuItemById(`historyMenuItem-${outdatedLocale}`);
+ const currentUpdateAppMenuItem = systemMenu.getMenuItemById(`updateAppMenuItem-${outdatedLocale}`);
+ const currentCheckForUpdateMenuItem = systemMenu.getMenuItemById(`checkForUpdateMenuItem-${outdatedLocale}`);
+ const currentKeyboardShortcutsMenuItem = systemMenu.getMenuItemById(`keyboardShortcutsMenuItem-${outdatedLocale}`);
+
+ // If we have previously added those languages, don't add new menu items, reshow them.
+ if (!systemMenu.getMenuItemById(`updateAppMenuItem-${updatedLocale}`)) {
+ // Update the labels and ids to use the translations.
+ localizeMenuItems(browserWindow, systemMenu);
+ }
+
+ // Show the localized menu items if there were visible before we updated the locale.
+ systemMenu.getMenuItemById(`updateAppMenuItem-${updatedLocale}`).visible = currentUpdateAppMenuItem.visible;
+ systemMenu.getMenuItemById(`checkForUpdateMenuItem-${updatedLocale}`).visible = currentCheckForUpdateMenuItem.visible;
+ systemMenu.getMenuItemById(`keyboardShortcutsMenuItem-${updatedLocale}`).visible = currentKeyboardShortcutsMenuItem.visible;
+ systemMenu.getMenuItemById(`historyMenuItem-${updatedLocale}`).visible = currentHistoryMenuItem.visible;
+
+ // Since we can't remove menu items, we hide the old ones.
+ currentUpdateAppMenuItem.visible = false;
+ currentCheckForUpdateMenuItem.visible = false;
+ currentKeyboardShortcutsMenuItem.visible = false;
+ currentHistoryMenuItem.visible = false;
+
+ Menu.setApplicationMenu(systemMenu);
+ });
+
ipcMain.on(ELECTRON_EVENTS.REQUEST_VISIBILITY, (event) => {
// This is how synchronous messages work in Electron
// eslint-disable-next-line no-param-reassign
diff --git a/package-lock.json b/package-lock.json
index 2c9854336b88..54b0c3234ef7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -131,7 +131,7 @@
"copy-webpack-plugin": "^6.4.1",
"css-loader": "^5.2.4",
"diff-so-fancy": "^1.3.0",
- "electron": "^21.0.0",
+ "electron": "^21.2.2",
"electron-builder": "23.5.0",
"electron-notarize": "^1.2.1",
"eslint": "^7.6.0",
@@ -21398,9 +21398,9 @@
}
},
"node_modules/electron": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/electron/-/electron-21.1.1.tgz",
- "integrity": "sha512-EM2hvRJtiS3n54yx25Z0Qv54t3LGG+WjUHf1AOl+PKjQj+fmXnjIgVeIF9pM21kP1BTcyjrgvN6Sff0A45OB6A==",
+ "version": "21.2.2",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-21.2.2.tgz",
+ "integrity": "sha512-Q0j1tzLTM5JRjSJVAfDSONZgdtuyruHR1pc1y2IbMYQz62pVJWVWAvcJXzpty5iRh2HKzW9+B9WVlmfWNFA8ag==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -58969,9 +58969,9 @@
}
},
"electron": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/electron/-/electron-21.1.1.tgz",
- "integrity": "sha512-EM2hvRJtiS3n54yx25Z0Qv54t3LGG+WjUHf1AOl+PKjQj+fmXnjIgVeIF9pM21kP1BTcyjrgvN6Sff0A45OB6A==",
+ "version": "21.2.2",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-21.2.2.tgz",
+ "integrity": "sha512-Q0j1tzLTM5JRjSJVAfDSONZgdtuyruHR1pc1y2IbMYQz62pVJWVWAvcJXzpty5iRh2HKzW9+B9WVlmfWNFA8ag==",
"dev": true,
"requires": {
"@electron/get": "^1.14.1",
diff --git a/package.json b/package.json
index f8d37ad9b841..47e984a63f4e 100644
--- a/package.json
+++ b/package.json
@@ -162,7 +162,7 @@
"copy-webpack-plugin": "^6.4.1",
"css-loader": "^5.2.4",
"diff-so-fancy": "^1.3.0",
- "electron": "^21.0.0",
+ "electron": "^21.2.2",
"electron-builder": "23.5.0",
"electron-notarize": "^1.2.1",
"eslint": "^7.6.0",
diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js
index a9f6fc360da4..4d47a0f405e8 100644
--- a/src/components/AvatarWithImagePicker.js
+++ b/src/components/AvatarWithImagePicker.js
@@ -272,7 +272,7 @@ class AvatarWithImagePicker extends React.Component {
src={Expensicons.Camera}
width={variables.iconSizeSmall}
height={variables.iconSizeSmall}
- fill={themeColors.iconReversed}
+ fill={themeColors.textLight}
/>
diff --git a/src/components/ConfirmationPage.js b/src/components/ConfirmationPage.js
new file mode 100644
index 000000000000..1162c81506a9
--- /dev/null
+++ b/src/components/ConfirmationPage.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import {Image, View} from 'react-native';
+import PropTypes from 'prop-types';
+import Text from './Text';
+import styles from '../styles/styles';
+import Button from './Button';
+import FixedFooter from './FixedFooter';
+import CONST from '../CONST';
+
+const propTypes = {
+ /** The asset to render */
+ illustration: PropTypes.string,
+
+ /** Heading of the confirmation page */
+ heading: PropTypes.string,
+
+ /** Description of the confirmation page */
+ description: PropTypes.string,
+
+ /** The text for the button label */
+ buttonText: PropTypes.string,
+
+ /** A function that is called when the button is clicked on */
+ onButtonPress: PropTypes.func,
+
+ /** Whether we should show a confirmation button */
+ shouldShowButton: PropTypes.bool,
+};
+
+const defaultProps = {
+ illustration: `${CONST.CLOUDFRONT_URL}/images/animations/animation__fireworks.gif`,
+ heading: '',
+ description: '',
+ buttonText: '',
+ onButtonPress: () => {},
+ shouldShowButton: false,
+};
+
+const ConfirmationPage = props => (
+ <>
+
+
+
+ {props.heading}
+
+
+ {props.description}
+
+
+ {props.shouldShowButton && (
+
+
+
+ )}
+ >
+);
+
+ConfirmationPage.propTypes = propTypes;
+ConfirmationPage.defaultProps = defaultProps;
+ConfirmationPage.displayName = 'ConfirmationPage';
+
+export default ConfirmationPage;
diff --git a/src/components/DisplayNames/index.js b/src/components/DisplayNames/index.js
index f6d207ecd8f1..fd23a011bf69 100644
--- a/src/components/DisplayNames/index.js
+++ b/src/components/DisplayNames/index.js
@@ -104,7 +104,7 @@ class DisplayNames extends PureComponent {
{displayName}
- {index < this.props.displayNamesWithTooltips.length - 1 && , }
+ {index < this.props.displayNamesWithTooltips.length - 1 && , }
))}
{this.props.displayNamesWithTooltips.length > 1 && this.state.isEllipsisActive
diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js
index f8bf5c228623..9fbe634ab74a 100644
--- a/src/components/MenuItem.js
+++ b/src/components/MenuItem.js
@@ -75,7 +75,7 @@ const MenuItem = (props) => {
}}
style={({hovered, pressed}) => ([
styles.popoverMenuItem,
- StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || hovered, pressed, props.success, props.disabled, props.interactive)),
+ StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || hovered, pressed, props.success, props.disabled, props.interactive), true),
..._.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle],
])}
disabled={props.disabled}
diff --git a/src/components/ReimbursementAccountLoadingIndicator.js b/src/components/ReimbursementAccountLoadingIndicator.js
index c472bb3ae0a4..2a7a6bfffde6 100644
--- a/src/components/ReimbursementAccountLoadingIndicator.js
+++ b/src/components/ReimbursementAccountLoadingIndicator.js
@@ -28,7 +28,7 @@ const ReimbursementAccountLoadingIndicator = props => (
{props.isSubmittingVerificationsData ? (
`Exceeds the max length of ${limit} characters`,
+ hasInvalidCharacter: ({invalidCharacter}) => `Please remove the ${invalidCharacter} from the name field.`,
+ comma: 'comma',
+ semicolon: 'semicolon',
},
},
resendValidationForm: {
@@ -1071,4 +1074,30 @@ export default {
message: 'Expensify does not have access to save attachments. To enable access, go to Settings and allow access',
},
},
+ desktopApplicationMenu: {
+ aboutExpensify: 'About New Expensify',
+ updateExpensify: 'Update New Expensify',
+ checkForUpdates: 'Check for updates',
+ history: 'History',
+ },
+ historyMenu: {
+ forward: 'Forward',
+ back: 'Back',
+ },
+ checkForUpdatesModal: {
+ available: {
+ title: 'Update Available',
+ message: 'The new version will be available shortly. We\'ll notify you when we\'re ready to update.',
+ soundsGood: 'Sounds good',
+ },
+ notAvailable: {
+ title: 'Update Not Available',
+ message: 'There is no update available as of now! Check again at a later time.',
+ okay: 'Okay',
+ },
+ error: {
+ title: 'Update Check Failed',
+ message: 'We couldn\'t look for an update. Please check again in a bit!',
+ },
+ },
};
diff --git a/src/languages/es.js b/src/languages/es.js
index b61eb2e72a8c..889b0ab8c6bf 100644
--- a/src/languages/es.js
+++ b/src/languages/es.js
@@ -496,7 +496,7 @@ export default {
heroHeading: 'Dividir cuentas\ny chatear con amigos.',
heroDescription: {
phrase1: 'El dinero habla. Y ahora que el chat y los pagos están en un solo lugar, también es fácil. Sus pagos le llegan tan rápido como puede transmitir su punto.',
- phrase2: 'New Expensify es de código abierto. Vista',
+ phrase2: 'Nuevo Expensify es de código abierto. Vista',
phrase3: 'el código',
phrase4: 'Vista',
phrase5: 'vacantes',
@@ -542,6 +542,9 @@ export default {
firstNameLength: 'El nombre no debe tener más de 50 caracteres',
lastNameLength: 'El apellido no debe tener más de 50 caracteres',
characterLimit: ({limit}) => `Supera el límite de ${limit} caracteres`,
+ hasInvalidCharacter: ({invalidCharacter}) => `Por favor elimina ${invalidCharacter} del campo nombre.`,
+ comma: 'la coma',
+ semicolon: 'el punto y coma',
},
},
resendValidationForm: {
@@ -1073,4 +1076,30 @@ export default {
message: 'Expensify no tiene acceso para guardar archivos. Para habilitar la descarga de archivos, entra en Settings y habilita el accesso',
},
},
+ desktopApplicationMenu: {
+ aboutExpensify: 'Sobre Nuevo Expensify',
+ updateExpensify: 'Actualizar Nuevo Expensify',
+ checkForUpdates: 'Buscar actualizaciones',
+ history: 'Historial',
+ },
+ historyMenu: {
+ forward: 'Adelante',
+ back: 'Atrás',
+ },
+ checkForUpdatesModal: {
+ available: {
+ title: 'Actualización disponible',
+ message: 'La nueva versión estará disponible dentro de poco. Te notificaremos cuando esté lista.',
+ soundsGood: 'Suena bien',
+ },
+ notAvailable: {
+ title: 'Actualización no disponible',
+ message: 'No existe ninguna actualización disponible! Inténtalo de nuevo más tarde.',
+ okay: 'Vale',
+ },
+ error: {
+ title: 'Comprobación fallida',
+ message: 'No hemos podido comprobar si existe una actualización. Inténtalo de nuevo más tarde!',
+ },
+ },
};
diff --git a/src/libs/Localize/LocaleListener/BaseLocaleListener.js b/src/libs/Localize/LocaleListener/BaseLocaleListener.js
new file mode 100644
index 000000000000..5de780a840f7
--- /dev/null
+++ b/src/libs/Localize/LocaleListener/BaseLocaleListener.js
@@ -0,0 +1,38 @@
+import Onyx from 'react-native-onyx';
+import CONST from '../../../CONST';
+import ONYXKEYS from '../../../ONYXKEYS';
+
+let preferredLocale = CONST.DEFAULT_LOCALE;
+
+/**
+ * Adds event listener for changes to the locale. Callbacks are executed when the locale changes in Onyx.
+ *
+ * @param {Function} [callbackAfterChange]
+ */
+const connect = (callbackAfterChange = () => {}) => {
+ Onyx.connect({
+ key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+ callback: (val) => {
+ if (!val || val === preferredLocale) {
+ return;
+ }
+
+ preferredLocale = val;
+ callbackAfterChange(val);
+ },
+ });
+};
+
+/*
+* @return {String}
+*/
+function getPreferredLocale() {
+ return preferredLocale;
+}
+
+const BaseLocaleListener = {
+ connect,
+ getPreferredLocale,
+};
+
+export default BaseLocaleListener;
diff --git a/src/libs/Localize/LocaleListener/index.desktop.js b/src/libs/Localize/LocaleListener/index.desktop.js
new file mode 100644
index 000000000000..379867fbb162
--- /dev/null
+++ b/src/libs/Localize/LocaleListener/index.desktop.js
@@ -0,0 +1,12 @@
+import ELECTRON_EVENTS from '../../../../desktop/ELECTRON_EVENTS';
+import BaseLocaleListener from './BaseLocaleListener';
+
+export default {
+ connect: (callbackAfterChange = () => {}) => BaseLocaleListener.connect((val) => {
+ // Send the updated locale to the Electron main process
+ window.electron.send(ELECTRON_EVENTS.LOCALE_UPDATED, val);
+
+ // Then execute the callback provided for the renderer process
+ callbackAfterChange(val);
+ }),
+};
diff --git a/src/libs/Localize/LocaleListener/index.js b/src/libs/Localize/LocaleListener/index.js
new file mode 100644
index 000000000000..e5f1ea03f93f
--- /dev/null
+++ b/src/libs/Localize/LocaleListener/index.js
@@ -0,0 +1,5 @@
+import BaseLocaleListener from './BaseLocaleListener';
+
+export default {
+ connect: BaseLocaleListener.connect,
+};
diff --git a/src/libs/Localize.js b/src/libs/Localize/index.js
similarity index 84%
rename from src/libs/Localize.js
rename to src/libs/Localize/index.js
index dad089feff79..d96eccbc0702 100644
--- a/src/libs/Localize.js
+++ b/src/libs/Localize/index.js
@@ -1,24 +1,15 @@
import _ from 'underscore';
import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
-import Onyx from 'react-native-onyx';
-import Log from './Log';
-import Config from '../CONFIG';
-import translations from '../languages/translations';
-import CONST from '../CONST';
-import ONYXKEYS from '../ONYXKEYS';
+import Log from '../Log';
+import Config from '../../CONFIG';
+import translations from '../../languages/translations';
+import CONST from '../../CONST';
+import LocaleListener from './LocaleListener';
+import BaseLocaleListener from './LocaleListener/BaseLocaleListener';
-let preferredLocale = CONST.DEFAULT_LOCALE;
-Onyx.connect({
- key: ONYXKEYS.NVP_PREFERRED_LOCALE,
- callback: (val) => {
- if (!val) {
- return;
- }
-
- preferredLocale = val;
- },
-});
+// Listener when an update in Onyx happens so we use the updated locale when translating/localizing items.
+LocaleListener.connect();
/**
* Return translated string for given locale and phrase
@@ -75,7 +66,7 @@ function translate(locale = CONST.DEFAULT_LOCALE, phrase, variables = {}) {
* @returns {String}
*/
function translateLocal(phrase, variables) {
- return translate(preferredLocale, phrase, variables);
+ return translate(BaseLocaleListener.getPreferredLocale(), phrase, variables);
}
/**
diff --git a/src/libs/LoginUtils.js b/src/libs/LoginUtils.js
index 8c0555af7b1a..4c7e8a7d3d48 100644
--- a/src/libs/LoginUtils.js
+++ b/src/libs/LoginUtils.js
@@ -1,16 +1,5 @@
-import _ from 'underscore';
import CONST from '../CONST';
-/**
- * Remove the MERGED_0@ prefix from merged account emails.
- *
- * @param {String} email
- * @returns {String}
- */
-function getEmailWithoutMergedAccountPrefix(email) {
- return email.replace(/^MERGED_\d@/, '');
-}
-
/**
* Remove the special chars from the phone number
*
@@ -31,29 +20,7 @@ function getPhoneNumberWithoutUSCountryCodeAndSpecialChars(phone) {
return getPhoneNumberWithoutSpecialChars(phone.replace(/^\+1/, ''));
}
-/**
- * Filter out all non-Expensify partners from login list
- *
- * @param {Object} loginList
- * @returns {Object}
- */
-function keepExpensifyPartners(loginList = {}) {
- return _.pick(loginList, login => login.partnerName === CONST.EXPENSIFY_PARTNER_NAME);
-}
-
-/**
- * Cleans login list that came from the server by only keeping logins with Expensify partner name
- *
- * @param {Object} loginList
- * @returns {Object}
- */
-function cleanLoginListServerResponse(loginList = {}) {
- return keepExpensifyPartners(loginList);
-}
-
export {
- getEmailWithoutMergedAccountPrefix,
getPhoneNumberWithoutSpecialChars,
getPhoneNumberWithoutUSCountryCodeAndSpecialChars,
- cleanLoginListServerResponse,
};
diff --git a/src/libs/Network/NetworkStore.js b/src/libs/Network/NetworkStore.js
index 6d4a9dd8856f..f4e5364fff7c 100644
--- a/src/libs/Network/NetworkStore.js
+++ b/src/libs/Network/NetworkStore.js
@@ -61,7 +61,7 @@ Onyx.connect({
Onyx.connect({
key: ONYXKEYS.CREDENTIALS,
callback: (val) => {
- credentials = val || null;
+ credentials = val || {};
checkRequiredData();
},
});
diff --git a/src/libs/Notification/PushNotification/index.native.js b/src/libs/Notification/PushNotification/index.native.js
index c3c5d451e2ff..b6787858191d 100644
--- a/src/libs/Notification/PushNotification/index.native.js
+++ b/src/libs/Notification/PushNotification/index.native.js
@@ -1,6 +1,6 @@
import _ from 'underscore';
import {AppState} from 'react-native';
-import {UrbanAirship, EventType} from 'urbanairship-react-native';
+import {UrbanAirship, EventType, iOS} from 'urbanairship-react-native';
import lodashGet from 'lodash/get';
import Log from '../../Log';
import NotificationType from './NotificationType';
@@ -70,6 +70,16 @@ function init() {
UrbanAirship.addListener(EventType.NotificationResponse, (event) => {
pushNotificationEventCallback(EventType.NotificationResponse, event.notification);
});
+
+ // This statement has effect on iOS only.
+ // It enables the App to display push notifications when the App is in foreground.
+ // By default, the push notifications are silenced on iOS if the App is in foreground.
+ // More info here https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/1649518-usernotificationcenter
+ UrbanAirship.setForegroundPresentationOptions([
+ iOS.ForegroundPresentationOption.Alert,
+ iOS.ForegroundPresentationOption.Sound,
+ iOS.ForegroundPresentationOption.Badge,
+ ]);
}
/**
diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.js
index 1573f9f74725..da0abf746fcb 100644
--- a/src/libs/ValidationUtils.js
+++ b/src/libs/ValidationUtils.js
@@ -3,6 +3,7 @@ import _ from 'underscore';
import CONST from '../CONST';
import * as CardUtils from './CardUtils';
import * as LoginUtils from './LoginUtils';
+import * as Localize from './Localize';
/**
* Implements the Luhn Algorithm, a checksum formula used to validate credit card
@@ -336,6 +337,25 @@ function doesFailCharacterLimitAfterTrim(maxLength, valuesToBeValidated) {
return _.map(valuesToBeValidated, value => value && value.trim().length > maxLength);
}
+/**
+ * Checks if input value includes comma or semicolon which are not accepted
+ *
+ * @param {String[]} valuesToBeValidated
+ * @returns {String[]}
+ */
+function findInvalidSymbols(valuesToBeValidated) {
+ return _.map(valuesToBeValidated, (value) => {
+ if (!value) {
+ return '';
+ }
+ let inValidSymbol = value.replace(/[,]+/g, '') !== value ? Localize.translateLocal('personalDetails.error.comma') : '';
+ if (_.isEmpty(inValidSymbol)) {
+ inValidSymbol = value.replace(/[;]+/g, '') !== value ? Localize.translateLocal('personalDetails.error.semicolon') : '';
+ }
+ return inValidSymbol;
+ });
+}
+
/**
* Checks if is one of the certain names which are reserved for default rooms
* and should not be used for policy rooms.
@@ -400,4 +420,5 @@ export {
isReservedRoomName,
isExistingRoomName,
isValidTaxID,
+ findInvalidSymbols,
};
diff --git a/src/libs/actions/App.js b/src/libs/actions/App.js
index 1e3afedb3566..874b45f1fc57 100644
--- a/src/libs/actions/App.js
+++ b/src/libs/actions/App.js
@@ -15,6 +15,7 @@ import Navigation from '../Navigation/Navigation';
import ROUTES from '../../ROUTES';
import * as SessionUtils from '../SessionUtils';
import getCurrentUrl from '../Navigation/currentUrl';
+import * as Session from './Session';
let currentUserAccountID;
let currentUserEmail = '';
@@ -203,8 +204,14 @@ function setUpPoliciesAndNavigate(session) {
const makeMeAdmin = url.searchParams.get('makeMeAdmin');
const policyName = url.searchParams.get('policyName');
+ // Sign out the current user if we're transitioning from oldDot with a different user
+ const isTransitioningFromOldDot = Str.startsWith(url.pathname, Str.normalizeUrl(ROUTES.TRANSITION_FROM_OLD_DOT));
+ if (isLoggingInAsNewUser && isTransitioningFromOldDot) {
+ Session.signOut();
+ }
+
const shouldCreateFreePolicy = !isLoggingInAsNewUser
- && Str.startsWith(url.pathname, Str.normalizeUrl(ROUTES.TRANSITION_FROM_OLD_DOT))
+ && isTransitioningFromOldDot
&& exitTo === ROUTES.WORKSPACE_NEW;
if (shouldCreateFreePolicy) {
Policy.createWorkspace(ownerEmail, makeMeAdmin, policyName, true);
diff --git a/src/libs/actions/PersonalDetails.js b/src/libs/actions/PersonalDetails.js
index 4546e351ed84..9f0247a75f56 100644
--- a/src/libs/actions/PersonalDetails.js
+++ b/src/libs/actions/PersonalDetails.js
@@ -49,16 +49,6 @@ function getDisplayName(login, personalDetail) {
return fullName || userLogin;
}
-/**
- * Returns max character error text if true.
- *
- * @param {Boolean} isError
- * @returns {String}
- */
-function getMaxCharacterError(isError) {
- return isError ? Localize.translateLocal('personalDetails.error.characterLimit', {limit: CONST.FORM_CHARACTER_LIMIT}) : '';
-}
-
/**
* Gets the first and last name from the user's personal details.
* If the login is the same as the displayName, then they don't exist,
@@ -340,7 +330,6 @@ export {
updateAvatar,
deleteAvatar,
openIOUModalPage,
- getMaxCharacterError,
extractFirstAndLastNameFromAvailableDetails,
updateDisplayName,
updatePronouns,
diff --git a/src/libs/actions/ReimbursementAccount/store.js b/src/libs/actions/ReimbursementAccount/store.js
index 34edd34a6a2f..1034b655b2ba 100644
--- a/src/libs/actions/ReimbursementAccount/store.js
+++ b/src/libs/actions/ReimbursementAccount/store.js
@@ -31,7 +31,7 @@ let credentials;
Onyx.connect({
key: ONYXKEYS.CREDENTIALS,
callback: (val) => {
- credentials = val;
+ credentials = val || {};
},
});
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index b4431a5cadad..e9d1662f49b3 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -16,7 +16,6 @@ import * as API from '../API';
import CONFIG from '../../CONFIG';
import CONST from '../../CONST';
import Log from '../Log';
-import * as LoginUtils from '../LoginUtils';
import * as ReportUtils from '../ReportUtils';
import DateUtils from '../DateUtils';
import * as ReportActionsUtils from '../ReportActionsUtils';
@@ -57,127 +56,6 @@ function getMaxSequenceNumber(reportID) {
return lodashGet(allReports, [reportID, 'maxSequenceNumber'], 0);
}
-/**
- * @param {Object} report
- * @return {String[]}
- */
-function getParticipantEmailsFromReport({sharedReportList, reportNameValuePairs, ownerEmail}) {
- const emailArray = _.map(sharedReportList, participant => participant.email);
- if (ReportUtils.isChatRoom(reportNameValuePairs)) {
- return emailArray;
- }
- if (ReportUtils.isPolicyExpenseChat(reportNameValuePairs)) {
- // The owner of the policyExpenseChat isn't in the sharedReportsList so they need to be explicitly included.
- return [ownerEmail, ...emailArray];
- }
-
- // The current user is excluded from the participants array in DMs/Group DMs because their participation is implied
- // by the chat being shared to them. This also prevents the user's own avatar from being a part of the chat avatar.
- return _.without(emailArray, currentUserEmail);
-}
-
-/**
- * Only store the minimal amount of data in Onyx that needs to be stored
- * because space is limited.
- *
- * @param {Object} report
- * @param {Number} report.reportID
- * @param {String} report.reportName
- * @param {Object} report.reportNameValuePairs
- * @returns {Object}
- */
-function getSimplifiedReportObject(report) {
- const lastActionCreated = lodashGet(report, 'lastActionCreated', 0);
- const lastActionMessage = lodashGet(report, ['lastActionMessage', 'html'], '');
- const isLastMessageAttachment = new RegExp(`]*${CONST.ATTACHMENT_SOURCE_ATTRIBUTE}\\s*=\\s*"[^"]*"[^>]*>`, 'gi').test(lastActionMessage);
- const chatType = lodashGet(report, ['reportNameValuePairs', 'chatType'], '');
-
- let lastMessageText = null;
- if (report.reportActionCount > 0) {
- // We are removing any html tags from the message html since we cannot access the text version of any comments as
- // the report only has the raw reportActionList and not the processed version returned by Report_GetHistory
- const parser = new ExpensiMark();
- lastMessageText = parser.htmlToText(lastActionMessage);
- lastMessageText = ReportUtils.formatReportLastMessageText(lastMessageText);
- }
-
- // Used for archived rooms, will store the policy name that the room used to belong to.
- const oldPolicyName = lodashGet(report, ['reportNameValuePairs', 'oldPolicyName'], '').toString();
-
- const lastActorEmail = lodashGet(report, 'lastActionActorEmail', '');
- const notificationPreference = lodashGet(report, ['reportNameValuePairs', 'notificationPreferences', currentUserAccountID], CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY);
-
- // Used for User Created Policy Rooms, will denote how access to a chat room is given among workspace members
- const visibility = lodashGet(report, ['reportNameValuePairs', 'visibility']);
- const lastReadSequenceNumber = lodashGet(report, [
- 'reportNameValuePairs',
- `lastRead_${currentUserAccountID}`,
- 'sequenceNumber',
- ]);
-
- return {
- // This needs to be cast to a string until the IOU API has been fully migrated to OfflineFirst API
- reportID: report.reportID.toString(),
- reportName: report.reportName,
- chatType,
- ownerEmail: LoginUtils.getEmailWithoutMergedAccountPrefix(lodashGet(report, ['ownerEmail'], '')),
- policyID: lodashGet(report, ['reportNameValuePairs', 'expensify_policyID'], ''),
- maxSequenceNumber: lodashGet(report, 'reportActionCount', 0),
- participants: getParticipantEmailsFromReport(report),
- isPinned: report.isPinned,
- lastReadTimestamp: lodashGet(report, [
- 'reportNameValuePairs',
- `lastRead_${currentUserAccountID}`,
- 'timestamp',
- ], 0),
- lastReadSequenceNumber,
- lastActionCreated,
- lastMessageText: isLastMessageAttachment ? '[Attachment]' : lastMessageText,
- lastActorEmail,
- notificationPreference,
- stateNum: report.state,
- statusNum: report.status,
- oldPolicyName,
- visibility,
- isOwnPolicyExpenseChat: lodashGet(report, ['isOwnPolicyExpenseChat'], false),
- lastMessageHtml: lastActionMessage,
- };
-}
-
-/**
- * Get a simplified version of an IOU report
- *
- * @param {Object} reportData
- * @param {String} reportData.transactionID
- * @param {Number} reportData.amount
- * @param {String} reportData.currency
- * @param {String} reportData.created
- * @param {String} reportData.comment
- * @param {Object[]} reportData.transactionList
- * @param {String} reportData.ownerEmail
- * @param {String} reportData.managerEmail
- * @param {Number} reportData.reportID
- * @param {Number|String} chatReportID
- * @returns {Object}
- */
-function getSimplifiedIOUReport(reportData, chatReportID) {
- return {
- reportID: reportData.reportID,
- ownerEmail: reportData.ownerEmail,
- managerEmail: reportData.managerEmail,
- currency: reportData.currency,
- chatReportID: Number(chatReportID),
- state: reportData.state,
- cachedTotal: reportData.cachedTotal,
- total: reportData.total,
- status: reportData.status,
- stateNum: reportData.stateNum,
- submitterPayPalMeAddress: reportData.submitterPayPalMeAddress,
- submitterPhoneNumbers: reportData.submitterPhoneNumbers,
- hasOutstandingIOU: reportData.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && reportData.total !== 0,
- };
-}
-
/**
* Get the private pusher channel name for a Report.
*
@@ -597,7 +475,7 @@ function loadMoreActions(reportID, reportActions, isLoadingMoreReportActions) {
/**
* Gets the IOUReport and the associated report actions.
*
- * @param {Number} chatReportID
+ * @param {String} chatReportID
* @param {Number} iouReportID
*/
function openPaymentDetailsPage(chatReportID, iouReportID) {
@@ -997,39 +875,6 @@ function saveReportActionDraft(reportID, reportActionID, draftMessage) {
Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${reportID}_${reportActionID}`, draftMessage);
}
-/**
- * Syncs up a chat report and an IOU report in Onyx after an IOU transaction has been made
- * by setting the iouReportID and hasOutstandingIOU for the chat report.
- * Even though both reports are updated in the back-end, the API doesn't handle syncing their reportIDs.
- * If we didn't sync these reportIDs, the paid IOU would still be shown to users as unpaid.
- * The iouReport being fetched here must be open, because only an open iouReport can be paid.
- *
- * @param {Object} chatReport
- * @param {Object} iouReport
- */
-function syncChatAndIOUReports(chatReport, iouReport) {
- // Return early in case there's a back-end issue preventing the IOU command from returning the report objects.
- if (!chatReport || !iouReport) {
- return;
- }
-
- const simplifiedIouReport = {};
- const simplifiedReport = {};
- const chatReportKey = `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`;
- const iouReportKey = `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`;
-
- // We don't want to sync an iou report that's already been reimbursed with its chat report.
- if (!iouReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED) {
- simplifiedReport[chatReportKey].iouReportID = iouReport.reportID;
- }
- simplifiedReport[chatReportKey] = getSimplifiedReportObject(chatReport);
- simplifiedReport[chatReportKey].hasOutstandingIOU = iouReport.stateNum
- === CONST.REPORT.STATE_NUM.PROCESSING && iouReport.total !== 0;
- simplifiedIouReport[iouReportKey] = getSimplifiedIOUReport(iouReport, chatReport.reportID);
- Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, simplifiedIouReport);
- Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, simplifiedReport);
-}
-
/**
* @param {String} reportID
* @param {String} previousValue
@@ -1375,7 +1220,6 @@ export {
handleUserDeletedLinks,
saveReportActionDraft,
deleteReportComment,
- syncChatAndIOUReports,
navigateToConciergeChat,
setReportWithDraft,
addPolicyReport,
diff --git a/src/libs/actions/Session/index.js b/src/libs/actions/Session/index.js
index 7b015e299b15..fd72ef0dac10 100644
--- a/src/libs/actions/Session/index.js
+++ b/src/libs/actions/Session/index.js
@@ -1,10 +1,8 @@
import Onyx from 'react-native-onyx';
-import Str from 'expensify-common/lib/str';
import _ from 'underscore';
import lodashGet from 'lodash/get';
import ONYXKEYS from '../../../ONYXKEYS';
import redirectToSignIn from '../SignInRedirect';
-import * as DeprecatedAPI from '../../deprecatedAPI';
import CONFIG from '../../../CONFIG';
import Log from '../../Log';
import PushNotification from '../../Notification/PushNotification';
@@ -14,7 +12,6 @@ import * as Localize from '../../Localize';
import UnreadIndicatorUpdater from '../../UnreadIndicatorUpdater';
import Timers from '../../Timers';
import * as Pusher from '../../Pusher/pusher';
-import * as User from '../User';
import * as Authentication from '../../Authentication';
import * as Welcome from '../Welcome';
import * as API from '../../API';
@@ -24,26 +21,9 @@ import DateUtils from '../../DateUtils';
let credentials = {};
Onyx.connect({
key: ONYXKEYS.CREDENTIALS,
- callback: val => credentials = val,
+ callback: val => credentials = val || {},
});
-/**
- * Sets API data in the store when we make a successful "Authenticate"/"CreateLogin" request
- *
- * @param {Object} data
- * @param {String} data.accountID
- * @param {String} data.authToken
- * @param {String} data.email
- */
-function setSuccessfulSignInData(data) {
- PushNotification.register(data.accountID);
- Onyx.merge(ONYXKEYS.SESSION, {
- errors: null,
- ..._.pick(data, 'authToken', 'accountID', 'email', 'encryptedAuthToken'),
- });
- Onyx.set(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, true);
-}
-
/**
* Clears the Onyx store and redirects user to the sign in page
*/
@@ -59,7 +39,7 @@ function signOut() {
{
onyxMethod: CONST.ONYX.METHOD.SET,
key: ONYXKEYS.CREDENTIALS,
- value: null,
+ value: {},
},
];
API.write('LogOut', {
@@ -159,64 +139,48 @@ function beginSignIn(login) {
}
/**
- *
* Will create a temporary login for the user in the passed authenticate response which is used when
* re-authenticating after an authToken expires.
*
- * @param {String} authToken
* @param {String} email
- * @return {Promise}
+ * @param {String} authToken
*/
-function createTemporaryLogin(authToken, email) {
- const autoGeneratedLogin = Str.guid('expensify.cash-');
- const autoGeneratedPassword = Str.guid();
+function signInWithShortLivedAuthToken(email, authToken) {
+ const optimisticData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ ...CONST.DEFAULT_ACCOUNT_DATA,
+ isLoading: true,
+ },
+ },
+ ];
- return DeprecatedAPI.CreateLogin({
- authToken,
- partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
- partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
- partnerUserID: autoGeneratedLogin,
- partnerUserSecret: autoGeneratedPassword,
- shouldRetry: false,
- forceNetworkRequest: true,
- email,
- includeEncryptedAuthToken: true,
- })
- .then((createLoginResponse) => {
- if (createLoginResponse.jsonCode !== 200) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {errors: {[DateUtils.getMicroseconds()]: Localize.translate('createLoginResponse.message')}});
- return createLoginResponse;
- }
-
- setSuccessfulSignInData(createLoginResponse);
-
- // If we have an old generated login for some reason
- // we should delete it before storing the new details
- if (credentials && credentials.autoGeneratedLogin) {
- DeprecatedAPI.DeleteLogin({
- partnerUserID: credentials.autoGeneratedLogin,
- partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
- partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
- shouldRetry: false,
- })
- .then((response) => {
- if (response.jsonCode === CONST.JSON_CODE.SUCCESS) {
- return;
- }
-
- Log.hmmm('[Session] Unable to delete login', false, {message: response.message, jsonCode: response.jsonCode});
- });
- }
-
- Onyx.merge(ONYXKEYS.CREDENTIALS, {
- autoGeneratedLogin,
- autoGeneratedPassword,
- });
- return createLoginResponse;
- })
- .finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
- });
+ const successData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: false,
+ },
+ },
+ ];
+
+ const failureData = [
+ {
+ onyxMethod: CONST.ONYX.METHOD.MERGE,
+ key: ONYXKEYS.ACCOUNT,
+ value: {
+ isLoading: false,
+ },
+ },
+ ];
+
+ // If the user is transitioning to newDot from a different account on oldDot the credentials may be tied to
+ // the old account. If so, should not pass the auto-generated login as it is not tied to the account being signed in
+ const oldPartnerUserID = credentials.login === email ? credentials.autoGeneratedLogin : '';
+ API.write('SignInWithShortLivedAuthToken', {authToken, oldPartnerUserID}, {optimisticData, successData, failureData});
}
/**
@@ -262,29 +226,6 @@ function signIn(password, twoFactorAuthCode) {
API.write('SigninUser', {email: credentials.login, password, twoFactorAuthCode}, {optimisticData, successData, failureData});
}
-/**
- * Uses a short lived authToken to continue a user's session from OldDot
- *
- * @param {String} email
- * @param {String} shortLivedAuthToken
- * @param {String} exitTo
- */
-function signInWithShortLivedAuthToken(email, shortLivedAuthToken) {
- Onyx.merge(ONYXKEYS.ACCOUNT, {...CONST.DEFAULT_ACCOUNT_DATA, isLoading: true});
-
- createTemporaryLogin(shortLivedAuthToken, email)
- .then((response) => {
- if (response.jsonCode !== CONST.JSON_CODE.SUCCESS) {
- return;
- }
-
- User.getUserDetails();
- Onyx.merge(ONYXKEYS.ACCOUNT, {success: true});
- }).finally(() => {
- Onyx.merge(ONYXKEYS.ACCOUNT, {isLoading: false});
- });
-}
-
/**
* User forgot the password so let's send them the link to reset their password
*/
@@ -361,7 +302,7 @@ function invalidateAuthToken() {
function clearSignInData() {
Onyx.multiSet({
[ONYXKEYS.ACCOUNT]: null,
- [ONYXKEYS.CREDENTIALS]: null,
+ [ONYXKEYS.CREDENTIALS]: {},
});
}
diff --git a/src/libs/actions/User.js b/src/libs/actions/User.js
index 6a4a0775fb4c..234fc9c1e0a6 100644
--- a/src/libs/actions/User.js
+++ b/src/libs/actions/User.js
@@ -15,10 +15,8 @@ import NetworkConnection from '../NetworkConnection';
import Growl from '../Growl';
import * as Localize from '../Localize';
import * as Link from './Link';
-import getSkinToneEmojiFromIndex from '../../components/EmojiPicker/getSkinToneEmojiFromIndex';
import * as SequentialQueue from '../Network/SequentialQueue';
import PusherUtils from '../PusherUtils';
-import * as LoginUtils from '../LoginUtils';
let currentUserAccountID = '';
Onyx.connect({
@@ -89,46 +87,6 @@ function closeAccount(message) {
});
}
-/**
- * Fetches the data needed for user settings
- */
-function getUserDetails() {
- DeprecatedAPI.Get({
- returnValueList: 'account, loginList, nameValuePairs',
- nvpNames: [
- CONST.NVP.PAYPAL_ME_ADDRESS,
- CONST.NVP.PREFERRED_EMOJI_SKIN_TONE,
- CONST.NVP.FREQUENTLY_USED_EMOJIS,
- CONST.NVP.BLOCKED_FROM_CONCIERGE,
- ].join(','),
- })
- .then((response) => {
- // Update the User onyx key
- const isSubscribedToNewsletter = lodashGet(response, 'account.subscribed', true);
- const validatedStatus = lodashGet(response, 'account.validated', false);
- Onyx.merge(ONYXKEYS.USER, {isSubscribedToNewsletter: !!isSubscribedToNewsletter, validated: !!validatedStatus});
-
- // Update login list
- const loginList = LoginUtils.cleanLoginListServerResponse(response.loginList);
- Onyx.set(ONYXKEYS.LOGIN_LIST, loginList);
-
- // Update the nvp_payPalMeAddress NVP
- const payPalMeAddress = lodashGet(response, `nameValuePairs.${CONST.NVP.PAYPAL_ME_ADDRESS}`, '');
- Onyx.merge(ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, payPalMeAddress);
-
- // Update the blockedFromConcierge NVP
- const blockedFromConcierge = lodashGet(response, `nameValuePairs.${CONST.NVP.BLOCKED_FROM_CONCIERGE}`, {});
- Onyx.merge(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE, blockedFromConcierge);
-
- const preferredSkinTone = lodashGet(response, `nameValuePairs.${CONST.NVP.PREFERRED_EMOJI_SKIN_TONE}`, {});
- Onyx.merge(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
- getSkinToneEmojiFromIndex(preferredSkinTone).skinTone);
-
- const frequentlyUsedEmojis = lodashGet(response, `nameValuePairs.${CONST.NVP.FREQUENTLY_USED_EMOJIS}`, []);
- Onyx.set(ONYXKEYS.FREQUENTLY_USED_EMOJIS, frequentlyUsedEmojis);
- });
-}
-
/**
* Resends a validation link to a given login
*
@@ -179,8 +137,7 @@ function setSecondaryLoginAndNavigate(login, password) {
password,
}).then((response) => {
if (response.jsonCode === 200) {
- const loginList = LoginUtils.cleanLoginListServerResponse(response.loginList);
- Onyx.set(ONYXKEYS.LOGIN_LIST, loginList);
+ Onyx.set(ONYXKEYS.LOGIN_LIST, response.loginList);
Navigation.navigate(ROUTES.SETTINGS_PROFILE);
return;
}
@@ -481,7 +438,6 @@ function generateStatementPDF(period) {
export {
updatePassword,
closeAccount,
- getUserDetails,
resendValidateCode,
updateNewsletterSubscription,
setSecondaryLoginAndNavigate,
diff --git a/src/libs/actions/Wallet.js b/src/libs/actions/Wallet.js
index 01b4038043c5..6d4abde2ec90 100644
--- a/src/libs/actions/Wallet.js
+++ b/src/libs/actions/Wallet.js
@@ -71,7 +71,7 @@ function setAdditionalDetailsErrorMessage(additionalErrorMessage) {
/**
* Save the ID of the chat whose IOU triggered showing the KYC wall.
*
- * @param {Number} chatReportID
+ * @param {String} chatReportID
*/
function setKYCWallSourceChatReportID(chatReportID) {
Onyx.merge(ONYXKEYS.WALLET_TERMS, {chatReportID});
diff --git a/src/libs/onScreenTransitionEnd/index.js b/src/libs/onScreenTransitionEnd/index.js
index 874be4fafb2e..5698f9744dda 100644
--- a/src/libs/onScreenTransitionEnd/index.js
+++ b/src/libs/onScreenTransitionEnd/index.js
@@ -1,5 +1,5 @@
/**
- * Call the callback after screen transiton has ended
+ * Call the callback after screen transition has ended
*
* @param {Object} navigation Screen navigation prop
* @param {Function} callback Method to call
diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js
index 67be90c921f2..8804257203af 100644
--- a/src/pages/AddPersonalBankAccountPage.js
+++ b/src/pages/AddPersonalBankAccountPage.js
@@ -2,7 +2,6 @@ import _ from 'underscore';
import React from 'react';
import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
-import {View} from 'react-native';
import PropTypes from 'prop-types';
import HeaderWithCloseButton from '../components/HeaderWithCloseButton';
import ScreenWrapper from '../components/ScreenWrapper';
@@ -13,15 +12,10 @@ import AddPlaidBankAccount from '../components/AddPlaidBankAccount';
import getPlaidOAuthReceivedRedirectURI from '../libs/getPlaidOAuthReceivedRedirectURI';
import compose from '../libs/compose';
import ONYXKEYS from '../ONYXKEYS';
-import Text from '../components/Text';
import styles from '../styles/styles';
-import * as Illustrations from '../components/Icon/Illustrations';
-import Icon from '../components/Icon';
-import defaultTheme from '../styles/themes/default';
-import Button from '../components/Button';
-import FixedFooter from '../components/FixedFooter';
import Form from '../components/Form';
import ROUTES from '../ROUTES';
+import ConfirmationPage from '../components/ConfirmationPage';
const propTypes = {
...withLocalizePropTypes,
@@ -94,33 +88,13 @@ class AddPersonalBankAccountPage extends React.Component {
onBackButtonPress={Navigation.goBack}
/>
{shouldShowSuccess ? (
- <>
-
-
-
-
- {this.props.translate('addPersonalBankAccountPage.successTitle')}
-
-
- {this.props.translate('addPersonalBankAccountPage.successMessage')}
-
-
-
-
-
- >
+ Navigation.navigate(ROUTES.SETTINGS_PAYMENTS)}
+ />
) : (