Skip to content

Commit 21c9a5f

Browse files
chore(runway): cherry-pick feat(predict): cp-7.62.0 add PredictPicks components for position display (#24594)
- feat(predict): cp-7.62.0 add PredictPicks components for position display (#24579) <!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** Adds new `PredictPicks` components to display user positions in prediction markets. **What changed:** - `PredictPicks` - Main component showing user picks with position details (size, outcome, P&L) and "Cash Out" button on market details view - `PredictPicksForCard` - Compact version for displaying picks within market cards with P&L and amount display **Why:** Users need visibility into their active positions on market detail views to track their picks and take action (cash out). <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/PRED-479 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <img width="387" height="114" alt="Screenshot 2026-01-15 at 17 54 00" src="https://github.com/user-attachments/assets/1b4998ce-e7ec-4e13-84f2-e4a503c50afe" /> <img width="383" height="38" alt="Screenshot 2026-01-15 at 17 55 41" src="https://github.com/user-attachments/assets/25352d0e-2aef-41af-83a2-d2a7cf5f60fb" /> <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] 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). - [ ] 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. ## **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] > Introduces UI to surface users’ active positions with real-time P&L and a guarded cash-out action. > > - Adds `PredictPicks` (market details) and `PredictPicksForCard` (compact) to display `initialValue/outcome`, live `cashPnl`, and amount; cash-out triggers guarded navigation to `Routes.PREDICT.MODALS.SELL_PREVIEW` > - Implements `useLivePositions` to compute live `currentValue`, `cashPnl`, and `percentPnl` from `bestBid` via `useLiveMarketPrices`; exports added in hooks index > - Updates i18n with `your_picks` and pick strings; includes comprehensive tests for components and the new hook > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 5853f5e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> [d0ae183](d0ae183) Co-authored-by: Caainã Jeronimo <caainaje@gmail.com>
1 parent 91f8ab4 commit 21c9a5f

9 files changed

Lines changed: 1692 additions & 1 deletion

File tree

app/components/UI/Predict/components/PredictPicks/PredictPicks.test.tsx

Lines changed: 615 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {
2+
Box,
3+
Button,
4+
ButtonVariant,
5+
Text,
6+
TextColor,
7+
TextVariant,
8+
} from '@metamask/design-system-react-native';
9+
import React from 'react';
10+
import { usePredictPositions } from '../../hooks/usePredictPositions';
11+
import { useLivePositions } from '../../hooks/useLivePositions';
12+
import { formatPrice } from '../../utils/format';
13+
import { PredictEventValues } from '../../constants/eventNames';
14+
import { NavigationProp, useNavigation } from '@react-navigation/native';
15+
import { usePredictActionGuard } from '../../hooks/usePredictActionGuard';
16+
import { PredictMarket, PredictPosition } from '../../types';
17+
import Routes from '../../../../../constants/navigation/Routes';
18+
import { PredictNavigationParamList } from '../../types/navigation';
19+
import { strings } from '../../../../../../locales/i18n';
20+
21+
interface PredictPicksProps {
22+
market: PredictMarket;
23+
/**
24+
* TestID for the component
25+
*/
26+
testID?: string;
27+
}
28+
29+
const PredictPicks: React.FC<PredictPicksProps> = ({
30+
market,
31+
testID = 'predict-picks',
32+
}) => {
33+
const { positions } = usePredictPositions({
34+
marketId: market.id,
35+
autoRefreshTimeout: 10000,
36+
});
37+
const { livePositions } = useLivePositions(positions);
38+
const navigation =
39+
useNavigation<NavigationProp<PredictNavigationParamList>>();
40+
const { navigate } = navigation;
41+
const { executeGuardedAction } = usePredictActionGuard({
42+
providerId: market.providerId,
43+
navigation,
44+
});
45+
46+
const onCashOut = (position: PredictPosition) => {
47+
executeGuardedAction(
48+
() => {
49+
const _outcome = market?.outcomes.find(
50+
(o) => o.id === position.outcomeId,
51+
);
52+
navigate(Routes.PREDICT.MODALS.SELL_PREVIEW, {
53+
market,
54+
position,
55+
outcome: _outcome,
56+
entryPoint: PredictEventValues.ENTRY_POINT.PREDICT_MARKET_DETAILS,
57+
});
58+
},
59+
{ attemptedAction: PredictEventValues.ATTEMPTED_ACTION.CASHOUT },
60+
);
61+
};
62+
63+
const hasPositions = livePositions.length > 0;
64+
65+
if (!hasPositions) {
66+
return null;
67+
}
68+
69+
return (
70+
<Box testID={testID} twClassName="flex-col">
71+
<Text variant={TextVariant.HeadingMd} twClassName="font-medium py-2">
72+
{strings('predict.market_details.your_picks')}
73+
</Text>
74+
{livePositions.map((position) => (
75+
<Box
76+
testID={testID}
77+
twClassName="flex-row justify-between items-center py-3"
78+
key={position.id}
79+
>
80+
<Box>
81+
<Text variant={TextVariant.BodyMd} twClassName="font-medium">
82+
{strings('predict.position_pick_info', {
83+
initialValue: formatPrice(position.initialValue, {
84+
maximumDecimals: 2,
85+
}),
86+
outcome: position.outcome,
87+
})}
88+
</Text>
89+
<Text
90+
variant={TextVariant.BodyMd}
91+
color={
92+
position.cashPnl < 0
93+
? TextColor.ErrorDefault
94+
: TextColor.SuccessDefault
95+
}
96+
twClassName="font-medium"
97+
testID={`predict-picks-pnl-${position.id}`}
98+
>
99+
{formatPrice(position.cashPnl, { maximumDecimals: 2 })}
100+
</Text>
101+
</Box>
102+
<Button
103+
variant={ButtonVariant.Secondary}
104+
twClassName="py-3 px-4 bg-muted/5"
105+
onPress={() => onCashOut(position)}
106+
testID={`predict-picks-cash-out-button-${position.id}`}
107+
>
108+
<Text variant={TextVariant.BodyMd} twClassName="font-medium">
109+
{strings('predict.cash_out')}
110+
</Text>
111+
</Button>
112+
</Box>
113+
))}
114+
</Box>
115+
);
116+
};
117+
118+
export default PredictPicks;

0 commit comments

Comments
 (0)