diff --git a/.github/actions/composite/setupNode/action.yml b/.github/actions/composite/setupNode/action.yml
index 0b32d8ee6dc1..c6a6029e06e0 100644
--- a/.github/actions/composite/setupNode/action.yml
+++ b/.github/actions/composite/setupNode/action.yml
@@ -18,13 +18,13 @@ runs:
desktop/package-lock.json
- id: cache-node-modules
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.os }}-node-modules-${{ hashFiles('package-lock.json', 'patches/**') }}
- id: cache-desktop-node-modules
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: desktop/node_modules
key: ${{ runner.os }}-desktop-node-modules-${{ hashFiles('desktop/package-lock.json', 'desktop/patches/**') }}
diff --git a/.github/workflows/checkE2ETestCode.yml b/.github/workflows/checkE2ETestCode.yml
new file mode 100644
index 000000000000..090b7a7f23e4
--- /dev/null
+++ b/.github/workflows/checkE2ETestCode.yml
@@ -0,0 +1,23 @@
+name: Check e2e test code builds correctly
+
+on:
+ workflow_call:
+ pull_request:
+ types: [opened, synchronize]
+ paths:
+ - 'tests/e2e/**'
+ - 'src/libs/E2E/**'
+
+jobs:
+ lint:
+ if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: ./.github/actions/composite/setupNode
+
+ - name: Verify e2e tests compile correctly
+ run: npm run e2e-test-runner-build
\ No newline at end of file
diff --git a/.github/workflows/e2ePerformanceTests.yml b/.github/workflows/e2ePerformanceTests.yml
index 338cb8313465..8a47ea4bb220 100644
--- a/.github/workflows/e2ePerformanceTests.yml
+++ b/.github/workflows/e2ePerformanceTests.yml
@@ -15,6 +15,10 @@ on:
type: string
required: true
+concurrency:
+ group: "${{ github.ref }}-e2e"
+ cancel-in-progress: true
+
jobs:
buildBaseline:
runs-on: ubuntu-latest-xl
@@ -175,23 +179,11 @@ jobs:
- name: Rename delta APK
run: mv "${{steps.downloadDeltaAPK.outputs.download-path}}/app-e2edelta-release.apk" "${{steps.downloadDeltaAPK.outputs.download-path}}/app-e2edeltaRelease.apk"
- - name: Copy e2e code into zip folder
- run: cp -r tests/e2e zip
+ - name: Compile test runner to be executable in a nodeJS environment
+ run: npm run e2e-test-runner-build
- # Note: we can't reuse the apps tsconfig, as it depends on modules that aren't available in the AWS Device Farm environment
- - name: Write tsconfig.json to zip folder
- run: |
- echo '{
- "compilerOptions": {
- "target": "ESNext",
- "module": "commonjs",
- "strict": true,
- "esModuleInterop": true,
- "skipLibCheck": true,
- "forceConsistentCasingInFileNames": true,
- "allowJs": true,
- }
- }' > zip/tsconfig.json
+ - name: Copy e2e code into zip folder
+ run: cp tests/e2e/dist/index.js zip/testRunner.js
- name: Zip everything in the zip directory up
run: zip -qr App.zip ./zip
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 33c850823413..50e886942c98 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -7,8 +7,13 @@ on:
branches-ignore: [staging, production]
paths: ['**.js', '**.ts', '**.tsx', '**.json', '**.mjs', '**.cjs', 'config/.editorconfig', '.watchmanconfig', '.imgbotconfig']
+concurrency:
+ group: "${{ github.ref }}-lint"
+ cancel-in-progress: true
+
jobs:
lint:
+ name: Run ESLint
if: ${{ github.actor != 'OSBotify' || github.event_name == 'workflow_call' }}
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml
index 818441828bf0..4d6597334447 100644
--- a/.github/workflows/platformDeploy.yml
+++ b/.github/workflows/platformDeploy.yml
@@ -194,7 +194,7 @@ jobs:
bundler-cache: true
- name: Cache Pod dependencies
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: pods-cache
with:
path: ios/Pods
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index bdc14950a337..71b4bc3d8fc3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,6 +7,10 @@ on:
branches-ignore: [staging, production]
paths: ['**.js', '**.ts', '**.tsx', '**.sh', 'package.json', 'package-lock.json']
+concurrency:
+ group: "${{ github.ref }}-jest"
+ cancel-in-progress: true
+
jobs:
jest:
if: ${{ github.actor != 'OSBotify' && github.actor != 'imgbot[bot]' || github.event_name == 'workflow_call' }}
@@ -31,7 +35,7 @@ jobs:
- name: Cache Jest cache
id: cache-jest-cache
- uses: actions/cache@ac25611caef967612169ab7e95533cf932c32270
+ uses: actions/cache@v4
with:
path: .jest-cache
key: ${{ runner.os }}-jest
diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml
index 9548c3a6e595..3f02430f3c1f 100644
--- a/.github/workflows/testBuild.yml
+++ b/.github/workflows/testBuild.yml
@@ -167,7 +167,7 @@ jobs:
bundler-cache: true
- name: Cache Pod dependencies
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: pods-cache
with:
path: ios/Pods
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 9886cd5cccec..e285d0bff26f 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 1001044600
- versionName "1.4.46-0"
+ versionCode 1001044601
+ versionName "1.4.46-1"
}
flavorDimensions "default"
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index dff05f61933e..374594698200 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.46.0
+ 1.4.46.1
ITSAppUsesNonExemptEncryption
LSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index fa6995f65b5a..e314ea1595e1 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 1.4.46.0
+ 1.4.46.1
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index e8cd0ebb4e0a..aaec6344175f 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString
1.4.46
CFBundleVersion
- 1.4.46.0
+ 1.4.46.1
NSExtension
NSExtensionPointIdentifier
diff --git a/jest.config.js b/jest.config.js
index 441507af4228..5b36e44c7581 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -23,7 +23,7 @@ module.exports = {
},
testEnvironment: 'jsdom',
setupFiles: ['/jest/setup.ts', './node_modules/@react-native-google-signin/google-signin/jest/build/setup.js'],
- setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect', '/jest/setupAfterEnv.ts', '/tests/perf-test/setupAfterEnv.js'],
+ setupFilesAfterEnv: ['/jest/setupAfterEnv.ts', '/tests/perf-test/setupAfterEnv.js'],
cacheDirectory: '/.jest-cache',
moduleNameMapper: {
'\\.(lottie)$': '/__mocks__/fileMock.ts',
diff --git a/jest/setupAfterEnv.ts b/jest/setupAfterEnv.ts
index 6f7836b64dbb..d59495874588 100644
--- a/jest/setupAfterEnv.ts
+++ b/jest/setupAfterEnv.ts
@@ -1 +1,4 @@
+// This is required in order for jest to recognize custom matchers like toBeDisabled. This can be removed once testing-library/react-native version is bumped to v12.4 or later
+import '@testing-library/jest-native/extend-expect';
+
jest.useRealTimers();
diff --git a/package-lock.json b/package-lock.json
index cafbead8f5eb..80d3d1c6e911 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.46-0",
+ "version": "1.4.46-1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.46-0",
+ "version": "1.4.46-1",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -202,7 +202,7 @@
"css-loader": "^6.7.2",
"diff-so-fancy": "^1.3.0",
"dotenv": "^16.0.3",
- "electron": "^26.6.8",
+ "electron": "^29.0.0",
"electron-builder": "24.6.4",
"eslint": "^7.6.0",
"eslint-config-airbnb-typescript": "^17.1.0",
@@ -24894,20 +24894,22 @@
}
},
"node_modules/browserify-sign": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz",
- "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==",
- "license": "ISC",
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz",
+ "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==",
"dependencies": {
- "bn.js": "^5.1.1",
- "browserify-rsa": "^4.0.1",
+ "bn.js": "^5.2.1",
+ "browserify-rsa": "^4.1.0",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
- "elliptic": "^6.5.3",
+ "elliptic": "^6.5.4",
"inherits": "^2.0.4",
- "parse-asn1": "^5.1.5",
- "readable-stream": "^3.6.0",
- "safe-buffer": "^5.2.0"
+ "parse-asn1": "^5.1.6",
+ "readable-stream": "^3.6.2",
+ "safe-buffer": "^5.2.1"
+ },
+ "engines": {
+ "node": ">= 4"
}
},
"node_modules/browserify-sign/node_modules/readable-stream": {
@@ -28644,14 +28646,14 @@
}
},
"node_modules/electron": {
- "version": "26.6.8",
- "resolved": "https://registry.npmjs.org/electron/-/electron-26.6.8.tgz",
- "integrity": "sha512-nuzJ5nVButL1jErc97IVb+A6jbContMg5Uuz5fhmZ4NLcygLkSW8FZpnOT7A4k8Saa95xDJOvqGZyQdI/OPNFw==",
+ "version": "29.0.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-29.0.0.tgz",
+ "integrity": "sha512-HhrRC5vWb6fAbWXP3A6ABwKUO9JvYSC4E141RzWFgnDBqNiNtabfmgC8hsVeCR65RQA2MLSDgC8uP52I9zFllQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@electron/get": "^2.0.0",
- "@types/node": "^18.11.18",
+ "@types/node": "^20.9.0",
"extract-zip": "^2.0.1"
},
"bin": {
@@ -28932,15 +28934,6 @@
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.435.tgz",
"integrity": "sha512-B0CBWVFhvoQCW/XtjRzgrmqcgVWg6RXOEM/dK59+wFV93BFGR6AeNKc4OyhM+T3IhJaOOG8o/V+33Y2mwJWtzw=="
},
- "node_modules/electron/node_modules/@types/node": {
- "version": "18.19.8",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.8.tgz",
- "integrity": "sha512-g1pZtPhsvGVTwmeVoexWZLTQaOvXwoSq//pTL0DHeNzUDrFnir4fgETdhjhIxjVnN+hKOuh98+E1eMLnUXstFg==",
- "dev": true,
- "dependencies": {
- "undici-types": "~5.26.4"
- }
- },
"node_modules/element-resize-detector": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.2.4.tgz",
diff --git a/package.json b/package.json
index 46ff187bf64c..c927c41134db 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.46-0",
+ "version": "1.4.46-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.",
@@ -55,7 +55,8 @@
"gh-actions-unused-styles": "./.github/scripts/findUnusedKeys.sh",
"workflow-test": "./workflow_tests/scripts/runWorkflowTests.sh",
"workflow-test:generate": "ts-node workflow_tests/utils/preGenerateTest.js",
- "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1"
+ "setup-https": "mkcert -install && mkcert -cert-file config/webpack/certificate.pem -key-file config/webpack/key.pem dev.new.expensify.com localhost 127.0.0.1",
+ "e2e-test-runner-build": "ncc build tests/e2e/testRunner.js -o tests/e2e/dist/"
},
"dependencies": {
"@dotlottie/react-player": "^1.6.3",
@@ -250,7 +251,7 @@
"css-loader": "^6.7.2",
"diff-so-fancy": "^1.3.0",
"dotenv": "^16.0.3",
- "electron": "^26.6.8",
+ "electron": "^29.0.0",
"electron-builder": "24.6.4",
"eslint": "^7.6.0",
"eslint-config-airbnb-typescript": "^17.1.0",
diff --git a/src/CONST.ts b/src/CONST.ts
index 6626b798d314..9ed2903941b6 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -691,6 +691,7 @@ const CONST = {
DOMAIN_ALL: 'domainAll',
POLICY_ROOM: 'policyRoom',
POLICY_EXPENSE_CHAT: 'policyExpenseChat',
+ SELF_DM: 'selfDM',
},
WORKSPACE_CHAT_ROOMS: {
ANNOUNCE: '#announce',
@@ -3338,6 +3339,10 @@ const CONST = {
SESSION_STORAGE_KEYS: {
INITIAL_URL: 'INITIAL_URL',
},
+
+ AUTH_TOKEN_TYPE: {
+ ANONYMOUS: 'anonymousAccount',
+ },
} as const;
type Country = keyof typeof CONST.ALL_COUNTRIES;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index fb99108c7e97..e9cdce4f6ed9 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -486,6 +486,10 @@ const ROUTES = {
route: 'workspace/:policyID/workflows',
getRoute: (policyID: string) => `workspace/${policyID}/workflows` as const,
},
+ WORKSPACE_WORKFLOWS_APPROVER: {
+ route: 'workspace/:policyID/settings/workflows/approver',
+ getRoute: (policyId: string) => `workspace/${policyId}/settings/workflows/approver` as const,
+ },
WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY: {
route: 'workspace/:policyID/settings/workflows/auto-reporting-frequency',
getRoute: (policyID: string) => `workspace/${policyID}/settings/workflows/auto-reporting-frequency` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index cc7df01524f7..ff3dbfd7f901 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -216,6 +216,7 @@ const SCREENS = {
CATEGORIES: 'Workspace_Categories',
CURRENCY: 'Workspace_Profile_Currency',
WORKFLOWS: 'Workspace_Workflows',
+ WORKFLOWS_APPROVER: 'Workspace_Workflows_Approver',
WORKFLOWS_AUTO_REPORTING_FREQUENCY: 'Workspace_Workflows_Auto_Reporting_Frequency',
WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET: 'Workspace_Workflows_Auto_Reporting_Monthly_Offset',
DESCRIPTION: 'Workspace_Profile_Description',
diff --git a/src/components/FocusModeNotification.js b/src/components/FocusModeNotification.tsx
similarity index 98%
rename from src/components/FocusModeNotification.js
rename to src/components/FocusModeNotification.tsx
index 9ec16beead15..7b3f567d256b 100644
--- a/src/components/FocusModeNotification.js
+++ b/src/components/FocusModeNotification.tsx
@@ -28,7 +28,6 @@ function FocusModeNotification() {
{translate('focusModeUpdateModal.prompt')}
{
User.clearFocusModeNotification();
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index df2781d3ea89..dfe1d96b0e5d 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -282,7 +282,7 @@ function MoneyRequestConfirmationList(props) {
}, [props.isEditingSplitBill, props.hasSmartScanFailed, transaction, didConfirmSplit]);
const isMerchantEmpty = !props.iouMerchant || props.iouMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
- const shouldDisplayMerchantError = props.isPolicyExpenseChat && !props.isScanRequest && isMerchantEmpty;
+ const shouldDisplayMerchantError = props.isPolicyExpenseChat && shouldDisplayFieldError && isMerchantEmpty;
useEffect(() => {
if (shouldDisplayFieldError && didConfirmSplit) {
@@ -750,14 +750,8 @@ function MoneyRequestConfirmationList(props) {
}}
disabled={didConfirm}
interactive={!props.isReadOnly}
- brickRoadIndicator={
- props.isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''
- }
- error={
- shouldDisplayMerchantError || (props.isPolicyExpenseChat && shouldDisplayFieldError && TransactionUtils.isMerchantMissing(transaction))
- ? translate('common.error.enterMerchant')
- : ''
- }
+ brickRoadIndicator={shouldDisplayMerchantError ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''}
+ error={shouldDisplayMerchantError ? translate('common.error.enterMerchant') : ''}
/>
)}
{shouldShowCategories && (
diff --git a/src/components/OptionsSelector/BaseOptionsSelector.js b/src/components/OptionsSelector/BaseOptionsSelector.js
index c5c7c3ec50b0..c0258f1252ef 100755
--- a/src/components/OptionsSelector/BaseOptionsSelector.js
+++ b/src/components/OptionsSelector/BaseOptionsSelector.js
@@ -125,8 +125,11 @@ class BaseOptionsSelector extends Component {
// Unregister the shortcut before registering a new one to avoid lingering shortcut listener
this.unSubscribeFromKeyboardShortcut();
if (this.props.isFocused) {
+ this.subscribeActiveElement();
this.subscribeToEnterShortcut();
this.subscribeToCtrlEnterShortcut();
+ } else {
+ this.unSubscribeActiveElement();
}
}
diff --git a/src/components/OptionsSelector/index.android.js b/src/components/OptionsSelector/index.android.js
index ace5a5614ffb..1ed2d56e8742 100644
--- a/src/components/OptionsSelector/index.android.js
+++ b/src/components/OptionsSelector/index.android.js
@@ -1,6 +1,9 @@
import React, {forwardRef} from 'react';
import BaseOptionsSelector from './BaseOptionsSelector';
+/**
+ * @deprecated Please use `SelectionList` instead.
+ */
const OptionsSelector = forwardRef((props, ref) => (
(
1;
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant);
@@ -44,6 +45,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, policy, participantAccountIDs);
const additionalText = moneyRequestOptions.map((item) => translate(`reportActionsView.iouTypes.${item}`)).join(', ');
const canEditPolicyDescription = ReportUtils.canEditPolicyDescription(policy);
+ const reportName = ReportUtils.getReportName(report);
const navigateToReport = () => {
if (!report?.reportID) {
@@ -53,12 +55,22 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID));
};
+ const welcomeHeroText = useMemo(() => {
+ if (isChatRoom) {
+ return translate('reportActionsView.welcomeToRoom', {roomName: reportName});
+ }
+
+ if (isSelfDM) {
+ return translate('reportActionsView.yourSpace');
+ }
+
+ return translate('reportActionsView.sayHello');
+ }, [isChatRoom, isSelfDM, translate, reportName]);
+
return (
<>
-
- {isChatRoom ? translate('reportActionsView.welcomeToRoom', {roomName: ReportUtils.getReportName(report)}) : translate('reportActionsView.sayHello')}
-
+ {welcomeHeroText}
{isPolicyExpenseChat &&
@@ -114,6 +126,11 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP
{roomWelcomeMessage.phrase2 !== undefined && {roomWelcomeMessage.phrase2}}
))}
+ {isSelfDM && (
+
+ {translate('reportActionsView.beginningOfChatHistorySelfDM')}
+
+ )}
{isDefault && (
{translate('reportActionsView.beginningOfChatHistory')}
diff --git a/src/components/SAMLLoadingIndicator.js b/src/components/SAMLLoadingIndicator.tsx
similarity index 79%
rename from src/components/SAMLLoadingIndicator.js
rename to src/components/SAMLLoadingIndicator.tsx
index 84f9098e564f..2be7b76e6cae 100644
--- a/src/components/SAMLLoadingIndicator.js
+++ b/src/components/SAMLLoadingIndicator.tsx
@@ -3,6 +3,7 @@ import {StyleSheet, View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import * as Illustrations from './Icon/Illustrations';
@@ -23,8 +24,13 @@ function SAMLLoadingIndicator() {
/>
{translate('samlSignIn.launching')}
-
- {translate('samlSignIn.oneMoment')}
+
+
+ {translate('samlSignIn.oneMoment')}
+
diff --git a/src/components/SelectionList/UserListItem.tsx b/src/components/SelectionList/UserListItem.tsx
index 0cfe4c1a509a..4619a2e54f74 100644
--- a/src/components/SelectionList/UserListItem.tsx
+++ b/src/components/SelectionList/UserListItem.tsx
@@ -85,7 +85,7 @@ function UserListItem({
style={[
styles.optionDisplayName,
isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText,
- styles.sidebarLinkTextBold,
+ item.isBold !== false && styles.sidebarLinkTextBold,
styles.pre,
item.alternateText ? styles.mb1 : null,
]}
diff --git a/src/components/SelectionList/types.ts b/src/components/SelectionList/types.ts
index eaaed4e572cb..005a8ab21cc1 100644
--- a/src/components/SelectionList/types.ts
+++ b/src/components/SelectionList/types.ts
@@ -61,6 +61,9 @@ type ListItem = {
/** Whether this option is disabled for selection */
isDisabled?: boolean;
+ /** List title is bold by default. Use this props to customize it */
+ isBold?: boolean;
+
/** User accountID */
accountID?: number | null;
diff --git a/src/components/ShowMoreButton/index.js b/src/components/ShowMoreButton.tsx
similarity index 74%
rename from src/components/ShowMoreButton/index.js
rename to src/components/ShowMoreButton.tsx
index 28c33d185cff..3411066a5376 100644
--- a/src/components/ShowMoreButton/index.js
+++ b/src/components/ShowMoreButton.tsx
@@ -1,42 +1,34 @@
-import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
-import _ from 'underscore';
-import Button from '@components/Button';
-import * as Expensicons from '@components/Icon/Expensicons';
-import Text from '@components/Text';
+import type {StyleProp, ViewStyle} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as NumberFormatUtils from '@libs/NumberFormatUtils';
-import stylePropTypes from '@styles/stylePropTypes';
+import Button from './Button';
+import * as Expensicons from './Icon/Expensicons';
+import Text from './Text';
-const propTypes = {
+type ShowMoreButtonProps = {
/** Additional styles for container */
- containerStyle: stylePropTypes,
+ containerStyle?: StyleProp;
/** The number of currently shown items */
- currentCount: PropTypes.number,
+ currentCount?: number;
/** The total number of items that could be shown */
- totalCount: PropTypes.number,
+ totalCount?: number;
/** A handler that fires when button has been pressed */
- onPress: PropTypes.func.isRequired,
+ onPress: () => void;
};
-const defaultProps = {
- containerStyle: {},
- currentCount: undefined,
- totalCount: undefined,
-};
-
-function ShowMoreButton({containerStyle, currentCount, totalCount, onPress}) {
+function ShowMoreButton({containerStyle, currentCount, totalCount, onPress}: ShowMoreButtonProps) {
const {translate, preferredLocale} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
- const shouldShowCounter = _.isNumber(currentCount) && _.isNumber(totalCount);
+ const shouldShowCounter = !!(currentCount && totalCount);
return (
@@ -67,7 +59,5 @@ function ShowMoreButton({containerStyle, currentCount, totalCount, onPress}) {
}
ShowMoreButton.displayName = 'ShowMoreButton';
-ShowMoreButton.propTypes = propTypes;
-ShowMoreButton.defaultProps = defaultProps;
export default ShowMoreButton;
diff --git a/src/components/TaxPicker/index.js b/src/components/TaxPicker.tsx
similarity index 68%
rename from src/components/TaxPicker/index.js
rename to src/components/TaxPicker.tsx
index be15cd546b36..664aa741c400 100644
--- a/src/components/TaxPicker/index.js
+++ b/src/components/TaxPicker.tsx
@@ -1,16 +1,32 @@
-import lodashGet from 'lodash/get';
import React, {useMemo, useState} from 'react';
-import _ from 'underscore';
-import OptionsSelector from '@components/OptionsSelector';
+import type {EdgeInsets} from 'react-native-safe-area-context';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
import CONST from '@src/CONST';
-import {defaultProps, propTypes} from './taxPickerPropTypes';
+import type {TaxRatesWithDefault} from '@src/types/onyx';
+import OptionsSelector from './OptionsSelector';
-function TaxPicker({selectedTaxRate, taxRates, insets, onSubmit}) {
+type TaxPickerProps = {
+ /** Collection of tax rates attached to a policy */
+ taxRates: TaxRatesWithDefault;
+
+ /** The selected tax rate of an expense */
+ selectedTaxRate?: string;
+
+ /**
+ * Safe area insets required for reflecting the portion of the view,
+ * that is not covered by navigation bars, tab bars, toolbars, and other ancestor views.
+ */
+ insets?: EdgeInsets;
+
+ /** Callback to fire when a tax is pressed */
+ onSubmit: () => void;
+};
+
+function TaxPicker({selectedTaxRate = '', taxRates, insets, onSubmit}: TaxPickerProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {translate} = useLocalize();
@@ -40,10 +56,11 @@ function TaxPicker({selectedTaxRate, taxRates, insets, onSubmit}) {
return taxRatesOptions;
}, [taxRates, searchValue, selectedOptions]);
- const selectedOptionKey = lodashGet(_.filter(lodashGet(sections, '[0].data', []), (taxRate) => taxRate.searchText === selectedTaxRate)[0], 'keyForList');
+ const selectedOptionKey = sections?.[0]?.data?.find((taxRate) => taxRate.searchText === selectedTaxRate)?.keyForList;
return (
`Welcome to ${roomName}!`,
usePlusButton: ({additionalText}: UsePlusButtonParams) => `\nYou can also use the + button to ${additionalText}, or assign a task!`,
iouTypes: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 20f4cf8aeac8..83ed2ca1c89c 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -485,8 +485,10 @@ export default {
beginningOfChatHistoryPolicyExpenseChatPartOne: '¡La colaboración entre ',
beginningOfChatHistoryPolicyExpenseChatPartTwo: ' y ',
beginningOfChatHistoryPolicyExpenseChatPartThree: ' empieza aquÃ! 🎉 Este es el lugar donde chatear, pedir dinero y pagar.',
+ beginningOfChatHistorySelfDM: 'Este es tu espacio personal. Úsalo para notas, tareas, borradores y recordatorios.',
chatWithAccountManager: 'Chatea con tu gestor de cuenta aquÃ',
sayHello: '¡Saluda!',
+ yourSpace: 'Tu espacio',
welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `¡Bienvenido a ${roomName}!`,
usePlusButton: ({additionalText}: UsePlusButtonParams) => `\n¡También puedes usar el botón + de abajo para ${additionalText}, o asignar una tarea!`,
iouTypes: {
diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
index 00c96d436496..30a8010ad801 100644
--- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx
+++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx
@@ -81,8 +81,7 @@ Onyx.connect({
currentAccountID = value.accountID ?? -1;
if (Navigation.isActiveRoute(ROUTES.SIGN_IN_MODAL)) {
- // This means sign in in RHP was successful, so we can dismiss the modal and subscribe to user events
- Navigation.dismissModal();
+ // This means sign in in RHP was successful, so we can subscribe to user events
User.subscribeToUserEvents();
}
},
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
index 527d93c2a3db..545641957c9a 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
@@ -243,6 +243,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/RatePage').default as React.ComponentType,
[SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT]: () => require('../../../pages/workspace/reimburse/WorkspaceRateAndUnitPage/UnitPage').default as React.ComponentType,
[SCREENS.WORKSPACE.INVITE]: () => require('../../../pages/workspace/WorkspaceInvitePage').default as React.ComponentType,
+ [SCREENS.WORKSPACE.WORKFLOWS_APPROVER]: () => require('../../../pages/workspace/workflows/WorkspaceWorkflowsApproverPage').default as React.ComponentType,
[SCREENS.WORKSPACE.INVITE_MESSAGE]: () => require('../../../pages/workspace/WorkspaceInviteMessagePage').default as React.ComponentType,
[SCREENS.WORKSPACE.NAME]: () => require('../../../pages/workspace/WorkspaceNamePage').default as React.ComponentType,
[SCREENS.WORKSPACE.DESCRIPTION]: () => require('../../../pages/workspace/WorkspaceProfileDescriptionPage').default as React.ComponentType,
diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
index 7e38ed99105e..618eddc9f62c 100755
--- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
@@ -5,6 +5,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> =
[SCREENS.WORKSPACE.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE],
[SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT],
[SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE],
+ [SCREENS.WORKSPACE.WORKFLOWS]: [SCREENS.WORKSPACE.WORKFLOWS_APPROVER],
};
export default CENTRAL_PANE_TO_RHP_MAPPING;
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 7e0e6c028ff1..276829e8c691 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -262,6 +262,9 @@ const config: LinkingOptions['config'] = {
[SCREENS.WORKSPACE.INVITE]: {
path: ROUTES.WORKSPACE_INVITE.route,
},
+ [SCREENS.WORKSPACE.WORKFLOWS_APPROVER]: {
+ path: ROUTES.WORKSPACE_WORKFLOWS_APPROVER.route,
+ },
[SCREENS.WORKSPACE.INVITE_MESSAGE]: {
path: ROUTES.WORKSPACE_INVITE_MESSAGE.route,
},
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 6d680ac7e190..a1e558869ebe 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -63,6 +63,9 @@ type CentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.WORKFLOWS]: {
policyID: string;
};
+ [SCREENS.WORKSPACE.WORKFLOWS_APPROVER]: {
+ policyID: string;
+ };
[SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: {
policyID: string;
};
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 145c72ccd080..bdf1ba90583d 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -114,6 +114,7 @@ type GetOptionsConfig = {
includeMultipleParticipantReports?: boolean;
includePersonalDetails?: boolean;
includeRecentReports?: boolean;
+ includeSelfDM?: boolean;
sortByReportTypeInSearch?: boolean;
searchInputValue?: string;
showChatPreviewLine?: boolean;
@@ -675,6 +676,8 @@ function createOption(
result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs ?? []);
result.isWaitingOnBankAccount = report.isWaitingOnBankAccount;
result.policyID = report.policyID;
+ result.isSelfDM = ReportUtils.isSelfDM(report);
+
hasMultipleParticipants = personalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat;
subtitle = ReportUtils.getChatRoomSubtitle(report);
@@ -804,6 +807,11 @@ function getEnabledCategoriesCount(options: PolicyCategories): number {
return Object.values(options).filter((option) => option.enabled).length;
}
+function getSearchValueForPhoneOrEmail(searchTerm: string) {
+ const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchTerm)));
+ return parsedPhoneNumber.possible ? parsedPhoneNumber.number?.e164 ?? '' : searchTerm.toLowerCase();
+}
+
/**
* Verifies that there is at least one enabled option
*/
@@ -1363,6 +1371,7 @@ function getOptions(
transactionViolations = {},
includeTaxRates,
taxRates,
+ includeSelfDM = false,
}: GetOptionsConfig,
): GetOptions {
if (includeCategories) {
@@ -1439,8 +1448,8 @@ function getOptions(
policies,
doesReportHaveViolations,
isInGSDMode: false,
-
excludeEmptyChats: false,
+ includeSelfDM,
});
});
@@ -1467,7 +1476,9 @@ function getOptions(
const isTaskReport = ReportUtils.isTaskReport(report);
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
- const accountIDs = report.visibleChatMemberAccountIDs ?? [];
+ const isSelfDM = ReportUtils.isSelfDM(report);
+ // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants.
+ const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? [];
if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) {
return;
@@ -1478,6 +1489,10 @@ function getOptions(
return;
}
+ if (isSelfDM && !includeSelfDM) {
+ return;
+ }
+
if (isThread && !includeThreads) {
return;
}
@@ -1728,6 +1743,7 @@ function getSearchOptions(reports: Record, personalDetails: Onyx
includeThreads: true,
includeMoneyRequests: true,
includeTasks: true,
+ includeSelfDM: true,
});
Timing.end(CONST.TIMING.LOAD_SEARCH_OPTIONS);
Performance.markEnd(CONST.TIMING.LOAD_SEARCH_OPTIONS);
@@ -1803,6 +1819,7 @@ function getFilteredOptions(
includeSelectedOptions = false,
includeTaxRates = false,
taxRates: TaxRatesWithDefault = {} as TaxRatesWithDefault,
+ includeSelfDM = false,
) {
return getOptions(reports, personalDetails, {
betas,
@@ -1824,6 +1841,7 @@ function getFilteredOptions(
includeSelectedOptions,
includeTaxRates,
taxRates,
+ includeSelfDM,
});
}
@@ -1857,6 +1875,7 @@ function getShareDestinationOptions(
excludeLogins,
includeOwnedWorkspaceChats,
excludeUnknownUsers,
+ includeSelfDM: true,
});
}
@@ -1882,7 +1901,7 @@ function formatMemberForList(member: ReportUtils.OptionData): MemberForList {
login: member.login ?? '',
icons: member.icons,
pendingAction: member.pendingAction,
- reportID: member.reportID,
+ reportID: member.reportID ?? '',
};
}
@@ -2026,6 +2045,7 @@ export {
getMemberInviteOptions,
getHeaderMessage,
getHeaderMessageForNonUserList,
+ getSearchValueForPhoneOrEmail,
getPersonalDetailsForAccountIDs,
getIOUConfirmationOptionsFromPayeePersonalDetail,
getIOUConfirmationOptionsFromParticipants,
diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts
index 1c517f42637f..9dd60eeebcef 100644
--- a/src/libs/PersonalDetailsUtils.ts
+++ b/src/libs/PersonalDetailsUtils.ts
@@ -24,9 +24,14 @@ Onyx.connect({
},
});
-function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true): string {
- const displayName = passedPersonalDetails?.displayName ? passedPersonalDetails.displayName.replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, '') : '';
+function getDisplayNameOrDefault(passedPersonalDetails?: Partial | null, defaultValue = '', shouldFallbackToHidden = true, shouldAddCurrentUserPostfix = false): string {
+ let displayName = passedPersonalDetails?.displayName ? passedPersonalDetails.displayName.replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, '') : '';
+ if (shouldAddCurrentUserPostfix && !!displayName) {
+ displayName = `${displayName} (${Localize.translateLocal('common.you').toLowerCase()})`;
+ }
+
const fallbackValue = shouldFallbackToHidden ? Localize.translateLocal('common.hidden') : '';
+
return displayName || defaultValue || fallbackValue;
}
@@ -56,6 +61,10 @@ function getPersonalDetailsByIDs(accountIDs: number[], currentUserAccountID: num
return result;
}
+function getPersonalDetailByEmail(email: string): PersonalDetails | undefined {
+ return (Object.values(allPersonalDetails ?? {}) as PersonalDetails[]).find((detail) => detail?.login === email);
+}
+
/**
* Given a list of logins, find the associated personal detail and return related accountIDs.
*
@@ -263,6 +272,7 @@ export {
isPersonalDetailsEmpty,
getDisplayNameOrDefault,
getPersonalDetailsByIDs,
+ getPersonalDetailByEmail,
getAccountIDsByLogins,
getLoginsByAccountIDs,
getNewPersonalDetailsOnyxData,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index a814c91ea8b2..b2fb456681cf 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -399,6 +399,7 @@ type OptionData = {
notificationPreference?: NotificationPreference | null;
isDisabled?: boolean | null;
name?: string | null;
+ isSelfDM?: boolean | null;
} & Report;
type OnyxDataTaskAssigneeChat = {
@@ -438,7 +439,7 @@ Onyx.connect({
currentUserEmail = value.email;
currentUserAccountID = value.accountID;
- isAnonymousUser = value.authTokenType === 'anonymousAccount';
+ isAnonymousUser = value.authTokenType === CONST.AUTH_TOKEN_TYPE.ANONYMOUS;
currentUserPrivateDomain = isEmailPublicDomain(currentUserEmail ?? '') ? '' : Str.extractEmailDomain(currentUserEmail ?? '');
},
});
@@ -909,6 +910,10 @@ function isDM(report: OnyxEntry): boolean {
return isChatReport(report) && !getChatType(report);
}
+function isSelfDM(report: OnyxEntry): boolean {
+ return getChatType(report) === CONST.REPORT.CHAT_TYPE.SELF_DM;
+}
+
/**
* Only returns true if this is our main 1:1 DM report with Concierge
*/
@@ -1611,6 +1616,10 @@ function getIcons(
return isPayer ? [managerIcon, ownerIcon] : [ownerIcon, managerIcon];
}
+ if (isSelfDM(report)) {
+ return getIconsForParticipants([currentUserAccountID ?? 0], personalDetails);
+ }
+
return getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails);
}
@@ -1633,7 +1642,7 @@ function getPersonalDetailsForAccountID(accountID: number): Partial {
const accountID = Number(user?.accountID);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- const displayName = getDisplayNameForParticipant(accountID, isMultipleParticipantReport, shouldFallbackToHidden) || user?.login || '';
+ const displayName = getDisplayNameForParticipant(accountID, isMultipleParticipantReport, shouldFallbackToHidden, shouldAddCurrentUserPostfix) || user?.login || '';
const avatar = UserUtils.getDefaultAvatar(accountID);
let pronouns = user?.pronouns ?? undefined;
@@ -2563,6 +2576,10 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu
formattedName += ` (${Localize.translateLocal('common.archived')})`;
}
+ if (isSelfDM(report)) {
+ formattedName = getDisplayNameForParticipant(currentUserAccountID, undefined, undefined, true);
+ }
+
if (formattedName) {
return formattedName;
}
@@ -2619,6 +2636,11 @@ function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigatio
function navigateToDetailsPage(report: OnyxEntry) {
const participantAccountIDs = report?.participantAccountIDs ?? [];
+ if (isSelfDM(report)) {
+ Navigation.navigate(ROUTES.PROFILE.getRoute(currentUserAccountID ?? 0));
+ return;
+ }
+
if (isOneOnOneChat(report)) {
Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountIDs[0]));
return;
@@ -3926,6 +3948,7 @@ function shouldReportBeInOptionList({
policies,
excludeEmptyChats,
doesReportHaveViolations,
+ includeSelfDM = false,
}: {
report: OnyxEntry;
currentReportId: string;
@@ -3934,6 +3957,7 @@ function shouldReportBeInOptionList({
policies: OnyxCollection;
excludeEmptyChats: boolean;
doesReportHaveViolations: boolean;
+ includeSelfDM?: boolean;
}) {
const isInDefaultMode = !isInGSDMode;
// Exclude reports that have no data because there wouldn't be anything to show in the option item.
@@ -3955,7 +3979,8 @@ function shouldReportBeInOptionList({
!isUserCreatedPolicyRoom(report) &&
!isArchivedRoom(report) &&
!isMoneyRequestReport(report) &&
- !isTaskReport(report))
+ !isTaskReport(report) &&
+ !isSelfDM(report))
) {
return false;
}
@@ -4017,6 +4042,10 @@ function shouldReportBeInOptionList({
return false;
}
+ if (isSelfDM(report)) {
+ return includeSelfDM;
+ }
+
return true;
}
@@ -4244,7 +4273,7 @@ function hasIOUWaitingOnCurrentUserBankAccount(chatReport: OnyxEntry): b
*/
function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, otherParticipants: number[]): boolean {
// User cannot request money in chat thread or in task report or in chat room
- if (isChatThread(report) || isTaskReport(report) || isChatRoom(report)) {
+ if (isChatThread(report) || isTaskReport(report) || isChatRoom(report) || isSelfDM(report)) {
return false;
}
@@ -4304,7 +4333,7 @@ function canRequestMoney(report: OnyxEntry, policy: OnyxEntry, o
*/
function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry, reportParticipants: number[]): Array> {
// In any thread or task report, we do not allow any new money requests yet
- if (isChatThread(report) || isTaskReport(report)) {
+ if (isChatThread(report) || isTaskReport(report) || isSelfDM(report)) {
return [];
}
@@ -4359,6 +4388,7 @@ function canLeaveRoom(report: OnyxEntry, isPolicyMember: boolean): boole
report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE ||
report?.chatType === CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT ||
report?.chatType === CONST.REPORT.CHAT_TYPE.DOMAIN_ALL ||
+ report?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM ||
!report?.chatType
) {
// DM chats don't have a chatType
@@ -5187,6 +5217,7 @@ export {
getAddWorkspaceRoomOrChatReportErrors,
getReportOfflinePendingActionAndErrors,
isDM,
+ isSelfDM,
getPolicy,
getPolicyExpenseChatReportIDByOwner,
getWorkspaceChats,
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 35cf52a5ff99..51233838e6cf 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -23,7 +23,6 @@ import * as TaskUtils from './TaskUtils';
import * as UserUtils from './UserUtils';
const visibleReportActionItems: ReportActions = {};
-const lastReportActions: ReportActions = {};
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
@@ -34,7 +33,6 @@ Onyx.connect({
const reportID = CollectionUtils.extractCollectionItemID(key);
const actionsArray: ReportAction[] = ReportActionsUtils.getSortedReportActions(Object.values(actions));
- lastReportActions[reportID] = actionsArray[actionsArray.length - 1];
// The report is only visible if it is the last action not deleted that
// does not match a closed or created state.
@@ -92,6 +90,7 @@ function getOrderedReportIDs(
policies,
excludeEmptyChats: true,
doesReportHaveViolations,
+ includeSelfDM: true,
});
});
@@ -221,7 +220,14 @@ function getOptionData({
isDeletedParentAction: false,
};
- const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs ?? [], personalDetails)) as PersonalDetails[];
+ let participantAccountIDs = report.participantAccountIDs ?? [];
+
+ // Currently, currentUser is not included in participantAccountIDs, so for selfDM we need to add the currentUser(report owner) as participants.
+ if (ReportUtils.isSelfDM(report)) {
+ participantAccountIDs = [report.ownerAccountID ?? 0];
+ }
+
+ const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails)) as PersonalDetails[];
const personalDetail = participantPersonalDetailList[0] ?? {};
const hasErrors = Object.keys(result.allReportErrors ?? {}).length !== 0;
@@ -258,6 +264,7 @@ function getOptionData({
result.isAllowedToComment = ReportUtils.canUserPerformWriteAction(report);
result.chatType = report.chatType;
result.isDeletedParentAction = report.isDeletedParentAction;
+ result.isSelfDM = ReportUtils.isSelfDM(report);
const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || ReportUtils.isExpenseReport(report);
const subtitle = ReportUtils.getChatRoomSubtitle(report);
@@ -267,7 +274,12 @@ function getOptionData({
const formattedLogin = Str.isSMSLogin(login) ? LocalePhoneNumber.formatPhoneNumber(login) : login;
// We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade.
- const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips((participantPersonalDetailList || []).slice(0, 10), hasMultipleParticipants);
+ const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(
+ (participantPersonalDetailList || []).slice(0, 10),
+ hasMultipleParticipants,
+ undefined,
+ ReportUtils.isSelfDM(report),
+ );
// If the last actor's details are not currently saved in Onyx Collection,
// then try to get that from the last report action if that action is valid
@@ -289,14 +301,12 @@ function getOptionData({
let lastMessageText = lastMessageTextFromReport;
- const reportAction = lastReportActions?.[report.reportID];
+ const lastAction = visibleReportActionItems[report.reportID];
const isThreadMessage =
- ReportUtils.isThread(report) && reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && reportAction?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
+ ReportUtils.isThread(report) && lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT && lastAction?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE;
if ((result.isChatRoom || result.isPolicyExpenseChat || result.isThread || result.isTaskReport || isThreadMessage) && !result.isArchivedRoom) {
- const lastAction = visibleReportActionItems[report.reportID];
-
if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) {
const newName = lastAction?.originalMessage?.newName ?? '';
result.alternateText = Localize.translate(preferredLocale, 'newRoomPage.roomRenamedTo', {newName});
diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts
index 013d86049150..76f335a3bec0 100644
--- a/src/libs/actions/Session/index.ts
+++ b/src/libs/actions/Session/index.ts
@@ -107,7 +107,7 @@ function signOut() {
* Checks if the account is an anonymous account.
*/
function isAnonymousUser(): boolean {
- return sessionAuthTokenType === 'anonymousAccount';
+ return sessionAuthTokenType === CONST.AUTH_TOKEN_TYPE.ANONYMOUS;
}
function signOutAndRedirectToSignIn(shouldReplaceCurrentScreen?: boolean) {
diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js
index 0b986adf1c6f..cf05b8e4ab28 100755
--- a/src/pages/ProfilePage.js
+++ b/src/pages/ProfilePage.js
@@ -95,9 +95,10 @@ const getPhoneNumber = (details) => {
function ProfilePage(props) {
const styles = useThemeStyles();
const accountID = Number(lodashGet(props.route.params, 'accountID', 0));
- const details = lodashGet(props.personalDetails, accountID, ValidationUtils.isValidAccountRoute(accountID) ? {} : {isloading: false});
+ const isCurrentUser = props.session.accountID === accountID;
- const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(details);
+ const details = lodashGet(props.personalDetails, accountID, ValidationUtils.isValidAccountRoute(accountID) ? {} : {isloading: false});
+ const displayName = PersonalDetailsUtils.getDisplayNameOrDefault(details, undefined, undefined, isCurrentUser);
const avatar = lodashGet(details, 'avatar', UserUtils.getDefaultAvatar());
const fallbackIcon = lodashGet(details, 'fallbackIcon', '');
const login = lodashGet(details, 'login', '');
@@ -116,7 +117,6 @@ function ProfilePage(props) {
const phoneNumber = getPhoneNumber(details);
const phoneOrEmail = isSMSLogin ? getPhoneNumber(details) : login;
- const isCurrentUser = props.session.accountID === accountID;
const hasMinimumDetails = !_.isEmpty(details.avatar);
const isLoading = lodashGet(details, 'isLoading', false) || _.isEmpty(details);
@@ -130,7 +130,7 @@ function ProfilePage(props) {
const navigateBackTo = lodashGet(props.route, 'params.backTo');
- const shouldShowNotificationPreference = !_.isEmpty(props.report) && props.report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ const shouldShowNotificationPreference = !_.isEmpty(props.report) && !isCurrentUser && props.report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
const notificationPreference = shouldShowNotificationPreference ? props.translate(`notificationPreferencesPage.notificationPreferences.${props.report.notificationPreference}`) : '';
// eslint-disable-next-line rulesdir/prefer-early-return
@@ -234,7 +234,7 @@ function ProfilePage(props) {
shouldShowRightIcon
/>
)}
- {!_.isEmpty(props.report) && (
+ {!_.isEmpty(props.report) && !isCurrentUser && (