Skip to content

Commit 71d5a1c

Browse files
authored
feat: braze banner (#29301)
## **Description** Adds a new `BrazeBanner` component to the wallet home screen that displays Braze campaign content as a dismissible banner card. **What changed:** - New BrazeBanner UI backed by BrazeBannerCard for rendering campaign content (title, body, image, CTA) - `useBrazeBanner` hook drives the state machine: `loading → visible | empty`, `visible → dismissed` - Banner deeplinks are validated against an allowlist before being routed through the app's deeplink pipeline - Dismissal is always in-memory for the session; for non-test-send campaigns with a `campaign_name` property, dismissal is also persisted to Redux and Braze is notified — so the same banner is suppressed on the following session's first render, then the guard is cleared - New `banners` Redux slice field `lastDismissedBrazeBanner` + migration 133 - New Braze core helpers: `getBannerForPlacement`, `refreshBrazeBanners`, `logBrazeBannerImpression`, `logBrazeBannerClick`, `dismissBrazeBanner` - Braze identity sync now refreshes banners on sign-in - Banner is integrated into the Wallet home screen behind the `brazeBannerHome` remote feature flag, taking priority over the existing Carousel, wrapped in `ComponentErrorBoundary` ## **Changelog** CHANGELOG entry: Added a Braze-driven promotional banner to the wallet home screen ## **Related issues** Fixes: ## **Manual testing steps** > To test with real banners, share your Braze profile ID with the author so a campaign can be targeted to your device. ```gherkin Feature: Braze home banner Scenario: User sees the banner on app open Given the user has an active Braze banner campaign targeting their profile When the user opens the app and navigates to the wallet home screen Then the banner is displayed at the top of the home screen Scenario: User dismisses the banner Given the banner is visible on the home screen When the user taps the dismiss button Then the banner disappears immediately And the banner does not reappear for the rest of that session And the same banner is not shown on the next app session Scenario: Banner rotates between sessions Given the user has previously dismissed a banner When the user closes the app and reopens it Then a different banner (if available) may be shown And the previously dismissed banner is not shown ``` ## **Screenshots/Recordings** ### **Before** <!-- [screenshots/recordings] --> ### **After** <img width="434" height="891" alt="image" src="https://github.com/user-attachments/assets/034d186c-0b77-43ef-8e01-55b6017b470b" /> ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Adds a new Braze-powered banner surface on wallet home with deeplink routing and persistent dismissal state, touching analytics and navigation paths. Risk is mitigated by an explicit deeplink allowlist, error boundary wrapping, and extensive unit tests, but it still introduces remote-content driven UI/flows. > > **Overview** > **Adds a new Braze-backed promotional banner on wallet home**, rendered via `BrazeBanner`/`BrazeBannerCard` and controlled by a small state machine (`loading`/`visible`/`empty`/`dismissed`) in `useBrazeBanner` with warm-cache probing, event subscription, timeouts, and foreground refresh. > > **Integrates the banner into the Wallet screen behind a new remote feature flag** (`brazeBannerHomeMinVersion`), taking priority over the existing `Carousel` when enabled and wrapped in `ComponentErrorBoundary` so failures don’t block the home UI. > > **Extends Braze and Redux plumbing**: adds core helpers for banner fetch/refresh and impression/click/dismiss logging; introduces `banners.lastDismissedBrazeBanner` with migration `134` to persist the last dismissed campaign; and adds a deeplink allowlist (`isAllowedBrazeDeeplink`) to block unsafe schemes/hosts and explicitly reject MWP connection links. Updates test mocks/registries accordingly. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 38b9ee6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 94e8c0f commit 71d5a1c

44 files changed

Lines changed: 3358 additions & 72 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/CODEOWNERS

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,16 @@ app/core/Engine/controllers/bridge-controller @MetaMask/swaps-enginee
146146
app/core/Engine/controllers/bridge-status-controller @MetaMask/swaps-engineers
147147

148148
# Notifications Team
149-
app/components/Views/Notifications @MetaMask/notifications
150-
app/components/Views/Settings/NotificationsSettings @MetaMask/notifications
151-
**/Notifications/** @MetaMask/notifications
152-
**/Notification/** @MetaMask/notifications
153-
**/notifications/** @MetaMask/notifications
154-
**/notification/** @MetaMask/notifications
149+
**/*Braze*/ @MetaMask/engagement
150+
**/*Braze* @MetaMask/engagement
151+
**/*braze*/ @MetaMask/engagement
152+
**/*braze* @MetaMask/engagement
153+
app/components/Views/Notifications @MetaMask/engagement
154+
app/components/Views/Settings/NotificationsSettings @MetaMask/engagement
155+
**/Notifications/** @MetaMask/engagement
156+
**/Notification/** @MetaMask/engagement
157+
**/notifications/** @MetaMask/engagement
158+
**/notification/** @MetaMask/engagement
155159

156160
# LavaMoat Team
157161
ses.cjs @MetaMask/supply-chain

.github/scripts/known-feature-flag-constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const FILE_SOURCES: Array<{ key: string; file: string; exportName: string }> = [
2020
{ key: 'FULL_PAGE_ACCOUNT_LIST_FLAG_NAME', file: sel('fullPageAccountList'), exportName: 'FULL_PAGE_ACCOUNT_LIST_FLAG_NAME' },
2121
{ key: 'IMPORT_SRP_WORD_SUGGESTION_FLAG_NAME', file: sel('importSrpWordSuggestion'), exportName: 'IMPORT_SRP_WORD_SUGGESTION_FLAG_NAME' },
2222
{ key: 'ASSETS_UNIFY_STATE_FLAG', file: sel('assetsUnifyState'), exportName: 'ASSETS_UNIFY_STATE_FLAG' },
23+
{ key: 'BRAZE_BANNER_HOME_FLAG_KEY', file: sel('brazeBannerHome'), exportName: 'BRAZE_BANNER_HOME_FLAG_KEY' },
2324
{ key: 'TOKEN_DETAILS_OHLCV_WS_INTEGRATION_FLAG_KEY', file: sel('tokenDetailsOhlcvWsIntegration'), exportName: 'TOKEN_DETAILS_OHLCV_WS_INTEGRATION_FLAG_KEY' },
2425
];
2526

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const BANNER_HEIGHT = 96;
2+
/** py-3 = 12px top + 12px bottom = 24px total vertical padding */
3+
export const BANNER_IMAGE_SIZE = BANNER_HEIGHT - 24;
4+
export const SKELETON_TIMEOUT_MS = 5000;

0 commit comments

Comments
 (0)