diff --git a/patches/@react-navigation+stack+6.3.16+002+dontDetachScreen.patch b/patches/@react-navigation+stack+6.3.16+002+dontDetachScreen.patch index 877521094cd4..c65ebbb98007 100644 --- a/patches/@react-navigation+stack+6.3.16+002+dontDetachScreen.patch +++ b/patches/@react-navigation+stack+6.3.16+002+dontDetachScreen.patch @@ -43,7 +43,7 @@ index 7558eb3..b7bb75e 100644 }) : STATE_TRANSITIONING_OR_BELOW_TOP; } + -+ const isHomeScreenAndNotOnTop = (route.name === 'BottomTabNavigator' || route.name === 'Settings_Root') && isScreenActive !== STATE_ON_TOP; ++ const isHomeScreenAndNotOnTop = (route.name === 'BottomTabNavigator' || route.name === 'Workspace_Initial') && isScreenActive !== STATE_ON_TOP; + const { headerShown = true, diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 8de1757fc1b4..f89ba19736ed 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -450,128 +450,128 @@ const ROUTES = { WORKSPACE_NEW: 'workspace/new', WORKSPACE_NEW_ROOM: 'workspace/new-room', WORKSPACE_INITIAL: { - route: 'workspace/:policyID', - getRoute: (policyID: string) => `workspace/${policyID}` as const, + route: 'settings/workspaces/:policyID', + getRoute: (policyID: string) => `settings/workspaces/${policyID}` as const, }, WORKSPACE_INVITE: { - route: 'workspace/:policyID/invite', - getRoute: (policyID: string) => `workspace/${policyID}/invite` as const, + route: 'settings/workspaces/:policyID/invite', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/invite` as const, }, WORKSPACE_INVITE_MESSAGE: { - route: 'workspace/:policyID/invite-message', - getRoute: (policyID: string) => `workspace/${policyID}/invite-message` as const, + route: 'settings/workspaces/:policyID/invite-message', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/invite-message` as const, }, WORKSPACE_PROFILE: { - route: 'workspace/:policyID/profile', - getRoute: (policyID: string) => `workspace/${policyID}/profile` as const, + route: 'settings/workspaces/:policyID/profile', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile` as const, }, WORKSPACE_PROFILE_CURRENCY: { - route: 'workspace/:policyID/profile/currency', - getRoute: (policyID: string) => `workspace/${policyID}/profile/currency` as const, + route: 'settings/workspaces/:policyID/profile/currency', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/currency` as const, }, WORKSPACE_PROFILE_NAME: { - route: 'workspace/:policyID/profile/name', - getRoute: (policyID: string) => `workspace/${policyID}/profile/name` as const, + route: 'settings/workspaces/:policyID/profile/name', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/name` as const, }, WORKSPACE_PROFILE_DESCRIPTION: { - route: 'workspace/:policyID/profile/description', - getRoute: (policyID: string) => `workspace/${policyID}/profile/description` as const, + route: 'settings/workspaces/:policyID/profile/description', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/description` as const, }, WORKSPACE_PROFILE_SHARE: { - route: 'workspace/:policyID/profile/share', - getRoute: (policyID: string) => `workspace/${policyID}/profile/share` as const, + route: 'settings/workspaces/:policyID/profile/share', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/profile/share` as const, }, WORKSPACE_AVATAR: { - route: 'workspace/:policyID/avatar', - getRoute: (policyID: string) => `workspace/${policyID}/avatar` as const, + route: 'settings/workspaces/:policyID/avatar', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/avatar` as const, }, WORKSPACE_JOIN_USER: { - route: 'workspace/:policyID/join', - getRoute: (policyID: string, inviterEmail: string) => `workspace/${policyID}/join?email=${inviterEmail}` as const, + route: 'settings/workspaces/:policyID/join', + getRoute: (policyID: string, inviterEmail: string) => `settings/workspaces/${policyID}/join?email=${inviterEmail}` as const, }, WORKSPACE_SETTINGS_CURRENCY: { - route: 'workspace/:policyID/settings/currency', - getRoute: (policyID: string) => `workspace/${policyID}/settings/currency` as const, + route: 'settings/workspaces/:policyID/settings/currency', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/settings/currency` as const, }, WORKSPACE_WORKFLOWS: { - route: 'workspace/:policyID/workflows', - getRoute: (policyID: string) => `workspace/${policyID}/workflows` as const, + route: 'settings/workspaces/:policyID/workflows', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/workflows` as const, }, WORKSPACE_WORKFLOWS_PAYER: { route: 'workspace/:policyID/settings/workflows/payer', getRoute: (policyId: string) => `workspace/${policyId}/settings/workflows/payer` as const, }, WORKSPACE_WORKFLOWS_APPROVER: { - route: 'workspace/:policyID/settings/workflows/approver', - getRoute: (policyId: string) => `workspace/${policyId}/settings/workflows/approver` as const, + route: 'settings/workspaces/:policyID/settings/workflows/approver', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/settings/workflows/approver` as const, }, WORKSPACE_WORKFLOWS_AUTOREPORTING_FREQUENCY: { - route: 'workspace/:policyID/settings/workflows/auto-reporting-frequency', - getRoute: (policyID: string) => `workspace/${policyID}/settings/workflows/auto-reporting-frequency` as const, + route: 'settings/workspaces/:policyID/settings/workflows/auto-reporting-frequency', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/settings/workflows/auto-reporting-frequency` as const, }, WORKSPACE_WORKFLOWS_AUTOREPORTING_MONTHLY_OFFSET: { - route: 'workspace/:policyID/settings/workflows/auto-reporting-frequency/monthly-offset', - getRoute: (policyID: string) => `workspace/${policyID}/settings/workflows/auto-reporting-frequency/monthly-offset` as const, + route: 'settings/workspaces/:policyID/settings/workflows/auto-reporting-frequency/monthly-offset', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/settings/workflows/auto-reporting-frequency/monthly-offset` as const, }, WORKSPACE_CARD: { - route: 'workspace/:policyID/card', - getRoute: (policyID: string) => `workspace/${policyID}/card` as const, + route: 'settings/workspaces/:policyID/card', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/card` as const, }, WORKSPACE_REIMBURSE: { - route: 'workspace/:policyID/reimburse', - getRoute: (policyID: string) => `workspace/${policyID}/reimburse` as const, + route: 'settings/workspaces/:policyID/reimburse', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/reimburse` as const, }, WORKSPACE_RATE_AND_UNIT: { - route: 'workspace/:policyID/rateandunit', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit` as const, + route: 'settings/workspaces/:policyID/rateandunit', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/rateandunit` as const, }, WORKSPACE_RATE_AND_UNIT_RATE: { - route: 'workspace/:policyID/rateandunit/rate', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit/rate` as const, + route: 'settings/workspaces/:policyID/rateandunit/rate', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/rateandunit/rate` as const, }, WORKSPACE_RATE_AND_UNIT_UNIT: { - route: 'workspace/:policyID/rateandunit/unit', - getRoute: (policyID: string) => `workspace/${policyID}/rateandunit/unit` as const, + route: 'settings/workspaces/:policyID/rateandunit/unit', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/rateandunit/unit` as const, }, WORKSPACE_BILLS: { - route: 'workspace/:policyID/bills', - getRoute: (policyID: string) => `workspace/${policyID}/bills` as const, + route: 'settings/workspaces/:policyID/bills', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/bills` as const, }, WORKSPACE_INVOICES: { - route: 'workspace/:policyID/invoices', - getRoute: (policyID: string) => `workspace/${policyID}/invoices` as const, + route: 'settings/workspaces/:policyID/invoices', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/invoices` as const, }, WORKSPACE_TRAVEL: { - route: 'workspace/:policyID/travel', - getRoute: (policyID: string) => `workspace/${policyID}/travel` as const, + route: 'settings/workspaces/:policyID/travel', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/travel` as const, }, WORKSPACE_MEMBERS: { - route: 'workspace/:policyID/members', - getRoute: (policyID: string) => `workspace/${policyID}/members` as const, + route: 'settings/workspaces/:policyID/members', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/members` as const, }, WORKSPACE_CATEGORIES: { - route: 'workspace/:policyID/categories', - getRoute: (policyID: string) => `workspace/${policyID}/categories` as const, + route: 'settings/workspaces/:policyID/categories', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories` as const, }, WORKSPACE_CATEGORY_SETTINGS: { - route: 'workspace/:policyID/categories/:categoryName', - getRoute: (policyID: string, categoryName: string) => `workspace/${policyID}/categories/${encodeURI(categoryName)}` as const, + route: 'settings/workspaces/:policyID/categories/:categoryName', + getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURI(categoryName)}` as const, }, WORKSPACE_CATEGORIES_SETTINGS: { - route: 'workspace/:policyID/categories/settings', - getRoute: (policyID: string) => `workspace/${policyID}/categories/settings` as const, + route: 'settings/workspaces/:policyID/categories/settings', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories/settings` as const, }, WORKSPACE_MORE_FEATURES: { route: 'workspace/:policyID/more-features', getRoute: (policyID: string) => `workspace/${policyID}/more-features` as const, }, WORKSPACE_CATEGORY_CREATE: { - route: 'workspace/:policyID/categories/new', - getRoute: (policyID: string) => `workspace/${policyID}/categories/new` as const, + route: 'settings/workspaces/:policyID/categories/new', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/categories/new` as const, }, WORKSPACE_TAGS: { - route: 'workspace/:policyID/tags', - getRoute: (policyID: string) => `workspace/${policyID}/tags` as const, + route: 'settings/workspaces/:policyID/tags', + getRoute: (policyID: string) => `settings/workspaces/${policyID}/tags` as const, }, WORKSPACE_TAGS_SETTINGS: { route: 'workspace/:policyID/tags/settings', @@ -582,12 +582,12 @@ const ROUTES = { getRoute: (policyID: string) => `workspace/${policyID}/tags/edit` as const, }, WORKSPACE_MEMBER_DETAILS: { - route: 'workspace/:policyID/members/:accountID', - getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}`, backTo), + route: 'settings/workspaces/:policyID/members/:accountID', + getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/members/${accountID}`, backTo), }, WORKSPACE_MEMBER_ROLE_SELECTION: { - route: 'workspace/:policyID/members/:accountID/role-selection', - getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}/role-selection`, backTo), + route: 'settings/workspaces/:policyID/members/:accountID/role-selection', + getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`settings/workspaces/${policyID}/members/${accountID}/role-selection`, backTo), }, WORKSPACE_DISTANCE_RATES: { route: 'workspace/:policyID/distance-rates', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index e5cf783b553f..dedb022415ca 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -22,6 +22,7 @@ const SCREENS = { VALIDATE_LOGIN: 'ValidateLogin', UNLINK_LOGIN: 'UnlinkLogin', SETTINGS_CENTRAL_PANE: 'SettingsCentralPane', + WORKSPACES_CENTRAL_PANE: 'WorkspacesCentralPane', SETTINGS: { ROOT: 'Settings_Root', SHARE_CODE: 'Settings_Share_Code', diff --git a/src/components/AvatarSkeleton.tsx b/src/components/AvatarSkeleton.tsx index 0887830aa07a..a6781448c3ba 100644 --- a/src/components/AvatarSkeleton.tsx +++ b/src/components/AvatarSkeleton.tsx @@ -1,21 +1,24 @@ import React from 'react'; import {Circle} from 'react-native-svg'; import useTheme from '@hooks/useTheme'; +import variables from '@styles/variables'; import SkeletonViewContentLoader from './SkeletonViewContentLoader'; function AvatarSkeleton() { const theme = useTheme(); + const skeletonCircleRadius = variables.componentSizeSmall / 2; + return ( ); diff --git a/src/components/AvatarWithIndicator.tsx b/src/components/AvatarWithIndicator.tsx index 2fd733d4b072..42b91b3d2d71 100644 --- a/src/components/AvatarWithIndicator.tsx +++ b/src/components/AvatarWithIndicator.tsx @@ -2,6 +2,7 @@ import React from 'react'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import * as UserUtils from '@libs/UserUtils'; +import CONST from '@src/CONST'; import Avatar from './Avatar'; import AvatarSkeleton from './AvatarSkeleton'; import * as Expensicons from './Icon/Expensicons'; @@ -33,6 +34,7 @@ function AvatarWithIndicator({source, tooltipText = '', fallbackIcon = Expensico ) : ( <> diff --git a/src/components/FloatingActionButton.tsx b/src/components/FloatingActionButton.tsx index 88938f31cd79..b9c52ad397ec 100644 --- a/src/components/FloatingActionButton.tsx +++ b/src/components/FloatingActionButton.tsx @@ -104,13 +104,13 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo }; return ( - - - + + + { fabPressable.current = el ?? null; @@ -136,9 +136,9 @@ function FloatingActionButton({onPress, isActive, accessibilityLabel, role}: Flo /> - - - + + + ); } diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index 3a1c35d46c94..21f3e9a3b605 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {Keyboard, StyleSheet, View} from 'react-native'; +import Avatar from '@components/Avatar'; import AvatarWithDisplayName from '@components/AvatarWithDisplayName'; import Header from '@components/Header'; import Icon from '@components/Icon'; @@ -32,7 +33,8 @@ function HeaderWithBackButton({ onThreeDotsButtonPress = () => {}, report = null, policy, - shouldShowAvatarWithDisplay = false, + policyAvatar, + shouldShowReportAvatarWithDisplay = false, shouldShowBackButton = true, shouldShowBorderBottom = false, shouldShowCloseButton = false, @@ -58,6 +60,7 @@ function HeaderWithBackButton({ shouldOverlay = false, singleExecution = (func) => func, shouldNavigateToTopMostReport = false, + style, }: HeaderWithBackButtonProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -81,6 +84,7 @@ function HeaderWithBackButton({ shouldShowBorderBottom && styles.borderBottom, shouldShowBackButton && styles.pl2, shouldOverlay && StyleSheet.absoluteFillObject, + style, ]} > @@ -118,7 +122,15 @@ function HeaderWithBackButton({ additionalStyles={[styles.mr2]} /> )} - {shouldShowAvatarWithDisplay ? ( + {policyAvatar && ( + + )} + {shouldShowReportAvatarWithDisplay ? ( & { /** Data to display a step counter in the header */ stepCounter?: StepCounterParams; - /** Whether we should show an avatar */ - shouldShowAvatarWithDisplay?: boolean; + /** Whether we should show a report avatar */ + shouldShowReportAvatarWithDisplay?: boolean; /** Parent report, if provided it will override props.report for AvatarWithDisplay */ parentReport?: OnyxEntry; @@ -121,6 +123,12 @@ type HeaderWithBackButtonProps = Partial & { /** Whether we should overlay the 3 dots menu */ shouldOverlayDots?: boolean; + + /** Policy avatar to display in the header */ + policyAvatar?: Icon; + + /** Additional styles to add to the component */ + style?: StyleProp; }; export type {ThreeDotsMenuItem}; diff --git a/src/components/Indicator.tsx b/src/components/Indicator.tsx index 1420a6abe189..e3d226a17999 100644 --- a/src/components/Indicator.tsx +++ b/src/components/Indicator.tsx @@ -1,17 +1,24 @@ import React from 'react'; import {StyleSheet, View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import * as PolicyUtils from '@libs/PolicyUtils'; import * as UserUtils from '@libs/UserUtils'; import * as PaymentMethods from '@userActions/PaymentMethods'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {BankAccountList, FundList, LoginList, UserWallet, WalletTerms} from '@src/types/onyx'; +import type {BankAccountList, FundList, LoginList, Policy, PolicyMembers, ReimbursementAccount, UserWallet, WalletTerms} from '@src/types/onyx'; type CheckingMethod = () => boolean; type IndicatorOnyxProps = { + /** The employee list of all policies (coming from Onyx) */ + allPolicyMembers: OnyxCollection; + + /** All the user's policies (from Onyx via withFullPolicy) */ + policies: OnyxCollection; + /** List of bank accounts */ bankAccountList: OnyxEntry; @@ -21,6 +28,9 @@ type IndicatorOnyxProps = { /** The user's wallet (coming from Onyx) */ userWallet: OnyxEntry; + /** Bank account attached to free plan */ + reimbursementAccount: OnyxEntry; + /** Information about the user accepting the terms for payments */ walletTerms: OnyxEntry; @@ -30,16 +40,25 @@ type IndicatorOnyxProps = { type IndicatorProps = IndicatorOnyxProps; -function Indicator({bankAccountList, fundList, userWallet, walletTerms, loginList}: IndicatorOnyxProps) { +function Indicator({reimbursementAccount, allPolicyMembers, policies, bankAccountList, fundList, userWallet, walletTerms, loginList}: IndicatorOnyxProps) { const theme = useTheme(); const styles = useThemeStyles(); + // If a policy was just deleted from Onyx, then Onyx will pass a null value to the props, and + // those should be cleaned out before doing any error checking + const cleanPolicies = Object.fromEntries(Object.entries(policies ?? {}).filter(([, policy]) => policy?.id)); + const cleanAllPolicyMembers = Object.fromEntries(Object.entries(allPolicyMembers ?? {}).filter(([, policyMembers]) => !!policyMembers)); + // All of the error & info-checking methods are put into an array. This is so that using _.some() will return // early as soon as the first error / info condition is returned. This makes the checks very efficient since // we only care if a single error / info condition exists anywhere. const errorCheckingMethods: CheckingMethod[] = [ () => Object.keys(userWallet?.errors ?? {}).length > 0, () => PaymentMethods.hasPaymentMethodError(bankAccountList, fundList), + () => Object.values(cleanPolicies).some(PolicyUtils.hasPolicyError), + () => Object.values(cleanPolicies).some(PolicyUtils.hasCustomUnitsError), + () => Object.values(cleanAllPolicyMembers).some(PolicyUtils.hasPolicyMemberError), + () => Object.keys(reimbursementAccount?.errors ?? {}).length > 0, () => !!loginList && UserUtils.hasLoginListError(loginList), // Wallet term errors that are not caused by an IOU (we show the red brick indicator for those in the LHN instead) @@ -58,9 +77,19 @@ function Indicator({bankAccountList, fundList, userWallet, walletTerms, loginLis Indicator.displayName = 'Indicator'; export default withOnyx({ + allPolicyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, bankAccountList: { key: ONYXKEYS.BANK_ACCOUNT_LIST, }, + // @ts-expect-error: ONYXKEYS.REIMBURSEMENT_ACCOUNT is conflicting with ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM + reimbursementAccount: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + }, fundList: { key: ONYXKEYS.FUND_LIST, }, diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index e0d6c39623ed..74fec2c606af 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -97,7 +97,7 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money return ( ; }; -type WorkspaceSwitcherButtonProps = {activeWorkspaceID?: string} & WorkspaceSwitcherButtonOnyxProps; +type WorkspaceSwitcherButtonProps = WorkspaceSwitcherButtonOnyxProps; -function WorkspaceSwitcherButton({activeWorkspaceID, policy}: WorkspaceSwitcherButtonProps) { +function WorkspaceSwitcherButton({policy}: WorkspaceSwitcherButtonProps) { const {translate} = useLocalize(); const theme = useTheme(); const {source, name, type} = useMemo(() => { - if (!activeWorkspaceID) { + if (!policy) { return {source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR}; } @@ -36,7 +34,7 @@ function WorkspaceSwitcherButton({activeWorkspaceID, policy}: WorkspaceSwitcherB name: policy?.name ?? '', type: CONST.ICON_TYPE_WORKSPACE, }; - }, [policy, activeWorkspaceID]); + }, [policy]); return ( @@ -71,8 +69,4 @@ function WorkspaceSwitcherButton({activeWorkspaceID, policy}: WorkspaceSwitcherB WorkspaceSwitcherButton.displayName = 'WorkspaceSwitcherButton'; -export default withOnyx({ - policy: { - key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, - }, -})(WorkspaceSwitcherButton); +export default WorkspaceSwitcherButton; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx index 3f18e0c3c30c..897ac9c71a84 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx @@ -186,13 +186,20 @@ const NewTeachersUniteNavigator = createModalStackNavigator require('../../../pages/TeachersUnite/ImTeacherPage').default as React.ComponentType, }); -const AccountSettingsModalStackNavigator = createModalStackNavigator( +const WorkspaceSettingsModalStackNavigator = createModalStackNavigator( { - [SCREENS.SETTINGS.PREFERENCES.ROOT]: () => require('../../../pages/settings/Preferences/PreferencesPage').default as React.ComponentType, - [SCREENS.SETTINGS.SECURITY]: () => require('../../../pages/settings/Security/SecuritySettingsPage').default as React.ComponentType, - [SCREENS.SETTINGS.PROFILE.ROOT]: () => require('../../../pages/settings/Profile/ProfilePage').default as React.ComponentType, - [SCREENS.SETTINGS.WALLET.ROOT]: () => require('../../../pages/settings/Wallet/WalletPage').default as React.ComponentType, - [SCREENS.SETTINGS.ABOUT]: () => require('../../../pages/settings/AboutPage/AboutPage').default as React.ComponentType, + [SCREENS.WORKSPACE.PROFILE]: () => require('../../../pages/workspace/WorkspaceProfilePage').default as React.ComponentType, + [SCREENS.WORKSPACE.CARD]: () => require('../../../pages/workspace/card/WorkspaceCardPage').default as React.ComponentType, + [SCREENS.WORKSPACE.WORKFLOWS]: () => require('../../../pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType, + [SCREENS.WORKSPACE.BILLS]: () => require('../../../pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.INVOICES]: () => require('../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MEMBERS]: () => require('../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType, + [SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.MORE_FEATURES]: () => require('../../../pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType, + [SCREENS.WORKSPACE.TAGS]: () => require('../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType, + [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType, }, (styles) => ({cardStyle: styles.navigationScreenCardStyle, headerShown: false}), ); @@ -313,7 +320,6 @@ const ProcessMoneyRequestHoldStackNavigator = createModalStackNavigator({ }); export { - AccountSettingsModalStackNavigator, AddPersonalBankAccountModalStackNavigator, DetailsModalStackNavigator, OnboardEngagementModalStackNavigator, @@ -342,4 +348,5 @@ export { TaskModalStackNavigator, WalletStatementStackNavigator, ProcessMoneyRequestHoldStackNavigator, + WorkspaceSettingsModalStackNavigator, }; diff --git a/src/libs/Navigation/AppNavigator/Navigators/BottomTabNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/BottomTabNavigator.tsx index ce03a8d5bcba..87a441f16ddb 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/BottomTabNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/BottomTabNavigator.tsx @@ -4,12 +4,11 @@ import React from 'react'; import createCustomBottomTabNavigator from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator'; import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; import type {BottomTabNavigatorParamList} from '@libs/Navigation/types'; -import AllSettingsScreen from '@pages/home/sidebar/AllSettingsScreen'; import SidebarScreen from '@pages/home/sidebar/SidebarScreen'; import SCREENS from '@src/SCREENS'; import ActiveRouteContext from './ActiveRouteContext'; -const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; +const loadInitialSettingsPage = () => require('../../../../pages/settings/InitialSettingsPage').default as React.ComponentType; const Tab = createCustomBottomTabNavigator(); @@ -20,6 +19,7 @@ const screenOptions: StackNavigationOptions = { function BottomTabNavigator() { const activeRoute = useNavigationState(getTopmostCentralPaneRoute); + return ( @@ -28,12 +28,8 @@ function BottomTabNavigator() { component={SidebarScreen} /> - diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx index 5a3af07a3d5a..16f403342a58 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx @@ -13,20 +13,13 @@ const openOnAdminRoom = url ? new URL(url).searchParams.get('openOnAdminRoom') : type Screens = Partial React.ComponentType>>; -const workspaceSettingsScreens = { +const settingsScreens = { [SCREENS.SETTINGS.WORKSPACES]: () => require('../../../../../pages/workspace/WorkspacesListPage').default as React.ComponentType, - [SCREENS.WORKSPACE.PROFILE]: () => require('../../../../../pages/workspace/WorkspaceProfilePage').default as React.ComponentType, - [SCREENS.WORKSPACE.CARD]: () => require('../../../../../pages/workspace/card/WorkspaceCardPage').default as React.ComponentType, - [SCREENS.WORKSPACE.WORKFLOWS]: () => require('../../../../../pages/workspace/workflows/WorkspaceWorkflowsPage').default as React.ComponentType, - [SCREENS.WORKSPACE.REIMBURSE]: () => require('../../../../../pages/workspace/reimburse/WorkspaceReimbursePage').default as React.ComponentType, - [SCREENS.WORKSPACE.BILLS]: () => require('../../../../../pages/workspace/bills/WorkspaceBillsPage').default as React.ComponentType, - [SCREENS.WORKSPACE.INVOICES]: () => require('../../../../../pages/workspace/invoices/WorkspaceInvoicesPage').default as React.ComponentType, - [SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType, - [SCREENS.WORKSPACE.MEMBERS]: () => require('../../../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType, - [SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType, - [SCREENS.WORKSPACE.MORE_FEATURES]: () => require('../../../../../pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType, - [SCREENS.WORKSPACE.TAGS]: () => require('../../../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType, - [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType, + [SCREENS.SETTINGS.PREFERENCES.ROOT]: () => require('../../../../../pages/settings/Preferences/PreferencesPage').default as React.ComponentType, + [SCREENS.SETTINGS.SECURITY]: () => require('../../../../../pages/settings/Security/SecuritySettingsPage').default as React.ComponentType, + [SCREENS.SETTINGS.PROFILE.ROOT]: () => require('../../../../../pages/settings/Profile/ProfilePage').default as React.ComponentType, + [SCREENS.SETTINGS.WALLET.ROOT]: () => require('../../../../../pages/settings/Wallet/WalletPage').default as React.ComponentType, + [SCREENS.SETTINGS.ABOUT]: () => require('../../../../../pages/settings/AboutPage/AboutPage').default as React.ComponentType, } satisfies Screens; function BaseCentralPaneNavigator() { @@ -46,8 +39,7 @@ function BaseCentralPaneNavigator() { initialParams={{openOnAdminRoom: openOnAdminRoom === 'true' || undefined}} component={ReportScreenWrapper} /> - - {Object.entries(workspaceSettingsScreens).map(([screenName, componentGetter]) => ( + {Object.entries(settingsScreens).map(([screenName, componentGetter]) => ( require('../../../../pages/settings/InitialSettingsPage').default as React.ComponentType; +const loadWorkspaceInitialPage = () => require('../../../../pages/workspace/WorkspaceInitialPage').default as React.ComponentType; const RootStack = createCustomFullScreenNavigator(); @@ -22,14 +22,14 @@ function FullScreenNavigator() { diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 58d9efb43df5..3a59e42bcca1 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -12,11 +12,11 @@ import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Session from '@libs/actions/Session'; -import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute'; import Navigation from '@libs/Navigation/Navigation'; import type {RootStackParamList, State} from '@libs/Navigation/types'; -import {checkIfWorkspaceSettingsTabHasRBR, getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils'; +import {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils'; +import BottomTabAvatar from '@pages/home/sidebar/BottomTabAvatar'; import BottomTabBarFloatingActionButton from '@pages/home/sidebar/BottomTabBarFloatingActionButton'; import variables from '@styles/variables'; import * as Welcome from '@userActions/Welcome'; @@ -43,14 +43,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps const navigationState = navigation.getState() as State | undefined; const routes = navigationState?.routes; const currentRoute = routes?.[navigationState?.index ?? 0]; - const bottomTabRoute = getTopmostBottomTabRoute(navigationState); - if ( - // When we are redirected to the Settings tab from the OldDot, we don't want to call the Welcome.show() method. - // To prevent this, the value of the bottomTabRoute?.name is checked here - bottomTabRoute?.name === SCREENS.WORKSPACE.INITIAL || - Boolean(currentRoute && currentRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR && currentRoute.name !== NAVIGATORS.CENTRAL_PANE_NAVIGATOR) || - Session.isAnonymousUser() - ) { + if (Boolean(currentRoute && currentRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR && currentRoute.name !== NAVIGATORS.CENTRAL_PANE_NAVIGATOR) || Session.isAnonymousUser()) { return; } @@ -64,22 +57,20 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps return topmostBottomTabRoute?.name ?? SCREENS.HOME; }); - const shouldShowWorkspaceRedBrickRoad = checkIfWorkspaceSettingsTabHasRBR(activeWorkspaceID) && currentTabName === SCREENS.HOME; - - const chatTabBrickRoad = currentTabName !== SCREENS.HOME ? getChatTabBrickRoad(activeWorkspaceID) : undefined; + const chatTabBrickRoad = getChatTabBrickRoad(activeWorkspaceID); return ( - - { - Navigation.navigate(ROUTES.HOME); - }} - role={CONST.ROLE.BUTTON} - accessibilityLabel={translate('common.chats')} - wrapperStyle={styles.flexGrow1} - style={styles.bottomTabBarItem} - > + { + Navigation.navigate(ROUTES.HOME); + }} + role={CONST.ROLE.BUTTON} + accessibilityLabel={translate('common.chats')} + wrapperStyle={styles.flex1} + style={styles.bottomTabBarItem} + > + )} - - + + + - - - interceptAnonymousUser(() => - activeWorkspaceID ? Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(activeWorkspaceID)) : Navigation.navigate(ROUTES.ALL_SETTINGS), - ) - } - role={CONST.ROLE.BUTTON} - accessibilityLabel={translate('common.settings')} - wrapperStyle={styles.flexGrow1} - style={styles.bottomTabBarItem} - > - - - {shouldShowWorkspaceRedBrickRoad && } - - - + + + ); } diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx index 4ed8869c1eaa..38bfe4af9ab6 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar.tsx @@ -1,36 +1,87 @@ import React from 'react'; import {View} from 'react-native'; -import Search from '@components/Search'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import Breadcrumbs from '@components/Breadcrumbs'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import {PressableWithoutFeedback} from '@components/Pressable'; +import Tooltip from '@components/Tooltip'; import WorkspaceSwitcherButton from '@components/WorkspaceSwitcherButton'; -import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import SignInOrAvatarWithOptionalStatus from '@pages/home/sidebar/SignInOrAvatarWithOptionalStatus'; +import SignInButton from '@pages/home/sidebar/SignInButton'; import * as Session from '@userActions/Session'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Policy} from '@src/types/onyx'; -function TopBar() { +type TopBarOnyxProps = { + policy: OnyxEntry; +}; + +// eslint-disable-next-line react/no-unused-prop-types +type TopBarProps = {activeWorkspaceID?: string} & TopBarOnyxProps; + +function TopBar({policy}: TopBarProps) { const styles = useThemeStyles(); + const theme = useTheme(); const {translate} = useLocalize(); - const {activeWorkspaceID} = useActiveWorkspace(); + + const headerBreadcrumb = policy?.name + ? {type: CONST.BREADCRUMB_TYPE.STRONG, text: policy.name} + : { + type: CONST.BREADCRUMB_TYPE.ROOT, + }; return ( - - - Navigation.navigate(ROUTES.SEARCH))} - containerStyle={[styles.flex1]} - /> - + + + + + + + + + + {Session.isAnonymousUser() ? ( + + ) : ( + + Navigation.navigate(ROUTES.SEARCH))} + > + + + + )} + ); } TopBar.displayName = 'TopBar'; -export default TopBar; +export default withOnyx({ + policy: { + key: ({activeWorkspaceID}) => `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, + }, +})(TopBar); diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/index.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/index.tsx index bd32c6cab73c..8c53027cf713 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/index.tsx @@ -9,7 +9,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import type {NavigationStateRoute} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; import BottomTabBar from './BottomTabBar'; -import TopBar from './TopBar'; type CustomNavigatorProps = DefaultNavigatorOptions, StackNavigationOptions, StackNavigationEventMap> & { initialRouteName: string; @@ -52,7 +51,6 @@ function CustomBottomTabNavigator({initialRouteName, children, screenOptions, .. shouldShowOfflineIndicator={false} > - | PartialState>; -const isAtLeastOneInState = (state: StackState, screenName: string): boolean => !!state.routes.find((route) => route.name === screenName); +const isAtLeastOneInState = (state: StackState, screenName: string): boolean => state.routes.some((route) => route.name === screenName); function adaptStateIfNecessary(state: StackState) { const isNarrowLayout = getIsNarrowLayout(); + const workspaceCentralPane = state.routes.at(-1); + const topmostWorkspaceCentralPaneRoute = workspaceCentralPane?.state?.routes[0]; - // There should always be SETTINGS.ROOT screen in the state to make sure go back works properly if we deeplinkg to a subpage of settings. - if (!isAtLeastOneInState(state, SCREENS.SETTINGS.ROOT)) { + // When a screen from the FullScreenNavigator is opened from the deeplink then params should be passed to SCREENS.WORKSPACE.INITIAL from the variable defined below. + const workspacesCentralPaneParams = + workspaceCentralPane?.params && 'params' in workspaceCentralPane.params ? (workspaceCentralPane.params.params as Record) : undefined; + + // There should always be WORKSPACE.INITIAL screen in the state to make sure go back works properly if we deeplinkg to a subpage of settings. + if (!isAtLeastOneInState(state, SCREENS.WORKSPACE.INITIAL)) { // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.stale = true; // eslint-disable-line @@ -20,27 +26,30 @@ function adaptStateIfNecessary(state: StackState) { // This is necessary for ts to narrow type down to PartialState. if (state.stale === true) { // Unshift the root screen to fill left pane. - state.routes.unshift({name: SCREENS.SETTINGS.ROOT}); + state.routes.unshift({ + name: SCREENS.WORKSPACE.INITIAL, + params: topmostWorkspaceCentralPaneRoute?.params ?? workspacesCentralPaneParams, + }); } } // If the screen is wide, there should be at least two screens inside: - // - SETINGS.ROOT to cover left pane. - // - SETTINGS_CENTRAL_PANE to cover central pane. + // - WORKSPACE.INITIAL to cover left pane. + // - WORKSPACES_CENTRAL_PANE to cover central pane. if (!isNarrowLayout) { - if (!isAtLeastOneInState(state, SCREENS.SETTINGS_CENTRAL_PANE)) { + if (!isAtLeastOneInState(state, SCREENS.WORKSPACES_CENTRAL_PANE)) { // @ts-expect-error Updating read only property // noinspection JSConstantReassignment state.stale = true; // eslint-disable-line - // Push the default settings central pane screen. if (state.stale === true) { state.routes.push({ - name: SCREENS.SETTINGS_CENTRAL_PANE, + name: SCREENS.WORKSPACES_CENTRAL_PANE, state: { routes: [ { - name: SCREENS.SETTINGS.PROFILE.ROOT, + name: SCREENS.WORKSPACE.PROFILE, + params: state.routes[0]?.params, }, ], }, diff --git a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.tsx b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.tsx index fb7ae24947c2..f35c609402b0 100644 --- a/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomFullScreenNavigator/index.tsx @@ -2,34 +2,12 @@ import type {ParamListBase, StackActionHelpers, StackNavigationState} from '@rea import {createNavigatorFactory, useNavigationBuilder} from '@react-navigation/native'; import type {StackNavigationEventMap, StackNavigationOptions} from '@react-navigation/stack'; import {StackView} from '@react-navigation/stack'; -import React, {useEffect, useMemo} from 'react'; +import React, {useEffect} from 'react'; import useWindowDimensions from '@hooks/useWindowDimensions'; import navigationRef from '@libs/Navigation/navigationRef'; -import SCREENS from '@src/SCREENS'; import CustomFullScreenRouter from './CustomFullScreenRouter'; import type {FullScreenNavigatorProps, FullScreenNavigatorRouterOptions} from './types'; -type Routes = StackNavigationState['routes']; -function reduceReportRoutes(routes: Routes): Routes { - const result: Routes = []; - let count = 0; - const reverseRoutes = [...routes].reverse(); - - reverseRoutes.forEach((route) => { - if (route.name === SCREENS.SETTINGS_CENTRAL_PANE) { - // Remove all report routes except the last 3. This will improve performance. - if (count < 3) { - result.push(route); - count++; - } - } else { - result.push(route); - } - }); - - return result.reverse(); -} - function CustomFullScreenNavigator(props: FullScreenNavigatorProps) { const {navigation, state, descriptors, NavigationContent} = useNavigationBuilder< StackNavigationState, @@ -45,16 +23,6 @@ function CustomFullScreenNavigator(props: FullScreenNavigatorProps) { const {isSmallScreenWidth} = useWindowDimensions(); - const stateToRender = useMemo(() => { - const result = reduceReportRoutes(state.routes); - - return { - ...state, - index: result.length - 1, - routes: [...result], - }; - }, [state]); - useEffect(() => { if (!navigationRef.isReady()) { return; @@ -69,7 +37,7 @@ function CustomFullScreenNavigator(props: FullScreenNavigatorProps) { diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 4cd6a141bd3b..c55145a5d580 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -19,7 +19,7 @@ import linkingConfig from './linkingConfig'; import linkTo from './linkTo'; import navigationRef from './navigationRef'; import switchPolicyID from './switchPolicyID'; -import type {State, StateOrRoute, SwitchPolicyIDParams} from './types'; +import type {NavigationStateRoute, State, StateOrRoute, SwitchPolicyIDParams} from './types'; let resolveNavigationIsReadyPromise: () => void; const navigationIsReadyPromise = new Promise((resolve) => { @@ -234,6 +234,18 @@ function goBack(fallbackRoute?: Route, shouldEnforceFallback = false, shouldPopT navigationRef.current.goBack(); } +/** + * Reset the navigation state to Home page + */ +function resetToHome() { + const rootState = navigationRef.getRootState(); + const bottomTabKey = rootState.routes.find((item: NavigationStateRoute) => item.name === NAVIGATORS.BOTTOM_TAB_NAVIGATOR)?.state?.key; + if (bottomTabKey) { + navigationRef.dispatch({...StackActions.popToTop(), target: bottomTabKey}); + } + navigationRef.dispatch({...StackActions.popToTop(), target: rootState.key}); +} + /** * Close the full screen modal. */ @@ -366,6 +378,7 @@ export default { parseHybridAppUrl, closeFullScreen, navigateWithSwitchPolicyID, + resetToHome, }; export {navigationRef}; diff --git a/src/libs/Navigation/getTopmostSettingsCentralPaneName.ts b/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts similarity index 76% rename from src/libs/Navigation/getTopmostSettingsCentralPaneName.ts rename to src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts index 0ddea6588ef6..db11368c1345 100644 --- a/src/libs/Navigation/getTopmostSettingsCentralPaneName.ts +++ b/src/libs/Navigation/getTopmostWorkspacesCentralPaneName.ts @@ -2,12 +2,12 @@ import type {NavigationState, PartialState} from '@react-navigation/native'; import SCREENS from '@src/SCREENS'; // Get the name of topmost report in the navigation stack. -function getTopmostSettingsCentralPaneName(state: NavigationState | PartialState): string | undefined { +function getTopmostWorkspacesCentralPaneName(state: NavigationState | PartialState): string | undefined { if (!state) { return; } - const topmostCentralPane = state.routes.filter((route) => typeof route !== 'number' && 'name' in route && route.name === SCREENS.SETTINGS_CENTRAL_PANE).at(-1); + const topmostCentralPane = state.routes.filter((route) => typeof route !== 'number' && 'name' in route && route.name === SCREENS.WORKSPACES_CENTRAL_PANE).at(-1); if (!topmostCentralPane) { return; @@ -24,4 +24,4 @@ function getTopmostSettingsCentralPaneName(state: NavigationState | PartialState return topmostCentralPane.state?.routes.at(-1)?.name; } -export default getTopmostSettingsCentralPaneName; +export default getTopmostWorkspacesCentralPaneName; diff --git a/src/libs/Navigation/linkTo.ts b/src/libs/Navigation/linkTo.ts index 371ea89df2e2..2a00895f0492 100644 --- a/src/libs/Navigation/linkTo.ts +++ b/src/libs/Navigation/linkTo.ts @@ -6,7 +6,6 @@ import {extractPolicyIDFromPath, getPathWithoutPolicyID} from '@libs/PolicyUtils import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; import type {Route} from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; import getActionsFromPartialDiff from './AppNavigator/getActionsFromPartialDiff'; import getPartialStateDiff from './AppNavigator/getPartialStateDiff'; import dismissModal from './dismissModal'; @@ -119,7 +118,6 @@ export default function linkTo(navigation: NavigationContainerRef)?.name === SCREENS.WORKSPACE.INITIAL && path.includes('workspace'); + const isFullScreenOnTop = rootState.routes?.at(-1)?.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR; - if (policyID && !isWorkspaceSettingsOpened) { + if (policyID && !isFullScreenOnTop) { // The stateFromPath doesn't include proper path if there is a policy passed with /w/id. // We need to replace the path in the state with the proper one. // To avoid this hacky solution we may want to create custom getActionFromState function in the future. diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts index 69c23b2ea07a..95233bfed079 100755 --- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts @@ -2,17 +2,40 @@ import type {CentralPaneName} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; const CENTRAL_PANE_TO_RHP_MAPPING: Partial> = { - [SCREENS.WORKSPACE.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE], - [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT], - [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE, SCREENS.WORKSPACE.MEMBER_DETAILS, SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION], - [SCREENS.WORKSPACE.WORKFLOWS]: [ - SCREENS.WORKSPACE.WORKFLOWS_APPROVER, - SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY, - SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET, - SCREENS.WORKSPACE.WORKFLOWS_PAYER, + [SCREENS.SETTINGS.PROFILE.ROOT]: [ + SCREENS.SETTINGS.PROFILE.DISPLAY_NAME, + SCREENS.SETTINGS.PROFILE.CONTACT_METHODS, + SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS, + SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD, + SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER, + SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE, + SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME, + SCREENS.SETTINGS.PROFILE.STATUS, + SCREENS.SETTINGS.PROFILE.PRONOUNS, + SCREENS.SETTINGS.PROFILE.TIMEZONE, + SCREENS.SETTINGS.PROFILE.TIMEZONE_SELECT, + SCREENS.SETTINGS.PROFILE.LEGAL_NAME, + SCREENS.SETTINGS.PROFILE.DATE_OF_BIRTH, + SCREENS.SETTINGS.PROFILE.ADDRESS, + SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY, + SCREENS.SETTINGS.SHARE_CODE, ], - [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT], - [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], + [SCREENS.SETTINGS.PREFERENCES.ROOT]: [SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE, SCREENS.SETTINGS.PREFERENCES.LANGUAGE, SCREENS.SETTINGS.PREFERENCES.THEME], + [SCREENS.SETTINGS.WALLET.ROOT]: [ + SCREENS.SETTINGS.WALLET.DOMAIN_CARD, + SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.NAME, + SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.PHONE, + SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.ADDRESS, + SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.CONFIRM, + SCREENS.SETTINGS.WALLET.TRANSFER_BALANCE, + SCREENS.SETTINGS.WALLET.CHOOSE_TRANSFER_ACCOUNT, + SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS, + SCREENS.SETTINGS.WALLET.CARD_ACTIVATE, + SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD, + SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS, + ], + [SCREENS.SETTINGS.SECURITY]: [SCREENS.SETTINGS.TWO_FACTOR_AUTH, SCREENS.SETTINGS.CLOSE], + [SCREENS.SETTINGS.ABOUT]: [SCREENS.SETTINGS.APP_DOWNLOAD_LINKS, SCREENS.SETTINGS.TROUBLESHOOT], }; export default CENTRAL_PANE_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts index f79e275007d7..99f6159549c5 100755 --- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts @@ -2,39 +2,17 @@ import type {FullScreenName} from '@libs/Navigation/types'; import SCREENS from '@src/SCREENS'; const FULL_SCREEN_TO_RHP_MAPPING: Partial> = { - [SCREENS.SETTINGS.PROFILE.ROOT]: [ - SCREENS.SETTINGS.PROFILE.DISPLAY_NAME, - SCREENS.SETTINGS.PROFILE.CONTACT_METHODS, - SCREENS.SETTINGS.PROFILE.CONTACT_METHOD_DETAILS, - SCREENS.SETTINGS.PROFILE.NEW_CONTACT_METHOD, - SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER, - SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_DATE, - SCREENS.SETTINGS.PROFILE.STATUS_CLEAR_AFTER_TIME, - SCREENS.SETTINGS.PROFILE.STATUS, - SCREENS.SETTINGS.PROFILE.PRONOUNS, - SCREENS.SETTINGS.PROFILE.TIMEZONE, - SCREENS.SETTINGS.PROFILE.TIMEZONE_SELECT, - SCREENS.SETTINGS.PROFILE.LEGAL_NAME, - SCREENS.SETTINGS.PROFILE.DATE_OF_BIRTH, - SCREENS.SETTINGS.PROFILE.ADDRESS, - SCREENS.SETTINGS.PROFILE.ADDRESS_COUNTRY, + [SCREENS.WORKSPACE.PROFILE]: [SCREENS.WORKSPACE.NAME, SCREENS.WORKSPACE.CURRENCY, SCREENS.WORKSPACE.DESCRIPTION, SCREENS.WORKSPACE.SHARE], + [SCREENS.WORKSPACE.REIMBURSE]: [SCREENS.WORKSPACE.RATE_AND_UNIT, SCREENS.WORKSPACE.RATE_AND_UNIT_RATE, SCREENS.WORKSPACE.RATE_AND_UNIT_UNIT], + [SCREENS.WORKSPACE.MEMBERS]: [SCREENS.WORKSPACE.INVITE, SCREENS.WORKSPACE.INVITE_MESSAGE, SCREENS.WORKSPACE.MEMBER_DETAILS, SCREENS.WORKSPACE.MEMBER_DETAILS_ROLE_SELECTION], + [SCREENS.WORKSPACE.WORKFLOWS]: [ + SCREENS.WORKSPACE.WORKFLOWS_APPROVER, + SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY, + SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET, + SCREENS.WORKSPACE.WORKFLOWS_PAYER, ], - [SCREENS.SETTINGS.PREFERENCES.ROOT]: [SCREENS.SETTINGS.PREFERENCES.PRIORITY_MODE, SCREENS.SETTINGS.PREFERENCES.LANGUAGE, SCREENS.SETTINGS.PREFERENCES.THEME], - [SCREENS.SETTINGS.WALLET.ROOT]: [ - SCREENS.SETTINGS.WALLET.DOMAIN_CARD, - SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.NAME, - SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.PHONE, - SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.ADDRESS, - SCREENS.SETTINGS.WALLET.CARD_GET_PHYSICAL.CONFIRM, - SCREENS.SETTINGS.WALLET.TRANSFER_BALANCE, - SCREENS.SETTINGS.WALLET.CHOOSE_TRANSFER_ACCOUNT, - SCREENS.SETTINGS.WALLET.ENABLE_PAYMENTS, - SCREENS.SETTINGS.WALLET.CARD_ACTIVATE, - SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD, - SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS, - ], - [SCREENS.SETTINGS.SECURITY]: [SCREENS.SETTINGS.TWO_FACTOR_AUTH, SCREENS.SETTINGS.CLOSE], - [SCREENS.SETTINGS.ABOUT]: [SCREENS.SETTINGS.APP_DOWNLOAD_LINKS, SCREENS.SETTINGS.TROUBLESHOOT], + [SCREENS.WORKSPACE.TAGS]: [SCREENS.WORKSPACE.TAGS_SETTINGS, SCREENS.WORKSPACE.TAGS_EDIT], + [SCREENS.WORKSPACE.CATEGORIES]: [SCREENS.WORKSPACE.CATEGORY_CREATE, SCREENS.WORKSPACE.CATEGORY_SETTINGS, SCREENS.WORKSPACE.CATEGORIES_SETTINGS], }; export default FULL_SCREEN_TO_RHP_MAPPING; diff --git a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts index b2939cf38d9f..78a644ab4aee 100755 --- a/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts +++ b/src/libs/Navigation/linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING.ts @@ -3,20 +3,13 @@ import SCREENS from '@src/SCREENS'; const TAB_TO_CENTRAL_PANE_MAPPING: Record = { [SCREENS.HOME]: [SCREENS.REPORT], - [SCREENS.ALL_SETTINGS]: [SCREENS.SETTINGS.WORKSPACES], - [SCREENS.WORKSPACE.INITIAL]: [ - SCREENS.WORKSPACE.PROFILE, - SCREENS.WORKSPACE.CARD, - SCREENS.WORKSPACE.WORKFLOWS, - SCREENS.WORKSPACE.REIMBURSE, - SCREENS.WORKSPACE.BILLS, - SCREENS.WORKSPACE.INVOICES, - SCREENS.WORKSPACE.TRAVEL, - SCREENS.WORKSPACE.MEMBERS, - SCREENS.WORKSPACE.CATEGORIES, - SCREENS.WORKSPACE.TAGS, - SCREENS.WORKSPACE.MORE_FEATURES, - SCREENS.WORKSPACE.DISTANCE_RATES, + [SCREENS.SETTINGS.ROOT]: [ + SCREENS.SETTINGS.PROFILE.ROOT, + SCREENS.SETTINGS.PREFERENCES.ROOT, + SCREENS.SETTINGS.SECURITY, + SCREENS.SETTINGS.WALLET.ROOT, + SCREENS.SETTINGS.ABOUT, + SCREENS.SETTINGS.WORKSPACES, ], }; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 98a32dcd62fc..a9042cb2bfac 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -31,10 +31,8 @@ const config: LinkingOptions['config'] = { initialRouteName: SCREENS.HOME, screens: { [SCREENS.HOME]: ROUTES.HOME, - [SCREENS.ALL_SETTINGS]: ROUTES.ALL_SETTINGS, - [SCREENS.WORKSPACE.INITIAL]: { - path: ROUTES.WORKSPACE_INITIAL.route, - exact: true, + [SCREENS.SETTINGS.ROOT]: { + path: ROUTES.SETTINGS, }, }, }, @@ -42,42 +40,27 @@ const config: LinkingOptions['config'] = { [NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: { screens: { [SCREENS.REPORT]: ROUTES.REPORT_WITH_ID.route, - - [SCREENS.SETTINGS.WORKSPACES]: ROUTES.SETTINGS_WORKSPACES, - [SCREENS.WORKSPACE.PROFILE]: ROUTES.WORKSPACE_PROFILE.route, - [SCREENS.WORKSPACE.CARD]: { - path: ROUTES.WORKSPACE_CARD.route, - }, - [SCREENS.WORKSPACE.WORKFLOWS]: { - path: ROUTES.WORKSPACE_WORKFLOWS.route, - }, - [SCREENS.WORKSPACE.REIMBURSE]: { - path: ROUTES.WORKSPACE_REIMBURSE.route, - }, - [SCREENS.WORKSPACE.BILLS]: { - path: ROUTES.WORKSPACE_BILLS.route, - }, - [SCREENS.WORKSPACE.INVOICES]: { - path: ROUTES.WORKSPACE_INVOICES.route, - }, - [SCREENS.WORKSPACE.TRAVEL]: { - path: ROUTES.WORKSPACE_TRAVEL.route, - }, - [SCREENS.WORKSPACE.MEMBERS]: { - path: ROUTES.WORKSPACE_MEMBERS.route, + [SCREENS.SETTINGS.PROFILE.ROOT]: { + path: ROUTES.SETTINGS_PROFILE, + exact: true, }, - [SCREENS.WORKSPACE.CATEGORIES]: { - path: ROUTES.WORKSPACE_CATEGORIES.route, + [SCREENS.SETTINGS.PREFERENCES.ROOT]: { + path: ROUTES.SETTINGS_PREFERENCES, + exact: true, }, - [SCREENS.WORKSPACE.MORE_FEATURES]: { - path: ROUTES.WORKSPACE_MORE_FEATURES.route, + [SCREENS.SETTINGS.SECURITY]: { + path: ROUTES.SETTINGS_SECURITY, + exact: true, }, - [SCREENS.WORKSPACE.TAGS]: { - path: ROUTES.WORKSPACE_TAGS.route, + [SCREENS.SETTINGS.WALLET.ROOT]: { + path: ROUTES.SETTINGS_WALLET, + exact: true, }, - [SCREENS.WORKSPACE.DISTANCE_RATES]: { - path: ROUTES.WORKSPACE_DISTANCE_RATES.route, + [SCREENS.SETTINGS.ABOUT]: { + path: ROUTES.SETTINGS_ABOUT, + exact: true, }, + [SCREENS.SETTINGS.WORKSPACES]: ROUTES.SETTINGS_WORKSPACES, }, }, [SCREENS.NOT_FOUND]: '*', @@ -560,30 +543,44 @@ const config: LinkingOptions['config'] = { [NAVIGATORS.FULL_SCREEN_NAVIGATOR]: { screens: { - [SCREENS.SETTINGS.ROOT]: { - path: ROUTES.SETTINGS, + [SCREENS.WORKSPACE.INITIAL]: { + path: ROUTES.WORKSPACE_INITIAL.route, }, - [SCREENS.SETTINGS_CENTRAL_PANE]: { + [SCREENS.WORKSPACES_CENTRAL_PANE]: { screens: { - [SCREENS.SETTINGS.PROFILE.ROOT]: { - path: ROUTES.SETTINGS_PROFILE, - exact: true, + [SCREENS.WORKSPACE.PROFILE]: ROUTES.WORKSPACE_PROFILE.route, + [SCREENS.WORKSPACE.CARD]: { + path: ROUTES.WORKSPACE_CARD.route, }, - [SCREENS.SETTINGS.PREFERENCES.ROOT]: { - path: ROUTES.SETTINGS_PREFERENCES, - exact: true, + [SCREENS.WORKSPACE.WORKFLOWS]: { + path: ROUTES.WORKSPACE_WORKFLOWS.route, }, - [SCREENS.SETTINGS.SECURITY]: { - path: ROUTES.SETTINGS_SECURITY, - exact: true, + [SCREENS.WORKSPACE.REIMBURSE]: { + path: ROUTES.WORKSPACE_REIMBURSE.route, }, - [SCREENS.SETTINGS.WALLET.ROOT]: { - path: ROUTES.SETTINGS_WALLET, - exact: true, + [SCREENS.WORKSPACE.BILLS]: { + path: ROUTES.WORKSPACE_BILLS.route, }, - [SCREENS.SETTINGS.ABOUT]: { - path: ROUTES.SETTINGS_ABOUT, - exact: true, + [SCREENS.WORKSPACE.INVOICES]: { + path: ROUTES.WORKSPACE_INVOICES.route, + }, + [SCREENS.WORKSPACE.TRAVEL]: { + path: ROUTES.WORKSPACE_TRAVEL.route, + }, + [SCREENS.WORKSPACE.MEMBERS]: { + path: ROUTES.WORKSPACE_MEMBERS.route, + }, + [SCREENS.WORKSPACE.CATEGORIES]: { + path: ROUTES.WORKSPACE_CATEGORIES.route, + }, + [SCREENS.WORKSPACE.MORE_FEATURES]: { + path: ROUTES.WORKSPACE_MORE_FEATURES.route, + }, + [SCREENS.WORKSPACE.TAGS]: { + path: ROUTES.WORKSPACE_TAGS.route, + }, + [SCREENS.WORKSPACE.DISTANCE_RATES]: { + path: ROUTES.WORKSPACE_DISTANCE_RATES.route, }, }, }, diff --git a/src/libs/Navigation/linkingConfig/customGetPathFromState.ts b/src/libs/Navigation/linkingConfig/customGetPathFromState.ts index 4f7023d14db4..4017b1b2b17c 100644 --- a/src/libs/Navigation/linkingConfig/customGetPathFromState.ts +++ b/src/libs/Navigation/linkingConfig/customGetPathFromState.ts @@ -8,20 +8,19 @@ import SCREENS from '@src/SCREENS'; const removePolicyIDParamFromState = (state: State) => { const stateCopy = _.cloneDeep(state); const bottomTabRoute = getTopmostBottomTabRoute(stateCopy); - if (bottomTabRoute?.name === SCREENS.HOME && bottomTabRoute?.params && 'policyID' in bottomTabRoute.params) { + if (bottomTabRoute?.params && 'policyID' in bottomTabRoute.params) { delete bottomTabRoute.params.policyID; } return stateCopy; }; const customGetPathFromState: typeof getPathFromState = (state, options) => { + // For the Home and Settings pages we should remove policyID from the params, because on small screens it's displayed twice in the URL const stateWithoutPolicyID = removePolicyIDParamFromState(state as State); - - // For the Home page we should remove policyID from the params, const path = getPathFromState(stateWithoutPolicyID, options); const policyIDFromState = getPolicyIDFromState(state as State); - const isWorkspaceSettingsOpened = getTopmostBottomTabRoute(state as State)?.name === SCREENS.WORKSPACE.INITIAL && path.includes('workspace'); - return `${policyIDFromState && !isWorkspaceSettingsOpened ? `/w/${policyIDFromState}` : ''}${path}`; + const isHomeOpened = getTopmostBottomTabRoute(state as State)?.name === SCREENS.HOME; + return `${policyIDFromState && isHomeOpened ? `/w/${policyIDFromState}` : ''}${path}`; }; export default customGetPathFromState; diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index 5f89b2cd4630..e4a8464d7ddd 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -14,6 +14,10 @@ import getMatchingBottomTabRouteForState from './getMatchingBottomTabRouteForSta import getMatchingCentralPaneRouteForState from './getMatchingCentralPaneRouteForState'; import replacePathInNestedState from './replacePathInNestedState'; +const RHP_SCREENS_OPENED_FROM_LHN = [SCREENS.SETTINGS.SHARE_CODE, SCREENS.SETTINGS.PROFILE.STATUS] as const; + +type RHPScreenOpenedFromLHN = (typeof RHP_SCREENS_OPENED_FROM_LHN)[number]; + type Metainfo = { // Sometimes modal screens don't have information about what should be visible under the overlay. // That means such screen can have different screens under the overlay depending on what was already in the state. @@ -73,14 +77,21 @@ function createCentralPaneNavigator(route: NavigationPartialRoute): NavigationPartialRoute { const routes = []; - routes.push({name: SCREENS.SETTINGS.ROOT}); + const policyID = route?.params && 'policyID' in route.params ? route.params.policyID : undefined; + + // Both routes in FullScreenNavigator should store a policyID in params, so here this param is also passed to the screen displayed in LHN in FullScreenNavigator + routes.push({ + name: SCREENS.WORKSPACE.INITIAL, + params: { + policyID, + }, + }); if (route) { routes.push({ - name: SCREENS.SETTINGS_CENTRAL_PANE, + name: SCREENS.WORKSPACES_CENTRAL_PANE, state: getRoutesWithIndex([route]), }); } - return { name: NAVIGATORS.FULL_SCREEN_NAVIGATOR, state: getRoutesWithIndex(routes), @@ -131,11 +142,6 @@ function getMatchingRootRouteForRHPRoute( return createFullScreenNavigator({name: fullScreenName as FullScreenName, params: route.params}); } } - - // This screen is opened from the LHN of the FullStackNavigator, so in this case we shouldn't push any CentralPane screen - if (route.name === SCREENS.SETTINGS.SHARE_CODE) { - return createFullScreenNavigator(); - } } function getAdaptedState(state: PartialState>, policyID?: string): GetAdaptedStateReturnType { @@ -165,18 +171,25 @@ function getAdaptedState(state: PartialState if (topmostNestedRHPRoute) { let matchingRootRoute = getMatchingRootRouteForRHPRoute(topmostNestedRHPRoute); - + const isRHPScreenOpenedFromLHN = topmostNestedRHPRoute?.name && RHP_SCREENS_OPENED_FROM_LHN.includes(topmostNestedRHPRoute?.name as RHPScreenOpenedFromLHN); // This may happen if this RHP doens't have a route that should be under the overlay defined. - if (!matchingRootRoute) { + if (!matchingRootRoute || isRHPScreenOpenedFromLHN) { metainfo.isCentralPaneAndBottomTabMandatory = false; metainfo.isFullScreenNavigatorMandatory = false; - matchingRootRoute = createCentralPaneNavigator({name: SCREENS.REPORT}); + matchingRootRoute = matchingRootRoute ?? createCentralPaneNavigator({name: SCREENS.REPORT}); } // If the root route is type of FullScreenNavigator, the default bottom tab will be added. const matchingBottomTabRoute = getMatchingBottomTabRouteForState({routes: [matchingRootRoute]}); routes.push(createBottomTabNavigator(matchingBottomTabRoute, policyID)); - routes.push(matchingRootRoute); + // When we open a screen in RHP from FullScreenNavigator, we need to add the appropriate screen in CentralPane. + // Then, when we close FullScreenNavigator, we will be redirected to the correct page in CentralPane. + if (matchingRootRoute.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR) { + routes.push(createCentralPaneNavigator({name: SCREENS.SETTINGS.WORKSPACES})); + } + if (!isNarrowLayout || !isRHPScreenOpenedFromLHN) { + routes.push(matchingRootRoute); + } } routes.push(rhpNavigator); @@ -230,14 +243,18 @@ function getAdaptedState(state: PartialState routes.push( createBottomTabNavigator( { - name: SCREENS.HOME, + name: SCREENS.SETTINGS.ROOT, }, policyID, ), ); - if (!isNarrowLayout) { - routes.push(createCentralPaneNavigator({name: SCREENS.REPORT})); - } + + routes.push( + createCentralPaneNavigator({ + name: SCREENS.SETTINGS.WORKSPACES, + }), + ); + routes.push(fullScreenNavigator); return { @@ -318,7 +335,6 @@ const getAdaptedStateFromPath: GetAdaptedStateFromPath = (path, options) => { const state = getStateFromPath(pathWithoutPolicyID, options) as PartialState>; replacePathInNestedState(state, path); - if (state === undefined) { throw new Error('Unable to parse path'); } diff --git a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts index ef4cd65942b0..fd45685acf23 100644 --- a/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts +++ b/src/libs/Navigation/linkingConfig/getMatchingBottomTabRouteForState.ts @@ -1,5 +1,6 @@ import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; import type {BottomTabName, NavigationPartialRoute, RootStackParamList, State} from '@libs/Navigation/types'; +import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; import {CENTRAL_PANE_TO_TAB_MAPPING} from './TAB_TO_CENTRAL_PANE_MAPPING'; @@ -7,6 +8,12 @@ import {CENTRAL_PANE_TO_TAB_MAPPING} from './TAB_TO_CENTRAL_PANE_MAPPING'; function getMatchingBottomTabRouteForState(state: State, policyID?: string): NavigationPartialRoute { const paramsWithPolicyID = policyID ? {policyID} : undefined; const defaultRoute = {name: SCREENS.HOME, params: paramsWithPolicyID}; + const isFullScreenNavigatorOpened = state.routes.some((route) => route.name === NAVIGATORS.FULL_SCREEN_NAVIGATOR); + + if (isFullScreenNavigatorOpened) { + return {name: SCREENS.SETTINGS.ROOT, params: paramsWithPolicyID}; + } + const topmostCentralPaneRoute = getTopmostCentralPaneRoute(state); if (topmostCentralPaneRoute === undefined) { @@ -14,9 +21,6 @@ function getMatchingBottomTabRouteForState(state: State, pol } const tabName = CENTRAL_PANE_TO_TAB_MAPPING[topmostCentralPaneRoute.name]; - if (tabName === SCREENS.WORKSPACE.INITIAL) { - return {name: tabName, params: topmostCentralPaneRoute.params}; - } return {name: tabName, params: paramsWithPolicyID}; } diff --git a/src/libs/Navigation/linkingConfig/getMatchingCentralPaneRouteForState.ts b/src/libs/Navigation/linkingConfig/getMatchingCentralPaneRouteForState.ts index d4d558708b63..51cb9e3aa9a5 100644 --- a/src/libs/Navigation/linkingConfig/getMatchingCentralPaneRouteForState.ts +++ b/src/libs/Navigation/linkingConfig/getMatchingCentralPaneRouteForState.ts @@ -4,8 +4,6 @@ import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; import TAB_TO_CENTRAL_PANE_MAPPING from './TAB_TO_CENTRAL_PANE_MAPPING'; -const WORKSPACES_SCREENS = TAB_TO_CENTRAL_PANE_MAPPING[SCREENS.WORKSPACE.INITIAL].concat(TAB_TO_CENTRAL_PANE_MAPPING[SCREENS.ALL_SETTINGS]); - /** * @param state - react-navigation state */ @@ -55,11 +53,11 @@ function getAlreadyOpenedSettingsScreen(rootState?: State, policyID?: string): k return undefined; } - // If one of the screen from WORKSPACES_SCREENS is now in the navigation state, we can decide which screen we should display. + // If one of the screen from TAB_TO_CENTRAL_PANE_MAPPING[SCREENS.SETTINGS.ROOT] is now in the navigation state, we can decide which screen we should display. // A screen from the navigation state can be pushed to the navigation state again only if it has a matching policyID with the currently selected workspace. // Otherwise, when we switch the workspace, we want to display the initial screen in the settings tab. const alreadyOpenedSettingsTab = rootState.routes - .filter((item) => item.params && 'screen' in item.params && WORKSPACES_SCREENS.includes(item.params.screen as keyof CentralPaneNavigatorParamList)) + .filter((item) => item.params && 'screen' in item.params && TAB_TO_CENTRAL_PANE_MAPPING[SCREENS.SETTINGS.ROOT].includes(item.params.screen as keyof CentralPaneNavigatorParamList)) .at(-1); if (!hasRouteMatchingPolicyID(alreadyOpenedSettingsTab as NavigationPartialRoute, policyID)) { @@ -82,7 +80,7 @@ function getMatchingCentralPaneRouteForState(state: State, r const centralPaneName = TAB_TO_CENTRAL_PANE_MAPPING[topmostBottomTabRoute.name][0]; - if (topmostBottomTabRoute.name === SCREENS.WORKSPACE.INITIAL) { + if (topmostBottomTabRoute.name === SCREENS.SETTINGS.ROOT) { // When we go back to the settings tab without switching the workspace id, we want to return to the previously opened screen const policyID = topmostBottomTabRoute?.params && 'policyID' in topmostBottomTabRoute.params ? (topmostBottomTabRoute.params.policyID as string) : undefined; const screen = getAlreadyOpenedSettingsScreen(rootState, policyID) ?? centralPaneName; diff --git a/src/libs/Navigation/switchPolicyID.ts b/src/libs/Navigation/switchPolicyID.ts index c425beca73fd..685c21d88e79 100644 --- a/src/libs/Navigation/switchPolicyID.ts +++ b/src/libs/Navigation/switchPolicyID.ts @@ -10,7 +10,6 @@ import SCREENS from '@src/SCREENS'; import getStateFromPath from './getStateFromPath'; import getTopmostCentralPaneRoute from './getTopmostCentralPaneRoute'; import linkingConfig from './linkingConfig'; -import TAB_TO_CENTRAL_PANE_MAPPING from './linkingConfig/TAB_TO_CENTRAL_PANE_MAPPING'; import type {NavigationRoot, RootStackParamList, StackNavigationAction, State, SwitchPolicyIDParams} from './types'; type ActionPayloadParams = { @@ -62,7 +61,7 @@ function getActionForBottomTabNavigator(action: StackNavigationAction, state: Na }; } -export default function switchPolicyID(navigation: NavigationContainerRef | null, {policyID, route, isPolicyAdmin = false}: SwitchPolicyIDParams) { +export default function switchPolicyID(navigation: NavigationContainerRef | null, {policyID, route}: SwitchPolicyIDParams) { if (!navigation) { throw new Error("Couldn't find a navigation object. Is your component inside a screen in a navigator?"); } @@ -110,7 +109,7 @@ export default function switchPolicyID(navigation: NavigationContainerRef); @@ -122,23 +121,12 @@ export default function switchPolicyID(navigation: NavigationContainerRef; }; -type SettingsCentralPaneNavigatorParamList = { - [SCREENS.SETTINGS.PROFILE.ROOT]: undefined; - [SCREENS.SETTINGS.PREFERENCES.ROOT]: undefined; - [SCREENS.SETTINGS.SECURITY]: undefined; - [SCREENS.SETTINGS.WALLET.ROOT]: undefined; - [SCREENS.SETTINGS.ABOUT]: undefined; +type WorkspacesCentralPaneNavigatorParamList = { + [SCREENS.WORKSPACE.PROFILE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.CARD]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_APPROVER]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_FREQUENCY]: { + policyID: string; + }; + [SCREENS.WORKSPACE.WORKFLOWS_AUTO_REPORTING_MONTHLY_OFFSET]: { + policyID: string; + }; + [SCREENS.WORKSPACE.REIMBURSE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.BILLS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.INVOICES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.TRAVEL]: { + policyID: string; + }; + [SCREENS.WORKSPACE.MEMBERS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.CATEGORIES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.MORE_FEATURES]: { + policyID: string; + }; + [SCREENS.WORKSPACE.TAGS]: { + policyID: string; + categoryName: string; + }; + [SCREENS.WORKSPACE.DISTANCE_RATES]: { + policyID: string; + }; }; type FullScreenNavigatorParamList = { - [SCREENS.SETTINGS.ROOT]: undefined; - [SCREENS.SETTINGS_CENTRAL_PANE]: NavigatorScreenParams; + [SCREENS.WORKSPACE.INITIAL]: { + policyID: string; + }; + [SCREENS.WORKSPACES_CENTRAL_PANE]: NavigatorScreenParams; }; type BottomTabNavigatorParamList = { [SCREENS.HOME]: undefined; - [SCREENS.ALL_SETTINGS]: undefined; - [SCREENS.WORKSPACE.INITIAL]: undefined; + [SCREENS.SETTINGS.ROOT]: undefined; }; type SharedScreensParamList = { @@ -621,7 +621,7 @@ type BottomTabName = keyof BottomTabNavigatorParamList; type CentralPaneName = keyof CentralPaneNavigatorParamList; -type FullScreenName = keyof SettingsCentralPaneNavigatorParamList; +type FullScreenName = keyof WorkspacesCentralPaneNavigatorParamList; type SwitchPolicyIDParams = { policyID?: string; @@ -675,5 +675,7 @@ export type { WorkspaceSwitcherNavigatorParamList, OnboardEngagementNavigatorParamList, SwitchPolicyIDParams, + FullScreenNavigatorParamList, + WorkspacesCentralPaneNavigatorParamList, BackToParams, }; diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 47d5419893f5..2c2baee9b96e 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -191,7 +191,7 @@ function hasAuthToken(): boolean { return !!session.authToken; } -function signOutAndRedirectToSignIn(shouldReplaceCurrentScreen?: boolean, shouldStashSession?: boolean) { +function signOutAndRedirectToSignIn(shouldResetToHome?: boolean, shouldStashSession?: boolean) { Log.info('Redirecting to Sign In because signOut() was called'); hideContextMenu(false); if (!isAnonymousUser()) { @@ -240,11 +240,10 @@ function signOutAndRedirectToSignIn(shouldReplaceCurrentScreen?: boolean, should if (Navigation.isActiveRoute(ROUTES.SIGN_IN_MODAL)) { return; } - if (shouldReplaceCurrentScreen) { - Navigation.navigate(ROUTES.SIGN_IN_MODAL, CONST.NAVIGATION.TYPE.UP); - } else { - Navigation.navigate(ROUTES.SIGN_IN_MODAL); + if (shouldResetToHome) { + Navigation.resetToHome(); } + Navigation.navigate(ROUTES.SIGN_IN_MODAL); Linking.getInitialURL().then((url) => { const reportID = ReportUtils.getReportIDFromLink(url); if (reportID) { diff --git a/src/pages/WorkspaceSwitcherPage.tsx b/src/pages/WorkspaceSwitcherPage.tsx index 3d46ede8b31e..2eb5ecaf373f 100644 --- a/src/pages/WorkspaceSwitcherPage.tsx +++ b/src/pages/WorkspaceSwitcherPage.tsx @@ -18,6 +18,7 @@ import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -102,7 +103,7 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { return; } - const {policyID, isPolicyAdmin} = option; + const {policyID} = option; if (policyID) { setSelectedOption(option); @@ -112,7 +113,7 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { setActiveWorkspaceID(policyID); Navigation.goBack(); if (policyID !== activeWorkspaceID) { - Navigation.navigateWithSwitchPolicyID({policyID, isPolicyAdmin}); + Navigation.navigateWithSwitchPolicyID({policyID}); } }, [activeWorkspaceID, setActiveWorkspaceID], @@ -219,7 +220,7 @@ function WorkspaceSwitcherPage({policies}: WorkspaceSwitcherPageProps) { role={CONST.ROLE.BUTTON} onPress={() => { Navigation.goBack(); - App.createWorkspaceWithPolicyDraftAndNavigateToIt(); + interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()); }} > {({hovered}) => ( diff --git a/src/pages/home/sidebar/AvatarWithOptionalStatus.js b/src/pages/home/sidebar/AvatarWithOptionalStatus.js index e1ff3982a0cc..942d5c1da1f2 100644 --- a/src/pages/home/sidebar/AvatarWithOptionalStatus.js +++ b/src/pages/home/sidebar/AvatarWithOptionalStatus.js @@ -1,61 +1,53 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import PropTypes from 'prop-types'; -import React, {useCallback} from 'react'; +import React from 'react'; import {View} from 'react-native'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, - /** Emoji status */ emojiStatus: PropTypes.string, + + /** Whether the avatar is selected */ + isSelected: PropTypes.bool, + + /** Callback called when the avatar or status icon is pressed */ + onPress: PropTypes.func, }; const defaultProps = { - isCreateMenuOpen: false, emojiStatus: '', + isSelected: false, + onPress: () => {}, }; -function AvatarWithOptionalStatus({emojiStatus, isCreateMenuOpen}) { +function AvatarWithOptionalStatus({emojiStatus, isSelected, onPress}) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const showStatusPage = useCallback(() => { - if (isCreateMenuOpen) { - // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon - return; - } - - Navigation.setShouldPopAllStateOnUP(); - Navigation.navigate(ROUTES.SETTINGS_STATUS); - }, [isCreateMenuOpen]); - return ( - + - - - {emojiStatus} - - + + {emojiStatus} + ); diff --git a/src/pages/home/sidebar/BottomTabAvatar.tsx b/src/pages/home/sidebar/BottomTabAvatar.tsx new file mode 100644 index 000000000000..15134b762161 --- /dev/null +++ b/src/pages/home/sidebar/BottomTabAvatar.tsx @@ -0,0 +1,49 @@ +/* eslint-disable rulesdir/onyx-props-must-have-default */ +import React, {useCallback} from 'react'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import interceptAnonymousUser from '@libs/interceptAnonymousUser'; +import Navigation from '@libs/Navigation/Navigation'; +import ROUTES from '@src/ROUTES'; +import AvatarWithOptionalStatus from './AvatarWithOptionalStatus'; +import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; + +type BottomTabAvatarProps = { + /** Whether the create menu is open or not */ + isCreateMenuOpen?: boolean; + + /** Whether the avatar is selected */ + isSelected?: boolean; +}; + +function BottomTabAvatar({isCreateMenuOpen = false, isSelected = false}: BottomTabAvatarProps) { + const currentUserPersonalDetails = useCurrentUserPersonalDetails(); + const emojiStatus = currentUserPersonalDetails?.status?.emojiCode ?? ''; + + const showSettingsPage = useCallback(() => { + if (isCreateMenuOpen) { + // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon + return; + } + + interceptAnonymousUser(() => Navigation.navigate(ROUTES.SETTINGS)); + }, [isCreateMenuOpen]); + + if (emojiStatus) { + return ( + + ); + } + return ( + + ); +} + +BottomTabAvatar.displayName = 'BottomTabAvatar'; +export default BottomTabAvatar; diff --git a/src/pages/home/sidebar/PressableAvatarWithIndicator.js b/src/pages/home/sidebar/PressableAvatarWithIndicator.js index 63c5936e957b..a7345ff6c14a 100644 --- a/src/pages/home/sidebar/PressableAvatarWithIndicator.js +++ b/src/pages/home/sidebar/PressableAvatarWithIndicator.js @@ -1,67 +1,65 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback} from 'react'; +import React from 'react'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import AvatarWithIndicator from '@components/AvatarWithIndicator'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; -import Navigation from '@libs/Navigation/Navigation'; import * as UserUtils from '@libs/UserUtils'; import personalDetailsPropType from '@pages/personalDetailsPropType'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, - /** The personal details of the person who is logged in */ currentUserPersonalDetails: personalDetailsPropType, /** Indicates whether the app is loading initial data */ isLoading: PropTypes.bool, + + /** Whether the avatar is selected */ + isSelected: PropTypes.bool, + + /** Callback called when the avatar is pressed */ + onPress: PropTypes.func, }; const defaultProps = { - isCreateMenuOpen: false, currentUserPersonalDetails: { pendingFields: {avatar: ''}, accountID: '', avatar: '', }, isLoading: true, + isSelected: false, + onPress: () => {}, }; -function PressableAvatarWithIndicator({isCreateMenuOpen, currentUserPersonalDetails, isLoading}) { +function PressableAvatarWithIndicator({currentUserPersonalDetails, isLoading, isSelected, onPress}) { const {translate} = useLocalize(); - - const showSettingsPage = useCallback(() => { - if (isCreateMenuOpen) { - // Prevent opening Settings page when click profile avatar quickly after clicking FAB icon - return; - } - - Navigation.navigate(ROUTES.SETTINGS); - }, [isCreateMenuOpen]); + const styles = useThemeStyles(); return ( - + + + ); diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index e872bbad008a..4d1585cd424a 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -1,11 +1,8 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {InteractionManager, StyleSheet, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; -import Breadcrumbs from '@components/Breadcrumbs'; import LHNOptionsList from '@components/LHNOptionsList/LHNOptionsList'; import OptionsListSkeletonView from '@components/OptionsListSkeletonView'; import useLocalize from '@hooks/useLocalize'; @@ -43,11 +40,11 @@ const propTypes = { isActiveReport: PropTypes.func.isRequired, }; -function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen, activePolicy}) { +function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priorityMode = CONST.PRIORITY_MODE.DEFAULT, isActiveReport, isCreateMenuOpen}) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const modal = useRef({}); - const {translate, updateLocale} = useLocalize(); + const {updateLocale} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); useEffect(() => { @@ -129,22 +126,6 @@ function SidebarLinks({onLinkClick, insets, optionListItems, isLoading, priority return ( - `${ONYXKEYS.COLLECTION.POLICY}${activeWorkspaceID}`, - }, -})(SidebarLinks); +export default SidebarLinks; export {basePropTypes}; diff --git a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js index 9188a859d175..2c2d28a0edbc 100644 --- a/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen/BaseSidebarScreen.js @@ -1,8 +1,10 @@ import React, {useEffect} from 'react'; import {View} from 'react-native'; import ScreenWrapper from '@components/ScreenWrapper'; +import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Browser from '@libs/Browser'; +import TopBar from '@libs/Navigation/AppNavigator/createCustomBottomTabNavigator/TopBar'; import Performance from '@libs/Performance'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; import Timing from '@userActions/Timing'; @@ -19,6 +21,7 @@ const startTimer = () => { function BaseSidebarScreen(props) { const styles = useThemeStyles(); + const {activeWorkspaceID} = useActiveWorkspace(); useEffect(() => { Performance.markStart(CONST.TIMING.SIDEBAR_LOADED); Timing.start(CONST.TIMING.SIDEBAR_LOADED, true); @@ -33,13 +36,16 @@ function BaseSidebarScreen(props) { includePaddingTop={false} > {({insets}) => ( - - - + <> + + + + + )} ); diff --git a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js b/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js deleted file mode 100644 index 0ea6195cd713..000000000000 --- a/src/pages/home/sidebar/SignInOrAvatarWithOptionalStatus.js +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable rulesdir/onyx-props-must-have-default */ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React from 'react'; -import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; -import * as Session from '@userActions/Session'; -import AvatarWithOptionalStatus from './AvatarWithOptionalStatus'; -import PressableAvatarWithIndicator from './PressableAvatarWithIndicator'; -import SignInButton from './SignInButton'; - -const propTypes = { - /** Whether the create menu is open or not */ - isCreateMenuOpen: PropTypes.bool, -}; - -const defaultProps = { - isCreateMenuOpen: false, -}; - -function SignInOrAvatarWithOptionalStatus({isCreateMenuOpen}) { - const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const emojiStatus = lodashGet(currentUserPersonalDetails, 'status.emojiCode', ''); - - if (Session.isAnonymousUser()) { - return ; - } - if (emojiStatus) { - return ( - - ); - } - return ; -} - -SignInOrAvatarWithOptionalStatus.propTypes = propTypes; -SignInOrAvatarWithOptionalStatus.defaultProps = defaultProps; -SignInOrAvatarWithOptionalStatus.displayName = 'SignInOrAvatarWithOptionalStatus'; -export default SignInOrAvatarWithOptionalStatus; diff --git a/src/pages/settings/InitialSettingsPage.tsx b/src/pages/settings/InitialSettingsPage.tsx index 2f2343027cf0..a57f308b5623 100755 --- a/src/pages/settings/InitialSettingsPage.tsx +++ b/src/pages/settings/InitialSettingsPage.tsx @@ -1,23 +1,24 @@ -import {useNavigationState} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AvatarWithImagePicker from '@components/AvatarWithImagePicker'; import ConfirmModal from '@components/ConfirmModal'; import CurrentUserPersonalDetailsSkeletonView from '@components/CurrentUserPersonalDetailsSkeletonView'; -import HeaderPageLayout from '@components/HeaderPageLayout'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import {PressableWithFeedback} from '@components/Pressable'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useActiveRoute from '@hooks/useActiveRoute'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useSingleExecution from '@hooks/useSingleExecution'; @@ -25,9 +26,10 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import getTopmostSettingsCentralPaneName from '@libs/Navigation/getTopmostSettingsCentralPaneName'; import Navigation from '@libs/Navigation/Navigation'; +import shouldShowSubscriptionsMenu from '@libs/shouldShowSubscriptionsMenu'; import * as UserUtils from '@libs/UserUtils'; +import {hasGlobalWorkspaceSettingsRBR} from '@libs/WorkspacesSettingsUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import variables from '@styles/variables'; import * as Link from '@userActions/Link'; @@ -40,7 +42,6 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import type {Icon as TIcon} from '@src/types/onyx/OnyxCommon'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -64,6 +65,12 @@ type InitialSettingsPageOnyxProps = { /** Login list for the user that is signed in */ loginList: OnyxEntry; + + /** The policies which the user has access to */ + policies: OnyxCollection; + + /** Members of all the workspaces the user is member of */ + policyMembers: OnyxCollection; }; type InitialSettingsPageProps = InitialSettingsPageOnyxProps & WithCurrentUserPersonalDetailsProps; @@ -88,7 +95,7 @@ type MenuData = { type Menu = {sectionStyle: StyleProp; sectionTranslationKey: TranslationPaths; items: MenuData[]}; -function InitialSettingsPage({session, userWallet, bankAccountList, fundList, walletTerms, loginList, currentUserPersonalDetails}: InitialSettingsPageProps) { +function InitialSettingsPage({session, userWallet, bankAccountList, fundList, walletTerms, loginList, currentUserPersonalDetails, policies, policyMembers}: InitialSettingsPageProps) { const network = useNetwork(); const theme = useTheme(); const styles = useThemeStyles(); @@ -96,7 +103,7 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa const waitForNavigate = useWaitForNavigation(); const popoverAnchor = useRef(null); const {translate, formatPhoneNumber} = useLocalize(); - const activeRoute = useNavigationState(getTopmostSettingsCentralPaneName); + const activeRoute = useActiveRoute(); const emojiCode = currentUserPersonalDetails?.status?.emojiCode ?? ''; const [shouldShowSignoutConfirmModal, setShouldShowSignoutConfirmModal] = useState(false); @@ -129,7 +136,6 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa const accountMenuItemsData: Menu = useMemo(() => { const profileBrickRoadIndicator = UserUtils.getLoginListBrickRoadIndicator(loginList); const paymentCardList = fundList; - const signOutTranslationKey = Session.isSupportAuthToken() && Session.hasStashedSession() ? 'initialSettingsPage.restoreStashed' : 'initialSettingsPage.signOut'; const defaultMenu: Menu = { sectionStyle: styles.accountSettingsSectionContainer, sectionTranslationKey: 'initialSettingsPage.account', @@ -164,32 +170,70 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa icon: Expensicons.Lock, routeName: ROUTES.SETTINGS_SECURITY, }, - { - translationKey: signOutTranslationKey, - icon: Expensicons.Exit, - action: () => { - signOut(false); - }, - }, ], }; return defaultMenu; - }, [loginList, fundList, styles.accountSettingsSectionContainer, bankAccountList, userWallet?.errors, walletTerms?.errors, signOut]); + }, [loginList, fundList, styles.accountSettingsSectionContainer, bankAccountList, userWallet?.errors, walletTerms?.errors]); + + /** + * Retuns a list of menu items data for workspace section + * @returns object with translationKey, style and items for the workspace section + */ + const workspaceMenuItemsData: Menu = useMemo(() => { + const items: MenuData[] = [ + { + translationKey: 'common.workspaces', + icon: Expensicons.Building, + routeName: ROUTES.SETTINGS_WORKSPACES, + brickRoadIndicator: hasGlobalWorkspaceSettingsRBR(policies, policyMembers) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined, + }, + { + translationKey: 'allSettingsScreen.cardsAndDomains', + icon: Expensicons.CardsAndDomains, + action: () => { + Link.openOldDotLink(CONST.OLDDOT_URLS.ADMIN_DOMAINS_URL); + }, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + link: () => Link.buildOldDotURL(CONST.OLDDOT_URLS.ADMIN_DOMAINS_URL), + }, + ]; + + if (shouldShowSubscriptionsMenu) { + items.splice(1, 0, { + translationKey: 'allSettingsScreen.subscriptions', + icon: Expensicons.MoneyBag, + action: () => { + Link.openOldDotLink(CONST.OLDDOT_URLS.ADMIN_POLICIES_URL); + }, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + link: () => Link.buildOldDotURL(CONST.OLDDOT_URLS.ADMIN_POLICIES_URL), + }); + } + + return { + sectionStyle: styles.workspaceSettingsSectionContainer, + sectionTranslationKey: 'common.workspaces', + items, + }; + }, [policies, policyMembers, styles.workspaceSettingsSectionContainer]); /** * Retuns a list of menu items data for general section * @returns object with translationKey, style and items for the general section */ - const generalMenuItemsData: Menu = useMemo( - () => ({ + const generalMenuItemsData: Menu = useMemo(() => { + const signOutTranslationKey = Session.isSupportAuthToken() && Session.hasStashedSession() ? 'initialSettingsPage.restoreStashed' : 'initialSettingsPage.signOut'; + const defaultMenu: Menu = { sectionStyle: { ...styles.pt4, }, - sectionTranslationKey: 'initialSettingsPage.general' as const, + sectionTranslationKey: 'initialSettingsPage.general', items: [ { - translationKey: 'initialSettingsPage.help' as const, + translationKey: 'initialSettingsPage.help', icon: Expensicons.QuestionMark, action: () => { Link.openExternalLink(CONST.NEWHELP_URL); @@ -199,14 +243,22 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa link: CONST.NEWHELP_URL, }, { - translationKey: 'initialSettingsPage.about' as const, + translationKey: 'initialSettingsPage.about', icon: Expensicons.Info, routeName: ROUTES.SETTINGS_ABOUT, }, + { + translationKey: signOutTranslationKey, + icon: Expensicons.Exit, + action: () => { + signOut(false); + }, + }, ], - }), - [styles.pt4], - ); + }; + + return defaultMenu; + }, [styles.pt4, signOut]); /** * Retuns JSX.Element with menu items @@ -291,13 +343,14 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa const accountMenuItems = useMemo(() => getMenuItemsSection(accountMenuItemsData), [accountMenuItemsData, getMenuItemsSection]); const generalMenuItems = useMemo(() => getMenuItemsSection(generalMenuItemsData), [generalMenuItemsData, getMenuItemsSection]); + const workspaceMenuItems = useMemo(() => getMenuItemsSection(workspaceMenuItemsData), [workspaceMenuItemsData, getMenuItemsSection]); const currentUserDetails = currentUserPersonalDetails; const avatarURL = currentUserDetails?.avatar ?? ''; const accountID = currentUserDetails?.accountID ?? ''; const headerContent = ( - + {isEmptyObject(currentUserPersonalDetails) || currentUserPersonalDetails.displayName === undefined ? ( ) : ( @@ -384,17 +437,15 @@ function InitialSettingsPage({session, userWallet, bankAccountList, fundList, wa ); return ( - Navigation.closeFullScreen()} - backgroundColor={theme.PAGE_THEMES[SCREENS.SETTINGS.ROOT].backgroundColor} - childrenContainerStyles={[styles.m0, styles.p0]} + - + + {headerContent} {accountMenuItems} + {workspaceMenuItems} {generalMenuItems} signOut(true)} onCancel={() => toggleSignoutConfirmModal(false)} /> - - + + ); } @@ -433,5 +484,11 @@ export default withCurrentUserPersonalDetails( session: { key: ONYXKEYS.SESSION, }, + policies: { + key: ONYXKEYS.COLLECTION.POLICY, + }, + policyMembers: { + key: ONYXKEYS.COLLECTION.POLICY_MEMBERS, + }, })(InitialSettingsPage), ); diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 58738a5c8f1c..38828a0406ef 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -1,26 +1,28 @@ +import {useNavigationState} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; -import Breadcrumbs from '@components/Breadcrumbs'; import ConfirmModal from '@components/ConfirmModal'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItem from '@components/MenuItem'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; -import useActiveRoute from '@hooks/useActiveRoute'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; import useWaitForNavigation from '@hooks/useWaitForNavigation'; +import getTopmostWorkspacesCentralPaneName from '@libs/Navigation/getTopmostWorkspacesCentralPaneName'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import type {BottomTabNavigatorParamList} from '@navigation/types'; +import {getDefaultWorkspaceAvatar} from '@libs/ReportUtils'; +import type {FullScreenNavigatorParamList} from '@navigation/types'; import * as App from '@userActions/App'; import * as Policy from '@userActions/Policy'; import * as ReimbursementAccount from '@userActions/ReimbursementAccount'; @@ -48,7 +50,7 @@ type WorkspaceInitialPageOnyxProps = { reimbursementAccount: OnyxEntry; }; -type WorkspaceInitialPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInitialPageOnyxProps & StackScreenProps; +type WorkspaceInitialPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceInitialPageOnyxProps & StackScreenProps; function dismissError(policyID: string) { PolicyUtils.goBackFromInvalidPolicy(); @@ -62,8 +64,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r const hasPolicyCreationError = !!(policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD && policy.errors); const waitForNavigate = useWaitForNavigation(); const {singleExecution, isExecuting} = useSingleExecution(); - const activeRoute = useActiveRoute(); - + const activeRoute = useNavigationState(getTopmostWorkspacesCentralPaneName); const {translate} = useLocalize(); const policyID = policy?.id ?? ''; @@ -216,31 +217,38 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r // We check isPendingDelete for both policy and prevPolicy to prevent the NotFound view from showing right after we delete the workspace (PolicyUtils.isPendingDeletePolicy(policy) && PolicyUtils.isPendingDeletePolicy(prevPolicy)); + const policyAvatar = useMemo(() => { + if (!policy) { + return {source: Expensicons.ExpensifyAppIcon, name: CONST.WORKSPACE_SWITCHER.NAME, type: CONST.ICON_TYPE_AVATAR}; + } + + const avatar = policy?.avatar ? policy.avatar : getDefaultWorkspaceAvatar(policy?.name); + return { + source: avatar, + name: policy?.name ?? '', + type: CONST.ICON_TYPE_WORKSPACE, + }; + }, [policy]); + return ( - + - + {/* Ideally we should use MenuList component for MenuItems with singleExecution/Navigation actions. In this case where user can click on workspace avatar or menu items, we need to have a check for `isExecuting`. So, we are directly mapping menuItems. diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 1aae3294be0d..818edc9c389e 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -31,7 +31,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; -import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -57,7 +57,7 @@ type WorkspaceMembersPageOnyxProps = { type WorkspaceMembersPageProps = WithPolicyAndFullscreenLoadingProps & WithCurrentUserPersonalDetailsProps & WorkspaceMembersPageOnyxProps & - StackScreenProps; + StackScreenProps; /** * Inverts an object, equivalent of _.invert diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 45a950e0fafb..000ba0db7bc7 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -9,7 +9,7 @@ import Section from '@components/Section'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types'; import * as Policy from '@userActions/Policy'; import type {TranslationPaths} from '@src/languages/types'; import type SCREENS from '@src/SCREENS'; @@ -21,7 +21,7 @@ import type {WithPolicyAndFullscreenLoadingProps} from './withPolicyAndFullscree import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading'; import ToggleSettingOptionRow from './workflows/ToggleSettingsOptionRow'; -type WorkspaceMoreFeaturesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; +type WorkspaceMoreFeaturesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps; type Item = { icon: IconAsset; diff --git a/src/pages/workspace/WorkspacePageWithSections.tsx b/src/pages/workspace/WorkspacePageWithSections.tsx index fb01a858642c..4904a4f35193 100644 --- a/src/pages/workspace/WorkspacePageWithSections.tsx +++ b/src/pages/workspace/WorkspacePageWithSections.tsx @@ -19,7 +19,6 @@ import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimburs import * as BankAccounts from '@userActions/BankAccounts'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; -import ROUTES from '@src/ROUTES'; import type {Policy, ReimbursementAccount, User} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type IconAsset from '@src/types/utils/IconAsset'; @@ -157,8 +156,8 @@ function WorkspacePageWithSections({ shouldShowOfflineIndicatorInWideScreen={shouldShowOfflineIndicatorInWideScreen && !shouldShow} > Navigation.goBack(backButtonRoute ?? ROUTES.WORKSPACE_INITIAL.getRoute(policyID))} + onBackButtonPress={() => Navigation.goBack(backButtonRoute)} icon={icon ?? undefined} + style={styles.headerBarDesktopHeight} /> {(isLoading || firstRender.current) && shouldShowLoading && isFocused ? ( diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index 9d90557b1d37..ddebc9d4b368 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -102,7 +102,7 @@ function WorkspaceProfilePage({policy, currencyList = {}, route}: WorkSpaceProfi > {(hasVBA?: boolean) => ( - +
App.createWorkspaceWithPolicyDraftAndNavigateToIt()} + onPress={() => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt())} /> @@ -366,7 +367,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r subtitle={translate('workspace.emptyWorkspace.subtitle')} ctaText={translate('workspace.new.newWorkspace')} ctaAccessibilityLabel={translate('workspace.new.newWorkspace')} - onCtaPress={() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()} + onCtaPress={() => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt())} illustration={LottieAnimations.WorkspacePlanet} // We use this style to vertically center the illustration, as the original illustration is not centered illustrationStyle={styles.emptyWorkspaceIllustrationStyle} @@ -394,7 +395,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r success medium text={translate('workspace.new.newWorkspace')} - onPress={() => App.createWorkspaceWithPolicyDraftAndNavigateToIt()} + onPress={() => interceptAnonymousUser(() => App.createWorkspaceWithPolicyDraftAndNavigateToIt())} /> ; +type WorkspaceBillsPageProps = StackScreenProps; function WorkspaceBillsPage({route}: WorkspaceBillsPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/workspace/card/WorkspaceCardPage.tsx b/src/pages/workspace/card/WorkspaceCardPage.tsx index 710ef3735026..079c715bffd6 100644 --- a/src/pages/workspace/card/WorkspaceCardPage.tsx +++ b/src/pages/workspace/card/WorkspaceCardPage.tsx @@ -4,7 +4,7 @@ import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; @@ -12,7 +12,7 @@ import WorkspaceCardNoVBAView from './WorkspaceCardNoVBAView'; import WorkspaceCardVBANoECardView from './WorkspaceCardVBANoECardView'; import WorkspaceCardVBAWithECardView from './WorkspaceCardVBAWithECardView'; -type WorkspaceCardPageProps = StackScreenProps; +type WorkspaceCardPageProps = StackScreenProps; function WorkspaceCardPage({route}: WorkspaceCardPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx index 17afefd1cea4..3f2ef8ce6aa6 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesPage.tsx @@ -21,7 +21,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import localeCompare from '@libs/LocaleCompare'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import type {CentralPaneNavigatorParamList} from '@navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; @@ -47,7 +47,7 @@ type WorkspaceCategoriesOnyxProps = { policyCategories: OnyxEntry; }; -type WorkspaceCategoriesPageProps = WorkspaceCategoriesOnyxProps & StackScreenProps; +type WorkspaceCategoriesPageProps = WorkspaceCategoriesOnyxProps & StackScreenProps; function WorkspaceCategoriesPage({policy, policyCategories, route}: WorkspaceCategoriesPageProps) { const {isSmallScreenWidth} = useWindowDimensions(); diff --git a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx index fd6466da1758..ca508791c028 100644 --- a/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx +++ b/src/pages/workspace/distanceRates/PolicyDistanceRatesPage.tsx @@ -20,7 +20,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import type {CentralPaneNavigatorParamList} from '@navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; @@ -44,7 +44,7 @@ type PolicyDistanceRatesPageOnyxProps = { policy: OnyxEntry; }; -type PolicyDistanceRatesPageProps = PolicyDistanceRatesPageOnyxProps & StackScreenProps; +type PolicyDistanceRatesPageProps = PolicyDistanceRatesPageOnyxProps & StackScreenProps; function PolicyDistanceRatesPage({policy, route}: PolicyDistanceRatesPageProps) { const {isSmallScreenWidth} = useWindowDimensions(); diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx index 96aa350496b5..a00c4959cedb 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx +++ b/src/pages/workspace/invoices/WorkspaceInvoicesPage.tsx @@ -4,14 +4,14 @@ import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import type {CentralPaneNavigatorParamList} from '@navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@navigation/types'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; import WorkspaceInvoicesNoVBAView from './WorkspaceInvoicesNoVBAView'; import WorkspaceInvoicesVBAView from './WorkspaceInvoicesVBAView'; -type WorkspaceInvoicesPageProps = StackScreenProps; +type WorkspaceInvoicesPageProps = StackScreenProps; function WorkspaceInvoicesPage({route}: WorkspaceInvoicesPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index cc809892d45e..baf3e8adb460 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -20,7 +20,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import * as PolicyUtils from '@libs/PolicyUtils'; -import type {CentralPaneNavigatorParamList} from '@navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@navigation/types'; import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper'; import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper'; import * as Policy from '@userActions/Policy'; @@ -43,7 +43,7 @@ type WorkspaceTagsOnyxProps = { policyTags: OnyxEntry; }; -type WorkspaceTagsPageProps = WorkspaceTagsOnyxProps & StackScreenProps; +type WorkspaceTagsPageProps = WorkspaceTagsOnyxProps & StackScreenProps; function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { const {isSmallScreenWidth} = useWindowDimensions(); diff --git a/src/pages/workspace/travel/WorkspaceTravelPage.tsx b/src/pages/workspace/travel/WorkspaceTravelPage.tsx index 88dfe5254fcf..c03bcc7cfb9b 100644 --- a/src/pages/workspace/travel/WorkspaceTravelPage.tsx +++ b/src/pages/workspace/travel/WorkspaceTravelPage.tsx @@ -4,14 +4,14 @@ import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import type {CentralPaneNavigatorParamList} from '@libs/Navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@libs/Navigation/types'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; import CONST from '@src/CONST'; import type SCREENS from '@src/SCREENS'; import WorkspaceTravelNoVBAView from './WorkspaceTravelNoVBAView'; import WorkspaceTravelVBAView from './WorkspaceTravelVBAView'; -type WorkspaceTravelPageProps = StackScreenProps; +type WorkspaceTravelPageProps = StackScreenProps; function WorkspaceTravelPage({route}: WorkspaceTravelPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 76126040652b..16af069fa0a2 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -8,7 +8,7 @@ import {withOnyx} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import taxPropTypes from '@components/taxPropTypes'; import {translatableTextPropTypes} from '@libs/Localize'; -import type {BottomTabNavigatorParamList, CentralPaneNavigatorParamList, SettingsNavigatorParamList} from '@navigation/types'; +import type {CentralPaneNavigatorParamList, FullScreenNavigatorParamList, SettingsNavigatorParamList, WorkspacesCentralPaneNavigatorParamList} from '@navigation/types'; import policyMemberPropType from '@pages/policyMemberPropType'; import * as Policy from '@userActions/Policy'; import CONST from '@src/CONST'; @@ -16,7 +16,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -type WorkspaceParamList = BottomTabNavigatorParamList & CentralPaneNavigatorParamList & SettingsNavigatorParamList; +type WorkspaceParamList = WorkspacesCentralPaneNavigatorParamList & FullScreenNavigatorParamList & CentralPaneNavigatorParamList & SettingsNavigatorParamList; type PolicyRoute = RouteProp>; function getPolicyIDFromRoute(route: PolicyRoute): string { @@ -148,5 +148,5 @@ export default function (WrappedComponent: })(forwardRef(WithPolicy)); } -export {policyPropTypes, policyDefaultProps}; -export type {WithPolicyOnyxProps, WithPolicyProps, PolicyRoute}; +export {policyDefaultProps, policyPropTypes}; +export type {PolicyRoute, WithPolicyOnyxProps, WithPolicyProps}; diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 34446fecdaa2..c6ace2b0856e 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -18,7 +18,7 @@ import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; -import type {CentralPaneNavigatorParamList} from '@navigation/types'; +import type {WorkspacesCentralPaneNavigatorParamList} from '@navigation/types'; import type {WithPolicyProps} from '@pages/workspace/withPolicy'; import withPolicy from '@pages/workspace/withPolicy'; import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; @@ -42,7 +42,7 @@ type WorkspaceWorkflowsPageOnyxProps = { /** Policy details */ session: OnyxEntry; }; -type WorkspaceWorkflowsPageProps = WithPolicyProps & WorkspaceWorkflowsPageOnyxProps & StackScreenProps; +type WorkspaceWorkflowsPageProps = WithPolicyProps & WorkspaceWorkflowsPageOnyxProps & StackScreenProps; function WorkspaceWorkflowsPage({policy, betas, route, reimbursementAccount, session}: WorkspaceWorkflowsPageProps) { const {translate, preferredLocale} = useLocalize(); diff --git a/src/styles/index.ts b/src/styles/index.ts index 0f129d898758..3cfb38ef4bab 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1442,10 +1442,16 @@ const styles = (theme: ThemeColors) => }, sidebarAvatar: { - backgroundColor: theme.icon, + borderRadius: 28, + height: variables.componentSizeSmall, + width: variables.componentSizeSmall, + }, + + selectedAvatarBorder: { + padding: 2, + borderWidth: 2, borderRadius: 20, - height: variables.componentSizeNormal, - width: variables.componentSizeNormal, + borderColor: theme.success, }, statusIndicator: (backgroundColor = theme.danger) => @@ -1455,10 +1461,10 @@ const styles = (theme: ThemeColors) => borderRadius: 8, borderWidth: 2, position: 'absolute', - right: -2, - top: -1, - height: 16, - width: 16, + right: -4, + top: -3, + height: 12, + width: 12, zIndex: 10, } satisfies ViewStyle), @@ -2648,6 +2654,12 @@ const styles = (theme: ThemeColors) => ...spacing.pt0, }, + workspaceSettingsSectionContainer: { + borderBottomWidth: 1, + borderBottomColor: theme.border, + ...spacing.pt4, + }, + centralPaneAnimation: { height: CONST.CENTRAL_PANE_ANIMATION_HEIGHT, }, @@ -3086,7 +3098,7 @@ const styles = (theme: ThemeColors) => smallEditIcon: { alignItems: 'center', backgroundColor: theme.buttonDefaultBG, - borderColor: theme.cardBG, + borderColor: theme.appBG, borderRadius: 20, borderWidth: 3, color: theme.textReversed, diff --git a/src/styles/variables.ts b/src/styles/variables.ts index d63fb5e9f339..61c16a8c2fd7 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -12,7 +12,7 @@ function getValueUsingPixelRatio(defaultValue: number, maxValue: number): number } export default { - bottomTabHeight: 80, + bottomTabHeight: 72, contentHeaderHeight: getValueUsingPixelRatio(72, 100), contentHeaderDesktopHeight: getValueUsingPixelRatio(80, 100), componentSizeSmall: getValueUsingPixelRatio(28, 32),