Skip to content

chore(predict): build predict portfolio module and shared portfolio UI components#30679

Merged
caieu merged 8 commits into
mainfrom
predict/PRED-899-build-predict-portfolio-module-and-shared-portfolio-ui-components
May 27, 2026
Merged

chore(predict): build predict portfolio module and shared portfolio UI components#30679
caieu merged 8 commits into
mainfrom
predict/PRED-899-build-predict-portfolio-module-and-shared-portfolio-ui-components

Conversation

@caieu
Copy link
Copy Markdown
Contributor

@caieu caieu commented May 27, 2026

Description

Adds the reusable Predict portfolio UI components for PRED-899.

This PR is stacked on top of predict/PRED-902-create-shared-predict-portfolio-data-hook-and-derived-model, which introduces the shared usePredictPortfolio data/model hook used by these components.

Included components:

  • PredictPortfolioModule for the homepage balance/portfolio surface
  • PredictPortfolioSummary for primary portfolio value and optional P&L/available balance display
  • PredictPortfolioActions and PredictPortfolioAction for Positions, Add funds, and Withdraw actions
  • PredictPortfolio test IDs and barrel exports

The module supports first-time, returning, loading, privacy mode, claimable winnings, positions badge count, guarded deposit/withdraw/claim flows, and amount-only P&L display when the portfolio model omits a percent.

This PR intentionally does not wire the portfolio module into PredictFeed, feature flag rendering, or confirmation navigation. That integration will be handled in a follow-up stacked branch.

Changelog

CHANGELOG entry: null

Related issues

Fixes: https://consensyssoftware.atlassian.net/browse/PRED-899

Depends on: https://consensyssoftware.atlassian.net/browse/PRED-902

Manual testing steps

Feature: Predict portfolio UI components

  Scenario: reusable portfolio module renders portfolio states
    Given the shared Predict portfolio hook returns first-time, returning, claimable, loading, or privacy-mode data

    When the Predict portfolio module is rendered
    Then it displays the expected value, optional P&L line, action row, positions badge, and claim CTA for that state

Manual app QA: N/A for this stacked PR because the components are not integrated into the Predict feed in this branch.

Screenshots/Recordings

N/A. This branch adds reusable components only; feed integration and visual QA screenshots will be covered by the follow-up integration branch.

Before

N/A

After

Screen.Recording.2026-05-26.at.13.23.31.mov

Testing

  • NODE_OPTIONS=--max-old-space-size=8192 node .yarn/releases/yarn-4.14.1.cjs jest app/components/UI/Predict/components/PredictPortfolio/PredictPortfolioModule.test.tsx app/components/UI/Predict/components/PredictPortfolio/PredictPortfolioSummary.test.tsx --runInBand --watchman=false --forceExit --silent --coverage=false
  • node .yarn/releases/yarn-4.14.1.cjs lint:tsc
  • git diff --check

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
  • I've tested with a power user scenario
  • I've instrumented key operations with Sentry traces for production performance metrics

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.

Note

Low Risk
New UI and hook tweaks only; deposit/withdraw/claim reuse existing guarded flows and the module is not yet integrated into the Predict feed.

Overview
Introduces reusable Predict portfolio UI for the homepage balance surface: PredictPortfolioModule composes summary, Positions / Add funds / Withdraw actions, and an optional claim CTA, wired through usePredictPortfolio, privacy mode, usePredictActionGuard, and deposit-wallet withdraw feature flags. PredictPortfolioSummary shows portfolio value with optional unrealized P&L and available balance (amount-only P&L when percent is omitted), loading skeletons, and masked values in privacy mode.

usePredictPortfolio now enables account state when portfolio value is positive (not only cash balance), and account state failures no longer surface on the aggregate error field. New predict.portfolio.* strings and test IDs support automation. Feed integration is explicitly out of scope for this branch.

Reviewed by Cursor Bugbot for commit 781b9ca. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

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.

