Skip to content

Add persistent bottom-right product marketing window - #96141

Merged
JS00001 merged 36 commits into
Expensify:mainfrom
KJ21-ENG:KJ21-ENG/95791-product-marketing-window
Aug 4, 2026
Merged

Add persistent bottom-right product marketing window#96141
JS00001 merged 36 commits into
Expensify:mainfrom
KJ21-ENG:KJ21-ENG/95791-product-marketing-window

Conversation

@KJ21-ENG

@KJ21-ENG KJ21-ENG commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

This PR adds a reusable product-marketing window that remains visible across authenticated app navigation until the user dismisses it or completes its CTA.

  • The manager is mounted outside changing route screens so the window persists while navigating between Home, Inbox, Spend, Workspaces, and other authenticated pages.
  • A single active announcement is configured at a time. The current July campaign uses the stable update key productUpdateJuly2026; dismissing it does not prevent a later campaign with a different key from appearing.
  • Announcement variants are audience-aware. The current "New role types for admins" campaign is admin-only, so it is shown only to users who administer at least one active workspace. The active admin workspace is targeted when available, otherwise the first eligible admin workspace is used.
  • Pressing Try it out stores the dismissal first and then opens the selected workspace's Members page. Pressing Dismiss stores the same update key without navigating.
  • Dismissal is persisted in the account-level nvp_lastDismissedMarketingWindow NVP through SetNameValuePair, with optimistic Onyx data and rollback data if the request fails.
  • The window waits for its dismissal and workspace-targeting data before rendering, avoiding a startup flash or an incorrect CTA destination. It is also excluded from logged-out, anonymous, and delegated-access sessions.
  • Centered and opted-in confirmation modals take precedence over the window. Ordinary popovers and route-backed right-docked navigation do not unnecessarily hide it.
  • The responsive card follows the supplied July mock and uses the inverse app color scheme: a light card in dark mode and a dark card in light mode. The artwork-to-heading, heading-to-body, and body-to-actions gaps are 16px, 2px, and 16px respectively.

Unit and navigation tests cover audience eligibility, workspace selection, dismissal and rollback behavior, CTA ordering and routing, modal precedence, startup loading, responsive styling, inverse themes, and persistence across route changes.

Fixed Issues

$ #95791
PROPOSAL: #95791 (comment)

Tests

Use eligible accounts that have not previously dismissed productUpdateJuly2026. Since dismissal is stored at account level, use a second fresh eligible account for the separate dismissal test.

  1. Sign in with account A, which is an admin of at least one active workspace.
  2. Verify the product-marketing window appears at the bottom-right on a wide layout and near the bottom edge on a narrow layout.
  3. Verify it shows the July artwork, the heading "New role types for admins", the approved body copy, Dismiss, and Try it out.
  4. Switch between light and dark app themes. Verify the window uses the inverse color scheme: dark in light mode and light in dark mode.
  5. Navigate between Home, Inbox, Spend, Workspaces, and Account. Verify the window remains visible in the same anchored position.
  6. Open a normal popover and verify the window remains visible. Close the popover.
  7. Open a centered confirmation modal, such as the delete-comment confirmation from a self-DM. Verify the window stays hidden while the modal is opening, visible, and closing, then reappears after the modal is fully hidden.
  8. Make an administered workspace active, press Try it out, and verify the window closes before navigation and the selected workspace's Members page opens.
  9. Refresh or restart the app and verify the window does not flash or reappear for account A.
  10. Sign in with fresh eligible account B, press Dismiss, and verify the window closes, stays closed during navigation, and remains dismissed after a refresh or restart.
  11. Sign in with an account that is not an admin of any active workspace and verify the current admin-only campaign is not shown.
  • Verify that no errors appear in the JS console

Offline tests

Use another eligible admin account that has not dismissed the current update.

  1. Sign in and verify the marketing window is visible.
  2. Go offline.
  3. Press Dismiss and verify the window closes immediately from the optimistic Onyx update.
  4. Go back online, allow the request to finish, then refresh the app.
  5. Verify the window remains dismissed and does not flash during startup.

QA Steps

