Skip to content

Commit 483b6bd

Browse files
feat(perps): new TPSL view design (#21396)
## **Description** This PR implements TAT-1729, adding expected profit/loss calculation display to the Auto Close (TP/SL) configuration screen. It also includes several UX improvements based on design refinements. ### What is the reason for the change? Users need to see the expected net profit or loss when setting TP/SL values, accounting for trading fees. This helps them make informed decisions about their risk management strategy. Additionally, several UX improvements were needed to match the final design specifications. ### What is the improvement/solution? 1. **Expected P&L Display**: Shows real-time expected profit/loss below TP and SL input fields - Calculates net P&L by deducting MetaMask and protocol fees from gross profit/loss - Dynamically displays "Expected profit: $X.XX" or "Expected loss: $X.XX" based on the net value - Handles edge cases where TP creates loss or SL creates profit (for modified positions) - Right-aligned text in BodyMD font with Alternative color - Formats values using existing `formatPerpsFiat` with PRICE_RANGES_MINIMAL_VIEW 2. **Terminology Update**: Renamed "TP/SL" to "Auto close" throughout the UI - Order view: "Auto close" label - All user-facing strings updated - Technical abbreviations "TP" and "SL" retained for compact displays 3. **UX Improvements**: - Centered header title with absolute positioned back button - "Clear" action buttons positioned on same row as section titles (right-aligned) - Dollar symbol ($) positioned on LEFT of input fields (matching currency convention) - Removed explainer text at top of view - Percentage buttons no longer stay selected after press (cleaner UI) - Auto-scroll to keep inputs visible when custom keypad is active - Action buttons: "Cancel" + "Set" when keypad inactive, "Done" when keypad active 4. **Implementation Approach** (DRY): - Reuses existing `calculatePnL()` utility from `pnlCalculations.ts` - Reuses existing `usePerpsOrderFees()` hook for accurate fee calculation - Reuses existing `calculatePositionSize()` for new orders - Adds small `calculateExpectedPnL()` wrapper that combines P&L with fee deduction - Extends `usePerpsTPSLForm` hook return values (no new hooks created) - Reuses existing formatting utilities (`formatPerpsFiat`) - Uses custom Keypad component (no native keyboard, no timeout needed) ## **Changelog** CHANGELOG entry: Added expected profit/loss display to Auto Close (TP/SL) configuration showing net profit after fees, improved UI layout and terminology ## **Related issues** - Fixes: TAT-1729 https://consensyssoftware.atlassian.net/browse/TAT-1729 - Depends on #21344 ## **Manual testing steps** ```gherkin Feature: Expected Profit/Loss Display for Auto Close (TP/SL) Scenario: User sets Take Profit for new long position Given user is on the Order View screen for BTC And user enters an order amount of $100 at 2x leverage When user taps "Auto close" And user sets Take Profit to $105,000 (assuming current price is $100,000) Then user sees "Expected profit: $X.XX" below the TP input (right-aligned, BodyMD, Alternative color) And the amount accounts for closing fees Scenario: User sets Stop Loss for new short position Given user is on the Order View screen for ETH And user enters a short order amount of $50 at 3x leverage When user taps "Auto close" And user sets Stop Loss to $3,200 (assuming current price is $3,000) Then user sees "Expected loss: $X.XX" below the SL input (right-aligned, BodyMD, Alternative color) And the amount accounts for closing fees Scenario: User modifies TP/SL for existing position Given user has an existing long position in BTC And user taps "Edit TP/SL" from position card When user sets Take Profit to $110,000 And user sets Stop Loss to $95,000 Then user sees expected profit below TP input And user sees expected loss below SL input And both values are calculated from actual position entry price Scenario: Edge case - TP creates loss on modified position Given user has a long position with entry price $100,000 And current price has moved down to $95,000 When user sets Take Profit at $98,000 (still below entry) Then user sees "Expected loss: $X.XX" (smaller loss than current) And text is displayed in Alternative color (not Error color) Scenario: Verify UI improvements Given user is viewing the Auto Close screen Then user sees centered "Auto close" title with back button on left And "Clear" buttons appear on same row as "Take Profit" and "Stop Loss" titles (right-aligned) And dollar symbol "$" appears on LEFT of price input fields And percentage buttons do not stay selected after press And no explainer text appears at top of screen Scenario: Verify action buttons behavior Given user is on the Auto Close screen When keypad is NOT active Then user sees "Cancel" button (left) and "Set" button (right) When user taps any price or percentage input Then keypad appears and user sees only "Done" button When user taps "Done" Then keypad dismisses and user sees "Cancel" and "Set" buttons again Scenario: Verify auto-scroll for custom keypad Given user is on the Auto Close screen When user taps Take Profit price input Then view scrolls to offset 150px to keep input visible When user taps Stop Loss price input Then view scrolls to offset 350px to keep input visible And user can still manually scroll to see current price info ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/5e11a134-9d53-4e13-a46e-6d991801b652 ## **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 - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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] > Displays fee-adjusted expected profit/loss in the TPSL view, passes needed params from Order view, and updates layout/strings and validations. > > - **Perps TPSL (Auto close)**: > - **Expected PnL**: Compute and display net expected PnL for TP/SL using `calculateExpectedPnL`, `usePerpsOrderFees`, and `calculatePositionSize`; added to `usePerpsTPSLForm` (`expectedTakeProfitPnL`, `expectedStopLossPnL`). > - **Params**: `PerpsOrderView` now passes `amount` and `szDecimals` to `Routes.PERPS.TPSL`; nav types extended. > - **UI/UX**: > - Header centered title with absolute back button; section-row "Clear" actions; `$` label moved left of inputs; non-sticky percentage buttons. > - Footer buttons now "Cancel" + "Set" (keypad inactive) and "Done" (keypad active); auto-scroll on input focus; muted backgrounds and refined styles. > - **Logic/Validation**: More robust direction handling; safeguards in RoE calculations; keypad dismissal flow improved. > - **Utilities**: > - Added `calculateExpectedPnL` in `utils/pnlCalculations`. > - Minor tweaks in `tpslValidation` (direction checks, mixed sign handling). > - **Strings**: > - Renamed "TP/SL" to "Auto close" and added new labels: `cancel`, `set`, `clear`, `expected_profit/loss`. > - **Tests**: > - Updated/expanded TPSL view and hook tests to cover new UI, formatting, and behaviors. > - **Misc**: > - Improved error propagation in `usePerpsTPSLUpdate`. > - Minor type/format fixes in Predict provider types. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4ac0c9d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Nick Gambino <[email protected]>
1 parent 4f90d1a commit 483b6bd

File tree

12 files changed

+1004
-1805
lines changed

12 files changed

+1004
-1805
lines changed

app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,8 @@ const PerpsOrderViewContentBase: React.FC = () => {
580580
limitPrice: orderForm.limitPrice,
581581
initialTakeProfitPrice: orderForm.takeProfitPrice,
582582
initialStopLossPrice: orderForm.stopLossPrice,
583+
amount: orderForm.amount,
584+
szDecimals: marketData?.szDecimals,
583585
onConfirm: async (takeProfitPrice?: string, stopLossPrice?: string) => {
584586
// Use the same clearing approach as the "Off" button
585587
// If values are undefined or empty, ensure they're cleared properly
@@ -599,11 +601,13 @@ const PerpsOrderViewContentBase: React.FC = () => {
599601
orderForm.leverage,
600602
orderForm.takeProfitPrice,
601603
orderForm.stopLossPrice,
604+
orderForm.amount,
602605
assetData.price,
603606
showToast,
604607
navigation,
605608
setTakeProfitPrice,
606609
setStopLossPrice,
610+
marketData?.szDecimals,
607611
]);
608612

609613
const handleAmountPress = () => {

app/components/UI/Perps/Views/PerpsTPSLView/PerpsTPSLView.styles.ts

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,22 @@ export const createStyles = (colors: Theme['colors']) =>
1414
header: {
1515
flexDirection: 'row',
1616
alignItems: 'center',
17-
gap: 16,
17+
justifyContent: 'center',
1818
paddingHorizontal: 16,
1919
paddingVertical: 16,
20-
borderBottomWidth: 1,
21-
borderBottomColor: colors.border.muted,
20+
position: 'relative',
21+
},
22+
headerBackButton: {
23+
position: 'absolute',
24+
left: 16,
25+
zIndex: 1,
26+
},
27+
headerTitleContainer: {
28+
flex: 1,
29+
alignItems: 'center',
30+
justifyContent: 'center',
2231
},
2332
footer: {
24-
paddingHorizontal: 16,
2533
paddingBottom: 16,
2634
},
2735
priceInfoContainer: {
@@ -53,10 +61,8 @@ export const createStyles = (colors: Theme['colors']) =>
5361
marginBottom: 8,
5462
},
5563
inputContainer: {
56-
backgroundColor: colors.background.default,
64+
backgroundColor: colors.background.muted,
5765
borderRadius: 8,
58-
borderWidth: 1,
59-
borderColor: colors.border.muted,
6066
paddingHorizontal: 16,
6167
paddingVertical: 12,
6268
flexDirection: 'row',
@@ -70,9 +76,11 @@ export const createStyles = (colors: Theme['colors']) =>
7076
marginLeft: 4,
7177
},
7278
inputContainerActive: {
79+
borderWidth: 1,
7380
borderColor: colors.primary.default,
7481
},
7582
inputContainerError: {
83+
borderWidth: 1,
7684
borderColor: colors.error.default,
7785
},
7886
input: {
@@ -81,38 +89,25 @@ export const createStyles = (colors: Theme['colors']) =>
8189
color: colors.text.default,
8290
paddingVertical: 0,
8391
textAlign: 'left',
84-
marginRight: 8,
92+
marginLeft: 8,
8593
},
8694

8795
percentageRow: {
8896
flexDirection: 'row',
89-
justifyContent: 'space-between',
9097
marginBottom: 12,
9198
gap: 8,
9299
},
93100
percentageButton: {
94101
flex: 1,
95102
paddingVertical: 10,
96103
paddingHorizontal: 8,
97-
backgroundColor: colors.background.pressed,
104+
backgroundColor: colors.background.muted,
98105
borderRadius: 8,
99106
alignItems: 'center',
100-
borderWidth: 1,
101-
borderColor: colors.border.muted,
102107
minWidth: 50,
103108
},
104109
percentageButtonOff: {
105-
backgroundColor: colors.background.pressed,
106-
borderWidth: 1,
107-
borderColor: colors.border.muted,
108-
},
109-
percentageButtonActiveTP: {
110-
borderWidth: 1,
111-
borderColor: colors.primary.default,
112-
},
113-
percentageButtonActiveSL: {
114-
borderWidth: 1,
115-
borderColor: colors.primary.default,
110+
backgroundColor: colors.background.muted,
116111
},
117112
helperText: {
118113
marginTop: 4,
@@ -172,7 +167,6 @@ export const createStyles = (colors: Theme['colors']) =>
172167
},
173168
percentageButtonsContainer: {
174169
flexDirection: 'row',
175-
justifyContent: 'space-between',
176170
marginBottom: 12,
177171
gap: 8,
178172
},
@@ -203,4 +197,22 @@ export const createStyles = (colors: Theme['colors']) =>
203197
width: '100%',
204198
marginBottom: 8,
205199
},
200+
expectedPnLText: {
201+
marginTop: 8,
202+
textAlign: 'right',
203+
},
204+
sectionTitleRow: {
205+
flexDirection: 'row',
206+
justifyContent: 'space-between',
207+
alignItems: 'center',
208+
marginBottom: 8,
209+
},
210+
footerButtonsRow: {
211+
flexDirection: 'row',
212+
gap: 12,
213+
width: '100%',
214+
},
215+
footerButton: {
216+
flex: 1,
217+
},
206218
});

0 commit comments

Comments
 (0)