diff --git a/android/app/build.gradle b/android/app/build.gradle index 9a930427ecdf..9886cd5cccec 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001044501 - versionName "1.4.45-1" + versionCode 1001044600 + versionName "1.4.46-0" } flavorDimensions "default" diff --git a/assets/images/chatbubble-add.svg b/assets/images/chatbubble-add.svg index 48eebf863cc3..3e2c270d681c 100644 --- a/assets/images/chatbubble-add.svg +++ b/assets/images/chatbubble-add.svg @@ -1 +1,10 @@ - \ No newline at end of file + + + + + diff --git a/assets/images/chatbubble-reply.svg b/assets/images/chatbubble-reply.svg new file mode 100644 index 000000000000..f8d53ebff807 --- /dev/null +++ b/assets/images/chatbubble-reply.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/assets/images/chatbubble-unread.svg b/assets/images/chatbubble-unread.svg index 492616cf2ab5..f5a27a74f5fe 100644 --- a/assets/images/chatbubble-unread.svg +++ b/assets/images/chatbubble-unread.svg @@ -1 +1,9 @@ - \ No newline at end of file + + + + + diff --git a/assets/images/product-illustrations/three_legged_laptop_woman.svg b/assets/images/product-illustrations/three_legged_laptop_woman.svg new file mode 100644 index 000000000000..6be000b92e37 --- /dev/null +++ b/assets/images/product-illustrations/three_legged_laptop_woman.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/articles/expensify-classic/getting-started/Create-a-company-workspace.md b/docs/articles/expensify-classic/getting-started/Create-a-company-workspace.md new file mode 100644 index 000000000000..4cc95cdcf918 --- /dev/null +++ b/docs/articles/expensify-classic/getting-started/Create-a-company-workspace.md @@ -0,0 +1,171 @@ +--- +title: Create a company workspace +description: Get started with Expensify by creating a workspace for your company +--- +
+ +# Overview + +Welcome to Expensify! If you are creating an Expensify account for your company, follow the steps below to get started. + +{% include info.html %} +You can also schedule a free private onboarding session where one of our Setup Specialists will walk you through the entire process. Check your email and notifications in Expensify for your unique signup link. +{% include end-info.html %} + +# 1. Meet Concierge + +Your personal assistant, Concierge, lives on your Expensify Home page on both desktop and the mobile app. + +Concierge will walk you through setting up your account and also provide: +- Reminders to do things like submit your expenses +- Alerts when more information is needed on an expense report +- Updates on new and improved account features + +You can also get support at any time by clicking the green chat bubble in the right corner. This will open a chat with Concierge where you can ask questions and receive direct support. + +# 2. Create a workspace + +
    +
  1. Hover over Settings, then click Workspaces.
  2. +
  3. Click the Group tab on the left.
  4. +
  5. Enter a name for the workspace (the name of your business or department is a great choice, if applicable), then click Select next to the workspace type that best fits your needs.
  6. +
+ +# 3. Add a business bank account + +Connecting your business bank account allows you to: +- Reimburse expenses via direct bank transfer +- Pay bills +- Collect invoice payments +- Issue Expensify Cards + +{% include info.html %} +The person who completes this process does not need to be a signer on the account, however they will be required to enter their own personal information as well. If someone other than the Expensify account holder enters their personal information in this section, the details will be flagged for review, and adding the bank account to Expensify will be delayed. +{% include end-info.html %} + +To add a business bank account, + +
    +
  1. Hover over Settings, then click Workspaces.
  2. +
  3. Click the Group tab on the left.
  4. +
  5. Click the desired workspace name.
  6. +
  7. Click the Reimbursement tab.
  8. +
  9. Click the Direct box, then click Add Business Bank Account.
  10. +
  11. Click Connect to your bank.
  12. +
  13. Click Continue to continue to Plaid.
  14. +
  15. Select the bank and log in to your business bank account.
  16. + +
  17. Select the bank account if multiple are available.
  18. +
  19. Verify the bank account by entering some additional information:
  20. + +
  21. Within 1-2 business days, Expensify will send three test transactions to your bank account that you’ll enter into Expensify to validate your bank account by either:
  22. + +