Same as the Tests and Offline tests sections above on staging. Use fresh eligible admin accounts for the CTA, dismissal, and offline legs, plus one account that is not an admin of any active workspace.

  • Verify that no errors appear in the JS console

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 followed proper code patterns (see Reviewing the code)
    • 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 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)
  • 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)
  • 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, I verified the components using Avatar are 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.
  • 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
Record_2026-07-30-01-40-24_40d2c9e2d8fb5b5aba3904834590021e.mp4
Android: mWeb Chrome
Record_2026-07-30-02-02-32.mp4
iOS: Native
Screen.Recording.2026-07-30.at.1.44.11.AM.mov
iOS: mWeb Safari
Screen.Recording.2026-07-30.at.1.59.59.AM.mov
MacOS: Chrome / Safari
Screen.Recording.2026-07-30.at.1.51.50.AM.mov

@melvin-bot

melvin-bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

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

@KJ21-ENG
KJ21-ENG force-pushed the KJ21-ENG/95791-product-marketing-window branch from 8546c9a to 292fa34 Compare July 15, 2026 07:23
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Coverage Δ
src/CONST/index.ts 94.81% <ø> (ø)
src/ONYXKEYS.ts 100.00% <ø> (ø)
src/components/ConfirmModal.tsx 100.00% <ø> (ø)
src/components/DecisionModal.tsx 100.00% <ø> (ø)
src/components/ExportDownloadStatusModal.tsx 89.47% <ø> (ø)
src/components/FeatureTrainingModal.tsx 96.29% <ø> (ø)
src/components/Modal/BaseModal.tsx 96.92% <100.00%> (+0.95%) ⬆️
...igation/RootNavigatorExtraContent/index.native.tsx 100.00% <100.00%> (ø)
...ts/OpenAppFailureModal/BaseOpenAppFailureModal.tsx 100.00% <ø> (ø)
src/components/ProactiveAppReviewModal.tsx 100.00% <ø> (ø)
... and 15 more
... and 8 files with indirect coverage changes

@KJ21-ENG
KJ21-ENG marked this pull request as ready for review July 15, 2026 22:01
@KJ21-ENG
KJ21-ENG requested review from a team as code owners July 15, 2026 22:01
@melvin-bot
melvin-bot Bot requested review from heyjennahay and huult and removed request for a team July 15, 2026 22:01
@melvin-bot

melvin-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

@huult 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]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 292fa34b95

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/ProductMarketingWindow/ProductMarketingWindowManager.tsx Outdated
@KJ21-ENG

Copy link
Copy Markdown
Contributor Author

This PR uses productMarketingWindow:expensePolicyPdf-2026-07 to persist the card dismissal. The DismissProductTraining API needs to support namespaced values in the format productMarketingWindow:. It currently returns jsonCode: 402, so the dismissal is not persisted by the backend.

Could an internal engineer confirm whether this backend support is already being handled?
On the frontend, should we also add rollback handling when this API request fails, or keep that outside the scope of this PR?

@shawnborton

Copy link
Copy Markdown
Contributor

Let us know when this is ready for a @Expensify/design review, thanks!

@KJ21-ENG

Copy link
Copy Markdown
Contributor Author

Let us know when this is ready for a @Expensify/design review, thanks!

The frontend UI is ready for Design review. Please take a look, backend work is being handled separately. Thanks!

cc: @shawnborton @Expensify/design

@github-actions

Copy link
Copy Markdown
Contributor

🚧 shawnborton has triggered a test Expensify/App build. You can view the workflow run here.

@dannymcclain

Copy link
Copy Markdown
Contributor

First thing I notice right off the bat in the videos: don't we want these to be themed opposite the user's current setting?

@KJ21-ENG

Copy link
Copy Markdown
Contributor Author

@shawnborton Ops, i missed to push few commits, can you please retrigger the build ?

@shawnborton

Copy link
Copy Markdown
Contributor

Agree Danny, we should match the mockups.

I'm heading out for the day shortly but @dannymcclain can help trigger new builds!

@OSBotify

