Skip to content

Commit d794ad7

Browse files
authored
chore: tweaks to end of season reward claiming (#24562)
## **Description** - Use in app browser for NANSEN and OTHERSIDE - Don't use LINEA error/loading state for balance, either show default sheet title or the variant which has the balance in it. - Small change when retrieving season metadata to detect if a season is no longer the current season. - Dedicated success toasts for metal card and linea. - Changed end of season bottom sheet label ## **Changelog** CHANGELOG entry: null <!-- CURSOR_SUMMARY --> --- > [!NOTE] > - Navigate to in-app browser (`Routes.BROWSER`) for `NANSEN`/`OTHERSIDE` rewards instead of `Linking.openURL` and update tests accordingly > - Simplify LINEA tokens title: show default or "You earned {{amount}} $LINEA" only; remove loading/error UI and related strings > - Update modal header copy to `Reward Details` and show success toasts per reward type (METAL_CARD contact info, LINEA address) > - Add coercion in `getDiscoverSeasons` to move expired `current` season into `previous`; broaden tests to use future dates and cover edge cases > - Minor i18n copy adjustments and associated unit test updates > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit b088248. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0a98f56 commit d794ad7

5 files changed

Lines changed: 216 additions & 104 deletions

File tree

app/components/UI/Rewards/components/EndOfSeasonClaimBottomSheet/EndOfSeasonClaimBottomSheet.test.tsx

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import React from 'react';
22
import { render, fireEvent, waitFor, act } from '@testing-library/react-native';
3-
import { Linking } from 'react-native';
43
import EndOfSeasonClaimBottomSheet from './EndOfSeasonClaimBottomSheet';
54
import { SeasonRewardType } from '../../../../../core/Engine/controllers/rewards-controller/types';
65
import { REWARDS_VIEW_SELECTORS } from '../../Views/RewardsView.constants';
6+
import Routes from '../../../../../constants/navigation/Routes';
77

88
// Mock selectors
99
import { selectSelectedAccountGroup } from '../../../../../selectors/multichainAccounts/accountTreeController';
@@ -41,17 +41,6 @@ jest.mock('@react-navigation/native', () => ({
4141
}),
4242
}));
4343

