Skip to content

[SAML Configuration] Add Domain flow#76231

Merged
NikkiWines merged 35 commits intoExpensify:mainfrom
software-mansion-labs:feat/add-domain
Dec 9, 2025
Merged

[SAML Configuration] Add Domain flow#76231
NikkiWines merged 35 commits intoExpensify:mainfrom
software-mansion-labs:feat/add-domain

Conversation

@mhawryluk
Copy link
Contributor

@mhawryluk mhawryluk commented Nov 27, 2025

Explanation of Change

Creates a flow for claiming a new domain, as well as one for non-admins to verify a domain to become its admins.

Fixed Issues

$ #72895
PROPOSAL: N/A

Tests

account that doesn't have any workspaces and domains

  1. Go to Workspaces tab
  2. An empty state card should be displayed

account that doesn't have any workspaces, but has domains

  1. Go to Workspaces tab
  2. An empty state card for workspaces should be displayed and a list of domains is below it

account that has workspaces, but doesn't have any domains

  1. Go to Workspaces tab
  2. List of workspaces should be displayed and below that an "Enhanced security" banner with a button opening the AddDomain RHP.
  3. The button at the top of the page says just "New workspace", like before

account that has workspaces and domains

  1. Go to Workspaces tab
  2. List of workspaces and domains should be displayed
  3. The button at the top of the page says "New" and is a dropdown button. Clicking on it should display menu with 2 options: "New workspace" and "New domain", each opening a dedicated RHP.

AddDomainPage, DomainAddedPage

  1. Go to Workspaces tab
  2. Create a new workspace if there aren't any
  3. Open the "Add domain" page (either new > new domain or "enable" in the enhanced security banner)
  4. Fill in the form
  5. Check if an error is shown when no value is typed in (but only after some interaction)
  6. Check if an error is shown when the domain name is not a valid domain name (no dot, unexpected characters etc.)
  7. Check if an error is shown when the domain is a public domain (e.g. gmail.com)
  8. Clicking on continue shouldn't do anything
  9. Enter a valid name of an existing domain
  10. An error should come from backend saying it already exists, nothing else happens
  11. Enter a valid name of a new domain.
  12. Check if claiming a new domain works.
  13. After a successful domain creation the user should be navigated to a "Domain added" RHP
  14. Click on Configure
  15. The user should be navigated to the created domain's page.

AddDomainVerifyAccountPage

  1. Log in with a new email and skip entering the magic code
  2. Open the AddDomain page
  3. Enter a valid domain name and click Continue
  4. No request should be made to backend
  5. The user should be navigated to the Verify Account page
  6. Enter the magic code and click on the button.
  7. User should be once again in the AddDomain page
  8. The user should be able to claim domain

DomainAccessRestrictedPage

  1. Log in with an account that is a member of a domain, but not an admin.
  2. Click on a row of the domain that the user is a member of, but not an admin.
  3. "Domain access restricted" RHP should open.
  4. Click on "Verify"
  5. The user should be navigated to the "Verify domain" RHP
  6. Successful verification should navigate to "Domain verified" RHP
  7. Clicking on the button in the RHP should then navigate to the domain settings page (?), the domain should be verified if it wasn't already and the user is now an admin.

for all RHPs

  1. Check if non-authorized access isn't possible via URL.
  2. Check if refreshing the page on web leads to a WorkspacesList page shown below the RHP.

Offline tests

  1. Go to Workspaces tab
  2. Open the AddDomain page
  3. The action button should be disabled when offline.

QA Steps

Same as tests.

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
android-1.mov
Android: mWeb Chrome
Nagranie.z.ekranu.2025-12-3.o.17.55.17.mov
iOS: Native
Simulator.Screen.Recording.-.iPhone.16.Pro.Max.-.2025-12-03.at.11.28.24.mp4
iOS: mWeb Safari
Simulator.Screen.Recording.-.iPhone.16.Pro.Max.-.2025-12-03.at.17.58.55.mp4
MacOS: Chrome / Safari
Nagranie.z.ekranu.2025-12-2.o.18.26.26.mov

(the recording below is slightly outdated, the input label has been fixed to say "Domain name")

