diff --git a/android/app/build.gradle b/android/app/build.gradle
index a28dc8661799..dd19b75b1e49 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -107,8 +107,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1009000107
- versionName "9.0.1-7"
+ versionCode 1009000114
+ versionName "9.0.1-14"
// 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/assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg b/assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg
new file mode 100644
index 000000000000..91af173f0357
--- /dev/null
+++ b/assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg
@@ -0,0 +1,52 @@
+
+
\ No newline at end of file
diff --git a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md
index bc9801060223..0fde76c8fa92 100644
--- a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md
+++ b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md
@@ -80,7 +80,7 @@ When you link your credit cards to Expensify, the transactions will appear in ea
After a card is connected via direct connection or via Approved! banks, Expensify will import 30-90 days' worth of historical transactions to your account (the timeframe is based on your bank's discretion). Any historical expenses beyond that date range can be imported using the CSV spreadsheet import method.
## Using eReceipts
-Expensify eReceipts serve as digital substitutes for paper receipts in your purchase transactions, eliminating the necessity to retain physical receipts or utilize SmartScanning receipts. In the case of Expensify Card transactions, eReceipts are automatically generated for all amounts. For other card programs, eReceipts are specifically generated for purchases amounting to $75 or less, provided the transactions are in USD.
+Expensify eReceipts serve as digital substitutes for paper receipts in your purchase transactions, eliminating the necessity to retain physical receipts or utilize SmartScanning receipts. In the case of Expensify Card transactions, eReceipts are automatically generated for all amounts in the following categories: Airlines, Commuter expenses, Gas, Groceries, Mail, Meals, Car rental, Taxis, and Utilities. For other card programs, eReceipts are specifically generated for purchases amounting to $75 or less, provided the transactions are in USD.
To ensure seamless automatic importation, it's essential to maintain your transactions in US Dollars. Additionally, eReceipts can be directly imported from your bank account. Please be aware that CSV/OFX imported files of bank transactions do not support eReceipts.
It's important to note that eReceipts are not generated for lodging expenses. Moreover, due to incomplete or inaccurate category information from certain banks, there may be instances of invalid eReceipts being generated for hotel purchases. If you choose to re-categorize expenses, a similar situation may arise. It's crucial to remember that our Expensify eReceipt Guarantee excludes coverage for hotel and motel expenses.
diff --git a/docs/articles/new-expensify/expenses/Set-up-your-wallet.md b/docs/articles/new-expensify/expenses/Set-up-your-wallet.md
index de1ee61066b0..7f7b6196707d 100644
--- a/docs/articles/new-expensify/expenses/Set-up-your-wallet.md
+++ b/docs/articles/new-expensify/expenses/Set-up-your-wallet.md
@@ -5,6 +5,8 @@ description: Send and receive payments by adding your payment account
To send and receive money using Expensify, you’ll first need to set up your Expensify Wallet by adding your payment account.
+{:width="100%"}
+
{% include selector.html values="desktop, mobile" %}
{% include option.html value="desktop" %}
diff --git a/docs/articles/new-expensify/getting-started/Create-a-company-workspace.md b/docs/articles/new-expensify/getting-started/Create-a-company-workspace.md
index cbe21c9db20a..e25ae580e87a 100644
--- a/docs/articles/new-expensify/getting-started/Create-a-company-workspace.md
+++ b/docs/articles/new-expensify/getting-started/Create-a-company-workspace.md
@@ -30,6 +30,12 @@ You can get support any time by locating your chat with Concierge in your chat i
Click Default Currency to set the currency for all expenses submitted under the workspace. Expensify automatically converts all other currencies to your default currency.
+{:width="100%"}
+
+{:width="100%"}
+
+{:width="100%"}
+
# 3. Invite members
@@ -46,6 +52,12 @@ Once the invite is accepted, the new members will appear in your members list.
You can also invite members on the workspace’s Profile page by clicking **Share** to share the workspace’s URL or QR code.
{% include end-info.html %}
+{:width="100%"}
+
+{:width="100%"}
+
+{:width="100%"}
+
# 4. Set admins
Admins are members of your workspace that have permissions to manage the workspace. The table below shows the difference between member and admin permissions:
diff --git a/docs/articles/new-expensify/getting-started/Join-your-company's-workspace.md b/docs/articles/new-expensify/getting-started/Join-your-company's-workspace.md
index 9c5aea0c61ae..a4747c2d95e5 100644
--- a/docs/articles/new-expensify/getting-started/Join-your-company's-workspace.md
+++ b/docs/articles/new-expensify/getting-started/Join-your-company's-workspace.md
@@ -51,6 +51,8 @@ Upload your expenses and check your reports right from your phone by downloading
{% include end-option.html %}
{% include end-selector.html %}
+
+{:width="100%"}
# 3. Meet Concierge
diff --git a/docs/articles/new-expensify/workspaces/Create-expense-categories.md b/docs/articles/new-expensify/workspaces/Create-expense-categories.md
index 7b8d29d09d1c..6f810d8f96d0 100644
--- a/docs/articles/new-expensify/workspaces/Create-expense-categories.md
+++ b/docs/articles/new-expensify/workspaces/Create-expense-categories.md
@@ -8,6 +8,8 @@ In Expensify, categories refer to the **chart of accounts, GL accounts, expense
An admin can manually create categories for a workspace, or they will be automatically imported if your workspace is connected to another platform such as QuickBooks Online, QuickBooks Desktop, Intacct, Xero, or NetSuite. These imported categories can be enabled or disabled to use as categories for expenses added to Expensify. Additionally, Expensify will learn how you apply categories to specific merchants over time and apply them automatically.
+{:width="100%"}
+
# Manually add or delete categories
{% include selector.html values="desktop, mobile" %}
diff --git a/docs/articles/new-expensify/workspaces/Create-expense-tags.md b/docs/articles/new-expensify/workspaces/Create-expense-tags.md
index cf7bdd6fc6a7..e048ad9ca768 100644
--- a/docs/articles/new-expensify/workspaces/Create-expense-tags.md
+++ b/docs/articles/new-expensify/workspaces/Create-expense-tags.md
@@ -8,6 +8,8 @@ In Expensify, tags refer to **classes, projects, cost centers, locations, custom
An admin can manually create tags for a workspace, or they will be automatically imported if your workspace is connected to an accounting system, like QuickBooks Online or Xero. These imported tags can be enabled or disabled to use as tags for expenses added to Expensify. Additionally, Expensify will learn how you apply tags to specific merchants over time and apply them automatically.
+{:width="100%"}
+
# Manually add or delete tags
{% include selector.html values="desktop, mobile" %}
@@ -24,6 +26,8 @@ To manually add a tag,
7. Click **Add Tag** in the top right.
8. Enter a name for the tag and click **Save**.
+{:width="100%"}
+
To delete a tag,
1. Click the tag on the Tags page.
diff --git a/docs/assets/images/ExpensifyHelp-QBO-1.png b/docs/assets/images/ExpensifyHelp-QBO-1.png
new file mode 100644
index 000000000000..c612cb760d58
Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-QBO-1.png differ
diff --git a/docs/assets/images/ExpensifyHelp-QBO-2.png b/docs/assets/images/ExpensifyHelp-QBO-2.png
new file mode 100644
index 000000000000..7fbc99503f2e
Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-QBO-2.png differ
diff --git a/docs/assets/images/ExpensifyHelp-QBO-3.png b/docs/assets/images/ExpensifyHelp-QBO-3.png
new file mode 100644
index 000000000000..600a5903c05f
Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-QBO-3.png differ
diff --git a/docs/assets/images/ExpensifyHelp-Xero-1.png b/docs/assets/images/ExpensifyHelp-Xero-1.png
new file mode 100644
index 000000000000..c612cb760d58
Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-Xero-1.png differ
diff --git a/docs/assets/images/ExpensifyHelp-Xero-2.png b/docs/assets/images/ExpensifyHelp-Xero-2.png
new file mode 100644
index 000000000000..7fbc99503f2e
Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-Xero-2.png differ
diff --git a/docs/assets/images/ExpensifyHelp-Xero-3.png b/docs/assets/images/ExpensifyHelp-Xero-3.png
new file mode 100644
index 000000000000..e340a302bd89
Binary files /dev/null and b/docs/assets/images/ExpensifyHelp-Xero-3.png differ
diff --git a/docs/assets/images/ExpensifyHelp_ApproveExpense_1.png b/docs/assets/images/ExpensifyHelp_ApproveExpense_1.png
new file mode 100644
index 000000000000..8a721744860f
Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_ApproveExpense_1.png differ
diff --git a/docs/assets/images/ExpensifyHelp_ApproveExpense_2.png b/docs/assets/images/ExpensifyHelp_ApproveExpense_2.png
new file mode 100644
index 000000000000..25e11f0e7624
Binary files /dev/null and b/docs/assets/images/ExpensifyHelp_ApproveExpense_2.png differ
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 687d2bdbb662..eb251dc53b1a 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 9.0.1.7
+ 9.0.1.14FullStoryOrgId
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 514500458c71..efc72698918f 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature????CFBundleVersion
- 9.0.1.7
+ 9.0.1.14
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index 548446ce7280..6a414d579761 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString9.0.1CFBundleVersion
- 9.0.1.7
+ 9.0.1.14NSExtensionNSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index 7b4c0faa515a..db6fa70d9598 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "9.0.1-7",
+ "version": "9.0.1-14",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "9.0.1-7",
+ "version": "9.0.1-14",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@@ -106,7 +106,7 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "2.0.52",
+ "react-native-onyx": "2.0.53",
"react-native-pager-view": "6.2.3",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
@@ -136,8 +136,7 @@
"react-web-config": "^1.0.0",
"react-webcam": "^7.1.1",
"react-window": "^1.8.9",
- "semver": "^7.5.2",
- "shim-keyboard-event-key": "^1.0.3"
+ "semver": "^7.5.2"
},
"devDependencies": {
"@actions/core": "1.10.0",
@@ -31608,9 +31607,9 @@
}
},
"node_modules/react-native-onyx": {
- "version": "2.0.52",
- "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.52.tgz",
- "integrity": "sha512-uXlNhQg1UStx1W/U+9GYtIhLvx3vTIpN1WwE1gsiVxvimnUzKpQX/JBkgpR9b48ZoxsdiZXOT5kKLlqGCa6O1g==",
+ "version": "2.0.53",
+ "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.53.tgz",
+ "integrity": "sha512-ObNk5MhLOAVkLgE0NCI04CEO3qaP5ZG+NY1Kn3UnxcHlhyLlDQb10EOiDWSLwNR2s4K3kK+ge7Xmo6N0VdMyyA==",
"dependencies": {
"ascii-table": "0.0.9",
"fast-equals": "^4.0.3",
@@ -33920,10 +33919,6 @@
"shellcheck": "shellcheck-stable/shellcheck"
}
},
- "node_modules/shim-keyboard-event-key": {
- "version": "1.0.3",
- "license": "MIT"
- },
"node_modules/side-channel": {
"version": "1.0.4",
"license": "MIT",
diff --git a/package.json b/package.json
index 115df5b986ca..f5261521cb7f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "9.0.1-7",
+ "version": "9.0.1-14",
"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.",
@@ -159,7 +159,7 @@
"react-native-linear-gradient": "^2.8.1",
"react-native-localize": "^2.2.6",
"react-native-modal": "^13.0.0",
- "react-native-onyx": "2.0.52",
+ "react-native-onyx": "2.0.53",
"react-native-pager-view": "6.2.3",
"react-native-pdf": "6.7.3",
"react-native-performance": "^5.1.0",
@@ -189,8 +189,7 @@
"react-web-config": "^1.0.0",
"react-webcam": "^7.1.1",
"react-window": "^1.8.9",
- "semver": "^7.5.2",
- "shim-keyboard-event-key": "^1.0.3"
+ "semver": "^7.5.2"
},
"devDependencies": {
"@actions/core": "1.10.0",
diff --git a/src/CONST.ts b/src/CONST.ts
index dc8fb1040a76..f8ce1a574d49 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -363,6 +363,7 @@ const CONST = {
SPOTNANA_TRAVEL: 'spotnanaTravel',
NETSUITE_ON_NEW_EXPENSIFY: 'netsuiteOnNewExpensify',
REPORT_FIELDS_FEATURE: 'reportFieldsFeature',
+ WORKSPACE_FEEDS: 'workspaceFeeds',
},
BUTTON_STATES: {
DEFAULT: 'default',
@@ -941,20 +942,6 @@ const CONST = {
RESIZE_DEBOUNCE_TIME: 100,
UNREAD_UPDATE_DEBOUNCE_TIME: 300,
},
- SEARCH_TABLE_COLUMNS: {
- RECEIPT: 'receipt',
- DATE: 'date',
- MERCHANT: 'merchant',
- DESCRIPTION: 'description',
- FROM: 'from',
- TO: 'to',
- CATEGORY: 'category',
- TAG: 'tag',
- TOTAL_AMOUNT: 'amount',
- TYPE: 'type',
- ACTION: 'action',
- TAX_AMOUNT: 'taxAmount',
- },
PRIORITY_MODE: {
GSD: 'gsd',
DEFAULT: 'default',
@@ -1264,6 +1251,7 @@ const CONST = {
},
CENTRAL_PANE_ANIMATION_HEIGHT: 200,
LHN_SKELETON_VIEW_ITEM_HEIGHT: 64,
+ SEARCH_SKELETON_VIEW_ITEM_HEIGHT: 108,
EXPENSIFY_PARTNER_NAME: 'expensify.com',
EMAIL: {
ACCOUNTING: 'accounting@expensify.com',
@@ -3540,12 +3528,7 @@ const CONST = {
SCAN: 'scan',
DISTANCE: 'distance',
},
- TAB_SEARCH: {
- ALL: 'all',
- SHARED: 'shared',
- DRAFTS: 'drafts',
- FINISHED: 'finished',
- },
+
STATUS_TEXT_MAX_LENGTH: 100,
DROPDOWN_BUTTON_SIZE: {
@@ -4846,28 +4829,52 @@ const CONST = {
ADHOC: ' AdHoc',
},
- SEARCH_TRANSACTION_TYPE: {
- CASH: 'cash',
- CARD: 'card',
- DISTANCE: 'distance',
- },
-
- SEARCH_RESULTS_PAGE_SIZE: 50,
-
- SEARCH_DATA_TYPES: {
- TRANSACTION: 'transaction',
- REPORT: 'report',
+ SEARCH: {
+ RESULTS_PAGE_SIZE: 50,
+ DATA_TYPES: {
+ TRANSACTION: 'transaction',
+ REPORT: 'report',
+ },
+ ACTION_TYPES: {
+ DONE: 'done',
+ PAID: 'paid',
+ VIEW: 'view',
+ },
+ TRANSACTION_TYPE: {
+ CASH: 'cash',
+ CARD: 'card',
+ DISTANCE: 'distance',
+ },
+ SORT_ORDER: {
+ ASC: 'asc',
+ DESC: 'desc',
+ },
+ TAB: {
+ ALL: 'all',
+ SHARED: 'shared',
+ DRAFTS: 'drafts',
+ FINISHED: 'finished',
+ },
+ TABLE_COLUMNS: {
+ RECEIPT: 'receipt',
+ DATE: 'date',
+ MERCHANT: 'merchant',
+ DESCRIPTION: 'description',
+ FROM: 'from',
+ TO: 'to',
+ CATEGORY: 'category',
+ TAG: 'tag',
+ TOTAL_AMOUNT: 'amount',
+ TYPE: 'type',
+ ACTION: 'action',
+ TAX_AMOUNT: 'taxAmount',
+ },
},
REFERRER: {
NOTIFICATION: 'notification',
},
- SORT_ORDER: {
- ASC: 'asc',
- DESC: 'desc',
- },
-
SUBSCRIPTION_SIZE_LIMIT: 20000,
PAYMENT_CARD_CURRENCY: {
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index c2c5a4e70dfe..d354ee1c47fd 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -917,6 +917,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/accounting/quickbooks-online/import/taxes',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/accounting/quickbooks-online/import/taxes` as const,
},
+ RESTRICTED_ACTION: {
+ route: 'restricted-action/workspace/:policyID',
+ getRoute: (policyID: string) => `restricted-action/workspace/${policyID}` as const,
+ },
} as const;
/**
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 5a6ec3675e24..d8a2d166099e 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -142,6 +142,7 @@ const SCREENS = {
TRAVEL: 'Travel',
SEARCH_REPORT: 'SearchReport',
SETTINGS_CATEGORIES: 'SettingsCategories',
+ RESTRICTED_ACTION: 'RestrictedAction',
},
ONBOARDING_MODAL: {
ONBOARDING: 'Onboarding',
@@ -384,6 +385,7 @@ const SCREENS = {
KEYBOARD_SHORTCUTS: 'KeyboardShortcuts',
TRANSACTION_RECEIPT: 'TransactionReceipt',
FEATURE_TRAINING_ROOT: 'FeatureTraining_Root',
+ RESTRICTED_ACTION_ROOT: 'RestrictedAction_Root',
} as const;
type Screen = DeepValueOf;
diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx
index 17a2f6212447..9bd6142b5604 100644
--- a/src/components/AddressSearch/index.tsx
+++ b/src/components/AddressSearch/index.tsx
@@ -14,6 +14,7 @@ import useNetwork from '@hooks/useNetwork';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
+import * as UserLocation from '@libs/actions/UserLocation';
import * as ApiUtils from '@libs/ApiUtils';
import getCurrentPosition from '@libs/getCurrentPosition';
import type {GeolocationErrorCodeType} from '@libs/getCurrentPosition/getCurrentPosition.types';
@@ -249,12 +250,17 @@ function AddressSearch(
setIsFetchingCurrentLocation(false);
setLocationErrorCode(null);
+ const {latitude, longitude} = successData.coords;
+
const location = {
- lat: successData.coords.latitude,
- lng: successData.coords.longitude,
+ lat: latitude,
+ lng: longitude,
address: CONST.YOUR_LOCATION_TEXT,
name: CONST.YOUR_LOCATION_TEXT,
};
+
+ // Update the current user location
+ UserLocation.setUserLocation({longitude, latitude});
onPress?.(location);
},
(errorData) => {
diff --git a/src/components/FlatList/index.tsx b/src/components/FlatList/index.tsx
index 9f42e9597c79..f54eddcbeb79 100644
--- a/src/components/FlatList/index.tsx
+++ b/src/components/FlatList/index.tsx
@@ -44,6 +44,8 @@ function MVCPFlatList({maintainVisibleContentPosition, horizontal = false
const mutationObserverRef = useRef(null);
const lastScrollOffsetRef = useRef(0);
const isListRenderedRef = useRef(false);
+ const mvcpAutoscrollToTopThresholdRef = useRef(mvcpAutoscrollToTopThreshold);
+ mvcpAutoscrollToTopThresholdRef.current = mvcpAutoscrollToTopThreshold;
const getScrollOffset = useCallback((): number => {
if (!scrollRef.current) {
@@ -105,11 +107,11 @@ function MVCPFlatList({maintainVisibleContentPosition, horizontal = false
const scrollOffset = getScrollOffset();
prevFirstVisibleOffsetRef.current = firstVisibleViewOffset;
scrollToOffset(scrollOffset + delta, false);
- if (mvcpAutoscrollToTopThreshold != null && scrollOffset <= mvcpAutoscrollToTopThreshold) {
+ if (mvcpAutoscrollToTopThresholdRef.current != null && scrollOffset <= mvcpAutoscrollToTopThresholdRef.current) {
scrollToOffset(0, true);
}
}
- }, [getScrollOffset, scrollToOffset, mvcpMinIndexForVisible, mvcpAutoscrollToTopThreshold, horizontal]);
+ }, [getScrollOffset, scrollToOffset, mvcpMinIndexForVisible, horizontal]);
const setupMutationObserver = useCallback(() => {
const contentView = getContentView();
diff --git a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx
index 1ea425f0f9fd..be5da8c49a78 100644
--- a/src/components/FocusTrap/FocusTrapForModal/index.web.tsx
+++ b/src/components/FocusTrap/FocusTrapForModal/index.web.tsx
@@ -9,7 +9,7 @@ function FocusTrapForModal({children, active}: FocusTrapForModalProps) {
active={active}
focusTrapOptions={{
trapStack: sharedTrapStack,
- allowOutsideClick: true,
+ clickOutsideDeactivates: true,
initialFocus: false,
fallbackFocus: document.body,
}}
diff --git a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
index dd1a65cdb75a..be772c6ae10c 100644
--- a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
+++ b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
@@ -33,6 +33,8 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [
SCREENS.WORKSPACE.REPORT_FIELDS,
SCREENS.WORKSPACE.DISTANCE_RATES,
SCREENS.SEARCH.CENTRAL_PANE,
+ SCREENS.SETTINGS.TROUBLESHOOT,
+ SCREENS.SETTINGS.SAVE_THE_WORLD,
];
export default WIDE_LAYOUT_INACTIVE_SCREENS;
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 3fe36239d631..e699badc43ec 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -61,6 +61,7 @@ import House from '@assets/images/simple-illustrations/simple-illustration__hous
import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg';
import Lightbulb from '@assets/images/simple-illustrations/simple-illustration__lightbulb.svg';
import LockClosed from '@assets/images/simple-illustrations/simple-illustration__lockclosed.svg';
+import LockClosedOrange from '@assets/images/simple-illustrations/simple-illustration__lockclosed_orange.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';
@@ -192,4 +193,5 @@ export {
SendMoney,
CheckmarkCircle,
CreditCardEyes,
+ LockClosedOrange,
};
diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx
index c6be99297182..283f7c396edb 100644
--- a/src/components/MapView/MapView.tsx
+++ b/src/components/MapView/MapView.tsx
@@ -25,7 +25,7 @@ import type {ComponentProps, MapViewOnyxProps} from './types';
import utils from './utils';
const MapView = forwardRef(
- ({accessToken, style, mapPadding, userLocation: cachedUserLocation, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => {
+ ({accessToken, style, mapPadding, userLocation, styleURL, pitchEnabled, initialState, waypoints, directionCoordinates, onMapReady, interactive = true}, ref) => {
const navigation = useNavigation();
const {isOffline} = useNetwork();
const {translate} = useLocalize();
@@ -34,7 +34,7 @@ const MapView = forwardRef(
const cameraRef = useRef(null);
const [isIdle, setIsIdle] = useState(false);
const initialLocation = useMemo(() => initialState && {longitude: initialState.location[0], latitude: initialState.location[1]}, [initialState]);
- const [currentPosition, setCurrentPosition] = useState(cachedUserLocation ?? initialLocation);
+ const currentPosition = userLocation ?? initialLocation;
const [userInteractedWithMap, setUserInteractedWithMap] = useState(false);
const shouldInitializeCurrentPosition = useRef(true);
@@ -50,7 +50,6 @@ const MapView = forwardRef(
return;
}
UserLocation.clearUserLocation();
- setCurrentPosition(initialLocation);
},
[initialLocation],
);
@@ -74,7 +73,6 @@ const MapView = forwardRef(
getCurrentPosition((params) => {
const currentCoords = {longitude: params.coords.longitude, latitude: params.coords.latitude};
- setCurrentPosition(currentCoords);
UserLocation.setUserLocation(currentCoords);
}, setCurrentPositionToInitialState);
}, [isOffline, shouldPanMapToCurrentPosition, setCurrentPositionToInitialState]),
diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx
index c573550e49fb..01d7134a96da 100644
--- a/src/components/MapView/MapView.website.tsx
+++ b/src/components/MapView/MapView.website.tsx
@@ -12,6 +12,7 @@ import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import * as Expensicons from '@components/Icon/Expensicons';
+import usePrevious from '@hooks/usePrevious';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -39,7 +40,7 @@ const MapView = forwardRef(
waypoints,
mapPadding,
accessToken,
- userLocation: cachedUserLocation,
+ userLocation,
directionCoordinates,
initialState = {location: CONST.MAPBOX.DEFAULT_COORDINATE, zoom: CONST.MAPBOX.DEFAULT_ZOOM},
interactive = true,
@@ -55,7 +56,8 @@ const MapView = forwardRef(
const [mapRef, setMapRef] = useState(null);
const initialLocation = useMemo(() => ({longitude: initialState.location[0], latitude: initialState.location[1]}), [initialState]);
- const [currentPosition, setCurrentPosition] = useState(cachedUserLocation ?? initialLocation);
+ const currentPosition = userLocation ?? initialLocation;
+ const prevUserPosition = usePrevious(currentPosition);
const [userInteractedWithMap, setUserInteractedWithMap] = useState(false);
const [shouldResetBoundaries, setShouldResetBoundaries] = useState(false);
const setRef = useCallback((newRef: MapRef | null) => setMapRef(newRef), []);
@@ -73,7 +75,6 @@ const MapView = forwardRef(
return;
}
UserLocation.clearUserLocation();
- setCurrentPosition(initialLocation);
},
[initialLocation],
);
@@ -97,7 +98,6 @@ const MapView = forwardRef(
getCurrentPosition((params) => {
const currentCoords = {longitude: params.coords.longitude, latitude: params.coords.latitude};
- setCurrentPosition(currentCoords);
UserLocation.setUserLocation(currentCoords);
}, setCurrentPositionToInitialState);
}, [isOffline, shouldPanMapToCurrentPosition, setCurrentPositionToInitialState]),
@@ -112,11 +112,15 @@ const MapView = forwardRef(
return;
}
+ // Avoid animating the naviagtion to the same location
+ const shouldAnimate = prevUserPosition.longitude !== currentPosition.longitude || prevUserPosition.latitude !== currentPosition.latitude;
+
mapRef.flyTo({
center: [currentPosition.longitude, currentPosition.latitude],
zoom: CONST.MAPBOX.DEFAULT_ZOOM,
+ animate: shouldAnimate,
});
- }, [currentPosition, userInteractedWithMap, mapRef, shouldPanMapToCurrentPosition]);
+ }, [currentPosition, mapRef, prevUserPosition, shouldPanMapToCurrentPosition]);
const resetBoundaries = useCallback(() => {
if (!waypoints || waypoints.length === 0) {
diff --git a/src/components/Search.tsx b/src/components/Search.tsx
index 93792120fe99..cfb0192669d1 100644
--- a/src/components/Search.tsx
+++ b/src/components/Search.tsx
@@ -35,7 +35,7 @@ type SearchProps = {
sortOrder?: SortOrder;
};
-const sortableSearchTabs: SearchQuery[] = [CONST.TAB_SEARCH.ALL];
+const sortableSearchTabs: SearchQuery[] = [CONST.SEARCH.TAB.ALL];
const transactionItemMobileHeight = 100;
const reportItemTransactionHeight = 52;
const listItemPadding = 12; // this is equivalent to 'mb3' on every transaction/report list item
@@ -125,7 +125,7 @@ function Search({query, policyIDs, sortBy, sortOrder}: SearchProps) {
return;
}
const currentOffset = searchResults?.search?.offset ?? 0;
- SearchActions.search({hash, query, offset: currentOffset + CONST.SEARCH_RESULTS_PAGE_SIZE, sortBy, sortOrder});
+ SearchActions.search({hash, query, offset: currentOffset + CONST.SEARCH.RESULTS_PAGE_SIZE, sortBy, sortOrder});
};
const type = SearchUtils.getSearchType(searchResults?.search);
diff --git a/src/components/SelectionList/Search/ActionCell.tsx b/src/components/SelectionList/Search/ActionCell.tsx
new file mode 100644
index 000000000000..6aabfebf0da9
--- /dev/null
+++ b/src/components/SelectionList/Search/ActionCell.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import {View} from 'react-native';
+import Badge from '@components/Badge';
+import Button from '@components/Button';
+import * as Expensicons from '@components/Icon/Expensicons';
+import useLocalize from '@hooks/useLocalize';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+
+type ActionCellProps = {
+ onButtonPress: () => void;
+ action?: string;
+ isLargeScreenWidth?: boolean;
+};
+
+function ActionCell({onButtonPress, action = CONST.SEARCH.ACTION_TYPES.VIEW, isLargeScreenWidth = true}: ActionCellProps) {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+ const theme = useTheme();
+ const StyleUtils = useStyleUtils();
+
+ if (action === CONST.SEARCH.ACTION_TYPES.PAID || action === CONST.SEARCH.ACTION_TYPES.DONE) {
+ const buttonTextKey = action === CONST.SEARCH.ACTION_TYPES.PAID ? 'iou.settledExpensify' : 'common.done';
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
+
+ActionCell.displayName = 'ActionCell';
+
+export default ActionCell;
diff --git a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
index c3f2529b04e8..8f46a5388da8 100644
--- a/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
+++ b/src/components/SelectionList/Search/ExpenseItemHeaderNarrow.tsx
@@ -1,6 +1,5 @@
import React, {memo} from 'react';
import {View} from 'react-native';
-import Button from '@components/Button';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import useStyleUtils from '@hooks/useStyleUtils';
@@ -8,6 +7,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import variables from '@styles/variables';
import type {SearchAccountDetails} from '@src/types/onyx/SearchResults';
+import ActionCell from './ActionCell';
import UserInfoCell from './UserInfoCell';
type ExpenseItemHeaderNarrowProps = {
@@ -15,11 +15,11 @@ type ExpenseItemHeaderNarrowProps = {
participantTo: SearchAccountDetails;
participantFromDisplayName: string;
participantToDisplayName: string;
- buttonText: string;
onButtonPress: () => void;
+ action?: string;
};
-function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, participantTo, participantToDisplayName, buttonText, onButtonPress}: ExpenseItemHeaderNarrowProps) {
+function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, participantTo, participantToDisplayName, onButtonPress, action}: ExpenseItemHeaderNarrowProps) {
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const theme = useTheme();
@@ -47,12 +47,10 @@ function ExpenseItemHeaderNarrow({participantFrom, participantFromDisplayName, p
-
diff --git a/src/components/SelectionList/Search/ReportListItem.tsx b/src/components/SelectionList/Search/ReportListItem.tsx
index 313794eb2064..37bbe66670f9 100644
--- a/src/components/SelectionList/Search/ReportListItem.tsx
+++ b/src/components/SelectionList/Search/ReportListItem.tsx
@@ -1,6 +1,5 @@
import React from 'react';
import {View} from 'react-native';
-import Button from '@components/Button';
import BaseListItem from '@components/SelectionList/BaseListItem';
import type {ListItem, ReportListItemProps, ReportListItemType, TransactionListItemType} from '@components/SelectionList/types';
import Text from '@components/Text';
@@ -14,6 +13,7 @@ import Navigation from '@libs/Navigation/Navigation';
import {getSearchParams} from '@libs/SearchUtils';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
+import ActionCell from './ActionCell';
import ExpenseItemHeaderNarrow from './ExpenseItemHeaderNarrow';
import TransactionListItem from './TransactionListItem';
import TransactionListItemRow from './TransactionListItemRow';
@@ -29,10 +29,6 @@ type ReportCellProps = {
reportItem: ReportListItemType;
} & CellProps;
-type ActionCellProps = {
- onButtonPress: () => void;
-} & CellProps;
-
function TotalCell({showTooltip, isLargeScreenWidth, reportItem}: ReportCellProps) {
const styles = useThemeStyles();
@@ -45,21 +41,6 @@ function TotalCell({showTooltip, isLargeScreenWidth, reportItem}: ReportCellProp
);
}
-function ActionCell({onButtonPress}: ActionCellProps) {
- const {translate} = useLocalize();
- const styles = useThemeStyles();
-
- return (
-
- );
-}
-
function ReportListItem({
item,
isFocused,
@@ -90,7 +71,7 @@ function ReportListItem({
const openReportInRHP = (transactionItem: TransactionListItemType) => {
const searchParams = getSearchParams();
- const currentQuery = searchParams?.query ?? CONST.TAB_SEARCH.ALL;
+ const currentQuery = searchParams?.query ?? CONST.SEARCH.TAB.ALL;
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute(currentQuery, transactionItem.transactionThreadReportID));
};
@@ -150,7 +131,7 @@ function ReportListItem({
participantFromDisplayName={participantFromDisplayName}
participantTo={participantTo}
participantToDisplayName={participantToDisplayName}
- buttonText={translate('common.view')}
+ action={reportItem.action}
onButtonPress={handleOnButtonPress}
/>
)}
@@ -173,12 +154,12 @@ function ReportListItem({
{isLargeScreenWidth && (
<>
{/** We add an empty view with type style to align the total with the table header */}
-
-
+
+
>
diff --git a/src/components/SelectionList/Search/TransactionListItemRow.tsx b/src/components/SelectionList/Search/TransactionListItemRow.tsx
index 0099aaf64625..001bd6eeae1b 100644
--- a/src/components/SelectionList/Search/TransactionListItemRow.tsx
+++ b/src/components/SelectionList/Search/TransactionListItemRow.tsx
@@ -1,13 +1,11 @@
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
-import Button from '@components/Button';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import ReceiptImage from '@components/ReceiptImage';
import type {TransactionListItemType} from '@components/SelectionList/types';
import TextWithTooltip from '@components/TextWithTooltip';
-import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -19,6 +17,7 @@ import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import type {SearchTransactionType} from '@src/types/onyx/SearchResults';
+import ActionCell from './ActionCell';
import ExpenseItemHeaderNarrow from './ExpenseItemHeaderNarrow';
import TextWithIconCell from './TextWithIconCell';
import UserInfoCell from './UserInfoCell';
@@ -34,10 +33,6 @@ type TransactionCellProps = {
transactionItem: TransactionListItemType;
} & CellProps;
-type ActionCellProps = {
- onButtonPress: () => void;
-} & CellProps;
-
type TotalCellProps = {
// eslint-disable-next-line react/no-unused-prop-types
isChildListItem: boolean;
@@ -54,11 +49,11 @@ type TransactionListItemRowProps = {
const getTypeIcon = (type?: SearchTransactionType) => {
switch (type) {
- case CONST.SEARCH_TRANSACTION_TYPE.CASH:
+ case CONST.SEARCH.TRANSACTION_TYPE.CASH:
return Expensicons.Cash;
- case CONST.SEARCH_TRANSACTION_TYPE.CARD:
+ case CONST.SEARCH.TRANSACTION_TYPE.CARD:
return Expensicons.CreditCard;
- case CONST.SEARCH_TRANSACTION_TYPE.DISTANCE:
+ case CONST.SEARCH.TRANSACTION_TYPE.DISTANCE:
return Expensicons.Car;
default:
return Expensicons.Cash;
@@ -148,21 +143,6 @@ function TypeCell({transactionItem, isLargeScreenWidth}: TransactionCellProps) {
);
}
-function ActionCell({onButtonPress}: ActionCellProps) {
- const {translate} = useLocalize();
- const styles = useThemeStyles();
-
- return (
-
- );
-}
-
function CategoryCell({isLargeScreenWidth, showTooltip, transactionItem}: TransactionCellProps) {
const styles = useThemeStyles();
return isLargeScreenWidth ? (
@@ -217,7 +197,6 @@ function TaxCell({transactionItem, showTooltip}: TransactionCellProps) {
function TransactionListItemRow({item, showTooltip, onButtonPress, showItemHeaderOnNarrowLayout = true, containerStyle, isChildListItem = false}: TransactionListItemRowProps) {
const styles = useThemeStyles();
- const {translate} = useLocalize();
const {isLargeScreenWidth} = useWindowDimensions();
const StyleUtils = useStyleUtils();
@@ -230,8 +209,8 @@ function TransactionListItemRow({item, showTooltip, onButtonPress, showItemHeade
participantFromDisplayName={item.formattedFrom}
participantTo={item.to}
participantToDisplayName={item.formattedTo}
- buttonText={translate('common.view')}
onButtonPress={onButtonPress}
+ action={item.action}
/>
)}
@@ -288,41 +267,41 @@ function TransactionListItemRow({item, showTooltip, onButtonPress, showItemHeade
return (
-
+
-
+
-
+
-
+
-
+
{item.shouldShowCategory && (
-
+
)}
{item.shouldShowTag && (
-
+
)}
{item.shouldShowTax && (
-
+
)}
-
+
-
+
-
+
diff --git a/src/components/SelectionList/SearchTableHeader.tsx b/src/components/SelectionList/SearchTableHeader.tsx
index 9cb7bb940bd5..6ba753273e8c 100644
--- a/src/components/SelectionList/SearchTableHeader.tsx
+++ b/src/components/SelectionList/SearchTableHeader.tsx
@@ -20,65 +20,65 @@ type SearchColumnConfig = {
const SearchColumns: SearchColumnConfig[] = [
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.RECEIPT,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.RECEIPT,
translationKey: 'common.receipt',
shouldShow: () => true,
isColumnSortable: false,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.DATE,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.DATE,
translationKey: 'common.date',
shouldShow: () => true,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.MERCHANT,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.MERCHANT,
translationKey: 'common.merchant',
shouldShow: (data: OnyxTypes.SearchResults['data']) => SearchUtils.getShouldShowMerchant(data),
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.DESCRIPTION,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION,
translationKey: 'common.description',
shouldShow: (data: OnyxTypes.SearchResults['data']) => !SearchUtils.getShouldShowMerchant(data),
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.FROM,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.FROM,
translationKey: 'common.from',
shouldShow: () => true,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.TO,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.TO,
translationKey: 'common.to',
shouldShow: () => true,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.CATEGORY,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.CATEGORY,
translationKey: 'common.category',
shouldShow: (data, metadata) => metadata?.columnsToShow.shouldShowCategoryColumn ?? false,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.TAG,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.TAG,
translationKey: 'common.tag',
shouldShow: (data, metadata) => metadata?.columnsToShow.shouldShowTagColumn ?? false,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.TAX_AMOUNT,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.TAX_AMOUNT,
translationKey: 'common.tax',
shouldShow: (data, metadata) => metadata?.columnsToShow.shouldShowTaxColumn ?? false,
isColumnSortable: false,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.TOTAL_AMOUNT,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT,
translationKey: 'common.total',
shouldShow: () => true,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.TYPE,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.TYPE,
translationKey: 'common.type',
shouldShow: () => true,
isColumnSortable: false,
},
{
- columnName: CONST.SEARCH_TABLE_COLUMNS.ACTION,
+ columnName: CONST.SEARCH.TABLE_COLUMNS.ACTION,
translationKey: 'common.action',
shouldShow: () => true,
isColumnSortable: false,
@@ -115,7 +115,7 @@ function SearchTableHeader({data, metadata, sortBy, sortOrder, isSortingAllowed,
}
const isActive = sortBy === columnName;
- const textStyle = columnName === CONST.SEARCH_TABLE_COLUMNS.RECEIPT ? StyleUtils.getTextOverflowStyle('clip') : null;
+ const textStyle = columnName === CONST.SEARCH.TABLE_COLUMNS.RECEIPT ? StyleUtils.getTextOverflowStyle('clip') : null;
const isSortable = isSortingAllowed && isColumnSortable;
return (
@@ -123,7 +123,7 @@ function SearchTableHeader({data, metadata, sortBy, sortOrder, isSortingAllowed,
key={translationKey}
text={translate(translationKey)}
textStyle={textStyle}
- sortOrder={sortOrder ?? CONST.SORT_ORDER.ASC}
+ sortOrder={sortOrder ?? CONST.SEARCH.SORT_ORDER.ASC}
isActive={isActive}
containerStyle={[StyleUtils.getSearchTableColumnStyles(columnName, shouldShowYear)]}
isSortable={isSortable}
diff --git a/src/components/SelectionList/SortableHeaderText.tsx b/src/components/SelectionList/SortableHeaderText.tsx
index bd5f4873bbbc..8b0accf45711 100644
--- a/src/components/SelectionList/SortableHeaderText.tsx
+++ b/src/components/SelectionList/SortableHeaderText.tsx
@@ -39,11 +39,11 @@ export default function SortableHeaderText({text, sortOrder, isActive, textStyle
);
}
- const icon = sortOrder === CONST.SORT_ORDER.ASC ? Expensicons.ArrowUpLong : Expensicons.ArrowDownLong;
+ const icon = sortOrder === CONST.SEARCH.SORT_ORDER.ASC ? Expensicons.ArrowUpLong : Expensicons.ArrowDownLong;
const displayIcon = isActive;
const activeColumnStyle = isSortable && isActive && styles.searchTableHeaderActive;
- const nextSortOrder = isActive && sortOrder === CONST.SORT_ORDER.DESC ? CONST.SORT_ORDER.ASC : CONST.SORT_ORDER.DESC;
+ const nextSortOrder = isActive && sortOrder === CONST.SEARCH.SORT_ORDER.DESC ? CONST.SEARCH.SORT_ORDER.ASC : CONST.SEARCH.SORT_ORDER.DESC;
return (
diff --git a/src/components/Skeletons/ItemListSkeletonView.tsx b/src/components/Skeletons/ItemListSkeletonView.tsx
index 5c46dbdddbfc..1ee2da8a8019 100644
--- a/src/components/Skeletons/ItemListSkeletonView.tsx
+++ b/src/components/Skeletons/ItemListSkeletonView.tsx
@@ -1,5 +1,6 @@
import React, {useMemo, useState} from 'react';
import {View} from 'react-native';
+import type {StyleProp, ViewStyle} from 'react-native';
import SkeletonViewContentLoader from '@components/SkeletonViewContentLoader';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -9,9 +10,11 @@ type ListItemSkeletonProps = {
shouldAnimate?: boolean;
renderSkeletonItem: (args: {itemIndex: number}) => React.ReactNode;
fixedNumItems?: number;
+ itemViewStyle?: StyleProp;
+ itemViewHeight?: number;
};
-function ItemListSkeletonView({shouldAnimate = true, renderSkeletonItem, fixedNumItems}: ListItemSkeletonProps) {
+function ItemListSkeletonView({shouldAnimate = true, renderSkeletonItem, fixedNumItems, itemViewStyle = {}, itemViewHeight = CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT}: ListItemSkeletonProps) {
const theme = useTheme();
const themeStyles = useThemeStyles();
@@ -20,20 +23,23 @@ function ItemListSkeletonView({shouldAnimate = true, renderSkeletonItem, fixedNu
const items = [];
for (let i = 0; i < numItems; i++) {
items.push(
-
- {renderSkeletonItem({itemIndex: i})}
- ,
+
+ {renderSkeletonItem({itemIndex: i})}
+
+ ,
);
}
return items;
- }, [numItems, shouldAnimate, theme, themeStyles, renderSkeletonItem]);
+ }, [numItems, shouldAnimate, theme, themeStyles, renderSkeletonItem, itemViewHeight, itemViewStyle]);
return (
(
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+ />
+ );
+ }
return (
`Actions on the ${workspaceName} workspace are currently restricted`,
+ workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}) =>
+ `Workspace owner, ${workspaceOwnerName} will need to add or update the payment card on file to unlock new workspace activity.`,
+ youWillNeedToAddOrUpdatePaymentCard: "You'll need to add or update the payment card on file to unlock new workspace activity.",
+ addPaymentCardToUnlock: 'Add a payment card to unlock!',
+ addPaymentCardToContinueUsingWorkspace: 'Add a payment card to continue using this workspace',
+ pleaseReachOutToYourWorkspaceAdmin: 'Please reach out to your workspace admin for any questions.',
+ chatWithYourAdmin: 'Chat with your admin',
+ chatInAdmins: 'Chat in #admins',
+ addPaymentCard: 'Add payment card',
+ },
},
getAssistancePage: {
title: 'Get assistance',
@@ -3363,4 +3376,7 @@ export default {
additionalInfoTitle: 'What software are you moving to and why?',
additionalInfoInputLabel: 'Your response',
},
+ roomChangeLog: {
+ updateRoomDescription: 'set the room description to:',
+ },
} satisfies TranslationBase;
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 28c7f60384cc..cc3ae848de6b 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -414,7 +414,7 @@ export default {
findMember: 'Encuentra un miembro',
},
videoChatButtonAndMenu: {
- tooltip: 'Iniciar una llamada',
+ tooltip: 'Programar una llamada',
},
hello: 'Hola',
phoneCountryCode: '34',
@@ -1179,8 +1179,8 @@ export default {
approver: 'Aprobador',
connectBankAccount: 'Conectar cuenta bancaria',
addApprovalsDescription: 'Requiere una aprobación adicional antes de autorizar un pago.',
- makeOrTrackPaymentsTitle: 'Realizar o seguir pagos',
- makeOrTrackPaymentsDescription: 'Añade un pagador autorizado para los pagos realizados en Expensify, o simplemente realiza un seguimiento de los pagos realizados en otro lugar.',
+ makeOrTrackPaymentsTitle: 'Pagos',
+ makeOrTrackPaymentsDescription: 'Añade un pagador autorizado para los pagos realizados en Expensify, o realiza un seguimiento de los pagos realizados en otro lugar.',
editor: {
submissionFrequency: 'Elige cuánto tiempo Expensify debe esperar antes de compartir los gastos sin errores.',
},
@@ -2768,6 +2768,19 @@ export default {
errorDescriptionPartTwo: 'contacta con el conserje',
errorDescriptionPartThree: 'por ayuda.',
},
+ restrictedAction: {
+ restricted: 'Restringido',
+ actionsAreCurrentlyRestricted: ({workspaceName}) => `Las acciones en el espacio de trabajo ${workspaceName} están actualmente restringidas`,
+ workspaceOwnerWillNeedToAddOrUpdatePaymentCard: ({workspaceOwnerName}) =>
+ `El propietario del espacio de trabajo, ${workspaceOwnerName} tendrá que añadir o actualizar la tarjeta de pago registrada para desbloquear nueva actividad en el espacio de trabajo.`,
+ youWillNeedToAddOrUpdatePaymentCard: 'Debes añadir o actualizar la tarjeta de pago registrada para desbloquear nueva actividad en el espacio de trabajo.',
+ addPaymentCardToUnlock: 'Añade una tarjeta para desbloquearlo!',
+ addPaymentCardToContinueUsingWorkspace: 'Añade una tarjeta de pago para seguir utilizando este espacio de trabajo',
+ pleaseReachOutToYourWorkspaceAdmin: 'Si tienes alguna pregunta, ponte en contacto con el administrador de su espacio de trabajo.',
+ chatWithYourAdmin: 'Chatea con tu administrador',
+ chatInAdmins: 'Chatea en #admins',
+ addPaymentCard: 'Agregar tarjeta de pago',
+ },
},
getAssistancePage: {
title: 'Obtener ayuda',
@@ -3867,4 +3880,7 @@ export default {
additionalInfoTitle: '¿A qué software está migrando y por qué?',
additionalInfoInputLabel: 'Tu respuesta',
},
+ roomChangeLog: {
+ updateRoomDescription: 'establece la descripción de la sala a:',
+ },
} satisfies EnglishTranslation;
diff --git a/src/libs/API/parameters/OpenPolicyInitialPageParams.ts b/src/libs/API/parameters/OpenPolicyInitialPageParams.ts
new file mode 100644
index 000000000000..764abe9a6a77
--- /dev/null
+++ b/src/libs/API/parameters/OpenPolicyInitialPageParams.ts
@@ -0,0 +1,5 @@
+type OpenPolicyInitialPageParams = {
+ policyID: string;
+};
+
+export default OpenPolicyInitialPageParams;
diff --git a/src/libs/API/parameters/OpenPolicyProfilePageParams.ts b/src/libs/API/parameters/OpenPolicyProfilePageParams.ts
new file mode 100644
index 000000000000..55dce33a3dac
--- /dev/null
+++ b/src/libs/API/parameters/OpenPolicyProfilePageParams.ts
@@ -0,0 +1,5 @@
+type OpenPolicyProfilePageParams = {
+ policyID: string;
+};
+
+export default OpenPolicyProfilePageParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 0eb9ae30336b..269821318889 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -191,6 +191,8 @@ export type {default as OpenPolicyDistanceRatesPageParams} from './OpenPolicyDis
export type {default as OpenPolicyTaxesPageParams} from './OpenPolicyTaxesPageParams';
export type {default as EnablePolicyTaxesParams} from './EnablePolicyTaxesParams';
export type {default as OpenPolicyMoreFeaturesPageParams} from './OpenPolicyMoreFeaturesPageParams';
+export type {default as OpenPolicyProfilePageParams} from './OpenPolicyProfilePageParams';
+export type {default as OpenPolicyInitialPageParams} from './OpenPolicyInitialPageParams';
export type {default as CreatePolicyDistanceRateParams} from './CreatePolicyDistanceRateParams';
export type {default as SetPolicyDistanceRatesUnitParams} from './SetPolicyDistanceRatesUnitParams';
export type {default as SetPolicyDistanceRatesDefaultCategoryParams} from './SetPolicyDistanceRatesDefaultCategoryParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 92b6d7679418..c5057d5c8d7c 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -498,6 +498,8 @@ const READ_COMMANDS = {
OPEN_POLICY_WORKFLOWS_PAGE: 'OpenPolicyWorkflowsPage',
OPEN_POLICY_DISTANCE_RATES_PAGE: 'OpenPolicyDistanceRatesPage',
OPEN_POLICY_MORE_FEATURES_PAGE: 'OpenPolicyMoreFeaturesPage',
+ OPEN_POLICY_PROFILE_PAGE: 'OpenPolicyProfilePage',
+ OPEN_POLICY_INITIAL_PAGE: 'OpenPolicyInitialPage',
OPEN_POLICY_ACCOUNTING_PAGE: 'OpenPolicyAccountingPage',
SEARCH: 'Search',
OPEN_SUBSCRIPTION_PAGE: 'OpenSubscriptionPage',
@@ -546,6 +548,8 @@ type ReadCommandParameters = {
[READ_COMMANDS.OPEN_POLICY_WORKFLOWS_PAGE]: Parameters.OpenPolicyWorkflowsPageParams;
[READ_COMMANDS.OPEN_POLICY_DISTANCE_RATES_PAGE]: Parameters.OpenPolicyDistanceRatesPageParams;
[READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE]: Parameters.OpenPolicyMoreFeaturesPageParams;
+ [READ_COMMANDS.OPEN_POLICY_PROFILE_PAGE]: Parameters.OpenPolicyProfilePageParams;
+ [READ_COMMANDS.OPEN_POLICY_INITIAL_PAGE]: Parameters.OpenPolicyInitialPageParams;
[READ_COMMANDS.OPEN_POLICY_ACCOUNTING_PAGE]: Parameters.OpenPolicyAccountingPageParams;
[READ_COMMANDS.SEARCH]: Parameters.SearchParams;
[READ_COMMANDS.OPEN_SUBSCRIPTION_PAGE]: null;
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index f53f7bd2c9b3..4a80eb474f6c 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -376,6 +376,10 @@ const SearchReportModalStackNavigator = createModalStackNavigator require('../../../../pages/home/ReportScreen').default,
});
+const RestrictedActionModalStackNavigator = createModalStackNavigator({
+ [SCREENS.RESTRICTED_ACTION_ROOT]: () => require('../../../../pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage').default as React.ComponentType,
+});
+
export {
AddPersonalBankAccountModalStackNavigator,
EditRequestStackNavigator,
@@ -405,4 +409,5 @@ export {
WalletStatementStackNavigator,
TransactionDuplicateStackNavigator,
SearchReportModalStackNavigator,
+ RestrictedActionModalStackNavigator,
};
diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
index 68dc193c3618..5c833a603fb7 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx
@@ -48,7 +48,7 @@ function BaseCentralPaneNavigator() {
/>
diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
index 093ac1b43fe1..05c23797fe0e 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx
@@ -148,6 +148,10 @@ function RightModalNavigator({navigation}: RightModalNavigatorProps) {
name={SCREENS.RIGHT_MODAL.SEARCH_REPORT}
component={ModalStackNavigators.SearchReportModalStackNavigator}
/>
+
diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
index 5407a451682a..5306f6b55054 100644
--- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
+++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
@@ -1,35 +1,15 @@
import {useEffect} from 'react';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx} from 'react-native-onyx';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import usePermissions from '@hooks/usePermissions';
import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {PersonalDetailsList, Policy, Report, ReportMetadata} from '@src/types/onyx';
+import type {Policy, Report, ReportMetadata} from '@src/types/onyx';
import type {ReportScreenWrapperProps} from './ReportScreenWrapper';
-type ReportScreenIDSetterComponentProps = {
- /** Available reports that would be displayed in this navigator */
- reports: OnyxCollection;
-
- /** The policies which the user has access to */
- policies: OnyxCollection;
-
- /** The personal details of the person who is logged in */
- personalDetails: OnyxEntry;
-
- /** Whether user is a new user */
- isFirstTimeNewExpensifyUser: OnyxEntry;
-
- /** The report metadata */
- reportMetadata: OnyxCollection;
-
- /** The accountID of the current user */
- accountID?: number;
-};
-
-type ReportScreenIDSetterProps = ReportScreenIDSetterComponentProps & ReportScreenWrapperProps;
+type ReportScreenIDSetterProps = ReportScreenWrapperProps;
/**
* Get the most recently accessed report for the user
@@ -57,11 +37,18 @@ const getLastAccessedReportID = (
return lastReport?.reportID;
};
-// This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params
-function ReportScreenIDSetter({route, reports, policies, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID, personalDetails}: ReportScreenIDSetterProps) {
+// This wrapper is responsible for opening the last accessed report if there is no reportID specified in the route params
+function ReportScreenIDSetter({route, navigation}: ReportScreenIDSetterProps) {
const {canUseDefaultRooms} = usePermissions();
const {activeWorkspaceID} = useActiveWorkspace();
+ const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {allowStaleData: true});
+ const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {allowStaleData: true});
+ const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST);
+ const [isFirstTimeNewExpensifyUser] = useOnyx(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, {initialValue: false});
+ const [reportMetadata] = useOnyx(ONYXKEYS.COLLECTION.REPORT_METADATA, {allowStaleData: true});
+ const [accountID] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.accountID});
+
useEffect(() => {
// Don't update if there is a reportID in the params already
if (route?.params?.reportID) {
@@ -101,28 +88,4 @@ function ReportScreenIDSetter({route, reports, policies, navigation, isFirstTime
ReportScreenIDSetter.displayName = 'ReportScreenIDSetter';
-export default withOnyx({
- reports: {
- key: ONYXKEYS.COLLECTION.REPORT,
- allowStaleData: true,
- },
- policies: {
- key: ONYXKEYS.COLLECTION.POLICY,
- allowStaleData: true,
- },
- isFirstTimeNewExpensifyUser: {
- key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER,
- initialValue: false,
- },
- reportMetadata: {
- key: ONYXKEYS.COLLECTION.REPORT_METADATA,
- allowStaleData: true,
- },
- accountID: {
- key: ONYXKEYS.SESSION,
- selector: (session) => session?.accountID,
- },
- personalDetails: {
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- },
-})(ReportScreenIDSetter);
+export default ReportScreenIDSetter;
diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx
index 9b2e9449c672..79cb33ef9b39 100644
--- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx
+++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.tsx
@@ -100,7 +100,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps
{
- interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)));
+ interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL)));
}}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.search')}
diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx
index 4fecfdcb0e3e..699ebebbc66c 100644
--- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx
+++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx
@@ -101,7 +101,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps
{
- interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL)));
+ interceptAnonymousUser(() => Navigation.navigate(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL)));
}}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('common.search')}
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 616445b55c60..1b01d515c557 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -748,6 +748,11 @@ const config: LinkingOptions['config'] = {
[SCREENS.SEARCH.REPORT_RHP]: ROUTES.SEARCH_REPORT.route,
},
},
+ [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: {
+ screens: {
+ [SCREENS.RESTRICTED_ACTION_ROOT]: ROUTES.RESTRICTED_ACTION.route,
+ },
+ },
},
},
diff --git a/src/libs/Navigation/switchPolicyID.ts b/src/libs/Navigation/switchPolicyID.ts
index 78d23cb9d53c..ffaaf8daa081 100644
--- a/src/libs/Navigation/switchPolicyID.ts
+++ b/src/libs/Navigation/switchPolicyID.ts
@@ -77,7 +77,7 @@ export default function switchPolicyID(navigation: NavigationContainerRef>;
const action: StackNavigationAction = getActionFromState(stateFromPath, linkingConfig.config);
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index d81c1a19f34f..1733f950ab08 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -777,6 +777,7 @@ type RightModalNavigatorParamList = {
[SCREENS.RIGHT_MODAL.TRANSACTION_DUPLICATE]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.TRAVEL]: NavigatorScreenParams;
[SCREENS.RIGHT_MODAL.SEARCH_REPORT]: NavigatorScreenParams;
+ [SCREENS.RIGHT_MODAL.RESTRICTED_ACTION]: NavigatorScreenParams;
};
type TravelNavigatorParamList = {
@@ -953,6 +954,12 @@ type SearchReportParamList = {
};
};
+type RestrictedActionParamList = {
+ [SCREENS.RESTRICTED_ACTION_ROOT]: {
+ policyID: string;
+ };
+};
+
type RootStackParamList = PublicScreensParamList & AuthScreensParamList & LeftModalNavigatorParamList;
type BottomTabName = keyof BottomTabNavigatorParamList;
@@ -1018,4 +1025,5 @@ export type {
WelcomeVideoModalNavigatorParamList,
TransactionDuplicateNavigatorParamList,
SearchReportParamList,
+ RestrictedActionParamList,
};
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 012bdfbd4a90..077fb5b72102 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -2518,7 +2518,7 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
let userToInvite = null;
if (canInviteUser) {
- if (recentReports.length === 0) {
+ if (recentReports.length === 0 && personalDetails.length === 0) {
userToInvite = getUserToInviteOption({
searchValue,
betas,
diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts
index 632c86387f70..79936d498280 100644
--- a/src/libs/Permissions.ts
+++ b/src/libs/Permissions.ts
@@ -48,6 +48,10 @@ function canUseReportFieldsFeature(betas: OnyxEntry): boolean {
return !!betas?.includes(CONST.BETAS.REPORT_FIELDS_FEATURE) || canUseAllBetas(betas);
}
+function canUseWorkspaceFeeds(betas: OnyxEntry): boolean {
+ return !!betas?.includes(CONST.BETAS.WORKSPACE_FEEDS) || canUseAllBetas(betas);
+}
+
/**
* Link previews are temporarily disabled.
*/
@@ -67,4 +71,5 @@ export default {
canUseSpotnanaTravel,
canUseNetSuiteIntegration,
canUseReportFieldsFeature,
+ canUseWorkspaceFeeds,
};
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index af3f3b264d13..e8e640ba49eb 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -153,6 +153,12 @@ function isExpensifyTeam(email: string | undefined): boolean {
const isPolicyAdmin = (policy: OnyxInputOrEntry | EmptyObject, currentUserLogin?: string): boolean =>
(policy?.role ?? (currentUserLogin && policy?.employeeList?.[currentUserLogin]?.role)) === CONST.POLICY.ROLE.ADMIN;
+/**
+ * Checks if the current user is an user of the policy.
+ */
+const isPolicyUser = (policy: OnyxInputOrEntry | EmptyObject, currentUserLogin?: string): boolean =>
+ (policy?.role ?? (currentUserLogin && policy?.employeeList?.[currentUserLogin]?.role)) === CONST.POLICY.ROLE.USER;
+
/**
* Checks if the policy is a free group policy.
*/
@@ -533,6 +539,7 @@ export {
isPaidGroupPolicy,
isPendingDeletePolicy,
isPolicyAdmin,
+ isPolicyUser,
isPolicyEmployee,
isPolicyFeatureEnabled,
isPolicyOwner,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 615c728a9d93..4d83b2b82445 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -2170,10 +2170,11 @@ function getDeletedParentActionMessageForChatReport(reportAction: OnyxEntry, reportOrID: OnyxEntry | string, shouldUseShortDisplayName = true): string {
- if (!ReportActionsUtils.isMoneyRequestAction(reportAction)) {
- return '';
- }
+function getReimbursementQueuedActionMessage(
+ reportAction: OnyxEntry>,
+ reportOrID: OnyxEntry | string,
+ shouldUseShortDisplayName = true,
+): string {
const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID;
const submitterDisplayName = getDisplayNameForParticipant(report?.ownerAccountID, shouldUseShortDisplayName) ?? '';
const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction);
@@ -2195,9 +2196,6 @@ function getReimbursementDeQueuedActionMessage(
reportOrID: OnyxEntry | EmptyObject | string,
isLHNPreview = false,
): string {
- if (!ReportActionsUtils.isReimbursementDeQueuedAction(reportAction)) {
- return '';
- }
const report = typeof reportOrID === 'string' ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] : reportOrID;
const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction);
const amount = originalMessage?.amount;
@@ -4622,7 +4620,13 @@ function buildOptimisticChatReport(
return optimisticChatReport;
}
-function buildOptimisticGroupChatReport(participantAccountIDs: number[], reportName: string, avatarUri: string, optimisticReportID?: string) {
+function buildOptimisticGroupChatReport(
+ participantAccountIDs: number[],
+ reportName: string,
+ avatarUri: string,
+ optimisticReportID?: string,
+ notificationPreference?: NotificationPreference,
+) {
return buildOptimisticChatReport(
participantAccountIDs,
reportName,
@@ -4633,7 +4637,7 @@ function buildOptimisticGroupChatReport(participantAccountIDs: number[], reportN
undefined,
undefined,
undefined,
- undefined,
+ notificationPreference,
undefined,
undefined,
undefined,
@@ -7012,6 +7016,10 @@ function getChatUsedForOnboarding(): OnyxEntry {
return Object.values(allReports ?? {}).find(isChatUsedForOnboarding);
}
+function findPolicyExpenseChatByPolicyID(policyID: string): OnyxEntry {
+ return Object.values(allReports ?? {}).find((report) => isPolicyExpenseChat(report) && report?.policyID === policyID);
+}
+
export {
addDomainToShortMention,
areAllRequestsBeingSmartScanned,
@@ -7286,6 +7294,7 @@ export {
createDraftWorkspaceAndNavigateToConfirmationScreen,
isChatUsedForOnboarding,
getChatUsedForOnboarding,
+ findPolicyExpenseChatByPolicyID,
};
export type {
diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts
index 01b9a61ab73f..5fdd12556d8a 100644
--- a/src/libs/SearchUtils.ts
+++ b/src/libs/SearchUtils.ts
@@ -13,22 +13,22 @@ import type {CentralPaneNavigatorParamList, RootStackParamList, State} from './N
import * as TransactionUtils from './TransactionUtils';
import * as UserUtils from './UserUtils';
-type SortOrder = ValueOf;
-type SearchColumnType = ValueOf;
+type SortOrder = ValueOf;
+type SearchColumnType = ValueOf;
const columnNamesToSortingProperty = {
- [CONST.SEARCH_TABLE_COLUMNS.TO]: 'formattedTo' as const,
- [CONST.SEARCH_TABLE_COLUMNS.FROM]: 'formattedFrom' as const,
- [CONST.SEARCH_TABLE_COLUMNS.DATE]: 'date' as const,
- [CONST.SEARCH_TABLE_COLUMNS.TAG]: 'tag' as const,
- [CONST.SEARCH_TABLE_COLUMNS.MERCHANT]: 'formattedMerchant' as const,
- [CONST.SEARCH_TABLE_COLUMNS.TOTAL_AMOUNT]: 'formattedTotal' as const,
- [CONST.SEARCH_TABLE_COLUMNS.CATEGORY]: 'category' as const,
- [CONST.SEARCH_TABLE_COLUMNS.TYPE]: 'type' as const,
- [CONST.SEARCH_TABLE_COLUMNS.ACTION]: 'action' as const,
- [CONST.SEARCH_TABLE_COLUMNS.DESCRIPTION]: null,
- [CONST.SEARCH_TABLE_COLUMNS.TAX_AMOUNT]: null,
- [CONST.SEARCH_TABLE_COLUMNS.RECEIPT]: null,
+ [CONST.SEARCH.TABLE_COLUMNS.TO]: 'formattedTo' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.FROM]: 'formattedFrom' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.DATE]: 'date' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.TAG]: 'tag' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.MERCHANT]: 'formattedMerchant' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT]: 'formattedTotal' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.CATEGORY]: 'category' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.TYPE]: 'type' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.ACTION]: 'action' as const,
+ [CONST.SEARCH.TABLE_COLUMNS.DESCRIPTION]: null,
+ [CONST.SEARCH.TABLE_COLUMNS.TAX_AMOUNT]: null,
+ [CONST.SEARCH.TABLE_COLUMNS.RECEIPT]: null,
};
/**
@@ -58,7 +58,7 @@ function getTransactionItemCommonFormattedProperties(
}
function isSearchDataType(type: string): type is SearchDataTypes {
- const searchDataTypes: string[] = Object.values(CONST.SEARCH_DATA_TYPES);
+ const searchDataTypes: string[] = Object.values(CONST.SEARCH.DATA_TYPES);
return searchDataTypes.includes(type);
}
@@ -203,12 +203,12 @@ function getReportSections(data: OnyxTypes.SearchResults['data'], metadata: Onyx
}
const searchTypeToItemMap: SearchTypeToItemMap = {
- [CONST.SEARCH_DATA_TYPES.TRANSACTION]: {
+ [CONST.SEARCH.DATA_TYPES.TRANSACTION]: {
listItem: TransactionListItem,
getSections: getTransactionsSections,
getSortedSections: getSortedTransactionData,
},
- [CONST.SEARCH_DATA_TYPES.REPORT]: {
+ [CONST.SEARCH.DATA_TYPES.REPORT]: {
listItem: ReportListItem,
getSections: getReportSections,
// sorting for ReportItems not yet implemented
@@ -263,13 +263,13 @@ function getSortedTransactionData(data: TransactionListItemType[], sortBy?: Sear
// We are guaranteed that both a and b will be string or number at the same time
if (typeof aValue === 'string' && typeof bValue === 'string') {
- return sortOrder === CONST.SORT_ORDER.ASC ? aValue.toLowerCase().localeCompare(bValue) : bValue.toLowerCase().localeCompare(aValue);
+ return sortOrder === CONST.SEARCH.SORT_ORDER.ASC ? aValue.toLowerCase().localeCompare(bValue) : bValue.toLowerCase().localeCompare(aValue);
}
const aNum = aValue as number;
const bNum = bValue as number;
- return sortOrder === CONST.SORT_ORDER.ASC ? aNum - bNum : bNum - aNum;
+ return sortOrder === CONST.SEARCH.SORT_ORDER.ASC ? aNum - bNum : bNum - aNum;
});
}
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index e44643f391c1..c659bc15e02d 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -93,7 +93,7 @@ function getOrderedReportIDs(
const doesReportHaveViolations = !!(
betas?.includes(CONST.BETAS.VIOLATIONS) &&
!!parentReportAction &&
- ReportUtils.doesTransactionThreadHaveViolations(report, transactionViolations, parentReportAction as OnyxEntry)
+ ReportUtils.shouldDisplayTransactionThreadViolations(report, transactionViolations, parentReportAction as OnyxEntry)
);
const isHidden = report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
const isFocused = report.reportID === currentReportId;
@@ -352,6 +352,11 @@ function getOptionData({
: ` ${Localize.translate(preferredLocale, 'workspace.invite.from')}`;
result.alternateText += `${preposition} ${roomName}`;
}
+ if (lastActionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.UPDATE_ROOM_DESCRIPTION) {
+ result.alternateText = `${lastActorDisplayName} ${Localize.translate(preferredLocale, 'roomChangeLog.updateRoomDescription')} ${
+ lastActionOriginalMessage?.description
+ }`.trim();
+ }
} else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.LEAVE_POLICY) {
result.alternateText = Localize.translateLocal('workspace.invite.leftWorkspace');
} else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && lastActorDisplayName && lastMessageTextFromReport) {
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index bf37d90e82d5..bc4569bf4603 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -354,7 +354,7 @@ function initMoneyRequest(reportID: string, policy: OnyxEntry,
amount: 0,
comment,
created,
- currency: currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
+ currency: policy?.outputCurrency ?? currentUserPersonalDetails.localCurrencyCode ?? CONST.CURRENCY.USD,
iouRequestType,
reportID,
transactionID: newTransactionID,
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index 9e5cb78ae16b..a81dc08ba587 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -17,7 +17,9 @@ import type {
EnablePolicyWorkflowsParams,
LeavePolicyParams,
OpenDraftWorkspaceRequestParams,
+ OpenPolicyInitialPageParams,
OpenPolicyMoreFeaturesPageParams,
+ OpenPolicyProfilePageParams,
OpenPolicyTaxesPageParams,
OpenPolicyWorkflowsPageParams,
OpenWorkspaceInvitePageParams,
@@ -1336,8 +1338,8 @@ function generateCustomUnitID(): string {
return NumberUtils.generateHexadecimalValue(13);
}
-function buildOptimisticCustomUnits(): OptimisticCustomUnits {
- const currency = allPersonalDetails?.[sessionAccountID]?.localCurrencyCode ?? CONST.CURRENCY.USD;
+function buildOptimisticCustomUnits(reportCurrency?: string): OptimisticCustomUnits {
+ const currency = reportCurrency ?? allPersonalDetails?.[sessionAccountID]?.localCurrencyCode ?? CONST.CURRENCY.USD;
const customUnitID = generateCustomUnitID();
const customUnitRateID = generateCustomUnitID();
@@ -1981,7 +1983,7 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string
const workspaceName = generateDefaultWorkspaceName(sessionEmail);
const employeeAccountID = iouReport.ownerAccountID;
const employeeEmail = iouReport.ownerEmail ?? '';
- const {customUnits, customUnitID, customUnitRateID} = buildOptimisticCustomUnits();
+ const {customUnits, customUnitID, customUnitRateID} = buildOptimisticCustomUnits(iouReport.currency);
const oldPersonalPolicyID = iouReport.policyID;
const iouReportID = iouReport.reportID;
@@ -2770,6 +2772,18 @@ function openPolicyMoreFeaturesPage(policyID: string) {
API.read(READ_COMMANDS.OPEN_POLICY_MORE_FEATURES_PAGE, params);
}
+function openPolicyProfilePage(policyID: string) {
+ const params: OpenPolicyProfilePageParams = {policyID};
+
+ API.read(READ_COMMANDS.OPEN_POLICY_PROFILE_PAGE, params);
+}
+
+function openPolicyInitialPage(policyID: string) {
+ const params: OpenPolicyInitialPageParams = {policyID};
+
+ API.read(READ_COMMANDS.OPEN_POLICY_INITIAL_PAGE, params);
+}
+
function setPolicyCustomTaxName(policyID: string, customTaxName: string) {
const policy = getPolicy(policyID);
const originalCustomTaxName = policy?.taxRates?.name;
@@ -2973,6 +2987,8 @@ export {
enablePolicyWorkflows,
enableDistanceRequestTax,
openPolicyMoreFeaturesPage,
+ openPolicyProfilePage,
+ openPolicyInitialPage,
generateCustomUnitID,
clearQBOErrorField,
clearXeroErrorField,
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index f562b44201f1..d6fb70ee177a 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -978,9 +978,20 @@ function navigateToAndOpenReport(
if (isEmptyObject(chat)) {
if (isGroupChat) {
// If we are creating a group chat then participantAccountIDs is expected to contain currentUserAccountID
- newChat = ReportUtils.buildOptimisticGroupChatReport(participantAccountIDs, reportName ?? '', avatarUri ?? '', optimisticReportID);
+ newChat = ReportUtils.buildOptimisticGroupChatReport(participantAccountIDs, reportName ?? '', avatarUri ?? '', optimisticReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
} else {
- newChat = ReportUtils.buildOptimisticChatReport([...participantAccountIDs, currentUserAccountID]);
+ newChat = ReportUtils.buildOptimisticChatReport(
+ [...participantAccountIDs, currentUserAccountID],
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ undefined,
+ CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
+ );
}
}
const report = isEmptyObject(chat) ? newChat : chat;
diff --git a/src/libs/actions/TeachersUnite.ts b/src/libs/actions/TeachersUnite.ts
index e2f9ce251ef0..ba67e579e95a 100644
--- a/src/libs/actions/TeachersUnite.ts
+++ b/src/libs/actions/TeachersUnite.ts
@@ -5,6 +5,7 @@ import type {AddSchoolPrincipalParams, ReferTeachersUniteVolunteerParams} from '
import {WRITE_COMMANDS} from '@libs/API/types';
import Navigation from '@libs/Navigation/Navigation';
import * as PhoneNumber from '@libs/PhoneNumber';
+import {getPolicy} from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import type {OptimisticCreatedReportAction} from '@libs/ReportUtils';
import CONST from '@src/CONST';
@@ -95,7 +96,7 @@ function addSchoolPrincipal(firstName: string, partnerUserID: string, lastName:
name: policyName,
role: CONST.POLICY.ROLE.USER,
owner: sessionEmail,
- outputCurrency: allPersonalDetails?.[sessionAccountID]?.localCurrencyCode ?? CONST.CURRENCY.USD,
+ outputCurrency: getPolicy(policyID)?.outputCurrency ?? allPersonalDetails?.[sessionAccountID]?.localCurrencyCode ?? CONST.CURRENCY.USD,
employeeList: {
[sessionEmail]: {
role: CONST.POLICY.ROLE.USER,
diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
index d6449bc2cf44..ba90def232d5 100644
--- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
+++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx
@@ -70,12 +70,12 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat
// Only navigate to concierge chat when central pane is visible
// Otherwise stay on the chats screen.
- if (isSmallScreenWidth) {
- Navigation.navigate(ROUTES.HOME);
- } else if (AccountUtils.isAccountIDOddNumber(accountID ?? 0)) {
- Report.navigateToSystemChat();
- } else {
- Report.navigateToConciergeChat();
+ if (!isSmallScreenWidth) {
+ if (AccountUtils.isAccountIDOddNumber(accountID ?? 0)) {
+ Report.navigateToSystemChat();
+ } else {
+ Report.navigateToConciergeChat();
+ }
}
// Small delay purely due to design considerations,
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index b680e99c1caa..7850098cfdb2 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -79,8 +79,8 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
const {translate} = useLocalize();
const {isOffline} = useNetwork();
const styles = useThemeStyles();
- const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID ?? ''}`);
- const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID ?? ''}`, {
+ const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report.parentReportID ?? '-1'}`);
+ const [sortedAllReportActions = []] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID ?? '-1'}`, {
canEvict: false,
selector: (allReportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true),
});
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx
new file mode 100644
index 000000000000..efc9c36de51a
--- /dev/null
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceAdminRestrictedAction.tsx
@@ -0,0 +1,74 @@
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ImageSVG from '@components/ImageSVG';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as Report from '@libs/actions/Report';
+import Navigation from '@libs/Navigation/Navigation';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import variables from '@styles/variables';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+
+type WorkspaceAdminRestrictedActionProps = {
+ policyID: string;
+};
+
+function WorkspaceAdminRestrictedAction({policyID}: WorkspaceAdminRestrictedActionProps) {
+ const {translate} = useLocalize();
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
+ const styles = useThemeStyles();
+
+ const openAdminsReport = useCallback(() => {
+ const reportID = `${PolicyUtils.getPolicy(policyID)?.chatReportIDAdmins}` ?? '-1';
+ Report.openReport(reportID);
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
+ }, [policyID]);
+
+ return (
+
+
+
+
+
+
+ {translate('workspace.restrictedAction.actionsAreCurrentlyRestricted', {workspaceName: policy?.name})}
+
+
+ {translate('workspace.restrictedAction.workspaceOwnerWillNeedToAddOrUpdatePaymentCard', {workspaceOwnerName: policy?.owner})}
+
+
+
+
+
+ );
+}
+
+WorkspaceAdminRestrictedAction.displayName = 'WorkspaceAdminRestrictedAction';
+
+export default WorkspaceAdminRestrictedAction;
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction.tsx
new file mode 100644
index 000000000000..fd6d0fc717b8
--- /dev/null
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceOwnerRestrictedAction.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import {View} from 'react-native';
+import Badge from '@components/Badge';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import Icon from '@components/Icon';
+import * as Expensicons from '@components/Icon/Expensicons';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
+import variables from '@styles/variables';
+import ROUTES from '@src/ROUTES';
+
+function WorkspaceOwnerRestrictedAction() {
+ const {translate} = useLocalize();
+ const styles = useThemeStyles();
+
+ const addPaymentCard = () => {
+ Navigation.navigate(ROUTES.SETTINGS_SUBSCRIPTION);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {translate('workspace.restrictedAction.addPaymentCardToContinueUsingWorkspace')}
+ {translate('workspace.restrictedAction.youWillNeedToAddOrUpdatePaymentCard')}
+
+
+
+
+ );
+}
+
+WorkspaceOwnerRestrictedAction.displayName = 'WorkspaceOwnerRestrictedAction';
+
+export default WorkspaceOwnerRestrictedAction;
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage.tsx
new file mode 100644
index 000000000000..42b1e64d8282
--- /dev/null
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceRestrictedActionPage.tsx
@@ -0,0 +1,43 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React from 'react';
+import {useOnyx} from 'react-native-onyx';
+import type {RestrictedActionParamList} from '@libs/Navigation/types';
+import * as PolicyUtils from '@libs/PolicyUtils';
+import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
+import ONYXKEYS from '@src/ONYXKEYS';
+import type SCREENS from '@src/SCREENS';
+import WorkspaceAdminRestrictedAction from './WorkspaceAdminRestrictedAction';
+import WorkspaceOwnerRestrictedAction from './WorkspaceOwnerRestrictedAction';
+import WorkspaceUserRestrictedAction from './WorkspaceUserRestrictedAction';
+
+type WorkspaceRestrictedActionPageProps = StackScreenProps;
+
+function WorkspaceRestrictedActionPage({
+ route: {
+ params: {policyID},
+ },
+}: WorkspaceRestrictedActionPageProps) {
+ const [session] = useOnyx(ONYXKEYS.SESSION);
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
+
+ // Workspace Owner
+ if (PolicyUtils.isPolicyOwner(policy, session?.accountID ?? -1)) {
+ return ;
+ }
+
+ // Workspace Admin
+ if (PolicyUtils.isPolicyAdmin(policy, session?.email)) {
+ return ;
+ }
+
+ // Workspace User
+ if (PolicyUtils.isPolicyUser(policy, session?.email)) {
+ return ;
+ }
+
+ return ;
+}
+
+WorkspaceRestrictedActionPage.displayName = 'WorkspaceRestrictedActionPage';
+
+export default WorkspaceRestrictedActionPage;
diff --git a/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx
new file mode 100644
index 000000000000..33c159446398
--- /dev/null
+++ b/src/pages/RestrictedAction/Workspace/WorkspaceUserRestrictedAction.tsx
@@ -0,0 +1,74 @@
+import React, {useCallback} from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import Button from '@components/Button';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import * as Illustrations from '@components/Icon/Illustrations';
+import ImageSVG from '@components/ImageSVG';
+import ScreenWrapper from '@components/ScreenWrapper';
+import ScrollView from '@components/ScrollView';
+import Text from '@components/Text';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as Report from '@libs/actions/Report';
+import Navigation from '@libs/Navigation/Navigation';
+import * as ReportUtils from '@libs/ReportUtils';
+import variables from '@styles/variables';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+
+type WorkspaceUserRestrictedActionProps = {
+ policyID: string;
+};
+
+function WorkspaceUserRestrictedAction({policyID}: WorkspaceUserRestrictedActionProps) {
+ const {translate} = useLocalize();
+ const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
+ const styles = useThemeStyles();
+
+ const openPolicyExpenseReport = useCallback(() => {
+ const reportID = ReportUtils.findPolicyExpenseChatByPolicyID(policyID)?.reportID ?? '-1';
+ Report.openReport(reportID);
+ Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID));
+ }, [policyID]);
+
+ return (
+
+
+
+
+
+
+ {translate('workspace.restrictedAction.actionsAreCurrentlyRestricted', {workspaceName: policy?.name})}
+
+
+ {translate('workspace.restrictedAction.pleaseReachOutToYourWorkspaceAdmin')}
+
+
+
+
+
+ );
+}
+
+WorkspaceUserRestrictedAction.displayName = 'WorkspaceUserRestrictedAction';
+
+export default WorkspaceUserRestrictedAction;
diff --git a/src/pages/Search/SearchFilters.tsx b/src/pages/Search/SearchFilters.tsx
index 6026a4570a91..bbb861364f00 100644
--- a/src/pages/Search/SearchFilters.tsx
+++ b/src/pages/Search/SearchFilters.tsx
@@ -34,27 +34,27 @@ function SearchFilters({query}: SearchFiltersProps) {
const filterItems: SearchMenuFilterItem[] = [
{
title: translate('common.expenses'),
- query: CONST.TAB_SEARCH.ALL,
+ query: CONST.SEARCH.TAB.ALL,
icon: Expensicons.Receipt,
- route: ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL),
+ route: ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL),
},
{
title: translate('common.shared'),
- query: CONST.TAB_SEARCH.SHARED,
+ query: CONST.SEARCH.TAB.SHARED,
icon: Expensicons.Send,
- route: ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.SHARED),
+ route: ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.SHARED),
},
{
title: translate('common.drafts'),
- query: CONST.TAB_SEARCH.DRAFTS,
+ query: CONST.SEARCH.TAB.DRAFTS,
icon: Expensicons.Pencil,
- route: ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.DRAFTS),
+ route: ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.DRAFTS),
},
{
title: translate('common.finished'),
- query: CONST.TAB_SEARCH.FINISHED,
+ query: CONST.SEARCH.TAB.FINISHED,
icon: Expensicons.CheckCircle,
- route: ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.FINISHED),
+ route: ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.FINISHED),
},
];
const activeItemIndex = filterItems.findIndex((item) => item.query === query);
diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx
index 1f7db3eeb2c5..771bb85d327c 100644
--- a/src/pages/Search/SearchPage.tsx
+++ b/src/pages/Search/SearchPage.tsx
@@ -26,7 +26,7 @@ function SearchPage({route}: SearchPageProps) {
const {query: rawQuery, policyIDs, sortBy, sortOrder} = route?.params ?? {};
const query = rawQuery as SearchQuery;
- const isValidQuery = Object.values(CONST.TAB_SEARCH).includes(query);
+ const isValidQuery = Object.values(CONST.SEARCH.TAB).includes(query);
const headerContent: {[key in SearchQuery]: {icon: IconAsset; title: string}} = {
all: {icon: Illustrations.MoneyReceipts, title: translate('common.expenses')},
@@ -35,7 +35,7 @@ function SearchPage({route}: SearchPageProps) {
finished: {icon: Illustrations.CheckmarkCircle, title: translate('common.finished')},
};
- const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL));
+ const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL));
// On small screens this page is not displayed, the configuration is in the file: src/libs/Navigation/AppNavigator/createCustomStackNavigator/index.tsx
// To avoid calling hooks in the Search component when this page isn't visible, we return null here.
diff --git a/src/pages/Search/SearchPageBottomTab.tsx b/src/pages/Search/SearchPageBottomTab.tsx
index c6baf7c5b008..a0a3c9969247 100644
--- a/src/pages/Search/SearchPageBottomTab.tsx
+++ b/src/pages/Search/SearchPageBottomTab.tsx
@@ -21,8 +21,8 @@ type SearchPageProps = StackScreenProps Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.TAB_SEARCH.ALL));
+ const handleOnBackButtonPress = () => Navigation.goBack(ROUTES.SEARCH.getRoute(CONST.SEARCH.TAB.ALL));
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index 6f02e4b1c8fd..a4841106440f 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -50,7 +50,6 @@ import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import HeaderView from './HeaderView';
-import getInitialPaginationSize from './report/getInitialPaginationSize';
import ReportActionsView from './report/ReportActionsView';
import ReportFooter from './report/ReportFooter';
import type {ActionListContextType, ReactionListRef, ScrollPosition} from './ReportScreenContext';
@@ -288,18 +287,10 @@ function ReportScreen({
const screenWrapperStyle: ViewStyle[] = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}];
const isEmptyChat = useMemo(() => ReportUtils.isEmptyReport(report), [report]);
const isOptimisticDelete = report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED;
- const indexOfLinkedMessage = useMemo(
- (): number => sortedAllReportActions.findIndex((obj) => String(obj.reportActionID) === String(reportActionIDFromRoute)),
+ const isLinkedMessageAvailable = useMemo(
+ (): boolean => sortedAllReportActions.findIndex((obj) => String(obj.reportActionID) === String(reportActionIDFromRoute)) > -1,
[sortedAllReportActions, reportActionIDFromRoute],
);
- const doesCreatedActionExists = ReportActionsUtils.isCreatedAction(reportActions.at(-1));
- const isLinkedMessageAvailable = useMemo(() => indexOfLinkedMessage > -1, [indexOfLinkedMessage]);
- const paginationSize = getInitialPaginationSize;
-
- // The linked report actions should have at least minimum amount (15) messages (count as 1 page) above it, to fill the screen.
- // If it is too much (same as web pagination size/50) and there is no cached messages on the report,
- // the OpenReport will be called each time user scroll up report a bit, click on reportpreview then go back
- const isLinkedMessagePageReady = isLinkedMessageAvailable && (sortedAllReportActions.length - indexOfLinkedMessage > 15 || doesCreatedActionExists);
// If there's a non-404 error for the report we should show it instead of blocking the screen
const hasHelpfulErrors = Object.keys(report?.errorFields ?? {}).some((key) => key !== 'notFound');
@@ -391,11 +382,13 @@ function ReportScreen({
const isLoading = isLoadingApp ?? (!reportIDFromRoute || (!isSidebarLoaded && !isReportOpenInRHP) || PersonalDetailsUtils.isPersonalDetailsEmpty());
const shouldShowSkeleton =
- (isLinkingToMessage && !isLinkedMessagePageReady) ||
- !isCurrentReportLoadedFromOnyx ||
- (reportActions.length < paginationSize && !doesCreatedActionExists && !!reportMetadata?.isLoadingInitialReportActions) ||
- isLoading;
-
+ !isLinkedMessageAvailable &&
+ (isLinkingToMessage ||
+ !isCurrentReportLoadedFromOnyx ||
+ (reportActions.length === 0 && !!reportMetadata?.isLoadingInitialReportActions) ||
+ isLoading ||
+ (!!reportActionIDFromRoute && reportMetadata?.isLoadingInitialReportActions));
+ const shouldShowReportActionList = isCurrentReportLoadedFromOnyx && !isLoading;
const currentReportIDFormRoute = route.params?.reportID;
// eslint-disable-next-line rulesdir/no-negated-variables
@@ -520,18 +513,6 @@ function ReportScreen({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoadingReportOnyx]);
- useEffect(() => {
- if (isLoadingReportOnyx || !reportActionIDFromRoute || isLinkedMessagePageReady) {
- return;
- }
-
- // This function is triggered when a user clicks on a link to navigate to a report.
- // For each link click, we retrieve the report data again, even though it may already be cached.
- // There should be only one openReport execution per page start or navigating
- fetchReportIfNeeded();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [route, isLinkedMessagePageReady]);
-
// If a user has chosen to leave a thread, and then returns to it (e.g. with the back button), we need to call `openReport` again in order to allow the user to rejoin and to receive real-time updates
useEffect(() => {
if (!shouldUseNarrowLayout || !isFocused || prevIsFocused || !ReportUtils.isChatThread(report) || report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) {
@@ -764,7 +745,7 @@ function ReportScreen({
style={[styles.flex1, styles.justifyContentEnd, styles.overflowHidden]}
onLayout={onListLayout}
>
- {!shouldShowSkeleton && (
+ {shouldShowReportActionList && (
;
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MERGED_WITH_CASH_TRANSACTION) {
children = ;
+ } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.ROOM_CHANGE_LOG.UPDATE_ROOM_DESCRIPTION) {
+ const message = `${translate('roomChangeLog.updateRoomDescription')} ${(originalMessage as OriginalMessageChangeLog)?.description}`;
+ children = ;
} else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.DISMISSED_VIOLATION)) {
children = ;
} else {
diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx
index 3f4a3ab726ce..976b7ed7c96f 100755
--- a/src/pages/home/report/ReportActionsView.tsx
+++ b/src/pages/home/report/ReportActionsView.tsx
@@ -251,6 +251,18 @@ function ReportActionsView({
const oldestReportAction = useMemo(() => reportActions?.at(-1), [reportActions]);
const hasCreatedAction = oldestReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED;
+ useEffect(() => {
+ if (!reportActionID || indexOfLinkedAction > -1) {
+ return;
+ }
+
+ // This function is triggered when a user clicks on a link to navigate to a report.
+ // For each link click, we retrieve the report data again, even though it may already be cached.
+ // There should be only one openReport execution per page start or navigating
+ Report.openReport(reportID, reportActionID);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [route, indexOfLinkedAction]);
+
useEffect(() => {
const wasLoginChangedDetected = prevAuthTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS && !session?.authTokenType;
if (wasLoginChangedDetected && didUserLogInDuringSession() && isUserCreatedPolicyRoom(report)) {
diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx
index bff2d322120b..2fc13af7040c 100644
--- a/src/pages/home/report/ReportFooter.tsx
+++ b/src/pages/home/report/ReportFooter.tsx
@@ -84,7 +84,7 @@ function ReportFooter({
const {windowWidth, isSmallScreenWidth} = useWindowDimensions();
const [shouldShowComposeInput] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT, {initialValue: false});
- const [isAnonymousUser] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS});
+ const [isAnonymousUser = false] = useOnyx(ONYXKEYS.SESSION, {selector: (session) => session?.authTokenType === CONST.AUTH_TOKEN_TYPES.ANONYMOUS});
const [isBlockedFromChat] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CHAT, {
selector: (dateString) => {
if (!dateString) {
diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx
index 07dfdeb7c0d7..6715bf21c911 100644
--- a/src/pages/workspace/WorkspaceInitialPage.tsx
+++ b/src/pages/workspace/WorkspaceInitialPage.tsx
@@ -1,4 +1,4 @@
-import {useNavigationState} from '@react-navigation/native';
+import {useFocusEffect, useNavigationState} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
@@ -86,7 +86,7 @@ function dismissError(policyID: string, pendingAction: PendingAction | undefined
}
}
-function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAccount, policyCategories}: WorkspaceInitialPageProps) {
+function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAccount, policyCategories, route}: WorkspaceInitialPageProps) {
const styles = useThemeStyles();
const policy = policyDraft?.id ? policyDraft : policyProp;
const [isCurrencyModalOpen, setIsCurrencyModalOpen] = useState(false);
@@ -133,6 +133,18 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc
setIsCurrencyModalOpen(false);
}, [policy?.outputCurrency, isCurrencyModalOpen]);
+ const fetchPolicyData = useCallback(() => {
+ Policy.openPolicyInitialPage(route.params.policyID);
+ }, [route.params.policyID]);
+
+ useNetwork({onReconnect: fetchPolicyData});
+
+ useFocusEffect(
+ useCallback(() => {
+ fetchPolicyData();
+ }, [fetchPolicyData]),
+ );
+
/** Call update workspace currency and hide the modal */
const confirmCurrencyChangeAndHideModal = useCallback(() => {
Policy.updateGeneralSettings(policyID, policyName, CONST.CURRENCY.USD);
diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx
index 29875b0da811..35cbf4db3581 100644
--- a/src/pages/workspace/WorkspaceInvitePage.tsx
+++ b/src/pages/workspace/WorkspaceInvitePage.tsx
@@ -95,6 +95,10 @@ function WorkspaceInvitePage({route, betas, invitedEmailsToAccountIDsDraft, poli
const excludedUsers = useMemo(() => PolicyUtils.getIneligibleInvitees(policy?.employeeList), [policy?.employeeList]);
useEffect(() => {
+ if (!areOptionsInitialized) {
+ return;
+ }
+
const newUsersToInviteDict: Record = {};
const newPersonalDetailsDict: Record = {};
const newSelectedOptionsDict: Record = {};
@@ -154,7 +158,7 @@ function WorkspaceInvitePage({route, betas, invitedEmailsToAccountIDsDraft, poli
setSelectedOptions(Object.values(newSelectedOptionsDict));
// eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change
- }, [options.personalDetails, policy?.employeeList, betas, debouncedSearchTerm, excludedUsers]);
+ }, [options.personalDetails, policy?.employeeList, betas, debouncedSearchTerm, excludedUsers, areOptionsInitialized]);
const sections: MembersSection[] = useMemo(() => {
const sectionsArr: MembersSection[] = [];
diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx
index 6a86a83b5d11..6f2097688b5c 100644
--- a/src/pages/workspace/WorkspaceProfilePage.tsx
+++ b/src/pages/workspace/WorkspaceProfilePage.tsx
@@ -1,3 +1,5 @@
+import {useFocusEffect} from '@react-navigation/native';
+import type {StackScreenProps} from '@react-navigation/stack';
import {ExpensiMark} from 'expensify-common';
import React, {useCallback, useState} from 'react';
import type {ImageStyle, StyleProp} from 'react-native';
@@ -15,12 +17,14 @@ import OfflineWithFeedback from '@components/OfflineWithFeedback';
import Section from '@components/Section';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useLocalize from '@hooks/useLocalize';
+import useNetwork from '@hooks/useNetwork';
import usePermissions from '@hooks/usePermissions';
import useThemeIllustrations from '@hooks/useThemeIllustrations';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
+import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import StringUtils from '@libs/StringUtils';
@@ -29,22 +33,23 @@ import * as Policy from '@userActions/Policy/Policy';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import withPolicy from './withPolicy';
import type {WithPolicyProps} from './withPolicy';
import WorkspacePageWithSections from './WorkspacePageWithSections';
-type WorkSpaceProfilePageOnyxProps = {
+type WorkspaceProfilePageOnyxProps = {
/** Constant, list of available currencies */
currencyList: OnyxEntry;
};
-type WorkSpaceProfilePageProps = WithPolicyProps & WorkSpaceProfilePageOnyxProps;
+type WorkspaceProfilePageProps = WithPolicyProps & WorkspaceProfilePageOnyxProps & StackScreenProps;
const parser = new ExpensiMark();
-function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfilePageProps) {
+function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkspaceProfilePageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isSmallScreenWidth} = useWindowDimensions();
@@ -82,6 +87,20 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi
const imageStyle: StyleProp = isSmallScreenWidth ? [styles.mhv12, styles.mhn5, styles.mbn5] : [styles.mhv8, styles.mhn8, styles.mbn5];
const shouldShowAddress = !readOnly || formattedAddress;
+ const fetchPolicyData = useCallback(() => {
+ Policy.openPolicyProfilePage(route.params.policyID);
+ }, [route.params.policyID]);
+
+ useNetwork({onReconnect: fetchPolicyData});
+
+ // We have the same focus effect in the WorkspaceInitialPage, this way we can get the policy data in narrow
+ // as well as in the wide layout when looking at policy settings.
+ useFocusEffect(
+ useCallback(() => {
+ fetchPolicyData();
+ }, [fetchPolicyData]),
+ );
+
const DefaultAvatar = useCallback(
() => (
({
+ withOnyx({
currencyList: {key: ONYXKEYS.CURRENCY_LIST},
})(WorkspaceProfilePage),
);
diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx
index ccc5fe2c5a25..1b8f35e4d483 100644
--- a/src/pages/workspace/withPolicy.tsx
+++ b/src/pages/workspace/withPolicy.tsx
@@ -22,6 +22,7 @@ type PolicyRoute = RouteProp<
NavigatorsParamList,
| typeof SCREENS.REIMBURSEMENT_ACCOUNT_ROOT
| typeof SCREENS.WORKSPACE.INITIAL
+ | typeof SCREENS.WORKSPACE.PROFILE
| typeof SCREENS.WORKSPACE.BILLS
| typeof SCREENS.WORKSPACE.MORE_FEATURES
| typeof SCREENS.WORKSPACE.MEMBERS
diff --git a/src/setup/platformSetup/index.website.ts b/src/setup/platformSetup/index.website.ts
index 0fef333e6508..77c373957510 100644
--- a/src/setup/platformSetup/index.website.ts
+++ b/src/setup/platformSetup/index.website.ts
@@ -1,6 +1,4 @@
import {AppRegistry} from 'react-native';
-// This is a polyfill for InternetExplorer to support the modern KeyboardEvent.key and KeyboardEvent.code instead of KeyboardEvent.keyCode
-import 'shim-keyboard-event-key';
import checkForUpdates from '@libs/checkForUpdates';
import DateUtils from '@libs/DateUtils';
import Visibility from '@libs/Visibility';
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index d2220b213846..f6f24404d928 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -1547,29 +1547,29 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({
getSearchTableColumnStyles: (columnName: string, shouldExtendDateColumn = false): ViewStyle => {
let columnWidth;
switch (columnName) {
- case CONST.SEARCH_TABLE_COLUMNS.RECEIPT:
+ case CONST.SEARCH.TABLE_COLUMNS.RECEIPT:
columnWidth = {...getWidthStyle(variables.w36), ...styles.alignItemsCenter};
break;
- case CONST.SEARCH_TABLE_COLUMNS.DATE:
- columnWidth = getWidthStyle(shouldExtendDateColumn ? variables.w84 : variables.w52);
+ case CONST.SEARCH.TABLE_COLUMNS.DATE:
+ columnWidth = getWidthStyle(shouldExtendDateColumn ? variables.w92 : variables.w52);
break;
- case CONST.SEARCH_TABLE_COLUMNS.MERCHANT:
- case CONST.SEARCH_TABLE_COLUMNS.FROM:
- case CONST.SEARCH_TABLE_COLUMNS.TO:
+ case CONST.SEARCH.TABLE_COLUMNS.MERCHANT:
+ case CONST.SEARCH.TABLE_COLUMNS.FROM:
+ case CONST.SEARCH.TABLE_COLUMNS.TO:
columnWidth = styles.flex1;
break;
- case CONST.SEARCH_TABLE_COLUMNS.CATEGORY:
- case CONST.SEARCH_TABLE_COLUMNS.TAG:
+ case CONST.SEARCH.TABLE_COLUMNS.CATEGORY:
+ case CONST.SEARCH.TABLE_COLUMNS.TAG:
columnWidth = {...getWidthStyle(variables.w36), ...styles.flex1};
break;
- case CONST.SEARCH_TABLE_COLUMNS.TAX_AMOUNT:
- case CONST.SEARCH_TABLE_COLUMNS.TOTAL_AMOUNT:
+ case CONST.SEARCH.TABLE_COLUMNS.TAX_AMOUNT:
+ case CONST.SEARCH.TABLE_COLUMNS.TOTAL_AMOUNT:
columnWidth = {...getWidthStyle(variables.w96), ...styles.alignItemsEnd};
break;
- case CONST.SEARCH_TABLE_COLUMNS.TYPE:
+ case CONST.SEARCH.TABLE_COLUMNS.TYPE:
columnWidth = {...getWidthStyle(variables.w20), ...styles.alignItemsCenter};
break;
- case CONST.SEARCH_TABLE_COLUMNS.ACTION:
+ case CONST.SEARCH.TABLE_COLUMNS.ACTION:
columnWidth = {...getWidthStyle(variables.w80), ...styles.alignItemsCenter};
break;
default:
diff --git a/src/styles/variables.ts b/src/styles/variables.ts
index 4bf981437135..954321315da8 100644
--- a/src/styles/variables.ts
+++ b/src/styles/variables.ts
@@ -207,6 +207,7 @@ export default {
oldDotWireframeIconWidth: 263.38,
oldDotWireframeIconHeight: 143.28,
sectionIllustrationHeight: 220,
+ restrictedActionIllustrationHeight: 136,
photoUploadPopoverWidth: 335,
onboardingModalWidth: 500,
welcomeVideoDelay: 1000,
@@ -239,14 +240,17 @@ export default {
eReceiptBackgroundImageMinWidth: 217,
searchTypeColumnWidth: 52,
- w20: 20,
+
+ h20: 20,
+ h28: 28,
h36: 36,
+ w20: 20,
w28: 28,
w36: 36,
w40: 40,
w44: 44,
w52: 52,
w80: 80,
- w84: 84,
+ w92: 92,
w96: 96,
} as const;
diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts
index c700dca53f34..edeeeab551b0 100644
--- a/src/types/onyx/OriginalMessage.ts
+++ b/src/types/onyx/OriginalMessage.ts
@@ -236,6 +236,9 @@ type OriginalMessageChangeLog = {
/** Name of the chat room */
roomName?: string;
+ /** Description of the chat room */
+ description?: string;
+
/** ID of the report */
reportID?: number;
};
diff --git a/src/types/onyx/SearchResults.ts b/src/types/onyx/SearchResults.ts
index 676a1d3873d9..c13cd89a0619 100644
--- a/src/types/onyx/SearchResults.ts
+++ b/src/types/onyx/SearchResults.ts
@@ -6,19 +6,19 @@ import type {SearchColumnType, SortOrder} from '@libs/SearchUtils';
import type CONST from '@src/CONST';
/** Types of search data */
-type SearchDataTypes = ValueOf;
+type SearchDataTypes = ValueOf;
/** Model of search result list item */
-type ListItemType = T extends typeof CONST.SEARCH_DATA_TYPES.TRANSACTION
+type ListItemType = T extends typeof CONST.SEARCH.DATA_TYPES.TRANSACTION
? typeof TransactionListItem
- : T extends typeof CONST.SEARCH_DATA_TYPES.REPORT
+ : T extends typeof CONST.SEARCH.DATA_TYPES.REPORT
? typeof ReportListItem
: never;
/** Model of search result section */
-type SectionsType = T extends typeof CONST.SEARCH_DATA_TYPES.TRANSACTION
+type SectionsType = T extends typeof CONST.SEARCH.DATA_TYPES.TRANSACTION
? TransactionListItemType[]
- : T extends typeof CONST.SEARCH_DATA_TYPES.REPORT
+ : T extends typeof CONST.SEARCH.DATA_TYPES.REPORT
? ReportListItemType[]
: never;
@@ -162,7 +162,7 @@ type SearchTransaction = {
category: string;
/** The type of request */
- type: ValueOf;
+ type: ValueOf;
/** The type of report the transaction is associated with */
reportType: string;
@@ -214,10 +214,10 @@ type SearchTransaction = {
type SearchAccountDetails = Partial;
/** Types of searchable transactions */
-type SearchTransactionType = ValueOf;
+type SearchTransactionType = ValueOf;
/** Types of search queries */
-type SearchQuery = ValueOf;
+type SearchQuery = ValueOf;
/** Model of search results */
type SearchResults = {
diff --git a/tests/perf-test/ReportScreen.perf-test.tsx b/tests/perf-test/ReportScreen.perf-test.tsx
index 6a2708da0bb9..fb011f9a1144 100644
--- a/tests/perf-test/ReportScreen.perf-test.tsx
+++ b/tests/perf-test/ReportScreen.perf-test.tsx
@@ -209,7 +209,7 @@ function ReportScreenWrapper(props: ReportScreenWrapperProps) {
}
const report = {...createRandomReport(1), policyID: '1'};
-const reportActions = ReportTestUtils.getMockedReportActionsMap(10);
+const reportActions = ReportTestUtils.getMockedReportActionsMap(1000);
const mockRoute = {params: {reportID: '1', reportActionID: ''}, key: 'Report', name: 'Report' as const};
test('[ReportScreen] should render ReportScreen', async () => {
@@ -305,6 +305,10 @@ test('[ReportScreen] should render report list', async () => {
[ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS],
[`${ONYXKEYS.COLLECTION.POLICY}`]: policies,
[ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT]: true,
+ [ONYXKEYS.IS_LOADING_APP]: false,
+ [`${ONYXKEYS.COLLECTION.REPORT_METADATA}${mockRoute.params.reportID}`]: {
+ isLoadingInitialReportActions: false,
+ },
...reportCollectionDataSet,
...reportActionsCollectionDataSet,
});
diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts
index a08b43c337cb..4a1171658f4d 100644
--- a/tests/unit/OptionsListUtilsTest.ts
+++ b/tests/unit/OptionsListUtilsTest.ts
@@ -2938,6 +2938,15 @@ describe('OptionsListUtils', () => {
expect(filteredOptions.recentReports.length).toBe(2);
});
+
+ it('should not return any user to invite if email exists on the personal details list', () => {
+ const searchText = 'natasharomanoff@expensify.com';
+ const options = OptionsListUtils.getSearchOptions(OPTIONS, '', [CONST.BETAS.ALL]);
+
+ const filteredOptions = OptionsListUtils.filterOptions(options, searchText);
+ expect(filteredOptions.personalDetails.length).toBe(1);
+ expect(filteredOptions.userToInvite).toBe(null);
+ });
});
describe('canCreateOptimisticPersonalDetailOption', () => {