@metamaskbotv2 metamaskbotv2 Bot added the team-predict Predict team label May 27, 2026
@caieu caieu marked this pull request as ready for review May 27, 2026 13:11
@caieu caieu requested a review from a team as a code owner May 27, 2026 13:11
Comment thread app/components/UI/Predict/components/PredictPortfolio/PredictPortfolioModule.tsx Outdated
Base automatically changed from predict/PRED-902-create-shared-predict-portfolio-data-hook-and-derived-model to main May 27, 2026 14:05
@MarioAslau
Copy link
Copy Markdown
Contributor

Medium severity

1. PredictPortfolioModule doesn't visually disable Withdraw while walletType is loading — silent no-op tap

  const handleWithdrawPress = useCallback(() => {
    executeGuardedAction(
      async () => {
        if (!portfolio.walletType) {
          return;
        }

        if (
          portfolio.walletType === 'deposit-wallet' &&
          !enableDepositWalletWithdraw
        ) {
          onDepositWalletWithdrawPress?.();
          return;
        }

        await portfolio.withdraw();
      },
      { attemptedAction: PredictEventValues.ATTEMPTED_ACTION.WITHDRAW },
    );
  }, [...]);
export interface PredictPortfolioActionsProps {
  isWithdrawDisabled?: boolean;
  onAddFundsPress: () => void;
  ...
}

const PredictPortfolioActions: React.FC<PredictPortfolioActionsProps> = ({
  isWithdrawDisabled = false,
  ...
}) => (
  <Box ...>
    ...
    <PredictPortfolioAction
      disabled={isWithdrawDisabled}
      iconName={IconName.Arrow2Down}
      label={strings('predict.deposit.withdraw')}
      onPress={onWithdrawPress}
      testID={PREDICT_PORTFOLIO_TEST_IDS.ACTION_WITHDRAW}
    />
  </Box>
);

The isWithdrawDisabled prop exists on PredictPortfolioActions (with full disabled styling support in PredictPortfolioAction's pressable opacity-50 / IconColor.IconMuted / text-muted), but PredictPortfolioModule never passes it. Combined with the early-return guard on !portfolio.walletType inside the press handler, the user experience is:

  1. User opens the homepage portfolio with a positive balance.
  2. usePredictAccountState({ enabled: availableBalance > 0 }) starts loading (only after balance > 0 is observed).
  3. During that window, the Withdraw icon looks identical to Positions/Add funds and is pressable.
  4. User taps → nothing happens. No spinner, no toast, no disabled state. The button looks broken.

Compare with the spiritual predecessor PredictBalance (app/components/UI/Predict/components/PredictBalance/PredictBalance.tsx), which sets isWithdrawDisabled = hasBalance && !walletType and threads it through to <Button isDisabled={isWithdrawDisabled}>. Same logic, but with the proper visual disable. The new module should mirror that pattern:

<PredictPortfolioActions
  isWithdrawDisabled={portfolio.availableBalance > 0 && !portfolio.walletType}
  onAddFundsPress={handleAddFundsPress}
  onPositionsPress={handlePositionsPress}
  onWithdrawPress={handleWithdrawPress}
  positionsBadgeCount={portfolio.positionsBadgeCount}
/>

The unit test does not open withdraw fallback while wallet type is loading only asserts that the press doesn't trigger the fallback — it doesn't assert the button is visually disabled. So the regression slipped past CI. Add an assertion that the Withdraw pressable is accessibilityState: { disabled: true } while walletType === undefined and availableBalance > 0.

2. accountStateQuery.error aggregated into error field elevates a supplementary failure to portfolio-level

    error:
      balanceError ??
      activePositionsQuery.error ??
      claimablePositionsQuery.error ??
      accountStateQuery.error ??
      null,

error is the field most callers (and the upcoming feed integration) will use as the fatal "portfolio failed to load" indicator. Bundling accountStateQuery.error in here means a transient wallet-type fetch failure surfaces the same way as a balance or positions failure, even though walletType is supplementary metadata (used only to decide between two withdraw paths). The portfolio is still perfectly usable for viewing balance, P&L, and tapping Positions / Add funds.

Two cleaner shapes (pick one):

// Option A: keep accountState out of the aggregated error.
error:
  balanceError ??
  activePositionsQuery.error ??
  claimablePositionsQuery.error ??
  null,
// accountStateError is still exposed separately for callers that need it.
// Option B: expose a separate `criticalError` for the fatal cases, leave `error`
// as the union for callers that explicitly want everything.

The dedicated accountStateError field is already on the model (line 169), so Option A is the minimal change and aligns the field's semantics with how PredictBalance consumes it today (it doesn't gate UI on account-state errors).

