diff --git a/android/app/build.gradle b/android/app/build.gradle
index a53c6dfb62af..c8a907417066 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
- versionCode 1001043600
- versionName "1.4.36-0"
+ versionCode 1001043602
+ versionName "1.4.36-2"
}
flavorDimensions "default"
diff --git a/docs/articles/expensify-classic/getting-started/Individual-Users.md b/docs/articles/expensify-classic/getting-started/Individual-Users.md
deleted file mode 100644
index 12029f80388b..000000000000
--- a/docs/articles/expensify-classic/getting-started/Individual-Users.md
+++ /dev/null
@@ -1,43 +0,0 @@
----
-title: Individual Users
-description: Learn how Expensify can help you track and submit your personal or self-employed business expenses.
----
-# Overview
-If you're an individual using Expensify, the Track and Submit plans are designed to assist self-employed users in effectively managing both their personal and business finances.
-
-# How to use the Track plan
-
-The Track plan is tailored for solo Expensify users who don't require expense submission to others. Individuals or sole proprietors can choose the Track plan to customize their Individual Workspace to align with their personal expense tracking requirements.
-
-You can select the Track plan from the Workspace settings. Navigate to **Settings > Workspace > Individual > *[Workspace Name]* > Plan** to select Track.
-You can also do this from the Pricing page at https://www.expensify.com/pricing.
-
-The Track plan includes a predefined set of categories designed to align with IRS Schedule C expense categories. However, you have the flexibility to add extra categories as needed. For a more detailed breakdown, you can also set up tags to create another layer of coding.
-
-The Track plan offers 25 free SmartScans per month. If you require more than 25 SmartScans, you can upgrade to a Monthly Individual subscription at a cost of $4.99 USD per month.
-
-# How to use the Submit plan
-The Submit plan is designed for individuals who need to keep track of their expenses and share them with someone else, such as their boss, accountant, or even a housemate. It's specifically tailored for single users who want to both track and submit their expenses efficiently.
-
-You can select the Track plan from the Workspace settings. Navigate to **Settings > Workspaces > Individual > *[Workspace Name]* > Plan** to select "Submit" or from the Pricing page at https://www.expensify.com/pricing.
-
-You will select who your expenses get sent to under **Settings > Workspace > Individual > *[Workspace Name]* > Reports**. If the recipient already has an Expensify account, they'll be able to see the report directly in the Expensify app. Otherwise, non-Expensify users will receive a PDF copy of the report attached to the email so it can be processed.
-
-The Submit plan includes a predefined set of categories designed to align with IRS Schedule C expense categories. However, you have the flexibility to add extra categories as needed. For a more detailed breakdown, you can also set up tags to create another layer of coding.
-
-The Submit plan offers 25 free SmartScans per month.If you require more than 25 SmartScans, you can upgrade to a Monthly Individual subscription at a cost of $4.99 USD per month.
-
-# FAQ
-
-## Who should use the Track plan?
-An individual who wants to store receipts, look to track spending by category to help with budgeting and a self-employed user who needs to track receipts and mileage for tax purposes.
-
-## Who should use the Submit plan?
-An individual who seeks to utilize the features of the track plan to monitor their expenses while also requiring the ability to submit those expenses to someone else.
-
-## How can I keep track of personal and business expenses in the same account?
-You have the capability to create distinct "business" and "personal" tags and assign them to your expenses for proper categorization. By doing so, you can effectively code your expenses based on their nature. Additionally, you can utilize filters to ensure that you only view the expenses that are relevant to your specific needs, whether they are business-related or personal.
-
-## How can I export expenses for tax purposes?
-From the expense page, you have the option to select all of your expenses and export them to a CSV (Comma-Separated Values) file. This CSV file can be conveniently imported directly into your tax software for easier tax preparation.
-
diff --git a/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md b/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md
index 099f381e6010..a31b972e683c 100644
--- a/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md
+++ b/docs/articles/expensify-classic/getting-started/Join-your-company's-workspace.md
@@ -212,7 +212,6 @@ Once you’ve created your expenses, they may be automatically added to an expen
Attach PDF: Select this checkbox to attach a copy of your report to the email.
Tap Submit.
-
{% include end-option.html %}
@@ -255,4 +254,4 @@ Add an extra layer of security to help keep your financial data safe and secure
When you log in to Expensify in the future, you’ll open your authenticator app to get the code and enter it into Expensify. A new code regenerates every few seconds, so the code is always different. If the code time runs out, you can generate a new code as needed.
-
\ No newline at end of file
+
diff --git a/docs/redirects.csv b/docs/redirects.csv
index df615431533f..988b07d729f0 100644
--- a/docs/redirects.csv
+++ b/docs/redirects.csv
@@ -49,3 +49,4 @@ https://community.expensify.com/discussion/5793/how-to-connect-your-personal-car
https://community.expensify.com/discussion/4826/how-to-set-your-annual-subscription-size,https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription
https://community.expensify.com/discussion/5667/deep-dive-how-does-the-annual-subscription-billing-work,https://help.expensify.com/articles/expensify-classic/billing-and-subscriptions/Annual-Subscription
https://help.expensify.com/expensify-classic/hubs/getting-started/plan-types,https://www.expensify.com/pricing
+https://help.expensify.com/articles/expensify-classic/getting-started/Employees,https://help.expensify.com/articles/expensify-classic/getting-started/Join-your-company's-workspace
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist
index 6a6a54e58c49..1506f2c5a4f5 100644
--- a/ios/NewExpensify/Info.plist
+++ b/ios/NewExpensify/Info.plist
@@ -40,7 +40,7 @@
CFBundleVersion
- 1.4.36.0
+ 1.4.36.2ITSAppUsesNonExemptEncryptionLSApplicationQueriesSchemes
diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist
index 763896fd45f1..34e909c1917f 100644
--- a/ios/NewExpensifyTests/Info.plist
+++ b/ios/NewExpensifyTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature????CFBundleVersion
- 1.4.36.0
+ 1.4.36.2
diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist
index a4bef0847c95..e13af4a39ed3 100644
--- a/ios/NotificationServiceExtension/Info.plist
+++ b/ios/NotificationServiceExtension/Info.plist
@@ -13,7 +13,7 @@
CFBundleShortVersionString1.4.36CFBundleVersion
- 1.4.36.0
+ 1.4.36.2NSExtensionNSExtensionPointIdentifier
diff --git a/package-lock.json b/package-lock.json
index 674325508da5..dea6f963b989 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "new.expensify",
- "version": "1.4.36-0",
+ "version": "1.4.36-2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "new.expensify",
- "version": "1.4.36-0",
+ "version": "1.4.36-2",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 0c1105f978c6..e928ef59bb60 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
- "version": "1.4.36-0",
+ "version": "1.4.36-2",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index 55a1cb14e526..34267688e1cd 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -499,8 +499,8 @@ type OnyxValues = {
[ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: OnyxTypes.DisplayNameForm;
[ONYXKEYS.FORMS.DISPLAY_NAME_FORM_DRAFT]: OnyxTypes.DisplayNameForm;
- [ONYXKEYS.FORMS.ROOM_NAME_FORM]: OnyxTypes.Form;
- [ONYXKEYS.FORMS.ROOM_NAME_FORM_DRAFT]: OnyxTypes.Form;
+ [ONYXKEYS.FORMS.ROOM_NAME_FORM]: OnyxTypes.RoomNameForm;
+ [ONYXKEYS.FORMS.ROOM_NAME_FORM_DRAFT]: OnyxTypes.RoomNameForm;
[ONYXKEYS.FORMS.WELCOME_MESSAGE_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.WELCOME_MESSAGE_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.LEGAL_NAME_FORM]: OnyxTypes.Form;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index a84dc9c8f9ae..78b97f8dcde0 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -293,10 +293,6 @@ const ROUTES = {
route: ':iouType/new/category/:reportID?',
getRoute: (iouType: string, reportID = '') => `${iouType}/new/category/${reportID}` as const,
},
- MONEY_REQUEST_TAG: {
- route: ':iouType/new/tag/:reportID?',
- getRoute: (iouType: string, reportID = '') => `${iouType}/new/tag/${reportID}` as const,
- },
MONEY_REQUEST_MERCHANT: {
route: ':iouType/new/merchant/:reportID?',
getRoute: (iouType: string, reportID = '') => `${iouType}/new/merchant/${reportID}` as const,
@@ -380,9 +376,9 @@ const ROUTES = {
getUrlWithBackToParam(`${action}/${iouType}/scan/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_TAG: {
- route: 'create/:iouType/tag/:transactionID/:reportID',
- getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
- getUrlWithBackToParam(`create/${iouType}/tag/${transactionID}/${reportID}`, backTo),
+ route: ':action/:iouType/tag/:transactionID/:reportID',
+ getRoute: (action: ValueOf, iouType: ValueOf, transactionID: string, reportID: string, backTo = '') =>
+ getUrlWithBackToParam(`${action}/${iouType}/tag/${transactionID}/${reportID}`, backTo),
},
MONEY_REQUEST_STEP_WAYPOINT: {
route: ':action/:iouType/waypoint/:transactionID/:reportID/:pageIndex',
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 96b284dbea2f..a64bf328ff28 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -152,7 +152,6 @@ const SCREENS = {
DATE: 'Money_Request_Date',
DESCRIPTION: 'Money_Request_Description',
CATEGORY: 'Money_Request_Category',
- TAG: 'Money_Request_Tag',
MERCHANT: 'Money_Request_Merchant',
WAYPOINT: 'Money_Request_Waypoint',
EDIT_WAYPOINT: 'Money_Request_Edit_Waypoint',
diff --git a/src/components/ButtonWithDropdownMenu.tsx b/src/components/ButtonWithDropdownMenu.tsx
index 19e65d577298..9466da601825 100644
--- a/src/components/ButtonWithDropdownMenu.tsx
+++ b/src/components/ButtonWithDropdownMenu.tsx
@@ -62,6 +62,9 @@ type ButtonWithDropdownMenuProps = {
/* ref for the button */
buttonRef: RefObject;
+
+ /** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */
+ enterKeyEventListenerPriority?: number;
};
function ButtonWithDropdownMenu({
@@ -79,6 +82,7 @@ function ButtonWithDropdownMenu({
onPress,
options,
onOptionSelected,
+ enterKeyEventListenerPriority = 0,
}: ButtonWithDropdownMenuProps) {
const theme = useTheme();
const styles = useThemeStyles();
@@ -129,6 +133,7 @@ function ButtonWithDropdownMenu({
large={isButtonSizeLarge}
medium={!isButtonSizeLarge}
innerStyles={[innerStyleDropButton]}
+ enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>
)}
diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js
index afabb40fd9f4..b6a91cf7a9c8 100755
--- a/src/components/MoneyRequestConfirmationList.js
+++ b/src/components/MoneyRequestConfirmationList.js
@@ -540,6 +540,7 @@ function MoneyRequestConfirmationList(props) {
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
shouldShowPersonalBankAccountOption
+ enterKeyEventListenerPriority={1}
/>
) : (
confirm(value)}
options={splitOrRequestOptions}
buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE}
+ enterKeyEventListenerPriority={1}
/>
);
@@ -769,7 +771,15 @@ function MoneyRequestConfirmationList(props) {
numberOfLinesTitle={2}
onPress={() => {
if (props.isEditingSplitBill) {
- Navigation.navigate(ROUTES.EDIT_SPLIT_BILL.getRoute(props.reportID, props.reportActionID, CONST.EDIT_REQUEST_FIELD.TAG));
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(
+ CONST.IOU.ACTION.EDIT,
+ CONST.IOU.TYPE.SPLIT,
+ props.transaction.transactionID,
+ props.reportID,
+ Navigation.getActiveRouteWithoutParams(),
+ ),
+ );
return;
}
Navigation.navigate(ROUTES.MONEY_REQUEST_TAG.getRoute(props.iouType, props.reportID));
diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
index 2aff0444a59e..be5cec7a2c0d 100755
--- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
+++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js
@@ -589,6 +589,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
+ enterKeyEventListenerPriority={1}
/>
) : (
confirm(value)}
options={splitOrRequestOptions}
buttonSize={CONST.DROPDOWN_BUTTON_SIZE.LARGE}
+ enterKeyEventListenerPriority={1}
/>
);
@@ -817,12 +819,14 @@ function MoneyTemporaryForRefactorRequestConfirmationList({
description={policyTagListName}
numberOfLinesTitle={2}
onPress={() =>
- Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()))
+ Navigation.navigate(
+ ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.CREATE, iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams()),
+ )
}
style={[styles.moneyRequestMenuItem]}
disabled={didConfirm}
interactive={!isReadOnly}
- rightLabel={canUseViolations && Boolean(policy.requiresTag) ? translate('common.required') : ''}
+ rightLabel={canUseViolations && lodashGet(policy, 'requiresTag', false) ? translate('common.required') : ''}
/>
)}
{shouldShowTax && (
diff --git a/src/components/OptionsList/BaseOptionsList.tsx b/src/components/OptionsList/BaseOptionsList.tsx
index 1975c0f1a88e..6f265bfb8440 100644
--- a/src/components/OptionsList/BaseOptionsList.tsx
+++ b/src/components/OptionsList/BaseOptionsList.tsx
@@ -175,15 +175,7 @@ function BaseOptionsList(
const renderItem: SectionListRenderItem = ({item, index, section}) => {
const isItemDisabled = isDisabled || !!section.isDisabled || !!item.isDisabled;
const isSelected = selectedOptions?.some((option) => {
- if (option.accountID && option.accountID === item.accountID) {
- return true;
- }
-
- if (option.reportID && option.reportID === item.reportID) {
- return true;
- }
-
- if (option.policyID && option.policyID === item.policyID) {
+ if (option.keyForList && option.keyForList === item.keyForList) {
return true;
}
diff --git a/src/components/PopoverProvider/index.tsx b/src/components/PopoverProvider/index.tsx
index a738d1f9798a..69728d7be126 100644
--- a/src/components/PopoverProvider/index.tsx
+++ b/src/components/PopoverProvider/index.tsx
@@ -27,9 +27,6 @@ function PopoverContextProvider(props: PopoverContextProps) {
}
activePopoverRef.current.close();
- if (activePopoverRef.current.onCloseCallback) {
- activePopoverRef.current.onCloseCallback();
- }
activePopoverRef.current = null;
setIsOpen(false);
}, []);
@@ -107,9 +104,6 @@ function PopoverContextProvider(props: PopoverContextProps) {
closePopover(activePopoverRef.current.anchorRef);
}
activePopoverRef.current = popoverParams;
- if (popoverParams?.onOpenCallback) {
- popoverParams.onOpenCallback();
- }
setIsOpen(true);
},
[closePopover],
diff --git a/src/components/PopoverProvider/types.ts b/src/components/PopoverProvider/types.ts
index 49705d7ea7a8..2a366ae2a712 100644
--- a/src/components/PopoverProvider/types.ts
+++ b/src/components/PopoverProvider/types.ts
@@ -16,8 +16,6 @@ type AnchorRef = {
ref: RefObject;
close: (anchorRef?: RefObject) => void;
anchorRef: RefObject;
- onOpenCallback?: () => void;
- onCloseCallback?: () => void;
};
export type {PopoverContextProps, PopoverContextValue, AnchorRef};
diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx
index 3c0e50b2c940..e2021360c11a 100644
--- a/src/components/ReportActionItem/MoneyReportView.tsx
+++ b/src/components/ReportActionItem/MoneyReportView.tsx
@@ -44,6 +44,7 @@ function MoneyReportView({report, policy, policyReportFields, shouldShowHorizont
const {isSmallScreenWidth} = useWindowDimensions();
const {canUseReportFields} = usePermissions();
const isSettled = ReportUtils.isSettled(report.reportID);
+ const isTotalUpdated = ReportUtils.hasUpdatedTotal(report);
const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report);
@@ -119,7 +120,7 @@ function MoneyReportView({report, policy, policyReportFields, shouldShowHorizont
)}
{formattedTotalAmount}
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index 3533506797bb..2ce02d598de2 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -336,7 +336,9 @@ function MoneyRequestView({
interactive={canEdit}
shouldShowRightIcon={canEdit}
titleStyle={styles.flex1}
- onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))}
+ onPress={() =>
+ Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, CONST.IOU.TYPE.REQUEST, transaction?.transactionID ?? '', report.reportID))
+ }
brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined}
/>
{canUseViolations && }
diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index 3e34b80da580..c572e9d5f896 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -130,9 +130,11 @@ function ReportPreview({
const isDraftExpenseReport = isPolicyExpenseChat && ReportUtils.isDraftExpenseReport(iouReport);
const isApproved = ReportUtils.isReportApproved(iouReport);
+ const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport);
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(iouReport);
const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID);
const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length;
+
const hasReceipts = transactionsWithReceipts.length > 0;
const isScanning = hasReceipts && areAllRequestsBeingSmartScanned;
const hasErrors = (hasReceipts && hasMissingSmartscanFields) || (canUseViolations && ReportUtils.hasViolations(iouReportID, transactionViolations));
@@ -306,6 +308,7 @@ function ReportPreview({
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
+ isDisabled={!canAllowSettlement}
/>
)}
{shouldShowSubmitButton && (
diff --git a/src/components/ReportHeaderSkeletonView.tsx b/src/components/ReportHeaderSkeletonView.tsx
index 2113abd85e88..5332e0c5032c 100644
--- a/src/components/ReportHeaderSkeletonView.tsx
+++ b/src/components/ReportHeaderSkeletonView.tsx
@@ -24,12 +24,12 @@ function ReportHeaderSkeletonView({shouldAnimate = true, onBackButtonPress = ()
const {isSmallScreenWidth} = useWindowDimensions();
return (
-
-
+
+
{isSmallScreenWidth && (
diff --git a/src/components/SettlementButton.tsx b/src/components/SettlementButton.tsx
index c2a22fa04bfe..8ed25853fcf3 100644
--- a/src/components/SettlementButton.tsx
+++ b/src/components/SettlementButton.tsx
@@ -89,6 +89,9 @@ type SettlementButtonProps = SettlementButtonOnyxProps & {
/** Whether the personal bank account option should be shown */
shouldShowPersonalBankAccountOption?: boolean;
+
+ /** The priority to assign the enter key event listener to buttons. 0 is the highest priority. */
+ enterKeyEventListenerPriority?: number;
};
function SettlementButton({
@@ -120,6 +123,7 @@ function SettlementButton({
shouldShowApproveButton = false,
style,
shouldShowPersonalBankAccountOption = false,
+ enterKeyEventListenerPriority = 0,
}: SettlementButtonProps) {
const {translate} = useLocalize();
const {isOffline} = useNetwork();
@@ -229,6 +233,7 @@ function SettlementButton({
style={style}
buttonSize={buttonSize}
anchorAlignment={paymentMethodDropdownAnchorAlignment}
+ enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>
)}
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 145d8414c1e3..95c8379aa4d5 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -679,6 +679,7 @@ export default {
always: 'Immediately',
daily: 'Daily',
mute: 'Mute',
+ hidden: 'Hidden',
},
},
loginField: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 517b3ba1e2f9..3926f074f653 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -673,6 +673,7 @@ export default {
always: 'Inmediatamente',
daily: 'Cada día',
mute: 'Nunca',
+ hidden: 'Oculto',
},
},
loginField: {
diff --git a/src/libs/ErrorUtils.ts b/src/libs/ErrorUtils.ts
index edc24bf94720..6298138a5825 100644
--- a/src/libs/ErrorUtils.ts
+++ b/src/libs/ErrorUtils.ts
@@ -113,7 +113,7 @@ type ErrorsList = Record;
* @param errors - An object containing current errors in the form
* @param message - Message to assign to the inputID errors
*/
-function addErrorMessage(errors: ErrorsList, inputID?: string, message?: TKey) {
+function addErrorMessage(errors: ErrorsList, inputID?: string, message?: TKey | Localize.MaybePhraseKey) {
if (!message || !inputID) {
return;
}
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
index 4606f867c3fc..8753c1e277f9 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
@@ -101,7 +101,6 @@ const MoneyRequestModalStackNavigator = createModalStackNavigator require('../../../pages/iou/MoneyRequestDatePage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.DESCRIPTION]: () => require('../../../pages/iou/MoneyRequestDescriptionPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.CATEGORY]: () => require('../../../pages/iou/MoneyRequestCategoryPage').default as React.ComponentType,
- [SCREENS.MONEY_REQUEST.TAG]: () => require('../../../pages/iou/MoneyRequestTagPage').default as React.ComponentType,
[SCREENS.MONEY_REQUEST.MERCHANT]: () => require('../../../pages/iou/MoneyRequestMerchantPage').default as React.ComponentType,
[SCREENS.IOU_SEND.ADD_BANK_ACCOUNT]: () => require('../../../pages/AddPersonalBankAccountPage').default as React.ComponentType,
[SCREENS.IOU_SEND.ADD_DEBIT_CARD]: () => require('../../../pages/settings/Wallet/AddDebitCardPage').default as React.ComponentType,
diff --git a/src/libs/Navigation/linkTo.ts b/src/libs/Navigation/linkTo.ts
index 3767e1ea010c..49dcee71eda4 100644
--- a/src/libs/Navigation/linkTo.ts
+++ b/src/libs/Navigation/linkTo.ts
@@ -216,16 +216,18 @@ export default function linkTo(navigation: NavigationContainerRef['config'] = {
[SCREENS.MONEY_REQUEST.CURRENCY]: ROUTES.MONEY_REQUEST_CURRENCY.route,
[SCREENS.MONEY_REQUEST.DESCRIPTION]: ROUTES.MONEY_REQUEST_DESCRIPTION.route,
[SCREENS.MONEY_REQUEST.CATEGORY]: ROUTES.MONEY_REQUEST_CATEGORY.route,
- [SCREENS.MONEY_REQUEST.TAG]: ROUTES.MONEY_REQUEST_TAG.route,
[SCREENS.MONEY_REQUEST.MERCHANT]: ROUTES.MONEY_REQUEST_MERCHANT.route,
[SCREENS.MONEY_REQUEST.RECEIPT]: ROUTES.MONEY_REQUEST_RECEIPT.route,
[SCREENS.MONEY_REQUEST.DISTANCE]: ROUTES.MONEY_REQUEST_DISTANCE.route,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index 3c4cf17853f1..d9f7197df887 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -225,12 +225,15 @@ type MoneyRequestNavigatorParamList = {
iouType: string;
reportID: string;
};
- [SCREENS.MONEY_REQUEST.TAG]: {
+ [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: {
iouType: string;
+ transactionID: string;
reportID: string;
+ backTo: string;
};
- [SCREENS.MONEY_REQUEST.STEP_TAX_AMOUNT]: {
- iouType: string;
+ [SCREENS.MONEY_REQUEST.STEP_TAG]: {
+ action: ValueOf;
+ iouType: ValueOf;
transactionID: string;
reportID: string;
backTo: string;
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 8cbe5bfa2d23..b6518b361381 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -66,6 +66,7 @@ type PayeePersonalDetails = {
descriptiveText: string;
login: string;
accountID: number;
+ keyForList: string;
};
type CategorySection = {
@@ -1737,6 +1738,7 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: Person
descriptiveText: amountText,
login: personalDetail.login ?? '',
accountID: personalDetail.accountID,
+ keyForList: String(personalDetail.accountID),
};
}
diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts
index 071cf0050688..a881dd411221 100644
--- a/src/libs/PersonalDetailsUtils.ts
+++ b/src/libs/PersonalDetailsUtils.ts
@@ -1,3 +1,4 @@
+import Str from 'expensify-common/lib/str';
import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import CONST from '@src/CONST';
@@ -8,6 +9,11 @@ import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Localize from './Localize';
import * as UserUtils from './UserUtils';
+type FirstAndLastName = {
+ firstName: string;
+ lastName: string;
+};
+
let personalDetails: Array = [];
let allPersonalDetails: OnyxEntry = {};
Onyx.connect({
@@ -195,6 +201,57 @@ function getEffectiveDisplayName(personalDetail?: PersonalDetails): string | und
return undefined;
}
+/**
+ * Creates a new displayName for a user based on passed personal details or login.
+ */
+function createDisplayName(login: string, passedPersonalDetails: Pick | OnyxEntry): string {
+ // If we have a number like +15857527441@expensify.sms then let's remove @expensify.sms and format it
+ // so that the option looks cleaner in our UI.
+ const userLogin = LocalePhoneNumber.formatPhoneNumber(login);
+
+ if (!passedPersonalDetails) {
+ return userLogin;
+ }
+
+ const firstName = passedPersonalDetails.firstName ?? '';
+ const lastName = passedPersonalDetails.lastName ?? '';
+ const fullName = `${firstName} ${lastName}`.trim();
+
+ // It's possible for fullName to be empty string, so we must use "||" to fallback to userLogin.
+ return fullName || userLogin;
+}
+
+/**
+ * Gets the first and last name from the user's personal details.
+ * If the login is the same as the displayName, then they don't exist,
+ * so we return empty strings instead.
+ */
+function extractFirstAndLastNameFromAvailableDetails({login, displayName, firstName, lastName}: PersonalDetails): FirstAndLastName {
+ // It's possible for firstName to be empty string, so we must use "||" to consider lastName instead.
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+ if (firstName || lastName) {
+ return {firstName: firstName ?? '', lastName: lastName ?? ''};
+ }
+ if (login && Str.removeSMSDomain(login) === displayName) {
+ return {firstName: '', lastName: ''};
+ }
+
+ if (displayName) {
+ const firstSpaceIndex = displayName.indexOf(' ');
+ const lastSpaceIndex = displayName.lastIndexOf(' ');
+ if (firstSpaceIndex === -1) {
+ return {firstName: displayName, lastName: ''};
+ }
+
+ return {
+ firstName: displayName.substring(0, firstSpaceIndex).trim(),
+ lastName: displayName.substring(lastSpaceIndex).trim(),
+ };
+ }
+
+ return {firstName: '', lastName: ''};
+}
+
/**
* Whether personal details is empty
*/
@@ -213,4 +270,6 @@ export {
getFormattedStreet,
getStreetLines,
getEffectiveDisplayName,
+ createDisplayName,
+ extractFirstAndLastNameFromAvailableDetails,
};
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index b6ee4ab3a353..aaae113b7da1 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -15,10 +15,7 @@ type UnitRate = {rate: number};
* These are policies that we can use to create reports with in NewDot.
*/
function getActivePolicies(policies: OnyxCollection): Policy[] | undefined {
- return Object.values(policies ?? {}).filter(
- (policy): policy is Policy =>
- policy !== null && policy && (policy.isPolicyExpenseChatEnabled || policy.areChatRoomsEnabled) && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
- );
+ return Object.values(policies ?? {}).filter((policy): policy is Policy => policy !== null && policy && policy.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
}
/**
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 186e489e79e7..d9b504a959da 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -4655,6 +4655,21 @@ function shouldDisplayThreadReplies(reportAction: OnyxEntry, repor
return hasReplies && !!reportAction?.childCommenterCount && !isThreadFirstChat(reportAction, reportID);
}
+/**
+ * Check if money report has any transactions updated optimistically
+ */
+function hasUpdatedTotal(report: OnyxEntry): boolean {
+ if (!report) {
+ return true;
+ }
+
+ const transactions = TransactionUtils.getAllReportTransactions(report.reportID);
+ const hasPendingTransaction = transactions.some((transaction) => !!transaction.pendingAction);
+ const hasTransactionWithDifferentCurrency = transactions.some((transaction) => transaction.currency !== report.currency);
+
+ return !(hasPendingTransaction && hasTransactionWithDifferentCurrency);
+}
+
/**
* Disable reply in thread action if:
*
@@ -4954,6 +4969,7 @@ export {
isValidReport,
isReportFieldOfTypeTitle,
isIOUReportUsingReport,
+ hasUpdatedTotal,
isReportFieldDisabled,
getAvailableReportFields,
getAllAncestorReportActionIDs,
diff --git a/src/libs/ValidationUtils.ts b/src/libs/ValidationUtils.ts
index 9b2b1d01b80b..b3e733d1f6b3 100644
--- a/src/libs/ValidationUtils.ts
+++ b/src/libs/ValidationUtils.ts
@@ -4,6 +4,7 @@ import {URL_REGEX_WITH_REQUIRED_PROTOCOL} from 'expensify-common/lib/Url';
import isDate from 'lodash/isDate';
import isEmpty from 'lodash/isEmpty';
import isObject from 'lodash/isObject';
+import type {OnyxCollection} from 'react-native-onyx';
import CONST from '@src/CONST';
import type {Report} from '@src/types/onyx';
import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
@@ -361,8 +362,8 @@ function isReservedRoomName(roomName: string): boolean {
/**
* Checks if the room name already exists.
*/
-function isExistingRoomName(roomName: string, reports: Record, policyID: string): boolean {
- return Object.values(reports).some((report) => report && report.policyID === policyID && report.reportName === roomName);
+function isExistingRoomName(roomName: string, reports: OnyxCollection, policyID: string): boolean {
+ return Object.values(reports ?? {}).some((report) => report && report.policyID === policyID && report.reportName === roomName);
}
/**
diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js
index 4db89a1e926b..7da6e13d1f86 100644
--- a/src/libs/actions/IOU.js
+++ b/src/libs/actions/IOU.js
@@ -178,8 +178,13 @@ function clearMoneyRequest(transactionID) {
* @param {String} transactionID
* @param {Number} amount
* @param {String} currency
+ * @param {Boolean} [removeOriginalCurrency]
*/
-function setMoneyRequestAmount_temporaryForRefactor(transactionID, amount, currency) {
+function setMoneyRequestAmount_temporaryForRefactor(transactionID, amount, currency, removeOriginalCurrency = false) {
+ if (removeOriginalCurrency) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null});
+ return;
+ }
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency});
}
@@ -194,11 +199,24 @@ function setMoneyRequestCreated_temporaryForRefactor(transactionID, created) {
/**
* @param {String} transactionID
* @param {String} currency
+ * @param {Boolean} [removeOriginalCurrency]
*/
-function setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency) {
+function setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency, removeOriginalCurrency = false) {
+ if (removeOriginalCurrency) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {currency, originalCurrency: null});
+ return;
+ }
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {currency});
}
+/**
+ * @param {String} transactionID
+ * @param {String} originalCurrency
+ */
+function setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID, originalCurrency) {
+ Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {originalCurrency});
+}
+
/**
* @param {String} transactionID
* @param {String} comment
@@ -234,17 +252,10 @@ function resetMoneyRequestCategory_temporaryForRefactor(transactionID) {
* @param {String} transactionID
* @param {String} tag
*/
-function setMoneyRequestTag_temporaryForRefactor(transactionID, tag) {
+function setMoneyRequestTag(transactionID, tag) {
Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {tag});
}
-/*
- * @param {String} transactionID
- */
-function resetMoneyRequestTag_temporaryForRefactor(transactionID) {
- Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {tag: null});
-}
-
/**
* @param {String} transactionID
* @param {Boolean} billable
@@ -3635,17 +3646,6 @@ function resetMoneyRequestCategory() {
Onyx.merge(ONYXKEYS.IOU, {category: ''});
}
-/*
- * @param {String} tag
- */
-function setMoneyRequestTag(tag) {
- Onyx.merge(ONYXKEYS.IOU, {tag});
-}
-
-function resetMoneyRequestTag() {
- Onyx.merge(ONYXKEYS.IOU, {tag: ''});
-}
-
/**
* @param {String} transactionID
* @param {Object} taxRate
@@ -3726,7 +3726,6 @@ function navigateToNextPage(iou, iouType, report, path = '') {
.value();
setMoneyRequestParticipants(participants);
resetMoneyRequestCategory();
- resetMoneyRequestTag();
}
Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID));
return;
@@ -3804,19 +3803,17 @@ export {
resetMoneyRequestCategory,
resetMoneyRequestCategory_temporaryForRefactor,
resetMoneyRequestInfo,
- resetMoneyRequestTag,
- resetMoneyRequestTag_temporaryForRefactor,
clearMoneyRequest,
setMoneyRequestAmount_temporaryForRefactor,
setMoneyRequestBillable_temporaryForRefactor,
setMoneyRequestCategory_temporaryForRefactor,
setMoneyRequestCreated_temporaryForRefactor,
setMoneyRequestCurrency_temporaryForRefactor,
+ setMoneyRequestOriginalCurrency_temporaryForRefactor,
setMoneyRequestDescription_temporaryForRefactor,
setMoneyRequestMerchant_temporaryForRefactor,
setMoneyRequestParticipants_temporaryForRefactor,
setMoneyRequestReceipt,
- setMoneyRequestTag_temporaryForRefactor,
setMoneyRequestAmount,
setMoneyRequestBillable,
setMoneyRequestCategory,
diff --git a/src/libs/actions/PersonalDetails.ts b/src/libs/actions/PersonalDetails.ts
index ac044b0bc25b..26c8937de3aa 100644
--- a/src/libs/actions/PersonalDetails.ts
+++ b/src/libs/actions/PersonalDetails.ts
@@ -1,4 +1,3 @@
-import Str from 'expensify-common/lib/str';
import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import * as API from '@libs/API';
@@ -16,7 +15,6 @@ import type {
import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
import type {CustomRNImageManipulatorResult} from '@libs/cropOrRotateImage/types';
import DateUtils from '@libs/DateUtils';
-import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
import Navigation from '@libs/Navigation/Navigation';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as UserUtils from '@libs/UserUtils';
@@ -27,11 +25,6 @@ import type {DateOfBirthForm, PersonalDetails, PersonalDetailsList, PrivatePerso
import type {SelectedTimezone, Timezone} from '@src/types/onyx/PersonalDetails';
import * as Session from './Session';
-type FirstAndLastName = {
- firstName: string;
- lastName: string;
-};
-
let currentUserEmail = '';
let currentUserAccountID = -1;
Onyx.connect({
@@ -54,69 +47,6 @@ Onyx.connect({
callback: (val) => (privatePersonalDetails = val),
});
-/**
- * Creates a new displayName for a user based on passed personal details or login.
- */
-function createDisplayName(login: string, personalDetails: Pick | OnyxEntry): string {
- // If we have a number like +15857527441@expensify.sms then let's remove @expensify.sms and format it
- // so that the option looks cleaner in our UI.
- const userLogin = LocalePhoneNumber.formatPhoneNumber(login);
-
- if (!personalDetails) {
- return userLogin;
- }
-
- const firstName = personalDetails.firstName ?? '';
- const lastName = personalDetails.lastName ?? '';
- const fullName = `${firstName} ${lastName}`.trim();
-
- // It's possible for fullName to be empty string, so we must use "||" to fallback to userLogin.
- return fullName || userLogin;
-}
-
-/**
- * Gets the first and last name from the user's personal details.
- * If the login is the same as the displayName, then they don't exist,
- * so we return empty strings instead.
- */
-function extractFirstAndLastNameFromAvailableDetails({login, displayName, firstName, lastName}: PersonalDetails): FirstAndLastName {
- // It's possible for firstName to be empty string, so we must use "||" to consider lastName instead.
- // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
- if (firstName || lastName) {
- return {firstName: firstName ?? '', lastName: lastName ?? ''};
- }
- if (login && Str.removeSMSDomain(login) === displayName) {
- return {firstName: '', lastName: ''};
- }
-
- if (displayName) {
- const firstSpaceIndex = displayName.indexOf(' ');
- const lastSpaceIndex = displayName.lastIndexOf(' ');
- if (firstSpaceIndex === -1) {
- return {firstName: displayName, lastName: ''};
- }
-
- return {
- firstName: displayName.substring(0, firstSpaceIndex).trim(),
- lastName: displayName.substring(lastSpaceIndex).trim(),
- };
- }
-
- return {firstName: '', lastName: ''};
-}
-
-/**
- * Convert country names obtained from the backend to their respective ISO codes
- * This is for backward compatibility of stored data before E/App#15507
- */
-function getCountryISO(countryName: string): string {
- if (!countryName || countryName.length === 2) {
- return countryName;
- }
-
- return Object.entries(CONST.ALL_COUNTRIES).find(([, value]) => value === countryName)?.[0] ?? '';
-}
-
function updatePronouns(pronouns: string) {
if (currentUserAccountID) {
const parameters: UpdatePronounsParams = {pronouns};
@@ -152,7 +82,7 @@ function updateDisplayName(firstName: string, lastName: string) {
[currentUserAccountID]: {
firstName,
lastName,
- displayName: createDisplayName(currentUserEmail ?? '', {
+ displayName: PersonalDetailsUtils.createDisplayName(currentUserEmail ?? '', {
firstName,
lastName,
}),
@@ -524,9 +454,6 @@ function getPrivatePersonalDetails(): OnyxEntry {
export {
clearAvatarErrors,
deleteAvatar,
- extractFirstAndLastNameFromAvailableDetails,
- getCountryISO,
- createDisplayName,
getPrivatePersonalDetails,
openPersonalDetailsPage,
openPublicProfilePage,
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 1ad7af58a3af..4bc5063629e9 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1162,7 +1162,6 @@ function createDraftInitialWorkspace(policyOwnerEmail = '', policyName = '', pol
isPolicyExpenseChatEnabled: true,
outputCurrency,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- areChatRoomsEnabled: true,
customUnits,
makeMeAdmin,
},
@@ -1224,7 +1223,6 @@ function createWorkspace(policyOwnerEmail = '', makeMeAdmin = false, policyName
isPolicyExpenseChatEnabled: true,
outputCurrency,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- areChatRoomsEnabled: true,
customUnits,
},
},
@@ -1604,7 +1602,6 @@ function createWorkspaceFromIOUPayment(iouReport: Report | EmptyObject): string
// Setting the currency to USD as we can only add the VBBA for this policy currency right now
outputCurrency: CONST.CURRENCY.USD,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
- areChatRoomsEnabled: true,
customUnits,
};
diff --git a/src/libs/actions/TeachersUnite.ts b/src/libs/actions/TeachersUnite.ts
index 055d1f2b53a2..ab48609e2d53 100644
--- a/src/libs/actions/TeachersUnite.ts
+++ b/src/libs/actions/TeachersUnite.ts
@@ -91,7 +91,6 @@ function addSchoolPrincipal(firstName: string, partnerUserID: string, lastName:
value: {
id: policyID,
isPolicyExpenseChatEnabled: true,
- areChatRoomsEnabled: true,
type: CONST.POLICY.TYPE.CORPORATE,
name: policyName,
role: CONST.POLICY.ROLE.USER,
diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts
index 93739ce68e47..09cc6e49e6cc 100644
--- a/src/libs/actions/User.ts
+++ b/src/libs/actions/User.ts
@@ -24,6 +24,7 @@ import * as ErrorUtils from '@libs/ErrorUtils';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import * as SequentialQueue from '@libs/Network/SequentialQueue';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as Pusher from '@libs/Pusher/pusher';
import PusherUtils from '@libs/PusherUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
@@ -40,7 +41,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import * as Link from './Link';
import * as OnyxUpdates from './OnyxUpdates';
-import * as PersonalDetails from './PersonalDetails';
import * as Report from './Report';
import * as Session from './Session';
@@ -705,7 +705,7 @@ function setContactMethodAsDefault(newDefaultContactMethod: string) {
value: {
[currentUserAccountID]: {
login: newDefaultContactMethod,
- displayName: PersonalDetails.createDisplayName(newDefaultContactMethod, myPersonalDetails),
+ displayName: PersonalDetailsUtils.createDisplayName(newDefaultContactMethod, myPersonalDetails),
},
},
},
diff --git a/src/pages/EnablePayments/AdditionalDetailsStep.js b/src/pages/EnablePayments/AdditionalDetailsStep.js
index 5ca51e208b49..cf769bf58fd3 100644
--- a/src/pages/EnablePayments/AdditionalDetailsStep.js
+++ b/src/pages/EnablePayments/AdditionalDetailsStep.js
@@ -16,10 +16,10 @@ import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultPro
import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import compose from '@libs/compose';
+import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import {parsePhoneNumber} from '@libs/PhoneNumber';
import * as ValidationUtils from '@libs/ValidationUtils';
import AddressForm from '@pages/ReimbursementAccount/AddressForm';
-import * as PersonalDetails from '@userActions/PersonalDetails';
import * as Wallet from '@userActions/Wallet';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -194,7 +194,7 @@ function AdditionalDetailsStep({walletAdditionalDetails, translate, currentUserP
label={translate(fieldNameTranslationKeys.legalFirstName)}
accessibilityLabel={translate(fieldNameTranslationKeys.legalFirstName)}
role={CONST.ROLE.PRESENTATION}
- defaultValue={PersonalDetails.extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).firstName}
+ defaultValue={PersonalDetailsUtils.extractFirstAndLastNameFromAvailableDetails(currentUserPersonalDetails).firstName}
shouldSaveDraft
/>
- Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(String(accountID)))}
+ accessibilityLabel={props.translate('common.profile')}
+ accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON}
>
- {({show}) => (
-
-
-
-
-
- )}
-
+
+
+
+
{Boolean(displayName) && (
{
- Navigation.goBack(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(iouType, report.reportID));
- };
-
- const updateTag = (selectedTag) => {
- if (selectedTag.searchText === iou.tag) {
- IOU.resetMoneyRequestTag();
- } else {
- IOU.setMoneyRequestTag(selectedTag.searchText);
- }
- navigateBack();
- };
-
- return (
-
- {({insets}) => (
- <>
-
- {translate('iou.tagSelection', {tagName: policyTagListName})}
-
- >
- )}
-
- );
-}
-
-MoneyRequestTagPage.displayName = 'MoneyRequestTagPage';
-MoneyRequestTagPage.propTypes = propTypes;
-MoneyRequestTagPage.defaultProps = defaultProps;
-
-export default compose(
- withOnyx({
- iou: {
- key: ONYXKEYS.IOU,
- },
- }),
- // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
- withOnyx({
- report: {
- key: ({route, iou}) => {
- const reportID = IOU.getIOUReportID(iou, route);
-
- return `${ONYXKEYS.COLLECTION.REPORT}${reportID}`;
- },
- },
- }),
- // eslint-disable-next-line rulesdir/no-multiple-onyx-in-file
- withOnyx({
- policyTags: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`,
- },
- }),
-)(MoneyRequestTagPage);
diff --git a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
index 99335b062f52..fe1f425a6d62 100644
--- a/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
+++ b/src/pages/iou/request/MoneyTemporaryForRefactorRequestParticipantsSelector.js
@@ -243,7 +243,7 @@ function MoneyTemporaryForRefactorRequestParticipantsSelector({
const headerMessage = useMemo(
() =>
OptionsListUtils.getHeaderMessage(
- _.get(newChatOptions, 'personalDetails.length', 0) + _.get(newChatOptions, 'recentReports.length', 0) !== 0,
+ _.get(newChatOptions, 'personalDetails', []).length + _.get(newChatOptions, 'recentReports', []).length !== 0,
Boolean(newChatOptions.userToInvite),
searchTerm.trim(),
maxParticipantsReached,
diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js
index 84e0ac8533c5..f91e7cea533b 100644
--- a/src/pages/iou/request/step/IOURequestStepAmount.js
+++ b/src/pages/iou/request/step/IOURequestStepAmount.js
@@ -1,6 +1,6 @@
import {useFocusEffect} from '@react-navigation/native';
import PropTypes from 'prop-types';
-import React, {useCallback, useRef} from 'react';
+import React, {useCallback, useEffect, useRef} from 'react';
import {withOnyx} from 'react-native-onyx';
import taxPropTypes from '@components/taxPropTypes';
import transactionPropTypes from '@components/transactionPropTypes';
@@ -59,18 +59,19 @@ const getTaxAmount = (transaction, defaultTaxValue, amount) => {
function IOURequestStepAmount({
report,
route: {
- params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency},
+ params: {iouType, reportID, transactionID, backTo},
},
transaction,
- transaction: {currency: originalCurrency},
+ transaction: {currency},
policyTaxRates,
policy,
}) {
const {translate} = useLocalize();
const textInput = useRef(null);
const focusTimeoutRef = useRef(null);
+ const isSaveButtonPressed = useRef(false);
+ const originalCurrency = useRef(null);
const iouRequestType = getRequestType(transaction);
- const currency = selectedCurrency || originalCurrency;
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report));
const isTaxTrackingEnabled = isPolicyExpenseChat && policy.isTaxTrackingEnabled;
@@ -87,6 +88,22 @@ function IOURequestStepAmount({
}, []),
);
+ useEffect(() => {
+ if (transaction.originalCurrency) {
+ originalCurrency.current = transaction.originalCurrency;
+ } else {
+ originalCurrency.current = currency;
+ IOU.setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID, currency);
+ }
+ return () => {
+ if (isSaveButtonPressed.current) {
+ return;
+ }
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, originalCurrency.current, true);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const navigateBack = () => {
Navigation.goBack(backTo || ROUTES.HOME);
};
@@ -99,6 +116,7 @@ function IOURequestStepAmount({
* @param {Number} amount
*/
const navigateToNextPage = ({amount}) => {
+ isSaveButtonPressed.current = true;
const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount));
if ((iouRequestType === CONST.IOU.REQUEST_TYPE.MANUAL || backTo) && isTaxTrackingEnabled) {
@@ -107,7 +125,7 @@ function IOURequestStepAmount({
IOU.setMoneyRequestTaxAmount(transaction.transactionID, taxAmountInSmallestCurrencyUnits);
}
- IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD);
+ IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true);
if (backTo) {
Navigation.goBack(backTo);
diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.js b/src/pages/iou/request/step/IOURequestStepConfirmation.js
index 0f1c2b27ad2e..2efba59e0acc 100644
--- a/src/pages/iou/request/step/IOURequestStepConfirmation.js
+++ b/src/pages/iou/request/step/IOURequestStepConfirmation.js
@@ -5,6 +5,7 @@ import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import categoryPropTypes from '@components/categoryPropTypes';
+import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import MoneyRequestConfirmationList from '@components/MoneyTemporaryForRefactorRequestConfirmationList';
@@ -103,6 +104,15 @@ function IOURequestStepConfirmation({
);
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(ReportUtils.getRootParentReport(report)), [report]);
+ useEffect(() => {
+ if (!transaction || !transaction.originalCurrency) {
+ return;
+ }
+ // If user somehow lands on this page without the currency reset, then reset it here.
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, transaction.originalCurrency, true);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
useEffect(() => {
const policyExpenseChat = _.find(participants, (participant) => participant.isPolicyExpenseChat);
if (policyExpenseChat) {
@@ -321,6 +331,10 @@ function IOURequestStepConfirmation({
IOU.setMoneyRequestBillable_temporaryForRefactor(transactionID, billable);
};
+ // This loading indicator is shown because the transaction originalCurrency is being updated later than the component mounts.
+ // To prevent the component from rendering with the wrong currency, we show a loading indicator until the correct currency is set.
+ const isLoading = !!(transaction && transaction.originalCurrency);
+
return (
-
+ {isLoading ? (
+
+ ) : (
+
+ )}
)}
diff --git a/src/pages/iou/request/step/IOURequestStepCurrency.js b/src/pages/iou/request/step/IOURequestStepCurrency.js
index b4281de4d16e..06af0ecf3ca4 100644
--- a/src/pages/iou/request/step/IOURequestStepCurrency.js
+++ b/src/pages/iou/request/step/IOURequestStepCurrency.js
@@ -59,18 +59,14 @@ function IOURequestStepCurrency({
const [searchValue, setSearchValue] = useState('');
const optionsSelectorRef = useRef();
- const navigateBack = (selectedCurrency = undefined) => {
+ const navigateBack = () => {
// If the currency selection was done from the confirmation step (eg. + > request money > manual > confirm > amount > currency)
// then the user needs taken back to the confirmation page instead of the initial amount page. This is because the route params
// are only able to handle one backTo param at a time and the user needs to go back to the amount page before going back
// to the confirmation page
if (pageIndex === 'confirm') {
const routeToAmountPageWithConfirmationAsBackTo = getUrlWithBackToParam(backTo, `/${ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(iouType, transactionID, reportID)}`);
- if (selectedCurrency) {
- Navigation.navigate(`${routeToAmountPageWithConfirmationAsBackTo}¤cy=${selectedCurrency}`);
- } else {
- Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo);
- }
+ Navigation.goBack(routeToAmountPageWithConfirmationAsBackTo);
return;
}
Navigation.goBack(backTo || ROUTES.HOME);
@@ -82,10 +78,8 @@ function IOURequestStepCurrency({
*/
const confirmCurrencySelection = (option) => {
Keyboard.dismiss();
- if (pageIndex !== 'confirm') {
- IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, option.currencyCode);
- }
- navigateBack(option.currencyCode);
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, option.currencyCode);
+ navigateBack();
};
const {sections, headerMessage, initiallyFocusedOptionKey} = useMemo(() => {
diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.js b/src/pages/iou/request/step/IOURequestStepParticipants.js
index 0d1177e231c4..5f1b22cab128 100644
--- a/src/pages/iou/request/step/IOURequestStepParticipants.js
+++ b/src/pages/iou/request/step/IOURequestStepParticipants.js
@@ -70,7 +70,7 @@ function IOURequestStepParticipants({
const goToNextStep = useCallback(() => {
const nextStepIOUType = numberOfParticipants.current === 1 ? iouType : CONST.IOU.TYPE.SPLIT;
- IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID);
+ IOU.setMoneyRequestTag(transactionID, '');
IOU.resetMoneyRequestCategory_temporaryForRefactor(transactionID);
Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(nextStepIOUType, transactionID, selectedReportID.current || reportID));
}, [iouType, transactionID, reportID]);
diff --git a/src/pages/iou/request/step/IOURequestStepTag.js b/src/pages/iou/request/step/IOURequestStepTag.js
index 7e2ccbe1a9dd..1297b98a0814 100644
--- a/src/pages/iou/request/step/IOURequestStepTag.js
+++ b/src/pages/iou/request/step/IOURequestStepTag.js
@@ -12,6 +12,7 @@ import Navigation from '@libs/Navigation/Navigation';
import * as PolicyUtils from '@libs/PolicyUtils';
import reportPropTypes from '@pages/reportPropTypes';
import * as IOU from '@userActions/IOU';
+import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import IOURequestStepRoutePropTypes from './IOURequestStepRoutePropTypes';
@@ -44,7 +45,7 @@ function IOURequestStepTag({
policyTags,
report,
route: {
- params: {transactionID, backTo},
+ params: {action, transactionID, backTo, iouType},
},
transaction: {tag},
}) {
@@ -54,6 +55,8 @@ function IOURequestStepTag({
// Fetches the first tag list of the policy
const tagListKey = _.first(_.keys(policyTags));
const policyTagListName = PolicyUtils.getTagListName(policyTags) || translate('common.tag');
+ const isEditing = action === CONST.IOU.ACTION.EDIT;
+ const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT;
const navigateBack = () => {
Navigation.goBack(backTo || ROUTES.HOME);
@@ -64,11 +67,19 @@ function IOURequestStepTag({
* @param {String} selectedTag.searchText
*/
const updateTag = (selectedTag) => {
- if (selectedTag.searchText === tag) {
- IOU.resetMoneyRequestTag_temporaryForRefactor(transactionID);
- } else {
- IOU.setMoneyRequestTag_temporaryForRefactor(transactionID, selectedTag.searchText);
+ const isSelectedTag = selectedTag.searchText === tag;
+ const updatedTag = !isSelectedTag ? selectedTag.searchText : '';
+ if (isSplitBill) {
+ IOU.setDraftSplitTransaction(transactionID, {tag: selectedTag.searchText});
+ navigateBack();
+ return;
}
+ if (isEditing) {
+ IOU.updateMoneyRequestTag(transactionID, report.reportID, updatedTag);
+ Navigation.dismissModal();
+ return;
+ }
+ IOU.setMoneyRequestTag(transactionID, updatedTag);
navigateBack();
};
@@ -79,13 +90,18 @@ function IOURequestStepTag({
shouldShowWrapper
testID={IOURequestStepTag.displayName}
>
- {translate('iou.tagSelection', {tagName: policyTagListName})}
-
+ {({insets}) => (
+ <>
+ {translate('iou.tagSelection', {tagName: policyTagListName})}
+
+ >
+ )}
);
}
diff --git a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
index a9f3195bfdfd..d1352f5b1f5d 100644
--- a/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
+++ b/src/pages/iou/request/step/IOURequestStepTaxAmountPage.js
@@ -1,5 +1,5 @@
import {useFocusEffect} from '@react-navigation/native';
-import React, {useCallback, useRef} from 'react';
+import React, {useCallback, useEffect, useRef} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
@@ -53,10 +53,10 @@ const getTaxAmount = (transaction, defaultTaxValue) => {
function IOURequestStepTaxAmountPage({
route: {
- params: {iouType, reportID, transactionID, backTo, currency: selectedCurrency},
+ params: {iouType, reportID, transactionID, backTo},
},
transaction,
- transaction: {currency: originalCurrency},
+ transaction: {currency},
report,
policyTaxRates,
}) {
@@ -65,10 +65,27 @@ function IOURequestStepTaxAmountPage({
const textInput = useRef(null);
const isEditing = Navigation.getActiveRoute().includes('taxAmount');
- const currency = selectedCurrency || originalCurrency;
-
const focusTimeoutRef = useRef(null);
+ const isSaveButtonPressed = useRef(false);
+ const originalCurrency = useRef(null);
+
+ useEffect(() => {
+ if (transaction.originalCurrency) {
+ originalCurrency.current = transaction.originalCurrency;
+ } else {
+ originalCurrency.current = currency;
+ IOU.setMoneyRequestOriginalCurrency_temporaryForRefactor(transactionID, currency);
+ }
+ return () => {
+ if (isSaveButtonPressed.current) {
+ return;
+ }
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, originalCurrency.current, true);
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
useFocusEffect(
useCallback(() => {
focusTimeoutRef.current = setTimeout(() => textInput.current && textInput.current.focus(), CONST.ANIMATED_TRANSITION);
@@ -93,10 +110,11 @@ function IOURequestStepTaxAmountPage({
};
const updateTaxAmount = (currentAmount) => {
+ isSaveButtonPressed.current = true;
const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount.amount));
IOU.setMoneyRequestTaxAmount(transactionID, amountInSmallestCurrencyUnits);
- IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD);
+ IOU.setMoneyRequestCurrency_temporaryForRefactor(transactionID, currency || CONST.CURRENCY.USD, true);
if (backTo) {
Navigation.goBack(backTo);
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
index 216154be9cd4..ea57d88579ae 100644
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js
@@ -88,7 +88,6 @@ function MoneyRequestParticipantsPage({iou, selectedTab, route, transaction}) {
const navigateToConfirmationStep = (moneyRequestType) => {
IOU.setMoneyRequestId(moneyRequestType);
IOU.resetMoneyRequestCategory();
- IOU.resetMoneyRequestTag();
Navigation.navigate(ROUTES.MONEY_REQUEST_CONFIRMATION.getRoute(moneyRequestType, reportID));
};
diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
index 3cf39d98426f..944bad76c96b 100755
--- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
+++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js
@@ -259,13 +259,13 @@ function MoneyRequestParticipantsSelector({
const headerMessage = useMemo(
() =>
OptionsListUtils.getHeaderMessage(
- newChatOptions.personalDetails.length + newChatOptions.recentReports.length !== 0,
+ _.get(newChatOptions, 'personalDetails', []).length + _.get(newChatOptions, 'recentReports', []).length !== 0,
Boolean(newChatOptions.userToInvite),
searchTerm.trim(),
maxParticipantsReached,
_.some(participants, (participant) => participant.searchText.toLowerCase().includes(searchTerm.trim().toLowerCase())),
),
- [maxParticipantsReached, newChatOptions.personalDetails.length, newChatOptions.recentReports.length, newChatOptions.userToInvite, participants, searchTerm],
+ [maxParticipantsReached, newChatOptions, participants, searchTerm],
);
// Right now you can't split a request with a workspace and other additional participants
diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js
index dfa4262549fc..0447261913ce 100755
--- a/src/pages/settings/InitialSettingsPage.js
+++ b/src/pages/settings/InitialSettingsPage.js
@@ -171,7 +171,7 @@ function InitialSettingsPage(props) {
action: () => {
Link.openOldDotLink(CONST.OLDDOT_URLS.INBOX);
},
- link: Link.buildOldDotURL(CONST.OLDDOT_URLS.INBOX),
+ link: () => Link.buildOldDotURL(CONST.OLDDOT_URLS.INBOX),
},
{
translationKey: 'initialSettingsPage.signOut',
@@ -225,6 +225,15 @@ function InitialSettingsPage(props) {
* @returns {String|undefined} the user's wallet balance
*/
const getWalletBalance = (isPaymentItem) => (isPaymentItem ? CurrencyUtils.convertToDisplayString(props.userWallet.currentBalance) : undefined);
+
+ const openPopover = (link, event) => {
+ if (typeof link === 'function') {
+ link().then((url) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, url, popoverAnchor.current));
+ } else if (link) {
+ ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, event, link, popoverAnchor.current);
+ }
+ };
+
return (
{translate(menuItemsData.sectionTranslationKey)}
@@ -259,9 +268,7 @@ function InitialSettingsPage(props) {
ref={popoverAnchor}
hoverAndPressStyle={styles.hoveredComponentBG}
shouldBlockSelection={Boolean(item.link)}
- onSecondaryInteraction={
- !_.isEmpty(item.link) ? (e) => ReportActionContextMenu.showContextMenu(CONST.CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor.current) : undefined
- }
+ onSecondaryInteraction={item.link ? (event) => openPopover(item.link, event) : undefined}
focused={activeRoute && item.routeName && activeRoute.toLowerCase().replaceAll('_', '') === item.routeName.toLowerCase().replaceAll('/', '')}
isPaneMenu
/>
@@ -313,6 +320,7 @@ function InitialSettingsPage(props) {
errors={lodashGet(props.currentUserPersonalDetails, 'errorFields.avatar', null)}
errorRowStyles={[styles.mt6]}
onErrorClose={PersonalDetails.clearAvatarErrors}
+ onViewPhotoPress={() => Navigation.navigate(ROUTES.PROFILE_AVATAR.getRoute(accountID))}
previewSource={UserUtils.getFullSizeAvatar(avatarURL, accountID)}
originalFileName={currentUserDetails.originalFileName}
headerTitle={props.translate('profilePage.profileAvatar')}
diff --git a/src/pages/settings/Report/NotificationPreferencePage.js b/src/pages/settings/Report/NotificationPreferencePage.js
deleted file mode 100644
index c6044bd81efe..000000000000
--- a/src/pages/settings/Report/NotificationPreferencePage.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import React from 'react';
-import _ from 'underscore';
-import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import SelectionList from '@components/SelectionList';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import compose from '@libs/compose';
-import * as ReportUtils from '@libs/ReportUtils';
-import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
-import reportPropTypes from '@pages/reportPropTypes';
-import * as Report from '@userActions/Report';
-import CONST from '@src/CONST';
-
-const propTypes = {
- ...withLocalizePropTypes,
-
- /** The report for which we are setting notification preferences */
- report: reportPropTypes.isRequired,
-};
-
-function NotificationPreferencePage(props) {
- const shouldDisableNotificationPreferences = ReportUtils.isArchivedRoom(props.report);
- const notificationPreferenceOptions = _.map(
- _.filter(_.values(CONST.REPORT.NOTIFICATION_PREFERENCE), (pref) => pref !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN),
- (preference) => ({
- value: preference,
- text: props.translate(`notificationPreferencesPage.notificationPreferences.${preference}`),
- keyForList: preference,
- isSelected: preference === props.report.notificationPreference,
- }),
- );
-
- return (
-
-
- ReportUtils.goBackToDetailsPage(props.report)}
- />
-
- Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, option.value, true, undefined, undefined, props.report)
- }
- initiallyFocusedOptionKey={_.find(notificationPreferenceOptions, (locale) => locale.isSelected).keyForList}
- />
-
-
- );
-}
-
-NotificationPreferencePage.displayName = 'NotificationPreferencePage';
-NotificationPreferencePage.propTypes = propTypes;
-
-export default compose(withLocalize, withReportOrNotFound())(NotificationPreferencePage);
diff --git a/src/pages/settings/Report/NotificationPreferencePage.tsx b/src/pages/settings/Report/NotificationPreferencePage.tsx
new file mode 100644
index 000000000000..05f3483f7ce8
--- /dev/null
+++ b/src/pages/settings/Report/NotificationPreferencePage.tsx
@@ -0,0 +1,54 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React from 'react';
+import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import useLocalize from '@hooks/useLocalize';
+import * as ReportUtils from '@libs/ReportUtils';
+import type {ReportSettingsNavigatorParamList} from '@navigation/types';
+import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
+import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound';
+import * as ReportActions from '@userActions/Report';
+import CONST from '@src/CONST';
+import type SCREENS from '@src/SCREENS';
+
+type NotificationPreferencePageProps = WithReportOrNotFoundProps & StackScreenProps;
+
+function NotificationPreferencePage({report}: NotificationPreferencePageProps) {
+ const {translate} = useLocalize();
+ const shouldDisableNotificationPreferences = ReportUtils.isArchivedRoom(report);
+ const notificationPreferenceOptions = Object.values(CONST.REPORT.NOTIFICATION_PREFERENCE)
+ .filter((pref) => pref !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN)
+ .map((preference) => ({
+ value: preference,
+ text: translate(`notificationPreferencesPage.notificationPreferences.${preference}`),
+ keyForList: preference,
+ isSelected: preference === report?.notificationPreference,
+ }));
+
+ return (
+
+
+ ReportUtils.goBackToDetailsPage(report)}
+ />
+
+ report && ReportActions.updateNotificationPreference(report.reportID, report.notificationPreference, option.value, true, undefined, undefined, report)
+ }
+ initiallyFocusedOptionKey={notificationPreferenceOptions.find((locale) => locale.isSelected)?.keyForList}
+ />
+
+
+ );
+}
+
+NotificationPreferencePage.displayName = 'NotificationPreferencePage';
+
+export default withReportOrNotFound()(NotificationPreferencePage);
diff --git a/src/pages/settings/Report/ReportSettingsPage.js b/src/pages/settings/Report/ReportSettingsPage.tsx
similarity index 75%
rename from src/pages/settings/Report/ReportSettingsPage.js
rename to src/pages/settings/Report/ReportSettingsPage.tsx
index c7cfd9c7850d..c1839b44553d 100644
--- a/src/pages/settings/Report/ReportSettingsPage.js
+++ b/src/pages/settings/Report/ReportSettingsPage.tsx
@@ -1,9 +1,6 @@
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
import React, {useMemo} from 'react';
import {ScrollView, View} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import DisplayNames from '@components/DisplayNames';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
@@ -15,71 +12,46 @@ import ScreenWrapper from '@components/ScreenWrapper';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
import {getGroupChatName} from '@libs/GroupChatUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
+import type {ReportSettingsNavigatorParamList} from '@navigation/types';
import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
-import reportPropTypes from '@pages/reportPropTypes';
-import * as Report from '@userActions/Report';
+import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound';
+import * as ReportActions from '@userActions/Report';
import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
-const propTypes = {
- /** Route params */
- route: PropTypes.shape({
- params: PropTypes.shape({
- /** Report ID passed via route r/:reportID/settings */
- reportID: PropTypes.string,
- }),
- }).isRequired,
+type ReportSettingsPageProps = WithReportOrNotFoundProps & StackScreenProps;
- /* Onyx Props */
-
- /** The active report */
- report: reportPropTypes.isRequired,
-
- /** The policies which the user has access to and which the report could be tied to */
- policies: PropTypes.shape({
- /** The policy name */
- name: PropTypes.string,
-
- /** ID of the policy */
- id: PropTypes.string,
- }),
-};
-
-const defaultProps = {
- policies: {},
-};
-
-function ReportSettingsPage(props) {
+function ReportSettingsPage({report, policies}: ReportSettingsPageProps) {
+ const reportID = report?.reportID ?? '';
const styles = useThemeStyles();
- const {report, policies} = props;
const {translate} = useLocalize();
// The workspace the report is on, null if the user isn't a member of the workspace
- const linkedWorkspace = useMemo(() => _.find(policies, (policy) => policy && policy.id === report.policyID), [policies, report.policyID]);
+ const linkedWorkspace = useMemo(() => Object.values(policies ?? {}).find((policy) => policy && policy.id === report?.policyID) ?? null, [policies, report?.policyID]);
const shouldDisableRename = useMemo(() => ReportUtils.shouldDisableRename(report, linkedWorkspace), [report, linkedWorkspace]);
const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
// We only want policy owners and admins to be able to modify the welcome message, but not in thread chat
const shouldDisableWelcomeMessage = ReportUtils.shouldDisableWelcomeMessage(report, linkedWorkspace);
- const shouldDisableSettings = _.isEmpty(report) || ReportUtils.isArchivedRoom(report);
+ const shouldDisableSettings = isEmptyObject(report) || ReportUtils.isArchivedRoom(report);
const shouldShowRoomName = !ReportUtils.isPolicyExpenseChat(report) && !ReportUtils.isChatThread(report);
const notificationPreference =
- report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN
+ report?.notificationPreference && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN
? translate(`notificationPreferencesPage.notificationPreferences.${report.notificationPreference}`)
: '';
- const writeCapability = ReportUtils.isAdminRoom(report) ? CONST.REPORT.WRITE_CAPABILITIES.ADMINS : report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL;
+ const writeCapability = ReportUtils.isAdminRoom(report) ? CONST.REPORT.WRITE_CAPABILITIES.ADMINS : report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL;
const writeCapabilityText = translate(`writeCapabilityPage.writeCapability.${writeCapability}`);
const shouldAllowWriteCapabilityEditing = useMemo(() => ReportUtils.canEditWriteCapability(report, linkedWorkspace), [report, linkedWorkspace]);
- const shouldShowNotificationPref = !isMoneyRequestReport && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ const shouldShowNotificationPref = !isMoneyRequestReport && report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
const roomNameLabel = translate(isMoneyRequestReport ? 'workspace.editor.nameInputLabel' : 'newRoomPage.roomName');
- const reportName = ReportUtils.isGroupChat(props.report) ? getGroupChatName(props.report) : ReportUtils.getReportName(props.report);
+ const reportName = ReportUtils.isGroupChat(report) ? getGroupChatName(report) : ReportUtils.getReportName(report);
const shouldShowWriteCapability = !isMoneyRequestReport;
@@ -88,7 +60,7 @@ function ReportSettingsPage(props) {
Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report.reportID))}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID))}
/>
{shouldShowNotificationPref && (
@@ -96,15 +68,15 @@ function ReportSettingsPage(props) {
shouldShowRightIcon
title={notificationPreference}
description={translate('notificationPreferencesPage.label')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_NOTIFICATION_PREFERENCES.getRoute(reportID))}
/>
)}
{shouldShowRoomName && (
Report.clearPolicyRoomNameErrors(report.reportID)}
+ onClose={() => ReportActions.clearPolicyRoomNameErrors(reportID)}
>
{shouldDisableRename ? (
@@ -115,7 +87,7 @@ function ReportSettingsPage(props) {
{roomNameLabel}
Navigation.navigate(ROUTES.REPORT_SETTINGS_ROOM_NAME.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_ROOM_NAME.getRoute(reportID))}
/>
)}
@@ -138,7 +110,7 @@ function ReportSettingsPage(props) {
shouldShowRightIcon
title={writeCapabilityText}
description={translate('writeCapabilityPage.label')}
- onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.getRoute(reportID))}
/>
) : (
@@ -157,7 +129,7 @@ function ReportSettingsPage(props) {
))}
- {Boolean(linkedWorkspace) && (
+ {linkedWorkspace !== null && (
)}
- {Boolean(report.visibility) && (
+ {report?.visibility !== undefined && (
Navigation.navigate(ROUTES.REPORT_WELCOME_MESSAGE.getRoute(report.reportID))}
+ onPress={() => Navigation.navigate(ROUTES.REPORT_WELCOME_MESSAGE.getRoute(reportID))}
shouldShowRightIcon
/>
)}
@@ -206,14 +178,6 @@ function ReportSettingsPage(props) {
);
}
-ReportSettingsPage.propTypes = propTypes;
-ReportSettingsPage.defaultProps = defaultProps;
ReportSettingsPage.displayName = 'ReportSettingsPage';
-export default compose(
- withReportOrNotFound(),
- withOnyx({
- policies: {
- key: ONYXKEYS.COLLECTION.POLICY,
- },
- }),
-)(ReportSettingsPage);
+
+export default withReportOrNotFound()(ReportSettingsPage);
diff --git a/src/pages/settings/Report/RoomNamePage.js b/src/pages/settings/Report/RoomNamePage.tsx
similarity index 71%
rename from src/pages/settings/Report/RoomNamePage.js
rename to src/pages/settings/Report/RoomNamePage.tsx
index 5f64faca50fc..30226bc6f502 100644
--- a/src/pages/settings/Report/RoomNamePage.js
+++ b/src/pages/settings/Report/RoomNamePage.tsx
@@ -1,59 +1,55 @@
import {useIsFocused} from '@react-navigation/native';
-import PropTypes from 'prop-types';
+import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useRef} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
+import type {OnyxFormValuesFields} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import type {AnimatedTextInputRef} from '@components/RNTextInput';
import RoomNameInput from '@components/RoomNameInput';
import ScreenWrapper from '@components/ScreenWrapper';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
+import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
import * as ErrorUtils from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import * as ValidationUtils from '@libs/ValidationUtils';
+import type {ReportSettingsNavigatorParamList} from '@navigation/types';
import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
-import reportPropTypes from '@pages/reportPropTypes';
-import * as Report from '@userActions/Report';
+import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound';
+import * as ReportActions from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {Policy, Report} from '@src/types/onyx';
-const propTypes = {
- ...withLocalizePropTypes,
-
- /** The room report for which the name is being edited */
- report: reportPropTypes.isRequired,
-
+type RoomNamePageOnyxProps = {
/** All reports shared with the user */
- reports: PropTypes.objectOf(reportPropTypes),
+ reports: OnyxCollection;
/** Policy of the report for which the name is being edited */
- policy: PropTypes.shape({
- role: PropTypes.string,
- owner: PropTypes.string,
- }),
-};
-const defaultProps = {
- reports: {},
- policy: {},
+ policy: OnyxEntry;
};
-function RoomNamePage({policy, report, reports, translate}) {
+type RoomNamePageProps = RoomNamePageOnyxProps & WithReportOrNotFoundProps & StackScreenProps;
+
+function RoomNamePage({report, policy, reports}: RoomNamePageProps) {
const styles = useThemeStyles();
- const roomNameInputRef = useRef(null);
+ const roomNameInputRef = useRef(null);
const isFocused = useIsFocused();
+ const {translate} = useLocalize();
const validate = useCallback(
- (values) => {
+ (values: OnyxFormValuesFields) => {
const errors = {};
// We should skip validation hence we return an empty errors and we skip Form submission on the onSubmit method
- if (values.roomName === report.reportName) {
+ if (values.roomName === report?.reportName) {
return errors;
}
@@ -66,7 +62,7 @@ function RoomNamePage({policy, report, reports, translate}) {
} else if (ValidationUtils.isReservedRoomName(values.roomName)) {
// Certain names are reserved for default rooms and should not be used for policy rooms.
ErrorUtils.addErrorMessage(errors, 'roomName', ['newRoomPage.roomNameReservedError', {reservedName: values.roomName}]);
- } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, report.policyID)) {
+ } else if (ValidationUtils.isExistingRoomName(values.roomName, reports, report?.policyID ?? '')) {
// The room name can't be set to one that already exists on the policy
ErrorUtils.addErrorMessage(errors, 'roomName', 'newRoomPage.roomAlreadyExistsError');
}
@@ -78,19 +74,19 @@ function RoomNamePage({policy, report, reports, translate}) {
return (
roomNameInputRef.current && roomNameInputRef.current.focus()}
+ onEntryTransitionEnd={() => roomNameInputRef.current?.focus()}
includeSafeAreaPaddingBottom={false}
testID={RoomNamePage.displayName}
>
Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report.reportID))}
+ onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? ''))}
/>
Report.updatePolicyRoomNameAndNavigate(report, values.roomName)}
+ onSubmit={(values) => report && ReportActions.updatePolicyRoomNameAndNavigate(report, values.roomName)}
validate={validate}
submitButtonText={translate('common.save')}
enabledWhenOffline
@@ -100,7 +96,7 @@ function RoomNamePage({policy, report, reports, translate}) {
InputComponent={RoomNameInput}
ref={roomNameInputRef}
inputID="roomName"
- defaultValue={report.reportName}
+ defaultValue={report?.reportName}
isFocused={isFocused}
/>
@@ -110,19 +106,15 @@ function RoomNamePage({policy, report, reports, translate}) {
);
}
-RoomNamePage.propTypes = propTypes;
-RoomNamePage.defaultProps = defaultProps;
RoomNamePage.displayName = 'RoomNamePage';
-export default compose(
- withLocalize,
- withReportOrNotFound(),
- withOnyx({
+export default withReportOrNotFound()(
+ withOnyx({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`,
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`,
},
- }),
-)(RoomNamePage);
+ })(RoomNamePage),
+);
diff --git a/src/pages/settings/Report/WriteCapabilityPage.js b/src/pages/settings/Report/WriteCapabilityPage.js
deleted file mode 100644
index fc587b028f7d..000000000000
--- a/src/pages/settings/Report/WriteCapabilityPage.js
+++ /dev/null
@@ -1,75 +0,0 @@
-import React from 'react';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
-import HeaderWithBackButton from '@components/HeaderWithBackButton';
-import ScreenWrapper from '@components/ScreenWrapper';
-import SelectionList from '@components/SelectionList';
-import withLocalize, {withLocalizePropTypes} from '@components/withLocalize';
-import compose from '@libs/compose';
-import Navigation from '@libs/Navigation/Navigation';
-import * as ReportUtils from '@libs/ReportUtils';
-import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
-import reportPropTypes from '@pages/reportPropTypes';
-import {policyDefaultProps, policyPropTypes} from '@pages/workspace/withPolicy';
-import * as Report from '@userActions/Report';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import ROUTES from '@src/ROUTES';
-
-const propTypes = {
- ...withLocalizePropTypes,
- ...policyPropTypes,
-
- /** The report for which we are setting write capability */
- report: reportPropTypes.isRequired,
-};
-
-const defaultProps = {
- ...policyDefaultProps,
-};
-
-function WriteCapabilityPage(props) {
- const writeCapabilityOptions = _.map(CONST.REPORT.WRITE_CAPABILITIES, (value) => ({
- value,
- text: props.translate(`writeCapabilityPage.writeCapability.${value}`),
- keyForList: value,
- isSelected: value === (props.report.writeCapability || CONST.REPORT.WRITE_CAPABILITIES.ALL),
- }));
-
- const isAbleToEdit = ReportUtils.canEditWriteCapability(props.report, props.policy);
-
- return (
-
-
- Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(props.report.reportID))}
- />
- Report.updateWriteCapabilityAndNavigate(props.report, option.value)}
- initiallyFocusedOptionKey={_.find(writeCapabilityOptions, (locale) => locale.isSelected).keyForList}
- />
-
-
- );
-}
-
-WriteCapabilityPage.displayName = 'WriteCapabilityPage';
-WriteCapabilityPage.propTypes = propTypes;
-WriteCapabilityPage.defaultProps = defaultProps;
-
-export default compose(
- withLocalize,
- withReportOrNotFound(),
- withOnyx({
- policy: {
- key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report.policyID}`,
- },
- }),
-)(WriteCapabilityPage);
diff --git a/src/pages/settings/Report/WriteCapabilityPage.tsx b/src/pages/settings/Report/WriteCapabilityPage.tsx
new file mode 100644
index 000000000000..5f5fe73e5199
--- /dev/null
+++ b/src/pages/settings/Report/WriteCapabilityPage.tsx
@@ -0,0 +1,71 @@
+import type {StackScreenProps} from '@react-navigation/stack';
+import React from 'react';
+import {withOnyx} from 'react-native-onyx';
+import type {OnyxEntry} from 'react-native-onyx';
+import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import SelectionList from '@components/SelectionList';
+import useLocalize from '@hooks/useLocalize';
+import Navigation from '@libs/Navigation/Navigation';
+import * as ReportUtils from '@libs/ReportUtils';
+import type {ReportSettingsNavigatorParamList} from '@navigation/types';
+import withReportOrNotFound from '@pages/home/report/withReportOrNotFound';
+import type {WithReportOrNotFoundProps} from '@pages/home/report/withReportOrNotFound';
+import * as ReportActions from '@userActions/Report';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {Policy} from '@src/types/onyx';
+
+type WriteCapabilityPageOnyxProps = {
+ /** The policy object for the current route */
+ policy: OnyxEntry;
+};
+
+type WriteCapabilityPageProps = WriteCapabilityPageOnyxProps &
+ WithReportOrNotFoundProps &
+ StackScreenProps;
+
+function WriteCapabilityPage({report, policy}: WriteCapabilityPageProps) {
+ const {translate} = useLocalize();
+ const writeCapabilityOptions = Object.values(CONST.REPORT.WRITE_CAPABILITIES).map((value) => ({
+ value,
+ text: translate(`writeCapabilityPage.writeCapability.${value}`),
+ keyForList: value,
+ isSelected: value === (report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL),
+ }));
+
+ const isAbleToEdit = ReportUtils.canEditWriteCapability(report, policy);
+
+ return (
+
+
+ Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? ''))}
+ />
+ report && ReportActions.updateWriteCapabilityAndNavigate(report, option.value)}
+ initiallyFocusedOptionKey={writeCapabilityOptions.find((locale) => locale.isSelected)?.keyForList}
+ />
+
+
+ );
+}
+
+WriteCapabilityPage.displayName = 'WriteCapabilityPage';
+
+export default withReportOrNotFound()(
+ withOnyx({
+ policy: {
+ key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`,
+ },
+ })(WriteCapabilityPage),
+);
diff --git a/src/pages/workspace/WorkspaceNewRoomPage.js b/src/pages/workspace/WorkspaceNewRoomPage.js
index b616b519ff32..fa128b2bfce6 100644
--- a/src/pages/workspace/WorkspaceNewRoomPage.js
+++ b/src/pages/workspace/WorkspaceNewRoomPage.js
@@ -106,11 +106,14 @@ function WorkspaceNewRoomPage(props) {
const workspaceOptions = useMemo(
() =>
- _.map(PolicyUtils.getActivePolicies(props.policies), (policy) => ({
- label: policy.name,
- key: policy.id,
- value: policy.id,
- })),
+ _.map(
+ _.filter(PolicyUtils.getActivePolicies(props.policies), (policy) => policy.type !== CONST.POLICY.TYPE.PERSONAL),
+ (policy) => ({
+ label: policy.name,
+ key: policy.id,
+ value: policy.id,
+ }),
+ ),
[props.policies],
);
const [policyID, setPolicyID] = useState(() => {
diff --git a/src/types/onyx/Form.ts b/src/types/onyx/Form.ts
index 48f386afcbb0..a73f8d4c9732 100644
--- a/src/types/onyx/Form.ts
+++ b/src/types/onyx/Form.ts
@@ -68,6 +68,10 @@ type CloseAccountForm = Form<{
phoneOrEmail: string;
}>;
+type RoomNameForm = Form<{
+ roomName: string;
+}>;
+
export default Form;
export type {
@@ -84,4 +88,5 @@ export type {
WorkspaceSettingsForm,
ReportFieldEditForm,
CloseAccountForm,
+ RoomNameForm,
};
diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts
index f55b3b797bf0..784cd546a961 100644
--- a/src/types/onyx/Policy.ts
+++ b/src/types/onyx/Policy.ts
@@ -76,9 +76,6 @@ type Policy = {
/** The custom units data for this policy */
customUnits?: Record;
- /** Whether chat rooms can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */
- areChatRoomsEnabled: boolean;
-
/** Whether policy expense chats can be created and used on this policy. Enabled manually by CQ/JS snippet. Always true for free policies. */
isPolicyExpenseChatEnabled: boolean;
diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts
index 46bb1ad596a1..4728c8872e75 100644
--- a/src/types/onyx/index.ts
+++ b/src/types/onyx/index.ts
@@ -19,6 +19,7 @@ import type {
NewRoomForm,
PrivateNotesForm,
ReportFieldEditForm,
+ RoomNameForm,
WorkspaceSettingsForm,
} from './Form';
import type Form from './Form';
@@ -167,4 +168,5 @@ export type {
IntroSchoolPrincipalForm,
PrivateNotesForm,
ReportFieldEditForm,
+ RoomNameForm,
};
diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js
index 04246c1c438a..3e40063dd040 100644
--- a/tests/utils/LHNTestUtils.js
+++ b/tests/utils/LHNTestUtils.js
@@ -252,7 +252,6 @@ function getFakePolicy(id = 1, name = 'Workspace-Test-001') {
avatar: '',
employeeList: [],
isPolicyExpenseChatEnabled: true,
- areChatRoomsEnabled: true,
lastModified: 1697323926777105,
autoReporting: true,
autoReportingFrequency: 'immediate',
diff --git a/tests/utils/collections/policies.ts b/tests/utils/collections/policies.ts
index 4223c7e41941..7ecf152122d3 100644
--- a/tests/utils/collections/policies.ts
+++ b/tests/utils/collections/policies.ts
@@ -7,7 +7,6 @@ export default function createRandomPolicy(index: number): Policy {
id: index.toString(),
name: randWord(),
type: rand(Object.values(CONST.POLICY.TYPE)),
- areChatRoomsEnabled: randBoolean(),
autoReporting: randBoolean(),
isPolicyExpenseChatEnabled: randBoolean(),
autoReportingFrequency: rand(Object.values(CONST.POLICY.AUTO_REPORTING_FREQUENCIES)),