Nagranie.z.ekranu.2025-11-28.o.11.35.32.mov
Nagranie.z.ekranu.2025-12-1.o.16.03.11.mov
Nagranie.z.ekranu.2025-12-3.o.11.33.53.mov

@melvin-bot
Copy link

melvin-bot bot commented Nov 27, 2025

Hey, I noticed you changed src/languages/en.ts in a PR from a fork. For security reasons, translations are not generated automatically for PRs from forks.

If you want to automatically generate translations for other locales, an Expensify employee will have to:

  1. Look at the code and make sure there are no malicious changes.
  2. Run the Generate static translations GitHub workflow. If you have write access and the K2 extension, you can simply click: [this button]

Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running:

npx ts-node ./scripts/generateTranslations.ts --help

Typically, you'd want to translate only what you changed by running npx ts-node ./scripts/generateTranslations.ts --compare-ref main

@codecov
Copy link

codecov bot commented Nov 27, 2025

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/CONST/index.ts 85.84% <ø> (+1.73%) ⬆️
src/ONYXKEYS.ts 100.00% <ø> (ø)
src/SCREENS.ts 100.00% <ø> (ø)
src/components/Domain/DomainMenuItem.tsx 77.77% <ø> (+66.66%) ⬆️
src/components/DomainsEmptyStateComponent.tsx 100.00% <100.00%> (ø)
src/libs/API/types.ts 100.00% <ø> (ø)
.../linkingConfig/RELATIONS/WORKSPACES_LIST_TO_RHP.ts 100.00% <ø> (ø)
src/libs/Navigation/linkingConfig/config.ts 75.00% <ø> (ø)
src/libs/actions/Domain.ts 13.20% <100.00%> (+13.20%) ⬆️
src/pages/domain/BaseDomainVerifiedPage.tsx 0.00% <0.00%> (ø)
... and 12 more
... and 320 files with indirect coverage changes

@OSBotify

This comment has been minimized.

@OSBotify
Copy link
Contributor

OSBotify commented Dec 1, 2025

🦜 Polyglot Parrot! 🦜

Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues:

View the translation diff
diff --git a/src/languages/de.ts b/src/languages/de.ts
index 246c82b3..1422551d 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -1339,7 +1339,7 @@ const translations: TranslationDeepObject<typeof en> = {
         updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) =>
             `änderte das ${translatedChangedField} zu ${newMerchant} (zuvor ${oldMerchant}), wodurch der Betrag auf ${newAmountToDisplay} aktualisiert wurde (zuvor ${oldAmountToDisplay})`,
         basedOnAI: 'basierend auf früheren Aktivitäten',
-        basedOnMCC: 'basierend auf Arbeitsbereichsregel',
+        basedOnMCC: 'Basierend auf der Arbeitsbereichsregel',
         threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `für ${comment}` : 'Ausgabe'}`,
         invoiceReportName: ({linkedReportID}: OriginalMessage<typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW>) => `Rechnungsbericht Nr. ${linkedReportID}`,
         threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} gesendet${comment ? `für ${comment}` : ''}`,
@@ -7745,18 +7745,23 @@ ${
                 `Bitte verifizieren Sie sich als autorisierte/r Unternehmensadministrator/in für <strong>${domainName}</strong>, wenn Sie Kontrolle über Folgendes benötigen:`,
             companyCardManagement: 'Firmenkartenverwaltung',
             accountCreationAndDeletion: 'Kontoerstellung und -löschung',
-            workspaceCreation: 'Erstellung eines Arbeitsbereichs',
-            samlSSO: 'SAML SSO',
+            workspaceCreation: 'Erstellung des Arbeitsbereichs',
+            samlSSO: 'SAML-SSO',
+        },
+        addDomain: {
+            title: 'Domain hinzufügen',
+            subtitle: 'Geben Sie den Namen der privaten Domain ein, auf die Sie zugreifen möchten (z. B. expensify.com).',
+            domainName: 'Domainname',
+            newDomain: 'Neue Domain',
         },