44-
// Mock Linking
45-
jest.mock('react-native', () => {
46-
const RN = jest.requireActual('react-native');
47-
return {
48-
...RN,
49-
Linking: {
50-
openURL: jest.fn(),
51-
},
52-
};
53-
});
54-
5544
// Mock useMetrics
5645
const mockTrackEvent = jest.fn();
5746
const mockCreateEventBuilder = jest.fn(() => ({
@@ -123,7 +112,7 @@ jest.mock('../../../../../util/theme', () => ({
123112
jest.mock('../../../../../../locales/i18n', () => ({
124113
strings: jest.fn((key: string, params?: Record<string, string>) => {
125114
const translations: Record<string, string> = {
126-
'rewards.end_of_season_rewards.redeem_your_reward': 'Redeem Your Reward',
115+
'rewards.end_of_season_rewards.reward_details': 'Reward Details',
127116
'rewards.end_of_season_rewards.confirm_label_default': 'Redeem',
128117
'rewards.end_of_season_rewards.confirm_label_access': 'Access',
129118
'rewards.end_of_season_rewards.redeem_success_title': 'Success!',
@@ -138,10 +127,7 @@ jest.mock('../../../../../../locales/i18n', () => ({
138127
'rewards.metal_card_claim.telegram_label': 'Telegram (optional)',
139128
'rewards.metal_card_claim.telegram_placeholder': '@username',
140129
'rewards.linea_tokens.default_title': 'Claim your LINEA tokens',
141-
'rewards.linea_tokens.loading_subtitle': 'Loading...',
142130
'rewards.linea_tokens.title_earned': `You earned ${params?.amount || ''} $LINEA`,
143-
'rewards.linea_tokens.error_fetching_title': 'Error loading',
144-
'rewards.linea_tokens.error_fetching_description': 'Please try again',
145131
};
146132
return translations[key] || key;
147133
}),
@@ -475,7 +461,7 @@ describe('EndOfSeasonClaimBottomSheet', () => {
475461
);
476462

477463
expect(getByTestId(REWARDS_VIEW_SELECTORS.CLAIM_MODAL)).toBeOnTheScreen();
478-
expect(getByText('Redeem Your Reward')).toBeOnTheScreen();
464+
expect(getByText('Reward Details')).toBeOnTheScreen();
479465
});
480466

481467
it('renders title for non-LINEA_TOKENS reward', () => {
@@ -737,7 +723,7 @@ describe('EndOfSeasonClaimBottomSheet', () => {
737723
});
738724

739725
describe('claim actions', () => {
740-
it('opens URL for NANSEN reward type', async () => {
726+
it('navigates to browser for NANSEN reward type', async () => {
741727
const testUrl = 'https://nansen.ai/claim/test';
742728

743729
const { getByTestId } = render(
@@ -757,10 +743,16 @@ describe('EndOfSeasonClaimBottomSheet', () => {
757743
fireEvent.press(confirmButton);
758744
});
759745

760-
expect(Linking.openURL).toHaveBeenCalledWith(testUrl);
746+
expect(mockNavigate).toHaveBeenCalledWith(Routes.BROWSER.HOME, {
747+
screen: Routes.BROWSER.VIEW,
748+
params: {
749+
newTabUrl: testUrl,
750+
timestamp: expect.any(Number),
751+
},
752+
});
761753
});
762754

763-
it('opens URL for OTHERSIDE reward type', async () => {
755+
it('navigates to browser for OTHERSIDE reward type', async () => {
764756
const testUrl = 'https://otherside.xyz/claim/test';
765757

766758
const { getByTestId } = render(
@@ -780,10 +772,16 @@ describe('EndOfSeasonClaimBottomSheet', () => {
780772
fireEvent.press(confirmButton);
781773
});
782774

783-
expect(Linking.openURL).toHaveBeenCalledWith(testUrl);
775+
expect(mockNavigate).toHaveBeenCalledWith(Routes.BROWSER.HOME, {
776+
screen: Routes.BROWSER.VIEW,
777+
params: {
778+
newTabUrl: testUrl,
779+
timestamp: expect.any(Number),
780+
},
781+
});
784782
});
785783

786-
it('does not open URL when URL is not provided for NANSEN', async () => {
784+
it('does not navigate when URL is not provided for NANSEN', async () => {
787785
const { getByTestId } = render(
788786
<EndOfSeasonClaimBottomSheet
789787
route={createRoute({
@@ -801,7 +799,7 @@ describe('EndOfSeasonClaimBottomSheet', () => {
801799
fireEvent.press(confirmButton);
802800
});
803801

804-
expect(Linking.openURL).not.toHaveBeenCalled();
802+
expect(mockNavigate).not.toHaveBeenCalled();
805803
});
806804

807805
it('calls claimReward for METAL_CARD with valid email', async () => {

app/components/UI/Rewards/components/EndOfSeasonClaimBottomSheet/EndOfSeasonClaimBottomSheet.tsx

Lines changed: 22 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import useRewardsToast from '../../hooks/useRewardsToast';
1212
import { MetaMetricsEvents, useMetrics } from '../../../../hooks/useMetrics';
1313
import { useNavigation } from '@react-navigation/native';
1414
import { strings } from '../../../../../../locales/i18n';
15-
import { Linking, TouchableOpacity, ActivityIndicator } from 'react-native';
15+
import { TouchableOpacity } from 'react-native';
16+
import Routes from '../../../../../constants/navigation/Routes';
1617
import {
1718
BoxFlexDirection,
1819
ButtonVariant,
@@ -50,10 +51,8 @@ import TextField, {
5051
} from '../../../../../component-library/components/Form/TextField';
5152
import useClaimReward from '../../hooks/useClaimReward';
5253
import useLineaSeasonOneTokenReward from '../../hooks/useLineaSeasonOneTokenReward';
53-
import RewardsErrorBanner from '../RewardsErrorBanner';
5454
import { validateEmail } from '../../utils/formatUtils';
5555
import { formatAssetAmount } from '../../utils/eventDetailsUtils';
56-
import { useTheme } from '../../../../../util/theme';
5756

5857
export interface ModalAction {
5958
label: string;
@@ -93,16 +92,11 @@ const EndOfSeasonClaimBottomSheet = ({
9392
}: EndOfSeasonClaimBottomSheetProps) => {
9493
const sheetRef = useRef<BottomSheetRef>(null);
9594
const tw = useTailwind();
96-
const theme = useTheme();
9795
const { showToast: showRewardsToast, RewardsToastOptions } =
9896
useRewardsToast();
9997
const { trackEvent, createEventBuilder } = useMetrics();
10098
const { claimReward, isClaimingReward } = useClaimReward();
101-
const {
102-
lineaTokenReward,
103-
isLoading: isLoadingLineaToken,
104-
error: lineaTokenError,
105-
} = useLineaSeasonOneTokenReward();
99+
const { lineaTokenReward } = useLineaSeasonOneTokenReward();
106100
const {
107101
seasonRewardId,
108102
title,
@@ -153,13 +147,16 @@ const EndOfSeasonClaimBottomSheet = ({
153147
}, [navigation]);
154148

155149
const showSuccessToastIfNeeded = useCallback(() => {
156-
if (
157-
rewardType === SeasonRewardType.LINEA_TOKENS ||
158-
rewardType === SeasonRewardType.METAL_CARD
159-
) {
150+
if (rewardType === SeasonRewardType.METAL_CARD) {
151+
showRewardsToast(
152+
RewardsToastOptions.success(
153+
strings('rewards.end_of_season_rewards.metal_card_claim_success'),
154+
),
155+
);
156+
} else if (rewardType === SeasonRewardType.LINEA_TOKENS) {
160157
showRewardsToast(
161158
RewardsToastOptions.success(
162-
strings('rewards.end_of_season_rewards.redeem_success_title'),
159+
strings('rewards.end_of_season_rewards.linea_tokens_claim_success'),
163160
),
164161
);
165162
}
@@ -201,7 +198,13 @@ const EndOfSeasonClaimBottomSheet = ({
201198
case SeasonRewardType.NANSEN:
202199
case SeasonRewardType.OTHERSIDE:
203200
if (!url) return;
204-
Linking.openURL(url);
201+
navigation.navigate(Routes.BROWSER.HOME, {
202+
screen: Routes.BROWSER.VIEW,
203+
params: {
204+
newTabUrl: url,
205+
timestamp: Date.now(),
206+
},
207+
});
205208
break;
206209
case SeasonRewardType.LINEA_TOKENS: {
207210
if (!rewardId) return;
@@ -272,6 +275,7 @@ const EndOfSeasonClaimBottomSheet = ({
272275
claimReward,
273276
showRewardsToast,
274277
RewardsToastOptions,
278+
navigation,
275279
]);
276280

277281
const confirmAction = useMemo(() => {
@@ -369,38 +373,7 @@ const EndOfSeasonClaimBottomSheet = ({
369373

370374
const renderTitle = () => {
371375
if (rewardType === SeasonRewardType.LINEA_TOKENS) {
372-
if (isLoadingLineaToken) {
373-
return (
374-
<Box
375-
twClassName="flex-col items-center justify-center w-full"
376-
testID={REWARDS_VIEW_SELECTORS.CLAIM_MODAL_LINEA_TOKENS_LOADING}
377-
>
378-
<Text variant={TextVariant.HeadingLg} twClassName="text-center">
379-
{strings('rewards.linea_tokens.default_title')}
380-
</Text>
381-
382-
<Box
383-
flexDirection={BoxFlexDirection.Row}
384-
alignItems={BoxAlignItems.Center}
385-
twClassName="gap-2 mt-2"
386-
>
387-
<Text
388-
variant={TextVariant.HeadingSm}
389-
twClassName="text-center text-alternative"
390-
>
391-
{strings('rewards.linea_tokens.loading_subtitle')}
392-
</Text>
393-
394-
<ActivityIndicator
395-
size="small"
396-
color={theme.colors.text.alternative}
397-
/>
398-
</Box>
399-
</Box>
400-
);
401-
}
402-
403-
// If loaded and amount > 0, show "You earned XXX $LINEA"
376+
// If we have a balance amount > 0, show "You earned XXX $LINEA"
404377
const parsedAmount = BigInt(lineaTokenReward?.amount || '0');
405378
if (parsedAmount > 0n) {
406379
// Format the amount with 18 decimals (standard ERC-20 decimals for LINEA)
@@ -422,28 +395,7 @@ const EndOfSeasonClaimBottomSheet = ({
422395
);
423396
}
424397

425-
// If error occurred while fetching, show error banner
426-
if (lineaTokenError) {
427-
return (
428-
<Box
429-
twClassName="flex-col items-center justify-center w-full"
430-
testID={REWARDS_VIEW_SELECTORS.CLAIM_MODAL_LINEA_TOKENS_ERROR}
431-
>
432-
<Text variant={TextVariant.HeadingLg} twClassName="text-center">
433-
{strings('rewards.linea_tokens.default_title')}
434-
</Text>
435-
<Box twClassName="w-full mt-4">
436-
<RewardsErrorBanner
437-
title={strings('rewards.linea_tokens.error_fetching_title')}
438-
description={strings(
439-
'rewards.linea_tokens.error_fetching_description',
440-
)}
441-
/>
442-
</Box>
443-
</Box>
444-
);
445-
}
446-
398+
// Otherwise, show default title (for loading, error, or zero balance cases)
447399
return (
448400
<Box
449401
twClassName="flex-col items-center justify-center w-full"
@@ -577,7 +529,7 @@ const EndOfSeasonClaimBottomSheet = ({
577529
keyboardAvoidingViewEnabled={!needsKeyboardAvoiding}
578530
>
579531
<BottomSheetHeader onClose={handleModalClose}>
580-
{strings('rewards.end_of_season_rewards.redeem_your_reward')}
532+
{strings('rewards.end_of_season_rewards.reward_details')}
581533
</BottomSheetHeader>
582534
{needsKeyboardAvoiding ? (
583535
<KeyboardAwareScrollView

0 commit comments

Comments
 (0)