diff --git a/.eslintrc.js b/.eslintrc.js index 75a74ed371c4..35a4a333f8af 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -37,10 +37,12 @@ module.exports = { overrides: [ { files: ['*.js', '*.jsx', '*.ts', '*.tsx'], + plugins: ['react'], rules: { 'rulesdir/no-multiple-onyx-in-file': 'off', 'rulesdir/onyx-props-must-have-default': 'off', 'react-native-a11y/has-accessibility-hint': ['off'], + 'react/jsx-no-constructed-context-values': 'error', 'react-native-a11y/has-valid-accessibility-descriptors': [ 'error', { @@ -116,7 +118,7 @@ module.exports = { }, { selector: ['parameter', 'method'], - format: ['camelCase'], + format: ['camelCase', 'PascalCase'], }, ], '@typescript-eslint/ban-types': [ diff --git a/.well-known/apple-app-site-association b/.well-known/apple-app-site-association index 1e63fdcb2d52..b3adf0f59b9c 100644 --- a/.well-known/apple-app-site-association +++ b/.well-known/apple-app-site-association @@ -80,6 +80,10 @@ "/": "/search/*", "comment": "Search" }, + { + "/": "/send/*", + "comment": "Send money" + }, { "/": "/money2020/*", "comment": "Money 2020" diff --git a/android/app/build.gradle b/android/app/build.gradle index 1b8eac0c5c20..fa2bd3865ca2 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001038500 - versionName "1.3.85-0" + versionCode 1001038708 + versionName "1.3.87-8" } flavorDimensions "default" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7419d5b1e1a7..74e91caa91d5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -70,6 +70,7 @@ + @@ -88,6 +89,7 @@ + diff --git a/assets/images/bankicons/american-express.svg b/assets/images/bankicons/american-express.svg index b22ccbb4169a..0ab8383d46ed 100644 --- a/assets/images/bankicons/american-express.svg +++ b/assets/images/bankicons/american-express.svg @@ -1,38 +1,23 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/bank-of-america.svg b/assets/images/bankicons/bank-of-america.svg index 0d962a914cfd..e4f87be611fc 100644 --- a/assets/images/bankicons/bank-of-america.svg +++ b/assets/images/bankicons/bank-of-america.svg @@ -1,22 +1,22 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/assets/images/bankicons/bb-t.svg b/assets/images/bankicons/bb-t.svg index 13dba55f68f4..7e7bf1f29ee4 100644 --- a/assets/images/bankicons/bb-t.svg +++ b/assets/images/bankicons/bb-t.svg @@ -1,27 +1,25 @@ - - - - - - - - - - - + + + + + + + + + diff --git a/assets/images/bankicons/capital-one.svg b/assets/images/bankicons/capital-one.svg index 116543884e52..c37c8e3ca582 100644 --- a/assets/images/bankicons/capital-one.svg +++ b/assets/images/bankicons/capital-one.svg @@ -1,55 +1,53 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/charles-schwab.svg b/assets/images/bankicons/charles-schwab.svg index 4ba4ca4f9488..181a668965da 100644 --- a/assets/images/bankicons/charles-schwab.svg +++ b/assets/images/bankicons/charles-schwab.svg @@ -1,59 +1,58 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/chase.svg b/assets/images/bankicons/chase.svg index 1df546e9785b..70f0b911f147 100644 --- a/assets/images/bankicons/chase.svg +++ b/assets/images/bankicons/chase.svg @@ -1,12 +1,13 @@ - - - - - - - + + + + + + + diff --git a/assets/images/bankicons/citibank.svg b/assets/images/bankicons/citibank.svg index 482f33c8b9c9..b03e1efe9bb6 100644 --- a/assets/images/bankicons/citibank.svg +++ b/assets/images/bankicons/citibank.svg @@ -1,18 +1,18 @@ - - - - - - - - + + + + + + + + diff --git a/assets/images/bankicons/citizens-bank.svg b/assets/images/bankicons/citizens-bank.svg index 19160a747490..a0cdc6c1df2b 100644 --- a/assets/images/bankicons/citizens-bank.svg +++ b/assets/images/bankicons/citizens-bank.svg @@ -1,49 +1,47 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/discover.svg b/assets/images/bankicons/discover.svg index 60396e16d29e..75db16e4d1c1 100644 --- a/assets/images/bankicons/discover.svg +++ b/assets/images/bankicons/discover.svg @@ -1 +1,47 @@ -Discover 4 \ No newline at end of file + + + + + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/expensify-background.png b/assets/images/bankicons/expensify-background.png new file mode 100644 index 000000000000..ab7b71d34e11 Binary files /dev/null and b/assets/images/bankicons/expensify-background.png differ diff --git a/assets/images/bankicons/expensify.svg b/assets/images/bankicons/expensify.svg new file mode 100644 index 000000000000..b61773e8d838 --- /dev/null +++ b/assets/images/bankicons/expensify.svg @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/assets/images/bankicons/fidelity.svg b/assets/images/bankicons/fidelity.svg index ac0a05babc95..d49eca17c12d 100644 --- a/assets/images/bankicons/fidelity.svg +++ b/assets/images/bankicons/fidelity.svg @@ -1,17 +1,17 @@ - - - - - - - + + + + + + + diff --git a/assets/images/bankicons/generic-bank-account.svg b/assets/images/bankicons/generic-bank-account.svg index 8912413c668d..493f06b335d8 100644 --- a/assets/images/bankicons/generic-bank-account.svg +++ b/assets/images/bankicons/generic-bank-account.svg @@ -1,14 +1,14 @@ - + - - + + diff --git a/assets/images/bankicons/huntington-bank.svg b/assets/images/bankicons/huntington-bank.svg index e6b43b78daaa..40909a273e19 100644 --- a/assets/images/bankicons/huntington-bank.svg +++ b/assets/images/bankicons/huntington-bank.svg @@ -1,24 +1,22 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/navy-federal-credit-union.svg b/assets/images/bankicons/navy-federal-credit-union.svg index 5541daa9f49a..898cd03768f0 100644 --- a/assets/images/bankicons/navy-federal-credit-union.svg +++ b/assets/images/bankicons/navy-federal-credit-union.svg @@ -1,89 +1,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/pnc.svg b/assets/images/bankicons/pnc.svg index 104abb28ba05..3f78dbe94f47 100644 --- a/assets/images/bankicons/pnc.svg +++ b/assets/images/bankicons/pnc.svg @@ -1,19 +1,17 @@ - - - - - - - - - - - + + + + + + + + + diff --git a/assets/images/bankicons/regions-bank.svg b/assets/images/bankicons/regions-bank.svg index 2de53c116064..bff045f0eb5a 100644 --- a/assets/images/bankicons/regions-bank.svg +++ b/assets/images/bankicons/regions-bank.svg @@ -1,40 +1,38 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/suntrust.svg b/assets/images/bankicons/suntrust.svg index 256b8157600f..b5b94c105b14 100644 --- a/assets/images/bankicons/suntrust.svg +++ b/assets/images/bankicons/suntrust.svg @@ -1,220 +1,217 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/bankicons/td-bank.svg b/assets/images/bankicons/td-bank.svg index 03f100171f67..84675de5f2bf 100644 --- a/assets/images/bankicons/td-bank.svg +++ b/assets/images/bankicons/td-bank.svg @@ -1,16 +1,14 @@ - - - - - - - - - - - + + + + + + + + + diff --git a/assets/images/bankicons/us-bank.svg b/assets/images/bankicons/us-bank.svg index d1364e253e62..e091ba0a6f50 100644 --- a/assets/images/bankicons/us-bank.svg +++ b/assets/images/bankicons/us-bank.svg @@ -1,29 +1,27 @@ - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/assets/images/bankicons/usaa.svg b/assets/images/bankicons/usaa.svg index 2552db28eca3..1e137fab626f 100644 --- a/assets/images/bankicons/usaa.svg +++ b/assets/images/bankicons/usaa.svg @@ -1,38 +1,36 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/american-express.svg b/assets/images/cardicons/american-express.svg new file mode 100644 index 000000000000..9e31f7c8a08e --- /dev/null +++ b/assets/images/cardicons/american-express.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/bank-of-america.svg b/assets/images/cardicons/bank-of-america.svg new file mode 100644 index 000000000000..62dd510b0649 --- /dev/null +++ b/assets/images/cardicons/bank-of-america.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + diff --git a/assets/images/cardicons/bb-t.svg b/assets/images/cardicons/bb-t.svg new file mode 100644 index 000000000000..ad3676458d21 --- /dev/null +++ b/assets/images/cardicons/bb-t.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/assets/images/cardicons/capital-one.svg b/assets/images/cardicons/capital-one.svg new file mode 100644 index 000000000000..ee4f756e2600 --- /dev/null +++ b/assets/images/cardicons/capital-one.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/charles-schwab.svg b/assets/images/cardicons/charles-schwab.svg new file mode 100644 index 000000000000..39c894042cd3 --- /dev/null +++ b/assets/images/cardicons/charles-schwab.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/chase.svg b/assets/images/cardicons/chase.svg new file mode 100644 index 000000000000..8e8ddb6d5378 --- /dev/null +++ b/assets/images/cardicons/chase.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/assets/images/cardicons/citibank.svg b/assets/images/cardicons/citibank.svg new file mode 100644 index 000000000000..f9869aee7146 --- /dev/null +++ b/assets/images/cardicons/citibank.svg @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/assets/images/cardicons/citizens.svg b/assets/images/cardicons/citizens.svg new file mode 100644 index 000000000000..3b4bf9ea1af3 --- /dev/null +++ b/assets/images/cardicons/citizens.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/discover.svg b/assets/images/cardicons/discover.svg new file mode 100644 index 000000000000..668e5634339d --- /dev/null +++ b/assets/images/cardicons/discover.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/expensify-card-dark.svg b/assets/images/cardicons/expensify-card-dark.svg new file mode 100644 index 000000000000..4a65afeeda9d --- /dev/null +++ b/assets/images/cardicons/expensify-card-dark.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/fidelity.svg b/assets/images/cardicons/fidelity.svg new file mode 100644 index 000000000000..c87f9c4aa56c --- /dev/null +++ b/assets/images/cardicons/fidelity.svg @@ -0,0 +1,21 @@ + + + + + + + + + diff --git a/assets/images/cardicons/generic-bank-card.svg b/assets/images/cardicons/generic-bank-card.svg new file mode 100644 index 000000000000..f700691ac29b --- /dev/null +++ b/assets/images/cardicons/generic-bank-card.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/assets/images/cardicons/huntington-bank.svg b/assets/images/cardicons/huntington-bank.svg new file mode 100644 index 000000000000..c108c7039898 --- /dev/null +++ b/assets/images/cardicons/huntington-bank.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/navy-federal-credit-union.svg b/assets/images/cardicons/navy-federal-credit-union.svg new file mode 100644 index 000000000000..5abc1103cce1 --- /dev/null +++ b/assets/images/cardicons/navy-federal-credit-union.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/pnc.svg b/assets/images/cardicons/pnc.svg new file mode 100644 index 000000000000..ae4d4aac8e41 --- /dev/null +++ b/assets/images/cardicons/pnc.svg @@ -0,0 +1,18 @@ + + + + + + + + + diff --git a/assets/images/cardicons/regions-bank.svg b/assets/images/cardicons/regions-bank.svg new file mode 100644 index 000000000000..1837ad2be41b --- /dev/null +++ b/assets/images/cardicons/regions-bank.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/suntrust.svg b/assets/images/cardicons/suntrust.svg new file mode 100644 index 000000000000..32ea5096f876 --- /dev/null +++ b/assets/images/cardicons/suntrust.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/cardicons/td-bank.svg b/assets/images/cardicons/td-bank.svg new file mode 100644 index 000000000000..19988e35bbbe --- /dev/null +++ b/assets/images/cardicons/td-bank.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + diff --git a/assets/images/cardicons/us-bank.svg b/assets/images/cardicons/us-bank.svg new file mode 100644 index 000000000000..321b4cb755b0 --- /dev/null +++ b/assets/images/cardicons/us-bank.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/assets/images/cardicons/usaa.svg b/assets/images/cardicons/usaa.svg new file mode 100644 index 000000000000..bb634f64e658 --- /dev/null +++ b/assets/images/cardicons/usaa.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 27656eeb68f0..de99bbcb48ef 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -256,6 +256,7 @@ GEM PLATFORMS arm64-darwin-22 + arm64-darwin-23 x86_64-darwin-20 x86_64-darwin-21 diff --git a/docs/_data/_routes.yml b/docs/_data/_routes.yml index c6733ac11715..84735e95e0e9 100644 --- a/docs/_data/_routes.yml +++ b/docs/_data/_routes.yml @@ -44,16 +44,21 @@ platforms: icon: /assets/images/hand-card.svg description: Explore how the Expensify Card combines convenience and security to enhance everyday business transactions. Discover how to apply for, oversee, and maximize your card perks here. - - href: exports - title: Exports - icon: /assets/images/monitor.svg - description: From exporting reports to creating custom templates, here is where you can learn more about Expensify's versatile export options. + - href: expensify-partner-program + title: Expensify Partner Program + icon: /assets/images/handshake.svg + description: Discover how to get the most out of Expensify as an ExpensifyApproved! accountant partner. Learn how to set up your clients, receive CPE credits, and take advantage of your partner discount. - href: get-paid-back title: Get Paid Back icon: /assets/images/money-into-wallet.svg description: Whether you submit an expense report or an invoice, find out here how to ensure a smooth and timely payback process every time. + - href: insights-and-custom-reporting + title: Insights & Custom Reporting + icon: /assets/images/monitor.svg + description: From exporting reports to creating custom templates, here is where you can learn more about Expensify's versatile export options. + - href: integrations title: Integrations icon: /assets/images/workflow.svg @@ -64,15 +69,15 @@ platforms: icon: /assets/images/envelope-receipt.svg description: Master the art of overseeing employees and reports by utilizing Expensify’s automation features and approval workflows. - - href: policy-and-domain-settings - title: Policy & Domain Settings - icon: /assets/images/shield.svg - description: Discover how to set up and manage policies, define user permissions, and implement compliance rules to maintain a secure and compliant financial management landscape. - - href: send-payments title: Send Payments icon: /assets/images/money-wings.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 + title: Workspace & Domain Settings + icon: /assets/images/shield.svg + description: Discover how to set up and manage workspace, define user permissions, and implement compliance rules to maintain a secure and compliant financial management landscape. - href: new-expensify title: New Expensify @@ -113,16 +118,21 @@ platforms: icon: /assets/images/hand-card.svg description: Explore how the Expensify Card combines convenience and security to enhance everyday business transactions. Discover how to apply for, oversee, and maximize your card perks here. - - href: exports - title: Exports - icon: /assets/images/monitor.svg - description: From exporting reports to creating custom templates, here is where you can learn more about Expensify's versatile export options. + - href: expensify-partner-program + title: Expensify Partner Program + icon: /assets/images/handshake.svg + description: Discover how to get the most out of Expensify as an ExpensifyApproved! accountant partner. Learn how to set up your clients, receive CPE credits, and take advantage of your partner discount. - href: get-paid-back title: Get Paid Back icon: /assets/images/money-into-wallet.svg description: Whether you submit an expense report or an invoice, find out here how to ensure a smooth and timely payback process every time. + - href: insights-and-custom-reporting + title: Insights & Custom Reporting + icon: /assets/images/monitor.svg + description: From exporting reports to creating custom templates, here is where you can learn more about Expensify's versatile export options. + - href: integrations title: Integrations icon: /assets/images/workflow.svg diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index 3ad2276713da..c887849ffd99 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -371,9 +371,26 @@ button { flex-wrap: wrap; } + h1 { + font-size: 1.5em; + padding: 20px 0 12px 0; + } + + h2 { + font-size: 1.125em; + font-weight: 500; + font-family: "ExpensifyNewKansas", "Helvetica Neue", "Helvetica", Arial, sans-serif; + } + + h3 { + font-size: 1em; + font-family: "ExpensifyNeue", "Helvetica Neue", "Helvetica", Arial, sans-serif; + } + h2, h3 { - font-family: "ExpensifyNewKansas", "Helvetica Neue", "Helvetica", Arial, sans-serif; + margin: 0; + padding: 12px 0 12px 0; } blockquote { diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md deleted file mode 100644 index 1fa5734293ac..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Business-Bank-Account-(AUD).md +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Add-a-Business-Bank-Account-(AUD).md -description: This article provides insight on setting up and using an Australian Business Bank account in Expensify. ---- - -# How to add an Australian business bank account (for admins) -A withdrawal account is the business bank account that you want to use to pay your employee reimbursements. - -_Your policy currency must be set to AUD and reimbursement setting set to Indirect to continue. If your main policy is used for something other than AUD, then you will need to create a new one and set that policy to AUD._ - -To set this up, you’ll run through the following steps: - -1. Go to **Settings > Your Account > Payments** and click **Add Verified Bank Account** -![Click the Verified Bank Account button in the bottom right-hand corner of the screen](https://help.expensify.com/assets/images/add-vba-australian-account.png){:width="100%"} - -2. Enter the required information to connect to your business bank account. If you don't know your Bank User ID/Direct Entry ID/APCA Number, please contact your bank and they will be able to provide this. -![Enter your information in each of the required fields](https://help.expensify.com/assets/images/add-vba-australian-account-modal.png){:width="100%"} - -3. Link the withdrawal account to your policy by heading to **Settings > Policies > Group > [Policy name] > Reimbursement** -4. Click **Direct reimbursement** -5. Set the default withdrawal account for processing reimbursements -6. Tell your employees to add their deposit accounts and start reimbursing. - -# How to delete a bank account -If you’re no longer using a bank account you previously connected to Expensify, you can delete it by doing the following: - -1. Navigate to Settings > Accounts > Payments -2. Click **Delete** -![Click the Delete button](https://help.expensify.com/assets/images/delete-australian-bank-account.png){:width="100%"} - -You can complete this process either via the web app (on a computer), or via the mobile app. - -# Deep Dive -## Bank-specific batch payment support - -If you are new to using Batch Payments in Australia, to reimburse your staff or process payroll, you may want to check out these bank-specific instructions for how to upload your .aba file: - -- ANZ Bank - [Import a file for payroll payments](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) -- CommBank - [Importing and using
 Direct Entry (EFT) files](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) -- Westpac - [Importing Payment Files](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) -- NAB - [Quick Reference Guide - Upload a payment file](https://www.nab.com.au/business/online-banking/nab-connect/help) -- Bendigo Bank - [Bulk payments user guide](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) -- Bank of Queensland - [Payments file upload facility FAQ](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) - -**Note:** Some financial institutions require an ABA file to include a *self-balancing transaction*. If you are unsure, please check with your bank to ensure whether to tick this option or not, as selecting an incorrect option will result in the ABA file not working with your bank's internet banking platform. - -## Enable Global Reimbursement - -If you have employees in other countries outside of Australia, you can now reimburse them directly using Global Reimbursement. - -To do this, you’ll first need to delete any existing Australian business bank accounts. Then, you’ll want to follow the instructions to enable Global Reimbursements diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md index 7c789942a2b3..b59f68a65ce6 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Business-Bank-Accounts-AUD.md @@ -1,5 +1,51 @@ --- -title: Business Bank Accounts - AUD -description: Business Bank Accounts - AUD +title: Add a Business Bank Account +description: This article provides insight on setting up and using an Australian Business Bank account in Expensify. --- -## Resource Coming Soon! + +# How to add an Australian business bank account (for admins) +A withdrawal account is the business bank account that you want to use to pay your employee reimbursements. + +_Your policy currency must be set to AUD and reimbursement setting set to Indirect to continue. If your main policy is used for something other than AUD, then you will need to create a new one and set that policy to AUD._ + +To set this up, you’ll run through the following steps: + +1. Go to **Settings > Your Account > Payments** and click **Add Verified Bank Account** +![Click the Verified Bank Account button in the bottom right-hand corner of the screen](https://help.expensify.com/assets/images/add-vba-australian-account.png){:width="100%"} + +2. Enter the required information to connect to your business bank account. If you don't know your Bank User ID/Direct Entry ID/APCA Number, please contact your bank and they will be able to provide this. +![Enter your information in each of the required fields](https://help.expensify.com/assets/images/add-vba-australian-account-modal.png){:width="100%"} + +3. Link the withdrawal account to your policy by heading to **Settings > Policies > Group > [Policy name] > Reimbursement** +4. Click **Direct reimbursement** +5. Set the default withdrawal account for processing reimbursements +6. Tell your employees to add their deposit accounts and start reimbursing. + +# How to delete a bank account +If you’re no longer using a bank account you previously connected to Expensify, you can delete it by doing the following: + +1. Navigate to Settings > Accounts > Payments +2. Click **Delete** +![Click the Delete button](https://help.expensify.com/assets/images/delete-australian-bank-account.png){:width="100%"} + +You can complete this process either via the web app (on a computer), or via the mobile app. + +# Deep Dive +## Bank-specific batch payment support + +If you are new to using Batch Payments in Australia, to reimburse your staff or process payroll, you may want to check out these bank-specific instructions for how to upload your .aba file: + +- ANZ Bank - [Import a file for payroll payments](https://www.anz.com.au/support/internet-banking/pay-transfer-business/payroll/import-file/) +- CommBank - [Importing and using
 Direct Entry (EFT) files](https://www.commbank.com.au/business/pds/003-279-importing-a-de-file.pdf) +- Westpac - [Importing Payment Files](https://www.westpac.com.au/business-banking/online-banking/support-faqs/import-files/) +- NAB - [Quick Reference Guide - Upload a payment file](https://www.nab.com.au/business/online-banking/nab-connect/help) +- Bendigo Bank - [Bulk payments user guide](https://www.bendigobank.com.au/globalassets/documents/business/bulk-payments-user-guide.pdf) +- Bank of Queensland - [Payments file upload facility FAQ](https://www.boq.com.au/help-and-support/online-banking/ob-faqs-and-support/faq-pfuf) + +**Note:** Some financial institutions require an ABA file to include a *self-balancing transaction*. If you are unsure, please check with your bank to ensure whether to tick this option or not, as selecting an incorrect option will result in the ABA file not working with your bank's internet banking platform. + +## Enable Global Reimbursement + +If you have employees in other countries outside of Australia, you can now reimburse them directly using Global Reimbursement. + +To do this, you’ll first need to delete any existing Australian business bank accounts. Then, you’ll want to follow the instructions to enable Global Reimbursements diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Deposit-Account-(AUD).md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md similarity index 83% rename from docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Deposit-Account-(AUD).md rename to docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md index 7273e5ece879..6114e98883e0 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/business-bank-accounts/Add-a-Deposit-Account-(AUD).md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-AUD.md @@ -1,12 +1,12 @@ --- -title: Add a Deposit Account (AUD) +title: Deposit Accounts (AUD) description: Expensify allows you to add a personal bank account to receive reimbursements for your expenses. We never take money out of this account — it is only a place for us to deposit funds from your employer. This article covers deposit accounts for Australian banks. --- ## How-to add your Australian personal deposit account information 1. Confirm with your Policy Admin that they’ve set up Global Reimbursment 2. Set your default policy (by selecting the correct policy after clicking on your profile picture) before adding your deposit account. -3. Go to *Settings > Account > Payments* and click *Add Deposit-Only Bank Account* +3. Go to **Settings > Account > Payments** and click **Add Deposit-Only Bank Account** ![Click the Add Deposit-Only Bank Account button](https://help.expensify.com/assets/images/add-australian-deposit-only-account.png){:width="100%"} 4. Enter your BSB, account number and name. If your screen looks different than the image below, that means your company hasn't enabled reimbursements through Expensify. Please contact your administrator and ask them to enable reimbursements. @@ -14,7 +14,7 @@ description: Expensify allows you to add a personal bank account to receive reim ![Fill in the required fields](https://help.expensify.com/assets/images/add-australian-deposit-only-account-modal.png){:width="100%"} # How-to delete a bank account -Bank accounts are easy to delete! Simply click the red “Delete” button in the bank account under *Settings > Account > Payments*. +Bank accounts are easy to delete! Simply click the red **Delete** button in the bank account under **Settings > Account > Payments**. ![Click the Delete button](https://help.expensify.com/assets/images/delete-australian-bank-account.png){:width="100%"} diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-AUS.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-AUS.md deleted file mode 100644 index 61e6dfd95e38..000000000000 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-AUS.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Deposit Accounts - AUD -description: Deposit Accounts - AUD ---- -## Resource Coming Soon! diff --git a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-USD.md b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-USD.md index 19010be95980..a4ff7503f7bb 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-USD.md +++ b/docs/articles/expensify-classic/bank-accounts-and-credit-cards/deposit-accounts/Deposit-Accounts-USD.md @@ -1,5 +1,75 @@ --- title: Deposit Accounts - USD -description: Deposit Accounts - USD +description: How to add a deposit account to receive payments for yourself or your business (US) --- -## Resource Coming Soon! +# Overview + +There are two types of deposit-only accounts: + +1. If you're an employee seeking reimbursement for expenses you’ve incurred, you’ll add a **Personal deposit-only bank account**. +2. If you're a vendor seeking payment for goods or services, you’ll add a **Business deposit-only account**. + +# How to connect a personal deposit-only bank account + +**Connect a personal deposit-only bank account if you are:** + +- An employee based in the US who gets reimbursed by their employer +- An employee based in Australia who gets reimbursed by their company via batch payments +- An international (non-US) employee whose US-based employers send international reimbursements + +**To establish the connection to a personal bank account, follow these steps:** + +1. Navigate to your **Settings > Account > Payments** and click the **Add Deposit-Only Bank Account** button. +2. Click **Log into your bank** button and click **Continue** on the Plaid connection pop-up window. +3. Search for your bank account in the list of banks and follow the prompts to sign-in to your bank account. +4. Enter your bank login credentials when prompted. + - If your bank doesn't appear, click the 'x' in the upper right corner of the Plaid pop-up window and click **Connect Manually**. + - Enter your account information, then click **Save & Continue**. + +You should be all set! You’ll receive reimbursement for your expense reports directly to this bank account. + +# How to connect a business deposit-only bank account + +**Connect a business deposit-only bank account if you are:** + +- A US-based vendor who wants to be paid directly for bills sent to customers/clients +- A US-based vendor who want to pay invoices directly via Expensify + +**To establish the connection to a business bank account, follow these steps:** + +1. Navigate to your **Settings > Account > Payments and click the Add Deposit-Only Bank Account** button. +2. Click **Log into your bank** button and click **Continue** on the Plaid connection pop-up window. +3. Search for your bank account in the list of banks and follow the prompts to sign-in to your bank account. +4. Enter your bank login credentials when prompted. + - If your bank doesn't appear, click the 'x' in the upper right corner of the Plaid pop-up window and click **Connect Manually**. + - Enter your account information, then click **Save & Continue**. +5. If you see the option to “Switch to Business” after entering the account owner information, click that link. +6. Enter your Company Name and FEIN or TIN information. +7. Enter your company’s website formatted as https://www.domain.com. + +You should be all set! The bank account will display as a deposit-only business account, and you’ll be paid directly for any invoices you submit for payment. + +# How to delete a deposit-only bank account + +**To delete a deposit-only bank account, do the following:** + +1. Navigate to **Settings > Account > Payments > Bank Accounts** +2. Click the **Delete** next to the bank account you want to remove + +# FAQ + +## **What happens if my bank requires an additional security check before adding it to a third-party?** + +If your bank account has 2FA enabled or another security step, you should be prompted to complete this when adding the account. If not, and you encounter an error, you can always select the option to “Connect Manually”. Either way, please double check that you are entering the correct bank account details to ensure successful payments. + +## **What if I also want to pay employees with my business bank account?** + +If you’ve added a business deposit-only account and also wish to also pay employees, vendors, or utilize the Expensify Card with this bank account, select “Verify” on the listed bank account. This will take you through the additional verification steps to use this account to issue payments. + +## **I connected my deposit-only bank account – Why haven’t I received my reimbursement?** + +There are a few reasons a reimbursement may be unsuccessful. The first step is to review the estimated deposit date on the report. If it’s after that date and you still haven’t seen the funds, it could have been unsuccessful because: + - The incorrect account was added. If you believe you may have entered the wrong account, please reach out to Concierge and provide the Report ID for the missing reimbursement. + - Your account wasn’t set up for Direct Deposit/ACH. You may want to contact your bank to confirm. + +If you aren’t sure, please reach out to Concierge and we can assist! diff --git a/docs/articles/expensify-classic/expensify-card/Card-Settings.md b/docs/articles/expensify-classic/expensify-card/Card-Settings.md index ab212354974a..35708b6fbb1e 100644 --- a/docs/articles/expensify-classic/expensify-card/Card-Settings.md +++ b/docs/articles/expensify-classic/expensify-card/Card-Settings.md @@ -1,5 +1,169 @@ --- -title: Card Settings -description: Card Settings +title: Expensify Card Settings +description: Admin Card Settings and Features --- -## Resource Coming Soon! +## Expensify Card - admin settings and features +​ +# Overview +​ +The Expensify Card offers a range of settings and functionality to customize how admins manage expenses and card usage in Expensify. To start, we'll lay out the best way to make these options work for you. +​ +Set Smart Limits to control card spend. Smart Limits are spend limits that can be set for individual cards or specific groups. Once a given Smart Limit is reached, the card is temporarily disabled until expenses are approved. +​ +Monitor spend using your Domain Limit and the Reconciliation Dashboard. +Your Domain Limit is the total Expensify Card limit across your entire organization. No member can spend more than what's available here, no matter what their individual Smart Limit is. A Domain Limit is dynamic and depends on a number of factors, which we'll explain below. +​ +Decide the settlement model that works best for your business +Monthly settlement is when your Expensify Card balance is paid in full on a certain day each month. Though the Expensify Card is set to settle daily by default, any Domain Admin can change this setting to monthly. +​ +Now, let's get into the mechanics of each piece mentioned above. +​ +# How to set Smart Limits +Smart Limits allow you to set a custom spend limit for each Expensify cardholder, or default limits for groups. Setting a Smart Limit is the step that activates an Expensify card for your user (and issues a virtual card for immediate use). +​ +## Set limits for individual cardholders +As a Domain Admin, you can set or edit Custom Smart Limits for a card by going to Settings > Domains > Domain Name > Company Cards. Simply click Edit Limit to set the limit. This limit will restrict the amount of unapproved (unsubmitted and Processing) expenses that a cardholder can incur. After the limit is reached, the cardholder won't be able to use their card until they submit outstanding expenses and have their card spend approved. If you set the Smart Limit to $0, the user's card can't be used. +## Set default group limits +Domain Admins can set or edit custom Smart Limits for a domain group by going to Settings > Domains > Domain Name > Groups. Just click on the limit in-line for your chosen group and amend the value. +​ +This limit will apply to all members of the Domain Group who do not have an individual limit set via Settings > Domains > Domain Name > Company Cards. +## Refreshing Smart Limits +To let cardholders keep spending, you can approve their pending expenses via the Reconciliation tab. This will free up their limit, allowing them to use their card again. +​ +To check an unapproved card balance and approve expenses, click on Reconciliation and enter a date range, then click though the Unapproved total to see what needs approving. You can add to a new report or approve an existing report from here. +​ +You can also increase a Smart Limit at any time by clicking Edit Limit. +​ +# Understanding your Domain Limit +​ +To get the most accurate Domain Limit for your company, connect your bank account via Plaid under Settings > Account > Payments > Add Verified Bank Account. +​ +If your bank isn't supported or you're having connection issues, you can request a custom limit under Settings > Domains > Domain Name > Company Cards > Request Limit Increase. As a note, you'll need to provide three months of unredacted bank statements for review by our risk management team. +​ +Your Domain Limit may fluctuate from time to time based on various factors, including: +​ +- Available funds in your Verified Business Bank Account: We regularly check bank balances via Plaid. A sudden drop in balance within the last 24 hours may affect your limit. For 'sweep' accounts, be sure to maintain a substantial balance even if you're sweeping daily. +- Pending expenses: Review the Reconciliation Dashboard to check for large pending expenses that may impact your available balance. Your Domain Limit will adjust automatically to include pending expenses. +- Processing settlements: Settlements need about three business days to process and clear. Several large settlements over consecutive days may impact your Domain Limit, which will dynamically update when settlements have cleared. +​ +As a note, if your Domain Limit is reduced to $0, your cardholders can't make purchases even if they have a larger Smart Limit set on their individual cards. +# How to reconcile Expensify Cards +## How to reconcile expenses +Reconciling expenses is essential to ensuring your financial records are accurate and up-to-date. +​ +Follow the steps below to quickly review and reconcile expenses associated with your Expensify Cards: +​ +1. Go to Settings > Domains > Domain Name > Company Cards > Reconciliation > Expenses +2. Enter your start and end dates, then click Run +3. The Imported Total will show all Expensify Card transactions for the period +4. You'll also see a list of all Expensify Cards, the total spend on each card, and a snapshot of expenses that have and have not been approved (Approved Total and Unapproved Total, respectively) +By clicking on the amounts, you can view the associated expenses +​ +## How to reconcile settlements +A settlement is the payment to Expensify for the purchases made using the Expensify Cards. +​ +The Expensify Card program can settle on either a daily or monthly basis. One thing to note is that not all transactions in a settlement will be approved when running reconciliation. +​ +You can view the Expensify Card settlements under Settings > Domains > Domain Name > Company Cards > Reconciliation > Settlements. +​ +By clicking each settlement amount, you can see the transactions contained in that specific payment amount. +​ +Follow the below steps to run reconciliation on the Expensify Card settlements: +​ +1. Log into the Expensify web app +2. Click Settings > Domains > Domain Name > Company Cards > Reconciliation tab > Settlements +3. Use the Search function to generate a statement for the specific period you need +4. The search results will include the following info for each entry: + - Date: when a purchase was made or funds were debited for payments + - Posted Date: when the purchase transaction posted + - Entry ID: a unique number grouping card payments and transactions settled by those payments + - Amount: the amount debited from the Business Bank Account for payments + - Merchant: the business where a purchase was made + - Card: refers to the Expensify credit card number and cardholder's email address + - Business Account: the business bank account connected to Expensify that the settlement is paid from + - Transaction ID: a special ID that helps Expensify support locate transactions if there's an issue +​ +5. Review the individual transactions (debits) and the payments (credits) that settled them +6. Every cardholder will have a virtual and a physical card listed. They're handled the same way for settlements, reconciliation, and exporting. +7. Click Download CSV for reconciliation +8. This will list everything that you see on screen +9. To reconcile pre-authorizations, you can use the Transaction ID column in the CSV file to locate the original purchase +10. Review account payments +11. You'll see payments made from the accounts listed under Settings > Account > Payments > Bank Accounts. Payment data won't show for deleted accounts. +​ +You can use the Reconciliation Dashboard to confirm the status of expenses that are missing from your accounting system. It allows you to view both approved and unapproved expenses within your selected date range that haven't been exported yet. +​ +# Deep dive +## Set a preferred workspace +Some customers choose to split their company card expenses from other expense types for coding purposes. Most commonly this is done by creating a separate workspace for card expenses. +​ +You can use the preferred workspace feature in conjunction with Scheduled Submit to make sure all newly imported card expenses are automatically added to reports connected to your card-specific workspace. +## How to change your settlement account +You can change your settlement account to any other verified business bank account in Expensify. If your bank account is closing, make sure you set up the replacement bank account in Expensify as early as possible. +​ +To select a different settlement account: +​ +1. Go to Settings > Domains > Domain Name > Company Cards > Settings tab +2. Use the Expensify Card settlement account dropdown to select a new account +3. Click Save +​ +## Change the settlement frequency +​ +By default, the Expensify Cards settle on a daily cadence. However, you can choose to have the cards settle on a monthly basis. +​ +1. Monthly settlement is only available if the settlement account hasn't had a negative balance in the last 90 days +2. There will be an initial settlement to settle any outstanding spend that happened before switching the settlement frequency +3. The date that the settlement is changed to monthly is the settlement date going forward (e.g. If you switch to monthly settlement on September 15th, Expensify Cards will settle on the 15th of each month going forward) +​ +To change the settlement frequency: +1. Go to Settings > Domains > Domain Name > Company Cards > Settings tab +2. Click the Settlement Frequency dropdown and select Monthly +3. Click Save to confirm the change +​ +​ +## Declined Expensify Card transactions +As long as you have 'Receive realtime alerts' enabled, you'll get a notification explaining the decline reason. You can enable alerts in the mobile app by clicking on three-bar icon in the upper-left corner > Settings > toggle Receive realtime alerts on. +​ +If you ever notice any unfamiliar purchases or need a new card, go to Settings > Account > Credit Card Import and click on Request a New Card right away. +​ +Here are some reasons an Expensify Card transaction might be declined: +​ +1. You have an insufficient card limit + - If a transaction amount exceeds the available limit on your Expensify Card, the transaction will be declined. It's essential to be aware of the available balance before making a purchase to avoid this - you can see the balance under Settings > Account > Credit Card Import on the web app or mobile app. Submitting expenses and having them approved will free up your limit for more spend. +​ +2. Your card hasn't been activated yet, or has been canceled + - If the card has been canceled or not yet activated, it won't process any transactions. +​ +3. Your card information was entered incorrectly. Entering incorrect card information, such as the CVC, ZIP or expiration date will also lead to declines. +​ +4. There was suspicious activity + - If Expensify detects unusual or suspicious activity, we may block transactions as a security measure. This could happen due to irregular spending patterns, attempted purchases from risky vendors, or multiple rapid transactions. Check your Expensify Home page to approve unsual merchants and try again. + If the spending looks suspicious, we may do a manual due diligence check, and our team will do this as quickly as possible - your cards will all be locked while this happens. +​ +5. The merchant is located in a restricted country + - Some countries may be off-limits for transactions. If a merchant or their headquarters (billing address) are physically located in one of these countries, Expensify Card purchases will be declined. This list may change at any time, so be sure to check back frequently: Belarus, Burundi, Cambodia, Central African Republic, Democratic Republic of the Congo, Cuba, Iran, Iraq, North Korea, Lebanon, Libya, Russia, Somalia, South Sudan, Syrian Arab Republic, Tanzania, Ukraine, Venezuela, Yemen, and Zimbabwe. +​ +# FAQ +## What happens when I reject an Expensify Card expense? +​ +​ +Rejecting an Expensify Card expense from an Expensify report will simply allow it to be reported on a different report. You cannot undo a credit card charge. +​ +If an Expensify Card expense needs to be rejected, you can reject the report or the specific expense so it can be added to a different report. The rejected expense will become Unreported and return to the submitter's Expenses page. +​ +If you want to dispute a card charge, please message Concierge to start the dispute process. +​ +If your employee has accidentally made an unauthorised purchase, you will need to work that out with the employee to determine how they will pay back your company. +​ +​ +## What happens when an Expensify Card transaction is refunded? +​ +​ +The way a refund is displayed in Expensify depends on the status of the expense (pending or posted) and whether or not the employee also submitted an accompanying SmartScanned receipt. Remember, a SmartScanned receipt will auto-merge with the Expensify Card expense. +​ +- Full refunds: +If a transaction is pending and doesn't have a receipt attached (except for eReceipts), getting a full refund will make the transaction disappear. +If a transaction is pending and has a receipt attached (excluding eReceipts), a full refund will zero-out the transaction (amount becomes zero). +- Partial refunds: +If a transaction is pending, a partial refund will reduce the amount of the transaction. +- If a transaction is posted, a partial refund will create a negative transaction for the refund amount. diff --git a/docs/articles/expensify-classic/expensify-card/Statements.md b/docs/articles/expensify-classic/expensify-card/Statements.md index b48d303a1a9b..5b583370b810 100644 --- a/docs/articles/expensify-classic/expensify-card/Statements.md +++ b/docs/articles/expensify-classic/expensify-card/Statements.md @@ -1,5 +1,73 @@ --- -title: Statements -description: Statements +title: — Expensify Card Statements and Settlements +description: Learn how the Expensify Card statement and settlements work! --- -## Resource Coming Soon! + +# Overview +Expensify offers several settlement types and a statement that provides a detailed view of transactions and settlements. We discuss specifics on both below. + +# How to use Expensify Card Statement and Settlements +## Using the statement +If your domain uses the Expensify Card and you have a validated Business Bank Account, access the Expensify Card statement at Settings > Domains > Company Cards > Reconciliation Tab > Settlements. + +The Expensify Card statement displays individual transactions (debits) and their corresponding settlements (credits). Each Expensify Cardholder has a Digital Card and a Physical Card, which are treated the same in settlement, reconciliation, and exporting to your accounting system. + +Here's a breakdown of crucial information in the statement: +- **Date:** For card payments, it shows the debit date; for card transactions, it displays the purchase date. +- **Entry ID:** This unique ID groups card payments and transactions together. +- **Withdrawn Amount:** This applies to card payments, matching the debited amount from the Business Bank Account. +- **Transaction Amount:** This applies to card transactions, matching the expense purchase amount. +- **User email:** Applies to card transactions, indicating the cardholder's Expensify email address. +- **Transaction ID:** A unique ID for locating transactions and assisting Expensify Support in case of issues. Transaction IDs are handy for reconciling pre-authorizations. To find the original purchase, locate the Transaction ID in the Settlements tab of the reconciliation dashboard, download the settlements as a CSV, and search for the Transaction ID within it. + +![Expanded card settlement that shows the various items that make up each card settlement.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExpanded.png){:width="100%"} + +The Expensify Card statement only shows payments from existing Business Bank Accounts under Settings > Account > Payments > Business Accounts. If a Business Account is deleted, the statement won't contain data for payments from that account. + +## Exporting your statement +When using the Expensify Card, you can export your statement to a CSV with these steps: + + 1. Login to your account on the web app and click on Settings > Domains > Company Cards. + 2. Click the Reconciliation tab at the top right, then select Settlements. + 3. Enter your desired statement dates using the Start and End fields. + 4. Click Search to access the statement for that period. + 5. You can view the table or select Download to export it as a CSV. + +![Click the Download CSV button in the middle of the page to export your card settlements.](https://help.expensify.com/assets/images/ExpensifyHelp_SettlementExport.png){:width="100%"} + +## Expensify Card Settlement Frequency +Paying your Expensify Card balance is simple with automatic settlement. There are two settlement frequency options: + - **Daily Settlement:** Your Expensify Card balance is paid in full every business day, meaning you’ll see an itemized debit each business day. + - **Monthly Settlement:** Expensify Cards are settled monthly, with your settlement date determined during the card activation process. With monthly, you’ll see only one itemized debit per month. (Available for Plaid-connected bank accounts with no recent negative balance.) + +## How settlement works +Each business day (Monday through Friday, excluding US bank holidays) or on your monthly settlement date, we calculate the total of posted Expensify Card transactions since the last settlement. The settlement amount represents what you must pay to bring your Expensify Card balance back to $0. + +We'll automatically withdraw this settlement amount from the Verified Business Bank Account linked to the primary domain admin. You can set up this bank account in the web app under Settings > Account > Payments > Bank Accounts. + +Once the payment is made, your Expensify Card balance will be $0, and the transactions are considered "settled." + +To change your settlement frequency or bank account, go to Settings > Domains > [Domain Name] > Company Cards. On the Company Cards page, click the Settings tab, choose a new settlement frequency or account from the dropdown menu, and click Save to confirm the change. + +![Change your card settlement account or settlement frequency via the dropdown menus in the middle of the screen.](https://help.expensify.com/assets/images/ExpensifyHelp_CardSettings.png){:width="100%"} + +# Expensify Card Statement and Settlements FAQs +## Can you pay your balance early if you've reached your Domain Limit? +If you've chosen Monthly Settlement, you can manually initiate settlement using the "Settle Now" button. We'll settle the outstanding balance and then perform settlement again on your selected predetermined monthly settlement date. + +If you opt for Daily Settlement, the Expensify Card statement will automatically settle daily through an automatic withdrawal from your business bank account. No additional action is needed on your part. + +## Will our domain limit change if our Verified Bank Account has a higher balance? +Your domain limit may fluctuate based on your cash balance, spending patterns, and history with Expensify. Suppose you've recently transferred funds to the business bank account linked to Expensify card settlements. In that case, you should expect a change in your domain limit within 24 hours of the transfer (assuming your business bank account is connected through Plaid). + +## How is the “Amount Owed” figure on the card list calculated? +The amount owed consists of all Expensify Card transactions, both pending and posted, since the last settlement date. The settlement amount withdrawn from your designated Verified Business Bank Account only includes posted transactions. + +Your amount owed decreases when the settlement clears. Any pending transactions that don't post timely will automatically expire, reducing your amount owed. + +## **How do I view all unsettled expenses?** +To view unsettled expenses since the last settlement, use the Reconciliation Dashboard's Expenses tab. Follow these steps: + 1. Note the dates of expenses in your last settlement. + 2. Switch to the Expenses tab on the Reconciliation Dashboard. + 3. Set the start date just after the last settled expenses and the end date to today. + 4. The Imported Total will show the outstanding amount, and you can click through to view individual expenses. diff --git a/docs/articles/new-expensify/exports/Coming-Soon.md b/docs/articles/expensify-classic/expensify-partner-program/Coming-Soon.md similarity index 100% rename from docs/articles/new-expensify/exports/Coming-Soon.md rename to docs/articles/expensify-classic/expensify-partner-program/Coming-Soon.md diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md index a7553e6ae179..d933e66cc2d1 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-Small-To-Medium-Sized-Businesses.md @@ -3,18 +3,18 @@ title: Expensify Playbook for Small to Medium-Sized Businesses description: Best practices for how to deploy Expensify for your business redirect_from: articles/playbooks/Expensify-Playbook-for-Small-to-Medium-Sized-Businesses/ --- -## Overview +# Overview This guide provides practical tips and recommendations for small businesses with 100 to 250 employees to effectively use Expensify to improve spend visibility, facilitate employee reimbursements, and reduce the risk of fraudulent expenses. - See our [US-based VC-Backed Startups](https://help.expensify.com/articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups) if you are more concerned with top-line revenue growth -## Who you are +# Who you are As a small to medium-sized business owner, your main aim is to achieve success and grow your business. To achieve your goals, it is crucial that you make worthwhile investments in both your workforce and your business processes. This means providing your employees with the resources they need to generate revenue effectively, while also adopting measures to guarantee that expenses are compliant. -## Step-by-step instructions for setting up Expensify +# Step-by-step instructions for setting up Expensify This playbook is built on best practices we’ve developed after processing expenses for tens of thousands of companies around the world. As such, use this playbook as your starting point, knowing that you can customize Expensify to suit your business needs. Every company is different, and your dedicated Setup Specialist is always one chat away with any questions you may have. -### Step 1: Create your Expensify account +## Step 1: Create your Expensify account If you don't already have one, go to *[new.expensify.com](https://new.expensify.com)* and sign up for an account with your work email address. The account is free so don’t worry about the cost at this stage. > _Employees really appreciate how easy it is to use, and the fact that the reimbursement drops right into their bank account. Since most employees are submitting expenses from their phones, the ease of use of the app is critical_ @@ -22,7 +22,7 @@ If you don't already have one, go to *[new.expensify.com](https://new.expensify. > **Robyn Gresham** > Senior Accounting Systems Manager at SunCommon -### Step 2: Create a Control Policy +## Step 2: Create a Control Policy There are three policy types, but for your small business needs we recommend the *Control Plan* for the following reasons: - *The Control Plan* is designed for organizations with a high volume of employee expense submissions, who also rely on compliance controls @@ -40,7 +40,7 @@ To create your Control Policy: The Control Plan also gives you access to a dedicated Setup Specialist. You can find yours by looking at your policy's *#admins* room in *[new.expensify.com](https://new.expensify.com)*, and in your company’s policy settings in the *Overview* tab, where you can chat with them and schedule an onboarding call to walk through any setup questions. The Control Plan bundled with the Expensify Card is only *$9 per user per month* (not taking into account cash back your earn) when you commit annually. That’s a 75% discount off the unbundled price point if you choose to use a different Corporate Card (or no) provider. -### Step 3: Connect your accounting system +## Step 3: Connect your accounting system As a small to medium-sized business, it's important to maintain proper spend management to ensure the success and stability of your organization. This requires paying close attention to your expenses, streamlining your financial processes, and making sure that your financial information is accurate, compliant, and transparent. Include best practices such as: - Every purchase is categorized into the correct account in your chart of accounts @@ -65,7 +65,7 @@ Check out the links below for more information on how to connect to your account *“Employees really appreciate how easy it is to use, and the fact that the reimbursement drops right into their bank account. Since most employees are submitting expenses from their phones, the ease of use of the app is critical.”* - Robyn Gresham, Senior Accounting Systems Manager at SunCommon -### Step 4: Set category-specific compliance controls +## Step 4: Set category-specific compliance controls Head over to the *Categories* tab to set compliance controls on your newly imported list of categories. More specifically, we recommend the following: 1. First, enable *People Must Categorize Expenses*. Employees must select a category for each expense, otherwise, in most cases, it’s more work on you and our accounting connections will simply reject any attempt to export. @@ -78,7 +78,7 @@ Head over to the *Categories* tab to set compliance controls on your newly impor 3. Disable any irrelevant expense categories that aren’t associated with employee spend 4. Configure *auto-categorization*, located just below your category list in the same tab. The section is titled *Default Categories*. Just find the right category, and match it with the presented category groups to allow for MCC (merchant category code) automated category selection with every imported connected card transaction. -### Step 5: Make sure tags are required, or defaults are set +## Step 5: Make sure tags are required, or defaults are set Tags in Expensify often relate to departments, projects/customers, classes, and so on. And in some cases they are *required* to be selected on every transactions. And in others, something like *departments* is a static field, meaning we could set it as an employee default and not enforce the tag selection with each expense. *Make Tags Required* @@ -89,7 +89,7 @@ In the tags tab in your policy settings, you’ll notice the option to enable th *Set Tags as an Employee Default* Separately, if your policy is connected to NetSuite or Sage Intacct, you can set departments, for example, as an employee default. All that means is we’ll apply the department (for example) that’s assigned to the employee record in your accounting package and apply that to every exported transaction, eliminating the need for the employee to have to manually select a department for each expense. -### Step 6: Set rules for all expenses regardless of categorization +## Step 6: Set rules for all expenses regardless of categorization In the Expenses tab in your group Control policy, you’ll notice a *Violations* section designed to enforce top-level compliance controls that apply to every expense, for every employee in your policy. We recommend the following confiuration: *Max Expense Age: 90 days (or leave it blank)* @@ -105,7 +105,7 @@ Receipts are important, and in most cases you prefer an itemized receipt. Howeve At this point, you’ve set enough compliance controls around categorical spend and general expenses for all employees, such that you can put trust in our solution to audit all expenses up front so you don’t have to. Next, let’s dive into how we can comfortably take on more automation, while relying on compliance controls to capture bad behavior (or better yet, instill best practices in our employees). -### Step 7: Set up scheduled submit +## Step 7: Set up scheduled submit For an efficient company, we recommend setting up [Scheduled Submit](https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-policy) on a *Daily* frequency: - Click *Settings > Policies* @@ -125,7 +125,7 @@ Expenses with violations will stay behind for the employee to fix, while expense > Kevin Valuska > AP/AR at Road Trippers -### Step 8: Connect your business bank account (US only) +## Step 8: Connect your business bank account (US only) If you’re located in the US, you can utilize Expensify’s payment processing and reimbursement features. *Note:* Before you begin, you’ll need the following to validate your business bank account: @@ -145,7 +145,7 @@ Let’s walk through the process of linking your business bank account: You only need to do this once: you are fully set up for not only reimbursing expense reports, but issuing Expensify Cards, collecting customer invoice payments online (if applicable), as well as paying supplier bills online. -### Step 9: Invite employees and set an approval workflow +## Step 9: Invite employees and set an approval workflow *Select an Approval Mode* We recommend you select *Advanced Approval* as your Approval Mode to set up a middle-management layer of a approval. If you have a single layer of approval, we recommend selecting [Submit & Approve](https://community.expensify.com/discussion/5643/deep-dive-submit-and-approve). But if *Advanced Approval* if your jam, keep reading! @@ -159,13 +159,13 @@ In most cases, at this stage, approvers prefer to review all expenses for a few In this case we recommend setting *Manually approve all expenses over: $0* -### Step 10: Configure Auto-Approval +## Step 10: Configure Auto-Approval Knowing you have all the control you need to review reports, we recommend configuring auto-approval for *all reports*. Why? Because you’ve already put reports through an entire approval workflow, and manually triggering reimbursement is an unnecessary action at this stage. 1. Navigate to *Settings > Policies > Group > [Policy Name] > Reimbursement* 2. Set your *Manual Reimbursement threshold to $20,0000* -### Step 11: Enable Domains and set up your corporate card feed for employees +## Step 11: Enable Domains and set up your corporate card feed for employees Expensify is optimized to work with corporate cards from all banks – or even better, use our own perfectly integrated *[Expensify Card](https://use.expensify.com/company-credit-card)*. The first step for connecting to any bank you use for corporate cards, and the Expensify Card is to validate your company’s domain in Domain settings. To do this: @@ -173,7 +173,7 @@ To do this: - Click *Settings* - Then select *Domains* -#### If you have an existing corporate card +### If you have an existing corporate card Expensify supports direct card feeds from most financial institutions. Setting up a corporate card feed will pull in the transactions from the connected cards on a daily basis. To set this up, do the following: 1. Go to *Company Cards >* Select your bank @@ -187,7 +187,7 @@ Expensify supports direct card feeds from most financial institutions. Setting u As mentioned above, we’ll be able to pull in transactions as they post (daily) and handle receipt matching for you and your employees. One benefit of the Expensify Card for your company is being able to see transactions at the point of purchase which provides you with real-time compliance. We even send users push notifications to SmartScan their receipt when it’s required and generate IRS-compliant e-receipts as a backup wherever applicable. -#### If you don't have a corporate card, use the Expensify Card (US only) +### If you don't have a corporate card, use the Expensify Card (US only) Expensify provides a corporate card with the following features: - Up to 2% cash back (up to 4% in your first 3 months!) @@ -214,7 +214,7 @@ Once the Expensify Cards have been assigned, each employee will be prompted to e If you have an accounting system we directly integrate with, check out how we take automation a step further with [Continuous Reconciliation](https://community.expensify.com/discussion/7335/faq-what-is-the-expensify-card-auto-reconciliation-process). We’ll create an Expensify Card clearing and liability account for you. Each time settlement occurs, we’ll take the total amount of your purchases and create a journal entry that credits the settlement account and debits the liability account - saving you hours of manual reconciliation work at the end of your statement period. -### Step 12: Set up Bill Pay and Invoicing +## Step 12: Set up Bill Pay and Invoicing As a small business, managing bills and invoices can be a complex and time-consuming task. Whether you receive bills from vendors or need to invoice clients, it's important to have a solution that makes the process simple, efficient, and cost-effective. Here are some of the key benefits of using Expensify for bill payments and invoicing: @@ -246,7 +246,7 @@ Reports, invoices, and bills are largely the same, in theory, just with differen You’ll notice it’s a slightly different flow from creating a Bill. Here, you are adding the transactions tied to the Invoice, and establishing a due date for when it needs to get paid. If you need to apply any markups, you can do so from your policy settings under the Invoices tab. Your customers can pay their invoice in Expensify via ACH, or Check, or Credit Card. -### Step 13: Run monthly, quarterly and annual reporting +## Step 13: Run monthly, quarterly and annual reporting At this stage, reporting is important and given that Expensify is the primary point of entry for all employee spend, we make reporting visually appealing and wildly customizable. 1. Head to the *Expenses* tab on the far left of your left-hand navigation @@ -261,7 +261,7 @@ We recommend reporting: ![Expenses!](https://help.expensify.com/assets/images/playbook-expenses.png){:width="100%"} -### Step 14: Set your Subscription Size and Add a Payment card +## Step 14: Set your Subscription Size and Add a Payment card Our pricing model is unique in the sense that you are in full control of your billing. Meaning, you have the ability to set a minimum number of employees you know will be active each month and you can choose which level of commitment fits best. We recommend setting your subscription to *Annual* to get an additional 50% off on your monthly Expensify bill. In the end, you've spent enough time getting your company fully set up with Expensify, and you've seen how well it supports you and your employees. Committing annually just makes sense. To set your subscription, head to: @@ -280,5 +280,5 @@ Now that we’ve gone through all of the steps for setting up your account, let 3. Enter your name, card number, postal code, expiration and CVV 4. Click *Accept Terms* -## You’re all set! +# You’re all set! Congrats, you are all set up! If you need any assistance with anything mentioned above or would like to understand other features available in Expensify, reach out to your Setup Specialist directly in *[new.expensify.com](https://new.expensify.com)*. Don’t have one yet? Create a Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. diff --git a/docs/articles/expensify-classic/exports/Custom-Templates.md b/docs/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates.md similarity index 100% rename from docs/articles/expensify-classic/exports/Custom-Templates.md rename to docs/articles/expensify-classic/insights-and-custom-reporting/Custom-Templates.md diff --git a/docs/articles/expensify-classic/exports/Default-Export-Templates.md b/docs/articles/expensify-classic/insights-and-custom-reporting/Default-Export-Templates.md similarity index 100% rename from docs/articles/expensify-classic/exports/Default-Export-Templates.md rename to docs/articles/expensify-classic/insights-and-custom-reporting/Default-Export-Templates.md diff --git a/docs/articles/expensify-classic/exports/Insights.md b/docs/articles/expensify-classic/insights-and-custom-reporting/Insights.md similarity index 100% rename from docs/articles/expensify-classic/exports/Insights.md rename to docs/articles/expensify-classic/insights-and-custom-reporting/Insights.md diff --git a/docs/articles/expensify-classic/exports/Other-Export-Options.md b/docs/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.md similarity index 100% rename from docs/articles/expensify-classic/exports/Other-Export-Options.md rename to docs/articles/expensify-classic/insights-and-custom-reporting/Other-Export-Options.md diff --git a/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md b/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md index 3ee1c8656b4b..98cc6f2bfdf6 100644 --- a/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md +++ b/docs/articles/expensify-classic/integrations/accounting-integrations/Xero.md @@ -1,5 +1,260 @@ --- -title: Coming Soon -description: Coming Soon +title: The Xero Integration +description: Everything you need to know about Expensify's direct integration with Xero --- -## Resource Coming Soon! + +# About + +The integration enables seamless import of expense accounts into Expensify and sends expense reports back to Xero as purchasing bills awaiting payment or "spend money" bank transactions. + +# How-to Connect to Xero + +## Prerequisites + +You must be a Workspace Admin in Expensify using a Collect or Control Workspace to connect your Xero account to Expensify. + +## Connect Expensify and Xero + +1. Let's get started by heading over to your Settings. You can find it by following this path: *Settings > Workspaces > Groups > [Workspace Name] > Connections > Xero.* +2. To connect Expensify to Xero, click on the "Connect to Xero” button, then choose "Create a new Xero connection." +3. Next, enter your Xero login details. After that, you'll need to select the Xero organization you want to link with Expensify. Remember, you can connect one organization for each Workspace. + +One important note: Starting in September 2021, there's a chance for Cashbook and Ledger-type organizations in Xero. Apps like Expensify won't be able to create invoices and bills for these accounts using the Xero API. So, if you're using a Cashbook or Ledger Xero account, please be aware that this might affect your Expensify integration. + +# How to Configure Export Settings for Xero + +When you integrate Expensify with Xero you gain control over several settings that determine how your reports will be displayed in Xero. To manage these settings simply follow this path: *Settings > Workspaces > Group > [Workspace Name] > Connections > Accounting Integrations > Xero > Configure > Export*. This is where you can fine-tune how your reports appear on the Xero side, making your expense management a breeze! + +## Xero Organization + +When you have multiple organizations set up in Xero you can choose which one you'd like to connect. Here are some essential things to keep in mind: + +1. Organization Selection: You'll see this option only if you have multiple organizations configured in Xero. +2. One Workspace, One Organization: Each Workspace can connect to just one organization at a time. It's a one-to-one connection. +3. Adding New Organizations: If you create a new organization in Xero after your initial connection, you'll need to disconnect and then reconnect it to Xero. Don't forget to take a screenshot of your current settings by clicking on "Configure" and checking the Export, Coding, and Advanced tabs. This way, you can easily set everything up again. + +Now you can seamlessly manage your connections with Xero while staying in control of your configurations! + +## Preferred Exporter + +Any Workspace admin can export to Xero, but only the preferred exporter will see reports that are ready for export in their Home. + +## Reimbursable Expenses + +Export to Xero as bills awaiting payment with the following additional settings: + +- Bill date — the bill is posted on the last day of the month in which expenses were incurred. + +To view the bills in Xero, navigate to *Business > Purchase Overview > Awaiting Payments*. Bills will be payable to the individual who created and reported the expense. + +## Non-reimbursable Expenses + +When you export non-reimbursable expenses, like company card transactions, to Xero they'll show up as bank transactions. Each expense is neatly listed as a separate line item in the bank account of your choice. Plus the transaction date matches the date on your bank statement for seamless tracking. + +To check out these expenses in Xero please follow these steps: + +1. Head over to your Dashboard. +2. Select your company card. +3. Locate the specific expense you're interested in. + +If you're managing company cards centrally, you can export expenses from each card to a designated account in Xero using Domains. This way, you have complete control and clarity over your company's finances! + +# How to Configure Coding for Xero + +The Coding tab in Expensify is where you configure Xero information to ensure accurate expense coding by your employees. Here's how you can access these settings: + +1. Navigate to Settings. +2. Go to Workspace within your specified group (Workspace Name). +3. Click on Connections, and then hit the Configure button. +4. Now, select the Coding tab. + +## Categories + +Xero expense accounts and those marked "Show In Expense Claims" will be automatically imported into Expensify as Categories. + +To manage these categories, follow these steps: + +1. After connecting, go to *Settings > Workspaces > Groups > [Workspace Name] > Categories*. +2. You can enable/disable categories using the checkbox. +3. For specific category rules (like default tax rate, maximum amount, receipts required, comments, and comment hints), click the settings cog. +4. Note that each expense must have a category selected for it to export to Xero, and these categories need to be imported from Xero; manual creation isn't an option within Workspace settings. + +## Tracking Categories + +1. If you use Tracking categories in Xero, you can import them into Expensify as Tags, Report Fields, or the Xero contact default. +- Tags apply a tracking category per expense. +- Report Field applies a tracking category to the entire report. +- Xero contact default applies the default tracking category set for the submitter in Xero. + +## Tax + +Looking to track tax in Expensify? Make sure that you have tax rates enabled in Xero and we will automatically grab those rates from Xero to allow your employees to categorize expenses with the appropriate tax rate. As an admin, you have the ability to set a default rate and also hide rates that are not applicable to the Workspace members. + +Tax tracking allows you to apply a tax rate and tax amount to each expense. +1. To set this up, enable Tax tracking in your Xero configuration. +2. After connecting, go to *Settings > Workspaces > Groups > [Workspace Name] > Tax to manage imported taxes from Xero.* +3. You can enable/disable taxes and set default tax rates for both Workspace currency expenses and foreign currency expenses. + +## Billable Expenses + +If you bill expenses to your customers, you can track and invoice them using Expensify and Xero. + +1. When enabled, Xero customer contacts are imported into Expensify as Tags for expense tracking. +- Note: In Xero, a Contact isn't a 'Customer' until they've had a bill raised against them. If you don't see your Customer imported as a tag, try raising a dummy invoice in Xero and then deleting/voiding it. +2. After exporting to Xero, tagged billable expenses can be included on a sales invoice to your customer. + +Please ensure that you meet the following requirements for expenses to be placed on a sales invoice: +1. Billable Expenses must be enabled in the Xero configuration settings. +2. The expense must be marked as billable. +3. The expense must be tagged with a customer. + +These steps should help you seamlessly manage your Xero integration within Expensify. + +# How to Configure Xero’s Advanced Settings + +If you've already set up your integration, but want to make adjustments, simply follow these steps: + +1. Go to Settings. +2. Then, navigate to Workspaces within your designated group [Workspace Name]. +3. Click on Connections, and next, hit the Configure button. + +From there, you can dive into the "Advanced" tab to make any additional tweaks. + +## Auto Sync + +For non-reimbursable reports: Once a report has completed the approval workflow in Expensify, we'll automatically queue it for export to Xero. + +But, if you've added a business bank account for ACH reimbursement, any reimbursable expenses will be sent to Xero automatically when the report is marked as reimbursed or enabled for reimbursement. + +### Controlling Newly Imported Categories: + +You can decide how newly imported categories behave in Expensify: + +1. Enabling or disabling this control determines the status of new categories imported from Xero to Expensify. Enabled categories are visible for employees when they categorize expenses, while disabled categories remain hidden. + +These settings give you the flexibility to manage your expenses and Workspace in the way that best suits your needs! + +## Sync Reimbursed Reports + +This nifty setting lets you synchronize the status of your reports between Expensify and Xero. Utilizing this setting will make sure that there is no confusion or possibility that a reimbursable report is paid out twice by mistake or that a non-reimbursable report is double entered throwing off month-end reconciliation. Here's how it works: + +1. When you reimburse a report via ACH direct deposit within Expensify, the purchase bill will automatically be marked as paid in Xero, and Expensify will note it as reimbursed. +2. Don't forget to pick the Xero account where the corresponding bill payment should be recorded. +3. It's a simple way to keep everything in sync, especially when you're awaiting payment. + +# Deep Dive + +## An Automatic Export Fails + +Sometimes, reports may encounter issues during automatic export to Xero. Not to worry, though! Here's what happens: + +1. The Technical Contact, your go-to person for technical matters, will receive an email explaining the problem. +2. You'll also find specific error messages at the bottom of the report. +3. To get things back on track, the report will be placed in the preferred exporter’s Home. They can review it and resolve any issues. + +## Consider Enforcing Expense Workspace Workflows: + +For added control, you can adjust your Workspace settings to strictly enforce expense Workspace. This way, you guarantee that your Workspace’s workflow is always followed. By default this flow is in place, but employees can modify the person they submit their reports to if it's not strictly enforced. + +## Customize Purchase Bill Status (Optional): + +You have the flexibility to set the status of your purchase bills just the way you want. Choose from the following options: + +1. Draft: Keep bills in a draft state until you're ready to finalize them. +2. Awaiting Approval: If you need approval before processing bills, this option is here for you. + +## Multi-Currency + +### Handling Multi-Currency in Xero + +When dealing with multi-currency transactions in Xero and exporting reimbursable expenses from Expensify here's what you need to know: + +1. The bill created in Xero will adopt the output currency set in your Expensify Workspace, provided that it's enabled in Xero. +2. Your general ledger reports will automatically convert to your home currency in Xero, leveraging the currency exchange rates defined in your Xero settings. It ensures everything aligns seamlessly. + +Now, for non-reimbursable expenses, things work slightly differently: + +1. Bank transactions will use the currency specified in your bank account in Xero, regardless of the currency used in Expensify. +2. If these currencies don't match, no worries! We apply a 1:1 exchange rate to make things smooth. To ensure a hassle-free experience, just ensure that the output currency in Expensify matches the currency specified in your Xero bank account. + +## Tax + +### Enabling Tax Tracking for Seamless Integration: + +To simplify tax tracking, enable it in your Xero configuration. This action will automatically bring all your Xero tax settings into Expensify, turning them into usable Taxes. + +### After connecting your Xero account with Expensify: + +1. Head to Settings. +2. Navigate to Workspaces within your specific group [Workspace Name]. +3. Click on Tax to view the taxes that have been imported from Xero. + +Now, here's where you can take control: + +1. Use the enable/disable button to choose which taxes your employees can apply to their expenses. Customize it to fit your needs. +2. You can set a default tax rate for expenses in your Workspace currency. Additionally, if you deal with foreign currency expenses, you have the option to set another default tax (including exempt) that will automatically apply to all new expenses in foreign currencies. + +This setup streamlines your tax management, making it effortless for your team to handle taxes on their expenses. + +## Export Invoices to Xero + +You can effortlessly export your invoices from Expensify to Xero and even attribute them to the right Customer. Plus, when you mark an invoice as paid in Expensify, the same status will smoothly transfer to Xero and vice versa, keeping your invoice tracking hassle-free. Let's dive in: + +### Setting up Invoice Export to Xero: + +1. Navigate to Settings. +2. Go to Workspaces within your designated group [Workspace Name]. +3. Click on Connections, then select Configuration. +4. Now, click on the Advanced tab. + +### Selecting Your Xero Invoice Collection Account: + +1. Scroll down until you find "Xero invoice collection account." You'll see a dropdown list of your available Accounts Receivable accounts imported from Xero. +2. Simply choose the account where you'd like your invoices to be exported. + +Pro Tip: If you don't see any accounts in the dropdown, try syncing your Xero connection. To do this, go back to the Connections page and hit "Sync Now." + +### Exporting an Invoice to Xero: + +Invoices will automatically make their way to Xero when they're in the Processing or Paid state. This ensures consistent tracking of unpaid and paid invoices. However, if you have Auto Sync disabled, you'll need to manually export your invoices along with your expense reports. Here's how: + +1. Head to your Reports page. +2. Use the filters to locate the invoices you want to export. +3. Select the invoices you wish to export. +4. Click Export to > Xero on the top right-hand side. + +### Matching Customers and Emails: + +When exporting to Xero, we match the recipient's email address with a customer record in Xero. So, make sure each customer in Xero has their email listed in their profile. +If we can't find a match, we'll create a new customer record in Xero. + +### Updating Invoice Status: + +1. When you mark an invoice as Paid in Expensify, this status will automatically reflect in Xero. +2. Similarly, if you mark an invoice as Paid in Xero, it will update automatically in Expensify. +3. The payment will be recorded in the Collection account you've chosen in your Advanced Settings Configuration. + +And that's it! You've successfully set up and managed your invoice exports to Xero, making your tracking smooth and efficient. + +# FAQ + +## Will receipt images be exported to Xero? + +Yes! The receipt images will be exported to Xero. To see them in Xero click the 'paper' icon in the upper right corner of the expense details and view a PDF of the Expensify report including the receipt image. + +## How does Auto Sync work if your workspace was initially connected to Xero with Auto Sync disabled? + +You can safely switch it on without affecting existing reports that haven't been exported. + +## How does Auto Sync work if a report has already been exported to Xero and reimbursed through ACH or marked as reimbursed in Expensify? + +It will be automatically marked as paid in Xero during the next sync. You may either manually update by clicking Sync Now in the Connections tab or Expensify does this on your behalf overnight every day! + +## How does Auto Sync work if a report has been exported to Xero and marked as paid in Xero? + +It will be automatically marked as reimbursed in Expensify during the next sync. If you need it updated immediately please go to the Connections tab and click Sync Now or if you can wait just let Expensify do it for you overnight. + +## How does Auto Sync work if a report has been exported to Xero and marked as paid in Xero? + +Reports that haven't been exported to Xero won't be sent automatically. + diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md b/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md index 3ee1c8656b4b..51bf658db248 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md +++ b/docs/articles/expensify-classic/integrations/travel-integrations/TravelPerk.md @@ -1,5 +1,71 @@ --- -title: Coming Soon -description: Coming Soon +title: Connecting TravelPerk to your Expensify Account +description: Help article that describes how to connect TravelPerk to your Expensify Account --- -## Resource Coming Soon! +# Connecting TravelPerk to your Expensify Account + +## Overview +Expensify and TravelPerk are two powerful tools that can streamline your expense management and travel booking processes. By integrating these two platforms, you can make tracking travel expenses even more efficient. This article will walk you through the steps to integrate Expensify with Travel Perk seamlessly. + +## How to Connect TravelPerk to your Expensify Account +**Prerequisites:** +Before you begin, ensure that you have the following: +- An active Expensify account. +- An active TravelPerk account. +- Administrative access to both Expensify and TravelPerk accounts. + +1. **Log in to your Expensify account (web)** + - Open your web browser and navigate to the Expensify login page. + - Enter your Expensify username and password. + - Click "Sign In" to access your Expensify account. + +2. **Access Your Expensify Account Settings** + - Once logged in, click on your profile icon or username in the upper-right corner. + - From the dropdown menu, select "Settings." + +3. **Navigate to Integrations** + - In the Settings menu, find and click on the "Integrations" option. + +4. **Search for TravelPerk Integration** + - In the Integrations section, locate the search bar. + - Type "TravelPerk" into the search bar and hit "Enter." + +5. **Connect TravelPerk to Expensify** + - Click on the Travel Perk integration option. + - You'll be prompted to log in to your Travel Perk account. Enter your TravelPerk credentials and log in. + +6. **Authorize the Integration** + - After logging in to TravelPerk, you'll be asked to authorize the integration. Review the permissions requested and click "Authorize" or "Allow." + +7. **Configure Integration Settings** + - Once the integration is authorized, you may have the option to configure settings such as expense categories and tags. + - Follow the on-screen prompts to customize the integration settings according to your preferences. + +8. **Save Integration Settings** + - After configuring the integration settings, click the "Save" or "Finish" button to confirm your choices. + +9. **Test the Integration** + - To ensure that the integration is working correctly, consider creating a test expense in TravelPerk. + - Wait for a few minutes and check your Expensify account to confirm that the expense has been automatically imported. + +10. **Regularly Review and Approve Expenses** + - With the integration in place, expenses from TravelPerk will be automatically synced to your Expensify account. + - Regularly review and approve these expenses in Expensify to keep your financial records up to date. + +## How to Book Travel +- From the Trips dashboard in TravelPerk, click Create Trip. +- Give your trip a unique name, then book your flights and hotels. +- Review your itinerary and click Confirm Payment, and your TravelPerk invoice and itinerary will automatically populate in Expensify! + +## Deep Dive on the TravelPerk Integration + +The integration between Expensify and TravelPerk enables a seamless flow of data between the two platforms. When employees book travel through TravelPerk, their travel expenses are automatically transferred to Expensify. + +## Key Benefits +- **Efficiency and accuracy:** The TravelPerk integration provides real-time data synchronization. Travel expenses are automatically input into Expensify, allowing for timely reporting and reimbursement. +- **Expense policy compliance:** TravelPerk helps enforce corporate travel policies by offering pre-approved travel options. Expenses generated from these bookings automatically adhere to company policies. +- **Visibility and control:** Finance teams gain greater visibility into travel expenses. They can track expenses in real-time, monitor spending trends, and enforce budget controls more effectively. +- **Streamlined approval workflows:** Expense approval workflows can be set up in Expensify. Managers can review and approve expenses with ease, ensuring adherence to company policies. + +Integrating Expensify with TravelPerk can significantly simplify your expense management process. By following these steps, you can ensure that your travel expenses are automatically imported into Expensify, making it easier to track and report expenses accurately. If you encounter any issues or have questions, don’t hesitate to reach out to your Account Manager or concierge@expensify.com with any questions. + diff --git a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md index 3ee1c8656b4b..1f69c1eee8f4 100644 --- a/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md +++ b/docs/articles/expensify-classic/manage-employees-and-report-approvals/Approval-Workflows.md @@ -1,5 +1,109 @@ --- -title: Coming Soon -description: Coming Soon +title: Managing employees and reports > Approval workflows +description: Set up the workflow that your employees reports should flow through. --- -## Resource Coming Soon! + + +# About +## Overview + + +This document explains how to manage employee expense reports and approval workflows in Expensify. + + +### Approval workflow modes + + +#### Submit and close +- This is a workflow where no approval occurs in Expensify. +- *What happens after submission?* The report state becomes Closed and is available to view by the member set in Submit reports to and any Workspace Admins. +- *Who should use this workflow?* This mode should be used where you don't require approvals in Expensify. + + +#### Submit and approve +- *Submit and approve* is a workflow where all reports are submitted to a single member for approval. New policies have Submit and Approve enabled by default. +- *What happens after submission?* The report state becomes Processing and it will be sent to the member indicated in Submit reports to for approval. When the member approves the report, the state will become Approved. +- *Who should use this workflow?* This mode should be used where the same person is responsible for approving all reports for your organization. If submitters have different approvers or multiple levels of approval are required, then you will need to use Advance Approval. + + +#### Advanced Approval +- This approval mode is used to handle more complex workflows, including: + - *Multiple levels of approval.* This is for companies that require more than one person to approve a report before it can be reimbursed. The most common scenario is when an employee needs to submit to their manager, and their manager needs to approve and forward that report to their finance department for final approval. + - *Varying approval workflows.* For example, if a company has Team A submitting reports to Manager A, and Team B to Manager B, use Advanced Approval. Group Workspace Admins can also set amount thresholds in the case that a report needs to go to a different approver based on the amount. +- *What happens after submission?* After the report is submitted, it will follow the set approval chain. The report state will be Processing until it is Final Approved. We have provided examples of how to set this up below. +- *Who should use this workflow?* Organizations with complex workflows or 2+ levels of approval. This could be based on manager approvals or where reports over a certain size require additional approvals. +- *For further automation:* use Concierge auto-approval for reports. You can set specific rules and guidelines in your Group Workspace for your team's expenses; if all expenses are below the Manual Approval Threshold and adhere to all the rules, then we will automatically approve these reports on behalf of the approver right after they are submitted. + + +### How to set an approval workflow + +- Step-by-step instructions on how to set this up at the Workspace level [here](link-to-instructions). + +# Deep Dive + +### Setting multiple levels of approval +- 'Submits to' is different than 'Approves to'. + - *Submits to* - is the person you are sending your reports to for 1st level approval + - *Approves to* - is the person you are sending the reports you've approved for higher-level approval +- In the example below, a report needs to be approved by multiple managers: *Submitter > Manager > Director > Finance/Accountant* + - *Submitter (aka. Employee):* This is the person listed under the member column of the People page. + - *First Approver (Manager):* This is the person listed under the Submits to column of the People Page. + - *Second Approver (Director):* This is the person listed as 'Approves to' in the Settings of the First Approver. + - *Final Approver (Finance/Accountant):* This is the person listed as the 'Approves to' in the Settings of the Second Approver. +- This is what this setup looks like in the Workspace Members table. + - Bryan submits his reports to Jim for 1st level approval. +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} + + - All of the reports Jim approves are submitted to Kevin. Kevin is the 'approves to' in Jim's Settings. +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} + + - All of the reports Kevin approves are submitted to Lucy. Lucy is the 'approves to' in Kevin's Settings. +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} + + + - Lucy is the final approver, so she doesn't submit her reports to anyone for review. +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} + + +- The final outcome: The member in the Submits To line is different than the person noted as the Approves To. +### Adding additional approver levels +- You can also set a specific approver for Reports Totals in Settings. +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} + +- An example: The submitter's manager can approve any report up to a certain limit, let's say $500, and forward it to accounting. However, if a report is over that $500 limit, it has to be also approved by the department head before being forwarded to accounting. +- To configure, click on Edit Settings next to the approving manager's email address and set the "If Report Total is Over" and "Then Approves to" fields. +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} +![Insert alt text for accessibility here](https://help.expensify.com/assets/images/image-name.png){:width="100%"} + + +### Setting category approvals +- If your expense reports should be reviewed by an additional approver based on specific categories or tags selected on the expenses within the report, set up category approvers and tag approvers. +- Category approvers can be set in the Category settings for each Workspace +- Tag approvers can be set in the Tag settings for each Workspace + + +#### Category approver +- A category approver is a member who is added to the approval workflow for any reports in your Expensify Workspace that contain expenses with a specific category. +- For example: Your HR director Jim may need to approve any relocation expenses submitted by employees. Set Jim up as the category approver for your Relocation category, then any reports containing Relocation expenses will first be routed to Jim before continuing through the approval workflow. +- Adding category approvers + - To add a category approver in your Workspace: + - Navigate to *Settings > Policies > Group > [Workspace Name] > Categories* + - Click *"Edit Settings"* next to the category that requires the additional approver + - Select an approver and click *“Save”* + + +#### Tag approver +- A tag approver is a member who is added to the approval workflow for any reports in your Expensify Workspace that contain expenses with a specific tag. +- For example: If employees must tag project-based expenses with the corresponding project tag. Pam, the project manager is set as the project approver for that project, then any reports containing expenses with that project tag will first be routed to Pam for approval before continuing through the approval workflow. +- Please note: Tag approvers are only supported for a single level of tags, not for multi-level tags. The order in which the report is sent to tag approvers relies on the date of the expense. +- Adding tag approvers + - To add a tag approver in your Workspace: + - Navigate to *Settings > Policies > Group > [Workspace Name] > Tags* + - Click in the "Approver" column next to the tag that requires an additional approver + + +Category and Tag approvers are inserted at the beginning of the approval workflow already set on the People page. This means the workflow will look something like: * *Submitter > Category Approver(s) > Tag Approver(s) > Submits To > Previous approver's Approves To.* + + +### Workflow enforcement +- If you want to ensure your employees cannot override the workflow you set - enable workflow enforcement by following the steps below. As a Workspace Admin, you can choose to enforce your approval workflow by going. \ No newline at end of file diff --git a/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md b/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md index 14ade143a35b..1a567dbe6fa3 100644 --- a/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md +++ b/docs/articles/expensify-classic/send-payments/Third-Party-Payments.md @@ -6,7 +6,7 @@ description: A help article that covers Third Party Payment options including Pa Expensify offers convenient third party payment options that allow you to streamline the process of reimbursing expenses and managing your finances. With these options, you can pay your expenses and get reimbursed faster and more efficiently. In this guide, we'll walk you through the steps to set up and use Expensify's third party payment options. -## Overview +# Overview Expensify offers integration with various third party payment providers, making it easy to reimburse employees and manage your expenses seamlessly. Some of the key benefits of using third-party payment options in Expensify include: @@ -14,7 +14,7 @@ Expensify offers integration with various third party payment providers, making - Secure Transactions: Benefit from the security features and protocols provided by trusted payment providers. - Centralized Expense Management: Consolidate all your expenses and payments within Expensify for a more efficient financial workflow. -## Setting Up Third Party Payments +# Setting Up Third Party Payments To get started with third party payments in Expensify, follow these steps: @@ -30,7 +30,7 @@ To get started with third party payments in Expensify, follow these steps: 6. **Verify Your Account**: Confirm your linked account to ensure it's correctly integrated with Expensify. -## Using Third Party Payments +# Using Third Party Payments Once you've set up your third party payment option, you can start using it to reimburse expenses and manage payments: @@ -42,22 +42,18 @@ Once you've set up your third party payment option, you can start using it to re 4. **Track Payment Status**: You can track the status of payments and view transaction details within your Expensify account. -## FAQ’s +# FAQ’s -### Q: Are there any fees associated with using third party payment options in Expensify? +## Q: Are there any fees associated with using third party payment options in Expensify? A: The fees associated with third party payments may vary depending on the payment provider you choose. Be sure to review the terms and conditions of your chosen provider for details on any applicable fees. -### Q: Can I use multiple third party payment providers with Expensify? +## Q: Can I use multiple third party payment providers with Expensify? A: Expensify allows you to link multiple payment providers if needed. You can select the most suitable payment method for each expense report. -### Q: Is there a limit on the amount I can reimburse using third party payments? +## Q: Is there a limit on the amount I can reimburse using third party payments? A: The reimbursement limit may depend on the policies and settings configured within your Expensify account and the limits imposed by your chosen payment provider. With Expensify's third party payment options, you can simplify your expense management and reimbursement processes. By following the steps outlined in this guide, you can set up and use third party payments efficiently. - - - - diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Categories.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Categories.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/Categories.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/Categories.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Domains-Overview.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/Domains-Overview.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/Domains-Overview.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Expenses.md similarity index 96% rename from docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/Expenses.md index 424338120010..388bb5d5cbc9 100644 --- a/docs/articles/expensify-classic/policy-and-domain-settings/Expenses.md +++ b/docs/articles/expensify-classic/workspace-and-domain-settings/Expenses.md @@ -4,7 +4,7 @@ description: Expense Settings --- # Overview -Expensify offers multiple ways to customize how expenses are created in your workspace. In this doc, you’ll learn how to set up and expense basics, distance expenses, and time expenses. +Expensify offers multiple ways to customize how expenses are created in your workspace. In this doc, you’ll learn how to set up expense basics, distance expenses, and time expenses. Whether you’re flying solo with your Individual workspace or submitting with a team on your Group workspace, we have settings to support how you use Expensify. @@ -69,7 +69,7 @@ Preliminary setup steps include: 3. Click **Add A Mileage Rate** to add as many rates as you need, 4. Set the reimbursable amount per mile or kilometer. -Note: _If a rate is toggled off it is immediately disabled. This means that users are no longer able to select it when creating a new distance expense. If only one rate is available then this rate will be toggled on by default._ +Note: _If a rate is toggled off it is immediately disabled. This means that users are no longer able to select it when creating a new distance expense. If only one rate is available then that rate will be toggled on by default._ ## Set an hourly rate @@ -96,7 +96,7 @@ Note: _If a report has audit alerts on it, you'll need to Review the report and ## Tracking tax on mileage expenses -If you’re tracking tax in Expensify you can also track tax on distance expenses. The first step is to enable tax the workspace. You can do this by going to **Settings** > **Workspaces** > **Individual** or **Group** > [_Workspace Name_] > **Tax**. +If you’re tracking tax in Expensify you can also track tax on distance expenses. The first step is to enable tax in the workspace. You can do this by going to **Settings** > **Workspaces** > **Individual** or **Group** > [_Workspace Name_] > **Tax**. Once tax is enabled on a workspace level you will see a toggle to _Track Tax_ in the Distance section of the workspace settings. If tax is disabled on the workspace the Track Tax toggle will not display. diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Invoicing.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/Invoicing.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/Invoicing.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Per-Diem.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/Per-Diem.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/Per-Diem.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Reimbursement.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/Reimbursement.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/Reimbursement.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/SAML-SSO.md b/docs/articles/expensify-classic/workspace-and-domain-settings/SAML-SSO.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/SAML-SSO.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/SAML-SSO.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/Tags.md b/docs/articles/expensify-classic/workspace-and-domain-settings/Tags.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/Tags.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/Tags.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Currency.md b/docs/articles/expensify-classic/workspace-and-domain-settings/reports/Currency.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/reports/Currency.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/reports/Currency.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Report-Fields-And-Titles.md b/docs/articles/expensify-classic/workspace-and-domain-settings/reports/Report-Fields-And-Titles.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/reports/Report-Fields-And-Titles.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/reports/Report-Fields-And-Titles.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/reports/Scheduled-Submit.md b/docs/articles/expensify-classic/workspace-and-domain-settings/reports/Scheduled-Submit.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/reports/Scheduled-Submit.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/reports/Scheduled-Submit.md diff --git a/docs/articles/expensify-classic/policy-and-domain-settings/tax-tracking.md b/docs/articles/expensify-classic/workspace-and-domain-settings/tax-tracking.md similarity index 100% rename from docs/articles/expensify-classic/policy-and-domain-settings/tax-tracking.md rename to docs/articles/expensify-classic/workspace-and-domain-settings/tax-tracking.md diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md b/docs/articles/new-expensify/expensify-partner-program/Coming-Soon.md similarity index 67% rename from docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md rename to docs/articles/new-expensify/expensify-partner-program/Coming-Soon.md index 3ee1c8656b4b..6b85bb0364b5 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/TrainLine.md +++ b/docs/articles/new-expensify/expensify-partner-program/Coming-Soon.md @@ -2,4 +2,3 @@ title: Coming Soon description: Coming Soon --- -## Resource Coming Soon! diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md index 996d7896502f..17c7a60b8e5a 100644 --- a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Admins.md @@ -4,16 +4,16 @@ description: Best Practices for Admins settings up Expensify Chat redirect_from: articles/other/Expensify-Chat-For-Admins/ --- -## Overview +# Overview Expensify Chat is an incredible way to build a community and foster long-term relationships between event producers and attendees, or attendees with each other. Admins are a huge factor in the success of the connections built in Expensify Chat during the events, as they are generally the drivers of the conference schedule, and help ensure safety and respect is upheld by all attendees both on and offline. -## Getting Started +# Getting Started We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your session attendees: - [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) - [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) - [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) -## Admin Best Practices +# Admin Best Practices In order to get the most out of Expensify Chat, we created a list of best practices for admins to review in order to use the tool to its fullest capabilities. **During the conference:** diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md index 20e15aaa6c72..30eeb4158902 100644 --- a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Attendees.md @@ -4,19 +4,19 @@ description: Best Practices for Conference Attendees redirect_from: articles/other/Expensify-Chat-For-Conference-Attendees/ --- -## Overview +# Overview Expensify Chat is the best way to meet and network with other event attendees. No more hunting down your contacts by walking the floor or trying to find someone in crowds at a party. Instead, you can use Expensify Chat to network and collaborate with others throughout the conference. To help get you set up for a great event, we’ve created a guide to help you get the most out of using Expensify Chat at the event you’re attending. -## Getting Started +# Getting Started We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your fellow attendees: - [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) - [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) - [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) -## Chat Best Practices +# Chat Best Practices To get the most out of your experience at your conference and engage people in a meaningful conversation that will fulfill your goals instead of turning people off, here are some tips on what to do and not to do as an event attendee using Expensify Chat: **Do:** diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md index 3e19cf6fe26a..652fc2ee4d2b 100644 --- a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-For-Conference-Speakers.md @@ -4,17 +4,17 @@ description: Best Practices for Conference Speakers redirect_from: articles/other/Expensify-Chat-For-Conference-Speakers/ --- -## Overview +# Overview Are you a speaker at an event? Great! We're delighted to provide you with an extraordinary opportunity to connect with your session attendees using Expensify Chat — before, during, and after the event. Expensify Chat offers a powerful platform for introducing yourself and your topic, fostering engaging discussions about your presentation, and maintaining the conversation with attendees even after your session is over. -## Getting Started +# Getting Started We’ve rounded up some resources to get you set up on Expensify Chat and ready to start connecting with your session attendees: - [How to get set up and start using Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-use-chat-in-expensify) - [How to format text in Expensify Chat](https://help.expensify.com/articles/other/Everything-About-Chat#how-to-format-text) - [How to flag content and/or users for moderation](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) -## Setting Up a Chatroom for Your Session: Checklist +# Setting Up a Chatroom for Your Session: Checklist To make the most of Expensify Chat for your session, here's a handy checklist: - Confirm that your session has an Expensify Chat room, and have the URL link ready to share with attendees in advance. - You can find the link by clicking on the avatar for your chatroom > “Share Code” > “Copy URL to dashboard” @@ -22,7 +22,7 @@ To make the most of Expensify Chat for your session, here's a handy checklist: - Consider having a session moderator with you on the day to assist with questions and discussions while you're presenting. - Include the QR code for your session's chat room in your presentation slides. Displaying it prominently on every slide ensures that attendees can easily join the chat throughout your presentation. -## Tips to Enhance Engagement Around Your Session +# Tips to Enhance Engagement Around Your Session By following these steps and utilizing Expensify Chat, you can elevate your session to promote valuable interactions with your audience, and leave a lasting impact beyond the conference. We can't wait to see your sessions thrive with the power of Expensify Chat! **Before the event:** diff --git a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md index a81aef2044a2..caeccd1920b1 100644 --- a/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md +++ b/docs/articles/new-expensify/getting-started/chat/Expensify-Chat-Playbook-For-Conferences.md @@ -3,10 +3,10 @@ title: Expensify Chat Playbook for Conferences description: Best practices for how to deploy Expensify Chat for your conference redirect_from: articles/playbooks/Expensify-Chat-Playbook-for-Conferences/ --- -## Overview +# Overview To help make setting up Expensify Chat for your event and your attendees super simple, we’ve created a guide for all of the technical setup details. -## Who you are +# Who you are As a conference organizer, you’re expected to amaze and inspire attendees. You want attendees to get to the right place on time, engage with the speakers, and create relationships with each other that last long after the conference is done. Enter Expensify Chat, a free feature that allows attendees to interact with organizers and other attendees in realtime. With Expensify Chat, you can: - Communicate logistics and key information @@ -21,20 +21,20 @@ Sounds good? Great! In order to ensure your team, your speakers, and your attend *Let’s get started!* -## Support +# Support Connect with your dedicated account manager in any new.expensify.com #admins room. Your account manager is excited to brainstorm the best ways to make the most out of your event and work through any questions you have about the setup steps below. We also have a number of [moderation tools](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) available to admins to help make sure your event is seamless, safe, and fun! -## Step by step instructions for setting up your conference on Expensify Chat +# Step by step instructions for setting up your conference on Expensify Chat Based on our experience running conferences atop Expensify Chat, we recommend the following simple steps: -### Step 1: Create your event workspace in Expensify +## Step 1: Create your event workspace in Expensify To create your event workspace in Expensify: 1. In [new.expensify.com](https://new.expensify.com): “+” > “New workspace” 1. Name the workspace (e.g. “ExpensiCon”) -### Step 2: Set up all the Expensify Chat rooms you want to feature at your event +## Step 2: Set up all the Expensify Chat rooms you want to feature at your event **Protip**: Your Expensify account manager can complete this step with you. Chat them in #admins on new.expensify.com to coordinate! To create a new chat room: @@ -54,7 +54,7 @@ For an easy-to-follow event, we recommend creating these chat rooms: **Protip** Check out our [moderation tools](https://help.expensify.com/articles/other/Everything-About-Chat#flagging-content-as-offensive) to help flag comments deemed to be spam, inconsiderate, intimidating, bullying, harassment, assault. On any comment just click the flag icon to moderate conversation. -### Step 3: Add chat room QR codes to the applicable session slide deck +## Step 3: Add chat room QR codes to the applicable session slide deck Gather QR codes: 1. Go to [new.expensify.com](https://new.expensify.com) 1. Click into a room and click the room name or avatar in the top header @@ -63,7 +63,7 @@ Gather QR codes: Add the QR code to every slide so that if folks forget to scan the QR code at the beginning of the presentation, they can still join the discussion. -### Step 4: Plan out your messaging and cadence before the event begins +## Step 4: Plan out your messaging and cadence before the event begins Expensify Chat is a great place to provide updates leading up to your event -- share news, get folks excited about speakers, and let attendees know of crucial event information like recommended attire, travel info, and more. For example, you might consider: **Prep your announcements:** @@ -80,15 +80,15 @@ Expensify Chat is a great place to provide updates leading up to your event -- s **Protip**: Your account manager can help you create this document, and would be happy to send each message at the appointed time for you. -### Step 5: Share Expensify Chat How-To Resources with Speakers, Attendees, Admins +## Step 5: Share Expensify Chat How-To Resources with Speakers, Attendees, Admins We’ve created a few helpful best practice docs for your speakers, admins, and attendees to help navigate using Expensify Chat at your event. Feel free to share the links below with them! - [Expensify Chat for Conference Attendees](https://help.expensify.com/articles/other/Expensify-Chat-For-Conference-Attendees) - [Expensify Chat for Conference Speakers](https://help.expensify.com/articles/other/Expensify-Chat-For-Conference-Speakers) - [Expensify Chat for Admins](https://help.expensify.com/articles/other/Expensify-Chat-For-Admins) -### Step 6: Follow up with attendees after the event +## Step 6: Follow up with attendees after the event Continue the connections by using Expensify Chat to keep your conference community connected. Encourage attendees to share photos, their favorite memories, funny stories, and more. -## Conclusion +# Conclusion Once you have completed the above steps you are ready to host your conference on Expensify Chat! Let your account manager know any questions you have over in your [new.expensify.com](https://new.expensify.com) #admins room and start driving activity in your Expensify Chat rooms. Once you’ve reviewed this doc you should have the foundations in place, so a great next step is to start training your speakers on how to use Expensify Chat for their sessions. Coordinate with your account manager to make sure everything goes smoothly! diff --git a/docs/articles/expensify-classic/integrations/travel-integrations/Grab.md b/docs/articles/new-expensify/insights-and-custom-reporting/Coming-Soon.md similarity index 67% rename from docs/articles/expensify-classic/integrations/travel-integrations/Grab.md rename to docs/articles/new-expensify/insights-and-custom-reporting/Coming-Soon.md index 3ee1c8656b4b..6b85bb0364b5 100644 --- a/docs/articles/expensify-classic/integrations/travel-integrations/Grab.md +++ b/docs/articles/new-expensify/insights-and-custom-reporting/Coming-Soon.md @@ -2,4 +2,3 @@ title: Coming Soon description: Coming Soon --- -## Resource Coming Soon! diff --git a/docs/articles/new-expensify/integrations/accounting-integrations/Xero.md b/docs/articles/new-expensify/integrations/accounting-integrations/Xero.md deleted file mode 100644 index 45aec32fb708..000000000000 --- a/docs/articles/new-expensify/integrations/accounting-integrations/Xero.md +++ /dev/null @@ -1,261 +0,0 @@ ---- -title: The Xero Integration -description: Everything you need to know about Expensify's direct integration with Xero ---- - - -# About - -The integration enables seamless import of expense accounts into Expensify and sends expense reports back to Xero as purchasing bills awaiting payment or "spend money" bank transactions. - -# How-to Connect to Xero - -## Prerequisites - -You must be a Workspace Admin in Expensify using a Collect or Control Workspace to connect your Xero account to Expensify. - -## Connect Expensify and Xero - -1. Let's get started by heading over to your Settings. You can find it by following this path: *Settings > Workspaces > Groups > [Workspace Name] > Connections > Xero.* -2. To connect Expensify to Xero, click on the "Connect to Xero” button, then choose "Create a new Xero connection." -3. Next, enter your Xero login details. After that, you'll need to select the Xero organization you want to link with Expensify. Remember, you can connect one organization for each Workspace. - -One important note: Starting in September 2021, there's a chance for Cashbook and Ledger-type organizations in Xero. Apps like Expensify won't be able to create invoices and bills for these accounts using the Xero API. So, if you're using a Cashbook or Ledger Xero account, please be aware that this might affect your Expensify integration. - -# How to Configure Export Settings for Xero - -When you integrate Expensify with Xero you gain control over several settings that determine how your reports will be displayed in Xero. To manage these settings simply follow this path: *Settings > Workspaces > Group > [Workspace Name] > Connections > Accounting Integrations > Xero > Configure > Export*. This is where you can fine-tune how your reports appear on the Xero side, making your expense management a breeze! - -## Xero Organization - -When you have multiple organizations set up in Xero you can choose which one you'd like to connect. Here are some essential things to keep in mind: - -1. Organization Selection: You'll see this option only if you have multiple organizations configured in Xero. -2. One Workspace, One Organization: Each Workspace can connect to just one organization at a time. It's a one-to-one connection. -3. Adding New Organizations: If you create a new organization in Xero after your initial connection, you'll need to disconnect and then reconnect it to Xero. Don't forget to take a screenshot of your current settings by clicking on "Configure" and checking the Export, Coding, and Advanced tabs. This way, you can easily set everything up again. - -Now you can seamlessly manage your connections with Xero while staying in control of your configurations! - -## Preferred Exporter - -Any Workspace admin can export to Xero, but only the preferred exporter will see reports that are ready for export in their Home. - -## Reimbursable Expenses - -Export to Xero as bills awaiting payment with the following additional settings: - -- Bill date — the bill is posted on the last day of the month in which expenses were incurred. - -To view the bills in Xero, navigate to *Business > Purchase Overview > Awaiting Payments*. Bills will be payable to the individual who created and reported the expense. - -## Non-reimbursable Expenses - -When you export non-reimbursable expenses, like company card transactions, to Xero they'll show up as bank transactions. Each expense is neatly listed as a separate line item in the bank account of your choice. Plus the transaction date matches the date on your bank statement for seamless tracking. - -To check out these expenses in Xero please follow these steps: - -1. Head over to your Dashboard. -2. Select your company card. -3. Locate the specific expense you're interested in. - -If you're managing company cards centrally, you can export expenses from each card to a designated account in Xero using Domains. This way, you have complete control and clarity over your company's finances! - -# How to Configure Coding for Xero - -The Coding tab in Expensify is where you configure Xero information to ensure accurate expense coding by your employees. Here's how you can access these settings: - -1. Navigate to Settings. -2. Go to Workspace within your specified group (Workspace Name). -3. Click on Connections, and then hit the Configure button. -4. Now, select the Coding tab. - -## Categories - -Xero expense accounts and those marked "Show In Expense Claims" will be automatically imported into Expensify as Categories. - -To manage these categories, follow these steps: - -1. After connecting, go to *Settings > Workspaces > Groups > [Workspace Name] > Categories*. -2. You can enable/disable categories using the checkbox. -3. For specific category rules (like default tax rate, maximum amount, receipts required, comments, and comment hints), click the settings cog. -4. Note that each expense must have a category selected for it to export to Xero, and these categories need to be imported from Xero; manual creation isn't an option within Workspace settings. - -## Tracking Categories - -1. If you use Tracking categories in Xero, you can import them into Expensify as Tags, Report Fields, or the Xero contact default. -- Tags apply a tracking category per expense. -- Report Field applies a tracking category to the entire report. -- Xero contact default applies the default tracking category set for the submitter in Xero. - -## Tax - -Looking to track tax in Expensify? Make sure that you have tax rates enabled in Xero and we will automatically grab those rates from Xero to allow your employees to categorize expenses with the appropriate tax rate. As an admin, you have the ability to set a default rate and also hide rates that are not applicable to the Workspace members. - -Tax tracking allows you to apply a tax rate and tax amount to each expense. -1. To set this up, enable Tax tracking in your Xero configuration. -2. After connecting, go to *Settings > Workspaces > Groups > [Workspace Name] > Tax to manage imported taxes from Xero.* -3. You can enable/disable taxes and set default tax rates for both Workspace currency expenses and foreign currency expenses. - -## Billable Expenses - -If you bill expenses to your customers, you can track and invoice them using Expensify and Xero. - -1. When enabled, Xero customer contacts are imported into Expensify as Tags for expense tracking. -- Note: In Xero, a Contact isn't a 'Customer' until they've had a bill raised against them. If you don't see your Customer imported as a tag, try raising a dummy invoice in Xero and then deleting/voiding it. -2. After exporting to Xero, tagged billable expenses can be included on a sales invoice to your customer. - -Please ensure that you meet the following requirements for expenses to be placed on a sales invoice: -1. Billable Expenses must be enabled in the Xero configuration settings. -2. The expense must be marked as billable. -3. The expense must be tagged with a customer. - -These steps should help you seamlessly manage your Xero integration within Expensify. - -# How to Configure Xero’s Advanced Settings - -If you've already set up your integration, but want to make adjustments, simply follow these steps: - -1. Go to Settings. -2. Then, navigate to Workspaces within your designated group [Workspace Name]. -3. Click on Connections, and next, hit the Configure button. - -From there, you can dive into the "Advanced" tab to make any additional tweaks. - -## Auto Sync - -For non-reimbursable reports: Once a report has completed the approval workflow in Expensify, we'll automatically queue it for export to Xero. - -But, if you've added a business bank account for ACH reimbursement, any reimbursable expenses will be sent to Xero automatically when the report is marked as reimbursed or enabled for reimbursement. - -### Controlling Newly Imported Categories: - -You can decide how newly imported categories behave in Expensify: - -1. Enabling or disabling this control determines the status of new categories imported from Xero to Expensify. Enabled categories are visible for employees when they categorize expenses, while disabled categories remain hidden. - -These settings give you the flexibility to manage your expenses and Workspace in the way that best suits your needs! - -## Sync Reimbursed Reports - -This nifty setting lets you synchronize the status of your reports between Expensify and Xero. Utilizing this setting will make sure that there is no confusion or possibility that a reimbursable report is paid out twice by mistake or that a non-reimbursable report is double entered throwing off month-end reconciliation. Here's how it works: - -1. When you reimburse a report via ACH direct deposit within Expensify, the purchase bill will automatically be marked as paid in Xero, and Expensify will note it as reimbursed. -2. Don't forget to pick the Xero account where the corresponding bill payment should be recorded. -3. It's a simple way to keep everything in sync, especially when you're awaiting payment. - -# Deep Dive - -## An Automatic Export Fails - -Sometimes, reports may encounter issues during automatic export to Xero. Not to worry, though! Here's what happens: - -1. The Technical Contact, your go-to person for technical matters, will receive an email explaining the problem. -2. You'll also find specific error messages at the bottom of the report. -3. To get things back on track, the report will be placed in the preferred exporter’s Home. They can review it and resolve any issues. - -## Consider Enforcing Expense Workspace Workflows: - -For added control, you can adjust your Workspace settings to strictly enforce expense Workspace. This way, you guarantee that your Workspace’s workflow is always followed. By default this flow is in place, but employees can modify the person they submit their reports to if it's not strictly enforced. - -## Customize Purchase Bill Status (Optional): - -You have the flexibility to set the status of your purchase bills just the way you want. Choose from the following options: - -1. Draft: Keep bills in a draft state until you're ready to finalize them. -2. Awaiting Approval: If you need approval before processing bills, this option is here for you. - -## Multi-Currency - -### Handling Multi-Currency in Xero - -When dealing with multi-currency transactions in Xero and exporting reimbursable expenses from Expensify here's what you need to know: - -1. The bill created in Xero will adopt the output currency set in your Expensify Workspace, provided that it's enabled in Xero. -2. Your general ledger reports will automatically convert to your home currency in Xero, leveraging the currency exchange rates defined in your Xero settings. It ensures everything aligns seamlessly. - -Now, for non-reimbursable expenses, things work slightly differently: - -1. Bank transactions will use the currency specified in your bank account in Xero, regardless of the currency used in Expensify. -2. If these currencies don't match, no worries! We apply a 1:1 exchange rate to make things smooth. To ensure a hassle-free experience, just ensure that the output currency in Expensify matches the currency specified in your Xero bank account. - -## Tax - -### Enabling Tax Tracking for Seamless Integration: - -To simplify tax tracking, enable it in your Xero configuration. This action will automatically bring all your Xero tax settings into Expensify, turning them into usable Taxes. - -### After connecting your Xero account with Expensify: - -1. Head to Settings. -2. Navigate to Workspaces within your specific group [Workspace Name]. -3. Click on Tax to view the taxes that have been imported from Xero. - -Now, here's where you can take control: - -1. Use the enable/disable button to choose which taxes your employees can apply to their expenses. Customize it to fit your needs. -2. You can set a default tax rate for expenses in your Workspace currency. Additionally, if you deal with foreign currency expenses, you have the option to set another default tax (including exempt) that will automatically apply to all new expenses in foreign currencies. - -This setup streamlines your tax management, making it effortless for your team to handle taxes on their expenses. - -## Export Invoices to Xero - -You can effortlessly export your invoices from Expensify to Xero and even attribute them to the right Customer. Plus, when you mark an invoice as paid in Expensify, the same status will smoothly transfer to Xero and vice versa, keeping your invoice tracking hassle-free. Let's dive in: - -### Setting up Invoice Export to Xero: - -1. Navigate to Settings. -2. Go to Workspaces within your designated group [Workspace Name]. -3. Click on Connections, then select Configuration. -4. Now, click on the Advanced tab. - -### Selecting Your Xero Invoice Collection Account: - -1. Scroll down until you find "Xero invoice collection account." You'll see a dropdown list of your available Accounts Receivable accounts imported from Xero. -2. Simply choose the account where you'd like your invoices to be exported. - -Pro Tip: If you don't see any accounts in the dropdown, try syncing your Xero connection. To do this, go back to the Connections page and hit "Sync Now." - -### Exporting an Invoice to Xero: - -Invoices will automatically make their way to Xero when they're in the Processing or Paid state. This ensures consistent tracking of unpaid and paid invoices. However, if you have Auto Sync disabled, you'll need to manually export your invoices along with your expense reports. Here's how: - -1. Head to your Reports page. -2. Use the filters to locate the invoices you want to export. -3. Select the invoices you wish to export. -4. Click Export to > Xero on the top right-hand side. - -### Matching Customers and Emails: - -When exporting to Xero, we match the recipient's email address with a customer record in Xero. So, make sure each customer in Xero has their email listed in their profile. -If we can't find a match, we'll create a new customer record in Xero. - -### Updating Invoice Status: - -1. When you mark an invoice as Paid in Expensify, this status will automatically reflect in Xero. -2. Similarly, if you mark an invoice as Paid in Xero, it will update automatically in Expensify. -3. The payment will be recorded in the Collection account you've chosen in your Advanced Settings Configuration. - -And that's it! You've successfully set up and managed your invoice exports to Xero, making your tracking smooth and efficient. - -# FAQ - -## Will receipt images be exported to Xero? - -Yes! The receipt images will be exported to Xero. To see them in Xero click the 'paper' icon in the upper right corner of the expense details and view a PDF of the Expensify report including the receipt image. - -## How does Auto Sync work if your workspace was initially connected to Xero with Auto Sync disabled? - -You can safely switch it on without affecting existing reports that haven't been exported. - -## How does Auto Sync work if a report has already been exported to Xero and reimbursed through ACH or marked as reimbursed in Expensify? - -It will be automatically marked as paid in Xero during the next sync. You may either manually update by clicking Sync Now in the Connections tab or Expensify does this on your behalf overnight every day! - -## How does Auto Sync work if a report has been exported to Xero and marked as paid in Xero? - -It will be automatically marked as reimbursed in Expensify during the next sync. If you need it updated immediately please go to the Connections tab and click Sync Now or if you can wait just let Expensify do it for you overnight. - -## How does Auto Sync work if a report has been exported to Xero and marked as paid in Xero? - -Reports that haven't been exported to Xero won't be sent automatically. ---> diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_1.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_1.png new file mode 100644 index 000000000000..d4e73beb16b3 Binary files /dev/null and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_1.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_2.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_2.png new file mode 100644 index 000000000000..45956a586d98 Binary files /dev/null and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_2.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_3.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_3.png new file mode 100644 index 000000000000..32aae12d3687 Binary files /dev/null and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_3.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_4.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_4.png new file mode 100644 index 000000000000..ccd9335025bf Binary files /dev/null and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_4.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_5.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_5.png new file mode 100644 index 000000000000..5363935f0ab5 Binary files /dev/null and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_5.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_6.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_6.png new file mode 100644 index 000000000000..739446de8383 Binary files /dev/null and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_6.png differ diff --git a/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_7.png b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_7.png new file mode 100644 index 000000000000..21a1d3416858 Binary files /dev/null and b/docs/assets/images/ManagingEmployeesAndReports_ApprovalWorkflows_7.png differ diff --git a/docs/assets/images/handshake.svg b/docs/assets/images/handshake.svg new file mode 100644 index 000000000000..04872bd3a88b --- /dev/null +++ b/docs/assets/images/handshake.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/expensify-classic/hubs/expensify-partner-program/index.html b/docs/expensify-classic/hubs/expensify-partner-program/index.html new file mode 100644 index 000000000000..c0a192c6e916 --- /dev/null +++ b/docs/expensify-classic/hubs/expensify-partner-program/index.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expensify Partner Program +--- + +{% include hub.html %} diff --git a/docs/expensify-classic/hubs/exports/index.html b/docs/expensify-classic/hubs/insights-and-custom-reporting/index.html similarity index 100% rename from docs/expensify-classic/hubs/exports/index.html rename to docs/expensify-classic/hubs/insights-and-custom-reporting/index.html diff --git a/docs/expensify-classic/hubs/policy-and-domain-settings/index.html b/docs/expensify-classic/hubs/workspace-and-domain-settings/index.html similarity index 100% rename from docs/expensify-classic/hubs/policy-and-domain-settings/index.html rename to docs/expensify-classic/hubs/workspace-and-domain-settings/index.html diff --git a/docs/expensify-classic/hubs/policy-and-domain-settings/reports.html b/docs/expensify-classic/hubs/workspace-and-domain-settings/reports.html similarity index 100% rename from docs/expensify-classic/hubs/policy-and-domain-settings/reports.html rename to docs/expensify-classic/hubs/workspace-and-domain-settings/reports.html diff --git a/docs/new-expensify/hubs/expensify-partner-program/index.html b/docs/new-expensify/hubs/expensify-partner-program/index.html new file mode 100644 index 000000000000..c0a192c6e916 --- /dev/null +++ b/docs/new-expensify/hubs/expensify-partner-program/index.html @@ -0,0 +1,6 @@ +--- +layout: default +title: Expensify Partner Program +--- + +{% include hub.html %} diff --git a/docs/new-expensify/hubs/exports/index.html b/docs/new-expensify/hubs/insights-and-custom-reporting/index.html similarity index 100% rename from docs/new-expensify/hubs/exports/index.html rename to docs/new-expensify/hubs/insights-and-custom-reporting/index.html diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f022cf3452a8..95a9a26df7f6 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.85 + 1.3.87 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.85.0 + 1.3.87.8 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index db42a9dc6d55..d41b75440036 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.85 + 1.3.87 CFBundleSignature ???? CFBundleVersion - 1.3.85.0 + 1.3.87.8 diff --git a/package-lock.json b/package-lock.json index 922c2a158654..65c2be0529e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.85-0", + "version": "1.3.87-8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.85-0", + "version": "1.3.87-8", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -168,7 +168,7 @@ "@types/react-dom": "^18.2.4", "@types/react-pdf": "^5.7.2", "@types/react-test-renderer": "^18.0.0", - "@types/semver": "^7.5.0", + "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", "@types/underscore": "^1.11.5", "@typescript-eslint/eslint-plugin": "^6.2.1", @@ -19876,9 +19876,9 @@ "license": "MIT" }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, "node_modules/@types/serve-index": { @@ -67255,9 +67255,9 @@ "integrity": "sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ==" }, "@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, "@types/serve-index": { diff --git a/package.json b/package.json index cd5c6034161a..aca3dc508c41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.85-0", + "version": "1.3.87-8", "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.", @@ -213,7 +213,7 @@ "@types/react-dom": "^18.2.4", "@types/react-pdf": "^5.7.2", "@types/react-test-renderer": "^18.0.0", - "@types/semver": "^7.5.0", + "@types/semver": "^7.5.4", "@types/setimmediate": "^1.0.2", "@types/underscore": "^1.11.5", "@typescript-eslint/eslint-plugin": "^6.2.1", diff --git a/src/CONST.ts b/src/CONST.ts index 501bc2e3aa19..048c2dee5bab 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -79,6 +79,10 @@ const CONST = { RESERVED_FIRST_NAMES: ['Expensify', 'Concierge'], }, + LEGAL_NAME: { + MAX_LENGTH: 40, + }, + PULL_REQUEST_NUMBER, MERCHANT_NAME_MAX_LENGTH: 255, @@ -517,6 +521,8 @@ const CONST = { DELETE_TAG: 'POLICYCHANGELOG_DELETE_TAG', IMPORT_CUSTOM_UNIT_RATES: 'POLICYCHANGELOG_IMPORT_CUSTOM_UNIT_RATES', IMPORT_TAGS: 'POLICYCHANGELOG_IMPORT_TAGS', + 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', @@ -551,6 +557,11 @@ const CONST = { UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED', UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE', }, + ROOMCHANGELOG: { + INVITE_TO_ROOM: 'INVITETOROOM', + REMOVE_FROM_ROOM: 'REMOVEFROMROOM', + JOIN_ROOM: 'JOINROOM', + }, }, }, ARCHIVE_REASON: { @@ -1062,6 +1073,12 @@ const CONST = { }, }, + KYC_WALL_SOURCE: { + REPORT: 'REPORT', // The user attempted to pay a money request + ENABLE_WALLET: 'ENABLE_WALLET', // The user clicked on the `Enable wallet` button on the Wallet page + TRANSFER_BALANCE: 'TRANSFER_BALANCE', // The user attempted to transfer their wallet balance to their bank account or debit card + }, + OS: { WINDOWS: 'Windows', MAC_OS: PLATFORM_OS_MACOS, @@ -1099,7 +1116,7 @@ const CONST = { EXPENSIFY: 'Expensify', VBBA: 'ACH', }, - MONEY_REQUEST_TYPE: { + TYPE: { SEND: 'send', SPLIT: 'split', REQUEST: 'request', @@ -1241,6 +1258,7 @@ const CONST = { NONE: 'none', }, STATE: { + STATE_NOT_ISSUED: 2, OPEN: 3, NOT_ACTIVATED: 4, STATE_DEACTIVATED: 5, @@ -1298,7 +1316,7 @@ const CONST = { SPECIAL_CHAR: /[,/?"{}[\]()&^%;`$=#<>!*]/g, get SPECIAL_CHAR_OR_EMOJI() { - return new RegExp(`[_~\\n\\s]|${this.SPECIAL_CHAR.source}|${this.EMOJI.source}`, 'gu'); + return new RegExp(`[~\\n\\s]|(_\\b(?!$))|${this.SPECIAL_CHAR.source}|${this.EMOJI.source}`, 'gu'); }, get SPACE_OR_EMOJI() { @@ -1424,6 +1442,7 @@ const CONST = { REPORT_DETAILS_MENU_ITEM: { SHARE_CODE: 'shareCode', MEMBERS: 'member', + INVITE: 'invite', SETTINGS: 'settings', LEAVE_ROOM: 'leaveRoom', WELCOME_MESSAGE: 'welcomeMessage', diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index f60506ab7245..68b3bd047ad8 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1,4 +1,5 @@ import {ValueOf} from 'type-fest'; +import {OnyxEntry} from 'react-native-onyx/lib/types'; import DeepValueOf from './types/utils/DeepValueOf'; import * as OnyxTypes from './types/onyx'; import CONST from './CONST'; @@ -259,6 +260,7 @@ const ONYXKEYS = { TRANSACTION: 'transactions_', SPLIT_TRANSACTION_DRAFT: 'splitTransactionDraft_', PRIVATE_NOTES_DRAFT: 'privateNotesDraft_', + NEXT_STEP: 'reportNextStep_', // Manual request tab selector SELECTED_TAB: 'selectedTab_', @@ -298,6 +300,7 @@ const ONYXKEYS = { PRIVATE_NOTES_FORM: 'privateNotesForm', I_KNOW_A_TEACHER_FORM: 'iKnowTeacherForm', INTRO_SCHOOL_PRINCIPAL_FORM: 'introSchoolPrincipalForm', + REPORT_PHYSICAL_CARD_FORM: 'requestPhysicalCardForm', REPORT_VIRTUAL_CARD_FRAUD: 'reportVirtualCardFraudForm', }, } as const; @@ -388,7 +391,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record; [ONYXKEYS.COLLECTION.REPORT]: OnyxTypes.Report; [ONYXKEYS.COLLECTION.REPORT_METADATA]: OnyxTypes.ReportMetadata; - [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportAction; + [ONYXKEYS.COLLECTION.REPORT_ACTIONS]: OnyxTypes.ReportActions; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: string; [ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions; [ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string; @@ -429,7 +432,10 @@ type OnyxValues = { [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_AFTER_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM]: OnyxTypes.Form; }; +type OnyxKeyValue = OnyxEntry; + export default ONYXKEYS; -export type {OnyxKey, OnyxCollectionKey, OnyxValues}; +export type {OnyxKey, OnyxCollectionKey, OnyxValues, OnyxKeyValue}; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b7896225557d..b5ceb8fc557d 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -90,6 +90,10 @@ export default { }, SETTINGS_WALLET_TRANSFER_BALANCE: 'settings/wallet/transfer-balance', SETTINGS_WALLET_CHOOSE_TRANSFER_ACCOUNT: 'settings/wallet/choose-transfer-account', + SETTINGS_WALLET_REPORT_CARD_LOST_OR_DAMAGED: { + route: '/settings/wallet/card/:domain/report-card-lost-or-damaged', + getRoute: (domain: string) => `/settings/wallet/card/${domain}/report-card-lost-or-damaged`, + }, SETTINGS_WALLET_CARD_ACTIVATE: { route: 'settings/wallet/card/:domain/activate', getRoute: (domain: string) => `settings/wallet/card/${domain}/activate`, @@ -209,8 +213,16 @@ export default { route: 'r/:reportID/notes/:accountID/edit', getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit`, }, + ROOM_MEMBERS: { + route: 'r/:reportID/members', + getRoute: (reportID: string) => `r/${reportID}/members`, + }, + ROOM_INVITE: { + route: 'r/:reportID/invite', + getRoute: (reportID: string) => `r/${reportID}/invite`, + }, - // To see the available iouType, please refer to CONST.IOU.MONEY_REQUEST_TYPE + // To see the available iouType, please refer to CONST.IOU.TYPE MONEY_REQUEST: { route: ':iouType/new/:reportID?', getRoute: (iouType: string, reportID = '') => `${iouType}/new/${reportID}`, @@ -292,6 +304,11 @@ export default { I_AM_A_TEACHER: 'teachersunite/i-am-a-teacher', INTRO_SCHOOL_PRINCIPAL: 'teachersunite/intro-school-principal', + ERECEIPT: { + route: 'eReceipt/:transactionID', + getRoute: (transactionID: string) => `eReceipt/${transactionID}`, + }, + WORKSPACE_NEW: 'workspace/new', WORKSPACE_NEW_ROOM: 'workspace/new-room', WORKSPACE_INITIAL: { diff --git a/src/components/AnimatedStep/AnimatedStepProvider.js b/src/components/AnimatedStep/AnimatedStepProvider.js index 280fbd1a2776..86d40b5bddeb 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.js +++ b/src/components/AnimatedStep/AnimatedStepProvider.js @@ -1,4 +1,4 @@ -import React, {useState} from 'react'; +import React, {useMemo, useState} from 'react'; import PropTypes from 'prop-types'; import AnimatedStepContext from './AnimatedStepContext'; import CONST from '../../CONST'; @@ -9,8 +9,9 @@ const propTypes = { function AnimatedStepProvider({children}) { const [animationDirection, setAnimationDirection] = useState(CONST.ANIMATION_DIRECTION.IN); + const contextValue = useMemo(() => ({animationDirection, setAnimationDirection}), [animationDirection, setAnimationDirection]); - return {children}; + return {children}; } AnimatedStepProvider.propTypes = propTypes; diff --git a/src/components/Attachments/AttachmentCarousel/Pager/index.js b/src/components/Attachments/AttachmentCarousel/Pager/index.js index 9779963dfc4a..d10a5abad6b7 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/index.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/index.js @@ -1,5 +1,5 @@ /* eslint-disable es/no-optional-chaining */ -import React, {useRef, useState, useImperativeHandle} from 'react'; +import React, {useRef, useState, useImperativeHandle, useMemo} from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import {GestureHandlerRootView, createNativeWrapper} from 'react-native-gesture-handler'; @@ -126,22 +126,25 @@ function AttachmentCarouselPager({ scrollEnabled: shouldPagerScroll.value, })); + const contextValue = useMemo( + () => ({ + canvasWidth: containerWidth, + canvasHeight: containerHeight, + isScrolling, + pagerRef, + shouldPagerScroll, + onPinchGestureChange, + onTap, + onSwipe, + onSwipeSuccess, + onSwipeDown, + }), + [containerWidth, containerHeight, isScrolling, pagerRef, shouldPagerScroll, onPinchGestureChange, onTap, onSwipe, onSwipeSuccess, onSwipeDown], + ); + return ( - + + + + + + ); + } + // Check both source and file.name since PDFs dragged into the text field // will appear with a source that is a blob if ((_.isString(source) && Str.isPDF(source)) || (file && Str.isPDF(file.name || translate('attachmentView.unknownFilename')))) { diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index 13abf057e4b1..e7f68e7011fc 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -63,7 +63,7 @@ function CategoryPicker({selectedCategory, policyCategories, policyRecentlyUsedC return categoryInitialFocusedIndex; }, [selectedCategory, searchValue, isCategoriesCountBelowThreshold, sections]); - const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, searchValue); + const headerMessage = OptionsListUtils.getHeaderMessageForNonUserList(lodashGet(sections, '[0].data.length', 0) > 0, searchValue); const shouldShowTextInput = !isCategoriesCountBelowThreshold; return ( diff --git a/src/components/ComposeProviders.js b/src/components/ComposeProviders.js deleted file mode 100644 index edcc0a917c51..000000000000 --- a/src/components/ComposeProviders.js +++ /dev/null @@ -1,29 +0,0 @@ -import _ from 'underscore'; -import React from 'react'; -import PropTypes from 'prop-types'; - -const propTypes = { - /** Provider components go here */ - components: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.object, PropTypes.func])).isRequired, - - /** Rendered child component */ - children: PropTypes.node.isRequired, -}; - -function ComposeProviders(props) { - return ( - <> - {_.reduceRight( - props.components, - (memo, Component) => ( - {memo} - ), - props.children, - )} - - ); -} - -ComposeProviders.propTypes = propTypes; -ComposeProviders.displayName = 'ComposeProviders'; -export default ComposeProviders; diff --git a/src/components/ComposeProviders.tsx b/src/components/ComposeProviders.tsx new file mode 100644 index 000000000000..bff36db25533 --- /dev/null +++ b/src/components/ComposeProviders.tsx @@ -0,0 +1,14 @@ +import React, {ComponentType, ReactNode} from 'react'; +import ChildrenProps from '../types/utils/ChildrenProps'; + +type ComposeProvidersProps = ChildrenProps & { + /** Provider components go here */ + components: Array>; +}; + +function ComposeProviders(props: ComposeProvidersProps): ReactNode { + return props.components.reduceRight((memo, Component) => {memo}, props.children); +} + +ComposeProviders.displayName = 'ComposeProviders'; +export default ComposeProviders; diff --git a/src/components/ConfirmedRoute.js b/src/components/ConfirmedRoute.js index dab30e60ca55..8544de62eeb9 100644 --- a/src/components/ConfirmedRoute.js +++ b/src/components/ConfirmedRoute.js @@ -97,7 +97,7 @@ function ConfirmedRoute({mapboxAccessToken, transaction}) { location: lodashGet(waypointMarkers, [0, 'coordinate'], CONST.MAPBOX.DEFAULT_COORDINATE), }} directionCoordinates={coordinates} - style={styles.mapView} + style={[styles.mapView, styles.br4]} waypoints={waypointMarkers} styleURL={CONST.MAPBOX.STYLE_URL} /> diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index 7c7837b8413d..f866de0b885e 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -31,7 +31,7 @@ const defaultProps = { function DistanceEReceipt({transaction}) { const {translate} = useLocalize(); const {isOffline} = useNetwork(); - const {thumbnail} = TransactionUtils.hasReceipt(transaction) ? ReceiptUtils.getThumbnailAndImageURIs(transaction.receipt.source, transaction.filename) : {}; + const {thumbnail} = TransactionUtils.hasReceipt(transaction) ? ReceiptUtils.getThumbnailAndImageURIs(transaction) : {}; const {amount: transactionAmount, currency: transactionCurrency, merchant: transactionMerchant, created: transactionDate} = ReportUtils.getTransactionDetails(transaction); const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : translate('common.tbd'); const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); diff --git a/src/components/DistanceRequest/DistanceRequestFooter.js b/src/components/DistanceRequest/DistanceRequestFooter.js index c96adfee9ba0..d8214774d2c1 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.js +++ b/src/components/DistanceRequest/DistanceRequestFooter.js @@ -115,18 +115,19 @@ function DistanceRequestFooter({waypoints, transaction, mapboxAccessToken, navig pitchEnabled={false} initialState={{ zoom: CONST.MAPBOX.DEFAULT_ZOOM, - location: CONST.MAPBOX.DEFAULT_COORDINATE, + location: lodashGet(waypointMarkers, [0, 'coordinate'], CONST.MAPBOX.DEFAULT_COORDINATE), }} directionCoordinates={lodashGet(transaction, 'routes.route0.geometry.coordinates', [])} - style={styles.mapView} + style={[styles.mapView, styles.mapEditView]} waypoints={waypointMarkers} styleURL={CONST.MAPBOX.STYLE_URL} - overlayStyle={styles.m4} + overlayStyle={styles.mapEditView} /> ) : ( )} diff --git a/src/components/DistanceRequest/index.js b/src/components/DistanceRequest/index.js index 416fefc5af89..bd35678273ec 100644 --- a/src/components/DistanceRequest/index.js +++ b/src/components/DistanceRequest/index.js @@ -2,7 +2,6 @@ import React, {useCallback, useEffect, useMemo, useState, useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; -import lodashIsEmpty from 'lodash/isEmpty'; import PropTypes from 'prop-types'; import _ from 'underscore'; import ROUTES from '../../ROUTES'; @@ -169,8 +168,7 @@ function DistanceRequest({transactionID, report, transaction, route, isEditingRe const newWaypoints = {}; _.each(data, (waypoint, index) => { - const newWaypoint = lodashGet(waypoints, waypoint, {}); - newWaypoints[`waypoint${index}`] = lodashIsEmpty(newWaypoint) ? null : newWaypoint; + newWaypoints[`waypoint${index}`] = lodashGet(waypoints, waypoint, {}); }); setOptimisticWaypoints(newWaypoints); @@ -276,7 +274,4 @@ export default withOnyx({ transaction: { key: ({transactionID}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID || 0}`, }, - mapboxAccessToken: { - key: ONYXKEYS.MAPBOX_ACCESS_TOKEN, - }, })(DistanceRequest); diff --git a/src/components/DragAndDrop/Provider/index.js b/src/components/DragAndDrop/Provider/index.js index 6408f6dbfbfa..f76bf13c99fd 100644 --- a/src/components/DragAndDrop/Provider/index.js +++ b/src/components/DragAndDrop/Provider/index.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {useRef, useCallback, useEffect} from 'react'; +import React, {useRef, useCallback, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import {PortalHost} from '@gorhom/portal'; import Str from 'expensify-common/lib/str'; @@ -37,8 +37,9 @@ function DragAndDropProvider({children, isDisabled = false, setIsDraggingOver = setIsDraggingOver(isDraggingOver); }, [isDraggingOver, setIsDraggingOver]); + const contextValue = useMemo(() => ({isDraggingOver, setOnDropHandler, dropZoneID: dropZoneID.current}), [isDraggingOver, setOnDropHandler]); return ( - + (dropZone.current = e)} style={[styles.flex1, styles.w100, styles.h100]} diff --git a/src/components/DraggableList/index.native.tsx b/src/components/DraggableList/index.native.tsx index 9f180ba35b2e..e3b7558c1e21 100644 --- a/src/components/DraggableList/index.native.tsx +++ b/src/components/DraggableList/index.native.tsx @@ -2,11 +2,15 @@ import React from 'react'; import DraggableFlatList from 'react-native-draggable-flatlist'; import {FlatList} from 'react-native-gesture-handler'; import type {DraggableListProps} from './types'; +import styles from '../../styles/styles'; function DraggableList({renderClone, shouldUsePortal, ...viewProps}: DraggableListProps, ref: React.ForwardedRef>) { return ( diff --git a/src/components/DraggableList/index.tsx b/src/components/DraggableList/index.tsx index 674a95179e5d..ea9ac548e850 100644 --- a/src/components/DraggableList/index.tsx +++ b/src/components/DraggableList/index.tsx @@ -73,6 +73,7 @@ function DraggableList( - + {currency} diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js index 3023a9abf95c..0dc967d257d2 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js @@ -28,12 +28,18 @@ function EmojiPickerButtonDropdown(props) { const emojiPopoverAnchor = useRef(null); useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); - const onPress = () => + const onPress = () => { + if (EmojiPickerAction.isEmojiPickerVisible()) { + EmojiPickerAction.hideEmojiPicker(); + return; + } + EmojiPickerAction.showEmojiPicker(props.onModalHide, (emoji) => props.onInputChange(emoji), emojiPopoverAnchor.current, { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, shiftVertical: 4, }); + }; return ( @@ -44,7 +50,7 @@ function EmojiPickerButtonDropdown(props) { onPress={onPress} nativeID="emojiDropdownButton" accessibilityLabel="statusEmoji" - accessibilityRole="text" + accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON} > {({hovered, pressed}) => ( diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 3a7551a872e9..0d7826ff3783 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {View, FlatList} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; @@ -8,7 +8,7 @@ import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import styles from '../../../styles/styles'; import * as StyleUtils from '../../../styles/StyleUtils'; -import emojis from '../../../../assets/emojis'; +import emojiAssets from '../../../../assets/emojis'; import EmojiPickerMenuItem from '../EmojiPickerMenuItem'; import Text from '../../Text'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../withWindowDimensions'; @@ -18,6 +18,7 @@ import getOperatingSystem from '../../../libs/getOperatingSystem'; import * as User from '../../../libs/actions/User'; import EmojiSkinToneList from '../EmojiSkinToneList'; import * as EmojiUtils from '../../../libs/EmojiUtils'; +import * as Browser from '../../../libs/Browser'; import CategoryShortcutBar from '../CategoryShortcutBar'; import TextInput from '../../TextInput'; import isEnterWhileComposition from '../../../libs/KeyboardShortcut/isEnterWhileComposition'; @@ -32,7 +33,6 @@ const propTypes = { /** Stores user's preferred skin tone */ preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - /** Stores user's frequently used emojis */ // eslint-disable-next-line react/forbid-prop-types frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.object), @@ -49,105 +49,35 @@ const defaultProps = { frequentlyUsedEmojis: [], }; -class EmojiPickerMenu extends Component { - constructor(props) { - super(props); - - // Ref for the emoji search input - this.searchInput = undefined; - - // Ref for emoji FlatList - this.emojiList = undefined; - - // We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will - // prevent auto focus when open picker for mobile device - this.shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); - - this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300); - this.highlightAdjacentEmoji = this.highlightAdjacentEmoji.bind(this); - this.setupEventHandlers = this.setupEventHandlers.bind(this); - this.cleanupEventHandlers = this.cleanupEventHandlers.bind(this); - this.renderItem = this.renderItem.bind(this); - this.isMobileLandscape = this.isMobileLandscape.bind(this); - this.onSelectionChange = this.onSelectionChange.bind(this); - this.updatePreferredSkinTone = this.updatePreferredSkinTone.bind(this); - this.setFirstNonHeaderIndex = this.setFirstNonHeaderIndex.bind(this); - this.getItemLayout = this.getItemLayout.bind(this); - this.scrollToHeader = this.scrollToHeader.bind(this); - - this.firstNonHeaderIndex = 0; - - const {filteredEmojis, headerEmojis, headerRowIndices} = this.getEmojisAndHeaderRowIndices(); - this.emojis = filteredEmojis; - this.headerEmojis = headerEmojis; - this.headerRowIndices = headerRowIndices; - - this.state = { - filteredEmojis: this.emojis, - headerIndices: this.headerRowIndices, - highlightedIndex: -1, - arePointerEventsDisabled: false, - selection: { - start: 0, - end: 0, - }, - isFocused: false, - isUsingKeyboardMovement: false, - }; - } +const throttleTime = Browser.isMobile() ? 200 : 50; - componentDidMount() { - // This callback prop is used by the parent component using the constructor to - // get a ref to the inner textInput element e.g. if we do - // this.textInput = el} /> this will not - // return a ref to the component, but rather the HTML element by default - if (this.shouldFocusInputOnScreenFocus && this.props.forwardedRef && _.isFunction(this.props.forwardedRef)) { - this.props.forwardedRef(this.searchInput); - } - this.setupEventHandlers(); - this.setFirstNonHeaderIndex(this.emojis); - } +function EmojiPickerMenu(props) { + const {forwardedRef, frequentlyUsedEmojis, preferredSkinTone, onEmojiSelected, preferredLocale, isSmallScreenWidth, windowHeight, translate} = props; - componentDidUpdate(prevProps) { - if (prevProps.frequentlyUsedEmojis === this.props.frequentlyUsedEmojis) { - return; - } + // Ref for the emoji search input + const searchInputRef = useRef(null); - const {filteredEmojis, headerEmojis, headerRowIndices} = this.getEmojisAndHeaderRowIndices(); - this.emojis = filteredEmojis; - this.headerEmojis = headerEmojis; - this.headerRowIndices = headerRowIndices; - this.setState({ - filteredEmojis: this.emojis, - headerIndices: this.headerRowIndices, - }); - } + // Ref for emoji FlatList + const emojiListRef = useRef(null); - componentWillUnmount() { - this.cleanupEventHandlers(); - } + // We want consistent auto focus behavior on input between native and mWeb so we have some auto focus management code that will + // prevent auto focus when open picker for mobile device + const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus(); - /** - * On text input selection change - * - * @param {Event} event - */ - onSelectionChange(event) { - this.setState({selection: event.nativeEvent.selection}); - } + const firstNonHeaderIndex = useRef(0); /** * Calculate the filtered + header emojis and header row indices * @returns {Object} */ - getEmojisAndHeaderRowIndices() { + function getEmojisAndHeaderRowIndices() { // If we're on Windows, don't display the flag emojis (the last category), // since Windows doesn't support them - const flagHeaderIndex = _.findIndex(emojis, (emoji) => emoji.header && emoji.code === 'flags'); + const flagHeaderIndex = _.findIndex(emojiAssets, (emoji) => emoji.header && emoji.code === 'flags'); const filteredEmojis = getOperatingSystem() === CONST.OS.WINDOWS - ? EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis.slice(0, flagHeaderIndex)) - : EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojis); + ? EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojiAssets.slice(0, flagHeaderIndex)) + : EmojiUtils.mergeEmojisWithFrequentlyUsedEmojis(emojiAssets); // Get the header emojis along with the code, index and icon. // index is the actual header index starting at the first emoji and counting each one @@ -161,76 +91,57 @@ class EmojiPickerMenu extends Component { return {filteredEmojis, headerEmojis, headerRowIndices}; } + const emojis = useRef([]); + if (emojis.current.length === 0) { + emojis.current = getEmojisAndHeaderRowIndices().filteredEmojis; + } + const headerRowIndices = useRef([]); + if (headerRowIndices.current.length === 0) { + headerRowIndices.current = getEmojisAndHeaderRowIndices().headerRowIndices; + } + const [headerEmojis, setHeaderEmojis] = useState(() => getEmojisAndHeaderRowIndices().headerEmojis); + + const [filteredEmojis, setFilteredEmojis] = useState(emojis.current); + const [headerIndices, setHeaderIndices] = useState(headerRowIndices.current); + const [highlightedIndex, setHighlightedIndex] = useState(-1); + const [arePointerEventsDisabled, setArePointerEventsDisabled] = useState(false); + const [selection, setSelection] = useState({start: 0, end: 0}); + const [isFocused, setIsFocused] = useState(false); + const [isUsingKeyboardMovement, setIsUsingKeyboardMovement] = useState(false); + const [selectTextOnFocus, setSelectTextOnFocus] = useState(false); + + useEffect(() => { + const emojisAndHeaderRowIndices = getEmojisAndHeaderRowIndices(); + emojis.current = emojisAndHeaderRowIndices.filteredEmojis; + headerRowIndices.current = emojisAndHeaderRowIndices.headerRowIndices; + setHeaderEmojis(emojisAndHeaderRowIndices.headerEmojis); + setFilteredEmojis(emojis.current); + setHeaderIndices(headerRowIndices.current); + }, [frequentlyUsedEmojis]); + /** - * Find and store index of the first emoji item - * @param {Array} filteredEmojis + * On text input selection change + * + * @param {Event} event */ - setFirstNonHeaderIndex(filteredEmojis) { - this.firstNonHeaderIndex = _.findIndex(filteredEmojis, (item) => !item.spacer && !item.header); - } + const onSelectionChange = useCallback((event) => { + setSelection(event.nativeEvent.selection); + }, []); /** - * Setup and attach keypress/mouse handlers for highlight navigation. + * Find and store index of the first emoji item + * @param {Array} filteredEmojisArr */ - setupEventHandlers() { - if (!document) { + function updateFirstNonHeaderIndex(filteredEmojisArr) { + firstNonHeaderIndex.current = _.findIndex(filteredEmojisArr, (item) => !item.spacer && !item.header); + } + + const mouseMoveHandler = useCallback(() => { + if (!arePointerEventsDisabled) { return; } - - this.keyDownHandler = (keyBoardEvent) => { - if (keyBoardEvent.key.startsWith('Arrow')) { - if (!this.state.isFocused || keyBoardEvent.key === 'ArrowUp' || keyBoardEvent.key === 'ArrowDown') { - keyBoardEvent.preventDefault(); - } - - // Move the highlight when arrow keys are pressed - this.highlightAdjacentEmoji(keyBoardEvent.key); - return; - } - - // Select the currently highlighted emoji if enter is pressed - if (!isEnterWhileComposition(keyBoardEvent) && keyBoardEvent.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && this.state.highlightedIndex !== -1) { - const item = this.state.filteredEmojis[this.state.highlightedIndex]; - if (!item) { - return; - } - const emoji = lodashGet(item, ['types', this.props.preferredSkinTone], item.code); - this.props.onEmojiSelected(emoji, item); - return; - } - - // Enable keyboard movement if tab or enter is pressed or if shift is pressed while the input - // is not focused, so that the navigation and tab cycling can be done using the keyboard without - // interfering with the input behaviour. - if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && this.searchInput && !this.searchInput.isFocused())) { - this.setState({isUsingKeyboardMovement: true}); - return; - } - - // We allow typing in the search box if any key is pressed apart from Arrow keys. - if (this.searchInput && !this.searchInput.isFocused()) { - this.setState({selectTextOnFocus: false}); - this.searchInput.focus(); - - // Re-enable selection on the searchInput - this.setState({selectTextOnFocus: true}); - } - }; - - // Keyboard events are not bubbling on TextInput in RN-Web, Bubbling was needed for this event to trigger - // event handler attached to document root. To fix this, trigger event handler in Capture phase. - document.addEventListener('keydown', this.keyDownHandler, true); - - // Re-enable pointer events and hovering over EmojiPickerItems when the mouse moves - this.mouseMoveHandler = () => { - if (!this.state.arePointerEventsDisabled) { - return; - } - - this.setState({arePointerEventsDisabled: false}); - }; - document.addEventListener('mousemove', this.mouseMoveHandler); - } + setArePointerEventsDisabled(false); + }, [arePointerEventsDisabled]); /** * This function will be used with FlatList getItemLayout property for optimization purpose that allows skipping @@ -242,179 +153,254 @@ class EmojiPickerMenu extends Component { * @param {Number} index row index * @returns {Object} */ - getItemLayout(data, index) { - return {length: CONST.EMOJI_PICKER_ITEM_HEIGHT, offset: CONST.EMOJI_PICKER_ITEM_HEIGHT * index, index}; - } + const getItemLayout = useCallback((data, index) => ({length: CONST.EMOJI_PICKER_ITEM_HEIGHT, offset: CONST.EMOJI_PICKER_ITEM_HEIGHT * index, index}), []); /** - * Cleanup all mouse/keydown event listeners that we've set up + * Focuses the search Input and has the text selected */ - cleanupEventHandlers() { - if (!document) { + function focusInputWithTextSelect() { + if (!searchInputRef.current) { return; } - document.removeEventListener('keydown', this.keyDownHandler, true); - document.removeEventListener('mousemove', this.mouseMoveHandler); + setSelectTextOnFocus(true); + searchInputRef.current.focus(); } - /** - * Focuses the search Input and has the text selected - */ - focusInputWithTextSelect() { - if (!this.searchInput) { + const filterEmojis = _.throttle((searchTerm) => { + const normalizedSearchTerm = searchTerm.toLowerCase().trim().replaceAll(':', ''); + if (emojiListRef.current) { + emojiListRef.current.scrollToOffset({offset: 0, animated: false}); + } + if (normalizedSearchTerm === '') { + // There are no headers when searching, so we need to re-make them sticky when there is no search term + setFilteredEmojis(emojis.current); + setHeaderIndices(headerRowIndices.current); + setHighlightedIndex(-1); + updateFirstNonHeaderIndex(emojis.current); return; } + const newFilteredEmojiList = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, preferredLocale, emojis.current.length); - this.setState({selectTextOnFocus: true}); - this.searchInput.focus(); - } + // Remove sticky header indices. There are no headers while searching and we don't want to make emojis sticky + setFilteredEmojis(newFilteredEmojiList); + setHeaderIndices([]); + setHighlightedIndex(0); + updateFirstNonHeaderIndex(newFilteredEmojiList); + }, throttleTime); /** * Highlights emojis adjacent to the currently highlighted emoji depending on the arrowKey * @param {String} arrowKey */ - highlightAdjacentEmoji(arrowKey) { - if (this.state.filteredEmojis.length === 0) { - return; - } - - // Arrow Down and Arrow Right enable arrow navigation when search is focused - if (this.searchInput && this.searchInput.isFocused()) { - if (arrowKey !== 'ArrowDown' && arrowKey !== 'ArrowRight') { + const highlightAdjacentEmoji = useCallback( + (arrowKey) => { + if (filteredEmojis.length === 0) { return; } - if (arrowKey === 'ArrowRight' && !(this.searchInput.value.length === this.state.selection.start && this.state.selection.start === this.state.selection.end)) { + // Arrow Down and Arrow Right enable arrow navigation when search is focused + if (searchInputRef.current && searchInputRef.current.isFocused()) { + if (arrowKey !== 'ArrowDown' && arrowKey !== 'ArrowRight') { + return; + } + + if (arrowKey === 'ArrowRight' && !(searchInputRef.current.value.length === selection.start && selection.start === selection.end)) { + return; + } + + // Blur the input, change the highlight type to keyboard, and disable pointer events + searchInputRef.current.blur(); + setArePointerEventsDisabled(true); + setIsUsingKeyboardMovement(true); + + // We only want to hightlight the Emoji if none was highlighted already + // If we already have a highlighted Emoji, lets just skip the first navigation + if (highlightedIndex !== -1) { + return; + } + } + + // If nothing is highlighted and an arrow key is pressed + // select the first emoji, apply keyboard movement styles, and disable pointer events + if (highlightedIndex === -1) { + setHighlightedIndex(firstNonHeaderIndex.current); + setArePointerEventsDisabled(true); + setIsUsingKeyboardMovement(true); return; } - // Blur the input, change the highlight type to keyboard, and disable pointer events - this.searchInput.blur(); - this.setState({isUsingKeyboardMovement: true, arePointerEventsDisabled: true}); + let newIndex = highlightedIndex; + const move = (steps, boundsCheck, onBoundReached = () => {}) => { + if (boundsCheck()) { + onBoundReached(); + return; + } - // We only want to hightlight the Emoji if none was highlighted already - // If we already have a highlighted Emoji, lets just skip the first navigation - if (this.state.highlightedIndex !== -1) { - return; + // Move in the prescribed direction until we reach an element that isn't a header + const isHeader = (e) => e.header || e.spacer; + do { + newIndex += steps; + if (newIndex < 0) { + break; + } + } while (isHeader(filteredEmojis[newIndex])); + }; + + switch (arrowKey) { + case 'ArrowDown': + move(CONST.EMOJI_NUM_PER_ROW, () => highlightedIndex + CONST.EMOJI_NUM_PER_ROW > filteredEmojis.length - 1); + break; + case 'ArrowLeft': + move( + -1, + () => highlightedIndex - 1 < firstNonHeaderIndex.current, + () => { + // Reaching start of the list, arrow left set the focus to searchInput. + focusInputWithTextSelect(); + newIndex = -1; + }, + ); + break; + case 'ArrowRight': + move(1, () => highlightedIndex + 1 > filteredEmojis.length - 1); + break; + case 'ArrowUp': + move( + -CONST.EMOJI_NUM_PER_ROW, + () => highlightedIndex - CONST.EMOJI_NUM_PER_ROW < firstNonHeaderIndex.current, + () => { + // Reaching start of the list, arrow up set the focus to searchInput. + focusInputWithTextSelect(); + newIndex = -1; + }, + ); + break; + default: + break; } - } - // If nothing is highlighted and an arrow key is pressed - // select the first emoji, apply keyboard movement styles, and disable pointer events - if (this.state.highlightedIndex === -1) { - this.setState({highlightedIndex: this.firstNonHeaderIndex, isUsingKeyboardMovement: true, arePointerEventsDisabled: true}); - return; - } + // Actually highlight the new emoji, apply keyboard movement styles, and disable pointer events + if (newIndex !== highlightedIndex) { + setHighlightedIndex(newIndex); + setArePointerEventsDisabled(true); + setIsUsingKeyboardMovement(true); + } + }, + [filteredEmojis, highlightedIndex, selection.end, selection.start], + ); - let newIndex = this.state.highlightedIndex; - const move = (steps, boundsCheck, onBoundReached = () => {}) => { - if (boundsCheck()) { - onBoundReached(); + const keyDownHandler = useCallback( + (keyBoardEvent) => { + if (keyBoardEvent.key.startsWith('Arrow')) { + if (!isFocused || keyBoardEvent.key === 'ArrowUp' || keyBoardEvent.key === 'ArrowDown') { + keyBoardEvent.preventDefault(); + } + + // Move the highlight when arrow keys are pressed + highlightAdjacentEmoji(keyBoardEvent.key); return; } - // Move in the prescribed direction until we reach an element that isn't a header - const isHeader = (e) => e.header || e.spacer; - do { - newIndex += steps; - if (newIndex < 0) { - break; + // Select the currently highlighted emoji if enter is pressed + if (!isEnterWhileComposition(keyBoardEvent) && keyBoardEvent.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && highlightedIndex !== -1) { + const item = filteredEmojis[highlightedIndex]; + if (!item) { + return; } - } while (isHeader(this.state.filteredEmojis[newIndex])); - }; + const emoji = lodashGet(item, ['types', preferredSkinTone], item.code); + onEmojiSelected(emoji, item); + return; + } - switch (arrowKey) { - case 'ArrowDown': - move(CONST.EMOJI_NUM_PER_ROW, () => this.state.highlightedIndex + CONST.EMOJI_NUM_PER_ROW > this.state.filteredEmojis.length - 1); - break; - case 'ArrowLeft': - move( - -1, - () => this.state.highlightedIndex - 1 < this.firstNonHeaderIndex, - () => { - // Reaching start of the list, arrow left set the focus to searchInput. - this.focusInputWithTextSelect(); - newIndex = -1; - }, - ); - break; - case 'ArrowRight': - move(1, () => this.state.highlightedIndex + 1 > this.state.filteredEmojis.length - 1); - break; - case 'ArrowUp': - move( - -CONST.EMOJI_NUM_PER_ROW, - () => this.state.highlightedIndex - CONST.EMOJI_NUM_PER_ROW < this.firstNonHeaderIndex, - () => { - // Reaching start of the list, arrow up set the focus to searchInput. - this.focusInputWithTextSelect(); - newIndex = -1; - }, - ); - break; - default: - break; - } + // Enable keyboard movement if tab or enter is pressed or if shift is pressed while the input + // is not focused, so that the navigation and tab cycling can be done using the keyboard without + // interfering with the input behaviour. + if (keyBoardEvent.key === 'Tab' || keyBoardEvent.key === 'Enter' || (keyBoardEvent.key === 'Shift' && searchInputRef.current && !searchInputRef.current.isFocused())) { + setIsUsingKeyboardMovement(true); + return; + } - // Actually highlight the new emoji, apply keyboard movement styles, and disable pointer events - if (newIndex !== this.state.highlightedIndex) { - this.setState({highlightedIndex: newIndex, isUsingKeyboardMovement: true, arePointerEventsDisabled: true}); - } - } + // We allow typing in the search box if any key is pressed apart from Arrow keys. + if (searchInputRef.current && !searchInputRef.current.isFocused()) { + setSelectTextOnFocus(false); + searchInputRef.current.focus(); - scrollToHeader(headerIndex) { - const calculatedOffset = Math.floor(headerIndex / CONST.EMOJI_NUM_PER_ROW) * CONST.EMOJI_PICKER_HEADER_HEIGHT; - this.emojiList.flashScrollIndicators(); - this.emojiList.scrollToOffset({offset: calculatedOffset, animated: true}); - } + // Re-enable selection on the searchInput + setSelectTextOnFocus(true); + } + }, + [filteredEmojis, highlightAdjacentEmoji, highlightedIndex, isFocused, onEmojiSelected, preferredSkinTone], + ); /** - * Filter the entire list of emojis to only emojis that have the search term in their keywords - * - * @param {String} searchTerm + * Setup and attach keypress/mouse handlers for highlight navigation. */ - filterEmojis(searchTerm) { - const normalizedSearchTerm = searchTerm.toLowerCase().trim().replaceAll(':', ''); - if (this.emojiList) { - this.emojiList.scrollToOffset({offset: 0, animated: false}); - } - if (normalizedSearchTerm === '') { - // There are no headers when searching, so we need to re-make them sticky when there is no search term - this.setState({ - filteredEmojis: this.emojis, - headerIndices: this.headerRowIndices, - highlightedIndex: -1, - }); - this.setFirstNonHeaderIndex(this.emojis); + const setupEventHandlers = useCallback(() => { + if (!document) { return; } - const newFilteredEmojiList = EmojiUtils.suggestEmojis(`:${normalizedSearchTerm}`, this.props.preferredLocale, this.emojis.length); - // Remove sticky header indices. There are no headers while searching and we don't want to make emojis sticky - this.setState({filteredEmojis: newFilteredEmojiList, headerIndices: [], highlightedIndex: 0}); - this.setFirstNonHeaderIndex(newFilteredEmojiList); - } + // Keyboard events are not bubbling on TextInput in RN-Web, Bubbling was needed for this event to trigger + // event handler attached to document root. To fix this, trigger event handler in Capture phase. + document.addEventListener('keydown', keyDownHandler, true); + + // Re-enable pointer events and hovering over EmojiPickerItems when the mouse moves + document.addEventListener('mousemove', mouseMoveHandler); + }, [keyDownHandler, mouseMoveHandler]); /** - * Check if its a landscape mode of mobile device - * - * @returns {Boolean} + * Cleanup all mouse/keydown event listeners that we've set up */ - isMobileLandscape() { - return this.props.isSmallScreenWidth && this.props.windowWidth >= this.props.windowHeight; - } + const cleanupEventHandlers = useCallback(() => { + if (!document) { + return; + } + + document.removeEventListener('keydown', keyDownHandler, true); + document.removeEventListener('mousemove', mouseMoveHandler); + }, [keyDownHandler, mouseMoveHandler]); + + useEffect(() => { + // This callback prop is used by the parent component using the constructor to + // get a ref to the inner textInput element e.g. if we do + // this.textInput = el} /> this will not + // return a ref to the component, but rather the HTML element by default + if (shouldFocusInputOnScreenFocus && forwardedRef && _.isFunction(forwardedRef)) { + forwardedRef(searchInputRef.current); + } + + setupEventHandlers(); + updateFirstNonHeaderIndex(emojis.current); + + return () => { + cleanupEventHandlers(); + }; + }, [forwardedRef, shouldFocusInputOnScreenFocus, cleanupEventHandlers, setupEventHandlers]); + + const scrollToHeader = useCallback((headerIndex) => { + if (!emojiListRef.current) { + return; + } + + const calculatedOffset = Math.floor(headerIndex / CONST.EMOJI_NUM_PER_ROW) * CONST.EMOJI_PICKER_HEADER_HEIGHT; + emojiListRef.current.flashScrollIndicators(); + emojiListRef.current.scrollToOffset({offset: calculatedOffset, animated: true}); + }, []); /** * @param {Number} skinTone */ - updatePreferredSkinTone(skinTone) { - if (this.props.preferredSkinTone === skinTone) { - return; - } + const updatePreferredSkinTone = useCallback( + (skinTone) => { + if (Number(preferredSkinTone) === Number(skinTone)) { + return; + } - User.updatePreferredSkinTone(skinTone); - } + User.updatePreferredSkinTone(skinTone); + }, + [preferredSkinTone], + ); /** * Return a unique key for each emoji item @@ -423,9 +409,7 @@ class EmojiPickerMenu extends Component { * @param {Number} index * @returns {String} */ - keyExtractor(item, index) { - return `emoji_picker_${item.code}_${index}`; - } + const keyExtractor = useCallback((item, index) => `emoji_picker_${item.code}_${index}`, []); /** * Given an emoji item object, render a component based on its type. @@ -436,109 +420,112 @@ class EmojiPickerMenu extends Component { * @param {Number} index * @returns {*} */ - renderItem({item, index}) { - const {code, header, types} = item; - if (item.spacer) { - return null; - } + const renderItem = useCallback( + ({item, index}) => { + const {code, header, types} = item; + if (item.spacer) { + return null; + } - if (header) { - return ( - - {this.props.translate(`emojiPicker.headers.${code}`)} - - ); - } + if (header) { + return ( + + {translate(`emojiPicker.headers.${code}`)} + + ); + } - const emojiCode = types && types[this.props.preferredSkinTone] ? types[this.props.preferredSkinTone] : code; + const emojiCode = types && types[preferredSkinTone] ? types[preferredSkinTone] : code; - const isEmojiFocused = index === this.state.highlightedIndex && this.state.isUsingKeyboardMovement; + const isEmojiFocused = index === highlightedIndex && isUsingKeyboardMovement; - return ( - this.props.onEmojiSelected(emoji, item)} - onHoverIn={() => { - if (!this.state.isUsingKeyboardMovement) { - return; - } - this.setState({isUsingKeyboardMovement: false}); - }} - emoji={emojiCode} - onFocus={() => this.setState({highlightedIndex: index})} - onBlur={() => - this.setState((prevState) => ({ + return ( + onEmojiSelected(emoji, item)} + onHoverIn={() => { + if (!isUsingKeyboardMovement) { + return; + } + setIsUsingKeyboardMovement(false); + }} + emoji={emojiCode} + onFocus={() => setHighlightedIndex(index)} + onBlur={() => // Only clear the highlighted index if the highlighted index is the same, // meaning that the focus changed to an element that is not an emoji item. - highlightedIndex: prevState.highlightedIndex === index ? -1 : prevState.highlightedIndex, - })) - } - isFocused={isEmojiFocused} - /> - ); - } - - render() { - const isFiltered = this.emojis.length !== this.state.filteredEmojis.length; - const listStyle = StyleUtils.getEmojiPickerListHeight(isFiltered, this.props.windowHeight); - const height = !listStyle.maxHeight || listStyle.height < listStyle.maxHeight ? listStyle.height : listStyle.maxHeight; - const overflowLimit = Math.floor(height / CONST.EMOJI_PICKER_ITEM_HEIGHT) * 8; - return ( - - - (this.searchInput = el)} - autoFocus={this.shouldFocusInputOnScreenFocus} - selectTextOnFocus={this.state.selectTextOnFocus} - onSelectionChange={this.onSelectionChange} - onFocus={() => this.setState({isFocused: true, highlightedIndex: -1, isUsingKeyboardMovement: false})} - onBlur={() => this.setState({isFocused: false})} - autoCorrect={false} - blurOnSubmit={this.state.filteredEmojis.length > 0} - /> - - {!isFiltered && ( - - )} - (this.emojiList = el)} - data={this.state.filteredEmojis} - renderItem={this.renderItem} - keyExtractor={this.keyExtractor} - numColumns={CONST.EMOJI_NUM_PER_ROW} - style={[ - listStyle, - // This prevents elastic scrolling when scroll reaches the start or end - {overscrollBehaviorY: 'contain'}, - // Set overflow to hidden to prevent elastic scrolling when there are not enough contents to scroll in FlatList - {overflowY: this.state.filteredEmojis.length > overflowLimit ? 'auto' : 'hidden'}, - // Set scrollPaddingTop to consider sticky headers while scrolling - {scrollPaddingTop: isFiltered ? 0 : CONST.EMOJI_PICKER_ITEM_HEIGHT}, - ]} - extraData={[this.state.filteredEmojis, this.state.highlightedIndex, this.props.preferredSkinTone]} - stickyHeaderIndices={this.state.headerIndices} - getItemLayout={this.getItemLayout} - contentContainerStyle={styles.flexGrow1} - ListEmptyComponent={{this.props.translate('common.noResultsFound')}} + setHighlightedIndex((prevState) => (prevState === index ? -1 : prevState)) + } + isFocused={isEmojiFocused} /> - + + { + setHighlightedIndex(-1); + setIsFocused(true); + setIsUsingKeyboardMovement(false); + }} + onBlur={() => setIsFocused(false)} + autoCorrect={false} + blurOnSubmit={filteredEmojis.length > 0} /> - ); - } + {!isFiltered && ( + + )} + + ); } EmojiPickerMenu.propTypes = propTypes; diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index 5c753120301a..c5ca5463aec4 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -69,6 +69,8 @@ class EmojiPickerMenuItem extends PureComponent { this.props.onPress(this.props.emoji)} + // In order to prevent haptic feedback, pass empty callback as onLongPress props. Please refer https://github.com/necolas/react-native-web/issues/2349#issuecomment-1195564240 + onLongPress={Browser.isMobileChrome() ? () => {} : undefined} onPressOut={Browser.isMobile() ? this.props.onHoverOut : undefined} onHoverIn={() => { if (this.props.onHoverIn) { diff --git a/src/components/FormAlertWrapper.js b/src/components/FormAlertWrapper.js index 704d9b5a241c..67e031ce6ab6 100644 --- a/src/components/FormAlertWrapper.js +++ b/src/components/FormAlertWrapper.js @@ -66,7 +66,7 @@ function FormAlertWrapper(props) { ); } else if (props.isMessageHtml) { - children = ${props.message}`} />; + children = ${props.message}`} />; } return ( diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index c806bedc31c7..04759b89e5d0 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -29,9 +29,13 @@ const customHTMLElementModels = { edited: defaultHTMLElementModels.span.extend({ tagName: 'edited', }), + 'alert-text': defaultHTMLElementModels.div.extend({ + tagName: 'alert-text', + mixedUAStyles: {...styles.formError, ...styles.mb0}, + }), 'muted-text': defaultHTMLElementModels.div.extend({ tagName: 'muted-text', - mixedUAStyles: {...styles.formError, ...styles.mb0}, + mixedUAStyles: {...styles.colorMuted, ...styles.mb0}, }), comment: defaultHTMLElementModels.div.extend({ tagName: 'comment', diff --git a/src/components/Icon/BankIcons.ts b/src/components/Icon/BankIcons.ts index 3118eec56a6d..a30594d1ab3f 100644 --- a/src/components/Icon/BankIcons.ts +++ b/src/components/Icon/BankIcons.ts @@ -1,5 +1,6 @@ import {SvgProps} from 'react-native-svg'; -import * as Expensicons from './Expensicons'; +import {CSSProperties} from 'react'; +import {ViewStyle} from 'react-native'; import AmericanExpress from '../../../assets/images/bankicons/american-express.svg'; import BankOfAmerica from '../../../assets/images/bankicons/bank-of-america.svg'; import BB_T from '../../../assets/images/bankicons/bb-t.svg'; @@ -19,11 +20,36 @@ import SunTrust from '../../../assets/images/bankicons/suntrust.svg'; import TdBank from '../../../assets/images/bankicons/td-bank.svg'; import USBank from '../../../assets/images/bankicons/us-bank.svg'; import USAA from '../../../assets/images/bankicons/usaa.svg'; +// Card Icons +import AmericanExpressCard from '../../../assets/images/cardicons/american-express.svg'; +import BankOfAmericaCard from '../../../assets/images/cardicons/bank-of-america.svg'; +import BB_TCard from '../../../assets/images/cardicons/bb-t.svg'; +import CapitalOneCard from '../../../assets/images/cardicons/capital-one.svg'; +import CharlesSchwabCard from '../../../assets/images/cardicons/charles-schwab.svg'; +import ChaseCard from '../../../assets/images/cardicons/chase.svg'; +import CitiBankCard from '../../../assets/images/cardicons/citibank.svg'; +import CitizensBankCard from '../../../assets/images/cardicons/citizens.svg'; +import DiscoverCard from '../../../assets/images/cardicons/discover.svg'; +import FidelityCard from '../../../assets/images/cardicons/fidelity.svg'; +import HuntingtonBankCard from '../../../assets/images/cardicons/huntington-bank.svg'; +import GenericBankCard from '../../../assets/images/cardicons/generic-bank-card.svg'; +import NavyFederalCreditUnionCard from '../../../assets/images/cardicons/navy-federal-credit-union.svg'; +import PNCCard from '../../../assets/images/cardicons/pnc.svg'; +import RegionsBankCard from '../../../assets/images/cardicons/regions-bank.svg'; +import SunTrustCard from '../../../assets/images/cardicons/suntrust.svg'; +import TdBankCard from '../../../assets/images/cardicons/td-bank.svg'; +import USBankCard from '../../../assets/images/cardicons/us-bank.svg'; +import USAACard from '../../../assets/images/cardicons/usaa.svg'; +import ExpensifyCardImage from '../../../assets/images/cardicons/expensify-card-dark.svg'; +import styles from '../../styles/styles'; import variables from '../../styles/variables'; type BankIcon = { icon: React.FC; iconSize?: number; + iconHeight?: number; + iconWidth?: number; + iconStyles?: Array; }; /** @@ -31,79 +57,83 @@ type BankIcon = { */ function getAssetIcon(bankName: string, isCard: boolean): React.FC { + if (bankName.includes('expensify')) { + return ExpensifyCardImage; + } + if (bankName.includes('americanexpress')) { - return AmericanExpress; + return isCard ? AmericanExpressCard : AmericanExpress; } if (bankName.includes('bank of america') || bankName.includes('bankofamerica')) { - return BankOfAmerica; + return isCard ? BankOfAmericaCard : BankOfAmerica; } if (bankName.startsWith('bbt')) { - return BB_T; + return isCard ? BB_TCard : BB_T; } if (bankName.startsWith('capital one') || bankName.includes('capitalone')) { - return CapitalOne; + return isCard ? CapitalOneCard : CapitalOne; } if (bankName.startsWith('chase') || bankName.includes('chase')) { - return Chase; + return isCard ? ChaseCard : Chase; } if (bankName.includes('charles schwab') || bankName.includes('charlesschwab')) { - return CharlesSchwab; + return isCard ? CharlesSchwabCard : CharlesSchwab; } if (bankName.startsWith('citibank') || bankName.includes('citibank')) { - return CitiBank; + return isCard ? CitiBankCard : CitiBank; } if (bankName.startsWith('citizens bank') || bankName.includes('citizensbank')) { - return CitizensBank; + return isCard ? CitizensBankCard : CitizensBank; } if (bankName.startsWith('discover ') || bankName.includes('discover.') || bankName === 'discover') { - return Discover; + return isCard ? DiscoverCard : Discover; } if (bankName.startsWith('fidelity')) { - return Fidelity; + return isCard ? FidelityCard : Fidelity; } if (bankName.startsWith('huntington bank') || bankName.includes('huntingtonnational') || bankName.includes('huntington national')) { - return HuntingtonBank; + return isCard ? HuntingtonBankCard : HuntingtonBank; } if (bankName.startsWith('navy federal credit union') || bankName.includes('navy federal credit union')) { - return NavyFederalCreditUnion; + return isCard ? NavyFederalCreditUnionCard : NavyFederalCreditUnion; } if (bankName.startsWith('pnc') || bankName.includes('pnc')) { - return PNC; + return isCard ? PNCCard : PNC; } if (bankName.startsWith('regions bank') || bankName.includes('regionsbank')) { - return RegionsBank; + return isCard ? RegionsBankCard : RegionsBank; } if (bankName.startsWith('suntrust') || bankName.includes('suntrust')) { - return SunTrust; + return isCard ? SunTrustCard : SunTrust; } if (bankName.startsWith('td bank') || bankName.startsWith('tdbank') || bankName.includes('tdbank')) { - return TdBank; + return isCard ? TdBankCard : TdBank; } if (bankName.startsWith('us bank') || bankName.startsWith('usbank')) { - return USBank; + return isCard ? USBankCard : USBank; } if (bankName.includes('usaa')) { - return USAA; + return isCard ? USAACard : USAA; } - return isCard ? Expensicons.CreditCard : GenericBank; + return isCard ? GenericBankCard : GenericBank; } /** @@ -112,7 +142,7 @@ function getAssetIcon(bankName: string, isCard: boolean): React.FC { export default function getBankIcon(bankName: string, isCard = false): BankIcon { const bankIcon: BankIcon = { - icon: isCard ? Expensicons.CreditCard : GenericBank, + icon: isCard ? GenericBankCard : GenericBank, }; if (bankName) { @@ -120,8 +150,13 @@ export default function getBankIcon(bankName: string, isCard = false): BankIcon } // For default Credit Card icon the icon size should not be set. - if (![Expensicons.CreditCard].includes(bankIcon.icon)) { + if (!isCard) { bankIcon.iconSize = variables.iconSizeExtraLarge; + bankIcon.iconStyles = [styles.bankIconContainer]; + } else { + bankIcon.iconHeight = variables.bankCardHeight; + bankIcon.iconWidth = variables.bankCardWidth; + bankIcon.iconStyles = [styles.assignedCardsIconContainer]; } return bankIcon; diff --git a/src/components/KYCWall/BaseKYCWall.js b/src/components/KYCWall/BaseKYCWall.js index db3c85ef818c..ccee8bc4e6a0 100644 --- a/src/components/KYCWall/BaseKYCWall.js +++ b/src/components/KYCWall/BaseKYCWall.js @@ -24,6 +24,7 @@ class KYCWall extends React.Component { this.continue = this.continue.bind(this); this.setMenuPosition = this.setMenuPosition.bind(this); + this.selectPaymentMethod = this.selectPaymentMethod.bind(this); this.anchorRef = React.createRef(null); this.state = { @@ -39,7 +40,6 @@ class KYCWall extends React.Component { if (this.props.shouldListenForResize) { this.dimensionsSubscription = Dimensions.addEventListener('change', this.setMenuPosition); } - Wallet.setKYCWallSourceChatReportID(this.props.chatReportID); } componentWillUnmount() { @@ -88,6 +88,18 @@ class KYCWall extends React.Component { }); } + /** + * @param {String} paymentMethod + */ + selectPaymentMethod(paymentMethod) { + this.props.onSelectPaymentMethod(paymentMethod); + if (paymentMethod === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + Navigation.navigate(this.props.addBankAccountRoute); + } else if (paymentMethod === CONST.PAYMENT_METHODS.DEBIT_CARD) { + Navigation.navigate(this.props.addDebitCardRoute); + } + } + /** * Take the position of the button that calls this method and show the Add Payment method menu when the user has no valid payment method. * If they do have a valid payment method they are navigated to the "enable payments" route to complete KYC checks. @@ -97,6 +109,14 @@ class KYCWall extends React.Component { * @param {String} iouPaymentType */ continue(event, iouPaymentType) { + const currentSource = lodashGet(this.props.walletTerms, 'source', this.props.source); + + /** + * Set the source, so we can tailor the process according to how we got here. + * We do not want to set this on mount, as the source can change upon completing the flow, e.g. when upgrading the wallet to Gold. + */ + Wallet.setKYCWallSource(this.props.source, this.props.chatReportID); + if (this.state.shouldShowAddPaymentMenu) { this.setState({shouldShowAddPaymentMenu: false}); return; @@ -111,9 +131,13 @@ class KYCWall extends React.Component { // Check to see if user has a valid payment method on file and display the add payment popover if they don't if ( (isExpenseReport && lodashGet(this.props.reimbursementAccount, 'achData.state', '') !== CONST.BANK_ACCOUNT.STATE.OPEN) || - (!isExpenseReport && !PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, this.props.bankAccountList)) + (!isExpenseReport && !PaymentUtils.hasExpensifyPaymentMethod(paymentCardList, this.props.bankAccountList, this.props.shouldIncludeDebitCard)) ) { Log.info('[KYC Wallet] User does not have valid payment method'); + if (!this.props.shouldIncludeDebitCard) { + this.selectPaymentMethod(CONST.PAYMENT_METHODS.BANK_ACCOUNT); + return; + } const clickedElementLocation = getClickedTargetLocation(targetElement); const position = this.getAnchorPosition(clickedElementLocation); this.setPositionAddPaymentMenu(position); @@ -132,7 +156,7 @@ class KYCWall extends React.Component { } } Log.info('[KYC Wallet] User has valid payment method and passed KYC checks or did not need them'); - this.props.onSuccessfulKYC(iouPaymentType); + this.props.onSuccessfulKYC(iouPaymentType, currentSource); } render() { @@ -149,11 +173,7 @@ class KYCWall extends React.Component { anchorAlignment={this.props.anchorAlignment} onItemSelected={(item) => { this.setState({shouldShowAddPaymentMenu: false}); - if (item === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { - Navigation.navigate(this.props.addBankAccountRoute); - } else if (item === CONST.PAYMENT_METHODS.DEBIT_CARD) { - Navigation.navigate(this.props.addDebitCardRoute); - } + this.selectPaymentMethod(item); }} /> {this.props.children(this.continue, this.anchorRef)} @@ -169,6 +189,9 @@ export default withOnyx({ userWallet: { key: ONYXKEYS.USER_WALLET, }, + walletTerms: { + key: ONYXKEYS.WALLET_TERMS, + }, fundList: { key: ONYXKEYS.FUND_LIST, }, diff --git a/src/components/KYCWall/kycWallPropTypes.js b/src/components/KYCWall/kycWallPropTypes.js index 6c117eb67f5b..b585535784dc 100644 --- a/src/components/KYCWall/kycWallPropTypes.js +++ b/src/components/KYCWall/kycWallPropTypes.js @@ -5,6 +5,7 @@ import bankAccountPropTypes from '../bankAccountPropTypes'; import cardPropTypes from '../cardPropTypes'; import iouReportPropTypes from '../../pages/iouReportPropTypes'; import reimbursementAccountPropTypes from '../../pages/ReimbursementAccount/ReimbursementAccountDraftPropTypes'; +import walletTermsPropTypes from '../../pages/EnablePayments/walletTermsPropTypes'; import CONST from '../../CONST'; const propTypes = { @@ -26,6 +27,12 @@ const propTypes = { /** The user's wallet */ userWallet: userWalletPropTypes, + /** Information related to the last step of the wallet activation flow */ + walletTerms: walletTermsPropTypes, + + /** The source that triggered the KYC wall */ + source: PropTypes.oneOf(_.values(CONST.KYC_WALL_SOURCE)).isRequired, + /** When the button is opened via an IOU, ID for the chatReport that the IOU is linked to */ chatReportID: PropTypes.string, @@ -49,10 +56,17 @@ const propTypes = { horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), }), + + /** Whether the option to add a debit card should be included */ + shouldIncludeDebitCard: PropTypes.bool, + + /** Callback for when a payment method has been selected */ + onSelectPaymentMethod: PropTypes.func, }; const defaultProps = { userWallet: {}, + walletTerms: {}, shouldListenForResize: false, isDisabled: false, chatReportID: '', @@ -66,6 +80,8 @@ const defaultProps = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, }, + shouldIncludeDebitCard: true, + onSelectPaymentMethod: () => {}, }; export {propTypes, defaultProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.js index 17c2ef0c1998..ba035c8b3baf 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.js @@ -186,7 +186,9 @@ function OptionRowLHN(props) { onSecondaryInteraction={(e) => { showPopover(e); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time - DomUtils.getActiveElement().blur(); + if (DomUtils.getActiveElement()) { + DomUtils.getActiveElement().blur(); + } }} withoutFocusOnSecondaryInteraction activeOpacity={0.8} diff --git a/src/components/MagicCodeInput.js b/src/components/MagicCodeInput.js index dcaa0273f96a..3a9cc6845194 100644 --- a/src/components/MagicCodeInput.js +++ b/src/components/MagicCodeInput.js @@ -103,6 +103,7 @@ function MagicCodeInput(props) { const [input, setInput] = useState(''); const [focusedIndex, setFocusedIndex] = useState(0); const [editIndex, setEditIndex] = useState(0); + const [wasSubmitted, setWasSubmitted] = useState(false); const blurMagicCodeInput = () => { inputRefs.current[editIndex].blur(); @@ -124,9 +125,12 @@ function MagicCodeInput(props) { const validateAndSubmit = () => { const numbers = decomposeString(props.value, props.maxLength); - if (!props.shouldSubmitOnComplete || _.filter(numbers, (n) => ValidationUtils.isNumeric(n)).length !== props.maxLength || props.network.isOffline) { + if (wasSubmitted || !props.shouldSubmitOnComplete || _.filter(numbers, (n) => ValidationUtils.isNumeric(n)).length !== props.maxLength || props.network.isOffline) { return; } + if (!wasSubmitted) { + setWasSubmitted(true); + } // Blurs the input and removes focus from the last input and, if it should submit // on complete, it will call the onFulfill callback. blurMagicCodeInput(); diff --git a/src/components/MapView/MapViewTypes.ts b/src/components/MapView/MapViewTypes.ts index dc56cb4642c4..6cc52ac91d18 100644 --- a/src/components/MapView/MapViewTypes.ts +++ b/src/components/MapView/MapViewTypes.ts @@ -33,6 +33,9 @@ type PendingMapViewProps = { /** Subtitle message below the title */ subtitle?: string; + + /** Style applied to PendingMapView */ + style?: StyleProp; }; // Initial state of the map diff --git a/src/components/MapView/PendingMapView.tsx b/src/components/MapView/PendingMapView.tsx index 6a35d2a9c369..d97d4aaee16f 100644 --- a/src/components/MapView/PendingMapView.tsx +++ b/src/components/MapView/PendingMapView.tsx @@ -8,11 +8,11 @@ import {PendingMapViewProps} from './MapViewTypes'; import BlockingView from '../BlockingViews/BlockingView'; import * as Expensicons from '../Icon/Expensicons'; -function PendingMapView({title = '', subtitle = ''}: PendingMapViewProps) { +function PendingMapView({title = '', subtitle = '', style}: PendingMapViewProps) { const hasTextContent = !_.isEmpty(title) || !_.isEmpty(subtitle); return ( - + {hasTextContent ? ( { @@ -117,39 +118,42 @@ const MenuItem = React.forwardRef((props, ref) => { return; } const parser = new ExpensiMark(); - setHtml(parser.replace(convertToLTR(props.title))); + setHtml(parser.replace(props.title)); titleRef.current = props.title; }, [props.title, props.shouldParseTitle]); const getProcessedTitle = useMemo(() => { + let title = ''; if (props.shouldRenderAsHTML) { - return convertToLTR(props.title); + title = convertToLTR(props.title); } if (props.shouldParseTitle) { - return html; + title = html; } - return ''; + return title ? `${title}` : ''; }, [props.title, props.shouldRenderAsHTML, props.shouldParseTitle, html]); const hasPressableRightComponent = props.iconRight || (props.rightComponent && props.shouldShowRightComponent); + const onPressAction = (e) => { + if (props.disabled || !props.interactive) { + return; + } + + if (e && e.type === 'click') { + e.currentTarget.blur(); + } + + props.onPress(e); + }; + return ( {(isHovered) => ( { - if (props.disabled || !props.interactive) { - return; - } - - if (e && e.type === 'click') { - e.currentTarget.blur(); - } - - props.onPress(e); - }, props.isAnonymousAction)} + onPress={props.shouldCheckActionAllowedOnPress ? Session.checkIfActionIsAllowed(onPressAction, props.isAnonymousAction) : onPressAction} onPressIn={() => props.shouldBlockSelection && isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} onPressOut={ControlSelection.unblock} onSecondaryInteraction={props.onSecondaryInteraction} diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index 49681f396181..8ae4672e758e 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -1,4 +1,5 @@ import React, {useMemo} from 'react'; +import _ from 'underscore'; import {withOnyx} from 'react-native-onyx'; import {View} from 'react-native'; import PropTypes from 'prop-types'; @@ -15,11 +16,13 @@ import Navigation from '../libs/Navigation/Navigation'; import ROUTES from '../ROUTES'; import ONYXKEYS from '../ONYXKEYS'; import CONST from '../CONST'; +import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; import SettlementButton from './SettlementButton'; import Button from './Button'; import * as IOU from '../libs/actions/IOU'; import * as CurrencyUtils from '../libs/CurrencyUtils'; import reportPropTypes from '../pages/reportPropTypes'; +import nextStepPropTypes from '../pages/nextStepPropTypes'; const propTypes = { /** The report currently being looked at */ @@ -40,6 +43,9 @@ const propTypes = { /** The chat report this report is linked to */ chatReport: reportPropTypes, + /** The next step for the report */ + nextStep: nextStepPropTypes, + /** Personal details so we can get the ones for the report participants */ personalDetails: PropTypes.objectOf(participantPropTypes).isRequired, @@ -54,13 +60,14 @@ const propTypes = { const defaultProps = { chatReport: {}, + nextStep: {}, session: { email: null, }, policy: {}, }; -function MoneyReportHeader({session, personalDetails, policy, chatReport, report: moneyRequestReport, isSmallScreenWidth}) { +function MoneyReportHeader({session, personalDetails, policy, chatReport, nextStep, report: moneyRequestReport, isSmallScreenWidth}) { const {translate} = useLocalize(); const reimbursableTotal = ReportUtils.getMoneyRequestReimbursableTotal(moneyRequestReport); const isApproved = ReportUtils.isReportApproved(moneyRequestReport); @@ -81,9 +88,11 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report return isManager && !isDraft && !isApproved && !isSettled; }, [policyType, isManager, isDraft, isApproved, isSettled]); const shouldShowSubmitButton = isDraft && reimbursableTotal !== 0; - const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton; + const shouldShowNextSteps = isDraft && nextStep && (!_.isEmpty(nextStep.message) || !_.isEmpty(nextStep.expenseMessage)); + const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextSteps; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableTotal, moneyRequestReport.currency); + const isMoreContentShown = shouldShowNextSteps || (shouldShowAnyButton && isSmallScreenWidth); return ( @@ -96,7 +105,8 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report personalDetails={personalDetails} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(ROUTES.HOME, false, true)} - shouldShowBorderBottom={!shouldShowAnyButton || !isSmallScreenWidth} + // Shows border if no buttons or next steps are showing below the header + shouldShowBorderBottom={!(shouldShowAnyButton && isSmallScreenWidth) && !(shouldShowNextSteps && !isSmallScreenWidth)} > {shouldShowSettlementButton && !isSmallScreenWidth && ( @@ -141,43 +151,50 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, report )} - {shouldShowSettlementButton && isSmallScreenWidth && ( - - IOU.payMoneyRequest(paymentType, chatReport, moneyRequestReport)} - enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} - addBankAccountRoute={bankAccountRoute} - shouldShowPaymentOptions - formattedAmount={formattedAmount} - /> - - )} - {shouldShowApproveButton && isSmallScreenWidth && ( - -