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, +};