Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added app/images/riv_animations/shield_icon.riv
Binary file not shown.
51 changes: 43 additions & 8 deletions shared/lib/shield.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { PRODUCT_TYPES, Subscription } from '@metamask/subscription-controller';
import {
PRODUCT_TYPES,
SUBSCRIPTION_STATUSES,
Subscription,
} from '@metamask/subscription-controller';
import {
ActiveSubscriptionStatuses,
PausedSubscriptionStatuses,
Expand Down Expand Up @@ -29,16 +33,28 @@ function getShieldSubscription(
return shieldSubscription;
}

export function getIsShieldSubscriptionActive(
function getValidShieldSubscription(
subscriptions: Subscription | Subscription[],
): boolean {
): Subscription | undefined {
// check the feature flag first
if (!getIsMetaMaskShieldFeatureEnabled()) {
return false;
return undefined;
}

const shieldSubscription = getShieldSubscription(subscriptions);

if (!shieldSubscription) {
return undefined;
}

return shieldSubscription;
}

export function getIsShieldSubscriptionActive(
subscriptions: Subscription | Subscription[],
): boolean {
const shieldSubscription = getValidShieldSubscription(subscriptions);

if (!shieldSubscription) {
return false;
}
Expand All @@ -49,18 +65,37 @@ export function getIsShieldSubscriptionActive(
export function getIsShieldSubscriptionPaused(
subscriptions: Subscription | Subscription[],
): boolean {
// check the feature flag first
if (!getIsMetaMaskShieldFeatureEnabled()) {
const shieldSubscription = getValidShieldSubscription(subscriptions);

if (!shieldSubscription) {
return false;
}

const shieldSubscription = getShieldSubscription(subscriptions);
return PausedSubscriptionStatuses.includes(shieldSubscription.status);
}

export function getIsShieldSubscriptionTrialing(
subscriptions: Subscription | Subscription[],
): boolean {
const shieldSubscription = getValidShieldSubscription(subscriptions);

if (!shieldSubscription) {
return false;
}

return PausedSubscriptionStatuses.includes(shieldSubscription.status);
return shieldSubscription.status === SUBSCRIPTION_STATUSES.trialing;
}

export function getIsShieldSubscriptionProvisional(
subscriptions: Subscription | Subscription[],
): boolean {
const shieldSubscription = getValidShieldSubscription(subscriptions);

if (!shieldSubscription) {
return false;
}

return shieldSubscription.status === SUBSCRIPTION_STATUSES.provisional;
}

export function getIsShieldSubscriptionEndingSoon(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ jest.mock('react-router-dom-v5-compat', () => ({
useNavigate: jest.fn(),
}));

jest.mock('../../../../../hooks/subscription/useSubscription', () => ({
useUserSubscriptions: jest.fn(() => ({
subscriptions: [],
loading: false,
error: null,
customerId: undefined,
trialedProducts: [],
})),
useUserSubscriptionByProduct: jest.fn(() => undefined),
}));

const render = (args?: Record<string, unknown>) => {
const store = configureStore(args ?? getMockPersonalSignConfirmState());

Expand Down
12 changes: 12 additions & 0 deletions ui/pages/confirmations/components/confirm/footer/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,15 @@
z-index: 1;
}
}

.riv-animation {
&__shield-icon-container {
height: 16px;
width: 16px;
}

&__canvas {
height: 100%;
width: 100%;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ jest.mock(
}),
);

jest.mock('./shield-icon-animation', () => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
__esModule: true,
default: () => <div data-testid="shield-icon-animation" />,
}));

describe('ShieldFooterCoverageIndicator', () => {
it('renders transaction shield label when coverage indicator is enabled', () => {
const transaction = genUnapprovedContractInteractionConfirmation();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,75 @@
import { SignatureRequest } from '@metamask/signature-controller';
import { TransactionMeta } from '@metamask/transaction-controller';
import React from 'react';
import React, { useMemo } from 'react';
import { ConfirmInfoAlertRow } from '../../../../../../components/app/confirm/info/row/alert-row/alert-row';
import { RowAlertKey } from '../../../../../../components/app/confirm/info/row/constants';
import { Box, Text } from '../../../../../../components/component-library';
import {
AlignItems,
Display,
FlexDirection,
Severity,
TextColor,
TextVariant,
} from '../../../../../../helpers/constants/design-system';
import { useI18nContext } from '../../../../../../hooks/useI18nContext';
import { useConfirmContext } from '../../../../context/confirm';
import { useEnableShieldCoverageChecks } from '../../../../hooks/transactions/useEnableShieldCoverageChecks';
import useAlerts from '../../../../../../hooks/useAlerts';
import { useUserSubscriptions } from '../../../../../../hooks/subscription/useSubscription';
import {
getIsShieldSubscriptionPaused,
getIsShieldSubscriptionProvisional,
getIsShieldSubscriptionTrialing,
} from '../../../../../../../shared/lib/shield';
import ShieldIconAnimation from './shield-icon-animation';

const ShieldFooterCoverageIndicator = () => {
const t = useI18nContext();
const { currentConfirmation } = useConfirmContext<
TransactionMeta | SignatureRequest
>();
const isShowShieldFooterCoverageIndicator = useEnableShieldCoverageChecks();
const { getFieldAlerts } = useAlerts(currentConfirmation?.id ?? '');
const { subscriptions } = useUserSubscriptions();

const isPaused = getIsShieldSubscriptionPaused(subscriptions);
const isTrialing = getIsShieldSubscriptionTrialing(subscriptions);
const isProvisional = getIsShieldSubscriptionProvisional(subscriptions);

const fieldAlerts = getFieldAlerts(RowAlertKey.ShieldFooterCoverageIndicator);
const selectedAlert = fieldAlerts[0];
const selectedAlertSeverity = selectedAlert?.severity;

const animationSeverity = useMemo(() => {
if (isPaused) {
return Severity.Warning;
}
return selectedAlertSeverity;
}, [isPaused, selectedAlertSeverity]);

if (!isShowShieldFooterCoverageIndicator) {
if (!currentConfirmation || !isShowShieldFooterCoverageIndicator) {
return null;
}

return (
<Box
display={Display.Flex}
flexDirection={FlexDirection.Row}
alignItems={AlignItems.center}
paddingLeft={4}
paddingRight={4}
// box shadow to match the original var(--shadow-size-md) on the footer,
// but only applied to the top of the box, so it doesn't overlap with
// the existing
style={{ boxShadow: '0 -4px 16px -8px var(--color-shadow-default)' }}
>
<Box marginTop={1}>
<ShieldIconAnimation
severity={animationSeverity}
playAnimation={isTrialing || isProvisional}
/>
</Box>
<ConfirmInfoAlertRow
alertKey={RowAlertKey.ShieldFooterCoverageIndicator}
ownerId={currentConfirmation.id}
Expand All @@ -42,7 +79,11 @@ const ShieldFooterCoverageIndicator = () => {
{t('transactionShield')}
</Text>
}
style={{ marginBottom: 0, alignItems: AlignItems.center }}
style={{
marginBottom: 0,
alignItems: AlignItems.center,
width: '100%',
}}
showAlertLoader
/>
</Box>
Expand Down
Loading
Loading