+ +{% include info.html %} +If after two business days you do not see these test transactions, click the green chat bubble in the right corner to get support from Concierge. +{% include end-info.html %} + +# 4. Connect your accounting system + +If you use an external accounting system like QuickBooks, you can link it with Expensify to help you import accounting data, code expenses, and more. + +To add an accounting system integration, +
    +
  1. Hover over Settings, then click Workspaces.
  2. +
  3. Click the Group tab on the left.
  4. +
  5. Click the desired workspace name.
  6. +
  7. Click the Connections tab.
  8. +
  9. Under Accounting Integrations, click the name of your accounting system, select the “Connect to…” option, and click the related button.
  10. +
  11. Depending on the integration you selected, you’ll either be prompted with a login screen for the accounting system or additional steps for how to proceed.
  12. +
+ +For a walkthrough for how to set up a specific accounting system, visit our [Integrations](https://help.expensify.com/expensify-classic/hubs/integrations/) articles. + +# 5. Set approval rules + +Determine the basic guidelines that apply to all submitted expenses. If a submitted expense does not meet these rules, it will be flagged as a violation. You can set rules for expenses, per diem, travel, and reports. + +
    +
  1. Hover over Settings, then click Workspaces.
  2. +
  3. Click the Group tab on the left.
  4. +
  5. Click the desired workspace name.
  6. +
  7. Click the Expenses tab and set the desired rules.
  8. + +
  9. Click the Reports tab and set the desired rules.
  10. + +
  11. Click the Travel tab and set the desired rules.
  12. + +
  13. Click the Per Diem tab and set the desired rules.
  14. + +
+ +# 6. Secure your account + +Add an extra layer of security to help keep your financial data safe and secure by enabling two-factor authentication. This will require you to enter a code generated by your preferred authenticator app (like Google Authenticator or Microsoft Authenticator) when you log in. + +
    +
  1. Hover over Settings, then click Account.
  2. +
  3. Under the Account Details tab, scroll down to the Two Factor Authentication section and enable the toggle.
  4. +
  5. Save a copy of your backup codes. This step is critical—You will lose access to your account if you cannot use your authenticator app and do not have your recovery codes.
  6. + +
  7. Click Continue.
  8. +
  9. Download or open your authenticator app and either:
  10. + +
+ +When you log in to Expensify in the future, you’ll be emailed a magic code that you’ll use to log in with. Then you’ll be prompted to open your authenticator app to get the 6-digit code and enter it into Expensify. A new code regenerates every few seconds, so the code is always different. If the code time runs out, you can generate a new code as needed. diff --git a/docs/articles/expensify-classic/settings/Account-Details.md b/docs/articles/expensify-classic/settings/Account-Details.md deleted file mode 100644 index 535e74eeb701..000000000000 --- a/docs/articles/expensify-classic/settings/Account-Details.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Account Details -description: The Account Details section of your account is where you can update your profile photo, enable 2FA, and change the email address associated with your Expensify account. ---- - -# Overview -The Account Details section of your account is where you can update your profile photo, enable 2FA, and change the email address associated with your account. - -You can have multiple email addresses tied to your account to make it easier to submit expenses or manage your account. Let’s go over how to configure the various account settings located under the Account Details section of your Expensify account. - -# How to add a profile photo -To update your name or add a profile photo, navigate to **Settings** > **Account** > **Account Details.** Under “your profile” you’ll notice a profile picture thumbnail, click “edit photo” underneath to update the profile image. - -# How to enable Two-Factor Authentication -Setting up Two-factor Authentication is one of the best ways to secure your account. This can be enabled individually in your account settings by following **Settings** > **Accounts** > **Account Details** > **Two Factor Authentication** and toggle the switch to **Enabled.** - -Save or download your **Recovery Codes.** It’s important to keep these safe! You WILL lose access to your account if you cannot use your authenticator app and do not have your recovery codes. - -Use your favorite authenticator app to connect to Expensify using the QR code or click the link to enter the secret key manually. - -Once connected, quickly enter the code generated by your app into Expensify before the timeframe runs out! - -# How to manage your devices -You can access your Expensify account on multiple devices, which allows for easy access to your account data. By heading to **Settings** > **Account** > **Account Details** > **Device Management**, you can review the devices that have access to your account. - -From that same place in your account, you can remove any devices that should no longer have access. To do this, select the **Revoke** button next to each device you wish to remove access to your account. - -# How to add a Secondary Login -A Secondary Login is helpful if you have multiple email addresses and don’t necessarily need multiple Expensify accounts. By adding additional emails to your Expensify account, you can use them to forward receipts to receipts@expensify.com and they will be uploaded to your main Expensify account. To get this added to your account, follow these steps: - -1. Log in to your Expensify account through a web browser at www.expensify.com. Please note that this process cannot be completed using the mobile app; it must be done from the website at expensify.com. -2. Navigate to **Settings** > **Account** > **Account Details**. Scroll down to find the 'Secondary Logins' section, then click the 'Add Secondary Login' button. -3. Input the email address or mobile phone number you wish to add, ensuring you include the international code if applicable. -4. You will receive a prompt to enter the Magic Code, which will be sent to the email address you're adding as a secondary login. - -# How to update your email address -Once a Secondary Login is added to your account, you can make it your primary email address. The primary address on an Expensify account is the address that will receive email notifications and updates regarding the account. Any new email addresses must be added as a secondary login before they can be made a primary address. - -1. Log in to your Expensify account through a web browser at www.expensify.com. Please note that this process cannot be completed using the mobile app; it must be done from the website at expensify.com. -2. Navigate to **Settings** > **Account** > **Account Details**. Scroll down to find the 'Secondary Logins' section, then select the **"Make Primary"** button next to the email address. -3. You can keep the old address as a secondary login or delete email addresses by selecting the **"Remove"** button. - - -# Deep Dive -## Managing emails connected to other Expensify accounts -A secondary login can only be added if it is not linked to an existing account. If you have two email addresses with Expensify accounts linked to them, you'll need to merge them instead. - -Alternatively, you can remove a personal email address from a previous work/organization account to use it elsewhere. - -Is your Secondary Login (personal email) validated in your company account? If so, do the following: -1. Navigate to expensify.com -2. Log in using your validated Secondary Login -3. Navigate to **Account** > **Settings** > **Account Details** > **Secondary Logins** -4. Remove your personal email address from the account by clicking the **"Remove"** button next to your email - -Is your Secondary Login (personal email) invalidated in your company account? If so, do the following: -1. Navigate to expensify.com -2. Enter your invalidated secondary login email address -3. You will be presented with a confirmation message saying Expensify sent you an email with a validation link -4. Head to your personal email account and follow the prompts -5. You'll receive a link in the email to click that will unlink the two accounts - -{% include faq-begin.md %} -## The profile picture on my account updated automatically. Why did this happen? -Our focus is always on making your experience user-friendly and saving you valuable time. One of the ways we achieve this is by utilizing a public API to retrieve public data linked to your email address. - -This tool searches for public accounts or profiles associated with your email address, such as on LinkedIn. When it identifies one, it pulls in the uploaded profile picture and name to Expensify. - -While this automated process is generally accurate, there may be instances where it's not entirely correct. If this happens, we apologize for any inconvenience caused. The good news is that rectifying such situations is a straightforward process. You can quickly update your information manually by following the directions provided above, ensuring your data is accurate and up to date in no time. - -{% include faq-end.md %} diff --git a/docs/articles/expensify-classic/settings/Merge-Accounts.md b/docs/articles/expensify-classic/settings/Merge-Accounts.md deleted file mode 100644 index 34bf422aa983..000000000000 --- a/docs/articles/expensify-classic/settings/Merge-Accounts.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Merge Accounts -description: How to merge two Expensify accounts and why this is useful. ---- - -# Overview - -Merging accounts allows you to combine two accounts. When you combine two accounts, all receipts, expenses, expense reports, invoices, bills, imported cards, secondary logins, co-pilots, and group workspace settings will be combined into one account. -This can be useful if you start off with an account of your own but your organization creates a separate account for you. You can then track both personal and business expenses via one account. - -# How to merge accounts -Merging two accounts together is fairly straightforward. Let’s go over how to do that below: -1. Navigate to [expensify.com](https://www.expensify.com) -2. Log into the account you want to set as the Primary account -3. Navigate to **Settings > Account > Account Details** -4. Scroll down to Merge Accounts and fill in the fields -6. Click Merge Accounts -7. Once you click Merge, a magic code is sent to you via email -8. Paste the code into the required field -If you have any questions about this process, feel free to reach out to Concierge for some assistance! - -{% include faq-begin.md %} -## Can you merge accounts from the mobile app? -No, accounts can only be merged from the full website at expensify.com. -## Can I administratively merge two accounts together? -No, only the account holder (member) can perform account merging. -## Is merging accounts reversible? -No, merging accounts is not reversible. It is a permanent action that cannot be undone. -## I have open expenses in the account I'm merging from. Will those expenses merge into the new account? -All expenses must be reported and submitted for them to merge into the new account. Any open expenses will not merge. -## Are there any restrictions on account merging? -Yes! Please see below: -- If your email address belongs to a verified domain (verified in Expensify), you must start the process from the email account under the verified domain. You cannot merge a verified company email account into a personal account. -- If you have two accounts with two different verified domains, you cannot merge them together. -## What happens to my “personal” Individual workspace when merging accounts? -The old “personal” Individual workspace is deleted. If you plan to submit reports under a different workspace in the future, ensure that any reports on the Individual workspace in the old account are marked as Open before merging the accounts. You can typically do this by selecting “Undo Submit” on any submitted reports. - -{% include faq-end.md %} diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index cf7cc7cc3caa..dff05f61933e 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.45 + 1.4.46 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.45.1 + 1.4.46.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 22bc17b167f6..fa6995f65b5a 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.45 + 1.4.46 CFBundleSignature ???? CFBundleVersion - 1.4.45.1 + 1.4.46.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 8055f08b8d7c..e8cd0ebb4e0a 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.45 + 1.4.46 CFBundleVersion - 1.4.45.1 + 1.4.46.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 066c140d915f..5f55ddd82868 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.45-1", + "version": "1.4.46-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.45-1", + "version": "1.4.46-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -52,7 +52,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#83ae6194b3e4feb363ea9d061085a7ab76e35ffb", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#a8ed0f8e1be3a1e09016e07a74cfd13c85bbc167", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", @@ -25920,9 +25920,9 @@ } }, "node_modules/classnames": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.4.0.tgz", - "integrity": "sha512-lWxiIlphgAhTLN657pwU/ofFxsUTOWc2CRIFeoV5st0MGRJHStUnWIUJgDHxjUO/F0mXzGufXIM4Lfu/8h+MpA==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.0.tgz", + "integrity": "sha512-FQuRlyKinxrb5gwJlfVASbSrDlikDJ07426TrfPsdGLvtochowmkbnSFdQGJ2aoXrSetq5KqGV9emvWpy+91xA==" }, "node_modules/clean-css": { "version": "5.3.2", @@ -30758,11 +30758,11 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#83ae6194b3e4feb363ea9d061085a7ab76e35ffb", - "integrity": "sha512-nAe0fPbfRn/VYHe6mCp/APmMbda/NiHE3aZq7q0kWhPmz1LVTukeaREmZ7SN8auyLOy9/mS0RIQLeV0AR8vsrA==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#a8ed0f8e1be3a1e09016e07a74cfd13c85bbc167", + "integrity": "sha512-3d/JHWgeS+LFPRahCAXdLwnBYQk4XUYybtgCm7VsdmMDtCeGUTksLsEY7F1Zqm+ULqZjmCtYwAi8IPKy0fsSOw==", "license": "MIT", "dependencies": { - "classnames": "2.4.0", + "classnames": "2.5.0", "clipboard": "2.0.11", "html-entities": "^2.4.0", "jquery": "3.6.0", @@ -30771,7 +30771,7 @@ "prop-types": "15.8.1", "react": "16.12.0", "react-dom": "16.12.0", - "semver": "^7.5.2", + "semver": "^7.6.0", "simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5", "ua-parser-js": "^1.0.37", "underscore": "1.13.6" @@ -47000,9 +47000,9 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dependencies": { "lru-cache": "^6.0.0" }, diff --git a/package.json b/package.json index 10b4f107aa9d..e3c23d4538d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.45-1", + "version": "1.4.46-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -100,7 +100,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#83ae6194b3e4feb363ea9d061085a7ab76e35ffb", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#a8ed0f8e1be3a1e09016e07a74cfd13c85bbc167", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.10.1", diff --git a/src/App.tsx b/src/App.tsx index cbe5948f8d4e..0e247d5faa53 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -83,7 +83,6 @@ function App({url}: AppProps) { - {/* @ts-expect-error TODO: Remove this once Expensify (https://github.com/Expensify/App/issues/25231) is migrated to TypeScript. */} diff --git a/src/CONST.ts b/src/CONST.ts index cf0b54bceb42..8d4eaac44a38 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1543,6 +1543,8 @@ const CONST = { PATH_WITHOUT_POLICY_ID: /\/w\/[a-zA-Z0-9]+(\/|$)/, POLICY_ID_FROM_PATH: /\/w\/([a-zA-Z0-9]+)(\/|$)/, + + SHORT_MENTION: new RegExp(`@[\\w\\-\\+\\'#]+(?:\\.[\\w\\-\\'\\+]+)*`, 'gim'), }, PRONOUNS: { @@ -3277,6 +3279,12 @@ const CONST = { REPORT_FIELD_TITLE_FIELD_ID: 'text_title', + /** Dimensions for illustration shown in Confirmation Modal */ + CONFIRM_CONTENT_SVG_SIZE: { + HEIGHT: 220, + WIDTH: 130, + }, + DEBUG_CONSOLE: { LEVELS: { INFO: 'INFO', @@ -3320,6 +3328,10 @@ const CONST = { PREFER_CLASSIC: 'preferClassic', }, }, + + SESSION_STORAGE_KEYS: { + INITIAL_URL: 'INITIAL_URL', + }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/Expensify.js b/src/Expensify.tsx similarity index 64% rename from src/Expensify.js rename to src/Expensify.tsx index dfb59a0f8848..f822862ec434 100644 --- a/src/Expensify.js +++ b/src/Expensify.tsx @@ -1,9 +1,8 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react'; +import type {NativeEventSubscription} from 'react-native'; import {AppState, Linking} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import Onyx, {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import ConfirmModal from './components/ConfirmModal'; import DeeplinkWrapper from './components/DeeplinkWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; @@ -12,14 +11,13 @@ import GrowlNotification from './components/GrowlNotification'; import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import SplashScreenHider from './components/SplashScreenHider'; import UpdateAppModal from './components/UpdateAppModal'; -import withLocalize, {withLocalizePropTypes} from './components/withLocalize'; import CONST from './CONST'; +import useLocalize from './hooks/useLocalize'; import * as EmojiPickerAction from './libs/actions/EmojiPickerAction'; import * as Report from './libs/actions/Report'; import * as User from './libs/actions/User'; import * as ActiveClientManager from './libs/ActiveClientManager'; import BootSplash from './libs/BootSplash'; -import compose from './libs/compose'; import * as Growl from './libs/Growl'; import Log from './libs/Log'; import migrateOnyx from './libs/migrateOnyx'; @@ -27,16 +25,18 @@ import Navigation from './libs/Navigation/Navigation'; import NavigationRoot from './libs/Navigation/NavigationRoot'; import NetworkConnection from './libs/NetworkConnection'; import PushNotification from './libs/Notification/PushNotification'; -// eslint-disable-next-line no-unused-vars -import subscribePushNotification from './libs/Notification/PushNotification/subscribePushNotification'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import './libs/Notification/PushNotification/subscribePushNotification'; import StartupTimer from './libs/StartupTimer'; // This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection -// eslint-disable-next-line no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-unused-vars import UnreadIndicatorUpdater from './libs/UnreadIndicatorUpdater'; import Visibility from './libs/Visibility'; import ONYXKEYS from './ONYXKEYS'; import PopoverReportActionContextMenu from './pages/home/report/ContextMenu/PopoverReportActionContextMenu'; import * as ReportActionContextMenu from './pages/home/report/ContextMenu/ReportActionContextMenu'; +import type {Route} from './ROUTES'; +import type {ScreenShareRequest, Session} from './types/onyx'; Onyx.registerLogger(({level, message}) => { if (level === 'alert') { @@ -47,82 +47,63 @@ Onyx.registerLogger(({level, message}) => { } }); -const propTypes = { - /* Onyx Props */ +type ExpensifyOnyxProps = { + /** Whether the app is waiting for the server's response to determine if a room is public */ + isCheckingPublicRoom: OnyxEntry; /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user authToken */ - authToken: PropTypes.string, - - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), + session: OnyxEntry; /** Whether a new update is available and ready to install. */ - updateAvailable: PropTypes.bool, + updateAvailable: OnyxEntry; - /** Tells us if the sidebar has rendered - TODO: We don't use it as temporary solution to fix not hidding splashscreen */ - // eslint-disable-next-line react/no-unused-prop-types - isSidebarLoaded: PropTypes.bool, + /** Tells us if the sidebar has rendered */ + isSidebarLoaded: OnyxEntry; /** Information about a screen share call requested by a GuidesPlus agent */ - screenShareRequest: PropTypes.shape({ - /** Access token required to join a screen share room, generated by the backend */ - accessToken: PropTypes.string, - - /** Name of the screen share room to join */ - roomName: PropTypes.string, - }), - - /** Whether the app is waiting for the server's response to determine if a room is public */ - isCheckingPublicRoom: PropTypes.bool, + screenShareRequest: OnyxEntry; /** True when the user must update to the latest minimum version of the app */ - updateRequired: PropTypes.bool, + updateRequired: OnyxEntry; /** Whether we should display the notification alerting the user that focus mode has been auto-enabled */ - focusModeNotification: PropTypes.bool, + focusModeNotification: OnyxEntry; /** Last visited path in the app */ - lastVisitedPath: PropTypes.string, - - ...withLocalizePropTypes, + lastVisitedPath: OnyxEntry; }; -const defaultProps = { - session: { - authToken: null, - accountID: null, - }, - updateAvailable: false, - isSidebarLoaded: false, - screenShareRequest: null, - isCheckingPublicRoom: true, - updateRequired: false, - focusModeNotification: false, - lastVisitedPath: undefined, -}; +type ExpensifyProps = ExpensifyOnyxProps; const SplashScreenHiddenContext = React.createContext({}); -function Expensify(props) { - const appStateChangeListener = useRef(null); +function Expensify({ + isCheckingPublicRoom = true, + session, + updateAvailable, + isSidebarLoaded = false, + screenShareRequest, + updateRequired = false, + focusModeNotification = false, + lastVisitedPath, +}: ExpensifyProps) { + const appStateChangeListener = useRef(null); const [isNavigationReady, setIsNavigationReady] = useState(false); const [isOnyxMigrated, setIsOnyxMigrated] = useState(false); const [isSplashHidden, setIsSplashHidden] = useState(false); const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false); - const [initialUrl, setInitialUrl] = useState(null); + const {translate} = useLocalize(); + const [initialUrl, setInitialUrl] = useState(null); useEffect(() => { - if (props.isCheckingPublicRoom) { + if (isCheckingPublicRoom) { return; } setAttemptedToOpenPublicRoom(true); - }, [props.isCheckingPublicRoom]); + }, [isCheckingPublicRoom]); - const isAuthenticated = useMemo(() => Boolean(lodashGet(props.session, 'authToken', null)), [props.session]); - const autoAuthState = useMemo(() => lodashGet(props.session, 'autoAuthState', ''), [props.session]); + const isAuthenticated = useMemo(() => !!(session?.authToken ?? null), [session]); + const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]); const contextValue = useMemo( () => ({ @@ -168,8 +149,16 @@ function Expensify(props) { Log.info('[BootSplash] splash screen status', false, {appState, status}); if (status === 'visible') { - const propsToLog = _.omit(props, ['children', 'session']); - propsToLog.isAuthenticated = isAuthenticated; + const propsToLog: Omit = { + isCheckingPublicRoom, + updateRequired, + updateAvailable, + isSidebarLoaded, + screenShareRequest, + focusModeNotification, + isAuthenticated, + lastVisitedPath, + }; Log.alert('[BootSplash] splash screen is still visible', {propsToLog}, false); } }); @@ -194,7 +183,7 @@ function Expensify(props) { // If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report Linking.getInitialURL().then((url) => { setInitialUrl(url); - Report.openReportFromDeepLink(url, isAuthenticated); + Report.openReportFromDeepLink(url ?? '', isAuthenticated); }); // Open chat report from a deep link (only mobile native) @@ -216,7 +205,7 @@ function Expensify(props) { return null; } - if (props.updateRequired) { + if (updateRequired) { throw new Error(CONST.ERROR.UPDATE_REQUIRED); } @@ -231,20 +220,19 @@ function Expensify(props) { {/* We include the modal for showing a new update at the top level so the option is always present. */} - {/* If the update is required we won't show this option since a full screen update view will be displayed instead. */} - {props.updateAvailable && !props.updateRequired ? : null} - {props.screenShareRequest ? ( + {updateAvailable && !updateRequired ? : null} + {screenShareRequest ? ( User.joinScreenShare(props.screenShareRequest.accessToken, props.screenShareRequest.roomName)} + title={translate('guides.screenShare')} + onConfirm={() => User.joinScreenShare(screenShareRequest.accessToken, screenShareRequest.roomName)} onCancel={User.clearScreenShareRequest} - prompt={props.translate('guides.screenShareRequest')} - confirmText={props.translate('common.join')} - cancelText={props.translate('common.decline')} + prompt={translate('guides.screenShareRequest')} + confirmText={translate('common.join')} + cancelText={translate('common.decline')} isVisible /> ) : null} - {props.focusModeNotification ? : null} + {focusModeNotification ? : null} )} @@ -254,7 +242,7 @@ function Expensify(props) { @@ -265,40 +253,35 @@ function Expensify(props) { ); } -Expensify.propTypes = propTypes; -Expensify.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - isCheckingPublicRoom: { - key: ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, - initWithStoredValues: false, - }, - session: { - key: ONYXKEYS.SESSION, - }, - updateAvailable: { - key: ONYXKEYS.UPDATE_AVAILABLE, - initWithStoredValues: false, - }, - isSidebarLoaded: { - key: ONYXKEYS.IS_SIDEBAR_LOADED, - }, - screenShareRequest: { - key: ONYXKEYS.SCREEN_SHARE_REQUEST, - }, - updateRequired: { - key: ONYXKEYS.UPDATE_REQUIRED, - initWithStoredValues: false, - }, - focusModeNotification: { - key: ONYXKEYS.FOCUS_MODE_NOTIFICATION, - initWithStoredValues: false, - }, - lastVisitedPath: { - key: ONYXKEYS.LAST_VISITED_PATH, - }, - }), -)(Expensify); +export default withOnyx({ + isCheckingPublicRoom: { + key: ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, + initWithStoredValues: false, + }, + session: { + key: ONYXKEYS.SESSION, + }, + updateAvailable: { + key: ONYXKEYS.UPDATE_AVAILABLE, + initWithStoredValues: false, + }, + updateRequired: { + key: ONYXKEYS.UPDATE_REQUIRED, + initWithStoredValues: false, + }, + isSidebarLoaded: { + key: ONYXKEYS.IS_SIDEBAR_LOADED, + }, + screenShareRequest: { + key: ONYXKEYS.SCREEN_SHARE_REQUEST, + }, + focusModeNotification: { + key: ONYXKEYS.FOCUS_MODE_NOTIFICATION, + initWithStoredValues: false, + }, + lastVisitedPath: { + key: ONYXKEYS.LAST_VISITED_PATH, + }, +})(Expensify); export {SplashScreenHiddenContext}; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 56425f64a51c..f6a56dc73088 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -160,8 +160,9 @@ function AttachmentView({ const encryptedSourceUrl = isAuthTokenRequired ? addEncryptedAuthTokenToURL(source) : source; const onPDFLoadComplete = (path) => { - if (path && (transaction.transactionID || reportActionID)) { - CachedPDFPaths.add(transaction.transactionID || reportActionID, path); + const id = (transaction && transaction.transactionID) || reportActionID; + if (path && id) { + CachedPDFPaths.add(id, path); } if (!loadComplete) { setLoadComplete(true); diff --git a/src/components/ButtonWithDropdownMenu.tsx b/src/components/ButtonWithDropdownMenu.tsx index 8aa3a5f0b9f0..9466da601825 100644 --- a/src/components/ButtonWithDropdownMenu.tsx +++ b/src/components/ButtonWithDropdownMenu.tsx @@ -10,30 +10,33 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; import type AnchorAlignment from '@src/types/utils/AnchorAlignment'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; import type IconAsset from '@src/types/utils/IconAsset'; import Button from './Button'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import PopoverMenu from './PopoverMenu'; -type DropdownOption = { - value: T; +type PaymentType = DeepValueOf; + +type DropdownOption = { + value: PaymentType; text: string; - icon?: IconAsset; + icon: IconAsset; iconWidth?: number; iconHeight?: number; iconDescription?: string; }; -type ButtonWithDropdownMenuProps = { +type ButtonWithDropdownMenuProps = { /** Text to display for the menu header */ menuHeaderText?: string; /** Callback to execute when the main button is pressed */ - onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: T) => void; + onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: PaymentType) => void; /** Callback to execute when a dropdown option is selected */ - onOptionSelected?: (option: DropdownOption) => void; + onOptionSelected?: (option: DropdownOption) => void; /** Call the onPress function on main button when Enter key is pressed */ pressOnEnter?: boolean; @@ -52,19 +55,19 @@ type ButtonWithDropdownMenuProps = { /** Menu options to display */ /** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */ - options: Array>; + options: DropdownOption[]; /** The anchor alignment of the popover menu */ anchorAlignment?: AnchorAlignment; /* ref for the button */ - buttonRef?: RefObject; + buttonRef: RefObject; /** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */ enterKeyEventListenerPriority?: number; }; -function ButtonWithDropdownMenu({ +function ButtonWithDropdownMenu({ isLoading = false, isDisabled = false, pressOnEnter = false, @@ -80,7 +83,7 @@ function ButtonWithDropdownMenu({ options, onOptionSelected, enterKeyEventListenerPriority = 0, -}: ButtonWithDropdownMenuProps) { +}: ButtonWithDropdownMenuProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index 94146a2c2957..c0c146bbae08 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -4,13 +4,17 @@ import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import colors from '@styles/theme/colors'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import type IconAsset from '@src/types/utils/IconAsset'; import Button from './Button'; import Header from './Header'; import Icon from './Icon'; +import ImageSVG from './ImageSVG'; import Text from './Text'; type ConfirmContentProps = { @@ -64,6 +68,9 @@ type ConfirmContentProps = { /** Styles for icon */ iconAdditionalStyles?: StyleProp; + + /** Image to display with content */ + image?: IconAsset; }; function ConfirmContent({ @@ -84,79 +91,95 @@ function ConfirmContent({ promptStyles, contentStyles, iconAdditionalStyles, + image, }: ConfirmContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const theme = useTheme(); const {isOffline} = useNetwork(); + const StyleUtils = useStyleUtils(); const isCentered = shouldCenterContent; return ( - - - {typeof iconSource === 'function' && ( - - + {!!image && ( + + + + )} + + + + {typeof iconSource === 'function' && ( + + + + )} + +
- )} - -
+ {typeof prompt === 'string' ? {prompt} : prompt} - {typeof prompt === 'string' ? {prompt} : prompt} - - {shouldStackButtons ? ( - <> -