feat(rewards): expose GET /campaigns endpoint through RewardsController#27108
Conversation
Expose the new GET /campaigns backend endpoint via the mobile rewards stack: - Add CampaignDto, CampaignType enum, and CampaignsState types - Add RewardsControllerGetCampaignsAction to controller actions - Implement getCampaigns in RewardsDataService with auth support - Implement getCampaigns in RewardsController with 5-minute cache and #withAuthRetry for 403 recovery - Add campaigns to controller state metadata and default state - Add unit tests for both data service and controller - Update initial-background-state.json and inline state snapshots Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove duplicate RewardsDataServiceGetSnapshotsAction from union - Rename getCampaigns data service test names to drop 'should' prefix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update index.test.ts.snap to include the new campaigns field in the serialized RewardsController state output. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add RewardsDataServiceGetCampaignsAction to AllowedActions and delegate the RewardsDataService:getCampaigns action through the messenger so the RewardsController can call it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change CampaignDto.termsAndConditions from Record<string, unknown> to Json | null to satisfy StateConstraint (state must be JSON-serializable) - Inline CampaignsState campaigns array type (same pattern as SnapshotsState) so TypeScript can verify StateConstraint compliance - Update createTestCampaign helper to use CampaignType enum and Json type Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
…rdCampaigns hook
- Add CAMPAIGNS_REWARDS_FLAG_NAME ('rewardsCampaignsEnabled') selector with
raw and gated variants in rewardsEnabled.ts; export from rewards flag index
- Inject isCampaignsEnabled into RewardsController constructor (defaults false);
getCampaigns now guards on both isRewardsFeatureEnabled() and isCampaignsEnabled()
- Wire selectCampaignsRewardsEnabledFlag into rewardsControllerInit
- Add campaigns slice state (campaigns, campaignsLoading, campaignsError) to
rewards reducer with setCampaigns / setCampaignsLoading / setCampaignsError actions
- Add selectCampaigns / selectCampaignsLoading / selectCampaignsError selectors
- Implement useRewardCampaigns hook: fetches via RewardsController:getCampaigns,
returns [] when flag is off or subscriptionId is absent, similar to useSnapshots
- Add 12 useRewardCampaigns tests covering flag guard, success, error, focus
effect and invalidation event wiring
- Add 2 new RewardsController getCampaigns tests for the campaigns feature flag;
update existing tests to pass isCampaignsEnabled: () => true
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tests Two explicit RewardsState object literals in index.test.ts were missing the new campaigns/campaignsLoading/campaignsError fields added to the reducer, causing tsc to fail with TS2739. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…all sites Inline array literals passed to useInvalidateByRewardEvents created a new reference on every render, causing the useEffect inside the hook to unsubscribe and re-subscribe to all events every cycle. Wrap each array in useMemo with an empty dep array so the reference is stable across renders. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TypeScript infers tuple literals as readonly arrays, so callers passing ['RewardsController:accountLinked', ...] were failing strict type checks. Widening the parameter to readonly RewardEvent[] fixes all affected hooks. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add tests for setCampaigns/setCampaignsLoading/setCampaignsError reducers, selectCampaigns/selectCampaignsLoading/selectCampaignsError state selectors, selectCampaignsRewardsEnabledRawFlag/selectCampaignsRewardsEnabledFlag feature flag selectors, and concurrent fetch guard in useRewardCampaigns hook. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection:
Since there are no E2E tests for Rewards functionality and the changes are additive, feature-flag gated, and don't affect any core wallet flows covered by the available E2E test tags, no E2E tests need to be run. Performance Test Selection: |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
| } | ||
|
|
||
| return (await response.json()) as CampaignDto[]; | ||
| } |
There was a problem hiding this comment.
Missing 403/401 AuthorizationFailedError in getCampaigns service
Medium Severity
The getCampaigns method checks !response.ok and throws a generic Error, but for a 401 response the makeRequest helper only auto-throws AuthorizationFailedError for 403. If the backend returns 401 (as it can for expired tokens), #withAuthRetry in the controller won't catch it for re-authentication, unlike getSubscriptionAccounts which explicitly handles 401 with AuthorizationFailedError.
|





Summary
CampaignTypeenum andCampaignDto/CampaignsStatetypes matching the backend DTO fromva-mmcx-rewards#469getCampaigns(subscriptionId)inRewardsDataServicecallingGET /campaignswith subscription authgetCampaigns(subscriptionId)inRewardsControllerwith 5-minute cache viawrapWithCacheand#withAuthRetryfor 403 recoverycampaignsto controller state metadata/default state (persisted, usedInUi)RewardsDataService:getCampaignsaction in the rewards-controller messengerinitial-background-state.jsonand state-logs snapshotBackend model (from
va-mmcx-rewards#469)Test plan
rewards-data-service.test.ts— 3 newgetCampaignstests: success (correct endpoint + auth headers), empty array, error responseRewardsController.test.ts— 5 newgetCampaignstests: disabled flag, API fetch + cache write, cache hit (fresh), cache miss (stale), loggingCHANGELOG entry: feat(rewards): expose GET /campaigns endpoint through RewardsController with 5-minute cache
🤖 Generated with Claude Code
Note
Medium Risk
Adds a new rewards API surface (
getCampaigns) with caching and persisted controller/UI state, plus a new feature flag controlling behavior. Moderate risk due to new network path and state persistence/invalidation changes affecting rewards UI refresh timing.Overview
Adds rewards campaigns support end-to-end. Introduces
CampaignType/CampaignDtotypes, a newRewardsDataService:getCampaignsaction that callsGET /campaigns, and aRewardsController:getCampaignshandler that caches results for 5 minutes and persists them in controller state.Wires campaigns into the UI/store and tightens event invalidation. Adds campaigns fields/actions/selectors to the rewards reducer, a
useRewardCampaignshook (focus fetch + invalidation on account/balance events, gated byselectCampaignsRewardsEnabledFlag), and updatesuseInvalidateByRewardEvents+ several rewards hooks to memoize their event lists and acceptreadonlyevent arrays to avoid unnecessary re-subscriptions. Tests and state snapshots/initial background state are updated accordingly.Written by Cursor Bugbot for commit d31ef7d. This will update automatically on new commits. Configure here.