From f81fcd28622de7816d7083fb2e64cfb3f1a91acc Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 28 Oct 2025 15:26:36 +0100 Subject: [PATCH 01/30] Create domain split navigator and first pages --- .../laptop-on-desk-with-coffee-and-key.svg | 163 ++++++++++++++++++ src/CONST/index.ts | 1 + src/NAVIGATORS.ts | 1 + src/ROUTES.ts | 20 ++- src/SCREENS.ts | 8 +- src/components/FeatureList.tsx | 5 + src/components/Icon/Illustrations.ts | 4 + src/languages/en.ts | 9 + .../Navigation/AppNavigator/AuthScreens.tsx | 7 + .../ModalStackNavigators/index.tsx | 6 +- .../Navigators/DomainSplitNavigator.tsx | 49 ++++++ .../linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts | 10 ++ .../RELATIONS/SIDEBAR_TO_SPLIT.ts | 1 + .../RELATIONS/TAB_TO_FULLSCREEN.ts | 2 +- .../RELATIONS/WORKSPACES_LIST_TO_RHP.ts | 9 +- src/libs/Navigation/linkingConfig/config.ts | 25 ++- src/libs/Navigation/types.ts | 16 +- src/pages/domain/DomainInitialPage.tsx | 126 ++++++++++++++ src/pages/domain/DomainSamlPage.tsx | 103 +++++++++++ src/pages/domain/DomainVerifiedPage.tsx | 2 +- src/pages/domain/VerifyDomainPage.tsx | 4 +- src/pages/workspace/WorkspacesListPage.tsx | 6 +- 22 files changed, 555 insertions(+), 22 deletions(-) create mode 100644 assets/images/laptop-on-desk-with-coffee-and-key.svg create mode 100644 src/libs/Navigation/AppNavigator/Navigators/DomainSplitNavigator.tsx create mode 100755 src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts create mode 100644 src/pages/domain/DomainInitialPage.tsx create mode 100644 src/pages/domain/DomainSamlPage.tsx diff --git a/assets/images/laptop-on-desk-with-coffee-and-key.svg b/assets/images/laptop-on-desk-with-coffee-and-key.svg new file mode 100644 index 0000000000000..94b62713900da --- /dev/null +++ b/assets/images/laptop-on-desk-with-coffee-and-key.svg @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 30b4ff441362c..501131385a3d6 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -1026,6 +1026,7 @@ const CONST = { MERGE_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/settings/Merge-Accounts', CONNECT_A_BUSINESS_BANK_ACCOUNT_HELP_URL: 'https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account', DOMAIN_VERIFICATION_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/domains/Claim-And-Verify-A-Domain', + SAML_HELP_URL: 'https://help.expensify.com/articles/expensify-classic/domains/Managing-Single-Sign-On-(SSO)-in-Expensify', REGISTER_FOR_WEBINAR_URL: 'https://events.zoom.us/eo/Aif1I8qCi1GZ7KnLnd1vwGPmeukSRoPjFpyFAZ2udQWn0-B86e1Z~AggLXsr32QYFjq8BlYLZ5I06Dg', TEST_RECEIPT_URL: `${CLOUDFRONT_URL}/images/fake-receipt__tacotodds.png`, // Use Environment.getEnvironmentURL to get the complete URL with port number diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts index 354cea71889e7..9e75a1468e1ae 100644 --- a/src/NAVIGATORS.ts +++ b/src/NAVIGATORS.ts @@ -14,6 +14,7 @@ export default { REPORTS_SPLIT_NAVIGATOR: 'ReportsSplitNavigator', SETTINGS_SPLIT_NAVIGATOR: 'SettingsSplitNavigator', WORKSPACE_SPLIT_NAVIGATOR: 'WorkspaceSplitNavigator', + DOMAIN_SPLIT_NAVIGATOR: 'DomainSplitNavigator', SEARCH_FULLSCREEN_NAVIGATOR: 'SearchFullscreenNavigator', SHARE_MODAL_NAVIGATOR: 'ShareModalNavigator', PUBLIC_RIGHT_MODAL_NAVIGATOR: 'PublicRightModalNavigator', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c31f009428c66..2bf1cb6ee0625 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -3303,14 +3303,30 @@ const ROUTES = { // eslint-disable-next-line no-restricted-syntax -- Legacy route generation getRoute: (backTo?: string) => getUrlWithBackToParam('test-tools' as const, backTo), }, - DOMAIN_VERIFY_DOMAIN: { + WORKSPACES_VERIFY_DOMAIN: { route: 'workspaces/verify-domain/:accountID', getRoute: (accountID: number) => `workspaces/verify-domain/${accountID}` as const, }, - DOMAIN_DOMAIN_VERIFIED: { + WORKSPACES_DOMAIN_VERIFIED: { route: 'workspaces/domain-verified/:accountID', getRoute: (accountID: number) => `workspaces/domain-verified/${accountID}` as const, }, + DOMAIN_INITIAL: { + route: 'domain/:accountID', + getRoute: (accountID: number) => `domain/${accountID}` as const, + }, + DOMAIN_SAML: { + route: 'domain/:accountID/saml', + getRoute: (accountID: number) => `domain/${accountID}/saml` as const, + }, + DOMAIN_VERIFY: { + route: 'domain/:accountID/verify', + getRoute: (accountID: number) => `domain/${accountID}/verify` as const, + }, + DOMAIN_VERIFIED: { + route: 'domain/:accountID/verified', + getRoute: (accountID: number) => `domain/${accountID}/verified` as const, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 6eee8f4ee2810..99aa877c456b8 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -813,9 +813,13 @@ const SCREENS = { TEST_TOOLS_MODAL: { ROOT: 'TestToolsModal_Root', }, + WORKSPACES_VERIFY_DOMAIN: 'Workspaces_Verify_Domain', + WORKSPACES_DOMAIN_VERIFIED: 'Workspaces_Domain_Verified', DOMAIN: { - VERIFY_DOMAIN: 'Domain_Verify_Domain', - DOMAIN_VERIFIED: 'Domain_Domain_Verified', + VERIFY: 'Domain_Verify', + VERIFIED: 'Domain_Verified', + INITIAL: 'Domain_Initial', + SAML: 'Domain_SAML', }, } as const; diff --git a/src/components/FeatureList.tsx b/src/components/FeatureList.tsx index da9144fbb3f3e..34f73a295d397 100644 --- a/src/components/FeatureList.tsx +++ b/src/components/FeatureList.tsx @@ -24,6 +24,9 @@ type FeatureListProps = { /** The text to display in the subtitle of the section */ subtitle?: string; + /** The component to display custom subtitle */ + renderSubtitle?: () => ReactNode; + /** Text of the call to action button */ ctaText?: string; @@ -76,6 +79,7 @@ function FeatureList({ contentPaddingOnLargeScreens, footer, isButtonDisabled = false, + renderSubtitle, }: FeatureListProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -92,6 +96,7 @@ function FeatureList({ titleStyles={titleStyles} illustrationContainerStyle={illustrationContainerStyle} contentPaddingOnLargeScreens={contentPaddingOnLargeScreens} + renderSubtitle={renderSubtitle} > diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index a91b8df2a2edf..ecc8f9e4c9042 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -25,6 +25,7 @@ import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_l import Computer from '@assets/images/computer.svg'; import EmptyCardState from '@assets/images/emptystate__expensifycard.svg'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; +import LaptopOnDeskWithCoffeeAndKey from '@assets/images/laptop-on-desk-with-coffee-and-key.svg'; import LaptopWithSecondScreenAndHourglass from '@assets/images/laptop-with-second-screen-and-hourglass.svg'; import LaptopWithSecondScreenSync from '@assets/images/laptop-with-second-screen-sync.svg'; import LaptopWithSecondScreenX from '@assets/images/laptop-with-second-screen-x.svg'; @@ -84,6 +85,7 @@ import Luggage from '@assets/images/simple-illustrations/simple-illustration__lu import Mailbox from '@assets/images/simple-illustrations/simple-illustration__mailbox.svg'; import ExpensifyMobileApp from '@assets/images/simple-illustrations/simple-illustration__mobileapp.svg'; import MoneyIntoWallet from '@assets/images/simple-illustrations/simple-illustration__moneyintowallet.svg'; +import OpenSafe from '@assets/images/simple-illustrations/simple-illustration__opensafe.svg'; import PalmTree from '@assets/images/simple-illustrations/simple-illustration__palmtree.svg'; import PaperAirplane from '@assets/images/simple-illustrations/simple-illustration__paperairplane.svg'; import Pencil from '@assets/images/simple-illustrations/simple-illustration__pencil.svg'; @@ -224,4 +226,6 @@ export { PaperAirplane, CardReplacementSuccess, EmptyShelves, + OpenSafe, + LaptopOnDeskWithCoffeeAndKey, }; diff --git a/src/languages/en.ts b/src/languages/en.ts index 9de13f6f17a55..f5e87b73c719f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7286,6 +7286,15 @@ const translations = { description: ({domainName}: {domainName: string}) => `The domain ${domainName} has been successfully verified and you can now set up SAML and other security features.`, }, + saml: 'SAML', + featureList: { + title: 'SAML Single Sign-On (SSO)', + subtitle: ({domainName}: {domainName: string}) => + `SAML SSO is a security feature that gives you more control over how members with ${domainName} emails log into Expensify. To enable it, you'll need to verify yourself as an authorized company admin.`, + fasterAndEasierLogin: 'Faster and easier login', + moreSecurityAndControl: 'More security and control', + onePasswordForAnything: 'One password for everything', + }, }, }; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 3405e2a94fea5..175f696f3d84e 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -88,6 +88,7 @@ const loadWorkspaceJoinUser = () => require('@pages/worksp const loadReportSplitNavigator = () => require('./Navigators/ReportsSplitNavigator').default; const loadSettingsSplitNavigator = () => require('./Navigators/SettingsSplitNavigator').default; const loadWorkspaceSplitNavigator = () => require('./Navigators/WorkspaceSplitNavigator').default; +const loadDomainSplitNavigator = () => require('./Navigators/DomainSplitNavigator').default; const loadSearchNavigator = () => require('./Navigators/SearchFullscreenNavigator').default; function initializePusher() { @@ -474,6 +475,7 @@ function AuthScreens() { NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR, NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR, + NAVIGATORS.DOMAIN_SPLIT_NAVIGATOR, NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR, NAVIGATORS.RIGHT_MODAL_NAVIGATOR, SCREENS.WORKSPACES_LIST, @@ -501,6 +503,11 @@ function AuthScreens() { options={getWorkspaceSplitNavigatorOptions} getComponent={loadWorkspaceSplitNavigator} /> + require('../../../../pages/workspace/receiptPartners/InviteReceiptPartnerPolicyPage').default, [SCREENS.WORKSPACE.RECEIPT_PARTNERS_INVITE_EDIT]: () => require('../../../../pages/workspace/receiptPartners/EditInviteReceiptPartnerPolicyPage').default, [SCREENS.WORKSPACE.RECEIPT_PARTNERS_CHANGE_BILLING_ACCOUNT]: () => require('../../../../pages/workspace/receiptPartners/ChangeReceiptBillingAccountPage').default, + [SCREENS.DOMAIN.VERIFY]: () => require('../../../../pages/domain/VerifyDomainPage').default, + [SCREENS.DOMAIN.VERIFIED]: () => require('../../../../pages/domain/DomainVerifiedPage').default, }); const TwoFactorAuthenticatorStackNavigator = createModalStackNavigator({ @@ -918,8 +920,8 @@ const ScheduleCallModalStackNavigator = createModalStackNavigator({ - [SCREENS.DOMAIN.VERIFY_DOMAIN]: () => require('../../../../pages/domain/VerifyDomainPage').default, - [SCREENS.DOMAIN.DOMAIN_VERIFIED]: () => require('../../../../pages/domain/DomainVerifiedPage').default, + [SCREENS.WORKSPACES_VERIFY_DOMAIN]: () => require('../../../../pages/domain/VerifyDomainPage').default, + [SCREENS.WORKSPACES_DOMAIN_VERIFIED]: () => require('../../../../pages/domain/DomainVerifiedPage').default, }); export { diff --git a/src/libs/Navigation/AppNavigator/Navigators/DomainSplitNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/DomainSplitNavigator.tsx new file mode 100644 index 0000000000000..c5eef9054293e --- /dev/null +++ b/src/libs/Navigation/AppNavigator/Navigators/DomainSplitNavigator.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import {View} from 'react-native'; +import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; +import createSplitNavigator from '@libs/Navigation/AppNavigator/createSplitNavigator'; +import useSplitNavigatorScreenOptions from '@libs/Navigation/AppNavigator/useSplitNavigatorScreenOptions'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {AuthScreensParamList, DomainSplitNavigatorParamList} from '@libs/Navigation/types'; +import type NAVIGATORS from '@src/NAVIGATORS'; +import SCREENS from '@src/SCREENS'; +import type ReactComponentModule from '@src/types/utils/ReactComponentModule'; + +const loadDomainInitialPage = () => require('../../../../pages/domain/DomainInitialPage').default; +const loadDomainSamlPage = () => require('../../../../pages/domain/DomainSamlPage').default; + +const Split = createSplitNavigator(); + +function DomainSplitNavigator({route}: PlatformStackScreenProps) { + const splitNavigatorScreenOptions = useSplitNavigatorScreenOptions(); + + return ( + + + + + + + + + + ); +} + +DomainSplitNavigator.displayName = 'DomainSplitNavigator'; + +export default DomainSplitNavigator; diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts new file mode 100755 index 0000000000000..b4b63e99d5b84 --- /dev/null +++ b/src/libs/Navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP.ts @@ -0,0 +1,10 @@ +import type {DomainSplitNavigatorParamList} from '@libs/Navigation/types'; +import SCREENS from '@src/SCREENS'; + +// This file is used to define relation between domain split navigator's central screens and RHP screens. +const DOMAIN_TO_RHP: Partial> = { + [SCREENS.DOMAIN.INITIAL]: [], + [SCREENS.DOMAIN.SAML]: [SCREENS.DOMAIN.VERIFY, SCREENS.DOMAIN.VERIFIED], +}; + +export default DOMAIN_TO_RHP; diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SIDEBAR_TO_SPLIT.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SIDEBAR_TO_SPLIT.ts index c4d18632ca682..47f9ada982818 100644 --- a/src/libs/Navigation/linkingConfig/RELATIONS/SIDEBAR_TO_SPLIT.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/SIDEBAR_TO_SPLIT.ts @@ -6,6 +6,7 @@ const SIDEBAR_TO_SPLIT = { [SCREENS.SETTINGS.ROOT]: NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR, [SCREENS.HOME]: NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, [SCREENS.WORKSPACE.INITIAL]: NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR, + [SCREENS.DOMAIN.INITIAL]: NAVIGATORS.DOMAIN_SPLIT_NAVIGATOR, }; export default SIDEBAR_TO_SPLIT; diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/TAB_TO_FULLSCREEN.ts b/src/libs/Navigation/linkingConfig/RELATIONS/TAB_TO_FULLSCREEN.ts index b0dc3fc7025de..3fd187d043e28 100644 --- a/src/libs/Navigation/linkingConfig/RELATIONS/TAB_TO_FULLSCREEN.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/TAB_TO_FULLSCREEN.ts @@ -8,7 +8,7 @@ const TAB_TO_FULLSCREEN: Record, FullScreenName[ [NAVIGATION_TABS.HOME]: [NAVIGATORS.REPORTS_SPLIT_NAVIGATOR], [NAVIGATION_TABS.SEARCH]: [NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR], [NAVIGATION_TABS.SETTINGS]: [NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR], - [NAVIGATION_TABS.WORKSPACES]: [SCREENS.WORKSPACES_LIST, NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR], + [NAVIGATION_TABS.WORKSPACES]: [SCREENS.WORKSPACES_LIST, NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR, NAVIGATORS.DOMAIN_SPLIT_NAVIGATOR], }; export default TAB_TO_FULLSCREEN; diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACES_LIST_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACES_LIST_TO_RHP.ts index f55994e84a6bc..6293aa1ed1c95 100644 --- a/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACES_LIST_TO_RHP.ts +++ b/src/libs/Navigation/linkingConfig/RELATIONS/WORKSPACES_LIST_TO_RHP.ts @@ -2,9 +2,12 @@ import type {DomainModalNavigatorParamList, WorkspaceDuplicateNavigatorParamList import SCREENS from '@src/SCREENS'; const WORKSPACES_LIST_TO_RHP = { - [SCREENS.WORKSPACES_LIST]: [SCREENS.WORKSPACE_DUPLICATE.SELECT_FEATURES, SCREENS.WORKSPACE_DUPLICATE.ROOT, SCREENS.DOMAIN.DOMAIN_VERIFIED, SCREENS.DOMAIN.VERIFY_DOMAIN] satisfies Array< - keyof WorkspaceDuplicateNavigatorParamList | keyof DomainModalNavigatorParamList - >, + [SCREENS.WORKSPACES_LIST]: [ + SCREENS.WORKSPACE_DUPLICATE.SELECT_FEATURES, + SCREENS.WORKSPACE_DUPLICATE.ROOT, + SCREENS.WORKSPACES_VERIFY_DOMAIN, + SCREENS.WORKSPACES_DOMAIN_VERIFIED, + ] satisfies Array, } as Record; export default WORKSPACES_LIST_TO_RHP; diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index c912ae4936c64..b3a2df37f0de9 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1073,6 +1073,12 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.PER_DIEM_EDIT_CURRENCY]: { path: ROUTES.WORKSPACE_PER_DIEM_EDIT_CURRENCY.route, }, + [SCREENS.DOMAIN.VERIFY]: { + path: ROUTES.DOMAIN_VERIFY.route, + }, + [SCREENS.DOMAIN.VERIFIED]: { + path: ROUTES.DOMAIN_VERIFIED.route, + }, }, }, [SCREENS.RIGHT_MODAL.TWO_FACTOR_AUTH]: { @@ -1717,12 +1723,12 @@ const config: LinkingOptions['config'] = { }, [SCREENS.RIGHT_MODAL.DOMAIN]: { screens: { - [SCREENS.DOMAIN.VERIFY_DOMAIN]: { - path: ROUTES.DOMAIN_VERIFY_DOMAIN.route, + [SCREENS.WORKSPACES_VERIFY_DOMAIN]: { + path: ROUTES.WORKSPACES_VERIFY_DOMAIN.route, exact: true, }, - [SCREENS.DOMAIN.DOMAIN_VERIFIED]: { - path: ROUTES.DOMAIN_DOMAIN_VERIFIED.route, + [SCREENS.WORKSPACES_DOMAIN_VERIFIED]: { + path: ROUTES.WORKSPACES_DOMAIN_VERIFIED.route, exact: true, }, }, @@ -1841,6 +1847,17 @@ const config: LinkingOptions['config'] = { }, }, + [NAVIGATORS.DOMAIN_SPLIT_NAVIGATOR]: { + screens: { + [SCREENS.DOMAIN.INITIAL]: { + path: ROUTES.DOMAIN_INITIAL.route, + }, + [SCREENS.DOMAIN.SAML]: { + path: ROUTES.DOMAIN_SAML.route, + }, + }, + }, + [NAVIGATORS.SEARCH_FULLSCREEN_NAVIGATOR]: { screens: { [SCREENS.SEARCH.ROOT]: { diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index dc69cb4c87fe8..1863b06de34be 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -67,6 +67,7 @@ type SplitNavigatorParamList = { [NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR]: SettingsSplitNavigatorParamList; [NAVIGATORS.REPORTS_SPLIT_NAVIGATOR]: ReportsSplitNavigatorParamList; [NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR]: WorkspaceSplitNavigatorParamList; + [NAVIGATORS.DOMAIN_SPLIT_NAVIGATOR]: DomainSplitNavigatorParamList; }; type SplitNavigatorBySidebar = (typeof SIDEBAR_TO_SPLIT)[T]; @@ -2034,10 +2035,10 @@ type MergeTransactionNavigatorParamList = { }; type DomainModalNavigatorParamList = { - [SCREENS.DOMAIN.VERIFY_DOMAIN]: { + [SCREENS.WORKSPACES_VERIFY_DOMAIN]: { accountID: number; }; - [SCREENS.DOMAIN.DOMAIN_VERIFIED]: { + [SCREENS.WORKSPACES_DOMAIN_VERIFIED]: { accountID: number; }; }; @@ -2325,6 +2326,15 @@ type WorkspaceSplitNavigatorParamList = { }; }; +type DomainSplitNavigatorParamList = { + [SCREENS.DOMAIN.INITIAL]: { + accountID: number; + }; + [SCREENS.DOMAIN.SAML]: { + accountID: number; + }; +}; + type OnboardingModalNavigatorParamList = { [SCREENS.ONBOARDING.PERSONAL_DETAILS]: { // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md @@ -2525,6 +2535,7 @@ type AuthScreensParamList = SharedScreensParamList & [NAVIGATORS.REPORTS_SPLIT_NAVIGATOR]: NavigatorScreenParams; [NAVIGATORS.SETTINGS_SPLIT_NAVIGATOR]: NavigatorScreenParams; [NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR]: NavigatorScreenParams; + [NAVIGATORS.DOMAIN_SPLIT_NAVIGATOR]: NavigatorScreenParams; [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: NavigatorScreenParams; [NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR]: NavigatorScreenParams; [NAVIGATORS.FEATURE_TRAINING_MODAL_NAVIGATOR]: NavigatorScreenParams; @@ -2791,4 +2802,5 @@ export type { MergeTransactionNavigatorParamList, AttachmentModalScreensParamList, DomainModalNavigatorParamList, + DomainSplitNavigatorParamList, }; diff --git a/src/pages/domain/DomainInitialPage.tsx b/src/pages/domain/DomainInitialPage.tsx new file mode 100644 index 0000000000000..f176e7ff582a8 --- /dev/null +++ b/src/pages/domain/DomainInitialPage.tsx @@ -0,0 +1,126 @@ +import {findFocusedRoute, useNavigationState} from '@react-navigation/native'; +import {Str} from 'expensify-common'; +import React, {useEffect, useMemo} from 'react'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import HighlightableMenuItem from '@components/HighlightableMenuItem'; +import {UserLock} from '@components/Icon/Expensicons'; +import NavigationTabBar from '@components/Navigation/NavigationTabBar'; +import NAVIGATION_TABS from '@components/Navigation/NavigationTabBar/NAVIGATION_TABS'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useSingleExecution from '@hooks/useSingleExecution'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWaitForNavigation from '@hooks/useWaitForNavigation'; +import {confirmReadyToOpenApp} from '@libs/actions/App'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type DOMAIN_TO_RHP from '@navigation/linkingConfig/RELATIONS/DOMAIN_TO_RHP'; +import type {DomainSplitNavigatorParamList} from '@navigation/types'; +import type CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; +import type IconAsset from '@src/types/utils/IconAsset'; + +type DomainTopLevelScreens = keyof typeof DOMAIN_TO_RHP; + +type DomainMenuItem = { + translationKey: TranslationPaths; + icon: IconAsset; + action: () => void; + brickRoadIndicator?: ValueOf; + screenName: DomainTopLevelScreens; + badgeText?: string; + highlighted?: boolean; +}; + +type DomainInitialPageProps = PlatformStackScreenProps; + +function DomainInitialPage({route}: DomainInitialPageProps) { + const styles = useThemeStyles(); + const waitForNavigate = useWaitForNavigation(); + const {singleExecution} = useSingleExecution(); + const activeRoute = useNavigationState((state) => findFocusedRoute(state)?.name); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {translate} = useLocalize(); + const shouldDisplayLHB = !shouldUseNarrowLayout; + + const accountID = route.params.accountID; + const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${accountID}`, {canBeMissing: true}); + const domainName = domain ? Str.extractEmailDomain(domain.email) : undefined; + + const domainMenuItems: DomainMenuItem[] = useMemo(() => { + const menuItems: DomainMenuItem[] = [ + { + translationKey: 'domain.saml', + icon: UserLock, + action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.DOMAIN_SAML.getRoute(accountID)))), + screenName: SCREENS.DOMAIN.SAML, + }, + ]; + + return menuItems; + }, [accountID, singleExecution, waitForNavigate]); + + useEffect(() => { + confirmReadyToOpenApp(); + }, []); + + return ( + } + > + + Navigation.goBack(ROUTES.WORKSPACES_LIST.route)} + shouldDisplayHelpButton={shouldUseNarrowLayout} + /> + + + + {/* + 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. + */} + {domainMenuItems.map((item) => ( + + ))} + + + {shouldDisplayLHB && } + + + ); +} + +DomainInitialPage.displayName = 'DomainInitialPage'; + +export default DomainInitialPage; diff --git a/src/pages/domain/DomainSamlPage.tsx b/src/pages/domain/DomainSamlPage.tsx new file mode 100644 index 0000000000000..c503302cc4da3 --- /dev/null +++ b/src/pages/domain/DomainSamlPage.tsx @@ -0,0 +1,103 @@ +import {Str} from 'expensify-common'; +import React from 'react'; +import {View} from 'react-native'; +import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; +import type {FeatureListItem} from '@components/FeatureList'; +import FeatureList from '@components/FeatureList'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import {LaptopOnDeskWithCoffeeAndKey, LockClosed, OpenSafe, ShieldYellow} from '@components/Icon/Illustrations'; +import RenderHTML from '@components/RenderHTML'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollViewWithContext from '@components/ScrollViewWithContext'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {DomainSplitNavigatorParamList} from '@libs/Navigation/types'; +import colors from '@styles/theme/colors'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type DomainSamlPageProps = PlatformStackScreenProps; + +const samlFeatures: FeatureListItem[] = [ + { + icon: OpenSafe, + translationKey: 'domain.featureList.fasterAndEasierLogin', + }, + { + icon: ShieldYellow, + translationKey: 'domain.featureList.moreSecurityAndControl', + }, + { + icon: LockClosed, + translationKey: 'domain.featureList.onePasswordForAnything', + }, +]; + +function DomainSamlPage({route}: DomainSamlPageProps) { + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {translate} = useLocalize(); + + const accountID = route.params.accountID; + const [domain, domainResults] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${accountID}`, {canBeMissing: true}); + const domainName = domain ? Str.extractEmailDomain(domain.email) : undefined; + + return ( + + Navigation.goBack(ROUTES.WORKSPACES_LIST.route)} + shouldShow={domainResults.status === 'loaded' && !domain} + shouldForceFullScreen + shouldDisplaySearchRouter + > + + + + + ( + + + + )} + ctaText={translate('domain.verifyDomain.title')} + ctaAccessibilityLabel={translate('domain.verifyDomain.title')} + onCtaPress={() => { + Navigation.navigate(ROUTES.DOMAIN_VERIFY.getRoute(accountID)); + }} + illustrationBackgroundColor={colors.blue700} + illustration={LaptopOnDeskWithCoffeeAndKey} + illustrationStyle={{width: 216, height: 186}} + illustrationContainerStyle={[styles.emptyStateCardIllustrationContainer, styles.justifyContentCenter]} + titleStyles={styles.textHeadlineH1} + /> + + + + + ); +} + +DomainSamlPage.displayName = 'DomainSamlPage'; + +export default DomainSamlPage; diff --git a/src/pages/domain/DomainVerifiedPage.tsx b/src/pages/domain/DomainVerifiedPage.tsx index de8372176d017..7aa6ebab3ff70 100644 --- a/src/pages/domain/DomainVerifiedPage.tsx +++ b/src/pages/domain/DomainVerifiedPage.tsx @@ -15,7 +15,7 @@ import type {DomainModalNavigatorParamList} from '@libs/Navigation/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -type DomainVerifiedPageProps = PlatformStackScreenProps; +type DomainVerifiedPageProps = PlatformStackScreenProps; function DomainVerifiedPage({route}: DomainVerifiedPageProps) { const {translate} = useLocalize(); diff --git a/src/pages/domain/VerifyDomainPage.tsx b/src/pages/domain/VerifyDomainPage.tsx index 7cb42aa877180..4aa6eb620230a 100644 --- a/src/pages/domain/VerifyDomainPage.tsx +++ b/src/pages/domain/VerifyDomainPage.tsx @@ -24,7 +24,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -type VerifyDomainPageProps = PlatformStackScreenProps; +type VerifyDomainPageProps = PlatformStackScreenProps; function OrderedListRow({index, children}: {index: number; children: React.ReactNode}) { const styles = useThemeStyles(); @@ -49,7 +49,7 @@ function VerifyDomainPage({route}: VerifyDomainPageProps) { if (!domain?.validated) { return; } - Navigation.navigate(ROUTES.DOMAIN_DOMAIN_VERIFIED.getRoute(accountID), {forceReplace: true}); + Navigation.navigate(ROUTES.WORKSPACES_DOMAIN_VERIFIED.getRoute(accountID), {forceReplace: true}); }, [accountID, domain]); useEffect(() => { diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index b2aa90e7ef773..9b811d3112d30 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -411,7 +411,7 @@ function WorkspacesListPage() { { icon: Expensicons.Globe, text: translate('domain.verifyDomain.title'), - onSelected: () => Navigation.navigate(ROUTES.DOMAIN_VERIFY_DOMAIN.getRoute(item.accountID)), + onSelected: () => Navigation.navigate(ROUTES.WORKSPACES_VERIFY_DOMAIN.getRoute(item.accountID)), }, ] : undefined; @@ -432,7 +432,7 @@ function WorkspacesListPage() { {({hovered}) => ( @@ -460,7 +460,7 @@ function WorkspacesListPage() { if (isValidated) { openOldDotLink(CONST.OLDDOT_URLS.ADMIN_DOMAINS_URL); } else { - Navigation.navigate(ROUTES.DOMAIN_VERIFY_DOMAIN.getRoute(accountID)); + Navigation.navigate(ROUTES.DOMAIN_INITIAL.getRoute(accountID)); } }, []); From e325f1da152cb6c119a89668b1f200d70a64ab9d Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 28 Oct 2025 16:41:30 +0100 Subject: [PATCH 02/30] Fix after merge --- .../Navigation/AppNavigator/Navigators/RightModalNavigator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx index 4411ce06c656a..f3b4465ac2c04 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/RightModalNavigator.tsx @@ -258,7 +258,7 @@ function RightModalNavigator({navigation, route}: RightModalNavigatorProps) { /> From dee2e6b973055f09e1549ecc6583295360016e56 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Tue, 28 Oct 2025 17:41:03 +0100 Subject: [PATCH 03/30] Divide VerifyDomain page between workspaces list/domain views --- src/libs/Navigation/types.ts | 6 + src/pages/domain/BaseVerifyDomainPage.tsx | 124 +++++++++++++++++ src/pages/domain/VerifyDomainPage.tsx | 129 ++---------------- .../domain/WorkspacesVerifyDomainPage.tsx | 19 +++ src/pages/workspace/WorkspacesListPage.tsx | 2 +- 5 files changed, 159 insertions(+), 121 deletions(-) create mode 100644 src/pages/domain/BaseVerifyDomainPage.tsx create mode 100644 src/pages/domain/WorkspacesVerifyDomainPage.tsx diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index f7cee5c19f32b..2f249cf6822a2 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1290,6 +1290,12 @@ type SettingsNavigatorParamList = { rateID: string; subRateID: string; }; + [SCREENS.DOMAIN.VERIFY]: { + accountID: number; + }; + [SCREENS.DOMAIN.VERIFIED]: { + accountID: number; + }; } & ReimbursementAccountNavigatorParamList; type TwoFactorAuthNavigatorParamList = { diff --git a/src/pages/domain/BaseVerifyDomainPage.tsx b/src/pages/domain/BaseVerifyDomainPage.tsx new file mode 100644 index 0000000000000..9ecdcf994cd2a --- /dev/null +++ b/src/pages/domain/BaseVerifyDomainPage.tsx @@ -0,0 +1,124 @@ +import {Str} from 'expensify-common'; +import React, {useEffect} from 'react'; +import {View} from 'react-native'; +import CopyableTextField from '@components/Domain/CopyableTextField'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Icon from '@components/Icon'; +import {Exclamation} from '@components/Icon/Expensicons'; +import RenderHTML from '@components/RenderHTML'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getDomainValidationCode, validateDomain} from '@libs/actions/Domain'; +import Navigation from '@libs/Navigation/Navigation'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; + +function OrderedListRow({index, children}: {index: number; children: React.ReactNode}) { + const styles = useThemeStyles(); + return ( + + {index}. + {children} + + ); +} + +function BaseVerifyDomainPage({accountID, forwardTo}: {accountID: number; forwardTo: 'WORKSPACES_DOMAIN_VERIFIED' | 'DOMAIN_VERIFIED'}) { + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); + + const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${accountID}`, {canBeMissing: true}); + const domainName = domain ? Str.extractEmailDomain(domain.email) : ''; + + useEffect(() => { + if (!domain?.validated) { + return; + } + Navigation.navigate(ROUTES[forwardTo].getRoute(accountID), {forceReplace: true}); + }, [accountID, domain, forwardTo]); + + useEffect(() => { + if (!accountID) { + return; + } + getDomainValidationCode(accountID); + }, [accountID]); + + return ( + + + + + + + + + + + + + + + + + + {translate('domain.verifyDomain.addTXTRecord')} + + + + + + + {translate('domain.verifyDomain.saveChanges')} + + + + + + + {translate('domain.verifyDomain.warning')} + + + + + validateDomain(accountID)} + message={domain?.validationError} + isAlertVisible={!!domain?.validationError} + containerStyles={styles.mb5} + isLoading={domain?.isValidationPending} + /> + + + ); +} + +BaseVerifyDomainPage.displayName = 'VerifyDomainPage'; +export default BaseVerifyDomainPage; diff --git a/src/pages/domain/VerifyDomainPage.tsx b/src/pages/domain/VerifyDomainPage.tsx index eeb56103f9e26..f1df1375b6834 100644 --- a/src/pages/domain/VerifyDomainPage.tsx +++ b/src/pages/domain/VerifyDomainPage.tsx @@ -1,130 +1,19 @@ -import {Str} from 'expensify-common'; -import React, {useEffect} from 'react'; -import {View} from 'react-native'; -import CopyableTextField from '@components/Domain/CopyableTextField'; -import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import Icon from '@components/Icon'; -import {Exclamation} from '@components/Icon/Expensicons'; -import RenderHTML from '@components/RenderHTML'; -import ScreenWrapper from '@components/ScreenWrapper'; -import ScrollView from '@components/ScrollView'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import {getDomainValidationCode, validateDomain} from '@libs/actions/Domain'; -import Navigation from '@libs/Navigation/Navigation'; +import React from 'react'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {WorkspacesDomainModalNavigatorParamList} from '@libs/Navigation/types'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import type SCREENS from '@src/SCREENS'; +import BaseVerifyDomainPage from './BaseVerifyDomainPage'; -type VerifyDomainPageProps = PlatformStackScreenProps; - -function OrderedListRow({index, children}: {index: number; children: React.ReactNode}) { - const styles = useThemeStyles(); - return ( - - {index}. - {children} - - ); -} +type VerifyDomainPageProps = PlatformStackScreenProps; function VerifyDomainPage({route}: VerifyDomainPageProps) { - const styles = useThemeStyles(); - const theme = useTheme(); - const {translate} = useLocalize(); - - const accountID = route.params?.accountID; - const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${accountID}`, {canBeMissing: true}); - const domainName = domain ? Str.extractEmailDomain(domain.email) : ''; - - useEffect(() => { - if (!domain?.validated) { - return; - } - Navigation.navigate(ROUTES.WORKSPACES_DOMAIN_VERIFIED.getRoute(accountID), {forceReplace: true}); - }, [accountID, domain]); - - useEffect(() => { - if (!accountID) { - return; - } - getDomainValidationCode(accountID); - }); - return ( - - - - - - - - - - - - - - - - - - {translate('domain.verifyDomain.addTXTRecord')} - - - - - - - {translate('domain.verifyDomain.saveChanges')} - - - - - - - {translate('domain.verifyDomain.warning')} - - - - - validateDomain(accountID)} - message={domain?.validationError} - isAlertVisible={!!domain?.validationError} - containerStyles={styles.mb5} - isLoading={domain?.isValidationPending} - /> - - + ); } -VerifyDomainPage.displayName = 'VerifyDomainPage'; +VerifyDomainPage.displayName = 'WorkspacesVerifyDomainPage'; export default VerifyDomainPage; diff --git a/src/pages/domain/WorkspacesVerifyDomainPage.tsx b/src/pages/domain/WorkspacesVerifyDomainPage.tsx new file mode 100644 index 0000000000000..441dd54ad3521 --- /dev/null +++ b/src/pages/domain/WorkspacesVerifyDomainPage.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {WorkspacesDomainModalNavigatorParamList} from '@libs/Navigation/types'; +import type SCREENS from '@src/SCREENS'; +import BaseVerifyDomainPage from './BaseVerifyDomainPage'; + +type WorkspacesVerifyDomainPageProps = PlatformStackScreenProps; + +function WorkspacesVerifyDomainPage({route}: WorkspacesVerifyDomainPageProps) { + return ( + + ); +} + +WorkspacesVerifyDomainPage.displayName = 'WorkspacesVerifyDomainPage'; +export default WorkspacesVerifyDomainPage; diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 99a15f616a003..1ba5f7cad97b0 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -460,7 +460,7 @@ function WorkspacesListPage() { if (isValidated) { openOldDotLink(CONST.OLDDOT_URLS.ADMIN_DOMAINS_URL); } else { - Navigation.navigate(ROUTES.WORKSPACES_VERIFY_DOMAIN.getRoute(accountID)); + Navigation.navigate(ROUTES.DOMAIN_SAML.getRoute(accountID)); } }, []); From a9f4dad318327f9cd8b7b3c9626fc74bc8b3f674 Mon Sep 17 00:00:00 2001 From: mhawryluk Date: Mon, 3 Nov 2025 12:34:44 +0100 Subject: [PATCH 04/30] Fix after merge --- .../ModalStackNavigators/index.tsx | 4 +- src/pages/domain/BaseVerifyDomainPage.tsx | 124 -------------- src/pages/domain/SamlVerifyDomainPage.tsx | 19 +++ src/pages/domain/VerifyDomainPage.tsx | 154 ++++++++++++++++-- .../domain/WorkspacesVerifyDomainPage.tsx | 2 +- 5 files changed, 164 insertions(+), 139 deletions(-) delete mode 100644 src/pages/domain/BaseVerifyDomainPage.tsx create mode 100644 src/pages/domain/SamlVerifyDomainPage.tsx diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index 9ad71a0e4b485..fa90e2ec15162 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -767,7 +767,7 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/receiptPartners/InviteReceiptPartnerPolicyPage').default, [SCREENS.WORKSPACE.RECEIPT_PARTNERS_INVITE_EDIT]: () => require('../../../../pages/workspace/receiptPartners/EditInviteReceiptPartnerPolicyPage').default, [SCREENS.WORKSPACE.RECEIPT_PARTNERS_CHANGE_BILLING_ACCOUNT]: () => require('../../../../pages/workspace/receiptPartners/ChangeReceiptBillingAccountPage').default, - [SCREENS.DOMAIN.VERIFY]: () => require('../../../../pages/domain/VerifyDomainPage').default, + [SCREENS.DOMAIN.VERIFY]: () => require('../../../../pages/domain/SamlVerifyDomainPage').default, [SCREENS.DOMAIN.VERIFIED]: () => require('../../../../pages/domain/DomainVerifiedPage').default, }); @@ -921,7 +921,7 @@ const ScheduleCallModalStackNavigator = createModalStackNavigator({ - [SCREENS.WORKSPACES_VERIFY_DOMAIN]: () => require('../../../../pages/domain/VerifyDomainPage').default, + [SCREENS.WORKSPACES_VERIFY_DOMAIN]: () => require('../../../../pages/domain/WorkspacesVerifyDomainPage').default, [SCREENS.WORKSPACES_DOMAIN_VERIFIED]: () => require('../../../../pages/domain/DomainVerifiedPage').default, }); diff --git a/src/pages/domain/BaseVerifyDomainPage.tsx b/src/pages/domain/BaseVerifyDomainPage.tsx deleted file mode 100644 index 9ecdcf994cd2a..0000000000000 --- a/src/pages/domain/BaseVerifyDomainPage.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import {Str} from 'expensify-common'; -import React, {useEffect} from 'react'; -import {View} from 'react-native'; -import CopyableTextField from '@components/Domain/CopyableTextField'; -import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import Icon from '@components/Icon'; -import {Exclamation} from '@components/Icon/Expensicons'; -import RenderHTML from '@components/RenderHTML'; -import ScreenWrapper from '@components/ScreenWrapper'; -import ScrollView from '@components/ScrollView'; -import Text from '@components/Text'; -import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; -import useTheme from '@hooks/useTheme'; -import useThemeStyles from '@hooks/useThemeStyles'; -import {getDomainValidationCode, validateDomain} from '@libs/actions/Domain'; -import Navigation from '@libs/Navigation/Navigation'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; - -function OrderedListRow({index, children}: {index: number; children: React.ReactNode}) { - const styles = useThemeStyles(); - return ( - - {index}. - {children} - - ); -} - -function BaseVerifyDomainPage({accountID, forwardTo}: {accountID: number; forwardTo: 'WORKSPACES_DOMAIN_VERIFIED' | 'DOMAIN_VERIFIED'}) { - const styles = useThemeStyles(); - const theme = useTheme(); - const {translate} = useLocalize(); - - const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${accountID}`, {canBeMissing: true}); - const domainName = domain ? Str.extractEmailDomain(domain.email) : ''; - - useEffect(() => { - if (!domain?.validated) { - return; - } - Navigation.navigate(ROUTES[forwardTo].getRoute(accountID), {forceReplace: true}); - }, [accountID, domain, forwardTo]); - - useEffect(() => { - if (!accountID) { - return; - } - getDomainValidationCode(accountID); - }, [accountID]); - - return ( - - - - - - - - - - - - - - - - - - {translate('domain.verifyDomain.addTXTRecord')} - - - - - - - {translate('domain.verifyDomain.saveChanges')} - - - - - - - {translate('domain.verifyDomain.warning')} - - - - - validateDomain(accountID)} - message={domain?.validationError} - isAlertVisible={!!domain?.validationError} - containerStyles={styles.mb5} - isLoading={domain?.isValidationPending} - /> - - - ); -} - -BaseVerifyDomainPage.displayName = 'VerifyDomainPage'; -export default BaseVerifyDomainPage; diff --git a/src/pages/domain/SamlVerifyDomainPage.tsx b/src/pages/domain/SamlVerifyDomainPage.tsx new file mode 100644 index 0000000000000..d39c7c8e31890 --- /dev/null +++ b/src/pages/domain/SamlVerifyDomainPage.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import type SCREENS from '@src/SCREENS'; +import BaseVerifyDomainPage from './VerifyDomainPage'; + +type VerifyDomainPageProps = PlatformStackScreenProps; + +function SamlVerifyDomainPage({route}: VerifyDomainPageProps) { + return ( + + ); +} + +SamlVerifyDomainPage.displayName = 'SamlVerifyDomainPage'; +export default SamlVerifyDomainPage; diff --git a/src/pages/domain/VerifyDomainPage.tsx b/src/pages/domain/VerifyDomainPage.tsx index f1df1375b6834..af7aa1cef7301 100644 --- a/src/pages/domain/VerifyDomainPage.tsx +++ b/src/pages/domain/VerifyDomainPage.tsx @@ -1,19 +1,149 @@ -import React from 'react'; -import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; -import type SCREENS from '@src/SCREENS'; -import BaseVerifyDomainPage from './BaseVerifyDomainPage'; +import {Str} from 'expensify-common'; +import React, {useEffect} from 'react'; +import type {PropsWithChildren} from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import CopyableTextField from '@components/Domain/CopyableTextField'; +import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; +import FormHelpMessage from '@components/FormHelpMessage'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import Icon from '@components/Icon'; +import {Exclamation} from '@components/Icon/Expensicons'; +import RenderHTML from '@components/RenderHTML'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useNetwork from '@hooks/useNetwork'; +import useOnyx from '@hooks/useOnyx'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {getDomainValidationCode, validateDomain} from '@libs/actions/Domain'; +import {getLatestErrorMessage} from '@libs/ErrorUtils'; +import Navigation from '@libs/Navigation/Navigation'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; -type VerifyDomainPageProps = PlatformStackScreenProps; +function OrderedListRow({index, children}: PropsWithChildren<{index: number}>) { + const styles = useThemeStyles(); + return ( + + {index}. + {children} + + ); +} + +function VerifyDomainPage({accountID, forwardTo}: {accountID: number; forwardTo: 'WORKSPACES_DOMAIN_VERIFIED' | 'DOMAIN_VERIFIED'}) { + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); + + const [domain] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${accountID}`, {canBeMissing: true}); + const domainName = domain ? Str.extractEmailDomain(domain.email) : ''; + const {isOffline} = useNetwork(); + + useEffect(() => { + if (!domain?.validated) { + return; + } + Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(ROUTES[forwardTo].getRoute(accountID), {forceReplace: true})); + }, [accountID, domain?.validated, forwardTo]); + + useEffect(() => { + if (!accountID) { + return; + } + getDomainValidationCode(accountID, domainName); + }, [accountID, domainName]); -function VerifyDomainPage({route}: VerifyDomainPageProps) { return ( - + + + + + + + + + + + + + + + + + + + {translate('domain.verifyDomain.addTXTRecord')} + + {!domain?.validateCodeError && ( + + )} + + + + {!!domain?.validateCodeError && ( + + +