diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/action.yml b/.github/actions/javascript/markPullRequestsAsDeployed/action.yml index f0ca77bdbf0..40dfc05e544 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/action.yml +++ b/.github/actions/javascript/markPullRequestsAsDeployed/action.yml @@ -14,7 +14,6 @@ inputs: GITHUB_TOKEN: description: "Github token for authentication" required: true - default: "${{ github.token }}" ANDROID: description: "Android job result ('success', 'failure', 'cancelled', or 'skipped')" required: true @@ -27,6 +26,12 @@ inputs: WEB: description: "Web job result ('success', 'failure', 'cancelled', or 'skipped')" required: true + DATE: + description: "The date of deployment" + required: false + NOTE: + description: "Additional note from the deployer" + required: false runs: using: "node20" main: "./index.js" diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/index.js b/.github/actions/javascript/markPullRequestsAsDeployed/index.js index 9f97e4a72d2..b38b0414139 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/index.js +++ b/.github/actions/javascript/markPullRequestsAsDeployed/index.js @@ -12713,9 +12713,15 @@ async function run() { const desktopResult = getDeployTableMessage(core.getInput('DESKTOP', { required: true })); const iOSResult = getDeployTableMessage(core.getInput('IOS', { required: true })); const webResult = getDeployTableMessage(core.getInput('WEB', { required: true })); + const date = core.getInput('DATE'); + const note = core.getInput('NOTE'); function getDeployMessage(deployer, deployVerb, prTitle) { let message = `šŸš€ [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}`; - message += ` by https://github.com/${deployer} in version: ${version} šŸš€`; + message += ` by https://github.com/${deployer} in version: ${version} `; + if (date) { + message += `on ${date}`; + } + message += `šŸš€`; message += `\n\nplatform | result\n---|---\nšŸ¤– android šŸ¤–|${androidResult}\nšŸ–„ desktop šŸ–„|${desktopResult}`; message += `\nšŸŽ iOS šŸŽ|${iOSResult}\nšŸ•ø web šŸ•ø|${webResult}`; if (deployVerb === 'Cherry-picked' && !/no ?qa/gi.test(prTitle ?? '')) { @@ -12723,6 +12729,9 @@ async function run() { message += '\n\n@Expensify/applauseleads please QA this PR and check it off on the [deploy checklist](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3AStagingDeployCash) if it passes.'; } + if (note) { + message += `\n\n_Note:_ ${note}`; + } return message; } if (isProd) { diff --git a/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts b/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts index 71a5c7d5c6e..e6424c89833 100644 --- a/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts +++ b/.github/actions/javascript/markPullRequestsAsDeployed/markPullRequestsAsDeployed.ts @@ -55,9 +55,16 @@ async function run() { const iOSResult = getDeployTableMessage(core.getInput('IOS', {required: true}) as PlatformResult); const webResult = getDeployTableMessage(core.getInput('WEB', {required: true}) as PlatformResult); + const date = core.getInput('DATE'); + const note = core.getInput('NOTE'); + function getDeployMessage(deployer: string, deployVerb: string, prTitle?: string): string { let message = `šŸš€ [${deployVerb}](${workflowURL}) to ${isProd ? 'production' : 'staging'}`; - message += ` by https://github.com/${deployer} in version: ${version} šŸš€`; + message += ` by https://github.com/${deployer} in version: ${version} `; + if (date) { + message += `on ${date}`; + } + message += `šŸš€`; message += `\n\nplatform | result\n---|---\nšŸ¤– android šŸ¤–|${androidResult}\nšŸ–„ desktop šŸ–„|${desktopResult}`; message += `\nšŸŽ iOS šŸŽ|${iOSResult}\nšŸ•ø web šŸ•ø|${webResult}`; @@ -67,6 +74,10 @@ async function run() { '\n\n@Expensify/applauseleads please QA this PR and check it off on the [deploy checklist](https://github.com/Expensify/App/issues?q=is%3Aopen+is%3Aissue+label%3AStagingDeployCash) if it passes.'; } + if (note) { + message += `\n\n_Note:_ ${note}`; + } + return message; } diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2ab19d13183..53afe03720f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -649,34 +649,14 @@ jobs: GITHUB_TOKEN: ${{ github.token }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} - postGithubComment: - name: Post a GitHub comments on all deployed PRs when platforms are done building and deploying - runs-on: ubuntu-latest + postGithubComments: + uses: ./.github/workflows/postDeployComments.yml if: ${{ always() && fromJSON(needs.checkDeploymentSuccess.outputs.IS_AT_LEAST_ONE_PLATFORM_DEPLOYED) }} needs: [prep, android, desktop, iOS, web, checkDeploymentSuccess, createPrerelease, finalizeRelease] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: ./.github/actions/composite/setupNode - - - name: Get Release Pull Request List - id: getReleasePRList - uses: ./.github/actions/javascript/getDeployPullRequestList - with: - TAG: ${{ needs.prep.outputs.APP_VERSION }} - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - IS_PRODUCTION_DEPLOY: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - - - name: Comment on issues - uses: ./.github/actions/javascript/markPullRequestsAsDeployed - with: - PR_LIST: ${{ steps.getReleasePRList.outputs.PR_LIST }} - IS_PRODUCTION_DEPLOY: ${{ fromJSON(env.SHOULD_DEPLOY_PRODUCTION) }} - DEPLOY_VERSION: ${{ needs.prep.outputs.APP_VERSION }} - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - ANDROID: ${{ needs.android.result }} - DESKTOP: ${{ needs.desktop.result }} - IOS: ${{ needs.iOS.result }} - WEB: ${{ needs.web.result }} + with: + version: ${{ needs.prep.outputs.APP_VERSION }} + env: ${{ github.ref == 'refs/heads/production' && 'production' || 'staging' }} + android: ${{ needs.android.result }} + ios: ${{ needs.iOS.result }} + web: ${{ needs.web.result }} + desktop: ${{ needs.desktop.result }} diff --git a/.github/workflows/postDeployComments.yml b/.github/workflows/postDeployComments.yml new file mode 100644 index 00000000000..3893d3cf3f7 --- /dev/null +++ b/.github/workflows/postDeployComments.yml @@ -0,0 +1,118 @@ +name: Post Deploy Comments + +on: + workflow_call: + inputs: + version: + description: The version that was deployed + required: true + type: string + env: + description: The environment that was deployed (staging or prod) + required: true + type: string + android: + description: Android deploy status + required: true + type: string + ios: + description: iOS deploy status + required: true + type: string + web: + description: Web deploy status + required: true + type: string + desktop: + description: Desktop deploy status + required: true + type: string + workflow_dispatch: + inputs: + version: + description: The version that was deployed + required: true + type: string + env: + description: The environment that was deployed (staging or prod) + required: true + type: choice + options: + - staging + - production + android: + description: Android deploy status + required: true + type: choice + options: + - success + - failure + - cancelled + - skipped + ios: + description: iOS deploy status + required: true + type: choice + options: + - success + - failure + - cancelled + - skipped + web: + description: Web deploy status + required: true + type: choice + options: + - success + - failure + - cancelled + - skipped + desktop: + description: Desktop deploy status + required: true + type: choice + options: + - success + - failure + - cancelled + - skipped + date: + description: The date when this deploy occurred + required: false + type: string + note: + description: Any additional note you want to include with the deploy comment + required: false + type: string + +jobs: + postDeployComments: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: ./.github/actions/composite/setupNode + + - name: Get pull request list + id: getPullRequestList + uses: ./.github/actions/javascript/getDeployPullRequestList + with: + TAG: ${{ inputs.version }} + GITHUB_TOKEN: ${{ github.token }} + IS_PRODUCTION_DEPLOY: ${{ inputs.env == 'production' }} + + - name: Comment on issues + uses: ./.github/actions/javascript/markPullRequestsAsDeployed + with: + PR_LIST: ${{ steps.getPullRequestList.outputs.PR_LIST }} + IS_PRODUCTION_DEPLOY: ${{ inputs.env == 'production' }} + DEPLOY_VERSION: ${{ inputs.version }} + GITHUB_TOKEN: ${{ github.token }} + ANDROID: ${{ inputs.android }} + DESKTOP: ${{ inputs.desktop }} + IOS: ${{ inputs.ios }} + WEB: ${{ inputs.web }} + DATE: ${{ inputs.date }} + NOTE: ${{ inputs.note }} diff --git a/android/app/build.gradle b/android/app/build.gradle index 491c810cb35..fffbd66aefa 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009003902 - versionName "9.0.39-2" + versionCode 1009004000 + versionName "9.0.40-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/android/build.gradle b/android/build.gradle index 85e547439cc..fd3f9997612 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -29,7 +29,7 @@ buildscript { classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1") classpath("com.google.firebase:perf-plugin:1.4.1") // Fullstory integration - classpath ("com.fullstory:gradle-plugin-local:1.49.0") + classpath ("com.fullstory:gradle-plugin-local:1.52.0") // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/assets/images/expensify-card.svg b/assets/images/expensify-card.svg index 52f55778b2b..2989f5025ae 100644 --- a/assets/images/expensify-card.svg +++ b/assets/images/expensify-card.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/babel.config.js b/babel.config.js index 3721edaa7af..663eb29d5d2 100644 --- a/babel.config.js +++ b/babel.config.js @@ -21,6 +21,7 @@ const defaultPlugins = [ '@babel/transform-runtime', '@babel/plugin-proposal-class-properties', + ['@babel/plugin-transform-object-rest-spread', {useBuiltIns: true, loose: true}], // We use `@babel/plugin-transform-class-properties` for transforming ReactNative libraries and do not use it for our own // source code transformation as we do not use class property assignment. diff --git a/docs/articles/new-expensify/expenses-&-payments/Connect-a-Personal-Bank-Account.md b/docs/articles/new-expensify/expenses-&-payments/Connect-a-Personal-Bank-Account.md index b8e66c937a0..2d33552f3e3 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Connect-a-Personal-Bank-Account.md +++ b/docs/articles/new-expensify/expenses-&-payments/Connect-a-Personal-Bank-Account.md @@ -8,7 +8,10 @@ Connecting a personal bank account to Expensify allows you to get reimbursed for 1. Click your profile image or icon in the bottom left menu. 2. Click **Wallet**. 3. Click **Add Bank Account**. -4. Click **Continue** (this will open a new window and redirect you to Plaid). + + ![Wallet Settings, showing where to connect a bank account](https://help.expensify.com/assets/images/addbankaccount_01.png){:width="100%"} + +5. Click **Continue** (this will open a new window and redirect you to Plaid). {% include info.html %} Plaid is an encrypted third-party financial data platform that Expensify uses to securely verify your banking information. @@ -19,4 +22,6 @@ Plaid is an encrypted third-party financial data platform that Expensify uses to 7. Choose which account you want to connect to Expensify. 8. Click **Save & continue**. + ![Wallet Settings, showing bank account connected](https://help.expensify.com/assets/images/addbankaccount_03.png){:width="100%"} + Once connected, all payments and reimbursements will be deposited directly into that bank account. diff --git a/docs/assets/images/invoices_01.png b/docs/assets/images/invoices_01.png new file mode 100644 index 00000000000..fc6d5587bb0 Binary files /dev/null and b/docs/assets/images/invoices_01.png differ diff --git a/docs/assets/images/invoices_02.png b/docs/assets/images/invoices_02.png new file mode 100644 index 00000000000..29038987c18 Binary files /dev/null and b/docs/assets/images/invoices_02.png differ diff --git a/docs/assets/images/invoices_03.png b/docs/assets/images/invoices_03.png new file mode 100644 index 00000000000..fd78aa73178 Binary files /dev/null and b/docs/assets/images/invoices_03.png differ diff --git a/docs/assets/images/invoices_04.png b/docs/assets/images/invoices_04.png new file mode 100644 index 00000000000..d2e301a9d1a Binary files /dev/null and b/docs/assets/images/invoices_04.png differ diff --git a/docs/assets/images/invoices_05.png b/docs/assets/images/invoices_05.png new file mode 100644 index 00000000000..8eae5efaa9d Binary files /dev/null and b/docs/assets/images/invoices_05.png differ diff --git a/docs/assets/images/invoices_06.png b/docs/assets/images/invoices_06.png new file mode 100644 index 00000000000..2858227891e Binary files /dev/null and b/docs/assets/images/invoices_06.png differ diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 7bed47c032a..7d3f7bba95f 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.39 + 9.0.40 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.39.2 + 9.0.40.0 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 6e01c0d2f91..aa39f6fba8a 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.39 + 9.0.40 CFBundleSignature ???? CFBundleVersion - 9.0.39.2 + 9.0.40.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 74158e9d574..f6a27648973 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.39 + 9.0.40 CFBundleVersion - 9.0.39.2 + 9.0.40.0 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile b/ios/Podfile index 2ed1752abf4..e807089c26b 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -121,4 +121,4 @@ target 'NotificationServiceExtension' do pod 'AirshipServiceExtension' end -pod 'FullStory', :http => 'https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz' \ No newline at end of file +pod 'FullStory', :http => 'https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz' \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0f1a42791d1..a801a7c4de1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -143,8 +143,8 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - "GoogleUtilities/NSData+zlib (~> 7.7)" - fmt (9.1.0) - - FullStory (1.49.0) - - fullstory_react-native (1.4.2): + - FullStory (1.52.0) + - fullstory_react-native (1.7.1): - DoubleConversion - FullStory (~> 1.14) - glog @@ -2700,7 +2700,7 @@ DEPENDENCIES: - ExpoModulesCore (from `../node_modules/expo-modules-core`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - "FullStory (from `{:http=>\"https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz\"}`)" + - "FullStory (from `{:http=>\"https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz\"}`)" - "fullstory_react-native (from `../node_modules/@fullstory/react-native`)" - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) @@ -2874,7 +2874,7 @@ EXTERNAL SOURCES: fmt: :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" FullStory: - :http: https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz + :http: https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz fullstory_react-native: :path: "../node_modules/@fullstory/react-native" glog: @@ -3089,7 +3089,7 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: FullStory: - :http: https://ios-releases.fullstory.com/fullstory-1.49.0-xcframework.tar.gz + :http: https://ios-releases.fullstory.com/fullstory-1.52.0-xcframework.tar.gz SPEC CHECKSUMS: Airship: bb32ff2c5a811352da074480357d9f02dbb8f327 @@ -3116,8 +3116,8 @@ SPEC CHECKSUMS: FirebasePerformance: 0c01a7a496657d7cea86d40c0b1725259d164c6c FirebaseRemoteConfig: 2d6e2cfdb49af79535c8af8a80a4a5009038ec2b fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 - FullStory: c95f74445f871bc344cdc4a4e4ece61b5554e55d - fullstory_react-native: 1818ee93dc38801665f26869f7ad68abb698a89a + FullStory: c8a10b2358c0d33c57be84d16e4c440b0434b33d + fullstory_react-native: 44dc2c85a6316df2713e6cb0048ce5719c3b0bab glog: 69ef571f3de08433d766d614c73a9838a06bf7eb GoogleAppMeasurement: 5ba1164e3c844ba84272555e916d0a6d3d977e91 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a @@ -3248,6 +3248,6 @@ SPEC CHECKSUMS: VisionCamera: c6c8aa4b028501fc87644550fbc35a537d4da3fb Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8 -PODFILE CHECKSUM: e479ec84cb53e5fd463486d71dfee91708d3fd9a +PODFILE CHECKSUM: a07e55247056ec5d84d1af31d694506efff3cfe2 COCOAPODS: 1.15.2 diff --git a/package-lock.json b/package-lock.json index 3c27c44a3bd..d5672be4b48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.39-2", + "version": "9.0.40-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.39-2", + "version": "9.0.40-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 6f4980f04ee..4965fb8f60e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.39-2", + "version": "9.0.40-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", diff --git a/src/CONST.ts b/src/CONST.ts index 17d49215b20..d770d2bda75 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2101,6 +2101,9 @@ const CONST = { ACCESS_VARIANTS: { CREATE: 'create', }, + PAGE_INDEX: { + CONFIRM: 'confirm', + }, PAYMENT_SELECTED: { BBA: 'BBA', PBA: 'PBA', @@ -2456,6 +2459,7 @@ const CONST = { }, }, EXPENSIFY_CARD: { + NAME: 'expensifyCard', BANK: 'Expensify Card', FRAUD_TYPES: { DOMAIN: 'domain', @@ -4559,7 +4563,7 @@ const CONST = { { type: 'setupTags', autoCompleted: false, - title: 'Set up tags (optional)', + title: 'Set up tags', description: ({workspaceMoreFeaturesLink}) => 'Tags can be used if you want more details with every expense. Use tags for projects, clients, locations, departments, and more. If you need multiple levels of tags you can upgrade to a control plan.\n' + '\n' + diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 64ad1f660c1..7fcb675dc19 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -207,8 +207,8 @@ const ONYXKEYS = { /** The NVP containing all information related to educational tooltip in workspace chat */ NVP_WORKSPACE_TOOLTIP: 'workspaceTooltip', - /** Whether to hide save search rename tooltip */ - NVP_SHOULD_HIDE_SAVED_SEARCH_RENAME_TOOLTIP: 'nvp_should_hide_saved_search_rename_tooltip', + /** Whether to show save search rename tooltip */ + SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP: 'shouldShowSavedSearchRenameTooltip', /** Whether to hide gbr tooltip */ NVP_SHOULD_HIDE_GBR_TOOLTIP: 'nvp_should_hide_gbr_tooltip', @@ -983,7 +983,7 @@ type OnyxValuesMapping = { [ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx; [ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet; [ONYXKEYS.LAST_ROUTE]: string; - [ONYXKEYS.NVP_SHOULD_HIDE_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; + [ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean; }; type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 9b351bd3189..6ed67c0d312 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -74,7 +74,7 @@ const ROUTES = { SUBMIT_EXPENSE: 'submit-expense', FLAG_COMMENT: { route: 'flag/:reportID/:reportActionID', - getRoute: (reportID: string, reportActionID: string) => `flag/${reportID}/${reportActionID}` as const, + getRoute: (reportID: string, reportActionID: string, backTo?: string) => getUrlWithBackToParam(`flag/${reportID}/${reportActionID}` as const, backTo), }, CHAT_FINDER: 'chat-finder', PROFILE: { @@ -287,11 +287,11 @@ const ROUTES = { }, EDIT_REPORT_FIELD_REQUEST: { route: 'r/:reportID/edit/policyField/:policyID/:fieldID', - getRoute: (reportID: string, policyID: string, fieldID: string) => `r/${reportID}/edit/policyField/${policyID}/${fieldID}` as const, + getRoute: (reportID: string, policyID: string, fieldID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/edit/policyField/${policyID}/${fieldID}` as const, backTo), }, REPORT_WITH_ID_DETAILS_SHARE_CODE: { route: 'r/:reportID/details/shareCode', - getRoute: (reportID: string) => `r/${reportID}/details/shareCode` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/details/shareCode` as const, backTo), }, ATTACHMENTS: { route: 'attachment', @@ -300,19 +300,19 @@ const ROUTES = { }, REPORT_PARTICIPANTS: { route: 'r/:reportID/participants', - getRoute: (reportID: string) => `r/${reportID}/participants` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants` as const, backTo), }, REPORT_PARTICIPANTS_INVITE: { route: 'r/:reportID/participants/invite', - getRoute: (reportID: string) => `r/${reportID}/participants/invite` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants/invite` as const, backTo), }, REPORT_PARTICIPANTS_DETAILS: { route: 'r/:reportID/participants/:accountID', - getRoute: (reportID: string, accountID: number) => `r/${reportID}/participants/${accountID}` as const, + getRoute: (reportID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants/${accountID}` as const, backTo), }, REPORT_PARTICIPANTS_ROLE_SELECTION: { route: 'r/:reportID/participants/:accountID/role', - getRoute: (reportID: string, accountID: number) => `r/${reportID}/participants/${accountID}/role` as const, + getRoute: (reportID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/participants/${accountID}/role` as const, backTo), }, REPORT_WITH_ID_DETAILS: { route: 'r/:reportID/details', @@ -320,65 +320,65 @@ const ROUTES = { }, REPORT_WITH_ID_DETAILS_EXPORT: { route: 'r/:reportID/details/export/:connectionName', - getRoute: (reportID: string, connectionName: ConnectionName) => `r/${reportID}/details/export/${connectionName}` as const, + getRoute: (reportID: string, connectionName: ConnectionName, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/details/export/${connectionName}` as const, backTo), }, REPORT_SETTINGS: { route: 'r/:reportID/settings', - getRoute: (reportID: string) => `r/${reportID}/settings` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings` as const, backTo), }, REPORT_SETTINGS_NAME: { route: 'r/:reportID/settings/name', - getRoute: (reportID: string) => `r/${reportID}/settings/name` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings/name` as const, backTo), }, REPORT_SETTINGS_NOTIFICATION_PREFERENCES: { route: 'r/:reportID/settings/notification-preferences', - getRoute: (reportID: string) => `r/${reportID}/settings/notification-preferences` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings/notification-preferences` as const, backTo), }, REPORT_SETTINGS_WRITE_CAPABILITY: { route: 'r/:reportID/settings/who-can-post', - getRoute: (reportID: string) => `r/${reportID}/settings/who-can-post` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings/who-can-post` as const, backTo), }, REPORT_SETTINGS_VISIBILITY: { route: 'r/:reportID/settings/visibility', - getRoute: (reportID: string) => `r/${reportID}/settings/visibility` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/settings/visibility` as const, backTo), }, SPLIT_BILL_DETAILS: { route: 'r/:reportID/split/:reportActionID', - getRoute: (reportID: string, reportActionID: string) => `r/${reportID}/split/${reportActionID}` as const, + getRoute: (reportID: string, reportActionID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/split/${reportActionID}` as const, backTo), }, TASK_TITLE: { route: 'r/:reportID/title', - getRoute: (reportID: string) => `r/${reportID}/title` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/title` as const, backTo), }, REPORT_DESCRIPTION: { route: 'r/:reportID/description', - getRoute: (reportID: string) => `r/${reportID}/description` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/description` as const, backTo), }, TASK_ASSIGNEE: { route: 'r/:reportID/assignee', - getRoute: (reportID: string) => `r/${reportID}/assignee` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/assignee` as const, backTo), }, PRIVATE_NOTES_LIST: { route: 'r/:reportID/notes', - getRoute: (reportID: string) => `r/${reportID}/notes` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/notes` as const, backTo), }, PRIVATE_NOTES_EDIT: { route: 'r/:reportID/notes/:accountID/edit', - getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/notes/${accountID}/edit` as const, + getRoute: (reportID: string, accountID: string | number, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/notes/${accountID}/edit` as const, backTo), }, ROOM_MEMBERS: { route: 'r/:reportID/members', - getRoute: (reportID: string) => `r/${reportID}/members` as const, + getRoute: (reportID: string, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/members` as const, backTo), }, ROOM_MEMBER_DETAILS: { route: 'r/:reportID/members/:accountID', - getRoute: (reportID: string, accountID: string | number) => `r/${reportID}/members/${accountID}` as const, + getRoute: (reportID: string, accountID: string | number, backTo?: string) => getUrlWithBackToParam(`r/${reportID}/members/${accountID}` as const, backTo), }, ROOM_INVITE: { route: 'r/:reportID/invite/:role?', - getRoute: (reportID: string, role?: string) => { + getRoute: (reportID: string, role?: string, backTo?: string) => { const route = role ? (`r/${reportID}/invite/${role}` as const) : (`r/${reportID}/invite` as const); - return route; + return getUrlWithBackToParam(route, backTo); }, }, MONEY_REQUEST_HOLD_REASON: { @@ -406,9 +406,9 @@ const ROUTES = { `${action as string}/${iouType as string}/confirmation/${transactionID}/${reportID}${participantsAutoAssigned ? '?participantsAutoAssigned=true' : ''}` as const, }, MONEY_REQUEST_STEP_AMOUNT: { - route: ':action/:iouType/amount/:transactionID/:reportID', - getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`${action as string}/${iouType as string}/amount/${transactionID}/${reportID}`, backTo), + route: ':action/:iouType/amount/:transactionID/:reportID/:pageIndex?', + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string, reportID: string, pageIndex: string, backTo = '') => + getUrlWithBackToParam(`${action as string}/${iouType as string}/amount/${transactionID}/${reportID}/${pageIndex}`, backTo), }, MONEY_REQUEST_STEP_TAX_RATE: { route: ':action/:iouType/taxRate/:transactionID/:reportID?', @@ -536,12 +536,27 @@ const ROUTES = { IOU_SEND_ADD_DEBIT_CARD: 'pay/new/add-debit-card', IOU_SEND_ENABLE_PAYMENTS: 'pay/new/enable-payments', - NEW_TASK: 'new/task', - NEW_TASK_ASSIGNEE: 'new/task/assignee', + NEW_TASK: { + route: 'new/task', + getRoute: (backTo?: string) => getUrlWithBackToParam('new/task', backTo), + }, + NEW_TASK_ASSIGNEE: { + route: 'new/task/assignee', + getRoute: (backTo?: string) => getUrlWithBackToParam('new/task/assignee', backTo), + }, NEW_TASK_SHARE_DESTINATION: 'new/task/share-destination', - NEW_TASK_DETAILS: 'new/task/details', - NEW_TASK_TITLE: 'new/task/title', - NEW_TASK_DESCRIPTION: 'new/task/description', + NEW_TASK_DETAILS: { + route: 'new/task/details', + getRoute: (backTo?: string) => getUrlWithBackToParam('new/task/details', backTo), + }, + NEW_TASK_TITLE: { + route: 'new/task/title', + getRoute: (backTo?: string) => getUrlWithBackToParam('new/task/title', backTo), + }, + NEW_TASK_DESCRIPTION: { + route: 'new/task/description', + getRoute: (backTo?: string) => getUrlWithBackToParam('new/task/description', backTo), + }, TEACHERS_UNITE: 'settings/teachersunite', I_KNOW_A_TEACHER: 'settings/teachersunite/i-know-a-teacher', @@ -878,6 +893,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/members/:accountID', getRoute: (policyID: string, accountID: number) => `settings/workspaces/${policyID}/members/${accountID}` as const, }, + WORKSPACE_MEMBER_NEW_CARD: { + route: 'settings/workspaces/:policyID/members/:accountID/new-card', + getRoute: (policyID: string, accountID: number) => `settings/workspaces/${policyID}/members/${accountID}/new-card` as const, + }, WORKSPACE_MEMBER_ROLE_SELECTION: { route: 'settings/workspaces/:policyID/members/:accountID/role-selection', getRoute: (policyID: string, accountID: number) => `settings/workspaces/${policyID}/members/${accountID}/role-selection` as const, @@ -960,10 +979,6 @@ const ROUTES = { route: 'settings/workspaces/:policyID/company-cards/select-feed', getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards/select-feed` as const, }, - WORKSPACE_EXPENSIFY_CARD: { - route: 'settings/workspaces/:policyID/expensify-card', - getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, - }, WORKSPACE_COMPANY_CARDS_ASSIGN_CARD: { route: 'settings/workspaces/:policyID/company-cards/:feed/assign-card', getRoute: (policyID: string, feed: string) => `settings/workspaces/${policyID}/company-cards/${feed}/assign-card` as const, @@ -980,6 +995,10 @@ const ROUTES = { route: 'settings/workspaces/:policyID/company-cards/:bank/:cardID/edit/export', getRoute: (policyID: string, cardID: string, bank: string) => `settings/workspaces/${policyID}/company-cards/${bank}/${cardID}/edit/export` as const, }, + WORKSPACE_EXPENSIFY_CARD: { + route: 'settings/workspaces/:policyID/expensify-card', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card` as const, + }, WORKSPACE_EXPENSIFY_CARD_DETAILS: { route: 'settings/workspaces/:policyID/expensify-card/:cardID', getRoute: (policyID: string, cardID: string, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/expensify-card/${cardID}`, backTo), @@ -1093,7 +1112,10 @@ const ROUTES = { route: 'referral/:contentType', getRoute: (contentType: string, backTo?: string) => getUrlWithBackToParam(`referral/${contentType}`, backTo), }, - PROCESS_MONEY_REQUEST_HOLD: 'hold-expense-educational', + PROCESS_MONEY_REQUEST_HOLD: { + route: 'hold-expense-educational', + getRoute: (backTo?: string) => getUrlWithBackToParam('hold-expense-educational', backTo), + }, TRAVEL_MY_TRIPS: 'travel', TRAVEL_TCS: 'travel/terms', TRACK_TRAINING_MODAL: 'track-training', @@ -1122,39 +1144,39 @@ const ROUTES = { }, TRANSACTION_DUPLICATE_REVIEW_PAGE: { route: 'r/:threadReportID/duplicates/review', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review` as const, backTo), }, TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE: { route: 'r/:threadReportID/duplicates/review/merchant', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/merchant` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/merchant` as const, backTo), }, TRANSACTION_DUPLICATE_REVIEW_CATEGORY_PAGE: { route: 'r/:threadReportID/duplicates/review/category', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/category` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/category` as const, backTo), }, TRANSACTION_DUPLICATE_REVIEW_TAG_PAGE: { route: 'r/:threadReportID/duplicates/review/tag', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/tag` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/tag` as const, backTo), }, TRANSACTION_DUPLICATE_REVIEW_TAX_CODE_PAGE: { route: 'r/:threadReportID/duplicates/review/tax-code', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/tax-code` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/tax-code` as const, backTo), }, TRANSACTION_DUPLICATE_REVIEW_DESCRIPTION_PAGE: { route: 'r/:threadReportID/duplicates/review/description', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/description` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/description` as const, backTo), }, TRANSACTION_DUPLICATE_REVIEW_REIMBURSABLE_PAGE: { route: 'r/:threadReportID/duplicates/review/reimbursable', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/reimbursable` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/reimbursable` as const, backTo), }, TRANSACTION_DUPLICATE_REVIEW_BILLABLE_PAGE: { route: 'r/:threadReportID/duplicates/review/billable', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/review/billable` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/review/billable` as const, backTo), }, TRANSACTION_DUPLICATE_CONFIRMATION_PAGE: { route: 'r/:threadReportID/duplicates/confirm', - getRoute: (threadReportID: string) => `r/${threadReportID}/duplicates/confirm` as const, + getRoute: (threadReportID: string, backTo?: string) => getUrlWithBackToParam(`r/${threadReportID}/duplicates/confirm` as const, backTo), }, POLICY_ACCOUNTING_XERO_IMPORT: { route: 'settings/workspaces/:policyID/accounting/xero/import', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index da92d2b0940..920bd48dd42 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -463,6 +463,7 @@ const SCREENS = { CATEGORIES_IMPORTED: 'Categories_Imported', MORE_FEATURES: 'Workspace_More_Features', MEMBER_DETAILS: 'Workspace_Member_Details', + MEMBER_NEW_CARD: 'Workspace_Member_NewCard', OWNER_CHANGE_CHECK: 'Workspace_Owner_Change_Check', OWNER_CHANGE_SUCCESS: 'Workspace_Owner_Change_Success', OWNER_CHANGE_ERROR: 'Workspace_Owner_Change_Error', diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 1cd1bfb36d8..07845eca37b 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -79,10 +79,15 @@ function AvatarWithDisplayName({ actorAccountID.current = parentReportAction?.actorAccountID ?? -1; }, [parentReportActions, report]); + const goToDetailsPage = useCallback(() => { + ReportUtils.navigateToDetailsPage(report, Navigation.getReportRHPActiveRoute()); + }, [report]); + const showActorDetails = useCallback(() => { // We should navigate to the details page if the report is a IOU/expense report if (shouldEnableDetailPageNavigation) { - return ReportUtils.navigateToDetailsPage(report); + goToDetailsPage(); + return; } if (ReportUtils.isExpenseReport(report) && report?.ownerAccountID) { @@ -107,7 +112,7 @@ function AvatarWithDisplayName({ // Report detail route is added as fallback but based on the current implementation this route won't be executed Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID)); } - }, [report, shouldEnableDetailPageNavigation]); + }, [report, shouldEnableDetailPageNavigation, goToDetailsPage]); const headerView = ( @@ -172,7 +177,7 @@ function AvatarWithDisplayName({ return ( ReportUtils.navigateToDetailsPage(report)} + onPress={goToDetailsPage} style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]} accessibilityLabel={title} role={CONST.ROLE.BUTTON} diff --git a/src/components/EmptyStateComponent/index.tsx b/src/components/EmptyStateComponent/index.tsx index 876f1a74540..8eb991b17b6 100644 --- a/src/components/EmptyStateComponent/index.tsx +++ b/src/components/EmptyStateComponent/index.tsx @@ -22,6 +22,7 @@ function EmptyStateComponent({ buttonAction, containerStyles, title, + titleStyles, subtitle, headerStyles, headerContentStyles, @@ -30,7 +31,7 @@ function EmptyStateComponent({ }: EmptyStateComponentProps) { const styles = useThemeStyles(); const [videoAspectRatio, setVideoAspectRatio] = useState(VIDEO_ASPECT_RATIO); - const {isSmallScreenWidth} = useResponsiveLayout(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); const setAspectRatio = (event: VideoReadyForDisplayEvent | VideoLoadedEventType | undefined) => { if (!event) { @@ -82,7 +83,10 @@ function EmptyStateComponent({ }, [headerMedia, headerMediaType, headerContentStyles, videoAspectRatio, styles.emptyStateVideo, lottieWebViewStyles]); return ( - + {HeaderComponent} - - {title} - {subtitle} + + {title} + {typeof subtitle === 'string' ? {subtitle} : subtitle} {!!buttonText && !!buttonAction && (