3. enabled: availableBalance > 0 on usePredictAccountState excludes positions-only users

  const accountStateQuery = usePredictAccountState({
    enabled: availableBalance > 0,
  });

walletType only loads when the user has positive liquid balance. Consider this realistic flow:

  1. Returning user with $0 liquid balance but $50 in open positions.
  2. They sell the position; the proceeds settle into balance.
  3. Balance flips from 0 → $50; usePredictAccountState finally starts loading.
  4. User immediately taps Withdraw → silent no-op for the duration of the wallet-type query (issue Inject inpage js #1 again, but compounded because the user just expected the balance to be withdrawable).

A enabled: availableBalance > 0 || openPositionsValue > 0 || claimableAmount > 0 (or just portfolioValue > 0) condition would warm the cache earlier and avoid the wait-on-tap window for this common path. Worth aligning with what PredictBalance does (it gates on hasBalance = balance > 0, same behavior, same edge case — but at least it disables the button visually so the user doesn't tap into a no-op).

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 377ceff. Configure here.

@caieu caieu force-pushed the predict/PRED-899-build-predict-portfolio-module-and-shared-portfolio-ui-components branch from 377ceff to 781b9ca Compare May 27, 2026 18:15
@github-actions
Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePredictions, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: @PerformancePredict
  • Risk Level: medium
  • AI Confidence: 82%
click to see 🤖 AI reasoning details

E2E Test Selection:
The PR introduces new Predict portfolio UI components (PredictPortfolioModule, PredictPortfolioSummary, PredictPortfolioActions, PredictPortfolioAction) and modifies the usePredictPortfolio hook logic. Key changes:

  1. New portfolio components: These are new files building the portfolio display section for the Predict feature, showing portfolio value, available balance, unrealized PnL, and action buttons (Positions, Add Funds, Withdraw). The PredictPortfolioModule is not yet integrated into a parent view (only exported from index), but the hook changes are live.

  2. usePredictPortfolio hook fix: The calculation order was corrected (openPositionsValue and claimableAmount now computed before portfolioValue), and the accountStateQuery enabled condition changed from availableBalance > 0 to portfolioValue > 0. This is a behavioral change - account state will now be fetched when there are open positions or claimable winnings even if available balance is 0. Also removed accountStateQuery.error from error propagation.

  3. Localization: New strings added for portfolio accessibility labels.

SmokePredictions is the primary tag as these changes directly affect the Predict feature's portfolio display and data fetching logic. Per SmokePredictions description, SmokeWalletPlatform must also be selected (Predictions is a section inside Trending tab). SmokeConfirmations is required per SmokePredictions description (opening/closing positions are on-chain transactions).

Performance Test Selection:
The usePredictPortfolio hook change modifies when accountStateQuery is enabled (from availableBalance > 0 to portfolioValue > 0), which is a broader trigger condition that could cause additional API calls and affect the Predict tab's loading performance. The @PerformancePredict tag covers prediction market list loading, market details, deposit flows, and balance display - all of which are relevant to these changes.

View GitHub Actions results

@sonarqubecloud
Copy link
Copy Markdown

@caieu caieu enabled auto-merge May 27, 2026 19:03
@caieu caieu added this pull request to the merge queue May 27, 2026
Merged via the queue into main with commit 51596c8 May 27, 2026
201 of 203 checks passed
@caieu caieu deleted the predict/PRED-899-build-predict-portfolio-module-and-shared-portfolio-ui-components branch May 27, 2026 19:49
@github-actions github-actions Bot locked and limited conversation to collaborators May 27, 2026
@metamaskbotv2 metamaskbotv2 Bot added the release-7.80.0 Issue or pull request that will be included in release 7.80.0 label May 27, 2026
@caieu caieu restored the predict/PRED-899-build-predict-portfolio-module-and-shared-portfolio-ui-components branch May 27, 2026 23:04
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-7.80.0 Issue or pull request that will be included in release 7.80.0 size-L team-predict Predict team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants