Skip to content

[SAML Configuration] Display domains to members and admins in Workspaces tab#72713

Merged
mountiny merged 27 commits into
Expensify:mainfrom
software-mansion-labs:feat/saml-display-domain-list
Oct 23, 2025
Merged

[SAML Configuration] Display domains to members and admins in Workspaces tab#72713
mountiny merged 27 commits into
Expensify:mainfrom
software-mansion-labs:feat/saml-display-domain-list

Conversation

@mhawryluk

@mhawryluk mhawryluk commented Oct 15, 2025

Copy link
Copy Markdown
Contributor

Explanation of Change

Replaces the list of workspaces in the Workspaces tab with a SelectionList with sections, one for workspaces and one for domains. Clicking on a domain row links to old dot.

Fixed Issues

$ #72512
PROPOSAL: N/A

Tests

  • Verify that no errors appear in the JS console
  1. Open the Workspaces tab
  2. Check if all domains that the user is a member of or an admin of are listed below the list of workspaces
  3. Click on a row of a domain that the user is not an admin of -> nothing should happen
  4. Click on a row of a domain that the user is an admin of -> a domain settings in OldDot should be opened in the browser (the list of domains page if user has multiple or the specific domain's setting page if there is just one)
  5. Duplicate a workspace, see if auto scrolling to the new row works fine.
  6. Open the tab on an account that has no workspaces and no domains -> should look the same as currently.
  7. Open the tab on an account that has no workspaces but has domains -> a "you have no workspaces" message should be shown in place of the workspaces list, domains should be listed below.

Offline tests

Go offline. Perform the same steps as online tests, with the same outcome

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
    • MacOS: Desktop
  • 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
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message: <this translation already existed for other keys, so I didn't ask>
    • 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
Nagranie.z.ekranu.2025-10-21.o.16.48.01.mov
Android: mWeb Chrome
Nagranie.z.ekranu.2025-10-21.o.16.50.46.mov
iOS: Native
Simulator.Screen.Recording.-.iPhone.16.Pro.Max.-.2025-10-21.at.16.36.32.mp4
Simulator Screenshot - iPhone 16 Pro Max - 2025-10-21 at 16 39 54
iOS: mWeb Safari

(non-admin, non-clickable domain row)

Simulator.Screen.Recording.-.iPhone.16.Pro.Max.-.2025-10-21.at.16.45.43.mp4
MacOS: Chrome / Safari
Nagranie.z.ekranu.2025-10-22.o.12.00.08.mov
MacOS: Desktop
Nagranie.z.ekranu.2025-10-22.o.12.08.15.mov

@mhawryluk mhawryluk changed the title Display domains to members and admins in Workspaces tab [SAML Configuration] Display domains to members and admins in Workspaces tab Oct 16, 2025
@OSBotify

Copy link
Copy Markdown
Contributor

🦜 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 01f5088e..406b2028 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -674,6 +674,7 @@ const translations = {
         pinned: 'Angeheftet',
         read: 'Gelesen',
         copyToClipboard: 'In die Zwischenablage kopieren',
+        domains: 'Domänen',
     },
     supportalNoAccess: {
         title: 'Nicht so schnell',
@@ -2617,7 +2618,7 @@ ${amount} für ${merchant} - ${date}`,
                 descriptionTwo: 'Kategorisieren und taggen Sie Ausgaben',
                 descriptionThree: 'Berichte erstellen und teilen',
             },
-            price: 'Testen Sie es 30 Tage lang kostenlos, dann upgraden Sie für nur <strong>$5/Monat</strong>.',
+            price: 'Teste es 30 Tage kostenlos, anschließend für nur <strong>$5/Monat</strong> upgraden.',
             createWorkspace: 'Arbeitsbereich erstellen',
         },
         confirmWorkspace: {
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 0715f61e..1e90aad6 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -672,6 +672,7 @@ const translations = {
         pinned: 'Épinglé',
         read: 'Lu',
         copyToClipboard: 'Copier dans le presse-papiers',
+        domains: 'Domaines',
     },
     supportalNoAccess: {
         title: 'Pas si vite',
@@ -2613,7 +2614,7 @@ ${amount} pour ${merchant} - ${date}`,
                 descriptionTwo: 'Catégoriser et étiqueter les dépenses',
                 descriptionThree: 'Créer et partager des rapports',
             },
-            price: "Essayez-le gratuitement pendant 30 jours, puis passez à l'abonnement pour seulement <strong>5 $/mois</strong>.",
+            price: 'Essayez gratuitement pendant 30 jours, puis passez à la version payante pour seulement <strong>$5/mois</strong>.',
             createWorkspace: 'Créer un espace de travail',
         },
         confirmWorkspace: {
diff --git a/src/languages/it.ts b/src/languages/it.ts
index d7a29b41..95df534b 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -672,6 +672,7 @@ const translations = {
         pinned: 'Fissato',
         read: 'Letto',
         copyToClipboard: 'Copia negli appunti',
+        domains: 'Domini',
     },
     supportalNoAccess: {
         title: 'Non così in fretta',
@@ -2628,8 +2629,8 @@ ${amount} per ${merchant} - ${date}`,
                 descriptionTwo: 'Categorizza e tagga le spese',
                 descriptionThree: 'Crea e condividi rapporti',
             },
-            price: 'Provalo gratis per 30 giorni, poi passa al piano superiore per soli <strong>$5/mese</strong>.',
-            createWorkspace: 'Crea workspace',
+            price: "Provalo gratis per 30 giorni, poi esegui l'upgrade a soli <strong>$5/mese</strong>.",
+            createWorkspace: 'Crea spazio di lavoro',
         },
         confirmWorkspace: {
             title: 'Conferma workspace',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index bfa3e411..ea5e77e3 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -672,6 +672,7 @@ const translations = {
         pinned: '固定済み',
         read: '既読',
         copyToClipboard: 'クリップボードにコピー',
+        domains: 'ドメイン',
     },
     supportalNoAccess: {
         title: 'ちょっと待ってください',
@@ -2617,7 +2618,7 @@ ${date} - ${merchant}に${amount}`,
                 descriptionTwo: '経費を分類してタグ付けする',
                 descriptionThree: 'レポートを作成して共有する',
             },
-            price: '30日間無料でお試しいただけます。その後、<strong>$5/月</strong>でアップグレードしてください。',
+            price: '30日間無料でお試しください。以降は<strong>$5/月</strong>でアップグレードできます。',
             createWorkspace: 'ワークスペースを作成',
         },
         confirmWorkspace: {
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 5221cffe..bd8594b0 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -671,6 +671,7 @@ const translations = {
         pinned: 'Vastgezet',
         read: 'Gelezen',
         copyToClipboard: 'Kopiëren naar klembord',
+        domains: 'Domeinen',
     },
     supportalNoAccess: {
         title: 'Niet zo snel',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index fe39b9ee..3c9a8b71 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -672,6 +672,7 @@ const translations = {
         pinned: 'Przypięte',
         read: 'Przeczytane',
         copyToClipboard: 'Skopiuj do schowka',
+        domains: 'Domeny',
     },
     supportalNoAccess: {
         title: 'Nie tak szybko',
@@ -2623,7 +2624,7 @@ ${amount} dla ${merchant} - ${date}`,
                 descriptionTwo: 'Kategoryzuj i taguj wydatki',
                 descriptionThree: 'Twórz i udostępniaj raporty',
             },
-            price: 'Wypróbuj za darmo przez 30 dni, a następnie przejdź na wyższy plan za jedyne <strong>5 USD/miesiąc</strong>.',
+            price: 'Wypróbuj za darmo przez 30 dni, a następnie uaktualnij za jedyne <strong>$5/mies.</strong>',
             createWorkspace: 'Utwórz przestrzeń roboczą',
         },
         confirmWorkspace: {
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index e8b87062..e1fec78e 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -674,6 +674,7 @@ const translations = {
         pinned: 'Fixado',
         read: 'Lido',
         copyToClipboard: 'Copiar para a área de transferência',
+        domains: 'Domínios',
     },
     supportalNoAccess: {
         title: 'Não tão rápido',
@@ -2621,7 +2622,7 @@ ${amount} para ${merchant} - ${date}`,
                 descriptionTwo: 'Categorizar e etiquetar despesas',
                 descriptionThree: 'Criar e compartilhar relatórios',
             },
-            price: 'Experimente gratuitamente por 30 dias, depois faça o upgrade por apenas <strong>US$5/mês</strong>.',
+            price: 'Experimente grátis por 30 dias e depois faça o upgrade por apenas <strong>$5/mês</strong>.',
             createWorkspace: 'Criar espaço de trabalho',
         },
         confirmWorkspace: {
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 653cf6ca..c754d955 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -673,6 +673,7 @@ const translations = {
         pinned: '已固定',
         read: '已读',
         copyToClipboard: '复制到剪贴板',
+        domains: '域名',
     },
     supportalNoAccess: {
         title: '慢一点',
@@ -2594,7 +2595,7 @@ ${merchant}的${amount} - ${date}`,
                 descriptionTwo: '分类和标记费用',
                 descriptionThree: '创建和分享报告',
             },
-            price: '免费试用30天,然后只需<strong>$5/月</strong>升级。',
+            price: '免费试用30天,之后仅需<strong>$5/月</strong>即可升级。',
             createWorkspace: '创建工作区',
         },
         confirmWorkspace: {

Note

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

Comment on lines +97 to +102
type GetWorkspaceMenuItem = {item: WorkspaceItem; index: number};

type DomainItem = ListItem & {title: string; action: () => void; disabled: boolean};

// eslint-disable-next-line react/no-unused-prop-types
type GetDomainMenuItem = {item: DomainItem; index: number};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this eslint rule necessary? I see that we use both item and index later in the codebase. Besides, do we need to pass index to GetWorkspaceMenuItem if we disable the rule that checks if all props get used?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

it shouldn't be necessary, but there's an error without it. no idea why :/ and I think disabling this rule won't make it so that we pass a function with a wrong signature to the component, if that's what you mean. I think this rule should just check if the component uses all of its props in its implementation

<PressableWithoutFeedback
role={CONST.ROLE.BUTTON}
accessibilityLabel="row"
style={[styles.mh5]}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If it's just 1 style I would avoid passing it in an array. Direct object will suffice, otherwise we add overhead of flattening the array. Not a blocker, just a minor optimisation

text={translate('workspace.new.newWorkspace')}
onPress={() => interceptAnonymousUser(() => Navigation.navigate(ROUTES.WORKSPACE_CONFIRMATION.getRoute(ROUTES.WORKSPACES_LIST.route)))}
icon={Expensicons.Plus}
style={[shouldUseNarrowLayout && [styles.flexGrow1, styles.mb3]]}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If this can be flattened that would be awesome 😄

ref={listRef}
sections={sections}
sectionTitleStyles={[styles.ph5, styles.pb5, styles.mt0, styles.mb0, styles.pt3]}
onSelectRow={() => {}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is not the first time in the codebase when we pass an empty function to onSelectRow. After this gets merged it's gonna be 3/8 usages. I think that we can set onSelectRow as an optional prop for SelectionList

renderItem: getDomainMenuItem,
},
] as Array<SectionListDataType<WorkspaceItem | DomainItem>>,
[domains, filteredWorkspaces, getDomainMenuItem, getListHeaderComponent, getWorkspaceMenuItem, translate],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

getListHeaderComponent is not wrapped in useCallback which means that the memoization won't work, because it's gonna be recreated every render

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

eslint stopped working for me and I didn't realize 😶

@codecov

codecov Bot commented Oct 16, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 1.88679% with 52 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pages/workspace/WorkspacesListPage.tsx 0.00% 44 Missing ⚠️
src/components/Domain/DomainsListRow.tsx 11.11% 8 Missing ⚠️
Files with missing lines Coverage Δ
src/ONYXKEYS.ts 100.00% <ø> (ø)
src/styles/index.ts 45.10% <ø> (+5.97%) ⬆️
src/components/Domain/DomainsListRow.tsx 11.11% <11.11%> (ø)
src/pages/workspace/WorkspacesListPage.tsx 0.40% <0.00%> (-0.41%) ⬇️

... and 1683 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@staszekscp staszekscp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM 🎉

Comment thread src/languages/pl.ts
pinned: 'Przypięte',
read: 'Przeczytane',
copyToClipboard: 'Skopiuj do schowka',
domains: 'Domeny',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Great translation

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agreed

@mhawryluk mhawryluk marked this pull request as ready for review October 20, 2025 10:10
@mhawryluk mhawryluk requested review from a team as code owners October 20, 2025 10:10
@melvin-bot melvin-bot Bot requested review from ZhenjaHorbach and removed request for a team October 20, 2025 10:11
@melvin-bot

melvin-bot Bot commented Oct 20, 2025

Copy link
Copy Markdown

@ZhenjaHorbach Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

Comment on lines +520 to +531
const domains = useMemo(
() =>
Object.values(allDomains ?? {})
.filter((domain) => domain !== undefined)
.map((domain) => ({
title: Str.extractEmailDomain(domain.email),
action: navigateToDomain,
disabled: !adminAccess?.[`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${domain.accountID}`],
pendingAction: domain.pendingAction,
})) satisfies DomainItem[],
[navigateToDomain, allDomains, adminAccess],
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Let's optimize this code a bit

const domains = useMemo(() => {
    if (!allDomains) return [];

    return Object.values(allDomains).reduce<DomainItem[]>((acc, domain) => {
        if (!domain) return acc;

        acc.push({
            title: Str.extractEmailDomain(domain.email),
            action: navigateToDomain,
            disabled: !adminAccess?.[`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_ADMIN_ACCESS}${domain.accountID}`],
            pendingAction: domain.pendingAction,
        });

        return acc;
    }, []);
}, [navigateToDomain, allDomains, adminAccess]);

style={[shouldUseNarrowLayout && [styles.flexGrow1, styles.mb3]]}
/>
const getHeaderButton = () =>
workspaces.length > 0 ? (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

!!workspaces.length ? (

[
// workspaces empty state
{
data: workspaces.length === 0 ? [{}] : [],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you please clarify this moment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

when there are no workspaces, we create here a new section for the selection list, that shows the "you have no workspaces" empty state component. the section then has one element and it's an empty object, cause since there is only at-most one element to render, renderItem function doesn't need any input parameters

@mountiny

Copy link
Copy Markdown
Contributor

@mhawryluk Can you please take a look at the failing jest/ eslint?

@ZhenjaHorbach

ZhenjaHorbach commented Oct 21, 2025

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible 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 checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I verified proper code patterns were followed (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 verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • 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
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • 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 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.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
2025-10-23.09.32.43.mov
Android: mWeb Chrome
2025-10-23.09.39.14.mov
iOS: HybridApp
2025-10-23.09.27.26.mov
iOS: mWeb Safari
2025-10-23.09.24.31.mov
MacOS: Chrome / Safari
2025-10-23.09.21.01.mov
MacOS: Desktop
2025-10-23.09.21.01.mov

@mountiny

Copy link
Copy Markdown
Contributor

For what reason? Non domain admins can't do anything in domain on OldDot.

Just purely for the reason that you can take the other actions like ask to be admin there at that point which you cannot do in NewDot as part of this release

But I dont feel strongly about that

@mountiny

Copy link
Copy Markdown
Contributor

I agree that it's a good idea to try and minimize the chance of regression, therefore I think we should keep FlatList in this PR for now and then follow up with another PR replacing it with FlashList (either directly, through the new SelectionList, or with a new section-supporting component, I will consult this with @zfurtak once she's back in office).

We could just swap to FlashList, sorry, we dont have to use SelectionList here. The main thing is to do it in separate PR risk of regressions.

However, without the change, the users would see that janky scrolling you and @ZhenjaHorbach reported above, right? If yes then I think we should swap to FlashList and then merge this to fix that issue

@mhawryluk

Copy link
Copy Markdown
Contributor Author

I don't experience the janky animation bug with FlatList, I believe this is an issue specifically with React Native's SectionList

@mhawryluk

Copy link
Copy Markdown
Contributor Author

For what reason? Non domain admins can't do anything in domain on OldDot.

Just purely for the reason that you can take the other actions like ask to be admin there at that point which you cannot do in NewDot as part of this release

But I dont feel strongly about that

When I sign in with an account that is not an admin of any domain, the Domains tab in OldDot settings isn't visible nor accessible, so I don't think we should navigate there

@mountiny

Copy link
Copy Markdown
Contributor

When I sign in with an account that is not an admin of any domain, the Domains tab in OldDot settings isn't visible nor accessible, so I don't think we should navigate there

I think its only if you are on domain that is claimed/ or verified

@mountiny

Copy link
Copy Markdown
Contributor

Ok so are we good here from UX perspective @ZhenjaHorbach @mhawryluk? i am slightly confused if the bug you found is repro on the current shape of the branch

@mhawryluk

mhawryluk commented Oct 22, 2025

Copy link
Copy Markdown
Contributor Author

I believe that the current state of this branch (we're back on FlatList) should work and that the bug is now fixed. I tested it, I even tried setting lower values for windowSize to try and reproduce it now, but it seems to work (so the bug was only happening when using the old SelectionList which uses SectionList under the hood; both FlatList and FlashList work fine). but I leave it now for @ZhenjaHorbach for testing

@mhawryluk

mhawryluk commented Oct 22, 2025

Copy link
Copy Markdown
Contributor Author

all the review and design review comments (including the decision to not show domains when searching for a workspace) should also now be resolved btw ^^

@NikkiWines NikkiWines left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good! Two very small comments

return [];
}

return Object.values(allDomains).reduce<DomainItem[]>((acc, domain) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This variable name isn't very clear, maybe something like

Suggested change
return Object.values(allDomains).reduce<DomainItem[]>((acc, domain) => {
return Object.values(allDomains).reduce<DomainItem[]>((domainItems, domain) => {

Comment on lines +635 to +643
const data = useMemo(() => {
const shouldShowDomainsSection = !inputValue.trim().length && domains.length;

return [
!workspaces.length ? [{listItemType: 'workspaces-empty-state' as const}] : [],
filteredWorkspaces,
shouldShowDomainsSection ? [{listItemType: 'domains-header' as const}, ...domains] : [],
].flat();
}, [domains, filteredWorkspaces, workspaces.length, inputValue]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NAB: Might be good to add a comment here about what data contains (workspaces, domain header, and domains) for clarity

@mhawryluk

Copy link
Copy Markdown
Contributor Author

@NikkiWines implemented the suggestions, thanks

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

Sorry to bring this up again
But do we really want to use this hover color?

I thought we wanted the same hover color as when hovering over the arrow in a workspace item

2025-10-23.09.13.20.mov

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

And it seems that after the latest commits, domains are not displayed after searching

@trjExpensify
Could you clarify this moment?

During search, should we always show domains?
Or never?
Or so that they can be searched for?

2025-10-23.09.35.11.mov

@ZhenjaHorbach

ZhenjaHorbach commented Oct 23, 2025

Copy link
Copy Markdown
Contributor

But overall, the changes look good!

And if we want, we can merge this PR

@melvin-bot melvin-bot Bot requested a review from mountiny October 23, 2025 07:49
@mhawryluk

mhawryluk commented Oct 23, 2025

Copy link
Copy Markdown
Contributor Author

I also noticed that the icon colors don't match the workspaces row, but I guess they do match most of the other menu items in the app. About the domains not being displayed when searching for a workspace, there was a discussion about it on the issue #72512 (comment) We decided to not display domains when searching for workspaces for now; in the future we will add the ability to search for domains.

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

I also noticed that the icon colors don't match the workspaces row, but I guess they do match most of the other menu items in the app. About the domains not being displayed when searching for a workspace, there was a discussion about it on the issue #72512 (comment) We decided to not display domains when searching for workspaces for now; in the future we will add the ability to search for domains.

Nice then
No more questions 😄

@mountiny

Copy link
Copy Markdown
Contributor

I thought we wanted the same hover color as when hovering over the arrow in a workspace item

I agree I think the icon should have same hover colour @mhawryluk did the design team say otherwise before?

if no, lets fix it please

@github-actions

Copy link
Copy Markdown
Contributor

LGTM :feelsgood:. Thank you for your hard work!

@github-actions

Copy link
Copy Markdown
Contributor

HelpDot Documentation Review

Overall Assessment

This PR does not contain any HelpDot documentation changes. The PR is focused on implementing a UI feature to display domains in the Workspaces tab alongside workspace information. All changes are code-related, including new React components, language translations, and UI modifications.

Documentation Status

No documentation files (.md) were modified in this PR

Analysis Summary

  • Files Reviewed: 15 code files (TypeScript components, language files, styles)
  • Documentation Files: 0
  • Content Type: UI implementation for SAML domain display feature

Recommendations

Since this PR introduces new user-facing functionality (displaying domains in the Workspaces tab with clickable admin access), consider whether:

  1. Help Documentation Updates: This feature may warrant updates to existing HelpDot articles about workspace management or domain administration
  2. User Communication: Users should understand how domain visibility and interaction works in the updated Workspaces tab
  3. Feature Documentation: Consider if this feature needs to be documented in user-facing help articles

Next Steps

If this feature requires user documentation:

  • Create or update relevant HelpDot articles explaining the new domain display functionality
  • Document the different behaviors for domain members vs. domain admins
  • Include this functionality in workspace management documentation

Note: This assessment found no documentation changes to review. The PR contains only code implementation.

@mountiny mountiny left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will continue in a bit

@mountiny mountiny left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you!

@mountiny mountiny merged commit e385139 into Expensify:main Oct 23, 2025
30 checks passed
@OSBotify

Copy link
Copy Markdown
Contributor

✋ 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
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/mountiny in version: 9.2.37-1 🚀

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

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/blimpich in version: 9.2.37-8 🚀

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.

8 participants