-        addDomain: {title: 'Domain hinzufügen', subtitle: 'Geben Sie den Namen der privaten Domain ein, auf die Sie zugreifen möchten (z. B. expensify.com).', domainName: 'Domainname'},
         domainAdded: {
             title: 'Domain hinzugefügt',
-            description: 'Als Nächstes müssen Sie die Inhaberschaft der Domain verifizieren und Ihre Sicherheitseinstellungen anpassen.',
+            description: 'Als Nächstes müssen Sie die Inhaberschaft der Domain bestätigen und Ihre Sicherheitseinstellungen anpassen.',
             configure: 'Konfigurieren',
         },
         enhancedSecurity: {
             title: 'Verbesserte Sicherheit',
-            subtitle: 'Erzwingen Sie, dass sich Mitglieder Ihrer Domain über Single Sign-On anmelden, beschränken Sie die Erstellung von Workspaces und mehr.',
+            subtitle: 'Erzwingen Sie für Mitglieder Ihrer Domain die Anmeldung per Single Sign-On, schränken Sie die Erstellung von Workspaces ein und vieles mehr.',
             enable: 'Aktivieren',
         },
     },
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 153f85a7..29e4abf1 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -1343,7 +1343,7 @@ const translations: TranslationDeepObject<typeof en> = {
         updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) =>
             `a changé le ${translatedChangedField} en ${newMerchant} (précédemment ${oldMerchant}), ce qui a mis à jour le montant à ${newAmountToDisplay} (précédemment ${oldAmountToDisplay})`,
         basedOnAI: "basé sur l'activité passée",
-        basedOnMCC: "basé sur la règle de l'espace de travail",
+        basedOnMCC: 'Selon la règle de l’espace de travail',
         threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `pour ${comment}` : 'dépense'}`,
         invoiceReportName: ({linkedReportID}: OriginalMessage<typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW>) => `Rapport de Facture n°${linkedReportID}`,
         threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} envoyé${comment ? `pour ${comment}` : ''}`,
@@ -7746,17 +7746,22 @@ ${
         accessRestricted: {
             title: 'Accès restreint',
             subtitle: ({domainName}: {domainName: string}) =>
-                `Veuillez confirmer que vous êtes un administrateur d’entreprise autorisé pour <strong>${domainName}</strong> si vous avez besoin d’avoir le contrôle sur :`,
+                `Veuillez vous authentifier en tant qu’administrateur d’entreprise autorisé pour <strong>${domainName}</strong> si vous avez besoin d’avoir le contrôle sur :`,
             companyCardManagement: 'Gestion des cartes d’entreprise',
             accountCreationAndDeletion: 'Création et suppression de compte',
-            workspaceCreation: 'Création d’espace de travail',
+            workspaceCreation: "Création d'espace de travail",
             samlSSO: 'SSO SAML',
         },
-        addDomain: {title: 'Ajouter un domaine', subtitle: 'Saisissez le nom du domaine privé auquel vous souhaitez accéder (p. ex. expensify.com).', domainName: 'Nom de domaine'},
+        addDomain: {
+            title: 'Ajouter un domaine',
+            subtitle: 'Saisissez le nom du domaine privé auquel vous souhaitez accéder (par exemple expensify.com).',
+            domainName: 'Nom de domaine',
+            newDomain: 'Nouveau domaine',
+        },
         domainAdded: {title: 'Domaine ajouté', description: 'Ensuite, vous devrez vérifier la propriété du domaine et ajuster vos paramètres de sécurité.', configure: 'Configurer'},
         enhancedSecurity: {
             title: 'Sécurité renforcée',
-            subtitle: 'Exigez que les membres de votre domaine se connectent via l’authentification unique, restreignez la création d’espaces de travail, et plus encore.',
+            subtitle: "Exiger que les membres de votre domaine se connectent via l'authentification unique, restreindre la création d'espaces de travail, et plus encore.",
             enable: 'Activer',
         },
     },
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 5bf29d9b..82a25599 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -1337,7 +1337,7 @@ const translations: TranslationDeepObject<typeof en> = {
         updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) =>
             `ha cambiato il ${translatedChangedField} in ${newMerchant} (precedentemente ${oldMerchant}), il che ha aggiornato l'importo a ${newAmountToDisplay} (precedentemente ${oldAmountToDisplay})`,
         basedOnAI: 'basato su attività passate',
-        basedOnMCC: 'basato su regola dello spazio di lavoro',
+        basedOnMCC: 'in base alla regola dello spazio di lavoro',
         threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `per ${comment}` : 'spesa'}`,
         invoiceReportName: ({linkedReportID}: OriginalMessage<typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW>) => `Rapporto Fattura n. ${linkedReportID}`,
         threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} inviato${comment ? `per ${comment}` : ''}`,
