diff --git a/android/app/build.gradle b/android/app/build.gradle index 9e5a237afd1e..8527be5e433d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -98,8 +98,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001047404 - versionName "1.4.74-4" + versionCode 1001047500 + versionName "1.4.75-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/contributingGuides/CONTRIBUTING.md b/contributingGuides/CONTRIBUTING.md index 13f7592b65e1..aec527edabe0 100644 --- a/contributingGuides/CONTRIBUTING.md +++ b/contributingGuides/CONTRIBUTING.md @@ -95,9 +95,10 @@ Additionally, if you want to discuss an idea with the open source community with #### Propose a solution for the job 4. You can propose solutions on any issue at any time, but if you propose solutions to jobs before the `Help Wanted` label is applied, you do so at your own risk. Proposals will not be reviewed until the label is added and there is always a chance that we might not add the label or hire an external contributor for the job. -5. After you reproduce the issue, complete the [proposal template here](./PROPOSAL_TEMPLATE.md) and post it as a comment in the corresponding GitHub issue (linked in the Upwork job). +5. Contributors should **not** submit proposals on issues when they have assigned issues or PRs that are awaiting an action from them. If so, they will be in violation of Rule #1 (Get Shit Done) in our [Code of Conduct](https://github.com/Expensify/App/blob/main/CODE_OF_CONDUCT.md) and will receive a warning. Multiple warnings can lead to removal from the program. +6. After you reproduce the issue, complete the [proposal template here](./PROPOSAL_TEMPLATE.md) and post it as a comment in the corresponding GitHub issue (linked in the Upwork job). - Note: Before submitting a proposal on an issue, be sure to read any other existing proposals. ALL NEW PROPOSALS MUST BE DIFFERENT FROM EXISTING PROPOSALS. The *difference* should be important, meaningful or considerable. -6. Refrain from leaving additional comments until someone from the Contributor-Plus team and / or someone from Expensify provides feedback on your proposal (do not create a pull request yet). +7. Refrain from leaving additional comments until someone from the Contributor-Plus team and / or someone from Expensify provides feedback on your proposal (do not create a pull request yet). - Do not leave more than one proposal. - Do not make extensive changes to your current proposal until after it has been reviewed. - If you want to make an entirely new proposal or update an existing proposal, please go back and edit your original proposal, then post a new comment to the issue in this format to alert everyone that it has been updated: @@ -105,8 +106,8 @@ Additionally, if you want to discuss an idea with the open source community with ## Proposal [Updated](link to proposal) ``` -7. If your proposal is accepted by the Expensify engineer assigned to the issue, Expensify will hire you on Upwork and assign the GitHub issue to you. -8. Once hired, post a comment in the Github issue stating when you expect to have your PR ready for review. +8. If your proposal is accepted by the Expensify engineer assigned to the issue, Expensify will hire you on Upwork and assign the GitHub issue to you. +9. Once hired, post a comment in the Github issue stating when you expect to have your PR ready for review. #### Begin coding your solution in a pull request 9. When you are ready to start, fork the repository and create a new branch. diff --git a/docs/articles/expensify-classic/expenses/Referral-Program.md b/docs/articles/expensify-classic/expenses/Referral-Program.md deleted file mode 100644 index 24605dd17d3f..000000000000 --- a/docs/articles/expensify-classic/expenses/Referral-Program.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Earn money with Expensify referrals -description: Get paid with the Expensify referral program! Share your link, earn $250 per successful sign-up, and enjoy unlimited income potential. It’s that easy. -redirect_from: articles/other/Referral-Program/ ---- - - -# Earn money with Expensify referrals - -Picture this: You've found Expensify and it's transformed your approach to expense management and financial organization. You love it so much that you can't help but recommend it to friends, family, and colleagues. Wouldn’t it be nice if you could get rewarded just for spreading the word? - -With Expensify referrals, you can. Every time someone you invite to the platform signs up for a paid annual plan on Expensify, you’ll earn $250. Think of it as a thank-you gift from us to you! - -## How to get paid for Expensify referrals - -Here are a few easy ways to get paid for Expensify friend referrals: - -- Submit an expense report to your boss (even just one receipt!) -- Send an invoice to a client or customer -- Share your referral link with a friend - - To find your referral link, open your Expensify mobile app and go to **Settings > Refer a friend, earn cash! > Share invite link**. - -**If the person you referred commits to an annual subscription with two or more active users and makes two monthly payments, you’ll get $250. Cha-ching!** - -## Who can you refer? - -You can refer anyone who might benefit from Expensify. Seriously. Anybody. - -Know a small business owner? Refer them! An [accountant](https://use.expensify.com/accountants-program)? Refer them! A best friend from childhood who keeps losing paper receipts? Refer them! - -Plus, you can [refer an unlimited amount of new users](https://use.expensify.com/blog/earn-50000-by-referring-your-friends-to-expensify/) with the Expensify referral program, so your earning potential is truly sky-high. - -## Common questions about Expensify benefits - -Still have questions about the Expensify referral program? We’ve got answers. Check out our FAQ below. - -### How will I know if I am the first person to refer someone to Expensify? - -You’ll know if you’re the first person to refer someone to Expensify if we reach out to let you know that they’ve successfully adopted Expensify and have paid for two months of an annual subscription. - -Simply put, we check for the earliest recorded referrer of a member on the workspace, and if that’s you, then we’ll let you know. - -### My referral wasn’t counted! How can I appeal? - -If you think your Expensify friend referral wasn’t counted, please send a message to concierge@expensify.com with the email of the person you referred. Our team will review the referral and get back to you. - -## Share the Expensify love — and get paid in the process - -Who needs a side hustle when you have Expensify? With Expensify benefits, it’s not just about managing your expenses — it's about expanding your income too. Share your Expensify referral link now or send over an invoice to unlock unlimited earning potential. diff --git a/docs/articles/expensify-classic/expensify-card/Change-Expensify-Card-limit.md b/docs/articles/expensify-classic/expensify-card/Change-Expensify-Card-limit.md new file mode 100644 index 000000000000..81ce761f84f4 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Change-Expensify-Card-limit.md @@ -0,0 +1,25 @@ +--- +title: Change Expensify Card limit +description: Increase or decrease the limit for an Expensify Card or for a group +--- +
+ +You can set Expensify Card limits for each group in your organization, or you can set the limit per card. + +# Set a limit per card + +1. Hover over Settings, then click **Domains**. +2. Click the name of the domain. +3. Next to the card, click **Edit Limit**. +4. Ensure the Custom Smart Limit toggle is enabled to be able to set a specific card limit. Otherwise, the card limit will be determined by the limit set for the group that the employee is in. +5. In the Limit Amount field, enter the desired limit. If set to $0, the card will be disabled for use until the limit is increased. +6. Click **Save**. + +# Set a limit per group + +1. Hover over Settings, then click **Domains**. +2. Click the name of the domain. +3. Click the **Groups** tab on the left. +4. Click the Expensify Card Smart Limit field for the card and enter the desired limit. + +
diff --git a/docs/articles/expensify-classic/expensify-card/Deactivate-or-cancel-an-Expensify-Card.md b/docs/articles/expensify-classic/expensify-card/Deactivate-or-cancel-an-Expensify-Card.md new file mode 100644 index 000000000000..d7fa33221834 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Deactivate-or-cancel-an-Expensify-Card.md @@ -0,0 +1,28 @@ +--- +title: Deactivate or cancel an Expensify Card +description: Close an Expensify Card +--- +
+ +A cardholder or a Domain Admin can cancel an Expensify Card. You may want to cancel a card: +- To cancel an old Expensify Card after upgrading to the new Expensify Visa® Commercial Card +- After a fraudulent or suspicious charge +- After an employee leaves the company + +# Domain Admins + +To cancel an employee's Expensify Card as a Domain Admin, + +1. Hover over Settings, then click **Domains**. +2. Click the name of the domain. +3. Next to the card, click **Terminate**. + +# Cardholders + +To cancel an Expensify Card assigned to you, + +1. Hover over Settings, then click **Account**. +2. Click the **Credit Card Import** tab. +3. Click **Cancel** next to the card. + +
diff --git a/docs/articles/new-expensify/connections/Set-up-Xero-connection.md b/docs/articles/new-expensify/connections/Set-up-Xero-connection.md new file mode 100644 index 000000000000..73bff6ad5862 --- /dev/null +++ b/docs/articles/new-expensify/connections/Set-up-Xero-connection.md @@ -0,0 +1,102 @@ +--- +title: Set up Xero connection +description: Integrate Xero with Expensify +--- +
+ +{% include info.html %} +To use the Xero connection, you must have a Xero account and an Expensify Collect plan. +{% include end-info.html %} + +To set up your Xero connection, complete the 4 steps below. + +# Step 1: Connect Expensify to Xero + +
    +
  1. Click your profile image or icon in the bottom left menu.
  2. +
  3. Scroll down and click Workspaces in the left menu.
  4. +
  5. Select the workspace you want to connect to Xero.
  6. +
  7. Click More features in the left menu.
  8. +
  9. Scroll down to the Integrate section and enable the Accounting toggle.
  10. +
  11. Click Accounting in the left menu.
  12. +
  13. Click Set up to the right of Xero.
  14. +
  15. Enter your Xero login details to import your settings from Xero to Expensify.
  16. +
+ +# Step 2: Configure import settings + +The following steps help you determine how data will be imported from Xero to Expensify. + +
    +
  1. Under the Accounting settings for your workspace, click Import under the Xero connection.
  2. +
  3. Select an option for each of the following settings to determine what information will be imported from Xero into Expensify:
  4. + +
+ +# Step 3: Configure export settings +The following steps help you determine how data will be exported from Expensify to Xero. + +
    +
  1. Under the Accounting settings for your workspace, click Export under the Xero connection.
  2. +
  3. Review each of the following export settings:
  4. + +
+{% include info.html %} +- Other Workspace Admins will still be able to export to Xero. +- If you set different export accounts for individual company cards under your domain settings, then your Preferred Exporter must be a Domain Admin. +{% include end-info.html %} + +
    + +
+ +# Step 4: Configure advanced settings + +The following steps help you determine the advanced settings for your connection, like auto-sync. + +
    +
  1. Under the Accounting settings for your workspace, click Advanced under the Xero connection.
  2. +
  3. Select an option for each of the following settings:
  4. + +
+ +{% include faq-begin.md %} + +**How do I disconnect Xero from Expensify?** + +1. Click your profile image or icon in the bottom left menu. +2. Scroll down and click **Workspaces** in the left menu. +3. Select the workspace you want to disconnect from Xero. +4. Click **Accounting** in the left menu. +5. Click the three dot menu icon to the right of Xero and select **Disconnect**. +6. Click **Disconnect** to confirm. + +You will no longer see the imported options from Xero. +{% include faq-end.md %} + +
diff --git a/docs/articles/new-expensify/expenses/Approve-and-pay-expenses.md b/docs/articles/new-expensify/expenses/Approve-and-pay-expenses.md new file mode 100644 index 000000000000..0cf642c76e4c --- /dev/null +++ b/docs/articles/new-expensify/expenses/Approve-and-pay-expenses.md @@ -0,0 +1,72 @@ +--- +title: Approve and Pay Expenses +description: Approve, hold, or pay expenses submitted to you +--- +
+ +When expenses are sent to you for approval, you have the option to: +- Approve and pay the expenses. +- Hold the expenses if payment needs to be delayed or if the expenses require additional information before they can be approved. + +{% include info.html %} +If your workspace does not require expense approvals, or if the expense is sent to you by a friend, you will not need to approve the expense and instead can immediately pay the expense when you are ready. +{% include end-info.html %} + +# Approve expenses + +When someone sends an expense or a group of expenses to you for approval, you’ll receive the expense in Expensify Chat for the related workspace. Chats with new updates appear with a green dot to the right of the chat message. Concierge also sends you an email notification for the new expense. + +To approve an expense, + +1. Open the Expensify Chat thread for the expense. +2. Click the expense or group of expenses. +3. Review the expense details to ensure they are correct. Look at each receipt, the amount, the description, and any additional details. +4. Determine the next steps. + - **Approve**: When you’re satisfied with the expense, click **Approve**. + - **Handle holds**: If any of the expenses are on hold, you can choose to either approve only the expenses that are not on hold or approve the full amount, including any held expenses. + - **Request changes**: You can add a comment to the expense’s chat thread in your Expensify Chat inbox to request changes to the expense details. + +{% include info.html %} +Admins can modify an expense, if needed. +{% include end-info.html %} + +You’re now ready to pay the expense. + +# Hold an expense + +If you need to delay a payment or if you need more information on the expense before it can be approved, you can hold the expense. + +To hold an expense, + +1. Open the Expensify Chat thread for the expense. +2. Click the expense or group of expenses. +3. Click the three dot menu at the top right of the expense and select **Hold**. +4. Enter a reason for the delay. +5. Review the Hold Overview page and click **Got It**. + +When you’re ready, you can choose to: +- **Remove the hold**: Complete the steps above and select **Unhold**. +- **Approve the expense**: Complete the steps above for “Approve expenses.” +Once the expense has been approved, you can now pay the expense. + +{% include info.html %} +Held expenses will not be available for payment until they have been approved. +{% include end-info.html %} + +# Pay expenses + +Once you’ve approved an expense—or if the expense does not require approval—you’ll be able to pay it. + +{% include info.html %} +To pay expenses within Expensify, you’ll need to set up your Expensify Wallet. +{% include end-info.html %} + +To pay an expense, + +1. Open the Expensify Chat thread for the expense. +2. Click the expense or group of expenses. +3. Select a payment option. + - Click **Pay** to pay the full expense within Expensify. If the expenses contain one that has been held, the pay amount will only include the expenses that have not been held. Then you’ll select your payment method. + - Click **Pay Elsewhere** to indicate that a payment has been sent using a method outside of Expensify, such as cash or a check. This will label the expense as Paid. + +
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 9c9e78ef70b0..b541de448b1d 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.74 + 1.4.75 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.74.4 + 1.4.75.0 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 627c6dbf826c..bb98f8d05f2d 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.74 + 1.4.75 CFBundleSignature ???? CFBundleVersion - 1.4.74.4 + 1.4.75.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 477f83aa6d00..f003f5b35948 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 1.4.74 + 1.4.75 CFBundleVersion - 1.4.74.4 + 1.4.75.0 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d0155051fc3b..911d29e0ab78 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1731,7 +1731,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - RNCPicker (2.6.1): + - RNCPicker (2.7.6): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1831,7 +1831,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.69): + - RNLiveMarkdown (0.1.70): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1849,9 +1849,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.69) + - RNLiveMarkdown/common (= 0.1.70) - Yoga - - RNLiveMarkdown/common (0.1.69): + - RNLiveMarkdown/common (0.1.70): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2545,7 +2545,7 @@ SPEC CHECKSUMS: ReactCommon: 840a955d37b7f3358554d819446bffcf624b2522 RNAppleAuthentication: 0571c08da8c327ae2afc0261b48b4a515b0286a6 RNCClipboard: 081418ae3b391b1012c3f41d045e5e39f1beed71 - RNCPicker: a37026a67de0cf1a33ffe8722783527e3b18ea9f + RNCPicker: 106d11a1c159ce937009b2bd52db2bdb1577454f RNDeviceInfo: 449272e9faf2afe94a3fe2896d169e92277fffa8 RNDevMenu: 72807568fe4188bd4c40ce32675d82434b43c45d RNFBAnalytics: f76bfa164ac235b00505deb9fc1776634056898c @@ -2556,7 +2556,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: bfabd5938e5af5afc1e60e4e34286b17f8308184 + RNLiveMarkdown: 23250f3d64c9d5f82ff36c4733c03544af0222d2 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216 diff --git a/package-lock.json b/package-lock.json index a46198daa88a..26fa31a72568 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.74-4", + "version": "1.4.75-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.74-4", + "version": "1.4.75-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -34,7 +34,7 @@ "@react-native-firebase/crashlytics": "^12.3.0", "@react-native-firebase/perf": "^12.3.0", "@react-native-google-signin/google-signin": "^10.0.1", - "@react-native-picker/picker": "2.6.1", + "@react-native-picker/picker": "2.7.6", "@react-navigation/material-top-tabs": "^6.6.3", "@react-navigation/native": "6.1.12", "@react-navigation/stack": "6.3.29", @@ -56,7 +56,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#9a68635cdcef4c81593c0f816a007bc9c707d46a", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#1713f28214f0e7176c4fd13433fb0ea15491ebf9", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -100,7 +100,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.32", + "react-native-onyx": "2.0.41", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -125,7 +125,7 @@ "react-native-web-linear-gradient": "^1.1.2", "react-native-web-sound": "^0.1.3", "react-native-webview": "13.6.4", - "react-pdf": "^7.7.0", + "react-pdf": "^7.7.3", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", "react-webcam": "^7.1.1", @@ -238,7 +238,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "type-fest": "^4.10.2", - "typescript": "^5.3.2", + "typescript": "^5.4.5", "wait-port": "^0.2.9", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.5.0", @@ -8854,12 +8854,12 @@ } }, "node_modules/@react-native-picker/picker": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.6.1.tgz", - "integrity": "sha512-oJftvmLOj6Y6/bF4kPcK6L83yNBALGmqNYugf94BzP0FQGpHBwimVN2ygqkQ2Sn2ZU3pGUZMs0jV6+Gku2GyYg==", + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/@react-native-picker/picker/-/picker-2.7.6.tgz", + "integrity": "sha512-Cs3PxRmE2vu6TofM9vt9TV8ZYFOtEPSupNxwoorH9lpkKM9HGG8QwK2i29KOEoODpUbtudKHUTtqhMZSuX9pgA==", "peerDependencies": { - "react": ">=16", - "react-native": ">=0.57" + "react": "*", + "react-native": "*" } }, "node_modules/@react-native/assets-registry": { @@ -20301,8 +20301,8 @@ }, "node_modules/expensify-common": { "version": "1.0.0", - "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#9a68635cdcef4c81593c0f816a007bc9c707d46a", - "integrity": "sha512-9BHjM3kZs7/dil0oykEQFkEhXjVD5liTttmO7ZYtPZkl4j6g97mubY2p9lYpWwpkWckUfvU7nGuZQjahw9xSFA==", + "resolved": "git+ssh://git@github.com/Expensify/expensify-common.git#1713f28214f0e7176c4fd13433fb0ea15491ebf9", + "integrity": "sha512-uy1+axUTTuPKwAR06xNG/tGIJ+uaavmSQgKiNU7pQVR94ibNzDD2WESn2E7OEP9/QrHa61lfFlluTjFvvz5I8Q==", "license": "MIT", "dependencies": { "classnames": "2.5.0", @@ -31431,9 +31431,9 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.32", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.32.tgz", - "integrity": "sha512-tB9wqMJGTLOYfrfplRP+9aq5JdD8w/hV/OZsMAVH+ewbE1zLY8OymUsAsIFdF1v+cB8HhehP569JVLZmhm6bsg==", + "version": "2.0.41", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.41.tgz", + "integrity": "sha512-33r0sVBq7MV/GZwRneRt81uxgW8x3YG75VNJvThycB/dkCnGCfbxoVkZADVH3ET3jzfFXy9wnS06sZnZp78zMQ==", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", @@ -31959,9 +31959,9 @@ } }, "node_modules/react-pdf": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.1.tgz", - "integrity": "sha512-cbbf/PuRtGcPPw+HLhMI1f6NSka8OJgg+j/yPWTe95Owf0fK6gmVY7OXpTxMeh92O3T3K3EzfE0ML0eXPGwR5g==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-7.7.3.tgz", + "integrity": "sha512-a2VfDl8hiGjugpqezBTUzJHYLNB7IS7a2t7GD52xMI9xHg8LdVaTMsnM9ZlNmKadnStT/tvX5IfV0yLn+JvYmw==", "dependencies": { "clsx": "^2.0.0", "dequal": "^2.0.3", @@ -36029,9 +36029,10 @@ } }, "node_modules/typescript": { - "version": "5.3.3", + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "devOptional": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 1a449f42bece..5b9a8fdad14b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.74-4", + "version": "1.4.75-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -86,7 +86,7 @@ "@react-native-firebase/crashlytics": "^12.3.0", "@react-native-firebase/perf": "^12.3.0", "@react-native-google-signin/google-signin": "^10.0.1", - "@react-native-picker/picker": "2.6.1", + "@react-native-picker/picker": "2.7.6", "@react-navigation/material-top-tabs": "^6.6.3", "@react-navigation/native": "6.1.12", "@react-navigation/stack": "6.3.29", @@ -108,7 +108,7 @@ "date-fns-tz": "^2.0.0", "dom-serializer": "^0.2.2", "domhandler": "^4.3.0", - "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#9a68635cdcef4c81593c0f816a007bc9c707d46a", + "expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#1713f28214f0e7176c4fd13433fb0ea15491ebf9", "expo": "^50.0.3", "expo-av": "~13.10.4", "expo-image": "1.11.0", @@ -152,7 +152,7 @@ "react-native-linear-gradient": "^2.8.1", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.32", + "react-native-onyx": "2.0.41", "react-native-pager-view": "6.2.3", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -177,7 +177,7 @@ "react-native-web-linear-gradient": "^1.1.2", "react-native-web-sound": "^0.1.3", "react-native-webview": "13.6.4", - "react-pdf": "^7.7.0", + "react-pdf": "^7.7.3", "react-plaid-link": "3.3.2", "react-web-config": "^1.0.0", "react-webcam": "^7.1.1", @@ -290,7 +290,7 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "type-fest": "^4.10.2", - "typescript": "^5.3.2", + "typescript": "^5.4.5", "wait-port": "^0.2.9", "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.5.0", @@ -330,4 +330,4 @@ "node": "20.13.0", "npm": "10.5.2" } -} +} \ No newline at end of file diff --git a/src/CONST.ts b/src/CONST.ts index 4ef782c75506..5f3ef8e12162 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -74,7 +74,6 @@ type OnboardingPurposeType = ValueOf; const CONST = { RECENT_WAYPOINTS_NUMBER: 20, - MERGED_ACCOUNT_PREFIX: 'MERGED_', DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL], // Note: Group and Self-DM excluded as these are not tied to a Workspace @@ -167,6 +166,9 @@ const CONST = { PULL_REQUEST_NUMBER, + // Regex to get link in href prop inside of component + REGEX_LINK_IN_ANCHOR: /]*?\s+)?href="([^"]*)"/gi, + MERCHANT_NAME_MAX_LENGTH: 255, REQUEST_PREVIEW: { @@ -3736,6 +3738,7 @@ const CONST = { WELCOME_VIDEO_URL: `${CLOUDFRONT_URL}/videos/intro-1280.mp4`, + ONBOARDING_INTRODUCTION: 'Let’s get you set up 🔧', ONBOARDING_CHOICES: {...onboardingChoices}, ONBOARDING_CONCIERGE: { diff --git a/src/components/AddressForm.tsx b/src/components/AddressForm.tsx index 9ad4643e834a..296ecce7d092 100644 --- a/src/components/AddressForm.tsx +++ b/src/components/AddressForm.tsx @@ -4,7 +4,6 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import type {MaybePhraseKey} from '@libs/Localize'; -import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; import CONST from '@src/CONST'; import type {Country} from '@src/CONST'; @@ -149,8 +148,6 @@ function AddressForm({ label={translate('common.addressLine', {lineNumber: 1})} onValueChange={(data: unknown, key: unknown) => { onAddressChanged(data, key); - // This enforces the country selector to use the country from address instead of the country from URL - Navigation.setParams({country: undefined}); }} defaultValue={street1} renamedInputKeys={{ diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index fb6a8e911e87..de98ba79d23c 100644 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -607,7 +607,6 @@ export default withOnyx({ const transactionID = parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? parentReportAction?.originalMessage.IOUTransactionID ?? '0' : '0'; return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`; }, - initWithStoredValues: false, }, })(memo(AttachmentModal)); diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 8942bf97a7dd..156c27abbc9d 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -1,6 +1,6 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -12,7 +12,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, Policy, Report, ReportActions} from '@src/types/onyx'; import DisplayNames from './DisplayNames'; import MultipleAvatars from './MultipleAvatars'; import ParentNavigationSubtitle from './ParentNavigationSubtitle'; @@ -25,7 +25,7 @@ type AvatarWithDisplayNamePropsWithOnyx = { parentReportActions: OnyxEntry; /** Personal details of all users */ - personalDetails: OnyxCollection; + personalDetails: OnyxEntry; }; type AvatarWithDisplayNameProps = AvatarWithDisplayNamePropsWithOnyx & { diff --git a/src/components/KeyboardAvoidingView/index.ios.tsx b/src/components/KeyboardAvoidingView/index.ios.tsx index a7cd767377ef..171210eab7ac 100644 --- a/src/components/KeyboardAvoidingView/index.ios.tsx +++ b/src/components/KeyboardAvoidingView/index.ios.tsx @@ -3,7 +3,7 @@ */ import React from 'react'; import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native'; -import type KeyboardAvoidingViewProps from './types'; +import type {KeyboardAvoidingViewProps} from './types'; function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/KeyboardAvoidingView/index.tsx b/src/components/KeyboardAvoidingView/index.tsx index 09ec21e5b219..c0882ae1e9cc 100644 --- a/src/components/KeyboardAvoidingView/index.tsx +++ b/src/components/KeyboardAvoidingView/index.tsx @@ -3,7 +3,7 @@ */ import React from 'react'; import {View} from 'react-native'; -import type KeyboardAvoidingViewProps from './types'; +import type {KeyboardAvoidingViewProps} from './types'; function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { const {behavior, contentContainerStyle, enabled, keyboardVerticalOffset, ...rest} = props; diff --git a/src/components/KeyboardAvoidingView/types.ts b/src/components/KeyboardAvoidingView/types.ts index 48d354e8b53f..2c1ef64ced8f 100644 --- a/src/components/KeyboardAvoidingView/types.ts +++ b/src/components/KeyboardAvoidingView/types.ts @@ -1,3 +1,4 @@ -import {KeyboardAvoidingViewProps} from 'react-native'; +import type {KeyboardAvoidingViewProps} from 'react-native'; -export default KeyboardAvoidingViewProps; +// eslint-disable-next-line import/prefer-default-export +export type {KeyboardAvoidingViewProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 6bb900ecb489..b0418c9fdb28 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -58,13 +58,24 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], ); - if (!optionItem) { + if (!optionItem && !isFocused) { // rendering null as a render item causes the FlashList to render all - // its children and consume signficant memory. We can avoid this by - // rendering a placeholder view instead. + // its children and consume signficant memory on the first render. We can avoid this by + // rendering a placeholder view instead. This behaviour is only observed when we + // first sign in to the App. + // We can fix this by checking if the optionItem is null and the component is not focused. + // Which means that the currentReportID is not the same as the reportID. The currentReportID + // in this case is empty and hence the component is not focused. return ; } + if (!optionItem) { + // This is the case when the component is focused and the optionItem is null. + // For example, when you submit an expense in offline mode and click on the + // generated expense report, we would only see the Report Details but no item in LHN. + return null; + } + const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; const shouldShowGreenDotIndicator = !hasBrickError && ReportUtils.requiresAttentionFromCurrentUser(optionItem, optionItem.parentReportAction); const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 4eb3be871a8d..c9e73a125fee 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -13,6 +13,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; +import * as TransactionActions from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -125,9 +126,10 @@ function MoneyReportHeader({ const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); + const shouldShowMarkAsCashButton = isDraft && allHavePendingRTERViolation; const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation; - const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep; + const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); @@ -169,6 +171,16 @@ function MoneyReportHeader({ setIsDeleteRequestModalVisible(false); }, [moneyRequestReport?.reportID, requestParentReportAction, setIsDeleteRequestModalVisible]); + const markAsCash = useCallback(() => { + if (!requestParentReportAction) { + return; + } + const iouTransactionID = requestParentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? requestParentReportAction.originalMessage?.IOUTransactionID ?? '' : ''; + const reportID = transactionThreadReport?.reportID ?? ''; + + TransactionActions.markAsCash(iouTransactionID, reportID); + }, [requestParentReportAction, transactionThreadReport?.reportID]); + // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on const isWaitingForSubmissionFromCurrentUser = useMemo( () => chatReport?.isOwnPolicyExpenseChat && !policy?.harvesting?.enabled, @@ -249,20 +261,44 @@ function MoneyReportHeader({ /> )} + {shouldShowMarkAsCashButton && !shouldUseNarrowLayout && ( + +