Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ better storybook stories for the notification pages #27861

Draft
wants to merge 10 commits into
base: develop
Choose a base branch
from
Draft
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
13 changes: 6 additions & 7 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const path = require('path');
const { ProvidePlugin } = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const dotenv = require('dotenv');
dotenv.config({ path: path.resolve(__dirname, '../.metamaskrc') });

module.exports = {
core: {
disableTelemetry: true,
Expand Down Expand Up @@ -29,6 +32,7 @@ module.exports = {
env: (config) => ({
...config,
ENABLE_CONFIRMATION_REDESIGN: true,
INFURA_PROJECT_ID: process.env.INFURA_STORYBOOK_PROJECT_ID || '',
}),
// Uses babel.config.js settings and prevents "Missing class properties transform" error
babel: async (options) => ({
Expand Down Expand Up @@ -86,7 +90,7 @@ module.exports = {
sourceMap: true,
implementation: require('sass-embedded'),
sassOptions: {
includePaths: ['ui/css/', 'node_modules/',],
includePaths: ['ui/css/', 'node_modules/'],
},
},
},
Expand All @@ -96,12 +100,7 @@ module.exports = {
new CopyWebpackPlugin({
patterns: [
{
from: path.join(
'ui',
'css',
'utilities',
'fonts/',
),
from: path.join('ui', 'css', 'utilities', 'fonts/'),
to: 'fonts',
},
{
Expand Down
6 changes: 6 additions & 0 deletions builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ env:

- SENTRY_MMI_DSN: ''

###
# Storybook
###
- STORYBOOK_ENV: false
- INFURA_STORYBOOK_PROJECT_ID

###
# Notifications Feature
###
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@
"devtools:redux": "remotedev --hostname=localhost --port=8000",
"start:dev": "concurrently -k -n build,react,redux yarn:start yarn:devtools:react yarn:devtools:redux",
"announce": "node development/announcer.js",
"storybook": "storybook dev -p 6006 -c .storybook",
"storybook:build": "storybook build -c .storybook -o storybook-build",
"storybook:deploy": "storybook-to-ghpages --existing-output-dir storybook-build --remote storybook --branch master",
"storybook": "STORYBOOK_ENV=true storybook dev -p 6006 -c .storybook",
"storybook:build": "STORYBOOK_ENV=true storybook build -c .storybook -o storybook-build",
"storybook:deploy": "STORYBOOK_ENV=true storybook-to-ghpages --existing-output-dir storybook-build --remote storybook --branch master",
"update-changelog": "auto-changelog update",
"generate:migration": "./development/generate-migration.sh",
"lavamoat:build": "lavamoat development/build/index.js --policy lavamoat/build-system/policy.json --policyOverride lavamoat/build-system/policy-override.json",
Expand Down
8 changes: 5 additions & 3 deletions ui/helpers/utils/notification.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,9 +420,11 @@ export const getNetworkFees = async (notification: OnChainRawNotification) => {
const rpcUrl = getRpcUrlByChainId(`0x${chainId}` as HexChainId);
const connection = {
url: rpcUrl,
headers: {
'Infura-Source': 'metamask/metamask',
},
headers: process.env.STORYBOOK_ENV
? undefined
: {
'Infura-Source': 'metamask/metamask',
},
};
const provider = new JsonRpcProvider(connection);

Expand Down
166 changes: 166 additions & 0 deletions ui/pages/notification-details/notification-details.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import React from 'react';
import { Meta } from '@storybook/react';
import { Provider } from 'react-redux';
import configureStore from '../../store/store';
import NotificationDetails from './notification-details';
import testData from '../../../.storybook/test-data';
import { NotificationServicesController } from '@metamask/notification-services-controller';
import { Box } from '../../components/component-library';
import {
BlockSize,
Display,
FlexDirection,
JustifyContent,
} from '../../helpers/constants/design-system';
import { NotificationsPage } from '../../components/multichain';
import { Content } from '../../components/multichain/pages/page';
import { NotificationComponents } from '../notifications/notification-components';
import { NotificationDetailsHeader } from './notification-details-header/notification-details-header';
import { NotificationDetailsBody } from './notification-details-body/notification-details-body';
import { NotificationDetailsFooter } from './notification-details-footer/notification-details-footer';

type Notification = NotificationServicesController.Types.INotification;
const { processNotification } = NotificationServicesController.Processors;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of want us to use the new subpath exports.

In future I want us to deprecate imports like this (since they are expensive)


// Mock data
const {
createMockNotificationEthSent,
createMockNotificationEthReceived,
createMockNotificationERC20Sent,
createMockNotificationERC20Received,
createMockNotificationERC721Sent,
createMockNotificationERC721Received,
createMockNotificationERC1155Sent,
createMockNotificationERC1155Received,
createMockNotificationLidoReadyToBeWithdrawn,
createMockNotificationLidoStakeCompleted,
createMockNotificationLidoWithdrawalCompleted,
createMockNotificationLidoWithdrawalRequested,
createMockNotificationMetaMaskSwapsCompleted,
createMockNotificationRocketPoolStakeCompleted,
createMockNotificationRocketPoolUnStakeCompleted,
createMockFeatureAnnouncementRaw,
} = NotificationServicesController.Mocks;

const ethSentNotification: Notification = processNotification(
createMockNotificationEthSent(),
);

const ethReceivedNotification: Notification = processNotification(
createMockNotificationEthReceived(),
);

const erc20SentNotification: Notification = processNotification(
createMockNotificationERC20Sent(),
);

const erc20ReceivedNotification: Notification = processNotification(
createMockNotificationERC20Received(),
);

const erc721SentNotification: Notification = processNotification(
createMockNotificationERC721Sent(),
);

const erc721ReceivedNotification: Notification = processNotification(
createMockNotificationERC721Received(),
);

const erc1155SentNotification: Notification = processNotification(
createMockNotificationERC1155Sent(),
);

const erc1155ReceivedNotification: Notification = processNotification(
createMockNotificationERC1155Received(),
);

const store = configureStore({
...testData,
});

export default {
title: 'Pages/Notifications/NotificationDetails',
component: NotificationDetails,
decorators: [
(Story) => (
<Provider store={store}>
<Story />
</Provider>
),
],
} as Meta;

const Template = ({ notification }) => {
const ncs = NotificationComponents[notification.type];

return (
<Box marginLeft={'auto'} marginRight={'auto'}>
<NotificationsPage>
<NotificationDetailsHeader
onClickBack={() => console.log('click back')}
>
<ncs.details.title notification={notification} />
</NotificationDetailsHeader>
<Content padding={0}>
<Box
display={Display.Flex}
flexDirection={FlexDirection.Column}
gap={2}
width={BlockSize.Full}
height={BlockSize.Full}
justifyContent={JustifyContent.spaceBetween}
>
<NotificationDetailsBody
body={ncs.details.body}
notification={notification}
/>
<NotificationDetailsFooter
footer={ncs.footer}
notification={notification}
/>
</Box>
</Content>
</NotificationsPage>
</Box>
);
};

export const EthSent = Template.bind({});
EthSent.args = {
notification: ethSentNotification,
};

export const EthReceived = Template.bind({});
EthReceived.args = {
notification: ethReceivedNotification,
};

export const ERC20Sent = Template.bind({});
ERC20Sent.args = {
notification: erc20SentNotification,
};

export const ERC20Received = Template.bind({});
ERC20Received.args = {
notification: erc20ReceivedNotification,
};

export const ERC721Sent = Template.bind({});
ERC721Sent.args = {
notification: erc721SentNotification,
};

export const ERC721Received = Template.bind({});
ERC721Received.args = {
notification: erc721ReceivedNotification,
};

export const ERC1155Sent = Template.bind({});
ERC1155Sent.args = {
notification: erc1155SentNotification,
};

export const ERC1155Received = Template.bind({});
ERC1155Received.args = {
notification: erc1155ReceivedNotification,
};
142 changes: 142 additions & 0 deletions ui/pages/notifications/notifications-list-item.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React, { useState } from 'react';
import { Meta } from '@storybook/react';
import { processSnapNotifications } from './snap/utils/utils';
import { Box } from '../../components/component-library';
import type { SnapNotification } from './snap/types/types';
import { SnapComponent } from './notification-components/snap/snap';
import { NotificationsListItem } from './notifications-list-item';
import { NotificationServicesController } from '@metamask/notification-services-controller';

type Notification = NotificationServicesController.Types.INotification;
const { processNotification } = NotificationServicesController.Processors;

const snapNotifications = processSnapNotifications([
{
createdDate: 1728380597788,
id: 'TRYkTTkpGf1BqhajRQz8h',
message: 'Hello from within MetaMask!',
origin: 'npm:@metamask/notification-example-snap',
readDate: undefined,
},
]);

const {
createMockNotificationERC1155Received,
createMockNotificationERC1155Sent,
createMockNotificationERC20Received,
createMockNotificationERC20Sent,
createMockNotificationERC721Received,
createMockNotificationERC721Sent,
createMockNotificationEthReceived,
createMockNotificationEthSent,
createMockNotificationLidoReadyToBeWithdrawn,
createMockNotificationLidoStakeCompleted,
createMockNotificationLidoWithdrawalCompleted,
createMockNotificationLidoWithdrawalRequested,
createMockNotificationMetaMaskSwapsCompleted,
createMockNotificationRocketPoolStakeCompleted,
createMockNotificationRocketPoolUnStakeCompleted,
createMockFeatureAnnouncementRaw,
} = NotificationServicesController.Mocks;

const notifications: Notification[] = [
processNotification(createMockNotificationEthSent()),
processNotification(createMockNotificationEthReceived()),
processNotification(createMockNotificationERC20Sent()),
processNotification(createMockNotificationERC20Received()),
processNotification(createMockNotificationERC721Sent()),
processNotification(createMockNotificationERC721Received()),
processNotification(createMockNotificationERC1155Sent()),
processNotification(createMockNotificationERC1155Received()),
processNotification(createMockNotificationMetaMaskSwapsCompleted()),
processNotification(createMockNotificationRocketPoolStakeCompleted()),
processNotification(createMockNotificationRocketPoolUnStakeCompleted()),
processNotification(createMockNotificationLidoStakeCompleted()),
processNotification(createMockNotificationLidoWithdrawalRequested()),
processNotification(createMockNotificationLidoReadyToBeWithdrawn()),
processNotification(createMockNotificationLidoWithdrawalCompleted()),
processNotification(createMockFeatureAnnouncementRaw()),
];

export default {
title: 'Pages/Notifications/NotificationsListItems',
component: NotificationsListItem,
storyName: 'Default Matteo',
} as Meta;

const NotificationItemWrapper: React.FC<{
notification: Notification;
onRead: (id: string) => void;
}> = ({ notification, onRead }) => {
const handleCustomNotificationClick = () => {
onRead(notification.id);
};

return (
<div onClick={handleCustomNotificationClick}>
<NotificationsListItem notification={notification} />
</div>
);
};

const SnapNotificationWrapper: React.FC<{
snapNotification: SnapNotification;
onRead: (id: string) => void;
}> = ({ snapNotification, onRead }) => {
const handleSnapNotificationClick = () => {
onRead(snapNotification.id);
};

return (
<div onClick={handleSnapNotificationClick}>
<SnapComponent snapNotification={snapNotification} />
</div>
);
};

const Template = () => {
const [notificationList, setNotificationList] = useState(notifications);
const [snapNotificationList, setSnapNotificationList] =
useState(snapNotifications);

const markAsRead = (id: string) => {
setNotificationList((prevNotifications) =>
prevNotifications.map((notification) =>
notification.id === id
? { ...notification, isRead: true }
: notification,
),
);
};

const markSnapAsRead = (id: string) => {
setSnapNotificationList((prevSnapNotifications) =>
prevSnapNotifications.map((snapNotification) =>
snapNotification.id === id
? { ...snapNotification, readDate: Date.now() }
: snapNotification,
),
);
};

return (
<Box marginLeft={'auto'} marginRight={'auto'}>
{notificationList.map((notification) => (
<NotificationItemWrapper
key={notification.id}
notification={notification}
onRead={markAsRead} // Pass the markAsRead function
/>
))}
{snapNotificationList.map((snapNotification) => (
<SnapNotificationWrapper
key={snapNotification.id}
snapNotification={snapNotification}
onRead={markSnapAsRead} // Pass the markSnapAsRead function
/>
))}
</Box>
);
};

export const Default = Template.bind({});
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const NotificationsListReadAllButton = ({
paddingLeft={4}
paddingRight={4}
paddingTop={4}
paddingBottom={4}
className="notifications__list__read__all__button"
>
<Button
Expand Down
Loading
Loading