@@ -7724,17 +7724,26 @@ ${
         },
         accessRestricted: {
             title: 'Accesso limitato',
-            subtitle: ({domainName}: {domainName: string}) => `Conferma di essere un amministratore aziendale autorizzato per <strong>${domainName}</strong> se ti serve il controllo su:`,
+            subtitle: ({domainName}: {domainName: string}) => `Verificati come amministratore aziendale autorizzato per <strong>${domainName}</strong> se hai bisogno di gestire:`,
             companyCardManagement: 'Gestione delle carte aziendali',
-            accountCreationAndDeletion: "Creazione ed eliminazione dell'account",
+            accountCreationAndDeletion: "Creazione e cancellazione dell'account",
             workspaceCreation: 'Creazione dello spazio di lavoro',
             samlSSO: 'SAML SSO',
         },
-        addDomain: {title: 'Aggiungi dominio', subtitle: 'Inserisci il nome del dominio privato a cui desideri accedere (ad esempio expensify.com).', domainName: 'Nome di dominio'},
-        domainAdded: {title: 'Dominio aggiunto', description: 'Successivamente, dovrai verificare la proprietà del dominio e regolare le impostazioni di sicurezza.', configure: 'Configura'},
+        addDomain: {
+            title: 'Aggiungi dominio',
+            subtitle: 'Inserisci il nome del dominio privato a cui desideri accedere (ad esempio expensify.com).',
+            domainName: 'Nome di dominio',
+            newDomain: 'Nuovo dominio',
+        },
+        domainAdded: {
+            title: 'Dominio aggiunto',
+            description: 'Successivamente, dovrai verificare la proprietà del dominio e regolare le tue impostazioni di sicurezza.',
+            configure: 'Configura',
+        },
         enhancedSecurity: {
             title: 'Sicurezza avanzata',
-            subtitle: 'Obbliga i membri del tuo dominio ad accedere tramite single sign-on, limita la creazione di spazi di lavoro e altro ancora.',
+            subtitle: 'Richiedi ai membri del tuo dominio di accedere tramite Single Sign-On, limita la creazione di spazi di lavoro e altro ancora.',
             enable: 'Abilita',
         },
     },
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 244ec14b..68cd07e6 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -1339,7 +1339,7 @@ const translations: TranslationDeepObject<typeof en> = {
         updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) =>
             `${translatedChangedField}を${newMerchant}に変更しました(以前は${oldMerchant})、これにより金額が${newAmountToDisplay}に更新されました(以前は${oldAmountToDisplay})。`,
         basedOnAI: '過去のアクティビティに基づく',
