Skip to content

Commit 50ffd90

Browse files
authored
chore: prepare price alerts for prod (#32298)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> <!-- mms-check directive vocabulary — read by .github/scripts/shared/pr-template-checks.ts at module load to build the validation plan. Directives are invisible in rendered markdown and must NOT be removed or edited without updating the validator registry. type=text Section must contain non-placeholder prose. type=changelog Section must have a valid CHANGELOG entry: line. type=issue-link Section must have a Fixes:/Closes:/Refs: line with a value. type=manual-testing Section must have real testing steps or an explicit N/A. type=screenshot Section must have evidence (image/URL) or an explicit N/A. type=checklist Section must have all checkboxes consciously checked. required=true|false Whether a missing/invalid section runs the validator at all. blocking=true|false Whether a failure of this check fails the CI workflow. Default: false — failures are shown as warnings in the sticky comment but do not block the PR. Sections without a directive are checked for structural presence only. --> ## **Description** Removes the "Price reaches / Price change" tab toggle and the associated "under development" placeholder, since only the price-reaches flow is supported <!-- mms-check: type=text required=true --> <!-- 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** <!-- mms-check: type=changelog required=true blocking=true --> <!-- 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: prepare price alerts for prod ## **Related issues** <!-- mms-check: type=issue-link required=true --> Fixes: https://consensyssoftware.atlassian.net/browse/ASSETS-3456 ## **Manual testing steps** <!-- mms-check: type=manual-testing required=true --> ```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** <!-- mms-check: type=screenshot required=true --> <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <img width="443" height="917" alt="image" src="https://github.com/user-attachments/assets/101b6235-62dd-418a-8018-0574807430e2" /> <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** <!-- mms-check: type=checklist required=true --> <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [ ] 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. #### 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** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] 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] > **Low Risk** > UI-only simplification in price alerts with no changes to submit/API logic; remaining tests still cover the price-reaches flow. > > **Overview** > Prepares price alerts for production by **removing the unsupported “Price change” path** from the create flow. Users no longer see the **Price reaches / Price change** toggle or the **under development** placeholder. > > **`CreatePriceAlertView`** now always shows the target-price UI (keypad, quick percentages, recurring toggle, save). **`AlertTypeToggle`** and its tests are deleted, and **`PriceAlertType`** plus related test IDs are dropped from **`constants`**. Create/edit submit behavior is unchanged; this is UI and dead-code cleanup only. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 9445cd9. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 532878a commit 50ffd90

5 files changed

Lines changed: 138 additions & 466 deletions

File tree

app/components/UI/Assets/PriceAlerts/Views/CreatePriceAlertView/CreatePriceAlertView.test.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -156,19 +156,6 @@ describe('CreatePriceAlertView', () => {
156156
).not.toBeDisabled();
157157
});
158158

