diff --git a/.github/actions/javascript/authorChecklist/index.js b/.github/actions/javascript/authorChecklist/index.js
index 528a0a11498a..e267769dc457 100644
--- a/.github/actions/javascript/authorChecklist/index.js
+++ b/.github/actions/javascript/authorChecklist/index.js
@@ -283,14 +283,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -325,11 +325,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -359,7 +359,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/awaitStagingDeploys/index.js b/.github/actions/javascript/awaitStagingDeploys/index.js
index f042dbb38a91..dd2aef38e1ee 100644
--- a/.github/actions/javascript/awaitStagingDeploys/index.js
+++ b/.github/actions/javascript/awaitStagingDeploys/index.js
@@ -395,14 +395,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -437,11 +437,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -471,7 +471,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/checkDeployBlockers/index.js b/.github/actions/javascript/checkDeployBlockers/index.js
index 8e10f8b1d8b6..82092be7e0eb 100644
--- a/.github/actions/javascript/checkDeployBlockers/index.js
+++ b/.github/actions/javascript/checkDeployBlockers/index.js
@@ -362,14 +362,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -404,11 +404,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -438,7 +438,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js
index 4441348a3c36..1752ae62f86c 100644
--- a/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js
+++ b/.github/actions/javascript/createOrUpdateStagingDeploy/createOrUpdateStagingDeploy.js
@@ -40,8 +40,11 @@ async function run() {
 
         // Next, we generate the checklist body
         let checklistBody = '';
+        let checklistAssignees = [];
         if (shouldCreateNewDeployChecklist) {
-            checklistBody = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
+            const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
+            checklistBody = issueBody;
+            checklistAssignees = issueAssignees;
         } else {
             // Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs
             const PRList = _.reduce(
@@ -94,7 +97,7 @@ async function run() {
             }
 
             const didVersionChange = newVersionTag !== currentChecklistData.tag;
-            checklistBody = await GithubUtils.generateStagingDeployCashBody(
+            const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBody(
                 newVersionTag,
                 _.pluck(PRList, 'url'),
                 _.pluck(_.where(PRList, {isVerified: true}), 'url'),
@@ -105,6 +108,8 @@ async function run() {
                 didVersionChange ? false : currentChecklistData.isFirebaseChecked,
                 didVersionChange ? false : currentChecklistData.isGHStatusChecked,
             );
+            checklistBody = issueBody;
+            checklistAssignees = issueAssignees;
         }
 
         // Finally, create or update the checklist
@@ -119,7 +124,7 @@ async function run() {
                 ...defaultPayload,
                 title: `Deploy Checklist: New Expensify ${format(new Date(), CONST.DATE_FORMAT_STRING)}`,
                 labels: [CONST.LABELS.STAGING_DEPLOY],
-                assignees: [CONST.APPLAUSE_BOT],
+                assignees: [CONST.APPLAUSE_BOT].concat(checklistAssignees),
             });
             console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`);
             return newChecklist;
diff --git a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
index 154dacbdc3c3..9c9a42709af0 100644
--- a/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
+++ b/.github/actions/javascript/createOrUpdateStagingDeploy/index.js
@@ -49,8 +49,11 @@ async function run() {
 
         // Next, we generate the checklist body
         let checklistBody = '';
+        let checklistAssignees = [];
         if (shouldCreateNewDeployChecklist) {
-            checklistBody = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
+            const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBody(newVersionTag, _.map(mergedPRs, GithubUtils.getPullRequestURLFromNumber));
+            checklistBody = issueBody;
+            checklistAssignees = issueAssignees;
         } else {
             // Generate the updated PR list, preserving the previous state of `isVerified` for existing PRs
             const PRList = _.reduce(
@@ -103,7 +106,7 @@ async function run() {
             }
 
             const didVersionChange = newVersionTag !== currentChecklistData.tag;
-            checklistBody = await GithubUtils.generateStagingDeployCashBody(
+            const {issueBody, issueAssignees} = await GithubUtils.generateStagingDeployCashBody(
                 newVersionTag,
                 _.pluck(PRList, 'url'),
                 _.pluck(_.where(PRList, {isVerified: true}), 'url'),
@@ -114,6 +117,8 @@ async function run() {
                 didVersionChange ? false : currentChecklistData.isFirebaseChecked,
                 didVersionChange ? false : currentChecklistData.isGHStatusChecked,
             );
+            checklistBody = issueBody;
+            checklistAssignees = issueAssignees;
         }
 
         // Finally, create or update the checklist
@@ -128,7 +133,7 @@ async function run() {
                 ...defaultPayload,
                 title: `Deploy Checklist: New Expensify ${format(new Date(), CONST.DATE_FORMAT_STRING)}`,
                 labels: [CONST.LABELS.STAGING_DEPLOY],
-                assignees: [CONST.APPLAUSE_BOT],
+                assignees: [CONST.APPLAUSE_BOT].concat(checklistAssignees),
             });
             console.log(`Successfully created new StagingDeployCash! 🎉 ${newChecklist.html_url}`);
             return newChecklist;
@@ -434,14 +439,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -476,11 +481,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -510,7 +515,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/getArtifactInfo/index.js b/.github/actions/javascript/getArtifactInfo/index.js
index ea56ff5f4ebd..e4f7634bd849 100644
--- a/.github/actions/javascript/getArtifactInfo/index.js
+++ b/.github/actions/javascript/getArtifactInfo/index.js
@@ -321,14 +321,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -363,11 +363,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -397,7 +397,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/getDeployPullRequestList/index.js b/.github/actions/javascript/getDeployPullRequestList/index.js
index f272929d536a..f941c9524856 100644
--- a/.github/actions/javascript/getDeployPullRequestList/index.js
+++ b/.github/actions/javascript/getDeployPullRequestList/index.js
@@ -377,14 +377,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -419,11 +419,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -453,7 +453,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/getPullRequestDetails/index.js b/.github/actions/javascript/getPullRequestDetails/index.js
index b8d7d821d64e..f4168af28802 100644
--- a/.github/actions/javascript/getPullRequestDetails/index.js
+++ b/.github/actions/javascript/getPullRequestDetails/index.js
@@ -329,14 +329,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -371,11 +371,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -405,7 +405,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/getReleaseBody/index.js b/.github/actions/javascript/getReleaseBody/index.js
index cc1321ce5cd5..547aafe23038 100644
--- a/.github/actions/javascript/getReleaseBody/index.js
+++ b/.github/actions/javascript/getReleaseBody/index.js
@@ -329,14 +329,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -371,11 +371,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -405,7 +405,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/isStagingDeployLocked/index.js b/.github/actions/javascript/isStagingDeployLocked/index.js
index 8124c5795a5a..4938b5bb7745 100644
--- a/.github/actions/javascript/isStagingDeployLocked/index.js
+++ b/.github/actions/javascript/isStagingDeployLocked/index.js
@@ -313,14 +313,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -355,11 +355,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -389,7 +389,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js
index 36cd0aaefe4a..2e6ab7e018dd 100644
--- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js
+++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js
@@ -478,14 +478,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -520,11 +520,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -554,7 +554,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/postTestBuildComment/index.js b/.github/actions/javascript/postTestBuildComment/index.js
index 329e0d3aad5d..9dd23d68ca0a 100644
--- a/.github/actions/javascript/postTestBuildComment/index.js
+++ b/.github/actions/javascript/postTestBuildComment/index.js
@@ -388,14 +388,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -430,11 +430,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -464,7 +464,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/reopenIssueWithComment/index.js b/.github/actions/javascript/reopenIssueWithComment/index.js
index 6a5f89badb5e..42196053f63f 100644
--- a/.github/actions/javascript/reopenIssueWithComment/index.js
+++ b/.github/actions/javascript/reopenIssueWithComment/index.js
@@ -283,14 +283,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -325,11 +325,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -359,7 +359,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/reviewerChecklist/index.js b/.github/actions/javascript/reviewerChecklist/index.js
index 322b529b89bf..22335b36bd2b 100644
--- a/.github/actions/javascript/reviewerChecklist/index.js
+++ b/.github/actions/javascript/reviewerChecklist/index.js
@@ -283,14 +283,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -325,11 +325,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -359,7 +359,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/actions/javascript/verifySignedCommits/index.js b/.github/actions/javascript/verifySignedCommits/index.js
index ba188d3a2b86..239f20c9d258 100644
--- a/.github/actions/javascript/verifySignedCommits/index.js
+++ b/.github/actions/javascript/verifySignedCommits/index.js
@@ -283,14 +283,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -325,11 +325,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -359,7 +359,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/.github/libs/GithubUtils.js b/.github/libs/GithubUtils.js
index 0cd407c78153..e988167850ec 100644
--- a/.github/libs/GithubUtils.js
+++ b/.github/libs/GithubUtils.js
@@ -250,14 +250,14 @@ class GithubUtils {
             .then((data) => {
                 // The format of this map is following:
                 // {
-                //    'https://github.com/Expensify/App/pull/9641': [ 'PauloGasparSv', 'kidroca' ],
-                //    'https://github.com/Expensify/App/pull/9642': [ 'mountiny', 'kidroca' ]
+                //    'https://github.com/Expensify/App/pull/9641': 'PauloGasparSv',
+                //    'https://github.com/Expensify/App/pull/9642': 'mountiny'
                 // }
                 const internalQAPRMap = _.reduce(
                     _.filter(data, (pr) => !_.isEmpty(_.findWhere(pr.labels, {name: CONST.LABELS.INTERNAL_QA}))),
                     (map, pr) => {
                         // eslint-disable-next-line no-param-reassign
-                        map[pr.html_url] = _.compact(_.pluck(pr.assignees, 'login'));
+                        map[pr.html_url] = pr.merged_by.login;
                         return map;
                     },
                     {},
@@ -292,11 +292,11 @@ class GithubUtils {
                 if (!_.isEmpty(internalQAPRMap)) {
                     console.log('Found the following verified Internal QA PRs:', resolvedInternalQAPRs);
                     issueBody += '**Internal QA:**\r\n';
-                    _.each(internalQAPRMap, (assignees, URL) => {
-                        const assigneeMentions = _.reduce(assignees, (memo, assignee) => `${memo} @${assignee}`, '');
+                    _.each(internalQAPRMap, (merger, URL) => {
+                        const mergerMention = `@${merger}`;
                         issueBody += `${_.contains(resolvedInternalQAPRs, URL) ? '- [x]' : '- [ ]'} `;
                         issueBody += `${URL}`;
-                        issueBody += ` -${assigneeMentions}`;
+                        issueBody += ` - ${mergerMention}`;
                         issueBody += '\r\n';
                     });
                     issueBody += '\r\n\r\n';
@@ -326,7 +326,9 @@ class GithubUtils {
                 issueBody += `\r\n- [${isGHStatusChecked ? 'x' : ' '}] I checked [GitHub Status](https://www.githubstatus.com/) and verified there is no reported incident with Actions.`;
 
                 issueBody += '\r\n\r\ncc @Expensify/applauseleads\r\n';
-                return issueBody;
+                const issueAssignees = _.values(internalQAPRMap);
+                const issue = {issueBody, issueAssignees};
+                return issue;
             })
             .catch((err) => console.warn('Error generating StagingDeployCash issue body! Continuing...', err));
     }
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 73e8e61111dc..812f1008777d 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 1001044800
-        versionName "1.4.48-0"
+        versionCode 1001044903
+        versionName "1.4.49-3"
     }
 
     flavorDimensions "default"
diff --git a/android/build.gradle b/android/build.gradle
index 7b5dd81e5bf1..10600480d8bb 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -44,6 +44,9 @@ allprojects {
             force "com.facebook.react:react-native:" + REACT_NATIVE_VERSION
             force "com.facebook.react:hermes-engine:" + REACT_NATIVE_VERSION
 
+            //Fix Investigate App Crash MainActivity.onCreate #35655
+            force "com.facebook.soloader:soloader:0.10.4+"
+
             eachDependency { dependency ->
                 if (dependency.requested.group == 'org.bouncycastle') {
                     println dependency.requested.module
diff --git a/assets/images/document-slash.svg b/assets/images/document-slash.svg
new file mode 100644
index 000000000000..25a4c96038b4
--- /dev/null
+++ b/assets/images/document-slash.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
+    <path d="M18 13.9861V7.5C18 7.22386 17.7761 7 17.5 7H11.5C11.2239 7 11 6.77614 11 6.5V0.5C11 0.223858 10.7761 0 10.5 0H4C3.52923 0 3.09644 0.162656 2.75478 0.434835L18 13.9861Z"/>
+    <path d="M2 7.79165V18C2 19.1046 2.89543 20 4 20H15.7344L2 7.79165Z"/>
+    <path d="M13 0L18 5H13.5C13.2239 5 13 4.77614 13 4.5V0Z"/>
+    <path d="M1.66437 2.2526C1.25159 1.88568 0.619519 1.92286 0.252601 2.33565C-0.114317 2.74843 -0.0771359 3.3805 0.335647 3.74742L18.3356 19.7474C18.7484 20.1143 19.3805 20.0772 19.7474 19.6644C20.1143 19.2516 20.0772 18.6195 19.6644 18.2526L1.66437 2.2526Z"/>
+</svg>
diff --git a/assets/images/simple-illustrations/simple-illustration__car-ice.svg b/assets/images/simple-illustrations/simple-illustration__car-ice.svg
new file mode 100644
index 000000000000..ba2b79bca6aa
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__car-ice.svg
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 28.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 68 68" style="enable-background:new 0 0 68 68;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#4ED7DE;}
+	.st1{fill:#4BA6A6;}
+	.st2{fill:#28736D;}
+	.st3{fill:#FFED8F;}
+	.st4{fill:none;stroke:#002140;stroke-width:0.8571;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+	<path class="st0" d="M49.708,27.953c0,0-3.324-9.358-5.152-9.753c-1.828-0.395-11.801-2.123-20.018-0.156
+	c0,0-3.065,2.99-4.107,5.504c-1.042,2.517-1.564,4.484-1.564,4.484s-3.52,2.123-4.565,5.504c-1.042,3.381-0.391,11.247-0.391,11.247
+	s0.846,0.786,2.414,1.259c1.568,0.473,6.066,1.102,8.15,1.102s23.151-0.157,23.151-0.157l6.389-1.572c0,0,0.914-1.337,0.978-2.279
+	c0.064-0.942-0.26-8.494-0.26-8.494s-0.846-2.752-1.5-3.46c-0.654-0.708-3.52-3.225-3.52-3.225L49.708,27.953z"/>
+	<path class="st1" d="M18.931,27.563l-1.109-2.752l-2.478,0.708l-0.391,1.415l1.305,0.786L18.931,27.563z"/>
+	<path class="st1" d="M50.035,27.717l4.107-0.473v-1.021l-0.782-1.102l-2.542-0.235L50.035,27.717z"/>
+	<path class="st2" d="M23.496,46.751l21.974,0.078l-2.087-1.888H24.865L23.496,46.751z"/>
+	<path class="st2" d="M15.475,46.045l0.914,3.616l1.301,0.868l4.37-0.316l0.914-1.259l0.523-2.201L15.475,46.045z"/>
+	<path class="st2" d="M45.601,47.304l0.519,2.436l1.177,0.868l4.889-0.078l1.042-1.415l0.327-3.147l-8.086,0.864"/>
+	<path class="st3" d="M20.754,34.404l-2.997-2.044l-1.045,0.942l-0.26,2.283l0.978,1.02l3.129,1.259l1.305-1.099L20.754,34.404z"/>
+	<path class="st3" d="M51.6,32.514l-0.914-0.469l-2.87,2.279l-0.846,2.044l0.523,0.864l1.693-0.156l3.001-1.65L51.6,32.514z"/>
+	<path class="st3" d="M22.319,27.324l0.914,0.629h22.76l0.391-1.024l-3.001-6.294c0,0-5.479-1.181-9.977-1.024
+	c-4.498,0.156-8.477,1.102-8.477,1.102l-2.61,6.606V27.324z"/>
+	<path class="st4" d="M44.265,18.161c-4.27-1.376-13.728-1.849-20.05,0"/>
+	<path class="st4" d="M24.21,18.16c0,0-2.837,1.653-5.085,9.675c0,0-5.38,2.83-5.38,8.612c0,0-0.391,3.893,19.759,3.893
+	s21.423-3.776,21.423-3.776c0-5.782-5.38-8.612-5.38-8.612c-2.251-8.021-5.085-9.675-5.085-9.675"/>
+	<path class="st4" d="M13.747,36.447v6.923c0,1.241,0.757,2.29,1.774,2.468l6.23,1.074c0.572,0.1,1.152-0.096,1.6-0.537l0.878-0.868
+	c0.37-0.363,0.832-0.562,1.309-0.562h9.241"/>
+	<path class="st4" d="M54.906,36.434v6.923c0,1.241-0.757,2.29-1.774,2.468l-6.229,1.074c-0.573,0.099-1.152-0.096-1.6-0.537
+	l-0.878-0.868c-0.37-0.363-0.832-0.562-1.308-0.562h-9.241"/>
+	<path class="st4" d="M22.433,46.891l23.787-0.014"/>
+	<path class="st4" d="M15.521,45.838l0.412,2.667c0.178,1.148,1.01,1.984,1.981,1.984h3.264c0.946,0,1.764-0.793,1.966-1.906
+	l0.288-1.575"/>
+	<path class="st4" d="M53.505,46.016l-0.412,2.667c-0.178,1.148-1.01,1.984-1.981,1.984h-3.264c-0.946,0-1.764-0.793-1.966-1.906
+	l-0.288-1.575"/>
+	<path class="st4" d="M18.88,27.836l-0.544-2.35c-0.103-0.441-0.462-0.722-0.839-0.651l-1.888,0.356
+	c-0.245,0.046-0.455,0.235-0.562,0.508l-0.242,0.622c-0.228,0.579,0.107,1.252,0.636,1.287l3.438,0.231"/>
+	<path class="st4" d="M49.946,27.719l0.544-2.35c0.103-0.441,0.462-0.722,0.839-0.651l1.888,0.356
+	c0.245,0.046,0.455,0.235,0.562,0.508l0.242,0.622c0.228,0.58-0.107,1.252-0.636,1.287l-3.438,0.231"/>
+	<path class="st4" d="M16.833,33.047l-0.501,1.728c-0.185,0.633,0.092,1.323,0.615,1.547l2.869,1.223
+	c0.128,0.053,0.263,0.085,0.398,0.085l0.533,0.011c0.853,0.018,1.337-1.173,0.8-1.973l-0.764-1.145
+	c-0.06-0.092-0.135-0.174-0.217-0.242l-2.219-1.835c-0.548-0.452-1.298-0.156-1.518,0.597L16.833,33.047z"/>
+	<path class="st4" d="M51.678,32.811l0.501,1.728c0.185,0.633-0.092,1.323-0.615,1.547l-2.869,1.223
+	c-0.128,0.053-0.263,0.085-0.398,0.085l-0.533,0.011c-0.853,0.018-1.337-1.173-0.8-1.973l0.764-1.145
+	c0.06-0.093,0.135-0.174,0.217-0.242l2.219-1.835c0.548-0.452,1.298-0.157,1.518,0.597L51.678,32.811z"/>
+	<path class="st4" d="M45.53,28.111H23.197c-0.576,0-0.956-0.672-0.715-1.259l2.4-5.856c0.103-0.249,0.295-0.43,0.533-0.491
+	c1.589-0.42,8.118-1.852,17.668,0.028c0.245,0.05,0.459,0.228,0.569,0.48l2.581,5.817c0.263,0.59-0.117,1.284-0.704,1.284V28.111z"
+	/>
+	<path class="st4" d="M25.843,35.819c-0.686,0-3.065-0.395-3.324-3.069c-0.26-2.674-0.103-5.643-0.103-5.643"/>
+	<path class="st4" d="M42.71,35.778c0.722,0,3.221-0.395,3.499-3.069c0.277-2.674,0.107-5.643,0.107-5.643"/>
+	<path class="st4" d="M42.711,35.779c0,0-16.185,0.039-16.868,0.039"/>
+</svg>
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 935fb8a0083f..602cb1de71ba 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.4.48</string>
+	<string>1.4.49</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleURLTypes</key>
@@ -40,7 +40,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>1.4.48.0</string>
+	<string>1.4.49.3</string>
 	<key>ITSAppUsesNonExemptEncryption</key>
 	<false/>
 	<key>LSApplicationQueriesSchemes</key>
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 9d42e39387b4..61ac30356852 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
 	<key>CFBundlePackageType</key>
 	<string>BNDL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.4.48</string>
+	<string>1.4.49</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>1.4.48.0</string>
+	<string>1.4.49.3</string>
 </dict>
 </plist>
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index dd33f988f7c4..8b48846258a3 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
 	<key>CFBundleName</key>
 	<string>$(PRODUCT_NAME)</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.4.48</string>
+	<string>1.4.49</string>
 	<key>CFBundleVersion</key>
-	<string>1.4.48.0</string>
+	<string>1.4.49.3</string>
 	<key>NSExtension</key>
 	<dict>
 		<key>NSExtensionPointIdentifier</key>
diff --git a/package-lock.json b/package-lock.json
index 8f3a17edec48..8efb036099a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "new.expensify",
-  "version": "1.4.48-0",
+  "version": "1.4.49-3",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "new.expensify",
-      "version": "1.4.48-0",
+      "version": "1.4.49-3",
       "hasInstallScript": true,
       "license": "MIT",
       "dependencies": {
diff --git a/package.json b/package.json
index 46ee7afd6548..480806d7d111 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "new.expensify",
-  "version": "1.4.48-0",
+  "version": "1.4.49-3",
   "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/CONST.ts b/src/CONST.ts
index 70fecab70c39..645746c38a7d 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -1417,6 +1417,11 @@ const CONST = {
             MAKE_MEMBER: 'makeMember',
             MAKE_ADMIN: 'makeAdmin',
         },
+        DISTANCE_RATES_BULK_ACTION_TYPES: {
+            DELETE: 'delete',
+            DISABLE: 'disable',
+            ENABLE: 'enable',
+        },
     },
 
     CUSTOM_UNITS: {
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 2ed9fbc3666e..db0068365760 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -566,6 +566,10 @@ const ROUTES = {
         route: 'workspace/:policyID/members/:accountID/role-selection',
         getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}/role-selection`, backTo),
     },
+    WORKSPACE_DISTANCE_RATES: {
+        route: 'workspace/:policyID/distance-rates',
+        getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const,
+    },
 
     // Referral program promotion
     REFERRAL_DETAILS_MODAL: {
@@ -573,6 +577,10 @@ const ROUTES = {
         getRoute: (contentType: string, backTo?: string) => getUrlWithBackToParam(`referral/${contentType}`, backTo),
     },
     PROCESS_MONEY_REQUEST_HOLD: 'hold-request-educational',
+    TRANSACTION_RECEIPT: {
+        route: 'r/:reportID/transaction/:transactionID/receipt',
+        getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const,
+    },
 } as const;
 
 /**
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 6fc61aec61a0..8546f543b77a 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -229,6 +229,7 @@ const SCREENS = {
         CATEGORIES_SETTINGS: 'Categories_Settings',
         MEMBER_DETAILS: 'Workspace_Member_Details',
         MEMBER_DETAILS_ROLE_SELECTION: 'Workspace_Member_Details_Role_Selection',
+        DISTANCE_RATES: 'Distance_Rates',
     },
 
     EDIT_REQUEST: {
@@ -276,6 +277,7 @@ const SCREENS = {
     GET_ASSISTANCE: 'GetAssistance',
     REFERRAL_DETAILS: 'Referral_Details',
     KEYBOARD_SHORTCUTS: 'KeyboardShortcuts',
+    TRANSACTION_RECEIPT: 'TransactionReceipt',
 } as const;
 
 type Screen = DeepValueOf<typeof SCREENS>;
diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx
index eed40d75387e..2f80af7f572a 100755
--- a/src/components/AttachmentModal.tsx
+++ b/src/components/AttachmentModal.tsx
@@ -288,7 +288,7 @@ function AttachmentModal({
     const deleteAndCloseModal = useCallback(() => {
         IOU.detachReceipt(transaction?.transactionID ?? '');
         setIsDeleteReceiptConfirmModalVisible(false);
-        Navigation.dismissModal(report?.reportID);
+        Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? ''));
     }, [transaction, report]);
 
     const isValidFile = useCallback((fileObject: FileObject) => {
diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx
index 61d3409c65ab..5f426f77b731 100644
--- a/src/components/ButtonWithDropdownMenu/index.tsx
+++ b/src/components/ButtonWithDropdownMenu/index.tsx
@@ -32,6 +32,7 @@ function ButtonWithDropdownMenu<IValueType>({
     options,
     onOptionSelected,
     enterKeyEventListenerPriority = 0,
+    wrapperStyle,
 }: ButtonWithDropdownMenuProps<IValueType>) {
     const theme = useTheme();
     const styles = useThemeStyles();
@@ -66,7 +67,7 @@ function ButtonWithDropdownMenu<IValueType>({
     }, [windowWidth, windowHeight, isMenuVisible, anchorAlignment.vertical]);
 
     return (
-        <View>
+        <View style={wrapperStyle}>
             {shouldAlwaysShowDropdownMenu || options.length > 1 ? (
                 <View style={[styles.flexRow, styles.justifyContentBetween, styles.alignItemsCenter, style]}>
                     <Button
diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts
index 9975c10c13c3..fcf1baaa6aed 100644
--- a/src/components/ButtonWithDropdownMenu/types.ts
+++ b/src/components/ButtonWithDropdownMenu/types.ts
@@ -10,6 +10,8 @@ type PaymentType = DeepValueOf<typeof CONST.IOU.PAYMENT_TYPE | typeof CONST.IOU.
 
 type WorkspaceMemberBulkActionType = DeepValueOf<typeof CONST.POLICY.MEMBERS_BULK_ACTION_TYPES>;
 
+type WorkspaceDistanceRatesBulkActionType = DeepValueOf<typeof CONST.POLICY.DISTANCE_RATES_BULK_ACTION_TYPES>;
+
 type DropdownOption<TValueType> = {
     value: TValueType;
     text: string;
@@ -66,6 +68,9 @@ type ButtonWithDropdownMenuProps<TValueType> = {
 
     /** Whether the dropdown menu should be shown even if it has only one option */
     shouldAlwaysShowDropdownMenu?: boolean;
+
+    /** Additional style to add to the wrapper */
+    wrapperStyle?: StyleProp<ViewStyle>;
 };
 
-export type {PaymentType, WorkspaceMemberBulkActionType, DropdownOption, ButtonWithDropdownMenuProps};
+export type {PaymentType, WorkspaceMemberBulkActionType, WorkspaceDistanceRatesBulkActionType, DropdownOption, ButtonWithDropdownMenuProps};
diff --git a/src/components/FlatList/index.android.tsx b/src/components/FlatList/index.android.tsx
index 863930203863..1246367d29e8 100644
--- a/src/components/FlatList/index.android.tsx
+++ b/src/components/FlatList/index.android.tsx
@@ -1,7 +1,7 @@
 import {useFocusEffect} from '@react-navigation/native';
 import type {ForwardedRef} from 'react';
 import React, {forwardRef, useCallback, useContext} from 'react';
-import type {FlatListProps} from 'react-native';
+import type {FlatListProps, NativeScrollEvent, NativeSyntheticEvent} from 'react-native';
 import {FlatList} from 'react-native';
 import {ActionListContext} from '@pages/home/ReportScreenContext';
 
@@ -22,6 +22,9 @@ function CustomFlatList<T>(props: FlatListProps<T>, ref: ForwardedRef<FlatList>)
         }
     }, [scrollPosition?.offset, ref]);
 
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+    const onMomentumScrollEnd = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => setScrollPosition({offset: event.nativeEvent.contentOffset.y}), []);
+
     useFocusEffect(
         useCallback(() => {
             onScreenFocus();
@@ -32,10 +35,8 @@ function CustomFlatList<T>(props: FlatListProps<T>, ref: ForwardedRef<FlatList>)
         <FlatList<T>
             // eslint-disable-next-line react/jsx-props-no-spreading
             {...props}
-            onScroll={(event) => props.onScroll?.(event)}
-            onMomentumScrollEnd={(event) => {
-                setScrollPosition({offset: event.nativeEvent.contentOffset.y});
-            }}
+            onScroll={props.onScroll}
+            onMomentumScrollEnd={onMomentumScrollEnd}
             ref={ref}
         />
     );
diff --git a/src/components/HeaderWithBackButton/types.ts b/src/components/HeaderWithBackButton/types.ts
index 83afbad8636b..c3ffb500080b 100644
--- a/src/components/HeaderWithBackButton/types.ts
+++ b/src/components/HeaderWithBackButton/types.ts
@@ -1,9 +1,9 @@
 import type {ReactNode} from 'react';
-import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
 import type {Action} from '@hooks/useSingleExecution';
 import type {StepCounterParams} from '@src/languages/types';
 import type {AnchorPosition} from '@src/styles';
-import type {PersonalDetails, Policy, Report} from '@src/types/onyx';
+import type {Policy, Report} from '@src/types/onyx';
 import type ChildrenProps from '@src/types/utils/ChildrenProps';
 import type IconAsset from '@src/types/utils/IconAsset';
 
@@ -101,9 +101,6 @@ type HeaderWithBackButtonProps = Partial<ChildrenProps> & {
     /** The report's policy, if we're showing the details for a report and need info about it for AvatarWithDisplay */
     policy?: OnyxEntry<Policy>;
 
-    /** Policies, if we're showing the details for a report and need participant details for AvatarWithDisplay */
-    personalDetails?: OnyxCollection<PersonalDetails>;
-
     /** Single execution function to prevent concurrent navigation actions */
     singleExecution?: <T extends unknown[]>(action: Action<T>) => Action<T>;
 
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index d9f46203a703..3f1cde92a583 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -42,6 +42,7 @@ import Concierge from '@assets/images/concierge.svg';
 import Connect from '@assets/images/connect.svg';
 import Copy from '@assets/images/copy.svg';
 import CreditCard from '@assets/images/creditcard.svg';
+import DocumentSlash from '@assets/images/document-slash.svg';
 import Document from '@assets/images/document.svg';
 import DotIndicatorUnfilled from '@assets/images/dot-indicator-unfilled.svg';
 import DotIndicator from '@assets/images/dot-indicator.svg';
@@ -194,6 +195,7 @@ export {
     CreditCard,
     DeletedRoomAvatar,
     Document,
+    DocumentSlash,
     DomainRoomAvatar,
     DotIndicator,
     DotIndicatorUnfilled,
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 7f60ad3867c8..58cefb1877ce 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -33,6 +33,7 @@ import Approval from '@assets/images/simple-illustrations/simple-illustration__a
 import BankArrow from '@assets/images/simple-illustrations/simple-illustration__bank-arrow.svg';
 import BigRocket from '@assets/images/simple-illustrations/simple-illustration__bigrocket.svg';
 import PinkBill from '@assets/images/simple-illustrations/simple-illustration__bill.svg';
+import CarIce from '@assets/images/simple-illustrations/simple-illustration__car-ice.svg';
 import ChatBubbles from '@assets/images/simple-illustrations/simple-illustration__chatbubbles.svg';
 import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__coffeemug.svg';
 import CommentBubbles from '@assets/images/simple-illustrations/simple-illustration__commentbubbles.svg';
@@ -148,4 +149,5 @@ export {
     ThreeLeggedLaptopWoman,
     House,
     Tag,
+    CarIce,
 };
diff --git a/src/components/InlineSystemMessage.tsx b/src/components/InlineSystemMessage.tsx
index bef4c21289d5..4cf7aeb4433e 100644
--- a/src/components/InlineSystemMessage.tsx
+++ b/src/components/InlineSystemMessage.tsx
@@ -33,3 +33,5 @@ function InlineSystemMessage({message = ''}: InlineSystemMessageProps) {
 InlineSystemMessage.displayName = 'InlineSystemMessage';
 
 export default InlineSystemMessage;
+
+export type {InlineSystemMessageProps};
diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx
index e28400505280..0549e19c2eb4 100644
--- a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx
+++ b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx
@@ -6,6 +6,11 @@ import FlatList from '@components/FlatList';
 const WINDOW_SIZE = 15;
 const AUTOSCROLL_TO_TOP_THRESHOLD = 128;
 
+const maintainVisibleContentPosition = {
+    minIndexForVisible: 0,
+    autoscrollToTopThreshold: AUTOSCROLL_TO_TOP_THRESHOLD,
+};
+
 function BaseInvertedFlatList<T>(props: FlatListProps<T>, ref: ForwardedRef<FlatList>) {
     return (
         <FlatList
@@ -13,10 +18,7 @@ function BaseInvertedFlatList<T>(props: FlatListProps<T>, ref: ForwardedRef<Flat
             {...props}
             ref={ref}
             windowSize={WINDOW_SIZE}
-            maintainVisibleContentPosition={{
-                minIndexForVisible: 0,
-                autoscrollToTopThreshold: AUTOSCROLL_TO_TOP_THRESHOLD,
-            }}
+            maintainVisibleContentPosition={maintainVisibleContentPosition}
             inverted
         />
     );
diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx
index f5545f402b14..93eac30d5477 100644
--- a/src/components/LHNOptionsList/LHNOptionsList.tsx
+++ b/src/components/LHNOptionsList/LHNOptionsList.tsx
@@ -1,9 +1,8 @@
 import {FlashList} from '@shopify/flash-list';
 import type {ReactElement} from 'react';
-import React, {memo, useCallback} from 'react';
+import React, {memo, useCallback, useMemo} from 'react';
 import {StyleSheet, View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
-import withCurrentReportID from '@components/withCurrentReportID';
 import usePermissions from '@hooks/usePermissions';
 import useThemeStyles from '@hooks/useThemeStyles';
 import * as ReportActionsUtils from '@libs/ReportActionsUtils';
@@ -28,7 +27,6 @@ function LHNOptionsList({
     preferredLocale = CONST.LOCALES.DEFAULT,
     personalDetails = {},
     transactions = {},
-    currentReportID = '',
     draftComments = {},
     transactionViolations = {},
     onFirstItemRendered = () => {},
@@ -86,7 +84,7 @@ function LHNOptionsList({
                     lastReportActionTransaction={lastReportActionTransaction}
                     receiptTransactions={transactions}
                     viewMode={optionMode}
-                    isFocused={!shouldDisableFocusOptions && reportID === currentReportID}
+                    isFocused={!shouldDisableFocusOptions}
                     onSelectRow={onSelectRow}
                     preferredLocale={preferredLocale}
                     comment={itemComment}
@@ -98,7 +96,6 @@ function LHNOptionsList({
             );
         },
         [
-            currentReportID,
             draftComments,
             onSelectRow,
             optionMode,
@@ -116,6 +113,8 @@ function LHNOptionsList({
         ],
     );
 
+    const extraData = useMemo(() => [reportActions, reports, policy, personalDetails], [reportActions, reports, policy, personalDetails]);
+
     return (
         <View style={style ?? styles.flex1}>
             <FlashList
@@ -127,7 +126,7 @@ function LHNOptionsList({
                 keyExtractor={keyExtractor}
                 renderItem={renderItem}
                 estimatedItemSize={optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight}
-                extraData={[currentReportID]}
+                extraData={extraData}
                 showsVerticalScrollIndicator={false}
             />
         </View>
@@ -136,33 +135,31 @@ function LHNOptionsList({
 
 LHNOptionsList.displayName = 'LHNOptionsList';
 
-export default withCurrentReportID(
-    withOnyx<LHNOptionsListProps, LHNOptionsListOnyxProps>({
-        reports: {
-            key: ONYXKEYS.COLLECTION.REPORT,
-        },
-        reportActions: {
-            key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
-        },
-        policy: {
-            key: ONYXKEYS.COLLECTION.POLICY,
-        },
-        preferredLocale: {
-            key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-        },
-        personalDetails: {
-            key: ONYXKEYS.PERSONAL_DETAILS_LIST,
-        },
-        transactions: {
-            key: ONYXKEYS.COLLECTION.TRANSACTION,
-        },
-        draftComments: {
-            key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT,
-        },
-        transactionViolations: {
-            key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
-        },
-    })(memo(LHNOptionsList)),
-);
+export default withOnyx<LHNOptionsListProps, LHNOptionsListOnyxProps>({
+    reports: {
+        key: ONYXKEYS.COLLECTION.REPORT,
+    },
+    reportActions: {
+        key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+    },
+    policy: {
+        key: ONYXKEYS.COLLECTION.POLICY,
+    },
+    preferredLocale: {
+        key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+    },
+    personalDetails: {
+        key: ONYXKEYS.PERSONAL_DETAILS_LIST,
+    },
+    transactions: {
+        key: ONYXKEYS.COLLECTION.TRANSACTION,
+    },
+    draftComments: {
+        key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT,
+    },
+    transactionViolations: {
+        key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
+    },
+})(memo(LHNOptionsList));
 
 export type {LHNOptionsListProps};
diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx
index a3394190d0c1..9b22b50b64fe 100644
--- a/src/components/LHNOptionsList/OptionRowLHNData.tsx
+++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx
@@ -1,5 +1,6 @@
 import {deepEqual} from 'fast-equals';
 import React, {useEffect, useMemo, useRef} from 'react';
+import useCurrentReportID from '@hooks/useCurrentReportID';
 import * as ReportUtils from '@libs/ReportUtils';
 import SidebarUtils from '@libs/SidebarUtils';
 import * as Report from '@userActions/Report';
@@ -32,6 +33,8 @@ function OptionRowLHNData({
     ...propsToForward
 }: OptionRowLHNDataProps) {
     const reportID = propsToForward.reportID;
+    const currentReportIDValue = useCurrentReportID();
+    const isReportFocused = isFocused && currentReportIDValue?.currentReportID === reportID;
 
     const optionItemRef = useRef<OptionData>();
 
@@ -85,7 +88,7 @@ function OptionRowLHNData({
         <OptionRowLHN
             // eslint-disable-next-line react/jsx-props-no-spreading
             {...propsToForward}
-            isFocused={isFocused}
+            isFocused={isReportFocused}
             optionItem={optionItem}
         />
     );
diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts
index c122ab018392..4ca30358f9b1 100644
--- a/src/components/LHNOptionsList/types.ts
+++ b/src/components/LHNOptionsList/types.ts
@@ -3,7 +3,6 @@ import type {RefObject} from 'react';
 import type {LayoutChangeEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native';
 import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
-import type {CurrentReportIDContextValue} from '@components/withCurrentReportID';
 import type CONST from '@src/CONST';
 import type {OptionData} from '@src/libs/ReportUtils';
 import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx';
@@ -64,7 +63,7 @@ type CustomLHNOptionsListProps = {
     reportIDsWithErrors: Record<string, OnyxCommon.Errors>;
 };
 
-type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps;
+type LHNOptionsListProps = CustomLHNOptionsListProps & LHNOptionsListOnyxProps;
 
 type OptionRowLHNDataProps = {
     /** Whether row should be focused */
diff --git a/src/components/LocalePicker.tsx b/src/components/LocalePicker.tsx
index 3a2d9a0fd7b9..b6e59643e499 100644
--- a/src/components/LocalePicker.tsx
+++ b/src/components/LocalePicker.tsx
@@ -4,14 +4,18 @@ import {withOnyx} from 'react-native-onyx';
 import useLocalize from '@hooks/useLocalize';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
+import AccountUtils from '@libs/AccountUtils';
 import * as App from '@userActions/App';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import type {Locale} from '@src/types/onyx';
+import type {Account, Locale} from '@src/types/onyx';
 import Picker from './Picker';
 import type {PickerSize} from './Picker/types';
 
 type LocalePickerOnyxProps = {
+    /** The details about the account that the user is signing in with */
+    account: OnyxEntry<Account>;
+
     /** Indicates which locale the user currently has selected */
     preferredLocale: OnyxEntry<Locale>;
 };
@@ -21,7 +25,7 @@ type LocalePickerProps = LocalePickerOnyxProps & {
     size?: PickerSize;
 };
 
-function LocalePicker({preferredLocale = CONST.LOCALES.DEFAULT, size = 'normal'}: LocalePickerProps) {
+function LocalePicker({account, preferredLocale = CONST.LOCALES.DEFAULT, size = 'normal'}: LocalePickerProps) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const {translate} = useLocalize();
@@ -31,6 +35,7 @@ function LocalePicker({preferredLocale = CONST.LOCALES.DEFAULT, size = 'normal'}
         keyForList: language,
         isSelected: preferredLocale === language,
     }));
+    const shouldDisablePicker = AccountUtils.isValidateCodeFormSubmitting(account);
 
     return (
         <Picker
@@ -42,7 +47,10 @@ function LocalePicker({preferredLocale = CONST.LOCALES.DEFAULT, size = 'normal'}
 
                 App.setLocale(locale);
             }}
+            isDisabled={shouldDisablePicker}
             items={localesToLanguages}
+            shouldAllowDisabledStyle={false}
+            shouldShowOnlyTextWhenDisabled={false}
             size={size}
             value={preferredLocale}
             containerStyles={size === 'small' ? styles.pickerContainerSmall : {}}
@@ -54,6 +62,9 @@ function LocalePicker({preferredLocale = CONST.LOCALES.DEFAULT, size = 'normal'}
 LocalePicker.displayName = 'LocalePicker';
 
 export default withOnyx<LocalePickerProps, LocalePickerOnyxProps>({
+    account: {
+        key: ONYXKEYS.ACCOUNT,
+    },
     preferredLocale: {
         key: ONYXKEYS.NVP_PREFERRED_LOCALE,
     },
diff --git a/src/components/MagicCodeInput.tsx b/src/components/MagicCodeInput.tsx
index 93febc4fd3c0..deff56a534ee 100644
--- a/src/components/MagicCodeInput.tsx
+++ b/src/components/MagicCodeInput.tsx
@@ -430,4 +430,4 @@ function MagicCodeInput(
 MagicCodeInput.displayName = 'MagicCodeInput';
 
 export default forwardRef(MagicCodeInput);
-export type {AutoCompleteVariant, MagicCodeInputHandle};
+export type {AutoCompleteVariant, MagicCodeInputHandle, MagicCodeInputProps};
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 102f85ea49b9..2520520fd467 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -8,7 +8,6 @@ import useWindowDimensions from '@hooks/useWindowDimensions';
 import * as CurrencyUtils from '@libs/CurrencyUtils';
 import * as HeaderUtils from '@libs/HeaderUtils';
 import Navigation from '@libs/Navigation/Navigation';
-import * as PolicyUtils from '@libs/PolicyUtils';
 import * as ReportUtils from '@libs/ReportUtils';
 import * as IOU from '@userActions/IOU';
 import CONST from '@src/CONST';
@@ -21,7 +20,6 @@ import ConfirmModal from './ConfirmModal';
 import HeaderWithBackButton from './HeaderWithBackButton';
 import * as Expensicons from './Icon/Expensicons';
 import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
-import {usePersonalDetails} from './OnyxProvider';
 import SettlementButton from './SettlementButton';
 
 type PaymentType = DeepValueOf<typeof CONST.IOU.PAYMENT_TYPE>;
@@ -46,20 +44,15 @@ type MoneyReportHeaderProps = MoneyReportHeaderOnyxProps & {
 };
 
 function MoneyReportHeader({session, policy, chatReport, nextStep, report: moneyRequestReport}: MoneyReportHeaderProps) {
-    const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
     const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(moneyRequestReport);
-    const isApproved = ReportUtils.isReportApproved(moneyRequestReport);
     const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
     const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport);
     const policyType = policy?.type;
-    const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(moneyRequestReport, policy);
-    const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicy(moneyRequestReport);
-    const isManager = ReportUtils.isMoneyRequestReport(moneyRequestReport) && session?.accountID === moneyRequestReport.managerID;
     const isPayer = ReportUtils.isPayer(session, moneyRequestReport);
-    const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport);
+    const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport);
     const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);
 
     const cancelPayment = useCallback(() => {
@@ -70,25 +63,15 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
         setIsConfirmModalVisible(false);
     }, [moneyRequestReport, chatReport]);
 
-    const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
-    const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
-    const shouldShowPayButton = useMemo(
-        () => isPayer && !isDraft && !isSettled && !moneyRequestReport.isWaitingOnBankAccount && reimbursableSpend !== 0 && !ReportUtils.isArchivedRoom(chatReport) && !isAutoReimbursable,
-        [isPayer, isDraft, isSettled, moneyRequestReport, reimbursableSpend, chatReport, isAutoReimbursable],
-    );
-    const shouldShowApproveButton = useMemo(() => {
-        if (!isPaidGroupPolicy) {
-            return false;
-        }
-        if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) {
-            return false;
-        }
-        return isManager && !isDraft && !isApproved && !isSettled;
-    }, [isPaidGroupPolicy, isManager, isDraft, isApproved, isSettled, isOnInstantSubmitPolicy, isOnSubmitAndClosePolicy]);
+    const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]);
+
+    const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]);
+
     const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton;
+
     const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0;
     const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
-    const shouldShowNextStep = isFromPaidPolicy && !!nextStep?.message?.length;
+    const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length;
     const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep;
     const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
     const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency);
@@ -117,7 +100,6 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
                 shouldShowPinButton={false}
                 report={moneyRequestReport}
                 policy={policy}
-                personalDetails={personalDetails}
                 shouldShowBackButton={isSmallScreenWidth}
                 onBackButtonPress={() => Navigation.goBack(undefined, false, true)}
                 // Shows border if no buttons or next steps are showing below the header
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 7f9ab3fe0dc9..338796cd856e 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -21,7 +21,6 @@ import HeaderWithBackButton from './HeaderWithBackButton';
 import HoldBanner from './HoldBanner';
 import * as Expensicons from './Icon/Expensicons';
 import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
-import {usePersonalDetails} from './OnyxProvider';
 import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu';
 
 type MoneyRequestHeaderOnyxProps = {
@@ -54,7 +53,6 @@ type MoneyRequestHeaderProps = MoneyRequestHeaderOnyxProps & {
 };
 
 function MoneyRequestHeader({session, parentReport, report, parentReportAction, transaction, shownHoldUseExplanation = false, policy}: MoneyRequestHeaderProps) {
-    const personalDetails = usePersonalDetails() || CONST.EMPTY_OBJECT;
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
@@ -167,7 +165,6 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction,
                         ownerAccountID: parentReport?.ownerAccountID,
                     }}
                     policy={policy}
-                    personalDetails={personalDetails}
                     shouldShowBackButton={isSmallScreenWidth}
                     onBackButtonPress={() => Navigation.goBack(undefined, false, true)}
                 />
diff --git a/src/components/Picker/BasePicker.tsx b/src/components/Picker/BasePicker.tsx
index c86d3b71c1d9..03f1e0f496a8 100644
--- a/src/components/Picker/BasePicker.tsx
+++ b/src/components/Picker/BasePicker.tsx
@@ -33,7 +33,9 @@ function BasePicker<TPickerValue>(
         containerStyles,
         placeholder = {},
         size = 'normal',
+        shouldAllowDisabledStyle = true,
         shouldFocusPicker = false,
+        shouldShowOnlyTextWhenDisabled = true,
         onBlur = () => {},
         additionalPickerEvents = () => {},
     }: BasePickerProps<TPickerValue>,
@@ -155,7 +157,7 @@ function BasePicker<TPickerValue>(
 
     const hasError = !!errorText;
 
-    if (isDisabled) {
+    if (isDisabled && shouldShowOnlyTextWhenDisabled) {
         return (
             <View>
                 {!!label && (
@@ -176,14 +178,20 @@ function BasePicker<TPickerValue>(
         <>
             <View
                 ref={root}
-                style={[styles.pickerContainer, isDisabled && styles.inputDisabled, containerStyles, isHighlighted && styles.borderColorFocus, hasError && styles.borderColorDanger]}
+                style={[
+                    styles.pickerContainer,
+                    isDisabled && shouldAllowDisabledStyle && styles.inputDisabled,
+                    containerStyles,
+                    isHighlighted && styles.borderColorFocus,
+                    hasError && styles.borderColorDanger,
+                ]}
             >
                 {label && <Text style={[styles.pickerLabel, styles.textLabelSupporting, styles.pointerEventsNone]}>{label}</Text>}
                 <RNPickerSelect
                     onValueChange={onValueChange}
                     // We add a text color to prevent white text on white background dropdown items on Windows
                     items={items.map((item) => ({...item, color: itemColor}))}
-                    style={size === 'normal' ? styles.picker(isDisabled, backgroundColor) : styles.pickerSmall(backgroundColor)}
+                    style={size === 'normal' ? styles.picker(isDisabled, backgroundColor) : styles.pickerSmall(isDisabled, backgroundColor)}
                     useNativeAndroidPickerStyle={false}
                     placeholder={pickerPlaceholder}
                     value={value}
diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts
index a12f4cbe683a..3f7c0282d35a 100644
--- a/src/components/Picker/types.ts
+++ b/src/components/Picker/types.ts
@@ -70,9 +70,15 @@ type BasePickerProps<TPickerValue> = {
     /** The ID used to uniquely identify the input in a Form */
     inputID?: string;
 
+    /** Show disabled style when disabled */
+    shouldAllowDisabledStyle?: boolean;
+
     /** Saves a draft of the input value when used in a form */
     shouldSaveDraft?: boolean;
 
+    /** Show only picker's label and value when disabled */
+    shouldShowOnlyTextWhenDisabled?: boolean;
+
     /** A callback method that is called when the value changes and it receives the selected value as an argument */
     onInputChange: (value: TPickerValue, index?: number) => void;
 
diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx
index 1a740d51a2af..60dbfc07966a 100644
--- a/src/components/ReportActionItem/MoneyReportView.tsx
+++ b/src/components/ReportActionItem/MoneyReportView.tsx
@@ -68,107 +68,111 @@ function MoneyReportView({report, policy, policyReportFields, shouldShowHorizont
         <View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth, true)]}>
             <AnimatedEmptyStateBackground />
             <View style={[StyleUtils.getReportWelcomeTopMarginStyle(isSmallScreenWidth, true)]}>
-                {ReportUtils.reportFieldsEnabled(report) &&
-                    sortedPolicyReportFields.map((reportField) => {
-                        const isTitleField = ReportUtils.isReportFieldOfTypeTitle(reportField);
-                        const fieldValue = isTitleField ? report.reportName : reportField.value ?? reportField.defaultValue;
-                        const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, reportField, policy);
-
-                        return (
-                            <OfflineWithFeedback
-                                pendingAction={report.pendingFields?.[reportField.fieldID]}
-                                errors={report.errorFields?.[reportField.fieldID]}
-                                errorRowStyles={styles.ph5}
-                                key={`menuItem-${reportField.fieldID}`}
-                            >
-                                <MenuItemWithTopDescription
-                                    description={Str.UCFirst(reportField.name)}
-                                    title={fieldValue}
-                                    onPress={() => Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))}
-                                    shouldShowRightIcon
-                                    disabled={isFieldDisabled}
-                                    wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
-                                    shouldGreyOutWhenDisabled={false}
-                                    numberOfLinesTitle={0}
-                                    interactive
-                                    shouldStackHorizontally={false}
-                                    onSecondaryInteraction={() => {}}
-                                    hoverAndPressStyle={false}
-                                    titleWithTooltips={[]}
-                                />
-                            </OfflineWithFeedback>
-                        );
-                    })}
-                <View style={[styles.flexRow, styles.pointerEventsNone, styles.containerWithSpaceBetween, styles.ph5, styles.pv2]}>
-                    <View style={[styles.flex1, styles.justifyContentCenter]}>
-                        <Text
-                            style={[styles.textLabelSupporting]}
-                            numberOfLines={1}
-                        >
-                            {translate('common.total')}
-                        </Text>
-                    </View>
-                    <View style={[styles.flexRow, styles.justifyContentCenter]}>
-                        {isSettled && (
-                            <View style={[styles.defaultCheckmarkWrapper, styles.mh2]}>
-                                <Icon
-                                    src={Expensicons.Checkmark}
-                                    fill={theme.success}
-                                />
-                            </View>
-                        )}
-                        <Text
-                            numberOfLines={1}
-                            style={[styles.taskTitleMenuItem, styles.alignSelfCenter, !isTotalUpdated && styles.offlineFeedback.pending]}
-                        >
-                            {formattedTotalAmount}
-                        </Text>
-                    </View>
-                </View>
-                {Boolean(shouldShowBreakdown) && (
+                {!ReportUtils.isClosedExpenseReportWithNoExpenses(report) && (
                     <>
-                        <View style={[styles.flexRow, styles.pointerEventsNone, styles.containerWithSpaceBetween, styles.ph5, styles.pv1]}>
-                            <View style={[styles.flex1, styles.justifyContentCenter]}>
-                                <Text
-                                    style={[styles.textLabelSupporting]}
-                                    numberOfLines={1}
-                                >
-                                    {translate('cardTransactions.outOfPocket')}
-                                </Text>
-                            </View>
-                            <View style={[styles.flexRow, styles.justifyContentCenter]}>
-                                <Text
-                                    numberOfLines={1}
-                                    style={subAmountTextStyles}
-                                >
-                                    {formattedOutOfPocketAmount}
-                                </Text>
-                            </View>
-                        </View>
-                        <View style={[styles.flexRow, styles.pointerEventsNone, styles.containerWithSpaceBetween, styles.ph5, styles.pv1]}>
+                        {ReportUtils.reportFieldsEnabled(report) &&
+                            sortedPolicyReportFields.map((reportField) => {
+                                const isTitleField = ReportUtils.isReportFieldOfTypeTitle(reportField);
+                                const fieldValue = isTitleField ? report.reportName : reportField.value ?? reportField.defaultValue;
+                                const isFieldDisabled = ReportUtils.isReportFieldDisabled(report, reportField, policy);
+
+                                return (
+                                    <OfflineWithFeedback
+                                        pendingAction={report.pendingFields?.[reportField.fieldID]}
+                                        errors={report.errorFields?.[reportField.fieldID]}
+                                        errorRowStyles={styles.ph5}
+                                        key={`menuItem-${reportField.fieldID}`}
+                                    >
+                                        <MenuItemWithTopDescription
+                                            description={Str.UCFirst(reportField.name)}
+                                            title={fieldValue}
+                                            onPress={() => Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))}
+                                            shouldShowRightIcon
+                                            disabled={isFieldDisabled}
+                                            wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
+                                            shouldGreyOutWhenDisabled={false}
+                                            numberOfLinesTitle={0}
+                                            interactive
+                                            shouldStackHorizontally={false}
+                                            onSecondaryInteraction={() => {}}
+                                            hoverAndPressStyle={false}
+                                            titleWithTooltips={[]}
+                                        />
+                                    </OfflineWithFeedback>
+                                );
+                            })}
+                        <View style={[styles.flexRow, styles.pointerEventsNone, styles.containerWithSpaceBetween, styles.ph5, styles.pv2]}>
                             <View style={[styles.flex1, styles.justifyContentCenter]}>
                                 <Text
                                     style={[styles.textLabelSupporting]}
                                     numberOfLines={1}
                                 >
-                                    {translate('cardTransactions.companySpend')}
+                                    {translate('common.total')}
                                 </Text>
                             </View>
                             <View style={[styles.flexRow, styles.justifyContentCenter]}>
+                                {isSettled && (
+                                    <View style={[styles.defaultCheckmarkWrapper, styles.mh2]}>
+                                        <Icon
+                                            src={Expensicons.Checkmark}
+                                            fill={theme.success}
+                                        />
+                                    </View>
+                                )}
                                 <Text
                                     numberOfLines={1}
-                                    style={subAmountTextStyles}
+                                    style={[styles.taskTitleMenuItem, styles.alignSelfCenter, !isTotalUpdated && styles.offlineFeedback.pending]}
                                 >
-                                    {formattedCompanySpendAmount}
+                                    {formattedTotalAmount}
                                 </Text>
                             </View>
                         </View>
+                        {Boolean(shouldShowBreakdown) && (
+                            <>
+                                <View style={[styles.flexRow, styles.pointerEventsNone, styles.containerWithSpaceBetween, styles.ph5, styles.pv1]}>
+                                    <View style={[styles.flex1, styles.justifyContentCenter]}>
+                                        <Text
+                                            style={[styles.textLabelSupporting]}
+                                            numberOfLines={1}
+                                        >
+                                            {translate('cardTransactions.outOfPocket')}
+                                        </Text>
+                                    </View>
+                                    <View style={[styles.flexRow, styles.justifyContentCenter]}>
+                                        <Text
+                                            numberOfLines={1}
+                                            style={subAmountTextStyles}
+                                        >
+                                            {formattedOutOfPocketAmount}
+                                        </Text>
+                                    </View>
+                                </View>
+                                <View style={[styles.flexRow, styles.pointerEventsNone, styles.containerWithSpaceBetween, styles.ph5, styles.pv1]}>
+                                    <View style={[styles.flex1, styles.justifyContentCenter]}>
+                                        <Text
+                                            style={[styles.textLabelSupporting]}
+                                            numberOfLines={1}
+                                        >
+                                            {translate('cardTransactions.companySpend')}
+                                        </Text>
+                                    </View>
+                                    <View style={[styles.flexRow, styles.justifyContentCenter]}>
+                                        <Text
+                                            numberOfLines={1}
+                                            style={subAmountTextStyles}
+                                        >
+                                            {formattedCompanySpendAmount}
+                                        </Text>
+                                    </View>
+                                </View>
+                            </>
+                        )}
+                        <SpacerView
+                            shouldShow={shouldShowHorizontalRule}
+                            style={[shouldShowHorizontalRule && styles.reportHorizontalRule]}
+                        />
                     </>
                 )}
-                <SpacerView
-                    shouldShow={shouldShowHorizontalRule}
-                    style={[shouldShowHorizontalRule && styles.reportHorizontalRule]}
-                />
             </View>
         </View>
     );
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 1d048640b30b..70c65d1d66ce 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -144,7 +144,10 @@ function MoneyRequestView({
     const shouldShowBillable = isPolicyExpenseChat && (!!transactionBillable || !(policy?.disabledFields?.defaultBillable ?? true));
 
     const {getViolationsForField} = useViolations(transactionViolations ?? []);
-    const hasViolations = useCallback((field: ViolationField): boolean => !!canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]);
+    const hasViolations = useCallback(
+        (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']): boolean => !!canUseViolations && getViolationsForField(field, data).length > 0,
+        [canUseViolations, getViolationsForField],
+    );
 
     let amountDescription = `${translate('iou.amount')}`;
 
@@ -197,7 +200,7 @@ function MoneyRequestView({
     const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => transaction?.pendingFields?.[fieldPath] ?? pendingAction;
 
     const getErrorForField = useCallback(
-        (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], shouldShowViolations = true) => {
+        (field: ViolationField, data?: OnyxTypes.TransactionViolation['data']) => {
             // Checks applied when creating a new money request
             // NOTE: receipt field can return multiple violations, so we need to handle it separately
             const fieldChecks: Partial<Record<ViolationField, {isError: boolean; translationPath: TranslationPaths}>> = {
@@ -223,9 +226,8 @@ function MoneyRequestView({
             }
 
             // Return violations if there are any
-            // At the moment, we only return violations for tags for workspaces with single-level tags
-            if (canUseViolations && shouldShowViolations && hasViolations(field)) {
-                const violations = getViolationsForField(field);
+            if (canUseViolations && hasViolations(field, data)) {
+                const violations = getViolationsForField(field, data);
                 return ViolationsUtils.getViolationTranslation(violations[0], translate);
             }
 
@@ -262,7 +264,6 @@ function MoneyRequestView({
                                     filename={receiptURIs?.filename}
                                     transaction={transaction}
                                     enablePreviewModal
-                                    canEditReceipt={canEditReceipt}
                                 />
                             )}
                         </View>
@@ -396,8 +397,8 @@ function MoneyRequestView({
                                         ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, index, transaction?.transactionID ?? '', report.reportID),
                                     )
                                 }
-                                brickRoadIndicator={getErrorForField('tag', {}, policyTagLists.length === 1) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
-                                error={getErrorForField('tag', {}, policyTagLists.length === 1)}
+                                brickRoadIndicator={getErrorForField('tag', {tagListIndex: index, tagListName: name}) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
+                                error={getErrorForField('tag', {tagListIndex: index, tagListName: name})}
                             />
                         </OfflineWithFeedback>
                     ))}
diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx
index d47604738fbc..f71f98998026 100644
--- a/src/components/ReportActionItem/ReportActionItemImage.tsx
+++ b/src/components/ReportActionItem/ReportActionItemImage.tsx
@@ -4,7 +4,6 @@ import type {ReactElement} from 'react';
 import type {ImageSourcePropType, ViewStyle} from 'react-native';
 import {View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
-import AttachmentModal from '@components/AttachmentModal';
 import EReceiptThumbnail from '@components/EReceiptThumbnail';
 import * as Expensicons from '@components/Icon/Expensicons';
 import Image from '@components/Image';
@@ -14,10 +13,12 @@ import {ShowContextMenuContext} from '@components/ShowContextMenuContext';
 import ThumbnailImage from '@components/ThumbnailImage';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
 import * as TransactionUtils from '@libs/TransactionUtils';
 import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
+import ROUTES from '@src/ROUTES';
 import type {Transaction} from '@src/types/onyx';
 
 type ReportActionItemImageProps = {
@@ -36,9 +37,6 @@ type ReportActionItemImageProps = {
     /** whether thumbnail is refer the local file or not */
     isLocalFile?: boolean;
 
-    /** whether the receipt can be replaced */
-    canEditReceipt?: boolean;
-
     /** Filename of attachment */
     filename?: string;
 
@@ -52,16 +50,7 @@ type ReportActionItemImageProps = {
  * and optional preview modal as well.
  */
 
-function ReportActionItemImage({
-    thumbnail,
-    image,
-    enablePreviewModal = false,
-    transaction,
-    canEditReceipt = false,
-    isLocalFile = false,
-    filename,
-    isSingleImage = true,
-}: ReportActionItemImageProps) {
+function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, transaction, isLocalFile = false, filename, isSingleImage = true}: ReportActionItemImageProps) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const attachmentModalSource = tryResolveUrlFromApiRoot(image ?? '');
@@ -118,26 +107,14 @@ function ReportActionItemImage({
         return (
             <ShowContextMenuContext.Consumer>
                 {({report}) => (
-                    <AttachmentModal
-                        source={attachmentModalSource}
-                        isAuthTokenRequired={!isLocalFile}
-                        report={report}
-                        isReceiptAttachment
-                        canEditReceipt={canEditReceipt}
-                        allowDownload={!isEReceipt}
-                        originalFileName={filename}
+                    <PressableWithoutFocus
+                        style={[styles.w100, styles.h100, styles.noOutline as ViewStyle]}
+                        onPress={() => Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(report?.reportID ?? '', transaction?.transactionID ?? ''))}
+                        accessibilityLabel={translate('accessibilityHints.viewAttachment')}
+                        accessibilityRole={CONST.ROLE.BUTTON}
                     >
-                        {({show}) => (
-                            <PressableWithoutFocus
-                                style={[styles.w100, styles.h100, styles.noOutline as ViewStyle]}
-                                onPress={show}
-                                accessibilityRole={CONST.ROLE.BUTTON}
-                                accessibilityLabel={translate('accessibilityHints.viewAttachment')}
-                            >
-                                {receiptImageComponent}
-                            </PressableWithoutFocus>
-                        )}
-                    </AttachmentModal>
+                        {receiptImageComponent}
+                    </PressableWithoutFocus>
                 )}
             </ShowContextMenuContext.Consumer>
         );
diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index 381302489699..743bfd8fff88 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -19,7 +19,6 @@ import ControlSelection from '@libs/ControlSelection';
 import * as CurrencyUtils from '@libs/CurrencyUtils';
 import * as DeviceCapabilities from '@libs/DeviceCapabilities';
 import Navigation from '@libs/Navigation/Navigation';
-import * as PolicyUtils from '@libs/PolicyUtils';
 import * as ReceiptUtils from '@libs/ReceiptUtils';
 import * as ReportActionUtils from '@libs/ReportActionsUtils';
 import * as ReportUtils from '@libs/ReportUtils';
@@ -30,7 +29,7 @@ import CONST from '@src/CONST';
 import type {TranslationPaths} from '@src/languages/types';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
-import type {Policy, Report, ReportAction, Session, Transaction, TransactionViolations} from '@src/types/onyx';
+import type {Policy, Report, ReportAction, Transaction, TransactionViolations} from '@src/types/onyx';
 import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
 import ReportActionItemImages from './ReportActionItemImages';
 
@@ -44,9 +43,6 @@ type ReportPreviewOnyxProps = {
     /** Active IOU Report for current report */
     iouReport: OnyxEntry<Report>;
 
-    /** Session info for the currently logged in user. */
-    session: OnyxEntry<Session>;
-
     /** All the transactions, used to update ReportPreview label and status */
     transactions: OnyxCollection<Transaction>;
 
@@ -85,7 +81,6 @@ type ReportPreviewProps = ReportPreviewOnyxProps & {
 
 function ReportPreview({
     iouReport,
-    session,
     policy,
     iouReportID,
     policyID,
@@ -118,16 +113,13 @@ function ReportPreview({
     );
 
     const managerID = iouReport?.managerID ?? 0;
-    const isCurrentUserManager = managerID === session?.accountID;
     const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport);
-    const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy);
 
     const iouSettled = ReportUtils.isSettled(iouReportID);
-    const iouCanceled = ReportUtils.isArchivedRoom(chatReport);
     const numberOfRequests = ReportActionUtils.getNumberOfMoneyRequests(action);
     const moneyRequestComment = action?.childLastMoneyRequestComment ?? '';
     const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
-    const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(iouReport);
+    const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport);
 
     const isApproved = ReportUtils.isReportApproved(iouReport);
     const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport);
@@ -155,7 +147,7 @@ function ReportPreview({
             pendingReceipts: numberOfPendingRequests,
         });
 
-    const shouldShowSubmitButton = isDraftExpenseReport && reimbursableSpend !== 0;
+    const shouldShowSubmitButton = isOpenExpenseReport && reimbursableSpend !== 0;
 
     // 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(
@@ -208,23 +200,10 @@ function ReportPreview({
 
     const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
 
-    const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport);
-    const isPayer = ReportUtils.isPayer(session, iouReport);
-    const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
-    const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
-    const shouldShowPayButton = useMemo(
-        () => isPayer && !isDraftExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable,
-        [isPayer, isDraftExpenseReport, iouSettled, reimbursableSpend, iouCanceled, isAutoReimbursable, iouReport],
-    );
-    const shouldShowApproveButton = useMemo(() => {
-        if (!isPaidGroupPolicy) {
-            return false;
-        }
-        if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) {
-            return false;
-        }
-        return isCurrentUserManager && !isDraftExpenseReport && !isApproved && !iouSettled;
-    }, [isPaidGroupPolicy, isCurrentUserManager, isDraftExpenseReport, isApproved, isOnInstantSubmitPolicy, isOnSubmitAndClosePolicy, iouSettled]);
+    const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(iouReport, chatReport, policy), [iouReport, chatReport, policy]);
+
+    const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(iouReport, chatReport, policy), [iouReport, chatReport, policy]);
+
     const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton;
 
     /*
@@ -353,9 +332,6 @@ export default withOnyx<ReportPreviewProps, ReportPreviewOnyxProps>({
     iouReport: {
         key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`,
     },
-    session: {
-        key: ONYXKEYS.SESSION,
-    },
     transactions: {
         key: ONYXKEYS.COLLECTION.TRANSACTION,
     },
diff --git a/src/components/VideoPlayer/utils.ts b/src/components/VideoPlayer/utils.ts
index 57f5422d0ce5..c13af0f874d1 100644
--- a/src/components/VideoPlayer/utils.ts
+++ b/src/components/VideoPlayer/utils.ts
@@ -1,9 +1,11 @@
-import {format} from 'date-fns';
-
-// Converts milliseconds to 'minutes:seconds' format
+// Converts milliseconds to '[hours:]minutes:seconds' format
 const convertMillisecondsToTime = (milliseconds: number) => {
-    const date = new Date(milliseconds);
-    return format(date, 'mm:ss');
+    const hours = Math.floor(milliseconds / 3600000);
+    const minutes = Math.floor((milliseconds / 60000) % 60);
+    const seconds = Math.floor((milliseconds / 1000) % 60)
+        .toFixed(0)
+        .padStart(2, '0');
+    return hours > 0 ? `${hours}:${String(minutes).padStart(2, '0')}:${seconds}` : `${minutes}:${seconds}`;
 };
 
 export default convertMillisecondsToTime;
diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts
index 29b2dcb86718..3df457f1205a 100644
--- a/src/hooks/useViolations.ts
+++ b/src/hooks/useViolations.ts
@@ -58,7 +58,34 @@ function useViolations(violations: TransactionViolation[]) {
         }
         return violationGroups ?? new Map();
     }, [violations]);
-    const getViolationsForField = useCallback((field: ViolationField) => violationsByField.get(field) ?? [], [violationsByField]);
+
+    const getViolationsForField = useCallback(
+        (field: ViolationField, data?: TransactionViolation['data']) => {
+            const currentViolations = violationsByField.get(field) ?? [];
+
+            // someTagLevelsRequired has special logic becase data.errorIndexes is a bit unique in how it denotes the tag list that has the violation
+            // tagListIndex can be 0 so we compare with undefined
+            if (currentViolations[0]?.name === 'someTagLevelsRequired' && data?.tagListIndex !== undefined && Array.isArray(currentViolations[0]?.data?.errorIndexes)) {
+                return currentViolations
+                    .filter((violation) => violation.data?.errorIndexes?.includes(data?.tagListIndex ?? -1))
+                    .map((violation) => ({
+                        ...violation,
+                        data: {
+                            ...violation.data,
+                            tagName: data?.tagListName,
+                        },
+                    }));
+            }
+
+            // tagOutOfPolicy has special logic because we have to account for multi-level tags and use tagName to find the right tag to put the violation on
+            if (currentViolations[0]?.name === 'tagOutOfPolicy' && data?.tagListName !== undefined && currentViolations[0]?.data?.tagName) {
+                return currentViolations.filter((violation) => violation.data?.tagName === data?.tagListName);
+            }
+
+            return currentViolations;
+        },
+        [violationsByField],
+    );
 
     return {
         getViolationsForField,
diff --git a/src/languages/en.ts b/src/languages/en.ts
index afda15377fcc..5f0f1b0b57a8 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -1755,6 +1755,8 @@ export default {
             workspaceAvatar: 'Workspace avatar',
             mustBeOnlineToViewMembers: 'You must be online in order to view members of this workspace.',
             requested: 'Requested',
+            distanceRates: 'Distance rates',
+            selected: ({selectedNumber}) => `${selectedNumber} selected`,
         },
         type: {
             free: 'Free',
@@ -1816,7 +1818,6 @@ export default {
             makeMember: 'Make member',
             makeAdmin: 'Make admin',
             selectAll: 'Select all',
-            selected: ({selectedNumber}) => `${selectedNumber} selected`,
             error: {
                 genericAdd: 'There was a problem adding this workspace member.',
                 cannotRemove: 'You cannot remove yourself or the workspace owner.',
@@ -1909,6 +1910,23 @@ export default {
             welcomeNote: ({workspaceName}: WelcomeNoteParams) =>
                 `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`,
         },
+        distanceRates: {
+            oopsNotSoFast: 'Oops! Not so fast...',
+            workspaceNeeds: 'A workspace needs at least one enabled distance rate.',
+            distance: 'Distance',
+            centrallyManage: 'Centrally manage rates, choose to track in miles or kilometers, and set a default category.',
+            rate: 'Rate',
+            addRate: 'Add rate',
+            deleteRate: 'Delete rate',
+            deleteRates: 'Delete rates',
+            enableRate: 'Enable rate',
+            disableRate: 'Disable rate',
+            disableRates: 'Disable rates',
+            enableRates: 'Enable rates',
+            status: 'Status',
+            enabled: 'Enabled',
+            disabled: 'Disabled',
+        },
         editor: {
             descriptionInputLabel: 'Description',
             nameInputLabel: 'Name',
@@ -2204,6 +2222,7 @@ export default {
         viewAttachment: 'View attachment',
     },
     parentReportAction: {
+        deletedReport: '[Deleted report]',
         deletedMessage: '[Deleted message]',
         deletedRequest: '[Deleted request]',
         reversedTransaction: '[Reversed transaction]',
@@ -2411,7 +2430,7 @@ export default {
             return '';
         },
         smartscanFailed: 'Receipt scanning failed. Enter details manually.',
-        someTagLevelsRequired: 'Missing tag',
+        someTagLevelsRequired: ({tagName}: ViolationsTagOutOfPolicyParams) => `Missing ${tagName ?? 'Tag'}`,
         tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams) => `${tagName ?? 'Tag'} no longer valid`,
         taxAmountChanged: 'Tax amount was modified',
         taxOutOfPolicy: ({taxName}: ViolationsTaxOutOfPolicyParams) => `${taxName ?? 'Tax'} no longer valid`,
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 88f065682d14..b95beffa0ece 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -1779,6 +1779,8 @@ export default {
             workspaceAvatar: 'Espacio de trabajo avatar',
             mustBeOnlineToViewMembers: 'Debes estar en línea para poder ver los miembros de este espacio de trabajo.',
             requested: 'Solicitado',
+            distanceRates: 'Tasas de distancia',
+            selected: ({selectedNumber}) => `${selectedNumber} seleccionados`,
         },
         type: {
             free: 'Gratis',
@@ -1840,7 +1842,6 @@ export default {
             makeMember: 'Hacer miembro',
             makeAdmin: 'Hacer administrador',
             selectAll: 'Seleccionar todo',
-            selected: ({selectedNumber}) => `${selectedNumber} seleccionados`,
             error: {
                 genericAdd: 'Ha ocurrido un problema al añadir el miembro al espacio de trabajo.',
                 cannotRemove: 'No puedes eliminarte ni a ti mismo ni al dueño del espacio de trabajo.',
@@ -1934,6 +1935,23 @@ export default {
             welcomeNote: ({workspaceName}: WelcomeNoteParams) =>
                 `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`,
         },
+        distanceRates: {
+            oopsNotSoFast: 'Ups! No tan rápido...',
+            workspaceNeeds: 'Un espacio de trabajo necesita al menos una tasa de distancia activa.',
+            distance: 'Distancia',
+            centrallyManage: 'Gestiona centralizadamente las tasas, elige si contabilizar en millas o kilómetros, y define una categoría por defecto',
+            rate: 'Tasa',
+            addRate: 'Agregar tasa',
+            deleteRate: 'Eliminar tasa',
+            deleteRates: 'Eliminar tasas',
+            enableRate: 'Activar tasa',
+            disableRate: 'Desactivar tasa',
+            disableRates: 'Desactivar tasas',
+            enableRates: 'Activar tasas',
+            status: 'Estado',
+            enabled: 'Activada',
+            disabled: 'Desactivada',
+        },
         editor: {
             nameInputLabel: 'Nombre',
             descriptionInputLabel: 'Descripción',
@@ -2692,6 +2710,7 @@ export default {
         viewAttachment: 'Ver archivo adjunto',
     },
     parentReportAction: {
+        deletedReport: '[Informe eliminado]',
         deletedMessage: '[Mensaje eliminado]',
         deletedRequest: '[Solicitud eliminada]',
         reversedTransaction: '[Transacción anulada]',
@@ -2903,7 +2922,7 @@ export default {
             return '';
         },
         smartscanFailed: 'No se pudo escanear el recibo. Introduce los datos manualmente',
-        someTagLevelsRequired: 'Falta etiqueta',
+        someTagLevelsRequired: ({tagName}: ViolationsTagOutOfPolicyParams) => `Falta ${tagName ?? 'Tag'}`,
         tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams) => `La etiqueta ${tagName ? `${tagName} ` : ''}ya no es válida`,
         taxAmountChanged: 'El importe del impuesto fue modificado',
         taxOutOfPolicy: ({taxName}: ViolationsTaxOutOfPolicyParams) => `${taxName ?? 'El impuesto'} ya no es válido`,
diff --git a/src/libs/API/parameters/OpenPolicyDistanceRatesPageParams.ts b/src/libs/API/parameters/OpenPolicyDistanceRatesPageParams.ts
new file mode 100644
index 000000000000..6594e258fdb6
--- /dev/null
+++ b/src/libs/API/parameters/OpenPolicyDistanceRatesPageParams.ts
@@ -0,0 +1,5 @@
+type OpenPolicyDistanceRatesPageParams = {
+    policyID: string;
+};
+
+export default OpenPolicyDistanceRatesPageParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index f529032130bb..b56398f6c4ad 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -159,3 +159,4 @@ export type {default as SwitchToOldDotParams} from './SwitchToOldDotParams';
 export type {default as AcceptJoinRequestParams} from './AcceptJoinRequest';
 export type {default as DeclineJoinRequestParams} from './DeclineJoinRequest';
 export type {default as JoinPolicyInviteLinkParams} from './JoinPolicyInviteLink';
+export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDistanceRatesPageParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 1b41ced4f1d7..d2aa1c84a9a1 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -351,6 +351,7 @@ const READ_COMMANDS = {
     OPEN_POLICY_CATEGORIES_PAGE: 'OpenPolicyCategoriesPage',
     OPEN_WORKSPACE_INVITE_PAGE: 'OpenWorkspaceInvitePage',
     OPEN_DRAFT_WORKSPACE_REQUEST: 'OpenDraftWorkspaceRequest',
+    OPEN_POLICY_DISTANCE_RATES_PAGE: 'OpenPolicyDistanceRatesPage',
 } as const;
 
 type ReadCommand = ValueOf<typeof READ_COMMANDS>;
@@ -386,6 +387,7 @@ type ReadCommandParameters = {
     [READ_COMMANDS.OPEN_POLICY_CATEGORIES_PAGE]: Parameters.OpenPolicyCategoriesPageParams;
     [READ_COMMANDS.OPEN_WORKSPACE_INVITE_PAGE]: Parameters.OpenWorkspaceInvitePageParams;
     [READ_COMMANDS.OPEN_DRAFT_WORKSPACE_REQUEST]: Parameters.OpenDraftWorkspaceRequestParams;
+    [READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE]: Parameters.OpenPolicyDistanceRatesPageParams;
 };
 
 const SIDE_EFFECT_REQUEST_COMMANDS = {
diff --git a/src/libs/AccountUtils.ts b/src/libs/AccountUtils.ts
new file mode 100644
index 000000000000..d903584e15b4
--- /dev/null
+++ b/src/libs/AccountUtils.ts
@@ -0,0 +1,8 @@
+import type {OnyxEntry} from 'react-native-onyx';
+import CONST from '@src/CONST';
+import type {Account} from '@src/types/onyx';
+
+const isValidateCodeFormSubmitting = (account: OnyxEntry<Account>) =>
+    !!account?.isLoading && account.loadingForm === (account.requiresTwoFactorAuth ? CONST.FORMS.VALIDATE_TFA_CODE_FORM : CONST.FORMS.VALIDATE_CODE_FORM);
+
+export default {isValidateCodeFormSubmitting};
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index 6f5dcdf9cda9..8c582b8ab259 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -63,6 +63,7 @@ const loadConciergePage = () => require('../../../pages/ConciergePage').default
 const loadProfileAvatar = () => require('../../../pages/settings/Profile/ProfileAvatar').default as React.ComponentType;
 const loadWorkspaceAvatar = () => require('../../../pages/workspace/WorkspaceAvatar').default as React.ComponentType;
 const loadReportAvatar = () => require('../../../pages/ReportAvatar').default as React.ComponentType;
+const loadReceiptView = () => require('../../../pages/TransactionReceiptPage').default as React.ComponentType;
 const loadWorkspaceJoinUser = () => require('@pages/workspace/WorkspaceJoinUserPage').default as React.ComponentType;
 
 let timezone: Timezone | null;
@@ -363,8 +364,18 @@ function AuthScreens({session, lastOpenedPublicRoomID, isUsingMemoryOnlyKeys = f
                         headerShown: false,
                         presentation: 'transparentModal',
                     }}
+                    listeners={modalScreenListeners}
                     getComponent={loadWorkspaceJoinUser}
                 />
+                <RootStack.Screen
+                    name={SCREENS.TRANSACTION_RECEIPT}
+                    options={{
+                        headerShown: false,
+                        presentation: 'transparentModal',
+                    }}
+                    getComponent={loadReceiptView}
+                    listeners={modalScreenListeners}
+                />
             </RootStack.Navigator>
         </View>
     );
diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
index 976699e31716..28e3bc8f5a88 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
@@ -25,6 +25,7 @@ const workspaceSettingsScreens = {
     [SCREENS.WORKSPACE.MEMBERS]: () => require('../../../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType,
     [SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType,
     [SCREENS.WORKSPACE.TAGS]: () => require('../../../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType,
+    [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType,
 } satisfies Screens;
 
 function BaseCentralPaneNavigator() {
diff --git a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
index f4316009b70b..6641b2c88f1a 100755
--- a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts
@@ -14,6 +14,7 @@ const TAB_TO_CENTRAL_PANE_MAPPING: Record<BottomTabName, CentralPaneName[]> = {
         SCREENS.WORKSPACE.TRAVEL,
         SCREENS.WORKSPACE.MEMBERS,
         SCREENS.WORKSPACE.CATEGORIES,
+        SCREENS.WORKSPACE.DISTANCE_RATES,
     ],
 };
 
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 8a24dc177a80..fc3ad1668cd4 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -22,6 +22,7 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
         [SCREENS.PROFILE_AVATAR]: ROUTES.PROFILE_AVATAR.route,
         [SCREENS.WORKSPACE_AVATAR]: ROUTES.WORKSPACE_AVATAR.route,
         [SCREENS.REPORT_AVATAR]: ROUTES.REPORT_AVATAR.route,
+        [SCREENS.TRANSACTION_RECEIPT]: ROUTES.TRANSACTION_RECEIPT.route,
         [SCREENS.WORKSPACE_JOIN_USER]: ROUTES.WORKSPACE_JOIN_USER.route,
 
         // Sidebar
@@ -71,6 +72,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
                 [SCREENS.WORKSPACE.TAGS]: {
                     path: ROUTES.WORKSPACE_TAGS.route,
                 },
+                [SCREENS.WORKSPACE.DISTANCE_RATES]: {
+                    path: ROUTES.WORKSPACE_DISTANCE_RATES.route,
+                },
             },
         },
         [SCREENS.NOT_FOUND]: '*',
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index decb905ac52f..9f100d0e1efb 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -95,6 +95,9 @@ type CentralPaneNavigatorParamList = {
         policyID: string;
         categoryName: string;
     };
+    [SCREENS.WORKSPACE.DISTANCE_RATES]: {
+        policyID: string;
+    };
 };
 
 type WorkspaceSwitcherNavigatorParamList = {
@@ -592,6 +595,10 @@ type AuthScreensParamList = SharedScreensParamList & {
     [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: NavigatorScreenParams<RightModalNavigatorParamList>;
     [NAVIGATORS.FULL_SCREEN_NAVIGATOR]: NavigatorScreenParams<FullScreenNavigatorParamList>;
     [SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined;
+    [SCREENS.TRANSACTION_RECEIPT]: {
+        reportID: string;
+        transactionID: string;
+    };
 };
 
 type RootStackParamList = PublicScreensParamList & AuthScreensParamList;
diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts
index f03c34b1696e..0a5cfad2d146 100644
--- a/src/libs/NextStepUtils.ts
+++ b/src/libs/NextStepUtils.ts
@@ -73,7 +73,7 @@ function buildNextStep(
 
     const {policyID = '', ownerAccountID = -1, managerID = -1} = report;
     const policy = ReportUtils.getPolicy(policyID);
-    const {submitsTo, harvesting, isPreventSelfApprovalEnabled, preventSelfApprovalEnabled, autoReportingFrequency, autoReportingOffset} = policy;
+    const {submitsTo, harvesting, isPreventSelfApprovalEnabled, preventSelfApproval, autoReportingFrequency, autoReportingOffset} = policy;
     const isOwner = currentUserAccountID === ownerAccountID;
     const isManager = currentUserAccountID === managerID;
     const isSelfApproval = currentUserAccountID === submitsTo;
@@ -164,7 +164,7 @@ function buildNextStep(
             }
 
             // Prevented self submitting
-            if ((isPreventSelfApprovalEnabled ?? preventSelfApprovalEnabled) && isSelfApproval) {
+            if ((isPreventSelfApprovalEnabled ?? preventSelfApproval) && isSelfApproval) {
                 optimisticNextStep.message = [
                     {
                         text: "Oops! Looks like you're submitting to ",
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index fd803a508b4a..3dd23752d5db 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -584,7 +584,7 @@ function getLastMessageTextForReport(report: OnyxEntry<Report>, lastActorDetails
         const properSchemaForModifiedExpenseMessage = ModifiedExpenseMessage.getForReportAction(report?.reportID, lastReportAction);
         lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(properSchemaForModifiedExpenseMessage, true);
     } else if (ReportActionUtils.isTaskAction(lastReportAction)) {
-        lastMessageTextFromReport = TaskUtils.getTaskReportActionMessage(lastReportAction).text;
+        lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(TaskUtils.getTaskReportActionMessage(lastReportAction).text);
     } else if (ReportActionUtils.isCreatedTaskReportAction(lastReportAction)) {
         lastMessageTextFromReport = TaskUtils.getTaskCreatedMessage(lastReportAction);
     } else if (ReportActionUtils.isApprovedOrSubmittedReportAction(lastReportAction)) {
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index f6534e075773..9f5b0ada4cb1 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -236,7 +236,7 @@ function isInstantSubmitEnabled(policy: OnyxEntry<Policy> | EmptyObject): boolea
 /**
  * Checks if policy's approval mode is "optional", a.k.a. "Submit & Close"
  */
-function isSubmitAndClose(policy: OnyxEntry<Policy>): boolean {
+function isSubmitAndClose(policy: OnyxEntry<Policy> | EmptyObject): boolean {
     return policy?.approvalMode === CONST.POLICY.APPROVAL_MODE.OPTIONAL;
 }
 
diff --git a/src/libs/Pusher/pusher.ts b/src/libs/Pusher/pusher.ts
index 3cb15c0f3fc3..48c5e5c1409f 100644
--- a/src/libs/Pusher/pusher.ts
+++ b/src/libs/Pusher/pusher.ts
@@ -1,5 +1,6 @@
 import isObject from 'lodash/isObject';
 import type {Channel, ChannelAuthorizerGenerator, Options} from 'pusher-js/with-encryption';
+import {InteractionManager} from 'react-native';
 import Onyx from 'react-native-onyx';
 import type {LiteralUnion, ValueOf} from 'type-fest';
 import Log from '@libs/Log';
@@ -226,48 +227,50 @@ function subscribe<EventName extends PusherEventName>(
     onResubscribe = () => {},
 ): Promise<void> {
     return new Promise((resolve, reject) => {
-        // We cannot call subscribe() before init(). Prevent any attempt to do this on dev.
-        if (!socket) {
-            throw new Error(`[Pusher] instance not found. Pusher.subscribe()
+        InteractionManager.runAfterInteractions(() => {
+            // We cannot call subscribe() before init(). Prevent any attempt to do this on dev.
+            if (!socket) {
+                throw new Error(`[Pusher] instance not found. Pusher.subscribe()
             most likely has been called before Pusher.init()`);
-        }
+            }
 
-        Log.info('[Pusher] Attempting to subscribe to channel', false, {channelName, eventName});
-        let channel = getChannel(channelName);
-
-        if (!channel || !channel.subscribed) {
-            channel = socket.subscribe(channelName);
-            let isBound = false;
-            channel.bind('pusher:subscription_succeeded', () => {
-                // Check so that we do not bind another event with each reconnect attempt
-                if (!isBound) {
-                    bindEventToChannel(channel, eventName, eventCallback);
-                    resolve();
-                    isBound = true;
-                    return;
-                }
-
-                // When subscribing for the first time we register a success callback that can be
-                // called multiple times when the subscription succeeds again in the future
-                // e.g. as a result of Pusher disconnecting and reconnecting. This callback does
-                // not fire on the first subscription_succeeded event.
-                onResubscribe();
-            });
-
-            channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => {
-                const {type, error, status} = data;
-                Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', {
-                    channelName,
-                    status,
-                    type,
-                    error,
+            Log.info('[Pusher] Attempting to subscribe to channel', false, {channelName, eventName});
+            let channel = getChannel(channelName);
+
+            if (!channel || !channel.subscribed) {
+                channel = socket.subscribe(channelName);
+                let isBound = false;
+                channel.bind('pusher:subscription_succeeded', () => {
+                    // Check so that we do not bind another event with each reconnect attempt
+                    if (!isBound) {
+                        bindEventToChannel(channel, eventName, eventCallback);
+                        resolve();
+                        isBound = true;
+                        return;
+                    }
+
+                    // When subscribing for the first time we register a success callback that can be
+                    // called multiple times when the subscription succeeds again in the future
+                    // e.g. as a result of Pusher disconnecting and reconnecting. This callback does
+                    // not fire on the first subscription_succeeded event.
+                    onResubscribe();
                 });
-                reject(error);
-            });
-        } else {
-            bindEventToChannel(channel, eventName, eventCallback);
-            resolve();
-        }
+
+                channel.bind('pusher:subscription_error', (data: PusherSubscribtionErrorData = {}) => {
+                    const {type, error, status} = data;
+                    Log.hmmm('[Pusher] Issue authenticating with Pusher during subscribe attempt.', {
+                        channelName,
+                        status,
+                        type,
+                        error,
+                    });
+                    reject(error);
+                });
+            } else {
+                bindEventToChannel(channel, eventName, eventCallback);
+                resolve();
+            }
+        });
     });
 }
 
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index c623cb4d167b..b828ad1f17e4 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -45,7 +45,7 @@ import type {
     ReimbursementDeQueuedMessage,
 } from '@src/types/onyx/OriginalMessage';
 import type {Status} from '@src/types/onyx/PersonalDetails';
-import type {NotificationPreference} from '@src/types/onyx/Report';
+import type {NotificationPreference, PendingChatMember} from '@src/types/onyx/Report';
 import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction';
 import type {Receipt, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction';
 import type {EmptyObject} from '@src/types/utils/EmptyObject';
@@ -706,7 +706,7 @@ function isReportApproved(reportOrID: OnyxEntry<Report> | string | EmptyObject):
 /**
  * Checks if the supplied report is an expense report in Open state and status.
  */
-function isDraftExpenseReport(report: OnyxEntry<Report> | EmptyObject): boolean {
+function isOpenExpenseReport(report: OnyxEntry<Report> | EmptyObject): boolean {
     return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN;
 }
 
@@ -1076,6 +1076,20 @@ function findLastAccessedReport(
     return adminReport ?? sortedReports.at(-1) ?? null;
 }
 
+/**
+ * Whether the provided report has expenses
+ */
+function hasExpenses(reportID?: string): boolean {
+    return !!Object.values(allTransactions ?? {}).find((transaction) => `${transaction?.reportID}` === `${reportID}`);
+}
+
+/**
+ * Whether the provided report is a closed expense report with no expenses
+ */
+function isClosedExpenseReportWithNoExpenses(report: OnyxEntry<Report>): boolean {
+    return report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED && isExpenseReport(report) && !hasExpenses(report.reportID);
+}
+
 /**
  * Whether the provided report is an archived room
  */
@@ -2131,7 +2145,7 @@ function getMoneyRequestReportName(report: OnyxEntry<Report>, policy: OnyxEntry<
         return Localize.translateLocal('iou.payerSpentAmount', {payer: payerOrApproverName, amount: formattedAmount});
     }
 
-    if (isProcessingReport(report) || isDraftExpenseReport(report) || moneyRequestTotal === 0) {
+    if (isProcessingReport(report) || isOpenExpenseReport(report) || moneyRequestTotal === 0) {
         return Localize.translateLocal('iou.payerOwesAmount', {payer: payerOrApproverName, amount: formattedAmount});
     }
 
@@ -2212,7 +2226,7 @@ function canEditMoneyRequest(reportAction: OnyxEntry<ReportAction>): boolean {
     const isManager = currentUserAccountID === moneyRequestReport?.managerID;
 
     // Admin & managers can always edit coding fields such as tag, category, billable, etc. As long as the report has a state higher than OPEN.
-    if ((isAdmin || isManager) && !isDraftExpenseReport(moneyRequestReport)) {
+    if ((isAdmin || isManager) && !isOpenExpenseReport(moneyRequestReport)) {
         return true;
     }
 
@@ -2620,6 +2634,10 @@ function getReportName(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> = nu
         return parentReportActionMessage;
     }
 
+    if (isClosedExpenseReportWithNoExpenses(report)) {
+        return Localize.translateLocal('parentReportAction.deletedReport');
+    }
+
     if (isTaskReport(report) && isCanceledTaskReport(report, parentReportAction)) {
         return Localize.translateLocal('parentReportAction.deletedTask');
     }
@@ -2679,6 +2697,14 @@ function getChatRoomSubtitle(report: OnyxEntry<Report>): string | undefined {
     return getPolicyName(report);
 }
 
+/**
+ * Get pending members for reports
+ */
+function getPendingChatMembers(accountIDs: number[], previousPendingChatMembers: PendingChatMember[], pendingAction: PendingAction): PendingChatMember[] {
+    const pendingChatMembers = accountIDs.map((accountID) => ({accountID: accountID.toString(), pendingAction}));
+    return [...previousPendingChatMembers, ...pendingChatMembers];
+}
+
 /**
  * Gets the parent navigation subtitle for the report
  */
@@ -3811,16 +3837,19 @@ function buildOptimisticWorkspaceChats(policyID: string, policyName: string): Op
     const announceReportActionData = {
         [announceCreatedAction.reportActionID]: announceCreatedAction,
     };
-
-    const adminsChatData = buildOptimisticChatReport(
-        [currentUserAccountID ?? -1],
-        CONST.REPORT.WORKSPACE_CHAT_ROOMS.ADMINS,
-        CONST.REPORT.CHAT_TYPE.POLICY_ADMINS,
-        policyID,
-        CONST.POLICY.OWNER_ACCOUNT_ID_FAKE,
-        false,
-        policyName,
-    );
+    const pendingChatMembers = getPendingChatMembers(currentUserAccountID ? [currentUserAccountID] : [], [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+    const adminsChatData = {
+        ...buildOptimisticChatReport(
+            [currentUserAccountID ?? -1],
+            CONST.REPORT.WORKSPACE_CHAT_ROOMS.ADMINS,
+            CONST.REPORT.CHAT_TYPE.POLICY_ADMINS,
+            policyID,
+            CONST.POLICY.OWNER_ACCOUNT_ID_FAKE,
+            false,
+            policyName,
+        ),
+        pendingChatMembers,
+    };
     const adminsChatReportID = adminsChatData.reportID;
     const adminsCreatedAction = buildOptimisticCreatedReportAction(CONST.POLICY.OWNER_EMAIL_FAKE);
     const adminsReportActionData = {
@@ -5142,8 +5171,8 @@ function getAllAncestorReportActionIDs(report: Report | null | undefined): Ances
     return allAncestorIDs;
 }
 
-function canBeAutoReimbursed(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> = null): boolean {
-    if (!policy) {
+function canBeAutoReimbursed(report: OnyxEntry<Report>, policy: OnyxEntry<Policy> | EmptyObject): boolean {
+    if (isEmptyObject(policy)) {
         return false;
     }
     type CurrencyType = (typeof CONST.DIRECT_REIMBURSEMENT_CURRENCIES)[number];
@@ -5192,6 +5221,7 @@ export {
     getPolicyName,
     getPolicyType,
     isArchivedRoom,
+    isClosedExpenseReportWithNoExpenses,
     isExpensifyOnlyParticipantInReport,
     canCreateTaskInReport,
     isPolicyExpenseChatAdmin,
@@ -5336,7 +5366,7 @@ export {
     getIOUReportActionDisplayMessage,
     isWaitingForAssigneeToCompleteTask,
     isGroupChat,
-    isDraftExpenseReport,
+    isOpenExpenseReport,
     shouldUseFullTitleToDisplay,
     parseReportRouteParams,
     getReimbursementQueuedActionMessage,
@@ -5369,6 +5399,7 @@ export {
     getAvailableReportFields,
     reportFieldsEnabled,
     getAllAncestorReportActionIDs,
+    getPendingChatMembers,
     canEditRoomVisibility,
     canEditPolicyDescription,
     getPolicyDescriptionText,
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 3aa4cb63df9a..71b3fd23a03c 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -164,8 +164,11 @@ function getOrderedReportIDs(
     if (isInDefaultMode) {
         nonArchivedReports.sort((a, b) => {
             const compareDates = a?.lastVisibleActionCreated && b?.lastVisibleActionCreated ? compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated) : 0;
+            if (compareDates) {
+                return compareDates;
+            }
             const compareDisplayNames = a?.displayName && b?.displayName ? localeCompare(a.displayName, b.displayName) : 0;
-            return compareDates || compareDisplayNames;
+            return compareDisplayNames;
         });
         // For archived reports ensure that most recent reports are at the top by reversing the order
         archivedReports.sort((a, b) => (a?.lastVisibleActionCreated && b?.lastVisibleActionCreated ? compareStringDates(b.lastVisibleActionCreated, a.lastVisibleActionCreated) : 0));
@@ -328,7 +331,7 @@ function getOptionData({
             const newName = lastAction?.originalMessage?.newName ?? '';
             result.alternateText = Localize.translate(preferredLocale, 'newRoomPage.roomRenamedTo', {newName});
         } else if (ReportActionsUtils.isTaskAction(lastAction)) {
-            result.alternateText = TaskUtils.getTaskReportActionMessage(lastAction).text;
+            result.alternateText = ReportUtils.formatReportLastMessageText(TaskUtils.getTaskReportActionMessage(lastAction).text);
         } else if (
             lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM ||
             lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM ||
diff --git a/src/libs/Violations/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts
index 6153ea62cd0d..fe2e5af537a7 100644
--- a/src/libs/Violations/ViolationsUtils.ts
+++ b/src/libs/Violations/ViolationsUtils.ts
@@ -7,6 +7,106 @@ import type {TranslationPaths} from '@src/languages/types';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {PolicyCategories, PolicyTagList, Transaction, TransactionViolation} from '@src/types/onyx';
 
+/**
+ * Calculates tag out of policy and missing tag violations for the given transaction
+ */
+function getTagViolationsForSingleLevelTags(
+    updatedTransaction: Transaction,
+    transactionViolations: TransactionViolation[],
+    policyRequiresTags: boolean,
+    policyTagList: PolicyTagList,
+): TransactionViolation[] {
+    const policyTagKeys = Object.keys(policyTagList);
+    const policyTagListName = policyTagKeys[0];
+    const policyTags = policyTagList[policyTagListName]?.tags;
+    const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.TAG_OUT_OF_POLICY);
+    const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.MISSING_TAG);
+    const isTagInPolicy = policyTags ? !!policyTags[updatedTransaction.tag ?? '']?.enabled : false;
+    let newTransactionViolations = [...transactionViolations];
+
+    // Add 'tagOutOfPolicy' violation if tag is not in policy
+    if (!hasTagOutOfPolicyViolation && updatedTransaction.tag && !isTagInPolicy) {
+        newTransactionViolations.push({name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, type: 'violation'});
+    }
+
+    // Remove 'tagOutOfPolicy' violation if tag is in policy
+    if (hasTagOutOfPolicyViolation && updatedTransaction.tag && isTagInPolicy) {
+        newTransactionViolations = reject(newTransactionViolations, {name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY});
+    }
+
+    // Remove 'missingTag' violation if tag is valid according to policy
+    if (hasMissingTagViolation && isTagInPolicy) {
+        newTransactionViolations = reject(newTransactionViolations, {name: CONST.VIOLATIONS.MISSING_TAG});
+    }
+
+    // Add 'missingTag violation' if tag is required and not set
+    if (!hasMissingTagViolation && !updatedTransaction.tag && policyRequiresTags) {
+        newTransactionViolations.push({name: CONST.VIOLATIONS.MISSING_TAG, type: 'violation'});
+    }
+    return newTransactionViolations;
+}
+
+/**
+ * Calculates some tag levels required and missing tag violations for the given transaction
+ */
+function getTagViolationsForMultiLevelTags(
+    updatedTransaction: Transaction,
+    transactionViolations: TransactionViolation[],
+    policyRequiresTags: boolean,
+    policyTagList: PolicyTagList,
+): TransactionViolation[] {
+    const policyTagKeys = Object.keys(policyTagList);
+    const selectedTags = updatedTransaction.tag?.split(CONST.COLON) ?? [];
+    let newTransactionViolations = [...transactionViolations];
+    newTransactionViolations = newTransactionViolations.filter(
+        (violation) => violation.name !== CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED && violation.name !== CONST.VIOLATIONS.TAG_OUT_OF_POLICY,
+    );
+
+    // We first get the errorIndexes for someTagLevelsRequired. If it's not empty, we puth SOME_TAG_LEVELS_REQUIRED in Onyx.
+    // Otherwise, we put TAG_OUT_OF_POLICY in Onyx (when applicable)
+    const errorIndexes = [];
+    for (let i = 0; i < policyTagKeys.length; i++) {
+        const isTagRequired = policyTagList[policyTagKeys[i]].required ?? true;
+        const isTagSelected = Boolean(selectedTags[i]);
+        if (isTagRequired && (!isTagSelected || (selectedTags.length === 1 && selectedTags[0] === ''))) {
+            errorIndexes.push(i);
+        }
+    }
+    if (errorIndexes.length !== 0) {
+        newTransactionViolations.push({
+            name: CONST.VIOLATIONS.SOME_TAG_LEVELS_REQUIRED,
+            type: 'violation',
+            data: {
+                errorIndexes,
+            },
+        });
+    } else {
+        let hasInvalidTag = false;
+        for (let i = 0; i < policyTagKeys.length; i++) {
+            const selectedTag = selectedTags[i];
+            const tags = policyTagList[policyTagKeys[i]].tags;
+            const isTagInPolicy = Object.values(tags).some((tag) => tag.name === selectedTag && Boolean(tag.enabled));
+            if (!isTagInPolicy) {
+                newTransactionViolations.push({
+                    name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY,
+                    type: 'violation',
+                    data: {
+                        tagName: policyTagKeys[i],
+                    },
+                });
+                hasInvalidTag = true;
+                break;
+            }
+        }
+        if (!hasInvalidTag) {
+            newTransactionViolations = reject(newTransactionViolations, {
+                name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY,
+            });
+        }
+    }
+    return newTransactionViolations;
+}
+
 const ViolationsUtils = {
     /**
      * Checks a transaction for policy violations and returns an object with Onyx method, key and updated transaction
@@ -22,6 +122,7 @@ const ViolationsUtils = {
     ): OnyxUpdate {
         let newTransactionViolations = [...transactionViolations];
 
+        // Calculate client-side category violations
         if (policyRequiresCategories) {
             const hasCategoryOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === 'categoryOutOfPolicy');
             const hasMissingCategoryViolation = transactionViolations.some((violation) => violation.name === 'missingCategory');
@@ -49,36 +150,12 @@ const ViolationsUtils = {
             }
         }
 
+        // Calculate client-side tag violations
         if (policyRequiresTags) {
-            const policyTagKeys = Object.keys(policyTagList);
-
-            // At the moment, we only return violations for tags for workspaces with single-level tags
-            if (policyTagKeys.length === 1) {
-                const policyTagListName = policyTagKeys[0];
-                const policyTags = policyTagList[policyTagListName]?.tags;
-                const hasTagOutOfPolicyViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.TAG_OUT_OF_POLICY);
-                const hasMissingTagViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.MISSING_TAG);
-                const isTagInPolicy = policyTags ? !!policyTags[updatedTransaction.tag ?? '']?.enabled : false;
-
-                // Add 'tagOutOfPolicy' violation if tag is not in policy
-                if (!hasTagOutOfPolicyViolation && updatedTransaction.tag && !isTagInPolicy) {
-                    newTransactionViolations.push({name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY, type: 'violation'});
-                }
-
-                // Remove 'tagOutOfPolicy' violation if tag is in policy
-                if (hasTagOutOfPolicyViolation && updatedTransaction.tag && isTagInPolicy) {
-                    newTransactionViolations = reject(newTransactionViolations, {name: CONST.VIOLATIONS.TAG_OUT_OF_POLICY});
-                }
-
-                // Remove 'missingTag' violation if tag is valid according to policy
-                if (hasMissingTagViolation && isTagInPolicy) {
-                    newTransactionViolations = reject(newTransactionViolations, {name: CONST.VIOLATIONS.MISSING_TAG});
-                }
-                // Add 'missingTag violation' if tag is required and not set
-                if (!hasMissingTagViolation && !updatedTransaction.tag && policyRequiresTags) {
-                    newTransactionViolations.push({name: CONST.VIOLATIONS.MISSING_TAG, type: 'violation'});
-                }
-            }
+            newTransactionViolations =
+                Object.keys(policyTagList).length === 1
+                    ? getTagViolationsForSingleLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList)
+                    : getTagViolationsForMultiLevelTags(updatedTransaction, newTransactionViolations, policyRequiresTags, policyTagList);
         }
 
         return {
@@ -181,7 +258,7 @@ const ViolationsUtils = {
             case 'smartscanFailed':
                 return translate('violations.smartscanFailed');
             case 'someTagLevelsRequired':
-                return translate('violations.someTagLevelsRequired');
+                return translate('violations.someTagLevelsRequired', {tagName});
             case 'tagOutOfPolicy':
                 return translate('violations.tagOutOfPolicy', {tagName});
             case 'taxAmountChanged':
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index b2e70627b803..4b88bb7a77a8 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -3679,10 +3679,73 @@ function sendMoneyWithWallet(report: OnyxTypes.Report, amount: number, currency:
     Report.notifyNewAction(params.chatReportID, managerID);
 }
 
+function canIOUBePaid(iouReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, chatReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, policy: OnyxEntry<OnyxTypes.Policy> | EmptyObject) {
+    const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
+    const iouCanceled = ReportUtils.isArchivedRoom(chatReport);
+
+    if (isEmptyObject(iouReport)) {
+        return false;
+    }
+
+    const isPayer = ReportUtils.isPayer(
+        {
+            email: currentUserEmail,
+            accountID: userAccountID,
+        },
+        iouReport,
+    );
+
+    const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport);
+    const iouSettled = ReportUtils.isSettled(iouReport?.reportID);
+
+    const {reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport);
+    const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy);
+    return isPayer && !isOpenExpenseReport && !iouSettled && !iouReport?.isWaitingOnBankAccount && reimbursableSpend !== 0 && !iouCanceled && !isAutoReimbursable;
+}
+
+function canApproveIOU(iouReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, chatReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, policy: OnyxEntry<OnyxTypes.Policy> | EmptyObject) {
+    if (isEmptyObject(chatReport)) {
+        return false;
+    }
+    const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(chatReport);
+    if (!isPaidGroupPolicy) {
+        return false;
+    }
+
+    const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
+    const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
+    if (isOnInstantSubmitPolicy && isOnSubmitAndClosePolicy) {
+        return false;
+    }
+
+    const managerID = iouReport?.managerID ?? 0;
+    const isCurrentUserManager = managerID === userAccountID;
+    const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(chatReport);
+
+    const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport);
+    const isApproved = ReportUtils.isReportApproved(iouReport);
+    const iouSettled = ReportUtils.isSettled(iouReport?.reportID);
+
+    return isCurrentUserManager && !isOpenExpenseReport && !isApproved && !iouSettled;
+}
+
+function hasIOUToApproveOrPay(chatReport: OnyxEntry<OnyxTypes.Report> | EmptyObject, excludedIOUReportID: string): boolean {
+    const chatReportActions = ReportActionsUtils.getAllReportActions(chatReport?.reportID ?? '');
+
+    return Object.values(chatReportActions).some((action) => {
+        const iouReport = ReportUtils.getReport(action.childReportID ?? '');
+        const policy = ReportUtils.getPolicy(iouReport?.policyID);
+
+        const shouldShowSettlementButton = canIOUBePaid(iouReport, chatReport, policy) || canApproveIOU(iouReport, chatReport, policy);
+        return action.childReportID?.toString() !== excludedIOUReportID && action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && shouldShowSettlementButton;
+    });
+}
+
 function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) {
     const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null;
     const optimisticApprovedReportAction = ReportUtils.buildOptimisticApprovedReportAction(expenseReport.total ?? 0, expenseReport.currency ?? '', expenseReport.reportID);
     const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, CONST.REPORT.STATUS_NUM.APPROVED);
+    const chatReport = ReportUtils.getReport(expenseReport.chatReportID);
 
     const optimisticReportActionsData: OnyxUpdate = {
         onyxMethod: Onyx.METHOD.MERGE,
@@ -3705,12 +3768,21 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) {
             statusNum: CONST.REPORT.STATUS_NUM.APPROVED,
         },
     };
+
+    const optimisticChatReportData: OnyxUpdate = {
+        onyxMethod: Onyx.METHOD.MERGE,
+        key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport?.chatReportID}`,
+        value: {
+            hasOutstandingChildRequest: hasIOUToApproveOrPay(chatReport, expenseReport?.reportID ?? ''),
+        },
+    };
+
     const optimisticNextStepData: OnyxUpdate = {
         onyxMethod: Onyx.METHOD.MERGE,
         key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`,
         value: optimisticNextStep,
     };
-    const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionsData, optimisticNextStepData];
+    const optimisticData: OnyxUpdate[] = [optimisticIOUReportData, optimisticReportActionsData, optimisticNextStepData, optimisticChatReportData];
 
     const successData: OnyxUpdate[] = [
         {
@@ -3734,6 +3806,13 @@ function approveMoneyRequest(expenseReport: OnyxTypes.Report | EmptyObject) {
                 },
             },
         },
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.chatReportID}`,
+            value: {
+                hasOutstandingChildRequest: chatReport?.hasOutstandingChildRequest,
+            },
+        },
         {
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`,
@@ -4333,4 +4412,6 @@ export {
     cancelPayment,
     navigateToStartStepIfScanFileCannotBeRead,
     savePreferredPaymentMethod,
+    canIOUBePaid,
+    canApproveIOU,
 };
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 1e88520bfa87..0c5df5a85d57 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -17,6 +17,7 @@ import type {
     DeleteWorkspaceParams,
     OpenDraftWorkspaceRequestParams,
     OpenPolicyCategoriesPageParams,
+    OpenPolicyDistanceRatesPageParams,
     OpenWorkspaceInvitePageParams,
     OpenWorkspaceMembersPageParams,
     OpenWorkspaceParams,
@@ -57,7 +58,7 @@ import type {
     ReportAction,
     Transaction,
 } from '@src/types/onyx';
-import type {Errors} from '@src/types/onyx/OnyxCommon';
+import type {Errors, PendingAction} from '@src/types/onyx/OnyxCommon';
 import type {OriginalMessageJoinPolicyChangeLog} from '@src/types/onyx/OriginalMessage';
 import type {Attributes, CustomUnit, Rate, Unit} from '@src/types/onyx/Policy';
 import type {OnyxData} from '@src/types/onyx/Request';
@@ -66,6 +67,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
 
 type AnnounceRoomMembersOnyxData = {
     onyxOptimisticData: OnyxUpdate[];
+    onyxSuccessData: OnyxUpdate[];
     onyxFailureData: OnyxUpdate[];
 };
 
@@ -373,6 +375,7 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]
     const announceRoomMembers: AnnounceRoomMembersOnyxData = {
         onyxOptimisticData: [],
         onyxFailureData: [],
+        onyxSuccessData: [],
     };
 
     if (!announceReport) {
@@ -382,6 +385,7 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]
     if (announceReport?.participantAccountIDs) {
         // Everyone in special policy rooms is visible
         const participantAccountIDs = [...announceReport.participantAccountIDs, ...accountIDs];
+        const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReport?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
 
         announceRoomMembers.onyxOptimisticData.push({
             onyxMethod: Onyx.METHOD.MERGE,
@@ -389,6 +393,7 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]
             value: {
                 participantAccountIDs,
                 visibleChatMemberAccountIDs: participantAccountIDs,
+                pendingChatMembers,
             },
         });
     }
@@ -399,6 +404,14 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]
         value: {
             participantAccountIDs: announceReport?.participantAccountIDs,
             visibleChatMemberAccountIDs: announceReport?.visibleChatMemberAccountIDs,
+            pendingChatMembers: announceReport?.pendingChatMembers ?? null,
+        },
+    });
+    announceRoomMembers.onyxSuccessData.push({
+        onyxMethod: Onyx.METHOD.MERGE,
+        key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`,
+        value: {
+            pendingChatMembers: announceReport?.pendingChatMembers ?? null,
         },
     });
     return announceRoomMembers;
@@ -572,6 +585,7 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: numbe
     const announceRoomMembers: AnnounceRoomMembersOnyxData = {
         onyxOptimisticData: [],
         onyxFailureData: [],
+        onyxSuccessData: [],
     };
 
     if (!announceReport) {
@@ -580,12 +594,15 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: numbe
 
     if (announceReport?.participantAccountIDs) {
         const remainUsers = announceReport.participantAccountIDs.filter((e) => !accountIDs.includes(e));
+        const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReport?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
+
         announceRoomMembers.onyxOptimisticData.push({
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`,
             value: {
                 participantAccountIDs: [...remainUsers],
                 visibleChatMemberAccountIDs: [...remainUsers],
+                pendingChatMembers,
             },
         });
 
@@ -595,6 +612,14 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: numbe
             value: {
                 participantAccountIDs: announceReport.participantAccountIDs,
                 visibleChatMemberAccountIDs: announceReport.visibleChatMemberAccountIDs,
+                pendingChatMembers: announceReport?.pendingChatMembers ?? null,
+            },
+        });
+        announceRoomMembers.onyxSuccessData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`,
+            value: {
+                pendingChatMembers: announceReport?.pendingChatMembers ?? null,
             },
         });
     }
@@ -638,6 +663,26 @@ function removeMembers(accountIDs: number[], policyID: string) {
         ...announceRoomMembers.onyxOptimisticData,
     ];
 
+    const successData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: membersListKey,
+            value: successMembersState,
+        },
+        ...announceRoomMembers.onyxSuccessData,
+    ];
+
+    const failureData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: membersListKey,
+            value: failureMembersState,
+        },
+        ...announceRoomMembers.onyxFailureData,
+    ];
+
+    const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
+
     workspaceChats.forEach((report) => {
         optimisticData.push({
             onyxMethod: Onyx.METHOD.MERGE,
@@ -647,6 +692,21 @@ function removeMembers(accountIDs: number[], policyID: string) {
                 stateNum: CONST.REPORT.STATE_NUM.APPROVED,
                 oldPolicyName: policy.name,
                 hasDraft: false,
+                pendingChatMembers,
+            },
+        });
+        successData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`,
+            value: {
+                pendingChatMembers: null,
+            },
+        });
+        failureData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`,
+            value: {
+                pendingChatMembers: null,
             },
         });
     });
@@ -683,23 +743,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
         }
     }
 
-    const successData: OnyxUpdate[] = [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: membersListKey,
-            value: successMembersState,
-        },
-    ];
-
     const filteredWorkspaceChats = workspaceChats.filter((report): report is Report => report !== null);
-    const failureData: OnyxUpdate[] = [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: membersListKey,
-            value: failureMembersState,
-        },
-        ...announceRoomMembers.onyxFailureData,
-    ];
 
     filteredWorkspaceChats.forEach(({reportID, stateNum, statusNum, hasDraft, oldPolicyName = null}) => {
         failureData.push({
@@ -750,7 +794,7 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`,
             value: {
-                ...memberRoles.reduce((member: Record<number, {role: string; pendingAction: string | null}>, current) => {
+                ...memberRoles.reduce((member: Record<number, {role: string; pendingAction: PendingAction}>, current) => {
                     // eslint-disable-next-line no-param-reassign
                     member[current.accountID] = {role: current?.role, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE};
                     return member;
@@ -765,7 +809,7 @@ function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newR
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}`,
             value: {
-                ...memberRoles.reduce((member: Record<number, {role: string; pendingAction: string | null}>, current) => {
+                ...memberRoles.reduce((member: Record<number, {role: string; pendingAction: PendingAction}>, current) => {
                     // eslint-disable-next-line no-param-reassign
                     member[current.accountID] = {role: current?.role, pendingAction: null};
                     return member;
@@ -847,6 +891,12 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I
                 },
                 isOptimisticReport: true,
                 hasOutstandingChildRequest,
+                pendingChatMembers: [
+                    {
+                        accountID: accountID.toString(),
+                        pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+                    },
+                ],
             },
         });
         workspaceMembersChats.onyxOptimisticData.push({
@@ -866,6 +916,7 @@ function createPolicyExpenseChats(policyID: string, invitedEmailsToAccountIDs: I
                     createChat: null,
                 },
                 isOptimisticReport: false,
+                pendingChatMembers: null,
             },
         });
         workspaceMembersChats.onyxSuccessData.push({
@@ -943,6 +994,7 @@ function addMembersToWorkspace(invitedEmailsToAccountIDs: InvitedEmailsToAccount
         },
         ...newPersonalDetailsOnyxData.finallyData,
         ...membersChats.onyxSuccessData,
+        ...announceRoomMembers.onyxSuccessData,
     ];
 
     const failureData: OnyxUpdate[] = [
@@ -1577,7 +1629,6 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
         expenseReportActionData,
         expenseCreatedReportActionID,
     } = ReportUtils.buildOptimisticWorkspaceChats(policyID, workspaceName);
-
     const optimisticData: OnyxUpdate[] = [
         {
             onyxMethod: Onyx.METHOD.SET,
@@ -1701,6 +1752,7 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
                     addWorkspaceRoom: null,
                 },
                 pendingAction: null,
+                pendingChatMembers: [],
             },
         },
         {
@@ -2707,6 +2759,16 @@ function declineJoinRequest(reportID: string, reportAction: OnyxEntry<ReportActi
     API.write(WRITE_COMMANDS.DECLINE_JOIN_REQUEST, parameters, {optimisticData, failureData, successData});
 }
 
+function openPolicyDistanceRatesPage(policyID?: string) {
+    if (!policyID) {
+        return;
+    }
+
+    const params: OpenPolicyDistanceRatesPageParams = {policyID};
+
+    API.read(READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE, params);
+}
+
 export {
     removeMembers,
     updateWorkspaceMembersRole,
@@ -2761,4 +2823,5 @@ export {
     declineJoinRequest,
     createPolicyCategory,
     clearCategoryErrors,
+    openPolicyDistanceRatesPage,
 };
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 94fe324d306a..f3cbabcd453c 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -2252,7 +2252,7 @@ function openReportFromDeepLink(url: string, isAuthenticated: boolean) {
             Navigation.waitForProtectedRoutes().then(() => {
                 const route = ReportUtils.getRouteFromLink(url);
 
-                if (route && Session.isAnonymousUser() && !Session.canAccessRouteByAnonymousUser(route)) {
+                if (route && Session.isAnonymousUser() && !Session.canAnonymousUserAccessRoute(route)) {
                     Session.signOutAndRedirectToSignIn(true);
                     return;
                 }
@@ -2416,6 +2416,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record<string
 
     const logins = inviteeEmails.map((memberLogin) => PhoneNumber.addSMSDomainIfPhoneNumber(memberLogin));
     const newPersonalDetailsOnyxData = PersonalDetailsUtils.getNewPersonalDetailsOnyxData(logins, inviteeAccountIDs);
+    const pendingChatMembers = ReportUtils.getPendingChatMembers(inviteeAccountIDs, report?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
 
     const optimisticData: OnyxUpdate[] = [
         {
@@ -2424,13 +2425,22 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record<string
             value: {
                 participantAccountIDs: participantAccountIDsAfterInvitation,
                 visibleChatMemberAccountIDs: visibleMemberAccountIDsAfterInvitation,
+                pendingChatMembers,
             },
         },
         ...newPersonalDetailsOnyxData.optimisticData,
     ];
 
-    const successData: OnyxUpdate[] = newPersonalDetailsOnyxData.finallyData;
-
+    const successData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+            value: {
+                pendingChatMembers: report?.pendingChatMembers ?? null,
+            },
+        },
+        ...newPersonalDetailsOnyxData.finallyData,
+    ];
     const failureData: OnyxUpdate[] = [
         {
             onyxMethod: Onyx.METHOD.MERGE,
@@ -2438,6 +2448,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record<string
             value: {
                 participantAccountIDs: report.participantAccountIDs,
                 visibleChatMemberAccountIDs: report.visibleChatMemberAccountIDs,
+                pendingChatMembers: report?.pendingChatMembers ?? null,
             },
         },
         ...newPersonalDetailsOnyxData.finallyData,
@@ -2459,6 +2470,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) {
 
     const participantAccountIDsAfterRemoval = report?.participantAccountIDs?.filter((id: number) => !targetAccountIDs.includes(id));
     const visibleChatMemberAccountIDsAfterRemoval = report?.visibleChatMemberAccountIDs?.filter((id: number) => !targetAccountIDs.includes(id));
+    const pendingChatMembers = ReportUtils.getPendingChatMembers(targetAccountIDs, report?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
 
     const optimisticData: OnyxUpdate[] = [
         {
@@ -2467,6 +2479,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) {
             value: {
                 participantAccountIDs: participantAccountIDsAfterRemoval,
                 visibleChatMemberAccountIDs: visibleChatMemberAccountIDsAfterRemoval,
+                pendingChatMembers,
             },
         },
     ];
@@ -2478,6 +2491,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) {
             value: {
                 participantAccountIDs: report?.participantAccountIDs,
                 visibleChatMemberAccountIDs: report?.visibleChatMemberAccountIDs,
+                pendingChatMembers: report?.pendingChatMembers ?? null,
             },
         },
     ];
@@ -2491,6 +2505,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) {
             value: {
                 participantAccountIDs: participantAccountIDsAfterRemoval,
                 visibleChatMemberAccountIDs: visibleChatMemberAccountIDsAfterRemoval,
+                pendingChatMembers: report?.pendingChatMembers ?? null,
             },
         },
     ];
@@ -2889,6 +2904,17 @@ function clearNewRoomFormError() {
     });
 }
 
+function getReportDraftStatus(reportID: string) {
+    if (!allReports) {
+        return false;
+    }
+
+    if (!allReports[reportID]) {
+        return false;
+    }
+    return allReports[reportID]?.hasDraft;
+}
+
 function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEntry<ReportAction>, resolution: ValueOf<typeof CONST.REPORT.ACTIONABLE_MENTION_WHISPER_RESOLUTION>) {
     const message = reportAction?.message?.[0];
     if (!message) {
@@ -2939,6 +2965,7 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt
 }
 
 export {
+    getReportDraftStatus,
     searchInServer,
     addComment,
     addAttachment,
diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts
index 6a0f53c3d058..07bc7f3ed418 100644
--- a/src/libs/actions/Session/index.ts
+++ b/src/libs/actions/Session/index.ts
@@ -925,7 +925,7 @@ function signInWithValidateCodeAndNavigate(accountID: number, validateCode: stri
     if (exitTo) {
         handleExitToNavigation(exitTo);
     } else {
-        Navigation.navigate(ROUTES.HOME);
+        Navigation.goBack();
     }
 }
 
@@ -935,7 +935,7 @@ function signInWithValidateCodeAndNavigate(accountID: number, validateCode: stri
  * @param {string} route
  */
 
-const canAccessRouteByAnonymousUser = (route: string) => {
+const canAnonymousUserAccessRoute = (route: string) => {
     const reportID = ReportUtils.getReportIDFromLink(route);
     if (reportID) {
         return true;
@@ -948,9 +948,10 @@ const canAccessRouteByAnonymousUser = (route: string) => {
     if (route.startsWith('/')) {
         routeRemovedReportId = routeRemovedReportId.slice(1);
     }
-    const routesCanAccessByAnonymousUser = [ROUTES.SIGN_IN_MODAL, ROUTES.REPORT_WITH_ID_DETAILS.route, ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE.route, ROUTES.CONCIERGE];
+    const routesAccessibleByAnonymousUser = [ROUTES.SIGN_IN_MODAL, ROUTES.REPORT_WITH_ID_DETAILS.route, ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE.route, ROUTES.CONCIERGE];
+    const isMagicLink = CONST.REGEX.ROUTES.VALIDATE_LOGIN.test(`/${route}`);
 
-    if ((routesCanAccessByAnonymousUser as string[]).includes(routeRemovedReportId)) {
+    if ((routesAccessibleByAnonymousUser as string[]).includes(routeRemovedReportId) || isMagicLink) {
         return true;
     }
     return false;
@@ -986,7 +987,7 @@ export {
     toggleTwoFactorAuth,
     validateTwoFactorAuth,
     waitForUserSignIn,
-    canAccessRouteByAnonymousUser,
+    canAnonymousUserAccessRoute,
     signInWithSupportAuthToken,
     isSupportAuthToken,
     hasStashedSession,
diff --git a/src/libs/navigateAfterJoinRequest/index.ts b/src/libs/navigateAfterJoinRequest/index.ts
index b9e533208ec2..b53c59d678c9 100644
--- a/src/libs/navigateAfterJoinRequest/index.ts
+++ b/src/libs/navigateAfterJoinRequest/index.ts
@@ -4,5 +4,6 @@ import ROUTES from '@src/ROUTES';
 const navigateAfterJoinRequest = () => {
     Navigation.goBack(undefined, false, true);
     Navigation.navigate(ROUTES.ALL_SETTINGS);
+    Navigation.navigate(ROUTES.SETTINGS_WORKSPACES);
 };
 export default navigateAfterJoinRequest;
diff --git a/src/pages/ReportParticipantsPage.tsx b/src/pages/ReportParticipantsPage.tsx
index 24d696ca2fb0..a2b2f094ac26 100755
--- a/src/pages/ReportParticipantsPage.tsx
+++ b/src/pages/ReportParticipantsPage.tsx
@@ -50,8 +50,10 @@ const getAllParticipants = (
                 !!userPersonalDetail?.login && !CONST.RESTRICTED_ACCOUNT_IDS.includes(accountID) ? LocalePhoneNumber.formatPhoneNumber(userPersonalDetail.login) : translate('common.hidden');
             const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(userPersonalDetail);
 
+            const pendingChatMember = report?.pendingChatMembers?.find((member) => member.accountID === accountID.toString());
             return {
                 alternateText: userLogin,
+                pendingAction: pendingChatMember?.pendingAction,
                 displayName,
                 accountID: userPersonalDetail?.accountID ?? accountID,
                 icons: [
diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx
index 557211dc0235..47ba5174e4b0 100644
--- a/src/pages/RoomMembersPage.tsx
+++ b/src/pages/RoomMembersPage.tsx
@@ -183,6 +183,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
                     return;
                 }
             }
+            const pendingChatMember = report?.pendingChatMembers?.find((member) => member.accountID === accountID.toString());
 
             result.push({
                 keyForList: String(accountID),
@@ -199,6 +200,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
                         id: Number(accountID),
                     },
                 ],
+                pendingAction: pendingChatMember?.pendingAction,
             });
         });
 
diff --git a/src/pages/TransactionReceiptPage.tsx b/src/pages/TransactionReceiptPage.tsx
new file mode 100644
index 000000000000..8db9e05a5139
--- /dev/null
+++ b/src/pages/TransactionReceiptPage.tsx
@@ -0,0 +1,79 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useEffect} from 'react';
+import type {OnyxEntry} from 'react-native-onyx';
+import {withOnyx} from 'react-native-onyx';
+import AttachmentModal from '@components/AttachmentModal';
+import Navigation from '@libs/Navigation/Navigation';
+import type {AuthScreensParamList} from '@libs/Navigation/types';
+import * as ReceiptUtils from '@libs/ReceiptUtils';
+import * as ReportActionUtils from '@libs/ReportActionsUtils';
+import * as ReportUtils from '@libs/ReportUtils';
+import * as TransactionUtils from '@libs/TransactionUtils';
+import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
+import * as ReportActions from '@userActions/Report';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {Report, ReportMetadata, Transaction} from '@src/types/onyx';
+
+type TransactionReceiptOnyxProps = {
+    report: OnyxEntry<Report>;
+    transaction: OnyxEntry<Transaction>;
+    reportMetadata: OnyxEntry<ReportMetadata>;
+};
+
+type TransactionReceiptProps = TransactionReceiptOnyxProps & StackScreenProps<AuthScreensParamList, typeof SCREENS.TRANSACTION_RECEIPT>;
+
+function TransactionReceipt({transaction, report, reportMetadata = {isLoadingInitialReportActions: true}, route}: TransactionReceiptProps) {
+    const receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction);
+
+    const imageSource = tryResolveUrlFromApiRoot(receiptURIs.image);
+
+    const isLocalFile = receiptURIs.isLocalFile;
+
+    const parentReportAction = ReportActionUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? '');
+    const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT);
+    const isEReceipt = transaction && TransactionUtils.hasEReceipt(transaction);
+
+    useEffect(() => {
+        if (report && transaction) {
+            return;
+        }
+        ReportActions.openReport(route.params.reportID);
+        // I'm disabling the warning, as it expects to use exhaustive deps, even though we want this useEffect to run only on the first render.
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, []);
+
+    return (
+        <AttachmentModal
+            source={imageSource}
+            isAuthTokenRequired={!isLocalFile}
+            report={report}
+            isReceiptAttachment
+            canEditReceipt={canEditReceipt}
+            allowDownload={!isEReceipt}
+            originalFileName={receiptURIs?.filename}
+            defaultOpen
+            onModalClose={() => {
+                Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? ''));
+            }}
+            isLoading={!transaction && reportMetadata?.isLoadingInitialReportActions}
+            shouldShowNotFoundPage={(report?.parentReportID ?? '') !== transaction?.reportID}
+        />
+    );
+}
+
+TransactionReceipt.displayName = 'TransactionReceipt';
+
+export default withOnyx<TransactionReceiptProps, TransactionReceiptOnyxProps>({
+    report: {
+        key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '0'}`,
+    },
+    transaction: {
+        key: ({route}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${route.params.transactionID ?? '0'}`,
+    },
+    reportMetadata: {
+        key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_METADATA}${route.params.reportID ?? '0'}`,
+    },
+})(TransactionReceipt);
diff --git a/src/pages/ValidateLoginPage/index.tsx b/src/pages/ValidateLoginPage/index.tsx
index 2289547afe56..d7e975890186 100644
--- a/src/pages/ValidateLoginPage/index.tsx
+++ b/src/pages/ValidateLoginPage/index.tsx
@@ -16,7 +16,7 @@ function ValidateLoginPage({
     useEffect(() => {
         // Wait till navigation becomes available
         Navigation.isNavigationReady().then(() => {
-            if (session?.authToken) {
+            if (session?.authToken && session?.authTokenType !== CONST.AUTH_TOKEN_TYPES.ANONYMOUS) {
                 // If already signed in, do not show the validate code if not on web,
                 // because we don't want to block the user with the interstitial page.
                 if (exitTo) {
diff --git a/src/pages/ValidateLoginPage/index.website.tsx b/src/pages/ValidateLoginPage/index.website.tsx
index 866b061d964f..2acad7815754 100644
--- a/src/pages/ValidateLoginPage/index.website.tsx
+++ b/src/pages/ValidateLoginPage/index.website.tsx
@@ -20,7 +20,7 @@ function ValidateLoginPage({
 }: ValidateLoginPageProps<ValidateLoginPageOnyxProps>) {
     const login = credentials?.login;
     const autoAuthState = session?.autoAuthState ?? CONST.AUTO_AUTH_STATE.NOT_STARTED;
-    const isSignedIn = !!session?.authToken;
+    const isSignedIn = !!session?.authToken && session?.authTokenType !== CONST.AUTH_TOKEN_TYPES.ANONYMOUS;
     const is2FARequired = !!account?.requiresTwoFactorAuth;
     const cachedAccountID = credentials?.accountID;
 
diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js
index da5a8e4aae27..2e19a2c6a940 100644
--- a/src/pages/home/ReportScreen.js
+++ b/src/pages/home/ReportScreen.js
@@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native';
 import lodashGet from 'lodash/get';
 import PropTypes from 'prop-types';
 import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
-import {View} from 'react-native';
+import {InteractionManager, View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
 import _ from 'underscore';
 import Banner from '@components/Banner';
@@ -387,8 +387,13 @@ function ReportScreen({
         Performance.markEnd(CONST.TIMING.CHAT_RENDER);
 
         fetchReportIfNeeded();
-        ComposerActions.setShouldShowComposeInput(true);
+        const interactionTask = InteractionManager.runAfterInteractions(() => {
+            ComposerActions.setShouldShowComposeInput(true);
+        });
         return () => {
+            if (interactionTask) {
+                interactionTask.cancel();
+            }
             if (!didSubscribeToReportLeavingEvents) {
                 return;
             }
@@ -474,10 +479,20 @@ function ReportScreen({
         // any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null.
         // Existing reports created will have empty fields for `pendingFields`.
         const didCreateReportSuccessfully = !report.pendingFields || (!report.pendingFields.addWorkspaceRoom && !report.pendingFields.createChat);
+        let interactionTask;
         if (!didSubscribeToReportLeavingEvents.current && didCreateReportSuccessfully) {
-            Report.subscribeToReportLeavingEvents(reportID);
-            didSubscribeToReportLeavingEvents.current = true;
+            interactionTask = InteractionManager.runAfterInteractions(() => {
+                Report.subscribeToReportLeavingEvents(reportID);
+                didSubscribeToReportLeavingEvents.current = true;
+            });
         }
+
+        return () => {
+            if (!interactionTask) {
+                return;
+            }
+            interactionTask.cancel();
+        };
     }, [report, didSubscribeToReportLeavingEvents, reportID]);
 
     const onListLayout = useCallback((e) => {
@@ -571,8 +586,8 @@ function ReportScreen({
                                 )}
 
                                 {/* Note: The ReportActionsSkeletonView should be allowed to mount even if the initial report actions are not loaded.
-                     If we prevent rendering the report while they are loading then
-                     we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
+                                         If we prevent rendering the report while they are loading then
+                                         we'll unnecessarily unmount the ReportActionsView which will clear the new marker lines initial state. */}
                                 {(!isReportReadyForDisplay || isLoadingInitialReportActions || isLoading) && <ReportActionsSkeletonView />}
 
                                 {isReportReadyForDisplay ? (
@@ -584,9 +599,7 @@ function ReportScreen({
                                         isEmptyChat={isEmptyChat}
                                         lastReportAction={lastReportAction}
                                     />
-                                ) : (
-                                    <ReportFooter isReportReadyForDisplay={false} />
-                                )}
+                                ) : null}
                             </View>
                         </DragAndDropProvider>
                     </FullPageNotFoundView>
diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
index af2d0b9eab56..308eac71b5b7 100644
--- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
+++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.tsx
@@ -419,15 +419,23 @@ function ComposerWithSuggestions(
                 Report.setReportWithDraft(reportID, true);
             }
 
+            const hasDraftStatus = Report.getReportDraftStatus(reportID);
+
+            /**
+             * The extra `!hasDraftStatus` check is to prevent the draft being set
+             * when the user navigates to the ReportScreen. This doesn't alter anything
+             * in terms of functionality.
+             */
             // The draft has been deleted.
-            if (newCommentConverted.length === 0) {
+            if (newCommentConverted.length === 0 && hasDraftStatus) {
                 Report.setReportWithDraft(reportID, false);
             }
 
             commentRef.current = newCommentConverted;
+            const isDraftCommentEmpty = getDraftComment(reportID) === '';
             if (shouldDebounceSaveComment) {
                 debouncedSaveReportComment(reportID, newCommentConverted);
-            } else {
+            } else if (isDraftCommentEmpty && newCommentConverted.length !== 0) {
                 Report.saveReportComment(reportID, newCommentConverted || '');
             }
             if (newCommentConverted) {
@@ -676,13 +684,6 @@ function ComposerWithSuggestions(
     useEffect(() => {
         // Scrolls the composer to the bottom and sets the selection to the end, so that longer drafts are easier to edit
         updateMultilineInputRange(textInputRef.current, !!shouldAutoFocus);
-
-        if (value.length === 0) {
-            return;
-        }
-
-        Report.setReportWithDraft(reportID, true);
-
         // eslint-disable-next-line react-hooks/exhaustive-deps
     }, []);
     useImperativeHandle(
diff --git a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
index 1abc6567bc7b..f3780528cabe 100644
--- a/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
+++ b/src/pages/home/report/ReportActionCompose/SilentCommentUpdater/index.tsx
@@ -17,7 +17,17 @@ function SilentCommentUpdater({comment, commentRef, reportID, value, updateComme
     const prevPreferredLocale = usePrevious(preferredLocale);
 
     useEffect(() => {
+        /**
+         * Schedules the callback to run when the main thread is idle.
+         */
+        if ('requestIdleCallback' in window) {
+            const callbackID = requestIdleCallback(() => {
+                updateComment(comment ?? '');
+            });
+            return () => cancelIdleCallback(callbackID);
+        }
         updateComment(comment ?? '');
+
         // eslint-disable-next-line react-hooks/exhaustive-deps -- We need to run this on mount
     }, []);
 
diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx
index cc8676467958..7285f550d3ca 100644
--- a/src/pages/home/report/ReportActionItem.tsx
+++ b/src/pages/home/report/ReportActionItem.tsx
@@ -435,7 +435,9 @@ function ReportActionItem({
                 />
             );
         } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW) {
-            children = (
+            children = ReportUtils.isClosedExpenseReportWithNoExpenses(iouReport) ? (
+                <RenderHTML html={`<comment>${translate('parentReportAction.deletedReport')}</comment>`} />
+            ) : (
                 <ReportPreview
                     iouReportID={ReportActionsUtils.getIOUReportIDFromReportActionPreview(action)}
                     chatReportID={report.reportID}
@@ -878,16 +880,16 @@ export default withOnyx<ReportActionItemProps, ReportActionItemOnyxProps>({
     iouReport: {
         key: ({action}) => {
             const iouReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action);
-            return `${ONYXKEYS.COLLECTION.REPORT}${iouReportID ?? ''}`;
+            return `${ONYXKEYS.COLLECTION.REPORT}${iouReportID ?? 0}`;
         },
         initialValue: {} as OnyxTypes.Report,
     },
     policyReportFields: {
-        key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID ?? ''}`,
+        key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID ?? 0}`,
         initialValue: {},
     },
     policy: {
-        key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID ?? ''}`,
+        key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID ?? 0}`,
         initialValue: {} as OnyxTypes.Policy,
     },
     emojiReactions: {
diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js
index dabf7a9f8d36..4711a105a8f1 100644
--- a/src/pages/home/report/ReportActionsList.js
+++ b/src/pages/home/report/ReportActionsList.js
@@ -125,6 +125,8 @@ function isMessageUnread(message, lastReadTime) {
     return Boolean(message && lastReadTime && message.created && lastReadTime < message.created);
 }
 
+const onScrollToIndexFailed = () => {};
+
 function ReportActionsList({
     report,
     parentReportAction,
@@ -314,7 +316,9 @@ function ReportActionsList({
             if (unsubscribe) {
                 unsubscribe();
             }
-            Report.unsubscribeFromReportChannel(report.reportID);
+            InteractionManager.runAfterInteractions(() => {
+                Report.unsubscribeFromReportChannel(report.reportID);
+            });
         };
 
         newActionUnsubscribeMap[report.reportID] = cleanup;
@@ -341,11 +345,12 @@ function ReportActionsList({
         }
     };
 
-    const trackVerticalScrolling = (event) => {
+    const trackVerticalScrolling = useCallback((event) => {
         scrollingVerticalOffset.current = event.nativeEvent.contentOffset.y;
         handleUnreadFloatingButton();
         onScroll(event);
-    };
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, []);
 
     const scrollToBottomAndMarkReportAsRead = () => {
         reportScrollManager.scrollToBottom();
@@ -493,7 +498,7 @@ function ReportActionsList({
 
     // Native mobile does not render updates flatlist the changes even though component did update called.
     // To notify there something changes we can use extraData prop to flatlist
-    const extraData = [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)];
+    const extraData = useMemo(() => [isSmallScreenWidth ? currentUnreadMarker : undefined, ReportUtils.isArchivedRoom(report)], [currentUnreadMarker, isSmallScreenWidth, report]);
     const hideComposer = !ReportUtils.canUserPerformWriteAction(report);
     const shouldShowReportRecipientLocalTime = ReportUtils.canShowReportRecipientLocalTime(personalDetailsList, report, currentUserPersonalDetails.accountID) && !isComposerFullSize;
 
@@ -570,7 +575,7 @@ function ReportActionsList({
                     keyboardShouldPersistTaps="handled"
                     onLayout={onLayoutInner}
                     onScroll={trackVerticalScrolling}
-                    onScrollToIndexFailed={() => {}}
+                    onScrollToIndexFailed={onScrollToIndexFailed}
                     extraData={extraData}
                 />
             </Animated.View>
diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js
index ca3ee7d2ab6a..e1c98a22bec8 100755
--- a/src/pages/home/report/ReportActionsView.js
+++ b/src/pages/home/report/ReportActionsView.js
@@ -2,6 +2,7 @@ import {useIsFocused} from '@react-navigation/native';
 import lodashGet from 'lodash/get';
 import PropTypes from 'prop-types';
 import React, {useCallback, useContext, useEffect, useMemo, useRef} from 'react';
+import {InteractionManager} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
 import _ from 'underscore';
 import networkPropTypes from '@components/networkPropTypes';
@@ -117,7 +118,15 @@ function ReportActionsView(props) {
     };
 
     useEffect(() => {
-        openReportIfNecessary();
+        const interactionTask = InteractionManager.runAfterInteractions(() => {
+            openReportIfNecessary();
+        });
+        return () => {
+            if (!interactionTask) {
+                return;
+            }
+            interactionTask.cancel();
+        };
         // eslint-disable-next-line react-hooks/exhaustive-deps
     }, []);
 
@@ -171,10 +180,20 @@ function ReportActionsView(props) {
         // any `pendingFields.createChat` or `pendingFields.addWorkspaceRoom` fields are set to null.
         // Existing reports created will have empty fields for `pendingFields`.
         const didCreateReportSuccessfully = !props.report.pendingFields || (!props.report.pendingFields.addWorkspaceRoom && !props.report.pendingFields.createChat);
+        let interactionTask;
         if (!didSubscribeToReportTypingEvents.current && didCreateReportSuccessfully) {
-            Report.subscribeToReportTypingEvents(reportID);
-            didSubscribeToReportTypingEvents.current = true;
+            interactionTask = InteractionManager.runAfterInteractions(() => {
+                Report.subscribeToReportTypingEvents(reportID);
+                didSubscribeToReportTypingEvents.current = true;
+            });
         }
+
+        return () => {
+            if (!interactionTask) {
+                return;
+            }
+            interactionTask.cancel();
+        };
     }, [props.report.pendingFields, didSubscribeToReportTypingEvents, reportID]);
 
     const oldestReportAction = useMemo(() => _.last(props.reportActions), [props.reportActions]);
diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js
index 0a97f00c5002..dc5d5d4d07dd 100644
--- a/src/pages/home/sidebar/SidebarLinks.js
+++ b/src/pages/home/sidebar/SidebarLinks.js
@@ -1,7 +1,7 @@
 /* eslint-disable rulesdir/onyx-props-must-have-default */
 import lodashGet from 'lodash/get';
 import PropTypes from 'prop-types';
-import React, {useCallback, useEffect, useMemo, useRef} from 'react';
+import React, {memo, useCallback, useEffect, useMemo, useRef} from 'react';
 import {InteractionManager, StyleSheet, View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
 import _ from 'underscore';
@@ -173,5 +173,5 @@ export default withOnyx({
     activePolicy: {
         key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`,
     },
-})(SidebarLinks);
+})(memo(SidebarLinks));
 export {basePropTypes};
diff --git a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
index 428df32bf032..8111e8d39afa 100755
--- a/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
+++ b/src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
@@ -17,6 +17,7 @@ import useNetwork from '@hooks/useNetwork';
 import usePrevious from '@hooks/usePrevious';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useThemeStyles from '@hooks/useThemeStyles';
+import AccountUtils from '@libs/AccountUtils';
 import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
 import * as ErrorUtils from '@libs/ErrorUtils';
 import * as ValidationUtils from '@libs/ValidationUtils';
@@ -76,8 +77,7 @@ function BaseValidateCodeForm({account, credentials, session, autoComplete, isUs
     const hasError = !!account && !isEmptyObject(account?.errors) && !needToClearError;
     const isLoadingResendValidationForm = account?.loadingForm === CONST.FORMS.RESEND_VALIDATE_CODE_FORM;
     const shouldDisableResendValidateCode = isOffline ?? account?.isLoading;
-    const isValidateCodeFormSubmitting =
-        account?.isLoading && account?.loadingForm === (account?.requiresTwoFactorAuth ? CONST.FORMS.VALIDATE_TFA_CODE_FORM : CONST.FORMS.VALIDATE_CODE_FORM);
+    const isValidateCodeFormSubmitting = AccountUtils.isValidateCodeFormSubmitting(account);
 
     useEffect(() => {
         if (!(inputValidateCodeRef.current && hasError && (session?.autoAuthState === CONST.AUTO_AUTH_STATE.FAILED || account?.isLoading))) {
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index c4f4d6399dbd..745f95c30c80 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -162,6 +162,12 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
             action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_TAGS.getRoute(policyID)))),
             routeName: SCREENS.WORKSPACE.TAGS,
         },
+        {
+            translationKey: 'workspace.common.distanceRates',
+            icon: Expensicons.Car,
+            action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES.getRoute(policyID)))),
+            routeName: SCREENS.WORKSPACE.DISTANCE_RATES,
+        },
     ];
 
     const menuItems: WorkspaceMenuItem[] = [
diff --git a/src/pages/workspace/WorkspaceJoinUserPage.tsx b/src/pages/workspace/WorkspaceJoinUserPage.tsx
index 8167e6fc1ebf..ffcf871ae70d 100644
--- a/src/pages/workspace/WorkspaceJoinUserPage.tsx
+++ b/src/pages/workspace/WorkspaceJoinUserPage.tsx
@@ -36,7 +36,7 @@ function WorkspaceJoinUserPage({route, policies}: WorkspaceJoinUserPageProps) {
         if (!isJoinLinkUsed) {
             return;
         }
-        Navigation.goBack(undefined, false, true);
+        navigateAfterJoinRequest();
     }, []);
 
     useEffect(() => {
diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx
index 42f29f885c00..2715a008c991 100644
--- a/src/pages/workspace/WorkspaceMembersPage.tsx
+++ b/src/pages/workspace/WorkspaceMembersPage.tsx
@@ -87,6 +87,10 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se
     const {isSmallScreenWidth} = useWindowDimensions();
     const dropdownButtonRef = useRef(null);
     const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy);
+    const isLoading = useMemo(
+        () => !isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policyMembers)),
+        [isOfflineAndNoMemberDataAvailable, personalDetails, policyMembers],
+    );
 
     /**
      * Get filtered personalDetails list with current policyMembers
@@ -257,15 +261,11 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se
     /** Opens the member details page */
     const openMemberDetails = useCallback(
         (item: MemberOption) => {
-            if (!isPolicyAdmin) {
+            if (!isPolicyAdmin || !PolicyUtils.isPaidGroupPolicy(policy)) {
                 Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID));
                 return;
             }
 
-            if (!PolicyUtils.isPaidGroupPolicy(policy)) {
-                return;
-            }
-
             Navigation.navigate(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(route.params.policyID, item.accountID, Navigation.getActiveRoute()));
         },
         [isPolicyAdmin, policy, route.params.policyID],
@@ -376,7 +376,8 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se
         if (isOfflineAndNoMemberDataAvailable) {
             return translate('workspace.common.mustBeOnlineToViewMembers');
         }
-        return !data.length ? translate('workspace.common.memberNotFound') : '';
+
+        return !isLoading && isEmptyObject(policyMembers) ? translate('workspace.common.memberNotFound') : '';
     };
 
     const getHeaderContent = () => (
@@ -468,7 +469,7 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se
                     <ButtonWithDropdownMenu<WorkspaceMemberBulkActionType>
                         shouldAlwaysShowDropdownMenu
                         pressOnEnter
-                        customText={translate('workspace.people.selected', {selectedNumber: selectedEmployees.length})}
+                        customText={translate('workspace.common.selected', {selectedNumber: selectedEmployees.length})}
                         buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
                         onPress={() => null}
                         options={getBulkActionsButtonOptions()}
@@ -546,7 +547,7 @@ function WorkspaceMembersPage({policyMembers, personalDetails, route, policy, se
                         onCheckboxPress={(item) => toggleUser(item.accountID)}
                         onSelectAll={() => toggleAllUsers(data)}
                         onDismissError={dismissError}
-                        showLoadingPlaceholder={!isOfflineAndNoMemberDataAvailable && (!OptionsListUtils.isPersonalDetailsReady(personalDetails) || isEmptyObject(policyMembers))}
+                        showLoadingPlaceholder={isLoading}
                         showScrollIndicator
                         shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
                         ref={textInputRef}
diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
index 52d18d8de276..17afefd1cea4 100644
--- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
+++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx
@@ -143,6 +143,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat
                 medium
                 onPress={navigateToCategoriesSettings}
                 icon={Expensicons.Gear}
+                iconStyles={[styles.mr2]}
                 text={translate('common.settings')}
                 style={[isSmallScreenWidth && styles.w50]}
             />
@@ -166,7 +167,7 @@ function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCat
                         {!isSmallScreenWidth && headerButtons}
                     </HeaderWithBackButton>
                     {isSmallScreenWidth && <View style={[styles.pl5, styles.pr5]}>{headerButtons}</View>}
-                    <View style={[styles.ph5, styles.pb5]}>
+                    <View style={[styles.ph5, styles.pb5, styles.pt3]}>
                         <Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.categories.subtitle')}</Text>
                     </View>
                     {isLoading && (
diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx
new file mode 100644
index 000000000000..fd6466da1758
--- /dev/null
+++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx
@@ -0,0 +1,292 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React, {useEffect, useMemo, useRef, useState} from 'react';
+import {ActivityIndicator, View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
+import {withOnyx} from 'react-native-onyx';
+import Button from '@components/Button';
+import type {DropdownOption, WorkspaceDistanceRatesBulkActionType} from '@components/ButtonWithDropdownMenu/types';
+import ConfirmModal from '@components/ConfirmModal';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import TableListItem from '@components/SelectionList/TableListItem';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import * as CurrencyUtils from '@libs/CurrencyUtils';
+import type {CentralPaneNavigatorParamList} from '@navigation/types';
+import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
+import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
+import * as Policy from '@userActions/Policy';
+import ButtonWithDropdownMenu from '@src/components/ButtonWithDropdownMenu';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
+import type * as OnyxTypes from '@src/types/onyx';
+import type {CustomUnit, Rate} from '@src/types/onyx/Policy';
+
+type RateForList = {
+    value: string;
+    text: string;
+    keyForList: string;
+    isSelected: boolean;
+    rightElement: React.ReactNode;
+};
+
+type PolicyDistanceRatesPageOnyxProps = {
+    /** Policy details */
+    policy: OnyxEntry<OnyxTypes.Policy>;
+};
+
+type PolicyDistanceRatesPageProps = PolicyDistanceRatesPageOnyxProps & StackScreenProps<CentralPaneNavigatorParamList, typeof SCREENS.WORKSPACE.DISTANCE_RATES>;
+
+function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) {
+    const {isSmallScreenWidth} = useWindowDimensions();
+    const styles = useThemeStyles();
+    const theme = useTheme();
+    const {translate} = useLocalize();
+    const [selectedDistanceRates, setSelectedDistanceRates] = useState<Rate[]>([]);
+    const [isWarningModalVisible, setIsWarningModalVisible] = useState(false);
+    const dropdownButtonRef = useRef(null);
+
+    const customUnit: CustomUnit | undefined = useMemo(
+        () => (policy?.customUnits !== undefined ? policy?.customUnits[Object.keys(policy?.customUnits)[0]] : undefined),
+        [policy?.customUnits],
+    );
+    const customUnitRates: Record<string, Rate> = useMemo(() => customUnit?.rates ?? {}, [customUnit]);
+
+    function fetchDistanceRates() {
+        Policy.openPolicyDistanceRatesPage(route.params.policyID);
+    }
+
+    const {isOffline} = useNetwork({onReconnect: fetchDistanceRates});
+
+    useEffect(() => {
+        fetchDistanceRates();
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, []);
+
+    const distanceRatesList = useMemo<RateForList[]>(
+        () =>
+            Object.values(customUnitRates).map((value) => ({
+                value: value.customUnitRateID ?? '',
+                text: `${CurrencyUtils.convertAmountToDisplayString(value.rate, value.currency ?? CONST.CURRENCY.USD)} / ${translate(
+                    `common.${customUnit?.attributes?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}`,
+                )}`,
+                keyForList: value.customUnitRateID ?? '',
+                isSelected: selectedDistanceRates.find((rate) => rate.customUnitRateID === value.customUnitRateID) !== undefined,
+                rightElement: (
+                    <View style={styles.flexRow}>
+                        <Text style={[styles.alignSelfCenter, !value.enabled && styles.textSupporting]}>
+                            {value.enabled ? translate('workspace.distanceRates.enabled') : translate('workspace.distanceRates.disabled')}
+                        </Text>
+                        <View style={[styles.p1, styles.pl2]}>
+                            <Icon
+                                src={Expensicons.ArrowRight}
+                                fill={theme.icon}
+                            />
+                        </View>
+                    </View>
+                ),
+            })),
+        [customUnit?.attributes?.unit, customUnitRates, selectedDistanceRates, styles.alignSelfCenter, styles.flexRow, styles.p1, styles.pl2, styles.textSupporting, theme.icon, translate],
+    );
+
+    const addRate = () => {
+        // Navigation.navigate(ROUTES.WORKSPACE_CREATE_DISTANCE_RATE.getRoute(route.params.policyID));
+    };
+
+    const openSettings = () => {
+        // Navigation.navigate(ROUTES.WORKSPACE_DISTANCE_RATES_SETTINGS.getRoute(route.params.policyID));
+    };
+
+    const editRate = () => {
+        // Navigation.navigate(ROUTES.WORKSPACE_EDIT_DISTANCE_RATE.getRoute(route.params.policyID, rateID));
+    };
+
+    const disableRates = () => {
+        if (selectedDistanceRates.length !== Object.values(customUnitRates).length) {
+            // run enableWorkspaceDistanceRates for all selected rows
+            return;
+        }
+
+        setIsWarningModalVisible(true);
+    };
+
+    const enableRates = () => {
+        // run enableWorkspaceDistanceRates for all selected rows
+    };
+
+    const deleteRates = () => {
+        if (selectedDistanceRates.length !== Object.values(customUnitRates).length) {
+            // run deleteWorkspaceDistanceRates for all selected rows
+            return;
+        }
+
+        setIsWarningModalVisible(true);
+    };
+
+    const toggleRate = (rate: RateForList) => {
+        if (selectedDistanceRates.find((selectedRate) => selectedRate.customUnitRateID === rate.value) !== undefined) {
+            setSelectedDistanceRates((prev) => prev.filter((selectedRate) => selectedRate.customUnitRateID !== rate.value));
+        } else {
+            setSelectedDistanceRates((prev) => [...prev, customUnitRates[rate.value]]);
+        }
+    };
+
+    const toggleAllRates = () => {
+        if (selectedDistanceRates.length === Object.values(customUnitRates).length) {
+            setSelectedDistanceRates([]);
+        } else {
+            setSelectedDistanceRates([...Object.values(customUnitRates)]);
+        }
+    };
+
+    const getCustomListHeader = () => (
+        <View style={[styles.flex1, styles.flexRow, styles.justifyContentBetween, styles.pl3, styles.pr9]}>
+            <Text style={styles.searchInputStyle}>{translate('workspace.distanceRates.rate')}</Text>
+            <Text style={[styles.searchInputStyle, styles.textAlignCenter]}>{translate('statusPage.status')}</Text>
+        </View>
+    );
+
+    const getBulkActionsButtonOptions = () => {
+        const options: Array<DropdownOption<WorkspaceDistanceRatesBulkActionType>> = [
+            {
+                text: translate(`workspace.distanceRates.${selectedDistanceRates.length <= 1 ? 'deleteRate' : 'deleteRates'}`),
+                value: CONST.POLICY.DISTANCE_RATES_BULK_ACTION_TYPES.DELETE,
+                icon: Expensicons.Trashcan,
+                onSelected: deleteRates,
+            },
+        ];
+
+        const enabledRates = selectedDistanceRates.filter((rate) => rate.enabled);
+        if (enabledRates.length > 0) {
+            options.push({
+                text: translate(`workspace.distanceRates.${enabledRates.length <= 1 ? 'disableRate' : 'disableRates'}`),
+                value: CONST.POLICY.DISTANCE_RATES_BULK_ACTION_TYPES.DISABLE,
+                icon: Expensicons.DocumentSlash,
+                onSelected: disableRates,
+            });
+        }
+
+        const disabledRates = selectedDistanceRates.filter((rate) => !rate.enabled);
+        if (disabledRates.length > 0) {
+            options.push({
+                text: translate(`workspace.distanceRates.${disabledRates.length <= 1 ? 'enableRate' : 'enableRates'}`),
+                value: CONST.POLICY.DISTANCE_RATES_BULK_ACTION_TYPES.ENABLE,
+                icon: Expensicons.DocumentSlash,
+                onSelected: enableRates,
+            });
+        }
+
+        return options;
+    };
+
+    const isLoading = !isOffline && customUnit === undefined;
+
+    const headerButtons = (
+        <View style={[styles.w100, styles.flexRow, isSmallScreenWidth && styles.mb3]}>
+            {selectedDistanceRates.length === 0 ? (
+                <>
+                    <Button
+                        medium
+                        text={translate('workspace.distanceRates.addRate')}
+                        onPress={addRate}
+                        style={[styles.mr3, isSmallScreenWidth && styles.flexGrow1]}
+                        icon={Expensicons.Plus}
+                        iconStyles={[styles.mr2]}
+                        success
+                    />
+
+                    <Button
+                        medium
+                        text={translate('workspace.common.settings')}
+                        onPress={openSettings}
+                        style={[isSmallScreenWidth && styles.flexGrow1]}
+                        icon={Expensicons.Gear}
+                        iconStyles={[styles.mr2]}
+                    />
+                </>
+            ) : (
+                <ButtonWithDropdownMenu<WorkspaceDistanceRatesBulkActionType>
+                    shouldAlwaysShowDropdownMenu
+                    pressOnEnter
+                    customText={translate('workspace.common.selected', {selectedNumber: selectedDistanceRates.length})}
+                    buttonSize={CONST.DROPDOWN_BUTTON_SIZE.MEDIUM}
+                    onPress={() => null}
+                    options={getBulkActionsButtonOptions()}
+                    buttonRef={dropdownButtonRef}
+                    style={[isSmallScreenWidth && styles.flexGrow1]}
+                    wrapperStyle={styles.w100}
+                />
+            )}
+        </View>
+    );
+
+    return (
+        <AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
+            <PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
+                <ScreenWrapper
+                    includeSafeAreaPaddingBottom={false}
+                    style={[styles.defaultModalContainer]}
+                    testID={PolicyDistanceRatesPage.displayName}
+                    shouldShowOfflineIndicatorInWideScreen
+                >
+                    <HeaderWithBackButton
+                        icon={Illustrations.CarIce}
+                        title={translate('workspace.common.distanceRates')}
+                        shouldShowBackButton={isSmallScreenWidth}
+                    >
+                        {!isSmallScreenWidth && headerButtons}
+                    </HeaderWithBackButton>
+                    {isSmallScreenWidth && <View style={[styles.ph5]}>{headerButtons}</View>}
+                    <View style={[styles.ph5, styles.pb5, styles.pt3]}>
+                        <Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.distanceRates.centrallyManage')}</Text>
+                    </View>
+                    {isLoading && (
+                        <ActivityIndicator
+                            size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
+                            style={[styles.flex1]}
+                            color={theme.spinner}
+                        />
+                    )}
+                    {Object.values(customUnitRates).length > 0 && (
+                        <SelectionList
+                            canSelectMultiple
+                            ListItem={TableListItem}
+                            onSelectAll={toggleAllRates}
+                            onCheckboxPress={toggleRate}
+                            sections={[{data: distanceRatesList, indexOffset: 0, isDisabled: false}]}
+                            onSelectRow={editRate}
+                            showScrollIndicator
+                            customListHeader={getCustomListHeader()}
+                            listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
+                        />
+                    )}
+                    <ConfirmModal
+                        onConfirm={() => setIsWarningModalVisible(false)}
+                        isVisible={isWarningModalVisible}
+                        title={translate('workspace.distanceRates.oopsNotSoFast')}
+                        prompt={translate('workspace.distanceRates.workspaceNeeds')}
+                        confirmText={translate('common.buttonConfirm')}
+                        shouldShowCancelButton={false}
+                    />
+                </ScreenWrapper>
+            </PaidPolicyAccessOrNotFoundWrapper>
+        </AdminPolicyAccessOrNotFoundWrapper>
+    );
+}
+
+PolicyDistanceRatesPage.displayName = 'PolicyDistanceRatesPage';
+
+export default withOnyx<PolicyDistanceRatesPageProps, PolicyDistanceRatesPageOnyxProps>({
+    policy: {
+        key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY}${route.params.policyID}`,
+    },
+})(PolicyDistanceRatesPage);
diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx
index c82740eff361..fb7611ae681c 100644
--- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx
+++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx
@@ -107,7 +107,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) {
                         title={translate('workspace.common.tags')}
                         shouldShowBackButton={isSmallScreenWidth}
                     />
-                    <View style={[styles.ph5, styles.pb5]}>
+                    <View style={[styles.ph5, styles.pb5, styles.pt3]}>
                         <Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.tags.subtitle')}</Text>
                     </View>
                     {tagList.length ? (
diff --git a/src/stories/InlineSystemMessage.stories.js b/src/stories/InlineSystemMessage.stories.tsx
similarity index 59%
rename from src/stories/InlineSystemMessage.stories.js
rename to src/stories/InlineSystemMessage.stories.tsx
index b7fe21c8b10e..5c00a41ac479 100644
--- a/src/stories/InlineSystemMessage.stories.js
+++ b/src/stories/InlineSystemMessage.stories.tsx
@@ -1,24 +1,28 @@
+import type {ComponentMeta, ComponentStory} from '@storybook/react';
 import React from 'react';
 import InlineSystemMessage from '@components/InlineSystemMessage';
+import type {InlineSystemMessageProps} from '@components/InlineSystemMessage';
+
+type InlineSystemMessageStory = ComponentStory<typeof InlineSystemMessage>;
 
 /**
  * We use the Component Story Format for writing stories. Follow the docs here:
  *
  * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
  */
-const story = {
+const story: ComponentMeta<typeof InlineSystemMessage> = {
     title: 'Components/InlineSystemMessage',
     component: InlineSystemMessage,
 };
 
-function Template(args) {
+function Template(props: InlineSystemMessageProps) {
     // eslint-disable-next-line react/jsx-props-no-spreading
-    return <InlineSystemMessage {...args} />;
+    return <InlineSystemMessage {...props} />;
 }
 
 // Arguments can be passed to the component by binding
 // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
-const Default = Template.bind({});
+const Default: InlineSystemMessageStory = Template.bind({});
 Default.args = {
     message: 'This is an error message',
 };
diff --git a/src/stories/MagicCodeInput.stories.js b/src/stories/MagicCodeInput.stories.tsx
similarity index 70%
rename from src/stories/MagicCodeInput.stories.js
rename to src/stories/MagicCodeInput.stories.tsx
index 14b234996ce1..bb86c1685593 100644
--- a/src/stories/MagicCodeInput.stories.js
+++ b/src/stories/MagicCodeInput.stories.tsx
@@ -1,24 +1,28 @@
+import type {ComponentMeta, ComponentStory} from '@storybook/react';
 import React, {useState} from 'react';
 import MagicCodeInput from '@components/MagicCodeInput';
+import type {MagicCodeInputProps} from '@components/MagicCodeInput';
+
+type MagicCodeInputStory = ComponentStory<typeof MagicCodeInput>;
 
 /**
  * We use the Component Story Format for writing stories. Follow the docs here:
  *
  * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
  */
-const story = {
+const story: ComponentMeta<typeof MagicCodeInput> = {
     title: 'Components/MagicCodeInput',
     component: MagicCodeInput,
 };
 
-function Template(args) {
+function Template(props: MagicCodeInputProps) {
     const [value, setValue] = useState('');
     return (
         <MagicCodeInput
             value={value}
             onChangeText={setValue}
             // eslint-disable-next-line react/jsx-props-no-spreading
-            {...args}
+            {...props}
         />
     );
 }
@@ -26,17 +30,15 @@ function Template(args) {
 // Arguments can be passed to the component by binding
 // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
 
-const AutoFocus = Template.bind({});
+const AutoFocus: MagicCodeInputStory = Template.bind({});
 AutoFocus.args = {
-    label: 'Auto-focused magic code input',
     name: 'AutoFocus',
     autoFocus: true,
     autoComplete: 'one-time-code',
 };
 
-const SubmitOnComplete = Template.bind({});
+const SubmitOnComplete: MagicCodeInputStory = Template.bind({});
 SubmitOnComplete.args = {
-    label: 'Submits when the magic code input is complete',
     name: 'SubmitOnComplete',
     autoComplete: 'one-time-code',
     shouldSubmitOnComplete: true,
diff --git a/src/stories/Picker.stories.js b/src/stories/Picker.stories.tsx
similarity index 77%
rename from src/stories/Picker.stories.js
rename to src/stories/Picker.stories.tsx
index b42cfed8f471..a277db387f79 100644
--- a/src/stories/Picker.stories.js
+++ b/src/stories/Picker.stories.tsx
@@ -1,25 +1,30 @@
+import type {ComponentMeta, ComponentStory} from '@storybook/react';
 import React, {useState} from 'react';
 import Picker from '@components/Picker';
+import type {BasePickerProps} from '@components/Picker/types';
+
+type PickerStory = ComponentStory<typeof Picker<string>>;
+
+type TemplateProps = Omit<BasePickerProps<string>, 'onInputChange'>;
 
 /**
  * We use the Component Story Format for writing stories. Follow the docs here:
  *
  * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
  */
-const story = {
+const story: ComponentMeta<typeof Picker> = {
     title: 'Components/Picker',
     component: Picker,
 };
 
-// eslint-disable-next-line react/jsx-props-no-spreading
-function Template(args) {
+function Template(props: TemplateProps) {
     const [value, setValue] = useState('');
     return (
         <Picker
             value={value}
             onInputChange={(e) => setValue(e)}
             // eslint-disable-next-line react/jsx-props-no-spreading
-            {...args}
+            {...props}
         />
     );
 }
@@ -27,10 +32,9 @@ function Template(args) {
 // Arguments can be passed to the component by binding
 // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
 
-const Default = Template.bind({});
+const Default: PickerStory = Template.bind({});
 Default.args = {
     label: 'Default picker',
-    name: 'Default',
     hintText: 'Default hint text',
     items: [
         {
@@ -44,10 +48,9 @@ Default.args = {
     ],
 };
 
-const PickerWithValue = Template.bind({});
+const PickerWithValue: PickerStory = Template.bind({});
 PickerWithValue.args = {
     label: 'Picker with defined value',
-    name: 'Picker with defined value',
     value: 'apple',
     hintText: 'Picker with hint text',
     items: [
@@ -62,10 +65,9 @@ PickerWithValue.args = {
     ],
 };
 
-const ErrorStory = Template.bind({});
+const ErrorStory: PickerStory = Template.bind({});
 ErrorStory.args = {
     label: 'Picker with error',
-    name: 'PickerWithError',
     hintText: 'Picker hint text',
     errorText: 'This field has an error.',
     items: [
@@ -80,10 +82,9 @@ ErrorStory.args = {
     ],
 };
 
-const Disabled = Template.bind({});
+const Disabled: PickerStory = Template.bind({});
 Disabled.args = {
     label: 'Picker disabled',
-    name: 'Disabled',
     value: 'orange',
     isDisabled: true,
     hintText: 'Picker hint text',
diff --git a/src/stories/TextInput.stories.js b/src/stories/TextInput.stories.tsx
similarity index 72%
rename from src/stories/TextInput.stories.js
rename to src/stories/TextInput.stories.tsx
index dd2fcced68b0..b8e647949c0f 100644
--- a/src/stories/TextInput.stories.js
+++ b/src/stories/TextInput.stories.tsx
@@ -1,66 +1,70 @@
+import type {ComponentMeta, ComponentStory} from '@storybook/react';
 import React, {useState} from 'react';
 import TextInput from '@components/TextInput';
+import type {BaseTextInputProps} from '@components/TextInput/BaseTextInput/types';
+
+type TextInputStory = ComponentStory<typeof TextInput>;
 
 /**
  * We use the Component Story Format for writing stories. Follow the docs here:
  *
  * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
  */
-const story = {
+const story: ComponentMeta<typeof TextInput> = {
     title: 'Components/TextInput',
     component: TextInput,
 };
 
-function Template(args) {
+function Template(props: BaseTextInputProps) {
     // eslint-disable-next-line react/jsx-props-no-spreading
-    return <TextInput {...args} />;
+    return <TextInput {...props} />;
 }
 
 // Arguments can be passed to the component by binding
 // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
 
-const AutoFocus = Template.bind({});
+const AutoFocus: TextInputStory = Template.bind({});
 AutoFocus.args = {
     label: 'Auto-focused text input',
     name: 'AutoFocus',
     autoFocus: true,
 };
 
-const DefaultInput = Template.bind({});
+const DefaultInput: TextInputStory = Template.bind({});
 DefaultInput.args = {
     label: 'Default text input',
     name: 'Default',
 };
 
-const DefaultValueInput = Template.bind({});
+const DefaultValueInput: TextInputStory = Template.bind({});
 DefaultValueInput.args = {
     label: 'Default value input',
     name: 'DefaultValue',
     defaultValue: 'My default value',
 };
 
-const ErrorInput = Template.bind({});
+const ErrorInput: TextInputStory = Template.bind({});
 ErrorInput.args = {
     label: 'Error input',
     name: 'InputWithError',
     errorText: "Oops! Looks like there's an error",
 };
 
-const ForceActiveLabel = Template.bind({});
+const ForceActiveLabel: TextInputStory = Template.bind({});
 ForceActiveLabel.args = {
     label: 'Force active label',
     placeholder: 'My placeholder text',
     forceActiveLabel: true,
 };
 
-const PlaceholderInput = Template.bind({});
+const PlaceholderInput: TextInputStory = Template.bind({});
 PlaceholderInput.args = {
     label: 'Placeholder input',
     name: 'Placeholder',
     placeholder: 'My placeholder text',
 };
 
-const PrefixedInput = Template.bind({});
+const PrefixedInput: TextInputStory = Template.bind({});
 PrefixedInput.args = {
     label: 'Prefixed input',
     name: 'Prefixed',
@@ -68,7 +72,7 @@ PrefixedInput.args = {
     prefixCharacter: '@',
 };
 
-const MaxLengthInput = Template.bind({});
+const MaxLengthInput: TextInputStory = Template.bind({});
 MaxLengthInput.args = {
     label: 'MaxLength input',
     name: 'MaxLength',
@@ -76,12 +80,12 @@ MaxLengthInput.args = {
     maxLength: 50,
 };
 
-function HintAndErrorInput(args) {
+function HintAndErrorInput(props: BaseTextInputProps) {
     const [error, setError] = useState('');
     return (
         <TextInput
             // eslint-disable-next-line react/jsx-props-no-spreading
-            {...args}
+            {...props}
             onChangeText={(value) => {
                 if (value && value.toLowerCase() === 'oops!') {
                     setError("Oops! Looks like there's an error");
@@ -101,23 +105,23 @@ HintAndErrorInput.args = {
 };
 
 // To use autoGrow we need to control the TextInput's value
-function AutoGrowSupportInput(args) {
-    const [value, setValue] = useState(args.value || '');
+function AutoGrowSupportInput(props: BaseTextInputProps) {
+    const [value, setValue] = useState(props.value ?? '');
     React.useEffect(() => {
-        setValue(args.value || '');
-    }, [args.value]);
+        setValue(props.value ?? '');
+    }, [props.value]);
 
     return (
         <TextInput
             // eslint-disable-next-line react/jsx-props-no-spreading
-            {...args}
+            {...props}
             onChangeText={setValue}
             value={value}
         />
     );
 }
 
-const AutoGrowInput = AutoGrowSupportInput.bind({});
+const AutoGrowInput: TextInputStory = AutoGrowSupportInput.bind({});
 AutoGrowInput.args = {
     label: 'Autogrow input',
     name: 'AutoGrow',
@@ -132,7 +136,7 @@ AutoGrowInput.args = {
     value: '',
 };
 
-const AutoGrowHeightInput = AutoGrowSupportInput.bind({});
+const AutoGrowHeightInput: TextInputStory = AutoGrowSupportInput.bind({});
 AutoGrowHeightInput.args = {
     label: 'Autogrowheight input',
     name: 'AutoGrowHeight',
diff --git a/src/stories/Tooltip.stories.js b/src/stories/Tooltip.stories.tsx
similarity index 76%
rename from src/stories/Tooltip.stories.js
rename to src/stories/Tooltip.stories.tsx
index 900cd6dedd76..c9caf7bc6496 100644
--- a/src/stories/Tooltip.stories.js
+++ b/src/stories/Tooltip.stories.tsx
@@ -1,27 +1,29 @@
+import type {ComponentMeta, ComponentStory} from '@storybook/react';
 import React from 'react';
 import Tooltip from '@components/Tooltip';
+import type {TooltipExtendedProps} from '@components/Tooltip/types';
+
+type TooltipStory = ComponentStory<typeof Tooltip>;
 
 /**
  * We use the Component Story Format for writing stories. Follow the docs here:
  *
  * https://storybook.js.org/docs/react/writing-stories/introduction#component-story-format
  */
-const story = {
+const story: ComponentMeta<typeof Tooltip> = {
     title: 'Components/Tooltip',
     component: Tooltip,
 };
 
-function Template(args) {
+function Template(props: TooltipExtendedProps) {
     return (
-        <div
-            style={{
-                width: 100,
-            }}
-        >
+        <div style={{width: 100}}>
             <Tooltip
                 // eslint-disable-next-line react/jsx-props-no-spreading
-                {...args}
-                maxWidth={args.maxWidth || undefined}
+                {...props}
+                // Disable nullish coalescing to handle cases when maxWidth is 0
+                // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+                maxWidth={props.maxWidth || undefined}
             >
                 <div
                     style={{
@@ -42,7 +44,7 @@ function Template(args) {
 
 // Arguments can be passed to the component by binding
 // See: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
-const Default = Template.bind({});
+const Default: TooltipStory = Template.bind({});
 Default.args = {
     text: 'Tooltip',
     numberOfLines: 1,
@@ -63,12 +65,7 @@ function RenderContent() {
     );
 
     return (
-        <div
-            style={{
-                width: 100,
-            }}
-        >
-            {/* eslint-disable-next-line react/jsx-props-no-spreading */}
+        <div style={{width: 100}}>
             <Tooltip renderTooltipContent={renderTooltipContent}>
                 {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
                 <div
diff --git a/src/styles/index.ts b/src/styles/index.ts
index 405a05cfce78..303e531ea52d 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -750,7 +750,7 @@ const styles = (theme: ThemeColors) =>
             height: 140,
         },
 
-        pickerSmall: (backgroundColor = theme.highlightBG) =>
+        pickerSmall: (disabled = false, backgroundColor = theme.highlightBG) =>
             ({
                 inputIOS: {
                     fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
@@ -792,7 +792,7 @@ const styles = (theme: ThemeColors) =>
                     height: 26,
                     opacity: 1,
                     backgroundColor,
-                    ...cursor.cursorPointer,
+                    ...(disabled ? cursor.cursorDisabled : cursor.cursorPointer),
                 },
                 inputAndroid: {
                     fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
@@ -4642,6 +4642,7 @@ const styles = (theme: ThemeColors) =>
         videoPlayerTimeComponentWidth: {
             width: 40,
         },
+
         colorSchemeStyle: (colorScheme: ColorScheme) => ({colorScheme}),
 
         updateAnimation: {
diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts
index 61b63106ad8a..6ca9a7ab2103 100644
--- a/src/types/onyx/Policy.ts
+++ b/src/types/onyx/Policy.ts
@@ -155,7 +155,7 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback<
         isPreventSelfApprovalEnabled?: boolean;
 
         /** Whether the self approval or submitting is enabled */
-        preventSelfApprovalEnabled?: boolean;
+        preventSelfApproval?: boolean;
 
         /** When the monthly scheduled submit should happen */
         autoReportingOffset?: AutoReportingOffset;
diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts
index 22618bb357d0..1f3b49ff77b0 100644
--- a/src/types/onyx/Report.ts
+++ b/src/types/onyx/Report.ts
@@ -17,6 +17,12 @@ type Note = OnyxCommon.OnyxValueWithOfflineFeedback<{
     errors?: OnyxCommon.Errors;
 }>;
 
+/** The pending member of report */
+type PendingChatMember = {
+    accountID: string;
+    pendingAction: OnyxCommon.PendingAction;
+};
+
 type Participant = {
     hidden: boolean;
     role?: 'admin' | 'member';
@@ -170,6 +176,9 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback<
         isLoadingPrivateNotes?: boolean;
         selected?: boolean;
 
+        /** Pending members of the report */
+        pendingChatMembers?: PendingChatMember[];
+
         /** If the report contains reportFields, save the field id and its value */
         reportFields?: Record<string, PolicyReportField>;
     },
@@ -180,4 +189,4 @@ type ReportCollectionDataSet = CollectionDataSet<typeof ONYXKEYS.COLLECTION.REPO
 
 export default Report;
 
-export type {NotificationPreference, RoomVisibility, WriteCapability, Note, ReportCollectionDataSet};
+export type {NotificationPreference, RoomVisibility, WriteCapability, Note, PendingChatMember, ReportCollectionDataSet};
diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts
index 9133eca63c65..28de4582bd5e 100644
--- a/src/types/onyx/TransactionViolation.ts
+++ b/src/types/onyx/TransactionViolation.ts
@@ -26,6 +26,9 @@ type TransactionViolation = {
         isTransactionOlderThan7Days?: boolean;
         member?: string;
         taxName?: string;
+        tagListIndex?: number;
+        tagListName?: string;
+        errorIndexes?: number[];
     };
 };
 
diff --git a/tests/e2e/utils/installApp.ts b/tests/e2e/utils/installApp.ts
index b443344e6f02..dc6a9d64053f 100644
--- a/tests/e2e/utils/installApp.ts
+++ b/tests/e2e/utils/installApp.ts
@@ -17,7 +17,7 @@ export default function (packageName: string, path: string, platform = 'android'
         execAsync(`adb uninstall ${packageName}`)
             .catch((error: ExecException) => {
                 // Ignore errors
-                Logger.warn('Failed to uninstall app:', error);
+                Logger.warn('Failed to uninstall app:', error.message);
             })
             // eslint-disable-next-line @typescript-eslint/no-misused-promises
             .finally(() => execAsync(`adb install ${path}`))
diff --git a/tests/e2e/utils/logger.js b/tests/e2e/utils/logger.ts
similarity index 66%
rename from tests/e2e/utils/logger.js
rename to tests/e2e/utils/logger.ts
index e120c90482b5..ebe8fc05e66a 100644
--- a/tests/e2e/utils/logger.js
+++ b/tests/e2e/utils/logger.ts
@@ -1,7 +1,6 @@
 /* eslint-disable import/no-import-module-exports */
 import fs from 'fs';
 import path from 'path';
-import _ from 'underscore';
 import CONFIG from '../config';
 
 const COLOR_DIM = '\x1b[2m';
@@ -12,7 +11,7 @@ const COLOR_GREEN = '\x1b[32m';
 
 const getDateString = () => `[${Date()}] `;
 
-const writeToLogFile = (...args) => {
+const writeToLogFile = (...args: string[]) => {
     if (!fs.existsSync(CONFIG.LOG_FILE)) {
         // Check that the directory exists
         const logDir = path.dirname(CONFIG.LOG_FILE);
@@ -24,45 +23,46 @@ const writeToLogFile = (...args) => {
     }
     fs.appendFileSync(
         CONFIG.LOG_FILE,
-        `${_.map(args, (arg) => {
-            if (typeof arg === 'string') {
-                // Remove color codes from arg, because they are not supported in log files
-                // eslint-disable-next-line no-control-regex
-                return arg.replace(/\x1b\[\d+m/g, '');
-            }
-            return arg;
-        })
+        `${args
+            .map((arg) => {
+                if (typeof arg === 'string') {
+                    // Remove color codes from arg, because they are not supported in log files
+                    // eslint-disable-next-line no-control-regex
+                    return arg.replace(/\x1b\[\d+m/g, '');
+                }
+                return arg;
+            })
             .join(' ')
             .trim()}\n`,
     );
 };
 
-const log = (...args) => {
+const log = (...args: string[]) => {
     const argsWithTime = [getDateString(), ...args];
     console.debug(...argsWithTime);
     writeToLogFile(...argsWithTime);
 };
 
-const info = (...args) => {
+const info = (...args: string[]) => {
     log('▶️', ...args);
 };
 
-const success = (...args) => {
+const success = (...args: string[]) => {
     const lines = ['✅', COLOR_GREEN, ...args, COLOR_RESET];
     log(...lines);
 };
 
-const warn = (...args) => {
+const warn = (...args: string[]) => {
     const lines = ['⚠️', COLOR_YELLOW, ...args, COLOR_RESET];
     log(...lines);
 };
 
-const note = (...args) => {
+const note = (...args: string[]) => {
     const lines = [COLOR_DIM, ...args, COLOR_RESET];
     log(...lines);
 };
 
-const error = (...args) => {
+const error = (...args: string[]) => {
     const lines = ['🔴', COLOR_RED, ...args, COLOR_RESET];
     log(...lines);
 };
diff --git a/tests/perf-test/SignInPage.perf-test.tsx b/tests/perf-test/SignInPage.perf-test.tsx
index 3ff62fd60701..e3e2c20ae72a 100644
--- a/tests/perf-test/SignInPage.perf-test.tsx
+++ b/tests/perf-test/SignInPage.perf-test.tsx
@@ -18,6 +18,14 @@ import * as TestHelper from '../utils/TestHelper';
 import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
 import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates';
 
+jest.mock('../../src/libs/Log');
+
+jest.mock('../../src/libs/API', () => ({
+    write: jest.fn(),
+    makeRequestWithSideEffects: jest.fn(),
+    read: jest.fn(),
+}));
+
 const mockedNavigate = jest.fn();
 jest.mock('@react-navigation/native', () => {
     const actualNav = jest.requireActual('@react-navigation/native');
diff --git a/tests/unit/GithubUtilsTest.ts b/tests/unit/GithubUtilsTest.ts
index 794139286527..24e56402f1ea 100644
--- a/tests/unit/GithubUtilsTest.ts
+++ b/tests/unit/GithubUtilsTest.ts
@@ -376,6 +376,7 @@ describe('GithubUtils', () => {
                         login: 'hubot',
                     },
                 ],
+                merged_by: {login: 'octocat'},
             },
             {
                 number: 7,
@@ -392,14 +393,8 @@ describe('GithubUtils', () => {
                         color: 'f29513',
                     },
                 ],
-                assignees: [
-                    {
-                        login: 'octocat',
-                    },
-                    {
-                        login: 'hubot',
-                    },
-                ],
+                assignees: [],
+                merged_by: {login: 'hubot'},
             },
         ];
         const mockGithub = jest.fn(() => ({
@@ -446,7 +441,8 @@ describe('GithubUtils', () => {
         const internalQAHeader = '\r\n\r\n**Internal QA:**';
         const lineBreak = '\r\n';
         const lineBreakDouble = '\r\n\r\n';
-        const assignOctocatHubot = ' - @octocat @hubot';
+        const assignOctocat = ' - @octocat';
+        const assignHubot = ' - @hubot';
         const deployerVerificationsHeader = '\r\n**Deployer verifications:**';
         // eslint-disable-next-line max-len
         const timingDashboardVerification =
@@ -468,8 +464,8 @@ describe('GithubUtils', () => {
             `${lineBreak}`;
 
         test('Test no verified PRs', () => {
-            githubUtils.generateStagingDeployCashBody(tag, basePRList).then((issueBody: string) => {
-                expect(issueBody).toBe(
+            githubUtils.generateStagingDeployCashBody(tag, basePRList).then((issue) => {
+                expect(issue.issueBody).toBe(
                     `${baseExpectedOutput}` +
                         `${openCheckbox}${basePRList[2]}` +
                         `${lineBreak}${openCheckbox}${basePRList[0]}` +
@@ -482,12 +478,13 @@ describe('GithubUtils', () => {
                         `${lineBreak}${openCheckbox}${ghVerification}` +
                         `${lineBreakDouble}${ccApplauseLeads}`,
                 );
+                expect(issue.issueAssignees).toEqual([]);
             });
         });
 
         test('Test some verified PRs', () => {
-            githubUtils.generateStagingDeployCashBody(tag, basePRList, [basePRList[0]]).then((issueBody: string) => {
-                expect(issueBody).toBe(
+            githubUtils.generateStagingDeployCashBody(tag, basePRList, [basePRList[0]]).then((issue) => {
+                expect(issue.issueBody).toBe(
                     `${baseExpectedOutput}` +
                         `${openCheckbox}${basePRList[2]}` +
                         `${lineBreak}${closedCheckbox}${basePRList[0]}` +
@@ -500,12 +497,13 @@ describe('GithubUtils', () => {
                         `${lineBreak}${openCheckbox}${ghVerification}` +
                         `${lineBreakDouble}${ccApplauseLeads}`,
                 );
+                expect(issue.issueAssignees).toEqual([]);
             });
         });
 
         test('Test all verified PRs', () => {
-            githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList).then((issueBody: string) => {
-                expect(issueBody).toBe(
+            githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList).then((issue) => {
+                expect(issue.issueBody).toBe(
                     `${allVerifiedExpectedOutput}` +
                         `${lineBreak}${deployerVerificationsHeader}` +
                         `${lineBreak}${openCheckbox}${timingDashboardVerification}` +
@@ -513,12 +511,13 @@ describe('GithubUtils', () => {
                         `${lineBreak}${openCheckbox}${ghVerification}` +
                         `${lineBreakDouble}${ccApplauseLeads}`,
                 );
+                expect(issue.issueAssignees).toEqual([]);
             });
         });
 
         test('Test no resolved deploy blockers', () => {
-            githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList).then((issueBody: string) => {
-                expect(issueBody).toBe(
+            githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList).then((issue) => {
+                expect(issue.issueBody).toBe(
                     `${allVerifiedExpectedOutput}` +
                         `${lineBreak}${deployBlockerHeader}` +
                         `${lineBreak}${openCheckbox}${baseDeployBlockerList[0]}` +
@@ -529,12 +528,13 @@ describe('GithubUtils', () => {
                         `${lineBreak}${openCheckbox}${ghVerification}${lineBreak}` +
                         `${lineBreak}${ccApplauseLeads}`,
                 );
+                expect(issue.issueAssignees).toEqual([]);
             });
         });
 
         test('Test some resolved deploy blockers', () => {
-            githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList, [baseDeployBlockerList[0]]).then((issueBody: string) => {
-                expect(issueBody).toBe(
+            githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList, [baseDeployBlockerList[0]]).then((issue) => {
+                expect(issue.issueBody).toBe(
                     `${allVerifiedExpectedOutput}` +
                         `${lineBreak}${deployBlockerHeader}` +
                         `${lineBreak}${closedCheckbox}${baseDeployBlockerList[0]}` +
@@ -545,12 +545,13 @@ describe('GithubUtils', () => {
                         `${lineBreak}${openCheckbox}${ghVerification}` +
                         `${lineBreakDouble}${ccApplauseLeads}`,
                 );
+                expect(issue.issueAssignees).toEqual([]);
             });
         });
 
         test('Test all resolved deploy blockers', () => {
-            githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList, baseDeployBlockerList).then((issueBody: string) => {
-                expect(issueBody).toBe(
+            githubUtils.generateStagingDeployCashBody(tag, basePRList, basePRList, baseDeployBlockerList, baseDeployBlockerList).then((issue) => {
+                expect(issue.issueBody).toBe(
                     `${baseExpectedOutput}` +
                         `${closedCheckbox}${basePRList[2]}` +
                         `${lineBreak}${closedCheckbox}${basePRList[0]}` +
@@ -566,12 +567,13 @@ describe('GithubUtils', () => {
                         `${lineBreak}${openCheckbox}${ghVerification}` +
                         `${lineBreakDouble}${ccApplauseLeads}`,
                 );
+                expect(issue.issueAssignees).toEqual([]);
             });
         });
 
         test('Test internalQA PRs', () => {
-            githubUtils.generateStagingDeployCashBody(tag, [...basePRList, ...internalQAPRList]).then((issueBody: string) => {
-                expect(issueBody).toBe(
+            githubUtils.generateStagingDeployCashBody(tag, [...basePRList, ...internalQAPRList]).then((issue) => {
+                expect(issue.issueBody).toBe(
                     `${baseExpectedOutput}` +
                         `${openCheckbox}${basePRList[2]}` +
                         `${lineBreak}${openCheckbox}${basePRList[0]}` +
@@ -579,20 +581,21 @@ describe('GithubUtils', () => {
                         `${lineBreak}${closedCheckbox}${basePRList[4]}` +
                         `${lineBreak}${closedCheckbox}${basePRList[5]}` +
                         `${lineBreak}${internalQAHeader}` +
-                        `${lineBreak}${openCheckbox}${internalQAPRList[0]}${assignOctocatHubot}` +
-                        `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocatHubot}` +
+                        `${lineBreak}${openCheckbox}${internalQAPRList[0]}${assignOctocat}` +
+                        `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignHubot}` +
                         `${lineBreakDouble}${deployerVerificationsHeader}` +
                         `${lineBreak}${openCheckbox}${timingDashboardVerification}` +
                         `${lineBreak}${openCheckbox}${firebaseVerification}` +
                         `${lineBreak}${openCheckbox}${ghVerification}` +
                         `${lineBreakDouble}${ccApplauseLeads}`,
                 );
+                expect(issue.issueAssignees).toEqual(['octocat', 'hubot']);
             });
         });
 
         test('Test some verified internalQA PRs', () => {
-            githubUtils.generateStagingDeployCashBody(tag, [...basePRList, ...internalQAPRList], [], [], [], [internalQAPRList[0]]).then((issueBody: string) => {
-                expect(issueBody).toBe(
+            githubUtils.generateStagingDeployCashBody(tag, [...basePRList, ...internalQAPRList], [], [], [], [internalQAPRList[0]]).then((issue) => {
+                expect(issue.issueBody).toBe(
                     `${baseExpectedOutput}` +
                         `${openCheckbox}${basePRList[2]}` +
                         `${lineBreak}${openCheckbox}${basePRList[0]}` +
@@ -600,14 +603,15 @@ describe('GithubUtils', () => {
                         `${lineBreak}${closedCheckbox}${basePRList[4]}` +
                         `${lineBreak}${closedCheckbox}${basePRList[5]}` +
                         `${lineBreak}${internalQAHeader}` +
-                        `${lineBreak}${closedCheckbox}${internalQAPRList[0]}${assignOctocatHubot}` +
-                        `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignOctocatHubot}` +
+                        `${lineBreak}${closedCheckbox}${internalQAPRList[0]}${assignOctocat}` +
+                        `${lineBreak}${openCheckbox}${internalQAPRList[1]}${assignHubot}` +
                         `${lineBreakDouble}${deployerVerificationsHeader}` +
                         `${lineBreak}${openCheckbox}${timingDashboardVerification}` +
                         `${lineBreak}${openCheckbox}${firebaseVerification}` +
                         `${lineBreak}${openCheckbox}${ghVerification}` +
                         `${lineBreakDouble}${ccApplauseLeads}`,
                 );
+                expect(issue.issueAssignees).toEqual(['octocat', 'hubot']);
             });
         });
     });
diff --git a/tests/unit/NextStepUtilsTest.ts b/tests/unit/NextStepUtilsTest.ts
index 622881bc7979..79b6985ed94a 100644
--- a/tests/unit/NextStepUtilsTest.ts
+++ b/tests/unit/NextStepUtilsTest.ts
@@ -340,7 +340,7 @@ describe('libs/NextStepUtils', () => {
 
                 return Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {
                     submitsTo: currentUserAccountID,
-                    preventSelfApprovalEnabled: true,
+                    preventSelfApproval: true,
                 }).then(() => {
                     const result = NextStepUtils.buildNextStep(report, CONST.REPORT.STATUS_NUM.OPEN);
 
diff --git a/tests/unit/ViolationUtilsTest.js b/tests/unit/ViolationUtilsTest.js
index ff86b5fc6753..15a3a4f7de07 100644
--- a/tests/unit/ViolationUtilsTest.js
+++ b/tests/unit/ViolationUtilsTest.js
@@ -132,11 +132,6 @@ describe('getViolationsOnyxData', () => {
                         Lunch: {name: 'Lunch', enabled: true},
                         Dinner: {name: 'Dinner', enabled: true},
                     },
-                    Tag: {
-                        name: 'Tag',
-                        required: true,
-                        tags: {Lunch: {enabled: true}, Dinner: {enabled: true}},
-                    },
                 },
             };
             transaction.tag = 'Lunch';
@@ -201,4 +196,77 @@ describe('getViolationsOnyxData', () => {
             expect(result.value).not.toContainEqual([missingTagViolation]);
         });
     });
+    describe('policy has multi level tags', () => {
+        beforeEach(() => {
+            policyRequiresTags = true;
+            policyTags = {
+                Department: {
+                    name: 'Department',
+                    tags: {
+                        Accounting: {
+                            name: 'Accounting',
+                            enabled: true,
+                        },
+                    },
+                    required: true,
+                },
+                Region: {
+                    name: 'Region',
+                    tags: {
+                        Africa: {
+                            name: 'Africa',
+                            enabled: true,
+                        },
+                    },
+                },
+                Project: {
+                    name: 'Project',
+                    tags: {
+                        Project1: {
+                            name: 'Project1',
+                            enabled: true,
+                        },
+                    },
+                    required: true,
+                },
+            };
+        });
+        it('should return someTagLevelsRequired when a required tag is missing', () => {
+            const someTagLevelsRequiredViolation = {
+                name: 'someTagLevelsRequired',
+                type: 'violation',
+                data: {
+                    errorIndexes: [0, 1, 2],
+                },
+            };
+
+            // Test case where transaction has no tags
+            let result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories);
+            expect(result.value).toEqual([someTagLevelsRequiredViolation]);
+
+            // Test case where transaction has 1 tag
+            transaction.tag = 'Accounting';
+            someTagLevelsRequiredViolation.data = {errorIndexes: [1, 2]};
+            result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories);
+            expect(result.value).toEqual([someTagLevelsRequiredViolation]);
+
+            // Test case where transaction has 2 tags
+            transaction.tag = 'Accounting::Project1';
+            someTagLevelsRequiredViolation.data = {errorIndexes: [1]};
+            result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories);
+            expect(result.value).toEqual([someTagLevelsRequiredViolation]);
+
+            // Test case where transaction has all tags
+            transaction.tag = 'Accounting:Africa:Project1';
+            result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories);
+            expect(result.value).toEqual([]);
+        });
+        it('should return tagOutOfPolicy when a tag is not enabled in the policy but is set in the transaction', () => {
+            policyTags.Department.tags.Accounting.enabled = false;
+            transaction.tag = 'Accounting:Africa:Project1';
+            const result = ViolationsUtils.getViolationsOnyxData(transaction, transactionViolations, policyRequiresTags, policyTags, policyRequiresCategories, policyCategories);
+            const violation = {...tagOutOfPolicyViolation, data: {tagName: 'Department'}};
+            expect(result.value).toEqual([violation]);
+        });
+    });
 });
diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx
index 80f28002f975..85c2d67f80bc 100644
--- a/tests/utils/LHNTestUtils.tsx
+++ b/tests/utils/LHNTestUtils.tsx
@@ -260,7 +260,7 @@ function getFakePolicy(id = '1', name = 'Workspace-Test-001'): Policy {
             enabled: true,
         },
         autoReportingOffset: 1,
-        preventSelfApprovalEnabled: true,
+        preventSelfApproval: true,
         submitsTo: 123456,
         defaultBillable: false,
         disabledFields: {defaultBillable: true, reimbursable: false},
diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts
index ba5108d49481..8dd04f4750a9 100644
--- a/tests/utils/collections/policies.ts
+++ b/tests/utils/collections/policies.ts
@@ -14,7 +14,7 @@ export default function createRandomPolicy(index: number): Policy {
             enabled: randBoolean(),
         },
         autoReportingOffset: 1,
-        preventSelfApprovalEnabled: randBoolean(),
+        preventSelfApproval: randBoolean(),
         submitsTo: index,
         outputCurrency: randCurrencyCode(),
         role: rand(Object.values(CONST.POLICY.ROLE)),