diff --git a/.eslintrc.js b/.eslintrc.js
index 54d035ab3850..6194ccd39d3f 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -24,6 +24,26 @@ const restrictedImportPaths = [
         importNames: ['CSSProperties'],
         message: "Please use 'ViewStyle', 'TextStyle', 'ImageStyle' from 'react-native' instead.",
     },
+    {
+        name: '@styles/index',
+        importNames: ['default', 'defaultStyles'],
+        message: 'Do not import styles directly. Please use the `useThemeStyles` hook or `withThemeStyles` HOC instead.',
+    },
+    {
+        name: '@styles/utils',
+        importNames: ['default', 'DefaultStyleUtils'],
+        message: 'Do not import StyleUtils directly. Please use the `useStyleUtils` hook or `withStyleUtils` HOC instead.',
+    },
+    {
+        name: '@styles/theme',
+        importNames: ['default', 'defaultTheme'],
+
+        message: 'Do not import themes directly. Please use the `useTheme` hook or `withTheme` HOC instead.',
+    },
+    {
+        name: '@styles/theme/illustrations',
+        message: 'Do not import theme illustrations directly. Please use the `useThemeIllustrations` hook instead.',
+    },
 ];
 
 const restrictedImportPatterns = [
@@ -31,6 +51,18 @@ const restrictedImportPatterns = [
         group: ['**/assets/animations/**/*.json'],
         message: "Do not import animations directly. Please use the 'src/components/LottieAnimations' import instead.",
     },
+    {
+        group: ['@styles/theme/themes/**'],
+        message: 'Do not import themes directly. Please use the `useTheme` hook or `withTheme` HOC instead.',
+    },
+    {
+        group: ['@styles/utils/**', '!@styles/utils/FontUtils', '!@styles/utils/types'],
+        message: 'Do not import style util functions directly. Please use the `useStyleUtils` hook or `withStyleUtils` HOC instead.',
+    },
+    {
+        group: ['@styles/theme/illustrations/themes/**'],
+        message: 'Do not import theme illustrations directly. Please use the `useThemeIllustrations` hook instead.',
+    },
 ];
 
 module.exports = {
diff --git a/.github/actions/javascript/validateReassureOutput/action.yml b/.github/actions/javascript/validateReassureOutput/action.yml
index 4fd53e838fb5..b3b18c244a8f 100644
--- a/.github/actions/javascript/validateReassureOutput/action.yml
+++ b/.github/actions/javascript/validateReassureOutput/action.yml
@@ -7,9 +7,6 @@ inputs:
   COUNT_DEVIATION:
     description: Allowable deviation for the mean count in regression test results.
     required: true
-  REGRESSION_OUTPUT:
-    description: Refers to the results obtained from regression tests `.reassure/output.json`.
-    required: true
 runs:
   using: 'node20'
   main: './index.js'
diff --git a/.github/actions/javascript/validateReassureOutput/index.js b/.github/actions/javascript/validateReassureOutput/index.js
index 6cc59af1de48..e70c379697cd 100644
--- a/.github/actions/javascript/validateReassureOutput/index.js
+++ b/.github/actions/javascript/validateReassureOutput/index.js
@@ -8,9 +8,10 @@
 /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
 
 const core = __nccwpck_require__(186);
+const fs = __nccwpck_require__(147);
 
 const run = () => {
-    const regressionOutput = JSON.parse(core.getInput('REGRESSION_OUTPUT', {required: true}));
+    const regressionOutput = JSON.parse(fs.readFileSync('.reassure/output.json', 'utf8'));
     const countDeviation = core.getInput('COUNT_DEVIATION', {required: true});
     const durationDeviation = core.getInput('DURATION_DEVIATION_PERCENTAGE', {required: true});
 
diff --git a/.github/actions/javascript/validateReassureOutput/validateReassureOutput.js b/.github/actions/javascript/validateReassureOutput/validateReassureOutput.js
index da81d88c9885..214dc9e8b6d4 100644
--- a/.github/actions/javascript/validateReassureOutput/validateReassureOutput.js
+++ b/.github/actions/javascript/validateReassureOutput/validateReassureOutput.js
@@ -1,7 +1,8 @@
 const core = require('@actions/core');
+const fs = require('fs');
 
 const run = () => {
-    const regressionOutput = JSON.parse(core.getInput('REGRESSION_OUTPUT', {required: true}));
+    const regressionOutput = JSON.parse(fs.readFileSync('.reassure/output.json', 'utf8'));
     const countDeviation = core.getInput('COUNT_DEVIATION', {required: true});
     const durationDeviation = core.getInput('DURATION_DEVIATION_PERCENTAGE', {required: true});
 
diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml
index c49530c46faa..116f178868c1 100644
--- a/.github/workflows/reassurePerformanceTests.yml
+++ b/.github/workflows/reassurePerformanceTests.yml
@@ -36,17 +36,10 @@ jobs:
           npm install --force
           npx reassure --branch
 
-      - name: Read output.json
-        id: reassure
-        uses: juliangruber/read-file-action@v1
-        with:
-          path: .reassure/output.json
-
       - name: Validate output.json
         id: validateReassureOutput
         uses: ./.github/actions/javascript/validateReassureOutput
         with:
           DURATION_DEVIATION_PERCENTAGE: 20
           COUNT_DEVIATION: 0
-          REGRESSION_OUTPUT: ${{ steps.reassure.outputs.content }}
 
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 4c6289456041..63aa4215cd90 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -96,8 +96,8 @@ android {
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
         multiDexEnabled rootProject.ext.multiDexEnabled
-        versionCode 1001042104
-        versionName "1.4.21-4"
+        versionCode 1001042203
+        versionName "1.4.22-3"
     }
 
     flavorDimensions "default"
diff --git a/assets/emojis/index.ts b/assets/emojis/index.ts
index 5e5565da1499..02328001674e 100644
--- a/assets/emojis/index.ts
+++ b/assets/emojis/index.ts
@@ -1,10 +1,13 @@
+import type {Locale} from '@src/types/onyx';
 import emojis from './common';
 import enEmojis from './en';
 import esEmojis from './es';
-import type {Emoji} from './types';
+import type {Emoji, EmojisList} from './types';
 
 type EmojiTable = Record<string, Emoji>;
 
+type LocaleEmojis = Partial<Record<Locale, EmojisList>>;
+
 const emojiNameTable = emojis.reduce<EmojiTable>((prev, cur) => {
     const newValue = prev;
     if (!('header' in cur) && cur.name) {
@@ -26,10 +29,10 @@ const emojiCodeTableWithSkinTones = emojis.reduce<EmojiTable>((prev, cur) => {
     return newValue;
 }, {});
 
-const localeEmojis = {
+const localeEmojis: LocaleEmojis = {
     en: enEmojis,
     es: esEmojis,
-} as const;
+};
 
 export default emojis;
 export {emojiNameTable, emojiCodeTableWithSkinTones, localeEmojis};
diff --git a/assets/emojis/types.ts b/assets/emojis/types.ts
index e8c222fde948..e659924a7fa4 100644
--- a/assets/emojis/types.ts
+++ b/assets/emojis/types.ts
@@ -3,7 +3,7 @@ import type IconAsset from '@src/types/utils/IconAsset';
 type Emoji = {
     code: string;
     name: string;
-    types?: string[];
+    types?: readonly string[];
 };
 
 type HeaderEmoji = {
@@ -12,8 +12,10 @@ type HeaderEmoji = {
     code: string;
 };
 
-type PickerEmojis = Array<Emoji | HeaderEmoji>;
+type PickerEmoji = Emoji | HeaderEmoji;
+
+type PickerEmojis = PickerEmoji[];
 
 type EmojisList = Record<string, {keywords: string[]; name?: string}>;
 
-export type {Emoji, HeaderEmoji, EmojisList, PickerEmojis};
+export type {Emoji, HeaderEmoji, EmojisList, PickerEmojis, PickerEmoji};
diff --git a/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg b/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg
new file mode 100644
index 000000000000..829d3ee2e3fe
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__commentbubbles.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 27.3.1, 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:#E96DF2;}
+	.st1{fill:#F9B5FE;}
+	.st2{fill:none;stroke:#002140;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<path class="st0" d="M12.75,37.082c0,0-5.432-7.6-3.379-12.306c2.048-4.71,4.946-12.07,18.824-12.19
+	c13.877-0.12,18.223,12.791,17.501,17.136c-0.722,4.345-6.758,11.585-13.637,12.671s-10.138-0.12-10.138-0.12
+	s-6.638,3.74-13.396,1.688C8.525,43.96,12.024,39.615,12.75,37.082z"/>
+<path class="st1" d="M45.21,24.411c0,0,1.932,9.777-5.067,14.242c-6.033,3.62-9.172,4.706-17.257,4.105c0,0,0.726,4.947,6.277,8.205
+	s12.671,3.984,15.93,2.533c0,0,9.171,3.139,13.517,1.567c0,0-4.225-5.552-4.105-7.239c0,0,4.826-4.586,3.62-10.863
+	c-1.207-6.277-7.724-12.19-12.911-12.551H45.21z"/>
+<path class="st2" d="M12.682,36.829c-0.457,3.551-4.277,7.223-4.277,7.223s9.584,1.022,13.942-1.607"/>
+<path class="st2" d="M22.37,42.441c1.96,6.662,9.139,11.601,17.694,11.601c1.732,0,3.407-0.2,4.995-0.581"/>
+<path class="st2" d="M54.723,47.845c2.241-2.537,3.564-5.672,3.564-9.071c0-6.851-5.387-12.647-12.803-14.579"/>
+<path class="st2" d="M54.723,47.845C55.18,51.396,59,55.068,59,55.068s-9.584,1.022-13.942-1.607"/>
+<path class="st2" d="M12.682,36.829c-2.241-2.537-3.564-5.672-3.564-9.071c0-8.43,8.157-15.264,18.223-15.264
+	c10.065,0,18.223,6.834,18.223,15.268c0,8.434-8.157,15.268-18.223,15.268c-1.732,0-3.407-0.2-4.995-0.581"/>
+</svg>
diff --git a/assets/images/simple-illustrations/simple-illustration__hourglass.svg b/assets/images/simple-illustrations/simple-illustration__hourglass.svg
new file mode 100644
index 000000000000..539e1e45b795
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__hourglass.svg
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 27.3.1, 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:#0164BF;stroke:#002140;stroke-linecap:round;stroke-linejoin:round;}
+	.st1{fill:#B0D9FF;}
+	.st2{fill:#FFFAF0;}
+	.st3{fill:#FED607;}
+	.st4{fill:none;stroke:#002140;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<path class="st0" d="M42.97,59c0.508,0,1.959-0.373,1.959-1.847s-1.397-1.847-1.959-1.847c-0.261,0-17.466,0-17.709,0
+	c-0.508,0-1.959,0.373-1.959,1.847S24.699,59,25.261,59H42.97z"/>
+<path class="st0" d="M42.97,12.164c0.508,0,1.959-0.373,1.959-1.847c0-1.474-1.397-1.847-1.959-1.847H25.261
+	c-0.508,0-1.959,0.373-1.959,1.847c0,1.474,1.397,1.847,1.959,1.847H42.97z"/>
+<path class="st1" d="M45.72,14.024c2.382,1.784,3.154,4.35,2.382,7.284c-1.424,5.406-11.144,8.529-11.211,12.429v0.04
+	c0.067,3.9,9.782,7.023,11.211,12.429c0.773,2.934,0,5.5-2.382,7.284c-2.382,1.784-6.412,2.498-11.458,2.498h-0.193
+	c-5.046,0-9.077-0.715-11.459-2.498c-2.382-1.784-3.154-4.35-2.377-7.284c1.425-5.406,11.144-8.529,11.207-12.429v-0.04
+	c-0.067-3.9-9.782-7.023-11.207-12.429c-0.773-2.934-0.004-5.5,2.377-7.284s6.412-2.498,11.459-2.498h0.193
+	C39.308,11.526,43.339,12.24,45.72,14.024z M42.768,52.116c1.186-0.593,1.33-1.361,0.445-1.968c0,0-5.94-4.035-8.947-4.035h-0.099
+	c-3.006,0-8.947,4.035-8.947,4.035c-0.885,0.607-0.746,1.375,0.44,1.968c1.258,0.629,2.889,1.191,8.461,1.191h0.189
+	c5.572,0,7.203-0.562,8.461-1.191H42.768z M34.787,33.369c0-0.445-0.36-0.809-0.809-0.809c-0.449,0-0.809,0.364-0.809,0.809
+	c0,0.445,0.36,0.809,0.809,0.809C34.428,34.178,34.787,33.814,34.787,33.369z M34.787,39.943c0-0.445-0.36-0.809-0.809-0.809
+	c-0.449,0-0.809,0.364-0.809,0.809s0.36,0.809,0.809,0.809C34.428,40.752,34.787,40.388,34.787,39.943z"/>
+<path class="st2" d="M44.277,43.668c2.377,2.444,3.155,5.127,1.645,5.554c-1.708,0.476-1.64-1.802-4.732-4.193
+	c-2.764-2.134-3.191-3.091-2.413-3.954C39.46,40.312,41.343,40.653,44.277,43.668z"/>
+<path class="st3" d="M43.208,50.152c0.885,0.607,0.746,1.375-0.445,1.968c-1.258,0.629-2.889,1.191-8.461,1.191h-0.189
+	c-5.572,0-7.203-0.562-8.461-1.191c-1.186-0.593-1.326-1.362-0.44-1.968c0,0,5.941-4.035,8.947-4.035h0.099
+	c3.006,0,8.947,4.035,8.947,4.035H43.208z"/>
+<path class="st3" d="M41.532,22.962c0.706,0,0.998,0.836,0.422,1.218c0,0-5.217,4.354-7.891,4.354s-8.07-4.354-8.07-4.354
+	c-0.571-0.382-0.283-1.218,0.422-1.218h15.112H41.532z"/>
+<path class="st3" d="M33.978,32.564c0.449,0,0.809,0.364,0.809,0.809s-0.36,0.809-0.809,0.809c-0.449,0-0.809-0.364-0.809-0.809
+	S33.529,32.564,33.978,32.564z"/>
+<path class="st3" d="M33.978,39.139c0.449,0,0.809,0.364,0.809,0.809c0,0.445-0.36,0.809-0.809,0.809
+	c-0.449,0-0.809-0.364-0.809-0.809C33.169,39.503,33.529,39.139,33.978,39.139z"/>
+<path class="st2" d="M45.177,15.795c2.098,2.507,1.609,6.956-0.234,6.938c-1.609-0.014-0.126-1.546-1.267-4.04
+	c-1.141-2.494-3.73-2.746-2.894-4.201c0.62-1.087,2.894-0.49,4.395,1.308V15.795z"/>
+<path class="st4" d="M31.435,33.778c-0.067,3.9-9.782,7.023-11.207,12.429c-0.773,2.934-0.005,5.5,2.377,7.284
+	c2.382,1.784,6.412,2.498,11.459,2.498"/>
+<path class="st4" d="M36.89,33.778c0.067,3.9,9.782,7.023,11.211,12.429c0.773,2.934,0,5.5-2.382,7.284
+	c-2.382,1.784-6.412,2.498-11.459,2.498"/>
+<path class="st4" d="M36.89,33.778v-0.04c0.067-3.9,9.782-7.023,11.211-12.429c0.773-2.934,0-5.5-2.382-7.284
+	s-6.412-2.498-11.459-2.498"/>
+<path class="st4" d="M31.435,33.778v-0.04c-0.067-3.9-9.782-7.023-11.207-12.429c-0.773-2.934-0.005-5.5,2.377-7.284
+	s6.412-2.498,11.459-2.498"/>
+<path class="st4" d="M26.42,22.962h15.112c0.706,0,0.998,0.836,0.422,1.218c0,0-5.217,4.354-7.891,4.354s-8.07-4.354-8.07-4.354
+	c-0.571-0.382-0.283-1.218,0.422-1.218H26.42z"/>
+<path class="st4" d="M34.113,53.307c-5.572,0-7.203-0.562-8.461-1.191c-1.186-0.593-1.326-1.362-0.44-1.968
+	c0,0,5.941-4.035,8.947-4.035"/>
+<path class="st4" d="M34.302,53.307c5.572,0,7.203-0.562,8.461-1.191c1.186-0.593,1.33-1.362,0.445-1.968
+	c0,0-5.941-4.035-8.947-4.035"/>
+<path class="st4" d="M34.787,33.373c0,0.445-0.36,0.809-0.809,0.809c-0.449,0-0.809-0.364-0.809-0.809s0.36-0.809,0.809-0.809
+	C34.428,32.564,34.787,32.928,34.787,33.373z"/>
+<path class="st4" d="M34.787,39.947c0,0.445-0.36,0.809-0.809,0.809c-0.449,0-0.809-0.364-0.809-0.809
+	c0-0.445,0.36-0.809,0.809-0.809C34.428,39.139,34.787,39.503,34.787,39.947z"/>
+</svg>
diff --git a/assets/images/simple-illustrations/simple-illustration__trashcan.svg b/assets/images/simple-illustrations/simple-illustration__trashcan.svg
new file mode 100644
index 000000000000..4e66efa0a67e
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__trashcan.svg
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 27.3.1, 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:#4BA6A6;}
+	.st1{fill:#002140;}
+	.st2{fill:#FFED8F;}
+	.st3{fill:#FFFAF0;}
+	.st4{fill:#FBCCFF;}
+	.st5{fill:none;stroke:#002140;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;}
+</style>
+<path class="st0" d="M16.303,18.365l3.777,36.478c0,0,4.688,3.777,14.591,3.648c9.903-0.13,13.68-3.907,13.68-3.907L52,18.365
+	c0,0-5.733,2.996-14.069,2.867C29.595,21.102,19.562,20.972,16.303,18.365L16.303,18.365z"/>
+<path class="st1" d="M18.32,18.691l-0.715-2.607c0,0-1.304,0.455-1.174,1.5C16.561,18.624,18.32,18.691,18.32,18.691z"/>
+<path class="st1" d="M49.066,15.891l1.174,3.192c0,0,2.281-0.911,1.563-1.826C51.084,16.343,49.066,15.891,49.066,15.891z"/>
+<path class="st2" d="M22.687,14.132l-4.429-1.759c0,0-0.887,3.161-0.652,3.715c0.236,0.554,0.848,2.996,0.848,2.996l3.715,0.911
+	l0.522-5.862H22.687z"/>
+<path class="st3" d="M33.367,13.154c0,0,2.395-3.766,3.255-3.648c0.86,0.118,2.67,1.174,3.974,1.237
+	c1.304,0.067,4.3-1.107,4.3-1.107s3.451,5.081,3.581,5.277c0.13,0.196,1.366,4.559,1.366,4.559l-5.603,1.304l-10.421-3.581
+	l-0.456-4.041H33.367z"/>
+<path class="st4" d="M44.248,20.776l-1.15-2.67l-4.084-1.736l-3.302,1.041l-1.041-3.475l-5.297-2.78l-1.826,2.953l-4.084-0.695
+	l-0.868,2.171l-0.436,4.41c0,0,7.209,1.064,13.201,1.15c5.992,0.086,8.882-0.369,8.882-0.369H44.248z"/>
+<path class="st5" d="M48.666,15.523c2.077,0.597,3.294,1.327,3.294,2.116c0,2.014-7.971,3.648-17.807,3.648
+	s-17.807-1.633-17.807-3.648c0-0.499,0.487-0.97,1.362-1.402"/>
+<path class="st5" d="M48.27,54.191c0,2.399-6.318,4.343-14.116,4.343c-7.798,0-14.116-1.944-14.116-4.343"/>
+<path class="st5" d="M20.041,54.192L16.35,17.635"/>
+<path class="st5" d="M48.27,54.192l3.691-36.557"/>
+<path class="st5" d="M19.538,46.374l15.459,12.161"/>
+<path class="st5" d="M18.234,34.908L45.418,56.19"/>
+<path class="st5" d="M17.104,22.747l31.865,24.522"/>
+<path class="st5" d="M32.182,21.408l17.941,13.806"/>
+<path class="st5" d="M45.293,20.544l5.427,4.174"/>
+<path class="st5" d="M48.635,46.201L33.176,58.361"/>
+<path class="st5" d="M50.025,34.994L22.837,56.276"/>
+<path class="st5" d="M51.151,22.837L19.291,47.355"/>
+<path class="st5" d="M36.077,21.495L18.137,35.301"/>
+<path class="st5" d="M22.966,20.63l-5.427,4.174"/>
+<path class="st5" d="M26.544,16.08c1.343-1.539,2.27-3.436,2.658-5.442c1.614,1.417,3.451,2.548,5.482,3.251
+	c0.114,0.039,0.236,0.082,0.31,0.177c0.078,0.102,0.09,0.24,0.098,0.365c0.118,1.677,0.507,3.334,1.143,4.893
+	c-2.109,0.063-4.229-0.432-6.09-1.422"/>
+<path class="st5" d="M27.698,14.121c-1.162-0.389-2.34-0.73-3.526-1.029c-0.291-0.071-0.62-0.134-0.868,0.035
+	c-0.153,0.106-0.24,0.279-0.314,0.448c-0.84,1.948-0.55,4.209-1.182,6.236"/>
+<path class="st5" d="M33.612,12.546c0.691-1.095,1.551-2.085,2.541-2.921c0.157-0.134,0.33-0.267,0.534-0.298
+	c0.291-0.043,0.562,0.137,0.805,0.302c1.155,0.774,2.47,1.382,3.86,1.425c1.39,0.043,2.855-0.573,3.55-1.775
+	c2.081,3.192,4.205,6.84,4.837,10.111"/>
+<path class="st5" d="M35.732,17.328c0.742-0.314,1.488-0.624,2.23-0.938c0.153-0.063,0.306-0.13,0.471-0.137
+	c0.161-0.008,0.322,0.039,0.475,0.086c1.19,0.365,2.38,0.726,3.569,1.092c0.126,0.039,0.255,0.079,0.357,0.161
+	c0.137,0.106,0.212,0.271,0.283,0.432c0.346,0.774,0.691,1.547,1.033,2.321"/>
+<path class="st5" d="M22.593,13.967c-1.512-0.499-2.937-1.252-4.201-2.215c-0.432,1.586-0.62,3.243-0.554,4.889
+	c0.035,0.852,0.149,1.736,0.605,2.458"/>
+</svg>
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index ac41382486a5..f58687c66c63 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.21</string>
+	<string>1.4.22</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleURLTypes</key>
@@ -40,7 +40,7 @@
 		</dict>
 	</array>
 	<key>CFBundleVersion</key>
-	<string>1.4.21.4</string>
+	<string>1.4.22.3</string>
 	<key>ITSAppUsesNonExemptEncryption</key>
 	<false/>
 	<key>LSApplicationQueriesSchemes</key>
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index ab58f3c293bd..b7b8c9d3416b 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.21</string>
+	<string>1.4.22</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>1.4.21.4</string>
+	<string>1.4.22.3</string>
 </dict>
 </plist>
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 77c390c46416..ee54d98895a5 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -614,7 +614,7 @@ PODS:
     - React-Core
   - react-native-pager-view (6.2.0):
     - React-Core
-  - react-native-pdf (6.7.3):
+  - react-native-pdf (6.7.4):
     - React-Core
   - react-native-performance (5.1.0):
     - React-Core
@@ -1265,7 +1265,7 @@ SPEC CHECKSUMS:
   react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5
   react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
   react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
-  react-native-pdf: b4ca3d37a9a86d9165287741c8b2ef4d8940c00e
+  react-native-pdf: 79aa75e39a80c1d45ffe58aa500f3cf08f267a2e
   react-native-performance: cef2b618d47b277fb5c3280b81a3aad1e72f2886
   react-native-plaid-link-sdk: df1618a85a615d62ff34e34b76abb7a56497fbc1
   react-native-quick-sqlite: bcc7a7a250a40222f18913a97cd356bf82d0a6c4
diff --git a/package-lock.json b/package-lock.json
index 15964d8c5f3e..55bfafbec2f2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "new.expensify",
-  "version": "1.4.21-4",
+  "version": "1.4.22-3",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "new.expensify",
-      "version": "1.4.21-4",
+      "version": "1.4.22-3",
       "hasInstallScript": true,
       "license": "MIT",
       "dependencies": {
@@ -96,7 +96,7 @@
         "react-native-modal": "^13.0.0",
         "react-native-onyx": "1.0.118",
         "react-native-pager-view": "^6.2.0",
-        "react-native-pdf": "^6.7.3",
+        "react-native-pdf": "^6.7.4",
         "react-native-performance": "^5.1.0",
         "react-native-permissions": "^3.9.3",
         "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5",
@@ -47635,9 +47635,9 @@
       }
     },
     "node_modules/react-native-pdf": {
-      "version": "6.7.3",
-      "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz",
-      "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==",
+      "version": "6.7.4",
+      "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz",
+      "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==",
       "dependencies": {
         "crypto-js": "4.2.0",
         "deprecated-react-native-prop-types": "^2.3.0"
@@ -90531,9 +90531,9 @@
       "requires": {}
     },
     "react-native-pdf": {
-      "version": "6.7.3",
-      "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz",
-      "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==",
+      "version": "6.7.4",
+      "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz",
+      "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==",
       "requires": {
         "crypto-js": "4.2.0",
         "deprecated-react-native-prop-types": "^2.3.0"
diff --git a/package.json b/package.json
index 29ade80b518d..7264cb5fa25e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "new.expensify",
-  "version": "1.4.21-4",
+  "version": "1.4.22-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.",
@@ -144,7 +144,7 @@
     "react-native-modal": "^13.0.0",
     "react-native-onyx": "1.0.118",
     "react-native-pager-view": "^6.2.0",
-    "react-native-pdf": "^6.7.3",
+    "react-native-pdf": "^6.7.4",
     "react-native-performance": "^5.1.0",
     "react-native-permissions": "^3.9.3",
     "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5",
diff --git a/src/CONST.ts b/src/CONST.ts
index b5563825e016..2fd592f539c2 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -103,6 +103,10 @@ const CONST = {
 
     MERCHANT_NAME_MAX_LENGTH: 255,
 
+    REQUEST_PREVIEW: {
+        MAX_LENGTH: 83,
+    },
+
     CALENDAR_PICKER: {
         // Numbers were arbitrarily picked.
         MIN_YEAR: CURRENT_YEAR - 100,
@@ -3054,6 +3058,42 @@ const CONST = {
         CAROUSEL: 3,
     },
 
+    VIOLATIONS: {
+        ALL_TAG_LEVELS_REQUIRED: 'allTagLevelsRequired',
+        AUTO_REPORTED_REJECTED_EXPENSE: 'autoReportedRejectedExpense',
+        BILLABLE_EXPENSE: 'billableExpense',
+        CASH_EXPENSE_WITH_NO_RECEIPT: 'cashExpenseWithNoReceipt',
+        CATEGORY_OUT_OF_POLICY: 'categoryOutOfPolicy',
+        CONVERSION_SURCHARGE: 'conversionSurcharge',
+        CUSTOM_UNIT_OUT_OF_POLICY: 'customUnitOutOfPolicy',
+        DUPLICATED_TRANSACTION: 'duplicatedTransaction',
+        FIELD_REQUIRED: 'fieldRequired',
+        FUTURE_DATE: 'futureDate',
+        INVOICE_MARKUP: 'invoiceMarkup',
+        MAX_AGE: 'maxAge',
+        MISSING_CATEGORY: 'missingCategory',
+        MISSING_COMMENT: 'missingComment',
+        MISSING_TAG: 'missingTag',
+        MODIFIED_AMOUNT: 'modifiedAmount',
+        MODIFIED_DATE: 'modifiedDate',
+        NON_EXPENSIWORKS_EXPENSE: 'nonExpensiworksExpense',
+        OVER_AUTO_APPROVAL_LIMIT: 'overAutoApprovalLimit',
+        OVER_CATEGORY_LIMIT: 'overCategoryLimit',
+        OVER_LIMIT: 'overLimit',
+        OVER_LIMIT_ATTENDEE: 'overLimitAttendee',
+        PER_DAY_LIMIT: 'perDayLimit',
+        RECEIPT_NOT_SMART_SCANNED: 'receiptNotSmartScanned',
+        RECEIPT_REQUIRED: 'receiptRequired',
+        RTER: 'rter',
+        SMARTSCAN_FAILED: 'smartscanFailed',
+        SOME_TAG_LEVELS_REQUIRED: 'someTagLevelsRequired',
+        TAG_OUT_OF_POLICY: 'tagOutOfPolicy',
+        TAX_AMOUNT_CHANGED: 'taxAmountChanged',
+        TAX_OUT_OF_POLICY: 'taxOutOfPolicy',
+        TAX_RATE_CHANGED: 'taxRateChanged',
+        TAX_REQUIRED: 'taxRequired',
+    },
+
     /** Context menu types */
     CONTEXT_MENU_TYPES: {
         LINK: 'LINK',
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 35fa4bbf0837..b6e62814466b 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -358,9 +358,9 @@ const ROUTES = {
             getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}/`, backTo),
     },
     MONEY_REQUEST_STEP_MERCHANT: {
-        route: 'create/:iouType/merchante/:transactionID/:reportID/',
+        route: 'create/:iouType/merchant/:transactionID/:reportID/',
         getRoute: (iouType: ValueOf<typeof CONST.IOU.TYPE>, transactionID: string, reportID: string, backTo = '') =>
-            getUrlWithBackToParam(`create/${iouType}/merchante/${transactionID}/${reportID}/`, backTo),
+            getUrlWithBackToParam(`create/${iouType}/merchant/${transactionID}/${reportID}/`, backTo),
     },
     MONEY_REQUEST_STEP_PARTICIPANTS: {
         route: 'create/:iouType/participants/:transactionID/:reportID/',
@@ -479,6 +479,7 @@ const ROUTES = {
         route: 'referral/:contentType',
         getRoute: (contentType: string) => `referral/${contentType}` as const,
     },
+    PROCESS_MONEY_REQUEST_HOLD: 'hold-request-educational',
 } as const;
 
 export {getUrlWithBackToParam};
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 91c4153bacd2..703cb309d641 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -108,6 +108,7 @@ const SCREENS = {
         ROOM_MEMBERS: 'RoomMembers',
         ROOM_INVITE: 'RoomInvite',
         REFERRAL: 'Referral',
+        PROCESS_MONEY_REQUEST_HOLD: 'ProcessMoneyRequestHold',
     },
     SIGN_IN_WITH_APPLE_DESKTOP: 'AppleSignInDesktop',
     SIGN_IN_WITH_GOOGLE_DESKTOP: 'GoogleSignInDesktop',
@@ -231,6 +232,7 @@ const SCREENS = {
     SIGN_IN_ROOT: 'SignIn_Root',
     DETAILS_ROOT: 'Details_Root',
     PROFILE_ROOT: 'Profile_Root',
+    PROCESS_MONEY_REQUEST_HOLD_ROOT: 'ProcessMoneyRequestHold_Root',
     REPORT_WELCOME_MESSAGE_ROOT: 'Report_WelcomeMessage_Root',
     REPORT_PARTICIPANTS_ROOT: 'ReportParticipants_Root',
     ROOM_MEMBERS_ROOT: 'RoomMembers_Root',
diff --git a/src/TIMEZONES.ts b/src/TIMEZONES.ts
index 1eb49f291495..238563134872 100644
--- a/src/TIMEZONES.ts
+++ b/src/TIMEZONES.ts
@@ -1,5 +1,6 @@
+/* eslint-disable @typescript-eslint/naming-convention */
 // All timezones were taken from: https://raw.githubusercontent.com/leon-do/Timezones/main/timezone.json
-export default [
+const TIMEZONES = [
     'Africa/Abidjan',
     'Africa/Accra',
     'Africa/Addis_Ababa',
@@ -419,3 +420,137 @@ export default [
     'Pacific/Wake',
     'Pacific/Wallis',
 ] as const;
+
+/**
+ * The timezones supported in browser and on native devices differ, so we must map each timezone to its supported equivalent.
+ * Data sourced from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
+ */
+const timezoneBackwardMap: Record<string, (typeof TIMEZONES)[number]> = {
+    'Africa/Asmera': 'Africa/Nairobi',
+    'Africa/Timbuktu': 'Africa/Abidjan',
+    'America/Argentina/ComodRivadavia': 'America/Argentina/Catamarca',
+    'America/Atka': 'America/Adak',
+    'America/Buenos_Aires': 'America/Argentina/Buenos_Aires',
+    'America/Catamarca': 'America/Argentina/Catamarca',
+    'America/Coral_Harbour': 'America/Panama',
+    'America/Cordoba': 'America/Argentina/Cordoba',
+    'America/Ensenada': 'America/Tijuana',
+    'America/Fort_Wayne': 'America/Indiana/Indianapolis',
+    'America/Godthab': 'America/Nuuk',
+    'America/Indianapolis': 'America/Indiana/Indianapolis',
+    'America/Jujuy': 'America/Argentina/Jujuy',
+    'America/Knox_IN': 'America/Indiana/Knox',
+    'America/Louisville': 'America/Kentucky/Louisville',
+    'America/Mendoza': 'America/Argentina/Mendoza',
+    'America/Montreal': 'America/Toronto',
+    'America/Nipigon': 'America/Toronto',
+    'America/Pangnirtung': 'America/Iqaluit',
+    'America/Porto_Acre': 'America/Rio_Branco',
+    'America/Rainy_River': 'America/Winnipeg',
+    'America/Rosario': 'America/Argentina/Cordoba',
+    'America/Santa_Isabel': 'America/Tijuana',
+    'America/Shiprock': 'America/Denver',
+    'America/Thunder_Bay': 'America/Toronto',
+    'America/Virgin': 'America/Puerto_Rico',
+    'America/Yellowknife': 'America/Edmonton',
+    'Antarctica/South_Pole': 'Pacific/Auckland',
+    'Asia/Ashkhabad': 'Asia/Ashgabat',
+    'Asia/Calcutta': 'Asia/Kolkata',
+    'Asia/Chongqing': 'Asia/Shanghai',
+    'Asia/Chungking': 'Asia/Shanghai',
+    'Asia/Dacca': 'Asia/Dhaka',
+    'Asia/Harbin': 'Asia/Shanghai',
+    'Asia/Istanbul': 'Europe/Istanbul',
+    'Asia/Kashgar': 'Asia/Urumqi',
+    'Asia/Katmandu': 'Asia/Kathmandu',
+    'Asia/Macao': 'Asia/Macau',
+    'Asia/Rangoon': 'Asia/Yangon',
+    'Asia/Saigon': 'Asia/Ho_Chi_Minh',
+    'Asia/Tel_Aviv': 'Asia/Jerusalem',
+    'Asia/Thimbu': 'Asia/Thimphu',
+    'Asia/Ujung_Pandang': 'Asia/Makassar',
+    'Asia/Ulan_Bator': 'Asia/Ulaanbaatar',
+    'Atlantic/Faeroe': 'Atlantic/Faroe',
+    'Atlantic/Jan_Mayen': 'Europe/Berlin',
+    'Australia/ACT': 'Australia/Sydney',
+    'Australia/Canberra': 'Australia/Sydney',
+    'Australia/Currie': 'Australia/Hobart',
+    'Australia/LHI': 'Australia/Lord_Howe',
+    'Australia/NSW': 'Australia/Sydney',
+    'Australia/North': 'Australia/Darwin',
+    'Australia/Queensland': 'Australia/Brisbane',
+    'Australia/South': 'Australia/Adelaide',
+    'Australia/Tasmania': 'Australia/Hobart',
+    'Australia/Victoria': 'Australia/Melbourne',
+    'Australia/West': 'Australia/Perth',
+    'Australia/Yancowinna': 'Australia/Broken_Hill',
+    'Brazil/Acre': 'America/Rio_Branco',
+    'Brazil/DeNoronha': 'America/Noronha',
+    'Brazil/East': 'America/Sao_Paulo',
+    'Brazil/West': 'America/Manaus',
+    'Canada/Atlantic': 'America/Halifax',
+    'Canada/Central': 'America/Winnipeg',
+    'Canada/Eastern': 'America/Toronto',
+    'Canada/Mountain': 'America/Edmonton',
+    'Canada/Newfoundland': 'America/St_Johns',
+    'Canada/Pacific': 'America/Vancouver',
+    'Canada/Saskatchewan': 'America/Regina',
+    'Canada/Yukon': 'America/Whitehorse',
+    'Chile/Continental': 'America/Santiago',
+    'Chile/EasterIsland': 'Pacific/Easter',
+    Cuba: 'America/Havana',
+    Egypt: 'Africa/Cairo',
+    Eire: 'Europe/Dublin',
+    'Europe/Belfast': 'Europe/London',
+    'Europe/Kiev': 'Europe/Kyiv',
+    'Europe/Nicosia': 'Asia/Nicosia',
+    'Europe/Tiraspol': 'Europe/Chisinau',
+    'Europe/Uzhgorod': 'Europe/Kyiv',
+    'Europe/Zaporozhye': 'Europe/Kyiv',
+    GB: 'Europe/London',
+    'GB-Eire': 'Europe/London',
+    Hongkong: 'Asia/Hong_Kong',
+    Iceland: 'Africa/Abidjan',
+    Iran: 'Asia/Tehran',
+    Israel: 'Asia/Jerusalem',
+    Jamaica: 'America/Jamaica',
+    Japan: 'Asia/Tokyo',
+    Kwajalein: 'Pacific/Kwajalein',
+    Libya: 'Africa/Tripoli',
+    'Mexico/BajaNorte': 'America/Tijuana',
+    'Mexico/BajaSur': 'America/Mazatlan',
+    'Mexico/General': 'America/Mexico_City',
+    NZ: 'Pacific/Auckland',
+    'NZ-CHAT': 'Pacific/Chatham',
+    Navajo: 'America/Denver',
+    PRC: 'Asia/Shanghai',
+    'Pacific/Enderbury': 'Pacific/Kanton',
+    'Pacific/Johnston': 'Pacific/Honolulu',
+    'Pacific/Ponape': 'Pacific/Guadalcanal',
+    'Pacific/Samoa': 'Pacific/Pago_Pago',
+    'Pacific/Truk': 'Pacific/Port_Moresby',
+    'Pacific/Yap': 'Pacific/Port_Moresby',
+    Poland: 'Europe/Warsaw',
+    Portugal: 'Europe/Lisbon',
+    ROC: 'Asia/Taipei',
+    ROK: 'Asia/Seoul',
+    Singapore: 'Asia/Singapore',
+    Turkey: 'Europe/Istanbul',
+    'US/Alaska': 'America/Anchorage',
+    'US/Aleutian': 'America/Adak',
+    'US/Arizona': 'America/Phoenix',
+    'US/Central': 'America/Chicago',
+    'US/East-Indiana': 'America/Indiana/Indianapolis',
+    'US/Eastern': 'America/New_York',
+    'US/Hawaii': 'Pacific/Honolulu',
+    'US/Indiana-Starke': 'America/Indiana/Knox',
+    'US/Michigan': 'America/Detroit',
+    'US/Mountain': 'America/Denver',
+    'US/Pacific': 'America/Los_Angeles',
+    'US/Samoa': 'Pacific/Pago_Pago',
+    'W-SU': 'Europe/Moscow',
+};
+
+export {timezoneBackwardMap};
+
+export default TIMEZONES;
diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js
index 31b04a3d954f..9f2635633318 100644
--- a/src/components/AddressSearch/index.js
+++ b/src/components/AddressSearch/index.js
@@ -349,6 +349,7 @@ function AddressSearch({
                     lat: successData.coords.latitude,
                     lng: successData.coords.longitude,
                     address: CONST.YOUR_LOCATION_TEXT,
+                    name: CONST.YOUR_LOCATION_TEXT,
                 };
                 onPress(location);
             },
diff --git a/src/components/AmountTextInput.tsx b/src/components/AmountTextInput.tsx
index e1bbb7cdfbdd..0f3416076cc0 100644
--- a/src/components/AmountTextInput.tsx
+++ b/src/components/AmountTextInput.tsx
@@ -26,13 +26,16 @@ type AmountTextInputProps = {
     style?: StyleProp<TextStyle>;
 
     /** Style for the container */
-    containerStyles?: StyleProp<ViewStyle>;
+    touchableInputWrapperStyle?: StyleProp<ViewStyle>;
 
     /** Function to call to handle key presses in the text input */
     onKeyPress?: () => void;
 };
 
-function AmountTextInput({formattedAmount, onChangeAmount, placeholder, selection, onSelectionChange, style, containerStyles, onKeyPress}: AmountTextInputProps, ref: BaseTextInputRef) {
+function AmountTextInput(
+    {formattedAmount, onChangeAmount, placeholder, selection, onSelectionChange, style, touchableInputWrapperStyle, onKeyPress}: AmountTextInputProps,
+    ref: BaseTextInputRef,
+) {
     const styles = useThemeStyles();
     return (
         <TextInput
@@ -51,7 +54,7 @@ function AmountTextInput({formattedAmount, onChangeAmount, placeholder, selectio
             onSelectionChange={onSelectionChange}
             role={CONST.ROLE.PRESENTATION}
             onKeyPress={onKeyPress}
-            containerStyles={containerStyles}
+            touchableInputWrapperStyle={touchableInputWrapperStyle}
         />
     );
 }
diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js
index d24d1e18907f..1b4d350f7d4f 100755
--- a/src/components/AttachmentModal.js
+++ b/src/components/AttachmentModal.js
@@ -19,7 +19,6 @@ import fileDownload from '@libs/fileDownload';
 import * as FileUtils from '@libs/fileDownload/FileUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import * as ReportActionsUtils from '@libs/ReportActionsUtils';
-import * as ReportUtils from '@libs/ReportUtils';
 import * as TransactionUtils from '@libs/TransactionUtils';
 import useNativeDriver from '@libs/useNativeDriver';
 import reportPropTypes from '@pages/reportPropTypes';
@@ -95,6 +94,9 @@ const propTypes = {
 
     /** Whether it is a receipt attachment or not */
     isReceiptAttachment: PropTypes.bool,
+
+    /** Whether the receipt can be replaced */
+    canEditReceipt: PropTypes.bool,
 };
 
 const defaultProps = {
@@ -113,6 +115,7 @@ const defaultProps = {
     onCarouselAttachmentChange: () => {},
     isWorkspaceAvatar: false,
     isReceiptAttachment: false,
+    canEditReceipt: false,
 };
 
 function AttachmentModal(props) {
@@ -126,7 +129,7 @@ function AttachmentModal(props) {
     const [isAuthTokenRequired, setIsAuthTokenRequired] = useState(props.isAuthTokenRequired);
     const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState('');
     const [attachmentInvalidReason, setAttachmentInvalidReason] = useState(null);
-    const [source, setSource] = useState(props.source);
+    const [source, setSource] = useState(() => props.source);
     const [modalType, setModalType] = useState(CONST.MODAL.MODAL_TYPE.CENTERED_UNSWIPEABLE);
     const [isConfirmButtonDisabled, setIsConfirmButtonDisabled] = useState(false);
     const [confirmButtonFadeAnimation] = useState(() => new Animated.Value(1));
@@ -359,7 +362,7 @@ function AttachmentModal(props) {
     }, []);
 
     useEffect(() => {
-        setSource(props.source);
+        setSource(() => props.source);
     }, [props.source]);
 
     useEffect(() => {
@@ -372,13 +375,9 @@ function AttachmentModal(props) {
         if (!props.isReceiptAttachment || !props.parentReport || !props.parentReportActions) {
             return [];
         }
-        const menuItems = [];
-        const parentReportAction = props.parentReportActions[props.report.parentReportActionID];
 
-        const canEdit =
-            ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, props.parentReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT, props.transaction) &&
-            !TransactionUtils.isDistanceRequest(props.transaction);
-        if (canEdit) {
+        const menuItems = [];
+        if (props.canEditReceipt) {
             menuItems.push({
                 icon: Expensicons.Camera,
                 text: props.translate('common.replace'),
@@ -393,7 +392,7 @@ function AttachmentModal(props) {
             text: props.translate('common.download'),
             onSelected: () => downloadAttachment(source),
         });
-        if (TransactionUtils.hasReceipt(props.transaction) && !TransactionUtils.isReceiptBeingScanned(props.transaction) && canEdit) {
+        if (TransactionUtils.hasReceipt(props.transaction) && !TransactionUtils.isReceiptBeingScanned(props.transaction) && props.canEditReceipt) {
             menuItems.push({
                 icon: Expensicons.Trashcan,
                 text: props.translate('receipt.deleteReceipt'),
diff --git a/src/components/ConfirmPopover.js b/src/components/ConfirmPopover.js
deleted file mode 100644
index 83001736b471..000000000000
--- a/src/components/ConfirmPopover.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import ConfirmContent from './ConfirmContent';
-import Popover from './Popover';
-import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
-
-const propTypes = {
-    /** Title of the modal */
-    title: PropTypes.string.isRequired,
-
-    /** A callback to call when the form has been submitted */
-    onConfirm: PropTypes.func.isRequired,
-
-    /** A callback to call when the form has been closed */
-    onCancel: PropTypes.func,
-
-    /** Modal visibility */
-    isVisible: PropTypes.bool.isRequired,
-
-    /** Confirm button text */
-    confirmText: PropTypes.string,
-
-    /** Cancel button text */
-    cancelText: PropTypes.string,
-
-    /** Is the action destructive */
-    danger: PropTypes.bool,
-
-    /** Whether we should show the cancel button */
-    shouldShowCancelButton: PropTypes.bool,
-
-    /** Modal content text/element */
-    prompt: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
-
-    /** Where the popover should be positioned */
-    anchorPosition: PropTypes.shape({
-        top: PropTypes.number,
-        left: PropTypes.number,
-    }).isRequired,
-
-    /** Styles for view */
-    // eslint-disable-next-line react/forbid-prop-types
-    contentStyles: PropTypes.arrayOf(PropTypes.object),
-
-    ...windowDimensionsPropTypes,
-};
-
-const defaultProps = {
-    confirmText: '',
-    cancelText: '',
-    danger: false,
-    onCancel: () => {},
-    shouldShowCancelButton: true,
-    prompt: '',
-    contentStyles: [],
-};
-
-function ConfirmPopover(props) {
-    return (
-        <Popover
-            onSubmit={props.onConfirm}
-            onClose={props.onCancel}
-            isVisible={props.isVisible}
-            anchorPosition={props.anchorPosition}
-        >
-            <ConfirmContent
-                contentStyles={props.contentStyles}
-                title={props.title}
-                prompt={props.prompt}
-                confirmText={props.confirmText}
-                cancelText={props.cancelText}
-                danger={props.danger}
-                shouldShowCancelButton={props.shouldShowCancelButton}
-                onConfirm={props.onConfirm}
-                onCancel={props.onCancel}
-                onClose={props.onCancel}
-            />
-        </Popover>
-    );
-}
-
-ConfirmPopover.propTypes = propTypes;
-ConfirmPopover.defaultProps = defaultProps;
-ConfirmPopover.displayName = 'ConfirmPopover';
-export default withWindowDimensions(ConfirmPopover);
diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx
index 5904b1521f98..1c0306741048 100644
--- a/src/components/EmojiSuggestions.tsx
+++ b/src/components/EmojiSuggestions.tsx
@@ -1,9 +1,9 @@
 import type {ReactElement} from 'react';
 import React, {useCallback} from 'react';
 import {View} from 'react-native';
+import type {Emoji} from '@assets/emojis/types';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useThemeStyles from '@hooks/useThemeStyles';
-import type {SimpleEmoji} from '@libs/EmojiTrie';
 import * as EmojiUtils from '@libs/EmojiUtils';
 import getStyledTextArray from '@libs/GetStyledTextArray';
 import AutoCompleteSuggestions from './AutoCompleteSuggestions';
@@ -16,7 +16,7 @@ type EmojiSuggestionsProps = {
     highlightedEmojiIndex?: number;
 
     /** Array of suggested emoji */
-    emojis: SimpleEmoji[];
+    emojis: Emoji[];
 
     /** Fired when the user selects an emoji */
     onSelect: (index: number) => void;
@@ -40,7 +40,7 @@ type EmojiSuggestionsProps = {
 /**
  * Create unique keys for each emoji item
  */
-const keyExtractor = (item: SimpleEmoji, index: number): string => `${item.name}+${index}}`;
+const keyExtractor = (item: Emoji, index: number): string => `${item.name}+${index}}`;
 
 function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferredSkinToneIndex, highlightedEmojiIndex = 0, measureParentContainer = () => {}}: EmojiSuggestionsProps) {
     const styles = useThemeStyles();
@@ -49,7 +49,7 @@ function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferr
      * Render an emoji suggestion menu item component.
      */
     const renderSuggestionMenuItem = useCallback(
-        (item: SimpleEmoji): ReactElement => {
+        (item: Emoji): ReactElement => {
             const styledTextArray = getStyledTextArray(item.name, prefix);
 
             return (
diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
index 0b5cbad29983..46d04ca9404d 100755
--- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
+++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js
@@ -4,7 +4,7 @@ import {defaultHTMLElementModels, RenderHTMLConfigProvider, TRenderEngineProvide
 import _ from 'underscore';
 import useThemeStyles from '@hooks/useThemeStyles';
 import convertToLTR from '@libs/convertToLTR';
-import singleFontFamily from '@styles/utils/fontFamily/singleFontFamily';
+import FontUtils from '@styles/utils/FontUtils';
 import * as HTMLEngineUtils from './htmlEngineUtils';
 import htmlRenderers from './HTMLRenderers';
 
@@ -62,7 +62,7 @@ function BaseHTMLEngineProvider(props) {
             'mention-here': defaultHTMLElementModels.span.extend({tagName: 'mention-here'}),
             'next-step': defaultHTMLElementModels.span.extend({
                 tagName: 'next-step',
-                mixedUAStyles: {...styles.textLabelSupporting},
+                mixedUAStyles: {...styles.textLabelSupporting, ...styles.lh16},
             }),
             'next-step-email': defaultHTMLElementModels.span.extend({tagName: 'next-step-email'}),
             video: defaultHTMLElementModels.div.extend({
@@ -70,7 +70,7 @@ function BaseHTMLEngineProvider(props) {
                 mixedUAStyles: {whiteSpace: 'pre'},
             }),
         }),
-        [styles.colorMuted, styles.formError, styles.mb0, styles.textLabelSupporting],
+        [styles.colorMuted, styles.formError, styles.mb0, styles.textLabelSupporting, styles.lh16],
     );
 
     // We need to memoize this prop to make it referentially stable.
@@ -82,7 +82,7 @@ function BaseHTMLEngineProvider(props) {
             baseStyle={styles.webViewStyles.baseFontStyle}
             tagsStyles={styles.webViewStyles.tagStyles}
             enableCSSInlineProcessing={false}
-            systemFonts={_.values(singleFontFamily)}
+            systemFonts={_.values(FontUtils.fontFamily.single)}
             domVisitors={{
                 // eslint-disable-next-line no-param-reassign
                 onText: (text) => (text.data = convertToLTR(text.data)),
diff --git a/src/components/HeaderPageLayout.js b/src/components/HeaderPageLayout.tsx
similarity index 55%
rename from src/components/HeaderPageLayout.js
rename to src/components/HeaderPageLayout.tsx
index 9ef5d4f83a06..304bb2ce49b1 100644
--- a/src/components/HeaderPageLayout.js
+++ b/src/components/HeaderPageLayout.tsx
@@ -1,56 +1,54 @@
-import PropTypes from 'prop-types';
 import React, {useMemo} from 'react';
+import type {ReactNode} from 'react';
 import {ScrollView, View} from 'react-native';
-import _ from 'underscore';
+import type {StyleProp, ViewStyle} from 'react-native';
 import useNetwork from '@hooks/useNetwork';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import * as Browser from '@libs/Browser';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
 import FixedFooter from './FixedFooter';
 import HeaderWithBackButton from './HeaderWithBackButton';
-import headerWithBackButtonPropTypes from './HeaderWithBackButton/headerWithBackButtonPropTypes';
+import type HeaderWithBackButtonProps from './HeaderWithBackButton/types';
 import ScreenWrapper from './ScreenWrapper';
 
-const propTypes = {
-    ...headerWithBackButtonPropTypes,
+type HeaderPageLayoutProps = ChildrenProps &
+    HeaderWithBackButtonProps & {
+        /** The background color to apply in the upper half of the screen. */
+        backgroundColor?: string;
 
-    /** Children to display in the lower half of the page (below the header section w/ an animation) */
-    children: PropTypes.node.isRequired,
+        /** A fixed footer to display at the bottom of the page. */
+        footer?: ReactNode;
 
-    /** The background color to apply in the upper half of the screen. */
-    backgroundColor: PropTypes.string,
+        /** The image to display in the upper half of the screen. */
+        headerContent?: ReactNode;
 
-    /** A fixed footer to display at the bottom of the page. */
-    footer: PropTypes.node,
+        /** Style to apply to the header image container */
+        headerContainerStyles?: StyleProp<ViewStyle>;
 
-    /** The image to display in the upper half of the screen. */
-    header: PropTypes.node,
+        /** Style to apply to the ScrollView container */
+        scrollViewContainerStyles?: StyleProp<ViewStyle>;
 
-    /** Style to apply to the header image container */
-    // eslint-disable-next-line react/forbid-prop-types
-    headerContainerStyles: PropTypes.arrayOf(PropTypes.object),
+        /** Style to apply to the children container */
+        childrenContainerStyles?: StyleProp<ViewStyle>;
 
-    /** Style to apply to the ScrollView container */
-    // eslint-disable-next-line react/forbid-prop-types
-    scrollViewContainerStyles: PropTypes.arrayOf(PropTypes.object),
+        /** Style to apply to the whole section container */
+        style?: StyleProp<ViewStyle>;
+    };
 
-    /** Style to apply to the children container */
-    // eslint-disable-next-line react/forbid-prop-types
-    childrenContainerStyles: PropTypes.arrayOf(PropTypes.object),
-};
-
-const defaultProps = {
-    backgroundColor: undefined,
-    header: null,
-    headerContainerStyles: [],
-    scrollViewContainerStyles: [],
-    childrenContainerStyles: [],
-    footer: null,
-};
-
-function HeaderPageLayout({backgroundColor, children, footer, headerContainerStyles, scrollViewContainerStyles, childrenContainerStyles, style, headerContent, ...propsToPassToHeader}) {
+function HeaderPageLayout({
+    backgroundColor,
+    children,
+    footer,
+    headerContainerStyles,
+    scrollViewContainerStyles,
+    childrenContainerStyles,
+    style,
+    headerContent,
+    ...rest
+}: HeaderPageLayoutProps) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
@@ -58,7 +56,7 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty
     const {isOffline} = useNetwork();
     const appBGColor = StyleUtils.getBackgroundColorStyle(theme.appBG);
     const {titleColor, iconFill} = useMemo(() => {
-        const isColorfulBackground = (backgroundColor || theme.appBG) !== theme.appBG && (backgroundColor || theme.highlightBG) !== theme.highlightBG;
+        const isColorfulBackground = (backgroundColor ?? theme.appBG) !== theme.appBG && (backgroundColor ?? theme.highlightBG) !== theme.highlightBG;
         return {
             titleColor: isColorfulBackground ? theme.textColorfulBackground : undefined,
             iconFill: isColorfulBackground ? theme.iconColorfulBackground : undefined,
@@ -67,7 +65,7 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty
 
     return (
         <ScreenWrapper
-            style={[StyleUtils.getBackgroundColorStyle(backgroundColor || theme.appBG)]}
+            style={[StyleUtils.getBackgroundColorStyle(backgroundColor ?? theme.appBG)]}
             shouldEnablePickerAvoiding={false}
             includeSafeAreaPaddingBottom={false}
             offlineIndicatorStyle={[appBGColor]}
@@ -77,29 +75,26 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty
                 <>
                     <HeaderWithBackButton
                         // eslint-disable-next-line react/jsx-props-no-spreading
-                        {...propsToPassToHeader}
+                        {...rest}
                         titleColor={titleColor}
                         iconFill={iconFill}
                     />
-                    <View style={[styles.flex1, appBGColor, !isOffline && !_.isNull(footer) ? safeAreaPaddingBottomStyle : {}]}>
+                    <View style={[styles.flex1, appBGColor, !isOffline && footer ? safeAreaPaddingBottomStyle : {}]}>
                         {/** Safari on ios/mac has a bug where overscrolling the page scrollview shows green background color. This is a workaround to fix that. https://github.com/Expensify/App/issues/23422 */}
                         {Browser.isSafari() && (
                             <View style={styles.dualColorOverscrollSpacer}>
-                                <View style={[styles.flex1, StyleUtils.getBackgroundColorStyle(backgroundColor || theme.appBG)]} />
+                                <View style={[styles.flex1, StyleUtils.getBackgroundColorStyle(backgroundColor ?? theme.appBG)]} />
                                 <View style={[isSmallScreenWidth ? styles.flex1 : styles.flex3, appBGColor]} />
                             </View>
                         )}
-                        <ScrollView
-                            contentContainerStyle={[safeAreaPaddingBottomStyle, style, scrollViewContainerStyles]}
-                            offlineIndicatorStyle={[appBGColor]}
-                        >
-                            {!Browser.isSafari() && <View style={styles.overscrollSpacer(backgroundColor || theme.appBG, windowHeight)} />}
-                            <View style={[styles.alignItemsCenter, styles.justifyContentEnd, StyleUtils.getBackgroundColorStyle(backgroundColor || theme.appBG), ...headerContainerStyles]}>
+                        <ScrollView contentContainerStyle={[safeAreaPaddingBottomStyle, style, scrollViewContainerStyles]}>
+                            {!Browser.isSafari() && <View style={styles.overscrollSpacer(backgroundColor ?? theme.appBG, windowHeight)} />}
+                            <View style={[styles.alignItemsCenter, styles.justifyContentEnd, StyleUtils.getBackgroundColorStyle(backgroundColor ?? theme.appBG), headerContainerStyles]}>
                                 {headerContent}
                             </View>
                             <View style={[styles.pt5, appBGColor, childrenContainerStyles]}>{children}</View>
                         </ScrollView>
-                        {!_.isNull(footer) && <FixedFooter>{footer}</FixedFooter>}
+                        {!!footer && <FixedFooter>{footer}</FixedFooter>}
                     </View>
                 </>
             )}
@@ -107,8 +102,7 @@ function HeaderPageLayout({backgroundColor, children, footer, headerContainerSty
     );
 }
 
-HeaderPageLayout.propTypes = propTypes;
-HeaderPageLayout.defaultProps = defaultProps;
 HeaderPageLayout.displayName = 'HeaderPageLayout';
 
+export type {HeaderPageLayoutProps};
 export default HeaderPageLayout;
diff --git a/src/components/HoldMenuSectionList.tsx b/src/components/HoldMenuSectionList.tsx
new file mode 100644
index 000000000000..aa5dd75ce159
--- /dev/null
+++ b/src/components/HoldMenuSectionList.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import {View} from 'react-native';
+import type {ImageSourcePropType} from 'react-native';
+import type {SvgProps} from 'react-native-svg';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
+import type {TranslationPaths} from '@src/languages/types';
+import Icon from './Icon';
+import * as Illustrations from './Icon/Illustrations';
+import Text from './Text';
+
+type HoldMenuSection = {
+    /** The icon supplied with the section */
+    icon: React.FC<SvgProps> | ImageSourcePropType;
+
+    /** Translation key for the title */
+    titleTranslationKey: TranslationPaths;
+
+    /** Translation key for the description */
+    descriptionTranslationKey: TranslationPaths;
+};
+
+function HoldMenuSectionList() {
+    const {translate} = useLocalize();
+    const styles = useThemeStyles();
+
+    const holdMenuSections: HoldMenuSection[] = [
+        {
+            icon: Illustrations.Hourglass,
+            titleTranslationKey: 'iou.whatIsHoldTitle',
+            descriptionTranslationKey: 'iou.whatIsHoldExplain',
+        },
+        {
+            icon: Illustrations.CommentBubbles,
+            titleTranslationKey: 'iou.holdIsTemporaryTitle',
+            descriptionTranslationKey: 'iou.holdIsTemporaryExplain',
+        },
+        {
+            icon: Illustrations.TrashCan,
+            titleTranslationKey: 'iou.deleteHoldTitle',
+            descriptionTranslationKey: 'iou.deleteHoldExplain',
+        },
+    ];
+
+    return (
+        <>
+            {holdMenuSections.map((section, i) => (
+                <View
+                    // eslint-disable-next-line react/no-array-index-key
+                    key={i}
+                    style={[styles.flexRow, styles.alignItemsCenter, styles.mb5]}
+                >
+                    <Icon
+                        width={variables.holdMenuIconSize}
+                        height={variables.holdMenuIconSize}
+                        src={section.icon}
+                        additionalStyles={[styles.mr3]}
+                    />
+                    <View style={[styles.flex1, styles.justifyContentCenter]}>
+                        <Text style={[styles.textStrong, styles.mb1]}>{translate(section.titleTranslationKey)}</Text>
+                        <Text
+                            style={[styles.textNormal]}
+                            numberOfLines={3}
+                        >
+                            {translate(section.descriptionTranslationKey)}
+                        </Text>
+                    </View>
+                </View>
+            ))}
+        </>
+    );
+}
+
+HoldMenuSectionList.displayName = 'HoldMenuSectionList';
+
+export type {HoldMenuSection};
+
+export default HoldMenuSectionList;
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 1e574504001d..954c8d0392fc 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -32,6 +32,7 @@ import BigRocket from '@assets/images/simple-illustrations/simple-illustration__
 import PinkBill from '@assets/images/simple-illustrations/simple-illustration__bill.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';
 import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg';
 import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg';
 import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg';
@@ -39,6 +40,7 @@ import EmailAddress from '@assets/images/simple-illustrations/simple-illustratio
 import HandCard from '@assets/images/simple-illustrations/simple-illustration__handcard.svg';
 import HandEarth from '@assets/images/simple-illustrations/simple-illustration__handearth.svg';
 import HotDogStand from '@assets/images/simple-illustrations/simple-illustration__hotdogstand.svg';
+import Hourglass from '@assets/images/simple-illustrations/simple-illustration__hourglass.svg';
 import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg';
 import LockOpen from '@assets/images/simple-illustrations/simple-illustration__lockopen.svg';
 import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg';
@@ -53,6 +55,7 @@ import ShieldYellow from '@assets/images/simple-illustrations/simple-illustratio
 import SmallRocket from '@assets/images/simple-illustrations/simple-illustration__smallrocket.svg';
 import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg';
 import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg';
+import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg';
 import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg';
 
 export {
@@ -111,5 +114,8 @@ export {
     Hands,
     HandEarth,
     SmartScan,
+    Hourglass,
+    CommentBubbles,
+    TrashCan,
     TeleScope,
 };
diff --git a/src/components/IllustratedHeaderPageLayout.js b/src/components/IllustratedHeaderPageLayout.js
deleted file mode 100644
index 9980d8a7879a..000000000000
--- a/src/components/IllustratedHeaderPageLayout.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import useTheme from '@hooks/useTheme';
-import useThemeStyles from '@hooks/useThemeStyles';
-import HeaderPageLayout from './HeaderPageLayout';
-import headerWithBackButtonPropTypes from './HeaderWithBackButton/headerWithBackButtonPropTypes';
-import Lottie from './Lottie';
-
-const propTypes = {
-    ...headerWithBackButtonPropTypes,
-
-    /** Children to display in the lower half of the page (below the header section w/ an animation) */
-    children: PropTypes.node.isRequired,
-
-    /** The illustration to display in the header. Can be either an SVG component or a JSON object representing a Lottie animation. */
-    illustration: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired,
-
-    /** The background color to apply in the upper half of the screen. */
-    backgroundColor: PropTypes.string,
-
-    /** A fixed footer to display at the bottom of the page. */
-    footer: PropTypes.node,
-
-    /** Overlay content to display on top of animation */
-    overlayContent: PropTypes.func,
-};
-
-const defaultProps = {
-    backgroundColor: undefined,
-    footer: null,
-    overlayContent: null,
-};
-
-function IllustratedHeaderPageLayout({backgroundColor, children, illustration, footer, overlayContent, ...propsToPassToHeader}) {
-    const theme = useTheme();
-    const styles = useThemeStyles();
-    return (
-        <HeaderPageLayout
-            backgroundColor={backgroundColor || theme.appBG}
-            title={propsToPassToHeader.title}
-            headerContent={
-                <>
-                    <Lottie
-                        source={illustration}
-                        style={styles.w100}
-                        webStyle={styles.w100}
-                        autoPlay
-                        loop
-                    />
-                    {overlayContent && overlayContent()}
-                </>
-            }
-            headerContainerStyles={[styles.justifyContentCenter, styles.w100]}
-            footer={footer}
-            // eslint-disable-next-line react/jsx-props-no-spreading
-            {...propsToPassToHeader}
-        >
-            {children}
-        </HeaderPageLayout>
-    );
-}
-
-IllustratedHeaderPageLayout.propTypes = propTypes;
-IllustratedHeaderPageLayout.defaultProps = defaultProps;
-IllustratedHeaderPageLayout.displayName = 'IllustratedHeaderPageLayout';
-
-export default IllustratedHeaderPageLayout;
diff --git a/src/components/IllustratedHeaderPageLayout.tsx b/src/components/IllustratedHeaderPageLayout.tsx
new file mode 100644
index 000000000000..72ec0adf7672
--- /dev/null
+++ b/src/components/IllustratedHeaderPageLayout.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import type {ReactNode} from 'react';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import HeaderPageLayout from './HeaderPageLayout';
+import type {HeaderPageLayoutProps} from './HeaderPageLayout';
+import Lottie from './Lottie';
+import type DotLottieAnimation from './LottieAnimations/types';
+
+type IllustratedHeaderPageLayoutProps = HeaderPageLayoutProps & {
+    /** The illustration to display in the header. Can be a JSON object representing a Lottie animation. */
+    illustration: DotLottieAnimation;
+
+    /** The background color to apply in the upper half of the screen. */
+    backgroundColor?: string;
+
+    /** Overlay content to display on top of animation */
+    overlayContent?: () => ReactNode;
+};
+
+function IllustratedHeaderPageLayout({backgroundColor, children, illustration, overlayContent, ...rest}: IllustratedHeaderPageLayoutProps) {
+    const theme = useTheme();
+    const styles = useThemeStyles();
+    return (
+        <HeaderPageLayout
+            backgroundColor={backgroundColor ?? theme.appBG}
+            headerContent={
+                <>
+                    <Lottie
+                        source={illustration}
+                        style={styles.w100}
+                        webStyle={styles.w100}
+                        autoPlay
+                        loop
+                    />
+                    {overlayContent?.()}
+                </>
+            }
+            headerContainerStyles={[styles.justifyContentCenter, styles.w100]}
+            // eslint-disable-next-line react/jsx-props-no-spreading
+            {...rest}
+        >
+            {children}
+        </HeaderPageLayout>
+    );
+}
+
+IllustratedHeaderPageLayout.displayName = 'IllustratedHeaderPageLayout';
+
+export default IllustratedHeaderPageLayout;
diff --git a/src/components/InvertedFlatList/index.native.tsx b/src/components/InvertedFlatList/index.native.tsx
index 76c4b774d0cc..70cabf5a536a 100644
--- a/src/components/InvertedFlatList/index.native.tsx
+++ b/src/components/InvertedFlatList/index.native.tsx
@@ -1,18 +1,15 @@
 import type {ForwardedRef} from 'react';
 import React, {forwardRef} from 'react';
 import type {FlatList, FlatListProps} from 'react-native';
-import useThemeStyles from '@hooks/useThemeStyles';
 import BaseInvertedFlatList from './BaseInvertedFlatList';
 import CellRendererComponent from './CellRendererComponent';
 
 function BaseInvertedFlatListWithRef<T>(props: FlatListProps<T>, ref: ForwardedRef<FlatList>) {
-    const styles = useThemeStyles();
     return (
         <BaseInvertedFlatList
             // eslint-disable-next-line react/jsx-props-no-spreading
             {...props}
             ref={ref}
-            contentContainerStyle={styles.justifyContentEnd}
             CellRendererComponent={CellRendererComponent}
             /**
              * To achieve absolute positioning and handle overflows for list items, the property must be disabled
diff --git a/src/components/InvertedFlatList/index.tsx b/src/components/InvertedFlatList/index.tsx
index 0fc35957e687..a96058a3046f 100644
--- a/src/components/InvertedFlatList/index.tsx
+++ b/src/components/InvertedFlatList/index.tsx
@@ -7,7 +7,7 @@ import BaseInvertedFlatList from './BaseInvertedFlatList';
 
 // This is adapted from https://codesandbox.io/s/react-native-dsyse
 // It's a HACK alert since FlatList has inverted scrolling on web
-function InvertedFlatList<T>({onScroll: onScrollProp = () => {}, contentContainerStyle, ...props}: FlatListProps<T>, ref: ForwardedRef<FlatList>) {
+function InvertedFlatList<T>({onScroll: onScrollProp = () => {}, ...props}: FlatListProps<T>, ref: ForwardedRef<FlatList>) {
     const lastScrollEvent = useRef<number | null>(null);
     const scrollEndTimeout = useRef<NodeJS.Timeout | null>(null);
     const updateInProgress = useRef<boolean>(false);
@@ -86,7 +86,6 @@ function InvertedFlatList<T>({onScroll: onScrollProp = () => {}, contentContaine
             // eslint-disable-next-line react/jsx-props-no-spreading
             {...props}
             ref={ref}
-            contentContainerStyle={contentContainerStyle}
             onScroll={handleScroll}
         />
     );
diff --git a/src/components/Lightbox.js b/src/components/Lightbox.js
index 06f8ee4cfeb6..45326edb4610 100644
--- a/src/components/Lightbox.js
+++ b/src/components/Lightbox.js
@@ -183,8 +183,8 @@ function Lightbox({isAuthTokenRequired, source, onScaleChanged, onPress, onError
                                     onError={onError}
                                     onLoadEnd={() => setImageLoaded(true)}
                                     onLoad={(e) => {
-                                        const width = (e.nativeEvent?.width || 0) / PixelRatio.get();
-                                        const height = (e.nativeEvent?.height || 0) / PixelRatio.get();
+                                        const width = (e.nativeEvent?.width || 0) * PixelRatio.get();
+                                        const height = (e.nativeEvent?.height || 0) * PixelRatio.get();
                                         setImageDimensions({...imageDimensions, lightboxSize: {width, height}});
                                     }}
                                 />
@@ -205,8 +205,8 @@ function Lightbox({isAuthTokenRequired, source, onScaleChanged, onPress, onError
                                 isAuthTokenRequired={isAuthTokenRequired}
                                 onLoadEnd={() => setFallbackLoaded(true)}
                                 onLoad={(e) => {
-                                    const width = e.nativeEvent?.width || 0;
-                                    const height = e.nativeEvent?.height || 0;
+                                    const width = (e.nativeEvent?.width || 0) * PixelRatio.get();
+                                    const height = (e.nativeEvent?.height || 0) * PixelRatio.get();
 
                                     if (imageDimensions?.lightboxSize != null) {
                                         return;
diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx
index 14dfd0e8dbc4..86e77ae4bfc3 100644
--- a/src/components/MenuItem.tsx
+++ b/src/components/MenuItem.tsx
@@ -35,20 +35,6 @@ import RenderHTML from './RenderHTML';
 import SelectCircle from './SelectCircle';
 import Text from './Text';
 
-type ResponsiveProps = {
-    /** Function to fire when component is pressed */
-    onPress: (event: GestureResponderEvent | KeyboardEvent) => void;
-
-    interactive?: true;
-};
-
-type UnresponsiveProps = {
-    onPress?: undefined;
-
-    /** Whether the menu item should be interactive at all */
-    interactive: false;
-};
-
 type IconProps = {
     /** Flag to choose between avatar image or an icon */
     iconType?: typeof CONST.ICON_TYPE_ICON;
@@ -69,170 +55,175 @@ type NoIcon = {
     icon?: undefined;
 };
 
-type MenuItemProps = (ResponsiveProps | UnresponsiveProps) &
-    (IconProps | AvatarProps | NoIcon) & {
-        /** Text to be shown as badge near the right end. */
-        badgeText?: string;
+type MenuItemProps = (IconProps | AvatarProps | NoIcon) & {
+    /** Function to fire when component is pressed */
+    onPress?: (event: GestureResponderEvent | KeyboardEvent) => void;
+
+    /** Whether the menu item should be interactive at all */
+    interactive?: boolean;
 
-        /** Used to apply offline styles to child text components */
-        style?: ViewStyle;
+    /** Text to be shown as badge near the right end. */
+    badgeText?: string;
 
-        /** Any additional styles to apply */
-        wrapperStyle?: StyleProp<ViewStyle>;
+    /** Used to apply offline styles to child text components */
+    style?: ViewStyle;
 
-        /** Any additional styles to apply on the outer element */
-        containerStyle?: StyleProp<ViewStyle>;
+    /** Any additional styles to apply */
+    wrapperStyle?: StyleProp<ViewStyle>;
 
-        /** Used to apply styles specifically to the title */
-        titleStyle?: ViewStyle;
+    /** Any additional styles to apply on the outer element */
+    containerStyle?: StyleProp<ViewStyle>;
 
-        /** Any adjustments to style when menu item is hovered or pressed */
-        hoverAndPressStyle?: StyleProp<AnimatedStyle<ViewStyle>>;
+    /** Used to apply styles specifically to the title */
+    titleStyle?: ViewStyle;
 
-        /** Additional styles to style the description text below the title */
-        descriptionTextStyle?: StyleProp<TextStyle>;
+    /** Any adjustments to style when menu item is hovered or pressed */
+    hoverAndPressStyle?: StyleProp<AnimatedStyle<ViewStyle>>;
 
-        /** The fill color to pass into the icon. */
-        iconFill?: string;
+    /** Additional styles to style the description text below the title */
+    descriptionTextStyle?: StyleProp<TextStyle>;
 
-        /** Secondary icon to display on the left side of component, right of the icon */
-        secondaryIcon?: IconAsset;
+    /** The fill color to pass into the icon. */
+    iconFill?: string;
 
-        /** The fill color to pass into the secondary icon. */
-        secondaryIconFill?: string;
+    /** Secondary icon to display on the left side of component, right of the icon */
+    secondaryIcon?: IconAsset;
 
-        /** Icon Width */
-        iconWidth?: number;
+    /** The fill color to pass into the secondary icon. */
+    secondaryIconFill?: string;
 
-        /** Icon Height */
-        iconHeight?: number;
+    /** Icon Width */
+    iconWidth?: number;
 
-        /** Any additional styles to pass to the icon container. */
-        iconStyles?: StyleProp<ViewStyle>;
+    /** Icon Height */
+    iconHeight?: number;
 
-        /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
-        fallbackIcon?: IconAsset;
+    /** Any additional styles to pass to the icon container. */
+    iconStyles?: StyleProp<ViewStyle>;
 
-        /** An icon to display under the main item */
-        furtherDetailsIcon?: IconAsset;
+    /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
+    fallbackIcon?: IconAsset;
 
-        /** Boolean whether to display the title right icon */
-        shouldShowTitleIcon?: boolean;
+    /** An icon to display under the main item */
+    furtherDetailsIcon?: IconAsset;
 
-        /** Icon to display at right side of title */
-        titleIcon?: IconAsset;
+    /** Boolean whether to display the title right icon */
+    shouldShowTitleIcon?: boolean;
 
-        /** Boolean whether to display the right icon */
-        shouldShowRightIcon?: boolean;
+    /** Icon to display at right side of title */
+    titleIcon?: IconAsset;
 
-        /** Overrides the icon for shouldShowRightIcon */
-        iconRight?: IconAsset;
+    /** Boolean whether to display the right icon */
+    shouldShowRightIcon?: boolean;
 
-        /** Should render component on the right */
-        shouldShowRightComponent?: boolean;
+    /** Overrides the icon for shouldShowRightIcon */
+    iconRight?: IconAsset;
 
-        /** Component to be displayed on the right */
-        rightComponent?: ReactNode;
+    /** Should render component on the right */
+    shouldShowRightComponent?: boolean;
 
-        /** A description text to show under the title */
-        description?: string;
+    /** Component to be displayed on the right */
+    rightComponent?: ReactNode;
 
-        /** Should the description be shown above the title (instead of the other way around) */
-        shouldShowDescriptionOnTop?: boolean;
+    /** A description text to show under the title */
+    description?: string;
 
-        /** Error to display below the title */
-        error?: string;
+    /** Should the description be shown above the title (instead of the other way around) */
+    shouldShowDescriptionOnTop?: boolean;
 
-        /** Error to display at the bottom of the component */
-        errorText?: string;
+    /** Error to display below the title */
+    error?: string;
 
-        /** A boolean flag that gives the icon a green fill if true */
-        success?: boolean;
+    /** Error to display at the bottom of the component */
+    errorText?: string;
 
-        /** Whether item is focused or active */
-        focused?: boolean;
+    /** A boolean flag that gives the icon a green fill if true */
+    success?: boolean;
 
-        /** Should we disable this menu item? */
-        disabled?: boolean;
+    /** Whether item is focused or active */
+    focused?: boolean;
 
-        /** Text that appears above the title */
-        label?: string;
+    /** Should we disable this menu item? */
+    disabled?: boolean;
 
-        /** Label to be displayed on the right */
-        rightLabel?: string;
+    /** Text that appears above the title */
+    label?: string;
 
-        /** Text to display for the item */
-        title?: string;
+    /** Label to be displayed on the right */
+    rightLabel?: string;
 
-        /** A right-aligned subtitle for this menu option */
-        subtitle?: string | number;
+    /** Text to display for the item */
+    title?: string;
 
-        /** Should the title show with normal font weight (not bold) */
-        shouldShowBasicTitle?: boolean;
+    /** A right-aligned subtitle for this menu option */
+    subtitle?: string | number;
 
-        /** Should we make this selectable with a checkbox */
-        shouldShowSelectedState?: boolean;
+    /** Should the title show with normal font weight (not bold) */
+    shouldShowBasicTitle?: boolean;
 
-        /** Whether this item is selected */
-        isSelected?: boolean;
+    /** Should we make this selectable with a checkbox */
+    shouldShowSelectedState?: boolean;
 
-        /** Prop to identify if we should load avatars vertically instead of diagonally */
-        shouldStackHorizontally?: boolean;
+    /** Whether this item is selected */
+    isSelected?: boolean;
 
-        /** Prop to represent the size of the avatar images to be shown */
-        avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE];
+    /** Prop to identify if we should load avatars vertically instead of diagonally */
+    shouldStackHorizontally?: boolean;
 
-        /** Avatars to show on the right of the menu item */
-        floatRightAvatars?: IconType[];
+    /** Prop to represent the size of the avatar images to be shown */
+    avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE];
 
-        /** Prop to represent the size of the float right avatar images to be shown */
-        floatRightAvatarSize?: ValueOf<typeof CONST.AVATAR_SIZE>;
+    /** Avatars to show on the right of the menu item */
+    floatRightAvatars?: IconType[];
 
-        /** Affects avatar size  */
-        viewMode?: ValueOf<typeof CONST.OPTION_MODE>;
+    /** Prop to represent the size of the float right avatar images to be shown */
+    floatRightAvatarSize?: ValueOf<typeof CONST.AVATAR_SIZE>;
 
-        /** Used to truncate the text with an ellipsis after computing the text layout */
-        numberOfLinesTitle?: number;
+    /** Affects avatar size  */
+    viewMode?: ValueOf<typeof CONST.OPTION_MODE>;
 
-        /**  Whether we should use small avatar subscript sizing the for menu item */
-        isSmallAvatarSubscriptMenu?: boolean;
+    /** Used to truncate the text with an ellipsis after computing the text layout */
+    numberOfLinesTitle?: number;
 
-        /** The type of brick road indicator to show. */
-        brickRoadIndicator?: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS>;
+    /**  Whether we should use small avatar subscript sizing the for menu item */
+    isSmallAvatarSubscriptMenu?: boolean;
 
-        /** Should render the content in HTML format */
-        shouldRenderAsHTML?: boolean;
+    /** The type of brick road indicator to show. */
+    brickRoadIndicator?: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS>;
 
-        /** Should we grey out the menu item when it is disabled? */
-        shouldGreyOutWhenDisabled?: boolean;
+    /** Should render the content in HTML format */
+    shouldRenderAsHTML?: boolean;
 
-        /** The action accept for anonymous user or not */
-        isAnonymousAction?: boolean;
+    /** Should we grey out the menu item when it is disabled? */
+    shouldGreyOutWhenDisabled?: boolean;
 
-        /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */
-        shouldBlockSelection?: boolean;
+    /** The action accept for anonymous user or not */
+    isAnonymousAction?: boolean;
 
-        /** Whether should render title as HTML or as Text */
-        shouldParseTitle?: false;
+    /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */
+    shouldBlockSelection?: boolean;
 
-        /** Should check anonymous user in onPress function */
-        shouldCheckActionAllowedOnPress?: boolean;
+    /** Whether should render title as HTML or as Text */
+    shouldParseTitle?: false;
 
-        /** Text to display under the main item */
-        furtherDetails?: string;
+    /** Should check anonymous user in onPress function */
+    shouldCheckActionAllowedOnPress?: boolean;
 
-        /** The function that should be called when this component is LongPressed or right-clicked. */
-        onSecondaryInteraction?: () => void;
+    /** Text to display under the main item */
+    furtherDetails?: string;
 
-        /** Array of objects that map display names to their corresponding tooltip */
-        titleWithTooltips?: DisplayNameWithTooltip[];
+    /** The function that should be called when this component is LongPressed or right-clicked. */
+    onSecondaryInteraction?: (event: GestureResponderEvent | MouseEvent) => void;
 
-        /** Icon should be displayed in its own color */
-        displayInDefaultIconColor?: boolean;
+    /** Array of objects that map display names to their corresponding tooltip */
+    titleWithTooltips?: DisplayNameWithTooltip[];
 
-        /** Determines how the icon should be resized to fit its container */
-        contentFit?: ImageContentFit;
-    };
+    /** Icon should be displayed in its own color */
+    displayInDefaultIconColor?: boolean;
+
+    /** Determines how the icon should be resized to fit its container */
+    contentFit?: ImageContentFit;
+};
 
 function MenuItem(
     {
@@ -525,17 +516,19 @@ function MenuItem(
                                                 <Text style={[styles.textLabelError]}>{error}</Text>
                                             </View>
                                         )}
-                                        {furtherDetailsIcon && !!furtherDetails && (
+                                        {!!furtherDetails && (
                                             <View style={[styles.flexRow, styles.mt1, styles.alignItemsCenter]}>
-                                                <Icon
-                                                    src={furtherDetailsIcon}
-                                                    height={variables.iconSizeNormal}
-                                                    width={variables.iconSizeNormal}
-                                                    fill={theme.icon}
-                                                    inline
-                                                />
+                                                {!!furtherDetailsIcon && (
+                                                    <Icon
+                                                        src={furtherDetailsIcon}
+                                                        height={variables.iconSizeNormal}
+                                                        width={variables.iconSizeNormal}
+                                                        fill={theme.icon}
+                                                        inline
+                                                    />
+                                                )}
                                                 <Text
-                                                    style={[styles.furtherDetailsText, styles.ph2, styles.pt1]}
+                                                    style={furtherDetailsIcon ? [styles.furtherDetailsText, styles.ph2, styles.pt1] : styles.textLabelSupporting}
                                                     numberOfLines={2}
                                                 >
                                                     {furtherDetails}
diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js
deleted file mode 100644
index c9eee8e888e1..000000000000
--- a/src/components/MenuItemList.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import _ from 'underscore';
-import useSingleExecution from '@hooks/useSingleExecution';
-import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
-import CONST from '@src/CONST';
-import MenuItem from './MenuItem';
-import menuItemPropTypes from './menuItemPropTypes';
-
-const propTypes = {
-    /** An array of props that are pass to individual MenuItem components */
-    menuItems: PropTypes.arrayOf(PropTypes.shape(menuItemPropTypes)),
-
-    /** Whether or not to use the single execution hook */
-    shouldUseSingleExecution: PropTypes.bool,
-};
-const defaultProps = {
-    menuItems: [],
-    shouldUseSingleExecution: false,
-};
-
-function MenuItemList(props) {
-    let popoverAnchor;
-    const {isExecuting, singleExecution} = useSingleExecution();
-
-    /**
-     * Handle the secondary interaction for a menu item.
-     *
-     * @param {*} link the menu item link or function to get the link
-     * @param {Event} e the interaction event
-     */
-    const secondaryInteraction = (link, e) => {
-        if (typeof link === 'function') {
-            link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, url, popoverAnchor));
-        } else if (!_.isEmpty(link)) {
-            ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, link, popoverAnchor);
-        }
-    };
-
-    return (
-        <>
-            {_.map(props.menuItems, (menuItemProps) => (
-                <MenuItem
-                    key={menuItemProps.title}
-                    onSecondaryInteraction={!_.isUndefined(menuItemProps.link) ? (e) => secondaryInteraction(menuItemProps.link, e) : undefined}
-                    ref={(el) => (popoverAnchor = el)}
-                    shouldBlockSelection={Boolean(menuItemProps.link)}
-                    // eslint-disable-next-line react/jsx-props-no-spreading
-                    {...menuItemProps}
-                    disabled={menuItemProps.disabled || isExecuting}
-                    onPress={props.shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress}
-                />
-            ))}
-        </>
-    );
-}
-
-MenuItemList.displayName = 'MenuItemList';
-MenuItemList.propTypes = propTypes;
-MenuItemList.defaultProps = defaultProps;
-
-export default MenuItemList;
diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx
new file mode 100644
index 000000000000..f83f173a644f
--- /dev/null
+++ b/src/components/MenuItemList.tsx
@@ -0,0 +1,63 @@
+import React, {useRef} from 'react';
+import type {GestureResponderEvent, View} from 'react-native';
+import useSingleExecution from '@hooks/useSingleExecution';
+import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
+import CONST from '@src/CONST';
+import type {MenuItemProps} from './MenuItem';
+import MenuItem from './MenuItem';
+
+type MenuItemLink = string | (() => Promise<string>);
+
+type MenuItemWithLink = MenuItemProps & {
+    /** The link to open when the menu item is clicked */
+    link: MenuItemLink;
+};
+
+type MenuItemListProps = {
+    /** An array of props that are pass to individual MenuItem components */
+    menuItems: MenuItemWithLink[];
+
+    /** Whether or not to use the single execution hook */
+    shouldUseSingleExecution?: boolean;
+};
+
+function MenuItemList({menuItems = [], shouldUseSingleExecution = false}: MenuItemListProps) {
+    const popoverAnchor = useRef<View>(null);
+    const {isExecuting, singleExecution} = useSingleExecution();
+
+    /**
+     * Handle the secondary interaction for a menu item.
+     *
+     * @param link the menu item link or function to get the link
+     * @param event the interaction event
+     */
+    const secondaryInteraction = (link: MenuItemLink, event: GestureResponderEvent | MouseEvent) => {
+        if (typeof link === 'function') {
+            link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, url, popoverAnchor.current));
+        } else if (link) {
+            ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, link, popoverAnchor.current);
+        }
+    };
+
+    return (
+        <>
+            {menuItems.map((menuItemProps) => (
+                <MenuItem
+                    key={menuItemProps.title}
+                    onSecondaryInteraction={menuItemProps.link !== undefined ? (e) => secondaryInteraction(menuItemProps.link, e) : undefined}
+                    ref={popoverAnchor}
+                    shouldBlockSelection={!!menuItemProps.link}
+                    // eslint-disable-next-line react/jsx-props-no-spreading
+                    {...menuItemProps}
+                    disabled={!!menuItemProps.disabled || isExecuting}
+                    onPress={shouldUseSingleExecution ? singleExecution(menuItemProps.onPress) : menuItemProps.onPress}
+                />
+            ))}
+        </>
+    );
+}
+
+MenuItemList.displayName = 'MenuItemList';
+
+export type {MenuItemWithLink};
+export default MenuItemList;
diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js
index 8ed6d0746438..5b59fca6cdae 100644
--- a/src/components/MoneyReportHeader.js
+++ b/src/components/MoneyReportHeader.js
@@ -87,9 +87,9 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt
     const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
     const policyType = lodashGet(policy, 'type');
     const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && lodashGet(policy, 'role') === CONST.POLICY.ROLE.ADMIN;
-    const isGroupPolicy = _.contains([CONST.POLICY.TYPE.CORPORATE, CONST.POLICY.TYPE.TEAM], policyType);
+    const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicy(moneyRequestReport);
     const isManager = ReportUtils.isMoneyRequestReport(moneyRequestReport) && lodashGet(session, 'accountID', null) === moneyRequestReport.managerID;
-    const isPayer = isGroupPolicy
+    const isPayer = isPaidGroupPolicy
         ? // In a group policy, the admin approver can pay the report directly by skipping the approval step
           isPolicyAdmin && (isApproved || isManager)
         : isPolicyAdmin || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && isManager);
@@ -99,11 +99,11 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt
         [isPayer, isDraft, isSettled, moneyRequestReport, reimbursableTotal, chatReport],
     );
     const shouldShowApproveButton = useMemo(() => {
-        if (!isGroupPolicy) {
+        if (!isPaidGroupPolicy) {
             return false;
         }
         return isManager && !isDraft && !isApproved && !isSettled;
-    }, [isGroupPolicy, isManager, isDraft, isApproved, isSettled]);
+    }, [isPaidGroupPolicy, isManager, isDraft, isApproved, isSettled]);
     const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton;
     const shouldShowSubmitButton = isDraft && reimbursableTotal !== 0;
     const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index 7ec95aec951f..2fee67a3d632 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -277,7 +277,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
     const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(_.values(policyTagList));
 
     // A flag for showing tax rate
-    const shouldShowTax = isPolicyExpenseChat && policy.isTaxTrackingEnabled;
+    const shouldShowTax = isPolicyExpenseChat && policy && policy.isTaxTrackingEnabled;
 
     // A flag for showing the billable field
     const shouldShowBillable = !lodashGet(policy, 'disabledFields.defaultBillable', true);
diff --git a/src/components/Onfido/BaseOnfidoWeb.js b/src/components/Onfido/BaseOnfidoWeb.js
index 09ec96cf5b1e..ee206b15fc24 100644
--- a/src/components/Onfido/BaseOnfidoWeb.js
+++ b/src/components/Onfido/BaseOnfidoWeb.js
@@ -5,8 +5,7 @@ import _ from 'underscore';
 import useLocalize from '@hooks/useLocalize';
 import useTheme from '@hooks/useTheme';
 import Log from '@libs/Log';
-import fontFamily from '@styles/utils/fontFamily';
-import fontWeightBold from '@styles/utils/fontWeight/bold';
+import FontUtils from '@styles/utils/FontUtils';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import './index.css';
@@ -18,11 +17,11 @@ function initializeOnfido({sdkToken, onSuccess, onError, onUserExit, preferredLo
         containerId: CONST.ONFIDO.CONTAINER_ID,
         useMemoryHistory: true,
         customUI: {
-            fontFamilyTitle: `${fontFamily.EXP_NEUE}, -apple-system, serif`,
-            fontFamilySubtitle: `${fontFamily.EXP_NEUE}, -apple-system, serif`,
-            fontFamilyBody: `${fontFamily.EXP_NEUE}, -apple-system, serif`,
+            fontFamilyTitle: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`,
+            fontFamilySubtitle: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`,
+            fontFamilyBody: `${FontUtils.fontFamily.platform.EXP_NEUE}, -apple-system, serif`,
             fontSizeTitle: `${variables.fontSizeLarge}px`,
-            fontWeightTitle: fontWeightBold,
+            fontWeightTitle: FontUtils.fontWeight.bold,
             fontWeightSubtitle: 400,
             fontSizeSubtitle: `${variables.fontSizeNormal}px`,
             colorContentTitle: theme.text,
diff --git a/src/components/PopoverWithMeasuredContent.tsx b/src/components/PopoverWithMeasuredContent.tsx
index b636f90cd7ca..792002441ac6 100644
--- a/src/components/PopoverWithMeasuredContent.tsx
+++ b/src/components/PopoverWithMeasuredContent.tsx
@@ -36,6 +36,12 @@ function PopoverWithMeasuredContent({
     },
     children,
     withoutOverlay = false,
+    fullscreen = true,
+    shouldCloseOnOutsideClick = false,
+    shouldSetModalVisibility = true,
+    statusBarTranslucent = true,
+    avoidKeyboard = false,
+    hideModalContentWhileAnimating = false,
     ...props
 }: PopoverWithMeasuredContentProps) {
     const styles = useThemeStyles();
@@ -115,6 +121,12 @@ function PopoverWithMeasuredContent({
             anchorAlignment={anchorAlignment}
             isVisible={isVisible}
             withoutOverlay={withoutOverlay}
+            fullscreen={fullscreen}
+            shouldCloseOnOutsideClick={shouldCloseOnOutsideClick}
+            shouldSetModalVisibility={shouldSetModalVisibility}
+            statusBarTranslucent={statusBarTranslucent}
+            avoidKeyboard={avoidKeyboard}
+            hideModalContentWhileAnimating={hideModalContentWhileAnimating}
             // eslint-disable-next-line react/jsx-props-no-spreading
             {...props}
             anchorPosition={shiftedAnchorPosition}
diff --git a/src/components/ProcessMoneyRequestHoldMenu.tsx b/src/components/ProcessMoneyRequestHoldMenu.tsx
new file mode 100644
index 000000000000..1b711633ed3b
--- /dev/null
+++ b/src/components/ProcessMoneyRequestHoldMenu.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import {View} from 'react-native';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Button from './Button';
+import HoldMenuSectionList from './HoldMenuSectionList';
+import type {PopoverAnchorPosition} from './Modal/types';
+import Popover from './Popover';
+import type {AnchorAlignment} from './Popover/types';
+import Text from './Text';
+import TextPill from './TextPill';
+
+type ProcessMoneyRequestHoldMenuProps = {
+    /** Whether the content is visible */
+    isVisible: boolean;
+
+    /** Method to trigger when pressing outside of the popover menu to close it */
+    onClose: () => void;
+
+    /** Method to trigger when pressing confirm button */
+    onConfirm: () => void;
+
+    /** The anchor position of the popover menu */
+    anchorPosition?: PopoverAnchorPosition;
+
+    /** The anchor alignment of the popover menu */
+    anchorAlignment: AnchorAlignment;
+
+    /** The anchor ref of the popover menu */
+    anchorRef: React.RefObject<HTMLElement>;
+};
+
+function ProcessMoneyRequestHoldMenu({isVisible, onClose, onConfirm, anchorPosition, anchorAlignment, anchorRef}: ProcessMoneyRequestHoldMenuProps) {
+    const {translate} = useLocalize();
+    const styles = useThemeStyles();
+
+    return (
+        <Popover
+            isVisible={isVisible}
+            onClose={onClose}
+            anchorPosition={anchorPosition}
+            anchorRef={anchorRef}
+            anchorAlignment={anchorAlignment}
+            disableAnimation={false}
+            withoutOverlay={false}
+        >
+            <View style={[styles.mh5, styles.mv5]}>
+                <View style={[styles.flexRow, styles.alignItemsCenter, styles.mb5]}>
+                    <Text style={[styles.textHeadline, styles.mr2]}>{translate('iou.holdEducationalTitle')}</Text>
+                    <TextPill textStyles={styles.holdRequestInline}>{translate('iou.hold')}</TextPill>;
+                </View>
+                <HoldMenuSectionList />
+                <Button
+                    success
+                    style={[styles.mt5]}
+                    text={translate('common.buttonConfirm')}
+                    onPress={onConfirm}
+                />
+            </View>
+        </Popover>
+    );
+}
+
+ProcessMoneyRequestHoldMenu.displayName = 'ProcessMoneyRequestHoldMenu';
+
+export default ProcessMoneyRequestHoldMenu;
diff --git a/src/components/Reactions/AddReactionBubble.js b/src/components/Reactions/AddReactionBubble.tsx
similarity index 62%
rename from src/components/Reactions/AddReactionBubble.js
rename to src/components/Reactions/AddReactionBubble.tsx
index a9bfdd367615..52751368a0ae 100644
--- a/src/components/Reactions/AddReactionBubble.js
+++ b/src/components/Reactions/AddReactionBubble.tsx
@@ -1,81 +1,75 @@
-import PropTypes from 'prop-types';
 import React, {useEffect, useRef} from 'react';
 import {View} from 'react-native';
+import type {Emoji} from '@assets/emojis/types';
 import Icon from '@components/Icon';
 import * as Expensicons from '@components/Icon/Expensicons';
 import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
 import Text from '@components/Text';
 import Tooltip from '@components/Tooltip/PopoverAnchorTooltip';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useThemeStyles from '@hooks/useThemeStyles';
 import getButtonState from '@libs/getButtonState';
 import variables from '@styles/variables';
 import * as EmojiPickerAction from '@userActions/EmojiPickerAction';
+import type {AnchorOrigin} from '@userActions/EmojiPickerAction';
 import * as Session from '@userActions/Session';
 import CONST from '@src/CONST';
+import type {ReportAction} from '@src/types/onyx';
+import type {CloseContextMenuCallback, OpenPickerCallback, PickerRefElement} from './QuickEmojiReactions/types';
 
-const propTypes = {
+type AddReactionBubbleProps = {
     /** Whether it is for context menu so we can modify its style */
-    isContextMenu: PropTypes.bool,
+    isContextMenu?: boolean;
 
     /**
      * Called when the user presses on the icon button.
      * Will have a function as parameter which you can call
      * to open the picker.
      */
-    onPressOpenPicker: PropTypes.func,
+    onPressOpenPicker?: (openPicker: OpenPickerCallback) => void;
 
     /**
      * Will get called the moment before the picker opens.
      */
-    onWillShowPicker: PropTypes.func,
+    onWillShowPicker?: (callback: CloseContextMenuCallback) => void;
 
     /**
      * Called when the user selects an emoji.
      */
-    onSelectEmoji: PropTypes.func.isRequired,
+    onSelectEmoji: (emoji: Emoji) => void;
 
     /**
      * ReportAction for EmojiPicker.
      */
-    reportAction: PropTypes.shape({
-        reportActionID: PropTypes.string.isRequired,
-    }),
-
-    ...withLocalizePropTypes,
-};
-
-const defaultProps = {
-    isContextMenu: false,
-    onWillShowPicker: () => {},
-    onPressOpenPicker: undefined,
-    reportAction: {},
+    reportAction: ReportAction;
 };
 
-function AddReactionBubble(props) {
+function AddReactionBubble({onSelectEmoji, reportAction, onPressOpenPicker, onWillShowPicker = () => {}, isContextMenu = false}: AddReactionBubbleProps) {
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
-    const ref = useRef();
+    const ref = useRef<View | HTMLDivElement>(null);
+    const {translate} = useLocalize();
+
     useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []);
 
     const onPress = () => {
-        const openPicker = (refParam, anchorOrigin) => {
+        const openPicker = (refParam?: PickerRefElement, anchorOrigin?: AnchorOrigin) => {
             EmojiPickerAction.showEmojiPicker(
                 () => {},
                 (emojiCode, emojiObject) => {
-                    props.onSelectEmoji(emojiObject);
+                    onSelectEmoji(emojiObject);
                 },
-                refParam || ref,
+                refParam ?? ref,
                 anchorOrigin,
-                props.onWillShowPicker,
-                props.reportAction.reportActionID,
+                onWillShowPicker,
+                reportAction.reportActionID,
             );
         };
 
-        if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) {
-            if (props.onPressOpenPicker) {
-                props.onPressOpenPicker(openPicker);
+        if (!EmojiPickerAction.emojiPickerRef.current?.isEmojiPickerVisible) {
+            if (onPressOpenPicker) {
+                onPressOpenPicker(openPicker);
             } else {
                 openPicker();
             }
@@ -85,21 +79,21 @@ function AddReactionBubble(props) {
     };
 
     return (
-        <Tooltip text={props.translate('emojiReactions.addReactionTooltip')}>
+        <Tooltip text={translate('emojiReactions.addReactionTooltip')}>
             <PressableWithFeedback
                 ref={ref}
-                style={({hovered, pressed}) => [styles.emojiReactionBubble, styles.userSelectNone, StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, false, props.isContextMenu)]}
+                style={({hovered, pressed}) => [styles.emojiReactionBubble, styles.userSelectNone, StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, false, isContextMenu)]}
                 onPress={Session.checkIfActionIsAllowed(onPress)}
-                onMouseDown={(e) => {
+                onMouseDown={(event) => {
                     // Allow text input blur when Add reaction is right clicked
-                    if (!e || e.button === 2) {
+                    if (!event || event.button === 2) {
                         return;
                     }
 
                     // Prevent text input blur when Add reaction is left clicked
-                    e.preventDefault();
+                    event.preventDefault();
                 }}
-                accessibilityLabel={props.translate('emojiReactions.addReactionTooltip')}
+                accessibilityLabel={translate('emojiReactions.addReactionTooltip')}
                 role={CONST.ROLE.BUTTON}
                 // disable dimming
                 pressDimmingValue={1}
@@ -110,12 +104,12 @@ function AddReactionBubble(props) {
                         {/* This (invisible) text will make the view have the same size as a regular
                emoji reaction. We make the text invisible and put the
                icon on top of it. */}
-                        <Text style={[styles.opacity0, StyleUtils.getEmojiReactionBubbleTextStyle(props.isContextMenu)]}>{'\u2800\u2800'}</Text>
+                        <Text style={[styles.opacity0, StyleUtils.getEmojiReactionBubbleTextStyle(isContextMenu)]}>{'\u2800\u2800'}</Text>
                         <View style={styles.pAbsolute}>
                             <Icon
                                 src={Expensicons.AddReaction}
-                                width={props.isContextMenu ? variables.iconSizeNormal : variables.iconSizeSmall}
-                                height={props.isContextMenu ? variables.iconSizeNormal : variables.iconSizeSmall}
+                                width={isContextMenu ? variables.iconSizeNormal : variables.iconSizeSmall}
+                                height={isContextMenu ? variables.iconSizeNormal : variables.iconSizeSmall}
                                 fill={StyleUtils.getIconFillColor(getButtonState(hovered, pressed))}
                             />
                         </View>
@@ -126,8 +120,6 @@ function AddReactionBubble(props) {
     );
 }
 
-AddReactionBubble.propTypes = propTypes;
-AddReactionBubble.defaultProps = defaultProps;
 AddReactionBubble.displayName = 'AddReactionBubble';
 
-export default withLocalize(AddReactionBubble);
+export default AddReactionBubble;
diff --git a/src/components/Reactions/EmojiReactionBubble.js b/src/components/Reactions/EmojiReactionBubble.js
deleted file mode 100644
index 3fd22a758f67..000000000000
--- a/src/components/Reactions/EmojiReactionBubble.js
+++ /dev/null
@@ -1,110 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
-import Text from '@components/Text';
-import {withCurrentUserPersonalDetailsDefaultProps} from '@components/withCurrentUserPersonalDetails';
-import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions';
-import useStyleUtils from '@hooks/useStyleUtils';
-import useThemeStyles from '@hooks/useThemeStyles';
-import CONST from '@src/CONST';
-
-const propTypes = {
-    /**
-     * The emoji codes to display in the bubble.
-     */
-    emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired,
-
-    /**
-     * Called when the user presses on the reaction bubble.
-     */
-    onPress: PropTypes.func.isRequired,
-
-    /**
-     * Called when the user long presses or right clicks
-     * on the reaction bubble.
-     */
-    onReactionListOpen: PropTypes.func,
-
-    /**
-     * The number of reactions to display in the bubble.
-     */
-    count: PropTypes.number,
-
-    /** Whether it is for context menu so we can modify its style */
-    isContextMenu: PropTypes.bool,
-
-    /**
-     * Returns true if the current account has reacted to the report action (with the given skin tone).
-     */
-    hasUserReacted: PropTypes.bool,
-
-    /** We disable reacting with emojis on report actions that have errors */
-    shouldBlockReactions: PropTypes.bool,
-
-    ...windowDimensionsPropTypes,
-};
-
-const defaultProps = {
-    count: 0,
-    onReactionListOpen: () => {},
-    isContextMenu: false,
-    shouldBlockReactions: false,
-
-    ...withCurrentUserPersonalDetailsDefaultProps,
-};
-
-function EmojiReactionBubble(props) {
-    const styles = useThemeStyles();
-    const StyleUtils = useStyleUtils();
-    return (
-        <PressableWithSecondaryInteraction
-            style={({hovered, pressed}) => [
-                styles.emojiReactionBubble,
-                StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, props.hasUserReacted, props.isContextMenu),
-                props.shouldBlockReactions && styles.cursorDisabled,
-                styles.userSelectNone,
-            ]}
-            onPress={() => {
-                if (props.shouldBlockReactions) {
-                    return;
-                }
-
-                props.onPress();
-            }}
-            onSecondaryInteraction={props.onReactionListOpen}
-            ref={props.forwardedRef}
-            enableLongPressWithHover={props.isSmallScreenWidth}
-            onMouseDown={(e) => {
-                // Allow text input blur when emoji reaction is right clicked
-                if (e && e.button === 2) {
-                    return;
-                }
-
-                // Prevent text input blur when emoji reaction is left clicked
-                e.preventDefault();
-            }}
-            role={CONST.ROLE.BUTTON}
-            accessibilityLabel={props.emojiCodes.join('')}
-            dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
-        >
-            <Text style={[styles.emojiReactionBubbleText, StyleUtils.getEmojiReactionBubbleTextStyle(props.isContextMenu)]}>{props.emojiCodes.join('')}</Text>
-            {props.count > 0 && <Text style={[styles.reactionCounterText, StyleUtils.getEmojiReactionCounterTextStyle(props.hasUserReacted)]}>{props.count}</Text>}
-        </PressableWithSecondaryInteraction>
-    );
-}
-
-EmojiReactionBubble.propTypes = propTypes;
-EmojiReactionBubble.defaultProps = defaultProps;
-EmojiReactionBubble.displayName = 'EmojiReactionBubble';
-
-const EmojiReactionBubbleWithRef = React.forwardRef((props, ref) => (
-    <EmojiReactionBubble
-        // eslint-disable-next-line react/jsx-props-no-spreading
-        {...props}
-        forwardedRef={ref}
-    />
-));
-
-EmojiReactionBubbleWithRef.displayName = 'EmojiReactionBubbleWithRef';
-
-export default withWindowDimensions(EmojiReactionBubbleWithRef);
diff --git a/src/components/Reactions/EmojiReactionBubble.tsx b/src/components/Reactions/EmojiReactionBubble.tsx
new file mode 100644
index 000000000000..d83689de2dc1
--- /dev/null
+++ b/src/components/Reactions/EmojiReactionBubble.tsx
@@ -0,0 +1,93 @@
+import React from 'react';
+import type {PressableRef} from '@components/Pressable/GenericPressable/types';
+import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
+import Text from '@components/Text';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useThemeStyles from '@hooks/useThemeStyles';
+import useWindowDimensions from '@hooks/useWindowDimensions';
+import type {ReactionListEvent} from '@pages/home/ReportScreenContext';
+import CONST from '@src/CONST';
+
+type EmojiReactionBubbleProps = {
+    /**
+     * The emoji codes to display in the bubble.
+     */
+    emojiCodes: string[];
+
+    /**
+     * Called when the user presses on the reaction bubble.
+     */
+    onPress: () => void;
+
+    /**
+     * Called when the user long presses or right clicks
+     * on the reaction bubble.
+     */
+    onReactionListOpen?: (event: ReactionListEvent) => void;
+
+    /**
+     * The number of reactions to display in the bubble.
+     */
+    count?: number;
+
+    /** Whether it is for context menu so we can modify its style */
+    isContextMenu?: boolean;
+
+    /**
+     * Returns true if the current account has reacted to the report action (with the given skin tone).
+     */
+    hasUserReacted?: boolean;
+
+    /** We disable reacting with emojis on report actions that have errors */
+    shouldBlockReactions?: boolean;
+};
+
+function EmojiReactionBubble(
+    {onPress, onReactionListOpen = () => {}, emojiCodes, hasUserReacted = false, count = 0, isContextMenu = false, shouldBlockReactions = false}: EmojiReactionBubbleProps,
+    ref: PressableRef,
+) {
+    const styles = useThemeStyles();
+    const StyleUtils = useStyleUtils();
+    const {isSmallScreenWidth} = useWindowDimensions();
+
+    return (
+        <PressableWithSecondaryInteraction
+            style={({hovered, pressed}) => [
+                styles.emojiReactionBubble,
+                StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, hasUserReacted, isContextMenu),
+                shouldBlockReactions && styles.cursorDisabled,
+                styles.userSelectNone,
+            ]}
+            onPress={() => {
+                if (shouldBlockReactions) {
+                    return;
+                }
+
+                onPress();
+            }}
+            onSecondaryInteraction={onReactionListOpen}
+            ref={ref}
+            enableLongPressWithHover={isSmallScreenWidth}
+            onMouseDown={(event) => {
+                // Allow text input blur when emoji reaction is right clicked
+                if (event?.button === 2) {
+                    return;
+                }
+
+                // Prevent text input blur when emoji reaction is left clicked
+                event.preventDefault();
+            }}
+            role={CONST.ROLE.BUTTON}
+            accessibilityLabel={emojiCodes.join('')}
+            accessible
+            dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
+        >
+            <Text style={[styles.emojiReactionBubbleText, StyleUtils.getEmojiReactionBubbleTextStyle(isContextMenu)]}>{emojiCodes.join('')}</Text>
+            {count > 0 && <Text style={[styles.reactionCounterText, StyleUtils.getEmojiReactionCounterTextStyle(hasUserReacted)]}>{count}</Text>}
+        </PressableWithSecondaryInteraction>
+    );
+}
+
+EmojiReactionBubble.displayName = 'EmojiReactionBubble';
+
+export default React.forwardRef(EmojiReactionBubble);
diff --git a/src/components/Reactions/MiniQuickEmojiReactions.js b/src/components/Reactions/MiniQuickEmojiReactions.tsx
similarity index 56%
rename from src/components/Reactions/MiniQuickEmojiReactions.js
rename to src/components/Reactions/MiniQuickEmojiReactions.tsx
index 376afcb9ade5..9f38da6bdb3d 100644
--- a/src/components/Reactions/MiniQuickEmojiReactions.js
+++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx
@@ -1,49 +1,38 @@
-import PropTypes from 'prop-types';
 import React, {useRef} from 'react';
 import {View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
 import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
+import type {Emoji} from '@assets/emojis/types';
 import BaseMiniContextMenuItem from '@components/BaseMiniContextMenuItem';
 import Icon from '@components/Icon';
 import * as Expensicons from '@components/Icon/Expensicons';
 import Text from '@components/Text';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
 import * as EmojiUtils from '@libs/EmojiUtils';
 import getButtonState from '@libs/getButtonState';
 import * as EmojiPickerAction from '@userActions/EmojiPickerAction';
 import * as Session from '@userActions/Session';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import {baseQuickEmojiReactionsDefaultProps, baseQuickEmojiReactionsPropTypes} from './QuickEmojiReactions/BaseQuickEmojiReactions';
+import type {ReportActionReactions} from '@src/types/onyx';
+import type {BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types';
 
-const propTypes = {
-    ...baseQuickEmojiReactionsPropTypes,
+type MiniQuickEmojiReactionsOnyxProps = {
+    /** All the emoji reactions for the report action. */
+    emojiReactions: OnyxEntry<ReportActionReactions>;
 
+    /** The user's preferred skin tone. */
+    preferredSkinTone: OnyxEntry<string | number>;
+};
+
+type MiniQuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & {
     /**
      * Will be called when the user closed the emoji picker
      * without selecting an emoji.
      */
-    onEmojiPickerClosed: PropTypes.func,
-
-    /**
-     * ReportAction for EmojiPicker.
-     */
-    reportAction: PropTypes.shape({
-        reportActionID: PropTypes.string.isRequired,
-    }),
-
-    ...withLocalizePropTypes,
-    preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
-};
-
-const defaultProps = {
-    ...baseQuickEmojiReactionsDefaultProps,
-    onEmojiPickerClosed: () => {},
-    preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
-    reportAction: {},
+    onEmojiPickerClosed?: () => void;
 };
 
 /**
@@ -51,56 +40,63 @@ const defaultProps = {
  * emoji picker icon button. This is used for the mini
  * context menu which we just show on web, when hovering
  * a message.
- * @param {Props} props
- * @returns {JSX.Element}
  */
-function MiniQuickEmojiReactions(props) {
+function MiniQuickEmojiReactions({
+    reportAction,
+    onEmojiSelected,
+    preferredLocale = CONST.LOCALES.DEFAULT,
+    preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE,
+    emojiReactions = {},
+    onPressOpenPicker = () => {},
+    onEmojiPickerClosed = () => {},
+}: MiniQuickEmojiReactionsProps) {
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
-    const ref = useRef();
+    const ref = useRef<View>(null);
+    const {translate} = useLocalize();
 
     const openEmojiPicker = () => {
-        props.onPressOpenPicker();
+        onPressOpenPicker();
         EmojiPickerAction.showEmojiPicker(
-            props.onEmojiPickerClosed,
+            onEmojiPickerClosed,
             (emojiCode, emojiObject) => {
-                props.onEmojiSelected(emojiObject, props.emojiReactions);
+                onEmojiSelected(emojiObject, emojiReactions);
             },
             ref,
             undefined,
             () => {},
-            props.reportAction.reportActionID,
+            reportAction.reportActionID,
         );
     };
 
     return (
         <View style={styles.flexRow}>
-            {_.map(CONST.QUICK_REACTIONS, (emoji) => (
+            {CONST.QUICK_REACTIONS.map((emoji: Emoji) => (
                 <BaseMiniContextMenuItem
                     key={emoji.name}
                     isDelayButtonStateComplete={false}
-                    tooltipText={`:${EmojiUtils.getLocalizedEmojiName(emoji.name, props.preferredLocale)}:`}
-                    onPress={Session.checkIfActionIsAllowed(() => props.onEmojiSelected(emoji, props.emojiReactions))}
+                    tooltipText={`:${EmojiUtils.getLocalizedEmojiName(emoji.name, preferredLocale)}:`}
+                    onPress={Session.checkIfActionIsAllowed(() => onEmojiSelected(emoji, emojiReactions))}
                 >
                     <Text
                         style={[styles.miniQuickEmojiReactionText, styles.userSelectNone]}
                         dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}}
                     >
-                        {EmojiUtils.getPreferredEmojiCode(emoji, props.preferredSkinTone)}
+                        {EmojiUtils.getPreferredEmojiCode(emoji, preferredSkinTone)}
                     </Text>
                 </BaseMiniContextMenuItem>
             ))}
             <BaseMiniContextMenuItem
                 ref={ref}
                 onPress={Session.checkIfActionIsAllowed(() => {
-                    if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) {
+                    if (!EmojiPickerAction.emojiPickerRef.current?.isEmojiPickerVisible) {
                         openEmojiPicker();
                     } else {
-                        EmojiPickerAction.emojiPickerRef.current.hideEmojiPicker();
+                        EmojiPickerAction.emojiPickerRef.current?.hideEmojiPicker();
                     }
                 })}
                 isDelayButtonStateComplete={false}
-                tooltipText={props.translate('emojiReactions.addReactionTooltip')}
+                tooltipText={translate('emojiReactions.addReactionTooltip')}
             >
                 {({hovered, pressed}) => (
                     <Icon
@@ -115,19 +111,12 @@ function MiniQuickEmojiReactions(props) {
 }
 
 MiniQuickEmojiReactions.displayName = 'MiniQuickEmojiReactions';
-MiniQuickEmojiReactions.propTypes = propTypes;
-MiniQuickEmojiReactions.defaultProps = defaultProps;
-export default compose(
-    withLocalize,
-    // ESLint throws an error because it can't see that emojiReactions is defined in props. It is defined in props, but
-    // because of a couple spread operators, I think that's why ESLint struggles to see it
-    // eslint-disable-next-line rulesdir/onyx-props-must-have-default
-    withOnyx({
-        preferredSkinTone: {
-            key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
-        },
-        emojiReactions: {
-            key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`,
-        },
-    }),
-)(MiniQuickEmojiReactions);
+
+export default withOnyx<MiniQuickEmojiReactionsProps, MiniQuickEmojiReactionsOnyxProps>({
+    preferredSkinTone: {
+        key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
+    },
+    emojiReactions: {
+        key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`,
+    },
+})(MiniQuickEmojiReactions);
diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js
deleted file mode 100644
index c932632f7bff..000000000000
--- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js
+++ /dev/null
@@ -1,106 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {View} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import AddReactionBubble from '@components/Reactions/AddReactionBubble';
-import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble';
-import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes';
-import Tooltip from '@components/Tooltip';
-import useThemeStyles from '@hooks/useThemeStyles';
-import * as EmojiUtils from '@libs/EmojiUtils';
-import * as Session from '@userActions/Session';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-
-const baseQuickEmojiReactionsPropTypes = {
-    emojiReactions: EmojiReactionsPropTypes,
-
-    /**
-     * Callback to fire when an emoji is selected.
-     */
-    onEmojiSelected: PropTypes.func.isRequired,
-
-    /**
-     * Will be called when the emoji picker is about to show.
-     */
-    onWillShowPicker: PropTypes.func,
-
-    /**
-     * Callback to fire when the "open emoji picker" button is pressed.
-     * The function receives an argument which can be called
-     * to actually open the emoji picker.
-     */
-    onPressOpenPicker: PropTypes.func,
-
-    /**
-     * ReportAction for EmojiPicker.
-     */
-    reportAction: PropTypes.object,
-
-    preferredLocale: PropTypes.string,
-};
-
-const baseQuickEmojiReactionsDefaultProps = {
-    emojiReactions: {},
-    onWillShowPicker: () => {},
-    onPressOpenPicker: () => {},
-    reportAction: {},
-    preferredLocale: CONST.LOCALES.DEFAULT,
-};
-
-const propTypes = {
-    ...baseQuickEmojiReactionsPropTypes,
-    preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
-};
-
-const defaultProps = {
-    ...baseQuickEmojiReactionsDefaultProps,
-    preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE,
-};
-
-function BaseQuickEmojiReactions(props) {
-    const styles = useThemeStyles();
-    return (
-        <View style={styles.quickReactionsContainer}>
-            {_.map(CONST.QUICK_REACTIONS, (emoji) => (
-                <Tooltip
-                    text={`:${EmojiUtils.getLocalizedEmojiName(emoji.name, props.preferredLocale)}:`}
-                    key={emoji.name}
-                >
-                    <View>
-                        <EmojiReactionBubble
-                            emojiCodes={[EmojiUtils.getPreferredEmojiCode(emoji, props.preferredSkinTone)]}
-                            isContextMenu
-                            onPress={Session.checkIfActionIsAllowed(() => props.onEmojiSelected(emoji, props.emojiReactions))}
-                        />
-                    </View>
-                </Tooltip>
-            ))}
-            <AddReactionBubble
-                isContextMenu
-                onPressOpenPicker={props.onPressOpenPicker}
-                onWillShowPicker={props.onWillShowPicker}
-                onSelectEmoji={(emoji) => props.onEmojiSelected(emoji, props.emojiReactions)}
-                reportAction={props.reportAction}
-            />
-        </View>
-    );
-}
-
-BaseQuickEmojiReactions.displayName = 'BaseQuickEmojiReactions';
-BaseQuickEmojiReactions.propTypes = propTypes;
-BaseQuickEmojiReactions.defaultProps = defaultProps;
-export default withOnyx({
-    preferredSkinTone: {
-        key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
-    },
-    emojiReactions: {
-        key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`,
-    },
-    preferredLocale: {
-        key: ONYXKEYS.NVP_PREFERRED_LOCALE,
-    },
-})(BaseQuickEmojiReactions);
-
-export {baseQuickEmojiReactionsPropTypes, baseQuickEmojiReactionsDefaultProps};
diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx
new file mode 100644
index 000000000000..58973e90b9c4
--- /dev/null
+++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import {View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
+import type {Emoji} from '@assets/emojis/types';
+import AddReactionBubble from '@components/Reactions/AddReactionBubble';
+import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble';
+import Tooltip from '@components/Tooltip';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as EmojiUtils from '@libs/EmojiUtils';
+import * as Session from '@userActions/Session';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type {BaseQuickEmojiReactionsOnyxProps, BaseQuickEmojiReactionsProps} from './types';
+
+function BaseQuickEmojiReactions({
+    reportAction,
+    onEmojiSelected,
+    preferredLocale = CONST.LOCALES.DEFAULT,
+    preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE,
+    emojiReactions = {},
+    onPressOpenPicker = () => {},
+    onWillShowPicker = () => {},
+}: BaseQuickEmojiReactionsProps) {
+    const styles = useThemeStyles();
+
+    return (
+        <View style={styles.quickReactionsContainer}>
+            {CONST.QUICK_REACTIONS.map((emoji: Emoji) => (
+                <Tooltip
+                    text={`:${EmojiUtils.getLocalizedEmojiName(emoji.name, preferredLocale)}:`}
+                    key={emoji.name}
+                >
+                    <View>
+                        <EmojiReactionBubble
+                            emojiCodes={[EmojiUtils.getPreferredEmojiCode(emoji, preferredSkinTone)]}
+                            isContextMenu
+                            onPress={Session.checkIfActionIsAllowed(() => onEmojiSelected(emoji, emojiReactions))}
+                        />
+                    </View>
+                </Tooltip>
+            ))}
+            <AddReactionBubble
+                isContextMenu
+                onPressOpenPicker={onPressOpenPicker}
+                onWillShowPicker={onWillShowPicker}
+                onSelectEmoji={(emoji) => onEmojiSelected(emoji, emojiReactions)}
+                reportAction={reportAction}
+            />
+        </View>
+    );
+}
+
+BaseQuickEmojiReactions.displayName = 'BaseQuickEmojiReactions';
+
+export default withOnyx<BaseQuickEmojiReactionsProps, BaseQuickEmojiReactionsOnyxProps>({
+    preferredSkinTone: {
+        key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE,
+    },
+    emojiReactions: {
+        key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`,
+    },
+    preferredLocale: {
+        key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+    },
+})(BaseQuickEmojiReactions);
diff --git a/src/components/Reactions/QuickEmojiReactions/index.js b/src/components/Reactions/QuickEmojiReactions/index.js
deleted file mode 100644
index 0366071f9c81..000000000000
--- a/src/components/Reactions/QuickEmojiReactions/index.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {contextMenuRef} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
-import CONST from '@src/CONST';
-import BaseQuickEmojiReactions, {baseQuickEmojiReactionsPropTypes} from './BaseQuickEmojiReactions';
-
-const propTypes = {
-    ...baseQuickEmojiReactionsPropTypes,
-
-    /**
-     * Function that can be called to close the
-     * context menu in which this component is
-     * rendered.
-     */
-    closeContextMenu: PropTypes.func.isRequired,
-};
-
-function QuickEmojiReactions(props) {
-    const onPressOpenPicker = (openPicker) => {
-        openPicker(contextMenuRef.current.contentRef, {
-            horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
-            vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
-        });
-    };
-
-    return (
-        <BaseQuickEmojiReactions
-            // eslint-disable-next-line react/jsx-props-no-spreading
-            {...props}
-            onPressOpenPicker={onPressOpenPicker}
-            onWillShowPicker={props.closeContextMenu}
-        />
-    );
-}
-
-QuickEmojiReactions.displayName = 'QuickEmojiReactions';
-QuickEmojiReactions.propTypes = propTypes;
-export default QuickEmojiReactions;
diff --git a/src/components/Reactions/QuickEmojiReactions/index.native.js b/src/components/Reactions/QuickEmojiReactions/index.native.tsx
similarity index 56%
rename from src/components/Reactions/QuickEmojiReactions/index.native.js
rename to src/components/Reactions/QuickEmojiReactions/index.native.tsx
index c29bd2665e4a..b0eb88b31b68 100644
--- a/src/components/Reactions/QuickEmojiReactions/index.native.js
+++ b/src/components/Reactions/QuickEmojiReactions/index.native.tsx
@@ -1,41 +1,30 @@
-import PropTypes from 'prop-types';
 import React from 'react';
 import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
-import BaseQuickEmojiReactions, {baseQuickEmojiReactionsPropTypes} from './BaseQuickEmojiReactions';
+import BaseQuickEmojiReactions from './BaseQuickEmojiReactions';
+import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types';
 
-const propTypes = {
-    ...baseQuickEmojiReactionsPropTypes,
-
-    /**
-     * Function that can be called to close the
-     * context menu in which this component is
-     * rendered.
-     */
-    closeContextMenu: PropTypes.func.isRequired,
-};
-
-function QuickEmojiReactions(props) {
-    const onPressOpenPicker = (openPicker) => {
+function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsProps) {
+    const onPressOpenPicker = (openPicker?: OpenPickerCallback) => {
         // We first need to close the menu as it's a popover.
         // The picker is a popover as well and on mobile there can only
         // be one active popover at a time.
-        props.closeContextMenu(() => {
+        closeContextMenu(() => {
             // As the menu which includes the button to open the emoji picker
             // gets closed, before the picker actually opens, we pass the composer
             // ref as anchor for the emoji picker popover.
-            openPicker(ReportActionComposeFocusManager.composerRef);
+            openPicker?.(ReportActionComposeFocusManager.composerRef);
         });
     };
 
     return (
         <BaseQuickEmojiReactions
             // eslint-disable-next-line react/jsx-props-no-spreading
-            {...props}
+            {...rest}
             onPressOpenPicker={onPressOpenPicker}
         />
     );
 }
 
 QuickEmojiReactions.displayName = 'QuickEmojiReactions';
-QuickEmojiReactions.propTypes = propTypes;
+
 export default QuickEmojiReactions;
diff --git a/src/components/Reactions/QuickEmojiReactions/index.tsx b/src/components/Reactions/QuickEmojiReactions/index.tsx
new file mode 100644
index 000000000000..3b44f4fe4826
--- /dev/null
+++ b/src/components/Reactions/QuickEmojiReactions/index.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import {contextMenuRef} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
+import CONST from '@src/CONST';
+import BaseQuickEmojiReactions from './BaseQuickEmojiReactions';
+import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types';
+
+function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsProps) {
+    const onPressOpenPicker = (openPicker?: OpenPickerCallback) => {
+        openPicker?.(contextMenuRef.current?.contentRef, {
+            horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
+            vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP,
+        });
+    };
+
+    return (
+        <BaseQuickEmojiReactions
+            // eslint-disable-next-line react/jsx-props-no-spreading
+            {...rest}
+            onPressOpenPicker={onPressOpenPicker}
+            onWillShowPicker={closeContextMenu}
+        />
+    );
+}
+
+QuickEmojiReactions.displayName = 'QuickEmojiReactions';
+
+export default QuickEmojiReactions;
diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts
new file mode 100644
index 000000000000..d782d5ae35c7
--- /dev/null
+++ b/src/components/Reactions/QuickEmojiReactions/types.ts
@@ -0,0 +1,56 @@
+import type {RefObject} from 'react';
+import type {TextInput, View} from 'react-native';
+import type {OnyxEntry} from 'react-native-onyx';
+import type {Emoji} from '@assets/emojis/types';
+import type {AnchorOrigin} from '@userActions/EmojiPickerAction';
+import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx';
+
+type PickerRefElement = RefObject<TextInput | View>;
+
+type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrigin) => void;
+
+type CloseContextMenuCallback = () => void;
+
+type BaseQuickEmojiReactionsOnyxProps = {
+    /** All the emoji reactions for the report action. */
+    emojiReactions: OnyxEntry<ReportActionReactions>;
+
+    /** The user's preferred locale. */
+    preferredLocale: OnyxEntry<Locale>;
+
+    /** The user's preferred skin tone. */
+    preferredSkinTone: OnyxEntry<string | number>;
+};
+
+type BaseQuickEmojiReactionsProps = BaseQuickEmojiReactionsOnyxProps & {
+    /** Callback to fire when an emoji is selected. */
+    onEmojiSelected: (emoji: Emoji, emojiReactions: OnyxEntry<ReportActionReactions>) => void;
+
+    /**
+     * Will be called when the emoji picker is about to show.
+     */
+    onWillShowPicker?: (callback: CloseContextMenuCallback) => void;
+
+    /**
+     * Callback to fire when the "open emoji picker" button is pressed.
+     * The function receives an argument which can be called
+     * to actually open the emoji picker.
+     */
+    onPressOpenPicker?: (openPicker?: OpenPickerCallback) => void;
+
+    /** ReportAction for EmojiPicker. */
+    reportAction: ReportAction;
+
+    /** Id of the ReportAction for EmojiPicker. */
+    reportActionID: string;
+};
+
+type QuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & {
+    /**
+     * Function that can be called to close the context menu
+     * in which this component is rendered.
+     */
+    closeContextMenu: (callback: CloseContextMenuCallback) => void;
+};
+
+export type {BaseQuickEmojiReactionsProps, BaseQuickEmojiReactionsOnyxProps, QuickEmojiReactionsProps, OpenPickerCallback, CloseContextMenuCallback, PickerRefElement};
diff --git a/src/components/Reactions/ReactionTooltipContent.js b/src/components/Reactions/ReactionTooltipContent.js
deleted file mode 100644
index bb6b03c5918b..000000000000
--- a/src/components/Reactions/ReactionTooltipContent.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import PropTypes from 'prop-types';
-import React, {useMemo} from 'react';
-import {View} from 'react-native';
-import _ from 'underscore';
-import Text from '@components/Text';
-import {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
-import withLocalize from '@components/withLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
-import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
-
-const propTypes = {
-    /**
-     * A list of emoji codes to display in the tooltip.
-     */
-    emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired,
-
-    /**
-     * The name of the emoji to display in the tooltip.
-     */
-    emojiName: PropTypes.string.isRequired,
-
-    /**
-     * A list of account IDs to display in the tooltip.
-     */
-    accountIDs: PropTypes.arrayOf(PropTypes.number).isRequired,
-
-    ...withCurrentUserPersonalDetailsPropTypes,
-};
-
-const defaultProps = {
-    ...withCurrentUserPersonalDetailsDefaultProps,
-};
-
-function ReactionTooltipContent(props) {
-    const styles = useThemeStyles();
-    const users = useMemo(
-        () => PersonalDetailsUtils.getPersonalDetailsByIDs(props.accountIDs, props.currentUserPersonalDetails.accountID, true),
-        [props.currentUserPersonalDetails.accountID, props.accountIDs],
-    );
-    const namesString = _.filter(
-        _.map(users, (user) => user && user.displayName),
-        (n) => n,
-    ).join(', ');
-    return (
-        <View style={[styles.alignItemsCenter, styles.ph2]}>
-            <View style={styles.flexRow}>
-                {_.map(props.emojiCodes, (emojiCode) => (
-                    <Text
-                        key={emojiCode}
-                        style={styles.reactionEmojiTitle}
-                    >
-                        {emojiCode}
-                    </Text>
-                ))}
-            </View>
-
-            <Text style={[styles.mt1, styles.textMicroBold, styles.textReactionSenders, styles.textAlignCenter]}>{namesString}</Text>
-
-            <Text style={[styles.textMicro, styles.fontColorReactionLabel]}>{`${props.translate('emojiReactions.reactedWith')} :${props.emojiName}:`}</Text>
-        </View>
-    );
-}
-
-ReactionTooltipContent.propTypes = propTypes;
-ReactionTooltipContent.defaultProps = defaultProps;
-ReactionTooltipContent.displayName = 'ReactionTooltipContent';
-export default React.memo(withLocalize(ReactionTooltipContent));
diff --git a/src/components/Reactions/ReactionTooltipContent.tsx b/src/components/Reactions/ReactionTooltipContent.tsx
new file mode 100644
index 000000000000..198eba1f969c
--- /dev/null
+++ b/src/components/Reactions/ReactionTooltipContent.tsx
@@ -0,0 +1,58 @@
+import React, {useMemo} from 'react';
+import {View} from 'react-native';
+import Text from '@components/Text';
+import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
+
+type ReactionTooltipContentProps = Pick<WithCurrentUserPersonalDetailsProps, 'currentUserPersonalDetails'> & {
+    /**
+     * A list of emoji codes to display in the tooltip.
+     */
+    emojiCodes: string[];
+
+    /**
+     * The name of the emoji to display in the tooltip.
+     */
+    emojiName: string;
+
+    /**
+     * A list of account IDs to display in the tooltip.
+     */
+    accountIDs: number[];
+};
+
+function ReactionTooltipContent({accountIDs, currentUserPersonalDetails = {}, emojiCodes, emojiName}: ReactionTooltipContentProps) {
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+    const users = useMemo(() => PersonalDetailsUtils.getPersonalDetailsByIDs(accountIDs, currentUserPersonalDetails.accountID, true), [currentUserPersonalDetails.accountID, accountIDs]);
+
+    const namesString = users
+        .map((user) => user?.displayName)
+        .filter((name) => name)
+        .join(', ');
+
+    return (
+        <View style={[styles.alignItemsCenter, styles.ph2]}>
+            <View style={styles.flexRow}>
+                {emojiCodes.map((emojiCode) => (
+                    <Text
+                        key={emojiCode}
+                        style={styles.reactionEmojiTitle}
+                    >
+                        {emojiCode}
+                    </Text>
+                ))}
+            </View>
+
+            <Text style={[styles.mt1, styles.textMicroBold, styles.textReactionSenders, styles.textAlignCenter]}>{namesString}</Text>
+
+            <Text style={[styles.textMicro, styles.fontColorReactionLabel]}>{`${translate('emojiReactions.reactedWith')} :${emojiName}:`}</Text>
+        </View>
+    );
+}
+
+ReactionTooltipContent.displayName = 'ReactionTooltipContent';
+
+export default React.memo(ReactionTooltipContent);
diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.js b/src/components/Reactions/ReportActionItemEmojiReactions.tsx
similarity index 52%
rename from src/components/Reactions/ReportActionItemEmojiReactions.js
rename to src/components/Reactions/ReportActionItemEmojiReactions.tsx
index 547f4089857f..d1a2cf56b6a5 100644
--- a/src/components/Reactions/ReportActionItemEmojiReactions.js
+++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx
@@ -1,63 +1,98 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
+import sortBy from 'lodash/sortBy';
 import React, {useContext, useRef} from 'react';
 import {View} from 'react-native';
-import _ from 'underscore';
+import type {OnyxEntry} from 'react-native-onyx';
+import type {Emoji} from '@assets/emojis/types';
 import OfflineWithFeedback from '@components/OfflineWithFeedback';
 import Tooltip from '@components/Tooltip';
-import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
-import withLocalize from '@components/withLocalize';
+import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails';
+import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails';
 import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
 import * as EmojiUtils from '@libs/EmojiUtils';
-import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
 import {ReactionListContext} from '@pages/home/ReportScreenContext';
+import type {ReactionListAnchor, ReactionListEvent} from '@pages/home/ReportScreenContext';
+import CONST from '@src/CONST';
+import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx';
+import type {PendingAction} from '@src/types/onyx/OnyxCommon';
 import AddReactionBubble from './AddReactionBubble';
 import EmojiReactionBubble from './EmojiReactionBubble';
-import EmojiReactionsPropTypes from './EmojiReactionsPropTypes';
 import ReactionTooltipContent from './ReactionTooltipContent';
 
-const propTypes = {
-    emojiReactions: EmojiReactionsPropTypes,
+type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & {
+    /** All the emoji reactions for the report action. */
+    emojiReactions: OnyxEntry<ReportActionReactions>;
+
+    /** The user's preferred locale. */
+    preferredLocale: OnyxEntry<Locale>;
 
     /** The report action that these reactions are for */
-    reportAction: PropTypes.shape(reportActionPropTypes).isRequired,
+    reportAction: ReportAction;
 
     /**
      * Function to call when the user presses on an emoji.
      * This can also be an emoji the user already reacted with,
      * hence this function asks to toggle the reaction by emoji.
      */
-    toggleReaction: PropTypes.func.isRequired,
+    toggleReaction: (emoji: Emoji) => void;
 
     /** We disable reacting with emojis on report actions that have errors */
-    shouldBlockReactions: PropTypes.bool,
-
-    ...withCurrentUserPersonalDetailsPropTypes,
+    shouldBlockReactions?: boolean;
 };
 
-const defaultProps = {
-    ...withCurrentUserPersonalDetailsDefaultProps,
-    emojiReactions: {},
-    shouldBlockReactions: false,
+type PopoverReactionListAnchors = Record<string, ReactionListAnchor>;
+
+type FormattedReaction = {
+    /** The emoji codes to display in the bubble */
+    emojiCodes: string[];
+
+    /** IDs of users used the reaction */
+    userAccountIDs: number[];
+
+    /** Total reaction count */
+    reactionCount: number;
+
+    /** Whether the current account has reacted to the report action */
+    hasUserReacted: boolean;
+
+    /** Oldest timestamp of when the emoji was added */
+    oldestTimestamp: string;
+
+    /** Callback to fire on press */
+    onPress: () => void;
+
+    /** Callback to fire on reaction list open */
+    onReactionListOpen: (event: ReactionListEvent) => void;
+
+    /** The name of the emoji */
+    reactionEmojiName: string;
+
+    /** The type of action that's pending  */
+    pendingAction: PendingAction;
 };
 
-function ReportActionItemEmojiReactions(props) {
+function ReportActionItemEmojiReactions({
+    reportAction,
+    currentUserPersonalDetails,
+    toggleReaction,
+    emojiReactions = {},
+    shouldBlockReactions = false,
+    preferredLocale = CONST.LOCALES.DEFAULT,
+}: ReportActionItemEmojiReactionsProps) {
     const styles = useThemeStyles();
     const reactionListRef = useContext(ReactionListContext);
-    const popoverReactionListAnchors = useRef({});
+    const popoverReactionListAnchors = useRef<PopoverReactionListAnchors>({});
 
     let totalReactionCount = 0;
 
-    const reportAction = props.reportAction;
     const reportActionID = reportAction.reportActionID;
 
-    const formattedReactions = _.chain(props.emojiReactions)
-        .map((emojiReaction, emojiName) => {
+    // Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone
+    const formattedReactions: Array<FormattedReaction | null> = sortBy(
+        Object.entries(emojiReactions ?? {}).map(([emojiName, emojiReaction]) => {
             const {emoji, emojiCodes, reactionCount, hasUserReacted, userAccountIDs, oldestTimestamp} = EmojiUtils.getEmojiReactionDetails(
                 emojiName,
                 emojiReaction,
-                props.currentUserPersonalDetails.accountID,
+                currentUserPersonalDetails.accountID,
             );
 
             if (reactionCount === 0) {
@@ -66,11 +101,11 @@ function ReportActionItemEmojiReactions(props) {
             totalReactionCount += reactionCount;
 
             const onPress = () => {
-                props.toggleReaction(emoji);
+                toggleReaction(emoji);
             };
 
-            const onReactionListOpen = (event) => {
-                reactionListRef.current.showReactionList(event, popoverReactionListAnchors.current[emojiName], emojiName, reportActionID);
+            const onReactionListOpen = (event: ReactionListEvent) => {
+                reactionListRef?.current?.showReactionList(event, popoverReactionListAnchors.current[emojiName], emojiName, reportActionID);
             };
 
             return {
@@ -84,15 +119,14 @@ function ReportActionItemEmojiReactions(props) {
                 reactionEmojiName: emojiName,
                 pendingAction: emojiReaction.pendingAction,
             };
-        })
-        // Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone
-        .sortBy('oldestTimestamp')
-        .value();
+        }),
+        ['oldestTimestamp'],
+    );
 
     return (
         totalReactionCount > 0 && (
             <View style={[styles.flexRow, styles.flexWrap, styles.gap1, styles.mt2]}>
-                {_.map(formattedReactions, (reaction) => {
+                {formattedReactions.map((reaction) => {
                     if (reaction === null) {
                         return;
                     }
@@ -100,19 +134,19 @@ function ReportActionItemEmojiReactions(props) {
                         <Tooltip
                             renderTooltipContent={() => (
                                 <ReactionTooltipContent
-                                    emojiName={EmojiUtils.getLocalizedEmojiName(reaction.reactionEmojiName, props.preferredLocale)}
+                                    emojiName={EmojiUtils.getLocalizedEmojiName(reaction.reactionEmojiName, preferredLocale)}
                                     emojiCodes={reaction.emojiCodes}
                                     accountIDs={reaction.userAccountIDs}
-                                    currentUserPersonalDetails={props.currentUserPersonalDetails}
+                                    currentUserPersonalDetails={currentUserPersonalDetails}
                                 />
                             )}
-                            renderTooltipContentKey={[..._.map(reaction.userAccountIDs, String), ...reaction.emojiCodes]}
+                            renderTooltipContentKey={[...reaction.userAccountIDs.map(String), ...reaction.emojiCodes]}
                             key={reaction.reactionEmojiName}
                         >
                             <View>
                                 <OfflineWithFeedback
                                     pendingAction={reaction.pendingAction}
-                                    shouldDisableOpacity={Boolean(lodashGet(reportAction, 'pendingAction'))}
+                                    shouldDisableOpacity={!!reportAction.pendingAction}
                                 >
                                     <EmojiReactionBubble
                                         ref={(ref) => (popoverReactionListAnchors.current[reaction.reactionEmojiName] = ref)}
@@ -121,17 +155,17 @@ function ReportActionItemEmojiReactions(props) {
                                         onPress={reaction.onPress}
                                         hasUserReacted={reaction.hasUserReacted}
                                         onReactionListOpen={reaction.onReactionListOpen}
-                                        shouldBlockReactions={props.shouldBlockReactions}
+                                        shouldBlockReactions={shouldBlockReactions}
                                     />
                                 </OfflineWithFeedback>
                             </View>
                         </Tooltip>
                     );
                 })}
-                {!props.shouldBlockReactions && (
+                {!shouldBlockReactions && (
                     <AddReactionBubble
-                        onSelectEmoji={props.toggleReaction}
-                        reportAction={{reportActionID}}
+                        onSelectEmoji={toggleReaction}
+                        reportAction={reportAction}
                     />
                 )}
             </View>
@@ -140,6 +174,5 @@ function ReportActionItemEmojiReactions(props) {
 }
 
 ReportActionItemEmojiReactions.displayName = 'ReportActionItemReactions';
-ReportActionItemEmojiReactions.propTypes = propTypes;
-ReportActionItemEmojiReactions.defaultProps = defaultProps;
-export default compose(withLocalize, withCurrentUserPersonalDetails)(ReportActionItemEmojiReactions);
+
+export default withCurrentUserPersonalDetails(ReportActionItemEmojiReactions);
diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js
index f0e818ddff4d..c052a885245f 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview.js
+++ b/src/components/ReportActionItem/MoneyRequestPreview.js
@@ -1,3 +1,4 @@
+import {truncate} from 'lodash';
 import lodashGet from 'lodash/get';
 import PropTypes from 'prop-types';
 import React from 'react';
@@ -151,8 +152,9 @@ function MoneyRequestPreview(props) {
     // Pay button should only be visible to the manager of the report.
     const isCurrentUserManager = managerID === sessionAccountID;
 
-    const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant: requestMerchant} = ReportUtils.getTransactionDetails(props.transaction);
-    const description = requestComment;
+    const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant} = ReportUtils.getTransactionDetails(props.transaction);
+    const description = truncate(requestComment, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH});
+    const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH});
     const hasReceipt = TransactionUtils.hasReceipt(props.transaction);
     const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(props.transaction);
     const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(props.transaction);
@@ -208,7 +210,7 @@ function MoneyRequestPreview(props) {
         }
 
         let message = translate('iou.cash');
-        if (ReportUtils.isGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) {
+        if (ReportUtils.isPaidGroupPolicyExpenseReport(props.iouReport) && ReportUtils.isReportApproved(props.iouReport) && !ReportUtils.isSettled(props.iouReport)) {
             message += ` • ${translate('iou.approved')}`;
         } else if (props.iouReport.isWaitingOnBankAccount) {
             message += ` • ${translate('iou.pending')}`;
diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js
index 514dc71ffe2c..37ff163f23c8 100644
--- a/src/components/ReportActionItem/MoneyRequestView.js
+++ b/src/components/ReportActionItem/MoneyRequestView.js
@@ -1,7 +1,7 @@
 import lodashGet from 'lodash/get';
 import lodashValues from 'lodash/values';
 import PropTypes from 'prop-types';
-import React, {useMemo} from 'react';
+import React, {useCallback} from 'react';
 import {View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
 import categoryPropTypes from '@components/categoryPropTypes';
@@ -14,12 +14,14 @@ import Switch from '@components/Switch';
 import tagPropTypes from '@components/tagPropTypes';
 import Text from '@components/Text';
 import transactionPropTypes from '@components/transactionPropTypes';
+import ViolationMessages from '@components/ViolationMessages';
 import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
 import useLocalize from '@hooks/useLocalize';
 import usePermissions from '@hooks/usePermissions';
 import useStyleUtils from '@hooks/useStyleUtils';
 import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
+import useViolations from '@hooks/useViolations';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import * as CardUtils from '@libs/CardUtils';
 import compose from '@libs/compose';
@@ -35,12 +37,39 @@ import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateB
 import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
 import iouReportPropTypes from '@pages/iouReportPropTypes';
 import reportPropTypes from '@pages/reportPropTypes';
+import {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy';
 import * as IOU from '@userActions/IOU';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import ReportActionItemImage from './ReportActionItemImage';
 
+const violationNames = lodashValues(CONST.VIOLATIONS);
+
+const transactionViolationPropType = PropTypes.shape({
+    type: PropTypes.string.isRequired,
+    name: PropTypes.oneOf(violationNames).isRequired,
+    data: PropTypes.shape({
+        rejectedBy: PropTypes.string,
+        rejectReason: PropTypes.string,
+        amount: PropTypes.string,
+        surcharge: PropTypes.number,
+        invoiceMarkup: PropTypes.number,
+        maxAge: PropTypes.number,
+        tagName: PropTypes.string,
+        formattedLimitAmount: PropTypes.string,
+        categoryLimit: PropTypes.string,
+        limit: PropTypes.string,
+        category: PropTypes.string,
+        brokenBankConnection: PropTypes.bool,
+        isAdmin: PropTypes.bool,
+        email: PropTypes.string,
+        isTransactionOlderThan7Days: PropTypes.bool,
+        member: PropTypes.string,
+        taxName: PropTypes.string,
+    }),
+});
+
 const propTypes = {
     /** The report currently being looked at */
     report: reportPropTypes.isRequired,
@@ -55,12 +84,18 @@ const propTypes = {
     /** The actions from the parent report */
     parentReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
 
+    /** The policy the report is tied to */
+    ...policyPropTypes,
+
     /** Collection of categories attached to a policy */
     policyCategories: PropTypes.objectOf(categoryPropTypes),
 
     /** The transaction associated with the transactionThread */
     transaction: transactionPropTypes,
 
+    /** Violations detected in this transaction */
+    transactionViolations: PropTypes.arrayOf(transactionViolationPropType),
+
     /** Collection of tags attached to a policy */
     policyTags: tagPropTypes,
 
@@ -70,16 +105,18 @@ const propTypes = {
 const defaultProps = {
     parentReport: {},
     parentReportActions: {},
-    policyCategories: {},
     transaction: {
         amount: 0,
         currency: CONST.CURRENCY.USD,
         comment: {comment: ''},
     },
+    transactionViolations: [],
+    policyCategories: {},
     policyTags: {},
+    ...policyDefaultProps,
 };
 
-function MoneyRequestView({report, parentReport, parentReportActions, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy}) {
+function MoneyRequestView({report, parentReport, parentReportActions, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy, transactionViolations}) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
@@ -115,12 +152,18 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
     // Flags for allowing or disallowing editing a money request
     const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID);
     const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU;
+
+    // Used for non-restricted fields such as: description, category, tag, billable, etc.
     const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction);
-    const canEditAmount = canEdit && !isSettled && !isCardTransaction;
-    const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, moneyRequestReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT);
+    const canEditAmount = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT);
+    const canEditMerchant = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT);
+    const canEditDate = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE);
+    const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT);
+    const canEditDistance = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE);
 
     // A flag for verifying that the current report is a sub-report of a workspace chat
-    const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]);
+    // if the policy of the report is either Collect or Control, then this report must be tied to workspace chat
+    const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report);
 
     // Fetches only the first tag, for now
     const policyTag = PolicyUtils.getTag(policyTags);
@@ -131,6 +174,9 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
     const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList)));
     const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true));
 
+    const {getViolationsForField} = useViolations(transactionViolations);
+    const hasViolations = useCallback((field) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]);
+
     let amountDescription = `${translate('iou.amount')}`;
 
     if (isCardTransaction) {
@@ -158,18 +204,12 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
         }
     }
 
-    // A temporary solution to hide the transaction detail
-    // This will be removed after we properly add the transaction as a prop
-    if (ReportActionsUtils.isDeletedAction(parentReportAction)) {
-        return null;
-    }
-
     const hasReceipt = TransactionUtils.hasReceipt(transaction);
     let receiptURIs;
     let hasErrors = false;
     if (hasReceipt) {
         receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction);
-        hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction);
+        hasErrors = canEditReceipt && TransactionUtils.hasMissingSmartscanFields(transaction);
     }
 
     const pendingAction = lodashGet(transaction, 'pendingAction');
@@ -188,16 +228,18 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
                                 isLocalFile={receiptURIs.isLocalFile}
                                 transaction={transaction}
                                 enablePreviewModal
+                                canEditReceipt={canEditReceipt}
                             />
                         </View>
                     </OfflineWithFeedback>
                 )}
-                {!hasReceipt && canEditReceipt && !isSettled && canUseViolations && (
+                {!hasReceipt && canEditReceipt && canUseViolations && (
                     <ReceiptEmptyState
                         hasError={hasErrors}
                         onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT))}
                     />
                 )}
+                {canUseViolations && <ViolationMessages violations={getViolationsForField('receipt')} />}
                 <OfflineWithFeedback pendingAction={getPendingFieldAction('pendingFields.amount')}>
                     <MenuItemWithTopDescription
                         title={formattedTransactionAmount ? formattedTransactionAmount.toString() : ''}
@@ -208,9 +250,10 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
                         interactive={canEditAmount}
                         shouldShowRightIcon={canEditAmount}
                         onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))}
-                        brickRoadIndicator={hasErrors && transactionAmount === 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
+                        brickRoadIndicator={hasViolations('amount') || (hasErrors && transactionAmount === 0) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
                         error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''}
                     />
+                    {canUseViolations && <ViolationMessages violations={getViolationsForField('amount')} />}
                 </OfflineWithFeedback>
                 <OfflineWithFeedback pendingAction={getPendingFieldAction('pendingFields.comment')}>
                     <MenuItemWithTopDescription
@@ -222,16 +265,18 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
                         titleStyle={styles.flex1}
                         onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION))}
                         wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
+                        brickRoadIndicator={hasViolations('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
                         numberOfLinesTitle={0}
                     />
+                    {canUseViolations && <ViolationMessages violations={getViolationsForField('comment')} />}
                 </OfflineWithFeedback>
                 {isDistanceRequest ? (
                     <OfflineWithFeedback pendingAction={lodashGet(transaction, 'pendingFields.waypoints') || lodashGet(transaction, 'pendingAction')}>
                         <MenuItemWithTopDescription
                             description={translate('common.distance')}
                             title={hasPendingWaypoints ? transactionMerchant.replace(CONST.REGEX.FIRST_SPACE, translate('common.tbd')) : transactionMerchant}
-                            interactive={canEdit && !isSettled}
-                            shouldShowRightIcon={canEdit && !isSettled}
+                            interactive={canEditDistance}
+                            shouldShowRightIcon={canEditDistance}
                             titleStyle={styles.flex1}
                             onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DISTANCE))}
                         />
@@ -241,26 +286,28 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
                         <MenuItemWithTopDescription
                             description={translate('common.merchant')}
                             title={isEmptyMerchant ? '' : transactionMerchant}
-                            interactive={canEdit}
-                            shouldShowRightIcon={canEdit}
+                            interactive={canEditMerchant}
+                            shouldShowRightIcon={canEditMerchant}
                             titleStyle={styles.flex1}
                             onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))}
-                            brickRoadIndicator={hasErrors && isEmptyMerchant ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
+                            brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
                             error={hasErrors && isEmptyMerchant ? translate('common.error.enterMerchant') : ''}
                         />
+                        {canUseViolations && <ViolationMessages violations={getViolationsForField('merchant')} />}
                     </OfflineWithFeedback>
                 )}
                 <OfflineWithFeedback pendingAction={getPendingFieldAction('pendingFields.created')}>
                     <MenuItemWithTopDescription
                         description={translate('common.date')}
                         title={transactionDate}
-                        interactive={canEdit && !isSettled}
-                        shouldShowRightIcon={canEdit && !isSettled}
+                        interactive={canEditDate}
+                        shouldShowRightIcon={canEditDate}
                         titleStyle={styles.flex1}
                         onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))}
-                        brickRoadIndicator={hasErrors && transactionDate === '' ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
+                        brickRoadIndicator={hasViolations('date') || (hasErrors && transactionDate === '') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
                         error={hasErrors && transactionDate === '' ? translate('common.error.enterDate') : ''}
                     />
+                    {canUseViolations && <ViolationMessages violations={getViolationsForField('date')} />}
                 </OfflineWithFeedback>
                 {shouldShowCategory && (
                     <OfflineWithFeedback pendingAction={lodashGet(transaction, 'pendingFields.category') || lodashGet(transaction, 'pendingAction')}>
@@ -271,7 +318,9 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
                             shouldShowRightIcon={canEdit}
                             titleStyle={styles.flex1}
                             onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))}
+                            brickRoadIndicator={hasViolations('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
                         />
+                        {canUseViolations && <ViolationMessages violations={getViolationsForField('category')} />}
                     </OfflineWithFeedback>
                 )}
                 {shouldShowTag && (
@@ -283,7 +332,9 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
                             shouldShowRightIcon={canEdit}
                             titleStyle={styles.flex1}
                             onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))}
+                            brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
                         />
+                        {canUseViolations && <ViolationMessages violations={getViolationsForField('tag')} />}
                     </OfflineWithFeedback>
                 )}
                 {isCardTransaction && (
@@ -295,15 +346,24 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate
                         />
                     </OfflineWithFeedback>
                 )}
+
                 {shouldShowBillable && (
-                    <View style={[styles.flexRow, styles.optionRow, styles.justifyContentBetween, styles.alignItemsCenter, styles.ml5, styles.mr8]}>
-                        <Text color={!transactionBillable ? theme.textSupporting : undefined}>{translate('common.billable')}</Text>
-                        <Switch
-                            accessibilityLabel={translate('common.billable')}
-                            isOn={transactionBillable}
-                            onToggle={(value) => IOU.editMoneyRequest(transaction, report.reportID, {billable: value})}
-                        />
-                    </View>
+                    <>
+                        <View style={[styles.flexRow, styles.optionRow, styles.justifyContentBetween, styles.alignItemsCenter, styles.ml5, styles.mr8]}>
+                            <Text color={!transactionBillable ? theme.textSupporting : undefined}>{translate('common.billable')}</Text>
+                            <Switch
+                                accessibilityLabel={translate('common.billable')}
+                                isOn={transactionBillable}
+                                onToggle={(value) => IOU.editMoneyRequest(transaction, report.reportID, {billable: value})}
+                            />
+                        </View>
+                        {hasViolations('billable') && (
+                            <ViolationMessages
+                                violations={getViolationsForField('billable')}
+                                isLast
+                            />
+                        )}
+                    </>
                 )}
             </View>
             <SpacerView
@@ -349,5 +409,15 @@ export default compose(
                 return `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`;
             },
         },
+        transactionViolation: {
+            key: ({report}) => {
+                const parentReportAction = ReportActionsUtils.getParentReportAction(report);
+                const transactionID = lodashGet(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0);
+                return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`;
+            },
+        },
+        policyTags: {
+            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`,
+        },
     }),
 )(MoneyRequestView);
diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js
index 4336a5eddd8a..1495dcbd9111 100644
--- a/src/components/ReportActionItem/ReportActionItemImage.js
+++ b/src/components/ReportActionItem/ReportActionItemImage.js
@@ -31,6 +31,9 @@ const propTypes = {
 
     /** whether thumbnail is refer the local file or not */
     isLocalFile: PropTypes.bool,
+
+    /** whether the receipt can be replaced */
+    canEditReceipt: PropTypes.bool,
 };
 
 const defaultProps = {
@@ -38,6 +41,7 @@ const defaultProps = {
     transaction: {},
     enablePreviewModal: false,
     isLocalFile: false,
+    canEditReceipt: false,
 };
 
 /**
@@ -46,7 +50,7 @@ const defaultProps = {
  * and optional preview modal as well.
  */
 
-function ReportActionItemImage({thumbnail, image, enablePreviewModal, transaction, isLocalFile}) {
+function ReportActionItemImage({thumbnail, image, enablePreviewModal, transaction, canEditReceipt, isLocalFile}) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const imageSource = tryResolveUrlFromApiRoot(image || '');
@@ -88,6 +92,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio
                         isAuthTokenRequired={!isLocalFile}
                         report={report}
                         isReceiptAttachment
+                        canEditReceipt={canEditReceipt}
                         allowToDownload
                         originalFileName={transaction.filename}
                     >
diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js
index e88c057a615d..27447a10a32b 100644
--- a/src/components/ReportActionItem/ReportPreview.js
+++ b/src/components/ReportActionItem/ReportPreview.js
@@ -243,10 +243,10 @@ function ReportPreview(props) {
         // eslint-disable-next-line react-hooks/exhaustive-deps
     }, []);
 
-    const isGroupPolicy = ReportUtils.isGroupPolicyExpenseChat(props.chatReport);
+    const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(props.chatReport);
     const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && lodashGet(props.policy, 'role') === CONST.POLICY.ROLE.ADMIN;
-    const isPayer = isGroupPolicy
-        ? // In a group policy, the admin approver can pay the report directly by skipping the approval step
+    const isPayer = isPaidGroupPolicy
+        ? // In a paid group policy, the admin approver can pay the report directly by skipping the approval step
           isPolicyAdmin && (isApproved || isCurrentUserManager)
         : isPolicyAdmin || (isMoneyRequestReport && isCurrentUserManager);
     const shouldShowPayButton = useMemo(
@@ -254,11 +254,11 @@ function ReportPreview(props) {
         [isPayer, isDraftExpenseReport, iouSettled, reimbursableSpend, iouCanceled, props.iouReport],
     );
     const shouldShowApproveButton = useMemo(() => {
-        if (!isGroupPolicy) {
+        if (!isPaidGroupPolicy) {
             return false;
         }
         return isCurrentUserManager && !isDraftExpenseReport && !isApproved && !iouSettled;
-    }, [isGroupPolicy, isCurrentUserManager, isDraftExpenseReport, isApproved, iouSettled]);
+    }, [isPaidGroupPolicy, isCurrentUserManager, isDraftExpenseReport, isApproved, iouSettled]);
     const shouldShowSettlementButton = shouldShowPayButton || shouldShowApproveButton;
     return (
         <OfflineWithFeedback pendingAction={lodashGet(props, 'iouReport.pendingFields.preview')}>
diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js
index a7728045f407..414f030d4fc7 100644
--- a/src/components/ReportActionItem/TaskPreview.js
+++ b/src/components/ReportActionItem/TaskPreview.js
@@ -123,7 +123,10 @@ function TaskPreview(props) {
                         style={[styles.mr2]}
                         containerStyle={[styles.taskCheckbox]}
                         isChecked={isTaskCompleted}
-                        disabled={!Task.canModifyTask(props.taskReport, props.currentUserPersonalDetails.accountID, lodashGet(props.rootParentReportpolicy, 'role', ''))}
+                        disabled={
+                            _.isEmpty(props.taskReport) ||
+                            !Task.canModifyTask(props.taskReport, props.currentUserPersonalDetails.accountID, lodashGet(props.rootParentReportpolicy, 'role', ''))
+                        }
                         onPress={Session.checkIfActionIsAllowed(() => {
                             if (isTaskCompleted) {
                                 Task.reopenTask(props.taskReport);
diff --git a/src/components/Section/IconSection.js b/src/components/Section/IconSection.js
deleted file mode 100644
index 307331aa36d6..000000000000
--- a/src/components/Section/IconSection.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {View} from 'react-native';
-import Icon from '@components/Icon';
-import sourcePropTypes from '@components/Image/sourcePropTypes';
-import useThemeStyles from '@hooks/useThemeStyles';
-
-const iconSectionPropTypes = {
-    icon: sourcePropTypes,
-    IconComponent: PropTypes.IconComponent,
-    iconContainerStyles: PropTypes.iconContainerStyles,
-};
-
-const defaultIconSectionPropTypes = {
-    icon: null,
-    IconComponent: null,
-    iconContainerStyles: [],
-};
-
-function IconSection({icon, IconComponent, iconContainerStyles}) {
-    const styles = useThemeStyles();
-
-    return (
-        <View style={[styles.flexGrow1, styles.flexRow, styles.justifyContentEnd, ...iconContainerStyles]}>
-            {Boolean(icon) && (
-                <Icon
-                    src={icon}
-                    height={68}
-                    width={68}
-                />
-            )}
-            {Boolean(IconComponent) && <IconComponent />}
-        </View>
-    );
-}
-
-IconSection.displayName = 'IconSection';
-IconSection.propTypes = iconSectionPropTypes;
-IconSection.defaultProps = defaultIconSectionPropTypes;
-
-export default IconSection;
diff --git a/src/components/Section/IconSection.tsx b/src/components/Section/IconSection.tsx
new file mode 100644
index 000000000000..cc42c6b7ace5
--- /dev/null
+++ b/src/components/Section/IconSection.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import type {StyleProp, ViewStyle} from 'react-native';
+import {View} from 'react-native';
+import Icon from '@components/Icon';
+import useThemeStyles from '@hooks/useThemeStyles';
+import type IconAsset from '@src/types/utils/IconAsset';
+
+type IconSectionProps = {
+    icon?: IconAsset;
+    iconContainerStyles?: StyleProp<ViewStyle>;
+};
+
+function IconSection({icon, iconContainerStyles}: IconSectionProps) {
+    const styles = useThemeStyles();
+
+    return (
+        <View style={[styles.flexGrow1, styles.flexRow, styles.justifyContentEnd, iconContainerStyles]}>
+            {!!icon && (
+                <Icon
+                    src={icon}
+                    height={68}
+                    width={68}
+                />
+            )}
+        </View>
+    );
+}
+
+IconSection.displayName = 'IconSection';
+
+export default IconSection;
diff --git a/src/components/Section/index.js b/src/components/Section/index.js
deleted file mode 100644
index 50576abef025..000000000000
--- a/src/components/Section/index.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import {View} from 'react-native';
-import sourcePropTypes from '@components/Image/sourcePropTypes';
-import MenuItemList from '@components/MenuItemList';
-import menuItemPropTypes from '@components/menuItemPropTypes';
-import Text from '@components/Text';
-import useThemeStyles from '@hooks/useThemeStyles';
-import IconSection from './IconSection';
-
-const CARD_LAYOUT = {
-    ICON_ON_TOP: 'iconOnTop',
-    ICON_ON_RIGHT: 'iconOnRight',
-};
-
-const propTypes = {
-    /** An array of props that are pass to individual MenuItem components */
-    menuItems: PropTypes.arrayOf(PropTypes.shape(menuItemPropTypes)),
-
-    /** The text to display in the title of the section */
-    title: PropTypes.string.isRequired,
-
-    /** The text to display in the subtitle of the section */
-    subtitle: PropTypes.string,
-
-    /** The icon to display along with the title */
-    icon: sourcePropTypes,
-
-    /** Icon component */
-    IconComponent: PropTypes.func,
-
-    /** Card layout that affects icon positioning, margins, sizes. */
-    // eslint-disable-next-line rulesdir/prefer-underscore-method
-    cardLayout: PropTypes.oneOf(Object.values(CARD_LAYOUT)),
-
-    /** Contents to display inside the section */
-    children: PropTypes.node,
-
-    /** Customize the Section container */
-    // eslint-disable-next-line react/forbid-prop-types
-    containerStyles: PropTypes.arrayOf(PropTypes.object),
-
-    /** Customize the Section container */
-    // eslint-disable-next-line react/forbid-prop-types
-    titleStyles: PropTypes.arrayOf(PropTypes.object),
-
-    /** Customize the Section container */
-    // eslint-disable-next-line react/forbid-prop-types
-    subtitleStyles: PropTypes.arrayOf(PropTypes.object),
-
-    /** Whether the subtitle should have a muted style */
-    subtitleMuted: PropTypes.bool,
-
-    /** Customize the Section container */
-    // eslint-disable-next-line react/forbid-prop-types
-    childrenStyles: PropTypes.arrayOf(PropTypes.object),
-
-    /** Customize the Icon container */
-    // eslint-disable-next-line react/forbid-prop-types
-    iconContainerStyles: PropTypes.arrayOf(PropTypes.object),
-};
-
-const defaultProps = {
-    menuItems: null,
-    children: null,
-    icon: null,
-    IconComponent: null,
-    cardLayout: CARD_LAYOUT.ICON_ON_RIGHT,
-    containerStyles: [],
-    iconContainerStyles: [],
-    titleStyles: [],
-    subtitleStyles: [],
-    subtitleMuted: false,
-    childrenStyles: [],
-    subtitle: null,
-};
-
-function Section({children, childrenStyles, containerStyles, icon, IconComponent, cardLayout, iconContainerStyles, menuItems, subtitle, subtitleStyles, subtitleMuted, title, titleStyles}) {
-    const styles = useThemeStyles();
-
-    return (
-        <>
-            <View style={[styles.pageWrapper, styles.cardSection, ...containerStyles]}>
-                {cardLayout === CARD_LAYOUT.ICON_ON_TOP && (
-                    <IconSection
-                        icon={icon}
-                        IconComponent={IconComponent}
-                        iconContainerStyles={[...iconContainerStyles, styles.alignSelfStart, styles.mb3]}
-                    />
-                )}
-                <View style={[styles.flexRow, styles.alignItemsCenter, styles.w100, cardLayout === CARD_LAYOUT.ICON_ON_TOP && styles.mh1, ...titleStyles]}>
-                    <View style={[styles.flexShrink1]}>
-                        <Text style={[styles.textHeadline, styles.cardSectionTitle]}>{title}</Text>
-                    </View>
-                    {cardLayout === CARD_LAYOUT.ICON_ON_RIGHT && (
-                        <IconSection
-                            icon={icon}
-                            IconComponent={IconComponent}
-                            iconContainerStyles={iconContainerStyles}
-                        />
-                    )}
-                </View>
-
-                {Boolean(subtitle) && (
-                    <View style={[styles.flexRow, styles.alignItemsCenter, styles.w100, cardLayout === CARD_LAYOUT.ICON_ON_TOP ? [styles.mt1, styles.mh1] : styles.mt4, ...subtitleStyles]}>
-                        <Text style={[styles.textNormal, subtitleMuted && styles.colorMuted]}>{subtitle}</Text>
-                    </View>
-                )}
-
-                <View style={[styles.w100, ...childrenStyles]}>{children}</View>
-
-                <View style={[styles.w100]}>{Boolean(menuItems) && <MenuItemList menuItems={menuItems} />}</View>
-            </View>
-        </>
-    );
-}
-Section.displayName = 'Section';
-Section.propTypes = propTypes;
-Section.defaultProps = defaultProps;
-
-export {CARD_LAYOUT};
-export default Section;
diff --git a/src/components/Section/index.tsx b/src/components/Section/index.tsx
new file mode 100644
index 000000000000..f24316a5f1bb
--- /dev/null
+++ b/src/components/Section/index.tsx
@@ -0,0 +1,106 @@
+import React from 'react';
+import type {StyleProp, ViewStyle} from 'react-native';
+import {View} from 'react-native';
+import type {ValueOf} from 'type-fest';
+import type {MenuItemWithLink} from '@components/MenuItemList';
+import MenuItemList from '@components/MenuItemList';
+import Text from '@components/Text';
+import useThemeStyles from '@hooks/useThemeStyles';
+import type ChildrenProps from '@src/types/utils/ChildrenProps';
+import type IconAsset from '@src/types/utils/IconAsset';
+import IconSection from './IconSection';
+
+const CARD_LAYOUT = {
+    ICON_ON_TOP: 'iconOnTop',
+    ICON_ON_RIGHT: 'iconOnRight',
+} as const;
+
+type SectionProps = ChildrenProps & {
+    /** An array of props that are passed to individual MenuItem components */
+    menuItems?: MenuItemWithLink[];
+
+    /** The text to display in the title of the section */
+    title: string;
+
+    /** The text to display in the subtitle of the section */
+    subtitle?: string;
+
+    /** The icon to display along with the title */
+    icon?: IconAsset;
+
+    /** Card layout that affects icon positioning, margins, sizes */
+    cardLayout?: ValueOf<typeof CARD_LAYOUT>;
+
+    /** Whether the subtitle should have a muted style */
+    subtitleMuted?: boolean;
+
+    /** Customize the Section container */
+    containerStyles?: StyleProp<ViewStyle>;
+
+    /** Customize the Section container */
+    titleStyles?: StyleProp<ViewStyle>;
+
+    /** Customize the Section container */
+    subtitleStyles?: StyleProp<ViewStyle>;
+
+    /** Customize the Section container */
+    childrenStyles?: StyleProp<ViewStyle>;
+
+    /** Customize the Icon container */
+    iconContainerStyles?: StyleProp<ViewStyle>;
+};
+
+function Section({
+    children,
+    childrenStyles,
+    containerStyles,
+    icon,
+    cardLayout = CARD_LAYOUT.ICON_ON_RIGHT,
+    iconContainerStyles,
+    menuItems,
+    subtitle,
+    subtitleStyles,
+    subtitleMuted = false,
+    title,
+    titleStyles,
+}: SectionProps) {
+    const styles = useThemeStyles();
+
+    return (
+        <>
+            <View style={[styles.pageWrapper, styles.cardSection, containerStyles]}>
+                {cardLayout === CARD_LAYOUT.ICON_ON_TOP && (
+                    <IconSection
+                        icon={icon}
+                        iconContainerStyles={[iconContainerStyles, styles.alignSelfStart, styles.mb3]}
+                    />
+                )}
+                <View style={[styles.flexRow, styles.alignItemsCenter, styles.w100, cardLayout === CARD_LAYOUT.ICON_ON_TOP && styles.mh1, titleStyles]}>
+                    <View style={[styles.flexShrink1]}>
+                        <Text style={[styles.textHeadline, styles.cardSectionTitle]}>{title}</Text>
+                    </View>
+                    {cardLayout === CARD_LAYOUT.ICON_ON_RIGHT && (
+                        <IconSection
+                            icon={icon}
+                            iconContainerStyles={iconContainerStyles}
+                        />
+                    )}
+                </View>
+
+                {!!subtitle && (
+                    <View style={[styles.flexRow, styles.alignItemsCenter, styles.w100, cardLayout === CARD_LAYOUT.ICON_ON_TOP ? [styles.mt1, styles.mh1] : styles.mt4, subtitleStyles]}>
+                        <Text style={[styles.textNormal, subtitleMuted && styles.colorMuted]}>{subtitle}</Text>
+                    </View>
+                )}
+
+                <View style={[styles.w100, childrenStyles]}>{children}</View>
+
+                <View style={[styles.w100]}>{!!menuItems && <MenuItemList menuItems={menuItems} />}</View>
+            </View>
+        </>
+    );
+}
+Section.displayName = 'Section';
+
+export {CARD_LAYOUT};
+export default Section;
diff --git a/src/components/SelectionList/BaseListItem.js b/src/components/SelectionList/BaseListItem.js
index 443b930d5e7a..64363b4acb2d 100644
--- a/src/components/SelectionList/BaseListItem.js
+++ b/src/components/SelectionList/BaseListItem.js
@@ -89,8 +89,9 @@ function BaseListItem({
                         textStyles={[
                             styles.optionDisplayName,
                             isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText,
-                            isUserItem || item.isSelected ? styles.sidebarLinkTextBold : null,
+                            isUserItem || item.isSelected || item.alternateText ? styles.sidebarLinkTextBold : null,
                             styles.pre,
+                            item.alternateText ? styles.mb1 : null,
                         ]}
                         alternateTextStyles={[styles.optionAlternateText, styles.textLabelSupporting, isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText, styles.pre]}
                         isDisabled={isDisabled}
diff --git a/src/components/SelectionList/BaseSelectionList.js b/src/components/SelectionList/BaseSelectionList.js
index 213c55e8d8b9..88c4018f823c 100644
--- a/src/components/SelectionList/BaseSelectionList.js
+++ b/src/components/SelectionList/BaseSelectionList.js
@@ -76,8 +76,7 @@ function BaseSelectionList({
     const activeElement = useActiveElement();
     const isFocused = useIsFocused();
     const [maxToRenderPerBatch, setMaxToRenderPerBatch] = useState(shouldUseDynamicMaxToRenderPerBatch ? 0 : CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT);
-    const [isInitialRender, setIsInitialRender] = useState(true);
-    const wrapperStyles = useMemo(() => ({opacity: isInitialRender ? 0 : 1}), [isInitialRender]);
+    const [isInitialSectionListRender, setIsInitialSectionListRender] = useState(true);
 
     /**
      * Iterates through the sections and items inside each section, and builds 3 arrays along the way:
@@ -331,13 +330,13 @@ function BaseSelectionList({
                 setMaxToRenderPerBatch((Math.ceil(listHeight / itemHeight) || 0) + CONST.MAX_TO_RENDER_PER_BATCH.DEFAULT);
             }
 
-            if (!isInitialRender) {
+            if (!isInitialSectionListRender) {
                 return;
             }
             scrollToIndex(focusedIndex, false);
-            setIsInitialRender(false);
+            setIsInitialSectionListRender(false);
         },
-        [focusedIndex, isInitialRender, scrollToIndex, shouldUseDynamicMaxToRenderPerBatch],
+        [focusedIndex, isInitialSectionListRender, scrollToIndex, shouldUseDynamicMaxToRenderPerBatch],
     );
 
     const updateAndScrollToFocusedIndex = useCallback(
@@ -365,7 +364,7 @@ function BaseSelectionList({
 
     useEffect(() => {
         // do not change focus on the first render, as it should focus on the selected item
-        if (isInitialRender) {
+        if (isInitialSectionListRender) {
             return;
         }
 
@@ -401,7 +400,7 @@ function BaseSelectionList({
             {/* <View style={[styles.flex1, !isKeyboardShown && safeAreaPaddingBottomStyle, wrapperStyle]}> */}
             <SafeAreaConsumer>
                 {({safeAreaPaddingBottomStyle}) => (
-                    <View style={[styles.flex1, !isKeyboardShown && safeAreaPaddingBottomStyle, wrapperStyles, StyleUtils.parseStyleAsArray(containerStyle)]}>
+                    <View style={[styles.flex1, !isKeyboardShown && safeAreaPaddingBottomStyle, StyleUtils.parseStyleAsArray(containerStyle)]}>
                         {shouldShowTextInput && (
                             <View style={[styles.ph5, styles.pb3]}>
                                 <TextInput
@@ -479,7 +478,7 @@ function BaseSelectionList({
                                     viewabilityConfig={{viewAreaCoveragePercentThreshold: 95}}
                                     testID="selection-list"
                                     onLayout={scrollToFocusedIndexOnFirstRender}
-                                    style={!maxToRenderPerBatch && styles.opacity0}
+                                    style={(!maxToRenderPerBatch || isInitialSectionListRender) && styles.opacity0}
                                 />
                                 {children}
                             </>
diff --git a/src/components/TabSelector/TabLabel.tsx b/src/components/TabSelector/TabLabel.tsx
index 40f4dc30bb97..548b4ebccbc8 100644
--- a/src/components/TabSelector/TabLabel.tsx
+++ b/src/components/TabSelector/TabLabel.tsx
@@ -1,5 +1,6 @@
 import React from 'react';
-import {Animated, StyleSheet, Text, View} from 'react-native';
+import {Animated, StyleSheet, View} from 'react-native';
+import Text from '@components/Text';
 import useThemeStyles from '@hooks/useThemeStyles';
 
 type TabLabelProps = {
diff --git a/src/components/Text.tsx b/src/components/Text.tsx
index f8f8a90168fa..f436b9f4495a 100644
--- a/src/components/Text.tsx
+++ b/src/components/Text.tsx
@@ -3,7 +3,8 @@ import React from 'react';
 import {Text as RNText, StyleSheet} from 'react-native';
 import type {TextProps as RNTextProps, TextStyle} from 'react-native';
 import useTheme from '@hooks/useTheme';
-import fontFamily from '@styles/utils/fontFamily';
+import type {FontUtilsType} from '@styles/utils/FontUtils';
+import FontUtils from '@styles/utils/FontUtils';
 import variables from '@styles/variables';
 import type ChildrenProps from '@src/types/utils/ChildrenProps';
 
@@ -22,7 +23,7 @@ type TextProps = RNTextProps &
         children: React.ReactNode;
 
         /** The family of the font to use */
-        family?: keyof typeof fontFamily;
+        family?: keyof FontUtilsType['fontFamily']['platform'];
     };
 
 function Text({color, fontSize = variables.fontSizeNormal, textAlign = 'left', children, family = 'EXP_NEUE', style = {}, ...props}: TextProps, ref: ForwardedRef<RNText>) {
@@ -32,7 +33,7 @@ function Text({color, fontSize = variables.fontSizeNormal, textAlign = 'left', c
         color: color ?? theme.text,
         fontSize,
         textAlign,
-        fontFamily: fontFamily[family],
+        fontFamily: FontUtils.fontFamily.platform[family],
         ...StyleSheet.flatten(style),
     };
 
diff --git a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js
index 78c37e94196a..78f06b4075e0 100644
--- a/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js
+++ b/src/components/TextInput/BaseTextInput/baseTextInputPropTypes.js
@@ -26,6 +26,9 @@ const propTypes = {
     /** Customize the TextInput container */
     textInputContainerStyles: PropTypes.arrayOf(PropTypes.object),
 
+    /** Customizes the touchable wrapper of the TextInput component */
+    touchableInputWrapperStyle: PropTypes.arrayOf(PropTypes.object),
+
     /** Customize the main container */
     containerStyles: PropTypes.arrayOf(PropTypes.object),
 
diff --git a/src/components/TextInput/BaseTextInput/index.tsx b/src/components/TextInput/BaseTextInput/index.tsx
index d548041b0cf8..9c3899979aaa 100644
--- a/src/components/TextInput/BaseTextInput/index.tsx
+++ b/src/components/TextInput/BaseTextInput/index.tsx
@@ -37,6 +37,7 @@ function BaseTextInput(
         errorText = '',
         icon = null,
         textInputContainerStyles,
+        touchableInputWrapperStyle,
         containerStyles,
         inputStyle,
         forceActiveLabel = false,
@@ -287,7 +288,7 @@ function BaseTextInput(
                     style={[
                         autoGrowHeight && styles.autoGrowHeightInputContainer(textInputHeight, variables.componentSizeLarge, typeof maxHeight === 'number' ? maxHeight : 0),
                         !isMultiline && styles.componentHeightLarge,
-                        containerStyles,
+                        touchableInputWrapperStyle,
                     ]}
                 >
                     <View
diff --git a/src/components/TextInput/BaseTextInput/types.ts b/src/components/TextInput/BaseTextInput/types.ts
index 7530e7cbc2c4..21875d4dcc64 100644
--- a/src/components/TextInput/BaseTextInput/types.ts
+++ b/src/components/TextInput/BaseTextInput/types.ts
@@ -29,6 +29,9 @@ type CustomBaseTextInputProps = {
     /** Customize the TextInput container */
     textInputContainerStyles?: StyleProp<ViewStyle>;
 
+    /** Customizes the touchable wrapper of the TextInput component */
+    touchableInputWrapperStyle?: StyleProp<ViewStyle>;
+
     /** Customize the main container */
     containerStyles?: StyleProp<ViewStyle>;
 
diff --git a/src/components/TextPill.tsx b/src/components/TextPill.tsx
new file mode 100644
index 000000000000..6d473b189534
--- /dev/null
+++ b/src/components/TextPill.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import type {StyleProp, TextStyle} from 'react-native';
+// eslint-disable-next-line no-restricted-imports
+import useThemeStyles from '@hooks/useThemeStyles';
+import colors from '@styles/theme/colors';
+import Text from './Text';
+
+type TextPillProps = {
+    /** The color of the text/ */
+    color?: string;
+
+    /** Styles to apply to the text */
+    textStyles: StyleProp<TextStyle>;
+
+    children: React.ReactNode;
+};
+
+function TextPill({color, textStyles, children}: TextPillProps) {
+    const styles = useThemeStyles();
+
+    return <Text style={[{backgroundColor: color ?? colors.red, borderRadius: 6}, styles.overflowHidden, styles.textStrong, styles.ph2, styles.pv1, textStyles]}>{children}</Text>;
+}
+
+TextPill.displayName = 'TextPill';
+
+export default TextPill;
diff --git a/src/components/ThemeIllustrationsProvider.tsx b/src/components/ThemeIllustrationsProvider.tsx
index 3b83a00960a4..1d54d6782daf 100644
--- a/src/components/ThemeIllustrationsProvider.tsx
+++ b/src/components/ThemeIllustrationsProvider.tsx
@@ -1,7 +1,8 @@
 import React, {useMemo} from 'react';
 import useThemePreference from '@hooks/useThemePreference';
 import ThemeIllustrationsContext from '@styles/theme/context/ThemeIllustrationsContext';
-import Illustrations from '@styles/theme/illustrations';
+// eslint-disable-next-line no-restricted-imports
+import illustrations from '@styles/theme/illustrations';
 
 type ThemeIllustrationsProviderProps = {
     children: React.ReactNode;
@@ -10,9 +11,9 @@ type ThemeIllustrationsProviderProps = {
 function ThemeIllustrationsProvider({children}: ThemeIllustrationsProviderProps) {
     const themePreference = useThemePreference();
 
-    const illustrations = useMemo(() => Illustrations[themePreference], [themePreference]);
+    const themeIllustrations = useMemo(() => illustrations[themePreference], [themePreference]);
 
-    return <ThemeIllustrationsContext.Provider value={illustrations}>{children}</ThemeIllustrationsContext.Provider>;
+    return <ThemeIllustrationsContext.Provider value={themeIllustrations}>{children}</ThemeIllustrationsContext.Provider>;
 }
 
 ThemeIllustrationsProvider.displayName = 'ThemeIllustrationsProvider';
diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx
index f3d2d9e64ca7..76371bbbc9e1 100644
--- a/src/components/ThemeProvider.tsx
+++ b/src/components/ThemeProvider.tsx
@@ -2,6 +2,7 @@
 import PropTypes from 'prop-types';
 import React, {useMemo} from 'react';
 import useThemePreferenceWithStaticOverride from '@hooks/useThemePreferenceWithStaticOverride';
+// eslint-disable-next-line no-restricted-imports
 import themes from '@styles/theme';
 import ThemeContext from '@styles/theme/context/ThemeContext';
 import type {ThemePreferenceWithoutSystem} from '@styles/theme/types';
diff --git a/src/components/ThemeStylesProvider.tsx b/src/components/ThemeStylesProvider.tsx
index f0d25d9e4dde..3d9431e51446 100644
--- a/src/components/ThemeStylesProvider.tsx
+++ b/src/components/ThemeStylesProvider.tsx
@@ -1,7 +1,9 @@
 import React, {useMemo} from 'react';
 import useTheme from '@hooks/useTheme';
-import stylesGenerator from '@styles/index';
+// eslint-disable-next-line no-restricted-imports
+import styles from '@styles/index';
 import ThemeStylesContext from '@styles/theme/context/ThemeStylesContext';
+// eslint-disable-next-line no-restricted-imports
 import createStyleUtils from '@styles/utils';
 
 type ThemeStylesProviderProps = React.PropsWithChildren;
@@ -9,9 +11,9 @@ type ThemeStylesProviderProps = React.PropsWithChildren;
 function ThemeStylesProvider({children}: ThemeStylesProviderProps) {
     const theme = useTheme();
 
-    const styles = useMemo(() => stylesGenerator(theme), [theme]);
-    const StyleUtils = useMemo(() => createStyleUtils(theme, styles), [theme, styles]);
-    const contextValue = useMemo(() => ({styles, StyleUtils}), [styles, StyleUtils]);
+    const themeStyles = useMemo(() => styles(theme), [theme]);
+    const StyleUtils = useMemo(() => createStyleUtils(theme, themeStyles), [theme, themeStyles]);
+    const contextValue = useMemo(() => ({styles: themeStyles, StyleUtils}), [themeStyles, StyleUtils]);
 
     return <ThemeStylesContext.Provider value={contextValue}>{children}</ThemeStylesContext.Provider>;
 }
diff --git a/src/components/TimePicker/TimePicker.js b/src/components/TimePicker/TimePicker.js
index 20f52e2d08ee..a9b4566a390c 100644
--- a/src/components/TimePicker/TimePicker.js
+++ b/src/components/TimePicker/TimePicker.js
@@ -469,7 +469,7 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) {
                             setSelectionHour(e.nativeEvent.selection);
                         }}
                         style={styles.timePickerInput}
-                        containerStyles={[styles.timePickerHeight100]}
+                        touchableInputWrapperStyle={styles.timePickerHeight100}
                         selection={selectionHour}
                         showSoftInputOnFocus={false}
                     />
@@ -497,7 +497,7 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) {
                             setSelectionMinute(e.nativeEvent.selection);
                         }}
                         style={styles.timePickerInput}
-                        containerStyles={[styles.timePickerHeight100]}
+                        touchableInputWrapperStyle={styles.timePickerHeight100}
                         selection={selectionMinute}
                         showSoftInputOnFocus={false}
                     />
diff --git a/src/components/ValuePicker/ValueSelectorModal.js b/src/components/ValuePicker/ValueSelectorModal.js
index edc5a48d0bb3..79608edb3ef4 100644
--- a/src/components/ValuePicker/ValueSelectorModal.js
+++ b/src/components/ValuePicker/ValueSelectorModal.js
@@ -41,7 +41,7 @@ function ValueSelectorModal({items, selectedItem, label, isVisible, onClose, onI
     const [sectionsData, setSectionsData] = useState([]);
 
     useEffect(() => {
-        const itemsData = _.map(items, (item) => ({value: item.value, keyForList: item.value, text: item.label, isSelected: item === selectedItem}));
+        const itemsData = _.map(items, (item) => ({value: item.value, alternateText: item.description, keyForList: item.value, text: item.label, isSelected: item === selectedItem}));
         setSectionsData(itemsData);
     }, [items, selectedItem]);
 
diff --git a/src/components/ValuePicker/index.js b/src/components/ValuePicker/index.js
index b5ddaa7dcb73..a21402b9993f 100644
--- a/src/components/ValuePicker/index.js
+++ b/src/components/ValuePicker/index.js
@@ -29,6 +29,9 @@ const propTypes = {
     /** Callback to call when the input changes */
     onInputChange: PropTypes.func,
 
+    /** Text to display under the main menu item */
+    furtherDetails: PropTypes.string,
+
     /** A ref to forward to MenuItemWithTopDescription */
     forwardedRef: refPropTypes,
 };
@@ -40,10 +43,11 @@ const defaultProps = {
     items: {},
     forwardedRef: undefined,
     errorText: '',
+    furtherDetails: undefined,
     onInputChange: () => {},
 };
 
-function ValuePicker({value, label, items, placeholder, errorText, onInputChange, forwardedRef}) {
+function ValuePicker({value, label, items, placeholder, errorText, onInputChange, furtherDetails, forwardedRef}) {
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
     const [isPickerVisible, setIsPickerVisible] = useState(false);
@@ -76,6 +80,7 @@ function ValuePicker({value, label, items, placeholder, errorText, onInputChange
                 descriptionTextStyle={descStyle}
                 description={label}
                 onPress={showPickerModal}
+                furtherDetails={furtherDetails}
             />
             <View style={styles.ml5}>
                 <FormHelpMessage message={errorText} />
diff --git a/src/components/ViolationMessages.tsx b/src/components/ViolationMessages.tsx
new file mode 100644
index 000000000000..8eb555184596
--- /dev/null
+++ b/src/components/ViolationMessages.tsx
@@ -0,0 +1,26 @@
+import React, {useMemo} from 'react';
+import {View} from 'react-native';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import ViolationsUtils from '@libs/ViolationsUtils';
+import type {TransactionViolation} from '@src/types/onyx';
+import Text from './Text';
+
+export default function ViolationMessages({violations, isLast}: {violations: TransactionViolation[]; isLast?: boolean}) {
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+    const violationMessages = useMemo(() => violations.map((violation) => [violation.name, ViolationsUtils.getViolationTranslation(violation, translate)]), [translate, violations]);
+
+    return (
+        <View style={[styles.mtn2, isLast ? styles.mb2 : styles.mb1]}>
+            {violationMessages.map(([name, message]) => (
+                <Text
+                    key={`violationMessages.${name}`}
+                    style={[styles.ph5, styles.textLabelError]}
+                >
+                    {message}
+                </Text>
+            ))}
+        </View>
+    );
+}
diff --git a/src/hooks/useSingleExecution/index.ts b/src/hooks/useSingleExecution/index.ts
index f1be359f0355..909416dd848b 100644
--- a/src/hooks/useSingleExecution/index.ts
+++ b/src/hooks/useSingleExecution/index.ts
@@ -9,9 +9,9 @@ type Action<T extends unknown[]> = (...params: T) => void | Promise<void>;
  */
 export default function useSingleExecution() {
     const singleExecution = useCallback(
-        <T extends unknown[]>(action: Action<T>) =>
+        <T extends unknown[]>(action?: Action<T>) =>
             (...params: T) => {
-                action(...params);
+                action?.(...params);
             },
         [],
     );
diff --git a/src/hooks/useViolations.ts b/src/hooks/useViolations.ts
index 0f43abdff6e2..76d48158237b 100644
--- a/src/hooks/useViolations.ts
+++ b/src/hooks/useViolations.ts
@@ -2,12 +2,12 @@ import {useCallback, useMemo} from 'react';
 import type {TransactionViolation, ViolationName} from '@src/types/onyx';
 
 /**
- * Names of Fields where violations can occur
+ * Names of Fields where violations can occur.
  */
 type ViolationField = 'amount' | 'billable' | 'category' | 'comment' | 'date' | 'merchant' | 'receipt' | 'tag' | 'tax';
 
 /**
- * Map from Violation Names to the field where that violation can occur
+ * Map from Violation Names to the field where that violation can occur.
  */
 const violationFields: Record<ViolationName, ViolationField> = {
     allTagLevelsRequired: 'tag',
@@ -60,13 +60,12 @@ function useViolations(violations: TransactionViolation[]) {
         return violationGroups ?? new Map();
     }, [violations]);
 
-    const hasViolations = useCallback((field: ViolationField) => Boolean(violationsByField.get(field)?.length), [violationsByField]);
     const getViolationsForField = useCallback((field: ViolationField) => violationsByField.get(field) ?? [], [violationsByField]);
 
     return {
-        hasViolations,
         getViolationsForField,
     };
 }
 
 export default useViolations;
+export type {ViolationField};
diff --git a/src/languages/en.ts b/src/languages/en.ts
index e223dd0a9aaf..0b8983a8361b 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -74,6 +74,20 @@ import type {
     UpdatedTheDistanceParams,
     UpdatedTheRequestParams,
     UserIsAlreadyMemberParams,
+    ViolationsAutoReportedRejectedExpenseParams,
+    ViolationsCashExpenseWithNoReceiptParams,
+    ViolationsConversionSurchargeParams,
+    ViolationsInvoiceMarkupParams,
+    ViolationsMaxAgeParams,
+    ViolationsMissingTagParams,
+    ViolationsOverAutoApprovalLimitParams,
+    ViolationsOverCategoryLimitParams,
+    ViolationsOverLimitParams,
+    ViolationsPerDayLimitParams,
+    ViolationsReceiptRequiredParams,
+    ViolationsRterParams,
+    ViolationsTagOutOfPolicyParams,
+    ViolationsTaxOutOfPolicyParams,
     WaitingOnBankAccountParams,
     WalletProgramParams,
     WelcomeEnterMagicCodeParams,
@@ -627,6 +641,14 @@ export default {
         },
         waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Started settling up, payment is held until ${submitterDisplayName} enables their Wallet`,
         enableWallet: 'Enable Wallet',
+        hold: 'Hold',
+        holdEducationalTitle: 'This request is on',
+        whatIsHoldTitle: 'What is hold?',
+        whatIsHoldExplain: 'Hold is our way of streamlining financial collaboration. "Reject" is so harsh!',
+        holdIsTemporaryTitle: 'Hold is usually temporary',
+        holdIsTemporaryExplain: "Because hold is used to clear up confusion or clarify an important detail before payment, it's not permanent.",
+        deleteHoldTitle: "Delete whatever won't be paid",
+        deleteHoldExplain: "In the rare case where something is put on hold and won't be paid, it's on the person requesting payment to delete it.",
         set: 'set',
         changed: 'changed',
         removed: 'removed',
@@ -2035,38 +2057,49 @@ export default {
         copyReferralLink: 'Copy invite link',
     },
     violations: {
-        allTagLevelsRequired: 'dummy.violations.allTagLevelsRequired',
-        autoReportedRejectedExpense: 'dummy.violations.autoReportedRejectedExpense',
-        billableExpense: 'dummy.violations.billableExpense',
-        cashExpenseWithNoReceipt: 'dummy.violations.cashExpenseWithNoReceipt',
-        categoryOutOfPolicy: 'dummy.violations.categoryOutOfPolicy',
-        conversionSurcharge: 'dummy.violations.conversionSurcharge',
-        customUnitOutOfPolicy: 'dummy.violations.customUnitOutOfPolicy',
-        duplicatedTransaction: 'dummy.violations.duplicatedTransaction',
-        fieldRequired: 'dummy.violations.fieldRequired',
-        futureDate: 'dummy.violations.futureDate',
-        invoiceMarkup: 'dummy.violations.invoiceMarkup',
-        maxAge: 'dummy.violations.maxAge',
-        missingCategory: 'dummy.violations.missingCategory',
-        missingComment: 'dummy.violations.missingComment',
-        missingTag: 'dummy.violations.missingTag',
-        modifiedAmount: 'dummy.violations.modifiedAmount',
-        modifiedDate: 'dummy.violations.modifiedDate',
-        nonExpensiworksExpense: 'dummy.violations.nonExpensiworksExpense',
-        overAutoApprovalLimit: 'dummy.violations.overAutoApprovalLimit',
-        overCategoryLimit: 'dummy.violations.overCategoryLimit',
-        overLimit: 'dummy.violations.overLimit',
-        overLimitAttendee: 'dummy.violations.overLimitAttendee',
-        perDayLimit: 'dummy.violations.perDayLimit',
-        receiptNotSmartScanned: 'dummy.violations.receiptNotSmartScanned',
-        receiptRequired: 'dummy.violations.receiptRequired',
-        rter: 'dummy.violations.rter',
-        smartscanFailed: 'dummy.violations.smartscanFailed',
-        someTagLevelsRequired: 'dummy.violations.someTagLevelsRequired',
-        tagOutOfPolicy: 'dummy.violations.tagOutOfPolicy',
-        taxAmountChanged: 'dummy.violations.taxAmountChanged',
-        taxOutOfPolicy: 'dummy.violations.taxOutOfPolicy',
-        taxRateChanged: 'dummy.violations.taxRateChanged',
-        taxRequired: 'dummy.violations.taxRequired',
+        allTagLevelsRequired: 'All tags required',
+        autoReportedRejectedExpense: ({rejectReason, rejectedBy}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rejected this expense with the comment "${rejectReason}"`,
+        billableExpense: 'Billable no longer valid',
+        cashExpenseWithNoReceipt: ({amount}: ViolationsCashExpenseWithNoReceiptParams) => `Receipt required over ${amount}`,
+        categoryOutOfPolicy: 'Category no longer valid',
+        conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams) => `Applied ${surcharge}% conversion surcharge`,
+        customUnitOutOfPolicy: 'Unit no longer valid',
+        duplicatedTransaction: 'Potential duplicate',
+        fieldRequired: 'Report fields are required',
+        futureDate: 'Future date not allowed',
+        invoiceMarkup: ({invoiceMarkup}: ViolationsInvoiceMarkupParams) => `Marked up by ${invoiceMarkup}%`,
+        maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Date older than ${maxAge} days`,
+        missingCategory: 'Missing category',
+        missingComment: 'Description required for selected category',
+        missingTag: ({tagName}: ViolationsMissingTagParams) => `Missing ${tagName ?? 'tag'}`,
+        modifiedAmount: 'Amount greater than scanned receipt',
+        modifiedDate: 'Date differs from scanned receipt',
+        nonExpensiworksExpense: 'Non-Expensiworks expense',
+        overAutoApprovalLimit: ({formattedLimitAmount}: ViolationsOverAutoApprovalLimitParams) => `Expense exceeds auto approval limit of ${formattedLimitAmount}`,
+        overCategoryLimit: ({categoryLimit}: ViolationsOverCategoryLimitParams) => `Amount over ${categoryLimit}/person category limit`,
+        overLimit: ({amount}: ViolationsOverLimitParams) => `Amount over ${amount}/person limit`,
+        overLimitAttendee: ({amount}: ViolationsOverLimitParams) => `Amount over ${amount}/person limit`,
+        perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Amount over daily ${limit}/person category limit`,
+        receiptNotSmartScanned: 'Receipt not verified. Please confirm accuracy.',
+        receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Receipt required over ${amount} ${category ? ' category limit' : ''}`,
+        rter: ({brokenBankConnection, email, isAdmin, isTransactionOlderThan7Days, member}: ViolationsRterParams) => {
+            if (brokenBankConnection) {
+                return isAdmin
+                    ? `Can't auto-match receipt due to broken bank connection which ${email} needs to fix`
+                    : "Can't auto-match receipt due to broken bank connection which you need to fix";
+            }
+            if (!isTransactionOlderThan7Days) {
+                return isAdmin ? `Ask ${member} to mark as a cash or wait 7 days and try again` : 'Awaiting merge with card transaction.';
+            }
+
+            return '';
+        },
+        smartscanFailed: 'Receipt scanning failed. Enter details manually.',
+        someTagLevelsRequired: 'Missing tag',
+        tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams) => `${tagName ?? ''} no longer valid`,
+        taxAmountChanged: 'Tax amount was modified',
+        taxOutOfPolicy: ({taxName}: ViolationsTaxOutOfPolicyParams) => `${taxName ?? ''} no longer valid`,
+        taxRateChanged: 'Tax rate was modified',
+        taxRequired: 'Missing tax rate',
     },
 } satisfies TranslationBase;
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 42743f43a098..a1afde53482b 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -73,6 +73,20 @@ import type {
     UpdatedTheDistanceParams,
     UpdatedTheRequestParams,
     UserIsAlreadyMemberParams,
+    ViolationsAutoReportedRejectedExpenseParams,
+    ViolationsCashExpenseWithNoReceiptParams,
+    ViolationsConversionSurchargeParams,
+    ViolationsInvoiceMarkupParams,
+    ViolationsMaxAgeParams,
+    ViolationsMissingTagParams,
+    ViolationsOverAutoApprovalLimitParams,
+    ViolationsOverCategoryLimitParams,
+    ViolationsOverLimitParams,
+    ViolationsPerDayLimitParams,
+    ViolationsReceiptRequiredParams,
+    ViolationsRterParams,
+    ViolationsTagOutOfPolicyParams,
+    ViolationsTaxOutOfPolicyParams,
     WaitingOnBankAccountParams,
     WalletProgramParams,
     WelcomeEnterMagicCodeParams,
@@ -622,6 +636,14 @@ export default {
         },
         waitingOnEnabledWallet: ({submitterDisplayName}: WaitingOnBankAccountParams) => `Inició el pago, pero no se procesará hasta que ${submitterDisplayName} active su Billetera`,
         enableWallet: 'Habilitar Billetera',
+        hold: 'Hold',
+        holdEducationalTitle: 'Esta solicitud está en',
+        whatIsHoldTitle: '¿Qué es Hold?',
+        whatIsHoldExplain: 'Hold es nuestra forma de agilizar la colaboración financiera. ¡"Rechazar" es tan duro!',
+        holdIsTemporaryTitle: 'Hold suele ser temporal',
+        holdIsTemporaryExplain: 'Debido a que hold se utiliza para aclarar confusión o aclarar un detalle importante antes del pago, no es permanente.',
+        deleteHoldTitle: 'Eliminar lo que no se pagará',
+        deleteHoldExplain: 'En el raro caso de que algo se ponga en hold y no se pague, la persona que solicita el pago debe eliminarlo.',
         set: 'estableció',
         changed: 'cambió',
         removed: 'eliminó',
@@ -2522,38 +2544,50 @@ export default {
         copyReferralLink: 'Copiar enlace de invitación',
     },
     violations: {
-        allTagLevelsRequired: 'dummy.violations.allTagLevelsRequired',
-        autoReportedRejectedExpense: 'dummy.violations.autoReportedRejectedExpense',
-        billableExpense: 'dummy.violations.billableExpense',
-        cashExpenseWithNoReceipt: 'dummy.violations.cashExpenseWithNoReceipt',
-        categoryOutOfPolicy: 'dummy.violations.categoryOutOfPolicy',
-        conversionSurcharge: 'dummy.violations.conversionSurcharge',
-        customUnitOutOfPolicy: 'dummy.violations.customUnitOutOfPolicy',
-        duplicatedTransaction: 'dummy.violations.duplicatedTransaction',
-        fieldRequired: 'dummy.violations.fieldRequired',
-        futureDate: 'dummy.violations.futureDate',
-        invoiceMarkup: 'dummy.violations.invoiceMarkup',
-        maxAge: 'dummy.violations.maxAge',
-        missingCategory: 'dummy.violations.missingCategory',
-        missingComment: 'dummy.violations.missingComment',
-        missingTag: 'dummy.violations.missingTag',
-        modifiedAmount: 'dummy.violations.modifiedAmount',
-        modifiedDate: 'dummy.violations.modifiedDate',
-        nonExpensiworksExpense: 'dummy.violations.nonExpensiworksExpense',
-        overAutoApprovalLimit: 'dummy.violations.overAutoApprovalLimit',
-        overCategoryLimit: 'dummy.violations.overCategoryLimit',
-        overLimit: 'dummy.violations.overLimit',
-        overLimitAttendee: 'dummy.violations.overLimitAttendee',
-        perDayLimit: 'dummy.violations.perDayLimit',
-        receiptNotSmartScanned: 'dummy.violations.receiptNotSmartScanned',
-        receiptRequired: 'dummy.violations.receiptRequired',
-        rter: 'dummy.violations.rter',
-        smartscanFailed: 'dummy.violations.smartscanFailed',
-        someTagLevelsRequired: 'dummy.violations.someTagLevelsRequired',
-        tagOutOfPolicy: 'dummy.violations.tagOutOfPolicy',
-        taxAmountChanged: 'dummy.violations.taxAmountChanged',
-        taxOutOfPolicy: 'dummy.violations.taxOutOfPolicy',
-        taxRateChanged: 'dummy.violations.taxRateChanged',
-        taxRequired: 'dummy.violations.taxRequired',
+        allTagLevelsRequired: 'Todas las etiquetas son obligatorias',
+        autoReportedRejectedExpense: ({rejectedBy, rejectReason}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rechazó la solicitud y comentó "${rejectReason}"`,
+        billableExpense: 'La opción facturable ya no es válida',
+        cashExpenseWithNoReceipt: ({amount}: ViolationsCashExpenseWithNoReceiptParams) => `Recibo obligatorio para montos mayores a ${amount}`,
+        categoryOutOfPolicy: 'La categoría ya no es válida',
+        conversionSurcharge: ({surcharge}: ViolationsConversionSurchargeParams) => `${surcharge}% de recargo aplicado`,
+        customUnitOutOfPolicy: 'Unidad ya no es válida',
+        duplicatedTransaction: 'Potencial duplicado',
+        fieldRequired: 'Los campos del informe son obligatorios',
+        futureDate: 'Fecha futura no permitida',
+        invoiceMarkup: ({invoiceMarkup}: ViolationsInvoiceMarkupParams) => `Incrementado un ${invoiceMarkup}%`,
+        maxAge: ({maxAge}: ViolationsMaxAgeParams) => `Fecha de más de ${maxAge} días`,
+        missingCategory: 'Falta categoría',
+        missingComment: 'Descripción obligatoria para categoría seleccionada',
+        missingTag: ({tagName}: ViolationsMissingTagParams) => `Falta ${tagName}`,
+        modifiedAmount: 'Importe superior al del recibo escaneado',
+        modifiedDate: 'Fecha difiere del recibo escaneado',
+        nonExpensiworksExpense: 'Gasto no es de Expensiworks',
+        overAutoApprovalLimit: ({formattedLimitAmount}: ViolationsOverAutoApprovalLimitParams) => `Importe supera el límite de aprobación automática de ${formattedLimitAmount}`,
+        overCategoryLimit: ({categoryLimit}: ViolationsOverCategoryLimitParams) => `Importe supera el límite para la categoría de ${categoryLimit}/persona`,
+        overLimit: ({amount}: ViolationsOverLimitParams) => `Importe supera el límite de ${amount}/persona`,
+        overLimitAttendee: ({amount}: ViolationsOverLimitParams) => `Importe supera el límite de ${amount}/persona`,
+        perDayLimit: ({limit}: ViolationsPerDayLimitParams) => `Importe supera el límite diario de la categoría de ${limit}/persona`,
+        receiptNotSmartScanned: 'Recibo no verificado. Por favor, confirma su exactitud',
+        receiptRequired: ({amount, category}: ViolationsReceiptRequiredParams) => `Recibo obligatorio para importes sobre ${category ? 'el limite de la categoría de ' : ''}${amount}`,
+        rter: ({brokenBankConnection, isAdmin, email, isTransactionOlderThan7Days, member}: ViolationsRterParams) => {
+            if (brokenBankConnection) {
+                return isAdmin
+                    ? `No se puede adjuntar recibo debido a una conexión con su banco que ${email} necesita arreglar`
+                    : 'No se puede adjuntar recibo debido a una conexión con su banco que necesitas arreglar';
+            }
+            if (!isTransactionOlderThan7Days) {
+                return isAdmin
+                    ? `Pídele a ${member} que marque la transacción como efectivo o espera 7 días e intenta de nuevo`
+                    : 'Esperando adjuntar automáticamente a transacción de tarjeta de crédito';
+            }
+            return '';
+        },
+        smartscanFailed: 'No se pudo escanear el recibo. Introduce los datos manualmente',
+        someTagLevelsRequired: 'Falta etiqueta',
+        tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams) => `Le etiqueta ${tagName} ya no es válida`,
+        taxAmountChanged: 'El importe del impuesto fue modificado',
+        taxOutOfPolicy: ({taxName}: ViolationsTaxOutOfPolicyParams) => `${taxName} ya no es válido`,
+        taxRateChanged: 'La tasa de impuesto fue modificada',
+        taxRequired: 'Falta tasa de impuesto',
     },
 } satisfies EnglishTranslation;
diff --git a/src/languages/types.ts b/src/languages/types.ts
index dd2d339858b0..5b6e56a38689 100644
--- a/src/languages/types.ts
+++ b/src/languages/types.ts
@@ -209,6 +209,40 @@ type TagSelectionParams = {tagName: string};
 
 type WalletProgramParams = {walletProgram: string};
 
+type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string};
+
+type ViolationsCashExpenseWithNoReceiptParams = {amount: string};
+
+type ViolationsConversionSurchargeParams = {surcharge?: number};
+
+type ViolationsInvoiceMarkupParams = {invoiceMarkup?: number};
+
+type ViolationsMaxAgeParams = {maxAge: number};
+
+type ViolationsMissingTagParams = {tagName?: string};
+
+type ViolationsOverAutoApprovalLimitParams = {formattedLimitAmount: string};
+
+type ViolationsOverCategoryLimitParams = {categoryLimit: string};
+
+type ViolationsOverLimitParams = {amount: string};
+
+type ViolationsPerDayLimitParams = {limit: string};
+
+type ViolationsReceiptRequiredParams = {amount: string; category?: string};
+
+type ViolationsRterParams = {
+    brokenBankConnection: boolean;
+    isAdmin: boolean;
+    email?: string;
+    isTransactionOlderThan7Days: boolean;
+    member?: string;
+};
+
+type ViolationsTagOutOfPolicyParams = {tagName?: string};
+
+type ViolationsTaxOutOfPolicyParams = {taxName?: string};
+
 type TaskCreatedActionParams = {title: string};
 
 /* Translation Object types */
@@ -250,87 +284,101 @@ type TranslationFlatObject = {
 };
 
 export type {
-    TranslationBase,
-    TranslationPaths,
-    EnglishTranslation,
-    TranslationFlatObject,
+    ApprovedAmountParams,
     AddressLineParams,
-    CharacterLimitParams,
-    MaxParticipantsReachedParams,
-    ZipCodeExampleFormatParams,
-    LoggedInAsParams,
-    NewFaceEnterMagicCodeParams,
-    WelcomeEnterMagicCodeParams,
     AlreadySignedInParams,
-    GoBackMessageParams,
-    LocalTimeParams,
-    EditActionParams,
-    DeleteActionParams,
-    DeleteConfirmationParams,
-    BeginningOfChatHistoryDomainRoomPartOneParams,
+    AmountEachParams,
     BeginningOfChatHistoryAdminRoomPartOneParams,
     BeginningOfChatHistoryAnnounceRoomPartOneParams,
     BeginningOfChatHistoryAnnounceRoomPartTwo,
-    WelcomeToRoomParams,
-    ReportArchiveReasonsClosedParams,
-    ReportArchiveReasonsMergedParams,
-    ReportArchiveReasonsRemovedFromPolicyParams,
-    ReportArchiveReasonsPolicyDeletedParams,
-    RequestCountParams,
-    SettleExpensifyCardParams,
-    RequestAmountParams,
-    RequestedAmountMessageParams,
-    SplitAmountParams,
+    BeginningOfChatHistoryDomainRoomPartOneParams,
+    CanceledRequestParams,
+    CharacterLimitParams,
+    ConfirmThatParams,
+    DateShouldBeAfterParams,
+    DateShouldBeBeforeParams,
+    DeleteActionParams,
+    DeleteConfirmationParams,
     DidSplitAmountMessageParams,
-    AmountEachParams,
+    EditActionParams,
+    EnglishTranslation,
+    EnterMagicCodeParams,
+    FormattedMaxLengthParams,
+    GoBackMessageParams,
+    GoToRoomParams,
+    IncorrectZipFormatParams,
+    InstantSummaryParams,
+    LocalTimeParams,
+    LoggedInAsParams,
+    ManagerApprovedAmountParams,
+    ManagerApprovedParams,
+    MaxParticipantsReachedParams,
+    NewFaceEnterMagicCodeParams,
+    NoLongerHaveAccessParams,
+    NotAllowedExtensionParams,
+    NotYouParams,
+    OOOEventSummaryFullDayParams,
+    OOOEventSummaryPartialDayParams,
+    OurEmailProviderParams,
+    PaidElsewhereWithAmountParams,
+    PaidWithExpensifyWithAmountParams,
+    ParentNavigationSummaryParams,
     PayerOwesAmountParams,
     PayerOwesParams,
     PayerPaidAmountParams,
     PayerPaidParams,
-    ApprovedAmountParams,
-    ManagerApprovedParams,
-    ManagerApprovedAmountParams,
     PayerSettledParams,
-    WaitingOnBankAccountParams,
-    CanceledRequestParams,
-    SettledAfterAddedBankAccountParams,
-    PaidElsewhereWithAmountParams,
-    PaidWithExpensifyWithAmountParams,
-    ThreadRequestReportNameParams,
-    ThreadSentMoneyReportNameParams,
-    SizeExceededParams,
+    RemovedTheRequestParams,
+    RenamedRoomActionParams,
+    ReportArchiveReasonsClosedParams,
+    ReportArchiveReasonsMergedParams,
+    ReportArchiveReasonsPolicyDeletedParams,
+    ReportArchiveReasonsRemovedFromPolicyParams,
+    RequestAmountParams,
+    RequestCountParams,
+    RequestedAmountMessageParams,
     ResolutionConstraintsParams,
-    NotAllowedExtensionParams,
-    EnterMagicCodeParams,
-    TransferParams,
-    InstantSummaryParams,
-    NotYouParams,
-    DateShouldBeBeforeParams,
-    DateShouldBeAfterParams,
-    IncorrectZipFormatParams,
-    WeSentYouMagicSignInLinkParams,
-    ToValidateLoginParams,
-    NoLongerHaveAccessParams,
-    OurEmailProviderParams,
-    ConfirmThatParams,
-    UntilTimeParams,
-    StepCounterParams,
-    UserIsAlreadyMemberParams,
-    GoToRoomParams,
-    WelcomeNoteParams,
     RoomNameReservedErrorParams,
-    RenamedRoomActionParams,
     RoomRenamedToParams,
-    OOOEventSummaryFullDayParams,
-    OOOEventSummaryPartialDayParams,
-    ParentNavigationSummaryParams,
+    SetTheDistanceParams,
     SetTheRequestParams,
-    UpdatedTheRequestParams,
-    RemovedTheRequestParams,
-    FormattedMaxLengthParams,
+    SettleExpensifyCardParams,
+    SettledAfterAddedBankAccountParams,
+    SizeExceededParams,
+    SplitAmountParams,
+    StepCounterParams,
     TagSelectionParams,
-    SetTheDistanceParams,
+    TaskCreatedActionParams,
+    ThreadRequestReportNameParams,
+    ThreadSentMoneyReportNameParams,
+    ToValidateLoginParams,
+    TransferParams,
+    TranslationBase,
+    TranslationFlatObject,
+    TranslationPaths,
+    UntilTimeParams,
     UpdatedTheDistanceParams,
+    UpdatedTheRequestParams,
+    UserIsAlreadyMemberParams,
+    ViolationsAutoReportedRejectedExpenseParams,
+    ViolationsCashExpenseWithNoReceiptParams,
+    ViolationsConversionSurchargeParams,
+    ViolationsInvoiceMarkupParams,
+    ViolationsMaxAgeParams,
+    ViolationsMissingTagParams,
+    ViolationsOverAutoApprovalLimitParams,
+    ViolationsOverCategoryLimitParams,
+    ViolationsOverLimitParams,
+    ViolationsPerDayLimitParams,
+    ViolationsReceiptRequiredParams,
+    ViolationsRterParams,
+    ViolationsTagOutOfPolicyParams,
+    ViolationsTaxOutOfPolicyParams,
+    WaitingOnBankAccountParams,
     WalletProgramParams,
-    TaskCreatedActionParams,
+    WeSentYouMagicSignInLinkParams,
+    WelcomeEnterMagicCodeParams,
+    WelcomeNoteParams,
+    WelcomeToRoomParams,
+    ZipCodeExampleFormatParams,
 };
diff --git a/src/libs/DateUtils.ts b/src/libs/DateUtils.ts
index 08f61a50b645..c02d200a7c83 100644
--- a/src/libs/DateUtils.ts
+++ b/src/libs/DateUtils.ts
@@ -30,6 +30,7 @@ import Onyx from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
+import {timezoneBackwardMap} from '@src/TIMEZONES';
 import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails';
 import * as CurrentDate from './actions/CurrentDate';
 import * as Localize from './Localize';
@@ -697,6 +698,22 @@ function formatWithUTCTimeZone(datetime: string, dateFormat: string = CONST.DATE
     return '';
 }
 
+/**
+ *
+ * @param timezone
+ * function format unsupported timezone to supported timezone
+ * @returns Timezone
+ */
+function formatToSupportedTimezone(timezoneInput: Timezone): Timezone {
+    if (!timezoneInput?.selected) {
+        return timezoneInput;
+    }
+    return {
+        selected: timezoneBackwardMap[timezoneInput.selected] ?? timezoneInput.selected,
+        automatic: timezoneInput.automatic,
+    };
+}
+
 const DateUtils = {
     formatToDayOfWeek,
     formatToLongDateWithWeekday,
@@ -739,6 +756,7 @@ const DateUtils = {
     getWeekStartsOn,
     getWeekEndsOn,
     isTimeAtLeastOneMinuteInFuture,
+    formatToSupportedTimezone,
 };
 
 export default DateUtils;
diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts
index 4c441facdd46..d0f0b0dcfab6 100644
--- a/src/libs/EmojiTrie.ts
+++ b/src/libs/EmojiTrie.ts
@@ -1,38 +1,11 @@
 import emojis, {localeEmojis} from '@assets/emojis';
+import type {Emoji, HeaderEmoji, PickerEmoji} from '@assets/emojis/types';
 import CONST from '@src/CONST';
-import type IconAsset from '@src/types/utils/IconAsset';
 import Timing from './actions/Timing';
 import Trie from './Trie';
 
-type HeaderEmoji = {
-    code: string;
-    header: boolean;
-    icon: IconAsset;
-};
-
-type SimpleEmoji = {
-    code: string;
-    name: string;
-    types?: string[];
-};
-
-type Emoji = HeaderEmoji | SimpleEmoji;
-
-type LocalizedEmoji = {
-    name?: string;
-    keywords: string[];
-};
-
-type LocalizedEmojis = Record<string, LocalizedEmoji>;
-
-type Suggestion = {
-    code: string;
-    types?: string[];
-    name: string;
-};
-
 type EmojiMetaData = {
-    suggestions?: Suggestion[];
+    suggestions?: Emoji[];
     code?: string;
     types?: string[];
     name?: string;
@@ -56,7 +29,7 @@ type EmojiTrie = {
  * @param name The localized name of the emoji.
  * @param shouldPrependKeyword Prepend the keyword (instead of append) to the suggestions
  */
-function addKeywordsToTrie(trie: Trie<EmojiMetaData>, keywords: string[], item: SimpleEmoji, name: string, shouldPrependKeyword = false) {
+function addKeywordsToTrie(trie: Trie<EmojiMetaData>, keywords: string[], item: Emoji, name: string, shouldPrependKeyword = false) {
     keywords.forEach((keyword) => {
         const keywordNode = trie.search(keyword);
         if (!keywordNode) {
@@ -85,13 +58,13 @@ function getNameParts(name: string): string[] {
 
 function createTrie(lang: SupportedLanguage = CONST.LOCALES.DEFAULT): Trie<EmojiMetaData> {
     const trie = new Trie();
-    const langEmojis: LocalizedEmojis = localeEmojis[lang];
-    const defaultLangEmojis: LocalizedEmojis = localeEmojis[CONST.LOCALES.DEFAULT];
+    const langEmojis = localeEmojis[lang];
+    const defaultLangEmojis = localeEmojis[CONST.LOCALES.DEFAULT];
     const isDefaultLocale = lang === CONST.LOCALES.DEFAULT;
 
     emojis
-        .filter((item: Emoji): item is SimpleEmoji => !(item as HeaderEmoji).header)
-        .forEach((item: SimpleEmoji) => {
+        .filter((item: PickerEmoji): item is Emoji => !(item as HeaderEmoji).header)
+        .forEach((item: Emoji) => {
             const englishName = item.name;
             const localeName = langEmojis?.[item.code]?.name ?? englishName;
 
@@ -127,4 +100,4 @@ const emojiTrie: EmojiTrie = supportedLanguages.reduce((prev, cur) => ({...prev,
 Timing.end(CONST.TIMING.TRIE_INITIALIZATION);
 
 export default emojiTrie;
-export type {SimpleEmoji, SupportedLanguage};
+export type {SupportedLanguage};
diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts
index 06bbd5c871ed..02d1b34c69c1 100644
--- a/src/libs/EmojiUtils.ts
+++ b/src/libs/EmojiUtils.ts
@@ -2,11 +2,12 @@ import {getUnixTime} from 'date-fns';
 import Str from 'expensify-common/lib/str';
 import memoize from 'lodash/memoize';
 import Onyx from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
 import * as Emojis from '@assets/emojis';
 import type {Emoji, HeaderEmoji, PickerEmojis} from '@assets/emojis/types';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
-import type {FrequentlyUsedEmoji} from '@src/types/onyx';
+import type {FrequentlyUsedEmoji, Locale} from '@src/types/onyx';
 import type {ReportActionReaction, UsersReactions} from '@src/types/onyx/ReportActionReactions';
 import type IconAsset from '@src/types/utils/IconAsset';
 import type {SupportedLanguage} from './EmojiTrie';
@@ -48,13 +49,13 @@ const getEmojiName = (emoji: Emoji, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT):
 /**
  * Given an English emoji name, get its localized version
  */
-const getLocalizedEmojiName = (name: string, lang: 'en' | 'es'): string => {
+const getLocalizedEmojiName = (name: string, lang: OnyxEntry<Locale>): string => {
     if (lang === CONST.LOCALES.DEFAULT) {
         return name;
     }
 
     const emojiCode = Emojis.emojiNameTable[name]?.code ?? '';
-    return Emojis.localeEmojis[lang]?.[emojiCode]?.name ?? '';
+    return (lang && Emojis.localeEmojis[lang]?.[emojiCode]?.name) ?? '';
 };
 
 /**
@@ -438,8 +439,8 @@ const getPreferredSkinToneIndex = (value: string | number | null): number => {
  * Given an emoji object it returns the correct emoji code
  * based on the users preferred skin tone.
  */
-const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: number): string => {
-    if (emoji.types) {
+const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: OnyxEntry<string | number>): string => {
+    if (emoji.types && typeof preferredSkinTone === 'number') {
         const emojiCodeWithSkinTone = emoji.types[preferredSkinTone];
 
         // Note: it can happen that preferredSkinTone has a outdated format,
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index 58825ae6f2b1..7286615e6ba6 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -2,6 +2,7 @@ import React, {memo, useEffect, useRef} from 'react';
 import {View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import Onyx, {withOnyx} from 'react-native-onyx';
+import useStyleUtils from '@hooks/useStyleUtils';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
 import KeyboardShortcut from '@libs/KeyboardShortcut';
@@ -130,8 +131,9 @@ const modalScreenListeners = {
 
 function AuthScreens({lastUpdateIDAppliedToClient, session, lastOpenedPublicRoomID, isUsingMemoryOnlyKeys = false}: AuthScreensProps) {
     const styles = useThemeStyles();
+    const StyleUtils = useStyleUtils();
     const {isSmallScreenWidth} = useWindowDimensions();
-    const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth, styles);
+    const screenOptions = getRootNavigatorScreenOptions(isSmallScreenWidth, styles, StyleUtils);
     const isInitialRender = useRef(true);
 
     if (isInitialRender.current) {
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
index e177f1c2003d..4be1c988561b 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
@@ -283,6 +283,10 @@ const ReferralModalStackNavigator = createModalStackNavigator<ReferralDetailsNav
     [SCREENS.REFERRAL_DETAILS]: () => require('../../../pages/ReferralDetailsPage').default as React.ComponentType,
 });
 
+const ProcessMoneyRequestHoldStackNavigator = createModalStackNavigator({
+    [SCREENS.PROCESS_MONEY_REQUEST_HOLD_ROOT]: () => require('../../../pages/ProcessMoneyRequestHoldPage').default as React.ComponentType,
+});
+
 export {
     MoneyRequestModalStackNavigator,
     SplitDetailsModalStackNavigator,
@@ -309,4 +313,5 @@ export {
     RoomMembersModalStackNavigator,
     RoomInviteModalStackNavigator,
     ReferralModalStackNavigator,
+    ProcessMoneyRequestHoldStackNavigator,
 };
diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
index ca33b32113bb..7721a64adea9 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
@@ -1,6 +1,6 @@
 import type {StackScreenProps} from '@react-navigation/stack';
 import {createStackNavigator} from '@react-navigation/stack';
-import React, {useMemo} from 'react';
+import React, {useMemo, useRef} from 'react';
 import {View} from 'react-native';
 import NoDropZone from '@components/DragAndDrop/NoDropZone';
 import useThemeStyles from '@hooks/useThemeStyles';
@@ -20,10 +20,21 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) {
     const styles = useThemeStyles();
     const {isSmallScreenWidth} = useWindowDimensions();
     const screenOptions = useMemo(() => ModalNavigatorScreenOptions(styles), [styles]);
+    const isExecutingRef = useRef<boolean>(false);
 
     return (
         <NoDropZone>
-            {!isSmallScreenWidth && <Overlay onPress={navigation.goBack} />}
+            {!isSmallScreenWidth && (
+                <Overlay
+                    onPress={() => {
+                        if (isExecutingRef.current) {
+                            return;
+                        }
+                        isExecutingRef.current = true;
+                        navigation.goBack();
+                    }}
+                />
+            )}
             <View style={styles.RHPNavigatorContainer(isSmallScreenWidth)}>
                 <Stack.Navigator screenOptions={screenOptions}>
                     <Stack.Screen
@@ -118,6 +129,10 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) {
                         name={SCREENS.RIGHT_MODAL.PRIVATE_NOTES}
                         component={ModalStackNavigators.PrivateNotesModalStackNavigator}
                     />
+                    <Stack.Screen
+                        name="ProcessMoneyRequestHold"
+                        component={ModalStackNavigators.ProcessMoneyRequestHoldStackNavigator}
+                    />
                 </Stack.Navigator>
             </View>
         </NoDropZone>
diff --git a/src/libs/Navigation/AppNavigator/createModalCardStyleInterpolator.ts b/src/libs/Navigation/AppNavigator/createModalCardStyleInterpolator.ts
new file mode 100644
index 000000000000..7a88976b3e03
--- /dev/null
+++ b/src/libs/Navigation/AppNavigator/createModalCardStyleInterpolator.ts
@@ -0,0 +1,40 @@
+import type {StackCardInterpolatedStyle, StackCardInterpolationProps} from '@react-navigation/stack';
+import {Animated} from 'react-native';
+import type {StyleUtilsType} from '@styles/utils';
+import variables from '@styles/variables';
+
+type ModalCardStyleInterpolator = (
+    isSmallScreenWidth: boolean,
+    isFullScreenModal: boolean,
+    stackCardInterpolationProps: StackCardInterpolationProps,
+    outputRangeMultiplier?: number,
+) => StackCardInterpolatedStyle;
+type CreateModalCardStyleInterpolator = (StyleUtils: StyleUtilsType) => ModalCardStyleInterpolator;
+
+const createModalCardStyleInterpolator: CreateModalCardStyleInterpolator =
+    (StyleUtils) =>
+    (isSmallScreenWidth, isFullScreenModal, {current: {progress}, inverted, layouts: {screen}}, outputRangeMultiplier = 1) => {
+        const translateX = Animated.multiply(
+            progress.interpolate({
+                inputRange: [0, 1],
+                outputRange: [outputRangeMultiplier * (isSmallScreenWidth ? screen.width : variables.sideBarWidth), 0],
+                extrapolate: 'clamp',
+            }),
+            inverted,
+        );
+
+        const cardStyle = StyleUtils.getCardStyles(screen.width);
+
+        if (!isFullScreenModal || isSmallScreenWidth) {
+            cardStyle.transform = [{translateX}];
+        }
+
+        return {
+            containerStyle: {
+                overflow: 'hidden',
+            },
+            cardStyle,
+        };
+    };
+
+export default createModalCardStyleInterpolator;
diff --git a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts
index 1c0f06283226..31b8d49e74c0 100644
--- a/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts
+++ b/src/libs/Navigation/AppNavigator/getRootNavigatorScreenOptions.ts
@@ -1,9 +1,9 @@
 import type {StackCardInterpolationProps, StackNavigationOptions} from '@react-navigation/stack';
 import type {ThemeStyles} from '@styles/index';
-import getNavigationModalCardStyle from '@styles/utils/getNavigationModalCardStyles';
+import type {StyleUtilsType} from '@styles/utils';
 import variables from '@styles/variables';
 import CONFIG from '@src/CONFIG';
-import modalCardStyleInterpolator from './modalCardStyleInterpolator';
+import createModalCardStyleInterpolator from './createModalCardStyleInterpolator';
 
 type ScreenOptions = Record<string, StackNavigationOptions>;
 
@@ -17,75 +17,83 @@ const commonScreenOptions: StackNavigationOptions = {
 
 const SLIDE_LEFT_OUTPUT_RANGE_MULTIPLIER = -1;
 
-export default (isSmallScreenWidth: boolean, themeStyles: ThemeStyles): ScreenOptions => ({
-    rightModalNavigator: {
-        ...commonScreenOptions,
-        cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, false, props),
-        presentation: 'transparentModal',
-
-        // We want pop in RHP since there are some flows that would work weird otherwise
-        animationTypeForReplace: 'pop',
-        cardStyle: {
-            ...getNavigationModalCardStyle(),
-
-            // This is necessary to cover translated sidebar with overlay.
-            width: isSmallScreenWidth ? '100%' : '200%',
-            // Excess space should be on the left so we need to position from right.
-            right: 0,
+type GetRootNavigatorScreenOptions = (isSmallScreenWidth: boolean, styles: ThemeStyles, StyleUtils: StyleUtilsType) => ScreenOptions;
+
+const getRootNavigatorScreenOptions: GetRootNavigatorScreenOptions = (isSmallScreenWidth, themeStyles, StyleUtils) => {
+    const modalCardStyleInterpolator = createModalCardStyleInterpolator(StyleUtils);
+
+    return {
+        rightModalNavigator: {
+            ...commonScreenOptions,
+            cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, false, props),
+            presentation: 'transparentModal',
+
+            // We want pop in RHP since there are some flows that would work weird otherwise
+            animationTypeForReplace: 'pop',
+            cardStyle: {
+                ...StyleUtils.getNavigationModalCardStyle(),
+
+                // This is necessary to cover translated sidebar with overlay.
+                width: isSmallScreenWidth ? '100%' : '200%',
+                // Excess space should be on the left so we need to position from right.
+                right: 0,
+            },
         },
-    },
-    leftModalNavigator: {
-        ...commonScreenOptions,
-        cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, false, props, SLIDE_LEFT_OUTPUT_RANGE_MULTIPLIER),
-        presentation: 'transparentModal',
-
-        // We want pop in LHP since there are some flows that would work weird otherwise
-        animationTypeForReplace: 'pop',
-        cardStyle: {
-            ...getNavigationModalCardStyle(),
-
-            // This is necessary to cover translated sidebar with overlay.
-            width: isSmallScreenWidth ? '100%' : '200%',
-
-            // LHP should be displayed in place of the sidebar
-            left: isSmallScreenWidth ? 0 : -variables.sideBarWidth,
+        leftModalNavigator: {
+            ...commonScreenOptions,
+            cardStyleInterpolator: (props) => modalCardStyleInterpolator(isSmallScreenWidth, false, props, SLIDE_LEFT_OUTPUT_RANGE_MULTIPLIER),
+            presentation: 'transparentModal',
+
+            // We want pop in LHP since there are some flows that would work weird otherwise
+            animationTypeForReplace: 'pop',
+            cardStyle: {
+                ...StyleUtils.getNavigationModalCardStyle(),
+
+                // This is necessary to cover translated sidebar with overlay.
+                width: isSmallScreenWidth ? '100%' : '200%',
+
+                // LHP should be displayed in place of the sidebar
+                left: isSmallScreenWidth ? 0 : -variables.sideBarWidth,
+            },
         },
-    },
-    homeScreen: {
-        title: CONFIG.SITE_TITLE,
-        ...commonScreenOptions,
-        cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, false, props),
-
-        cardStyle: {
-            ...getNavigationModalCardStyle(),
-            width: isSmallScreenWidth ? '100%' : variables.sideBarWidth,
-
-            // We need to shift the sidebar to not be covered by the StackNavigator so it can be clickable.
-            marginLeft: isSmallScreenWidth ? 0 : -variables.sideBarWidth,
-            ...(isSmallScreenWidth ? {} : themeStyles.borderRight),
+        homeScreen: {
+            title: CONFIG.SITE_TITLE,
+            ...commonScreenOptions,
+            cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, false, props),
+
+            cardStyle: {
+                ...StyleUtils.getNavigationModalCardStyle(),
+                width: isSmallScreenWidth ? '100%' : variables.sideBarWidth,
+
+                // We need to shift the sidebar to not be covered by the StackNavigator so it can be clickable.
+                marginLeft: isSmallScreenWidth ? 0 : -variables.sideBarWidth,
+                ...(isSmallScreenWidth ? {} : themeStyles.borderRight),
+            },
         },
-    },
 
-    fullScreen: {
-        ...commonScreenOptions,
-        cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, true, props),
-        cardStyle: {
-            ...getNavigationModalCardStyle(),
+        fullScreen: {
+            ...commonScreenOptions,
+            cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, true, props),
+            cardStyle: {
+                ...StyleUtils.getNavigationModalCardStyle(),
 
-            // This is necessary to cover whole screen. Including translated sidebar.
-            marginLeft: isSmallScreenWidth ? 0 : -variables.sideBarWidth,
+                // This is necessary to cover whole screen. Including translated sidebar.
+                marginLeft: isSmallScreenWidth ? 0 : -variables.sideBarWidth,
+            },
         },
-    },
 
-    centralPaneNavigator: {
-        title: CONFIG.SITE_TITLE,
-        ...commonScreenOptions,
-        animationEnabled: isSmallScreenWidth,
-        cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, true, props),
+        centralPaneNavigator: {
+            title: CONFIG.SITE_TITLE,
+            ...commonScreenOptions,
+            animationEnabled: isSmallScreenWidth,
+            cardStyleInterpolator: (props: StackCardInterpolationProps) => modalCardStyleInterpolator(isSmallScreenWidth, true, props),
 
-        cardStyle: {
-            ...getNavigationModalCardStyle(),
-            paddingRight: isSmallScreenWidth ? 0 : variables.sideBarWidth,
+            cardStyle: {
+                ...StyleUtils.getNavigationModalCardStyle(),
+                paddingRight: isSmallScreenWidth ? 0 : variables.sideBarWidth,
+            },
         },
-    },
-});
+    };
+};
+
+export default getRootNavigatorScreenOptions;
diff --git a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts b/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts
deleted file mode 100644
index fd59b02e724d..000000000000
--- a/src/libs/Navigation/AppNavigator/modalCardStyleInterpolator.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import type {StackCardInterpolatedStyle, StackCardInterpolationProps} from '@react-navigation/stack';
-import {Animated} from 'react-native';
-import getCardStyles from '@styles/utils/cardStyles';
-import variables from '@styles/variables';
-
-export default (
-    isSmallScreenWidth: boolean,
-    isFullScreenModal: boolean,
-    {current: {progress}, inverted, layouts: {screen}}: StackCardInterpolationProps,
-    outputRangeMultiplier = 1,
-): StackCardInterpolatedStyle => {
-    const translateX = Animated.multiply(
-        progress.interpolate({
-            inputRange: [0, 1],
-            outputRange: [outputRangeMultiplier * (isSmallScreenWidth ? screen.width : variables.sideBarWidth), 0],
-            extrapolate: 'clamp',
-        }),
-        inverted,
-    );
-
-    const cardStyle = getCardStyles(screen.width);
-
-    if (!isFullScreenModal || isSmallScreenWidth) {
-        cardStyle.transform = [{translateX}];
-    }
-
-    return {
-        containerStyle: {
-            overflow: 'hidden',
-        },
-        cardStyle,
-    };
-};
diff --git a/src/libs/Navigation/linkingConfig.ts b/src/libs/Navigation/linkingConfig.ts
index 3b2350afcc43..5f2a607b5f78 100644
--- a/src/libs/Navigation/linkingConfig.ts
+++ b/src/libs/Navigation/linkingConfig.ts
@@ -492,6 +492,11 @@ const linkingConfig: LinkingOptions<RootStackParamList> = {
                             [SCREENS.REFERRAL_DETAILS]: ROUTES.REFERRAL_DETAILS_MODAL.route,
                         },
                     },
+                    ProcessMoneyRequestHold: {
+                        screens: {
+                            ProcessMoneyRequestHold_Root: ROUTES.PROCESS_MONEY_REQUEST_HOLD,
+                        },
+                    },
                 },
             },
         },
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 505bc82180f4..90f5361f11f4 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -331,6 +331,10 @@ type ReferralDetailsNavigatorParamList = {
     [SCREENS.REFERRAL_DETAILS]: undefined;
 };
 
+type ProcessMoneyRequestHoldNavigatorParamList = {
+    [SCREENS.PROCESS_MONEY_REQUEST_HOLD_ROOT]: undefined;
+};
+
 type PrivateNotesNavigatorParamList = {
     [SCREENS.PRIVATE_NOTES.VIEW]: {
         reportID: string;
@@ -372,6 +376,7 @@ type RightModalNavigatorParamList = {
     [SCREENS.RIGHT_MODAL.FLAG_COMMENT]: NavigatorScreenParams<FlagCommentNavigatorParamList>;
     [SCREENS.RIGHT_MODAL.EDIT_REQUEST]: NavigatorScreenParams<EditRequestNavigatorParamList>;
     [SCREENS.RIGHT_MODAL.SIGN_IN]: NavigatorScreenParams<SignInNavigatorParamList>;
+    [SCREENS.RIGHT_MODAL.PROCESS_MONEY_REQUEST_HOLD]: NavigatorScreenParams<ProcessMoneyRequestHoldNavigatorParamList>;
     [SCREENS.RIGHT_MODAL.REFERRAL]: NavigatorScreenParams<ReferralDetailsNavigatorParamList>;
     [SCREENS.RIGHT_MODAL.PRIVATE_NOTES]: NavigatorScreenParams<PrivateNotesNavigatorParamList>;
 };
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index 9efff6f2fdb7..0dc12c720f31 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -322,9 +322,9 @@ function getSearchText(report, reportName, personalDetailList, isChatRoomOrPolic
 
             Array.prototype.push.apply(searchTerms, chatRoomSubtitle.split(/[,\s]/));
         } else {
-            const participantAccountIDs = report.participantAccountIDs || [];
-            for (let i = 0; i < participantAccountIDs.length; i++) {
-                const accountID = participantAccountIDs[i];
+            const visibleChatMemberAccountIDs = report.visibleChatMemberAccountIDs || [];
+            for (let i = 0; i < visibleChatMemberAccountIDs.length; i++) {
+                const accountID = visibleChatMemberAccountIDs[i];
 
                 if (allPersonalDetails[accountID] && allPersonalDetails[accountID].login) {
                     searchTerms = searchTerms.concat(allPersonalDetails[accountID].login);
@@ -506,7 +506,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, {
         result.isPinned = report.isPinned;
         result.iouReportID = report.iouReportID;
         result.keyForList = String(report.reportID);
-        result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs || []);
+        result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs || []);
         result.isWaitingOnBankAccount = report.isWaitingOnBankAccount;
         result.policyID = report.policyID;
 
@@ -573,7 +573,7 @@ function getPolicyExpenseReportOption(report) {
     const expenseReport = policyExpenseReports[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`];
 
     const option = createOption(
-        expenseReport.participantAccountIDs,
+        expenseReport.visibleChatMemberAccountIDs,
         allPersonalDetails,
         expenseReport,
         {},
@@ -1342,7 +1342,7 @@ function getOptions(
         const isTaskReport = ReportUtils.isTaskReport(report);
         const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
         const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
-        const accountIDs = report.participantAccountIDs || [];
+        const accountIDs = report.visibleChatMemberAccountIDs || [];
 
         if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) {
             return;
diff --git a/src/libs/PopoverWithMeasuredContentUtils.ts b/src/libs/PopoverWithMeasuredContentUtils.ts
index b932249211be..5d5af6f7c83d 100644
--- a/src/libs/PopoverWithMeasuredContentUtils.ts
+++ b/src/libs/PopoverWithMeasuredContentUtils.ts
@@ -1,5 +1,5 @@
-import roundToNearestMultipleOfFour from '@styles/utils/roundToNearestMultipleOfFour';
 import variables from '@styles/variables';
+import roundToNearestMultipleOfFour from './roundToNearestMultipleOfFour';
 
 /**
  * Compute the amount that the Context menu's Anchor needs to be horizontally shifted
diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts
index 734c40271208..bcba68a3a0bd 100644
--- a/src/libs/ReceiptUtils.ts
+++ b/src/libs/ReceiptUtils.ts
@@ -66,7 +66,7 @@ function getThumbnailAndImageURIs(transaction: Transaction, receiptPath: string
         image = ReceiptSVG;
     }
 
-    const isLocalFile = path.startsWith('blob:') || path.startsWith('file:');
+    const isLocalFile = typeof path === 'number' || path.startsWith('blob:') || path.startsWith('file:') || path.startsWith('/');
     return {thumbnail: image, image: path, isLocalFile};
 }
 
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 33a18b8534df..0e159cf69095 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -211,6 +211,7 @@ type OptimisticChatReport = Pick<
     | 'parentReportActionID'
     | 'parentReportID'
     | 'participantAccountIDs'
+    | 'visibleChatMemberAccountIDs'
     | 'policyID'
     | 'reportID'
     | 'reportName'
@@ -265,6 +266,7 @@ type OptimisticTaskReport = Pick<
     | 'description'
     | 'ownerAccountID'
     | 'participantAccountIDs'
+    | 'visibleChatMemberAccountIDs'
     | 'managerID'
     | 'type'
     | 'parentReportID'
@@ -302,6 +304,7 @@ type OptimisticIOUReport = Pick<
     | 'managerID'
     | 'ownerAccountID'
     | 'participantAccountIDs'
+    | 'visibleChatMemberAccountIDs'
     | 'reportID'
     | 'state'
     | 'stateNum'
@@ -543,7 +546,8 @@ function isExpenseReport(report: OnyxEntry<Report> | EmptyObject): boolean {
 /**
  * Checks if a report is an IOU report.
  */
-function isIOUReport(report: OnyxEntry<Report>): boolean {
+function isIOUReport(reportOrID: OnyxEntry<Report> | string | EmptyObject): boolean {
+    const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID;
     return report?.type === CONST.REPORT.TYPE.IOU;
 }
 
@@ -599,14 +603,15 @@ function isReportManager(report: OnyxEntry<Report>): boolean {
 /**
  * Checks if the supplied report has been approved
  */
-function isReportApproved(report: OnyxEntry<Report> | EmptyObject): boolean {
+function isReportApproved(reportOrID: OnyxEntry<Report> | string | EmptyObject): boolean {
+    const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null : reportOrID;
     return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS.APPROVED;
 }
 
 /**
  * Checks if the supplied report is an expense report in Open state and status.
  */
-function isDraftExpenseReport(report: OnyxEntry<Report>): boolean {
+function isDraftExpenseReport(report: OnyxEntry<Report> | EmptyObject): boolean {
     return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN;
 }
 
@@ -713,9 +718,17 @@ function isControlPolicyExpenseChat(report: OnyxEntry<Report>): boolean {
 }
 
 /**
- * Whether the provided report belongs to a Control or Collect policy
+ * Whether the provided report belongs to a Free, Collect or Control policy
  */
 function isGroupPolicy(report: OnyxEntry<Report>): boolean {
+    const policyType = getPolicyType(report, allPolicies);
+    return policyType === CONST.POLICY.TYPE.CORPORATE || policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.FREE;
+}
+
+/**
+ * Whether the provided report belongs to a Control or Collect policy
+ */
+function isPaidGroupPolicy(report: OnyxEntry<Report>): boolean {
     const policyType = getPolicyType(report, allPolicies);
     return policyType === CONST.POLICY.TYPE.CORPORATE || policyType === CONST.POLICY.TYPE.TEAM;
 }
@@ -723,8 +736,8 @@ function isGroupPolicy(report: OnyxEntry<Report>): boolean {
 /**
  * Whether the provided report belongs to a Control or Collect policy and is an expense chat
  */
-function isGroupPolicyExpenseChat(report: OnyxEntry<Report>): boolean {
-    return isPolicyExpenseChat(report) && isGroupPolicy(report);
+function isPaidGroupPolicyExpenseChat(report: OnyxEntry<Report>): boolean {
+    return isPolicyExpenseChat(report) && isPaidGroupPolicy(report);
 }
 
 /**
@@ -737,8 +750,8 @@ function isControlPolicyExpenseReport(report: OnyxEntry<Report>): boolean {
 /**
  * Whether the provided report belongs to a Control or Collect policy and is an expense report
  */
-function isGroupPolicyExpenseReport(report: OnyxEntry<Report>): boolean {
-    return isExpenseReport(report) && isGroupPolicy(report);
+function isPaidGroupPolicyExpenseReport(report: OnyxEntry<Report>): boolean {
+    return isExpenseReport(report) && isPaidGroupPolicy(report);
 }
 
 /**
@@ -816,7 +829,7 @@ function isConciergeChatReport(report: OnyxEntry<Report>): boolean {
 /**
  * Returns true if report is still being processed
  */
-function isProcessingReport(report: OnyxEntry<Report>): boolean {
+function isProcessingReport(report: OnyxEntry<Report> | EmptyObject): boolean {
     return report?.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && report?.statusNum === CONST.REPORT.STATUS.SUBMITTED;
 }
 
@@ -1847,9 +1860,13 @@ function getTransactionDetails(transaction: OnyxEntry<Transaction>, createdDateF
  *    - the current user is the requestor and is not settled yet
  * - in case of expense report
  *    - the current user is the requestor and is not settled yet
- *    - or the user is an admin on the policy the expense report is tied to
+ *    - the current user is the manager of the report
+ *    - or the current user is an admin on the policy the expense report is tied to
+ *
+ *    This is used in conjunction with canEditRestrictedField to control editing of specific fields like amount, currency, created, receipt, and distance.
+ *    On its own, it only controls allowing/disallowing navigating to the editing pages or showing/hiding the 'Edit' icon on report actions
  */
-function canEditMoneyRequest(reportAction: OnyxEntry<ReportAction>, fieldToEdit = ''): boolean {
+function canEditMoneyRequest(reportAction: OnyxEntry<ReportAction>): boolean {
     const isDeleted = ReportActionsUtils.isDeletedAction(reportAction);
 
     if (isDeleted) {
@@ -1872,52 +1889,76 @@ function canEditMoneyRequest(reportAction: OnyxEntry<ReportAction>, fieldToEdit
     }
 
     const moneyRequestReport = getReport(String(moneyRequestReportID));
-    const isReportSettled = isSettled(moneyRequestReport?.reportID);
-    const isApproved = isReportApproved(moneyRequestReport);
-    const isAdmin = isExpenseReport(moneyRequestReport) && (getPolicy(moneyRequestReport?.policyID ?? '')?.role ?? '') === CONST.POLICY.ROLE.ADMIN;
     const isRequestor = currentUserAccountID === reportAction?.actorAccountID;
 
-    if (isAdmin && !isRequestor && fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) {
-        return false;
+    if (isIOUReport(moneyRequestReport)) {
+        return isProcessingReport(moneyRequestReport) && isRequestor;
     }
 
-    if (isAdmin) {
+    const policy = getPolicy(moneyRequestReport?.policyID ?? '');
+    const isAdmin = policy.role === CONST.POLICY.ROLE.ADMIN;
+    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)) {
         return true;
     }
 
-    return !isApproved && !isReportSettled && isRequestor;
+    return !isReportApproved(moneyRequestReport) && !isSettled(moneyRequestReport?.reportID) && isRequestor;
 }
 
 /**
  * Checks if the current user can edit the provided property of a money request
  *
  */
-function canEditFieldOfMoneyRequest(
-    reportAction: OnyxEntry<ReportAction>,
-    reportID: string,
-    fieldToEdit: ValueOf<typeof CONST.EDIT_REQUEST_FIELD>,
-    transaction: OnyxEntry<Transaction>,
-): boolean {
+function canEditFieldOfMoneyRequest(reportAction: OnyxEntry<ReportAction>, fieldToEdit: ValueOf<typeof CONST.EDIT_REQUEST_FIELD>): boolean {
     // A list of fields that cannot be edited by anyone, once a money request has been settled
-    const nonEditableFieldsWhenSettled: string[] = [
+    const restrictedFields: string[] = [
         CONST.EDIT_REQUEST_FIELD.AMOUNT,
         CONST.EDIT_REQUEST_FIELD.CURRENCY,
+        CONST.EDIT_REQUEST_FIELD.MERCHANT,
         CONST.EDIT_REQUEST_FIELD.DATE,
         CONST.EDIT_REQUEST_FIELD.RECEIPT,
         CONST.EDIT_REQUEST_FIELD.DISTANCE,
     ];
 
-    // Checks if this user has permissions to edit this money request
-    if (!canEditMoneyRequest(reportAction, fieldToEdit)) {
-        return false; // User doesn't have permission to edit
+    if (!canEditMoneyRequest(reportAction)) {
+        return false;
+    }
+
+    // If we're editing fields such as category, tag, description, etc. the check above should be enough for handling the permission
+    if (!restrictedFields.includes(fieldToEdit)) {
+        return true;
     }
-    if (!isEmpty(transaction) && fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT && TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction)) {
+
+    const iouMessage = reportAction?.originalMessage as IOUMessage;
+    const moneyRequestReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouMessage?.IOUReportID}`] ?? ({} as Report);
+    const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${iouMessage?.IOUTransactionID}`] ?? ({} as Transaction);
+
+    if (isSettled(String(moneyRequestReport.reportID)) || isReportApproved(String(moneyRequestReport.reportID))) {
         return false;
     }
 
-    // Checks if the report is settled
-    // Checks if the provided property is a restricted one
-    return !isSettled(reportID) || !nonEditableFieldsWhenSettled.includes(fieldToEdit);
+    if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.AMOUNT || fieldToEdit === CONST.EDIT_REQUEST_FIELD.CURRENCY) {
+        if (TransactionUtils.isCardTransaction(transaction)) {
+            return false;
+        }
+
+        if (TransactionUtils.isDistanceRequest(transaction)) {
+            const policy = getPolicy(moneyRequestReport?.reportID ?? '');
+            const isAdmin = isExpenseReport(moneyRequestReport) && policy.role === CONST.POLICY.ROLE.ADMIN;
+            const isManager = isExpenseReport(moneyRequestReport) && currentUserAccountID === moneyRequestReport?.managerID;
+
+            return isAdmin || isManager;
+        }
+    }
+
+    if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.RECEIPT) {
+        const isRequestor = currentUserAccountID === reportAction?.actorAccountID;
+        return !TransactionUtils.isReceiptBeingScanned(transaction) && !TransactionUtils.isDistanceRequest(transaction) && isRequestor;
+    }
+
+    return true;
 }
 
 /**
@@ -2057,7 +2098,7 @@ function getReportPreviewMessage(
 
     const formattedAmount = CurrencyUtils.convertToDisplayString(totalAmount, report.currency);
 
-    if (isReportApproved(report) && isGroupPolicy(report)) {
+    if (isReportApproved(report) && isPaidGroupPolicy(report)) {
         return Localize.translateLocal('iou.managerApprovedAmount', {
             manager: payerName ?? '',
             amount: formattedAmount,
@@ -2505,6 +2546,10 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number
     const formattedTotal = CurrencyUtils.convertToDisplayString(total, currency);
     const personalDetails = getPersonalDetailsForAccountID(payerAccountID);
     const payerEmail = 'login' in personalDetails ? personalDetails.login : '';
+
+    // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same
+    const participantsAccountIDs = [payeeAccountID, payerAccountID];
+
     return {
         type: CONST.REPORT.TYPE.IOU,
         cachedTotal: formattedTotal,
@@ -2512,7 +2557,8 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number
         currency,
         managerID: payerAccountID,
         ownerAccountID: payeeAccountID,
-        participantAccountIDs: [payeeAccountID, payerAccountID],
+        participantAccountIDs: participantsAccountIDs,
+        visibleChatMemberAccountIDs: participantsAccountIDs,
         reportID: generateReportID(),
         state: CONST.REPORT.STATE.SUBMITTED,
         stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.PROCESSING,
@@ -3048,7 +3094,9 @@ function buildOptimisticChatReport(
         ownerAccountID: ownerAccountID || CONST.REPORT.OWNER_ACCOUNT_ID_FAKE,
         parentReportActionID,
         parentReportID,
+        // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same
         participantAccountIDs: participantList,
+        visibleChatMemberAccountIDs: participantList,
         policyID,
         reportID: generateReportID(),
         reportName,
@@ -3250,12 +3298,16 @@ function buildOptimisticTaskReport(
     description?: string,
     policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE,
 ): OptimisticTaskReport {
+    // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same
+    const participantsAccountIDs = assigneeAccountID && assigneeAccountID !== ownerAccountID ? [assigneeAccountID] : [];
+
     return {
         reportID: generateReportID(),
         reportName: title,
         description,
         ownerAccountID,
-        participantAccountIDs: assigneeAccountID && assigneeAccountID !== ownerAccountID ? [assigneeAccountID] : [],
+        participantAccountIDs: participantsAccountIDs,
+        visibleChatMemberAccountIDs: participantsAccountIDs,
         managerID: assigneeAccountID,
         type: CONST.REPORT.TYPE.TASK,
         parentReportID,
@@ -4082,6 +4134,8 @@ function getTaskAssigneeChatOnyxData(
 
 /**
  * Returns an array of the participants Ids of a report
+ *
+ * @deprecated Use getVisibleMemberIDs instead
  */
 function getParticipantsIDs(report: OnyxEntry<Report>): number[] {
     if (!report) {
@@ -4099,6 +4153,25 @@ function getParticipantsIDs(report: OnyxEntry<Report>): number[] {
     return participants;
 }
 
+/**
+ * Returns an array of the visible member accountIDs for a report*
+ */
+function getVisibleMemberIDs(report: OnyxEntry<Report>): number[] {
+    if (!report) {
+        return [];
+    }
+
+    const visibleChatMemberAccountIDs = report.visibleChatMemberAccountIDs ?? [];
+
+    // Build participants list for IOU/expense reports
+    if (isMoneyRequestReport(report)) {
+        const onlyTruthyValues = [report.managerID, report.ownerAccountID, ...visibleChatMemberAccountIDs].filter(Boolean) as number[];
+        const onlyUnique = [...new Set([...onlyTruthyValues])];
+        return onlyUnique;
+    }
+    return visibleChatMemberAccountIDs;
+}
+
 /**
  * Return iou report action display message
  */
@@ -4309,10 +4382,12 @@ export {
     formatReportLastMessageText,
     chatIncludesConcierge,
     isPolicyExpenseChat,
+    isGroupPolicy,
+    isPaidGroupPolicy,
     isControlPolicyExpenseChat,
     isControlPolicyExpenseReport,
-    isGroupPolicyExpenseChat,
-    isGroupPolicyExpenseReport,
+    isPaidGroupPolicyExpenseChat,
+    isPaidGroupPolicyExpenseReport,
     getIconsForParticipants,
     getIcons,
     getRoomWelcomeMessage,
@@ -4417,6 +4492,7 @@ export {
     getTransactionDetails,
     getTaskAssigneeChatOnyxData,
     getParticipantsIDs,
+    getVisibleMemberIDs,
     canEditMoneyRequest,
     canEditFieldOfMoneyRequest,
     buildTransactionThread,
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index db16cf5cb552..6e46ec320066 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -127,10 +127,10 @@ function getOrderedReportIDs(
         [currentReportId, allReports, betas, policies, priorityMode, allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1],
         (key, value: unknown) => {
             /**
-             *  Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' not to overwhelm a cached key value with huge data,
+             *  Exclude some properties not to overwhelm a cached key value with huge data,
              *  which we don't need to store in a cacheKey
              */
-            if (key === 'participantAccountIDs' || key === 'participants' || key === 'lastMessageText') {
+            if (key === 'participantAccountIDs' || key === 'participants' || key === 'lastMessageText' || key === 'visibleChatMemberAccountIDs') {
                 return undefined;
             }
 
@@ -276,6 +276,7 @@ function getOptionData(
         isExpenseRequest: false,
         isWaitingOnBankAccount: false,
         isAllowedToComment: true,
+        isDeletedParentAction: false,
     };
     const participantPersonalDetailList: PersonalDetails[] = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs ?? [], personalDetails));
     const personalDetail = participantPersonalDetailList[0] ?? {};
@@ -304,13 +305,14 @@ function getOptionData(
     result.isPinned = report.isPinned;
     result.iouReportID = report.iouReportID;
     result.keyForList = String(report.reportID);
-    result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs ?? []);
+    result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs ?? []);
     result.hasOutstandingChildRequest = report.hasOutstandingChildRequest;
     result.parentReportID = report.parentReportID ?? '';
     result.isWaitingOnBankAccount = report.isWaitingOnBankAccount;
     result.notificationPreference = report.notificationPreference;
     result.isAllowedToComment = ReportUtils.canUserPerformWriteAction(report);
     result.chatType = report.chatType;
+    result.isDeletedParentAction = report.isDeletedParentAction;
 
     const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || ReportUtils.isExpenseReport(report);
     const subtitle = ReportUtils.getChatRoomSubtitle(report);
diff --git a/src/libs/ViolationsUtils.ts b/src/libs/ViolationsUtils.ts
index 748f0ed86b7f..2637686e726b 100644
--- a/src/libs/ViolationsUtils.ts
+++ b/src/libs/ViolationsUtils.ts
@@ -1,7 +1,9 @@
 import reject from 'lodash/reject';
 import Onyx from 'react-native-onyx';
+import type {TranslationPaths} from '@src/languages/types';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {PolicyCategories, PolicyTags, Transaction, TransactionViolation} from '@src/types/onyx';
+import type {Phrase, PhraseParameters} from './Localize';
 
 const ViolationsUtils = {
     /**
@@ -80,6 +82,104 @@ const ViolationsUtils = {
             value: newTransactionViolations,
         };
     },
+    /**
+     * Gets the translated message for each violation type.
+     *
+     * Necessary because `translate` throws a type error if you attempt to pass it a template strings, when the
+     * possible values could be either translation keys that resolve to  strings or translation keys that resolve to
+     * functions.
+     */
+    getViolationTranslation(
+        violation: TransactionViolation,
+        translate: <TKey extends TranslationPaths>(phraseKey: TKey, ...phraseParameters: PhraseParameters<Phrase<TKey>>) => string,
+    ): string {
+        switch (violation.name) {
+            case 'allTagLevelsRequired':
+                return translate('violations.allTagLevelsRequired');
+            case 'autoReportedRejectedExpense':
+                return translate('violations.autoReportedRejectedExpense', {
+                    rejectedBy: violation.data?.rejectedBy ?? '',
+                    rejectReason: violation.data?.rejectReason ?? '',
+                });
+            case 'billableExpense':
+                return translate('violations.billableExpense');
+            case 'cashExpenseWithNoReceipt':
+                return translate('violations.cashExpenseWithNoReceipt', {amount: violation.data?.amount ?? ''});
+            case 'categoryOutOfPolicy':
+                return translate('violations.categoryOutOfPolicy');
+            case 'conversionSurcharge':
+                return translate('violations.conversionSurcharge', {surcharge: violation.data?.surcharge});
+            case 'customUnitOutOfPolicy':
+                return translate('violations.customUnitOutOfPolicy');
+            case 'duplicatedTransaction':
+                return translate('violations.duplicatedTransaction');
+            case 'fieldRequired':
+                return translate('violations.fieldRequired');
+            case 'futureDate':
+                return translate('violations.futureDate');
+            case 'invoiceMarkup':
+                return translate('violations.invoiceMarkup', {invoiceMarkup: violation.data?.invoiceMarkup});
+            case 'maxAge':
+                return translate('violations.maxAge', {maxAge: violation.data?.maxAge ?? 0});
+            case 'missingCategory':
+                return translate('violations.missingCategory');
+            case 'missingComment':
+                return translate('violations.missingComment');
+            case 'missingTag':
+                return translate('violations.missingTag', {tagName: violation.data?.tagName});
+            case 'modifiedAmount':
+                return translate('violations.modifiedAmount');
+            case 'modifiedDate':
+                return translate('violations.modifiedDate');
+            case 'nonExpensiworksExpense':
+                return translate('violations.nonExpensiworksExpense');
+            case 'overAutoApprovalLimit':
+                return translate('violations.overAutoApprovalLimit', {formattedLimitAmount: violation.data?.formattedLimitAmount ?? ''});
+            case 'overCategoryLimit':
+                return translate('violations.overCategoryLimit', {categoryLimit: violation.data?.categoryLimit ?? ''});
+            case 'overLimit':
+                return translate('violations.overLimit', {amount: violation.data?.amount ?? ''});
+            case 'overLimitAttendee':
+                return translate('violations.overLimitAttendee', {amount: violation.data?.amount ?? ''});
+            case 'perDayLimit':
+                return translate('violations.perDayLimit', {limit: violation.data?.limit ?? ''});
+            case 'receiptNotSmartScanned':
+                return translate('violations.receiptNotSmartScanned');
+            case 'receiptRequired':
+                return translate('violations.receiptRequired', {
+                    amount: violation.data?.amount ?? '0',
+                    category: violation.data?.category ?? '',
+                });
+            case 'rter':
+                return translate('violations.rter', {
+                    brokenBankConnection: violation.data?.brokenBankConnection ?? false,
+                    isAdmin: violation.data?.isAdmin ?? false,
+                    email: violation.data?.email,
+                    isTransactionOlderThan7Days: Boolean(violation.data?.isTransactionOlderThan7Days),
+                    member: violation.data?.member,
+                });
+            case 'smartscanFailed':
+                return translate('violations.smartscanFailed');
+            case 'someTagLevelsRequired':
+                return translate('violations.someTagLevelsRequired');
+            case 'tagOutOfPolicy':
+                return translate('violations.tagOutOfPolicy', {tagName: violation.data?.tagName});
+            case 'taxAmountChanged':
+                return translate('violations.taxAmountChanged');
+            case 'taxOutOfPolicy':
+                return translate('violations.taxOutOfPolicy', {taxName: violation.data?.taxName});
+            case 'taxRateChanged':
+                return translate('violations.taxRateChanged');
+            case 'taxRequired':
+                return translate('violations.taxRequired');
+            default:
+                // The interpreter should never get here because the switch cases should be exhaustive.
+                // If typescript is showing an error on the assertion below it means the switch statement is out of
+                // sync with the `ViolationNames` type, and one or the other needs to be updated.
+                // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+                return violation.name as never;
+        }
+    },
 };
 
 export default ViolationsUtils;
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index f88bfe09e516..798d94bfb0e0 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -7,6 +7,7 @@ import Onyx from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import * as API from '@libs/API';
 import * as Browser from '@libs/Browser';
+import DateUtils from '@libs/DateUtils';
 import Log from '@libs/Log';
 import getCurrentUrl from '@libs/Navigation/currentUrl';
 import Navigation from '@libs/Navigation/Navigation';
@@ -448,6 +449,8 @@ function openProfile(personalDetails: OnyxTypes.PersonalDetails) {
         };
     }
 
+    newTimezoneData = DateUtils.formatToSupportedTimezone(newTimezoneData);
+
     type OpenProfileParams = {
         timezone: string;
     };
diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts
index 064c52f9b7ef..56a5f34c0b8e 100644
--- a/src/libs/actions/EmojiPickerAction.ts
+++ b/src/libs/actions/EmojiPickerAction.ts
@@ -1,6 +1,9 @@
 import React from 'react';
-import type {View} from 'react-native';
+import type {MutableRefObject} from 'react';
+import type {TextInput, View} from 'react-native';
 import type {ValueOf} from 'type-fest';
+import type {Emoji} from '@assets/emojis/types';
+import type {CloseContextMenuCallback} from '@components/Reactions/QuickEmojiReactions/types';
 import type CONST from '@src/CONST';
 
 type AnchorOrigin = {
@@ -8,23 +11,31 @@ type AnchorOrigin = {
     vertical: ValueOf<typeof CONST.MODAL.ANCHOR_ORIGIN_VERTICAL>;
 };
 
+type EmojiPopoverAnchor = MutableRefObject<View | HTMLDivElement | TextInput | null>;
+
+type OnWillShowPicker = (callback: CloseContextMenuCallback) => void;
+
+type OnModalHideValue = () => void;
+
 // TODO: Move this type to src/components/EmojiPicker/EmojiPicker.js once it is converted to TS
 type EmojiPickerRef = {
     showEmojiPicker: (
-        onModalHideValue?: () => void,
-        onEmojiSelectedValue?: () => void,
-        emojiPopoverAnchor?: React.MutableRefObject<View | HTMLElement | null>,
+        onModalHideValue: OnModalHideValue,
+        onEmojiSelectedValue: OnEmojiSelected,
+        emojiPopoverAnchor: EmojiPopoverAnchor,
         anchorOrigin?: AnchorOrigin,
-        onWillShow?: () => void,
+        onWillShow?: OnWillShowPicker,
         id?: string,
     ) => void;
     isActive: (id: string) => boolean;
     clearActive: () => void;
-    hideEmojiPicker: (isNavigating: boolean) => void;
+    hideEmojiPicker: (isNavigating?: boolean) => void;
     isEmojiPickerVisible: boolean;
     resetEmojiPopoverAnchor: () => void;
 };
 
+type OnEmojiSelected = (emojiCode: string, emojiObject: Emoji) => void;
+
 const emojiPickerRef = React.createRef<EmojiPickerRef>();
 
 /**
@@ -37,7 +48,14 @@ const emojiPickerRef = React.createRef<EmojiPickerRef>();
  * @param onWillShow - Run a callback when Popover will show
  * @param id - Unique id for EmojiPicker
  */
-function showEmojiPicker(onModalHide = () => {}, onEmojiSelected = () => {}, emojiPopoverAnchor = undefined, anchorOrigin = undefined, onWillShow = () => {}, id = undefined) {
+function showEmojiPicker(
+    onModalHide: OnModalHideValue,
+    onEmojiSelected: OnEmojiSelected,
+    emojiPopoverAnchor: EmojiPopoverAnchor,
+    anchorOrigin?: AnchorOrigin,
+    onWillShow: OnWillShowPicker = () => {},
+    id?: string,
+) {
     if (!emojiPickerRef.current) {
         return;
     }
@@ -92,3 +110,4 @@ function resetEmojiPopoverAnchor() {
 }
 
 export {emojiPickerRef, showEmojiPicker, hideEmojiPicker, isActive, clearActive, isEmojiPickerVisible, resetEmojiPopoverAnchor};
+export type {AnchorOrigin};
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index b999435cc3e7..fb4e9f02f1b6 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -22,6 +22,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils';
 import * as ReportUtils from '@libs/ReportUtils';
 import * as TransactionUtils from '@libs/TransactionUtils';
 import * as UserUtils from '@libs/UserUtils';
+import ViolationsUtils from '@libs/ViolationsUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
@@ -306,6 +307,27 @@ function getReceiptError(receipt, filename, isScanRequest = true) {
         : ErrorUtils.getMicroSecondOnyxErrorObject({error: CONST.IOU.RECEIPT_ERROR, source: receipt.source, filename});
 }
 
+/**
+ * Builds the Onyx data for a money request.
+ *
+ * @param {Object} chatReport
+ * @param {Object} iouReport
+ * @param {Object} transaction
+ * @param {Object} chatCreatedAction
+ * @param {Object} iouCreatedAction
+ * @param {Object} iouAction
+ * @param {Object} optimisticPersonalDetailListAction
+ * @param {Object} reportPreviewAction
+ * @param {Array} optimisticPolicyRecentlyUsedCategories
+ * @param {Array} optimisticPolicyRecentlyUsedTags
+ * @param {boolean} isNewChatReport
+ * @param {boolean} isNewIOUReport
+ * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts)
+ * @param {Array} policyTags
+ * @param {Array} policyCategories
+ * @param {Boolean} hasOutstandingChildRequest
+ * @returns {Array} - An array containing the optimistic data, success data, and failure data.
+ */
 function buildOnyxDataForMoneyRequest(
     chatReport,
     iouReport,
@@ -319,6 +341,9 @@ function buildOnyxDataForMoneyRequest(
     optimisticPolicyRecentlyUsedTags,
     isNewChatReport,
     isNewIOUReport,
+    policy,
+    policyTags,
+    policyCategories,
     hasOutstandingChildRequest = false,
 ) {
     const isScanRequest = TransactionUtils.isScanRequest(transaction);
@@ -556,6 +581,22 @@ function buildOnyxDataForMoneyRequest(
         },
     ];
 
+    // Policy won't be set for P2P cases for which we don't need to compute violations
+    if (!policy || !policy.id) {
+        return [optimisticData, successData, failureData];
+    }
+
+    const violationsOnyxData = ViolationsUtils.getViolationsOnyxData(transaction, [], policy.requiresTag, policyTags, policy.requiresCategory, policyCategories);
+
+    if (violationsOnyxData) {
+        optimisticData.push(violationsOnyxData);
+        failureData.push({
+            onyxMethod: Onyx.METHOD.SET,
+            key: `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`,
+            value: [],
+        });
+    }
+
     return [optimisticData, successData, failureData];
 }
 
@@ -577,6 +618,9 @@ function buildOnyxDataForMoneyRequest(
  * @param {String} [category]
  * @param {String} [tag]
  * @param {Boolean} [billable]
+ * @param {Object} [policy]
+ * @param {Object} [policyTags]
+ * @param {Object} [policyCategories]
  * @returns {Object} data
  * @returns {String} data.payerEmail
  * @returns {Object} data.iouReport
@@ -606,6 +650,9 @@ function getMoneyRequestInformation(
     category = undefined,
     tag = undefined,
     billable = undefined,
+    policy = undefined,
+    policyTags = undefined,
+    policyCategories = undefined,
 ) {
     const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login);
     const payerAccountID = Number(participant.accountID);
@@ -639,7 +686,6 @@ function getMoneyRequestInformation(
     let needsToBeManuallySubmitted = false;
     let isFromPaidPolicy = false;
     if (isPolicyExpenseChat) {
-        const policy = ReportUtils.getPolicy(chatReport.policyID);
         isFromPaidPolicy = PolicyUtils.isPaidGroupPolicy(policy);
 
         // If the scheduled submit is turned off on the policy, user needs to manually submit the report which is indicated by GBR in LHN
@@ -774,6 +820,9 @@ function getMoneyRequestInformation(
         optimisticPolicyRecentlyUsedTags,
         isNewChatReport,
         isNewIOUReport,
+        policy,
+        policyTags,
+        policyCategories,
         hasOutstandingChildRequest,
     );
 
@@ -808,9 +857,12 @@ function getMoneyRequestInformation(
  * @param {String} currency
  * @param {String} merchant
  * @param {Boolean} [billable]
- * @param {Obejct} validWaypoints
+ * @param {Object} validWaypoints
+ * @param {Object} policy - May be undefined, an empty object, or an object matching the Policy type (src/types/onyx/Policy.ts)
+ * @param {Array} policyTags
+ * @param {Array} policyCategories
  */
-function createDistanceRequest(report, participant, comment, created, category, tag, amount, currency, merchant, billable, validWaypoints) {
+function createDistanceRequest(report, participant, comment, created, category, tag, amount, currency, merchant, billable, validWaypoints, policy, policyTags, policyCategories) {
     // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
     const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
     const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report;
@@ -834,6 +886,9 @@ function createDistanceRequest(report, participant, comment, created, category,
         category,
         tag,
         billable,
+        policy,
+        policyTags,
+        policyCategories,
     );
     API.write(
         'CreateDistanceRequest',
@@ -926,7 +981,10 @@ function getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, t
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThread.reportID}`,
             value: {
-                [updatedReportAction.reportActionID]: updatedReportAction,
+                [updatedReportAction.reportActionID]: {
+                    ...updatedReportAction,
+                    errors: ErrorUtils.getMicroSecondOnyxError('iou.error.genericEditFailureMessage'),
+                },
             },
         });
 
@@ -1051,6 +1109,21 @@ function updateMoneyRequestDate(transactionID, transactionThreadReportID, val) {
     API.write('UpdateMoneyRequestDate', params, onyxData);
 }
 
+/**
+ * Updates the created date of a money request
+ *
+ * @param {String} transactionID
+ * @param {Number} transactionThreadReportID
+ * @param {String} tag
+ */
+function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag) {
+    const transactionChanges = {
+        tag,
+    };
+    const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true);
+    API.write('UpdateMoneyRequestTag', params, onyxData);
+}
+
 /**
  * Edits an existing distance request
  *
@@ -1086,6 +1159,9 @@ function updateDistanceRequest(transactionID, transactionThreadReportID, transac
  * @param {String} [taxCode]
  * @param {Number} [taxAmount]
  * @param {Boolean} [billable]
+ * @param {Object} [policy]
+ * @param {Object} [policyTags]
+ * @param {Object} [policyCategories]
  */
 function requestMoney(
     report,
@@ -1103,12 +1179,33 @@ function requestMoney(
     taxCode = '',
     taxAmount = 0,
     billable = undefined,
+    policy = undefined,
+    policyTags = undefined,
+    policyCategories = undefined,
 ) {
     // If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
     const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
     const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report;
     const {payerAccountID, payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} =
-        getMoneyRequestInformation(currentChatReport, participant, comment, amount, currency, created, merchant, payeeAccountID, payeeEmail, receipt, undefined, category, tag, billable);
+        getMoneyRequestInformation(
+            currentChatReport,
+            participant,
+            comment,
+            amount,
+            currency,
+            created,
+            merchant,
+            payeeAccountID,
+            payeeEmail,
+            receipt,
+            undefined,
+            category,
+            tag,
+            billable,
+            policy,
+            policyTags,
+            policyCategories,
+        );
     const activeReportID = isMoneyRequestReport ? report.reportID : chatReport.reportID;
 
     API.write(
@@ -2340,7 +2437,7 @@ function deleteMoneyRequest(transactionID, reportAction, isSingleTransactionView
     // STEP 2: Decide if we need to:
     // 1. Delete the transactionThread - delete if there are no visible comments in the thread
     // 2. Update the moneyRequestPreview to show [Deleted request] - update if the transactionThread exists AND it isn't being deleted
-    const shouldDeleteTransactionThread = transactionThreadID ? ReportActionsUtils.getLastVisibleMessage(transactionThreadID).lastMessageText.length === 0 : false;
+    const shouldDeleteTransactionThread = transactionThreadID ? lodashGet(reportAction, 'childVisibleActionCount', 0) === 0 : false;
     const shouldShowDeletedRequestMessage = transactionThreadID && !shouldDeleteTransactionThread;
 
     // STEP 3: Update the IOU reportAction and decide if the iouReport should be deleted. We delete the iouReport if there are no visible comments left in the report.
@@ -3490,6 +3587,7 @@ export {
     setUpDistanceTransaction,
     navigateToNextPage,
     updateMoneyRequestDate,
+    updateMoneyRequestTag,
     updateMoneyRequestAmountAndCurrency,
     replaceReceipt,
     detachReceipt,
diff --git a/src/libs/actions/OnyxUpdates.ts b/src/libs/actions/OnyxUpdates.ts
index 2291e6d0af4a..a0772db49585 100644
--- a/src/libs/actions/OnyxUpdates.ts
+++ b/src/libs/actions/OnyxUpdates.ts
@@ -7,6 +7,7 @@ import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {OnyxUpdateEvent, OnyxUpdatesFromServer, Request} from '@src/types/onyx';
 import type Response from '@src/types/onyx/Response';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import * as QueuedOnyxUpdates from './QueuedOnyxUpdates';
 
 // This key needs to be separate from ONYXKEYS.ONYX_UPDATES_FROM_SERVER so that it can be updated without triggering the callback when the server IDs are updated. If that
@@ -74,7 +75,18 @@ function apply({lastUpdateID, type, request, response, updates}: OnyxUpdatesFrom
     Log.info(`[OnyxUpdateManager] Applying update type: ${type} with lastUpdateID: ${lastUpdateID}`, false, {command: request?.command});
 
     if (lastUpdateID && lastUpdateIDAppliedToClient && Number(lastUpdateID) <= lastUpdateIDAppliedToClient) {
-        Log.info('[OnyxUpdateManager] Update received was older or the same than current state, returning without applying the updates', false);
+        Log.info('[OnyxUpdateManager] Update received was older than or the same as current state, returning without applying the updates other than successData and failureData');
+
+        // In this case, we're already received the OnyxUpdate included in the response, so we don't need to apply it again.
+        // However, we do need to apply the successData and failureData from the request
+        if (type === CONST.ONYX_UPDATE_TYPES.HTTPS && request && response && (!isEmptyObject(request.successData) || !isEmptyObject(request.failureData))) {
+            Log.info('[OnyxUpdateManager] Applying success or failure data from request without onyxData from response');
+
+            // We use a spread here instead of delete because we don't want to change the response for other middlewares
+            const {onyxData, ...responseWithoutOnyxData} = response;
+            return applyHTTPSOnyxUpdates(request, responseWithoutOnyxData);
+        }
+
         return Promise.resolve();
     }
     if (lastUpdateID && (lastUpdateIDAppliedToClient === null || Number(lastUpdateID) > lastUpdateIDAppliedToClient)) {
diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts
index 20c6d8fef247..0e1662da4d55 100644
--- a/src/libs/actions/PersonalDetails.ts
+++ b/src/libs/actions/PersonalDetails.ts
@@ -3,6 +3,7 @@ import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
 import Onyx from 'react-native-onyx';
 import * as API from '@libs/API';
 import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
+import DateUtils from '@libs/DateUtils';
 import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
 import Navigation from '@libs/Navigation/Navigation';
 import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
@@ -12,6 +13,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import type {DateOfBirthForm, PersonalDetails, PersonalDetailsList, PrivatePersonalDetails} from '@src/types/onyx';
 import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails';
+import * as Session from './Session';
 
 type FirstAndLastName = {
     firstName: string;
@@ -263,6 +265,10 @@ function updateAddress(street: string, street2: string, city: string, state: str
  * selected timezone if set to automatically update.
  */
 function updateAutomaticTimezone(timezone: Timezone) {
+    if (Session.isAnonymousUser()) {
+        return;
+    }
+
     if (!currentUserAccountID) {
         return;
     }
@@ -270,9 +276,9 @@ function updateAutomaticTimezone(timezone: Timezone) {
     type UpdateAutomaticTimezoneParams = {
         timezone: string;
     };
-
+    const formatedTimezone = DateUtils.formatToSupportedTimezone(timezone);
     const parameters: UpdateAutomaticTimezoneParams = {
-        timezone: JSON.stringify(timezone),
+        timezone: JSON.stringify(formatedTimezone),
     };
 
     API.write('UpdateAutomaticTimezone', parameters, {
@@ -282,7 +288,7 @@ function updateAutomaticTimezone(timezone: Timezone) {
                 key: ONYXKEYS.PERSONAL_DETAILS_LIST,
                 value: {
                     [currentUserAccountID]: {
-                        timezone,
+                        timezone: formatedTimezone,
                     },
                 },
             },
diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js
index f148f27b3713..212fd3ead898 100644
--- a/src/libs/actions/Policy.js
+++ b/src/libs/actions/Policy.js
@@ -274,11 +274,15 @@ function buildAnnounceRoomMembersOnyxData(policyID, accountIDs) {
         return announceRoomMembers;
     }
 
+    // Everyone in special policy rooms is visible
+    const participantAccountIDs = [...announceReport.participantAccountIDs, ...accountIDs];
+
     announceRoomMembers.onyxOptimisticData.push({
         onyxMethod: Onyx.METHOD.MERGE,
         key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`,
         value: {
-            participantAccountIDs: [...announceReport.participantAccountIDs, ...accountIDs],
+            participantAccountIDs,
+            visibleChatMemberAccountIDs: participantAccountIDs,
         },
     });
 
@@ -287,6 +291,7 @@ function buildAnnounceRoomMembersOnyxData(policyID, accountIDs) {
         key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`,
         value: {
             participantAccountIDs: announceReport.participantAccountIDs,
+            visibleChatMemberAccountIDs: announceReport.visibleChatMemberAccountIDs,
         },
     });
     return announceRoomMembers;
@@ -315,6 +320,7 @@ function removeOptimisticAnnounceRoomMembers(policyID, accountIDs) {
         key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`,
         value: {
             participantAccountIDs: [...remainUsers],
+            visibleChatMemberAccountIDs: [...remainUsers],
         },
     });
 
@@ -323,6 +329,7 @@ function removeOptimisticAnnounceRoomMembers(policyID, accountIDs) {
         key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`,
         value: {
             participantAccountIDs: announceReport.participantAccountIDs,
+            visibleChatMemberAccountIDs: announceReport.visibleChatMemberAccountIDs,
         },
     });
     return announceRoomMembers;
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 13e4222f942f..cef236a3e1bb 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -2120,6 +2120,24 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
         },
     ];
 
+    if (report.parentReportID && report.parentReportActionID) {
+        optimisticData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`,
+            value: {[report.parentReportActionID]: {childReportNotificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}},
+        });
+        successData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`,
+            value: {[report.parentReportActionID]: {childReportNotificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN}},
+        });
+        failureData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`,
+            value: {[report.parentReportActionID]: {childReportNotificationPreference: report.notificationPreference}},
+        });
+    }
+
     type LeaveRoomParameters = {
         reportID: string;
     };
@@ -2134,6 +2152,8 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
         const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]);
         const chat = ReportUtils.getChatByParticipants(participantAccountIDs);
         if (chat?.reportID) {
+            // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
+            Navigation.goBack(ROUTES.HOME);
             Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID));
         }
     }
@@ -2152,6 +2172,9 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record<string
     const participantAccountIDsAfterInvitation = [...new Set([...(report?.participantAccountIDs ?? []), ...inviteeAccountIDs])].filter(
         (accountID): accountID is number => typeof accountID === 'number',
     );
+    const visibleMemberAccountIDsAfterInvitation = [...new Set([...(report?.visibleChatMemberAccountIDs ?? []), ...inviteeAccountIDs])].filter(
+        (accountID): accountID is number => typeof accountID === 'number',
+    );
 
     type PersonalDetailsOnyxData = {
         optimisticData: OnyxUpdate[];
@@ -2168,6 +2191,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record<string
             key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
             value: {
                 participantAccountIDs: participantAccountIDsAfterInvitation,
+                visibleChatMemberAccountIDs: visibleMemberAccountIDsAfterInvitation,
             },
         },
         ...newPersonalDetailsOnyxData.optimisticData,
@@ -2181,6 +2205,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record<string
             key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
             value: {
                 participantAccountIDs: report.participantAccountIDs,
+                visibleChatMemberAccountIDs: report.visibleChatMemberAccountIDs,
             },
         },
         ...newPersonalDetailsOnyxData.failureData,
@@ -2204,6 +2229,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) {
     const report = allReports?.[reportID];
 
     const participantAccountIDsAfterRemoval = report?.participantAccountIDs?.filter((id: number) => !targetAccountIDs.includes(id));
+    const visibleChatMemberAccountIDsAfterRemoval = report?.visibleChatMemberAccountIDs?.filter((id: number) => !targetAccountIDs.includes(id));
 
     const optimisticData: OnyxUpdate[] = [
         {
@@ -2211,6 +2237,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) {
             key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
             value: {
                 participantAccountIDs: participantAccountIDsAfterRemoval,
+                visibleChatMemberAccountIDs: visibleChatMemberAccountIDsAfterRemoval,
             },
         },
     ];
@@ -2221,6 +2248,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) {
             key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
             value: {
                 participantAccountIDs: report?.participantAccountIDs,
+                visibleChatMemberAccountIDs: report?.visibleChatMemberAccountIDs,
             },
         },
     ];
@@ -2233,6 +2261,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) {
             key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
             value: {
                 participantAccountIDs: participantAccountIDsAfterRemoval,
+                visibleChatMemberAccountIDs: visibleChatMemberAccountIDsAfterRemoval,
             },
         },
     ];
@@ -2411,6 +2440,10 @@ const updatePrivateNotes = (reportID: string, accountID: number, note: string) =
 
 /** Fetches all the private notes for a given report */
 function getReportPrivateNote(reportID: string) {
+    if (Session.isAnonymousUser()) {
+        return;
+    }
+
     if (!reportID) {
         return;
     }
diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js
index 31cfd96c0bd3..a83a2a42fa68 100644
--- a/src/libs/actions/Task.js
+++ b/src/libs/actions/Task.js
@@ -15,6 +15,7 @@ import * as UserUtils from '@libs/UserUtils';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
+import {isNotEmptyObject} from '@src/types/utils/EmptyObject';
 import * as Report from './Report';
 
 let currentUserEmail;
@@ -180,6 +181,12 @@ function createTaskAndNavigate(parentReportID, title, description, assigneeEmail
         },
     );
 
+    // If needed, update optimistic data for parent report action of the parent report.
+    const optimisticParentReportData = ReportUtils.getOptimisticDataForParentReportAction(parentReportID, currentTime, CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD);
+    if (isNotEmptyObject(optimisticParentReportData)) {
+        optimisticData.push(optimisticParentReportData);
+    }
+
     // FOR PARENT REPORT (SHARE DESTINATION)
     successData.push({
         onyxMethod: Onyx.METHOD.MERGE,
@@ -392,6 +399,7 @@ function editTask(report, {title, description}) {
                     ...(title && {reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
                     ...(description && {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}),
                 },
+                errorFields: null,
             },
         },
     ];
@@ -489,8 +497,10 @@ function editTaskAssignee(report, ownerAccountID, assigneeEmail, assigneeAccount
     // Check if the assignee actually changed
     if (assigneeAccountID && assigneeAccountID !== report.managerID && assigneeAccountID !== ownerAccountID && assigneeChatReport) {
         const participants = lodashGet(report, 'participantAccountIDs', []);
-        if (!participants.includes(assigneeAccountID)) {
+        const visibleMembers = lodashGet(report, 'visibleChatMemberAccountIDs', []);
+        if (!visibleMembers.includes(assigneeAccountID)) {
             optimisticReport.participantAccountIDs = [...participants, assigneeAccountID];
+            optimisticReport.visibleChatMemberAccountIDs = [...visibleMembers, assigneeAccountID];
         }
 
         assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData(
@@ -776,6 +786,19 @@ function deleteTask(taskReportID, taskTitle, originalStateNum, originalStatusNum
         },
     ];
 
+    // Update optimistic data for parent report action if the report is a child report and the task report has no visible child
+    const childVisibleActionCount = lodashGet(parentReportAction, 'childVisibleActionCount', 0);
+    if (childVisibleActionCount === 0) {
+        const optimisticParentReportData = ReportUtils.getOptimisticDataForParentReportAction(
+            parentReport.reportID,
+            parentReport.lastVisibleActionCreated || '',
+            CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+        );
+        if (isNotEmptyObject(optimisticParentReportData)) {
+            optimisticData.push(optimisticParentReportData);
+        }
+    }
+
     const successData = [
         {
             onyxMethod: Onyx.METHOD.MERGE,
diff --git a/src/styles/utils/roundToNearestMultipleOfFour.ts b/src/libs/roundToNearestMultipleOfFour.ts
similarity index 100%
rename from src/styles/utils/roundToNearestMultipleOfFour.ts
rename to src/libs/roundToNearestMultipleOfFour.ts
diff --git a/src/libs/updatePropsPaperWorklet/index.js b/src/libs/updatePropsPaperWorklet/index.js
deleted file mode 100644
index 1bca6ea13cdc..000000000000
--- a/src/libs/updatePropsPaperWorklet/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function () {
-    'worklet';
-}
diff --git a/src/libs/updatePropsPaperWorklet/index.native.js b/src/libs/updatePropsPaperWorklet/index.native.js
deleted file mode 100644
index ed79b38ffab5..000000000000
--- a/src/libs/updatePropsPaperWorklet/index.native.js
+++ /dev/null
@@ -1,13 +0,0 @@
-export default function (viewTag, viewName, updates) {
-    'worklet';
-
-    // _updatePropsPaper is a function that is worklet function from react-native-reanimated which is not available on web
-    // eslint-disable-next-line no-undef
-    _updatePropsPaper([
-        {
-            tag: viewTag,
-            name: viewName,
-            updates,
-        },
-    ]);
-}
diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js
index e41f30779f22..bc05c110ab2f 100644
--- a/src/pages/EditRequestPage.js
+++ b/src/pages/EditRequestPage.js
@@ -1,7 +1,7 @@
 import lodashGet from 'lodash/get';
 import lodashValues from 'lodash/values';
 import PropTypes from 'prop-types';
-import React, {useCallback, useEffect, useMemo} from 'react';
+import React, {useCallback, useEffect} from 'react';
 import {withOnyx} from 'react-native-onyx';
 import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
 import categoryPropTypes from '@components/categoryPropTypes';
@@ -47,9 +47,6 @@ const propTypes = {
     /** The report object for the thread report */
     report: reportPropTypes,
 
-    /** The parent report object for the thread report */
-    parentReport: reportPropTypes,
-
     /** Collection of categories attached to a policy */
     policyCategories: PropTypes.objectOf(categoryPropTypes),
 
@@ -65,14 +62,13 @@ const propTypes = {
 
 const defaultProps = {
     report: {},
-    parentReport: {},
     policyCategories: {},
     policyTags: {},
     parentReportActions: {},
     transaction: {},
 };
 
-function EditRequestPage({report, route, parentReport, policyCategories, policyTags, parentReportActions, transaction}) {
+function EditRequestPage({report, route, policyCategories, policyTags, parentReportActions, transaction}) {
     const parentReportActionID = lodashGet(report, 'parentReportActionID', '0');
     const parentReportAction = lodashGet(parentReportActions, parentReportActionID, {});
     const {
@@ -93,7 +89,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT
     const tagListName = PolicyUtils.getTagListName(policyTags);
 
     // A flag for verifying that the current report is a sub-report of a workspace chat
-    const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]);
+    const isPolicyExpenseChat = ReportUtils.isGroupPolicy(report);
 
     // A flag for showing the categories page
     const shouldShowCategories = isPolicyExpenseChat && (transactionCategory || OptionsListUtils.hasEnabledOptions(lodashValues(policyCategories)));
@@ -104,7 +100,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT
     // Decides whether to allow or disallow editing a money request
     useEffect(() => {
         // Do not dismiss the modal, when a current user can edit this property of the money request.
-        if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, parentReport.reportID, fieldToEdit, transaction)) {
+        if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, fieldToEdit)) {
             return;
         }
 
@@ -112,7 +108,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT
         Navigation.isNavigationReady().then(() => {
             Navigation.dismissModal();
         });
-    }, [parentReportAction, parentReport.reportID, fieldToEdit, transaction]);
+    }, [parentReportAction, fieldToEdit]);
 
     // Update the transaction object and close the modal
     function editMoneyRequest(transactionChanges) {
@@ -149,6 +145,19 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT
         [transaction, report],
     );
 
+    const saveTag = useCallback(
+        ({tag: newTag}) => {
+            let updatedTag = newTag;
+            if (newTag === transactionTag) {
+                // In case the same tag has been selected, reset the tag.
+                updatedTag = '';
+            }
+            IOU.updateMoneyRequestTag(transaction.transactionID, report.reportID, updatedTag);
+            Navigation.dismissModal();
+        },
+        [transactionTag, transaction.transactionID, report.reportID],
+    );
+
     if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) {
         return (
             <EditRequestDescriptionPage
@@ -238,15 +247,7 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT
                 defaultTag={transactionTag}
                 tagName={tagListName}
                 policyID={lodashGet(report, 'policyID', '')}
-                onSubmit={(transactionChanges) => {
-                    let updatedTag = transactionChanges.tag;
-
-                    // In case the same tag has been selected, reset the tag.
-                    if (transactionTag === updatedTag) {
-                        updatedTag = '';
-                    }
-                    editMoneyRequest({tag: updatedTag, tagListName});
-                }}
+                onSubmit={saveTag}
             />
         );
     }
@@ -298,9 +299,6 @@ export default compose(
         policyTags: {
             key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
         },
-        parentReport: {
-            key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT}${report ? report.parentReportID : '0'}`,
-        },
         parentReportActions: {
             key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : '0'}`,
             canEvict: false,
diff --git a/src/pages/ProcessMoneyRequestHoldPage.js b/src/pages/ProcessMoneyRequestHoldPage.js
new file mode 100644
index 000000000000..c9de16f874a2
--- /dev/null
+++ b/src/pages/ProcessMoneyRequestHoldPage.js
@@ -0,0 +1,51 @@
+import React, {useCallback, useMemo} from 'react';
+import {View} from 'react-native';
+import Button from '@components/Button';
+import HeaderPageLayout from '@components/HeaderPageLayout';
+import HoldMenuSectionList from '@components/HoldMenuSectionList';
+import Text from '@components/Text';
+import TextPill from '@components/TextPill';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+
+function ProcessMoneyRequestHoldPage() {
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+
+    const onConfirm = useCallback(() => {
+        // Currently only goes back, this will be changed after backends for hold will be merged
+        Navigation.goBack();
+    }, []);
+
+    const footerComponent = useMemo(
+        () => (
+            <Button
+                success
+                text={translate('common.buttonConfirm')}
+                onPress={onConfirm}
+            />
+        ),
+        [onConfirm, translate],
+    );
+
+    return (
+        <HeaderPageLayout
+            title={translate('common.back')}
+            footer={footerComponent}
+            onBackButtonPress={() => Navigation.goBack()}
+        >
+            <View style={[styles.mh5, styles.flex1]}>
+                <View style={[styles.flexRow, styles.alignItemsCenter, styles.mb5]}>
+                    <Text style={[styles.textHeadline, styles.mr2]}>{translate('iou.holdEducationalTitle')}</Text>
+                    <TextPill textStyles={styles.holdRequestInline}>{translate('iou.hold')}</TextPill>;
+                </View>
+                <HoldMenuSectionList />
+            </View>
+        </HeaderPageLayout>
+    );
+}
+
+ProcessMoneyRequestHoldPage.displayName = 'ProcessMoneyRequestHoldPage';
+
+export default ProcessMoneyRequestHoldPage;
diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js
index dc8b77db4ac8..ff9ed62c6a65 100644
--- a/src/pages/ReportDetailsPage.js
+++ b/src/pages/ReportDetailsPage.js
@@ -73,7 +73,7 @@ function ReportDetailsPage(props) {
     // eslint-disable-next-line react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx
     const chatRoomSubtitle = useMemo(() => ReportUtils.getChatRoomSubtitle(props.report), [props.report, policy]);
     const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report);
-    const participants = useMemo(() => ReportUtils.getParticipantsIDs(props.report), [props.report]);
+    const participants = useMemo(() => ReportUtils.getVisibleMemberIDs(props.report), [props.report]);
 
     const isGroupDMChat = useMemo(() => ReportUtils.isDM(props.report) && participants.length > 1, [props.report, participants.length]);
 
diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js
index fddf5176f815..7dbc1c7036c4 100755
--- a/src/pages/ReportParticipantsPage.js
+++ b/src/pages/ReportParticipantsPage.js
@@ -56,7 +56,7 @@ const defaultProps = {
  * @return {Array}
  */
 const getAllParticipants = (report, personalDetails, translate) =>
-    _.chain(ReportUtils.getParticipantsIDs(report))
+    _.chain(ReportUtils.getVisibleMemberIDs(report))
         .map((accountID, index) => {
             const userPersonalDetail = lodashGet(personalDetails, accountID, {displayName: personalDetails.displayName || translate('common.hidden'), avatar: ''});
             const userLogin = LocalePhoneNumber.formatPhoneNumber(userPersonalDetail.login || '') || translate('common.hidden');
diff --git a/src/pages/RoomInvitePage.js b/src/pages/RoomInvitePage.js
index aebdec047895..f8d2d32bfe79 100644
--- a/src/pages/RoomInvitePage.js
+++ b/src/pages/RoomInvitePage.js
@@ -70,7 +70,10 @@ function RoomInvitePage(props) {
     const [userToInvite, setUserToInvite] = useState(null);
 
     // Any existing participants and Expensify emails should not be eligible for invitation
-    const excludedUsers = useMemo(() => [...PersonalDetailsUtils.getLoginsByAccountIDs(lodashGet(props.report, 'participantAccountIDs', [])), ...CONST.EXPENSIFY_EMAILS], [props.report]);
+    const excludedUsers = useMemo(
+        () => [...PersonalDetailsUtils.getLoginsByAccountIDs(lodashGet(props.report, 'visibleChatMemberAccountIDs', [])), ...CONST.EXPENSIFY_EMAILS],
+        [props.report],
+    );
 
     useEffect(() => {
         const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers);
diff --git a/src/pages/RoomMembersPage.js b/src/pages/RoomMembersPage.js
index 27e1cd1da2e6..67228e574de6 100644
--- a/src/pages/RoomMembersPage.js
+++ b/src/pages/RoomMembersPage.js
@@ -174,7 +174,7 @@ function RoomMembersPage(props) {
     const getMemberOptions = () => {
         let result = [];
 
-        _.each(props.report.participantAccountIDs, (accountID) => {
+        _.each(props.report.visibleChatMemberAccountIDs, (accountID) => {
             const details = personalDetails[accountID];
 
             if (!details) {
diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js
index 1f062a42f8bf..344e7f4bc886 100644
--- a/src/pages/ShareCodePage.js
+++ b/src/pages/ShareCodePage.js
@@ -55,7 +55,7 @@ class ShareCodePage extends React.Component {
         }
         if (ReportUtils.isMoneyRequestReport(this.props.report)) {
             // generate subtitle from participants
-            return _.map(ReportUtils.getParticipantsIDs(this.props.report), (accountID) => ReportUtils.getDisplayNameForParticipant(accountID)).join(' & ');
+            return _.map(ReportUtils.getVisibleMemberIDs(this.props.report), (accountID) => ReportUtils.getDisplayNameForParticipant(accountID)).join(' & ');
         }
 
         if (isReport) {
diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts
index 98d593b92d91..3b4e574e01a1 100644
--- a/src/pages/home/ReportScreenContext.ts
+++ b/src/pages/home/ReportScreenContext.ts
@@ -1,9 +1,13 @@
 import type {RefObject} from 'react';
 import {createContext} from 'react';
-import type {FlatList, GestureResponderEvent} from 'react-native';
+import type {FlatList, GestureResponderEvent, View} from 'react-native';
+
+type ReactionListAnchor = View | HTMLDivElement | null;
+
+type ReactionListEvent = GestureResponderEvent | MouseEvent;
 
 type ReactionListRef = {
-    showReactionList: (event: GestureResponderEvent | undefined, reactionListAnchor: Element, emojiName: string, reportActionID: string) => void;
+    showReactionList: (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor, emojiName: string, reportActionID: string) => void;
     hideReactionList: () => void;
     isActiveReportAction: (actionID: number | string) => boolean;
 };
@@ -21,4 +25,4 @@ const ActionListContext = createContext<ActionListContextType>({flatListRef: nul
 const ReactionListContext = createContext<ReactionListContextType>(null);
 
 export {ActionListContext, ReactionListContext};
-export type {ReactionListRef, ActionListContextType, ReactionListContextType, FlatListRefType};
+export type {ReactionListRef, ActionListContextType, ReactionListContextType, FlatListRefType, ReactionListAnchor, ReactionListEvent};
diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts
index 317c3846d160..5b64d90da5da 100644
--- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts
+++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts
@@ -1,5 +1,6 @@
 import React from 'react';
-import type {GestureResponderEvent, Text as RNText} from 'react-native';
+import type {RefObject} from 'react';
+import type {GestureResponderEvent, Text as RNText, View} from 'react-native';
 import type {OnyxEntry} from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import type CONST from '@src/CONST';
@@ -17,7 +18,7 @@ type ShowContextMenu = (
     type: ContextMenuType,
     event: GestureResponderEvent | MouseEvent,
     selection: string,
-    contextMenuAnchor: RNText | null,
+    contextMenuAnchor: View | RNText | null,
     reportID?: string,
     reportActionID?: string,
     originalReportID?: string,
@@ -39,6 +40,7 @@ type ReportActionContextMenu = {
     instanceID: string;
     runAndResetOnPopoverHide: () => void;
     clearActiveReportAction: () => void;
+    contentRef: RefObject<View>;
 };
 
 const contextMenuRef = React.createRef<ReportActionContextMenu>();
@@ -94,7 +96,7 @@ function showContextMenu(
     type: ContextMenuType,
     event: GestureResponderEvent | MouseEvent,
     selection: string,
-    contextMenuAnchor: RNText | null,
+    contextMenuAnchor: View | RNText | null,
     reportID = '0',
     reportActionID = '0',
     originalReportID = '0',
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js
index da79db6f2ec8..c072666920ae 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
 import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import {View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
-import {runOnJS, useAnimatedRef} from 'react-native-reanimated';
+import {runOnJS, setNativeProps, useAnimatedRef} from 'react-native-reanimated';
 import _ from 'underscore';
 import AttachmentModal from '@components/AttachmentModal';
 import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton';
@@ -24,7 +24,6 @@ import getDraftComment from '@libs/ComposerUtils/getDraftComment';
 import * as DeviceCapabilities from '@libs/DeviceCapabilities';
 import getModalState from '@libs/getModalState';
 import * as ReportUtils from '@libs/ReportUtils';
-import updatePropsPaperWorklet from '@libs/updatePropsPaperWorklet';
 import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside';
 import ParticipantLocalTime from '@pages/home/report/ParticipantLocalTime';
 import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
@@ -348,13 +347,10 @@ function ReportActionCompose({
             return;
         }
 
-        const viewTag = animatedRef();
-        const viewName = 'RCTMultilineTextInputView';
-        const updates = {text: ''};
         // We are setting the isCommentEmpty flag to true so the status of it will be in sync of the native text input state
         runOnJS(setIsCommentEmpty)(true);
         runOnJS(resetFullComposerSize)();
-        updatePropsPaperWorklet(viewTag, viewName, updates); // clears native text input on the UI thread
+        setNativeProps(animatedRef, {text: ''}); // clears native text input on the UI thread
         runOnJS(submitForm)();
     }, [isSendDisabled, resetFullComposerSize, submitForm, animatedRef, isReportReadyForDisplay]);
 
diff --git a/src/pages/home/report/ReportActionItemMessage.tsx b/src/pages/home/report/ReportActionItemMessage.tsx
index d98d1b901578..3a71ee8356b3 100644
--- a/src/pages/home/report/ReportActionItemMessage.tsx
+++ b/src/pages/home/report/ReportActionItemMessage.tsx
@@ -40,13 +40,15 @@ function ReportActionItemMessage({action, displayAsGroup, reportID, style, isHid
         const fragment = ReportActionsUtils.getMemberChangeMessageFragment(action);
 
         return (
-            <TextCommentFragment
-                fragment={fragment}
-                displayAsGroup={displayAsGroup}
-                style={style}
-                source=""
-                styleAsDeleted={false}
-            />
+            <View style={[styles.chatItemMessage, style]}>
+                <TextCommentFragment
+                    fragment={fragment}
+                    displayAsGroup={displayAsGroup}
+                    style={style}
+                    source=""
+                    styleAsDeleted={false}
+                />
+            </View>
         );
     }
 
diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js
index dbc77a41817b..580cc7909fd1 100644
--- a/src/pages/home/sidebar/SidebarLinksData.js
+++ b/src/pages/home/sidebar/SidebarLinksData.js
@@ -168,6 +168,7 @@ const chatReportSelector = (report) =>
         // Other important less obivous properties for filtering:
         parentReportActionID: report.parentReportActionID,
         parentReportID: report.parentReportID,
+        isDeletedParentAction: report.isDeletedParentAction,
     };
 
 /**
diff --git a/src/pages/iou/IOUCurrencySelection.js b/src/pages/iou/IOUCurrencySelection.js
index eab9ab5a7510..24833fc96fdc 100644
--- a/src/pages/iou/IOUCurrencySelection.js
+++ b/src/pages/iou/IOUCurrencySelection.js
@@ -86,7 +86,7 @@ function IOUCurrencySelection(props) {
         const parentReportAction = ReportActionsUtils.getReportAction(report.parentReportID, report.parentReportActionID);
 
         // Do not dismiss the modal, when a current user can edit this currency of this money request.
-        if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, report.parentReportID, CONST.EDIT_REQUEST_FIELD.CURRENCY)) {
+        if (ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.CURRENCY)) {
             return;
         }
 
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js
index 893c735aac3b..d41442edd670 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.js
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js
@@ -1,12 +1,15 @@
 import lodashGet from 'lodash/get';
+import PropTypes from 'prop-types';
 import React, {useCallback, useEffect, useMemo, useState} from 'react';
 import {View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
 import _ from 'underscore';
+import categoryPropTypes from '@components/categoryPropTypes';
 import HeaderWithBackButton from '@components/HeaderWithBackButton';
 import * as Expensicons from '@components/Icon/Expensicons';
 import MoneyRequestConfirmationList from '@components/MoneyTemporaryForRefactorRequestConfirmationList';
 import ScreenWrapper from '@components/ScreenWrapper';
+import tagPropTypes from '@components/tagPropTypes';
 import transactionPropTypes from '@components/transactionPropTypes';
 import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
 import useLocalize from '@hooks/useLocalize';
@@ -46,6 +49,12 @@ const propTypes = {
     /** The policy of the report */
     ...policyPropTypes,
 
+    /** The tag configuration of the report's policy */
+    policyTags: tagPropTypes,
+
+    /** The category configuration of the report's policy */
+    policyCategories: PropTypes.objectOf(categoryPropTypes),
+
     /** The full IOU report */
     report: reportPropTypes,
 
@@ -55,6 +64,8 @@ const propTypes = {
 const defaultProps = {
     personalDetails: {},
     policy: {},
+    policyCategories: {},
+    policyTags: {},
     report: {},
     transaction: {},
     ...withCurrentUserPersonalDetailsDefaultProps,
@@ -63,6 +74,8 @@ function IOURequestStepConfirmation({
     currentUserPersonalDetails,
     personalDetails,
     policy,
+    policyTags,
+    policyCategories,
     report,
     route: {
         params: {iouType, reportID, transactionID},
@@ -164,9 +177,12 @@ function IOURequestStepConfirmation({
                 transactionTaxCode,
                 transactionTaxAmount,
                 transaction.billable,
+                policy,
+                policyTags,
+                policyCategories,
             );
         },
-        [report, transaction, transactionTaxCode, transactionTaxAmount, currentUserPersonalDetails.login, currentUserPersonalDetails.accountID],
+        [report, transaction, transactionTaxCode, transactionTaxAmount, currentUserPersonalDetails.login, currentUserPersonalDetails.accountID, policy, policyTags, policyCategories],
     );
 
     /**
@@ -187,9 +203,12 @@ function IOURequestStepConfirmation({
                 transaction.merchant,
                 transaction.billable,
                 TransactionUtils.getValidWaypoints(transaction.comment.waypoints, true),
+                policy,
+                policyTags,
+                policyCategories,
             );
         },
-        [report, transaction],
+        [policy, policyCategories, policyTags, report, transaction],
     );
 
     const createTransaction = useCallback(
@@ -375,7 +394,13 @@ export default compose(
     // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
     withOnyx({
         policy: {
-            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${lodashGet(report, 'policyID', '0')}`,
+            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
+        },
+        policyCategories: {
+            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`,
+        },
+        policyTags: {
+            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
         },
     }),
 )(IOURequestStepConfirmation);
diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.js b/src/pages/iou/request/step/IOURequestStepMerchant.js
index 355bb76b89b0..091e7d8023c3 100644
--- a/src/pages/iou/request/step/IOURequestStepMerchant.js
+++ b/src/pages/iou/request/step/IOURequestStepMerchant.js
@@ -36,13 +36,15 @@ function IOURequestStepMerchant({
     route: {
         params: {transactionID, backTo},
     },
-    transaction: {merchant},
+    transaction: {merchant, participants},
 }) {
     const styles = useThemeStyles();
     const {translate} = useLocalize();
     const {inputCallbackRef} = useAutoFocusInput();
     const isEmptyMerchant = merchant === '' || merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
 
+    const isMerchantRequired = _.some(participants, (participant) => Boolean(participant.isPolicyExpenseChat));
+
     const navigateBack = () => {
         Navigation.goBack(backTo || ROUTES.HOME);
     };
@@ -51,15 +53,18 @@ function IOURequestStepMerchant({
      * @param {Object} value
      * @param {String} value.moneyRequestMerchant
      */
-    const validate = useCallback((value) => {
-        const errors = {};
+    const validate = useCallback(
+        (value) => {
+            const errors = {};
 
-        if (_.isEmpty(value.moneyRequestMerchant)) {
-            errors.moneyRequestMerchant = 'common.error.fieldRequired';
-        }
+            if (isMerchantRequired && _.isEmpty(value.moneyRequestMerchant)) {
+                errors.moneyRequestMerchant = 'common.error.fieldRequired';
+            }
 
-        return errors;
-    }, []);
+            return errors;
+        },
+        [isMerchantRequired],
+    );
 
     /**
      * @param {Object} value
diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js
index 3476b2304875..1738ac78df47 100644
--- a/src/pages/iou/steps/MoneyRequestConfirmPage.js
+++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js
@@ -4,11 +4,13 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import {View} from 'react-native';
 import {withOnyx} from 'react-native-onyx';
 import _ from 'underscore';
+import categoryPropTypes from '@components/categoryPropTypes';
 import HeaderWithBackButton from '@components/HeaderWithBackButton';
 import * as Expensicons from '@components/Icon/Expensicons';
 import MoneyRequestConfirmationList from '@components/MoneyRequestConfirmationList';
 import {usePersonalDetails} from '@components/OnyxProvider';
 import ScreenWrapper from '@components/ScreenWrapper';
+import tagPropTypes from '@components/tagPropTypes';
 import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails';
 import withLocalize from '@components/withLocalize';
 import useInitialValue from '@hooks/useInitialValue';
@@ -23,6 +25,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils';
 import * as ReportUtils from '@libs/ReportUtils';
 import {iouDefaultProps, iouPropTypes} from '@pages/iou/propTypes';
 import reportPropTypes from '@pages/reportPropTypes';
+import {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy';
 import * as IOU from '@userActions/IOU';
 import * as Policy from '@userActions/Policy';
 import CONST from '@src/CONST';
@@ -47,12 +50,22 @@ const propTypes = {
     /** Holds data related to Money Request view state, rather than the underlying Money Request data. */
     iou: iouPropTypes,
 
+    /** The policy of the current request */
+    policy: policyPropTypes,
+
+    policyTags: tagPropTypes,
+
+    policyCategories: PropTypes.objectOf(categoryPropTypes),
+
     ...withCurrentUserPersonalDetailsPropTypes,
 };
 
 const defaultProps = {
     report: {},
+    policyCategories: {},
+    policyTags: {},
     iou: iouDefaultProps,
+    policy: policyDefaultProps,
     ...withCurrentUserPersonalDetailsDefaultProps,
 };
 
@@ -163,6 +176,9 @@ function MoneyRequestConfirmPage(props) {
                 props.iou.category,
                 props.iou.tag,
                 props.iou.billable,
+                props.policy,
+                props.policyTags,
+                props.policyCategories,
             );
         },
         [
@@ -176,6 +192,9 @@ function MoneyRequestConfirmPage(props) {
             props.iou.category,
             props.iou.tag,
             props.iou.billable,
+            props.policy,
+            props.policyTags,
+            props.policyCategories,
         ],
     );
 
@@ -197,9 +216,25 @@ function MoneyRequestConfirmPage(props) {
                 props.iou.currency,
                 props.iou.merchant,
                 props.iou.billable,
+                props.policy,
+                props.policyTags,
+                props.policyCategories,
             );
         },
-        [props.report, props.iou.created, props.iou.transactionID, props.iou.category, props.iou.tag, props.iou.amount, props.iou.currency, props.iou.merchant, props.iou.billable],
+        [
+            props.report,
+            props.iou.created,
+            props.iou.transactionID,
+            props.iou.category,
+            props.iou.tag,
+            props.iou.amount,
+            props.iou.currency,
+            props.iou.merchant,
+            props.iou.billable,
+            props.policy,
+            props.policyTags,
+            props.policyCategories,
+        ],
     );
 
     const createTransaction = useCallback(
@@ -424,10 +459,15 @@ export default compose(
             key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`,
         },
     }),
-    // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
     withOnyx({
         policy: {
             key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
         },
+        policyCategories: {
+            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${report ? report.policyID : '0'}`,
+        },
+        policyTags: {
+            key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
+        },
     }),
 )(MoneyRequestConfirmPage);
diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js
index d2d7bb480ed6..329bf66f7275 100644
--- a/src/pages/reportPropTypes.js
+++ b/src/pages/reportPropTypes.js
@@ -47,6 +47,9 @@ export default PropTypes.shape({
     /** List of accountIDs of participants of the report */
     participantAccountIDs: PropTypes.arrayOf(PropTypes.number),
 
+    /** List of accountIDs of visible members of the report */
+    visibleChatMemberAccountIDs: PropTypes.arrayOf(PropTypes.number),
+
     /** Linked policy's ID */
     policyID: PropTypes.string,
 
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js
index f0c3d3ada0c2..8585fdf7ab97 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.js
+++ b/src/pages/workspace/WorkspaceNewRoomPage.js
@@ -13,7 +13,6 @@ import KeyboardAvoidingView from '@components/KeyboardAvoidingView';
 import OfflineIndicator from '@components/OfflineIndicator';
 import RoomNameInput from '@components/RoomNameInput';
 import ScreenWrapper from '@components/ScreenWrapper';
-import Text from '@components/Text';
 import TextInput from '@components/TextInput';
 import ValuePicker from '@components/ValuePicker';
 import withNavigationFocus from '@components/withNavigationFocus';
@@ -132,9 +131,7 @@ function WorkspaceNewRoomPage(props) {
             '',
             visibility,
             writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL,
-
-            // The room might contain all policy members so notifying always should be opt-in only.
-            CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY,
+            CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
             '',
             '',
             parsedWelcomeMessage,
@@ -329,9 +326,9 @@ function WorkspaceNewRoomPage(props) {
                                         items={visibilityOptions}
                                         onValueChange={setVisibility}
                                         value={visibility}
+                                        furtherDetails={visibilityDescription}
                                     />
                                 </View>
-                                <Text style={[styles.textLabel, styles.colorMuted]}>{visibilityDescription}</Text>
                             </FormProvider>
                             {isSmallScreenWidth && <OfflineIndicator />}
                         </KeyboardAvoidingView>
diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx
index 90eda3651a04..ec38b61fb0dc 100644
--- a/src/pages/workspace/withPolicy.tsx
+++ b/src/pages/workspace/withPolicy.tsx
@@ -54,6 +54,18 @@ const policyPropTypes = {
          * }
          */
         errorFields: PropTypes.objectOf(PropTypes.objectOf(PropTypes.string)),
+
+        /** Whether or not the policy requires tags */
+        requiresTag: PropTypes.bool,
+
+        /** Whether or not the policy requires categories */
+        requiresCategory: PropTypes.bool,
+
+        /** Whether or not the policy has multiple tag lists */
+        hasMultipleTagLists: PropTypes.bool,
+
+        /** Whether or not the policy has tax tracking enabled */
+        isTaxTrackingEnabled: PropTypes.bool,
     }),
 
     /** The employee list of this policy */
diff --git a/src/stories/CheckboxWithLabel.stories.js b/src/stories/CheckboxWithLabel.stories.js
index 2da4713e81b3..f978856aaefb 100644
--- a/src/stories/CheckboxWithLabel.stories.js
+++ b/src/stories/CheckboxWithLabel.stories.js
@@ -1,6 +1,7 @@
 import React from 'react';
 import CheckboxWithLabel from '@components/CheckboxWithLabel';
 import Text from '@components/Text';
+// eslint-disable-next-line no-restricted-imports
 import {defaultStyles} from '@styles/index';
 
 /**
diff --git a/src/stories/Composer.stories.js b/src/stories/Composer.stories.js
index 04fa9be7de86..e4051a4ab72a 100644
--- a/src/stories/Composer.stories.js
+++ b/src/stories/Composer.stories.js
@@ -6,7 +6,9 @@ import RenderHTML from '@components/RenderHTML';
 import Text from '@components/Text';
 import withNavigationFallback from '@components/withNavigationFallback';
 import useStyleUtils from '@hooks/useStyleUtils';
+// eslint-disable-next-line no-restricted-imports
 import {defaultStyles} from '@styles/index';
+// eslint-disable-next-line no-restricted-imports
 import {defaultTheme} from '@styles/theme';
 import CONST from '@src/CONST';
 
diff --git a/src/stories/DragAndDrop.stories.js b/src/stories/DragAndDrop.stories.js
index 8e540dcd2a42..57995df1edad 100644
--- a/src/stories/DragAndDrop.stories.js
+++ b/src/stories/DragAndDrop.stories.js
@@ -4,6 +4,7 @@ import {Image, View} from 'react-native';
 import DragAndDropConsumer from '@components/DragAndDrop/Consumer';
 import DragAndDropProvider from '@components/DragAndDrop/Provider';
 import Text from '@components/Text';
+// eslint-disable-next-line no-restricted-imports
 import {defaultStyles} from '@styles/index';
 
 /**
diff --git a/src/stories/Form.stories.js b/src/stories/Form.stories.js
index 7802b59605a5..6a4274c87eda 100644
--- a/src/stories/Form.stories.js
+++ b/src/stories/Form.stories.js
@@ -11,6 +11,7 @@ import Text from '@components/Text';
 import TextInput from '@components/TextInput';
 import NetworkConnection from '@libs/NetworkConnection';
 import * as ValidationUtils from '@libs/ValidationUtils';
+// eslint-disable-next-line no-restricted-imports
 import {defaultStyles} from '@styles/index';
 import * as FormActions from '@userActions/FormActions';
 import CONST from '@src/CONST';
diff --git a/src/stories/SelectionList.stories.js b/src/stories/SelectionList.stories.js
index c08ef2df783a..835bf67fbfd7 100644
--- a/src/stories/SelectionList.stories.js
+++ b/src/stories/SelectionList.stories.js
@@ -3,6 +3,7 @@ import {View} from 'react-native';
 import _ from 'underscore';
 import SelectionList from '@components/SelectionList';
 import Text from '@components/Text';
+// eslint-disable-next-line no-restricted-imports
 import {defaultStyles} from '@styles/index';
 import CONST from '@src/CONST';
 
diff --git a/src/styles/index.ts b/src/styles/index.ts
index ce39e69a445a..6d3cbd93c6c8 100644
--- a/src/styles/index.ts
+++ b/src/styles/index.ts
@@ -20,8 +20,7 @@ import cursor from './utils/cursor';
 import display from './utils/display';
 import editedLabelStyles from './utils/editedLabelStyles';
 import flex from './utils/flex';
-import fontFamily from './utils/fontFamily';
-import fontWeightBold from './utils/fontWeight/bold';
+import FontUtils from './utils/FontUtils';
 import getPopOverVerticalOffset from './utils/getPopOverVerticalOffset';
 import objectFit from './utils/objectFit';
 import optionAlternateTextPlatformStyles from './utils/optionAlternateTextPlatformStyles';
@@ -91,7 +90,7 @@ const picker = (theme: ThemeColors) =>
     ({
         backgroundColor: theme.transparent,
         color: theme.text,
-        fontFamily: fontFamily.EXP_NEUE,
+        fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
         fontSize: variables.fontSizeNormal,
         lineHeight: variables.fontSizeNormalHeight,
         paddingBottom: 8,
@@ -107,7 +106,7 @@ const link = (theme: ThemeColors) =>
     ({
         color: theme.link,
         textDecorationColor: theme.link,
-        fontFamily: fontFamily.EXP_NEUE,
+        fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
     } satisfies ViewStyle & MixedStyleDeclaration);
 
 const baseCodeTagStyles = (theme: ThemeColors) =>
@@ -119,7 +118,7 @@ const baseCodeTagStyles = (theme: ThemeColors) =>
     } satisfies ViewStyle & MixedStyleDeclaration);
 
 const headlineFont = {
-    fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM,
+    fontFamily: FontUtils.fontFamily.platform.EXP_NEW_KANSAS_MEDIUM,
     fontWeight: '500',
 } satisfies TextStyle;
 
@@ -139,7 +138,7 @@ const webViewStyles = (theme: ThemeColors) =>
         // component.
         tagStyles: {
             em: {
-                fontFamily: fontFamily.EXP_NEUE,
+                fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
                 fontStyle: 'italic',
             },
 
@@ -149,7 +148,7 @@ const webViewStyles = (theme: ThemeColors) =>
             },
 
             strong: {
-                fontFamily: fontFamily.EXP_NEUE,
+                fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
                 fontWeight: 'bold',
             },
 
@@ -184,7 +183,7 @@ const webViewStyles = (theme: ThemeColors) =>
                 paddingBottom: 12,
                 paddingRight: 8,
                 paddingLeft: 8,
-                fontFamily: fontFamily.MONOSPACE,
+                fontFamily: FontUtils.fontFamily.platform.MONOSPACE,
                 marginTop: 0,
                 marginBottom: 0,
             },
@@ -194,7 +193,7 @@ const webViewStyles = (theme: ThemeColors) =>
                 ...(codeStyles.codeTextStyle as MixedStyleDeclaration),
                 paddingLeft: 5,
                 paddingRight: 5,
-                fontFamily: fontFamily.MONOSPACE,
+                fontFamily: FontUtils.fontFamily.platform.MONOSPACE,
                 // Font size is determined by getCodeFontSize function in `StyleUtils.js`
             },
 
@@ -218,7 +217,7 @@ const webViewStyles = (theme: ThemeColors) =>
         baseFontStyle: {
             color: theme.text,
             fontSize: variables.fontSizeNormal,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             flex: 1,
             lineHeight: variables.fontSizeNormalHeight,
             ...writingDirection.ltr,
@@ -293,8 +292,8 @@ const styles = (theme: ThemeColors) =>
         },
 
         mentionSuggestionsDisplayName: {
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
         },
 
         textSupporting: {
@@ -308,7 +307,7 @@ const styles = (theme: ThemeColors) =>
         linkMuted: {
             color: theme.textSupporting,
             textDecorationColor: theme.textSupporting,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
         },
 
         linkMutedHovered: {
@@ -324,9 +323,9 @@ const styles = (theme: ThemeColors) =>
         },
 
         h4: {
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
             fontSize: variables.fontSizeLabel,
-            fontWeight: fontWeightBold,
+            fontWeight: FontUtils.fontWeight.bold,
         },
 
         textAlignCenter: {
@@ -367,29 +366,29 @@ const styles = (theme: ThemeColors) =>
         },
 
         textMicro: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeSmall,
             lineHeight: variables.lineHeightSmall,
         },
 
         textMicroBold: {
             color: theme.text,
-            fontWeight: fontWeightBold,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
             fontSize: variables.fontSizeSmall,
             lineHeight: variables.lineHeightSmall,
         },
 
         textMicroSupporting: {
             color: theme.textSupporting,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeSmall,
             lineHeight: variables.lineHeightSmall,
         },
 
         textExtraSmallSupporting: {
             color: theme.textSupporting,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeExtraSmall,
         },
 
@@ -411,13 +410,13 @@ const styles = (theme: ThemeColors) =>
 
         textHero: {
             fontSize: variables.fontSizeHero,
-            fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEW_KANSAS_MEDIUM,
             lineHeight: variables.lineHeightHero,
         },
 
         textStrong: {
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
         },
 
         textHeadline: {
@@ -501,9 +500,9 @@ const styles = (theme: ThemeColors) =>
 
         buttonText: {
             color: theme.text,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
             fontSize: variables.fontSizeNormal,
-            fontWeight: fontWeightBold,
+            fontWeight: FontUtils.fontWeight.bold,
             textAlign: 'center',
             flexShrink: 1,
 
@@ -540,22 +539,22 @@ const styles = (theme: ThemeColors) =>
 
         buttonSmallText: {
             fontSize: variables.fontSizeSmall,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
             textAlign: 'center',
         },
 
         buttonMediumText: {
             fontSize: variables.fontSizeLabel,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
             textAlign: 'center',
         },
 
         buttonLargeText: {
             fontSize: variables.fontSizeNormal,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
             textAlign: 'center',
         },
 
@@ -681,7 +680,7 @@ const styles = (theme: ThemeColors) =>
         pickerSmall: (backgroundColor = theme.highlightBG) =>
             ({
                 inputIOS: {
-                    fontFamily: fontFamily.EXP_NEUE,
+                    fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
                     fontSize: variables.fontSizeSmall,
                     paddingLeft: 0,
                     paddingRight: 17,
@@ -708,7 +707,7 @@ const styles = (theme: ThemeColors) =>
                     backgroundColor: theme.highlightBG,
                 },
                 inputWeb: {
-                    fontFamily: fontFamily.EXP_NEUE,
+                    fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
                     fontSize: variables.fontSizeSmall,
                     paddingLeft: 0,
                     paddingRight: 17,
@@ -723,7 +722,7 @@ const styles = (theme: ThemeColors) =>
                     ...cursor.cursorPointer,
                 },
                 inputAndroid: {
-                    fontFamily: fontFamily.EXP_NEUE,
+                    fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
                     fontSize: variables.fontSizeSmall,
                     paddingLeft: 0,
                     paddingRight: 17,
@@ -858,16 +857,16 @@ const styles = (theme: ThemeColors) =>
 
         headerAnonymousFooter: {
             color: theme.heading,
-            fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEW_KANSAS_MEDIUM,
             fontSize: variables.fontSizeXLarge,
             lineHeight: variables.lineHeightXXLarge,
         },
 
         headerText: {
             color: theme.heading,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
             fontSize: variables.fontSizeNormal,
-            fontWeight: fontWeightBold,
+            fontWeight: FontUtils.fontWeight.bold,
         },
 
         headerGap: {
@@ -886,7 +885,7 @@ const styles = (theme: ThemeColors) =>
 
         chatItemComposeSecondaryRowSubText: {
             color: theme.textSupporting,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeSmall,
             lineHeight: variables.lineHeightSmall,
         },
@@ -983,7 +982,7 @@ const styles = (theme: ThemeColors) =>
             top: 0,
             fontSize: variables.fontSizeNormal,
             color: theme.textSupporting,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             width: '100%',
         },
 
@@ -1005,7 +1004,7 @@ const styles = (theme: ThemeColors) =>
             } satisfies TextStyle),
 
         baseTextInput: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeNormal,
             lineHeight: variables.lineHeightXLarge,
             color: theme.text,
@@ -1050,7 +1049,7 @@ const styles = (theme: ThemeColors) =>
             borderColor: theme.border,
             borderWidth: 1,
             color: theme.text,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeNormal,
             paddingLeft: 12,
             paddingRight: 12,
@@ -1073,7 +1072,7 @@ const styles = (theme: ThemeColors) =>
 
         textInputPrefix: {
             color: theme.text,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeNormal,
             verticalAlign: 'middle',
         },
@@ -1150,20 +1149,20 @@ const styles = (theme: ThemeColors) =>
         noOutline: addOutlineWidth(theme, {}, 0),
 
         labelStrong: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontWeight: 'bold',
             fontSize: variables.fontSizeLabel,
             lineHeight: variables.lineHeightNormal,
         },
 
         textLabelSupporting: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeLabel,
             color: theme.textSupporting,
         },
 
         textLabelError: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeLabel,
             color: theme.textError,
         },
@@ -1176,14 +1175,14 @@ const styles = (theme: ThemeColors) =>
         },
 
         subTextReceiptUpload: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             lineHeight: variables.lineHeightLarge,
             textAlign: 'center',
             color: theme.text,
         },
 
         furtherDetailsText: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeSmall,
             color: theme.textSupporting,
         },
@@ -1386,7 +1385,7 @@ const styles = (theme: ThemeColors) =>
             color: theme.textSupporting,
             fontSize: variables.fontSizeSmall,
             textDecorationLine: 'none',
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             lineHeight: 20,
         },
 
@@ -1475,7 +1474,7 @@ const styles = (theme: ThemeColors) =>
         },
 
         createMenuHeaderText: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeLabel,
             color: theme.heading,
         },
@@ -1573,8 +1572,8 @@ const styles = (theme: ThemeColors) =>
         },
 
         sidebarLinkTextBold: {
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
             color: theme.heading,
         },
 
@@ -1591,7 +1590,7 @@ const styles = (theme: ThemeColors) =>
         },
 
         optionDisplayName: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             minHeight: variables.alternateTextHeight,
             lineHeight: variables.lineHeightXLarge,
             ...whiteSpace.noWrap,
@@ -1699,7 +1698,6 @@ const styles = (theme: ThemeColors) =>
         },
 
         chatContentScrollView: {
-            flexGrow: 1,
             justifyContent: 'flex-end',
             paddingBottom: 16,
         },
@@ -1738,9 +1736,9 @@ const styles = (theme: ThemeColors) =>
 
         chatItemMessageHeaderSender: {
             color: theme.heading,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
             fontSize: variables.fontSizeNormal,
-            fontWeight: fontWeightBold,
+            fontWeight: FontUtils.fontWeight.bold,
             lineHeight: variables.lineHeightXXLarge,
             ...wordBreak.breakWord,
         },
@@ -1755,7 +1753,7 @@ const styles = (theme: ThemeColors) =>
         chatItemMessage: {
             color: theme.text,
             fontSize: variables.fontSizeNormal,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             lineHeight: variables.lineHeightXLarge,
             maxWidth: '100%',
             ...cursor.cursorAuto,
@@ -1766,7 +1764,7 @@ const styles = (theme: ThemeColors) =>
         renderHTMLTitle: {
             color: theme.text,
             fontSize: variables.fontSizeNormal,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             lineHeight: variables.lineHeightXLarge,
             maxWidth: '100%',
             ...whiteSpace.preWrap,
@@ -1837,7 +1835,7 @@ const styles = (theme: ThemeColors) =>
                 backgroundColor: theme.componentBG,
                 borderColor: theme.border,
                 color: theme.text,
-                fontFamily: fontFamily.EXP_NEUE,
+                fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
                 fontSize: variables.fontSizeNormal,
                 borderWidth: 0,
                 height: 'auto',
@@ -1903,8 +1901,8 @@ const styles = (theme: ThemeColors) =>
 
         emojiSkinToneTitle: {
             ...spacing.pv1,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
             color: theme.heading,
             fontSize: variables.fontSizeSmall,
         },
@@ -2365,7 +2363,7 @@ const styles = (theme: ThemeColors) =>
         },
 
         twoFactorAuthCode: {
-            fontFamily: fontFamily.MONOSPACE,
+            fontFamily: FontUtils.fontFamily.platform.MONOSPACE,
             width: 112,
             textAlign: 'center',
         },
@@ -2413,7 +2411,7 @@ const styles = (theme: ThemeColors) =>
             height: 20,
         },
         anonymousRoomFooterLogoTaglineText: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeMedium,
             color: theme.text,
         },
@@ -2454,7 +2452,7 @@ const styles = (theme: ThemeColors) =>
         avatarInnerTextChat: {
             color: theme.textLight,
             fontSize: variables.fontSizeXLarge,
-            fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEW_KANSAS_MEDIUM,
             textAlign: 'center',
             fontWeight: 'normal',
             position: 'absolute',
@@ -2528,9 +2526,9 @@ const styles = (theme: ThemeColors) =>
 
         unreadIndicatorText: {
             color: theme.unreadIndicator,
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
             fontSize: variables.fontSizeSmall,
-            fontWeight: fontWeightBold,
+            fontWeight: FontUtils.fontWeight.bold,
             textTransform: 'capitalize',
         },
 
@@ -2850,7 +2848,7 @@ const styles = (theme: ThemeColors) =>
 
         growlNotificationText: {
             fontSize: variables.fontSizeNormal,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             width: '90%',
             lineHeight: variables.fontSizeNormalHeight,
             color: theme.textReversed,
@@ -3082,7 +3080,7 @@ const styles = (theme: ThemeColors) =>
             color: theme.text,
             fontSize: variables.fontSizeNormal,
             lineHeight: variables.fontSizeNormalHeight,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             flex: 1,
         },
 
@@ -3190,7 +3188,7 @@ const styles = (theme: ThemeColors) =>
         inlineSystemMessage: {
             color: theme.textSupporting,
             fontSize: variables.fontSizeLabel,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             marginLeft: 6,
         },
 
@@ -3368,7 +3366,7 @@ const styles = (theme: ThemeColors) =>
 
         validateCodeDigits: {
             color: theme.text,
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeXXLarge,
             letterSpacing: 4,
         },
@@ -3418,7 +3416,7 @@ const styles = (theme: ThemeColors) =>
         },
 
         loginHeroHeader: {
-            fontFamily: fontFamily.EXP_NEW_KANSAS_MEDIUM,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEW_KANSAS_MEDIUM,
             color: theme.success,
             fontWeight: '500',
             textAlign: 'center',
@@ -3448,28 +3446,28 @@ const styles = (theme: ThemeColors) =>
         },
 
         eReceiptMerchant: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeXLarge,
             lineHeight: variables.lineHeightXXLarge,
             color: theme.textColorfulBackground,
         },
 
         eReceiptWaypointTitle: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeSmall,
             lineHeight: variables.lineHeightSmall,
             color: colors.green400,
         },
 
         eReceiptWaypointAddress: {
-            fontFamily: fontFamily.MONOSPACE,
+            fontFamily: FontUtils.fontFamily.platform.MONOSPACE,
             fontSize: variables.fontSizeNormal,
             lineHeight: variables.lineHeightNormal,
             color: theme.textColorfulBackground,
         },
 
         eReceiptGuaranteed: {
-            fontFamily: fontFamily.MONOSPACE,
+            fontFamily: FontUtils.fontFamily.platform.MONOSPACE,
             fontSize: variables.fontSizeSmall,
             lineHeight: variables.lineHeightSmall,
             color: theme.textColorfulBackground,
@@ -3509,7 +3507,7 @@ const styles = (theme: ThemeColors) =>
         },
 
         loginHeroBody: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeSignInHeroBody,
             color: theme.textLight,
             textAlign: 'center',
@@ -3561,7 +3559,7 @@ const styles = (theme: ThemeColors) =>
         },
 
         taskTitleDescription: {
-            fontFamily: fontFamily.EXP_NEUE,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
             fontSize: variables.fontSizeLabel,
             color: theme.textSupporting,
             lineHeight: variables.lineHeightNormal,
@@ -3579,8 +3577,8 @@ const styles = (theme: ThemeColors) =>
         },
 
         assigneeTextStyle: {
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
             minHeight: variables.avatarSizeSubscript,
         },
 
@@ -3629,7 +3627,7 @@ const styles = (theme: ThemeColors) =>
 
         headerEnvBadgeText: {
             fontSize: 7,
-            fontWeight: fontWeightBold,
+            fontWeight: FontUtils.fontWeight.bold,
             lineHeight: undefined,
         },
 
@@ -3707,10 +3705,11 @@ const styles = (theme: ThemeColors) =>
         tabText: (isSelected: boolean) =>
             ({
                 marginLeft: 8,
-                fontFamily: isSelected ? fontFamily.EXP_NEUE_BOLD : fontFamily.EXP_NEUE,
-                fontWeight: isSelected ? fontWeightBold : '400',
+                fontFamily: isSelected ? FontUtils.fontFamily.platform.EXP_NEUE_BOLD : FontUtils.fontFamily.platform.EXP_NEUE,
+                fontWeight: isSelected ? FontUtils.fontWeight.bold : '400',
                 color: isSelected ? theme.text : theme.textSupporting,
-                lineHeight: 14,
+                lineHeight: variables.lineHeightNormal,
+                fontSize: variables.fontSizeNormal,
             } satisfies TextStyle),
 
         tabBackground: (hovered: boolean, isFocused: boolean, background: string | Animated.AnimatedInterpolation<string>) => ({
@@ -4070,6 +4069,21 @@ const styles = (theme: ThemeColors) =>
             marginBottom: 16,
         },
 
+        holdRequestInline: {
+            ...headlineFont,
+            ...whiteSpace.preWrap,
+            color: theme.heading,
+            fontSize: variables.fontSizeXLarge,
+            lineHeight: variables.lineHeightXXLarge,
+
+            backgroundColor: colors.red,
+            borderRadius: variables.componentBorderRadiusMedium,
+            overflow: 'hidden',
+
+            paddingHorizontal: 8,
+            paddingVertical: 4,
+        },
+
         walletCard: {
             borderRadius: variables.componentBorderRadiusLarge,
             position: 'relative',
@@ -4078,8 +4092,8 @@ const styles = (theme: ThemeColors) =>
         },
 
         walletCardMenuItem: {
-            fontFamily: fontFamily.EXP_NEUE_BOLD,
-            fontWeight: fontWeightBold,
+            fontFamily: FontUtils.fontFamily.platform.EXP_NEUE_BOLD,
+            fontWeight: FontUtils.fontWeight.bold,
             color: theme.text,
             fontSize: variables.fontSizeNormal,
             lineHeight: variables.lineHeightXLarge,
@@ -4104,7 +4118,7 @@ const styles = (theme: ThemeColors) =>
 
         walletRedDotSectionTitle: {
             color: theme.text,
-            fontWeight: fontWeightBold,
+            fontWeight: FontUtils.fontWeight.bold,
             fontSize: variables.fontSizeNormal,
             lineHeight: variables.lineHeightXLarge,
         },
diff --git a/src/styles/theme/context/ThemeIllustrationsContext.ts b/src/styles/theme/context/ThemeIllustrationsContext.ts
index f762a35b0800..92e6d9514dcd 100644
--- a/src/styles/theme/context/ThemeIllustrationsContext.ts
+++ b/src/styles/theme/context/ThemeIllustrationsContext.ts
@@ -1,7 +1,8 @@
 import React from 'react';
-import {DefaultIllustrations} from '@styles/theme/illustrations';
-import type {IllustrationsType} from '@styles/theme/illustrations/types';
+// eslint-disable-next-line no-restricted-imports
+import {defaultIllustrations} from '@styles/theme/illustrations';
+import type IllustrationsType from '@styles/theme/illustrations/types';
 
-const ThemeIllustrationsContext = React.createContext<IllustrationsType>(DefaultIllustrations);
+const ThemeIllustrationsContext = React.createContext<IllustrationsType>(defaultIllustrations);
 
 export default ThemeIllustrationsContext;
diff --git a/src/styles/theme/context/ThemeStylesContext.ts b/src/styles/theme/context/ThemeStylesContext.ts
index a1c81bbd9c7c..0c7137d206a5 100644
--- a/src/styles/theme/context/ThemeStylesContext.ts
+++ b/src/styles/theme/context/ThemeStylesContext.ts
@@ -1,6 +1,8 @@
 import React from 'react';
+// eslint-disable-next-line no-restricted-imports
 import {defaultStyles} from '@styles/index';
 import type {ThemeStyles} from '@styles/index';
+// eslint-disable-next-line no-restricted-imports
 import {DefaultStyleUtils} from '@styles/utils';
 import type {StyleUtilsType} from '@styles/utils';
 
diff --git a/src/styles/theme/illustrations/index.ts b/src/styles/theme/illustrations/index.ts
index 2686f4205351..abcb20a22d78 100644
--- a/src/styles/theme/illustrations/index.ts
+++ b/src/styles/theme/illustrations/index.ts
@@ -2,14 +2,14 @@ import type {ThemePreferenceWithoutSystem} from '@styles/theme/types';
 import CONST from '@src/CONST';
 import darkIllustrations from './themes/dark';
 import lightIllustrations from './themes/light';
-import type {IllustrationsType} from './types';
+import type IllustrationsType from './types';
 
-const Illustrations = {
+const illustrations = {
     [CONST.THEME.LIGHT]: lightIllustrations,
     [CONST.THEME.DARK]: darkIllustrations,
 } satisfies Record<ThemePreferenceWithoutSystem, IllustrationsType>;
 
-const DefaultIllustrations = Illustrations[CONST.THEME.FALLBACK];
+const defaultIllustrations = illustrations[CONST.THEME.FALLBACK];
 
-export default Illustrations;
-export {DefaultIllustrations};
+export default illustrations;
+export {defaultIllustrations};
diff --git a/src/styles/theme/illustrations/themes/dark.ts b/src/styles/theme/illustrations/themes/dark.ts
index 2920081ac137..e24827e8c9eb 100644
--- a/src/styles/theme/illustrations/themes/dark.ts
+++ b/src/styles/theme/illustrations/themes/dark.ts
@@ -1,7 +1,7 @@
 import EmptyStateBackgroundImage from '@assets/images/themeDependent/empty-state_background-fade-dark.png';
 import ExampleCheckEN from '@assets/images/themeDependent/example-check-image-dark-en.png';
 import ExampleCheckES from '@assets/images/themeDependent/example-check-image-dark-es.png';
-import type {IllustrationsType} from '@styles/theme/illustrations/types';
+import type IllustrationsType from '@styles/theme/illustrations/types';
 
 const illustrations = {
     EmptyStateBackgroundImage,
diff --git a/src/styles/theme/illustrations/themes/light.ts b/src/styles/theme/illustrations/themes/light.ts
index 6eb9c11851e4..cc8dc39806e1 100644
--- a/src/styles/theme/illustrations/themes/light.ts
+++ b/src/styles/theme/illustrations/themes/light.ts
@@ -1,7 +1,7 @@
 import EmptyStateBackgroundImage from '@assets/images/themeDependent/empty-state_background-fade-light.png';
 import ExampleCheckEN from '@assets/images/themeDependent/example-check-image-light-en.png';
 import ExampleCheckES from '@assets/images/themeDependent/example-check-image-light-es.png';
-import type {IllustrationsType} from '@styles/theme/illustrations/types';
+import type IllustrationsType from '@styles/theme/illustrations/types';
 
 const illustrations = {
     EmptyStateBackgroundImage,
diff --git a/src/styles/theme/illustrations/types.ts b/src/styles/theme/illustrations/types.ts
index b37ff17965e2..810e3ad2673a 100644
--- a/src/styles/theme/illustrations/types.ts
+++ b/src/styles/theme/illustrations/types.ts
@@ -6,5 +6,4 @@ type IllustrationsType = {
     ExampleCheckEN: ImageSourcePropType;
 };
 
-// eslint-disable-next-line import/prefer-default-export
-export {type IllustrationsType};
+export default IllustrationsType;
diff --git a/src/styles/utils/fontFamily/bold/index.android.ts b/src/styles/utils/FontUtils/fontFamily/bold/index.android.ts
similarity index 100%
rename from src/styles/utils/fontFamily/bold/index.android.ts
rename to src/styles/utils/FontUtils/fontFamily/bold/index.android.ts
diff --git a/src/styles/utils/fontFamily/bold/index.ios.ts b/src/styles/utils/FontUtils/fontFamily/bold/index.ios.ts
similarity index 100%
rename from src/styles/utils/fontFamily/bold/index.ios.ts
rename to src/styles/utils/FontUtils/fontFamily/bold/index.ios.ts
diff --git a/src/styles/utils/fontFamily/bold/index.ts b/src/styles/utils/FontUtils/fontFamily/bold/index.ts
similarity index 100%
rename from src/styles/utils/fontFamily/bold/index.ts
rename to src/styles/utils/FontUtils/fontFamily/bold/index.ts
diff --git a/src/styles/utils/fontFamily/bold/types.ts b/src/styles/utils/FontUtils/fontFamily/bold/types.ts
similarity index 100%
rename from src/styles/utils/fontFamily/bold/types.ts
rename to src/styles/utils/FontUtils/fontFamily/bold/types.ts
diff --git a/src/styles/utils/fontFamily/index.native.ts b/src/styles/utils/FontUtils/fontFamily/index.native.ts
similarity index 100%
rename from src/styles/utils/fontFamily/index.native.ts
rename to src/styles/utils/FontUtils/fontFamily/index.native.ts
diff --git a/src/styles/utils/fontFamily/index.ts b/src/styles/utils/FontUtils/fontFamily/index.ts
similarity index 100%
rename from src/styles/utils/fontFamily/index.ts
rename to src/styles/utils/FontUtils/fontFamily/index.ts
diff --git a/src/styles/utils/fontFamily/multiFontFamily.ts b/src/styles/utils/FontUtils/fontFamily/multiFontFamily.ts
similarity index 100%
rename from src/styles/utils/fontFamily/multiFontFamily.ts
rename to src/styles/utils/FontUtils/fontFamily/multiFontFamily.ts
diff --git a/src/styles/utils/fontFamily/singleFontFamily.ts b/src/styles/utils/FontUtils/fontFamily/singleFontFamily.ts
similarity index 100%
rename from src/styles/utils/fontFamily/singleFontFamily.ts
rename to src/styles/utils/FontUtils/fontFamily/singleFontFamily.ts
diff --git a/src/styles/utils/fontFamily/types.ts b/src/styles/utils/FontUtils/fontFamily/types.ts
similarity index 100%
rename from src/styles/utils/fontFamily/types.ts
rename to src/styles/utils/FontUtils/fontFamily/types.ts
diff --git a/src/styles/utils/fontWeight/bold/index.android.ts b/src/styles/utils/FontUtils/fontWeight/bold/index.android.ts
similarity index 100%
rename from src/styles/utils/fontWeight/bold/index.android.ts
rename to src/styles/utils/FontUtils/fontWeight/bold/index.android.ts
diff --git a/src/styles/utils/fontWeight/bold/index.ts b/src/styles/utils/FontUtils/fontWeight/bold/index.ts
similarity index 100%
rename from src/styles/utils/fontWeight/bold/index.ts
rename to src/styles/utils/FontUtils/fontWeight/bold/index.ts
diff --git a/src/styles/utils/fontWeight/bold/types.ts b/src/styles/utils/FontUtils/fontWeight/bold/types.ts
similarity index 100%
rename from src/styles/utils/fontWeight/bold/types.ts
rename to src/styles/utils/FontUtils/fontWeight/bold/types.ts
diff --git a/src/styles/utils/FontUtils/index.ts b/src/styles/utils/FontUtils/index.ts
new file mode 100644
index 000000000000..ac07fdbf026e
--- /dev/null
+++ b/src/styles/utils/FontUtils/index.ts
@@ -0,0 +1,20 @@
+import fontFamily from './fontFamily';
+import multiFontFamily from './fontFamily/multiFontFamily';
+import singleFontFamily from './fontFamily/singleFontFamily';
+import fontWeightBold from './fontWeight/bold';
+
+const FontUtils = {
+    fontFamily: {
+        platform: fontFamily,
+        single: singleFontFamily,
+        multi: multiFontFamily,
+    },
+    fontWeight: {
+        bold: fontWeightBold,
+    },
+};
+
+type FontUtilsType = typeof FontUtils;
+
+export default FontUtils;
+export type {FontUtilsType};
diff --git a/src/styles/utils/cardStyles/index.ts b/src/styles/utils/cardStyles/index.ts
index 3a1f778f36f4..af3e465653b8 100644
--- a/src/styles/utils/cardStyles/index.ts
+++ b/src/styles/utils/cardStyles/index.ts
@@ -1,3 +1,4 @@
+// eslint-disable-next-line no-restricted-imports
 import positioning from '@styles/utils/positioning';
 import type GetCardStyles from './types';
 
diff --git a/src/styles/utils/editedLabelStyles/index.ts b/src/styles/utils/editedLabelStyles/index.ts
index 9397966a88a5..0be5e94b3000 100644
--- a/src/styles/utils/editedLabelStyles/index.ts
+++ b/src/styles/utils/editedLabelStyles/index.ts
@@ -1,4 +1,6 @@
+// eslint-disable-next-line no-restricted-imports
 import display from '@styles/utils/display';
+// eslint-disable-next-line no-restricted-imports
 import flex from '@styles/utils/flex';
 import type EditedLabelStyles from './types';
 
diff --git a/src/styles/utils/generators/TooltipStyleUtils.ts b/src/styles/utils/generators/TooltipStyleUtils.ts
index 90cef9a6f568..5bd72928df2b 100644
--- a/src/styles/utils/generators/TooltipStyleUtils.ts
+++ b/src/styles/utils/generators/TooltipStyleUtils.ts
@@ -1,8 +1,10 @@
 import type {TextStyle, View, ViewStyle} from 'react-native';
 import {Animated} from 'react-native';
-import fontFamily from '@styles/utils/fontFamily';
+import roundToNearestMultipleOfFour from '@libs/roundToNearestMultipleOfFour';
+import FontUtils from '@styles/utils/FontUtils';
+// eslint-disable-next-line no-restricted-imports
 import positioning from '@styles/utils/positioning';
-import roundToNearestMultipleOfFour from '@styles/utils/roundToNearestMultipleOfFour';
+// eslint-disable-next-line no-restricted-imports
 import spacing from '@styles/utils/spacing';
 import variables from '@styles/variables';
 import type StyleUtilGenerator from './types';
@@ -272,7 +274,7 @@ const createTooltipStyleUtils: StyleUtilGenerator<GetTooltipStylesStyleUtil> = (
             },
             textStyle: {
                 color: theme.textReversed,
-                fontFamily: fontFamily.EXP_NEUE,
+                fontFamily: FontUtils.fontFamily.platform.EXP_NEUE,
                 fontSize: variables.fontSizeSmall,
                 overflow: 'hidden',
                 lineHeight: variables.lineHeightSmall,
diff --git a/src/styles/utils/getNavigationModalCardStyles/index.desktop.ts b/src/styles/utils/getNavigationModalCardStyles/index.desktop.ts
index c7a469cbd114..df47e76379c5 100644
--- a/src/styles/utils/getNavigationModalCardStyles/index.desktop.ts
+++ b/src/styles/utils/getNavigationModalCardStyles/index.desktop.ts
@@ -1,3 +1,4 @@
+// eslint-disable-next-line no-restricted-imports
 import positioning from '@styles/utils/positioning';
 import type GetNavigationModalCardStyles from './types';
 
diff --git a/src/styles/utils/getNavigationModalCardStyles/index.website.ts b/src/styles/utils/getNavigationModalCardStyles/index.website.ts
index c7a469cbd114..df47e76379c5 100644
--- a/src/styles/utils/getNavigationModalCardStyles/index.website.ts
+++ b/src/styles/utils/getNavigationModalCardStyles/index.website.ts
@@ -1,3 +1,4 @@
+// eslint-disable-next-line no-restricted-imports
 import positioning from '@styles/utils/positioning';
 import type GetNavigationModalCardStyles from './types';
 
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index b3b4924ebb19..e2d237c6bbae 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -4,6 +4,7 @@ import type {EdgeInsets} from 'react-native-safe-area-context';
 import type {ValueOf} from 'type-fest';
 import * as Browser from '@libs/Browser';
 import * as UserUtils from '@libs/UserUtils';
+// eslint-disable-next-line no-restricted-imports
 import {defaultTheme} from '@styles/theme';
 import colors from '@styles/theme/colors';
 import type {ThemeColors} from '@styles/theme/types';
@@ -12,12 +13,14 @@ import CONST from '@src/CONST';
 import type {Transaction} from '@src/types/onyx';
 import {defaultStyles} from '..';
 import type {ThemeStyles} from '..';
+import getCardStyles from './cardStyles';
 import containerComposeStyles from './containerComposeStyles';
-import fontFamily from './fontFamily';
+import FontUtils from './FontUtils';
 import createModalStyleUtils from './generators/ModalStyleUtils';
 import createReportActionContextMenuStyleUtils from './generators/ReportActionContextMenuStyleUtils';
 import createTooltipStyleUtils from './generators/TooltipStyleUtils';
 import getContextMenuItemStyles from './getContextMenuItemStyles';
+import getNavigationModalCardStyle from './getNavigationModalCardStyles';
 import {compactContentContainerStyles} from './optionRowStyles';
 import positioning from './positioning';
 import type {
@@ -518,11 +521,11 @@ function getModalPaddingStyles({
  * Takes fontStyle and fontWeight and returns the correct fontFamily
  */
 function getFontFamilyMonospace({fontStyle, fontWeight}: TextStyle): string {
-    const italic = fontStyle === 'italic' && fontFamily.MONOSPACE_ITALIC;
-    const bold = fontWeight === 'bold' && fontFamily.MONOSPACE_BOLD;
-    const italicBold = italic && bold && fontFamily.MONOSPACE_BOLD_ITALIC;
+    const italic = fontStyle === 'italic' && FontUtils.fontFamily.platform.MONOSPACE_ITALIC;
+    const bold = fontWeight === 'bold' && FontUtils.fontFamily.platform.MONOSPACE_BOLD;
+    const italicBold = italic && bold && FontUtils.fontFamily.platform.MONOSPACE_BOLD_ITALIC;
 
-    return italicBold || bold || italic || fontFamily.MONOSPACE;
+    return italicBold || bold || italic || FontUtils.fontFamily.platform.MONOSPACE;
 }
 /**
  * Returns the font size for the HTML code tag renderer.
@@ -1005,6 +1008,7 @@ function getTransparentColor(color: string) {
 }
 
 const staticStyleUtils = {
+    positioning,
     combineStyles,
     displayIfTrue,
     getAmountFontSizeAndLineHeight,
@@ -1064,6 +1068,8 @@ const staticStyleUtils = {
     parseStyleFromFunction,
     getEReceiptColorStyles,
     getEReceiptColorCode,
+    getNavigationModalCardStyle,
+    getCardStyles,
 };
 
 const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
diff --git a/src/styles/utils/spacing.ts b/src/styles/utils/spacing.ts
index 27d70529dd8a..6def4858229f 100644
--- a/src/styles/utils/spacing.ts
+++ b/src/styles/utils/spacing.ts
@@ -223,6 +223,10 @@ export default {
         marginTop: 'auto',
     },
 
+    mtn2: {
+        marginTop: -8,
+    },
+
     mtn6: {
         marginTop: -24,
     },
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 4d717389cdb6..08a89526e4c3 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -142,7 +142,7 @@ export default {
     signInLogoWidthLargeScreen: 144,
     signInLogoHeightLargeScreen: 108,
     signInLogoWidthPill: 132,
-    tabSelectorButtonHeight: 40,
+    tabSelectorButtonHeight: 42,
     tabSelectorButtonPadding: 12,
     lhnLogoWidth: 95.09,
     lhnLogoHeight: 22.33,
@@ -196,4 +196,5 @@ export default {
     cardPreviewHeight: 148,
     cardPreviewWidth: 235,
     cardNameWidth: 156,
+    holdMenuIconSize: 64,
 } as const;
diff --git a/src/types/onyx/FrequentlyUsedEmoji.ts b/src/types/onyx/FrequentlyUsedEmoji.ts
index 333721b25b52..c8f6a5179fc6 100644
--- a/src/types/onyx/FrequentlyUsedEmoji.ts
+++ b/src/types/onyx/FrequentlyUsedEmoji.ts
@@ -12,7 +12,7 @@ type FrequentlyUsedEmoji = {
     lastUpdatedAt: number;
 
     /** The emoji skin tone type */
-    types?: string[];
+    types?: readonly string[];
 
     /** The emoji keywords */
     keywords?: string[];
diff --git a/src/types/onyx/PolicyCategory.ts b/src/types/onyx/PolicyCategory.ts
index b6dfb7bbab9a..03d5877bc5b5 100644
--- a/src/types/onyx/PolicyCategory.ts
+++ b/src/types/onyx/PolicyCategory.ts
@@ -20,5 +20,5 @@ type PolicyCategory = {
 };
 
 type PolicyCategories = Record<string, PolicyCategory>;
-export default PolicyCategory;
-export type {PolicyCategories};
+
+export type {PolicyCategory, PolicyCategories};
diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts
index 7807dcc00433..58a21dcf4df5 100644
--- a/src/types/onyx/PolicyTag.ts
+++ b/src/types/onyx/PolicyTag.ts
@@ -12,5 +12,4 @@ type PolicyTag = {
 
 type PolicyTags = Record<string, PolicyTag>;
 
-export default PolicyTag;
-export type {PolicyTags};
+export type {PolicyTag, PolicyTags};
diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts
index 840bbd1e2e2f..f6af87038d00 100644
--- a/src/types/onyx/Report.ts
+++ b/src/types/onyx/Report.ts
@@ -120,6 +120,7 @@ type Report = {
     lastActorAccountID?: number;
     ownerAccountID?: number;
     participantAccountIDs?: number[];
+    visibleChatMemberAccountIDs?: number[];
     total?: number;
     currency?: string;
     parentReportActionIDs?: number[];
diff --git a/src/types/onyx/ReportActionReactions.ts b/src/types/onyx/ReportActionReactions.ts
index be117aafc4c5..0173fcf244f5 100644
--- a/src/types/onyx/ReportActionReactions.ts
+++ b/src/types/onyx/ReportActionReactions.ts
@@ -24,7 +24,7 @@ type ReportActionReaction = {
     users: UsersReactions;
 
     /** Is this action pending? */
-    pendingAction?: OnyxCommon.PendingAction;
+    pendingAction: OnyxCommon.PendingAction;
 };
 
 type ReportActionReactions = Record<string, ReportActionReaction>;
diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts
index f7bc5ea1ee8b..dd7a9ef65746 100644
--- a/src/types/onyx/TransactionViolation.ts
+++ b/src/types/onyx/TransactionViolation.ts
@@ -1,46 +1,34 @@
+import type CONST from '@src/CONST';
+
 /**
- * Names of transaction violations
+ * Names of violations.
+ * Derived from `CONST.VIOLATIONS` to maintain a single source of truth.
  */
-type ViolationName =
-    | 'allTagLevelsRequired'
-    | 'autoReportedRejectedExpense'
-    | 'billableExpense'
-    | 'cashExpenseWithNoReceipt'
-    | 'categoryOutOfPolicy'
-    | 'conversionSurcharge'
-    | 'customUnitOutOfPolicy'
-    | 'duplicatedTransaction'
-    | 'fieldRequired'
-    | 'futureDate'
-    | 'invoiceMarkup'
-    | 'maxAge'
-    | 'missingCategory'
-    | 'missingComment'
-    | 'missingTag'
-    | 'modifiedAmount'
-    | 'modifiedDate'
-    | 'nonExpensiworksExpense'
-    | 'overAutoApprovalLimit'
-    | 'overCategoryLimit'
-    | 'overLimit'
-    | 'overLimitAttendee'
-    | 'perDayLimit'
-    | 'receiptNotSmartScanned'
-    | 'receiptRequired'
-    | 'rter'
-    | 'smartscanFailed'
-    | 'someTagLevelsRequired'
-    | 'tagOutOfPolicy'
-    | 'taxAmountChanged'
-    | 'taxOutOfPolicy'
-    | 'taxRateChanged'
-    | 'taxRequired';
+type ViolationName = (typeof CONST.VIOLATIONS)[keyof typeof CONST.VIOLATIONS];
 
 type TransactionViolation = {
     type: string;
     name: ViolationName;
     userMessage: string;
-    data?: Record<string, string>;
+    data?: {
+        rejectedBy?: string;
+        rejectReason?: string;
+        amount?: string;
+        surcharge?: number;
+        invoiceMarkup?: number;
+        maxAge?: number;
+        tagName?: string;
+        formattedLimitAmount?: string;
+        categoryLimit?: string;
+        limit?: string;
+        category?: string;
+        brokenBankConnection?: boolean;
+        isAdmin?: boolean;
+        email?: string;
+        isTransactionOlderThan7Days?: boolean;
+        member?: string;
+        taxName?: string;
+    };
 };
 
 export type {TransactionViolation, ViolationName};
diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts
index de71202dcc2a..7bd9c321be5e 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -27,13 +27,11 @@ import type {PersonalDetailsList} from './PersonalDetails';
 import type PersonalDetails from './PersonalDetails';
 import type PlaidData from './PlaidData';
 import type Policy from './Policy';
-import type {PolicyCategories} from './PolicyCategory';
-import type PolicyCategory from './PolicyCategory';
+import type {PolicyCategories, PolicyCategory} from './PolicyCategory';
 import type {PolicyMembers} from './PolicyMember';
 import type PolicyMember from './PolicyMember';
 import type PolicyReportField from './PolicyReportField';
-import type {PolicyTags} from './PolicyTag';
-import type PolicyTag from './PolicyTag';
+import type {PolicyTag, PolicyTags} from './PolicyTag';
 import type PrivatePersonalDetails from './PrivatePersonalDetails';
 import type RecentlyUsedCategories from './RecentlyUsedCategories';
 import type RecentlyUsedReportFields from './RecentlyUsedReportFields';
diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js
index 4d9ce42a08ce..0a1ac84d52f9 100644
--- a/tests/actions/IOUTest.js
+++ b/tests/actions/IOUTest.js
@@ -2238,6 +2238,86 @@ describe('actions/IOU', () => {
             expect(report).toBeFalsy();
         });
 
+        it('delete the transaction thread if there are only changelogs (i.e. MODIFIEDEXPENSE actions) in the thread', async () => {
+            // Given all promises are resolved
+            await waitForBatchedUpdates();
+            jest.advanceTimersByTime(10);
+
+            // Given a transaction thread
+            thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID);
+
+            Onyx.connect({
+                key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`,
+                callback: (val) => (reportActions = val),
+            });
+
+            await waitForBatchedUpdates();
+
+            jest.advanceTimersByTime(10);
+
+            // Given User logins from the participant accounts
+            const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs);
+
+            // When Opening a thread report with the given details
+            Report.openReport(thread.reportID, userLogins, thread, createIOUAction.reportActionID);
+            await waitForBatchedUpdates();
+
+            // Then The iou action has the transaction report id as a child report ID
+            const allReportActions = await new Promise((resolve) => {
+                const connectionID = Onyx.connect({
+                    key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+                    waitForCollectionCallback: true,
+                    callback: (actions) => {
+                        Onyx.disconnect(connectionID);
+                        resolve(actions);
+                    },
+                });
+            });
+            const reportActionsForIOUReport = allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.iouReportID}`];
+            createIOUAction = _.find(reportActionsForIOUReport, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
+            expect(createIOUAction.childReportID).toBe(thread.reportID);
+
+            await waitForBatchedUpdates();
+
+            jest.advanceTimersByTime(10);
+            IOU.editMoneyRequest(transaction, thread.reportID, {amount: 20000, comment: 'Double the amount!'});
+            await waitForBatchedUpdates();
+
+            // Verify there are two actions (created + changelog)
+            expect(_.size(reportActions)).toBe(2);
+
+            // Fetch the updated IOU Action from Onyx
+            await new Promise((resolve) => {
+                const connectionID = Onyx.connect({
+                    key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+                    waitForCollectionCallback: true,
+                    callback: (reportActionsForReport) => {
+                        Onyx.disconnect(connectionID);
+                        createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
+                        resolve();
+                    },
+                });
+            });
+
+            // When Deleting a money request
+            IOU.deleteMoneyRequest(transaction.transactionID, createIOUAction, false);
+            await waitForBatchedUpdates();
+
+            // Then, the report for the given thread ID does not exist
+            const report = await new Promise((resolve) => {
+                const connectionID = Onyx.connect({
+                    key: `${ONYXKEYS.COLLECTION.REPORT}${thread.reportID}`,
+                    waitForCollectionCallback: true,
+                    callback: (reportData) => {
+                        Onyx.disconnect(connectionID);
+                        resolve(reportData);
+                    },
+                });
+            });
+
+            expect(report).toBeFalsy();
+        });
+
         it('does not delete the transaction thread if there are visible comments in the thread', async () => {
             // Given initial environment is set up
             await waitForBatchedUpdates();
@@ -2369,6 +2449,20 @@ describe('actions/IOU', () => {
             Report.addComment(thread.reportID, 'Testing a comment');
             await waitForBatchedUpdates();
 
+            // Fetch the updated IOU Action from Onyx due to addition of comment to transaction thread.
+            // This needs to be fetched as `deleteMoneyRequest` depends on `childVisibleActionCount` in `createIOUAction`.
+            await new Promise((resolve) => {
+                const connectionID = Onyx.connect({
+                    key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`,
+                    waitForCollectionCallback: true,
+                    callback: (reportActionsForReport) => {
+                        Onyx.disconnect(connectionID);
+                        createIOUAction = _.find(reportActionsForReport, (reportAction) => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU);
+                        resolve();
+                    },
+                });
+            });
+
             let resultAction = _.find(reportActions, (ra) => ra.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT);
             reportActionID = resultAction.reportActionID;
 
diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js
index a82903762631..88f3fe99b347 100644
--- a/tests/perf-test/ReportScreen.perf-test.js
+++ b/tests/perf-test/ReportScreen.perf-test.js
@@ -153,7 +153,7 @@ function ReportScreenWrapper(args) {
 
 const runs = CONST.PERFORMANCE_TESTS.RUNS;
 
-test('[ReportScreen] should render ReportScreen with composer interactions', () => {
+test.skip('[ReportScreen] should render ReportScreen with composer interactions', () => {
     const {triggerTransitionEnd, addListener} = createAddListenerMock();
     const scenario = async () => {
         /**
@@ -226,7 +226,7 @@ test('[ReportScreen] should render ReportScreen with composer interactions', ()
         );
 });
 
-test('[ReportScreen] should press of the report item', () => {
+test.skip('[ReportScreen] should press of the report item', () => {
     const {triggerTransitionEnd, addListener} = createAddListenerMock();
     const scenario = async () => {
         /**
diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js
index f4126ff34313..e4d4d877f66b 100644
--- a/tests/ui/UnreadIndicatorsTest.js
+++ b/tests/ui/UnreadIndicatorsTest.js
@@ -5,6 +5,7 @@ import lodashGet from 'lodash/get';
 import React from 'react';
 import {AppState, DeviceEventEmitter, Linking} from 'react-native';
 import Onyx from 'react-native-onyx';
+import FontUtils from '@styles/utils/FontUtils';
 import App from '../../src/App';
 import CONFIG from '../../src/CONFIG';
 import CONST from '../../src/CONST';
@@ -20,7 +21,6 @@ import * as Pusher from '../../src/libs/Pusher/pusher';
 import PusherConnectionManager from '../../src/libs/PusherConnectionManager';
 import ONYXKEYS from '../../src/ONYXKEYS';
 import appSetup from '../../src/setup';
-import fontWeightBold from '../../src/styles/utils/fontWeight/bold';
 import * as TestHelper from '../utils/TestHelper';
 import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
 import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct';
@@ -294,7 +294,7 @@ describe('Unread Indicators', () => {
                 // And that the text is bold
                 const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
                 const displayNameText = screen.queryByLabelText(displayNameHintText);
-                expect(lodashGet(displayNameText, ['props', 'style', 'fontWeight'])).toBe(fontWeightBold);
+                expect(lodashGet(displayNameText, ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
 
                 return navigateToSidebarOption(0);
             })
@@ -437,11 +437,11 @@ describe('Unread Indicators', () => {
                 const displayNameTexts = screen.queryAllByLabelText(displayNameHintTexts);
                 expect(displayNameTexts).toHaveLength(2);
                 const firstReportOption = displayNameTexts[0];
-                expect(lodashGet(firstReportOption, ['props', 'style', 'fontWeight'])).toBe(fontWeightBold);
+                expect(lodashGet(firstReportOption, ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
                 expect(lodashGet(firstReportOption, ['props', 'children'])).toBe('C User');
 
                 const secondReportOption = displayNameTexts[1];
-                expect(lodashGet(secondReportOption, ['props', 'style', 'fontWeight'])).toBe(fontWeightBold);
+                expect(lodashGet(secondReportOption, ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
                 expect(lodashGet(secondReportOption, ['props', 'children'])).toBe('B User');
 
                 // Tap the new report option and navigate back to the sidebar again via the back button
@@ -456,7 +456,7 @@ describe('Unread Indicators', () => {
                 expect(displayNameTexts).toHaveLength(2);
                 expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(undefined);
                 expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('C User');
-                expect(lodashGet(displayNameTexts[1], ['props', 'style', 'fontWeight'])).toBe(fontWeightBold);
+                expect(lodashGet(displayNameTexts[1], ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
                 expect(lodashGet(displayNameTexts[1], ['props', 'children'])).toBe('B User');
             }));
 
@@ -488,7 +488,7 @@ describe('Unread Indicators', () => {
                 const hintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames');
                 const displayNameTexts = screen.queryAllByLabelText(hintText);
                 expect(displayNameTexts).toHaveLength(1);
-                expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(fontWeightBold);
+                expect(lodashGet(displayNameTexts[0], ['props', 'style', 'fontWeight'])).toBe(FontUtils.fontWeight.bold);
                 expect(lodashGet(displayNameTexts[0], ['props', 'children'])).toBe('B User');
 
                 // Navigate to the report again and back to the sidebar
diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js
index 9ac0186d2396..961df1fa3e90 100644
--- a/tests/unit/OptionsListUtilsTest.js
+++ b/tests/unit/OptionsListUtilsTest.js
@@ -16,6 +16,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 1,
             participantAccountIDs: [2, 1],
+            visibleChatMemberAccountIDs: [2, 1],
             reportName: 'Iron Man, Mister Fantastic',
             hasDraft: true,
             type: CONST.REPORT.TYPE.CHAT,
@@ -26,6 +27,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 2,
             participantAccountIDs: [3],
+            visibleChatMemberAccountIDs: [3],
             reportName: 'Spider-Man',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -37,6 +39,7 @@ describe('OptionsListUtils', () => {
             isPinned: true,
             reportID: 3,
             participantAccountIDs: [1],
+            visibleChatMemberAccountIDs: [1],
             reportName: 'Mister Fantastic',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -46,6 +49,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 4,
             participantAccountIDs: [4],
+            visibleChatMemberAccountIDs: [4],
             reportName: 'Black Panther',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -55,6 +59,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 5,
             participantAccountIDs: [5],
+            visibleChatMemberAccountIDs: [5],
             reportName: 'Invisible Woman',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -64,6 +69,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 6,
             participantAccountIDs: [6],
+            visibleChatMemberAccountIDs: [6],
             reportName: 'Thor',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -75,6 +81,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 7,
             participantAccountIDs: [7],
+            visibleChatMemberAccountIDs: [7],
             reportName: 'Captain America',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -86,6 +93,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 8,
             participantAccountIDs: [12],
+            visibleChatMemberAccountIDs: [12],
             reportName: 'Silver Surfer',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -97,6 +105,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 9,
             participantAccountIDs: [8],
+            visibleChatMemberAccountIDs: [8],
             reportName: 'Mister Sinister',
             iouReportID: 100,
             type: CONST.REPORT.TYPE.CHAT,
@@ -109,6 +118,7 @@ describe('OptionsListUtils', () => {
             reportID: 10,
             isPinned: false,
             participantAccountIDs: [2, 7],
+            visibleChatMemberAccountIDs: [2, 7],
             reportName: '',
             oldPolicyName: "SHIELD's workspace",
             chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
@@ -187,6 +197,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 11,
             participantAccountIDs: [999],
+            visibleChatMemberAccountIDs: [999],
             reportName: 'Concierge',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -200,6 +211,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 12,
             participantAccountIDs: [1000],
+            visibleChatMemberAccountIDs: [1000],
             reportName: 'Chronos',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -213,6 +225,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 13,
             participantAccountIDs: [1001],
+            visibleChatMemberAccountIDs: [1001],
             reportName: 'Receipts',
             type: CONST.REPORT.TYPE.CHAT,
         },
@@ -226,6 +239,7 @@ describe('OptionsListUtils', () => {
             isPinned: false,
             reportID: 14,
             participantAccountIDs: [1, 10, 3],
+            visibleChatMemberAccountIDs: [1, 10, 3],
             reportName: '',
             oldPolicyName: 'Avengers Room',
             isArchivedRoom: false,