diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml
index 31bfdf963525..60ba16164eb6 100644
--- a/.github/workflows/e2ePerformanceTests.yml
+++ b/.github/workflows/e2ePerformanceTests.yml
@@ -200,9 +200,10 @@ jobs:
if: failure()
run: |
echo ${{ steps.schedule-awsdf-main.outputs.data }}
- cat "./mainResults/Host_Machine_Files/\$WORKING_DIRECTORY/Test spec output.txt"
unzip "Customer Artifacts.zip" -d mainResults
- cat ./mainResults/Host_Machine_Files/\$WORKING_DIRECTORY/debug.log
+ cat "./mainResults/Host_Machine_Files/\$WORKING_DIRECTORY/logcat.txt" || true
+ cat ./mainResults/Host_Machine_Files/\$WORKING_DIRECTORY/debug.log || true
+ cat "./mainResults/Host_Machine_Files/\$WORKING_DIRECTORY/Test spec output.txt" || true
- name: Unzip AWS Device Farm results
if: ${{ always() }}
diff --git a/.github/workflows/failureNotifier.yml b/.github/workflows/failureNotifier.yml
new file mode 100644
index 000000000000..17e18e6e53f0
--- /dev/null
+++ b/.github/workflows/failureNotifier.yml
@@ -0,0 +1,96 @@
+name: Notify on Workflow Failure
+
+on:
+ workflow_run:
+ workflows: ["Process new code merged to main"]
+ types:
+ - completed
+
+permissions:
+ issues: write
+
+jobs:
+ notifyFailure:
+ runs-on: ubuntu-latest
+ if: ${{ github.event.workflow_run.conclusion == 'failure' }}
+ steps:
+ - name: Fetch Workflow Run Jobs
+ id: fetch-workflow-jobs
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const runId = "${{ github.event.workflow_run.id }}";
+ const jobsData = await github.rest.actions.listJobsForWorkflowRun({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: runId,
+ });
+ return jobsData.data;
+
+ - name: Process Each Failed Job
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const jobs = ${{ steps.fetch-workflow-jobs.outputs.result }};
+
+ const headCommit = "${{ github.event.workflow_run.head_commit.id }}";
+ const prData = await github.rest.repos.listPullRequestsAssociatedWithCommit({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ commit_sha: headCommit,
+ });
+
+ const pr = prData.data[0];
+ const prLink = pr.html_url;
+ const prAuthor = pr.user.login;
+ const prMerger = "${{ github.event.workflow_run.actor.login }}";
+
+ const failureLabel = 'Workflow Failure';
+ for (let i = 0; i < jobs.total_count; i++) {
+ if (jobs.jobs[i].conclusion == 'failure') {
+ const jobName = jobs.jobs[i].name;
+ const jobLink = jobs.jobs[i].html_url;
+ const issues = await github.rest.issues.listForRepo({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ labels: failureLabel,
+ state: 'open'
+ });
+ const existingIssue = issues.data.find(issue => issue.title.includes(jobName));
+ if (!existingIssue) {
+ const annotations = await github.rest.checks.listAnnotations({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ check_run_id: jobs.jobs[i].id,
+ });
+ let errorMessage = "";
+ for(let j = 0; j < annotations.data.length; j++) {
+ errorMessage += annotations.data[j].annotation_level + ": ";
+ errorMessage += annotations.data[j].message + "\n";
+ }
+ const issueTitle = `Investigate workflow job failing on main: ${ jobName }`;
+ const issueBody = `🚨 **Failure Summary** 🚨:\n\n` +
+ `- **📋 Job Name**: [${ jobName }](${ jobLink })\n` +
+ `- **🔧 Failure in Workflow**: Process new code merged to main\n` +
+ `- **🔗 Triggered by PR**: [PR Link](${ prLink })\n` +
+ `- **👤 PR Author**: @${ prAuthor }\n` +
+ `- **🤝 Merged by**: @${ prMerger }\n` +
+ `- **🐛 Error Message**: \n ${errorMessage}\n\n` +
+ `⚠️ **Action Required** ⚠️:\n\n` +
+ `🛠️ A recent merge appears to have caused a failure in the job named [${ jobName }](${ jobLink }).\n` +
+ `This issue has been automatically created and labeled with \`${ failureLabel }\` for investigation. \n\n` +
+ `👀 **Please look into the following**:\n` +
+ `1. **Why the PR caused the job to fail?**\n` +
+ `2. **Address any underlying issues.**\n\n` +
+ `🐛 We appreciate your help in squashing this bug!`;
+ github.rest.issues.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title: issueTitle,
+ body: issueBody,
+ labels: [failureLabel, 'Daily'],
+ assignees: [prMerger, prAuthor]
+ });
+ }
+ }
+ }
diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml
index 8f9512062e9d..f09865de0194 100644
--- a/.github/workflows/preDeploy.yml
+++ b/.github/workflows/preDeploy.yml
@@ -1,3 +1,4 @@
+# Reminder: If this workflow's name changes, update the name in the dependent workflow at .github/workflows/failureNotifier.yml.
name: Process new code merged to main
on:
diff --git a/__mocks__/@react-native-community/push-notification-ios.js b/__mocks__/@react-native-community/push-notification-ios.js
deleted file mode 100644
index 0fe8354b9e08..000000000000
--- a/__mocks__/@react-native-community/push-notification-ios.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- addEventListener: jest.fn(),
- requestPermissions: jest.fn(() => Promise.resolve()),
- getInitialNotification: jest.fn(() => Promise.resolve()),
-};
diff --git a/__mocks__/@react-native-firebase/perf.js b/__mocks__/@react-native-firebase/perf.js
deleted file mode 100644
index 2d1ec238274a..000000000000
--- a/__mocks__/@react-native-firebase/perf.js
+++ /dev/null
@@ -1 +0,0 @@
-export default () => {};
diff --git a/__mocks__/@react-native-firebase/perf.ts b/__mocks__/@react-native-firebase/perf.ts
new file mode 100644
index 000000000000..e304b1a1f007
--- /dev/null
+++ b/__mocks__/@react-native-firebase/perf.ts
@@ -0,0 +1,5 @@
+type PerfMock = () => void;
+
+const perfMock: PerfMock = () => {};
+
+export default perfMock;
diff --git a/__mocks__/push-notification-ios.js b/__mocks__/push-notification-ios.js
deleted file mode 100644
index 0fe8354b9e08..000000000000
--- a/__mocks__/push-notification-ios.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default {
- addEventListener: jest.fn(),
- requestPermissions: jest.fn(() => Promise.resolve()),
- getInitialNotification: jest.fn(() => Promise.resolve()),
-};
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 5dd4858fe0d7..c01c6052562b 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001043706
- versionName "1.4.37-6"
+ versionCode 1001043801
+ versionName "1.4.38-1"
}
flavorDimensions "default"
diff --git a/assets/images/simple-illustrations/simple-illustration__gears.svg b/assets/images/simple-illustrations/simple-illustration__gears.svg
new file mode 100644
index 000000000000..3b4cbc001e3b
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__gears.svg
@@ -0,0 +1,101 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__lockclosed.svg b/assets/images/simple-illustrations/simple-illustration__lockclosed.svg
new file mode 100644
index 000000000000..3779b92b0b0f
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__lockclosed.svg
@@ -0,0 +1,17 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__palmtree.svg b/assets/images/simple-illustrations/simple-illustration__palmtree.svg
new file mode 100644
index 000000000000..2aef4956cde9
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__palmtree.svg
@@ -0,0 +1,15 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__profile.svg b/assets/images/simple-illustrations/simple-illustration__profile.svg
new file mode 100644
index 000000000000..85312f26e186
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__profile.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/images/simple-illustrations/simple-illustration__qr-code.svg b/assets/images/simple-illustrations/simple-illustration__qr-code.svg
new file mode 100644
index 000000000000..10268d747588
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__qr-code.svg
@@ -0,0 +1,4 @@
+
diff --git a/babel.config.js b/babel.config.js
index 0a17f2b0f01c..d3bcecdae8cb 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,5 +1,7 @@
require('dotenv').config();
+const IS_E2E_TESTING = process.env.E2E_TESTING === 'true';
+
const defaultPresets = ['@babel/preset-react', '@babel/preset-env', '@babel/preset-flow', '@babel/preset-typescript'];
const defaultPlugins = [
// Adding the commonjs: true option to react-native-web plugin can cause styling conflicts
@@ -72,7 +74,8 @@ const metro = {
],
env: {
production: {
- plugins: [['transform-remove-console', {exclude: ['error', 'warn']}]],
+ // Keep console logs for e2e tests
+ plugins: IS_E2E_TESTING ? [] : [['transform-remove-console', {exclude: ['error', 'warn']}]],
},
},
};
diff --git a/ios/NewApp_AdHoc.mobileprovision.gpg b/ios/NewApp_AdHoc.mobileprovision.gpg
index 454857981834..643c81bd0b9c 100644
Binary files a/ios/NewApp_AdHoc.mobileprovision.gpg and b/ios/NewApp_AdHoc.mobileprovision.gpg differ
diff --git a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg
index ba9258840638..8a5170cfe697 100644
Binary files a/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg and b/ios/NewApp_AdHoc_Notification_Service.mobileprovision.gpg differ
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index e01da07d28b7..f329d6d25473 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.4.37
+ 1.4.38
CFBundleSignature
????
CFBundleURLTypes
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.37.6
+ 1.4.38.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index f2004108628c..b2708f520b28 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -15,10 +15,10 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 1.4.37
+ 1.4.38
CFBundleSignature
????
CFBundleVersion
- 1.4.37.6
+ 1.4.38.1
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 6d85c5af21a5..653420df99a6 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -11,9 +11,9 @@
CFBundleName
$(PRODUCT_NAME)
CFBundleShortVersionString
- 1.4.37
+ 1.4.38
CFBundleVersion
- 1.4.37.6
+ 1.4.38.1
NSExtension
NSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index 5d63067adc81..5b3949c10a84 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.37-6",
+ "version": "1.4.38-1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.37-6",
+ "version": "1.4.38-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index e7b8631496bf..e4de321aff32 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.37-6",
+ "version": "1.4.38-1",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index e987c5b94d7d..5617c063bb57 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -281,10 +281,6 @@ const ROUTES = {
route: ':iouType/new/currency/:reportID?',
getRoute: (iouType: string, reportID: string, currency: string, backTo: string) => `${iouType}/new/currency/${reportID}?currency=${currency}&backTo=${backTo}` as const,
},
- MONEY_REQUEST_DESCRIPTION: {
- route: ':iouType/new/description/:reportID?',
- getRoute: (iouType: string, reportID = '') => `${iouType}/new/description/${reportID}` as const,
- },
MONEY_REQUEST_CATEGORY: {
route: ':iouType/new/category/:reportID?',
getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}` as const,
@@ -347,9 +343,9 @@ const ROUTES = {
getUrlWithBackToParam(`create/${iouType}/date/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_DESCRIPTION: {
- route: 'create/:iouType/description/:transactionID/:reportID',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
- getUrlWithBackToParam(`create/${iouType}/description/${transactionID}/${reportID}`, backTo),
+ route: ':action/:iouType/description/:transactionID/:reportID',
+ getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
+ getUrlWithBackToParam(`${action}/${iouType}/description/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_DISTANCE: {
route: 'create/:iouType/distance/:transactionID/:reportID',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index e2f0e9745561..538c72addc9a 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -150,7 +150,6 @@ const SCREENS = {
CONFIRMATION: 'Money_Request_Confirmation',
CURRENCY: 'Money_Request_Currency',
DATE: 'Money_Request_Date',
- DESCRIPTION: 'Money_Request_Description',
CATEGORY: 'Money_Request_Category',
MERCHANT: 'Money_Request_Merchant',
WAYPOINT: 'Money_Request_Waypoint',
diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx
index 6af3a4c6d477..34bc3f7e30c8 100644
--- a/src/components/Breadcrumbs.tsx
+++ b/src/components/Breadcrumbs.tsx
@@ -38,7 +38,7 @@ function Breadcrumbs({breadcrumbs, style}: BreadcrumbsProps) {
const [primaryBreadcrumb, secondaryBreadcrumb] = breadcrumbs;
return (
-
+
{primaryBreadcrumb.type === CONST.BREADCRUMB_TYPE.ROOT ? (
Navigation.goBack(ROUTES.HOME),
@@ -65,12 +67,21 @@ function HeaderWithBackButton({
const {isKeyboardShown} = useKeyboardState();
const waitForNavigate = useWaitForNavigation();
+ // If the icon is present, the header bar should be taller and use different font.
+ const isCentralPaneSettings = !!icon;
+
return (
{shouldShowBackButton && (
@@ -99,6 +110,14 @@ function HeaderWithBackButton({
)}
+ {icon && (
+
+ )}
{shouldShowAvatarWithDisplay ? (
)}
diff --git a/src/components/HeaderWithBackButton/types.ts b/src/components/HeaderWithBackButton/types.ts
index 88f7e717a44d..83afbad8636b 100644
--- a/src/components/HeaderWithBackButton/types.ts
+++ b/src/components/HeaderWithBackButton/types.ts
@@ -28,6 +28,13 @@ type HeaderWithBackButtonProps = Partial & {
/** Title color */
titleColor?: string;
+ /**
+ * Icon displayed on the left of the title.
+ * If it is passed, the new styling is applied to the component:
+ * taller header on desktop and different font of the title.
+ * */
+ icon?: IconAsset;
+
/** Method to trigger when pressing download button of the header */
onDownloadButtonPress?: () => void;
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 954c8d0392fc..51422e7b4a49 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -37,11 +37,13 @@ import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustra
import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg';
import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg';
import EmailAddress from '@assets/images/simple-illustrations/simple-illustration__email-address.svg';
+import Gears from '@assets/images/simple-illustrations/simple-illustration__gears.svg';
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 LockClosed from '@assets/images/simple-illustrations/simple-illustration__lockclosed.svg';
import LockOpen from '@assets/images/simple-illustrations/simple-illustration__lockopen.svg';
import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg';
import Mailbox from '@assets/images/simple-illustrations/simple-illustration__mailbox.svg';
@@ -50,6 +52,9 @@ import MoneyBadge from '@assets/images/simple-illustrations/simple-illustration_
import MoneyIntoWallet from '@assets/images/simple-illustrations/simple-illustration__moneyintowallet.svg';
import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg';
import OpenSafe from '@assets/images/simple-illustrations/simple-illustration__opensafe.svg';
+import PalmTree from '@assets/images/simple-illustrations/simple-illustration__palmtree.svg';
+import Profile from '@assets/images/simple-illustrations/simple-illustration__profile.svg';
+import QrCode from '@assets/images/simple-illustrations/simple-illustration__qr-code.svg';
import SanFrancisco from '@assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg';
import ShieldYellow from '@assets/images/simple-illustrations/simple-illustration__shield.svg';
import SmallRocket from '@assets/images/simple-illustrations/simple-illustration__smallrocket.svg';
@@ -118,4 +123,9 @@ export {
CommentBubbles,
TrashCan,
TeleScope,
+ Profile,
+ PalmTree,
+ LockClosed,
+ Gears,
+ QrCode,
};
diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx
index c1f4df8d4c99..7fc5013f58a0 100644
--- a/src/components/Modal/BaseModal.tsx
+++ b/src/components/Modal/BaseModal.tsx
@@ -176,6 +176,8 @@ function BaseModal(
return (
e.stopPropagation()}
onBackdropPress={handleBackdropPress}
// Note: Escape key on web/desktop will trigger onBackButtonPress callback
// eslint-disable-next-line react/jsx-props-no-multi-spaces
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index b6a91cf7a9c8..e9a5f2ba635e 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -665,11 +665,15 @@ function MoneyRequestConfirmationList(props) {
title={props.iouComment}
description={translate('common.description')}
onPress={() => {
- if (props.isEditingSplitBill) {
- Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION));
- return;
- }
- Navigation.navigate(ROUTES.MONEY_REQUEST_DESCRIPTION.getRoute(props.iouType, props.reportID));
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ props.iouType,
+ transaction.transactionID,
+ props.reportID,
+ Navigation.getActiveRouteWithoutParams(),
+ ),
+ );
}}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index be5cec7a2c0d..e088dae0cadf 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -693,11 +693,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
title={iouComment}
description={translate('common.description')}
onPress={() => {
- if (isEditingSplitBill) {
- Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(reportID, reportActionID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION));
- return;
- }
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()));
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()),
+ );
}}
style={[styles.moneyRequestMenuItem]}
titleStyle={styles.flex1}
diff --git a/src/components/PopoverWithoutOverlay/index.tsx b/src/components/PopoverWithoutOverlay/index.tsx
index 58d022ef9d65..a87e1f1f0412 100644
--- a/src/components/PopoverWithoutOverlay/index.tsx
+++ b/src/components/PopoverWithoutOverlay/index.tsx
@@ -121,6 +121,8 @@ function PopoverWithoutOverlay(
e.stopPropagation()}
>
Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION))}
+ onPress={() =>
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ CONST.IOU.TYPE.REQUEST,
+ transaction?.transactionID ?? '',
+ report.reportID,
+ Navigation.getActiveRouteWithoutParams(),
+ ),
+ )
+ }
wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
brickRoadIndicator={hasViolations('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
numberOfLinesTitle={0}
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
index 110c13fa07bf..0e2863fed5f1 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
@@ -99,7 +99,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/steps/MoneyRequestConfirmPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.DATE]: () => require('../../../pages/iou/MoneyRequestDatePage').default as React.ComponentType,
- [SCREENS.MONEY_REQUEST.DESCRIPTION]: () => require('../../../pages/iou/MoneyRequestDescriptionPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.CATEGORY]: () => require('../../../pages/iou/MoneyRequestCategoryPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.MERCHANT]: () => require('../../../pages/iou/MoneyRequestMerchantPage').default as React.ComponentType,
[SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: () => require('../../../pages/AddPersonalBankAccountPage').default as React.ComponentType,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 6f96642953af..1eeede4506cc 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -404,7 +404,6 @@ const config: LinkingOptions['config'] = {
[SCREENS.MONEY_REQUEST.CONFIRMATION]: ROUTES.MONEY_REQUEST_CONFIRMATION.route,
[SCREENS.MONEY_REQUEST.DATE]: ROUTES.MONEY_REQUEST_DATE.route,
[SCREENS.MONEY_REQUEST.CURRENCY]: ROUTES.MONEY_REQUEST_CURRENCY.route,
- [SCREENS.MONEY_REQUEST.DESCRIPTION]: ROUTES.MONEY_REQUEST_DESCRIPTION.route,
[SCREENS.MONEY_REQUEST.CATEGORY]: ROUTES.MONEY_REQUEST_CATEGORY.route,
[SCREENS.MONEY_REQUEST.MERCHANT]: ROUTES.MONEY_REQUEST_MERCHANT.route,
[SCREENS.MONEY_REQUEST.RECEIPT]: ROUTES.MONEY_REQUEST_RECEIPT.route,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index d544c2ffa3b6..0abe267bdaa3 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -215,11 +215,12 @@ type MoneyRequestNavigatorParamList = {
field: string;
threadReportID: string;
};
- [SCREENS.MONEY_REQUEST.DESCRIPTION]: {
- iouType: string;
+ [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: {
+ action: ValueOf;
+ iouType: ValueOf;
+ transactionID: string;
reportID: string;
- field: string;
- threadReportID: string;
+ backTo: string;
};
[SCREENS.MONEY_REQUEST.CATEGORY]: {
iouType: string;
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index dd118c36a8a1..0ce540bea456 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -286,9 +286,8 @@ function setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID: str
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {originalCurrency});
}
-// eslint-disable-next-line @typescript-eslint/naming-convention
-function setMoneyRequestDescription_temporaryForRefactor(transactionID: string, comment: string) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {comment: {comment: comment.trim()}});
+function setMoneyRequestDescription(transactionID: string, comment: string, isDraft: boolean) {
+ Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {comment: {comment: comment.trim()}});
}
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -3554,10 +3553,6 @@ function setMoneyRequestCurrency(currency: string) {
Onyx.merge(ONYXKEYS.IOU, {currency});
}
-function setMoneyRequestDescription(comment: string) {
- Onyx.merge(ONYXKEYS.IOU, {comment: comment.trim()});
-}
-
function setMoneyRequestMerchant(merchant: string) {
Onyx.merge(ONYXKEYS.IOU, {merchant: merchant.trim()});
}
@@ -3698,8 +3693,8 @@ export {
setMoneyRequestCategory_temporaryForRefactor,
setMoneyRequestCreated_temporaryForRefactor,
setMoneyRequestCurrency_temporaryForRefactor,
+ setMoneyRequestDescription,
setMoneyRequestOriginalCurrency_temporaryForRefactor,
- setMoneyRequestDescription_temporaryForRefactor,
setMoneyRequestMerchant_temporaryForRefactor,
setMoneyRequestParticipants_temporaryForRefactor,
setMoneyRequestReceipt,
@@ -3708,7 +3703,6 @@ export {
setMoneyRequestCategory,
setMoneyRequestCreated,
setMoneyRequestCurrency,
- setMoneyRequestDescription,
setMoneyRequestId,
setMoneyRequestMerchant,
setMoneyRequestParticipantsFromReport,
diff --git a/src/pages/EditRequestDescriptionPage.js b/src/pages/EditRequestDescriptionPage.js
deleted file mode 100644
index 9b2a9e465746..000000000000
--- a/src/pages/EditRequestDescriptionPage.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import {useFocusEffect} from '@react-navigation/native';
-import PropTypes from 'prop-types';
-import React, {useCallback, useRef} from 'react';
-import {View} from 'react-native';
-import FormProvider from '@components/Form/FormProvider';
-import InputWrapperWithRef from '@components/Form/InputWrapper';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import TextInput from '@components/TextInput';
-import useLocalize from '@hooks/useLocalize';
-import useThemeStyles from '@hooks/useThemeStyles';
-import * as Browser from '@libs/Browser';
-import updateMultilineInputRange from '@libs/updateMultilineInputRange';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-
-const propTypes = {
- /** Transaction default description value */
- defaultDescription: PropTypes.string.isRequired,
-
- /** Callback to fire when the Save button is pressed */
- onSubmit: PropTypes.func.isRequired,
-};
-
-function EditRequestDescriptionPage({defaultDescription, onSubmit}) {
- const styles = useThemeStyles();
- const {translate} = useLocalize();
- const descriptionInputRef = useRef(null);
- const focusTimeoutRef = useRef(null);
-
- useFocusEffect(
- useCallback(() => {
- focusTimeoutRef.current = setTimeout(() => {
- if (descriptionInputRef.current) {
- descriptionInputRef.current.focus();
- }
- return () => {
- if (!focusTimeoutRef.current) {
- return;
- }
- clearTimeout(focusTimeoutRef.current);
- };
- }, CONST.ANIMATED_TRANSITION);
- }, []),
- );
-
- return (
-
-
-
-
- {
- if (!el) {
- return;
- }
- descriptionInputRef.current = el;
- updateMultilineInputRange(descriptionInputRef.current);
- }}
- autoGrowHeight
- containerStyles={[styles.autoGrowHeightMultilineInput]}
- submitOnEnter={!Browser.isMobile()}
- />
-
-
-
- );
-}
-
-EditRequestDescriptionPage.propTypes = propTypes;
-EditRequestDescriptionPage.displayName = 'EditRequestDescriptionPage';
-
-export default EditRequestDescriptionPage;
diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js
index 3eb9d88f1120..7e1a9f7d9b7b 100644
--- a/src/pages/EditRequestPage.js
+++ b/src/pages/EditRequestPage.js
@@ -22,7 +22,6 @@ import ROUTES from '@src/ROUTES';
import EditRequestAmountPage from './EditRequestAmountPage';
import EditRequestCategoryPage from './EditRequestCategoryPage';
import EditRequestCreatedPage from './EditRequestCreatedPage';
-import EditRequestDescriptionPage from './EditRequestDescriptionPage';
import EditRequestDistancePage from './EditRequestDistancePage';
import EditRequestMerchantPage from './EditRequestMerchantPage';
import EditRequestReceiptPage from './EditRequestReceiptPage';
@@ -74,7 +73,6 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep
const {
amount: transactionAmount,
currency: transactionCurrency,
- comment: transactionDescription,
merchant: transactionMerchant,
category: transactionCategory,
tag: transactionTag,
@@ -180,26 +178,6 @@ function EditRequestPage({report, route, policyCategories, policyTags, parentRep
[transactionCategory, transaction.transactionID, report.reportID],
);
- const saveComment = useCallback(
- ({comment: newComment}) => {
- // Only update comment if it has changed
- if (newComment.trim() !== transactionDescription) {
- IOU.updateMoneyRequestDescription(transaction.transactionID, report.reportID, newComment.trim());
- }
- Navigation.dismissModal();
- },
- [transactionDescription, transaction.transactionID, report.reportID],
- );
-
- if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) {
- return (
-
- );
- }
-
if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DATE) {
return (
{
- setDraftSplitTransaction({
- comment: transactionChanges.comment.trim(),
- });
- }}
- />
- );
- }
-
if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DATE) {
return (
Navigation.goBack(isReport ? ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID) : ROUTES.SETTINGS)}
shouldShowBackButton={isReport || isSmallScreenWidth}
+ icon={Illustrations.QrCode}
/>
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index 7b299a18a030..f6f14d01889d 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -235,7 +235,7 @@ function HeaderView(props) {
style={[shouldShowBorderBottom && styles.borderBottom]}
dataSet={{dragArea: true}}
>
-
+
{isLoading ? (
diff --git a/src/pages/home/sidebar/AllSettingsScreen.tsx b/src/pages/home/sidebar/AllSettingsScreen.tsx
index 0406c38cf659..3f6d0ab6a318 100644
--- a/src/pages/home/sidebar/AllSettingsScreen.tsx
+++ b/src/pages/home/sidebar/AllSettingsScreen.tsx
@@ -107,7 +107,7 @@ function AllSettingsScreen({policies, policyMembers}: AllSettingsScreenProps) {
text: translate('common.settings'),
},
]}
- style={[styles.pb5, styles.ph5]}
+ style={[styles.mb5, styles.ph5]}
/>
{
- focusTimeoutRef.current = setTimeout(() => {
- if (inputRef.current) {
- inputRef.current.focus();
- }
- return () => {
- if (!focusTimeoutRef.current) {
- return;
- }
- clearTimeout(focusTimeoutRef.current);
- };
- }, CONST.ANIMATED_TRANSITION);
- }, []),
- );
-
- useEffect(() => {
- const moneyRequestId = `${iouType}${reportID}`;
- const shouldReset = iou.id !== moneyRequestId;
- if (shouldReset) {
- IOU.resetMoneyRequestInfo(moneyRequestId);
- }
-
- if (!isDistanceRequest && (_.isEmpty(iou.participants) || (iou.amount === 0 && !iou.receiptPath) || shouldReset)) {
- Navigation.goBack(ROUTES.MONEY_REQUEST.getRoute(iouType, reportID), true);
- }
- }, [iou.id, iou.participants, iou.amount, iou.receiptPath, iouType, reportID, isDistanceRequest]);
-
- function navigateBack() {
- Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, reportID));
- }
-
- /**
- * Sets the money request comment by saving it to Onyx.
- *
- * @param {Object} value
- * @param {String} value.moneyRequestComment
- */
- function updateComment(value) {
- IOU.setMoneyRequestDescription(value.moneyRequestComment);
- navigateBack();
- }
-
- return (
-
- <>
- navigateBack()}
- />
- updateComment(value)}
- submitButtonText={translate('common.save')}
- enabledWhenOffline
- >
-
- {
- if (!el) {
- return;
- }
- inputRef.current = el;
- updateMultilineInputRange(inputRef.current);
- }}
- autoGrowHeight
- containerStyles={[styles.autoGrowHeightMultilineInput]}
- submitOnEnter={!Browser.isMobile()}
- />
-
-
- >
-
- );
-}
-
-MoneyRequestDescriptionPage.propTypes = propTypes;
-MoneyRequestDescriptionPage.defaultProps = defaultProps;
-MoneyRequestDescriptionPage.displayName = 'MoneyRequestDescriptionPage';
-
-export default withOnyx({
- iou: {
- key: ONYXKEYS.IOU,
- },
- selectedTab: {
- key: `${ONYXKEYS.COLLECTION.SELECTED_TAB}${CONST.TAB.RECEIPT_TAB_ID}`,
- },
-})(MoneyRequestDescriptionPage);
diff --git a/src/pages/iou/request/step/IOURequestStepDescription.js b/src/pages/iou/request/step/IOURequestStepDescription.js
index 849f3276667e..25477170f505 100644
--- a/src/pages/iou/request/step/IOURequestStepDescription.js
+++ b/src/pages/iou/request/step/IOURequestStepDescription.js
@@ -1,7 +1,9 @@
import {useFocusEffect} from '@react-navigation/native';
import lodashGet from 'lodash/get';
+import lodashIsEmpty from 'lodash/isEmpty';
import React, {useCallback, useRef} from 'react';
import {View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
import FormProvider from '@components/Form/FormProvider';
import InputWrapperWithRef from '@components/Form/InputWrapper';
import TextInput from '@components/TextInput';
@@ -28,23 +30,31 @@ const propTypes = {
/** Onyx Props */
/** Holds data related to Money Request view state, rather than the underlying Money Request data. */
transaction: transactionPropTypes,
+
+ /** The draft transaction that holds data to be persisted on the current transaction */
+ splitDraftTransaction: transactionPropTypes,
};
const defaultProps = {
transaction: {},
+ splitDraftTransaction: {},
};
function IOURequestStepDescription({
route: {
- params: {transactionID, backTo},
+ params: {action, iouType, reportID, backTo},
},
transaction,
+ splitDraftTransaction,
}) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const inputRef = useRef(null);
const focusTimeoutRef = useRef(null);
-
+ // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value
+ const isEditingSplitBill = iouType === CONST.IOU.TYPE.SPLIT && action === CONST.IOU.ACTION.EDIT;
+ const currentDescription =
+ isEditingSplitBill && !lodashIsEmpty(splitDraftTransaction) ? lodashGet(splitDraftTransaction, 'comment.comment', '') : lodashGet(transaction, 'comment.comment', '');
useFocusEffect(
useCallback(() => {
focusTimeoutRef.current = setTimeout(() => {
@@ -70,7 +80,27 @@ function IOURequestStepDescription({
* @param {String} value.moneyRequestComment
*/
const updateComment = (value) => {
- IOU.setMoneyRequestDescription_temporaryForRefactor(transactionID, value.moneyRequestComment);
+ const newComment = value.moneyRequestComment.trim();
+
+ // Only update comment if it has changed
+ if (newComment === currentDescription) {
+ navigateBack();
+ return;
+ }
+
+ // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value
+ if (isEditingSplitBill) {
+ IOU.setDraftSplitTransaction(transaction.transactionID, {comment: newComment});
+ navigateBack();
+ return;
+ }
+
+ IOU.setMoneyRequestDescription(transaction.transactionID, newComment, action === CONST.IOU.ACTION.CREATE);
+
+ if (action === CONST.IOU.ACTION.EDIT) {
+ IOU.updateMoneyRequestDescription(transaction.transactionID, reportID, newComment);
+ }
+
navigateBack();
};
@@ -93,10 +123,10 @@ function IOURequestStepDescription({
InputComponent={TextInput}
inputID="moneyRequestComment"
name="moneyRequestComment"
- defaultValue={lodashGet(transaction, 'comment.comment', '')}
+ defaultValue={currentDescription}
label={translate('moneyRequestConfirmationList.whatsItFor')}
accessibilityLabel={translate('moneyRequestConfirmationList.whatsItFor')}
- role={CONST.ACCESSIBILITY_ROLE.TEXT}
+ role={CONST.ROLE.PRESENTATION}
ref={(el) => {
if (!el) {
return;
@@ -106,7 +136,6 @@ function IOURequestStepDescription({
}}
autoGrowHeight
containerStyles={[styles.autoGrowHeightMultilineInput]}
- inputStyle={[styles.verticalAlignTop]}
submitOnEnter={!Browser.isMobile()}
/>
@@ -119,4 +148,15 @@ IOURequestStepDescription.propTypes = propTypes;
IOURequestStepDescription.defaultProps = defaultProps;
IOURequestStepDescription.displayName = 'IOURequestStepDescription';
-export default compose(withWritableReportOrNotFound, withFullTransactionOrNotFound)(IOURequestStepDescription);
+export default compose(
+ withWritableReportOrNotFound,
+ withFullTransactionOrNotFound,
+ withOnyx({
+ splitDraftTransaction: {
+ key: ({route}) => {
+ const transactionID = lodashGet(route, 'params.transactionID', 0);
+ return `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`;
+ },
+ },
+ }),
+)(IOURequestStepDescription);
diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js
index 1297b98a0814..a05a14cb4403 100644
--- a/src/pages/iou/request/step/IOURequestStepTag.js
+++ b/src/pages/iou/request/step/IOURequestStepTag.js
@@ -69,7 +69,7 @@ function IOURequestStepTag({
const updateTag = (selectedTag) => {
const isSelectedTag = selectedTag.searchText === tag;
const updatedTag = !isSelectedTag ? selectedTag.searchText : '';
- if (isSplitBill) {
+ if (isSplitBill && isEditing) {
IOU.setDraftSplitTransaction(transactionID, {tag: selectedTag.searchText});
navigateBack();
return;
diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.js b/src/pages/iou/request/step/IOURequestStepWaypoint.js
index 4c35951bc297..93a87baa0481 100644
--- a/src/pages/iou/request/step/IOURequestStepWaypoint.js
+++ b/src/pages/iou/request/step/IOURequestStepWaypoint.js
@@ -113,8 +113,8 @@ function IOURequestStepWaypoint({
const locationBias = useLocationBias(allWaypoints, userLocation);
const waypointAddress = lodashGet(currentWaypoint, 'address', '');
- // Hide the menu when there is only start and finish waypoint
- const shouldShowThreeDotsButton = waypointCount > 2;
+ // Hide the menu when there is only start and finish waypoint or the current waypoint is empty
+ const shouldShowThreeDotsButton = waypointCount > 2 && waypointAddress;
const shouldDisableEditor =
isFocused &&
(Number.isNaN(parsedWaypointIndex) || parsedWaypointIndex < 0 || parsedWaypointIndex > waypointCount || (filledWaypointCount < 2 && parsedWaypointIndex >= waypointCount));
diff --git a/src/pages/settings/AboutPage/AboutPage.tsx b/src/pages/settings/AboutPage/AboutPage.tsx
index 93948cf0565e..a78d248d1d4c 100644
--- a/src/pages/settings/AboutPage/AboutPage.tsx
+++ b/src/pages/settings/AboutPage/AboutPage.tsx
@@ -4,6 +4,7 @@ import {View} from 'react-native';
import type {GestureResponderEvent, Text as RNText} from 'react-native';
import DeviceInfo from 'react-native-device-info';
import * as Expensicons from '@components/Icon/Expensicons';
+import * as Illustrations from '@components/Icon/Illustrations';
import IllustratedHeaderPageLayout from '@components/IllustratedHeaderPageLayout';
import LottieAnimations from '@components/LottieAnimations';
import MenuItemList from '@components/MenuItemList';
@@ -132,6 +133,7 @@ function AboutPage() {
backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.ABOUT].backgroundColor}
overlayContent={overlayContent}
shouldShowOfflineIndicatorInWideScreen
+ icon={Illustrations.PalmTree}
testID={AboutPage.displayName}
>
diff --git a/src/pages/settings/Preferences/PreferencesPage.js b/src/pages/settings/Preferences/PreferencesPage.js
index f95daf8ad7d2..6ea7e1eb5280 100755
--- a/src/pages/settings/Preferences/PreferencesPage.js
+++ b/src/pages/settings/Preferences/PreferencesPage.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
+import * as Illustrations from '@components/Icon/Illustrations';
import IllustratedHeaderPageLayout from '@components/IllustratedHeaderPageLayout';
import LottieAnimations from '@components/LottieAnimations';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
@@ -55,6 +56,7 @@ function PreferencesPage(props) {
illustration={LottieAnimations.PreferencesDJ}
shouldShowBackButton={isSmallScreenWidth}
shouldShowOfflineIndicatorInWideScreen
+ icon={Illustrations.Gears}
testID={PreferencesPage.displayName}
>
diff --git a/src/pages/settings/Profile/ProfilePage.js b/src/pages/settings/Profile/ProfilePage.js
index c090a478f416..204fe3452db0 100755
--- a/src/pages/settings/Profile/ProfilePage.js
+++ b/src/pages/settings/Profile/ProfilePage.js
@@ -6,6 +6,7 @@ import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
+import * as Illustrations from '@components/Icon/Illustrations';
import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import ScreenWrapper from '@components/ScreenWrapper';
@@ -111,6 +112,7 @@ function ProfilePage(props) {
title={props.translate('common.profile')}
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS)}
shouldShowBackButton={props.isSmallScreenWidth}
+ icon={Illustrations.Profile}
/>
diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx
index 05c879669e78..b6881a9b3683 100644
--- a/src/pages/settings/Security/SecuritySettingsPage.tsx
+++ b/src/pages/settings/Security/SecuritySettingsPage.tsx
@@ -1,6 +1,7 @@
import React, {useMemo} from 'react';
import {ScrollView, View} from 'react-native';
import * as Expensicons from '@components/Icon/Expensicons';
+import * as Illustrations from '@components/Icon/Illustrations';
import IllustratedHeaderPageLayout from '@components/IllustratedHeaderPageLayout';
import LottieAnimations from '@components/LottieAnimations';
import MenuItemList from '@components/MenuItemList';
@@ -53,6 +54,7 @@ function SecuritySettingsPage() {
illustration={LottieAnimations.Safe}
backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.SECURITY].backgroundColor}
shouldShowOfflineIndicatorInWideScreen
+ icon={Illustrations.LockClosed}
testID={SecuritySettingsPage.displayName}
>
diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js
index 989b4f8838b9..417d5b890dea 100644
--- a/src/pages/settings/Wallet/WalletPage/WalletPage.js
+++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js
@@ -341,6 +341,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod
Navigation.goBack(ROUTES.SETTINGS)}
+ icon={Illustrations.MoneyIntoWallet}
shouldShowBackButton={isSmallScreenWidth}
/>
diff --git a/src/pages/workspace/WorkspaceInitialPage.js b/src/pages/workspace/WorkspaceInitialPage.js
index 8141fe92a5d9..7cd4fcb536b6 100644
--- a/src/pages/workspace/WorkspaceInitialPage.js
+++ b/src/pages/workspace/WorkspaceInitialPage.js
@@ -187,7 +187,7 @@ function WorkspaceInitialPage(props) {
text: translate('common.settings'),
},
]}
- style={[styles.ph5, styles.pb5]}
+ style={[styles.ph5, styles.mb5]}
/>
textDecorationLine: 'none',
},
+ breadcrumsContainer: {
+ height: 24,
+ },
+
breadcrumb: {
color: theme.textSupporting,
fontSize: variables.fontSizeh1,
@@ -2264,6 +2268,10 @@ const styles = (theme: ThemeColors) =>
width: '100%',
},
+ headerBarDesktopHeight: {
+ height: variables.contentHeaderDesktopHeight,
+ },
+
imageViewContainer: {
width: '100%',
height: '100%',
diff --git a/src/styles/theme/themes/dark.ts b/src/styles/theme/themes/dark.ts
index b9bf97037072..54a26a587e81 100644
--- a/src/styles/theme/themes/dark.ts
+++ b/src/styles/theme/themes/dark.ts
@@ -109,7 +109,7 @@ const darkTheme = {
statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT,
},
[SCREENS.SETTINGS.WORKSPACES]: {
- backgroundColor: colors.pink800,
+ backgroundColor: colors.productDark100,
statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT,
},
[SCREENS.SETTINGS.WALLET.ROOT]: {
diff --git a/src/styles/theme/themes/light.ts b/src/styles/theme/themes/light.ts
index 3514e4c4b105..7aea2d48aac0 100644
--- a/src/styles/theme/themes/light.ts
+++ b/src/styles/theme/themes/light.ts
@@ -109,7 +109,7 @@ const lightTheme = {
statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT,
},
[SCREENS.SETTINGS.WORKSPACES]: {
- backgroundColor: colors.pink800,
+ backgroundColor: colors.productLight100,
statusBarStyle: CONST.STATUS_BAR_STYLE.LIGHT_CONTENT,
},
[SCREENS.SETTINGS.WALLET.ROOT]: {
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 296780abf0ae..d643ad811a19 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -12,9 +12,9 @@ function getValueUsingPixelRatio(defaultValue: number, maxValue: number): number
}
export default {
- // do we need to change it?
bottomTabHeight: 80,
- contentHeaderHeight: getValueUsingPixelRatio(80, 100),
+ contentHeaderHeight: getValueUsingPixelRatio(72, 100),
+ contentHeaderDesktopHeight: getValueUsingPixelRatio(80, 100),
componentSizeSmall: getValueUsingPixelRatio(28, 32),
componentSizeNormal: 40,
componentSizeMedium: 48,
@@ -81,6 +81,7 @@ export default {
iconSizeSuperLarge: 60,
iconSizeUltraLarge: 120,
iconBottomBar: 24,
+ iconHeader: 48,
emojiSize: 20,
emojiLineHeight: 28,
iouAmountTextSize: 40,
diff --git a/src/types/onyx/PolicyCategory.ts b/src/types/onyx/PolicyCategory.ts
index 03d5877bc5b5..4bc926e21419 100644
--- a/src/types/onyx/PolicyCategory.ts
+++ b/src/types/onyx/PolicyCategory.ts
@@ -1,3 +1,5 @@
+import type * as OnyxCommon from './OnyxCommon';
+
type PolicyCategory = {
/** Name of a category */
name: string;
@@ -17,6 +19,9 @@ type PolicyCategory = {
/** The external accounting service that this category comes from */
origin: string;
+
+ /** A list of errors keyed by microtime */
+ errors?: OnyxCommon.Errors;
};
type PolicyCategories = Record;
diff --git a/tests/perf-test/SearchPage.perf-test.js b/tests/perf-test/SearchPage.perf-test.js
index b27a2e2115be..8c6148f9e103 100644
--- a/tests/perf-test/SearchPage.perf-test.js
+++ b/tests/perf-test/SearchPage.perf-test.js
@@ -103,7 +103,7 @@ function SearchPageWrapper(args) {
);
}
-test('[Search Page] should interact when text input changes', async () => {
+test.skip('[Search Page] should interact when text input changes', async () => {
const {addListener} = TestHelper.createAddListenerMock();
const scenario = async () => {
@@ -130,7 +130,7 @@ test('[Search Page] should interact when text input changes', async () => {
.then(() => measurePerformance(, {scenario}));
});
-test('[Search Page] should render options list', async () => {
+test.skip('[Search Page] should render options list', async () => {
const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock();
const smallMockedPersonalDetails = getMockedPersonalDetails(5);
@@ -156,7 +156,7 @@ test('[Search Page] should render options list', async () => {
.then(() => measurePerformance(, {scenario}));
});
-test('[Search Page] should search in options list', async () => {
+test.skip('[Search Page] should search in options list', async () => {
const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock();
const scenario = async () => {
@@ -183,7 +183,7 @@ test('[Search Page] should search in options list', async () => {
.then(() => measurePerformance(, {scenario}));
});
-test('[Search Page] should click on list item', async () => {
+test.skip('[Search Page] should click on list item', async () => {
const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock();
const scenario = async () => {
diff --git a/web/index.html b/web/index.html
index 7b2fbe018111..ba57c852d921 100644
--- a/web/index.html
+++ b/web/index.html
@@ -3,7 +3,7 @@
New Expensify
-
+
@@ -11,7 +11,7 @@
-
+
diff --git a/workflow_tests/assertions/failureNotifierAssertions.js b/workflow_tests/assertions/failureNotifierAssertions.js
new file mode 100644
index 000000000000..2491c4fa8469
--- /dev/null
+++ b/workflow_tests/assertions/failureNotifierAssertions.js
@@ -0,0 +1,20 @@
+const utils = require('../utils/utils');
+
+const assertNotifyFailureJobExecuted = (workflowResult, didExecute = true) => {
+ const steps = [
+ utils.createStepAssertion('Fetch Workflow Run Jobs', true, null, 'NOTIFYFAILURE', 'Fetch Workflow Run Jobs', [], []),
+ utils.createStepAssertion('Process Each Failed Job', true, null, 'NOTIFYFAILURE', 'Process Each Failed Job', [], []),
+ ];
+
+ for (const expectedStep of steps) {
+ if (didExecute) {
+ expect(workflowResult).toEqual(expect.arrayContaining([expectedStep]));
+ } else {
+ expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep]));
+ }
+ }
+};
+
+module.exports = {
+ assertNotifyFailureJobExecuted,
+};
diff --git a/workflow_tests/failureNotifier.test.js b/workflow_tests/failureNotifier.test.js
new file mode 100644
index 000000000000..655d5ed64d83
--- /dev/null
+++ b/workflow_tests/failureNotifier.test.js
@@ -0,0 +1,59 @@
+const path = require('path');
+const kieMockGithub = require('@kie/mock-github');
+const assertions = require('./assertions/failureNotifierAssertions');
+const mocks = require('./mocks/failureNotifierMocks');
+const eAct = require('./utils/ExtendedAct');
+
+jest.setTimeout(90 * 1000);
+let mockGithub;
+const FILES_TO_COPY_INTO_TEST_REPO = [
+ {
+ src: path.resolve(__dirname, '..', '.github', 'workflows', 'failureNotifier.yml'),
+ dest: '.github/workflows/failureNotifier.yml',
+ },
+];
+
+describe('test workflow failureNotifier', () => {
+ const actor = 'Dummy Actor';
+ beforeEach(async () => {
+ // create a local repository and copy required files
+ mockGithub = new kieMockGithub.MockGithub({
+ repo: {
+ testFailureNotifierWorkflowRepo: {
+ files: FILES_TO_COPY_INTO_TEST_REPO,
+
+ // if any branches besides main are need add: pushedBranches: ['staging', 'production'],
+ },
+ },
+ });
+
+ await mockGithub.setup();
+ });
+
+ afterEach(async () => {
+ await mockGithub.teardown();
+ });
+ it('runs the notify failure when main fails', async () => {
+ const repoPath = mockGithub.repo.getPath('testFailureNotifierWorkflowRepo') || '';
+ const workflowPath = path.join(repoPath, '.github', 'workflows', 'failureNotifier.yml');
+ let act = new eAct.ExtendedAct(repoPath, workflowPath);
+ const event = 'workflow_run';
+ act = act.setEvent({
+ workflow_run: {
+ name: 'Process new code merged to main',
+ conclusion: 'failure',
+ },
+ });
+ const testMockSteps = {
+ notifyFailure: mocks.FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS,
+ };
+ const result = await act.runEvent(event, {
+ workflowFile: path.join(repoPath, '.github', 'workflows', 'failureNotifier.yml'),
+ mockSteps: testMockSteps,
+ actor,
+ });
+
+ // assert execution with imported assertions
+ assertions.assertNotifyFailureJobExecuted(result);
+ });
+});
diff --git a/workflow_tests/mocks/failureNotifierMocks.js b/workflow_tests/mocks/failureNotifierMocks.js
new file mode 100644
index 000000000000..6d37f08ff7ac
--- /dev/null
+++ b/workflow_tests/mocks/failureNotifierMocks.js
@@ -0,0 +1,11 @@
+/* eslint-disable rulesdir/no-negated-variables */
+const utils = require('../utils/utils');
+
+// notifyfailure
+const FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK = utils.createMockStep('Fetch Workflow Run Jobs', 'Fetch Workflow Run Jobs', 'NOTIFYFAILURE', [], []);
+const FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK = utils.createMockStep('Process Each Failed Job', 'Process Each Failed Job', 'NOTIFYFAILURE', [], []);
+const FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS = [FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK, FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK];
+
+module.exports = {
+ FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS,
+};