OSBotify commented Aug 4, 2026

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 e883beb145c..010ad2a6a10 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -10293,9 +10293,9 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
     productMarketingWindow: {
         roleTypes: {
             admin: {
-                heading: 'Neue Rollentypen für Administratoren',
-                body: 'Gib deinem Team mit neuen Rollen für Karten-, Personen- und Zahlungsadministratoren detailliertere Berechtigungen.',
-                cta: 'Ausprobieren',
+                heading: 'Neue Rollentypen für Admins',
+                body: 'Geben Sie Ihrem Team detailliertere Berechtigungen mit neuen Administratorrollen für Karten, Personen und Zahlungen.',
+                cta: 'Probieren Sie es aus',
             },
         },
     },
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 979af3e5110..1fbe3e7c74a 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -10454,8 +10454,8 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
     productMarketingWindow: {
         roleTypes: {
             admin: {
-                heading: 'Nuevos tipos de roles para administradores',
-                body: 'Otorga a tu equipo permisos más granulares con los nuevos roles de administrador de tarjetas, personas y pagos.',
+                heading: 'Nuevos tipos de roles para admins',
+                body: 'Ofrece a tu equipo permisos más granulares con los nuevos roles de administrador de tarjetas, personas y pagos.',
                 cta: 'Pruébalo',
             },
         },
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index f0e9fbdbccc..7f3349f7b1a 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -10328,8 +10328,8 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
         roleTypes: {
             admin: {
                 heading: 'Nouveaux types de rôles pour les administrateurs',
-                body: 'Accordez à votre équipe des autorisations plus granulaires grâce aux nouveaux rôles d’administrateur des cartes, des personnes et des paiements.',
-                cta: 'Essayer',
+                body: 'Offrez à votre équipe des autorisations plus granulaires avec de nouveaux rôles d’administrateur pour les cartes, les personnes et les paiements.',
+                cta: 'EssayerEssaye',
             },
         },
     },
diff --git a/src/languages/it.ts b/src/languages/it.ts
index f1d3e2f4860..159bb9669db 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -10270,8 +10270,8 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
         roleTypes: {
             admin: {
                 heading: 'Nuovi tipi di ruolo per gli amministratori',
-                body: 'Offri al tuo team autorizzazioni più granulari con i nuovi ruoli di amministratore delle carte, delle persone e dei pagamenti.',
-                cta: 'Prova ora',
+                body: 'Offri al tuo team autorizzazioni più granulari con i nuovi ruoli di amministratore per carte, persone e pagamenti.',
+                cta: 'Provalo',
             },
         },
     },
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 7a48b6b64e8..c8fd8084f12 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -10128,13 +10128,7 @@ ${reportName}`,
         },
     },
     productMarketingWindow: {
-        roleTypes: {
-            admin: {
-                heading: '管理者向けの新しい役割タイプ',
-                body: '新しいカード管理者、ユーザー管理者、支払い管理者の役割により、チームによりきめ細かな権限を付与できます。',
-                cta: 'お試しください',
-            },
-        },
+        roleTypes: {admin: {heading: '管理者向けの新しいロールタイプ', body: '新しいカード、メンバー、支払いの各管理者ロールで、チームにより細かな権限を付与できます。', cta: '試してみる'}},
     },
     productTrainingTooltip: {
         conciergeLHNGBR: '<tooltip>まずは<strong>こちらから!</strong></tooltip>',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index b62dad32a24..38026a2fe68 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -10240,7 +10240,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
         roleTypes: {
             admin: {
                 heading: 'Nieuwe roltypen voor beheerders',
-                body: 'Geef je team gedetailleerdere rechten met de nieuwe beheerdersrollen voor kaarten, personen en betalingen.',
+                body: 'Geef je team meer gedetailleerde toegangsrechten met nieuwe kaart-, personen- en betalingsbeheerrollen.',
                 cta: 'Probeer het uit',
             },
         },
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index 614bd9dd823..c7ff7a962bd 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -10208,9 +10208,9 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
     productMarketingWindow: {
         roleTypes: {
             admin: {
-                heading: 'Nowe typy ról administratora',
-                body: 'Nadaj zespołowi bardziej szczegółowe uprawnienia dzięki nowym rolom administratora kart, osób i płatności.',
-                cta: 'Wypróbuj',
+                heading: 'Nowe typy ról dla administratorów',
+                body: 'Daj swojemu zespołowi bardziej szczegółowe uprawnienia dzięki nowym rolom administratora kart, osób i płatności.',
+                cta: 'Wypróbuj to',
             },
         },
     },
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 9fba459c752..c90af64ed27 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -10229,7 +10229,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
         roleTypes: {
             admin: {
                 heading: 'Novos tipos de função para administradores',
-                body: 'Dê à sua equipe permissões mais granulares com as novas funções de administrador de cartões, pessoas e pagamentos.',
+                body: 'Dê à sua equipe permissões mais granulares com novos papéis de administrador para cartões, pessoas e pagamentos.',
                 cta: 'Experimente',
             },
         },
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 236425a94d9..ab712395ded 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -9894,15 +9894,7 @@ ${reportName}`,
             inviteBoss: '准备好后即可邀请你的上司',
         },
     },
