diff --git a/.eslintrc.js b/.eslintrc.js index 85a4e86797b6..822a7f66b474 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -14,11 +14,21 @@ const restrictedImportPaths = [ importNames: ['TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback', 'TouchableHighlight'], message: "Please use 'PressableWithFeedback' and/or 'PressableWithoutFeedback' from 'src/components/Pressable' instead.", }, + { + name: 'awesome-phonenumber', + importNames: ['parsePhoneNumber'], + message: "Please use '@libs/PhoneNumber' instead.", + }, { name: 'react-native-safe-area-context', importNames: ['useSafeAreaInsets', 'SafeAreaConsumer', 'SafeAreaInsetsContext'], message: "Please use 'useSafeAreaInsets' from 'src/hooks/useSafeAreaInset' and/or 'SafeAreaConsumer' from 'src/components/SafeAreaConsumer' instead.", }, + { + name: 'react', + importNames: ['CSSProperties'], + message: "Please use 'ViewStyle', 'TextStyle', 'ImageStyle' from 'react-native' instead.", + }, ]; const restrictedImportPatterns = [ diff --git a/.github/workflows/deployExpensifyHelp.yml b/.github/workflows/deployExpensifyHelp.yml index 82cd62c5e832..975f1f69e219 100644 --- a/.github/workflows/deployExpensifyHelp.yml +++ b/.github/workflows/deployExpensifyHelp.yml @@ -52,6 +52,14 @@ jobs: projectName: helpdot directory: ./docs/_site + - name: Setup Cloudflare CLI + run: pip3 install cloudflare + + - name: Purge Cloudflare cache + run: /home/runner/.local/bin/cli4 --delete hosts=["help.expensify.com"] /zones/:9ee042e6cfc7fd45e74aa7d2f78d617b/purge_cache + env: + CF_API_KEY: ${{ secrets.CLOUDFLARE_TOKEN }} + - name: Leave a comment on the PR uses: actions-cool/maintain-one-comment@de04bd2a3750d86b324829a3ff34d47e48e16f4b if: ${{ github.event_name == 'pull_request' }} diff --git a/Gemfile.lock b/Gemfile.lock index 93dab195ebdd..fcf4f878e2de 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,7 +81,8 @@ GEM declarative (0.0.20) digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.6.20231109) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) escape (0.0.4) @@ -261,6 +262,9 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1) unicode-display_width (2.5.0) webrick (1.8.1) word_wrap (1.0.0) @@ -294,4 +298,4 @@ RUBY VERSION ruby 2.6.10p210 BUNDLED WITH - 2.1.4 + 2.4.7 diff --git a/__mocks__/@ua/react-native-airship.js b/__mocks__/@ua/react-native-airship.js index 29be662e96a1..65e7c1a8b97e 100644 --- a/__mocks__/@ua/react-native-airship.js +++ b/__mocks__/@ua/react-native-airship.js @@ -28,6 +28,7 @@ const Airship = { enableUserNotifications: () => Promise.resolve(false), clearNotifications: jest.fn(), getNotificationStatus: () => Promise.resolve({airshipOptIn: false, systemEnabled: false}), + getActiveNotifications: () => Promise.resolve([]), }, contact: { identify: jest.fn(), diff --git a/android/app/build.gradle b/android/app/build.gradle index 685e9f206eb4..b7ed29f176dc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001041200 - versionName "1.4.12-0" + versionCode 1001041402 + versionName "1.4.14-2" } flavorDimensions "default" diff --git a/android/app/src/main/java/com/expensify/chat/MainApplication.java b/android/app/src/main/java/com/expensify/chat/MainApplication.java index a4f2bc97416d..bc76be739949 100644 --- a/android/app/src/main/java/com/expensify/chat/MainApplication.java +++ b/android/app/src/main/java/com/expensify/chat/MainApplication.java @@ -3,7 +3,6 @@ import android.content.Context; import android.database.CursorWindow; -import androidx.appcompat.app.AppCompatDelegate; import androidx.multidex.MultiDexApplication; import com.expensify.chat.bootsplash.BootSplashPackage; @@ -67,9 +66,6 @@ public ReactNativeHost getReactNativeHost() { public void onCreate() { super.onCreate(); - // Use night (dark) mode so native UI defaults to dark theme. - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - SoLoader.init(this, /* native exopackage */ false); if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { // If you opted-in for the New Architecture, we load the native entry point for this app. diff --git a/assets/animations/Fireworks.lottie b/assets/animations/Fireworks.lottie index f5a782c62f3a..142efdcd8fdc 100644 Binary files a/assets/animations/Fireworks.lottie and b/assets/animations/Fireworks.lottie differ diff --git a/assets/animations/ReviewingBankInfo.lottie b/assets/animations/ReviewingBankInfo.lottie index 93addc052e8b..a9974366cae7 100644 Binary files a/assets/animations/ReviewingBankInfo.lottie and b/assets/animations/ReviewingBankInfo.lottie differ diff --git a/contributingGuides/TS_STYLE.md b/contributingGuides/TS_STYLE.md index bc62020ffd54..a583941bf71d 100644 --- a/contributingGuides/TS_STYLE.md +++ b/contributingGuides/TS_STYLE.md @@ -24,6 +24,8 @@ - [1.17 `.tsx`](#tsx) - [1.18 No inline prop types](#no-inline-prop-types) - [1.19 Satisfies operator](#satisfies-operator) + - [1.20 Hooks instead of HOCs](#hooks-instead-of-hocs) + - [1.21 `compose` usage](#compose-usage) - [Exception to Rules](#exception-to-rules) - [Communication Items](#communication-items) - [Migration Guidelines](#migration-guidelines) @@ -124,7 +126,7 @@ type Foo = { -- [1.2](#d-ts-extension) **`d.ts` Extension**: Do not use `d.ts` file extension even when a file contains only type declarations. Only exceptions are `src/types/global.d.ts` and `src/types/modules/*.d.ts` files in which third party packages can be modified using module augmentation. Refer to the [Communication Items](#communication-items) section to learn more about module augmentation. +- [1.2](#d-ts-extension) **`d.ts` Extension**: Do not use `d.ts` file extension even when a file contains only type declarations. Only exceptions are `src/types/global.d.ts` and `src/types/modules/*.d.ts` files in which third party packages and JavaScript's built-in modules (e.g. `window` object) can be modified using module augmentation. Refer to the [Communication Items](#communication-items) section to learn more about module augmentation. > Why? Type errors in `d.ts` files are not checked by TypeScript [^1]. @@ -509,6 +511,102 @@ type Foo = { } satisfies Record; ``` + + +- [1.20](#hooks-instead-of-hocs) **Hooks instead of HOCs**: Replace HOCs usage with Hooks whenever possible. + + > Why? Hooks are easier to use (can be used inside the function component), and don't need nesting or `compose` when exporting the component. It also allows us to remove `compose` completely in some components since it has been bringing up some issues with TypeScript. Read the [`compose` usage](#compose-usage) section for further information about the TypeScript issues with `compose`. + + > Note: Because Onyx doesn't provide a hook yet, in a component that accesses Onyx data with `withOnyx` HOC, please make sure that you don't use other HOCs (if applicable) to avoid HOC nesting. + + ```tsx + // BAD + type ComponentOnyxProps = { + session: OnyxEntry; + }; + + type ComponentProps = WindowDimensionsProps & + WithLocalizeProps & + ComponentOnyxProps & { + someProp: string; + }; + + function Component({windowWidth, windowHeight, translate, session, someProp}: ComponentProps) { + // component's code + } + + export default compose( + withWindowDimensions, + withLocalize, + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + }), + )(Component); + + // GOOD + type ComponentOnyxProps = { + session: OnyxEntry; + }; + + type ComponentProps = ComponentOnyxProps & { + someProp: string; + }; + + function Component({session, someProp}: ComponentProps) { + const {windowWidth, windowHeight} = useWindowDimensions(); + const {translate} = useLocalize(); + // component's code + } + + // There is no hook alternative for withOnyx yet. + export default withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + })(Component); + ``` + + + +- [1.21](#compose-usage) **`compose` usage**: Avoid the usage of `compose` function to compose HOCs in TypeScript files. Use nesting instead. + + > Why? `compose` function doesn't work well with TypeScript when dealing with several HOCs being used in a component, many times resulting in wrong types and errors. Instead, nesting can be used to allow a seamless use of multiple HOCs and result in a correct return type of the compoment. Also, you can use [hooks instead of HOCs](#hooks-instead-of-hocs) whenever possible to minimize or even remove the need of HOCs in the component. + + ```ts + // BAD + export default compose( + withCurrentUserPersonalDetails, + withReportOrNotFound(), + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + }), + )(Component); + + // GOOD + export default withCurrentUserPersonalDetails( + withReportOrNotFound()( + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + })(Component), + ), + ); + + // GOOD - alternative to HOC nesting + const ComponentWithOnyx = withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + })(Component); + const ComponentWithReportOrNotFound = withReportOrNotFound()(ComponentWithOnyx); + export default withCurrentUserPersonalDetails(ComponentWithReportOrNotFound); + ``` + ## Exception to Rules Most of the rules are enforced in ESLint or checked by TypeScript. If you think your particular situation warrants an exception, post the context in the `#expensify-open-source` Slack channel with your message prefixed with `TS EXCEPTION:`. The internal engineer assigned to the PR should be the one that approves each exception, however all discussion regarding granting exceptions should happen in the public channel instead of the GitHub PR page so that the TS migration team can access them easily. @@ -521,7 +619,7 @@ This rule will apply until the migration is done. After the migration, discussio > Comment in the `#expensify-open-source` Slack channel if any of the following situations are encountered. Each comment should be prefixed with `TS ATTENTION:`. Internal engineers will access each situation and prescribe solutions to each case. Internal engineers should refer to general solutions to each situation that follows each list item. -- I think types definitions in a third party library is incomplete or incorrect +- I think types definitions in a third party library or JavaScript's built-in module are incomplete or incorrect When the library indeed contains incorrect or missing type definitions and it cannot be updated, use module augmentation to correct them. All module augmentation code should be contained in `/src/types/modules/*.d.ts`, each library as a separate file. @@ -540,7 +638,7 @@ declare module "external-library-name" { > This section contains instructions that are applicable during the migration. -- 🚨 DO NOT write new code in TypeScript yet. The only time you write TypeScript code is when the file you're editing has already been migrated to TypeScript by the migration team. This guideline will be updated once it's time for new code to be written in TypeScript. If you're doing a major overhaul or refactoring of particular features or utilities of App and you believe it might be beneficial to migrate relevant code to TypeScript as part of the refactoring, please ask in the #expensify-open-source channel about it (and prefix your message with `TS ATTENTION:`). +- 🚨 DO NOT write new code in TypeScript yet. The only time you write TypeScript code is when the file you're editing has already been migrated to TypeScript by the migration team, or when you need to add new files under `src/libs`, `src/hooks`, `src/styles`, and `src/languages` directories. This guideline will be updated once it's time for new code to be written in TypeScript. If you're doing a major overhaul or refactoring of particular features or utilities of App and you believe it might be beneficial to migrate relevant code to TypeScript as part of the refactoring, please ask in the #expensify-open-source channel about it (and prefix your message with `TS ATTENTION:`). - If you're migrating a module that doesn't have a default implementation (i.e. `index.ts`, e.g. `getPlatform`), convert `index.website.js` to `index.ts`. Without `index.ts`, TypeScript cannot get type information where the module is imported. @@ -579,6 +677,25 @@ object?.foo ?? 'bar'; const y: number = 123; // TS error: Unused '@ts-expect-error' directive. ``` +- The TS issue I'm working on is blocked by another TS issue because of type errors. What should I do? + + In order to proceed with the migration faster, we are now allowing the use of `@ts-expect-error` annotation to temporally suppress those errors and help you unblock your issues. The only requirements is that you MUST add the annotation with a comment explaining that it must be removed when the blocking issue is migrated, e.g.: + + ```tsx + return ( + + ); + ``` + + **You will also need to reference the blocking issue in your PR.** You can find all the TS issues [here](https://github.com/orgs/Expensify/projects/46). + ## Learning Resources ### Quickest way to learn TypeScript diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 39b186beb022..6f62e6a5ba00 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -8,7 +8,7 @@ "license": "MIT", "dependencies": { "electron-context-menu": "^2.3.0", - "electron-log": "^4.4.7", + "electron-log": "^4.4.8", "electron-serve": "^1.2.0", "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" @@ -140,9 +140,9 @@ "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" }, "node_modules/electron-log": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.7.tgz", - "integrity": "sha512-uFZQdgevOp9Fn5lDOrJMU/bmmYxDLZitbIHJM7VXN+cpB59ZnPt1FQL4bOf/Dl2gaIMPYJEfXx38GvJma5iV6A==" + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz", + "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" }, "node_modules/electron-serve": { "version": "1.2.0", @@ -531,9 +531,9 @@ "integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw==" }, "electron-log": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.7.tgz", - "integrity": "sha512-uFZQdgevOp9Fn5lDOrJMU/bmmYxDLZitbIHJM7VXN+cpB59ZnPt1FQL4bOf/Dl2gaIMPYJEfXx38GvJma5iV6A==" + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-4.4.8.tgz", + "integrity": "sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA==" }, "electron-serve": { "version": "1.2.0", diff --git a/desktop/package.json b/desktop/package.json index 7689c18f0dbd..7545e4b57dba 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -5,7 +5,7 @@ "scripts": {}, "dependencies": { "electron-context-menu": "^2.3.0", - "electron-log": "^4.4.7", + "electron-log": "^4.4.8", "electron-serve": "^1.2.0", "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index e320b690c226..d4e12d396ceb 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -31,7 +31,7 @@ platforms: - href: billing-and-subscriptions title: Billing & Subscriptions - icon: /assets/images/money-wings.svg + icon: /assets/images/subscription-annual.svg description: Here is where you can review Expensify's billing and subscription options, plan types, and payment methods. - href: expense-and-report-features @@ -71,7 +71,7 @@ platforms: - href: send-payments title: Send Payments - icon: /assets/images/money-wings.svg + icon: /assets/images/send-money.svg description: Uncover step-by-step guidance on sending direct reimbursements to employees, paying an invoice to a vendor, and utilizing third-party payment options. - href: workspace-and-domain-settings @@ -105,7 +105,7 @@ platforms: - href: billing-and-plan-types title: Billing & Plan Types - icon: /assets/images/money-wings.svg + icon: /assets/images/subscription-annual.svg description: Here is where you can review Expensify's billing and subscription options, plan types, and payment methods. - href: expensify-card diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md new file mode 100644 index 000000000000..65361ba1af9a --- /dev/null +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Certinia.md @@ -0,0 +1,150 @@ +--- +title: Certinia +description: Guide to connecting Expensify and Certinia FFA and PSA/SRP (formerly known as FinancialForce) +--- +# Overview +[Cetinia](https://use.expensify.com/financialforce) (Formerly known as FinancialForce)is a cloud-based software solution that provides a range of financial management and accounting applications built on the Salesforce platform. There are two versions: PSA/SRP and FFA and we support both. + +# Before connecting to Certinia +Install the Expensify bundle in Certinia using the relevant installer: +* [PSA/SRP](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2M000002J0BHD%252Fpackaging%252FinstallPackage.apexp%253Fp0%253D04t2M000002J0BH) +* [FFA](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4p000001UQVj) + +## Check contact details in Certinia +First, make sure you have a user and contact in Certinia that match your main email in Expensify. Then, create contacts for all employees who will be sending expense reports. Ensure that each contact's email matches the one they use in their Expensify account. + +## If you use PSA/SRP +Each report approver needs both a User and a Contact. The user does not need to have a SalesForce license. These can be free chatter users. +Set permission controls in Certinia for your user for each contact/resource. +* Go to Permission Controls + - Create a new permission control + - Set yourself (exporter) as the user + - Select the resource (report submitter) + - Grant all available permissions +* Set permissions on any project you are exporting to + - Go to **Projects** > _select a project_ > **Project Attributes** > **Allow Expenses Without Assignment** + - Select the project > **Edit** + - Under the Project Attributes section, check **Allow Expenses Without Assignment** +* Set up Expense Types (categories in Expensify - _SRP only_) + - Go to **Main Menu** > _+ symbol_ > **Expense Type GLA Mappings** + - Click **New** to add new mappings + +# How to connect to Certinia +1. Go to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** in Expensify +2. Click **Create a New Certinia (FinancialForce) Connection** +3. Log into your Certinia account +4. Expensify and Certinia will begin to sync (in Expensify) + +# How to configure export settings for Certinia +## Preferred Exporter +The preferred exporter is the user who will be the main exporter of reports. This person will receive the notifications for errors. + +## Payable Invoice Status and Date +Reports can be exported as Complete or In Progress, using date of last expense, submitted date or exported date. + +## Reimbursable and non-reimbursable exports +Both reimbursable and non-reimbursable reports are exported as payable invoices (FFA) or expense reports (PSA/SRP). If you have both Reimbursable and Non-Reimbursable expenses on a single report, we will create a separate payable invoice/expense report for each type. + +## Default Vendor (FFA) +Choose from the full list of vendors from your Certinia FFA account, this will be applied to the non-reimbursable payable invoices. + +# How to Configure coding for Certinia +## Company +Select which FinancialForce company to import from/export to. + +## Chart of Accounts (FFA) +Prepaid Expense Type and Profit & Loss accounts are imported to be used as categories on each expense. + +## Expense Type GLA Mappings (PSA/SRP) +Your Expense Type GLA Mappings are enabled in Expensify to use as categories on each expense when using both PSA and SRP; however, PSA will not import or export categories, while SRP will. + +## Dimensions (FFA) +We import four dimension levels and each has three options to select from: + +* Do not map: FinancialForce defaults will apply to the payable invoice, without importing into Expensify +* Tags: These are shown in the Tag section of your workspace, and employees can select them on each expense created +* Report fields: These will show in the Reports section of your workspace. Employees can select one to be applied at the header level i.e. the entire report. + +## Projects, Assignments, or Projects & Assignments (PSA/SRP) +These can be imported as tags with **Milestones** being optional. When selecting to import only projects, we will derive the account from the project. If an assignment is selected, we will derive both the account and project from the assignment. + +Note: If you are using a project that does not have an assignment, the box **Allow Expenses Without Assignment** must be checked on the project in FinancialForce. + +## Tax +Import tax rates from Certinia to apply to expenses. + +# How to configure advanced settings for Certinia +## Auto Sync +Auto Sync in Certinia performs daily updates to your coding. Additionally, it automatically exports reports after they receive final approval. For Non-Reimbursable expenses, syncing happens immediately upon final approval of the report. In the case of Reimbursable expenses, syncing occurs as soon as the report is reimbursed or marked as reimbursed. + +## Export tax as non-billable +When exporting Billable expenses, this dictates whether you will also bill the tax component to your clients/customers. + +# Deep Dive +## Multi-Currency in Certinia PSA/SRP +When exporting to Certinia PSA/SRP you may see up to three different currencies on the expense report in Certinia, if employees are submitting expenses in more than one original currency. +* Summary Total Reimbursement Amount: this currency is derived from the currency of the project selected on the expense. +* Amount field on the Expense line: this currency is derived from the Expensify workspace default report currency. +* Reimbursable Amount on the Expense line: this currency is derived from the currency of the resource with an email matching the report submitter. + +# FAQ +## What happens if the report can’t be exported to Certinia? +* The preferred exporter will receive an email outlining the issue and any specific error messages +* Any error messages preventing the export from taking place will be recorded in the report’s history +* The report will be listed in the exporter’s Expensify Inbox as awaiting export. + +## If I enable Auto Sync, what happens to existing approved and reimbursed reports? +You can activate Auto Sync without worry because it relies on Final Approval to trigger auto-export. Existing Approved reports won't be affected. However, for Approved reports that haven't been exported to Certinia, you'll need to either manually export them or mark them as manually entered. + +## How do I export tax? +Tax rates are created in Expensify through the tax tracking feature under **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Tax**. We export the tax amount calculated on the expenses. + +## How do reports map to Payable Invoices in Certinia FFA? +* Account Name - Account associated with Expensify submitter’s email address +* Reference 1 - Report URL +* Invoice Description - Report title + +## How do reports map to Expense Reports in Certinia PSA/SRP? +* Expense report name - Report title +* Resource - User associated with Expensify submitter’s email address +* Description - Report URL +* Approver - Expensify report approver + +# Sync and Export Errors + +## ExpensiError FF0047: You must have an Ops Edit permission to edit approved records. +This error indicates that the permission control setup between the connected user and the report submitter or region is missing Ops Edit permission. + +In Certinia go to Permission Controls and click the one you need to edit. Make sure that Expense Ops Edit is selected under Permissions. + +## ExpensiError FF0076: Could not find employee in Certinia +Go to Contacts in Certinia and add the report creator/submitter's Expensify email address to their employee record, or create a record with that email listed. + +If a record already exists then search for their email address to confirm it is not associated with multiple records. + +## ExpensiError FF0089: Expense Reports for this Project require an Assignment +This error indicates that the project needs to have the permissions adjusted in Certinia + +Go to Projects > [project name] > Project Attributes and check Allow Expense Without Assignment. + +## ExpensiError FF0091: Bad Field Name — [field] is invalid for [object] +This means the field in question is not accessible to the user profile in Certinia for the user whose credentials were used to make the connection within Expensify. + +To correct this: +* Go to Setup > Build > expand Create > Object within Certinia +* Then go to Payable Invoice > Custom Fields and Relationships +* Click View Field Accessibility +* Find the employee profile in the list and select Hidden +* Make sure both checkboxes for Visible are selected + +Once this step has been completed, sync the connection within Expensify by going to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** > **Sync Now** and then attempt to export the report again. + +## ExpensiError FF0132: Insufficient access. Make sure you are connecting to Certinia with a user that has the 'Modify All Data' permission + +Log into Certinia and go to Setup > Manage Users > Users and find the user whose credentials made the connection. + +* Click on their profile on the far right side of the page +* Go to System > System Permissions +* Enable Modify All Data and save + +Sync the connection within Expensify by going to **Settings** > **Workspaces** > **Groups** > _[Workspace Name]_ > **Connections** > **Sync Now** and then attempt to export the report again diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md b/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md deleted file mode 100644 index 18c78ac7fc10..000000000000 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/FinancalForce.md +++ /dev/null @@ -1,111 +0,0 @@ ---- -title: Financial Force -description: Guide to connecting Expensify and FinancialForce FFA and PSA/SRP ---- -# Overview -[FinancialForce](https://use.expensify.com/financialforce) is a cloud-based software solution that provides a range of financial management and accounting applications built on the Salesforce platform. There are two versions: PSA/SRP and FFA and we support both. - -# Before connecting to FinancialForce -Install the Expensify bundle in SalesForce using the relevant installer: -* [PSA/SRP](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2M000002J0BHD%252Fpackaging%252FinstallPackage.apexp%253Fp0%253D04t2M000002J0BH) -* [FFA](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t4p000001UQVj) - -## Check contact details in FF -First, make sure you have a user and contact in FinancialForce that match your main email in Expensify. Then, create contacts for all employees who will be sending expense reports. Ensure that each contact's email matches the one they use in their Expensify account. - -## If you use PSA/SRP -Each report approver needs both a User and a Contact. The user does not need to have a SalesForce license. These can be free chatter users. -Set permission controls in FinancialForce for your user for each contact/resource. -* Go to Permission Controls - - Create a new permission control - - Set yourself (exporter) as the user - - Select the resource (report submitter) - - Grant all available permissions -* Set permissions on any project you are exporting to - - Go to **Projects** > _select a project_ > **Project Attributes** > **Allow Expenses Without Assignment** - - Select the project > **Edit** - - Under the Project Attributes section, check **Allow Expenses Without Assignment** -* Set up Expense Types (categories in Expensify - _SRP only_) - - Go to **Main Menu** > _+ symbol_ > **Expense Type GLA Mappings** - - Click **New** to add new mappings - -# How to connect to FinancialForce -1. Go to **Settings** > **Policies** > **Groups** > _[Policy Name]_ > **Connections in Expensify** -2. Click **Create a New FinancialForce Connection** -3. Log into your FinancialForce account -4. Expensify and FinancialForce will begin to sync (in Expensify) - -# How to configure export settings for FinancialForce -## Preferred Exporter -The preferred exporter is the user who will be the main exporter of reports. This person will receive the notifications for errors. - -## Payable Invoice Status and Date -Reports can be exported as Complete or In Progress, using date of last expense, submitted date or exported date. - -## Reimbursable and non-reimbursable exports -Both reimbursable and non-reimbursable reports are exported as payable invoices (FFA) or expense reports (PSA/SRP). If you have both Reimbursable and Non-Reimbursable expenses on a single report, we will create a separate payable invoice/expense report for each type. - -## Default Vendor (FFA) -Choose from the full list of vendors from your FinancialForce FFA account, this will be applied to the non-reimbursable payable invoices. - -# How to Configure coding for Financial Force -## Company -Select which FinancialForce company to import from/export to. - -## Chart of Accounts (FFA) -Prepaid Expense Type and Profit & Loss accounts are imported to be used as categories on each expense. - -## Expense Type GLA Mappings (PSA/SRP) -Your Expense Type GLA Mappings are enabled in Expensify to use as categories on each expense when using both PSA and SRP; however, PSA will not import or export categories, while SRP will. - -## Dimensions (FFA) -We import four dimension levels and each has three options to select from: - -* Do not map: FinancialForce defaults will apply to the payable invoice, without importing into Expensify -* Tags: These are shown in the Tag section of your policy, and employees can select them on each expense created -* Report fields: These will show in the Reports section of your policy. Employees can select one to be applied at the header level i.e. the entire report. - -## Projects, Assignments, or Projects & Assignments (PSA/SRP) -These can be imported as tags with **Milestones** being optional. When selecting to import only projects, we will derive the account from the project. If an assignment is selected, we will derive both the account and project from the assignment. - -Note: If you are using a project that does not have an assignment, the box **Allow Expenses Without Assignment** must be checked on the project in FinancialForce. - -## Tax -Import tax rates from FinancialForce to apply to expenses. - -# How to configure advanced settings for Financial Force -## Auto Sync -Auto Sync in FinancialForce performs daily updates to your coding. Additionally, it automatically exports reports after they receive final approval. For Non-Reimbursable expenses, syncing happens immediately upon final approval of the report. In the case of Reimbursable expenses, syncing occurs as soon as the report is reimbursed or marked as reimbursed. - -## Export tax as non-billable -When exporting Billable expenses, this dictates whether you will also bill the tax component to your clients/customers. - -# Deep Dive -## Multi-Currency in FinancialForce PSA/SRP -When exporting to FinancialForce PSA/SRP you may see up to three different currencies on the expense report in FinancialForce, if employees are submitting expenses in more than one original currency. -* Summary Total Reimbursement Amount: this currency is derived from the currency of the project selected on the expense. -* Amount field on the Expense line: this currency is derived from the Expensify policy default report currency. -* Reimbursable Amount on the Expense line: this currency is derived from the currency of the resource with an email matching the report submitter. - -# FAQ -## What happens if the report can’t be exported to FinancialForce? -* The preferred exporter will receive an email outlining the issue and any specific error messages -* Any error messages preventing the export from taking place will be recorded in the report’s history -* The report will be listed in the exporter’s Expensify Inbox as awaiting export. - -## If I enable Auto Sync, what happens to existing approved and reimbursed reports? -You can activate Auto Sync without worry because it relies on Final Approval to trigger auto-export. Existing Approved reports won't be affected. However, for Approved reports that haven't been exported to FinancialForce, you'll need to either manually export them or mark them as manually entered. - -## How do I export tax? -Tax rates are created in Expensify through the tax tracking feature under **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Tax**. We export the tax amount calculated on the expenses. - -## How do reports map to Payable Invoices in FinancialForce FFA? -* Account Name - Account associated with Expensify submitter’s email address -* Reference 1 - Report URL -* Invoice Description - Report title - -## How do reports map to Expense Reports in FinancialForce PSA/SRP? -* Expense report name - Report title -* Resource - User associated with Expensify submitter’s email address -* Description - Report URL -* Approver - Expensify report approver diff --git a/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md new file mode 100644 index 000000000000..3c5bc0fe2421 --- /dev/null +++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Budgets.md @@ -0,0 +1,56 @@ +--- +title: Budgets +description: Track employee spending across categories and tags by using Expensify's Budgets feature. +--- + +# About +Expensify’s Budgets feature allows you to: +- Set monthly and yearly budgets +- Track spending across categories and tags on an individual and workspace basis +- Get notified when a budget has met specific thresholds + +# How-to +## Category Budgets +1. Navigate to **Settings > Group > [Workspace Name] > Categories** +2. Click the **Edit Rules** button for the category you want to add a budget to +3. Select the **Budget** tab at the top of the modal that opens +4. Click the switch next to **Enable Budget** +5. Once enabled, you will see additional settings to configure: + - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget + - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace + - **Per individual budget**: you can enter an amount if you want to set a budget per person + - **Notification threshold** - this is the % in which you will be notified as the budgets are hit + +## Single-level Tag Budgets +1. Navigate to **Settings > Group > [Workspace Name] > Tags** +2. Click **Edit Budget** next to the tag you want to add a budget to +3. Click the switch next to **Enable Budget** +4. Once enabled, you will see additional settings to configure: + - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget + - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace + - **Per individual budget**: you can enter an amount if you want to set a budget per person + - **Notification threshold** - this is the % in which you will be notified as the budgets are hit + +## Multi-level Tag Budgets +1. Navigate to **Settings > Group > [Workspace Name] > Tags** +2. Click the **Edit Tags** button +3. Click the **Edit Budget** button next to the subtag you want to apply a budget to +4. Click the switch next to **Enable Budget** +5. Once enabled, you will see additional settings to configure: + - **Budget frequency**: you can select if you want this to be a monthly or a yearly budget + - **Total workspace budget**: you can enter an amount if you want to set a budget for the entire workspace + - **Per individual budget**: you can enter an amount if you want to set a budget per person + - **Notification threshold** - this is the % in which you will be notified as the budgets are hit + +# FAQ +## Can I import budgets as a CSV? +At this time, you cannot import budgets via CSV since we don’t import categories or tags from direct accounting integrations. + +## When will I be notified as a budget is hit? +Notifications are sent twice: + - When your notification threshold is hit (i.e, if you set this as 50%, you’ll be notified when 50% of the budget is met) + - When 100% of the budget is met + +## How will I be notified when a budget is hit? +A message will be sent in the #admins room of the Workspace. + diff --git a/docs/articles/new-expensify/get-paid-back/Referral-Program.md b/docs/articles/new-expensify/get-paid-back/Referral-Program.md index 34a35f5dc7c8..6ffb923aeb76 100644 --- a/docs/articles/new-expensify/get-paid-back/Referral-Program.md +++ b/docs/articles/new-expensify/get-paid-back/Referral-Program.md @@ -12,13 +12,16 @@ As a thank you, every time you bring a new customer into New Expensify, you'll g # How to get paid to refer anyone to New Expensify -The sky's the limit for this referral program! Your referral can be anyone - a friend, family member, boss, coworker, neighbor, or even social media follower. We're making it as easy as possible to get that cold hard referral $$$. +The sky's the limit for this referral program! Your referral can be anyone - a friend, family member, boss, coworker, neighbor, or even social media follower. We're making it as easy as possible to get that cold hard $$$. -1. There are a bunch of different ways to kick off a referral in New Expensify: +1. There are a bunch of different ways to refer someone to New Expensify: - Start a chat - Request money - Send money - - @ mention someone + - Split a bill + - Assign them a task + - @ mention them + - Invite them to a room - Add them to a workspace 2. You'll get $250 for each referral as long as: diff --git a/docs/assets/images/money-wings.svg b/docs/assets/images/money-wings.svg deleted file mode 100644 index 87ffdf28ec4b..000000000000 --- a/docs/assets/images/money-wings.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/assets/images/send-money.svg b/docs/assets/images/send-money.svg new file mode 100644 index 000000000000..e858f0d5c327 --- /dev/null +++ b/docs/assets/images/send-money.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/subscription-annual.svg b/docs/assets/images/subscription-annual.svg new file mode 100644 index 000000000000..a4b99a43b16e --- /dev/null +++ b/docs/assets/images/subscription-annual.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f4ef6d22bea6..67c47dbb30cf 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.12 + 1.4.14 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.12.0 + 1.4.14.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes @@ -120,8 +120,6 @@ UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIUserInterfaceStyle - Dark UIViewControllerBasedStatusBarAppearance diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index c7fb13979540..d13112319dd6 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.12 + 1.4.14 CFBundleSignature ???? CFBundleVersion - 1.4.12.0 + 1.4.14.2 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 390511397b0e..0fac30a26430 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,25 +1,25 @@ PODS: - - Airship (16.11.3): - - Airship/Automation (= 16.11.3) - - Airship/Basement (= 16.11.3) - - Airship/Core (= 16.11.3) - - Airship/ExtendedActions (= 16.11.3) - - Airship/MessageCenter (= 16.11.3) - - Airship/Automation (16.11.3): + - Airship (16.12.1): + - Airship/Automation (= 16.12.1) + - Airship/Basement (= 16.12.1) + - Airship/Core (= 16.12.1) + - Airship/ExtendedActions (= 16.12.1) + - Airship/MessageCenter (= 16.12.1) + - Airship/Automation (16.12.1): - Airship/Core - - Airship/Basement (16.11.3) - - Airship/Core (16.11.3): + - Airship/Basement (16.12.1) + - Airship/Core (16.12.1): - Airship/Basement - - Airship/ExtendedActions (16.11.3): + - Airship/ExtendedActions (16.12.1): - Airship/Core - - Airship/MessageCenter (16.11.3): + - Airship/MessageCenter (16.12.1): - Airship/Core - - Airship/PreferenceCenter (16.11.3): + - Airship/PreferenceCenter (16.12.1): - Airship/Core - - AirshipFrameworkProxy (2.0.8): - - Airship (= 16.11.3) - - Airship/MessageCenter (= 16.11.3) - - Airship/PreferenceCenter (= 16.11.3) + - AirshipFrameworkProxy (2.1.1): + - Airship (= 16.12.1) + - Airship/MessageCenter (= 16.12.1) + - Airship/PreferenceCenter (= 16.12.1) - AppAuth (1.6.2): - AppAuth/Core (= 1.6.2) - AppAuth/ExternalUserAgent (= 1.6.2) @@ -558,8 +558,8 @@ PODS: - React-jsinspector (0.72.4) - React-logger (0.72.4): - glog - - react-native-airship (15.2.6): - - AirshipFrameworkProxy (= 2.0.8) + - react-native-airship (15.3.1): + - AirshipFrameworkProxy (= 2.1.1) - React-Core - react-native-blob-util (0.17.3): - React-Core @@ -1160,8 +1160,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - Airship: c70eed50e429f97f5adb285423c7291fb7a032ae - AirshipFrameworkProxy: 7bc4130c668c6c98e2d4c60fe4c9eb61a999be99 + Airship: 2f4510b497a8200780752a5e0304a9072bfffb6d + AirshipFrameworkProxy: ea1b6c665c798637b93c465b5e505be3011f1d9d AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 boost: 57d2868c099736d80fcd648bf211b4431e51a558 BVLinearGradient: 421743791a59d259aec53f4c58793aad031da2ca @@ -1224,7 +1224,7 @@ SPEC CHECKSUMS: React-jsiexecutor: c7f826e40fa9cab5d37cab6130b1af237332b594 React-jsinspector: aaed4cf551c4a1c98092436518c2d267b13a673f React-logger: da1ebe05ae06eb6db4b162202faeafac4b435e77 - react-native-airship: 5d19f4ba303481cf4101ff9dee9249ef6a8a6b64 + react-native-airship: 6ded22e4ca54f2f80db80b7b911c2b9b696d9335 react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6 react-native-cameraroll: 8ffb0af7a5e5de225fd667610e2979fc1f0c2151 react-native-config: 7cd105e71d903104e8919261480858940a6b9c0e diff --git a/package-lock.json b/package-lock.json index c03809caece0..1c2ae325575d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.12-0", + "version": "1.4.14-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.12-0", + "version": "1.4.14-2", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -41,7 +41,7 @@ "@rnmapbox/maps": "^10.0.11", "@shopify/flash-list": "^1.6.1", "@types/node": "^18.14.0", - "@ua/react-native-airship": "^15.2.6", + "@ua/react-native-airship": "^15.3.1", "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", @@ -50,7 +50,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#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#398bf7c6a6d37f229a41d92bd7a4324c0fd32849", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", @@ -20437,9 +20437,9 @@ } }, "node_modules/@ua/react-native-airship": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-15.2.6.tgz", - "integrity": "sha512-dVlBPPYXD/4SEshv/X7mmt3xF8WfnNqiSNzCyqJSLAZ1aJuPpP9Z5WemCYsa2iv6goRZvtJSE4P79QKlfoTwXw==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-15.3.1.tgz", + "integrity": "sha512-g5YX4/fpBJ0ml//7ave8HE68uF4QFriCuei0xJwK+ClzbTDWYB6OldvE/wj5dMpgQ95ZFSbr5LU77muYScxXLQ==", "engines": { "node": ">= 16.0.0" }, @@ -29894,8 +29894,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", - "integrity": "sha512-s9l/Zy3UjDBrq0WTkgEue1DXLRkkYtuqnANQlVmODHJ9HkJADjrVSv2D0U3ltqd9X7vLCLCmmwl5AUE6466gGg==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#398bf7c6a6d37f229a41d92bd7a4324c0fd32849", + "integrity": "sha512-H7UrLgWIr8mCoPc1oxbeYW2RwLzUWI6jdjbV6cRnrlp8cDW3IyZISF+BQSPFDj7bMhNAbczQPtEOE1gld21Cvg==", "license": "MIT", "dependencies": { "classnames": "2.3.1", @@ -29911,7 +29911,7 @@ "simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5", "string.prototype.replaceall": "^1.0.6", "ua-parser-js": "^1.0.35", - "underscore": "1.13.1" + "underscore": "1.13.6" } }, "node_modules/expensify-common/node_modules/prop-types": { @@ -29983,12 +29983,6 @@ "node": "*" } }, - "node_modules/expensify-common/node_modules/underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", - "license": "MIT" - }, "node_modules/express": { "version": "4.18.1", "license": "MIT", @@ -67439,9 +67433,9 @@ } }, "@ua/react-native-airship": { - "version": "15.2.6", - "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-15.2.6.tgz", - "integrity": "sha512-dVlBPPYXD/4SEshv/X7mmt3xF8WfnNqiSNzCyqJSLAZ1aJuPpP9Z5WemCYsa2iv6goRZvtJSE4P79QKlfoTwXw==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@ua/react-native-airship/-/react-native-airship-15.3.1.tgz", + "integrity": "sha512-g5YX4/fpBJ0ml//7ave8HE68uF4QFriCuei0xJwK+ClzbTDWYB6OldvE/wj5dMpgQ95ZFSbr5LU77muYScxXLQ==", "requires": {} }, "@vercel/ncc": { @@ -74403,9 +74397,9 @@ } }, "expensify-common": { - "version": "git+ssh://git@github.com/Expensify/expensify-common.git#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", - "integrity": "sha512-s9l/Zy3UjDBrq0WTkgEue1DXLRkkYtuqnANQlVmODHJ9HkJADjrVSv2D0U3ltqd9X7vLCLCmmwl5AUE6466gGg==", - "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", + "version": "git+ssh://git@github.com/Expensify/expensify-common.git#398bf7c6a6d37f229a41d92bd7a4324c0fd32849", + "integrity": "sha512-H7UrLgWIr8mCoPc1oxbeYW2RwLzUWI6jdjbV6cRnrlp8cDW3IyZISF+BQSPFDj7bMhNAbczQPtEOE1gld21Cvg==", + "from": "expensify-common@git+ssh://git@github.com/Expensify/expensify-common.git#398bf7c6a6d37f229a41d92bd7a4324c0fd32849", "requires": { "classnames": "2.3.1", "clipboard": "2.0.4", @@ -74420,7 +74414,7 @@ "simply-deferred": "git+https://github.com/Expensify/simply-deferred.git#77a08a95754660c7bd6e0b6979fdf84e8e831bf5", "string.prototype.replaceall": "^1.0.6", "ua-parser-js": "^1.0.35", - "underscore": "1.13.1" + "underscore": "1.13.6" }, "dependencies": { "prop-types": { @@ -74467,11 +74461,6 @@ "version": "1.0.35", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz", "integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==" - }, - "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" } } }, diff --git a/package.json b/package.json index c6a9d9fde386..7281ab12cfa7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.12-0", + "version": "1.4.14-2", "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.", @@ -89,7 +89,7 @@ "@rnmapbox/maps": "^10.0.11", "@shopify/flash-list": "^1.6.1", "@types/node": "^18.14.0", - "@ua/react-native-airship": "^15.2.6", + "@ua/react-native-airship": "^15.3.1", "awesome-phonenumber": "^5.4.0", "babel-polyfill": "^6.26.0", "canvas-size": "^1.2.6", @@ -98,7 +98,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#927c8409e4454e15a1b95ed0a312ff8fee38f0f0", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#398bf7c6a6d37f229a41d92bd7a4324c0fd32849", "fbjs": "^3.0.2", "htmlparser2": "^7.2.0", "idb-keyval": "^6.2.1", diff --git a/patches/react-native-fast-image+8.6.3.patch b/patches/react-native-fast-image+8.6.3+001+initial.patch similarity index 100% rename from patches/react-native-fast-image+8.6.3.patch rename to patches/react-native-fast-image+8.6.3+001+initial.patch diff --git a/patches/react-native-fast-image+8.6.3+002+bitmap-downsampling.patch b/patches/react-native-fast-image+8.6.3+002+bitmap-downsampling.patch new file mode 100644 index 000000000000..a626d5b16b2f --- /dev/null +++ b/patches/react-native-fast-image+8.6.3+002+bitmap-downsampling.patch @@ -0,0 +1,62 @@ +diff --git a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +index 1339f5c..9dfec0c 100644 +--- a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java ++++ b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +@@ -176,7 +176,8 @@ class FastImageViewWithUrl extends AppCompatImageView { + .apply(FastImageViewConverter + .getOptions(context, imageSource, mSource) + .placeholder(mDefaultSource) // show until loaded +- .fallback(mDefaultSource)); // null will not be treated as error ++ .fallback(mDefaultSource)) ++ .transform(new ResizeTransformation()); + + if (key != null) + builder.listener(new FastImageRequestListener(key)); +diff --git a/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java +new file mode 100644 +index 0000000..1daa227 +--- /dev/null ++++ b/node_modules/react-native-fast-image/android/src/main/java/com/dylanvann/fastimage/ResizeTransformation.java +@@ -0,0 +1,41 @@ ++package com.dylanvann.fastimage; ++ ++ import android.content.Context; ++ import android.graphics.Bitmap; ++ ++ import androidx.annotation.NonNull; ++ ++ import com.bumptech.glide.load.Transformation; ++ import com.bumptech.glide.load.engine.Resource; ++ import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; ++ import com.bumptech.glide.load.resource.bitmap.BitmapResource; ++ ++ import java.security.MessageDigest; ++ ++ public class ResizeTransformation implements Transformation { ++ ++ private final double MAX_BYTES = 25000000.0; ++ ++ @NonNull ++ @Override ++ public Resource transform(@NonNull Context context, @NonNull Resource resource, int outWidth, int outHeight) { ++ Bitmap toTransform = resource.get(); ++ ++ if (toTransform.getByteCount() > MAX_BYTES) { ++ double scaleFactor = Math.sqrt(MAX_BYTES / (double) toTransform.getByteCount()); ++ int newHeight = (int) (outHeight * scaleFactor); ++ int newWidth = (int) (outWidth * scaleFactor); ++ ++ BitmapPool pool = GlideApp.get(context).getBitmapPool(); ++ Bitmap scaledBitmap = Bitmap.createScaledBitmap(toTransform, newWidth, newHeight, true); ++ return BitmapResource.obtain(scaledBitmap, pool); ++ } ++ ++ return resource; ++ } ++ ++ @Override ++ public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { ++ messageDigest.update(("ResizeTransformation").getBytes()); ++ } ++ } +\ No newline at end of file diff --git a/src/App.js b/src/App.js index 12fc6a9426e1..3553900bbc7f 100644 --- a/src/App.js +++ b/src/App.js @@ -8,8 +8,8 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; import ColorSchemeWrapper from './components/ColorSchemeWrapper'; import ComposeProviders from './components/ComposeProviders'; -import CustomStatusBar from './components/CustomStatusBar'; -import CustomStatusBarContextProvider from './components/CustomStatusBar/CustomStatusBarContextProvider'; +import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackground'; +import CustomStatusBarAndBackgroundContextProvider from './components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; @@ -68,10 +68,10 @@ function App() { ReportAttachmentsProvider, PickerStateProvider, EnvironmentProvider, - CustomStatusBarContextProvider, + CustomStatusBarAndBackgroundContextProvider, ]} > - + diff --git a/src/CONST.ts b/src/CONST.ts index c0e3d64b5eee..bc0a0c3216f0 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import dateAdd from 'date-fns/add'; +import dateSubtract from 'date-fns/sub'; import Config from 'react-native-config'; import * as KeyCommand from 'react-native-key-command'; import * as Url from './libs/Url'; @@ -18,6 +20,8 @@ const PLATFORM_IOS = 'iOS'; const ANDROID_PACKAGE_NAME = 'com.expensify.chat'; const CURRENT_YEAR = new Date().getFullYear(); const PULL_REQUEST_NUMBER = Config?.PULL_REQUEST_NUMBER ?? ''; +const MAX_DATE = dateAdd(new Date(), {years: 1}); +const MIN_DATE = dateSubtract(new Date(), {years: 20}); const keyModifierControl = KeyCommand?.constants?.keyModifierControl ?? 'keyModifierControl'; const keyModifierCommand = KeyCommand?.constants?.keyModifierCommand ?? 'keyModifierCommand'; @@ -77,6 +81,12 @@ const CONST = { AVATAR_MAX_WIDTH_PX: 4096, AVATAR_MAX_HEIGHT_PX: 4096, + BREADCRUMB_TYPE: { + ROOT: 'root', + STRONG: 'strong', + NORMAL: 'normal', + }, + DEFAULT_AVATAR_COUNT: 24, OLD_DEFAULT_AVATAR_COUNT: 8, @@ -97,6 +107,8 @@ const CONST = { // Numbers were arbitrarily picked. MIN_YEAR: CURRENT_YEAR - 100, MAX_YEAR: CURRENT_YEAR + 100, + MAX_DATE, + MIN_DATE, }, DATE_BIRTH: { @@ -464,6 +476,7 @@ const CONST = { ONFIDO_TERMS_OF_SERVICE_URL: 'https://onfido.com/terms-of-service/', // Use Environment.getEnvironmentURL to get the complete URL with port number DEV_NEW_EXPENSIFY_URL: 'https://dev.new.expensify.com:', + EXPENSIFY_INBOX_URL: 'https://www.expensify.com/inbox', SIGN_IN_FORM_WIDTH: 300, @@ -533,11 +546,13 @@ const CONST = { DELETE_TAG: 'POLICYCHANGELOG_DELETE_TAG', IMPORT_CUSTOM_UNIT_RATES: 'POLICYCHANGELOG_IMPORT_CUSTOM_UNIT_RATES', IMPORT_TAGS: 'POLICYCHANGELOG_IMPORT_TAGS', + INDIVIDUAL_BUDGET_NOTIFICATION: 'POLICYCHANGELOG_INDIVIDUAL_BUDGET_NOTIFICATION', INVITE_TO_ROOM: 'POLICYCHANGELOG_INVITETOROOM', REMOVE_FROM_ROOM: 'POLICYCHANGELOG_REMOVEFROMROOM', SET_AUTOREIMBURSEMENT: 'POLICYCHANGELOG_SET_AUTOREIMBURSEMENT', SET_AUTO_JOIN: 'POLICYCHANGELOG_SET_AUTO_JOIN', SET_CATEGORY_NAME: 'POLICYCHANGELOG_SET_CATEGORY_NAME', + SHARED_BUDGET_NOTIFICATION: 'POLICYCHANGELOG_SHARED_BUDGET_NOTIFICATION', UPDATE_ACH_ACCOUNT: 'POLICYCHANGELOG_UPDATE_ACH_ACCOUNT', UPDATE_APPROVER_RULE: 'POLICYCHANGELOG_UPDATE_APPROVER_RULE', UPDATE_AUDIT_RATE: 'POLICYCHANGELOG_UPDATE_AUDIT_RATE', @@ -688,6 +703,7 @@ const CONST = { TIMING: { CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action', SEARCH_RENDER: 'search_render', + CHAT_RENDER: 'chat_render', HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render', REPORT_INITIAL_RENDER: 'report_initial_render', SWITCH_REPORT: 'switch_report', diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts index a3a041e65684..c68a950d3501 100644 --- a/src/NAVIGATORS.ts +++ b/src/NAVIGATORS.ts @@ -4,6 +4,7 @@ * */ export default { CENTRAL_PANE_NAVIGATOR: 'CentralPaneNavigator', + LEFT_MODAL_NAVIGATOR: 'LeftModalNavigator', RIGHT_MODAL_NAVIGATOR: 'RightModalNavigator', FULL_SCREEN_NAVIGATOR: 'FullScreenNavigator', } as const; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 0cc7934ad007..b4282cd8b842 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -373,7 +373,7 @@ type OnyxValues = { [ONYXKEYS.NETWORK]: OnyxTypes.Network; [ONYXKEYS.CUSTOM_STATUS_DRAFT]: OnyxTypes.CustomStatusDraft; [ONYXKEYS.INPUT_FOCUSED]: boolean; - [ONYXKEYS.PERSONAL_DETAILS_LIST]: Record; + [ONYXKEYS.PERSONAL_DETAILS_LIST]: OnyxTypes.PersonalDetailsList; [ONYXKEYS.PRIVATE_PERSONAL_DETAILS]: OnyxTypes.PrivatePersonalDetails; [ONYXKEYS.TASK]: OnyxTypes.Task; [ONYXKEYS.CURRENCY_LIST]: Record; diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2cd263237866..9e52ea0a38ca 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -81,10 +81,12 @@ const SCREENS = { SAVE_THE_WORLD: { ROOT: 'SaveTheWorld_Root', }, + LEFT_MODAL: { + SEARCH: 'Search', + }, RIGHT_MODAL: { SETTINGS: 'Settings', NEW_CHAT: 'NewChat', - SEARCH: 'Search', DETAILS: 'Details', PROFILE: 'Profile', REPORT_DETAILS: 'Report_Details', diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 4d01fa108e2a..4abe5655e307 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -48,6 +48,9 @@ const propTypes = { /** Currently logged in user accountID */ accountID: PropTypes.number, }), + + /** Whether the personal bank account option should be shown */ + shouldShowPersonalBankAccountOption: PropTypes.bool, }; const defaultProps = { @@ -59,9 +62,10 @@ const defaultProps = { }, anchorRef: () => {}, session: {}, + shouldShowPersonalBankAccountOption: false, }; -function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session}) { +function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session, shouldShowPersonalBankAccountOption}) { const {translate} = useLocalize(); // Users can choose to pay with business bank account in case of Expense reports or in case of P2P IOU report @@ -70,6 +74,8 @@ function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignme ReportUtils.isExpenseReport(iouReport) || (ReportUtils.isIOUReport(iouReport) && !ReportActionsUtils.hasRequestFromCurrentAccount(lodashGet(iouReport, 'reportID', 0), lodashGet(session, 'accountID', 0))); + const canUsePersonalBankAccount = shouldShowPersonalBankAccountOption || ReportUtils.isIOUReport(iouReport); + return ( - {props.isSmallScreenWidth && } - downloadAttachment(source)} - shouldShowCloseButton={!props.isSmallScreenWidth} - shouldShowBackButton={props.isSmallScreenWidth} - onBackButtonPress={closeModal} - onCloseButtonPress={closeModal} - shouldShowThreeDotsButton={shouldShowThreeDotsButton} - threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)} - threeDotsMenuItems={threeDotsMenuItems} - shouldOverlay - /> - - {!_.isEmpty(props.report) && !props.isReceiptAttachment ? ( - - ) : ( - Boolean(sourceForAttachmentView) && - shouldLoadAttachment && ( - + {props.isSmallScreenWidth && } + downloadAttachment(source)} + shouldShowCloseButton={!props.isSmallScreenWidth} + shouldShowBackButton={props.isSmallScreenWidth} + onBackButtonPress={closeModal} + onCloseButtonPress={closeModal} + shouldShowThreeDotsButton={shouldShowThreeDotsButton} + threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)} + threeDotsMenuItems={threeDotsMenuItems} + shouldOverlay + /> + + {!_.isEmpty(props.report) && !props.isReceiptAttachment ? ( + - ) - )} - - {/* If we have an onConfirm method show a confirmation button */} - {Boolean(props.onConfirm) && ( - - {({safeAreaPaddingBottomStyle}) => ( - -