159-
it('shows under development message when price change is selected', () => {
160-
const { getByTestId, getByText } = renderWithToast();
161-
162-
fireEvent.press(getByTestId(CreatePriceAlertTestIds.PRICE_CHANGE_TAB));
163-
164-
expect(
165-
getByTestId(CreatePriceAlertTestIds.UNDER_DEVELOPMENT),
166-
).toBeOnTheScreen();
167-
expect(
168-
getByText('This experience is currently under development'),
169-
).toBeOnTheScreen();
170-
});
171-
172159
it('updates the displayed price when a quick-percentage pill is pressed', () => {
173160
const { getByTestId, getByText } = renderWithToast();
174161

app/components/UI/Assets/PriceAlerts/Views/CreatePriceAlertView/CreatePriceAlertView.tsx

Lines changed: 138 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,12 @@ import {
4646
ToastVariants,
4747
} from '../../../../../../component-library/components/Toast';
4848
import { IconName } from '../../../../../../component-library/components/Icons/Icon';
49-
import AlertTypeToggle from '../../components/AlertTypeToggle';
5049
import {
5150
CreatePriceAlertRouteParams,
5251
CreatePriceAlertTestIds,
5352
CURRENCY_SYMBOLS,
5453
PRICE_ALERT_QUICK_PERCENTAGES,
5554
PriceAlert,
56-
PriceAlertType,
5755
} from '../../constants';
5856
import { priceAlertsQueryKey, useSubmitPriceAlert } from '../../api';
5957
import { trimTrailingZeros } from '../../../../Bridge/utils/trimTrailingZeros';
@@ -131,9 +129,6 @@ const CreatePriceAlertView: React.FC = () => {
131129

132130
const isEditing = Boolean(editingAlert);
133131
const displayTicker = ticker || symbol;
134-
const [alertType, setAlertType] = useState<PriceAlertType>(
135-
PriceAlertType.PriceReaches,
136-
);
137132
const [targetAmount, setTargetAmount] = useState(
138133
editingAlert ? toKeypadString(editingAlert.threshold) : KEYPAD_EMPTY,
139134
);
@@ -319,182 +314,158 @@ const CreatePriceAlertView: React.FC = () => {
319314
onBack={handleBack}
320315
/>
321316

322-
<AlertTypeToggle value={alertType} onChange={setAlertType} />
317+
<Box
318+
alignItems={BoxAlignItems.Center}
319+
twClassName="flex-1 justify-center px-4 pb-4"
320+
>
321+
<Text
322+
variant={TextVariant.BodySm}
323+
color={TextColor.TextAlternative}
324+
twClassName="mb-2"
325+
>
326+
{strings('price_alerts.enter_target_price')}
327+
</Text>
323328

324-
{alertType === PriceAlertType.PriceReaches ? (
325-
<>
326-
<Box
327-
alignItems={BoxAlignItems.Center}
328-
twClassName="flex-1 justify-center px-4 pb-4"
329+
<Box
330+
flexDirection={BoxFlexDirection.Row}
331+
alignItems={BoxAlignItems.Center}
332+
justifyContent={BoxJustifyContent.Center}
333+
twClassName="w-full"
334+
testID={CreatePriceAlertTestIds.TARGET_PRICE_INPUT}
335+
>
336+
<RNText
337+
numberOfLines={1}
338+
adjustsFontSizeToFit
339+
minimumFontScale={0.4}
340+
style={[
341+
tw.style('font-medium'),
342+
viewStyles.priceText,
343+
{
344+
color: hasInput
345+
? colors.text.default
346+
: colors.text.alternative,
347+
},
348+
]}
329349
>
330-
<Text
331-
variant={TextVariant.BodySm}
332-
color={TextColor.TextAlternative}
333-
twClassName="mb-2"
334-
>
335-
{strings('price_alerts.enter_target_price')}
336-
</Text>
337-
338-
<Box
339-
flexDirection={BoxFlexDirection.Row}
340-
alignItems={BoxAlignItems.Center}
341-
justifyContent={BoxJustifyContent.Center}
342-
twClassName="w-full"
343-
testID={CreatePriceAlertTestIds.TARGET_PRICE_INPUT}
344-
>
345-
<RNText
346-
numberOfLines={1}
347-
adjustsFontSizeToFit
348-
minimumFontScale={0.4}
349-
style={[
350-
tw.style('font-medium'),
351-
viewStyles.priceText,
352-
{
353-
color: hasInput
354-
? colors.text.default
355-
: colors.text.alternative,
356-
},
357-
]}
358-
>
359-
{displayText}
360-
</RNText>
361-
<Animated.View
362-
style={[
363-
tw.style('ml-1 h-10 w-0.5 bg-primary-default'),
364-
viewStyles.cursor,
365-
{ opacity: fadeAnim },
366-
]}
367-
/>
368-
</Box>
369-
370-
<Text
371-
variant={TextVariant.BodySm}
372-
color={TextColor.TextAlternative}
373-
testID={CreatePriceAlertTestIds.PERCENT_DIFF}
374-
twClassName="mt-2"
375-
>
376-
{percentDiff.direction === 'none' ? (
377-
strings('price_alerts.approx_percent', { percent: '0' })
378-
) : (
379-
<>
380-
{'≈ '}
381-
<Text
382-
variant={TextVariant.BodySm}
383-
fontWeight={FontWeight.Medium}
384-
color={
385-
percentDiff.direction === 'above'
386-
? TextColor.SuccessDefault
387-
: TextColor.ErrorDefault
388-
}
389-
>
390-
{`${percentDiff.rounded}%`}
391-
</Text>
392-
{` ${strings(
393-
percentDiff.direction === 'above'
394-
? 'price_alerts.approx_percent_above'
395-
: 'price_alerts.approx_percent_below',
396-
{ ticker: displayTicker },
397-
)}`}
398-
</>
399-
)}
400-
</Text>
401-
</Box>
402-
403-
<View style={tw.style('px-4 pb-2')}>
404-
{/* Recurring row — always visible above the bottom controls */}
405-
<Box
406-
flexDirection={BoxFlexDirection.Row}
407-
alignItems={BoxAlignItems.Center}
408-
justifyContent={BoxJustifyContent.Between}
409-
twClassName="mb-3 px-2"
410-
>
350+
{displayText}
351+
</RNText>
352+
<Animated.View
353+
style={[
354+
tw.style('ml-1 h-10 w-0.5 bg-primary-default'),
355+
viewStyles.cursor,
356+
{ opacity: fadeAnim },
357+
]}
358+
/>
359+
</Box>
360+
361+
<Text
362+
variant={TextVariant.BodySm}
363+
color={TextColor.TextAlternative}
364+
testID={CreatePriceAlertTestIds.PERCENT_DIFF}
365+
twClassName="mt-2"
366+
>
367+
{percentDiff.direction === 'none' ? (
368+
strings('price_alerts.approx_percent', { percent: '0' })
369+
) : (
370+
<>
371+
{'≈ '}
411372
<Text
412-
variant={TextVariant.BodyMd}
413-
color={TextColor.TextAlternative}
373+
variant={TextVariant.BodySm}
374+
fontWeight={FontWeight.Medium}
375+
color={
376+
percentDiff.direction === 'above'
377+
? TextColor.SuccessDefault
378+
: TextColor.ErrorDefault
379+
}
414380
>
415-
{strings('price_alerts.recurring')}
381+
{`${percentDiff.rounded}%`}
416382
</Text>
417-
<Switch
418-
value={isRecurring}
419-
onValueChange={setIsRecurring}
420-
trackColor={{
421-
true: colors.primary.default,
422-
false: colors.border.muted,
423-
}}
424-
thumbColor={brandColors.white}
425-
ios_backgroundColor={colors.border.muted}
426-
testID={CreatePriceAlertTestIds.RECURRING_TOGGLE}
427-
/>
428-
</Box>
429-
430-
{/* Quick-percentage pickers — always visible */}
431-
<Box
432-
flexDirection={BoxFlexDirection.Row}
433-
twClassName="mb-3 gap-2"
434-
>
435-
{PRICE_ALERT_QUICK_PERCENTAGES.map((percentage) => (
436-
<Button
437-
key={percentage}
438-
variant={ButtonVariant.Secondary}
439-
onPress={() => handleQuickPercentagePress(percentage)}
440-
testID={`${CreatePriceAlertTestIds.QUICK_PERCENTAGE_PREFIX}-${percentage}`}
441-
twClassName="flex-1"
442-
>
443-
{strings('price_alerts.quick_percentage', { percentage })}
444-
</Button>
445-
))}
446-
</Box>
447-
448-
{/* "price_alert" is intentionally not in the Keypad CURRENCIES map —
449-
unknown codes fall through to the decimals-aware branch in useCurrency,
450-
which is the only path that actually enforces the decimals cap. */}
451-
<KeypadComponent
452-
value={targetAmount}
453-
onChange={handleKeypadChange}
454-
currency="price_alert"
455-
decimals={keypadDecimals}
456-
/>
457-
458-
{/* Save button — always visible, sits below the keypad */}
459-
<Button
460-
variant={ButtonVariant.Primary}
461-
onPress={handleSaveAlert}
462-
isLoading={isSubmitting}
463-
isDisabled={
464-
isSubmitting ||
465-
!hasValidTarget ||
466-
isDuplicateThreshold ||
467-
isUnchanged
468-
}
469-
testID={CreatePriceAlertTestIds.SET_ALERT_BUTTON}
470-
twClassName="mt-3 w-full"
471-
>
472-
{isDuplicateThreshold
473-
? strings('price_alerts.duplicate_threshold')
474-
: strings(
475-
isEditing
476-
? 'price_alerts.update_price_alert'
477-
: 'price_alerts.set_price_alert',
478-
)}
479-
</Button>
480-
</View>
481-
</>
482-
) : (
383+
{` ${strings(
384+
percentDiff.direction === 'above'
385+
? 'price_alerts.approx_percent_above'
386+
: 'price_alerts.approx_percent_below',
387+
{ ticker: displayTicker },
388+
)}`}
389+
</>
390+
)}
391+
</Text>
392+
</Box>
393+
394+
<View style={tw.style('px-4 pb-2')}>
395+
{/* Recurring toggle */}
483396
<Box
397+
flexDirection={BoxFlexDirection.Row}
484398
alignItems={BoxAlignItems.Center}
485-
justifyContent={BoxJustifyContent.Center}
486-
twClassName="flex-1 px-6"
487-
testID={CreatePriceAlertTestIds.UNDER_DEVELOPMENT}
399+
justifyContent={BoxJustifyContent.Between}
400+
twClassName="mb-3 px-2"
488401
>
489402
<Text
490403
variant={TextVariant.BodyMd}
491404
color={TextColor.TextAlternative}
492-
twClassName="text-center"
493405
>
494-
{strings('price_alerts.price_change_under_development')}
406+
{strings('price_alerts.recurring')}
495407
</Text>
408+
<Switch
409+
value={isRecurring}
410+
onValueChange={setIsRecurring}
411+
trackColor={{
412+
true: colors.primary.default,
413+
false: colors.border.muted,
414+
}}
415+
thumbColor={brandColors.white}
416+
ios_backgroundColor={colors.border.muted}
417+
testID={CreatePriceAlertTestIds.RECURRING_TOGGLE}
418+
/>
419+
</Box>
420+
421+
{/* Quick-percentage pickers */}
422+
<Box flexDirection={BoxFlexDirection.Row} twClassName="mb-3 gap-2">
423+
{PRICE_ALERT_QUICK_PERCENTAGES.map((percentage) => (
424+
<Button
425+
key={percentage}
426+
variant={ButtonVariant.Secondary}
427+
onPress={() => handleQuickPercentagePress(percentage)}
428+
testID={`${CreatePriceAlertTestIds.QUICK_PERCENTAGE_PREFIX}-${percentage}`}
429+
twClassName="flex-1"
430+
>
431+
{strings('price_alerts.quick_percentage', { percentage })}
432+
</Button>
433+
))}
496434
</Box>
497-
)}
435+
436+
{/* "price_alert" is intentionally not in the Keypad CURRENCIES map —
437+
unknown codes fall through to the decimals-aware branch in useCurrency,
438+
which is the only path that actually enforces the decimals cap. */}
439+
<KeypadComponent
440+
value={targetAmount}
441+
onChange={handleKeypadChange}
442+
currency="price_alert"
443+
decimals={keypadDecimals}
444+
/>
445+
446+
{/* Save button — sits below the keypad */}
447+
<Button
448+
variant={ButtonVariant.Primary}
449+
onPress={handleSaveAlert}
450+
isLoading={isSubmitting}
451+
isDisabled={
452+
isSubmitting ||
453+
!hasValidTarget ||
454+
isDuplicateThreshold ||
455+
isUnchanged
456+
}
457+
testID={CreatePriceAlertTestIds.SET_ALERT_BUTTON}
458+
twClassName="mt-3 w-full"
459+
>
460+
{isDuplicateThreshold
461+
? strings('price_alerts.duplicate_threshold')
462+
: strings(
463+
isEditing
464+
? 'price_alerts.update_price_alert'
465+
: 'price_alerts.set_price_alert',
466+
)}
467+
</Button>
468+
</View>
498469
</Box>
499470
</SafeAreaView>
500471
);

0 commit comments

Comments
 (0)