-    productMarketingWindow: {
-        roleTypes: {
-            admin: {
-                heading: '新的管理员角色类型',
-                body: '通过新的卡片、成员和付款管理员角色,为团队分配更精细的权限。',
-                cta: '试用一下',
-            },
-        },
-    },
+    productMarketingWindow: {roleTypes: {admin: {heading: '新的管理员角色类型', body: '通过新增卡片、成员和付款管理员角色,为你的团队提供更精细的权限控制。', cta: '试用一下'}}},
     productTrainingTooltip: {
         conciergeLHNGBR: '<tooltip>从<strong>这里</strong>开始!</tooltip>',
         accountSwitcher: '<tooltip>您现在可以协同操作另一位用户的账户了!</tooltip>',

Note

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

View workflow run

@JS00001

JS00001 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

failing typechecks

@KJ21-ENG

KJ21-ENG commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

waiting for #97795, it will resolve the failing check, as its non related to this PR

cc: @JS00001

@JS00001

JS00001 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@KJ21-ENG pull main please

@KJ21-ENG

KJ21-ENG commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

on it 🏃🏻‍♂️

@JS00001

JS00001 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

:shipit:

@JS00001
JS00001 merged commit 42233c2 into Expensify:main Aug 4, 2026
35 checks passed
@OSBotify

OSBotify commented Aug 4, 2026

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.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🚧 JS00001 has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

OSBotify commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/JS00001 in version: 9.4.50-0 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor

🤖 No help site changes are required — no draft PR created.

I reviewed the changes in this PR against the help site articles under docs/articles and don't see anything that needs documenting.

Why:

  • The core change is UI chrome, not a documented feature. This PR adds a reusable, dismissible "product marketing window" (a persistent bottom-right promotional card) plus the copy for the current July 2026 campaign, "New role types for admins." A transient promotional/announcement banner isn't something users configure or look up in the help site, so it has no article to update.
  • The feature it promotes is already documented. The campaign advertises the granular Card admin, People admin, and Payments admin roles. Those three roles (plus their permissions and role filters) are already fully covered in docs/articles/new-expensify/workspaces/Managing-Workspace-Members.md, so there's no new product behavior here that the help site is missing.

If the intent was to document the marketing window itself, that's not typically a help-site topic — let me know if you'd like me to reconsider or if there's a specific article you had in mind.

@KJ21-ENG — since no help site PR was created, there's nothing to review here. If you disagree and think an article should change, reply with @MelvinBot <details> and I'll create the draft PR.

@mitarachim

Copy link
Copy Markdown

Deploy Blocker #97834 was identified to be related to this PR.

@mitarachim

Copy link
Copy Markdown

Deploy Blocker #97837 was identified to be related to this PR.

@KJ21-ENG

KJ21-ENG commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Looking into these blockers. I haven't investigated them thoroughly yet, but whatever I have seen till now, IMO, we can demote these. Though I will let you know if I find any blocker that is actually some major issue and we should revert the PR or not.

cc: @JS00001

@KJ21-ENG

KJ21-ENG commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Deploy Blocker #97837 was identified to be related to this PR.

Replied here.

@mitarachim

Copy link
Copy Markdown

Deploy Blocker #97846 was identified to be related to this PR.

@KJ21-ENG

KJ21-ENG commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Raised #97861 for following upon these.

#97834
#97846

cc: @huult @JS00001

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