-        basedOnMCC: 'ワークスペースルールに基づく',
+        basedOnMCC: 'ワークスペースのルールに基づく',
         threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `${comment}用` : '経費'}`,
         invoiceReportName: ({linkedReportID}: OriginalMessage<typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW>) => `請求書レポート #${linkedReportID}`,
         threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} 送信済み${comment ? `${comment} のために` : ''}`,
@@ -7653,17 +7653,22 @@ ${
         },
         accessRestricted: {
             title: 'アクセスが制限されています',
-            subtitle: ({domainName}: {domainName: string}) => `以下の管理が必要な場合は、<strong>${domainName}</strong> の権限を持つ会社管理者であることを確認してください:`,
-            companyCardManagement: '会社カードの管理',
+            subtitle: ({domainName}: {domainName: string}) => `以下を管理する必要がある場合は、<strong>${domainName}</strong> の認可された会社管理者であることを確認してください:`,
+            companyCardManagement: '法人カードの管理',
             accountCreationAndDeletion: 'アカウントの作成と削除',
             workspaceCreation: 'ワークスペースの作成',
-            samlSSO: 'SAML SSO',
+            samlSSO: 'SAML シングルサインオン',
+        },
+        addDomain: {
+            title: 'ドメインを追加',
+            subtitle: 'アクセスしたいプライベートドメイン名を入力してください(例:expensify.com)。',
+            domainName: 'ドメイン名',
+            newDomain: '新しいドメイン',
         },
-        addDomain: {title: 'ドメインを追加', subtitle: 'アクセスしたいプライベートドメインの名前を入力してください(例:expensify.com)。', domainName: 'ドメイン名'},
         domainAdded: {title: 'ドメインが追加されました', description: '次に、ドメインの所有権を確認し、セキュリティ設定を調整する必要があります。', configure: '設定'},
         enhancedSecurity: {
             title: '強化されたセキュリティ',
-            subtitle: 'ドメインのメンバーにシングルサインオンでのログインを必須にし、ワークスペースの作成を制限するなど。',
+            subtitle: 'ドメインのメンバーにシングルサインオンでのログインを必須化し、ワークスペースの作成を制限するなど、さらに多くのことができます。',
             enable: '有効にする',
         },
     },
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 7fc30d6b..b472822e 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -1337,7 +1337,7 @@ const translations: TranslationDeepObject<typeof en> = {
         updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) =>
             `veranderde de ${translatedChangedField} naar ${newMerchant} (voorheen ${oldMerchant}), wat het bedrag bijwerkte naar ${newAmountToDisplay} (voorheen ${oldAmountToDisplay})`,
         basedOnAI: 'op basis van eerdere activiteit',
-        basedOnMCC: 'op basis van werkruimteregel',
+        basedOnMCC: 'op basis van een werkruimteregel',
         threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `voor ${comment}` : 'uitgave'}`,
         invoiceReportName: ({linkedReportID}: OriginalMessage<typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW>) => `Factuurrapport #${linkedReportID}`,
         threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} verzonden${comment ? `voor ${comment}` : ''}`,
@@ -7697,17 +7697,26 @@ ${
         },
         accessRestricted: {
             title: 'Toegang beperkt',
-            subtitle: ({domainName}: {domainName: string}) => `Bevestig dat u een bevoegde bedrijfsbeheerder bent voor <strong>${domainName}</strong> als u controle nodig heeft over:`,
-            companyCardManagement: 'Bedrijfskaartbeheer',
-            accountCreationAndDeletion: 'Aanmaken en verwijderen van accounts',
+            subtitle: ({domainName}: {domainName: string}) => `Bevestig dat u een geautoriseerde bedrijfsbeheerder bent voor <strong>${domainName}</strong> als u controle nodig hebt over:`,
+            companyCardManagement: 'Beheer van bedrijfskaarten',
+            accountCreationAndDeletion: 'Accountaanmaak en -verwijdering',
             workspaceCreation: 'Werkruimte aanmaken',
             samlSSO: 'SAML SSO',
         },
-        addDomain: {title: 'Domein toevoegen', subtitle: 'Voer de naam in van het privédomein waartoe je toegang wilt krijgen (bijv. expensify.com).', domainName: 'Domeinnaam'},
-        domainAdded: {title: 'Domein toegevoegd', description: 'Vervolgens moet je de domeineigendom verifiëren en je beveiligingsinstellingen aanpassen.', configure: 'Configureren'},
+        addDomain: {
+            title: 'Domein toevoegen',
+            subtitle: 'Voer de naam in van het privédomein waartoe je toegang wilt krijgen (bijv. expensify.com).',
+            domainName: 'Domeinnaam',
+            newDomain: 'Nieuw domein',
+        },
+        domainAdded: {
+            title: 'Domein toegevoegd',
+            description: 'Vervolgens moet je het eigendom van het domein verifiëren en je beveiligingsinstellingen aanpassen.',
+            configure: 'Configureren',
+        },
         enhancedSecurity: {
             title: 'Verbeterde beveiliging',
-            subtitle: 'Vereis dat leden op je domein via single sign-on inloggen, beperk het aanmaken van werkruimtes en meer.',
+            subtitle: 'Verplicht leden van je domein om in te loggen via single sign-on, beperk het aanmaken van werkruimten en meer.',
             enable: 'Inschakelen',
         },
     },
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index ce9fef0e..4f812135 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -1336,7 +1336,7 @@ const translations: TranslationDeepObject<typeof en> = {
         updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) =>
             `zmienił ${translatedChangedField} na ${newMerchant} (wcześniej ${oldMerchant}), co zaktualizowało kwotę na ${newAmountToDisplay} (wcześniej ${oldAmountToDisplay})`,
         basedOnAI: 'na podstawie wcześniejszej aktywności',
-        basedOnMCC: 'na podstawie reguły przestrzeni roboczej',
+        basedOnMCC: 'na podstawie reguły obszaru roboczego',
         threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `dla ${comment}` : 'wydatek'}`,
         invoiceReportName: ({linkedReportID}: OriginalMessage<typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW>) => `Raport faktury nr ${linkedReportID}`,
         threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} wysłano${comment ? `dla ${comment}` : ''}`,
@@ -7686,17 +7686,22 @@ ${
         accessRestricted: {
             title: 'Dostęp ograniczony',
             subtitle: ({domainName}: {domainName: string}) =>
-                `Proszę zweryfikować się jako upoważniony administrator firmy dla <strong>${domainName}</strong>, jeśli potrzebujesz kontroli nad:`,
+                `Proszę zweryfikować się jako autoryzowany administrator firmy dla <strong>${domainName}</strong>, jeśli potrzebujesz kontroli nad:`,
             companyCardManagement: 'Zarządzanie kartami firmowymi',
             accountCreationAndDeletion: 'Tworzenie i usuwanie konta',
             workspaceCreation: 'Tworzenie obszaru roboczego',
             samlSSO: 'SAML SSO',
         },
-        addDomain: {title: 'Dodaj domenę', subtitle: 'Wprowadź nazwę prywatnej domeny, do której chcesz uzyskać dostęp (np. expensify.com).', domainName: 'Nazwa domeny'},
+        addDomain: {
+            title: 'Dodaj domenę',
+            subtitle: 'Wprowadź nazwę prywatnej domeny, do której chcesz uzyskać dostęp (np. expensify.com).',
+            domainName: 'Nazwa domeny',
+            newDomain: 'Nowa domena',
+        },
         domainAdded: {title: 'Dodano domenę', description: 'Następnie musisz zweryfikować własność domeny i dostosować ustawienia zabezpieczeń.', configure: 'Skonfiguruj'},
         enhancedSecurity: {
             title: 'Zwiększone bezpieczeństwo',
-            subtitle: 'Wymagaj, aby członkowie w Twojej domenie logowali się za pomocą logowania jednokrotnego (SSO), ograniczaj tworzenie obszarów roboczych i nie tylko.',
+            subtitle: 'Wymagaj, aby członkowie Twojej domeny logowali się przez Single Sign-On (SSO), ograniczaj tworzenie obszarów roboczych i nie tylko.',
             enable: 'Włącz',
         },
     },
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index c1a7beee..c38c032f 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -7701,13 +7701,18 @@ ${
         },
         accessRestricted: {
             title: 'Acesso restrito',
-            subtitle: ({domainName}: {domainName: string}) => `Confirme que você é um administrador autorizado da empresa <strong>${domainName}</strong> se precisar de controle sobre:`,
+            subtitle: ({domainName}: {domainName: string}) => `Confirme que você é um administrador autorizado da empresa para <strong>${domainName}</strong> se precisar de controle sobre:`,
             companyCardManagement: 'Gerenciamento de cartões corporativos',
-            accountCreationAndDeletion: 'Criação e exclusão de contas',
-            workspaceCreation: 'Criação de espaço de trabalho',
+            accountCreationAndDeletion: 'Criação e exclusão de conta',
+            workspaceCreation: 'Criação do espaço de trabalho',
             samlSSO: 'SSO SAML',
         },
-        addDomain: {title: 'Adicionar domínio', subtitle: 'Insira o nome do domínio privado que você deseja acessar (por exemplo, expensify.com).', domainName: 'Nome de domínio'},
+        addDomain: {
+            title: 'Adicionar domínio',
+            subtitle: 'Digite o nome do domínio privado que você deseja acessar (ex.: expensify.com).',
+            domainName: 'Nome de domínio',
+            newDomain: 'Novo domínio',
+        },
         domainAdded: {
             title: 'Domínio adicionado',
             description: 'Em seguida, você precisará verificar a propriedade do domínio e ajustar suas configurações de segurança.',
@@ -7715,7 +7720,7 @@ ${
         },
         enhancedSecurity: {
             title: 'Segurança aprimorada',
-            subtitle: 'Exija que os membros do seu domínio façam login por meio de single sign-on (SSO), restrinja a criação de espaços de trabalho e muito mais.',
+            subtitle: 'Exija que os membros do seu domínio façam login por meio de logon único (SSO), restrinja a criação de espaços de trabalho e muito mais.',
             enable: 'Ativar',
         },
     },
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index a0743192..b52bc088 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -1317,7 +1317,7 @@ const translations: TranslationDeepObject<typeof en> = {
         updatedTheDistanceMerchant: ({translatedChangedField, newMerchant, oldMerchant, newAmountToDisplay, oldAmountToDisplay}: UpdatedTheDistanceMerchantParams) =>
             `将${translatedChangedField}更改为${newMerchant}(之前为${oldMerchant}),这更新了金额为${newAmountToDisplay}(之前为${oldAmountToDisplay})`,
         basedOnAI: '基于过去的活动',
-        basedOnMCC: '基于工作空间规则',
+        basedOnMCC: '基于工作区规则',
         threadExpenseReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} ${comment ? `为${comment}` : '费用'}`,
         invoiceReportName: ({linkedReportID}: OriginalMessage<typeof CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW>) => `发票报告 #${linkedReportID}`,
         threadPaySomeoneReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} 已发送${comment ? `对于${comment}` : ''}`,
@@ -7531,15 +7531,15 @@ ${
         },
         accessRestricted: {
             title: '访问受限',
-            subtitle: ({domainName}: {domainName: string}) => `如果您需要对以下内容进行控制,请验证您是 <strong>${domainName}</strong> 的授权公司管理员:`,
+            subtitle: ({domainName}: {domainName: string}) => `如果您需要对以下内容进行管理,请验证您是 <strong>${domainName}</strong> 的授权公司管理员:`,
             companyCardManagement: '公司卡管理',
-            accountCreationAndDeletion: '账户创建与删除',
+            accountCreationAndDeletion: '账户创建和删除',
             workspaceCreation: '工作区创建',
             samlSSO: 'SAML 单点登录',
         },
-        addDomain: {title: '添加域', subtitle: '请输入您要访问的私有域名(例如 expensify.com)。', domainName: '域名'},
-        domainAdded: {title: '域名已添加', description: '接下来,您需要验证域名所有权并调整您的安全设置。', configure: '配置'},
-        enhancedSecurity: {title: '增强的安全性', subtitle: '要求您域内的成员通过单点登录进行登录,限制工作区创建等。', enable: '启用'},
+        addDomain: {title: '添加域', subtitle: '请输入您想访问的私有域名(例如:expensify.com)。', domainName: '域名', newDomain: '新域名'},
+        domainAdded: {title: '已添加域名', description: '接下来,您需要验证域名的所有权并调整您的安全设置。', configure: '配置'},
+        enhancedSecurity: {title: '增强的安全性', subtitle: '要求您域内的成员使用单点登录登录、限制工作区创建等。', enable: '启用'},
     },
 };
 // IMPORTANT: This line is manually replaced in generate translation files by scripts/generateTranslations.ts,

Note

You can apply these changes to your branch by copying the patch to your clipboard, then running pbpaste | git apply 😉

Copy link
Contributor

@blazejkustra blazejkustra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments, other than that lgtm!

@mhawryluk mhawryluk marked this pull request as ready for review December 3, 2025 11:12
@mhawryluk mhawryluk requested review from a team as code owners December 3, 2025 11:12
@mhawryluk
Copy link
Contributor Author

Minor issue with padding for header

thanks, should be fixed now

@mhawryluk
Copy link
Contributor Author

mhawryluk commented Dec 8, 2025

Actually, after adding a domain ,we have a console error

I removed logging the error, the domain update is pushed by the pusher after the domain creation succeeds, so the first time the useEffect runs the new domain is not there, and then when it appears we correctly navigate to it.

@mhawryluk
Copy link
Contributor Author

And I'm not sure if it's a bug But after verification of the domain and after pressing Go back button or Android back button, we navigate to SAML screen

But in the case of Got it button, we open the domain main screen

I think it's better to open the domain main screen in both cases.

I think it's correct the way it is. The domain verification flow is opened from the SAML page, so the back button (at least the system one) has to go back to the SAML page. I could change "Got it" button to navigate to the SAML page too, should I?

@mhawryluk
Copy link
Contributor Author

Minor issue But after verification domain, when we press Got it and then try to swipe back on iOS We have broken the bottom navigation bar

this looks like a global issue when navigating back via a swipe back from a screen that has the tab bar to a screen that doesn't have it.

Nagranie.z.ekranu.2025-12-8.o.11.14.12.mov

not sure I can fix this easily now, but I will talk to the navigation team

@ZhenjaHorbach
Copy link
Contributor

Minor issue But after verification domain, when we press Got it and then try to swipe back on iOS We have broken the bottom navigation bar

this looks like a global issue when navigating back via a swipe back from a screen that has the tab bar to a screen that doesn't have it.

Nagranie.z.ekranu.2025-12-8.o.11.14.12.mov
not sure I can fix this easily now, but I will talk to the navigation team

Oh
Sorry
Didn't see it before

But yes
It's not our issue

@mhawryluk
Copy link
Contributor Author

mhawryluk commented Dec 8, 2025

Minor issue But after verification domain, when we press Got it and then try to swipe back on iOS We have broken the bottom navigation bar

this looks like a global issue when navigating back via a swipe back from a screen that has the tab bar to a screen that doesn't have it.
Nagranie.z.ekranu.2025-12-8.o.11.14.12.mov
not sure I can fix this easily now, but I will talk to the navigation team

Oh Sorry Didn't see it before

But yes It's not our issue

actually, I think we found a fix. I made it so that on narrow layouts we show the top level navigation bar only if the workspaces list page is under the current page in the stack. it is a little bit risky though. let me know if I should move it to a separate PR/issue instead to not risk regressions. cc: @mountiny

Nagranie.z.ekranu.2025-12-8.o.13.09.56.mov

@ZhenjaHorbach
Copy link
Contributor

Minor issue But after verification domain, when we press Got it and then try to swipe back on iOS We have broken the bottom navigation bar

this looks like a global issue when navigating back via a swipe back from a screen that has the tab bar to a screen that doesn't have it.
Nagranie.z.ekranu.2025-12-8.o.11.14.12.mov
not sure I can fix this easily now, but I will talk to the navigation team

Oh Sorry Didn't see it before
But yes It's not our issue

actually, I think we found a fix. I made it so that on narrow layouts we show the top level navigation bar only if the workspaces list page is under the current page in the stack. it is a little bit risky though. let me know if I should move it to a separate PR/issue instead to not risk regressions. cc: @mountiny

Nagranie.z.ekranu.2025-12-8.o.13.09.56.mov

Yes
Since this issue is reproducible with workspaces, also
We can skip this issue here

@ZhenjaHorbach
Copy link
Contributor

And as for me, everything looks good!

@melvin-bot melvin-bot bot requested a review from mountiny December 8, 2025 12:39
@mhawryluk
Copy link
Contributor Author

okay, I reverted the changes to TopLevelNavigationTabBar for now, to not risk regression with this PR

Copy link
Contributor

@mountiny mountiny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing stands out to me, looks good!

Copy link
Contributor

@NikkiWines NikkiWines left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, looks good 🚢

@NikkiWines NikkiWines merged commit 15fa5a2 into Expensify:main Dec 9, 2025
32 checks passed
@OSBotify
Copy link
Contributor

OSBotify commented Dec 9, 2025

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/NikkiWines in version: 9.2.75-0 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 cancelled 🔪

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/NikkiWines in version: 9.2.75-0 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@IuliiaHerets
Copy link

IuliiaHerets commented Dec 11, 2025

@mhawryluk QA team cannot execute steps 6-7 because we’re unable to verify the domain on the DomainAccessRestrictedPage. Is it acceptable to skip these steps?”
image

@mhawryluk
Copy link
Contributor Author

mhawryluk commented Dec 11, 2025

not ideal, but I think it is acceptable. we tested it on some real private domains during implementation and I think the flow was also skipped in QA tests when we introduced the domain verification flow to new dot. cc: @NikkiWines

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/NikkiWines in version: 9.2.77-0 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 cancelled 🔪
🍎 iOS 🍎 cancelled 🔪

@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/yuwenmemon in version: 9.2.77-1 🚀

platform result
🖥 desktop 🖥 success ✅
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants