diff --git a/android/app/build.gradle b/android/app/build.gradle
index f0eddd6085bc..d85dda721838 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 1001037502
- versionName "1.3.75-2"
+ versionCode 1001037508
+ versionName "1.3.75-8"
}
flavorDimensions "default"
diff --git a/docs/Gemfile b/docs/Gemfile
index 7cad729ee45b..701ae50ca381 100644
--- a/docs/Gemfile
+++ b/docs/Gemfile
@@ -32,3 +32,6 @@ gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby]
gem "webrick", "~> 1.7"
gem 'jekyll-seo-tag'
+
+gem 'jekyll-redirect-from'
+
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index 1a5b26e2dc23..0963d3c73e6c 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -263,6 +263,7 @@ DEPENDENCIES
github-pages
http_parser.rb (~> 0.6.0)
jekyll-feed (~> 0.12)
+ jekyll-redirect-from
jekyll-seo-tag
tzinfo (~> 1.2)
tzinfo-data
@@ -270,4 +271,4 @@ DEPENDENCIES
webrick (~> 1.7)
BUNDLED WITH
- 2.4.3
+ 2.4.19
diff --git a/docs/_config.yml b/docs/_config.yml
index 114e562cae04..4a0ce8c053c5 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -17,3 +17,7 @@ exclude: [README.md, TEMPLATE.md, vendor]
plugins:
- jekyll-seo-tag
+ - jekyll-redirect-from
+
+whitelist:
+ - jekyll-redirect-from
diff --git a/docs/articles/expensify-classic/exports/Insights.md b/docs/articles/expensify-classic/exports/Insights.md
index 682c2a251228..6c71630015c5 100644
--- a/docs/articles/expensify-classic/exports/Insights.md
+++ b/docs/articles/expensify-classic/exports/Insights.md
@@ -1,6 +1,7 @@
---
title: Custom Reporting and Insights
description: How to get the most out of the Custom Reporing and Insights
+redirect_from: articles/other/Insights/
---
{% raw %}
diff --git a/docs/articles/expensify-classic/getting-started/Referral-Program.md b/docs/articles/expensify-classic/getting-started/Referral-Program.md
index 683e93d0277a..b4a2b4a7de74 100644
--- a/docs/articles/expensify-classic/getting-started/Referral-Program.md
+++ b/docs/articles/expensify-classic/getting-started/Referral-Program.md
@@ -1,6 +1,7 @@
---
title: Expensify Referral Program
description: Send your joining link, submit a receipt or invoice, and we'll pay you if your referral adopts Expensify.
+redirect_from: articles/other/Referral-Program/
---
diff --git a/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md
index b18531d43200..a8e1b0690b72 100644
--- a/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md
+++ b/docs/articles/expensify-classic/getting-started/approved-accountants/Card-Revenue-Share-For-Expensify-Approved-Partners.md
@@ -1,6 +1,7 @@
---
title: Expensify Card revenue share for ExpensifyApproved! partners
description: Earn money when your clients adopt the Expensify Card
+redirect_from: articles/other/Card-Revenue-Share-for-ExpensifyApproved!-Partners/
---
diff --git a/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md
index c7a5dc5a04ab..104cd49daf96 100644
--- a/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md
+++ b/docs/articles/expensify-classic/getting-started/approved-accountants/Your-Expensify-Partner-Manager.md
@@ -1,6 +1,7 @@
---
title: Your Expensify Partner Manager
description: Everything you need to know about your Expensify Partner Manager
+redirect_from: articles/other/Your-Expensify-Partner-Manager/
---
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 2b95a1d13fde..a7553e6ae179 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
@@ -1,6 +1,7 @@
---
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
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.
diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md
index 86c6a583c758..bef59546a13d 100644
--- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md
+++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md
@@ -1,6 +1,7 @@
---
title: Expensify Playbook for US-Based Bootstrapped Startups
description: Best practices for how to deploy Expensify for your business
+redirect_from: articles/playbooks/Expensify-Playbook-for-US-Based-Bootstrapped-Startups/
---
This playbook details best practices on how Bootstrapped Startups with less than 5 employees can use Expensify to prioritize product development while capturing business-related receipts for future reimbursement.
diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md
index 501d2f1538ef..bdce2cd7bf81 100644
--- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md
+++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-VC-Backed-Startups.md
@@ -1,6 +1,7 @@
---
title: Expensify Playbook for US-Based VC-Backed Startups
description: Best practices for how to deploy Expensify for your business
+redirect_from: articles/playbooks/Expensify-Playbook-for-US-based-VC-Backed-Startups/
---
This playbook details best practices on how Seed to Series A startups with under 100 employees can use Expensify to prioritize top-line revenue growth while managing spend responsibly.
diff --git a/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md b/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md
index 3ef47337a74c..a6fa0220c0dc 100644
--- a/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md
+++ b/docs/articles/expensify-classic/getting-started/support/Your-Expensify-Account-Manager.md
@@ -1,6 +1,7 @@
---
title: Your Expensify Account Manager
description: Everything you need to know about Having an Expensify account manager
+redirect_from: articles/other/Your-Expensify-Account-Manager/
---
diff --git a/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md b/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md
index 649212b00f7b..507d24503af8 100644
--- a/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md
+++ b/docs/articles/expensify-classic/getting-started/tips-and-tricks/Enable-Location-Access-On-Web.md
@@ -1,6 +1,7 @@
---
title: Enable Location Access on Web
description: How to enable location access for Expensify websites on your browser
+redirect_from: articles/other/Enable-Location-Access-on-Web/
---
diff --git a/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md
index 0a8d6b3493e0..e157ede1969d 100644
--- a/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md
+++ b/docs/articles/new-expensify/billing-and-plan-types/The-Free-Plan.md
@@ -1,6 +1,7 @@
---
title: The Free Plan
description: Everything you need to know about Expensify's Free Plan!
+redirect_from: articles/split-bills/workspaces/The-Free-Plan/
---
diff --git a/docs/articles/new-expensify/get-paid-back/Request-Money.md b/docs/articles/new-expensify/get-paid-back/Request-Money.md
index dc6de6656cc9..a2b765915af0 100644
--- a/docs/articles/new-expensify/get-paid-back/Request-Money.md
+++ b/docs/articles/new-expensify/get-paid-back/Request-Money.md
@@ -1,5 +1,6 @@
---
title: Request Money
description: Request Money
+redirect_from: articles/request-money/Request-and-Split-Bills/
---
## Resource Coming Soon!
diff --git a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md
index 01a2d7a9e250..bdccbe927769 100644
--- a/docs/articles/new-expensify/getting-started/Expensify-Lounge.md
+++ b/docs/articles/new-expensify/getting-started/Expensify-Lounge.md
@@ -1,6 +1,7 @@
---
title: Welcome to the Expensify Lounge!
description: How to get the most out of the Expensify Lounge.
+redirect_from: articles/other/Expensify-Lounge/
---
diff --git a/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md b/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md
index 9f73d1c759c2..77bbe54e8e2c 100644
--- a/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md
+++ b/docs/articles/new-expensify/getting-started/chat/Everything-About-Chat.md
@@ -1,6 +1,7 @@
---
title: Everything About Chat
description: Everything you need to know about Expensify's Chat Features!
+redirect_from: articles/other/Everything-About-Chat/
---
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 31de150d5b5e..996d7896502f 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
@@ -1,6 +1,7 @@
---
title: Expensify Chat for Admins
description: Best Practices for Admins settings up Expensify Chat
+redirect_from: articles/other/Expensify-Chat-For-Admins/
---
## Overview
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 3d30237dca5a..20e15aaa6c72 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
@@ -1,6 +1,7 @@
---
title: Expensify Chat for Conference Attendees
description: Best Practices for Conference Attendees
+redirect_from: articles/other/Expensify-Chat-For-Conference-Attendees/
---
## Overview
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 5bd52425d92b..3e19cf6fe26a 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
@@ -1,6 +1,7 @@
---
title: Expensify Chat for Conference Speakers
description: Best Practices for Conference Speakers
+redirect_from: articles/other/Expensify-Chat-For-Conference-Speakers/
---
## Overview
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 8f806bb03146..a81aef2044a2 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
@@ -1,6 +1,7 @@
---
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
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.
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 8536b4da82b5..fb13a410dd8e 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.3.75.2
+ 1.3.75.8
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 802ee97145ae..2168da376988 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.3.75.2
+ 1.3.75.8
diff --git a/package-lock.json b/package-lock.json
index 97b6f6ea7b38..e31fae9ded6d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.3.75-2",
+ "version": "1.3.75-8",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.3.75-2",
+ "version": "1.3.75-8",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 8804c2002a10..6f00a174971e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.3.75-2",
+ "version": "1.3.75-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.",
diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js
index 44075a4ec1eb..e7b18bbd8d69 100755
--- a/src/components/Composer/index.js
+++ b/src/components/Composer/index.js
@@ -358,7 +358,7 @@ function Composer({
const paddingTopAndBottom = parseInt(computedStyle.paddingBottom, 10) + parseInt(computedStyle.paddingTop, 10);
setTextInputWidth(computedStyle.width);
- const computedNumberOfLines = ComposerUtils.getNumberOfLines(maxLines, lineHeight, paddingTopAndBottom, textInput.current.scrollHeight);
+ const computedNumberOfLines = ComposerUtils.getNumberOfLines(lineHeight, paddingTopAndBottom, textInput.current.scrollHeight, maxLines);
const generalNumberOfLines = computedNumberOfLines === 0 ? numberOfLinesProp : computedNumberOfLines;
onNumberOfLinesChange(generalNumberOfLines);
diff --git a/src/components/TextInput/BaseTextInput.js b/src/components/TextInput/BaseTextInput.js
index b469f39c7037..bd6548607cb9 100644
--- a/src/components/TextInput/BaseTextInput.js
+++ b/src/components/TextInput/BaseTextInput.js
@@ -24,8 +24,8 @@ import useNativeDriver from '../../libs/useNativeDriver';
import * as Browser from '../../libs/Browser';
function BaseTextInput(props) {
- const inputValue = props.value || props.defaultValue || '';
- const initialActiveLabel = props.forceActiveLabel || inputValue.length > 0 || Boolean(props.prefixCharacter);
+ const initialValue = props.value || props.defaultValue || '';
+ const initialActiveLabel = props.forceActiveLabel || initialValue.length > 0 || Boolean(props.prefixCharacter);
const [isFocused, setIsFocused] = useState(false);
const [passwordHidden, setPasswordHidden] = useState(props.secureTextEntry);
@@ -145,30 +145,16 @@ function BaseTextInput(props) {
[props.autoGrowHeight, props.multiline],
);
- useEffect(() => {
- // Handle side effects when the value gets changed programatically from the outside
-
- // In some cases, When the value prop is empty, it is not properly updated on the TextInput due to its uncontrolled nature, thus manually clearing the TextInput.
- if (inputValue === '') {
- input.current.clear();
- }
-
- if (inputValue) {
- activateLabel();
- }
- }, [activateLabel, inputValue]);
-
- // We capture whether the input has a value or not in a ref.
- // It gets updated when the text gets changed.
- const hasValueRef = useRef(inputValue.length > 0);
+ // The ref is needed when the component is uncontrolled and we don't have a value prop
+ const hasValueRef = useRef(initialValue.length > 0);
+ const inputValue = props.value || '';
+ const hasValue = inputValue.length > 0 || hasValueRef.current;
- // Activate or deactivate the label when the focus changes:
+ // Activate or deactivate the label when either focus changes, or for controlled
+ // components when the value prop changes:
useEffect(() => {
- // We can't use inputValue here directly, as it might contain
- // the defaultValue, which doesn't get updated when the text changes.
- // We can't use props.value either, as it might be undefined.
if (
- hasValueRef.current ||
+ hasValue ||
isFocused ||
// If the text has been supplied by Chrome autofill, the value state is not synced with the value
// as Chrome doesn't trigger a change event. When there is autofill text, keep the label activated.
@@ -178,7 +164,16 @@ function BaseTextInput(props) {
} else {
deactivateLabel();
}
- }, [activateLabel, deactivateLabel, inputValue, isFocused]);
+ }, [activateLabel, deactivateLabel, hasValue, isFocused]);
+
+ // When the value prop gets cleared externally, we need to keep the ref in sync:
+ useEffect(() => {
+ // Return early when component uncontrolled, or we still have a value
+ if (props.value === undefined || !_.isEmpty(props.value)) {
+ return;
+ }
+ hasValueRef.current = false;
+ }, [props.value]);
/**
* Set Value & activateLabel
@@ -192,9 +187,13 @@ function BaseTextInput(props) {
}
Str.result(props.onChangeText, value);
+
if (value && value.length > 0) {
hasValueRef.current = true;
- activateLabel();
+ // When the componment is uncontrolled, we need to manually activate the label:
+ if (props.value === undefined) {
+ activateLabel();
+ }
} else {
hasValueRef.current = false;
}
diff --git a/src/libs/ComposerUtils/debouncedSaveReportComment.js b/src/libs/ComposerUtils/debouncedSaveReportComment.js
deleted file mode 100644
index c39da78c2c3e..000000000000
--- a/src/libs/ComposerUtils/debouncedSaveReportComment.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import _ from 'underscore';
-import * as Report from '../actions/Report';
-
-/**
- * Save draft report comment. Debounced to happen at most once per second.
- * @param {String} reportID
- * @param {String} comment
- */
-const debouncedSaveReportComment = _.debounce((reportID, comment) => {
- Report.saveReportComment(reportID, comment || '');
-}, 1000);
-
-export default debouncedSaveReportComment;
diff --git a/src/libs/ComposerUtils/debouncedSaveReportComment.ts b/src/libs/ComposerUtils/debouncedSaveReportComment.ts
new file mode 100644
index 000000000000..e449245edc52
--- /dev/null
+++ b/src/libs/ComposerUtils/debouncedSaveReportComment.ts
@@ -0,0 +1,11 @@
+import debounce from 'lodash/debounce';
+import * as Report from '../actions/Report';
+
+/**
+ * Save draft report comment. Debounced to happen at most once per second.
+ */
+const debouncedSaveReportComment = debounce((reportID: string, comment = '') => {
+ Report.saveReportComment(reportID, comment);
+}, 1000);
+
+export default debouncedSaveReportComment;
diff --git a/src/libs/ComposerUtils/getDraftComment.js b/src/libs/ComposerUtils/getDraftComment.ts
similarity index 75%
rename from src/libs/ComposerUtils/getDraftComment.js
rename to src/libs/ComposerUtils/getDraftComment.ts
index 854df1ac65ee..ac3d2f3d09be 100644
--- a/src/libs/ComposerUtils/getDraftComment.js
+++ b/src/libs/ComposerUtils/getDraftComment.ts
@@ -1,7 +1,7 @@
-import Onyx from 'react-native-onyx';
+import Onyx, {OnyxEntry} from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';
-const draftCommentMap = {};
+const draftCommentMap: Record> = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT,
callback: (value, key) => {
@@ -18,9 +18,7 @@ Onyx.connect({
* Returns a draft comment from the onyx collection.
* Note: You should use the HOCs/hooks to get onyx data, instead of using this directly.
* A valid use case to use this is if the value is only needed once for an initial value.
- * @param {String} reportID
- * @returns {String|undefined}
*/
-export default function getDraftComment(reportID) {
+export default function getDraftComment(reportID: string): OnyxEntry {
return draftCommentMap[reportID];
}
diff --git a/src/libs/ComposerUtils/getNumberOfLines/index.native.js b/src/libs/ComposerUtils/getNumberOfLines/index.native.js
deleted file mode 100644
index ff4a1c6d74b1..000000000000
--- a/src/libs/ComposerUtils/getNumberOfLines/index.native.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
- * Get the current number of lines in the composer
- *
- * @param {Number} lineHeight
- * @param {Number} paddingTopAndBottom
- * @param {Number} scrollHeight
- *
- * @returns {Number}
- */
-function getNumberOfLines(lineHeight, paddingTopAndBottom, scrollHeight) {
- return Math.ceil((scrollHeight - paddingTopAndBottom) / lineHeight);
-}
-
-export default getNumberOfLines;
diff --git a/src/libs/ComposerUtils/getNumberOfLines/index.native.ts b/src/libs/ComposerUtils/getNumberOfLines/index.native.ts
new file mode 100644
index 000000000000..9a7340b9a035
--- /dev/null
+++ b/src/libs/ComposerUtils/getNumberOfLines/index.native.ts
@@ -0,0 +1,8 @@
+import GetNumberOfLines from './types';
+
+/**
+ * Get the current number of lines in the composer
+ */
+const getNumberOfLines: GetNumberOfLines = (lineHeight, paddingTopAndBottom, scrollHeight) => Math.ceil((scrollHeight - paddingTopAndBottom) / lineHeight);
+
+export default getNumberOfLines;
diff --git a/src/libs/ComposerUtils/getNumberOfLines/index.js b/src/libs/ComposerUtils/getNumberOfLines/index.ts
similarity index 55%
rename from src/libs/ComposerUtils/getNumberOfLines/index.js
rename to src/libs/ComposerUtils/getNumberOfLines/index.ts
index a469da7516bb..cf85b45443d5 100644
--- a/src/libs/ComposerUtils/getNumberOfLines/index.js
+++ b/src/libs/ComposerUtils/getNumberOfLines/index.ts
@@ -1,17 +1,12 @@
+import GetNumberOfLines from './types';
+
/**
* Get the current number of lines in the composer
- *
- * @param {Number} maxLines
- * @param {Number} lineHeight
- * @param {Number} paddingTopAndBottom
- * @param {Number} scrollHeight
- *
- * @returns {Number}
*/
-function getNumberOfLines(maxLines, lineHeight, paddingTopAndBottom, scrollHeight) {
+const getNumberOfLines: GetNumberOfLines = (lineHeight, paddingTopAndBottom, scrollHeight, maxLines = 0) => {
let newNumberOfLines = Math.ceil((scrollHeight - paddingTopAndBottom) / lineHeight);
newNumberOfLines = maxLines <= 0 ? newNumberOfLines : Math.min(newNumberOfLines, maxLines);
return newNumberOfLines;
-}
+};
export default getNumberOfLines;
diff --git a/src/libs/ComposerUtils/getNumberOfLines/types.ts b/src/libs/ComposerUtils/getNumberOfLines/types.ts
new file mode 100644
index 000000000000..67bb790f726b
--- /dev/null
+++ b/src/libs/ComposerUtils/getNumberOfLines/types.ts
@@ -0,0 +1,3 @@
+type GetNumberOfLines = (lineHeight: number, paddingTopAndBottom: number, scrollHeight: number, maxLines?: number) => number;
+
+export default GetNumberOfLines;
diff --git a/src/libs/ComposerUtils/index.js b/src/libs/ComposerUtils/index.ts
similarity index 69%
rename from src/libs/ComposerUtils/index.js
rename to src/libs/ComposerUtils/index.ts
index dfe6cf446809..5e2a42fc65dd 100644
--- a/src/libs/ComposerUtils/index.js
+++ b/src/libs/ComposerUtils/index.ts
@@ -2,24 +2,22 @@ import getNumberOfLines from './getNumberOfLines';
import updateNumberOfLines from './updateNumberOfLines';
import * as DeviceCapabilities from '../DeviceCapabilities';
+type Selection = {
+ start: number;
+ end: number;
+};
+
/**
* Replace substring between selection with a text.
- * @param {String} text
- * @param {Object} selection
- * @param {String} textToInsert
- * @returns {String}
*/
-function insertText(text, selection, textToInsert) {
+function insertText(text: string, selection: Selection, textToInsert: string): string {
return text.slice(0, selection.start) + textToInsert + text.slice(selection.end, text.length);
}
/**
* Check whether we can skip trigger hotkeys on some specific devices.
- * @param {Boolean} isSmallScreenWidth
- * @param {Boolean} isKeyboardShown
- * @returns {Boolean}
*/
-function canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown) {
+function canSkipTriggerHotkeys(isSmallScreenWidth: boolean, isKeyboardShown: boolean): boolean {
// Do not trigger actions for mobileWeb or native clients that have the keyboard open
// because for those devices, we want the return key to insert newlines rather than submit the form
return (isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen()) || isKeyboardShown;
@@ -30,11 +28,9 @@ function canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown) {
* The common suffix is the number of characters shared by both strings
* at the end (suffix) until a mismatch is encountered.
*
- * @param {string} str1
- * @param {string} str2
- * @returns {number} The length of the common suffix between the strings.
+ * @returns The length of the common suffix between the strings.
*/
-function getCommonSuffixLength(str1, str2) {
+function getCommonSuffixLength(str1: string, str2: string): number {
let i = 0;
while (str1[str1.length - 1 - i] === str2[str2.length - 1 - i]) {
i++;
diff --git a/src/libs/ComposerUtils/types.ts b/src/libs/ComposerUtils/types.ts
new file mode 100644
index 000000000000..a417d951ff51
--- /dev/null
+++ b/src/libs/ComposerUtils/types.ts
@@ -0,0 +1,6 @@
+type ComposerProps = {
+ isFullComposerAvailable: boolean;
+ setIsFullComposerAvailable: (isFullComposerAvailable: boolean) => void;
+};
+
+export default ComposerProps;
diff --git a/src/libs/ComposerUtils/updateIsFullComposerAvailable.js b/src/libs/ComposerUtils/updateIsFullComposerAvailable.ts
similarity index 66%
rename from src/libs/ComposerUtils/updateIsFullComposerAvailable.js
rename to src/libs/ComposerUtils/updateIsFullComposerAvailable.ts
index 00b12d1742e3..5d73619482db 100644
--- a/src/libs/ComposerUtils/updateIsFullComposerAvailable.js
+++ b/src/libs/ComposerUtils/updateIsFullComposerAvailable.ts
@@ -1,11 +1,11 @@
import CONST from '../../CONST';
+import ComposerProps from './types';
/**
* Update isFullComposerAvailable if needed
- * @param {Object} props
- * @param {Number} numberOfLines The number of lines in the text input
+ * @param numberOfLines The number of lines in the text input
*/
-function updateIsFullComposerAvailable(props, numberOfLines) {
+function updateIsFullComposerAvailable(props: ComposerProps, numberOfLines: number) {
const isFullComposerAvailable = numberOfLines >= CONST.COMPOSER.FULL_COMPOSER_MIN_LINES;
if (isFullComposerAvailable !== props.isFullComposerAvailable) {
props.setIsFullComposerAvailable(isFullComposerAvailable);
diff --git a/src/libs/ComposerUtils/updateNumberOfLines/index.js b/src/libs/ComposerUtils/updateNumberOfLines/index.js
deleted file mode 100644
index ff8b4c56321a..000000000000
--- a/src/libs/ComposerUtils/updateNumberOfLines/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export default {};
diff --git a/src/libs/ComposerUtils/updateNumberOfLines/index.native.js b/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts
similarity index 77%
rename from src/libs/ComposerUtils/updateNumberOfLines/index.native.js
rename to src/libs/ComposerUtils/updateNumberOfLines/index.native.ts
index 5a13ae670d81..b22135b4f767 100644
--- a/src/libs/ComposerUtils/updateNumberOfLines/index.native.js
+++ b/src/libs/ComposerUtils/updateNumberOfLines/index.native.ts
@@ -1,23 +1,21 @@
-import lodashGet from 'lodash/get';
import styles from '../../../styles/styles';
import updateIsFullComposerAvailable from '../updateIsFullComposerAvailable';
import getNumberOfLines from '../getNumberOfLines';
+import UpdateNumberOfLines from './types';
/**
* Check the current scrollHeight of the textarea (minus any padding) and
* divide by line height to get the total number of rows for the textarea.
- * @param {Object} props
- * @param {Event} e
*/
-function updateNumberOfLines(props, e) {
+const updateNumberOfLines: UpdateNumberOfLines = (props, event) => {
const lineHeight = styles.textInputCompose.lineHeight;
const paddingTopAndBottom = styles.textInputComposeSpacing.paddingVertical * 2;
- const inputHeight = lodashGet(e, 'nativeEvent.contentSize.height', null);
+ const inputHeight = event?.nativeEvent?.contentSize?.height ?? null;
if (!inputHeight) {
return;
}
const numberOfLines = getNumberOfLines(lineHeight, paddingTopAndBottom, inputHeight);
updateIsFullComposerAvailable(props, numberOfLines);
-}
+};
export default updateNumberOfLines;
diff --git a/src/libs/ComposerUtils/updateNumberOfLines/index.ts b/src/libs/ComposerUtils/updateNumberOfLines/index.ts
new file mode 100644
index 000000000000..91a9c9c0f102
--- /dev/null
+++ b/src/libs/ComposerUtils/updateNumberOfLines/index.ts
@@ -0,0 +1,5 @@
+import UpdateNumberOfLines from './types';
+
+const updateNumberOfLines: UpdateNumberOfLines = () => {};
+
+export default updateNumberOfLines;
diff --git a/src/libs/ComposerUtils/updateNumberOfLines/types.ts b/src/libs/ComposerUtils/updateNumberOfLines/types.ts
new file mode 100644
index 000000000000..c0650be25433
--- /dev/null
+++ b/src/libs/ComposerUtils/updateNumberOfLines/types.ts
@@ -0,0 +1,6 @@
+import {NativeSyntheticEvent, TextInputContentSizeChangeEventData} from 'react-native';
+import ComposerProps from '../types';
+
+type UpdateNumberOfLines = (props: ComposerProps, event: NativeSyntheticEvent) => void;
+
+export default UpdateNumberOfLines;
diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js
index 66008ae5ae2a..e21d9fdd75c6 100644
--- a/src/libs/actions/Report.js
+++ b/src/libs/actions/Report.js
@@ -1683,7 +1683,7 @@ function showReportActionNotification(reportID, reportAction) {
const notificationParams = {
report,
reportAction,
- onClick: () => Navigation.navigate(ROUTES.getReportRoute(reportID)),
+ onClick: () => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)),
};
if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) {
LocalNotification.showModifiedExpenseNotification(notificationParams);
diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js
index 91267b9b1053..971289e5b9e4 100644
--- a/src/libs/actions/Task.js
+++ b/src/libs/actions/Task.js
@@ -825,6 +825,15 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum
},
},
},
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`,
+ value: {
+ [parentReportAction.reportActionID]: {
+ pendingAction: null,
+ },
+ },
+ },
];
const failureData = [
@@ -843,6 +852,15 @@ function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum
[optimisticReportActionID]: null,
},
},
+ {
+ onyxMethod: Onyx.METHOD.MERGE,
+ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`,
+ value: {
+ [parentReportAction.reportActionID]: {
+ pendingAction: null,
+ },
+ },
+ },
];
API.write('CancelTask', {cancelledTaskReportActionID: optimisticReportActionID, taskReportID}, {optimisticData, successData, failureData});
diff --git a/src/libs/fileDownload/FileUtils.js b/src/libs/fileDownload/FileUtils.js
index cee2b8877ef6..e508d096128d 100644
--- a/src/libs/fileDownload/FileUtils.js
+++ b/src/libs/fileDownload/FileUtils.js
@@ -157,7 +157,7 @@ const readFileAsync = (path, fileName) =>
return res.blob();
})
.then((blob) => {
- const file = new File([blob], cleanFileName(fileName));
+ const file = new File([blob], cleanFileName(fileName), {type: blob.type});
file.source = path;
// For some reason, the File object on iOS does not have a uri property
// so images aren't uploaded correctly to the backend
diff --git a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js
index 4f09df7330ff..1c54bc572a34 100644
--- a/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js
+++ b/src/pages/home/report/ContextMenu/PopoverReportActionContextMenu.js
@@ -278,6 +278,7 @@ function PopoverReportActionContextMenu(_props, ref) {
instanceID,
runAndResetOnPopoverHide,
clearActiveReportAction,
+ contentRef,
}));
const reportAction = reportActionRef.current;
@@ -321,8 +322,6 @@ function PopoverReportActionContextMenu(_props, ref) {
onConfirm={confirmDeleteAndHideModal}
onCancel={hideDeleteModal}
onModalHide={() => {
- reportIDRef.current = '0';
- reportActionRef.current = {};
callbackWhenDeleteModalHide.current();
}}
prompt={translate('reportActionContextMenu.deleteConfirmation', {action: reportAction})}
diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js
index 4de4e9bb9148..128782093718 100644
--- a/src/pages/iou/ReceiptSelector/index.native.js
+++ b/src/pages/iou/ReceiptSelector/index.native.js
@@ -99,7 +99,6 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator})
const appState = useRef(AppState.currentState);
const iouType = lodashGet(route, 'params.iouType', '');
- const reportID = lodashGet(route, 'params.reportID', '');
const pageIndex = lodashGet(route, 'params.pageIndex', 1);
const {translate} = useLocalize();
@@ -223,13 +222,13 @@ function ReceiptSelector({route, report, iou, transactionID, isInTabNavigator})
return;
}
- IOU.navigateToNextPage(iou, iouType, reportID, report, route.path);
+ IOU.navigateToNextPage(iou, iouType, report, route.path);
})
.catch((error) => {
showCameraAlert();
Log.warn('Error taking photo', error);
});
- }, [flash, iouType, iou, report, reportID, translate, transactionID, route.path]);
+ }, [flash, iouType, iou, report, translate, transactionID, route.path]);
CameraPermission.getCameraPermissionStatus().then((permissionStatus) => {
setPermissions(permissionStatus);
diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.js b/src/pages/iou/steps/MoneyRequestAmountForm.js
index c4fc29957179..cc918e3ee3df 100644
--- a/src/pages/iou/steps/MoneyRequestAmountForm.js
+++ b/src/pages/iou/steps/MoneyRequestAmountForm.js
@@ -109,7 +109,7 @@ function MoneyRequestAmountForm({amount, currency, isEditing, forwardedRef, onCu
if (!currency || !_.isNumber(amount)) {
return;
}
- const amountAsStringForState = CurrencyUtils.convertToFrontendAmount(amount).toString();
+ const amountAsStringForState = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : '';
setCurrentAmount(amountAsStringForState);
setSelection({
start: amountAsStringForState.length,
diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts
index ad1b59ec2253..0832b6a3978c 100644
--- a/src/styles/StyleUtils.ts
+++ b/src/styles/StyleUtils.ts
@@ -678,10 +678,10 @@ function extractValuesFromRGB(color: string): number[] | null {
* @returns The theme color as an RGB value.
*/
function getThemeBackgroundColor(bgColor: string = themeColors.appBG): string {
- const backdropOpacity = variables.modalFullscreenBackdropOpacity;
+ const backdropOpacity = variables.overlayOpacity;
const [backgroundRed, backgroundGreen, backgroundBlue] = extractValuesFromRGB(bgColor) ?? hexadecimalToRGBArray(bgColor) ?? [];
- const [backdropRed, backdropGreen, backdropBlue] = hexadecimalToRGBArray(themeColors.modalBackdrop) ?? [];
+ const [backdropRed, backdropGreen, backdropBlue] = hexadecimalToRGBArray(themeColors.overlay) ?? [];
const normalizedBackdropRGB = convertRGBToUnitValues(backdropRed, backdropGreen, backdropBlue);
const normalizedBackgroundRGB = convertRGBToUnitValues(backgroundRed, backgroundGreen, backgroundBlue);
const [red, green, blue] = convertRGBAToRGB(normalizedBackdropRGB, normalizedBackgroundRGB, backdropOpacity);
diff --git a/src/styles/themes/default.js b/src/styles/themes/default.js
index 75db4be30e2b..ef64c4caec35 100644
--- a/src/styles/themes/default.js
+++ b/src/styles/themes/default.js
@@ -53,7 +53,6 @@ const darkTheme = {
textMutedReversed: colors.darkIcons,
textError: colors.red,
offline: colors.darkIcons,
- modalBackdrop: colors.darkHighlightBackground,
modalBackground: colors.darkAppBackground,
cardBG: colors.darkHighlightBackground,
cardBorder: colors.darkHighlightBackground,
diff --git a/src/styles/themes/light.js b/src/styles/themes/light.js
index 8bc149c5af08..c459f9f10da6 100644
--- a/src/styles/themes/light.js
+++ b/src/styles/themes/light.js
@@ -51,7 +51,6 @@ const lightTheme = {
textMutedReversed: colors.lightIcons,
textError: colors.red,
offline: colors.lightIcons,
- modalBackdrop: colors.lightHighlightBackground,
modalBackground: colors.lightAppBackground,
cardBG: colors.lightHighlightBackground,
cardBorder: colors.lightHighlightBackground,
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 9ee9b64e6467..a7191ce5b002 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -78,7 +78,6 @@ export default {
extraSmallMobileResponsiveWidthBreakpoint: 320,
extraSmallMobileResponsiveHeightBreakpoint: 667,
mobileResponsiveWidthBreakpoint: 800,
- modalFullscreenBackdropOpacity: 0.5,
tabletResponsiveWidthBreakpoint: 1024,
safeInsertPercentage: 0.7,
sideBarWidth: 375,