diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 701db2ed766..11e59c96c67 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -6431,6 +6431,9 @@ "message": "Get $1 points/$2.", "description": "The $1 is the number of points, $2 is the plan interval (month or year)" }, + "shieldTxDetails3DescriptionLinkReward": { + "message": "Link Rewards" + }, "shieldTxDetails3DescriptionSignUp": { "message": "Sign up" }, diff --git a/app/_locales/en_GB/messages.json b/app/_locales/en_GB/messages.json index 701db2ed766..11e59c96c67 100644 --- a/app/_locales/en_GB/messages.json +++ b/app/_locales/en_GB/messages.json @@ -6431,6 +6431,9 @@ "message": "Get $1 points/$2.", "description": "The $1 is the number of points, $2 is the plan interval (month or year)" }, + "shieldTxDetails3DescriptionLinkReward": { + "message": "Link Rewards" + }, "shieldTxDetails3DescriptionSignUp": { "message": "Sign up" }, diff --git a/ui/hooks/rewards/useOptIn.ts b/ui/hooks/rewards/useOptIn.ts index fda2fe60199..784f6f3d0ee 100644 --- a/ui/hooks/rewards/useOptIn.ts +++ b/ui/hooks/rewards/useOptIn.ts @@ -1,6 +1,7 @@ import { useCallback, useState, useContext, useMemo } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { AccountGroupId, AccountWalletId } from '@metamask/account-api'; +import log from 'loglevel'; import { getMultichainAccountsByWalletId, getWalletIdAndNameByAccountAddress, @@ -173,8 +174,9 @@ export const useOptIn = (options?: UseOptInOptions): UseOptinResult => { options.rewardPoints, ), ); - } catch { + } catch (error) { // Silently fail - reward linking should not block opt-in + log.warn('Failed to link reward to shield subscription', error); } } } diff --git a/ui/pages/settings/transaction-shield-tab/transaction-shield.tsx b/ui/pages/settings/transaction-shield-tab/transaction-shield.tsx index a53a3f2921a..10fc8c099af 100644 --- a/ui/pages/settings/transaction-shield-tab/transaction-shield.tsx +++ b/ui/pages/settings/transaction-shield-tab/transaction-shield.tsx @@ -4,6 +4,7 @@ import { Product, PRODUCT_TYPES, RECURRING_INTERVALS, + Subscription, SUBSCRIPTION_STATUSES, } from '@metamask/subscription-controller'; import { useLocation, useNavigate } from 'react-router-dom-v5-compat'; @@ -17,6 +18,7 @@ import { IconSize, } from '@metamask/design-system-react'; import { useDispatch, useSelector } from 'react-redux'; +import log from 'loglevel'; import { BannerAlert, BannerAlertSeverity, @@ -83,6 +85,7 @@ import { useHandlePayment } from '../../../hooks/subscription/useHandlePayment'; import { MetaMaskReduxDispatch } from '../../../store/store'; import { setOnboardingModalOpen } from '../../../ducks/rewards'; import { getIntlLocale } from '../../../ducks/locale/locale'; +import { linkRewardToShieldSubscription } from '../../../store/actions'; import CancelMembershipModal from './cancel-membership-modal'; import { isCardPaymentMethod, isCryptoPaymentMethod } from './types'; import ShieldBannerAnimation from './shield-banner-animation'; @@ -126,8 +129,9 @@ const TransactionShield = () => { lastSubscription, ); // show current active shield subscription or last subscription if no active subscription - const displayedShieldSubscription = - currentShieldSubscription ?? lastShieldSubscription; + const displayedShieldSubscription: + | (Subscription & { rewardAccountId?: string }) // TODO: fix this type once we have controller released. + | undefined = currentShieldSubscription ?? lastShieldSubscription; const [timeoutCancelled, setTimeoutCancelled] = useState(false); useEffect(() => { @@ -280,6 +284,20 @@ const TransactionShield = () => { padding: 4, }; + const handleLinkRewardToShieldSubscription = useCallback( + async (subscriptionId: string, rewardPoints: number) => { + // link to shield only coz already opted in to rewards + try { + await dispatch( + linkRewardToShieldSubscription(subscriptionId, rewardPoints), + ); + } catch (error) { + log.warn('Failed to link reward to shield subscription', error); + } + }, + [dispatch], + ); + const buttonRow = (label: string, onClick: () => void, id?: string) => { return ( { )} + {hasOptedIntoRewards && + displayedShieldSubscription?.id && + claimedRewardsPoints && + !displayedShieldSubscription?.rewardAccountId && ( + + + + )} )}